[v0.1/P1] Version the pre-preface FRLY carrier envelope #332
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
Due date
No due date set.
Dependencies
No dependencies set
Reference
natsukium/felis#332
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?
Problem
The optional
FRLYcarrier block is parsed before theFLISversion preface, but its format has no version field. The reference freezes it “per protocol major”; the receiver cannot use that major to select a carrier parser because the major is only available after the carrier has already been parsed.The existing payload length makes unknown formats skippable in principle, but without a format discriminator every change to the carrier payload is indistinguishable from malformed version 1 data. The current “reject trailing bytes” rule also leaves no additive extension point.
#139 settled the entry/payload caps and degrade behavior. This issue does not reopen those choices; it addresses format evolution before the first byte layout freezes.
Proposed resolution
Add a carrier format version to the fixed envelope before the first public baseline, for example:
Version 1 retains the current entry encoding and the frozen 4096-entry / 1 MiB limits. A receiver that recognizes the envelope but not
format_versioncan consumepayload_length, skip the block, warn, and continue to theFLISpreface with no carrier environment. This matches the existing safe-degrade policy for an unsendable carrier block.The field widths and unknown-version behavior must be specified exactly; the example above is not a field-size decision.
Acceptance criteria
FLISpreface.FLIS.Evidence
crates/felis-protocol/src/preface.rscrates/felis-transport/src/preface.rsdocs/reference/ipc.md(“Relay carrier block”)docs/explanation/architecture/ipc.md(“Carrier choices”)Related: #5, #115, #139.
Decision: do, small; the version goes at the head of the payload, the 8-byte header stays (reviewed)
Why it matters:
ipc.mdpromises relay ↔ daemon skew never needs negotiation ("a relay that prepends the block works against any v1 daemon"), and that skew is routine after a package upgrade against a warm daemon. Today any change to the payload encoding would break that promise for the whole major.Shape: header unchanged (
FRLY | u32 payload_length). Payload becomesu16 format_version | <encoding selected by version>; version 1 is today'su32 entry_count | entriesunder #139's caps. The daemon reads the whole payload by length exactly as now (length cap checked before allocation), then the version: known → decode; unknown → warn, continue toFLISwith no carrier (the same degrade as a relay that prepends nothing). Skipping is free because the payload is already consumed by length, and the exact-consumption rule is untouched. Version 0 is malformed.Why the header is not widened: an old daemon would read a wider header's bytes as the length, and a length of 65536 against a shorter stream leaves both peers waiting. With the version in the payload, migration is fail-fast in both directions: an old
decode_payloadreads0x00 0x0v ..as an entry count ≥ 65536 > 4096 and closes withOverCapbefore reading an entry; an old relay's versionless payload starts0x00 0x00(the high half of a small count), which the new daemon reads as version 0 and closes. Remedies per direction inCHANGELOG.md: new relay / warm old daemon → restart the daemon; old relay / new daemon → upgrade the relay side, which in the normal deployment is the same package on the same host (felis-daemon relayis exec'd fresh per SSH connection), so one package upgrade plus one daemon restart covers both.Docs: REQ-104a/104c and
ipc.mdare reworded so the envelope and version word are the frozen-for-all-time part of the preface layer and the version selects the entry encoding; "frozen for the protocol major" goes, since the major arrives after the block. Rejected alternatives recorded: widened header, new magic per layout. Tests: no block / v1 / unknown version skipped andFLISresumed / version 0 / old-writer bytes vs new reader / new-writer bytes vs the old count cap / truncated header / truncated payload. Independent of the proto batch.