Files
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

139 lines
6.9 KiB
Markdown

---
id: w1-test-doubles
name: "Wave 1: test doubles (the in-memory seams)"
status: completed
depends_on: [w1-error-model, w1-key-sources, w1-host-key-policy]
scope: narrow
risk: low
impact: component
level: 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.