Plan: Standalone-SHINES Intake Test-Coverage Remediation
On this page
This plan remediates the test-coverage gaps found by an exhaustive audit of the standalone-SHINES intake path (the epic &58 / ADR-042 deliverable). The audit ran 12 per-component auditors (each sweeping happy/sad/evil/chaos × unit/integration/e2e) plus an evil/chaos completeness critic and a dedup/false-positive critic, producing 258 raw gap cells that collapse to roughly 50–70 concrete test functions + one fault-injection harness + one pre-push gate change + one design call.
Status
| Wave | Description | Status |
|---|---|---|
0 |
Fault-injection harness ( |
Done (2026-06-25) — !799 |
1 |
Sad/happy unit fills — mapper father-symmetry + parametric RefVal arms + multi-person routing + edges; crosswalk/narrative/fidelity |
Done (2026-06-25) — !800 |
2 |
Evil corpus — parametric malformed-JWS + kid-enumeration uniformity (signed path); per-field injection corpus; SSN-unicode; PII-in-errors |
Done (2026-06-25) — !801 |
3 |
Chaos — SHINES + keyring fault propagation; register-proxy relay; keyring concurrency/crash/restart; rate-limit burst; route/profile visibility |
Done (2026-06-25) — !803 |
4 |
Process — bake SDK suites (pytest + vitest) into the pre-push |
Done (2026-06-25) — !804 |
5 |
E2E top-ups — SHINES-mock-5xx surfaces a redacted form error; duplicate-kid 409 at keygen; document the secure-context keygen limit |
Done (2026-06-25) — !805 |
Epic: &59
Issues: #701 (W0) / #702 (W1) / #703 (W2) / #704 (W3) / #705 (W4) / #706 (W5); W4 relates #698 (SDK gating) + #700 (Python signer)
Scope depth: literal 100% — every audited cell at P1, P2, and P3
Branch (per wave): feature/intake-coverage-*
Context
The epic &58 runtime is functionally complete and proven against the SHINES mock + the real keyring sidecar, but the audit found coverage that is strong on unit happy/sad, thin on evil at the integration layer, and nearly absent on chaos (fault-injection) everywhere. Gap distribution: chaos 95 · evil 71 · sad 58 · happy 34; by layer integration 140 · unit 115 · e2e 3. The most consequential finding is that no test injects a SHINES or keyring fault — a SHINES outage is a likely production event and its handling is unverified end-to-end.
This is a test-and-resilience plan, not a feature plan. It does not resolve the external go-live data dependencies (#691 SHINES API contract, #692 RefVals, #693 required-field set) — those remain mocked/placeholder by design. It DOES add the test infrastructure (a fault-injection mock mode) those resilience tests need.
Scope
In scope: unit + integration + e2e tests across all four axes for: the CpsRequest mapper, the RefVals
crosswalk, ExtraFields/NarrativeView parse+sanitize, the boot fidelity gate, ShinesSink, the signed-submit
handler + register proxy + shared JWS verify, validation/Channel/relevance, config+boot+router, the
craig-intake-keyring sidecar, and the three SDKs; a craig-mock-server SHINES fault-injection mode; baking the
SDK suites into the pre-push gate.
Out of scope: the real SHINES API contract / RefVals / required-field set (#691/#692/#693); SHINES-outage buffering (#215 local-outbox — a feature, not a test); mutation-testing depth (tracked separately); any non-intake service.
Design
D1. Axes + layers (the coverage contract)
-
happy — valid input → correct success + correct output value/shape.
-
sad — well-formed but invalid / business-rejected input → correct typed 4xx (validation, required-when-relevant, reporter-type channel, enum rejection, missing/duplicate parent, unmappable RefVal).
-
evil — adversarial: injection (HTML/script/control-char/unicode), oversized/DoS, malformed/forged/replayed/ expired JWS, auth bypass, kid enumeration, PII in errors/logs, secret leakage.
-
chaos — fault-injection/resilience: upstream (SHINES/keyring) outage/timeout/5xx/non-JSON, concurrency/races, crash-recovery + atomic-write durability, cache staleness, restart/replay, clock-skew/iat window, rate-limit under load, route/profile visibility.
-
Layers: unit (in-crate
#[cfg(test)]/ SDK unit), integration (devstack-gatedtests/over real HTTP), e2e (Playwright). E2E stays deliberately thin — the HTTP-level integration tests carry the load; the browser keygen’scrypto.subtleneeds a secure context, whichhost.docker.internalis not, capping e2e crypto.
D2. The fault-injection harness (Wave 0 — prerequisite for Wave 3)
tools/craig-mock-server/src/shines.rs currently always returns 200 {record_id,status:"accepted"} — it cannot
exercise any chaos path. Add a fault-injection mode so a test can make the mock return a configured fault:
a Fault enum (Status(u16), Timeout/hang, NonJsonBody, WrongStatusField, Malformed), selected per
request via a header (e.g. X-Mock-Fault: status=500) or a spawn_for_test-configured state — mirroring the
existing craig_mock_server::spawn_for_test() ephemeral-port harness. This unblocks every Wave-3 ShinesSink
and (where the mock fronts the keyring) keyring-fault test. Keep the default behavior byte-identical (no fault
header ⇒ the current 200 path), so existing tests are unaffected.
D3. CRAIG→SHINES idempotency (design decision, settled in Wave 0)
received_request_id is minted per submission but never carried onto the CRAIG→SHINES POST, so a future retry
(once #215 buffering lands) could double-submit. Decision: there is no retry today, so no live bug; we
document the idempotency requirement on the proposed contract (the CpsRequest gains an idempotency key when
#691 is finalized + #215 retry is built) and add a regression test asserting the sink performs exactly one
POST per forward() (no accidental retry). We do NOT add an idempotency key to the wire now (that belongs with
#691/#215). Recorded as an errata/decision note in the proposed-API spec.
D4. SDK suites in the pre-push gate (Wave 4 — re-scopes #698)
CI is unreliable (recurring 2xlarge disk OOM), so the pre-push cargo xtask validate is the canonical
functional gate — the SDK suites belong there, not (only) in CI. This box has no host node/npm/python
toolchain, so the SDK steps run in Docker (raw docker run, consistent with the e2e pattern): a python:alpine
image for pytest sdks/python/tests and the existing Playwright/node image for vitest sdks/typescript/tests,
added as a new cargo xtask validate step (after the nextest step, before/with the docker-build step), gated
(blocking) and skippable under --skip-docker like the other Docker-dependent steps. Sequencing: the #700
Python-signer fix lands FIRST (else the Python signed tests fail the new gate), then the gate is added with the
Python signed-submit test re-enabled.
D5. Parametric corpora (evil)
Evil coverage uses parametric/table tests (the project’s evil-input-corpus pattern), not one-off cases: a
malformed-JWS corpus (alg=none / HS256 confusion / 2-segment / bad-base64 header·sig / missing jti·iat / tampered
payload / forged sig / wrong key / expired·future iat) all asserting a uniform bare 401 (kid-enumeration
defense); and an injection corpus over all 10 ExtraFields + 9 NarrativeView free-text fields
(HTML/script/control-char/unicode) asserting strip-before-forward, with the SSN field asserting NO transform
(J4). Per-field breadth is P2/P3 defense-in-depth (the sanitize mechanism is already unit-tested); literal-100%
includes it.
Waves
Each wave is one issue → one MR under the standard cadence (branch off fresh main → implement → dev reload
where server-behavior/devstack-gated tests are added → full cargo xtask validate → fresh-eyes J1–J8 over the
staged diff → skip-CI merge → close issue → prune). Every new test carries an // @axis: tag.
Wave 0 (harness + decision)
Files: tools/craig-mock-server/src/shines.rs (+ lib.rs if the Fault config surfaces through
spawn_for_test). Per D2 + D3. Verify: the mock returns each configured fault; default path unchanged
(existing ShinesSink round-trip test still green); the idempotency decision recorded in
interfaces/shines-cps-intake-proposed-api.adoc + a sink "exactly-one-POST" unit test.
Wave 1 (sad/happy unit fills)
Files: services/craig-intake/src/backend/shines/mapper.rs (#[cfg(test)]), crates/craig-reference/src/shines.rs,
backend/shines/{narrative.rs,fidelity.rs}. Concrete tests: duplicate_father + missing_required_father
(symmetry with mother); a parametric unmappable_refval table over marital/language/gender/reporter-type;
multiple_children_all_mapped + multiple_other_household_members; incident-date branches (datetime / date-only
fallback / both-absent); compose_address edges (all-empty / partial / unicode); map_ssn edges (whitespace /
all-zeros / unicode-digit→reject); child-no-gender; NarrativeView parse-all-9 + default-all-None; fidelity
sentinel-detected-fails-closed. A mapper round-trip proptest (arbitrary valid submission → maps without
panic, wire keys stable — the optional motherInfo/fatherInfo keys appear iff that slot is designated).
The sentinel-detected-fails-closed test required a small testability extraction in fidelity.rs (a generic
ensure_nonsentinel helper the crosswalk-totality check now delegates to — no behavior change). The
crates/craig-reference/src/shines.rs fill is race / marital-status / reporter-type RefVal uniqueness
invariants (parity with the existing language-uniqueness test; guards the #692 placeholder→authoritative swap).
Verify: cargo nextest run -p craig-intake -p craig-reference.
Wave 2 (evil corpus)
Files: services/craig-intake/tests/api/signed_submit.rs (or a new signed_evil.rs), api/validation.rs
tests, backend/shines/mapper.rs tests. Per D5: the parametric malformed-JWS corpus (uniform-401
kid-enumeration byte-identical-body assertion on the signed path); the per-field injection corpus over all
ExtraFields + NarrativeView text (strip-before-forward; SSN untouched); SSN unicode-digit rejection (evil);
PII-not-in-error assertions (malformed DOB / <script> name → value absent from the 4xx body).
Verify: cargo nextest run -p craig-intake + --run-ignored all.
Layer choices (living-spec, as built): the malformed-JWS corpus lives in signed_submit.rs (reusing its
keyring register_and_approve / post_signed helpers — no separate signed_evil.rs) as one parametric
integration test firing 13 failure modes (2-segment / bad-base64 header·sig / alg=none / HS256 / missing
jti·iat / expired·future iat / unknown-kid / wrong-key / forged-sig / tampered-payload) and asserting a uniform
byte-identical 401 (the standalone-path analogue of jws_error_uniformity.rs). The per-field injection corpus
is unit-level (validation.rs sanitize tests over all 10 ExtraFields + all 9 NarrativeView fields) — the
mechanism is already integration-proven, so per-field breadth is fastest+total at the unit layer. SSN
unicode-digit rejection is unit-locked in Wave 1 (mapper::map_ssn); Wave 2 adds the J4 invariant that the
sanitize pass leaves ssn byte-identical (so the unicode digits reach the mapper intact). PII-not-in-error is a
new pii_not_in_errors.rs integration test over the shared prepare_submission pipeline (public path).
Wave 3 (chaos — the resilience core; depends on Wave 0)
Files: services/craig-intake/tests/api/ (new chaos tests), backend/shines/sink.rs tests,
services/craig-intake-keyring/tests/. Using the Wave-0 mock faults:
ShinesSink — SHINES 4xx/5xx/timeout/non-JSON/status≠accepted → correct redacted 500 (no PII/body leak);
signed submit — keyring lookup/replay 5xx/timeout → 500 not 401; register-proxy relay of keyring 5xx/timeout/
409/429; signed-path pending/revoked-key cold-path → 401; iat too-old/too-future → 401 (signed path);
rate-limit burst → 429 (signed + public); route/profile visibility (status route absent under Shines,
/signed/v1 absent under None); keyring — concurrent register/approve races (exactly-one-wins, disk
stays valid JSON), atomic-write crash safety (recovery test), replay-dedup across restart, max_keys
429, retention-window pruning. Verify: dev reload → cargo nextest run -p craig-intake -p craig-intake-keyring --run-ignored all.
As built (living-spec): much of this wave was already covered by earlier waves + the keyring’s own suite, so
W3 adds only the genuine gaps. Already shipped: ShinesSink 5xx/timeout/non-JSON/status≠accepted→500 (W0);
iat too-old/too-future→401 (W2 corpus); the keyring store’s concurrency / atomic-write-no-residue / reload-across-
restart / max_keys / retention-pruning / replay-dedup (its existing store.rs suite). Discovered + fixed
first (own MR, user-directed): the keyring-lookup-outage→401 bug → #707 (MR !802) corrected it to 500; the
"keyring lookup 5xx→500" AC is satisfied by #707’s unit tests. W3 net-new: ShinesSink 4xx redaction
(forward_redacts_shines_4xx_without_leaking_body — a 400→400, a non-semantic 404→500, body redacted both — the
AC’s "4xx→500" holds only for non-semantic codes; a 400 is a client-rejection→400 per redact_upstream_error’s
HTTP semantics); the replay outage path (`JwsReplayClient transport-classification unit test
verify_signed_jws’s visible Transport→500 map); register-proxy relay status mapping (`map_keyring_register_error
unit: 400/409/429 relayed, 5xx redacted→500); pending + revoked cold-path→401 (live-keyring integration,
no-prior-lookup so the 60s signer cache is never populated); route/profile visibility (signed route 401-on-Shines
/ 404-on-None; status route bare-404-on-Shines / ProblemDetails-404-on-None); keyring exactly-one-wins on a
concurrent same-kid register race. Test mechanism (open-question resolution): keyring/registry faults use the
unreachable-SignerAuthClient/JwsReplayClient + verify_signed_jws unit pattern (the dummy_clients seam) —
no craig-mock-server keyring routes / wiremock. Deferred (tracked): the public/signed HTTP-level 429 burst
is unit-covered (rate_limiter_throttles_burst_per_ip) but the HTTP-path assertion is deferred to #708 — an
IP-keyed limiter on a shared devstack instance would poison sibling tests; the per-partner HTTP-429 is already in
per_partner_rate_limit.rs.
Wave 4 (process — SDK suites in the pre-push gate; re-scopes #698)
Files: sdks/python/ (the #700 signer fix + re-enable the signed-submit test), xtask/src/cmd/validate.rs
(+ docker.rs helpers) for the Docker-run pytest+vitest step, .githooks/ if needed. Per D4. Sequencing:
#700 first, then the gate. Verify: cargo xtask validate runs both SDK suites (Docker) and fails on a red
suite; --skip-docker skips them; a deliberately-broken SDK test is caught by the gate.
As built (living-spec): #700 had two joserfc>=1.0 breakages, both masked because the suite was not in
CI: (1) the strict header registry rejected the required jti/iat protected headers — fixed by passing an
extended JWSRegistry (registers jti/iat, pins algorithms=["ES256"]) to serialize_compact; (2) the test
keypair helpers used the pre-1.0 ECKey.as_dict(is_private=…) kwarg, silently swallowed under 1.x (it exported a
public key, no d) — renamed to as_dict(private=…). The deferred happy-path lockstep
(test_submit_report_signed_sends_jws_no_apikey) now lands. The gate is a new cargo xtask sdk-test subcommand
(the #698-anticipated entrypoint) running pytest in python:3.13-alpine + vitest in node:22-alpine; the
per-suite Docker mechanism (docker::run_sdk_suite) bind-mounts sdks/ read-only and copies it into the
container so the shared sdks/test-vectors/ relative path resolves and the host tree stays pristine. It is folded
into validate (a [11b/13] phase, skipped under --skip-docker). Correction to the §D4 assumption: the
pre-push hook runs validate --skip-docker (it skips the slow compose build; e2e covers Docker), so the
canonical pre-push coverage comes from a separate cargo xtask sdk-test step added to the hook’s
project-specific region (outside the managed markers; SKIP_SDK=1 opt-out), not from the in-validate
phase. #698 is thereby satisfied (CI → pre-push) and closed by this MR.
Wave 5 (e2e top-ups)
Files: tests/e2e/specs/intake-keygen-signed.spec.ts + intake-embedded-ui-shines.spec.ts,
tools/craig-mock-server/src/shines.rs (devstack-reachable fault toggle if needed). A SHINES-mock-5xx run that
asserts the form surfaces a redacted error (not a leak); a duplicate-kid 409 at /keygen; a spec comment
a one-line doc documenting the crypto.subtle secure-context limitation. Verify: cargo xtask e2e --no-refresh — --project=intake-ui-shines.
As built (living-spec): the "devstack-reachable fault toggle" is a one-shot fault-injection control endpoint on the host-owned SHINES mock — POST /shines/_control/fault (body {status}, default 503) arms the next cps-request to return that status with a deliberately leaky sentinel body, then auto-clears; DELETE clears an unconsumed fault. This is deliberately separate from the crate’s existing fault module (a spawn-time, every-route layer for in-process adapter tests — not runtime-toggleable over HTTP). One-shot + auto-clear makes it poison-free; combined with the e2e’s workers: 1 serial execution + a finally clear, it can’t leak into a sibling spec (so this wave did NOT need the deferred-#708 isolation). A new CRAIG_SHINES_MOCK_URL is derived in xtask (build_env_for_ports + write_ports_env, mirroring CRAIG_INTAKE_KEYRING_URL) and added to the craig-e2e compose env so the Playwright container reaches the mock. The redaction proof is the absence of the mock’s leak sentinel from the form’s .errors-box. The dup-kid 409 + the secure-context limitation are HTTP-level/doc top-ups (the secure-context note lives on the keyring page, the constraint already governed #690’s HTTP-level approach).
Verification (per wave)
cargo fmt --all; cargo xtask validate (fmt/clippy/budgets/axis/machete/check-docs/plan-lint + nextest
--run-ignored=all against the live devstack — dev reload first for any devstack-gated additions); a fresh
Explore subagent answers J1–J8 over the staged diff. The program is complete when every audited cell is
covered (or explicitly waived with a recorded reason) and cargo xtask validate is green with the SDK suites
gated.
Appendix — audit provenance
Source: a 14-agent coverage workflow (2026-06-25) — 12 per-component auditors + an evil/chaos completeness critic
+ a dedup/false-positive critic (which rated the raw findings ~85% trustworthy and removed 3 false positives:
the sanitize-mechanism, NarrativeView-integration-coverage, and Predicate::Not cells). Raw totals: 258 gap
cells; chaos 95 / evil 71 / sad 58 / happy 34; integration 140 / unit 115 / e2e 3; effort S 108 / M 128 / L 22.
The waves above are the de-duplicated, severity-normalized distillation.