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.
The client direction's substrate (client.md §The dial surface), the
mirror of w2-server-mount: dial(DialConfig) runs the fail-closed
four-step sequence — decrypt-at-dial key load (every KeySource
posture a distinct no-dial outcome), the bounded russh client Config
from Limits (keepalive 15 s / 3, the wall clock always Some, window/
packet/buffer verbatim), the injected host-key policy at
check_server_key (RejectAll connects to nothing; UnknownKey maps to
DialError::HostKeyRejected — its only producer), and exactly one
publickey-only auth attempt — and returns the authenticated
SshConnection handle everything else in the wave rides.
- DialConfig amendment (the sanctioned course-correction, recorded):
the required `username` field — the USERAUTH_REQUEST's user string;
no crate default, never identity, never logged (ADR-004 decision 5).
- The client Handler skeleton (src/client_handler.rs): every
server_channel_open_* hook overridden explicit-over-inherited with
a clean AdministrativelyProhibited reject (never russh's
accept-and-drop default, ADR-003 §4); should_accept_unknown_
server_channel the explicit false; decline events on the crate
vocabulary (no-acceptor / never-feature reasons).
- The in-process fake sshd (tests/common/sshd_double.rs): the
client-direction contract vehicle — a plain russh server on a real
loopback TCP listener, accept-set identity resolution, the auth_
succeeded handle carrier for the server-initiated-open rows, the
per-session outcome channel for the clean-close rows.
- tests/client_dial.rs: 10 contract rows — dial success, the five
key-source no-dial postures, RejectAll / pin-mismatch / case-2
policy rows, refused auth, the malformed-address dial-time refusal,
the bounded-config observable, the six-hook decline wire rows with
decline events, and the smoke: alkssh-client ↔ alkssh-server over a
real TCP loopback (real-sshd dial stays wave-5 e2e).
Verification: cargo test + per-feature + --all-features (14 binaries,
all green), cargo clippy ×2 (-D warnings), cargo fmt --check,
cargo doc --no-deps — all clean. taskgraph validate: 35 tasks valid.
Eight implementation tasks + the gate under tasks/implementation/,
folding the wave-3 gate's four booked findings in as dedicated tasks
(the gate's own disposition — wave-4 material, not wave-3 backports).
Hardening set (server-side, independent of the client chain,
scheduled first as cheap remediation):
- w4-git-exit-close: booked item 1 — the git row's status-before-close
by construction (the seam/shape decided + recorded in-task; the
saturated-buffer scenario pinned on the wire)
- w4-channel-not-held-rows: booked item 2 — the guard's wire rows on
git/tty pipe/shell, per build (test-only)
- w4-tty-coverage: booked item 3 — the channel_close-fired cancel
path + the Closed/Break no-exit-chunk rows + the write_all
no-deadline known-limit record
- w4-router-collision: booked item 4 — the ListenRouter
token-collision fail-closed pre-check
Client chain (sequential logistically — one client Handler impl):
- w4-dial: the russh client stack + the connection handle + the
client Handler skeleton at fail-closed defaults, over an in-process
fake-sshd server double
- w4-op-rows: exec/pty, direct-tcpip, git exec over the shared
grammar, the sftp client-side decline — the POC-3 §D.3 mechanics
- w4-reverse-listen: the -R leg — forward requests + the acceptor
registry + late-install (the wave's highest-risk task)
- w4-lifecycle: the OQ-12 primitives (cancellable handles, clean
disconnect, no reconnect policy)
review-wave-4 depends on all eight. The gate's item 5 (upstream asks)
stays booked with its owners — no task. Plan wave-4 section carries
the decomposition record + four flagged interpretations; wave table
status updated.
Verification: taskgraph validate ✓ (35 tasks)