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.