- the crate-root assembly example was a ```ignore block, which rustdoc
renders with the untested badge docs.rs surfaces on the crate page
(the tooltip 'This example is not tested'); ```no_run blocks are
compile-checked by doctests and get no badge
- the example body is gated with hidden cfg lines (#[cfg(feature =
"local")] around a hidden block): empty under default features,
compile-checked under --all-features; the doctest now passes in both
cargo test configurations instead of being skipped
- AGENTS.md convention 16 records the rule: doc examples are tested or
no_run, never ignore; feature-gated bodies via hidden cfg lines
Verification: cargo test (doctest src/lib.rs line 39 now compile-... ok
in default and --all-features); cargo doc --no-deps (no untested badge
in the rendered HTML); clippy --all-targets --all-features -D warnings;
fmt --check; wasm check; fuzz/shared corpus replay (9).
A subagent mid-dedash sweep read the 'commit and push when reasonable'
rule and pushed its own subset before the coordinator verified the
aggregate. State the assembly rule explicitly so spawned tasks report
instead.
- Rewrote ~1090 em dashes across docs/ (excluding docs/reviews/
history), README, CHANGELOG, and .opencode/agents prompts with varied
restructures (colons, semicolons, parentheses, commas, sentence
splits); ADR decisions, status lines, wire-shape examples, and code
fences preserved
- Rewrote 234 em dashes in src doc comments plus a handful of existing
inline comments and one test assert message; no code, JSON literals,
or wire shapes touched
- Added Project Convention 15: no em dashes in prose (doc comments,
markdown, agent prompts, commit messages); restructure instead; no
spaced hyphens as a substitute
Verification: cargo test; cargo clippy --all-targets -- -D warnings;
cargo fmt --check; cargo doc --no-deps; cargo test --all-features;
cargo test --manifest-path fuzz/shared/Cargo.toml; cargo check and
clippy for wasm32-unknown-unknown with -D warnings; em dash grep over
src + docs + agent prompts returns no matches outside docs/reviews/
- alkcall requirement 0.10.0 -> 0.10.1 (same-family version-only bump:
the adoption-window ordering fix, private fix surface, no type alktty
touches); lockfiles updated in both the root and fuzz workspaces
- AGENTS.md pin refreshed from the stale 0.9.0 note
Verification: cargo test 113 default / 137 all-features (unchanged from
the 0.8.0 baseline); fuzz corpus replay 9 green; clippy stable + wasm
(-D warnings); fmt; doc; wasm32-unknown-unknown check; publish dry run
(43 files)
The stub-sweep release changes no type alktty touches: the four
implemented stubs, the register_openable Pub-typed refusal (alktty's
op is Sub-typed), the register_imported Result return, and the removed
RelayError::PubTypedOpen are all surfaces alktty never exercises.
- alkcall requirement 0.8.1 -> 0.9.0; lockfile follows
- alktty 0.6.0 -> 0.7.0 (minor: raising the minimum alkcall requirement
is ecosystem-coordination breaking on a 0.x crate)
- one test fix: `establisher_ok_on_valid_params` formatted a
Result<Establishment, _> with {:?} — Establishment no longer derives
Debug in alkcall 0.9.0; the assert now branches on Err and formats
only the error
Verification: 113 default / 156 all-features tests, clippy
--all-targets -D warnings, fmt --check, cargo doc, fuzz corpus replay
(9 tests / 340 seeds), wasm32-unknown-unknown check + clippy,
publish --dry-run — all pass.
- Bump alkcall 0.8.0 -> 0.8.1 (duplicate adopt/open channel-state
destruction fix from alkcall's fuzz campaign; AGENTS.md stale-pin fix).
- Adopt fuzzing per docs/plans/fuzzing.md (mirrors alkcall's
docs/research/fuzzing.md as-built layout): fuzz/ workspace with own
[workspace] table, nightly pinned for the subtree only, invariant
logic in a stable-toolchain shared crate.
- Targets: chunk_frame (5-byte chunk codec), negotiation_frame
(negotiation framing + NegotiateRequest + error_response_bytes +
the cross-codec peek-disambiguation seam), control_json
(ControlMessage JSON + signal_from_name). src/local/ out of scope
(not wire-attacker-shaped).
- 328 committed seeds (deterministic generator); grown corpora
gitignored. Corpus replay on stable is the standing fuzz gate
(cargo test --manifest-path fuzz/shared/Cargo.toml).
- Detached-runner rule (fuzz/run-detached.sh): campaigns never run in
the foreground of an agent session - OOM in a target must cost the
fuzzer, never the session host.
- Root Cargo.toml gained [workspace] members/exclude (MSRV vs fuzz
nightly dev-deps footgun) and fuzz/ in the publish exclude.
Verification: cargo test (113), cargo test --all-features (156),
clippy stable + wasm32-unknown-unknown, fmt, doc, publish dry-run,
corpus replay 328 seeds - all green. 10-min detached campaigns on all
three targets: 0 crashes/hangs/OOMs/leaks (chunk_frame cov-saturated
at 710 edges; negotiation_frame 3.69M execs cov 3157; control_json
8.6M execs cov 2035). Corpus replay caught three harness-model
mismatches pre-campaign (payload-slice shape; the negotiation framing
layer is length-prefix-only - Json unreachable from read_frame;
zero-length frames admitted by the reader, rejected by the adapter's
parse). Campaigns recorded in docs/plans/fuzzing.md section 8.
- Bump alkcall 0.5.0 -> 0.6.0 (Establishment plan payload, ADR-049
amendment 2); alktty 0.2.0 -> 0.3.0 (breaking channels-path
allocation-failure shape)
- make_tty_establisher runs backend.allocate; failure maps to
EstablishmentError::DialFailed (channel:open_failed reason
dial_failed); the TtyHandle crosses to the pump handler via a
private per-open AllocatedHandle one-shot slot (the handle is not
Sync — it cannot be the ChannelPlan directly)
- make_tty_open_handler gains the Option<ChannelPlan> parameter; a
plan drives the new drive_session_pre_allocated (pumps only), a
None plan falls back to drive_session_pre_negotiated (inline
validate-and-allocate — defense-in-depth for no-establisher
registrations)
- tty_open_spec's channel:open_failed ErrorDefinition declares
dial_failed (four reachable reasons)
- Pinned in-band allocate test flipped:
allocate_failure_fails_open_as_dial_failed (end-to-end, no channel
survives); establisher unit gate for the plan slot + failure
mapping
- ADR-010 amended (§2A; §2 kept as historical record), tty-adapter.md,
session/channels/adapter docs, CHANGELOG, AGENTS.md alkcall pin
updated
Verification: cargo test (113) + --all-features (137), clippy
-D warnings (native + wasm32, default + all-features), fmt, doc,
wasm32 check — all clean
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.
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.
- M1: TtySession now handles the negotiation-rejection error frame.
from_halves peeks the first response byte (ADR-052 §5 disambiguation)
and returns NegotiationRejected on a 0x00-prefixed error frame;
ChunkReader gains peek_stream_type/read_chunk_after_peek.
- M2: wait() now surfaces MalformedExitChunk instead of collapsing it
to NoExitChunk. The exit watch channel carries a cloneable
ExitOutcome enum; MalformedExitChunk carries a String.
- L2: add EmittingBackend + recv_stdout_and_stderr_route_backend_data
test covering the consumer read-pump stdout/stderr routing.
- L4: MockBackend/MockControl/MockStdinSink are now #[cfg(test)]
pub(crate), removing them from the public API.
- L5: cargo fmt (the BAST drift test was unformatted).
- N1: fix all 9 rustdoc intra-doc links.
- N2: fix stale doc paths (crates/tty/ and docs/research/).
- N3: amend AGENTS.md §14 to accurately describe the local module's
libc::kill unsafe blocks.
- N5: consolidate nanos_seed into tests/common/mod.rs.
Verification: cargo test (84), cargo test --all-features (107),
clippy clean (native + wasm), fmt clean, doc clean, wasm check clean.
Coverage: session.rs 79.71% -> 87.43%, total 90.74% -> 91.47%.
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