feat: channels-path establisher migration (alkcall 0.5.0 / ADR-049) — bump to 0.2.0
Adopt alkcall 0.5.0's channel-open establishment phase (ADR-049 — review 006 E-01 + N-1) and migrate the channels-path semantic failures per its §5 sequencing (alktty ADR-010). - `register_openable` registers `channels/tty/sub` with an establisher (`register_openable_with_establisher`): full `NegotiateRequest` parse of schema-valid `input`, `carriage == "raw"`, non-empty `cmd`, backend lookup, and the ADR-050 ownership check run before the open reply; rejections are `channel:open_failed` with `details.reason` (`unknown_resource` / `handler_error` / `timeout`) — no phantom channel (the SSH contract holds consumer-visibly) - `backend.allocate` deliberately stays in the pump handler: `Establishment` is payloadless so the `TtyHandle` cannot cross the establisher→handler boundary, and re-allocating would violate ADR-005's kill-on-Drop contract — `allocate_failed` remains the one in-band failure class on the channels path (pinned by test) - `TtySessionError::ChannelsOpen` carries alkcall's typed `ChannelOpenError` (`#[from]`) instead of a flattened `String` — the N-1 fix at alktty's layer (breaking) - channels-path semantic failures change shape from `NegotiationRejected` in-band frames to `channel:open_failed` call errors (breaking); the direct-ALPN path is unchanged - `tty_open_spec()` gains a `description` (review 006 E-02) and an ErrorDefinition for `channel:open_failed` (ADR-016 — disclosed via services/schema) - alkcall = "0.5.0"; version 0.2.0; ADR-010 + ADR-009 amendment + tty-adapter.md + CHANGELOG Verification: cargo test (112 lib + integration), cargo test --all-features (136), clippy --all-targets -D warnings (host + wasm), fmt --check, cargo doc --no-deps clean; wasm32-unknown-unknown check confirms the default crate stays wasm-clean.
This commit is contained in:
@@ -65,11 +65,13 @@ channels path reuses — per [ADR-008](decisions/008-channels-pure-channel-multi
|
||||
TTY always uses its 5-byte format, so the same driver runs in
|
||||
both direct `alk/tty` and channels `alk/channels` modes; only the
|
||||
`BiStream` source differs. The two paths differ in where the
|
||||
`NegotiateRequest` comes from: the direct path reads the wire-frame
|
||||
negotiation (ADR-001 §"Negotiation Frame"); the channels path parses
|
||||
the open op's registry-validated `input` and runs
|
||||
`drive_session_pre_negotiated` (ADR-009 — the channels path carries no
|
||||
second negotiation frame on the channel's data stream).
|
||||
`NegotiateRequest` comes from and where semantic failures go: the
|
||||
direct path reads the wire-frame negotiation (ADR-001 §"Negotiation
|
||||
Frame") and answers failures with in-band error frames; the channels
|
||||
path parses the open op's registry-validated `input` and runs
|
||||
`drive_session_pre_negotiated` (ADR-009 — no second negotiation
|
||||
frame), with the establisher rejecting the semantic-failure classes
|
||||
as `channel:open_failed` before the handler spawns (ADR-010).
|
||||
|
||||
## Why
|
||||
|
||||
@@ -172,11 +174,20 @@ entering raw mode. The error response shape:
|
||||
{ "error": "unknown_backend", "backend": "kubernetes" }
|
||||
```
|
||||
|
||||
On the **direct-ALPN path** this is the failure surface for every
|
||||
negotiation/allocation failure class (below). On the **channels path**
|
||||
the semantic classes (unknown backend, malformed negotiation,
|
||||
ownership denial) are rejected by the establisher before the open
|
||||
reply and surface as `channel:open_failed` call errors (alkcall 0.5.0
|
||||
ADR-049 / alktty ADR-010) — the in-band frames on that path are
|
||||
defense-in-depth arms plus the one non-migratable class
|
||||
(`allocate_failed`, ADR-010 §2).
|
||||
|
||||
| Error | When | Shape |
|
||||
|-------|------|------|
|
||||
| `unknown_backend` | the `backend` string is not in the adapter's backend map | `{"error":"unknown_backend","backend":"..."}` |
|
||||
| `malformed_negotiation` | the negotiation frame failed to parse as JSON or failed `NegotiateRequest` validation — on the direct path the wire frame, on the channels path the open op's `input` (schema-valid values can still fail the typed parse, e.g. `cwd` typed as a number, because the schema is deliberately partial) | `{"error":"malformed_negotiation","message":"..."}` |
|
||||
| `allocate_failed` | `backend.allocate()` returned a `TtyError` | `{"error":"allocate_failed","message":"..."}` |
|
||||
| `unknown_backend` | the `backend` string is not in the adapter's backend map — direct path, or the channels path's defense-in-depth arm (the establisher rejects it as `channel:open_failed` / `unknown_resource` first) | `{"error":"unknown_backend","backend":"..."}` |
|
||||
| `malformed_negotiation` | the negotiation frame failed to parse as JSON or failed `NegotiateRequest` validation — on the direct path the wire frame; on the channels path the open op's `input` (schema-valid values can still fail the typed parse, e.g. `cwd` typed as a number, because the schema is deliberately partial — the establisher rejects it as `channel:open_failed` / `handler_error` first; the handler-side frame is defense-in-depth, ADR-010 §2) | `{"error":"malformed_negotiation","message":"..."}` |
|
||||
| `allocate_failed` | `backend.allocate()` returned a `TtyError` — on both paths: the establisher cannot carry the allocated handle across to the pump handler (ADR-010 §2), so allocation failure is the one in-band failure class on the channels path | `{"error":"allocate_failed","message":"..."}` |
|
||||
|
||||
After sending the error response, the adapter closes the write half of
|
||||
the bidi stream. The client reads the error frame and treats stream close
|
||||
@@ -358,6 +369,7 @@ architectural commitment.
|
||||
| Backend cleanup on session cancel | [ADR-005](decisions/005-backend-cleanup-on-session-cancel.md) | Dropping `exit_code` future kills the session target; the adapter triggers it by dropping the `TtyHandle` on cancel |
|
||||
| Channels pure channel multiplexing | [ADR-008](decisions/008-channels-pure-channel-multiplexing.md) | The same session driver runs in both direct and channels modes; only the `BiStream` source differs |
|
||||
| Negotiation carried in the open op | [ADR-009](decisions/009-channels-open-op-is-the-negotiation.md) | The channels path carries no second negotiation frame; the open op's registry-validated `input` is the negotiation (`drive_session_pre_negotiated`) |
|
||||
| Channels establishment failures are call errors | [ADR-010](decisions/010-channels-establisher-migration.md) | The channels path's semantic failures are rejected by the establisher (`channel:open_failed`, alkcall ADR-049); `allocate_failed` stays in-band |
|
||||
| Dynamic resource ownership | alknet ADR-050 | Terminal sessions as runtime-spawned resources; the adapter's access-control shape |
|
||||
|
||||
## Open Questions
|
||||
@@ -379,6 +391,11 @@ architectural commitment.
|
||||
the same session driver runs in channels mode
|
||||
- [ADR-009](decisions/009-channels-open-op-is-the-negotiation.md) — the
|
||||
channels path carries no second negotiation frame (the open op's
|
||||
registry-validated `input` is the negotiation)
|
||||
- [ADR-010](decisions/010-channels-establisher-migration.md) — the
|
||||
channels path's semantic failures are establisher call errors
|
||||
(`channel:open_failed`); the in-band frames shrink to
|
||||
`allocate_failed`
|
||||
`input` is the negotiation)
|
||||
- alknet ADR-050 — the ownership model the adapter's access control
|
||||
declares against
|
||||
|
||||
Reference in New Issue
Block a user