Commit Graph
16 Commits
Author SHA1 Message Date
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
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 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 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 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