docs(arch): alknet-tty Phase 1 specs — wire format, backend trait, local backend, exit-chunk ordering

Grounded in the alknet-docker POC (seed codec) and the alknet-tty POC
(2026-07-05, validated the control channel, the local-PTY blocking→async
bridge, and the signal-delivery contract).

Four ADRs:
- ADR-052: wire format — alknet/tty ALPN, two-carriage (JSON negotiation
  then raw chunks), fixed channel set 0-3, control as JSON, negotiation-
  error framing disambiguation
- ADR-053: TtyBackend trait + TtyHandle — the backend inversion point;
  exit_code as a Future; REQ-TTY-01 (backends need not be natively async)
- ADR-054: local backend as alknet-tty-local sibling crate behind a
  feature re-export; PTY vs pipe per-session; runner pattern preserved
- ADR-055: exit code on a stream_type 3 control chunk; "exit chunk is
  last" invariant; adapter owns the ordering

Five component specs (crates/tty/): README, overview, tty-wire, tty-
backend, tty-adapter, tty-local. The docker/SSH backends are future
crates (out of scope); the trait shape is committed so they can be
built against it.

Five OQs (OQ-43…47): two resolved (TtyControl Clone, stdin closure),
two deferred(scope) (terminal modes, runner API surface), one open
low-risk (flow control).

Zero critical issues from a general-subagent architecture review;
warnings addressed (inline rationale trimmed to ADR refs, TtyError and
StdinCmd defined, framing disambiguation documented, missing ADRs added
to index tables).
This commit is contained in:
glm-5.2 committed 2026-07-06 15:05:36 +00:00
1 parent 14c8340380
commit e4e6ccfb14
12 files changed
+2892 -1

No files matched your search

+122 -1
View File
@@ -1,6 +1,6 @@
---
status: draft
last_updated: 2026-07-05
last_updated: 2026-07-06
---
# Open Questions
@@ -1187,3 +1187,124 @@ is a feature extension, not an unmade architecture decision.
(`AccessControl`, `OperationSpec` — `resource_id_path` addition),
[alknet-docker POC summary](../../research/alknet-docker/poc-summary.md)
§"Open Unknowns" #3
## Theme: alknet-tty
### OQ-43: `TtyControl` as a `Clone` trait object
- **Origin**: [crates/tty/tty-backend.md](crates/tty/tty-backend.md)
(the `TtyHandle.control` field shape);
`docs/research/alknet-tty/phase-0-findings.md` OQ-TTY-01 (the trait-
shape open question the local-PTY POC resolved).
- **Status**: resolved
- **Door type**: One-way (the `TtyControl` trait shape is part of the
`TtyBackend` API surface — ADR-053), two-way (the concrete `Clone`
newtype mechanism)
- **Priority**: medium
- **Resolution**: `TtyHandle.control` is
`Option<Box<dyn TtyControl + Send + Unpin + Clone>>`. The `Clone`
trait-object bound is satisfied via an `Arc`-backed `Clone` newtype: a
small struct holding `Arc<dyn TtyControlInner>` where `TtyControlInner:
Send + Sync` has the `resize`/`signal` methods, and the public
`TtyControl` newtype implements `Clone` by cloning the `Arc`. The
local-PTY POC used a concrete `PtyControl` struct (inherently `Clone` —
it held `Arc<Mutex<...>>` fields); the trait-object form generalizes it
so a backend can produce its own control type without the adapter
knowing the concrete shape. The `Clone` constraint exists because the
adapter's control-chunk dispatcher needs to be handed off to the
spawned pump task (the POC's `session::drive_session` clones
`pty.control` for the client→backend pump). See ADR-053 and
`tty-backend.md`.
- **Cross-references**: ADR-053, [tty-backend.md](crates/tty/tty-backend.md),
`/workspace/alknet-tty-poc/src/local_pty.rs` (`PtyControl`)
### OQ-44: Terminal Modes (TTY modes)
- **Origin**: `docs/research/alknet-tty/phase-0-findings.md` OQ-TTY-02;
[crates/tty/tty-backend.md](crates/tty/tty-backend.md)
(`TerminalParams.modes` field).
- **Status**: deferred(scope)
- **Door type**: Two-way
- **Priority**: low
- **Blocked on**: a concrete mode-control use case (a deployment that
needs to set echo/raw/canonical/etc. modes on a PTY, beyond the backend's
defaults).
- **Resolution**: Not yet decidable. SSH's `pty_request` carries TTY
modes (echo, raw, canonical, etc.) as a packed bitmask. The common
case is "default terminal modes" — the `modes` field in
`TerminalParams` is `serde_json::Value` (reserved as `{}` in v1) for
when a concrete use case requires mode control. The backends
(`portable_pty`, docker `tty: true`, russh `pty_request`) all have
defaults that work for the common terminal case. Adding mode control
is additive (extend the `modes` JSON shape) and does not break
downstream; the decision is deferred until a use case forces it.
- **Cross-references**: ADR-053, [tty-backend.md](crates/tty/tty-backend.md),
[tty-wire.md](crates/tty/tty-wire.md)
### OQ-45: Flow Control for High-Throughput stdout
- **Origin**: `docs/research/alknet-tty/phase-0-findings.md` OQ-TTY-03;
[crates/tty/tty-wire.md](crates/tty/tty-wire.md) (no windowing in the
chunk format).
- **Status**: open (low risk)
- **Door type**: Two-way
- **Priority**: low
- **Resolution**: The chunk format has no windowing — QUIC's per-stream
flow control handles backpressure. This is expected to suffice for
high-throughput stdout (e.g., `cargo build` output): the docker POC's
logs subscription handled multi-line output without issue, and QUIC's
bidi-stream flow control is the established backpressure mechanism. A
POC with real high-volume output (a deliberately large `cargo build`
or `find /` over a high-bandwidth connection) would confirm. If a
flow-control problem surfaces, the reversal is a per-stream
flow-control window in the chunk format (a two-way-door extension to
the wire format, additive — a new control message type for window
updates, not a header change). Not blocking; the default assumption is
QUIC flow control suffices.
- **Cross-references**: ADR-052, [tty-wire.md](crates/tty/tty-wire.md),
[tty-adapter.md](crates/tty/tty-adapter.md)
### OQ-46: Runner API Surface
- **Origin**: `docs/research/alknet-tty/phase-0-findings.md` OQ-TTY-05;
[crates/tty/tty-local.md](crates/tty/tty-local.md) (the runner pattern
in pipe mode).
- **Status**: deferred(scope)
- **Door type**: Two-way
- **Priority**: low
- **Blocked on**: a concrete runner-policy use case that forces the API
surface (job management, log persistence, task graph integration).
- **Resolution**: Not yet decidable. The runner *mechanism* (pipe mode —
`TtyParams.terminal = None` → `std::process::Command` with piped stdio
→ framed byte stream + exit code) is in alknet-tty (ADR-054). The
runner *policy* (job management, log persistence, task graph
integration) is a downstream crate's job, not in scope for alknet-tty.
This OQ tracks whether a runner-policy crate (e.g., an
`alknet-runner` crate that builds on the pipe mode + the wire format
to provide job management) is needed, and what its API surface would
be. The decision is deferred until a concrete use case forces it; the
mechanism is preserved regardless. See ADR-054 and `tty-local.md`
§"The Runner Pattern".
- **Cross-references**: ADR-054, [tty-local.md](crates/tty/tty-local.md)
### OQ-47: Stdin Closure Canonical Signal
- **Origin**: `docs/research/alknet-docker/poc-summary.md` §"Open
Unknowns" #4 (stdin closure semantics for raw carriage);
[crates/tty/tty-wire.md](crates/tty/tty-wire.md) §"Stdin Closure".
- **Status**: resolved
- **Door type**: Two-way
- **Priority**: low
- **Resolution**: Either a zero-length stdin chunk (stream_type 0,
length 0 — the docker POC's sentinel) or a `{"type":"eof"}`
control chunk (stream_type 3 — the tty POC's explicit signal) closes
the client's stdin. Both are accepted by the adapter; the spec
recommends `eof` for explicitness (it's a control message, not a
data-length hack). The adapter handles both identically: signal EOF
to the backend's stdin (`ChildStdin::drop` / PTY writer close) and
keep pumping stdout until the exit resolves — the client may still
want to receive remaining output + the exit code. A third path
(client closes the write half of the bidi stream) is also accepted
and handled the same way. See ADR-052 and `tty-wire.md`.
- **Cross-references**: ADR-052, [tty-wire.md](crates/tty/tty-wire.md),
[tty-adapter.md](crates/tty/tty-adapter.md)