[v0.1/Proto Review] FRLY carrier block の 1MiB cap と診断は 1.0 で凍結すべき #139
Labels
No labels
priority/P0
priority/P1
priority/P2
release/v0.1.0
status/blocked
status/planned
type/bug
type/design
type/test-gap
type/tracker
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
natsukium/felis#139
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
背景
FRLYcarrier block は version preface の前で relay が daemon に自身の環境を渡すために 8 + 10 byte の固定 preface より先に書かれる唯一の可変長ブロック(crates/felis-transport/src/preface.rs)。凍結層であり、version negotiation 前なので minor による交渉ができない:MAX_CARRIER_ENTRIES = 4096/MAX_CARRIER_PAYLOAD_BYTES = 1 MiBが protocol major 単位で凍結。relay が送信前、daemon が受信時に両方で検査し、payload 長を先に読んでから payload を確保する。connect_or_spawn_daemonの auto-spawn 経路でも relay 経由の env がSpawnArgs.env_baseの第一候補になる(minor 4)。問題:
NixのPATHやsystemd --userの環境では 1 MiB を超える現実的なケースがある(nix developの多段PATH、LOCPATH、長いXDG_DATA_DIRS)。超えると relay は送信前にCarrierError::TooLargeで失敗し、daemon は受信時にCarrierError::TooLargeで接続を close する。ユーザには「daemon unreachable」に見えるだけで「env が大きすぎる」と診断されない。reference/ipc.mdにない。1.0 で
reference/ipc.mdの表に「なぜ 4096 / 1 MiB か」を刻むと同時に、値を上げるなら今しか破壊的に変えられない(major bump が必要)。問い
1.0 で cap をこのまま凍結するか、上げるか、あるいは交渉可能にするか:
CarrierErrorをConnectErrorのdaemon_unreachableではなくinvalid_request相当の typed error として CLI に「carrier block too large (N bytes > 1 MiB): trim env」と報告。reference/ipc.mdに根拠(「典型的なenv | wc -cは 10–50 KiB、1 MiB は 20×」)を追記。MAX_CARRIER_PAYLOAD_BYTES = 4 MiBに。受信側はpayload_lenを見てからVec::with_capacityするので DoS 上昇は 4×。Nix ユーザの実測を根拠にする。entries は 4096 のまま。FRLYpayload をentries u32の一括ではなく chunked にし、daemon 側は逐次読む。frame aware にする必要があるため relay が frame boundary を知ることになり、現行の「relay は byte pump」原則を崩す。1.0 でやるなら設計が大きい。提案
daemon_unreachableは誤診断)。cap 値自体は実測で決める:env | wc -cを Nix / non-Nix の数環境で測り、P99 ×4 を cap にする。超えるなら案Bで 1.0 の破壊的変更として上げる。案Cは deferred としてexplanation/architecture/ipc.md「Carrier choices」に Revisit trigger(「1 MiB を超える正当な env が継続的に現れたら chunked を検討」)を残す。reference/ipc.md「Version preface」「Semantic limits」節とexplanation/architecture/ipc.md「Handshake bootstrap」節に cap の根拠と凍結宣言を追記。crates/felis-transport/src/preface.rsの定数コメントにreference/ipc.mdへのリンクと「major 単位で凍結」ラベルを付与。判定基準
reference/ipc.mdのFRLY行を読んだ新 contributor が「なぜ 4096 / 1 MiB か」を再発明せずに理解できること。FRLYpayload が cap を超えた時に CLI がdaemon_unreachable(exit 2, retry) ではなくinvalid_request(exit 1, 環境を削れ) を返すこと。BREAKING.mdにbase: <sha>行を追加しjust proto-compatが green であること。felis --host <remote-nix>が cap で落ちないこと(実測で確認)。対象ファイル
crates/felis-protocol/src/preface.rs/crates/felis-transport/src/preface.rs(MAX_CARRIER_*)crates/felis-transport/src/relay.rs(送信前 check)crates/felis-client-core/src/connector.rs/crates/felis-daemon/src/serve.rs(受信時 handling)docs/reference/ipc.md「Version preface」「Semantic limits」docs/explanation/architecture/ipc.md「Handshake bootstrap」「Carrier choices」Parent: #12 および #11 / #5, #52 (P1 proto review)
Triage (2026-09-05)
Verdict: accept option A only, P1. Measured today:
env | wc -cis 53 KiB in a login shell and 59 KiB inside this repo'snix developshell, so the "Nix exceeds 1 MiB" premise has no evidence and option B (4 MiB) is not justified. Option C is deferred with a Revisit trigger ("a legitimate env over 1 MiB is observed") inexplanation/architecture/ipc.md.Do for v0.1.0: the rationale row for 4096 / 1 MiB in
reference/ipc.md(with the measurement), the "frozen per major" label on the constants inpreface.rs, and the typed "carrier block too large" diagnostic. The diagnostic moves the failure from exit 2 (retry) to exit 1 (fix your env), which is exit-class behavior, so it lands before the tag or not at all. Parent: #52.Triage plan (2026-09-05)
Source-grounded triage against
mainat07a77ae4, reviewed through six rounds of an independent reviewer (piluna then sol); every finding was verified against the source and folded in, and the design decisions below were settled with the maintainer on 2026-09-05. The order that supersedes the tracker's is posted on #12: this issue lands before #29 (schemas, goldens, and reference tables freeze there).FRLY carrier caps: freeze 4096 / 1 MiB, document the degrade path
Claim check
crates/felis-protocol/src/preface.rs:449-458. Their doccomments already carry the "frozen for the protocol major" label and
the allocation argument, so that part of the triage is done.
fail:
crates/felis-daemon/src/relay.rs:57-73prepend_environmentlogs a
warn!and continues without the block whenwrite_carrier_blockfails (includingCarrierError::OverCap), and thedaemon then takes the documented fallback (its own birth environment,
docs/reference/ipc.md:2119). The relay's stderr is the SSH session'sstderr, which
connector.rs:334leaves at the parent default, so thewarning reaches the caller's terminal. Nothing reports
daemon_unreachable. The daemon-sideOverCapclose(
felis-transport/src/preface.rs:63-75) can only be hit by a peer thatis not a felis relay, and
connector.rs:166correctly classifies that asa verdict, not a transient.
nix develop) stands; 1 MiB is ~17-20× that.Verdict
accept, docs-only (no typed diagnostic, no cap change). The triage's
"exit 1
invalid_request" item is dropped: there is no failure to type.Option C stays deferred with a Revisit trigger.
Approach
docs/reference/ipc.md"Relay carrier block": add a "Limits" paragraphor two table rows:
4096entries /1 MiBpayload, frozen per major,with the rationale (a login environment measures tens of KiB; the payload
is read into one allocation after the length word, so the cap bounds
that allocation before any bytes are trusted; entries cap bounds the
per-entry length words the same way). State the degrade contract: an
environment over either cap is not sent; the relay warns on its stderr
and the daemon falls back to its own environment for that connection's
creates.
docs/explanation/architecture/ipc.md"Carrier choices": why degraderather than fail (losing agent freshness beats losing the session, the
reason already in the relay's comment), why not chunked (the relay is a
byte pump; framing would make it protocol-aware), Revisit if a
legitimate environment over 1 MiB is observed.
crates/felis-daemon/src/relay.rshas no test that anover-cap environment degrades to "no block" (the tests at
:195-230cover the happy path and
encode().is_ok()), add one that drivesprepend_environmentwith an over-capCarrierBlockand asserts thedaemon-bound half received no bytes.
crates/felis-protocol/src/preface.rs:449-458: add theipc.mdsection name to the constant docs so the two stay linked; nothing else.
Dependencies / risk / labels
None. S. Keep labels. Parent #52.