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.
15 KiB
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 |
|
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
KeySourceErrorposture a no-dial outcome; the policy seam consumed at the hook (crate default connects to nothing; no accept-all built-in); theZeroizingwrap 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 validateclean- 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 russhConfigwith the keepalive 15 s / 3 posture and the always-set wall clock, the policy atcheck_server_key, the single publickey-only auth; the sanctionedusernameDialConfigamendment recorded honestly (a required parameter, no crate default, never identity, never logged); §Op rows, coveringclient_ops.rs, where every table row is present: the exec/pty/shell rows undersession, the direct rows undertunnel, the git exec row undergitover the shared grammar's construction arm, the sftp placeholder in every build; POC-3 §D.3 mechanics pinned in code plus rows (exec()notrequest_exec,request_exec, thewait()-drain Success/Failure, split-halves wait-pumps for the exit-consuming rows and straightinto_streamfor the exit-less, the fire-and-forget controls sent from the live pump, no queue consumption before any conversion); §The acceptor (client_accept.rs+ theclient_handler.rshooks, 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.rshandles +client.rs§Lifecycle posture, with explicit cancels,Disconnect::ByApplicationthe 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.tomlunchanged:session/git/tunnel/sftp,default = []); the client exports ride the same gates (RemoteSessionundersession,TunnelChannel+ the acceptor surface +ReverseListenerundertunnel,GitPackSessionundergit, 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 ridetests/client_dial.rs's decline-all row, and theclient_handler.rshooks 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
KeySourceErrorposture a no-dial outcome (DialError::KeySourcepreserved per-variant;error.rstaxonomy rows); the policy seam consumed at the hook (client_handler.rs::check_server_key, where the fingerprint rides the crate's single path,PresentedHostKeycarries host/port, the verdict passes through;RejectAllconnects to nothing, no accept-all built-in); the wave-1Zeroizingwrap 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 (ChannelHalveReaderover 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), thechannel_close-fired cancel path wire-witnessed, the Closed/Break no-exit-chunk shape row-pinned (Break's frame classification unit-rowed, wire-unreachable, recorded), thewrite_allno-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: noset_identity/take_call_connectionanywhere insrc/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
ChannelCapcounters against the sameLimits::max_concurrent_channelsceiling (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_openwithno-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 strvocabulary); 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 onsh), the full-circle reverse-listen smoke (tests/tunnel_mapping.rs); real-sshde2e 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-
sshdposture, 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 insideacceptwould strand one held slot for the connection's lifetime, a bounded-resource leak path on the new cap. Fixed with a panic-safe guard (HandoffCapGuardinsrc/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, andclient_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.rscrate-level wave-state doc still said the lifecycle primitives were "later implementation waves" (stale overw4-lifecycle; the same staleness class the wave-3 gate fixed inline). Corrected to the landed state.
Interpretations (flagged at decomposition), all confirmed
- The fail-closed client Handler skeleton in
w4-dial, the registry inw4-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. - The fake-
sshdclient-direction vehicle intests/common, confirmed: vanilla-parity, real loopback TCP, one dial per instance, and the harness'sForwardedAcceptorvocabulary was grown (not forked) intoclient_accept.rs's real surface. - No
DialConfigcourse-correction anticipated was confirmed, with the one sanctioned exception recorded in-task (the requiredusername, the USERAUTH_REQUEST's structural requirement; the amendment follows thew3-seam-configpattern and is honestly documented). - The hardening tasks ordered by single-writer needs were
confirmed:
tests/git_mapping.rsthentests/tty_mapping.rssequencing 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.