Six implementation tasks + the gate under tasks/implementation/,
chained sequentially (the auth hooks, dispatch, and channel cap all
edit the same per-connection Handler impl):
- w2-server-mount: SshAdapter::handle mount + russh Config
construction from the serve-config (honest advertisement, host-key
decrypt-at-construction, the Limits knob mapping) + the Handler
skeleton at fail-closed defaults
- w2-auth-bridge: the ADR-004 dispositions (ed25519-only explicit
decline, fingerprint-only resolution, explicit declines for every
non-publickey method)
- w2-loopback-harness: the in-process client/server contract-test
vehicle over the wave-1 doubles
- w2-dispatch-decline-core: parse-only dispatch + the fail-closed
decline core, contract-tested per feature build
- w2-spawn-return: the structural constraint pinned by test + the
exit-reporting mechanics via the cloned Session::handle()
- w2-bounded-resources: the in-crate concurrent-channel cap at the
handler surface
- review-wave-2: the gate
The plan carries the wave-2 decomposition record with four
interpretations flagged for the gate (skeleton-level git probe,
declining routing stubs, the adapter error surface, the harness
transport choice). taskgraph validate: 19 tasks, clean.
Review task record: the line-by-line ADR/OQ gate reading, the three
flagged interpretations confirmed (sibling-dep timing, vault-double
shape, policy verdict type), the minor inline fixes, and the green
verification summary. Wave 2 decomposition unblocked.
The `w1-test-doubles` task: the in-memory seams every later wave's
contract tests run against, behind their production seams.
- tests/common/mod.rs — the doubles, test-only (nothing reachable from
lib.rs, no feature): MemIdentityProvider (registered fingerprints ->
alkcall Identity resolutions over the IdentityProvider trait; token
path resolves none per ADR-004), TestVault (a wrapper over alkvault
0.1's shipped in-memory construction — resolving the flagged
vault-double question: wrap, not re-impl), MemHostKeyPolicy
(exact (host, fingerprint) rules + explicit default verdict,
fail-closed by default, inspectable decision log), and the fixture
table. Module docs record the location/vault/fixture decisions and
the row/stamp discipline (greppable Contract stamps).
- tests/vectors/ — one unified fixture set: four ssh-keygen-minted
ed25519 keypairs (committed .key + .pub + fingerprints.txt recorded
from ssh-keygen -lf, OpenSSH_9.6p1). The previous inline PEM
constants in src/key_source.rs were rejected by OpenSSH itself
(russh-only decodable); all keys were re-minted and the overlapping
vector set retired — one fixture set now serves the parity tests,
the custody round-trip, and every later auth/mapping test
(src-unit tests share it via include_str!).
- tests/doubles.rs — the end-to-end rows: register -> resolve,
seal -> source -> decode (+ locked/absent/tampered postures),
host-key set in order, policy verdicts honored-and-logged.
- tests/fingerprint_parity.rs and src/key_source.rs tests moved onto
the shared fixture files; src changes are #cfg(test) only.
Verification: cargo test (33 + 9 + 5 green; --all-features also green),
cargo clippy --all-targets -- -D warnings clean, cargo fmt --check
clean, taskgraph validate ok (12 tasks).
- HostKeyPolicy trait: async, dyn-compatible (Arc<dyn> handle, the
alkcall injected-handle pattern) — decide(&self, &PresentedHostKey)
-> bool with host/port/fingerprint as the decision input
- Verdict type pinned to the russh 0.65 check_server_key shape:
bool is the trust decision; decline reasons stay with the policy
(declines are dispositions, not errors). Flagged for the wave-1 gate
- Built-in default RejectAll: fail-closed reject-all (russh's default,
kept); no accept-all option exists in the crate
- async-trait 0.1 added for the dyn-compatible async handle
(alkcall-precedented; tree-deduped). Flagged for the wave-1 gate
- Tests: default rejects; custom verdict honored; both through the dyn
handle; the whole presented context reaches the policy
Verified: cargo test (14), cargo test --all-features, cargo clippy
--all-targets -- -D warnings, cargo fmt --check, taskgraph validate
- KeySourceError with exactly five variants (Locked, Absent,
Decryption, UnknownKeyVersion, Decode) — ADR-005 decision 4's
failure matrix as distinct shapes, the vault taxonomy reshaped
never collapsed; no catch-all, no Other/Unknown
- payload shapes pinned: Locked/Absent payload-free;
Decryption/UnknownKeyVersion carry the vault's opaque messages;
Decode carries a message so the RC-pinned ssh-key error type
never enters the public API (flagged for the wave-1 gate)
- each variant doc-commented with ADR-005 semantics + fail-closed
consequence; module stays engine-blind (no vault/russh types)
- no From<VaultServiceError>: non-mappable vault variants would
force a collapse; w1-key-sources maps explicitly at its seam
- exhaustive-match + Display/Debug/Clone unit tests
Verified: cargo test (default + all-features), cargo clippy
--all-targets --all-features -- -D warnings, cargo fmt --check,
cargo doc --no-deps, taskgraph validate