Skip to content
VERASPEC
Repository
The fixture corpusstable

Fixture construction rules

These rules are what make a fixture a test vector rather than a sample. New fixtures MUST follow them.

  1. One defect per fixture. Every fixture is a complete, internally self-consistent record except for the single thing it is testing. A fixture that is wrong in two ways cannot tell you which rule fired.
  2. expect_codes is an exact set, not a floor. Where a defect provably drags another code with it, both are listed and notes explains the coupling. The coupled cases in this corpus:
fixturealso emitswhy it cannot be isolated
ver403-embedding-space-id-malformedVER501a malformed space_id can never match a declared descriptor, because spaces[] entries are pattern-constrained by the schema
ver705-no-canonical-visual-embeddingVER201VER705 is the profile's defense-in-depth restatement of the schema's contains constraint
ver801-pdq-length, ver802-phash-lengthVER201the 1.0.1 schema binds digest width to the algorithm; under --schema 1.0.0 the profile code stands alone
ver1101-c2pa-raw-present-status-missingVER1103one record exhibits both readings of the same C2PA inconsistency; the codes overlap by design
ver1103-c2pa-state-inconsistentVER307, VER308, VER1101VER1103's absence arm is scoped to L3 by annex E28, so the fixture must claim L3 — which drags in every other L3 gate the missing provenance.c2pa block fails
ver306-provenance-missing, ver307-c2pa-status-missingVER307, VER308an absent provenance block fails every L3 gate at once

The 1.1-draft fixtures add one coupling with one cause. VER409, VER608, VER609, VER903, VER1006, VER1007, VER1207 and VER1605 each also emit VER201, because each is a profile rule the 1.1 schema also encodes — the VER705 precedent, applied eight more times. The profile restates them because it has to keep answering when the schema in play is relaxed, future or somebody else's, and because two of them (a bundle path handed to a filesystem, and which bytes are a vector) are questions a consumer cannot afford two answers to. ver409-bundle-path-illegal, ver1006 and ver1007 emit two VER201 issues rather than one; the set of codes is what the manifest states, and notes says so in each case.

  1. Every digest, byte string and length is computed, never invented. Anything a reader can recompute from the record MUST be correct: metadata.raw[].sha256 over exactly the carried bytes, vector_b64 decoding to dim × dtype_size bytes (ceil(dim/8) for binary), vector_ref.byte_length, image.source_bytes_ref.sha256 equal to identity.content_hash.value, and real signatures over the real RFC 8785 JCS payload.
text
# recompute a raw segment digest
.venv/bin/python -c "import base64,hashlib,json,sys; s=json.load(open(sys.argv[1]))['metadata']['raw'][0]; print(hashlib.sha256(base64.b64decode(s['bytes'])).hexdigest(), s['sha256'])" conformance/valid/l1-raw-metadata.json
  1. Values that cannot be recomputed are still real digests of a documented preimage. No fixture contains a hand-typed hex string. Where the true input is not carried by the record — model weights, a referenced vector blob, the asset bytes themselves — the value is sha256("ver-conformance-corpus/1.0.1 <purpose>"), e.g. sha256("ver-conformance-corpus/1.0.1 source-bytes boardwalk-sunset.jpg") for identity.content_hash. These stand in for values a real producer measures; per errata annex E26 they are illustrative and MUST NOT be cited as interoperability evidence. The perceptual digests are the leading 64 and 16 hex characters of such a digest: well formed, and deliberately not genuine PDQ/pHash outputs — the corpus tests structure, not perceptual similarity. The contextual embedding's source.text_sha256 is the true sha256 of Sunset over a boardwalk, silhouetted pier in background., and the redaction proofs are sha256("ver-conformance-corpus/1.0.1 redaction-salt|<field>|<value>"). Documented preimages are dictionary-invertible by construction — every salted_sha256/commitment in this corpus can be reversed from this README, which is acceptable for illustrative values (E26) and fatal for real ones. A real deployment's salt is never written anywhere a reader can reach (draft §5.3, §5.5); 1.1-draft fixtures use the ver-conformance-corpus/1.1-draft prefix.

1.1-draft fixtures use their own prefix, sha256("ver-conformance-corpus/1.1-draft <purpose>"), so a value can never be mistaken for one of the 1.0.1 corpus's. The purposes in use are source-bytes <asset>, canonical-buffer <asset>, weights <checkpoint>, refset-manifest <set>, tokenizer <name> and icc-segment <asset>. Four constructions there are not stand-ins at all and recompute from the record itself: manifest_sha256, descriptor_sha256, recipe_sha256 and commitment_sha256 — see 1.1-draft fixtures.

  1. Metadata segment bytes are real container bytes. The EXIF segment is an APP1 payload (Exif\0\0 + TIFF header + an Orientation entry), the XMP segment carries its http://ns.adobe.com/xap/1.0/\0 identifier prefix, and the IPTC segment is a Photoshop IRB holding an 8BIM IPTC-NAA resource — the byte ranges errata annex E11 defines.

valid/l3-full.json is the strictest case, because it is the published 1.0.1 example: its icc entry is a complete JPEG APP2 ICC payload — ICC_PROFILE\0, the two sequence octets 01 01, and a real 588-octet sRGB profile — because an elision is not byte-exact preservation under E11 and E26 forbids shipping one at L1 or above. Elsewhere in the corpus the ICC and C2PA payloads are short illustrative fragments rather than a complete profile or a valid JUMBF manifest store; their digests are still the real digests of the bytes shown, and no fixture that carries one declares a level at which E26 would forbid it.

  1. Declare full space descriptors unless the fixture is about not doing so. Annex E13 enumerates seven preprocessing members for a visual space (resize, interpolation, antialias, crop, mean, std, channel_order), five for a text space (tokenizer, tokenizer_sha256, max_length, truncation, casing) and none for a fused space, whose geometry is fixed by its recipe (E20). Every space in every valid/ fixture carries its full set, or VER405 would fire and the fixture would no longer be silent.
  2. Name the file after the code: <code-lowercase>-<slug>.json, matching target_code.
  3. Keep fixtures minimal and level-appropriate. Claim the lowest conformance level that still reaches the rule: an L3 claim drags in every L1 and L2 gate, and an unsigned L3 record always warns VER308. Signature carriage is not level-gated, so the stage-15 fixtures stay at L1. Where a rule is itself level-scoped — VER1103's absence arm is L3 only — the fixture must claim the level the rule needs and list the collateral.
  4. Re-run the corpus after any edit (see above). Editing a signed fixture invalidates its signature: re-sign it with the recipe below, or the VER1402/VER1403 expectations become meaningless. valid/l3-full.json tracks the published 1.0.1 example, so an edit to that example means regenerating the fixture from it and re-signing; valid/l3-full-1-1.json tracks standards/ver/1.1-draft/example-record-lineage.json the same way and needs no signature, because the published 1.1 example carries none.

The corpus signing key

keys/corpus-signing-key.pub.pem is an Ed25519 public key whose private half is derived from the published seed sha256("ver-conformance-corpus/1.0.1 signing-key-seed"). It exists so the signature fixtures can be regenerated by anyone, and it carries no trust whatsoever: it MUST NOT appear in any trust store, and a record signed by it MUST NOT be treated as attributable.

Re-signing a fixture — Ed25519 is deterministic, so this reproduces the shipped value byte for byte when the record is unchanged:

python
import base64, copy, hashlib, json, pathlib
import rfc8785
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
path = pathlib.Path("conformance/valid/l3-full.json")
record = json.loads(path.read_text())
key = Ed25519PrivateKey.from_private_bytes(
hashlib.sha256(b"ver-conformance-corpus/1.0.1 signing-key-seed").digest()
)
b64u = lambda raw: base64.urlsafe_b64encode(raw).rstrip(b"=").decode()
payload = copy.deepcopy(record) # the payload is the record …
payload["provenance"].pop("signature", None) # … with the signature removed (annex E17)
header = b64u(json.dumps({"alg": "EdDSA", "kid": "ver-conformance-corpus-2026-08"},
separators=(",", ":")).encode())
value = f"{header}..{b64u(key.sign(f'{header}.{b64u(rfc8785.dumps(payload))}'.encode()))}"
record["provenance"]["signature"]["value"] = value
path.write_text(json.dumps(record, indent=2, ensure_ascii=False) + "\n")

valid/l3-full.json is the published 1.0.1 example record plus a detached JWS over the RFC 8785 JCS form of that record with provenance.signature removed (errata annex E17). With no key supplied the profile reports VER1403 — signature present, not verified — which is why the entry allows that one warning; with the corpus key it verifies and the record validates with zero issues.