[v0.1/P1] Make the unset-means-default unions explicit oneof variants #331

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

Background

The project's own rule is "when the presence of a field is what selects a mode, the mode belongs in a type", and "optional fields are for data that is genuinely absent". Three unions still use an unset arm to carry meaning, so a non-Rust peer reading the descriptor cannot see the mode.

Findings

1. SessionAttach sibling target fields

SessionAttach (felis.proto:1664-1673) carries id and id_prefix as sibling fields with "exactly one set" enforced only by the validator. The proto comment rejects a oneof solely because it "would renumber a released field" — no released field exists before the freeze. Make it:

oneof target {
  bytes id = 1;
  string id_prefix = 2;
}

Then "both set" and "neither set" are unrepresentable in the schema instead of being decode refusals, and a generated peer reads the choice from the descriptor.

2. RetargetTarget unset carrier means "default local"

RetargetTarget (:2060-2061) documents an unset carrier as the third, default-local arm. Add an explicit empty variant (RetargetLocal {}) so the union has three named arms and the "nothing to carry, so unset" convention disappears. The byte cost is one tag-length on a one-shot request.

3. OpsStop unset mode means if_empty

OpsStop (:2145-2148) treats an unset mode oneof as StopIfEmpty, while SwitchScope declares an unset oneof malformed. Pick one policy for mode-selecting oneofs and apply it to both.

Acceptance criteria

  • Every mode-selecting union uses named oneof arms; no unset arm carries meaning.
  • The domain types, convert/, docs/reference/ipc.md, and the protocol tests match.
  • CHANGELOG.md records the wire change.
  • Parent: #326
  • #48 — correlation as an exact per-arm identity
  • #47 — frame-family taxonomy and routing
## Background The project's own rule is "when the presence of a field is what selects a mode, the mode belongs in a type", and "optional fields are for data that is genuinely absent". Three unions still use an unset arm to carry meaning, so a non-Rust peer reading the descriptor cannot see the mode. ## Findings ### 1. `SessionAttach` sibling target fields `SessionAttach` (`felis.proto:1664-1673`) carries `id` and `id_prefix` as sibling fields with "exactly one set" enforced only by the validator. The proto comment rejects a `oneof` solely because it "would renumber a released field" — no released field exists before the freeze. Make it: ```proto oneof target { bytes id = 1; string id_prefix = 2; } ``` Then "both set" and "neither set" are unrepresentable in the schema instead of being decode refusals, and a generated peer reads the choice from the descriptor. ### 2. `RetargetTarget` unset carrier means "default local" `RetargetTarget` (`:2060-2061`) documents an unset `carrier` as the third, default-local arm. Add an explicit empty variant (`RetargetLocal {}`) so the union has three named arms and the "nothing to carry, so unset" convention disappears. The byte cost is one tag-length on a one-shot request. ### 3. `OpsStop` unset mode means `if_empty` `OpsStop` (`:2145-2148`) treats an unset `mode` oneof as `StopIfEmpty`, while `SwitchScope` declares an unset oneof malformed. Pick one policy for mode-selecting oneofs and apply it to both. ## Acceptance criteria - [ ] Every mode-selecting union uses named oneof arms; no unset arm carries meaning. - [ ] The domain types, `convert/`, `docs/reference/ipc.md`, and the protocol tests match. - [ ] `CHANGELOG.md` records the wire change. ## Related - Parent: #326 - #48 — correlation as an exact per-arm identity - #47 — frame-family taxonomy and routing
Author
Owner

Duplicate of #327, which already tracks all three cases (RetargetTarget.carrier unset, OpsStop.mode unset, SessionAttach id/id_prefix siblings) plus the theme/palette reset spelling. Filed before the earlier review comment was visible; nothing unique here. Closing so the tracker keeps one issue per finding.

Duplicate of #327, which already tracks all three cases (RetargetTarget.carrier unset, OpsStop.mode unset, SessionAttach id/id_prefix siblings) plus the theme/palette reset spelling. Filed before the earlier review comment was visible; nothing unique here. Closing so the tracker keeps one issue per finding.
Commenting is not possible because the repository is archived.
No description provided.