protocol: keep session order and daemon identity on one authority #343

Merged
natsukium merged 2 commits from issue-333 into main 2026-09-22 11:34:28 +09:00
Owner

Closes #333

No public operation can produce protocol_mismatch: it named a
wire-generation mismatch the preface refuses before a verb runs, so the
token sat in the machine vocabulary with a #[allow(dead_code)] and no
call site. The comment beside it called the token frozen in v1, but
that freeze is against a consumer that cannot exist yet — v1 is
unreleased and no producer exists in the workspace — so removing it now
is the same pre-release break the proto batch makes, not a v bump.

A token for this class comes back under the normal additive
machine-output policy once a public operation can raise one.

Refs #333.

A public 1.0 daemon always stamps a creation sequence, so the optional
SessionInfo.sequence and the id-order fallback it fed only described a
daemon that predates the field — a peer no released build can be. Making
the field required puts one order under the switch ring: 0, which is
also what an omitted field writes, is refused at decode instead of
quietly demoting a row.

OpsStatusReply repeated the build string and the wire pair that
Welcome.identity and the preface's accept already establish on every
connection, which left a client with two authorities for one fact and no
rule for which to believe. The connector now keeps the accepted major
and the daemon's own minor (not the effective minor: against a newer
daemon the two differ, and the report is about the daemon) and composes
DaemonStatus from the handshake plus the live reply, so
felis daemon status fills the same JSON keys from the same numbers and
its output is unchanged in both formats.

Rejected: keeping the copy so one message carries the whole report. The
reply is read on a connection whose handshake the same client conducted,
so the copy can name nothing the client does not already hold.

The unknown-mode second decode in serve.rs stays and is recorded as
what it is: a diagnostic that recovers the raw enum number for a peer
not honoring the send-side contract, not a compatibility fallback. A
conforming client refuses a mode above the effective minor before
Hello, so ordinary skew never reaches that path.

Refs #333.

Review

pi luna and sol reviews both passed. Non-blocking findings folded: the "optional fields" explanation no longer lists sequence, the pool comment and session-lifecycle rationale drop the absent-sequence wording, from_halves states that it models same-build peers, and the daemon-ahead connector test asserts daemon_minor. Cascade after rebasing onto #341/#342: the felis-json v1 session row's sequence is required (schema regenerated).

just proto-compat, fmt-check, prose-check, clippy --all-features and the Windows cross clippy pass locally; the workspace test run shows only the pre-existing socket-path-length failures on this machine. The Criterion regression gate is a known-flaky bench job and fails on every PR.

Closes #333 No public operation can produce `protocol_mismatch`: it named a wire-generation mismatch the preface refuses before a verb runs, so the token sat in the machine vocabulary with a `#[allow(dead_code)]` and no call site. The comment beside it called the token frozen in `v1`, but that freeze is against a consumer that cannot exist yet — `v1` is unreleased and no producer exists in the workspace — so removing it now is the same pre-release break the proto batch makes, not a `v` bump. A token for this class comes back under the normal additive machine-output policy once a public operation can raise one. Refs #333. A public 1.0 daemon always stamps a creation sequence, so the optional `SessionInfo.sequence` and the id-order fallback it fed only described a daemon that predates the field — a peer no released build can be. Making the field required puts one order under the switch ring: `0`, which is also what an omitted field writes, is refused at decode instead of quietly demoting a row. `OpsStatusReply` repeated the build string and the wire pair that `Welcome.identity` and the preface's accept already establish on every connection, which left a client with two authorities for one fact and no rule for which to believe. The connector now keeps the accepted major and the daemon's own minor (not the effective minor: against a newer daemon the two differ, and the report is about the daemon) and composes `DaemonStatus` from the handshake plus the live reply, so `felis daemon status` fills the same JSON keys from the same numbers and its output is unchanged in both formats. Rejected: keeping the copy so one message carries the whole report. The reply is read on a connection whose handshake the same client conducted, so the copy can name nothing the client does not already hold. The unknown-mode second decode in `serve.rs` stays and is recorded as what it is: a diagnostic that recovers the raw enum number for a peer not honoring the send-side contract, not a compatibility fallback. A conforming client refuses a mode above the effective minor before `Hello`, so ordinary skew never reaches that path. Refs #333. ## Review pi luna and sol reviews both passed. Non-blocking findings folded: the "optional fields" explanation no longer lists `sequence`, the pool comment and session-lifecycle rationale drop the absent-sequence wording, `from_halves` states that it models same-build peers, and the daemon-ahead connector test asserts `daemon_minor`. Cascade after rebasing onto #341/#342: the `felis-json` v1 session row's `sequence` is required (schema regenerated). `just proto-compat`, fmt-check, prose-check, clippy `--all-features` and the Windows cross clippy pass locally; the workspace test run shows only the pre-existing socket-path-length failures on this machine. The Criterion regression gate is a known-flaky bench job and fails on every PR.
A public 1.0 daemon always stamps a creation sequence, so the optional
`SessionInfo.sequence` and the id-order fallback it fed only described a
daemon that predates the field — a peer no released build can be. Making
the field required puts one order under the switch ring: `0`, which is
also what an omitted field writes, is refused at decode instead of
quietly demoting a row.

`OpsStatusReply` repeated the build string and the wire pair that
`Welcome.identity` and the preface's accept already establish on every
connection, which left a client with two authorities for one fact and no
rule for which to believe. The connector now keeps the accepted major
and the daemon's own minor (not the effective minor: against a newer
daemon the two differ, and the report is about the daemon) and composes
`DaemonStatus` from the handshake plus the live reply, so
`felis daemon status` fills the same JSON keys from the same numbers and
its output is unchanged in both formats.

Rejected: keeping the copy so one message carries the whole report. The
reply is read on a connection whose handshake the same client conducted,
so the copy can name nothing the client does not already hold.

The unknown-mode second decode in `serve.rs` stays and is recorded as
what it is: a diagnostic that recovers the raw enum number for a peer
not honoring the send-side contract, not a compatibility fallback. A
conforming client refuses a mode above the effective minor before
`Hello`, so ordinary skew never reaches that path.

Refs #333.

Assisted-by: Claude Code
cli: drop the protocol_mismatch error token
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 5m53s
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 50s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 2m24s
pr / wire schema is compatible with the base (pull_request) Successful in 58s
pr / nix flake check (pull_request) Successful in 5m8s
pr / cargo build / clippy / test / deny (pull_request) Successful in 5m50s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 1m31s
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 nextest (Windows) (pull_request) Successful in 4m55s
windows / frontend smoke (Windows) (pull_request) Successful in 1m57s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
windows / cargo clippy (Windows cross) (pull_request) Successful in 13s
93d2518ac2
No public operation can produce `protocol_mismatch`: it named a
wire-generation mismatch the preface refuses before a verb runs, so the
token sat in the machine vocabulary with a `#[allow(dead_code)]` and no
call site. The comment beside it called the token frozen in `v1`, but
that freeze is against a consumer that cannot exist yet — `v1` is
unreleased and no producer exists in the workspace — so removing it now
is the same pre-release break the proto batch makes, not a `v` bump.

A token for this class comes back under the normal additive
machine-output policy once a public operation can raise one.

Refs #333.

Assisted-by: Claude Code
natsukium deleted branch issue-333 2026-09-22 11:34:28 +09:00
Commenting is not possible because the repository is archived.
No description provided.