docs(arch): alknet-tty spec sanity-check fixes — TtyControlHandle newtype (C1), backend cleanup contract (W1, ADR-056), clarifications (S1-S5)

C1: Box<dyn TtyControl + Clone> does not compile (Clone is not object-safe).
Replace with a TtyControlHandle newtype: #[derive(Clone)] wrapping
Arc<dyn TtyControl + Send + Sync>. The trait stays object-safe; the
newtype carries the Clone-ability. Updated across tty-backend.md,
ADR-053, OQ-43, and the tty README.

W1: 'Drop is the cleanup, threads exit on channel close' was false for
the local PTY waiter thread (blocked in Child::wait(), does not observe
channel close) — a child that ignores stdin EOF would be orphaned on
session cancel. New ADR-056 commits a backend cleanup contract: dropping
the exit_code future MUST kill the session target. The local backend
implements it via a ChildKiller held in the exit_code future's Drop
guard (disarmed on resolve). Corrected the lifecycle section in
tty-adapter.md, added the mechanism + constraint to tty-local.md, and
referenced ADR-056 from tty-backend.md, overview.md, both READMEs.

S1: error-frame < 16 MiB stated as a wire-format invariant (not an
assumption) so the 0x00-first-byte disambiguation trick is sound by
construction.
S2: carriage field validation (MUST be "raw" in v1, else
malformed_negotiation) specified.
S3: NegotiateRequest + TerminalParamsWire Rust structs added with
serde defaults and validation rules.
S4: modes field annotated 'backends MUST ignore content in v1' in both
tty-backend.md and ADR-053.
S5: AsyncWrite/Stream qualified with crate origins (tokio::io,
futures_core) in the TtyHandle struct definitions.
This commit is contained in:
glm-5.2 committed 2026-07-07 10:38:49 +00:00
1 parent b90d717ac2
commit 25f0f55592
11 files changed
+594 -92

No files matched your search

@@ -6,18 +6,22 @@
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)
`TtyBackend` API surface — ADR-053), two-way (the concrete
`TtyControlHandle` 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
- **Resolution**: `TtyHandle.control` is `Option<TtyControlHandle>`.
`TtyControlHandle` is a concrete `#[derive(Clone)]` newtype wrapping
`Arc<dyn TtyControl + Send + Sync>`; the adapter clones the `Arc` to
hand a handle to the spawned control-chunk dispatcher. The
`TtyControl` trait itself is NOT `Clone` — `Clone` is not object-safe
(`fn clone(&self) -> Self` returns `Self`, which forbids `dyn`
dispatch), so `Box<dyn TtyControl + Clone>` does not compile. The
design splits the concerns: the trait stays object-safe (`Send +
Sync`, no `Clone`); the newtype carries the `Clone`-ability. The
local-PTY POC used a concrete `PtyControl` struct (inherently `Clone`
— it held `Arc<Mutex<...>>` fields); the newtype generalizes the POC's
shape so a backend produces its own control type via
`TtyControlHandle::new(Arc::new(MyControl))` 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