Files
alkssh/tasks/implementation/review-wave-4.md
T
glm-5.3-flash 671a779ce3 docs(review): close the wave-4 review gate — verdict green (review-wave-4)
- Gate reading recorded line by line: client.md (dial surface, op
  rows, acceptor, lifecycle primitives), ADR-003 symmetry, ADR-005/
  OQ-05/OQ-07, bounded resources, tracing, smokes, docs honesty —
  all confirmed
- Wave-3 gate booked items 1-4 verified discharged; item 5 verified
  still booked with its owners (nothing absorbed)
- Inline fix: the accepted-open handoff counter now releases on
  completion or unwind (HandoffCapGuard panic-safety; a panicking
  assembly acceptor can no longer strand a held slot) + unit row
- Inline fix: lib.rs crate-level wave-state doc sentence (stale
  over w4-lifecycle)
- w4-reverse-listen / w4-lifecycle Summary placeholder lines filled

Verification: cargo test (default / --all-features / --features
tunnel) all green; clippy x2 -D warnings clean; cargo fmt --check
clean; cargo doc --no-deps 0 warnings; taskgraph validate (35 tasks)
2026-10-29 17:59:54 +00:00

15 KiB
Raw Blame History

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
review-wave-4 Wave 4 gate — the client-direction review completed
w4-dial
w4-op-rows
w4-reverse-listen
w4-lifecycle
w4-git-exit-close
w4-channel-not-held-rows
w4-tty-coverage
w4-router-collision
narrow low phase review

Description

The wave-4 review gate. The client direction completes the two-direction door — wave 5 builds the full matrix and the release on both directions, and the published crate freezes this surface. The gate reads the landed client direction against the architecture line by line:

  • client.md line-by-line — §The dial surface (key source decrypt-at-dial fail-closed; the policy at check_server_key; the bounded knobs with the keepalive 15 s / 3 posture), §Op rows (each table row read against the landed code — the POC-3 §D.3 mechanics pinned in code + rows), §The acceptor (served only under a registered feature's acceptor; unregistered/unfeatured declined cleanly — never accept-and-drop; late-install across a live connection), §Lifecycle primitives (explicitly cancellable handles; Disconnect::ByApplication; no reconnect/restore policy in the crate — the boundary held).
  • ADR-003 symmetry — the same features gate both directions; the per-build matrices green client-side (the acceptor rows per build); honest advertisement unchanged; the sftp placeholder declines in every build both directions (OQ-13); x11/auth-agent never-features untouched.
  • ADR-005 / OQ-05 / OQ-07 — decrypt-at-dial with every KeySourceError posture a no-dial outcome; the policy seam consumed at the hook (crate default connects to nothing; no accept-all built-in); the Zeroizing wrap posture intact.
  • The wave-3 gate's booked items 1–4 — every one discharged or explicitly re-booked: the git status-before-close by construction (w4-git-exit-close), the channel-not-held wire rows (w4-channel-not-held-rows), the tty coverage minors (w4-tty-coverage), the router token-collision pre-check (w4-router-collision). Item 5 (the upstream asks) verified still booked with their owners — not absorbed silently.
  • Bounded resources — no new unbounded shape on the client surfaces: the acceptor registry bounded, the listener bookkeeping bounded, the pumps bounded (the convention-9 read over every new serving/pumping path).
  • Tracing — the uniform event vocabulary across the new client-side decline/serve paths; the safety posture held (no secrets, no key material, no username).
  • The smokes — the client-direction real-tool postures recorded honestly (required/conditionally-skipped with reason); nothing silently green; anything named wave-5 e2e territory named explicitly.
  • Docs honesty — module docs name what each new surface is, what remains wave-5 material, and any new upstream notes (the russh client-hook shapes at this pin; the acceptor-registry shape decision; the DialConfig disposition).

Verdict: green (wave 5 decomposes, absorbing corrections) or findings booked (remediation before wave 5 decomposes).

Acceptance Criteria

  • Every client.md/ADR surface above read and confirmed (or corrected with the correction landed)
  • The client-side per-build matrices verified through the server double; the acceptor contract verified (registered/unregistered/ unfeatured/late-install); the wave-3 booked items 1–4 verified discharged
  • Full verification set green (cargo test, --all-features, clippy ×2 -D warnings, fmt, cargo doc --no-deps)
  • taskgraph validate clean
  • Verdict recorded; wave 5 decomposition unblocked or findings booked

References

  • docs/plans/implementation.md (wave 4 section + this gate's role)
  • docs/architecture/client.md (the wave's contract), mapping.md (the server-direction mirror), overview.md, open-questions.md (OQ-05/ 07/11/12/13), ADRs 002/003/005/006
  • tasks/implementation/review-wave-3.md (the booked items this gate verifies discharged)
  • docs/research/reverse-runner.md (the consumer pattern the client direction serves), docs/research/poc-3-mapping-bakeoff.md §D.3

Notes

Gate reading (line by line, 2026-10-29; commits 9cf5bfb…dec644e)

Commits verified against the decomposition: all eight tasks' hashes match docs/plans/implementation.md's decomposition record; every task file carries its completion records and checked acceptance criteria.

  • client.md line-by-line ✓ — read against the landed code: §The dial surface (client.rs — the four-step fail-closed sequence: decrypt-at-dial key load, the bounded russh Config with the keepalive 15 s / 3 posture and the always-set wall clock, the policy at check_server_key, the single publickey-only auth; the sanctioned username DialConfig amendment recorded honestly — a required parameter, no crate default, never identity, never logged); §Op rows (client_ops.rs — every table row present: the exec/pty/shell rows under session, the direct rows under tunnel, the git exec row under git over the shared grammar's construction arm, the sftp placeholder in every build; POC-3 §D.3 mechanics pinned in code + rows — exec() not request_exec, the wait()-drain Success/Failure, split-halves wait-pumps for the exit-consuming rows and straight into_stream for the exit-less, the fire-and-forget controls sent from the live pump, no queue consumption before any conversion); §The acceptor (client_accept.rs + the client_handler.rs hooks — per-type fixed slots, refuse-never- replace, arrival-time reads, decline-not-buffer, late-install over the live connection, listen rows gated pre-wire, the two dedicated counters against the one ceiling); §Lifecycle primitives (client_accept.rs handles + client.rs §Lifecycle posture — explicit cancels, Disconnect::ByApplication the only clean path, drop-is-never-the-clean-path row-pinned both ways, the no-reconnect/restore-policy boundary stated in module docs; the crate owns no policy). client.md's landing blocks (acceptor/ lifecycle/op-row shapes recorded at landing) are in the doc.
  • ADR-003 symmetry ✓ — the features gate both directions (Cargo.toml unchanged: session/git/tunnel/sftp, default = []); the client exports ride the same gates (RemoteSession under session, TunnelChannel + the acceptor surface + ReverseListener under tunnel, GitPackSession under git, the sftp row feature-free); the per-build matrices are green (the default build's 16 binaries + per-feature/ --all-features; the unfeatured builds' fail-closed wire rows ride tests/client_dial.rs's decline-all row — the client_handler.rs hooks are cfg-declined in every build); honest advertisement unchanged; x11/agent never-features untouched on both directions.
  • ADR-005 / OQ-05 / OQ-07 ✓ — decrypt-at-dial with every KeySourceError posture a no-dial outcome (DialError::KeySource preserved per-variant; error.rs taxonomy rows); the policy seam consumed at the hook (client_handler.rs::check_server_key — the fingerprint rides the crate's single path, PresentedHostKey carries host/port, the verdict passes through; RejectAll connects to nothing, no accept-all built-in); the wave-1 Zeroizing wrap posture intact (key_source.rs, untouched).
  • The wave-3 gate's booked items 1–4 ✓ — all discharged: item 1 (w4-git-exit-close) — the split-halves seam duplex (ChannelHalveReader over the owned close-free read half; no drop path ever messages the wire) with the by-construction property wire-pinned and the row falsifying the old shape deterministic-red-under-revert; item 2 (w4-channel-not-held-rows) — the guard's wire rows on all three served rows per build, vocabulary-asserting; item 3 (w4-tty-coverage) — the channel_close-fired cancel path wire-witnessed, the Closed/Break no-exit-chunk shape row-pinned (Break's frame classification unit-rowed, wire-unreachable, recorded), the write_all no-deadline limit recorded in module docs; item 4 (w4-router-collision) — the register-refuses pre-check with the error-surface decision recorded honestly (the wire-time decline disposition, not a taxonomy variant that would kill the session). Item 5 verified still booked with its owners, not absorbed: no set_identity/take_call_connection anywhere in src/ code (doc references only, recording the no-detach posture + the upstream ask), and mapping.md's correlation-miss event-vocabulary nuance records stand.
  • Bounded resources ✓ (convention 9 read over every new client serving/pumping path) — the acceptor registry bounded by construction (two slots); the reverse-listen count and the accepted-open handoff count are two dedicated ChannelCap counters against the same Limits::max_concurrent_channels ceiling (admit pre-wire/arrival, release at confirmed cancels / refused paths / the handoff task's end); the pumps bounded (64 KiB duplexes, the 16 KiB git pump buffer, the 1024-message launch-drain cap, russh windows verbatim, the always-on wall clock); the git row's command/repo gates answer pre-wire. One gap found and fixed inline (below).
  • Tracing ✓ — the client-side decline paths ride the uniform vocabulary (decline_open with no-acceptor / cap-exceeded / never-feature; the skeleton's decline rows event-pinned); the safety posture held — structurally no username/fingerprint/secret parameter exists on any event helper (the &'static str vocabulary); the client op rows' typed surfaces carry outcomes without events (the recorded asymmetry decision).
  • The smokes ✓ — three recorded honestly: the alkssh-client ↔ alkssh-server dial smoke (tests/client_dial.rs), the client-exec ↔ server-session-rows smoke (tests/client_ops.rs, check-gated on sh), the full-circle reverse-listen smoke (tests/tunnel_mapping.rs); real-sshd e2e named wave-5 territory explicitly in each task's records — nothing silently green, nothing dropped.
  • Docs honesty ✓ — module docs name each new surface and its remaining wave-5 material (the fake-sshd posture, the DialConfig amendment, the russh client hook shapes at this pin, the acceptor-registry shape decision, the recorded wire facts — the port-0-only mint, the drop/is-closed semantics); one stale crate-level sentence fixed inline (below).

Minor issues found and fixed inline (this session)

  • The accepted-open handoff counter's panic-unsafe release (the gate's one code fix): the counter released only after acceptor.accept(...) returned — an assembly acceptor panicking inside accept stranded one held slot for the connection's lifetime (a bounded-resource leak path on the new cap). Fixed with a panic-safe guard (HandoffCapGuard in src/client_accept.rs): the release fires at the handoff task's end, on completion or unwind; the recorded release-site wording updated in the module docs, the trait docs, and client_handler.rs's two references; a unit row (the_handoff_guard_releases_on_unwind) pins the unwind release. No surface change, no behavior change on any happy path.
  • lib.rs crate-level wave-state doc still said the lifecycle primitives were "later implementation waves" (stale over w4-lifecycle; the same staleness class the wave-3 gate fixed inline). Corrected to the landed state.

Interpretations (flagged at decomposition) — all confirmed

  1. The fail-closed client Handler skeleton in w4-dial, the registry in w4-reverse-listen — confirmed: the skeleton's decline-all default is row-pinned in every build, and the registry grows exactly the two forwarded hooks; the split kept both surfaces reviewable.
  2. The fake-sshd client-direction vehicle in tests/common — confirmed: vanilla-parity, real loopback TCP, one dial per instance, and the harness's ForwardedAcceptor vocabulary was grown (not forked) into client_accept.rs's real surface.
  3. No DialConfig course-correction anticipated — confirmed, with the one sanctioned exception recorded in-task (the required username — the USERAUTH_REQUEST's structural requirement; the amendment follows the w3-seam-config pattern and is honestly documented).
  4. The hardening tasks ordered by single-writer needs — confirmed: tests/git_mapping.rs then tests/tty_mapping.rs sequencing held, no cross-task file conflicts in the landed history.

Verification (all green, 2026-10-29)

cargo test (default build) · cargo test --all-features (all 16 binaries green) · cargo test --features tunnel (the gate's per-feature spot re-run, covering the edited acceptor surface + the new guard row) · cargo clippy --all-targets -- -D warnings ✓ · cargo clippy --all-features --all-targets -- -D warnings ✓ · cargo fmt --check ✓ · cargo doc --no-deps 0 warnings · taskgraph validate ✓ (35 tasks).

Summary

Verdict: green. The client direction reads clean against client.md/ADR-003/ADR-005 line by line: the four-step fail-closed dial over the bounded config (the sanctioned username amendment recorded), the op rows with the POC-3 mechanics pinned in code (split-halves wait-pumps vs straight conversions; the typed-open decline mirror family-typed; the sftp placeholder in every build), the acceptor registry as the refuse-never-replace per-type-slot gate with arrival-time declines and bounded two-counter bookkeeping, and the lifecycle primitives with the drop/clean-close boundary row-pinned both ways and the no-policy posture stated. All three interpretations confirmed except as recorded (all favorable); the wave-3 booked items 1–4 verified discharged and item 5 verified still booked upstream — nothing absorbed. The full verification set is green. Two minors fixed inline (the handoff counter's panic-unsafe release — the one code fix, guard + unit row; a stale crate-level wave-state doc sentence). No structural findings; wave 5 decomposition is unblocked and should absorb nothing beyond its own plan (the decline matrix, real-tool e2e, fuzzing, docs, the publish).

Summary

Verdict: green (2026-10-29) — see Notes for the full gate reading. All eight wave-4 tasks verified against the client.md/ADR contract surfaces line by line; the wave-3 gate's booked items 1–4 discharged, item 5 verified still booked upstream; the full verification set + taskgraph validate green. Two inline fixes landed with the gate: the accepted-open handoff counter's panic-safe release (HandoffCapGuard — the gate's one code fix) and the stale crate-level wave-state doc sentence in lib.rs. Wave 5 decomposition is unblocked.