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.
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 |
|
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
SshAdapterfromw2-server-mountover an in-process transport. The transport choice (tokio duplex pair vs 127.0.0.1:0 TCP loopback) is pinned here — both work withrun_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), theMemHostKeyPolicydouble 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 fromLimits. - 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 --checkpass
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.rsmodule docs): tokio duplex pair (64 KiB/direction) + russh'sconnect_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 samemod common;convention — test surface, never a public API export. Unlike the doubles it is a vehicle convention: later waves callcommon::harness::driverather 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 itsChannelOpenHandle→ the client observesChannelOpenFailure::AdministrativelyProhibited— the inherited fail-closed decline on the current skeleton stack. The harness's authenticated-session row matches the reason generically (Error::ChannelOpenFailure(_)), sow2-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 theMemHostKeyPolicydouble intocheck_server_key, computing the decision input ([PresentedHostKey]) exactly as the wave-4 dial wiring will: endpoints labeledLOOPBACK_HOST/LOOPBACK_PORT(the no-port marker0), the fingerprint on the crate's single fingerprint path.client_config— the keepalive/wall-clock posture fromLimits(interval/max verbatim, inactivity always set).- Server-side assemblers —
serve_config/adapterfrom the wave-1 doubles. drive→LoopbackSession— the mount-spawn + connect pair, with clean-disconnectunmount.- Auth helpers —
authenticate_publickey(fixture plumbing removed,AuthResultverbatim) andremaining_methods(the advertisement half of the failure shape). - Channel helpers —
open_session_channel(open-failure reason observed in theErr),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.