40 Commits
Author SHA1 Message Date
glm-5.3-flash e95459c425 feat: alkcall 0.8.0 adoption — bump to 0.5.0
- BREAKING (ecosystem-coordination): alkcall bumped to 0.8.0. No type
  alktty touches changed shape (ChannelCore/openable machinery,
  OpenEstablisher/OpenHandler/Establishment/ChannelPlan,
  ChannelClient::open_channel, ChannelOpenError::CallFailed are
  identical to 0.7.1); the new reply-projection / relay / hub-leg
  surfaces are additive and alktty exercises none of them. alktty's own
  open op is the standard-shape name channels/tty/sub, so the new
  flavor-form discovery path stays on the byte-stable standard
  derivation, and with no establisher reply-fields configured the open
  reply is byte-identical to pre-0.8.0. The minor bump is for
  downstream lockstep, since alktty's public signatures reference
  alkcall types.
- Verification counts unchanged from the 0.7.1 baseline (113 default /
  156 all-features); the adoption required no source change in alktty.

Verification:
- cargo test: 113 passed (default), 156 passed (--all-features)
- cargo clippy --all-targets -- -D warnings: clean
- cargo fmt --check: clean
- cargo doc --no-deps: clean
- cargo check/clippy --target wasm32-unknown-unknown -D warnings: clean
- cargo publish --dry-run --allow-dirty: clean
v0.5.0
2026-09-18 13:55:53 +00:00
glm-5.3-flash d14941ffb1 chore: bump to 0.4.1, changelog for the MSRV floor raise (1.85 -> 1.88)
Verification: 113 default / 156 all-features tests on stable (1.94) and
on a real 1.88 toolchain, clippy (all-targets) clean, fmt clean, doc
clean, wasm32 check + clippy clean, publish dry-run OK.
v0.4.1
2026-09-10 02:55:11 +00:00
glm-5.3-flash 01e94914c1 chore: raise rust-version floor to 1.88
The 1.85 claim is now false at the dependency level: the lockfile pins
alkcall 0.7.x, and alkcall 0.7.1 raised its MSRV floor to 1.88, so no
1.85 toolchain can resolve this dependency graph regardless of what
Cargo.toml declares. Raising the floor keeps the claim honest and
aligns with the ecosystem floor (noq's 1.88, matching the QUIC path;
iroh sits above at 1.91). alktty's own code is 1.85-clean (verified:
cargo check under 1.85 passes with the pre-bump lockfile) — the raise
is dependency-driven only, so no clippy fixes are needed.

- Cargo.toml: rust-version 1.85 -> 1.88 (version-line bump deferred to
  release time)

Verification:
- 1.88: cargo test --locked (113 default / 156 all-features pass),
  clippy --all-targets -D warnings
- stable 1.94: test --locked, clippy --all-targets -D warnings,
  fmt, doc --no-deps, wasm32-unknown-unknown check + clippy clean
2026-09-10 02:53:33 +00:00
glm-5.3-flash 32809af840 docs: fill changelog version link refs (0.4.0/0.3.0/0.2.0) v0.4.0 2026-09-07 18:07:13 +00:00
glm-5.3-flash 67cf253621 feat: alkcall 0.7.0 adoption (CF-006 identity alignment) — bump to 0.4.0
- BREAKING (ecosystem-coordination): alkcall bumped to 0.7.0. No type
  alktty touches changed shape (OpenEstablisher/OpenHandler/
  Establishment/ChannelPlan identical to 0.6.0); the minor bump is for
  downstream lockstep, since alktty's public signatures reference
  alkcall types.
- CF-006 alignment: the channels establisher and pump handler now take
  their identity from the per-call auth (alkcall 0.7 derives the
  dispatch-resolved opener — the same identity view the registry's ACL
  gate checked) instead of the captured install-time override.
  Behavior-identical in the per-connection-registry deployment; the
  ownership check and the ACL gate see the same subject in every
  deployment. make_tty_establisher / make_tty_open_handler drop their
  captured-identity parameter (both private).

Verification:
- cargo test: 113 passed (default), 156 passed (--all-features)
- cargo clippy --all-targets -- -D warnings: clean
- cargo fmt --check: clean
- cargo doc --no-deps: clean
- cargo check/clippy --target wasm32-unknown-unknown -D warnings: clean
- cargo publish --dry-run --allow-dirty: clean
2026-09-07 17:57:36 +00:00
glm-5.3-flash bc4a9b6024 docs: align remaining alloc-failure references with ADR-010 §2A (prepublish review for v0.3.0)
- README.md ADR index: ADR-010 status notes the §2A amendment
- ADR-009: amendment note records the §2A supersession (in-band path
  shrinks to nothing from registered producers)
- ADR-010 Consequences: dial_failed replaces the stale in-band
  allocate_failed retry-policy bullet
- AllocFailed doc comment + tty-backend.md: describe both failure
  surfaces (direct-path frame / channels-path dial_failed)
v0.3.0
2026-09-07 10:04:44 +00:00
glm-5.3-flash af9dbaedea feat: allocation moves into the channels establisher (alkcall 0.6.0 / review 007 R-01) — bump to 0.3.0
- 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
2026-09-07 09:53:57 +00:00
glm-5.3-flash 3352a02dd5 docs: fix dangling fragment in tty-adapter.md ADR list (ADR-010 bullet edit leftover) v0.2.0 2026-09-06 20:46:12 +00:00
glm-5.3-flash e2fa32b3c7 feat: channels-path establisher migration (alkcall 0.5.0 / ADR-049) — bump to 0.2.0
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.
2026-09-06 20:25:39 +00:00
glm-5.3-flash 66c6e693bb docs: record v0.1.0 publish in review #003 (review closed) 2026-09-05 17:02:12 +00:00
glm-5.3-flash 3baa993edb fix: local-bridge robustness, scope-gate order, sentinel contract; packaging/docs closeout (P8-P11, P2/P3/P15)
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.
v0.1.0
2026-09-05 16:47:45 +00:00
glm-5.3-flash 7b0bac0671 docs: mark review #003 P12 + P6 + P7 + P4 + P16 resolved (c8e4610) 2026-09-05 16:36:22 +00:00
glm-5.3-flash c8e4610cb0 feat: crate-root re-exports, wire write validation, peek guard; README + CHANGELOG
Review #003 Session 2 (P12 + P6 + P7 + P4 + P16):

- P12: primary types re-exported at the crate root (TtyAdapter,
  TtySession, TtyBackend/TtyHandle/TtyParams, Chunk codec, wire
  constants, ControlMessage, negotiation types, channels helpers;
  LocalTtyBackend under `local`). signal_from_name re-export is
  #[cfg(unix)]-gated to match its definition (wasm check caught it).
- P6: ChunkWriter validates before writing — stream_type > 4 or
  payload > MAX_CHUNK_LEN fails locally (RawError) instead of
  corrupting the peer's framing; length validated as u64 before the
  u32 cast so oversized payloads cannot wrap past the check.
  Empty-payload shape unified via a single write_validated helper.
- P7: ChunkReader tracks peeked state — read_chunk() after
  peek_stream_type() completes the peeked chunk instead of consuming
  a second header byte; second peek is idempotent;
  read_chunk_after_peek debug-asserts a peek. Session read pump drops
  its manual first_byte_peeked threading.
- P4: README.md (alkcall structure; quick-start examples per role
  using the new root paths) + readme = "README.md" in [package].
- P16: CHANGELOG.md with the full [0.1.0] entry and tag link.

Verification: cargo test 104 lib (default) / 147 (--all-features);
clippy all-targets + wasm32 -D warnings; fmt; wasm check; doc 0
warnings; publish dry-run 44 files OK.
2026-09-05 16:36:17 +00:00
glm-5.3-flash a720241021 docs: mark review #003 P1 + P13 resolved (87c52e5)
Session-1 resolution section records the concurrent-drainer fix shape,
the bug-validated regression test (fails with the old order
reinstated), the two P13 boundary/discard tests, and the deferred
accept-loop harness item. Verification counts updated (98 lib / 141
--all-features).
2026-09-05 10:05:49 +00:00
glm-5.3-flash 87c52e5e4d fix: pump_session deadlock when backend emits >64 chunks before exit (P1)
The stdout drainer started only after the pumps+exit join completed,
but the pumps share a bounded (64-slot) channel with it. Once the
channel filled, pump_stdout parked on send, the join never completed,
and the session hung with the client actively reading (review #003 P1;
reproduced empirically at N=62 ok / N=63 hang). Any real session (cat,
ls -R) exceeds 64 chunks instantly.

pump_session now spawns drain_chunks (owning writer_rx + client_write)
before the join; the exit chunk is still enqueued after the join and
writer_tx is dropped after it, so the single FIFO preserves the
exit-chunk-is-last invariant (ADR-005).

Tests (review #003 P13):
- backpressure regression: 80 chunks through a real drive_session,
  in-order delivery + sentinel + exit asserted, 10s per-read timeout
  so a regression fails instead of hanging (validated against the bug:
  fails with the old order reinstated)
- wire: MAX_CHUNK_LEN exact-boundary round trip (limit is inclusive)
- session: stream_type 0/3 from the server ignored mid-stream without
  desynchronizing framing or losing stdout/exit

Verification: cargo test 98 lib (default) / 141 --all-features,
clippy all-targets + wasm32 -D warnings, fmt --check, wasm check, doc
--no-deps all clean.
2026-09-05 10:05:38 +00:00
glm-5.3-flash da94d5e23f docs: add review #003 (prepublish review for v0.1.0)
Two subagent reviews (code + packaging/docs vs alkcall 0.4.1) with every
finding manually re-verified against source at 918af40. 13 open findings:
P1 pump_session deadlock at >=63 stdout chunks (empirically reproduced,
N=62 ok / N=63 hangs), P2 AGENTS.md ships in the crate, P3 stale
AGENTS.md/architecture-README text, P4 no README, P5-P13 API-shape and
robustness items, P15-P16 polish/changelog. P14 closed as alkcall parity.

Includes suggested session breakdown for distributing the remediation.

Verification baseline on the reviewed tree: cargo test 95 lib (default) /
138 --all-features, clippy (all-targets + wasm32) clean, fmt clean, doc
clean, publish --dry-run OK, CJK scan clean.
2026-09-05 09:30:32 +00:00
glm-5.3-flash 918af406dd feat: InvalidParams variant + #[non_exhaustive] TtySessionError (R5)
Review #002 R5 — the open_via_channels fail-fast parse (a params value
that fails the local NegotiateRequest parse before a channel is
allocated) surfaced as NegotiationSerialize, whose name and doc
describe serializing the negotiation frame, not parsing open-op params.

- add TtySessionError::InvalidParams(String); the fail-fast path maps
  to it (the serde_json::Error's From impl stays for
  NegotiationSerialize's real users — the direct-path serialize)
- mark TtySessionError #[non_exhaustive] — the same two-way-door
  pattern as TtyError (backend.rs) and alkcall's consumer-facing
  AdapterError; the policy rationale is in the enum's doc
- NegotiationError / RawError stay exhaustive (deliberate — they
  mirror fixed wire semantics; in-crate matchers keep exhaustiveness
  checking)
- the two fail-fast tests assert InvalidParams(_) now
- review #002: R5 resolved; the superseded deferral rationale is
  recorded (circular trigger — "first channels-path consumer exists"
  fires after the change becomes expensive; misapplied citation —
  ADR-009's version-skew note governs wire skew, not error enums;
  the "unreachable" framing belonged to R4's arm, not R5's — the
  fail-fast path is live today). The #[non_exhaustive] policy is
  decided on principle, pre-publish, while the variant addition is
  additive by construction

Verification: cargo test 95 lib (default) / 138 (--all-features);
clippy -D warnings native + wasm clean; fmt clean; doc 0 warnings.
2026-09-05 08:45:58 +00:00
glm-5.3-flash 8ee9216a07 fix: channels parse-failure path writes the negotiation error frame (R4)
Review #002 R4 — a NegotiateRequest parse failure of the open op's
schema-validated input died silently (log + return, channel teardown,
consumer observed NoExitChunk — indistinguishable from a crashed
producer), while the other post-open failure classes (unknown backend,
allocate_failed, ownership denial) wrote the 0x00-prefixed error frame.

- make_tty_open_handler now accepts the channel's BiStream and writes
  a malformed_negotiation frame via the shared
  crate::adapter::send_negotiation_error (now pub(crate)) before
  returning; the consumer's M1 peek surfaces NegotiationRejected
  unchanged
- the frame type and layout are unchanged (ADR-001 wire-stable
  contract); no new frame type, no wire change
- tests: make_tty_open_handler seam test with a hand-built
  schema-bypassing input (cwd: 42) + a real-registry end-to-end test
  via ChannelClient::open_channel (bypasses open_via_channels's local
  fail-fast parse — R5's path — so it exercises the producer handler)
- docs: ADR-009 amended (Parse-failure error frame section);
  tty-adapter.md malformed_negotiation row covers both paths;
  session.rs post-open failure lists updated; review #002 R4 resolved

Note: the review's "unreachable end-to-end" premise was refined —
open_via_channels parses params locally (fail-fast) so a TtySession
consumer never hits the producer-side parse failure, but direct
ChannelClient callers do; the schema is deliberately partial so a
schema-valid value (cwd typed as a number) reaches the handler.

Verification: cargo test 95 lib (default) / 138 (--all-features);
clippy -D warnings native + wasm clean; fmt clean; doc 0 warnings.
2026-09-05 08:37:02 +00:00
glm-5.3-flash 5b608117ff docs: add review #002 (follow-up on the 2026-09-05 resolution session)
Documents the post-hoc review of the five resolution commits:
- R1 (ADR-009 missing) and R2 (stale L1-redesign docs) — resolved in
  37ae07a
- R3 — install-time identity snapshot on the channels path, closed as
  intended (hub-proxy design predating the alkcall split; constraint
  recorded: registries must stay per-connection)
- R4 (silent death on the parse-failure path, no error frame) and R5
  (NegotiationSerialize mislabel on the fail-fast path) — open,
  deferred to the first post-1.0 error-surface decision
Also cross-links review #001's L1 resolution to ADR-009 and review #002.

Verification: cargo test 93 lib pass; fmt clean.
2026-09-05 08:08:22 +00:00
glm-5.3-flash 37ae07a4d4 docs: add ADR-009 (open op's input is the negotiation); fix stale doc refs
Review of the 2026-09-05 session commits:

- the ADR-009 decision cited by 96692d3 and docs/reviews/001 was never
  written — added decisions/009-channels-open-op-is-the-negotiation.md
  (context: the L1 two-gate disagreement, alkcall 0.4.0/0.4.1
  prerequisites, producer/consumer design, consequences, door type)
- channels.rs module doc + register_openable doc still described the
  old wire-frame negotiation read (drive_session) — aligned with
  drive_session_pre_negotiated and the enforced input schema
- tty-adapter.md: session-driver section + ADR tables now reference
  ADR-009 and the pre-negotiated driver; overview.md ADR index row

Verification: cargo test 93 lib (default) / 136 (--all-features);
clippy -D warnings native + wasm clean; fmt clean; doc 0 warnings.
2026-09-05 07:44:53 +00:00
glm-5.3-flash 6ad1d84fdb chore: verify MSRV 1.85, pin 1.85-compatible transitive deps (N6)
- cargo +1.85 check passes (default + --all-features), plus wasm
  target check and a full +1.85 test --all-features run (136 tests).
  The declared MSRV is real, not aspirational.
- the lockfile pins the 1.85-compatible transitive set (jsonschema
  0.46.9, idna_adapter 1.2.0, icu crates 2.0.x) — idna_adapter 1.2.2
  requires rustc 1.86, icu 2.3 requires 1.88; stable still resolves
  and all tests pass.
- review #001 is now fully resolved (status: fully-resolved). A CI
  MSRV job can gate on 'cargo +1.85 check' once CI exists.
2026-09-05 07:28:07 +00:00
glm-5.3-flash cdd6893046 test: readiness signals replace sleep-based timing (N4)
- signal tests use a marker-file readiness signal: the child's command
  is 'echo ready > <marker>; exec sleep 60', the test polls
  wait_for_file(marker, 5s) — marker exists = the shell exec'd, so the
  signal lands on the real target regardless of machine load. Applied
  in tests/pty.rs (both signal tests), tests/pipe.rs (SIGTERM), and
  the src/local unit tests; wait_for_file lives in tests/common.
- cancel-cleanup post-action sleeps became bounded polls for the
  child's death (kill(pid,0) -> ESRCH, 5s deadline) — faster and
  flake-proof in both directions.
- resize/cat-stdin tests need no readiness signal at all: the adapter's
  input pump processes chunks in order — the sleeps there were pure
  latency (integration suites now ~40ms, was 200-270ms).
2026-09-05 07:21:30 +00:00
glm-5.3-flash a73484203e test: pty bridge late-signal fallback chain; document unreachable error arms (L6)
- signal_after_child_exit_takes_both_kill_fallbacks: a late signal
  (after the child exited) takes kill(-pgid) fail -> kill(pid) fail ->
  warn + return, with no panic — the reachable part of the
  REQ-TTY-02 fallback chain.
- the remaining bridge error arms are documented-unreachable through
  the public path (per the review's disposition), with per-arm
  reasoning in the module doc: try_clone_reader (dup failure),
  reader read error (EIO -> EOF mapped), take_writer (second-take
  only), writer write/flush (externally-closed fd), waiter wait()
  (already-reaped child). The ADR-055 §4 -1 sentinel is covered at
  the adapter level (exit_error_sends_minus_one) — it also arises
  when the oneshot drops on kill-on-cancel.
- local/pty.rs line coverage 80.85% -> 86.01%; total 94.48%.
2026-09-05 07:17:48 +00:00
glm-5.3-flash 96692d3b6a feat: channels path carries the negotiation in the open op (L1, L3)
The channels path no longer carries a second negotiation frame on the
channel's data stream (ADR-009). The open op's registry-validated
input IS the negotiation:

- producer: make_tty_open_handler parses the open op's input into a
  NegotiateRequest and drives the new drive_session_pre_negotiated
  (same three-pump driver as drive_session, minus the wire-frame
  negotiation phase; validate/allocate factored into
  validate_and_allocate, shared by both paths). Post-open failures
  (unknown backend, allocate_failed, ownership denial) still go to the
  client as a 0x00-prefixed negotiation error frame, so the consumer's
  M1 disambiguation read applies unchanged. The tty:open scope gate is
  enforced by the registry's AccessControl (not re-checked in the
  handler).
- consumer: open_via_channels parses params locally (fail-fast before
  a channel is allocated), opens the channel, and starts raw-chunk
  mode directly (from_halves_raw — no negotiation write; the
  0x00-error-frame peek retained).
- tty_open_spec's input schema is now the partial NegotiateRequest
  shape (carriage/backend/cmd required; backend params stay free-form
  — raw JSON Schema is permissive on unknown keys).

Prerequisites landed upstream: alkcall 0.4.0 enforces
OperationSpec.input_schema at dispatch (the registry check this design
leans on never existed before); alkcall 0.4.1 parks early-arrival
chunks for un-adopted channels instead of dropping them — the open
response / producer's-first-write race was silently losing the first
chunks (found by L3's test; the session never resolved).

L3: open_via_channels + from_bidi_stream_via now covered end-to-end
(5 session tests + pre-negotiated adapter test + shared-harness tests
in the new crate::testing module; the channels harness moved there so
session tests share it).

Verification: cargo test 93 lib (default), 116 lib + 19 integration
(--all-features); clippy -D warnings native + wasm clean; fmt clean;
doc 0 warnings; wasm check clean.
2026-09-05 07:08:17 +00:00
glm-5.3-flash 9327a73496 chore: bump alkcall to 0.3.1; drop leftover bench dev-deps
- alkcall 0.1.1 -> 0.3.1 (crates.io latest). No API breakage in the
  surfaces alktty uses (core, channels, registry); all verification
  gates pass unchanged.
- remove criterion + alktype dev-deps and the [[bench]] section: the
  wire_vs_bast benchmark was extracted to the alktype project, leaving
  this config dead.
2026-09-05 06:15:34 +00:00
deepseek-v4-pro e5330e957b docs: mark review #001 findings resolved (M1, M2, L2, L4, L5, N1-N3, N5)
Update the remediation plan with a resolution section and per-finding
status. Nine findings resolved in commit 9944153; L1, L3, L6, N4, N6
remain open.
2026-08-17 12:13:35 +00:00
deepseek-v4-pro 99441530ab fix: address code review #001 findings (M1, M2, L2, L4, L5, N1-N3, N5)
- 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%.
2026-08-17 12:07:44 +00:00
deepseek-v4-pro 8ff7ba27f3 docs: add post-Phase-5 code review (coverage + correctness + smell)
First full review of the alktty crate after Phase 5 landed. Covers
coverage (cargo-llvm-cov), correctness, and code smell, with a
remediation plan.

- 2 Medium: TtySession never handles the negotiation-rejection error
  frame (M1); wait() swallows MalformedExitChunk (M2)
- 6 Low: channels input ignored (L1), consumer stdout/stderr routing
  untested (L2), open_via_channels 0% covered (L3), mocks leak into
  public API (L4), cargo fmt fails (L5), pty bridge error paths
  untested (L6)
- 6 Nit: 9 rustdoc warnings (N1), stale doc paths (N2), unsafe vs
  AGENTS.md §14 (N3), sleep-based timing (N4), duplicated seed helpers
  (N5), unverified MSRV (N6)

Verification: cargo test (81), cargo test --all-features (123),
clippy clean (native + wasm), wasm check clean, publish dry-run clean,
llvm-cov 90.74% lines / 92.24% functions.
2026-08-17 11:47:35 +00:00
glm-5.2 18c4924ad7 test: add BAST drift detection for the StreamType enum
Parse docs/architecture/tty-bast.md at test time, extract the BAST
JSON block, and assert the StreamType enum values match wire.rs's
STREAM_STDIN/STREAM_STDOUT/STREAM_STDERR/STREAM_CTRL_IN/
STREAM_CTRL_OUT constants (the index of each name in the enum's
values array is the integer on the wire). Catches the common drift
case — a new stream_type added to wire.rs but not the BAST, or vice
versa — without requiring alktype as a dep, just serde_json which
alktty already has.

This is the mitigation listed in the project setup plan's "Risk: BAST
schema drift" section. The test lives in src/wire.rs mod tests alongside
the STREAM_* constants it asserts against, so a developer editing the
constants sees the test fail locally before the BAST doc and the wire
format drift apart.

Verified the test catches drift by temporarily renaming CtrlOut ->
CtrlOutX in the BAST: the test fails with a clear diagnostic
("StreamType[4] is 'CtrlOutX' in BAST but 'CtrlOut' in wire.rs"),
then restored.

Verification:
- cargo test --all-features -> 123 tests pass (104 lib + 19
  integration; +1 from the new drift test)
- cargo clippy --all-targets --all-features -- -D warnings -> clean
- cargo clippy --target wasm32-unknown-unknown -- -D warnings -> clean
- cargo fmt --check -> clean
2026-08-17 11:23:31 +00:00
glm-5.2 da0d395ab3 docs: fix tty-bast.md to cover only binary framing, not JSON payloads
The previous BAST document modeled the JSON payloads (NegotiateRequest,
ControlMessage and its resize/signal/eof/exit variants, TerminalParams)
as BAST struct/union definitions with uint16/int32 fields. That was a
category error: BAST describes binary data layouts, and per the BAST
format spec itself, "a BAST document cannot validate a JSON payload."
The control and negotiation payloads on the wire are UTF-8 JSON text
serialized via serde_json, not struct-encoded binary — the uint16/int32
field widths implied a binary encoding that does not exist on the wire
and would have misled any generated validator.

The rewrite keeps only the genuinely-binary framing layer:

- ChunkHeader (5-byte: stream_type u8 + length u32 BE)
- StreamType enum (name->index table; documented deviation: on-wire
  is uint8, not BAST's standard u32 enum index)
- Chunk (header + length-prefixed bytes payload)
- NegotiationFrame (4-byte BE length prefix + UTF-8 JSON body,
  modeled as bytes since the body's JSON interpretation is above the
  BAST layer)

The JSON shapes (NegotiateRequest, ControlMessage, TerminalParams)
remain specified in tty-wire.md and implemented by the Rust source
(src/negotiation.rs, src/control.rs), which are the source of truth
for those payloads. Cross-references in tty-wire.md, overview.md, and
README.md updated to reflect the simplified scope.

The drift-detection test (project plan "Risk: BAST schema drift")
still works unchanged — it asserts the StreamType enum values match
wire.rs's STREAM_* constants, and that enum is retained.

Docs-only change; no Rust source changes.

Verification:
- cargo test --all-features -> 122 tests pass (unchanged)
- cargo clippy --all-targets --all-features -- -D warnings -> clean
- cargo fmt --check -> clean
- cargo doc --no-deps -> no new warnings (9 pre-existing rustdoc link
  warnings in src/, unchanged)
- BAST JSON block parses as valid JSON (4 : ChunkHeader,
  StreamType, Chunk, NegotiationFrame)
2026-08-17 11:20:23 +00:00
glm-5.2 b84c67bd8a phase 5: port integration tests (negotiation, pipe, pty)
Port the integration test suite from alknet-tty-local/tests/ into
tests/ at the crate root, plus the shared ClientSide harness. The
inline unit tests (wire, negotiation, control, adapter, session,
channels) were already ported in Phases 1-2 alongside the production
code; this completes Phase 5 step 2 (integration tests) — Phase 5
step 3 (channels integration tests) landed inline in src/channels.rs
mod tests in Phase 2.

Tests:
- tests/common/mod.rs — ClientSide wire-protocol harness +
  spawn_session helper + negotiate_pty_json / negotiate_pipe_json
  builders. Imports renamed alknet_core::auth::Identity ->
  alkcall::core::auth::Identity, alknet_tty::... -> alktty::...
- tests/negotiation.rs — 4 negotiation-error scenarios
  (unknown_backend, malformed_negotiation x3, allocate_failed)
- tests/pipe.rs — 6 pipe-mode scenarios (echo, separate stderr,
  SIGTERM, cancel cleanup, resize no-op, stdout sentinel). The 2
  cancel-cleanup / SIGTERM tests are #[cfg(unix)].
- tests/pty.rs — 8 PTY-mode scenarios (echo, interactive cat,
  resize, SIGINT, process-group signal, stdin-EOF sentinel,
  cancel cleanup, exit-chunk-is-last). The 4 signal /
  cancel-cleanup tests are #[cfg(unix)].

Each test file carries #![cfg(feature = "local")] so the default
crate (no features) skips the integration binaries and stays
wasm-buildable. The pty/pipe cancel-cleanup tests use
unsafe { libc::kill(pid, 0) } to probe the child — matching the
existing pattern in src/local/ (libc::kill is a safe libc crate
API wrapped in an unsafe block per Rust's foreign-function rules;
no new in-crate unsafe beyond what src/local/ already has).

Plan doc updated: Phase 5 marked landed 2026-08-17.

Verification:
- cargo test                         -> 80 lib tests pass
- cargo test --all-features          -> 99 tests pass (80 lib +
  5 negotiation + 6 pipe + 8 pty)
- cargo clippy --all-targets --all-features -- -D warnings -> clean
- cargo clippy --target wasm32-unknown-unknown -- -D warnings -> clean
- cargo fmt --check                  -> clean
2026-08-17 11:09:00 +00:00
glm-5.2 b3f50d1836 phase 4: architecture docs + BAST schema + renumbered ADRs
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
2026-08-17 10:52:26 +00:00
glm-5.2 712e7ae071 chore: add .gitignore and AGENTS.md; fix implementation-specialist conventions
The scaffold was missing basic repo hygiene, which led to drift: the
implementation-specialist agent's Project Conventions section was a
verbatim copy from alkcall (referencing OperationEnv, EventEnvelope,
Capabilities, alktype as a dep, call.aborted, vendored core types —
none of which exist in alktty), so spawned agents got alkcall
conventions injected.

- .gitignore: target/, node_modules/, .worktrees/ (matches alkcall/alktype)
- AGENTS.md: alktty-specific operating instructions — Git Workflow,
  14 Project Conventions (5-byte chunk header wire format, TtyBackend
  trait one-way door, local feature isolation / WASM invariant,
  BAST-as-doc-not-dep, 3-arg/4-arg ownership shapes, alknet ADR→alktty
  ADR renumbering plan), Verification Commands (incl. wasm checks),
  Architecture Context (phase status, ADR index, port origin)
- .opencode/agents/implementation-specialist.md: replace stale alkcall
  Project Conventions section with alktty-appropriate rules that mirror
  AGENTS.md

Verification: cargo test (80 passed), cargo fmt --check, cargo build
--target wasm32-unknown-unknown all clean. No source changes.
2026-08-17 10:32:21 +00:00
glm-5.2 e1610c2825 phase 3: port alknet-tty-local behind the local feature
Folds the alknet-tty-local crate into alktty as a feature-gated local
submodule (the single-crate + local-feature design from ADR-054; the
cyclic-dep workaround the mono-repo needed doesn't apply to one crate).

src/local/mod.rs:
- Module root, re-exports LocalTtyBackend, declares backend/pipe/pty
- Doc-commented as feature-gated + non-wasm by design (portable-pty +
  tokio::process need a real OS; enabling local on a wasm target is a
  build error by design)

src/local/backend.rs:
- LocalTtyBackend: implements crate::backend::TtyBackend
- allocate() dispatches on params.terminal: Some -> pty::allocate_pty,
  None -> pipe::allocate_pipe (ADR-054)
- 7 tests: PTY/pipe dispatch, empty-cmd rejection (both modes),
  resource_id returns None, new() constructs

src/local/pty.rs:
- portable_pty + 3 std threads (reader/writer/waiter) feeding tokio
  mpsc/oneshot (REQ-TTY-01 blocking->async bridge)
- PtyControl: resize + REQ-TTY-02 process-group signal forwarding
  (libc::kill(-pgid, sig) + kill(pid, sig) fallback + ChildKiller::kill
  for unknown names)
- LocalExitFuture: ADR-056 kill-on-Drop guard (ChildKiller on cancel,
  Option::take disarms on resolve)
- StdinSink: AsyncWrite over mpc::Sender<StdinCmd> with in-flight
  reserve+send parking for full-channel backpressure
- 7 tests incl. process-group reach (bash -c sleep), cancel-cleanup,
  unknown-signal fallback

src/local/pipe.rs:
- tokio::process::Command + tokio_util::io::ReaderStream
- PipeControl: no-op resize, libc::kill(pid, sig) with SIGKILL
  fallback for unknown names (pid-only, no process group - documented
  limitation of the runner case)
- PipeExitFuture: in-place Child::wait() poll (avoids self-referential
  borrow) + ADR-056 kill-on-Drop guard (start_kill on cancel)
- BytesStream: wraps ReaderStream, strips io::Error to EOF
- 9 tests incl. separate stderr, SIGTERM=-15, SIGKILL=-9 fallback,
  cancel-cleanup pid probe (kill(pid,0) returns ESRCH)

Import migration: alknet_tty::backend::{...} -> crate::backend::{...},
alknet_tty::control::signal_from_name -> crate::control::signal_from_name.

Cargo.toml: no changes needed (portable-pty + tokio-util optional, libc
under cfg(unix), and the local feature wiring tokio/process +
tokio/rt-multi-thread were already in the scaffold per Phase 0).

Also fixes pre-existing rustfmt drift in adapter.rs/channels.rs/
session.rs/lib.rs left by the phase 2 commit (cargo fmt --check without
--features local reported 28 diffs; cargo fmt does not accept
--features, so the earlier 'clean' check was a false negative — the
check errored on the unknown flag and grepped an empty stdout). Lesson:
run cargo fmt --check with no feature flags; cargo fmt doesn't gate on
features.

Verification:
- cargo test --features local -> 103/103 pass (was 80 at phase 2 end;
  +23 new tests across the 3 local modules: 7 backend, 7 pty, 9 pipe)
- cargo test (no features) -> 80/80 pass (local module not compiled)
- cargo clippy --features local --all-targets -> clean
- cargo clippy --all-targets (no features) -> clean
- cargo check --target wasm32-unknown-unknown -> clean (default crate
  stays wasm-clean; local is feature-gated and non-wasm by design)
- cargo fmt --check -> clean
2026-08-17 10:19:22 +00:00
glm-5.2 765f40ae34 phase 2: channels integration + TtySession consumer client
New code (not a port) — the producer/consumer halves of the channels
integration per alkcall's protocol-crate pattern.

Producer half (src/channels.rs):
- register_openable helper: builds the OperationSpec for channels/tty/sub
  (Sub-typed, channel_open marker for alk/tty, AccessControl with
  tty:open scope gate) and calls ChannelCore::register_openable
- TtyOpenHandler factory: receives the channel Connection, calls
  accept_bi(), runs drive_session on the BiStream — same code path as
  the direct-ALPN TtyAdapter (ADR-093)
- 8 tests: spec shape, registration, end-to-end open op returns
  channel_id, ACL denial without tty:open scope

Consumer half (src/session.rs):
- TtySession::connect_direct(connection, negotiate) — direct alk/tty
- TtySession::open_via_channels(client, params) — opens a channel via
  ChannelClient::open_channel, builds a Connection from the reassembled
  halves, runs the same negotiation + typed-methods flow
- Methods: send_stdin, close_stdin, resize, signal, recv_stdout,
  recv_stderr, wait
- Read pump: spawns a task that reads chunks off the BiStream and
  routes stdout/stderr to mpsc channels, parses Exit control chunks
  and resolves a watch channel for wait()
- Drop aborts the read pump
- 6 tests: negotiation frame write, stdin round-trip, resize/signal,
  stdout stream, NoExitChunk on early close, broken-stream error

Also: added Serialize to NegotiateRequest + TerminalParamsWire (was
Deserialize-only; the session client needs to serialize the negotiation
frame).

80/80 tests pass, wasm32-unknown-unknown clean, clippy clean.
2026-08-17 09:51:23 +00:00
glm-5.2 29dfa1a6af phase 1: port core types from alknet-tty
Port wire.rs, control.rs, negotiation.rs, backend.rs, adapter.rs from
alknet-tty (the protocol half of the alknet mono-repo split) into the
single alktty crate. All 65 unit tests pass; cargo check on
wasm32-unknown-unknown is clean (no features); clippy is clean.

Migration changes:
- adapter.rs imports: alknet_core::{auth, ownership, types} →
  alkcall::core::{auth, ownership, types + Connection/HandlerError/
  ProtocolHandler/StreamError re-exported at the crate root}
- adapter.rs TtyAdapter::alpn(): b"alknet/tty" → b"alk/tty"
- backend.rs BoxFuture type alias: Pin<Box<dyn Future + Send +
  'static>> → futures::future::BoxFuture<'static, T> (matches alkcall
  convention; alktty declares futures = 0.3 directly)
- doc comments: alknet/tty → alk/tty, alknet-tty → alktty, the
  alknet-tty-poc and findings.md cross-references trimmed to local
  docs

Test shape: alkcall's Identity.resources is HashMap<String, Vec<String>>
(was HashMap<String, String> in alknet-core); the tests construct with
StdHashMap::new() and infer the new shape from the struct, so no test
edits were needed. alkcall's OwnershipStore::record is 3-arg (no
action) and OwnershipProvider::owns is 4-arg (with action) — both
already match what the ported code calls.

Cargo.lock committed (matches alkcall/alktype convention; still
excluded from the published package via Cargo.toml's exclude list).
2026-08-17 09:36:24 +00:00
glm-5.2 66caa309ef plan: keep default crate wasm-clean; split tokio features for local
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
2026-08-17 09:23:21 +00:00
glm-5.2 2086817a90 plan: revise for landed upstream changes and fold alknet-tty-local
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.
2026-08-17 08:57:14 +00:00
deepseek-v4-pro 09797de4eb scaffold: Cargo.toml and project setup plan 2026-08-17 08:38:24 +00:00
deepseek-v4-pro a00a731f90 init 2026-08-14 12:41:38 +00:00