feat: InvalidParams variant + #[non_exhaustive] TtySessionError (R5)

Review #002 R5 — the open_via_channels fail-fast parse (a params value
that fails the local NegotiateRequest parse before a channel is
allocated) surfaced as NegotiationSerialize, whose name and doc
describe serializing the negotiation frame, not parsing open-op params.

- add TtySessionError::InvalidParams(String); the fail-fast path maps
  to it (the serde_json::Error's From impl stays for
  NegotiationSerialize's real users — the direct-path serialize)
- mark TtySessionError #[non_exhaustive] — the same two-way-door
  pattern as TtyError (backend.rs) and alkcall's consumer-facing
  AdapterError; the policy rationale is in the enum's doc
- NegotiationError / RawError stay exhaustive (deliberate — they
  mirror fixed wire semantics; in-crate matchers keep exhaustiveness
  checking)
- the two fail-fast tests assert InvalidParams(_) now
- review #002: R5 resolved; the superseded deferral rationale is
  recorded (circular trigger — "first channels-path consumer exists"
  fires after the change becomes expensive; misapplied citation —
  ADR-009's version-skew note governs wire skew, not error enums;
  the "unreachable" framing belonged to R4's arm, not R5's — the
  fail-fast path is live today). The #[non_exhaustive] policy is
  decided on principle, pre-publish, while the variant addition is
  additive by construction

Verification: cargo test 95 lib (default) / 138 (--all-features);
clippy -D warnings native + wasm clean; fmt clean; doc 0 warnings.
This commit is contained in:
2026-09-05 08:45:58 +00:00
parent 8ee9216a07
commit 918af406dd
2 changed files with 68 additions and 19 deletions
+40 -9
View File
@@ -1,5 +1,5 @@
---
status: partially-resolved
status: resolved
last_updated: 2026-09-05
reviewed_artifacts:
- docs/architecture/decisions/009-channels-open-op-is-the-negotiation.md
@@ -221,8 +221,37 @@ assert on `NegotiationSerialize(_)` today. Options:
2. Cheaper: reword the `NegotiationSerialize` doc to note the
channels-path fail-fast reuse. No API change, still mislabeled.
Defer until the first channels-path consumer exists (per the L1
version-skew note in ADR-009); option 1 at that point.
**Original disposition (superseded — see resolution)**: deferred until
the first channels-path consumer exists (per the L1 version-skew note
in ADR-009); option 1 at that point.
**Rationale for superseding the deferral (2026-09-05)**: the deferral
trigger was circular — "the first channels-path consumer exists" only
fires *after* the change has become expensive (an exhaustive match in
that consumer turns the additive variant into a break), so deferring
converted a zero-risk change into a medium-risk one by waiting. The
ADR-009 citation was also misapplied: the L1 version-skew note governs
*wire* skew (the removed negotiation frame), not API-surface error
enums — ADR-009 doesn't decide this. And the "effectively
unreachable" framing belonged to R4's producer-side arm, not R5's:
the fail-fast path is live today (any `open_via_channels` caller with
bad params gets `NegotiationSerialize` now). The substantive pending
item was the `#[non_exhaustive]` policy decision, which is answerable
on principle without consumers: `TtyError` is already
`#[non_exhaustive]` (backend.rs:44, the two-way-door extension
pattern); alkcall's consumer-facing `AdapterError` is
`#[non_exhaustive]` for exactly this reason; `NegotiationError` and
`RawError` mirror fixed wire semantics and stay exhaustive
(deliberate — in-crate matchers keep exhaustiveness checking).
**Resolution (2026-09-05)**: option 1 implemented pre-publish, when
adding a variant is additive (the crate is unpublished — there are no
consumers to break): `TtySessionError` is now `#[non_exhaustive]`
(with the policy rationale in its doc), the fail-fast path maps to
the new `InvalidParams(String)` variant, and the two fail-fast tests
assert `InvalidParams(_)`. No exhaustive `match` on `TtySessionError`
exists in-crate or in `tests/` (the tests use `matches!`, unaffected
by `#[non_exhaustive]`).
---
@@ -274,13 +303,15 @@ version-skew note in ADR-009); option 1 at that point.
| R2 | stale docs from the L1 redesign | align with ADR-009 | trivial | none | ✅ resolved (`37ae07a`) |
| R3 | install-time identity snapshot | accepted design (hub-proxy rationale) | none | none | ✅ closed as intended |
| R4 | silent death on parse-failure path | error frame or documented asymmetry | small | low | ✅ resolved (option 1) |
| R5 | `NegotiationSerialize` mislabel on fail-fast | additive variant (with `#[non_exhaustive]` decision) | small | medium (semver) | ⬜ open (deferred) |
| R5 | `NegotiationSerialize` mislabel on fail-fast | additive variant (with `#[non_exhaustive]` decision) | small | medium (semver) | ✅ resolved (option 1 + `#[non_exhaustive]`, pre-publish) |
R5 is deferred deliberately: it touches the consumer-facing error
surface, it is cheap, and the producer-side parse-failure arm (R4's
concern) is now client-visible regardless. Batch it with the first
post-1.0 API decision rather than churning the error enum before a
consumer exists.
Both R4 and R5 were originally deferred as consumer-surface churn, but
the deferral rationale didn't hold (circular trigger, misapplied
ADR-009 citation — see R5's superseded-disposition note). Both were
resolved pre-publish, while the changes are additive by construction:
R4 via the error frame (no API change), R5 via `InvalidParams` +
`#[non_exhaustive]` on `TtySessionError` (additive while the crate is
unpublished; the enum policy is now written down in its doc).
## Notes