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:
1 parent
14c8340380
commit
e4e6ccfb14
12 files changed
+2892
-1
No files matched your search
@@ -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)
|
||||
Reference in new issue
Block a user