Skip to content
VERASPEC
Repository
The conformance validatorstable

What it checks

StageConcernCodes
0The validator itself, not the recordVER001 validator-internal-error
1Parse (RFC 8259, UTF-8) and sizeVER101 unparseable-json · VER102 not-a-json-object · VER103 record-exceeds-hard-cap · VER104 record-size-policy (W) · VER105 record-size-exceeded ¹ · VER106 size-should-cap-exceeded (W) ¹
2Structure, then format/contentEncoding/pattern-anchoring assertionVER201 schema-invalid · VER202 format-invalid
3Conformance level gatesVER301 level-absent (W) · VER302 raw-preservation-missing (one per family) · VER303 availability-incomplete · VER304 normalized-view-missing · VER305 trust-entry-missing · VER306 provenance-missing · VER307 c2pa-status-missing · VER308 signature-absent (W)
4Space descriptorsVER401 duplicate-space-id · VER402 space-registry-conflict · VER403 embedding-space-id-malformed · VER404 space-reference-missing (W) · VER405 visual-preprocessing-underspecified (W, checked per kind) · VER406 hamming-tolerance-exceeds-dim ¹ · VER407 bundle-manifest-digest-mismatch ¹ · VER408 bundle-paths-unsorted-or-duplicate ¹ · VER409 bundle-path-illegal ¹ · VER410 artifact-digest-mismatch ¹ · VER411 descriptor-digest-mismatch ¹
5Referential integrityVER501 embedding-references-undeclared-space
6Dimensions, dtypes and component valuesVER601 dim-mismatch-vs-descriptor · VER602 dtype-mismatch-vs-descriptor · VER603 inline-vector-length-mismatch · VER604 b64-vector-byte-length-mismatch · VER605 ref-byte-length-mismatch · VER606 dim-dtype-unstated (W) · VER607 non-finite-vector-component · VER608 vector-carriage-not-exactly-one ¹ · VER609 binary-vector-inline ¹
7Role / kind agreementVER701 canonical-visual-in-nonvisual-space ² · VER702 contextual-in-nontext-space ² · VER703 fused-in-nonfused-space · VER704 fused-recipe-invalid · VER705 no-canonical-visual-embedding
8Perceptual digestsVER801 pdq-length · VER802 phash-length
9Availability completenessfolded into VER303; stage retained for traceability
10Availability basis and raw-segment carriageVER901 stripped-suspected-without-basis ² · VER902 raw-index-not-unique-or-contiguous ¹ · VER903 raw-index-missing-multisegment ¹ · VER904 raw-ref-digest-mismatch ¹
11Trust, conflict, redactionVER1001 redaction-availability-inconsistent ² · VER1002 redaction-missing-salted-hash (W, 1.0.x only) · VER1003 conflict-sources-not-distinct · VER1004 conflict-field-untracked (W) · VER1005 redacted-family-raw-present · VER1006 producer-performed-without-commitment ¹ · VER1007 upstream-withheld-with-commitment ¹ · VER1008 commitment-mismatch-ledger-vs-view ¹ · VER1009 redaction-pointer-unresolvable ¹ · VER1010 upstream-withheld-family-raw-present ¹ · VER1011 redacted-source-embedding-carried ¹
12C2PA stateVER1101 c2pa-raw-present-status-missing · VER1102 c2pa-valid-without-manifest-hash (W) · VER1103 c2pa-state-inconsistent
13Provenance chronology and action tokensVER1201 chain-not-chronological · VER1202 chain-precedes-acquisition · VER1203 record-precedes-chain (W) · VER1204 action-order-implausible (W) · VER1205 bare-action-token-unregistered ¹ · VER1206 registry-membership-unverified (W) ¹ · VER1207 sign-event-without-signature ¹ · VER1208 registry-snapshot-mismatch (W) ¹
14ChecksumsVER1301 b64-vector-undecodable · VER1302 raw-segment-sha256-mismatch · VER1305 source-ref-content-hash-mismatch
15SignatureVER1401 signature-object-invalid · VER1402 signature-verification-failed · VER1403 signature-unverified (W)
16LineageVER1601 lineage-duplicate-parent ¹ · VER1602 lineage-exported-pixel-hash-mismatch ¹ · VER1603 lineage-self-parent ¹ · VER1604 lineage-cycle ¹ · VER1605 lineage-recipe-uri-without-digest ¹ · VER1606 lineage-parent-content-hash-absent (W, error under --profile high-trust) ¹

(W) marks warning severity; everything else is an error. ¹ fires only under --schema 1.1-draft. ² reads a shape the 1.1 draft adds and is therefore worded differently there — see Four rules that read 1.1 shapes; no ² rule changes meaning, severity or 1.0.x behaviour. The registry holds 83 codes (67 error, 16 warning) plus one reserved number; 29 of them are the 1.1 additions marked ¹, and they are the only difference between the two profiles' code sets. Codes are stable and are never renumbered, and neither are their titles: a retired code keeps its number and moves to RESERVED_CODES rather than being reused. VER405's title still reads visual-preprocessing-underspecified from when the rule covered only visual spaces; the rule now covers every kind annex E13 enumerates members for, and the title keeps its original spelling because downstream documents index the registry by it.

Stage 16 is lineage, and it is a stage rather than an extension of stage 13 because every rule in it is a statement about a graph spanning records rather than about the document under test — the one class of question the rest of this validator never asks (VER-1.1-draft.md §15.4).

Stage 0 is the one stage that is not about the record. VER001 is emitted when a rule raises, which is a defect in this package. It is numbered 0 because it precedes and can interrupt any stage, and it is the reason status exists: see Fail-closed below.

Three size numbers that are not the same number (VER104), plus a fourth that is different again (VER103). VER103's hard cap and VER104's document advisory measure serialized bytes — the compact UTF-8 JSON encoding of the whole record, base64 text included. The other two VER104 advisories measure decoded payload octets:

QuantityThresholdMeasured on
serialized record document10 MiBthe JSON text as written
summed inline metadata.raw[].bytes6 MiBdecoded octets, before base64
any single inline vector6 MiBdecoded octets (see the proxy below)

The two payload numbers sit below the document number on purpose: base64 inflates 4:3, so a decoded budget at or above 10 MiB could never be exceeded before the document advisory fired first. The previous 16 MiB raw and 8 MiB vector figures had exactly that defect — unreachable inside a 10 MiB document, and therefore decoration rather than policy. Bytes carried by reference are governed separately and are counted against none of the three.

An inline JSON array is measured by a documented proxy of 8 bytes per component, independent of the declared dtype — a JSON array is not dtype-tagged, so charging it the declared dtype's width would size it as the reference form the record did not use. Eight is IEEE 754 binary64, the width a JSON number is read at, and the widest dtype VER 1.0 defines. The consequence a reader reconciling the corpus against "6 MiB" needs: the advisory fires at 786,433 components whatever the dtype, and the JSON text carrying them weighs a different number of bytes — smaller for a zero-filled array, several times larger for full-precision floats. The corpus fixture invalid/ver104-record-size-policy.json carries 1,048,577 components in 2.1 MB of text for exactly that reason: it trips the vector advisory (8,388,616 proxy bytes) and not the document one.

The same three quantities and the same thresholds are what VER105 reports under --schema 1.1-draft, where §7 makes them normative limits rather than advice; VER104 stands down there. See The 1.1 draft profile.

Profile strictness beyond the schema

Where this validator is deliberately stricter than a bare 2020-12 validator, each case grounded in the 1.0.1 errata annex or in the RFC the format names:

  • JSON means RFC 8259 JSON. NaN, Infinity and -Infinity are JavaScript literals that Python's json accepts by default; a document containing one is rejected as VER101 (stage 1). A document that is not valid UTF-8 is also VER101, naming the offending byte offset — never decoded with replacement characters and then validated, which would audit and signature-check a document the producer never wrote. A leading UTF-8 BOM is accepted and discarded.
  • Vector components must be finite (VER607, stage 6). Unreachable from a file, by the rule above; reachable through validate_record, which takes an already-parsed object. One NaN component makes every similarity computed against that vector NaN, so the vector indexes cleanly and then silently removes itself from every comparison.
  • format and contentEncoding are asserted, not annotated (annex E23). JSON Schema 2020-12 treats both as annotations by default. A record whose created_at is "yesterday" is schema-valid and useless, so the profile asserts them (VER202). jsonschema ships no date-time or uri checker unless optional dependencies are installed; this package supplies its own so an assertion it promised is never silently skipped.
  • uuid means the canonical spelling, not "something uuid.UUID will parse" (VER202). uuid.UUID is a parser and a permissive one: it strips urn:uuid:, drops braces, ignores where the hyphens fall and accepts uppercase, so seven different strings all "validate" as one UUID. A record_id is compared, indexed and used as a filename by systems that compare strings, so accepting several spellings of one identifier lets one record hold several identities. RFC 4122 section 3 defines exactly one string form — lowercase hex, hyphens in the 8-4-4-4-12 positions — and that is the one asserted, re-parsed and compared back.
  • uri means the RFC 3986 character set (VER202). The check used to be "a scheme, then anything that is not whitespace", which admits every C0 control character and " < > \ ^ { | }` — while VER202's own remediation promised RFC 3986. A URI is also the one field a consumer hands straight to a fetcher, so a NUL or a newline in one is how a request header or a log line gets split. Percent-escapes must be % HEXDIG HEXDIG.
  • JWS parts are strict, unpadded base64url (VER1401, stage 15). base64.urlsafe_b64decode is not a validator: it accepts + and / from the standard alphabet and ignores characters it does not recognise, so several distinct strings decode to the same octets. That is the wrong property for a signature — the signing input is the header part as text, so a verifier that normalizes what the signer never wrote produces a result no other implementation reproduces. RFC 7515 §2 admits one encoding, = padding included in what it forbids.
  • A crit or b64 protected header is refused (VER1401). crit (RFC 7515 §4.1.11) names parameters a verifier MUST understand or reject the signature; this validator implements no extensions, so any crit — even an empty one — is a signature it is required to refuse. b64 (RFC 7797) redefines the signing input to the unencoded payload, a construction VER 1.0 does not define (annex E17 signs the JCS form out of band with an empty payload part), so honouring it would verify against a preimage the signer did not use.
  • An ES256 signature is checked to be exactly 64 octets before it is split (VER1401). RFC 7518 §3.4 fixes it at R‖S, two 32-octet integers. Splitting any other length in half invents an (r, s) pair from the wrong octets, and the verification that then fails would be reported as VER1402 — "modified after signing" — over what is usually a DER/DSS signature pasted in where the raw form belongs.
  • pattern is evaluated with ECMA-262 anchoring (VER202, stage 2c). JSON Schema defines pattern over ECMA-262 regexes, where $ matches at end of input only. Python's $ also matches immediately before a trailing newline, which makes every Python-backed validator looser than a conformant JavaScript one: "<64 hex>\n" satisfies ^[a-f0-9]{64}$ here and fails it everywhere else. The profile validates against the published schema twice — once as written, once with every $ rewritten to (?![\s\S]) — and reports the difference. org.x.y/1.0 and org.x.y/1.0\n are two different, visually identical space identities (annex E18), and a 65-character content_hash is not a hex digest (annex E22).
  • Perceptual digest widths and alphabet are asserted under both schemas (annex E22). The 1.0.0 schema allows any hex length; spec section 5 does not. Stage 8 applies the prose, so a record validated at --schema 1.0.0 still fails a truncated PDQ digest — including one padded back to the right length with a character that is not a hex digit, which is why width and alphabet are tested independently and reported distinctly.
  • Base64 is strict RFC 4648 everywhere, in every code path: standard alphabet, padding present, no line breaks. Asserted as VER202 for metadata.raw[].bytes and VER1301 for vector_b64, and the size-accounting helper rejects whitespace rather than stripping it, so no part of the validator sizes a wrapped payload the rules would reject.
  • Digests over inline bytes are recomputed (stage 14). A raw segment that ships bytes and a sha256 that does not match them is self-refuting, whatever the schema says.

What it does not do

  • No network I/O. vector_ref and source_bytes_ref targets are never fetched, and neither are bytes_ref, recipe_uri, descriptor_uri, a lineage record_uri or a registry snapshot named by provenance.registry. An unfetchable reference is not evidence of non-conformance; code VER1303 is allocated for out-of-band verification and is deliberately never emitted (see RESERVED_CODES). Everything the 1.1 rules resolve is resolved out of band, from --registry and --lineage-peer, or not at all.
  • No tenancy, privacy or publication policy. Those are deployment decisions — properties of who may see a record, not of whether the record is well-formed and truthful — and belong to the Studio layer. That document is not in this repository, so the scope-out is stated on its own terms here rather than by cross-reference. (Its own stage list happens to number that concern "stage 16". The two numberings are unrelated: **stage 16 in this package is lineage**, and stage numbers here are this validator's, not another document's.)
  • COSE_Sign1 signatures are not verified; they are reported as unverified (VER1403). Detached JWS with EdDSA, ES256, RS256 or PS256 is verified when a key and the signatures extra are both present. A JWS that is malformed — wrong part count, a non-empty payload part, a header or signature that is not strict base64url, an alg this validator does not implement, a crit or b64 header, an ES256 signature that is not 64 octets — is VER1401, never VER1402: nothing was verified, and that is not evidence the record was modified.

Signature payload

Per annex E17 the signing payload is the record with provenance.signature removed and the result canonicalized with RFC 8785 JCS. Removal deletes exactly that member — it does not prune a provenance object left empty by the removal. Producers must sign that same preimage or verification will fail.