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.
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 |
|
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 →Identityresolutions 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 -lffingerprints, 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, andHostKeyPolicydoubles 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 --checkpass
References
- docs/architecture/decisions/004-auth-dispositions.md (the
IdentityProvidersurface) - 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 thatunlock_newarms in-memory. The double is a wrapper (TestVaultintests/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 productionVaultKeySource/VaultHostKeySourceseams, 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-keygenat 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 intests/vectors/fingerprints.txtand in the common module docs. - Location decision (recorded): the doubles live only under
tests/common/mod.rs, reached withmod common;(the alkgit convention, adapted). Nothing doubles-related is reachable fromlib.rs; no feature gates them.src/-unit tests that need the fixture material read the committed files viainclude_str!(src/key_source.rstests), never a copy; the single fixture set is shared by the src-side and tests/-side suites. dead_code is allowed module-wide intests/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 insrc/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 intests/vectors/fingerprints.txt, and the two sets unified into one fixture table,FIXTURE_KEYS(committed.key+.pubfiles, 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 greppableContract stamp:items citing the pinning ADR §, andgrep -rn "Contract stamp:" tests/is the inventory (11 stamps across the 11 landed rows). - No
src/non-test changes:src/key_source.rschanged only inside its#[cfg(test)]module (fixtureinclude_str!s + one parity row);tests/common/mod.rsandtests/doubles.rsare 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.