protocol: say "no value" by absence in the session roster #165

Merged
natsukium merged 2 commits from fix/proto-session-normalize-144 into main 2026-09-06 15:51:17 +09:00
Owner

Fixes #144.

Three fields of the session roster row spelled "the daemon has no value
here" with a value drawn from the field's own domain: sequence = 0 for
a daemon below minor 3, idle_seconds = 0 for an attached session, and
a hand-formatted string for an attach instant whose shape nothing could
check. Presence carries all three now:

  • SessionInfo.sequence is explicitly optional and 1-based, so a wire
    0 is a decode error rather than a fourth meaning; the switch ring
    falls back to id order only on absence.
  • SessionInfo.idle_seconds is absent while attached, which frees 0
    to mean the second it names.
  • Attachment.attached_at is a google.protobuf.Timestamp, whose range
    a decoder checks. The RFC 3339 formatter moves to felis-cli, its
    only user, so the CLI's machine surface keeps publishing the same
    string.

This is a pre-release wire break, acknowledged in
crates/felis-protocol/proto/BREAKING.md against the rebase base:
daemon and client must be rebuilt together.

Doc cascade: docs/reference/ipc.md (per-field absent-value semantics
and the minor-3 ledger row), docs/explanation/ipc.md (the argument for
absence over an in-band sentinel, recorded once rather than per field),
the docs/how-to/ reap recipe whose jq filter documented 0 as
"attached", and CHANGELOG.md.

Verified: just check green (fmt, clippy, nextest, deny, proto-compat);
reviewed by pi luna + pi sol; docs proofread by Gemini gemini-3.8-flash-high

Deferred

  • Convert the reference/ipc.md "Session (kind = 4)" bullets into a field
    table — #29/#146 own that table rewrite; the three fields' frozen
    semantics are already stated in the existing bullets.
  • Ledger row 2 wording — it names Attachment's fields only, none of
    which was renamed.
  • Attachment timestamp assertion can flake on a non-monotonic clock —
    describes a rare CI-flake risk from SystemTime::now()
    non-monotonicity, not a concrete wrong output or a named acceptance
    criterion; hardening beyond what #144 requires.
  • BREAKING.md/ipc.md/CHANGELOG.md overstate the wire-break mechanism for
    attached_at — duplicate of the "guaranteed decode failure" finding
    (same lines, same wire-type/decode-outcome inaccuracy); merged under
    that title and fixed there.
  • pi sol unavailable — tooling/infra unavailability of the pi sol
    reviewer, not a finding about the diff itself; no fix in this PR's
    scope addresses it.
Fixes #144. Three fields of the session roster row spelled "the daemon has no value here" with a value drawn from the field's own domain: `sequence = 0` for a daemon below minor 3, `idle_seconds = 0` for an attached session, and a hand-formatted string for an attach instant whose shape nothing could check. Presence carries all three now: - `SessionInfo.sequence` is explicitly optional and 1-based, so a wire `0` is a decode error rather than a fourth meaning; the switch ring falls back to id order only on absence. - `SessionInfo.idle_seconds` is absent while attached, which frees `0` to mean the second it names. - `Attachment.attached_at` is a `google.protobuf.Timestamp`, whose range a decoder checks. The RFC 3339 formatter moves to `felis-cli`, its only user, so the CLI's machine surface keeps publishing the same string. This is a pre-release wire break, acknowledged in `crates/felis-protocol/proto/BREAKING.md` against the rebase base: daemon and client must be rebuilt together. Doc cascade: `docs/reference/ipc.md` (per-field absent-value semantics and the minor-3 ledger row), `docs/explanation/ipc.md` (the argument for absence over an in-band sentinel, recorded once rather than per field), the `docs/how-to/` reap recipe whose jq filter documented `0` as "attached", and `CHANGELOG.md`. Verified: just check green (fmt, clippy, nextest, deny, proto-compat); reviewed by pi luna + pi sol; docs proofread by Gemini gemini-3.8-flash-high ## Deferred - Convert the reference/ipc.md "Session (kind = 4)" bullets into a field table — #29/#146 own that table rewrite; the three fields' frozen semantics are already stated in the existing bullets. - Ledger row 2 wording — it names `Attachment`'s fields only, none of which was renamed. - Attachment timestamp assertion can flake on a non-monotonic clock — describes a rare CI-flake risk from `SystemTime::now()` non-monotonicity, not a concrete wrong output or a named acceptance criterion; hardening beyond what #144 requires. - BREAKING.md/ipc.md/CHANGELOG.md overstate the wire-break mechanism for `attached_at` — duplicate of the "guaranteed decode failure" finding (same lines, same wire-type/decode-outcome inaccuracy); merged under that title and fixed there. - pi sol unavailable — tooling/infra unavailability of the pi sol reviewer, not a finding about the diff itself; no fix in this PR's scope addresses it.
Three fields of the roster row spelled "the daemon has no value here"
with a value from the field's own domain: sequence 0 for a daemon below
minor 3, idle_seconds 0 for an attached session, and a formatted string
for an attach instant whose shape nothing could check. A sentinel drawn
from the domain cannot be told apart from a peer that meant it, so the
exclusion lives in prose that every consumer has to remember, and the
one that forgets returns a wrong answer instead of an error.

Presence carries it now. sequence is explicitly optional and 1-based,
so a wire 0 is a decode error rather than a fourth meaning, and the
switch ring falls back to id order only on absence; idle_seconds is
absent while attached, which frees 0 to mean the second it names; and
attached_at is a google.protobuf.Timestamp, whose range a decoder
checks. A felis-private epoch integer was rejected: every protobuf
consumer already maps the well-known type and none maps our convention.
The RFC 3339 formatter moves to felis-cli, its only user, so the CLI
surface keeps publishing the same string.

This breaks the wire before the first release, acknowledged in
proto/BREAKING.md: a peer from either side of it fails to decode any
roster carrying an attachment, so daemon and client must be rebuilt
together.

Refs #144

Assisted-by: Claude Code
docs: freeze the roster row's absent-value semantics
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 2m7s
bench / Criterion regression gate (pull_request) Failing after 2m16s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m36s
pr / nix flake check (pull_request) Successful in 1m29s
windows / cargo nextest (Windows) (pull_request) Failing after 8m0s
windows / frontend smoke (Windows) (pull_request) Has been skipped
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
pr / cargo build / clippy / test / deny (pull_request) Successful in 2m58s
pr / wire schema is compatible with the base (pull_request) Successful in 13s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 4m40s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / cargo clippy (Windows cross) (pull_request) Failing after 19s
c19ccfcef9
The reference described idle_seconds as "Some(0) while attached" and
the ledger's minor-3 row as "every row reads 0", which left absence and
0 meaning the same thing in two of the three fields the roster row can
omit. State one meaning per field where a reader looks it up, record
the argument for absence over an in-band sentinel once in the IPC
explanation rather than per field, and fix the reap recipe, whose jq
filter documented 0 as "attached".

Refs #144

Assisted-by: Claude Code
natsukium force-pushed fix/proto-session-normalize-144 from c19ccfcef9
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 2m7s
bench / Criterion regression gate (pull_request) Failing after 2m16s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m36s
pr / nix flake check (pull_request) Successful in 1m29s
windows / cargo nextest (Windows) (pull_request) Failing after 8m0s
windows / frontend smoke (Windows) (pull_request) Has been skipped
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
pr / cargo build / clippy / test / deny (pull_request) Successful in 2m58s
pr / wire schema is compatible with the base (pull_request) Successful in 13s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 4m40s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / cargo clippy (Windows cross) (pull_request) Failing after 19s
to 14fe57a30b
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 49s
bench / Criterion regression gate (pull_request) Failing after 2m47s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m27s
pr / nix flake check (pull_request) Successful in 27s
pr / wire schema is compatible with the base (pull_request) Successful in 10s
windows / cargo clippy (Windows cross) (pull_request) Successful in 17s
windows / cargo nextest (Windows) (pull_request) Failing after 8m11s
windows / frontend smoke (Windows) (pull_request) Has been skipped
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m51s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 1m3s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
2026-09-06 15:26:54 +09:00
Compare
natsukium force-pushed fix/proto-session-normalize-144 from 14fe57a30b
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 49s
bench / Criterion regression gate (pull_request) Failing after 2m47s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m27s
pr / nix flake check (pull_request) Successful in 27s
pr / wire schema is compatible with the base (pull_request) Successful in 10s
windows / cargo clippy (Windows cross) (pull_request) Successful in 17s
windows / cargo nextest (Windows) (pull_request) Failing after 8m11s
windows / frontend smoke (Windows) (pull_request) Has been skipped
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m51s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 1m3s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
to 6f29d13313
All checks were successful
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 49s
bench / Criterion regression gate (pull_request) Successful in 2m8s
fuzz / cargo fuzz smoke (per target) (pull_request) Successful in 1m5s
pr / nix flake check (pull_request) Successful in 28s
pr / cargo build / clippy / test / deny (pull_request) Successful in 1m42s
pr / wire schema is compatible with the base (pull_request) Successful in 9s
windows / cargo nextest (Windows) (pull_request) Successful in 6m10s
pr / frontend smoke (x86_64-linux) (pull_request) Successful in 55s
pr / publish felis (x86_64-linux) (pull_request) Has been skipped
windows / cargo clippy (Windows cross) (pull_request) Successful in 14s
windows / frontend smoke (Windows) (pull_request) Successful in 1m46s
windows / package felis (x86_64-pc-windows-msvc) (pull_request) Has been skipped
fuzz / cargo fuzz nightly long-run (push) Has been skipped
darwin / build felis (aarch64-darwin) (push) Successful in 13s
fuzz / cargo fuzz smoke (per target) (push) Successful in 1m18s
pr / nix flake check (push) Successful in 6s
pr / cargo build / clippy / test / deny (push) Successful in 1m41s
pr / wire schema is compatible with the base (push) Successful in 11s
pr / frontend smoke (x86_64-linux) (push) Successful in 6s
windows / cargo clippy (Windows cross) (push) Successful in 14s
pr / publish felis (x86_64-linux) (push) Successful in 14s
windows / cargo nextest (Windows) (push) Successful in 6m38s
windows / frontend smoke (Windows) (push) Successful in 1m58s
windows / package felis (x86_64-pc-windows-msvc) (push) Successful in 1m54s
2026-09-06 15:42:58 +09:00
Compare
natsukium deleted branch fix/proto-session-normalize-144 2026-09-06 15:51:17 +09:00
Sign in to join this conversation.
No description provided.