[1.0 Review] IPC wire 凍結層(preface / frame / correlation / versioning)の最終確認 #5

Closed
opened 2026-09-03 16:03:11 +09:00 by natsukium · 1 comment
Owner

背景

IPC は felis の唯一の拡張 surface かつ daemon/client 間の唯一の永続境界。reference/ipc.mdexplanation/architecture/ipc.md に carrier / framing / versioning が仕様化され、felis.protofelis.v1)が唯一の機械可読 authority。1.0 で wire を凍結する前に、凍結層と進化則を再確認する。

現状の問題 / 凍結前に決めるべき点

1. frozen preface と frame header の endian 非対称

  • preface(FLIS 8B + FLIS 10B + optional FRLY carrier block)は big-endian(network order)で凍結。
  • frame header(len:u32 | kind:u16)は little-endian。

意図的な乖離として文書化されているが、1.0 で「なぜ2つの endian が混在するか」を新 contributor が誤解する余地がある。統一(全て little-endian)への破壊的変更は今しかできない。現行の「preface は自己完結した frozen artifact なので network order、frame は毎秒百万回 decode される hot path なので host order」は妥当だが、1.0 で凍結理由をもう一度明記するか、統一するかを決める。

2. FRLY carrier block の凍結 limits

MAX_CARRIER_ENTRIES = 4096 / MAX_CARRIER_PAYLOAD_BYTES = 1MiB が protocol major 単位で凍結。relay が送信前、daemon が受信時に両方で検査し、payload 長を先に読んでから payload を確保する。limit 自体は妥当だが、値を 1.0 で確定させる決定として reference/ipc.md の表に「なぜ 4096 / 1MiB か」の根拠(env の現実的な上限と DoS 耐性)を追記すべき。

3. protobuf vs row codec の二層

  • protobuf(prost)が frame body の唯一の encoding、row payload(RowEntry.packed_cells)だけが hand-written codec(reference/row-codec.md)で golden vector を持つ二層。
  • felis-protocoltokio / OS 依存を持たない purity は、satellite client(felis-tui, felis.el via bridge)のために必須。

問い: row codec を protobuf 内の bytes ではなく repeated Cell にする代替は、1.0 で完全に閉じるか? 現行の「RLE + version tag + limits-before-alloc」設計は性能と前方互換のために必要だが、1.0 で row-codec.md を「この interior は protobuf ではない」として凍結宣言する必要がある。

4. correlation envelope(field 100)

全 family wrapper の field 100 に Correlation {request_id, stream_id} を予約。Conn は deliberately 持たず、Grid/Image/Push/Input も持たない。client が2つの独立 sequence(各 1 始まり、再利用なし、枯渇で切断)を持つ。

破壊的見直し余地:

  • RequestIdStreamId を1つの Id に統一すると、実装は簡単になるが「inactive id が terminated なのか never-opened なのか」を区別できなくなり ABA が再発する(現行の2 sequence 分離の根拠)。1.0 でこの分離を凍結として明記するか。
  • stream_id を daemon-assigned にすると client からの cancel-before-first-item が不可能になる。現行の client-allocated を維持する決定を再確認。

5. versioning 則(major / minor / feature flag)

  • major: semantic break、deprecation window で side-by-side decoder。
  • minor: additive のみ、effective minor = min(client, daemon) の send-side contract。
  • feature flag: 振る舞い辞退(現状 pull_paced のみ)。

1.0 前は dev wire で major 1 のまま break を重ねている。今後 PROTOCOL_MAJOR = 2 への bump は「pool する価値がある semantic break が溜まったら」とされているが、1.0 で 1.x の minor ledger(reference/ipc.md の ledger 表)をどこまで埋めてから freeze するか。

6. frame kind の凍結

FrameKindConn=0, Input=1, Grid=2, Image=3, Session=4, Ops=5, Region=6, Notify=7, Push=8, Search=9)と各 family の oneof field number(Rust variant の1-based source order)が major 単位で凍結。reserved で retired number を再利用しない。新しい one-shot / push は新 kind を生やす規則。1.0 でこの「新 kind を生やす」規則を破る既存 family への variant 追加が無いか最終監査する。

提案

  • reference/ipc.md の「Version preface」「Frame layer」「Correlation」「Versioning」各節に「1.0 で凍結(frozen)」ラベルを付与し、変更時は major bump が必要であることを明示。
  • felis.proto の package felis.v1 が crate version(0.1.0)と独立していることを reference/workspace.md の Versioning 節に再掲し、1.0 での crate publish 時に semver と protocol version を混同しないガードを追加。
  • just proto の生成物が stale なら CI で落とす現行の proto job を、1.0 では buf breaking(前 commit からの wire-compat)も必須 gate に昇格。
  • FRLY の limits と endian 選択の rationale を explanation/architecture/ipc.md の「Handshake bootstrap」節に追記し、将来の変更が「凍結層なので不可」であることを明確化。

判定基準

  • explanation/architecture/ipc.md の「Rejected」節を読んだ新 contributor が、なぜ代替案が却下されたかを再発明せずに理解できること。
  • 1.0 時点の felis.protorow-codec.md の golden vector が、1.1 の minor 追加と衝突しないこと(field 100 の予約、kind 番号の空き)。

cc @natsukium

## 背景 IPC は felis の唯一の拡張 surface かつ daemon/client 間の唯一の永続境界。`reference/ipc.md` と `explanation/architecture/ipc.md` に carrier / framing / versioning が仕様化され、`felis.proto`(`felis.v1`)が唯一の機械可読 authority。1.0 で wire を凍結する前に、凍結層と進化則を再確認する。 ## 現状の問題 / 凍結前に決めるべき点 ### 1. frozen preface と frame header の endian 非対称 - preface(`FLIS` 8B + `FLIS` 10B + optional `FRLY` carrier block)は big-endian(network order)で凍結。 - frame header(`len:u32 | kind:u16`)は little-endian。 意図的な乖離として文書化されているが、1.0 で「なぜ2つの endian が混在するか」を新 contributor が誤解する余地がある。統一(全て little-endian)への破壊的変更は今しかできない。現行の「preface は自己完結した frozen artifact なので network order、frame は毎秒百万回 decode される hot path なので host order」は妥当だが、1.0 で凍結理由をもう一度明記するか、統一するかを決める。 ### 2. `FRLY` carrier block の凍結 limits `MAX_CARRIER_ENTRIES = 4096` / `MAX_CARRIER_PAYLOAD_BYTES = 1MiB` が protocol major 単位で凍結。relay が送信前、daemon が受信時に両方で検査し、payload 長を先に読んでから payload を確保する。limit 自体は妥当だが、値を 1.0 で確定させる決定として `reference/ipc.md` の表に「なぜ 4096 / 1MiB か」の根拠(env の現実的な上限と DoS 耐性)を追記すべき。 ### 3. protobuf vs row codec の二層 - protobuf(prost)が frame body の唯一の encoding、row payload(`RowEntry.packed_cells`)だけが hand-written codec(`reference/row-codec.md`)で golden vector を持つ二層。 - `felis-protocol` が `tokio` / OS 依存を持たない purity は、satellite client(`felis-tui`, `felis.el` via `bridge`)のために必須。 **問い:** row codec を protobuf 内の `bytes` ではなく `repeated Cell` にする代替は、1.0 で完全に閉じるか? 現行の「RLE + version tag + limits-before-alloc」設計は性能と前方互換のために必要だが、1.0 で `row-codec.md` を「この interior は protobuf ではない」として凍結宣言する必要がある。 ### 4. correlation envelope(field 100) 全 family wrapper の field 100 に `Correlation {request_id, stream_id}` を予約。`Conn` は deliberately 持たず、`Grid/Image/Push/Input` も持たない。client が2つの独立 sequence(各 1 始まり、再利用なし、枯渇で切断)を持つ。 破壊的見直し余地: - `RequestId` と `StreamId` を1つの `Id` に統一すると、実装は簡単になるが「inactive id が terminated なのか never-opened なのか」を区別できなくなり ABA が再発する(現行の2 sequence 分離の根拠)。1.0 でこの分離を凍結として明記するか。 - `stream_id` を daemon-assigned にすると client からの cancel-before-first-item が不可能になる。現行の client-allocated を維持する決定を再確認。 ### 5. versioning 則(major / minor / feature flag) - major: semantic break、deprecation window で side-by-side decoder。 - minor: additive のみ、effective minor = `min(client, daemon)` の send-side contract。 - feature flag: 振る舞い辞退(現状 `pull_paced` のみ)。 1.0 前は dev wire で major 1 のまま break を重ねている。今後 `PROTOCOL_MAJOR = 2` への bump は「pool する価値がある semantic break が溜まったら」とされているが、1.0 で `1.x` の minor ledger(`reference/ipc.md` の ledger 表)をどこまで埋めてから freeze するか。 ### 6. frame kind の凍結 `FrameKind`(`Conn=0, Input=1, Grid=2, Image=3, Session=4, Ops=5, Region=6, Notify=7, Push=8, Search=9`)と各 family の oneof field number(Rust variant の1-based source order)が major 単位で凍結。`reserved` で retired number を再利用しない。新しい one-shot / push は新 `kind` を生やす規則。1.0 でこの「新 kind を生やす」規則を破る既存 family への variant 追加が無いか最終監査する。 ## 提案 - `reference/ipc.md` の「Version preface」「Frame layer」「Correlation」「Versioning」各節に「1.0 で凍結(frozen)」ラベルを付与し、変更時は major bump が必要であることを明示。 - `felis.proto` の package `felis.v1` が crate version(`0.1.0`)と独立していることを `reference/workspace.md` の Versioning 節に再掲し、1.0 での crate publish 時に semver と protocol version を混同しないガードを追加。 - `just proto` の生成物が stale なら CI で落とす現行の `proto` job を、1.0 では `buf breaking`(前 commit からの wire-compat)も必須 gate に昇格。 - `FRLY` の limits と endian 選択の rationale を `explanation/architecture/ipc.md` の「Handshake bootstrap」節に追記し、将来の変更が「凍結層なので不可」であることを明確化。 ## 判定基準 - `explanation/architecture/ipc.md` の「Rejected」節を読んだ新 contributor が、なぜ代替案が却下されたかを再発明せずに理解できること。 - 1.0 時点の `felis.proto` と `row-codec.md` の golden vector が、1.1 の minor 追加と衝突しないこと(field 100 の予約、kind 番号の空き)。 cc @natsukium
Author
Owner

Superseded by #16, #19, #20, and #30. Those issues separate outbound limits, the compatibility gate, correlated spawn, and the clean protocol-2.0 baseline; #12 is the parent tracker.

Superseded by #16, #19, #20, and #30. Those issues separate outbound limits, the compatibility gate, correlated spawn, and the clean protocol-2.0 baseline; #12 is the parent tracker.
Sign in to join this conversation.
No description provided.