[interpretation] e2e content-enc emit口 + decrypt-at-build — 着手前合意 3点

@rail44.dev/kneume #370/#371 の続き、primitive 再設計の content-encryption emit口(A)と decrypt-at-build(B)を実装する前の解釈合意です。現行コードの裏取り調査は完了。

**保護目標と脅威モデル**(先に言語化):
- 目標 = encryption-enabled な private group の content kernel(post / tag / curation / ref / anchor / feedback)を age-v1 で `KernelEnvelope.enc` に封入し、**鍵を持たない読み手(content-blind server / log 取得者)には routing header(ev_id/ts/audience/namespace)だけ**見せる。kind/op/id/actor/fields は隠す。
- 鍵保持者(seed 持ちの gateway / SPA)は従来どおり全 content を読める。
- 据え置き(③ ceiling)= header の 4 フィールド + membership/identity 床は平文(server の配信ゲートが読むため)。

**解釈した連結 2 ピース**:
- (A) offline `rewrite`(鍵保持者運用)に content-encryption を差す。plaintext content kernel を group recipient へ age-v1 再封入。暗号化済み post body は旧 ciphertext を一度復号 → full kernel 化 → 再暗号化(鍵要)。
- (B) decrypt-at-build。disk に enc kernel が乗ると apply は kind/id 不明なので、鍵保持者の gateway / SPA が apply 直前に `enc`→平文 kernel 復号 →`apply_kernel(full)`。既存暗号化 post は**非破壊**(現行 typed 経路は据え置き、enc-kernel 経路だけ新規)。受け口(`apply_kernel` の enc 扱い)は #370 で ready。

**調査で出た設計判断 3 点(brief の候補と一部ズレたので確認したい)**:

1. **slicing(SPA の重さ起因)** — gateway は per-event apply なので decrypt-at-build は小さい。だが **SPA の projection は per-event でなく bulk-SQL ingest**(`read_json` が固定 typed-event schema を読む / decrypt は render 時 lazy)で、kernel 行は固定 schema に乗らず素通り(feed から消える)→ SPA 側は実質書き換えで重い。安全な rollout は「読み手が kernel enc を扱える後に rewrite」(= B 先行)。**提案: PR1 = Rust 読み側(gateway decrypt-at-build)+(A)+ golden test、PR2 = SPA decrypt-at-build**。SPA を follow-up に切って良い?それとも 1 PR 連結希望?

2. **(A)の置き場 + seed 供給(brief からの逸脱なので明示)** — brief は `src/bin/rewrite_to_kernel.rs`(server crate)と指定。但し server crate は**意図的に鍵レス**で `derive_group_keypair`(HKDF per-group)を持たない(`crypto.rs` に encrypt/decrypt だけ)。鍵 + seed acquisition は **quacker-channel** crate にしかない。→ content-encrypting rewrite は **quacker-channel 側の機構**(keys.rs / e2e.rs 再利用)にし、server-crate の `rewrite_to_kernel` は鍵レス plaintext 変換のまま残す、が正しいと判断。seed は proxy と同じ `acquire_master_seed()`(pairing cache)、headless 用に `--seed-file`/BIP-39 fallback。recipient は log の GroupMemberAdded.pubkey を自前 scan(offline 完結)。この crate 移動で OK?

3. **pubkey 未 announce メンバー** — recipient は `group_members.member_pubkey`。NULL のメンバーが居る group の扱い。現行 live の `encrypt_post_body` は fail-closed(平文流出を拒否)。→ それに揃えて **group 単位 fail-closed**(未 announce が居る group は rewrite を拒否し、どの group が blocked か surface)を提案。announced subset だけ暗号化(skip-absent)は未 announce メンバーが自分で読めなくなるので避けたい。fail-closed で良い?

ズレてたら直してください。返信もらってから着手します(自動で proceed しません)。

replies