protocol: ship the first wire contract as 1.0, not 1.14 #325

Merged
natsukium merged 3 commits from minor-reset into main 2026-09-21 21:34:24 +09:00
Owner

Closes #313.

The minor ledger reached 14 rows before any peer existed. Every one of those rows describes compatibility with a daemon that was never released, so the gates, fallbacks and downgrade paths they justify are dead weight the first release would have to carry forever — and the first ledger row that ever matters would be numbered 15.

This folds the whole pre-release schema into the minor-0 base.

What changes

  • PROTOCOL_MINOR 14 -> 0; the 14 MINOR_* constants are gone and MINOR_LEDGER has one row: (0, "the base schema of protocol major 1"). The preface golden vectors move to 1.0.
  • felis.proto: the PROTOCOL_MINOR header comment is 0 and all 11 nonzero (felis.v1.arm).since values become since: 0, keeping the explicit-zero style every base arm already used. Generated Rust regenerated with buf lint && buf generate.
  • All six GatedField tables in minor.rs are empty, and every since_minor() on the closed enums returns 0. The tables and the requires impls that fold them stay, so the first post-release field is one row and nothing else.
  • The Downgrade trait and every impl are deleted: with no older peer to map to, every closed-enum value is in the base schema, and a future narrowing needs its own mapping regardless.
  • Every hard-coded minor gate in the client, CLI and daemon is gone (check_scope_minor, check_key_input_minor, check_mode_minor, the atomic-create and attach-by-prefix fallbacks, effective_minor plumbing through the session task, subscriber and bridge link). check_arm_minor, FrameWriter::authorize, TransportError::MinorTooOld and ConnectError::MinorTooOld are retained.
  • The send-authorization frame is still exercised end to end by synthetic arms declared at PROTOCOL_MINOR + 1, at three layers: minor.rs (Requires/arm_requires), connector.rs (check_arm_minor) and framing.rs (FrameWriter::authorize via CheckedFrame::raw, behind feature = "test-util").
  • Docs: the docs/reference/ipc.md ledger table collapses to row 0 and ~12 prose sites lose their minor numbers; docs/reference/cli.md says wire: 1.0; the architecture, session-lifecycle and testing pages follow.
  • CHANGELOG: the four "protocol minor 11/12/13/14" Unreleased entries become plain Wire entries (features stay, numbers go) and a reset entry is added under Changed. The dated [0.1.0] and earlier sections are untouched.

Deviation worth a look

The brief says to drop ConnectionMode::since_minor, and I followed it literally, which also took check_mode_minor and the mode half of the gating with it. since_minor() is kept on AttachFailure, RefusalReason and ResourceKind so the closed-enum half of the_ledger_covers_every_executable_addition stays alive. If modes should keep a declared minor for symmetry, say so and I will put it back at 0.

Gates

  • cargo clippy --workspace --all-targets --all-features — clean, no warnings
  • cargo nextest run --workspace --all-features --all-targets --retries 2 — 3453 passed, 0 failed, 16 skipped
  • just proto-compat — wire-compatible with the base
  • just docs-links — 392 unique links, 0 errors
  • python3 tools/prose_check.py --range origin/main...HEAD — 0 hits
  • pre-commit hooks ran in full on the commit, no SKIP: buf breaking passed (field options are invisible to it)

The #164 follow-up is rewritten as a comment on that issue, since its body described the pre-reset ledger.

Closes #313. The minor ledger reached 14 rows before any peer existed. Every one of those rows describes compatibility with a daemon that was never released, so the gates, fallbacks and downgrade paths they justify are dead weight the first release would have to carry forever — and the first ledger row that ever matters would be numbered 15. This folds the whole pre-release schema into the minor-0 base. ## What changes - `PROTOCOL_MINOR` 14 -> 0; the 14 `MINOR_*` constants are gone and `MINOR_LEDGER` has one row: `(0, "the base schema of protocol major 1")`. The preface golden vectors move to `1.0`. - `felis.proto`: the `PROTOCOL_MINOR` header comment is 0 and all 11 nonzero `(felis.v1.arm).since` values become `since: 0`, keeping the explicit-zero style every base arm already used. Generated Rust regenerated with `buf lint && buf generate`. - All six `GatedField` tables in `minor.rs` are empty, and every `since_minor()` on the closed enums returns 0. The tables and the `requires` impls that fold them stay, so the first post-release field is one row and nothing else. - The `Downgrade` trait and every impl are deleted: with no older peer to map to, every closed-enum value is in the base schema, and a future narrowing needs its own mapping regardless. - Every hard-coded minor gate in the client, CLI and daemon is gone (`check_scope_minor`, `check_key_input_minor`, `check_mode_minor`, the atomic-create and attach-by-prefix fallbacks, `effective_minor` plumbing through the session task, subscriber and bridge link). `check_arm_minor`, `FrameWriter::authorize`, `TransportError::MinorTooOld` and `ConnectError::MinorTooOld` are retained. - The send-authorization frame is still exercised end to end by synthetic arms declared at `PROTOCOL_MINOR + 1`, at three layers: `minor.rs` (`Requires`/`arm_requires`), `connector.rs` (`check_arm_minor`) and `framing.rs` (`FrameWriter::authorize` via `CheckedFrame::raw`, behind `feature = "test-util"`). - Docs: the `docs/reference/ipc.md` ledger table collapses to row 0 and ~12 prose sites lose their minor numbers; `docs/reference/cli.md` says `wire: 1.0`; the architecture, session-lifecycle and testing pages follow. - CHANGELOG: the four "protocol minor 11/12/13/14" Unreleased entries become plain **Wire** entries (features stay, numbers go) and a reset entry is added under Changed. The dated `[0.1.0]` and earlier sections are untouched. ## Deviation worth a look The brief says to drop `ConnectionMode::since_minor`, and I followed it literally, which also took `check_mode_minor` and the mode half of the gating with it. `since_minor()` is kept on `AttachFailure`, `RefusalReason` and `ResourceKind` so the closed-enum half of `the_ledger_covers_every_executable_addition` stays alive. If modes should keep a declared minor for symmetry, say so and I will put it back at 0. ## Gates - `cargo clippy --workspace --all-targets --all-features` — clean, no warnings - `cargo nextest run --workspace --all-features --all-targets --retries 2` — 3453 passed, 0 failed, 16 skipped - `just proto-compat` — wire-compatible with the base - `just docs-links` — 392 unique links, 0 errors - `python3 tools/prose_check.py --range origin/main...HEAD` — 0 hits - pre-commit hooks ran in full on the commit, no `SKIP`: `buf breaking` passed (field options are invisible to it) The #164 follow-up is rewritten as a comment on that issue, since its body described the pre-reset ledger.
protocol: ship the first wire contract as 1.0, not 1.14
Some checks failed
bench / Criterion regression gate (pull_request) Failing after 2m24s
darwin / detect relevant changes (pull_request) Successful in 4s
darwin / darwin docs-only no-op (pull_request) Has been skipped
pr / nix flake check (pull_request) Successful in 50s
pr / wire schema is compatible with the base (pull_request) Successful in 10s
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 52s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m22s
pr / cargo build / clippy / test / deny (pull_request) Successful in 2m9s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 1m2s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / cargo nextest (Windows) (pull_request) Successful in 6m13s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been cancelled
windows / detect relevant changes (pull_request) Successful in 3s
windows / windows docs-only no-op (pull_request) Has been skipped
windows / cargo clippy (Windows cross) (pull_request) Failing after 15s
windows / frontend smoke (Windows) (pull_request) Has been cancelled
1945fe60e8
The minor ledger grew fourteen rows while the protocol was still
unreleased, so every row records compatibility with a peer that never
existed. Shipping 1.14 would make each build carry gates, downgrade
paths and fallbacks for development history no deployed daemon can be
speaking, and would leave the ledger's first real entry indexed at 15.

Fold the whole pre-release schema into the minor-0 base: one ledger
row, every arm, field, closed-enum value, connection mode and row-codec
version available from minor 0. The gating machinery stays and is
exercised by synthetic arms declared above PROTOCOL_MINOR, so the first
post-release addition is one ledger row and one table entry rather
than a rebuilt frame.

The Downgrade trait goes with the values it mapped down: with no
older peer to map to, every closed enum is in the base schema, and a
future narrowing would need its own mapping anyway.

Assisted-by: Claude Code
protocol: keep connection modes inside minor authorization
Some checks failed
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 52s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m16s
pr / wire schema is compatible with the base (pull_request) Successful in 16s
windows / cargo clippy (Windows cross) (pull_request) Failing after 13s
windows / cargo nextest (Windows) (pull_request) Successful in 5m39s
windows / frontend smoke (Windows) (pull_request) Successful in 2m1s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Failing after 2m25s
darwin / detect relevant changes (pull_request) Successful in 3s
darwin / darwin docs-only no-op (pull_request) Has been skipped
pr / nix flake check (pull_request) Successful in 57s
pr / cargo build / clippy / test / deny (pull_request) Successful in 2m18s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 1m9s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 3s
windows / windows docs-only no-op (pull_request) Has been skipped
405eca25ce
Folding the pre-release minors into the base left `ConnectionMode`
with no declared minor at all, so `ConnMsg::Hello` inherited only its
arm's base requirement. A mode added after the first release would
then reach an older daemon, which decodes the `Hello` as no mode and
drops the connection, instead of the sender-side `MinorTooOld` the
wire contract promises; the ledger coverage test also lost its only
mechanical check that a mode declares an introducing minor.

Restore `ConnectionMode::since_minor` with every shipped mode at 0,
fold it into `Hello`'s requirement, walk modes again in the generated
enum sweep, and put the pre-send check back in the connector. Both
gates are pinned by a synthetic minor one past this build, since no
real mode is post-baseline.

Reword three comments that still carried development-minor numbers the
reset deleted.

Assisted-by: Claude Code
Author
Owner

Sol round 1 blocker addressed in 405eca25 (additive commit, no rebase, no force-push).

Blocker — ConnectionMode is back inside minor authorization. The deviation I flagged in the description is reverted; treat that section of the body as stale.

  • ConnectionMode::since_minor() restored in crates/felis-protocol/src/minor.rs, every shipped mode at 0.
  • ConnMsg::Hello folds it again: arm.max(Requires::new(mode.since_minor(), "ConnectionMode")), so a mode is no longer carried by its arm's base requirement alone.
  • Modes are walked again in gated_enum_values, so the_ledger_covers_every_executable_addition mechanically checks that every mode declares an introducing minor.
  • check_mode_minor restored in crates/felis-client-core/src/connector.rs and called before the Hello goes out, yielding the non-transient ConnectError::MinorTooOld rather than an unexplained EOF.
  • Two tests, both pinned by a synthetic minor one past this build since no real mode is post-baseline: a_hello_costs_the_minor_that_defined_its_mode (protocol) and a_mode_above_the_effective_minor_is_refused (connector, asserts MinorTooOld and !is_transient()).
  • docs/reference/ipc.md gets its send-side half back: the Hello bullet says the sender names only a mode the effective minor defines, and "Connection modes" states both ends of the rule again.

Non-blocking — development-minor history removed from comments.

  • crates/felis-client-core/src/roster.rs: "a pre-minor-3 roster" -> "a roster that carries no sequences".
  • crates/felis-protocol/src/messages.rs: "a // Minor 13. beside a since:" -> "a comment beside a since:".
  • The sweep for minor [1-9] / minor-[1-9] / 1.[1-9] found one more: crates/felis-protocol/proto/BREAKING.md claimed "1.9 is the first public baseline", now "the first public baseline is still unpublished". Every other hit in the tree is an unrelated decimal (byte counts, ratios, MSRV).

Gates on 405eca25

  • cargo clippy --workspace --all-targets --all-features — clean
  • cargo nextest run --workspace --all-features --all-targets --retries 2 — 3455 passed, 0 failed, 16 skipped
  • just proto-compat — wire-compatible with the base
  • just docs-links — 392 unique, 0 errors
  • python3 tools/prose_check.py --range origin/main...HEAD — 0 hits
  • hooks on, no --no-verify, no SKIP
Sol round 1 blocker addressed in `405eca25` (additive commit, no rebase, no force-push). **Blocker — `ConnectionMode` is back inside minor authorization.** The deviation I flagged in the description is reverted; treat that section of the body as stale. - `ConnectionMode::since_minor()` restored in `crates/felis-protocol/src/minor.rs`, every shipped mode at 0. - `ConnMsg::Hello` folds it again: `arm.max(Requires::new(mode.since_minor(), "ConnectionMode"))`, so a mode is no longer carried by its arm's base requirement alone. - Modes are walked again in `gated_enum_values`, so `the_ledger_covers_every_executable_addition` mechanically checks that every mode declares an introducing minor. - `check_mode_minor` restored in `crates/felis-client-core/src/connector.rs` and called before the `Hello` goes out, yielding the non-transient `ConnectError::MinorTooOld` rather than an unexplained EOF. - Two tests, both pinned by a synthetic minor one past this build since no real mode is post-baseline: `a_hello_costs_the_minor_that_defined_its_mode` (protocol) and `a_mode_above_the_effective_minor_is_refused` (connector, asserts `MinorTooOld` and `!is_transient()`). - `docs/reference/ipc.md` gets its send-side half back: the `Hello` bullet says the sender names only a mode the effective minor defines, and "Connection modes" states both ends of the rule again. **Non-blocking — development-minor history removed from comments.** - `crates/felis-client-core/src/roster.rs`: "a pre-minor-3 roster" -> "a roster that carries no sequences". - `crates/felis-protocol/src/messages.rs`: "a `// Minor 13.` beside a `since:`" -> "a comment beside a `since:`". - The sweep for `minor [1-9]` / `minor-[1-9]` / `1.[1-9]` found one more: `crates/felis-protocol/proto/BREAKING.md` claimed "1.9 is the first public baseline", now "the first public baseline is still unpublished". Every other hit in the tree is an unrelated decimal (byte counts, ratios, MSRV). **Gates on `405eca25`** - `cargo clippy --workspace --all-targets --all-features` — clean - `cargo nextest run --workspace --all-features --all-targets --retries 2` — 3455 passed, 0 failed, 16 skipped - `just proto-compat` — wire-compatible with the base - `just docs-links` — 392 unique, 0 errors - `python3 tools/prose_check.py --range origin/main...HEAD` — 0 hits - hooks on, no `--no-verify`, no `SKIP`
cli: keep the frozen protocol_mismatch kind without a constructor
Some checks failed
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 1m13s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / detect relevant changes (pull_request) Successful in 4s
windows / windows docs-only no-op (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Failing after 2m22s
darwin / detect relevant changes (pull_request) Successful in 5s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 52s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m9s
pr / nix flake check (pull_request) Successful in 37s
pr / cargo build / clippy / test / deny (pull_request) Successful in 2m3s
pr / wire schema is compatible with the base (pull_request) Successful in 9s
windows / frontend smoke (Windows) (pull_request) Successful in 1m52s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
windows / cargo clippy (Windows cross) (pull_request) Successful in 14s
windows / cargo nextest (Windows) (pull_request) Successful in 4m40s
ec9d85a8ef
The only site that raised `ErrorKind::ProtocolMismatch` was the
keyboard-input minor gate, which went when the whole schema became
minor 0: with one wire generation and no post-baseline surface
version, nothing can reach the kind. The token is published in the
`v1` vocabulary, the golden fixture and the CLI reference, so deleting
the variant would break a consumer that already branches on it.

Silence `dead_code` on the variant alone. The workspace lint run hid
this because `--all-features` compiles the `schema` feature, whose
`ALL` slice names every variant; the Windows cross job lints without
it and failed.

Assisted-by: Claude Code
Author
Owner

Windows clippy fixed in ec9d85a8 (additive, no rebase, no force-push).

The failure is not target-specific. ErrorKind::ProtocolMismatch has no constructor on any target: its only one was the keyboard-input minor gate in cli_sessions.rs, which went when the whole schema became minor 0. ConnectError::MinorTooOld already maps to ErrorKind::Unsupported (cli_output.rs:267), target-independently and unchanged from main, so there was no unix-only mapping to generalize.

What hid it on the Linux job is the feature set, not the target: ErrorKind::ALL is #[cfg(any(test, feature = "schema"))] and names every variant, so --all-features constructs the variant while the Windows cross job — cargo clippy --workspace --all-targets --target x86_64-pc-windows-gnu, no --all-features — does not. cargo clippy -p felis-cli --all-targets on Linux reproduces it exactly.

Fix: the variant stays and is annotated deliberately. protocol_mismatch is published in crates/felis-cli/schemas/felis-cli-v1.schema.json, tests/golden/error_kinds.txt and docs/reference/cli.md, and the vocabulary is frozen within a v, so deleting it would break a consumer that already branches on it. The doc comment now says nothing can raise it while major 1 is the only generation and the schema is all minor 0, and #[allow(dead_code)] sits on that variant alone. The golden still lists it (felis-cli suite: 331 passed).

Verified

  • nix develop .#windows -c cargo clippy --workspace --all-targets --target x86_64-pc-windows-gnu -- -D warnings — passes. The one remaining warning is the pre-existing unused dependency \rustix`manifest warning infelis-pty`, unrelated to this PR and not fatal.
  • cargo clippy --workspace --all-targets (no --all-features) — clean
  • cargo clippy --workspace --all-targets --all-features — clean
  • cargo nextest run -p felis-cli --all-features --all-targets --retries 2 — 331 passed
  • python3 tools/prose_check.py --range origin/main...HEAD — 0 hits
  • hooks on, no --no-verify, no SKIP
Windows clippy fixed in `ec9d85a8` (additive, no rebase, no force-push). **The failure is not target-specific.** `ErrorKind::ProtocolMismatch` has no constructor on *any* target: its only one was the keyboard-input minor gate in `cli_sessions.rs`, which went when the whole schema became minor 0. `ConnectError::MinorTooOld` already maps to `ErrorKind::Unsupported` (`cli_output.rs:267`), target-independently and unchanged from main, so there was no unix-only mapping to generalize. What hid it on the Linux job is the feature set, not the target: `ErrorKind::ALL` is `#[cfg(any(test, feature = "schema"))]` and names every variant, so `--all-features` constructs the variant while the Windows cross job — `cargo clippy --workspace --all-targets --target x86_64-pc-windows-gnu`, no `--all-features` — does not. `cargo clippy -p felis-cli --all-targets` on Linux reproduces it exactly. **Fix:** the variant stays and is annotated deliberately. `protocol_mismatch` is published in `crates/felis-cli/schemas/felis-cli-v1.schema.json`, `tests/golden/error_kinds.txt` and `docs/reference/cli.md`, and the vocabulary is frozen within a `v`, so deleting it would break a consumer that already branches on it. The doc comment now says nothing can raise it while major 1 is the only generation and the schema is all minor 0, and `#[allow(dead_code)]` sits on that variant alone. The golden still lists it (`felis-cli` suite: 331 passed). **Verified** - `nix develop .#windows -c cargo clippy --workspace --all-targets --target x86_64-pc-windows-gnu -- -D warnings` — passes. The one remaining warning is the pre-existing `unused dependency \`rustix\`` manifest warning in `felis-pty`, unrelated to this PR and not fatal. - `cargo clippy --workspace --all-targets` (no `--all-features`) — clean - `cargo clippy --workspace --all-targets --all-features` — clean - `cargo nextest run -p felis-cli --all-features --all-targets --retries 2` — 331 passed - `python3 tools/prose_check.py --range origin/main...HEAD` — 0 hits - hooks on, no `--no-verify`, no `SKIP`
natsukium deleted branch minor-reset 2026-09-21 21:34:24 +09:00
Commenting is not possible because the repository is archived.
No description provided.