Files
alkssh/tasks/implementation/w2-loopback-harness.md
T
glm-5.3-flash 5b2654f492 docs(tasks): close the wave-2 review gate — verdict green
Gate reading (commits b90e89b..196289f) against the architecture line
by line: ADR-001 mount contract, ADR-002 Config construction,
ADR-003 feature gates, ADR-004 dispositions, ADR-006 constraints,
mapping.md skeleton decline matrix per build, bounded resources, and
the harness — all confirmed. All four flagged interpretations
confirmed as recorded.

Fixed inline before committing (task-file hygiene only):

- w2-loopback-harness, w2-dispatch-decline-core: acceptance boxes
  checked once verified against the landed rows/runs
- w2-bounded-resources: stale placeholder lines removed
- review-wave-2: verdict recorded

Booked finding (next wave's decomposition): decline-path
tracing/observability — the reason a given wire decline carried is
currently docs-only (tracing unused in src/).

Verification: cargo test 104 rows green; --all-features green;
clippy ×2 -D warnings green; fmt clean; cargo doc 0 warnings;
taskgraph validate 19 tasks.
2026-10-28 11:53:51 +00:00

7.1 KiB

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
w2-loopback-harness Wave 2 — the loopback harness (in-process client/server test pair) completed
w2-auth-bridge
w1-test-doubles
moderate medium phase implementation

Description

Land the in-process client/server test pair every later contract test runs through — the decline matrix, every mapping's contract tests (wave 3), and the client-direction smokes all drive the crate through this harness. In-memory doubles from wave 1; real-tool smokes ride the mapping waves (wave 3+) and the e2e wave (wave 5).

  • Server side — the SshAdapter from w2-server-mount over an in-process transport. The transport choice (tokio duplex pair vs 127.0.0.1:0 TCP loopback) is pinned here — both work with run_stream/russh's client connect; decide on determinism and portability and record the choice.
  • Client side — a russh client driven directly (the wave-4 dial surface does not exist yet; the harness builds the russh client config from the doubles): in-memory key from the ed25519 fixtures (FIXTURE_KEYS), the MemHostKeyPolicy double with the permissive test posture (with_default(true) — the accept-all posture exists only as a test choice, never a crate built-in), the keepalive posture from Limits.
  • Harness helpers — the vocabulary the contract tests speak: authenticate with a fixture key (and observe auth failure shapes), open a channel type (and observe the open-failure reason), send a session request (and observe success/failure), await exit-status / close. Helpers stay thin — the russh client API is the vocabulary; the harness removes boilerplate, not semantics.
  • Convention — lives with the wave-1 doubles under tests/common/ (or a sibling harness module — same convention, decide and record); documented as the contract-test vehicle for the crate.

The harness's first consumer is its own acceptance test: a full in-process connect → authenticate (fixture key through MemIdentityProvider) → authenticated-session observation, proving the wave-1 doubles work live through the wave-2 stack.

Acceptance Criteria

  • In-process server/client pair connects and authenticates end to end (fixture key → MemIdentityProvider → authenticated session) with no TCP port dependency (or the TCP choice recorded, if taken)
  • Harness helpers cover: auth (success + failure observation), channel open (success + open-failure reason), session requests (success/failure), exit-status/close observation
  • The transport choice and module placement are recorded
  • The harness is documented as the contract-test vehicle; the wave-1 doubles are exercised live through it
  • cargo test, cargo clippy --all-targets -- -D warnings, cargo fmt --check pass

References

  • docs/plans/implementation.md §Wave 2 (the loopback-harness bullet)
  • docs/architecture/mapping.md §Decline matrix (the rows the harness will drive)
  • docs/research/poc-1-auth-bridge.md (the decline-observability facts the helpers must be able to observe)
  • tests/common/mod.rs (the wave-1 doubles this harness drives)

Notes

  • Transport decision (recorded, in tests/common/harness.rs module docs): tokio duplex pair (64 KiB/direction) + russh's connect_stream. russh's real codec/kex/transport run over the pair verbatim; the one gap versus real sockets (no OS-socket semantics) is covered more cheaply by the real-tool smokes of wave 3+ — TCP loopback's listener lifecycle and timing flakiness buy nothing the harness needs. Deterministic, no port, no collision.
  • Placement decision (recorded, same docs): tests/common/harness.rs, a sibling module of the wave-1 doubles reached with the same mod common; convention — test surface, never a public API export. Unlike the doubles it is a vehicle convention: later waves call common::harness::drive rather than re-deriving a mount/connect pair.
  • russh 0.65 behavior surfaced by the harness (a dispatch-core input): each un-implemented server channel_open_* hook drops its ChannelOpenHandle → the client observes ChannelOpenFailure::AdministrativelyProhibited — the inherited fail-closed decline on the current skeleton stack. The harness's authenticated-session row matches the reason generically (Error::ChannelOpenFailure(_)), so w2-dispatch-decline-core's reason rows cannot break the vehicle.
  • Scope posture: the harness is a test-vehicle task — no src/ change. A russh-server behavior note rides the row docs only.

Summary

Landed the loopback harness (tests/common/harness.rs, wired through tests/common/mod.rs's pub mod harness), with both pinned decisions recorded in its module docs: tokio duplex pair as the in-process transport (russh::client::connect_stream against the mounted SshAdapter::handle; recorded over TCP loopback for determinism and no-port portability) and sibling-of-the-doubles placement under tests/common/ as the crate's contract-test vehicle convention.

Contents:

  • HarnessClientHandler — the russh client handler bridging the MemHostKeyPolicy double into check_server_key, computing the decision input ([PresentedHostKey]) exactly as the wave-4 dial wiring will: endpoints labeled LOOPBACK_HOST / LOOPBACK_PORT (the no-port marker 0), the fingerprint on the crate's single fingerprint path.
  • client_config — the keepalive/wall-clock posture from Limits (interval/max verbatim, inactivity always set).
  • Server-side assemblers — serve_config / adapter from the wave-1 doubles.
  • drive → LoopbackSession — the mount-spawn + connect pair, with clean-disconnect unmount.
  • Auth helpers — authenticate_publickey (fixture plumbing removed, AuthResult verbatim) and remaining_methods (the advertisement half of the failure shape).
  • Channel helpers — open_session_channel (open-failure reason observed in the Err), request_exec / request_shell / request_subsystem, and the message observations: next_message, await_exit_status, await_close.

Acceptance proof (tests/loopback_harness.rs, 6 rows, every row a Contract stamp:): full in-process connect → fixture-key auth through MemIdentityProvider → authenticated-session observation (host-key decision log carrying bravo's fingerprint under the loopback labels; a channel open answered with an observed decline reason); the auth failure shape with {publickey} remaining; the fail-closed host-key verdict as a clean dial decline; the pinned-rule trust-anchor case; and the Limits → client-config posture row.

Live-exercised doubles: MemIdentityProvider, MemHostKeySource, MemHostKeyPolicy (both verdicts). TestVault stays on the custody rows (fixture keys are unencrypted by contract). The session-request and exit-status/close helpers are exercised by construction this wave — channel opens don't confirm on the decline-only skeleton; their live rows arrive with w2-dispatch-decline-core.

Verification: cargo test (81 passed across 6 binaries), cargo clippy --all-targets -- -D warnings, cargo fmt --check — all green.