Files
alkssh/tasks/implementation/w1-test-doubles.md
T
glm-5.3-flash f4b5f896b4 docs: sweep em dashes from all markdown prose
Renames the written-usage rule into practice across the contract surface:
docs/ (architecture, research, plans, sdd), tasks/ (all 43 task files,
taskgraph validate passes), AGENTS.md, README.md, CHANGELOG.md, and the
.opencode/agents/ prompts. Every replacement is a sense-preserving
rewrite (colons, semicolons, commas, parentheses, sentence splits, varied),
never a mechanical swap; no spaced hyphens were substituted. Enum fields,
frontmatter, tables, wire data, and cited identifiers survive verbatim;
en dashes inside numeric ranges keep their meaning.

Verification: taskgraph validate (43 tasks), repo grep clean (single
exempt hit: the verification command's own search pattern in AGENTS.md),
cargo test, clippy --all-targets -D warnings, fmt --check, doc all green.
2026-11-04 20:28:00 +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, with the task pinning 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/, used for 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 §, and 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; the 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 plus an explicit default verdict; fail-closed by default and 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.