- 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)
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 — 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
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 — 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 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 (client_ops.rs— every table row 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 + rows —exec()notrequest_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 — 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 — 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 ✓ — 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 + per-feature/--all-features; the unfeatured builds' fail-closed wire rows ridetests/client_dial.rs's decline-all row — 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— 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, 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 ✓ (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
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 ✓ — the client-side decline paths ride the uniform
vocabulary (
decline_openwithno-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 strvocabulary); 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 onsh), the full-circle reverse-listen smoke (tests/tunnel_mapping.rs); real-sshde2e 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-
sshdposture, 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 insideacceptstranded 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, 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 — 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 —
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 — 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.