Freeze wire scalar shapes: attachment ids, resource report, env pair, pacing flag #328

Closed
opened 2026-09-21 23:15:26 +09:00 by natsukium · 1 comment
Owner

Background

Pre-freeze review (parent #326) found wire scalar shapes that force every future peer — including non-Rust ones reading felis.proto — to carry conversions the schema could state directly. None of these is covered by #326's six findings. Fixing any of them after the baseline costs a major bump; documenting the intent now costs a paragraph.

Cases

  1. Attachment.id is uint64 on the wire but a decimal string in CLI/bridge JSON, while session ids are 32-hex strings in the same objects. One object carries two integer spellings. Either unify the JSON spelling or freeze the "u64-as-string" rule (with the existing x-bound-exceeds-f64-precision annotation story) as the permanent convention for integer ids.
  2. ResourceReport uses three optional fields where absent ≠ 0 (max_subject_used, per_subject_limit, global_limit), and the human/--format json renderings branch on each. A reader under pressure (the report's stated purpose) must notice a missing key. Either split per-scope messages or collapse to 0 = unlimited, and freeze the human↔JSON correspondence table in cli.md.
  3. SpawnArgs.env (UTF-8 string pairs, user-typed) vs env_base (bytes pairs, captured): two origins, two spellings, both implemented by every peer. If both survive, freeze "only the relay sends env_base, and only over a local socket" as wire-enforced rather than convention (decode already checks the caps; the sender rule is prose only).
  4. ConnHello.pull_paced: bool beside ConnectionMode: the capability-bitmap rejection left exactly one bool standing. A second optimization reopens the bitmap debate. Freeze the rule now: the next optimization earns a mode, not a bool (ipc.md design record), or fold pacing into the mode enum while renumbering is still free.

Acceptance criteria

  • Each case has a frozen decision recorded in docs/reference/ipc.md / docs/reference/cli.md before the baseline in #115 lands.
  • Whatever the schema keeps is covered by convert/ narrowing tests and the arm-table sync test where applicable.
  • skills/felis/SKILL.md examples still match the frozen shapes.

Parent: #326 (final interface simplification audit).
Related: #313 (protocol 1.0 baseline).

## Background Pre-freeze review (parent #326) found wire scalar shapes that force every future peer — including non-Rust ones reading `felis.proto` — to carry conversions the schema could state directly. None of these is covered by #326's six findings. Fixing any of them after the baseline costs a major bump; documenting the intent now costs a paragraph. ## Cases 1. `Attachment.id` is `uint64` on the wire but a decimal **string** in CLI/bridge JSON, while session ids are 32-hex strings in the same objects. One object carries two integer spellings. Either unify the JSON spelling or freeze the "u64-as-string" rule (with the existing `x-bound-exceeds-f64-precision` annotation story) as the permanent convention for integer ids. 2. `ResourceReport` uses three `optional` fields where absent ≠ `0` (`max_subject_used`, `per_subject_limit`, `global_limit`), and the human/`--format json` renderings branch on each. A reader under pressure (the report's stated purpose) must notice a missing key. Either split per-scope messages or collapse to `0 = unlimited`, and freeze the human↔JSON correspondence table in `cli.md`. 3. `SpawnArgs.env` (UTF-8 `string` pairs, user-typed) vs `env_base` (`bytes` pairs, captured): two origins, two spellings, both implemented by every peer. If both survive, freeze "only the relay sends `env_base`, and only over a local socket" as wire-enforced rather than convention (decode already checks the caps; the sender rule is prose only). 4. `ConnHello.pull_paced: bool` beside `ConnectionMode`: the capability-bitmap rejection left exactly one bool standing. A second optimization reopens the bitmap debate. Freeze the rule now: the next optimization earns a mode, not a bool (`ipc.md` design record), or fold pacing into the mode enum while renumbering is still free. ## Acceptance criteria - [ ] Each case has a frozen decision recorded in `docs/reference/ipc.md` / `docs/reference/cli.md` before the baseline in #115 lands. - [ ] Whatever the schema keeps is covered by `convert/` narrowing tests and the arm-table sync test where applicable. - [ ] `skills/felis/SKILL.md` examples still match the frozen shapes. Parent: #326 (final interface simplification audit). Related: #313 (protocol 1.0 baseline).
Author
Owner

Decision: do; only case 2 changes the schema, cases 1, 3, 4 are recorded decisions (reviewed)

  1. Attachment.id u64 vs JSON string: freeze the rule, no schema change. "Integer ids beyond f64 precision are JSON strings" becomes the permanent convention in cli.md (the x-bound-exceeds-f64-precision annotation already exists).
  2. ResourceReport: schema change. 0 is legitimate for max_subject_used, so collapsing to 0 = unlimited is out. Applying #327's policy with every scope-dependent field inside its scope arm:
    message Limit { oneof limit { uint64 bounded = 1; Unlimited unlimited = 2; } }
    message ResourceReport {
      ResourceKind resource = 1; ResourceUnit unit = 2; uint64 total_used = 3;
      oneof scope { DaemonScope daemon = 4; SubjectScope subject = 5; }
    }
    message DaemonScope  { Limit global_limit = 1; }
    message SubjectScope { SubjectKind subject = 1; uint64 max_subject_used = 2; Limit per_subject_limit = 3; Limit global_limit = 4; }
    
    max_subject_used is plain under subject scope because the daemon always computes it there and never under daemon scope: the old optional encoded the scope, not availability. A daemon row with a per-subject limit becomes unrepresentable. CLI rendering is preserved by flattening: --format json keeps today's keys, an unlimited limit stays an omitted key, and only human output prints unlimited; the cli.md table is rewritten in terms of the arms. Own PR rebased on merged #330 (same message renumbered), own base: line.
  3. env / env_base: keep both; the sender rule stays a send-side invariant, the "wire-enforced" ask is declined. The rule is the one ipc.md and REQ-912a already state: the dialing process fills env_base only when its carrier is the local socket; an SSH dial leaves it absent and the relay's environment travels in FRLY. A daemon-side check is impossible: an over-cap relay legally omits FRLY and splices a bare FLIS stream, so the daemon cannot tell SSH from local, and the protobuf decoder has no carrier context. Recorded in the IPC explanation.
  4. pull_paced: bool: keep; record "the next optimization earns a mode, not a bool" in the IPC explanation. Folding it into the enum reopens the settled bitmap debate for no gain.
## Decision: do; only case 2 changes the schema, cases 1, 3, 4 are recorded decisions (reviewed) 1. **`Attachment.id` u64 vs JSON string: freeze the rule, no schema change.** "Integer ids beyond f64 precision are JSON strings" becomes the permanent convention in `cli.md` (the `x-bound-exceeds-f64-precision` annotation already exists). 2. **`ResourceReport`: schema change.** `0` is legitimate for `max_subject_used`, so collapsing to `0 = unlimited` is out. Applying #327's policy with every scope-dependent field inside its scope arm: ```proto message Limit { oneof limit { uint64 bounded = 1; Unlimited unlimited = 2; } } message ResourceReport { ResourceKind resource = 1; ResourceUnit unit = 2; uint64 total_used = 3; oneof scope { DaemonScope daemon = 4; SubjectScope subject = 5; } } message DaemonScope { Limit global_limit = 1; } message SubjectScope { SubjectKind subject = 1; uint64 max_subject_used = 2; Limit per_subject_limit = 3; Limit global_limit = 4; } ``` `max_subject_used` is plain under subject scope because the daemon always computes it there and never under daemon scope: the old `optional` encoded the scope, not availability. A daemon row with a per-subject limit becomes unrepresentable. CLI rendering is preserved by flattening: `--format json` keeps today's keys, an unlimited limit stays an omitted key, and only human output prints `unlimited`; the `cli.md` table is rewritten in terms of the arms. Own PR rebased on merged #330 (same message renumbered), own `base:` line. 3. **`env` / `env_base`: keep both; the sender rule stays a send-side invariant, the "wire-enforced" ask is declined.** The rule is the one `ipc.md` and REQ-912a already state: the dialing process fills `env_base` only when its carrier is the local socket; an SSH dial leaves it absent and the relay's environment travels in `FRLY`. A daemon-side check is impossible: an over-cap relay legally omits `FRLY` and splices a bare `FLIS` stream, so the daemon cannot tell SSH from local, and the protobuf decoder has no carrier context. Recorded in the IPC explanation. 4. **`pull_paced: bool`: keep; record "the next optimization earns a mode, not a bool"** in the IPC explanation. Folding it into the enum reopens the settled bitmap debate for no gain.
Commenting is not possible because the repository is archived.
No description provided.