Skip to content

Maintaining canonical CBOR fixtures

Conformance / contributor guide

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.

The ownership chain is:

model + codec + verifier + adapters
|
v
auths-testkit::corpus()
|
v
cargo xtask wire --update
|
v
fixtures/v1/**/*.cbor + manifest.json

crates/auths-testkit/src/lib.rs constructs each CorpusFixture and declares its stable name, class, inputs, and Expected outcome. xtask then:

  1. canonically encodes the action;
  2. runs the real portable verifier with the complete corpus registry;
  3. encodes the portable result;
  4. writes the generated files; and
  5. writes fixtures/v1/manifest.json with 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.

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.

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?”

  1. Build the smallest meaningful CorpusFixture in crates/auths-testkit/src/lib.rs. Reuse helpers only when they preserve the security property being demonstrated.

  2. Give it a stable, behavior-oriented kebab-case name such as verifier-configuration-mismatch; avoid implementation names like test-42.

  3. Set the exact Expected::Authorized, Expected::Denied(...), or Expected::Indeterminate(...) value.

  4. Add the constructor to auths_testkit::corpus(). A helper that is never listed there generates no vectors and provides no conformance coverage.

  5. 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.

  6. Run the normal wire check first:

    Terminal window
    cargo xtask wire

    For an intentional addition or semantic change, this should report the missing or changed vector.

  7. Generate only after reviewing the source-level intent:

    Terminal window
    cargo xtask wire --update
  8. Review the new manifest entry, expected decision/code/stage, digests, configuration commitment, resource fields, and the complete binary diff.

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.

Review in this order:

  1. Inventory: only expected fixture names and five-file groups were added, removed, or moved.
  2. Manifest semantics: class, decision, stable code, stage, profile, resource, plan, and registry fields match the intended case.
  3. Blast radius: every changed existing fixture has an explained shared cause.
  4. Canonical bytes: decode/re-encode checks remain exact; non-minimal or duplicate-key negative cases still fail for the intended reason.
  5. Cross-boundary consistency: proof, action, context, result, CDDL, algorithm prose, error-code registry, and adapter configuration agree.
  6. Determinism: regenerate again and confirm there is no second diff.

Then run:

Terminal window
cargo xtask wire
cargo xtask conformance
cargo xtask semantic-digest
cargo xtask spec-sync
cargo xtask ci

wire proves byte stability, conformance proves the target outcomes, semantic-digest gives a normalized corpus fingerprint, and ci exercises the complete hosted-equivalent gate.

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.