§3 Producer checklist — 1.0.0 → 1.0.1
Work through these in order. Steps 1–3 are the only ways a Record that the 1.0.0 schema accepted can fail the 1.0.1 schema; steps 4–7 are behaviours the annex pinned that a schema cannot check.
3.1 Revalidate your corpus under the 1.0.1 schema
(Point it at your own corpus in place of conformance/valid/*.json.)
Anything that now fails, failed 1.0.0's prose already. Three defects can newly surface, and they are reported through four (code, pointer) pairs, because a wrong-width perceptual digest is caught twice — once by the schema stage, once by the digest stage. One defect, two codes; fix it once.
| Defect | Reported as | Fix |
|---|---|---|
pdq/1.0 digest is not exactly 64 lowercase hex characters | VER801 at /identity/perceptual/N/value, and VER201 at the same pointer (schema delta 1 made the pattern algorithm-conditional) | §3.2 |
phash-dct/64 digest is not exactly 16 lowercase hex characters | VER802 at /identity/perceptual/N/value, and VER201 at the same pointer | §3.2 |
| An undeclared member inside a now-closed identity object | VER201 at /identity/content_hash or /identity/pixel_hash | Remove it. There is no extension point inside identity in 1.0; record the datum outside the Record until 1.1's extensions lands |
Those are the only ways a Record the 1.0.0 schema accepted can newly fail the 1.0.1 schema — the 1.0.1 delta is exactly three constraint changes. Other codes in the report are profile findings, not new-in-1.0.1 schema failures: they were already true of the Record under 1.0.0 and the profile validator is simply the first thing to check them. VER1302 (a raw segment whose sha256 does not match its own bytes) is the one you are most likely to meet; §3.3 covers it. The frozen 1.0.0 example demonstrates all of this at once:
3.2 Regenerate malformed perceptual digests
§5 always defined pdq/1.0 as 256-bit (64 hex) and phash-dct/64 as 64-bit (16 hex). The schema never enforced it, so producers shipped values that were too long, too short, uppercase, or algorithm-prefixed.
Three real failure modes, each with a different fix:
- Algorithm prefix in the value. A value like
pdq/1.0:f8f8f0ce…puts the algorithm identifier inside the digest field. Thealgfield carries the algorithm;valuecarries only the digest. Strip the prefix — the digest itself is intact and needs no recomputation (findingHASH-01). - Wrong width. If the value is not a recoverable digest, recompute it from the Canonical Buffer. Do not truncate a hash you cannot account for. The one truncation blessed in 1.0.1 is the golden example's, where the 96-hex value was provably the 64-hex digest with its own first 32 characters appended (
value[:32] == value[-32:]), sovalue[:64]recovers it rather than inventing it. - Uppercase hex. Lowercase it. Annex E22 pins all digests to lowercase hex, and the schema patterns are
[a-f0-9].
Regenerating a perceptual digest does not change pixel_hash, content_hash, or any embedding. It is a metadata repair, not a re-ingest.
3.3 Verify raw-segment digests
metadata.raw[].sha256 must be the SHA-256 of the exact bytes that metadata.raw[].bytes base64-encodes, and those bytes must be the segment as stored in the container. Two independent things can be wrong:
VER1302 catches the first failure. The second — bytes that are self-consistent but are not the container's stored bytes — no validator can catch, because the source asset is not in the Record. Annex E11 is the normative per-container table — read it there rather than from a summary, because it is the one place the byte ranges are stated and it covers containers this guide does not enumerate. Three consequences to check your extractor against:
- One
raw[]entry per stored segment, in file order. A preserved segment is the bytes as stored — for JPEG, the APP payload including its identifier prefix and any sequence octets; for PNG, the full chunk bytes, undecompressed. Do not concatenate a multi-segment family into one entry, and do not reassemble before hashing. E11's reassembly guidance is informative and applies to reading a family, never to what you store. - Byte-exact means bytes-as-stored. If your extractor re-frames, re-wraps, or normalizes a segment before hashing, both the bytes and the digest are wrong even though they agree with each other (finding
REC-03). - Not every family is byte-exactly preservable in every container. Where E11 says a byte range is undefined for a container — TIFF's container-native EXIF IFD subtree is the case that bites — report the family through
availabilityand rely oncontent_hashfor the asset bytes. Do not invent a serialization and claim it is the stored segment.
Re-extract from the source assets if you still hold them; if you do not, the honest options are to drop the raw segment and set the family's availability to unknown, or to keep it and stop claiming L1+.
Also check annex E25 while you are here: image.source_bytes_ref.sha256 MUST equal identity.content_hash.value (VER1305). They always referred to the same bytes.
3.4 Confirm E3 — the quantize step is a scale, not a clip
This is the only correction that can move a pixel_hash, and it affects only sources deeper than 8 bits per channel (routine in TIFF and PNG, both in scope).
- Correct behaviour: scale from the source bit depth to 8-bit,
round_half_up(v * 255 / maxval), then clamp. Ties round toward +∞ on non-negative values. - The literal 1.0.0 reading: clamp 16-bit values to 255, which turns every sample above 255 into pure white.
If your producer scales — and every interoperable one does — nothing changes, and you have no re-ingest to do. If it clips, you have been emitting destroyed Canonical Buffers for >8-bit sources, and those Records must be re-ingested from source. Check with a synthetic 16-bit test image before assuming either way.
3.5 Confirm E6 — orientation source and out-of-range values
- Orientation comes from EXIF/TIFF tag 274 in the primary EXIF IFD only.
- XMP
tiff:Orientationis reconciliation-layer data. CPNP-1 never consumes it. If your pipeline reads it, you are producing a different Canonical Buffer from every conformant producer for assets where the two disagree — a real case, since Adobe tooling writes both. - Out-of-range, zero, or malformed orientation values MUST be treated as 1 (upright), not rejected and not guessed.
If you consumed XMP orientation, re-ingest the affected assets: their pixel_hash values are wrong relative to the standard.
3.6 Confirm E2 — which dimensions you recorded
image.width/height are Canonical Buffer dimensions, i.e. post-orientation, and identity.pixel_hash.width/height MUST equal them. For Orientation 5–8 (the 90°/270° transposes) a producer that recorded stored dimensions has them swapped. Fixing this is a metadata repair — the buffer and its hash are unaffected, since the hash preimage already carried the post-orientation dimensions.
3.7 Check the rulings that make records non-conformant without changing bytes
Run the profile at your claimed level and read the errors:
The rulings most likely to surface in an existing corpus:
| Ruling | Code | What to do |
|---|---|---|
E18 — space_id unique within a Record | VER401 | Deduplicate spaces[]. Two descriptors under one id is a contradiction, not a merge |
E24 — dim/dtype must equal the descriptor's | VER601, VER602 | Fix whichever is wrong. If you omit them, the descriptor's values apply (VER606 warns) |
E20 — recipe.inputs are space_ids | VER704 | Replace role names with the space_ids declared in the same Record |
| E15 — no redaction claim over preserved evidence | VER1005 | Omit the raw segment from raw[], or stop claiming the family is redacted. Scrubbing the segment in place is not an option in 1.0.x — §5 covers the two-envelope pattern |
| E28 — C2PA state consistency | VER1103 | availability.c2pa: "present" with status: "missing" is a contradiction. Report what you actually checked |
E13 — a descriptor without reference backs no conformance claim | VER404 (warning) | Publish a reference set and tolerance, or stop presenting the space as reproducible |
E13 — a descriptor whose preprocessing omits the §6.1 members for its kind | VER405 (warning) | State the per-kind members in full: visual spaces need resize, interpolation, antialias, crop, mean, std, channel_order; text spaces need tokenizer, tokenizer_sha256, max_length, truncation, casing |
§7.2 — a stripped_suspected family needs a basis | VER901 | State the basis, or use unknown |
3.8 Adopt the definitions you were previously free to invent
No code change is needed unless you disagreed with the pinned reading. Where you did, the cost differs by item: E7, E8 and E9 sit on the CPNP side, so a producer on the non-selected reading has been emitting Canonical Buffers no other implementation reproduces — those pixel_hash values are withdrawn and the affected assets need re-ingesting, exactly as for E3 (§3.4) and E6 (§3.5). The rest are metadata definitions: adopting them costs a repair, never a re-ingest.
- E16 —
record_idis unique per (producer, record), UUIDv5 RECOMMENDED over a producer-scoped stable identity. Deriving it frompixel_hashalone collides across distinct Records of the same asset (findingIMPL-RECORD-ID-COLLIDES).created_atis record assembly time and SHOULD be at or after every chain timestamp; the chain SHOULD be chronological (VER1201–VER1204). - E17 — a Record signature is computed over the Record with
provenance.signatureremoved, RFC 8785 JCS-canonicalized, as a detached JWS or COSE. A signature over any other payload is unverifiable by anyone else. - E19 — grade is derived from
source.class; there is no grade field and none is coming in 1.0. Consumers satisfy §8's filtering MUST through the mapping table. - E8 — a present-but-unusable ICC profile fails closed. Reject, or emit a diagnostic state outside the Record. Silently assuming sRGB and emitting a conformant Record is prohibited, and an undeclared
icc_errorfield was never legal in a closed schema. - E9 — only an embedded ICC profile is consumed.
gAMA,cHRM,sRGBchunks and EXIFColorSpaceare not. - E7 — the first frame is the decoder's frame at logical index 0, fully coalesced;
animatedis true iff frame count > 1. - E12 — if your decoder does not pass a pinned implementation profile against a reference corpus, your results are PROVISIONAL. Say so rather than claiming conformance you cannot demonstrate. The corpus itself is a 1.1 deliverable.
3.9 Re-point your schema reference
If you validate against a vendored copy, take standards/ver/1.0.1/ver-record.schema.json. Do not overwrite your vendored 1.0.0 copy — a published $id is never reused, and keeping both lets you diff reports across releases.
Do not touch ver_version. It stays "1.0". Bumping it to "1.0.1" makes a Record that no consumer accepts: the field is a const in both schemas.
