Maintaining canonical CBOR fixtures
The files under auths-proof/fixtures/v1/ are not incidental test snapshots.
They are language-neutral examples of the protocol contract. Another
implementation should be able to consume the committed proof, context, action,
and expected result bytes and reach the same decision.
Their value comes from three properties:
- canonicality: there is one accepted byte representation for a value;
- semantic intent: every fixture names the behavior and stable result it demonstrates; and
- reproducibility: the bytes, their SHA-256 digests, and the verifier result are generated from reviewed source and checked in CI.
For that reason, never edit a .cbor file by hand.
Source of truth
Section titled “Source of truth”The ownership chain is:
model + codec + verifier + adapters | v auths-testkit::corpus() | v cargo xtask wire --update | v fixtures/v1/**/*.cbor + manifest.jsoncrates/auths-testkit/src/lib.rs constructs each CorpusFixture and declares
its stable name, class, inputs, and Expected outcome. xtask then:
- canonically encodes the action;
- runs the real portable verifier with the complete corpus registry;
- encodes the portable result;
- writes the generated files; and
- writes
fixtures/v1/manifest.jsonwith paths, hashes, semantic metadata, result commitments, and resource measurements.
The generated bytes are outputs. The Rust fixture builder, normative specification, codecs, and verifier behavior are the inputs that must be reviewed.
The five files in a fixture
Section titled “The five files in a fixture”Each case normally produces:
| Suffix | Meaning |
|---|---|
.proof.cbor |
The canonical proof bundle |
.context.cbor |
The verifier-owned policy, limits, trust, registries, configuration commitment, and composition requirement |
.action.cbor |
The separately supplied canonical action |
.body.cbor |
The exact canonical action body bytes |
.result.cbor |
The deterministic portable verification result |
manifest.json binds the inventory and SHA-256 digest of every artifact. A
change to one configuration record can intentionally change many context and
result files because VerifierConfigurationId commits to executable
decision-affecting configuration.
Fixture directories are assigned by CorpusFixture::class()—currently
valid, denied, indeterminate, invalid, and status. The class is a
review taxonomy; expected_decision and expected_code in the manifest are
the authoritative outcome.
When a change requires corpus work
Section titled “When a change requires corpus work”Use this decision table:
| Change | Required corpus response |
|---|---|
| New wire field, type, tag, canonical ordering rule, or portable result field | Update model, codec, CDDL, and normative prose together; regenerate affected vectors and add boundary/malformed cases |
| New principal method, key type, signature suite, status method, extension, profile, or registry handler | Add at least one end-to-end accepted case and targeted rejection/unsupported/substitution cases; add its executable configuration to the corpus registry |
| New denial reason or indeterminate requirement | Add a fixture that reaches that exact stable code and stage, plus the nearest successful or differently classified boundary where useful |
| New verifier policy, composition rule, or assurance rule | Add downgrade, omission, ambiguity, and boundary cases—not only a happy path |
| New or changed resource limit | Add exact-limit and one-over-limit coverage, including aggregate limits where multiple individually valid objects can exceed the total |
| Bug fix | Add the smallest fixture that would have caught the bug when it represents portable protocol behavior |
| Internal refactor with identical protocol semantics | Do not update vectors. cargo xtask wire must remain byte-stable |
| Dependency update that unexpectedly changes bytes | Stop and investigate. A new encoder or certificate representation is not sufficient justification by itself |
The standard is not “did some .cbor change?” It is “does the changed corpus
make the intended protocol behavior reviewable, and does every unrelated
fixture remain stable?”
Adding a fixture
Section titled “Adding a fixture”-
Build the smallest meaningful
CorpusFixtureincrates/auths-testkit/src/lib.rs. Reuse helpers only when they preserve the security property being demonstrated. -
Give it a stable, behavior-oriented kebab-case name such as
verifier-configuration-mismatch; avoid implementation names liketest-42. -
Set the exact
Expected::Authorized,Expected::Denied(...), orExpected::Indeterminate(...)value. -
Add the constructor to
auths_testkit::corpus(). A helper that is never listed there generates no vectors and provides no conformance coverage. -
If the fixture needs a new adapter or suite, add deterministic corpus material and construct that implementation in
xtask::generated_vectors. The context’s configuration commitment must match that executable registry except in a fixture specifically testing mismatch behavior. -
Run the normal wire check first:
Terminal window cargo xtask wireFor an intentional addition or semantic change, this should report the missing or changed vector.
-
Generate only after reviewing the source-level intent:
Terminal window cargo xtask wire --update -
Review the new manifest entry, expected decision/code/stage, digests, configuration commitment, resource fields, and the complete binary diff.
Adding an algorithm: corpus expectations
Section titled “Adding an algorithm: corpus expectations”Suppose a post-quantum signature suite is added. A useful corpus change would normally include:
- a valid proof signed by the new suite;
- deterministic key and signature material suitable for committed fixtures;
- an accepted registry entry and exact configuration commitment;
- wrong-suite and cross-suite substitution cases;
- truncated, oversized, and non-canonical key/signature cases;
- invalid-signature and domain-separation cases;
- exact byte/resource-limit boundaries for the larger artifacts; and
- a portable result that can be checked by non-Rust implementations.
Adding the suite constructor to the verifier registry is necessary but not sufficient. A fixture must actually select the suite, or the success path is not represented in the canonical corpus. The corresponding fuzzing guidance explains how that valid fixture should also seed end-to-end adversarial coverage.
Reviewing regenerated vectors
Section titled “Reviewing regenerated vectors”Review in this order:
- Inventory: only expected fixture names and five-file groups were added, removed, or moved.
- Manifest semantics: class, decision, stable code, stage, profile, resource, plan, and registry fields match the intended case.
- Blast radius: every changed existing fixture has an explained shared cause.
- Canonical bytes: decode/re-encode checks remain exact; non-minimal or duplicate-key negative cases still fail for the intended reason.
- Cross-boundary consistency: proof, action, context, result, CDDL, algorithm prose, error-code registry, and adapter configuration agree.
- Determinism: regenerate again and confirm there is no second diff.
Then run:
cargo xtask wirecargo xtask conformancecargo xtask semantic-digestcargo xtask spec-synccargo xtask ciwire proves byte stability, conformance proves the target outcomes,
semantic-digest gives a normalized corpus fingerprint, and ci exercises
the complete hosted-equivalent gate.
Removing or replacing fixtures
Section titled “Removing or replacing fixtures”Because the project is prelaunch, an obsolete fixture can be replaced directly
when the protocol contract changes. The generator removes stale .cbor files
that are no longer in auths_testkit::corpus().
The review must still explain:
- which normative behavior changed;
- why the old fixture is no longer correct;
- which new fixture preserves equivalent or stronger adversarial coverage; and
- whether this is part of a deliberate wire/protocol break.
After the first public protocol freeze, fixture removal or byte changes become compatibility events and must follow the project’s compatibility policy.