Files
alkssh/tasks/implementation/w1-test-doubles.md
T
glm-5.3-flash 214690b9c2 test(doubles): land the wave-1 in-memory seams + unified ed25519 fixtures
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).
2026-10-28 07:09:17 +00:00

6.9 KiB

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
w1-test-doubles Wave 1 — test doubles (the in-memory seams) completed
w1-error-model
w1-key-sources
w1-host-key-policy
narrow low component implementation

Description

Establish the test-double convention (adapted from the sibling crates) and land the in-memory seams every later wave's contract tests run against:

  • In-memory IdentityProvider — registered fingerprints → Identity resolutions over alkcall's trait; the double the auth bridge and dispatch tests resolve against.
  • In-memory vault seam — the encrypt/decrypt round-trip plus the failure postures (locked / absent / tampered) behind the same seam the key sources consume, so the custody tests run without a live vault service. If alkvault 0.1 ships an in-memory construction, wrap it; otherwise a local test double behind the source trait — the task pins which, flagged for the wave-1 gate.
  • In-memory HostKeyPolicy — a configurable record/verdict double (accept/reject per host, decision log inspectable) for the dial and acceptor tests.
  • ed25519 test-key fixtures — throwaway test keys (generated at test time or committed static PEMs — the task pins which; committed keys are throwaway fixtures, not secrets) with recorded ssh-keygen -lf fingerprints, shared with the fingerprint parity tests and every later auth/mapping test.

The convention: doubles live in-crate (test module or #[cfg(test)] utility module per the sibling posture), every protocol wave's gate runs its machines against them, and no double leaks into src/ non-test code.

Acceptance Criteria

  • In-memory IdentityProvider, vault seam, and HostKeyPolicy doubles in place behind their production seams
  • ed25519 test-key fixtures with recorded fingerprints available to the test suite
  • The doubles exercise the wave-1 seams end to end (a round-trip test: register fingerprint → resolve; encrypt → source → decode; policy verdict honored)
  • No double code outside test-gated compilation
  • cargo test, cargo clippy --all-targets -- -D warnings, cargo fmt --check pass

References

  • docs/architecture/decisions/004-auth-dispositions.md (the IdentityProvider surface)
  • docs/architecture/decisions/005-at-rest-key-custody.md (the custody seam the vault double backs)
  • docs/research/poc-2-vault-keys.md (the failure postures the vault double reproduces)
  • The alkgit test-double convention (tasks/implementation/core/ w1-test-doubles.md — reading reference, never copied code)

Notes

  • Vault-double shape (recorded, flagged for the wave-1 gate): alkvault 0.1 ships an in-memory construction — VaultServiceHandle::new() is an in-process vault that unlock_new arms in-memory. The double is a wrapper (TestVault in tests/common/mod.rs) around that construction, not a local re-impl behind the source trait: the real decrypt taxonomy, GCM integrity behavior, and rotation semantics are exercised verbatim through the production VaultKeySource / VaultHostKeySource seams, and the POC-2 §3 postures (locked / absent / tampered / wrong seed) ride the real seams.
  • Fixture decision (recorded): committed static OpenSSH-PEM test keys, minted by ssh-keygen at vector-generation time — not generated at test time (determinism, no tooling dependency). Committed private halves are throwaway fixtures by the task's own posture, documented as such in tests/vectors/fingerprints.txt and in the common module docs.
  • Location decision (recorded): the doubles live only under tests/common/mod.rs, reached with mod common; — the alkgit convention, adapted. Nothing doubles-related is reachable from lib.rs; no feature gates them. src/-unit tests that need the fixture material read the committed files via include_str! (src/key_source.rs tests), never a copy — the single fixture set is shared by the src-side and tests/-side suites. dead_code is allowed module-wide in tests/common/ (each test binary compiles the whole shared module and never exercises all of it).
  • The fixture set was unified and re-minted (flagged for the gate): the previous state had two overlapping throwaway sets — public-only vectors under tests/vectors/ (fingerprint parity) and inline private-PEM constants in src/key_source.rs — and the inline PEM constants, while decodable by russh, were rejected by OpenSSH itself (ssh-keygen → "error in libcrypto"): unusable as cross-issuer fixtures. All four keys (alpha/bravo/charlie/ delta) were freshly minted with OpenSSH_9.6p1's ssh-keygen, their fingerprints recorded byte-verbatim in tests/vectors/fingerprints.txt, and the two sets unified into one fixture table (FIXTURE_KEYS) — committed .key + .pub files, served to the fingerprint parity tests, the vault round-trip, and every later auth/mapping test. The old vector-only set was retired (its keys had no private halves).
  • The row/stamp discipline is established in tests/common/mod.rs's module docs (adapted from the sibling crates): rows state properties and carry greppable Contract stamp: items citing the pinning ADR § — grep -rn "Contract stamp:" tests/ is the inventory (11 stamps across the 11 landed rows).
  • No src/ non-test changes: src/key_source.rs changed only inside its #[cfg(test)] module (fixture include_str!s + one parity row); tests/common/mod.rs and tests/doubles.rs are the new surfaces.

Summary

The wave-1 test-double convention landed: tests/common/mod.rs carries MemIdentityProvider (registered fingerprints → alkcall Identity resolutions; token path resolves none — ADR-004's fingerprint-only disposition), TestVault (a wrapper over alkvault 0.1's shipped in-memory construction — the pinned resolution of the flagged vault-double question), MemHostKeyPolicy (exact (host, fingerprint) rules + explicit default verdict — fail-closed by default, permissive only by explicit choice — with a fully inspectable decision log), and the unified ed25519 fixture table (FIXTURE_KEYS: four committed ssh-keygen-minted OpenSSH-PEM key pairs with recorded ssh-keygen -lf fingerprints in tests/vectors/fingerprints.txt). The end-to-end rows in tests/doubles.rs run the seams through the production surfaces: register → resolve, seal → source → decode (round-trip and the locked/absent/tampered postures), verdict → honored-and-logged. src/key_source.rs and tests/fingerprint_parity.rs were moved onto the shared fixture files (one fixture set, no copies; the previous inline PEMs were discovered to be OpenSSH-rejected and were replaced by freshly minted keys). All doubles live in test-only compilation (tests/), with nothing doubles-related reachable from the public API. Verified: cargo test (33 + 9 + 5 green; also --all-features), cargo clippy --all-targets -- -D warnings, cargo fmt --check clean.