The wave-5 client-direction real-tool e2e: a dedicated `tests/e2e_client.rs` binary dialing a per-row spawned real OpenSSH server with the shipped crate — exec (exit-status parity through real reaping), the direct-tcpip dial, the `-R` leg (port-0 mint + accepted arrival + data leg + cancel tearing the real listen down + unknown- cancel ListenRefused), and the decline rows (sftp placeholder, host-key mismatch hard-decline, server-refused direct-tcpip with the connection staying usable). Fixture shape decided + recorded in the task file: per-row spawned sshd, minted ed25519 host key, publickey-only knobs, the decrypt-at-dial vault posture, the case-1 pin posture. No src/ changes; every row check-gated skipped-with-reason and deadline-bounded. Verification: `scripts/matrix.sh` green (cargo test per build none/git/ session/tunnel/sftp/--all-features — e2e_client rides 2/2/3/5/2/7 rows, clippy x2 `-D warnings`, fmt, doc), plus 10 consecutive all-features e2e_client runs green. taskgraph validate: 42 tasks valid.
9.3 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | |
|---|---|---|---|---|---|---|---|---|
| w5-e2e-client | Wave 5 — real-tool e2e, client direction (the door against a real sshd) | completed |
|
moderate | medium | phase | implementation |
Description
The client-direction real-tool e2e — the reverse-runner shape
re-landed against a real sshd (docs/plans/implementation.md
§Wave 5, the real-tool e2e bullet's client half; the wave-4 client
rows ran against the in-process fake sshd double, and the wave-4
gate named real-sshd e2e wave-5 territory explicitly). A dedicated
new binary (decide + record the placement — tests/e2e_client.rs
reusing tests/common is the recommended posture, disjoint from the
matrix task's binaries), under the check-gated
skipped-with-reason posture:
- The sshd fixture — a spawned real OpenSSH server the dial
targets: host key generated (ed25519), a listen port picked, the
HostKeyPolicyaccepting the fixture's known fingerprint (the case-1 pin posture — the policy seam consumed against a real presented key), a generated ed25519 user key authorized in the fixture'sauthorized_keys(the decrypt-at-dial path over the in-memory vault double). The fixture's exact shape (spawned per-test vs a shared per-binary instance; the sshd_config knobs — publickey-only, no password) decided + recorded. - The op rows against real sshd — exec (exit-status parity
through a real server's reaping), the direct-tcpip dial (a real
TCP connection through the server's forwarding —
-L-shaped), the-Rleg:tcpip_forwardwith the port-0 mint against a real server's actually-bound reply, the server-initiatedforwarded-tcpipopen served through the acceptor registry (the reverse-runner consumer pattern end to end), the cancel form tearing the real listen down. - The decline rows against a real server — the sftp client-side
placeholder decline (
subsystemsftp → the typed decline), the unfeatured direct-tcpip decline (the connection stays usable — the typed-open decline mirror's real-server row), the host-key policy mismatch hard-decline (a wrong-fingerprint fixture → no dial, the case-1 posture against a real presented key). - Every skip reasoned (no
sshdin the environment / fixture constraints), every finding fixed in-task or booked for the gate — never absorbed silently.
Acceptance Criteria
- The sshd fixture lands (spawned real server, ed25519 host + user keys, the policy pin posture); its shape decided + recorded
- Exec, direct-tcpip, and the
-Rleg (port-0 mint + accepted arrival + cancel) green against real sshd (or reasoned-skipped) - The decline rows green: sftp placeholder, unfeatured direct-tcpip (connection stays usable), host-key mismatch hard-decline
- Every finding fixed in-task or booked for the gate (recorded)
cargo test(+ the builds the new binary rides), clippy ×2 (-D warnings), fmt,cargo doc --no-depsgreen
References
- docs/plans/implementation.md §Wave 5 (the real-tool e2e bullet),
§Wave 4 (the fake-
sshdposture this task's real server replaces for e2e) - docs/architecture/client.md (the dial surface, op rows, the acceptor), docs/research/reverse-runner.md (the consumer pattern the client direction serves)
- docs/research/poc-2*.md / the reverse-runner extraction (the
real-
sshdclient-direction evidence this re-lands against the shipped crate) - tests/client_dial.rs, tests/client_ops.rs, tests/client_reverse_listen.rs (the double-driven rows this task mirrors against the real server), tests/common/sshd_double.rs (the double's semantics — what the real fixture must match)
Notes
The sshd fixture decided: tests/e2e_client.rs (the new binary), reusing tests/common — the
wave-4 client binaries stay single-writer per the decomposition's file map; this task edited only
its own binary (tests/e2e_client.rs, an in-file support module). Fixture shape: per-row
spawned real sshd — each row's config knobs differ (AllowTcpForwarding yes/no) and each row
dials its own server, so a shared per-binary instance buys nothing; per-row isolation (own workdir,
own minted ed25519 host key via ssh-keygen, own probe-verified port from the e2e-server's
monotonic allocator posture) beats the spawn cost. Knob set: publickey-only, StrictModes no (the
throwaway workdir carries none of the real home-dir hygiene), UsePAM no, UseDNS no,
PidFile none, PermitRootLogin prohibit-password, host key + authorized_keys (fixture alpha's
public blob) at absolute paths, per-row AllowTcpForwarding. Readiness is a bounded TCP probe; a
server that never comes up is the row's reasoned skip with the server's own -e log tail as
the reason (worked as non-root in this environment — verified against OpenSSH 9.6p1). The dial
rides the decrypt-at-dial path over the in-memory vault double (VaultKeySource over TestVault,
alpha sealed) and the case-1 pin posture (MemHostKeyPolicy::pin) accepts only the minted host
fingerprint.
Row inventory (all check-gated, #[cfg(unix)] + feature-gated per row group; deadline-bounded
end to end — the POC-1 §5 posture; Contract stamp: markers cite client.md / reverse-runner.md):
| Row | Build(s) | What it pins |
|---|---|---|
exec_row::real_sshd_exec_pumps_streams_and_reports_the_exit_status |
session (+all) | the wave-4 double-driven exec row re-landed: stdout/stderr separated through a real server, the nonzero exit (3) reported in-band last — the real server's reaping, parity not a canned 0; the case-1 pin accept decision on record against the really-presented key |
direct_tcpip_row::real_sshd_direct_tcpip_round_trips_a_live_tcp_connection |
tunnel (+all) | the -L-shaped dial through a real server's forwarding into a live loopback echo server, bytes end to end |
reverse_rows::real_sshd_port_zero_mint_serves_the_accepted_arrival_and_the_data_leg |
tunnel (+all) | the port-0 mint surfaces the actually-bound port (verified by dialing it), the server-initiated forwarded-tcpip arrives into the installed acceptor, the external dialer's bytes round trip — the reverse-runner consumer pattern end to end |
reverse_rows::real_sshd_cancel_tears_the_listen_down_and_unknown_cancels_refuse |
tunnel (+all) | explicit-port listen actually bound; the listener handle's cancel() frees the real bound port (later dials refused); a stateless cancel of the unknown listener is ListenRefused; connection stays usable |
declines::real_connection_sftp_placeholder_declines_with_the_connection_intact |
every build | the OQ-13 placeholder against a real authenticated connection: typed decline before any wire op, connection intact |
declines::real_presented_key_mismatch_hard_declines_and_the_right_pin_dials |
every build | the wrong-pinned policy hard-declines the dial (DialError::HostKeyRejected), the policy log records the real fingerprint as rejected, and the correctly-pinned dial to the same server succeeds — the decline is the policy's decision, not the server's |
unfeatured_direct_decline::real_server_refused_direct_tcpip_declines_and_the_connection_stays_usable |
tunnel+session (all-features) | a real server with AllowTcpForwarding no refuses the typed open → OpenDeclined(Prohibited), and a following exec is served — the connection-stays-usable fact end to end |
Decisions recorded (for the wave-5 gate):
- The "connection stays usable" decline row is gated
tunnelandsession(rides--all-featuresonly): the post-decline exec is the stays-usable proof's vehicle, and exec is asession-gated op. Under tunnel-only builds the row is absent; the fake-double mirror (tests/client_ops.rs) carries the tunnel-only stays-usable rows. - The sftp and host-key-mismatch rows run in every build (feature-free ops on
SshConnection), so even the no-feature matrix build exercises two real-server rows. - The fixture skips are honest: no
sshdin the environment / nossh-keygen/ spawn refusal / never-listening (the server's-elog tail carried in the reason) — never silently green.
Findings: no src/ changes needed — every observed issue was e2e-graph local (compile/fmt-level only). No bookings for the gate beyond the decisions above.
Summary
Completed 2026-10-30. The client-direction real-tool e2e landed as tests/e2e_client.rs (7 rows,
fixture shape above): exec (exit-status parity, nonzero through real reaping), the direct-tcpip
dial, the -R leg (port-0 mint + accepted arrival + data leg + cancel tearing the real listen
down + unknown-cancel ListenRefused), and the three decline rows (sftp placeholder, host-key
mismatch hard-decline with the real presented fingerprint on record, server-refused direct-tcpip
with the connection staying usable). Verification: full scripts/matrix.sh run green — cargo test per build (none / git / session / tunnel / sftp / --all-features; the new binary compiles
in every build and runs only its rows' builds: 2 / 2 / 3 / 5 / 2 / 7 rows), both clippy runs
-D warnings clean, fmt clean, cargo doc --no-deps clean; plus 10 consecutive all-features
e2e_client runs green (no flakes; per-row workdirs + the e2e-server's port allocator carried
over). Skips are reasoned prints only; on this host sshd 9.6p1 is present, so every row ran.