| name | wicked-vault-record-evidence |
| description | Record a claim-backing artifact in the vault and attach a deterministic verifier. Use when capturing evidence that "tests pass", "build clean", a commit exists, or a file's contents back a claim โ and when replacing stale evidence via supersede. Covers --run vs --artifact, verifier syntax, and contract pinning. |
wicked-vault:record-evidence
Capture an artifact, hash it (so naive/accidental mutation is detected on
re-derivation), and attach a verifier that can re-derive its verdict later.
The vault does the capture itself โ it never trusts a claimed status (G4). The
hash detects mutation; the committed git history is the durable tamper-evidence
(see the README "Tamper detection" section โ the envelope hash is unkeyed, so it
is not a defense against a determined local writer).
When to use
- Backing a claim with evidence: "tests pass", "build clean", "no secrets",
"commit landed", "config has the required field".
- Replacing stale evidence with a fresh artifact (use supersede, below).
Two ways to capture
1. Run a command and capture its result (--run)
The vault executes the source command and stores {command, exit_code, stdout, stderr, captured_at} as the payload.
npx wicked-vault record \
--scope checkout --phase build --claim tests-pass --kind test-run \
--source "npm test" --criteria "all unit tests pass (exit 0)" \
--verifier "exit_code_eq:0" --run
2. Hash an existing file (--artifact)
The vault reads the file and stores its bytes as the payload.
npx wicked-vault record \
--scope checkout --phase build --claim coverage-report --kind file \
--source "coverage/summary.json" --artifact coverage/summary.json \
--criteria "line coverage is at least 80%" \
--verifier "jq_pred:.total.lines.pct >= 80"
record requires either --run or --artifact, and always
--criteria.
Required fields
| Flag | Meaning |
|---|
--scope | the unit the claim is about (e.g. a service, module, PR) |
--phase | lifecycle phase (e.g. build, review, release) |
--claim | claim id this artifact backs (e.g. tests-pass) |
--kind | artifact kind (e.g. test-run, file, commit) |
--source | the command (--run) or path/description (--artifact) |
--criteria | mandatory โ the acceptance criteria this evidence claims to clear; inline text or @file. Hashed into the envelope and frozen to the evidence (G10) |
--verifier | optional deterministic sub-check (see below) โ a composable signal an independent evaluator can cite |
Acceptance criteria are mandatory (G10/D1)
Every artifact must state the bar it claims to clear โ record rejects evidence
with no --criteria. The criteria are hashed into the envelope, so the bar is
frozen to the evidence: the same evidence can never later be judged against
weaker criteria (anti-downgrade).
The trusted path is contract-pinned criteria: when declare-contract pins
criteria for the claim, a matching --criteria is stamped
criteria_authored_by: contract. Worker-supplied criteria are stamped
record (a weaker provenance class โ see wicked-vault:analyze-evidence's
threat model). A --criteria that contradicts a contract pin is a G8 downgrade
and is rejected.
The independent judgment of whether the evidence meets these criteria is the
job of wicked-vault:analyze-evidence (the judgment tier), not record.
What good criteria look like (especially for a high-blast-radius claim)
The vault freezes and binds whatever criteria you supply, but it cannot make
weak criteria strong โ a worker who writes a lax bar gets a guaranteed pass
(the judgment-tier T1 โ lax-bar self-grade risk in
wicked-vault:analyze-evidence). The single best defence is a contract-pinned
criterion (criteria_authored_by: contract, authored separately from the
worker). Beyond that, raise the floor on what the criterion asserts.
A criterion is weak when it is a vibe ("works", "looks good", "tests pass"). It
is strong when it is measurable, owned, and โ for a dangerous change โ names
how the change is undone. For a low-risk claim, a single measurable line is
enough (all unit tests pass (exit 0), line coverage >= 80%). For a
high-blast-radius claim (a schema/data change, an integration cutover, a
production transition), the criteria SHOULD assert all three of:
- Verification โ concrete, automatable evidence the change is correct:
row counts / checksums / reconciliation queries, a golden-master diff = 0,
contract tests green, a performance check against the known hotspots. Name
which check and who signs the reconciliation. Not "data migrated" but
"row counts match source ยฑ 0 and the reconciliation report is signed".
- Tested rollback โ a rollback procedure that has been rehearsed, not
merely written, with its trigger (who decides to roll back, on what
signal, by when) and the point of no return stated explicitly (after
which step rollback is no longer possible). "Rollback tested, not just
written" โ a dry-run record is itself recordable evidence.
- Enumerated blast radius โ every reader and writer of the affected
surface listed explicitly, not assumed, and how each is protected during
the change. An un-enumerated consumer is an unmitigated failure mode.
This shape is lifted from a migration factory's gated-change specs (the
verification / rollback / blast-radius triad of its db-endpoint-update and
cutover templates, and the "rollback tested, not written" cutover gate). It is
authoring guidance only โ the vault adds no gate, policy, or risk-class
behaviour from it; it stays a deliberately narrow evidence primitive. Use it to
write a stronger --criteria (or, better, a stronger contract pin in
wicked-vault:cross-check-evidence).
Worked example โ a strong criterion for a risky DB claim (@file keeps it
readable and reviewable in the diff):
Verification: post-migration row count of orders matches pre-migration
source ยฑ 0; checksum of (id,total) reconciles; recon report signed by DBA.
Rollback (TESTED): dry-run rehearsed 2026-06-10; trigger = error rate > 1%
in first 30 min, ops on-call decides; restore from pre-cutover snapshot;
POINT OF NO RETURN = after the dual-write window closes (step 7).
Blast radius: readers = [reporting-etl, mobile-api, billing-cron];
writers = [checkout-svc]; each pinned to the compat view for the window.
Verifier syntax
--verifier "kind:arg" (or a JSON object for advanced params). The v1 core
verifiers are deterministic and pure (G7):
| Verifier | Example | Passes when |
|---|
exit_code_eq | exit_code_eq:0 | captured exit code equals N (requires --run) |
regex_match | regex_match:[0-9a-f]{40} | pattern matches stdout+stderr / file text |
not_contains | not_contains:(?i)error | pattern is absent |
jq_pred | jq_pred:.ok == true | jq -e on the JSON payload is truthy (needs jq) |
commit_exists | commit_exists:<sha> | the git commit exists in the repo |
llm_eval is intentionally not a verifier kind โ a nondeterministic judge
would falsify the purity guarantee (G7).
Output
{ "id": "...", "envelope_hash": "...", "status_at_record": "pass", "status_detail": "exit_code=0" }
status_at_record is informational only โ verify and cross-check never
read it; they re-run the verifier (G3). The exit code is 0 on a successful
record (the recording succeeded), regardless of the verdict โ to gate on the
verdict, use verify or cross-check.
wicked-bus event
If wicked-bus is installed, record publishes wicked.test.evidence.recorded
(domain wicked-testing, subdomain vault.record) fire-and-forget โ the payload
is a single recorded envelope, distinct from the run-level
wicked.test.evidence.captured. supersede
publishes wicked.evidence.superseded. Emission is a silent no-op when the bus
is absent or WICKED_VAULT_NO_BUS=1 โ it never affects the output or exit code.
Replacing evidence (supersede)
Evidence is append-only (G6). To replace a stale artifact, record a replacement
and link it โ the old entry is flipped to superseded, never deleted:
npx wicked-vault supersede <old-id> \
--scope checkout --phase build --claim tests-pass --kind test-run \
--source "npm test" --verifier "exit_code_eq:0" --run
Crash-safe ordering: the replacement is written and confirmed on disk before
the old entry is flipped, so there is always an active artifact for the claim.
Contract pinning (G8)
If a contract pins this claim (see wicked-vault:cross-check-evidence), record rejects
a downgrade โ a kind, source, or verifier that differs from the pin throws
a G8 pin violation. This stops a weaker verifier from being swapped in to make
a claim pass.