Closes review #003 (prepublish review for v0.1.0).
- P8: StdinSink::poll_shutdown parks an inflight reserve+send on a
full channel (waker registered) — a stdin blast followed by EOF
delivers the EOF instead of stranding it
- P9: five poisoned-lock .expect() sites -> unwrap_or_else(into_inner)
- P10: three thread-spawn .expect() sites -> TtyError::AllocFailed
- P5: tty:open scope gate runs before carriage/cmd/backend-lookup
checks (no backend-name enumeration differential for unscoped ids)
- P11: recv_stdout terminates on the zero-length drained sentinel;
the sentinel is no longer yielded as an item (doc was already the
contract); stderr has no sentinel (doc noted)
- P2: exclude AGENTS.md + docs/plans/, drop dead Cargo.lock and
docs/research/ entries (package list: 42 files, 659.2KiB)
- P3: AGENTS.md phase status (all five landed), ADR range 001..009
(+ alktty-native ADR-009 in the mapping), alkcall guidance
corrected to v0.4.x / pin "0.4.0"; architecture README ADR-009 row
+ landed-phase status
- P15: backend.rs doc typo; redundant tokio-stream dev-dep removed;
NegotiationError::Io arm logs; set_identity failure logs;
input_pump.abort() at session end; TtySessionError::Open carries
the accept_bi StreamError (no io::Error flattening); borrowing
deserialize in open_via_channels (no params.clone());
error_response_bytes guards an "error" key in fields; trivial
inline comments promoted/removed; plan-doc test counts + doc
front-matter refreshed; session tests that raced session teardown
under the abort change use a GatedBackend (exit held until
released)
Verification: cargo test 104 lib / --all-features 147; clippy
(all-targets + wasm32) -D warnings; fmt; wasm check; doc 0 warnings;
publish dry-run OK.
Port the alknet-tty architecture docs into alktty and add the BAST
document for the alk/tty wire format. Docs-only; no Rust source
changes.
Spec docs (docs/architecture/, flat layout — single-crate repo):
- overview.md — crate purpose, two-carriage model, deps, ALPN,
backend location map, feature gates
- tty-wire.md — 5-byte chunk codec, control channel split
(STREAM_CTRL_IN=3 / STREAM_CTRL_OUT=4), sentinels
- tty-backend.md — TtyBackend trait, TtyHandle, TtyControl,
REQ-TTY-01 (backends need not be natively async)
- tty-adapter.md — TtyAdapter, three-pump driver, exit-chunk
ordering (ADR-004), cancel cleanup (ADR-005), access control
- tty-local.md — LocalTtyBackend (local feature module), PTY +
pipe modes, REQ-TTY-02 (signal forwarding to process group)
- README.md — architecture index
ADRs (docs/architecture/decisions/, renumbered 001..008 from
alknet 052,053,054,055,056,057,077,093 in order):
- 001 wire format + two-carriage model (incl. Phase 7 control-
channel split amendment)
- 002 TtyBackend trait + TtyHandle
- 003 local backend placement (records both the alknet sibling-
crate decision and the alktty single-crate consolidation behind
a local feature)
- 004 exit code on a control chunk
- 005 backend cleanup on session cancel
- 006 self-contained negotiation framing
- 007 tty inside channels (reversed by 008; kept for historical
context with reversal notice)
- 008 channels pure channel multiplexing (reverses 007; TTY
always uses its 5-byte format)
BAST document (docs/architecture/tty-bast.md):
- Normative JSON spec for the alk/tty wire format, conforming to
the BAST meta-schema at https://alk.dev/bast/v1/schema
- 5-byte chunk header (struct, big-endian: stream_type uint8,
length uint32) + StreamType enum (Stdin=0..CtrlOut=4)
- ControlMessage union (field-name discriminator on type:
resize/signal/eof/exit) with documented deviation that on-wire
control payloads are UTF-8 JSON, not BAST's binary union
encoding
- NegotiationFrame (4-byte BE length + UTF-8 JSON body) +
NegotiateRequest / TerminalParams JSON shapes
- StreamType enum deviation noted: on-wire uint8, not BAST's
standard u32 enum index (chunk header is 5 bytes, not 8)
- alktty does not depend on alktype; the hand-rolled wire.rs is
the runtime codec, the BAST is the human-readable contract
AGENTS.md: fixed the ADR mapping table to match the plan's 8-to-8
mapping (the previous table substituted ADR-050 for 054, relabeled
056 as control-message split, dropped 077, and added a new
control-split ADR at 006 — inconsistent with both the plan and the
prose). ADR-050 (dynamic resource ownership) is an alkcall/alknet-
core ADR, not tty-specific, and is not ported; the Phase 7 control
split stays as an amendment inside ADR-001, mirroring alknet.
Verification (all pass, no Rust source changed):
- cargo test (80 passed)
- cargo test --all-features (103 passed)
- cargo clippy --all-targets -- -D warnings (clean)
- cargo fmt --check (clean)
- cargo check --target wasm32-unknown-unknown (clean)
- cargo clippy --target wasm32-unknown-unknown -- -D warnings
(clean)
- cargo doc --no-deps: 9 pre-existing intra-doc-link warnings in
src/session.rs and src/channels.rs (untouched by this commit;
not introduced here)
- BAST JSON parses; StreamType indices match wire.rs constants
(0=Stdin..4=CtrlOut)
- all markdown cross-reference links resolve
The default crate (no features) must compile to wasm32-unknown-unknown
so the downstream TS/Python adapter story works — a wasm-compiled
alktty is the protocol layer for a sandboxed adapter. The local feature
is inherently non-wasm (portable-pty + tokio::process need a real OS)
and enabling it on wasm is a build error by design.
Cargo.toml:
- tokio: drop features = ["full"], use the wasm-clean subset alkcall
uses (rt, sync, io-util, macros) with default-features = false
- local feature adds tokio/process + tokio/rt-multi-thread
- document the wasm constraint in the [features] comment
Plan:
- Decision 1: add WASM target subsection recording the constraint
- Phase 0: mark the tokio feature split as done
- Risks: add WASM-target-regression risk with a cargo-check CI mitigation
Reflect what actually landed since the 2026-08-14 draft:
- alkcall 0.1.1: alk/tty + alk/channels ALPN rename (no public API change)
- alkcall Identity.resources now HashMap<String, Vec<String>>
- alkcall OwnershipProvider::owns gained action arg; OwnershipStore::record lost it
- alktype 0.2.0: kind-based BAST format (schema doc artifact, not a runtime dep)
Fold alknet-tty-local into the single crate as the feature-gated local
module (pty.rs + pipe.rs + backend.rs) — the cyclic-dep workaround it
required in the alknet mono-repo doesn't apply to a single crate.
Add Decision 7 (BAST schema as documentation artifact), Phase 0
(scaffold hygiene), Phase 4 BAST-schema step, OQ-6, and a BAST-drift
risk with a cheap test mitigation.
Cargo.toml: bump alkcall 0.1.0 -> 0.1.1; add futures = 0.3 as a direct
dep so the BoxFuture alias doesn't ride on a transitive.