[v0.1/P1] Reset the first public wire baseline to protocol 2.0 #30

Closed
opened 2026-09-03 16:18:08 +09:00 by natsukium · 2 comments
Owner

Parent: #12 (P1.9). Supersedes the pre-release versioning question in #5 and #9.

Why

Protocol major 1 was reused across incompatible untagged development snapshots. Freezing its minor-1-through-5 downgrade branches would preserve compatibility code without identifying a real supported peer.

Scope

  • Land after every other pre-release wire edit.
  • Move the first public schema to felis.v2, protocol major 2 minor 0.
  • Fold every shipped field into the baseline.
  • Delete development-only minor 1–5 sender gates, downgrade branches, and annotations.
  • Do not add a side-by-side v1 decoder because no tagged v1 exists.
  • Start future additive ledgers and vectors from the tagged minor-0 schema.

Acceptance criteria

  • A development-major peer is refused at the frozen preface.
  • The released minor-0 implementation has no historical sender gates.
  • Proto package, generated code, preface vectors, row vectors, reference ledger, and changelog all identify protocol 2.0.
  • The tagged schema is the CI compatibility baseline for future changes.
Parent: #12 (P1.9). Supersedes the pre-release versioning question in #5 and #9. ## Why Protocol major 1 was reused across incompatible untagged development snapshots. Freezing its minor-1-through-5 downgrade branches would preserve compatibility code without identifying a real supported peer. ## Scope - Land after every other pre-release wire edit. - Move the first public schema to `felis.v2`, protocol major 2 minor 0. - Fold every shipped field into the baseline. - Delete development-only minor 1–5 sender gates, downgrade branches, and annotations. - Do not add a side-by-side v1 decoder because no tagged v1 exists. - Start future additive ledgers and vectors from the tagged minor-0 schema. ## Acceptance criteria - [ ] A development-major peer is refused at the frozen preface. - [ ] The released minor-0 implementation has no historical sender gates. - [ ] Proto package, generated code, preface vectors, row vectors, reference ledger, and changelog all identify protocol 2.0. - [ ] The tagged schema is the CI compatibility baseline for future changes.
Author
Owner

Triage plan (2026-09-03)

Source-grounded triage against main at 69076d42, reviewed through seven rounds of an independent reviewer (pi sol/luna) until it passed with no findings. The dependency order that supersedes the tracker's is posted on #12. Where a "Review amendments" section below conflicts with an earlier section, the amendment is the decision.

Claim check

Accurate against HEAD. The three post-snapshot commits (#28, #53/#54) touched config and CLI only; the wire is unchanged.

  • PROTOCOL_MAJOR = 1, PROTOCOL_MINOR = 5 (crates/felis-protocol/src/preface.rs:52, :57; mirrored at felis.proto:40-41 and asserted by the_protocol_version_matches_the_schema, preface.rs:758-777). Package felis.v1 (felis.proto:3), generated at crates/felis-protocol/src/generated/felis/v1/, exposed as wire::v1 (src/wire.rs).
  • Development-era gates to delete: MINOR_GEOMETRY_OUT_OF_RANGE, MINOR_LIVE_ONLY_ATTACH, MINOR_ENV_BASE, MINOR_DAEMON_STATUS (preface.rs:59-80); SWITCH_SCOPE_MINOR + check_scope_minor (crates/felis-client-core/src/connector.rs:138-156, re-exported lib.rs:60, used by cli_bridge.rs:2067-2085); daemon_status gate (connector.rs:996-1005); geometry_refusal / session_limit_refusal / exited_refusal (crates/felis-daemon/src/serve.rs:1012-1039) and their call sites (:475, :496, :594); the pre-minor-2 scope decode test an_absent_switch_scope_decodes_as_the_all_mirror_form (convert.rs:377-400) and its scope: None → All branch in convert/ops.rs; proto "Minor N" annotations (felis.proto:209, 215, 222, 523, 578, 1097, 1155, 1204); the ledger rows 1-5 (docs/reference/ipc.md:1748-1752); the effective-minor tests at connector.rs:2077-2164 that assume minors 0..5 exist.
  • Preface vectors: preface.rs:488-490 (00 01 00 05), :512-514; row-codec vectors: crates/felis-grid/src/wire.rs (golden). felis daemon status prints the wire major/minor (CHANGELOG Unreleased entry; skills/felis/SKILL.md:220), so its JSON changes from 1.5 to 2.0.
  • Reserved numbers that exist only because of untagged dev history: RefusalReason 1, 2 (felis.proto:169-170), SearchMsg 3 "done" (:1630-1631), OpsStatusReply 6, 7 (:1416). The issue's "fold every shipped field into the baseline" implies these can be compacted; it does not say so explicitly.
  • CI: buf breaking uses WIRE_JSON (buf.yaml) and the pr.yml:45-60 proto job; a package rename is a breaking change by every rule, so this lands only through #19's explicit pre-release acknowledgement path.
  • "Do not add a side-by-side v1 decoder": docs/reference/ipc.md:1680-1687 and docs/explanation/architecture/ipc.md:914-922 say a post-freeze major owes a deprecation window; 2.0 is the first freeze, so no window is owed. Consistent.

Verdict

accept-with-changes: do it as scoped, plus two explicit decisions the issue leaves implicit: (a) compact the dev-only reserved numbers in the same change (there is no v1 peer to protect; a reserved line in a first public schema is history, which the docs rule says belongs to git log), and (b) state in the explanation that 2.0 opens with no deprecation window and that the first post-2.0 major is the first that owes one. Land strictly last, as the trackers say.

Approach

Mechanical, one commit series on top of every other wire edit:

  1. Schema: package felis.v2, header comment PROTOCOL_MAJOR = 2 / PROTOCOL_MINOR = 0, delete the eight "Minor N" annotations and the reserved lines above (renumber RefusalReason, SearchMsg, OpsStatusReply to dense source order, per the "1-based source order" rule at felis.proto:18-22). buf.gen.yaml output path stays; just proto regenerates into generated/felis/v2/; src/wire.rs exports pub mod v2 (grep wire::v1 across crates/ and update; convert/*.rs, codec.rs:96-100, :150-199, lib.rs:174, messages tests). Delete the felis/v1 generated directory.
  2. preface.rs: PROTOCOL_MAJOR = 2, PROTOCOL_MINOR = 0, delete MINOR_* (:59-80); update the golden bytes at :488-490, :512-514; this_build_serves_exactly_its_own_major (:780-784) stays.
  3. Delete the gates and downgrade helpers listed in the claim check; AttachFailure sends its real reason unconditionally; daemon_status sends unconditionally; SwitchScope decode: absent scope is a malformed frame or Default (pick Default only if #24/#26 did not already settle it; otherwise malformed, since 2.0 has no peer that omits it). Remove ConnectError::MinorTooOld only if #50 does not reuse it (it does; keep).
  4. Tests: rewrite the effective-minor tests (connector.rs:2077-2164, cli_bridge.rs:2067-2085) as the #50 synthetic-fixture tests; serve/tests.rs:401 (unsupported major) now sends major 1 and expects Refuse { 2, 2 }, which is the "development-major peer is refused" criterion; preface.rs duplex tests in felis-transport/src/preface.rs:115-134 follow the constants.
  5. Docs: docs/reference/ipc.md:320-336 (package felis.v2), :1680-1687 (drop "until the compatibility freeze ... under major 1"), :1745-1752 ledger collapses to the single minor-0 row naming 2.0 as the base schema, :199-206; docs/explanation/architecture/ipc.md:60-68 proto header mirror, :901-906, :914-922 (replace the pre-freeze paragraph with: 2.0 is the first tagged schema; dev major 1 was never tagged and gets no decoder; the first post-2.0 major owes the window); docs/reference/spec.md:54-57 if any REQ names major 1 (grep major 1, felis.v1, minor 5 across docs/ before declaring done); docs/reference/testing.md if it names the vectors; docs/reference/cli.md for daemon status output examples showing 1.5.
  6. CHANGELOG.md: under Unreleased / Changed, one entry: "Wire: the first public protocol is 2.0 (package felis.v2). The untagged development major 1 is refused at the preface; there is no v1 decoder. Every addition made during development (minor 1-5) is part of the 2.0 baseline." Fold the existing "Wire: protocol minor 5" bullet into it (an unreleased minor-5 line is meaningless once 2.0 is the first release).
  7. skills/felis/SKILL.md:220 and any example output showing 1.5; docs/reference/control-surfaces.md bridge JSON if it echoes the wire version.
  8. CI: land through #19's acknowledged-break path (pr.yml proto job). After the tag, buf breaking --against <tag> is the baseline (#19 acceptance "the tagged schema is the CI compatibility baseline").
  9. External repos (felis-tui, felis-web-component, felis.el, per docs/explanation/architecture/overview.md "Reuse paths for non-Rust clients") regenerate from felis.v2; note it in the commit body, not the docs.

Dependencies

After every other wire edit: #45, #47, #48, #49, #51, #20, #24, #26, and #46; #50's mechanism is built on this tree afterwards (see the #50 report for the #12/#52 ordering conflict and the recommended resolution: #30#50#29). #19 must be in place so the breaking check is real and the acknowledgement path exists. Then #52 closes and #31 runs the gates.

Risk/effort

M (1-2 days, wide but mechanical). Main risk: a missed felis.v1 / 1.5 / minor 5 mention in docs or in an external client's generated code; the grep sweep in step 5 and the the_protocol_version_matches_the_schema / every_message_kind_matches_the_schema_enum tests are the guards. Second: the compaction in step 1 must happen in the same commit as the package rename so no intermediate state has recycled numbers under felis.v1.

Labels

Keep priority/P1, release/v0.1.0. It is the last wire commit before the tag and gates #52/#31.

Review amendments (round 2)

  • Reserved-number audit corrected and completed. reserved 6, 7 at felis.proto:1419 belongs to ResourceKind, not OpsStatusReply. Full list of non-field-100 reservations at HEAD: RefusalReason 1, 2 (:169), ConnHello 1 (:637), ConnRefused 2 (:667), RetargetTarget 1-5 (:1305), ResourceKind 6, 7 (:1419), RegionRowsDone 1 (:1511), SearchMsg 3 (:1630). Decision: all seven are development-only history and are compacted to dense source order in the 2.0 reset. The wire-wide field-100 Correlation reservations (:621, :721, :797, :979 and the family wrappers) are design, not history, and stay.
## Triage plan (2026-09-03) Source-grounded triage against `main` at `69076d42`, reviewed through seven rounds of an independent reviewer (`pi` sol/luna) until it passed with no findings. The dependency order that supersedes the tracker's is posted on #12. Where a "Review amendments" section below conflicts with an earlier section, the amendment is the decision. ## Claim check Accurate against HEAD. The three post-snapshot commits (#28, #53/#54) touched config and CLI only; the wire is unchanged. - `PROTOCOL_MAJOR = 1`, `PROTOCOL_MINOR = 5` (`crates/felis-protocol/src/preface.rs:52`, `:57`; mirrored at `felis.proto:40-41` and asserted by `the_protocol_version_matches_the_schema`, `preface.rs:758-777`). Package `felis.v1` (`felis.proto:3`), generated at `crates/felis-protocol/src/generated/felis/v1/`, exposed as `wire::v1` (`src/wire.rs`). - Development-era gates to delete: `MINOR_GEOMETRY_OUT_OF_RANGE`, `MINOR_LIVE_ONLY_ATTACH`, `MINOR_ENV_BASE`, `MINOR_DAEMON_STATUS` (`preface.rs:59-80`); `SWITCH_SCOPE_MINOR` + `check_scope_minor` (`crates/felis-client-core/src/connector.rs:138-156`, re-exported `lib.rs:60`, used by `cli_bridge.rs:2067-2085`); `daemon_status` gate (`connector.rs:996-1005`); `geometry_refusal` / `session_limit_refusal` / `exited_refusal` (`crates/felis-daemon/src/serve.rs:1012-1039`) and their call sites (`:475`, `:496`, `:594`); the pre-minor-2 scope decode test `an_absent_switch_scope_decodes_as_the_all_mirror_form` (`convert.rs:377-400`) and its `scope: None → All` branch in `convert/ops.rs`; proto "Minor N" annotations (`felis.proto:209, 215, 222, 523, 578, 1097, 1155, 1204`); the ledger rows 1-5 (`docs/reference/ipc.md:1748-1752`); the effective-minor tests at `connector.rs:2077-2164` that assume minors 0..5 exist. - Preface vectors: `preface.rs:488-490` (`00 01 00 05`), `:512-514`; row-codec vectors: `crates/felis-grid/src/wire.rs` (golden). `felis daemon status` prints the wire major/minor (CHANGELOG Unreleased entry; `skills/felis/SKILL.md:220`), so its JSON changes from 1.5 to 2.0. - Reserved numbers that exist only because of untagged dev history: `RefusalReason 1, 2` (`felis.proto:169-170`), `SearchMsg 3 "done"` (`:1630-1631`), `OpsStatusReply 6, 7` (`:1416`). The issue's "fold every shipped field into the baseline" implies these can be compacted; it does not say so explicitly. - CI: `buf breaking` uses `WIRE_JSON` (`buf.yaml`) and the `pr.yml:45-60` proto job; a package rename is a breaking change by every rule, so this lands only through #19's explicit pre-release acknowledgement path. - "Do not add a side-by-side v1 decoder": `docs/reference/ipc.md:1680-1687` and `docs/explanation/architecture/ipc.md:914-922` say a post-freeze major owes a deprecation window; 2.0 is the first freeze, so no window is owed. Consistent. ## Verdict **accept-with-changes**: do it as scoped, plus two explicit decisions the issue leaves implicit: (a) compact the dev-only `reserved` numbers in the same change (there is no v1 peer to protect; a `reserved` line in a first public schema is history, which the docs rule says belongs to `git log`), and (b) state in the explanation that 2.0 opens with no deprecation window and that the first *post-2.0* major is the first that owes one. Land strictly last, as the trackers say. ## Approach Mechanical, one commit series on top of every other wire edit: 1. Schema: `package felis.v2`, header comment `PROTOCOL_MAJOR = 2` / `PROTOCOL_MINOR = 0`, delete the eight "Minor N" annotations and the `reserved` lines above (renumber `RefusalReason`, `SearchMsg`, `OpsStatusReply` to dense source order, per the "1-based source order" rule at `felis.proto:18-22`). `buf.gen.yaml` output path stays; `just proto` regenerates into `generated/felis/v2/`; `src/wire.rs` exports `pub mod v2` (grep `wire::v1` across `crates/` and update; `convert/*.rs`, `codec.rs:96-100`, `:150-199`, `lib.rs:174`, `messages` tests). Delete the `felis/v1` generated directory. 2. `preface.rs`: `PROTOCOL_MAJOR = 2`, `PROTOCOL_MINOR = 0`, delete `MINOR_*` (`:59-80`); update the golden bytes at `:488-490`, `:512-514`; `this_build_serves_exactly_its_own_major` (`:780-784`) stays. 3. Delete the gates and downgrade helpers listed in the claim check; `AttachFailure` sends its real reason unconditionally; `daemon_status` sends unconditionally; `SwitchScope` decode: absent scope is a malformed frame or `Default` (pick `Default` only if #24/#26 did not already settle it; otherwise malformed, since 2.0 has no peer that omits it). Remove `ConnectError::MinorTooOld` only if #50 does not reuse it (it does; keep). 4. Tests: rewrite the effective-minor tests (`connector.rs:2077-2164`, `cli_bridge.rs:2067-2085`) as the #50 synthetic-fixture tests; `serve/tests.rs:401` (unsupported major) now sends major 1 and expects `Refuse { 2, 2 }`, which is the "development-major peer is refused" criterion; `preface.rs` duplex tests in `felis-transport/src/preface.rs:115-134` follow the constants. 5. Docs: `docs/reference/ipc.md:320-336` (package `felis.v2`), `:1680-1687` (drop "until the compatibility freeze ... under major 1"), `:1745-1752` ledger collapses to the single minor-0 row naming 2.0 as the base schema, `:199-206`; `docs/explanation/architecture/ipc.md:60-68` proto header mirror, `:901-906`, `:914-922` (replace the pre-freeze paragraph with: 2.0 is the first tagged schema; dev major 1 was never tagged and gets no decoder; the first post-2.0 major owes the window); `docs/reference/spec.md:54-57` if any REQ names major 1 (grep `major 1`, `felis.v1`, `minor 5` across `docs/` before declaring done); `docs/reference/testing.md` if it names the vectors; `docs/reference/cli.md` for `daemon status` output examples showing `1.5`. 6. `CHANGELOG.md`: under Unreleased / Changed, one entry: "Wire: the first public protocol is 2.0 (package `felis.v2`). The untagged development major 1 is refused at the preface; there is no v1 decoder. Every addition made during development (minor 1-5) is part of the 2.0 baseline." Fold the existing "Wire: protocol minor 5" bullet into it (an unreleased minor-5 line is meaningless once 2.0 is the first release). 7. `skills/felis/SKILL.md:220` and any example output showing `1.5`; `docs/reference/control-surfaces.md` bridge JSON if it echoes the wire version. 8. CI: land through #19's acknowledged-break path (`pr.yml` proto job). After the tag, `buf breaking --against <tag>` is the baseline (#19 acceptance "the tagged schema is the CI compatibility baseline"). 9. External repos (`felis-tui`, `felis-web-component`, `felis.el`, per `docs/explanation/architecture/overview.md` "Reuse paths for non-Rust clients") regenerate from `felis.v2`; note it in the commit body, not the docs. ## Dependencies After every other wire edit: #45, #47, #48, #49, #51, #20, #24, #26, and #46; #50's mechanism is built on this tree afterwards (see the #50 report for the #12/#52 ordering conflict and the recommended resolution: #30 → #50 → #29). #19 must be in place so the breaking check is real and the acknowledgement path exists. Then #52 closes and #31 runs the gates. ## Risk/effort **M** (1-2 days, wide but mechanical). Main risk: a missed `felis.v1` / `1.5` / `minor 5` mention in docs or in an external client's generated code; the grep sweep in step 5 and the `the_protocol_version_matches_the_schema` / `every_message_kind_matches_the_schema_enum` tests are the guards. Second: the compaction in step 1 must happen in the same commit as the package rename so no intermediate state has recycled numbers under `felis.v1`. ## Labels Keep `priority/P1`, `release/v0.1.0`. It is the last wire commit before the tag and gates #52/#31. ## Review amendments (round 2) - **Reserved-number audit corrected and completed.** `reserved 6, 7` at `felis.proto:1419` belongs to `ResourceKind`, not `OpsStatusReply`. Full list of non-field-100 reservations at HEAD: `RefusalReason 1, 2` (`:169`), `ConnHello 1` (`:637`), `ConnRefused 2` (`:667`), `RetargetTarget 1-5` (`:1305`), `ResourceKind 6, 7` (`:1419`), `RegionRowsDone 1` (`:1511`), `SearchMsg 3` (`:1630`). Decision: all seven are development-only history and are compacted to dense source order in the 2.0 reset. The wire-wide field-100 `Correlation` reservations (`:621, :721, :797, :979` and the family wrappers) are design, not history, and stay.
Author
Owner

#30#138 の決定により 見送り とします。

  • package は felis.v1 維持、protocol は 1.0(現行 1.9 を含む)を初回公開 baseline としてリセット
  • BREAKING.mdfirst-release のみにリセット済み(f56f5529
  • felis.v2 / major 2 への bump は、将来 side-by-side decoder が必要になった時点で検討

この issue は close します。次の release gate(#115, #31)は 1.0 baseline として現行 proto を採用します。

#30 は #138 の決定により **見送り** とします。 - package は `felis.v1` 維持、protocol は `1.0`(現行 `1.9` を含む)を初回公開 baseline としてリセット - `BREAKING.md` を `first-release` のみにリセット済み(f56f5529) - `felis.v2` / major 2 への bump は、将来 side-by-side decoder が必要になった時点で検討 この issue は close します。次の release gate(#115, #31)は 1.0 baseline として現行 proto を採用します。
Sign in to join this conversation.
No description provided.