treewide: drop operation ids from the v0.1 wire contract #298

Merged
natsukium merged 2 commits from issue-296 into main 2026-09-19 13:55:00 +09:00
Owner

Implements the decision on #296: remove operation ids from the v0.1 contract (option 1).

Why

No message ever read the daemon's landing records back, so the minted token named a fan-out nothing could ask about: the CLI and the bridge report queued and exit, and the shipped agent skill lists the switch verbs under the surfaces it does not cover. Freezing <incarnation:016x>-<sequence> as contract before the consumer that reads it exists is the worst of both options, since a field whose meaning arrives later cannot change shape when it does. Adding the id back is an additive minor whenever a consumer appears.

Scope of the removal

  • Proto: OperationId, OperationEcho, OpsSwitched.operation_id, PushReattach.operation, PushRetargetHost.operation, SessionAttach.operation. Tags are left free, not reserved; the break is acknowledged with a base: entry in crates/felis-protocol/proto/BREAKING.md, and the codegen is regenerated.
  • Protocol crate: MINOR_OPERATION_IDS and its re-export, the four gated-field entries and their tests, the domain types, and the converts. The minor-8 ledger row keeps Spawn / Created and the acceptedqueued rename.
  • Daemon: src/operations.rs and the Operations handle on the pool, the mint at the OpsSwitch handler, the land call at the attach path, and the per-target stamping in the session task. A switch push now answers with a count rather than the list of attachment ids nothing reads.
  • Client: the echo threading through felis-client-core (connector.rs, dial.rs) and felis-client; attach_landing folds back into attach.
  • CLI / bridge: operation_id leaves the switch, ssh and window retarget objects and the bridge results, and crates/felis-cli/schemas/felis-cli-v1.schema.json is regenerated. The bridge goldens held no successful sessions.switch result, so a success-path test now pins {from, to, queued}.
  • Kept: queued as the sole completion claim, not_queued as its error kind.
  • Docs: docs/reference/ipc.md (Switch/Switched, the deleted "Operation ids" section, the minor-8 row), docs/reference/cli.md, the rewritten "What a switch reply can report" in docs/explanation/architecture/ipc.md, and a CHANGELOG.md entry for the machine-output and bridge-result key.

The skew outcome is stated in BREAKING.md rather than handled: the removed fields decode as unknown and therefore absent in both directions, so the switch path works between a daemon and a client on either side of the change.

Verification

  • just check green (fmt, clippy, tests, prose-check, skill-check, docs-links, deny, proto-compat, release-check). The pre-commit buf breaking hook has no acknowledgment path, so the commits skip that one hook; the CI gate just proto-compat reads BREAKING.md and passes.
  • grep -rn 'OperationId\|OperationEcho\|MINOR_OPERATION_IDS\|operation_id' crates/ docs/ skills/ returns only BREAKING.md.
  • Two openai-codex/gpt-5.6-sol reviews: round 1 PASS with two non-blocking findings (vestigial Vec<u64> in PushOutcome; the nonzero-queued-at-minor-0 invariant left unpinned), both fixed in 9403ccef; round 2 PASS with no findings.

Closes #296

Implements the decision on #296: [remove operation ids from the v0.1 contract](https://git.natsukium.com/natsukium/felis/issues/296#issuecomment-2971) (option 1). ## Why No message ever read the daemon's landing records back, so the minted token named a fan-out nothing could ask about: the CLI and the bridge report `queued` and exit, and the shipped agent skill lists the switch verbs under the surfaces it does not cover. Freezing `<incarnation:016x>-<sequence>` as contract before the consumer that reads it exists is the worst of both options, since a field whose meaning arrives later cannot change shape when it does. Adding the id back is an additive minor whenever a consumer appears. ## Scope of the removal - **Proto**: `OperationId`, `OperationEcho`, `OpsSwitched.operation_id`, `PushReattach.operation`, `PushRetargetHost.operation`, `SessionAttach.operation`. Tags are left free, not reserved; the break is acknowledged with a `base:` entry in `crates/felis-protocol/proto/BREAKING.md`, and the codegen is regenerated. - **Protocol crate**: `MINOR_OPERATION_IDS` and its re-export, the four gated-field entries and their tests, the domain types, and the converts. The minor-8 ledger row keeps `Spawn` / `Created` and the `accepted` → `queued` rename. - **Daemon**: `src/operations.rs` and the `Operations` handle on the pool, the mint at the `OpsSwitch` handler, the `land` call at the attach path, and the per-target stamping in the session task. A switch push now answers with a count rather than the list of attachment ids nothing reads. - **Client**: the echo threading through `felis-client-core` (`connector.rs`, `dial.rs`) and `felis-client`; `attach_landing` folds back into `attach`. - **CLI / bridge**: `operation_id` leaves the `switch`, `ssh` and `window retarget` objects and the bridge results, and `crates/felis-cli/schemas/felis-cli-v1.schema.json` is regenerated. The bridge goldens held no successful `sessions.switch` result, so a success-path test now pins `{from, to, queued}`. - **Kept**: `queued` as the sole completion claim, `not_queued` as its error kind. - **Docs**: `docs/reference/ipc.md` (Switch/Switched, the deleted "Operation ids" section, the minor-8 row), `docs/reference/cli.md`, the rewritten "What a switch reply can report" in `docs/explanation/architecture/ipc.md`, and a `CHANGELOG.md` entry for the machine-output and bridge-result key. The skew outcome is stated in `BREAKING.md` rather than handled: the removed fields decode as unknown and therefore absent in both directions, so the switch path works between a daemon and a client on either side of the change. ## Verification - `just check` green (fmt, clippy, tests, prose-check, skill-check, docs-links, deny, proto-compat, release-check). The pre-commit `buf breaking` hook has no acknowledgment path, so the commits skip that one hook; the CI gate `just proto-compat` reads `BREAKING.md` and passes. - `grep -rn 'OperationId\|OperationEcho\|MINOR_OPERATION_IDS\|operation_id' crates/ docs/ skills/` returns only `BREAKING.md`. - Two `openai-codex/gpt-5.6-sol` reviews: round 1 PASS with two non-blocking findings (vestigial `Vec<u64>` in `PushOutcome`; the nonzero-`queued`-at-minor-0 invariant left unpinned), both fixed in 9403ccef; round 2 PASS with no findings. Closes #296
No message ever read the daemon's landing records back, so the minted
token named a fan-out nothing could ask about: the CLI and the bridge
report `queued` and exit, and the shipped agent skill drives sessions
that have no window to move. Keeping the field on the wire would freeze
`<incarnation:016x>-<sequence>` as contract before the consumer that
reads it exists, and a shape cannot change when its meaning finally
arrives.

Completing the feature instead was rejected as L-sized work (timeout,
eviction, restart, carrier goldens, a `--wait` verb) for a consumer that
does not exist. Adding the id back later is an additive minor, and its
degradation for a peer that predates it is already on the books.
Removing the fan-out scope left no per-target verdict vector to
attribute either.

`queued` stays the sole completion claim and `not_queued` its error
kind. The tags are left free rather than reserved: no release baseline
carries them, and the break is acknowledged in `proto/BREAKING.md`,
which the `just proto-compat` gate reads.

Closes #296

Assisted-by: Claude Code
daemon: report a switch push as a count, not a target list
Some checks failed
bench / Criterion full-suite snapshot (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (pull_request) Has been skipped
bench / Criterion regression gate (pull_request) Failing after 2m10s
darwin / detect relevant changes (pull_request) Successful in 4s
darwin / darwin docs-only no-op (pull_request) Has been skipped
darwin / build felis (aarch64-darwin) (pull_request) Successful in 53s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m5s
pr / nix flake check (pull_request) Successful in 31s
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m37s
pr / wire schema is compatible with the base (pull_request) Successful in 11s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 55s
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
windows / cargo clippy (Windows cross) (pull_request) Successful in 12s
windows / cargo nextest (Windows) (pull_request) Successful in 4m10s
windows / frontend smoke (Windows) (pull_request) Successful in 1m34s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
9403ccefb9
The attachment ids a push reached were collected for the landing
records; nothing reads them now that the sole answer is how many
outboxes took the frame, so collecting them allocates per push for a
number the caller could have counted.

The minor gate keeps its own regression: a nonzero `queued` must stay
sendable at minor 0, since the field is tag 3 under a new name rather
than a post-baseline addition.

Assisted-by: Claude Code
natsukium scheduled this pull request to auto merge when all checks succeed 2026-09-19 13:54:40 +09:00
natsukium deleted branch issue-296 2026-09-19 13:55:00 +09:00
Sign in to join this conversation.
No description provided.