Files
alkssh/tasks/implementation/review-wave-4.md
T
glm-5.3-flash f4b5f896b4 docs: sweep em dashes from all markdown prose
Renames the written-usage rule into practice across the contract surface:
docs/ (architecture, research, plans, sdd), tasks/ (all 43 task files,
taskgraph validate passes), AGENTS.md, README.md, CHANGELOG.md, and the
.opencode/agents/ prompts. Every replacement is a sense-preserving
rewrite (colons, semicolons, commas, parentheses, sentence splits, varied),
never a mechanical swap; no spaced hyphens were substituted. Enum fields,
frontmatter, tables, wire data, and cited identifiers survive verbatim;
en dashes inside numeric ranges keep their meaning.

Verification: taskgraph validate (43 tasks), repo grep clean (single
exempt hit: the verification command's own search pattern in AGENTS.md),
cargo test, clippy --all-targets -D warnings, fmt --check, doc all green.
2026-11-04 20:28:00 +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, and 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, reading it section by section: §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, a boundary held).
  • ADR-003 symmetry, where 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, with 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, with 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, covering client_ops.rs, where every table row is 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 plus rows (exec() not request_exec, 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, being 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, with 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 ✓, as 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 plus per-feature/ --all-features; the unfeatured builds' fail-closed wire rows ride tests/client_dial.rs's decline-all row, and 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, where 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 and 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 ✓, with the convention-9 read over every new client serving/pumping path showing 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 ✓, with the client-side decline paths riding the uniform vocabulary (decline_open with no-acceptor / cap-exceeded / never-feature; the skeleton's decline rows event-pinned); the safety posture held, as structurally no username/fingerprint/secret parameter exists on any event helper (the &'static str vocabulary); the client op rows' typed surfaces also 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, with nothing silently green, nothing dropped.
  • Docs honesty ✓, as 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, and 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, so an assembly acceptor panicking inside accept would strand 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, whether 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 was 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 were 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, with nothing absorbed. The full verification set is green. Two minors fixed inline (the handoff counter's panic-unsafe release, the one code fix with its guard and 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.