Skip to content
VERASPEC
Repository
VER 1.0.1 errata annexstable

§3 Normative corrections and clarifications

E1 — The identity gradient overstates what pixel_hash survives

[1.0.0 text] §3, identity-gradient table, Pixel row: “pixel_hash — SHA-256 of Canonical Buffer | Survives: Metadata stripping and rewriting | Fails under: Recompression, resize, crop. Role: render identity.” Reinforced by §0: “metadata is split into render-affecting metadata (consumed exactly once during normalization, then discarded) and descriptive metadata (preserved, never consumed by the embedding layer). The determinism claim becomes precise instead of false.”

[Defect] The survival cell is false as written, and false for exactly the metadata the standard's own pipeline consumes. CPNP-1 §4 step 2 applies EXIF Orientation and step 3 applies the embedded ICC profile into the Canonical Buffer. Removing the EXIF segment from an asset whose Orientation is 5–8 changes the Canonical Buffer's dimensions and every one of its bytes; removing an ICC profile changes its colour values. A metadata stripper that touches nothing else therefore moves pixel_hash. Since most camera-originated JPEGs carry a non-1 Orientation, the failure case is the common case, not the corner case. §4's own concession that “cross-CMM bit-exactness for ICC-managed inputs is not guaranteed in 1.0” compounds it: for ICC-tagged assets the same asset may hash differently under two conformant producers.

[Correction] The Pixel row of the §3 identity gradient is corrected to read:

Survives: stripping and rewriting of descriptive metadata (§2) — captions, rights, GPS, device fields, timestamps, and any other metadata CPNP-1 does not consume.

Fails under: recompression, resize, crop; and removal, addition, or alteration of render-affecting metadata (§2) — EXIF/TIFF Orientation and the embedded ICC profile — because CPNP-1 consumes those into the Canonical Buffer by construction.

Reproducibility: pixel_hash is exact and cross-producer stable for assets with no embedded ICC profile. For ICC-managed assets it is exact per implementation profile (§4 step 3, E12) but is not guaranteed bit-identical across colour-management modules; such assets MUST be compared by hash only within one implementation profile, and MUST NOT be used as cross-producer conformance evidence until a reference CMM is pinned.

The following statements are normative and apply wherever the claim is restated, including §0's “orientation paradox” paragraph and §12's “Hash ambiguity” bullet:

  1. A Producer MUST NOT claim, and a Consumer MUST NOT assume, that pixel_hash is invariant under arbitrary metadata removal.
  2. A Consumer that observes two Records with equal content_hash and unequal pixel_hash MUST treat the difference as a render-affecting-metadata or implementation-profile difference, not as evidence of tampering.
  3. Conformance suites MUST place ICC-managed fixtures in a separate bucket from profile-absent fixtures and MUST NOT assert cross-implementation hash equality on the former.

Making pixel_hash genuinely orientation-invariant (for example by hashing the pre-orientation buffer together with the orientation value) is a CPNP change, creates a new pixel_hash universe under §13, and is therefore breaking-major. It is not done here.

[Classification] patch-correction

[Findings] SPEC-01 (standards-fresh-eyes), SPEC-01 (core-pipeline), MOD-02, THESIS-01, SPEC-07 (standards-fresh-eyes), SPEC-PIXHASH-TOL


E2 — image.width / image.height are Canonical Buffer dimensions

[1.0.0 text] §10, abridged shape: "image": { "format": "jpeg", "width": 4032, "height": 3024, … } alongside "pixel_hash": { "alg": "sha-256", "value": "…", "width": 4032, "height": 3024 }. No prose anywhere in the document defines image.width or image.height.

[Defect] Two of the six required members of image have no definition. For any asset with EXIF Orientation 5–8 the container's stored dimensions and the Canonical Buffer's dimensions are transposes of each other, so two conformant Producers emit swapped values for the same asset and neither is provably wrong. The schema declares image.width/height and identity.pixel_hash.width/height independently with no relation, so a Record may state 4032×3024 in one place and 100×100 in the other and still validate.

[Correction]

image.width and image.height are the dimensions of the Canonical Buffer (§4), i.e. the dimensions after orientation has been applied. They MUST equal identity.pixel_hash.width and identity.pixel_hash.height respectively, which are the same values as they appear in the pixel_hash preimage.

A Record MUST NOT report the container's stored (pre-orientation) dimensions in image. VER 1.0 has no field for stored dimensions; a Producer that needs to carry them MUST do so outside the Record.

This ruling sides with the reference implementation, which populates image from the post-orientation buffer. A Consumer that needs the stored dimensions can recover them from a preserved EXIF segment together with the orientation value, subject to E1's warning against re-applying consumed metadata. A distinct field for stored dimensions is 1.1 material.

[Classification] patch-correction

[Findings] SPEC-04 (standards-fresh-eyes), SPEC-01 (core-pipeline)


E3 — The quantize step is a scale, not a clip

[1.0.0 text] §4 step 6: “Quantize to 8 bits per channel, clamp to [0, 255], round half up.”

[Defect] The step is not executable as written. It names three operations in an order that cannot be performed (quantize, then clamp, then round), and no preceding step states the working buffer's domain — §4 step 1 admits “RGB or RGBA at full resolution” at any bit depth, and both TIFF and PNG, which are in scope per §1, routinely carry 16 bits per channel. Read literally, “clamp to [0, 255]” applied to a 16-bit buffer maps every sample above 255 to white: a 16-bit source with samples [0, 4096, 8192, …] yields a canonical channel of [0, 255, 255, …], destroying the image while still producing a schema-valid Record. Under that reading “round half up” is also meaningless, since the clamp has already discarded the fractional information. The step has no correct reading, only a destructive one and an intended one.

[Correction] §4 step 6 is restated as:

6. Quantize. Reduce every channel sample to an 8-bit unsigned integer:

out = clamp(round_half_up(v · 255 / maxval), 0, 255)

where v is the decoded channel sample and maxval is the maximum representable value of the source's channel encoding (255 for 8-bit sources, 65535 for 16-bit sources, 1.0 for normalized floating-point sources). Scaling is applied first, the clamp is applied to the scaled value, and round_half_up resolves exact ties toward +∞. Because the domain is non-negative after scaling, “half up” and “half away from zero” coincide; implementations MUST NOT use IEEE-754 round-half-to-even here.

For an 8-bit source this reduces to the identity, so no pixel_hash computed from an 8-bit source changes.

The destructive literal reading was never intended and was never interoperable: it makes two of the eight in-scope container formats unusable and contradicts §4's own framing of quantization as a depth reduction. It is corrected rather than preserved.

[Identity impact] Sources of 8 bits per channel: none. The corrected step reduces to the identity for them, so every pixel_hash computed from an 8-bit source is unchanged.

Sources deeper than 8 bits per channel: 1.0.0 admitted two readings — clip and scale — that produce different Canonical Buffers, and this item selects scale. Records produced by a clipping Producer were never interoperable and are non-conformant under this annex. Their pixel_hash values are withdrawn, not recomputed: a clipped value is not a stale version of a scaled value, it is a value of a different function. A Producer correcting its pipeline re-derives pixel_hash from the Asset; it MUST NOT attempt to map old values onto new ones. Records produced by a scaling Producer are unchanged. The reference implementation's current Pillow-based path clips; that deviation is recorded in the errata ledger as an implementation note.

[Classification] patch-correction. The audit classified the literal reading as breaking-major (CPNP-04) on the ground that scaling changes pixel_hash for sources deeper than 8 bits. This annex adopts the narrower position stated in the identity-impact note above: those Records never had one defined value to change, because 1.0.0 specified two contradictory operations for them. The classification is patch-correction for the standard, and a required pipeline correction for any Producer that clipped.

[Findings] CPNP-04, SPEC-11


E4 — Alpha compositing operates in the 8-bit domain

[1.0.0 text] §4 step 5: “Alpha: if present, composite over opaque white (255, 255, 255) per channel in the encoded (gamma) domain — out = round(src·α + 255·(1−α)), α = A/255 — and set alpha_present: true. Gamma-domain compositing is chosen for determinism and ecosystem consistency, not colorimetric ideality. Alpha is consumed.”

[Defect] The formula hardcodes the 8-bit domain (the white constant is 255 and α = A/255) but the step that establishes that domain is step 6, ordered after it. For a 16-bit RGBA source the formula is undefined: A/255 exceeds 1, and compositing against 255 against a 65535-scale sample is nonsense.

[Correction]

Step 5 operates on 8-bit values. For sources deeper than 8 bits per channel, the scaling defined in E3 is applied to the colour channels and to the alpha channel before compositing; the formula and constants are otherwise unchanged:

out = round_half_up(src · α + 255 · (1 − α)), α = A / 255, A the 8-bit alpha sample.

Equivalently: steps 5 and 6 are applied such that compositing occurs in the 8-bit domain. The composited result is already 8-bit and step 6 is then a no-op for it.

No formula changes. This fixes the ordering only.

[Classification] patch-correction

[Findings] SPEC-05 (standards-fresh-eyes), SPEC-25


E5 — Rendition selection happens at decode

[1.0.0 text] §4 step 4: “HDR: if the container carries an SDR base rendition (gain-map formats such as Ultra HDR), use it and set hdr_present: true. PQ/HLG-only sources are out of scope for CPNP-1.”

[Defect] Step 4 is ordered after decode (step 1), orientation (step 2), and colour management (step 3). You cannot colour-manage a buffer and then decide which rendition you decoded. As written the pipeline either performs step 3 twice or performs it on the wrong buffer.

[Correction]

Rendition selection is part of decoding. §4 step 1 is read as: “Decode the Asset with a conformant decoder into RGB or RGBA at full resolution; where the container carries an SDR base rendition alongside a gain map, the SDR base rendition is the decoded buffer.”

§4 step 4 is read as a recording step only: it sets image.hdr_present and performs no pixel operation. hdr_present is true iff the container declared a gain map or an HDR rendition that CPNP-1 did not use.

A Producer MUST reject a PQ- or HLG-only source. It MUST NOT tone-map such a source into a VER 1.0 Record; tone-mapping with a pinned operator is a §13 reserved extension point, not a 1.0 behaviour.

The detection procedure for hdr_present remains under-specified in 1.0 — 1.0.0 states no test, and a byte-scan for gain-map markers produces false positives on ordinary SDR assets whose XMP merely mentions the namespace. That gap is recorded in the errata ledger and is 1.1 material; this item fixes only the pipeline ordering.

[Classification] patch-correction

[Findings] SPEC-06 (standards-fresh-eyes); related, not fixed here: CPNP-02


E6 — One orientation source, and out-of-range values are upright

[1.0.0 text] §4 step 2: “Apply EXIF/TIFF Orientation (values 2–8) to the buffer; an absent tag is value 1. The tag is thereby consumed; Layer 1 MUST NOT observe it again.” §7.3: “Reconciliation precedence … C2PA-verified assertion > XMP > IPTC-IIM > EXIF.”

[Defect] Three gaps. (i) The orientation carrier is not pinned: Adobe tooling writes tiff:Orientation into XMP, and §7.3 ranks XMP above EXIF, so a Producer that applies §7.3 to the render path produces a different Canonical Buffer — and a different pixel_hash — from one that does not. (ii) Values outside 2–8 are undefined: 0, 9, 99, a non-SHORT tag type, and an unparseable EXIF block each admit both “treat as 1” and “reject as undecodable” under §4 step 1. (iii) Multiple EXIF IFDs are not addressed.

[Correction]

Orientation source. CPNP-1 consumes the Orientation value from EXIF/TIFF tag 274 in the primary EXIF IFD (IFD0) only. No other carrier is consumed. In particular, tiff:Orientation carried in XMP is reconciliation-layer data: it is subject to §7.3 precedence in the normalized view and MUST NOT be consumed by CPNP-1, whatever it says and whether or not it agrees with tag 274. §7.3 governs the normalized view; it never governs the render path.

Value handling. An Orientation value outside the range 1–8, a tag whose type is not SHORT, a tag that is absent, and an EXIF block that cannot be parsed are each treated as Orientation 1 (no transform). A Producer MUST NOT reject an Asset on the basis of a malformed or out-of-range Orientation value; the tag is metadata, and metadata availability MUST NOT be a precondition for producing the Canonical Buffer (§7, core rule).

[Identity impact] Assets whose EXIF IFD0 tag 274 is the only orientation carrier, or which carry no orientation at all: none. That is the overwhelming majority of Assets, and every pixel_hash over them is unchanged.

Two categories are affected, and in both 1.0.0 admitted two readings that produced different Canonical Buffers.

  • XMP tiff:Orientation disagreeing with EXIF tag 274. §7.3 ranks XMP above EXIF, so a Producer that applied §7.3 to the render path rotated differently from one that did not. This item selects EXIF IFD0 tag 274 only. Records produced by a Producer that consumed the XMP value were never interoperable and are non-conformant under this annex; their pixel_hash values are withdrawn, not recomputed.
  • Out-of-range, wrong-typed, or unparseable orientation. 1.0.0 admitted both "treat as 1" and "reject the Asset". This item selects treat as 1. A Producer that rejected such Assets emitted no Record, so nothing is withdrawn there; a Producer that guessed some other transform has its pixel_hash values withdrawn on the same terms.

[Classification] patch-correction

[Findings] SPEC-09 (standards-fresh-eyes), CPNP-08, SPEC-ORIENT-OOR


E7 — Animated sources: first frame, defined

[1.0.0 text] §1: “Animated sources are represented by their first frame with animated: true; multi-frame records are reserved for VER 1.1.” §4 step 1: “Decode the Asset with a conformant decoder into RGB or RGBA at full resolution.”

[Defect] The rule lives in Scope and never reaches the normative pipeline, and “first frame” is undefined for every animated container that matters. A GIF frame may be a sub-rectangle over a larger logical screen with its own disposal method and transparency index; APNG, animated WebP, and animated AVIF have comparable structures. A Producer that hashes the frame rectangle and one that hashes the composed logical screen produce different pixel_hash values for the same file, both defensibly.

[Correction] The following is normative in §4, as a clause of step 1:

Frame selection. Where the Asset carries more than one frame, CPNP-1 operates on the decoder's frame at logical index 0, fully coalesced: the frame is composed onto the container's logical screen at the container's full logical dimensions. The Canonical Buffer's dimensions are therefore the logical screen dimensions, never the frame rectangle's.

The canvas underlying frame 0 is fully transparent. Every pixel of the logical screen that frame 0's rectangle does not cover, and every pixel that frame 0 marks transparent, is RGBA = (0, 0, 0, 0) entering §4 step 5, and is therefore opaque white in the Canonical Buffer. A container-declared background colour is not consumed by CPNP-1: specifically, GIF89a's Logical Screen Descriptor Background Color Index is NOT consumed, and neither is the Background Color field of a WebP ANIM chunk. This matches every browser and web-platform rendering of the same Asset, which is §0's stated test; a Producer that painted the container's background colour instead would disagree with every such rendering.

Disposal never applies. Disposal methods describe how a frame is cleared before the next frame is drawn. CPNP-1 never advances past logical index 0, so no disposal method — of frame 0 or of any later frame — is consumed, and a Producer MUST NOT pre-apply one.

The same three rules govern animated GIF, APNG, animated WebP, and animated AVIF: coalesce onto the full logical screen, transparent canvas, no disposal. APNG carries no background field to consider; bKGD is a display hint and is not consumed. Animated AVIF and animated WebP frames are composited onto the canvas exactly as their own frame-rectangle and blend fields specify, with the canvas transparent beneath.

image.animated is true if and only if the decoded container's frame count is greater than 1. image.width and image.height are the logical screen dimensions, per E2.

[Identity impact] Single-frame Assets: none. Animated Assets whose frame 0 already covers the full logical screen opaquely — which includes most encoder output, since encoders commonly emit a full-screen key frame first: none.

For the rest, 1.0.0 admitted two readings that produced different Canonical Buffers, and this item selects one of each pair:

  • Frame rectangle vs. composed logical screen. This item selects the composed logical screen. Records produced by a Producer that hashed the frame rectangle were never interoperable — their Canonical Buffer does not even have the same dimensions — and are non-conformant under this annex. That reading is withdrawn; the corresponding pixel_hash values are withdrawn, not recomputed.
  • Background fill for the uncovered area. This item selects **transparent, hence white after step 5**. Records produced by a Producer that painted GIF's Background Color Index (which some decoding libraries, including Pillow, do) are non-conformant on the same terms.

Records produced under the selected readings are unchanged.

[Classification] patch-correction

[Findings] SPEC-10


E8 — A present-but-unusable ICC profile fails closed

[1.0.0 text] §4 step 3: “Color-manage: if an ICC profile is embedded, convert to sRGB (IEC 61966-2-1) with relative colorimetric intent and black-point compensation, then discard the profile. If absent, assume sRGB.”

[Defect] Only the success path is defined. A profile that is truncated, of an unsupported class, or against which no transform can be built admits at least three behaviours — reject the Asset, proceed as untagged sRGB, or convert with a more permissive CMM — producing three different Canonical Buffers and three different pixel_hash values for identical bytes. “If absent, assume sRGB” does not cover “present but unusable”, and no VER 1.0 field can record that the fallback was taken.

[Correction]

If an ICC profile is embedded but cannot be parsed, or no transform to sRGB can be constructed from it, the Producer MUST NOT emit a conformant VER Record for that Asset. It MUST either reject the Asset, or emit a non-conformant diagnostic state outside the VER Record.

The “assume sRGB” rule of §4 step 3 applies only to the profile-absent case. A Producer MUST NOT silently apply it to an unusable profile: doing so produces a Record whose pixel_hash is not reproducible from the Asset by any other implementation, which is precisely the failure the identity tier exists to prevent.

A Record MUST NOT carry an undeclared field to record this condition. In particular icc_error — or any other member not defined by the schema — is not conformant anywhere in a Record; the schema's root, image, and metadata objects are all closed. A machine-readable normalization-outcome field is 1.1 material.

This item overrides §7's core rule for one case, and says so. §7's core rule — “metadata availability MUST NOT be a precondition for producing the Canonical Buffer or any canonical visual embedding”, restated in §3 and §11 — is the most-emphasised invariant in VER 1.0. E8 carves exactly one exception out of it: an ICC profile that is present and unusable. Nothing else in this annex touches that rule, and the exception does not generalise. In particular it is not an exception for absent metadata: an Asset with no ICC profile at all still produces a Record, under §4 step 3's untouched absent-profile rule.

Why this case and not E6's. E6 invokes the same core rule to reach the opposite conclusion for malformed EXIF Orientation — treat it as 1 and carry on. The two cases differ in one respect that decides them both:

EXIF OrientationICC profile
Defined default when unusableYes — value 1, “no transform”, given by §4 step 2 itself (“an absent tag is value 1”)No for the present-but-unusable case. §4 step 3's default (“if absent, assume sRGB”) is a statement about Assets that declare no colour space, not about Assets that declare one the Producer cannot read
Effect of applying the defaultPreserves the Asset's visual meaning: an upright image is treated as uprightFabricates visual meaning: an Adobe RGB or CMYK-derived image is silently reinterpreted as sRGB, changing every colour value in the Canonical Buffer
Result if the default is wrongA rotated Canonical Buffer, reproducible by anyone who reads the same tag the same wayA Canonical Buffer no other implementation reproduces, because the choice depends on which CMM failed and how

So E6's default recovers the intended rendering and E8's would invent one. A pixel_hash computed over an invented rendering is not a weaker identity claim; it is a false one, and the identity tier exists precisely to prevent that. Fail-closed is the only remaining behaviour that does not put a fabricated value in the identity tier.

Why an annex may do this. A corrective release may not add a field, and 1.0 has no field in which a Record could confess “I assumed sRGB over a profile I could not read”. The choice is therefore between a Record that lies and no Record at all. Withholding the Record is the conservative direction: it removes Assets from the conformant set, it never adds a Record that a Consumer would trust wrongly, and it leaves the Asset fully processable outside VER. A normalization_outcome member that would let such a Record exist honestly is 1.1 material; when it lands, this exception can be narrowed rather than kept.

This ruling (disposition source: Studio PRD §16.2) is the opposite of the audit's implementation-side recommendation in CPNP-07, which preferred the untagged fallback on availability grounds. Producers that currently take the fallback — including the reference implementation, whose CPNP module records an icc_error and proceeds as untagged sRGB — are non-conformant under this annex; their behaviour is recorded in the errata ledger as an implementation note.

[Identity impact] Assets with no embedded ICC profile, and Assets whose embedded profile parses and yields a transform: none. Every pixel_hash over them is unchanged.

Assets with a present-but-unusable ICC profile: 1.0.0 admitted at least three readings — reject, proceed as untagged sRGB, or convert with a more permissive CMM — producing three different Canonical Buffers from identical bytes. This item selects none of them: it withdraws canonical identity for such Assets entirely. This is the one place in the annex where the outcome is not “one reading wins” but “no reading wins”, and it is stated that way because no available reading produced an identity another implementation could reproduce. Records already emitted for such Assets are non-conformant and their pixel_hash values are withdrawn; there is nothing to recompute them to inside VER 1.x.

[Classification] patch-correction

[Findings] SPEC-26, CPNP-07, App. D #3


E9 — CPNP-1 consumes only an embedded ICC profile

[1.0.0 text] §4 step 3, as quoted in E8. §2: “Render-affecting metadata — metadata that changes rendered appearance (EXIF Orientation, ICC profile, alpha semantics).”

[Defect] Real assets carry render-affecting colour metadata other than an embedded ICC profile: PNG gAMA, cHRM, and sRGB chunks; JPEG Adobe APP14 transform markers; EXIF ColorSpace and Interoperability R03. The specification never says whether they are consumed. “If absent, assume sRGB” is therefore ambiguous for a PNG that carries gAMA but no ICC profile, and two conformant Producers disagree.

[Correction]

CPNP-1 consumes only an embedded ICC profile. gAMA, cHRM, and sRGB PNG chunks, JPEG Adobe APP14 transform markers, EXIF ColorSpace, and EXIF Interoperability R03 are not consumed by CPNP-1 in VER 1.x. An Asset carrying such a tag but no embedded ICC profile is treated as untagged sRGB, exactly as §4 step 3's absent-profile rule states.

These tags remain descriptive metadata for preservation purposes (§7.1) and for the normalized view (§7.3). A Consumer MUST NOT re-apply them to the Canonical Buffer.

Making the pipeline honour those tags would change the Canonical Buffer for every Asset that carries one, hence would create a new pixel_hash universe, hence would be a CPNP change requiring CPNP-2 and a major version. That is not done here, and cannot be. What this item does is pick, within CPNP-1, which of two readings of “if absent, assume sRGB” governs — and it picks the reading the reference implementation already takes.

[Identity impact] Assets carrying an embedded ICC profile, and Assets carrying no colour metadata of any kind: none. Their treatment is unambiguous in 1.0.0 and unchanged here.

Assets carrying gAMA, cHRM, an sRGB chunk, an Adobe APP14 transform marker, EXIF ColorSpace, or EXIF Interoperability R03 but no embedded ICC profile: 1.0.0 admitted two readings — treat the tag as a colour space to convert from, or treat the Asset as untagged sRGB — which produce different Canonical Buffers. This item selects untagged sRGB. Records produced by a Producer that converted from a gAMA- or cHRM-declared space were never interoperable and are non-conformant under this annex; their pixel_hash values are withdrawn, not recomputed. Records produced under the selected reading are unchanged.

The scale matters and is stated rather than glossed: PNGs carrying gAMA with no ICC profile are common, so this is not a corner case. It is, however, a case in which the two readings were both defensible and one had to be chosen.

[Classification] patch-correction

[Findings] SPEC-08 (standards-fresh-eyes)


E10 — The pixel_hash preimage, byte for byte

[1.0.0 text] §4 step 8: `pixel_hash := SHA-256( UTF-8("VER1:" + width + ":"

  • height + ":sRGB8:") + raw_bytes ). §13: “Records carryver_version` and

cpnp_version. Any change to CPNP creates a new pixel_hash universe and is therefore a major version with a dual-publish migration window.”

[Defect] A byte-exact hash preimage that does not specify its own integer serialization is not byte-exact. Nothing says whether width is decimal, whether leading zeros are permitted, or whether a sign may appear. Nothing states the length or channel interleaving of raw_bytes beyond the literal sRGB8. Separately, the domain tag VER1: is hardcoded and encodes no CPNP version, while the schema models cpnp_version as independently versionable — so a future CPNP revision that kept ver_version at 1 would silently reuse the pixel_hash universe §13 says must be separated.

[Correction]

Integer serialization. width and height are serialized as decimal ASCII digits with no leading zeros, no sign, no grouping separators, and no whitespace. raw_bytes is exactly width · height · 3 octets: interleaved R, G, B, top-to-bottom, row-major, with no row padding and no alpha channel (alpha has been consumed by §4 step 5). The complete preimage is therefore b"VER1:" + str(width) + b":" + str(height) + b":sRGB8:" + raw_bytes with str the decimal form just defined.

Domain-tag coupling. The literal VER1: denotes CPNP-1, not ver_version. A Record whose pixel_hash was computed with the VER1: tag MUST carry cpnp_version: "1.0". Any future change to CPNP MUST introduce both a new domain tag and a new cpnp_version; a CPNP revision that reused VER1: would be non-conformant by construction.

The coupling of a hardcoded tag to an independently-modelled version field is a 1.x limitation, acknowledged here rather than papered over: within VER 1.x cpnp_version is effectively a constant. CPNP-2 MUST version its domain tag.

[Classification] patch-correction

[Findings] SPEC-12, SPEC-13


E11 — What a preserved raw segment is, per container

[1.0.0 text] §7.1: “Producers at conformance level L1+ MUST preserve every metadata segment present at acquisition, byte-exact: { segment: exif|xmp|iptc-iim|icc|c2pa|other, bytes (base64), sha256 }. Preservation is lossless even when parsing fails — unparseable metadata is still evidence.”

[Defect] “Byte-exact” has no referent. The specification never defines where a segment starts and stops, so container framing (marker, length field, CRC, identifier prefix), decompression, and multi-segment reassembly are all producer choices. Measured consequence: the same logical EXIF payload captured from a JPEG and from a WebP by the same library yields two different sha256 values, because one path includes the Exif\0\0 identifier prefix and the other does not; a compressed PNG iTXt chunk is silently decompressed, so the preserved “byte-exact” bytes never existed in the Asset. It is also undefined whether sha256 covers the segment octets or the base64 text.

[Correction] Three rules govern preservation, and they are stated before the table because the table is an application of them, not a substitute:

P1 — Bytes as stored. The preserved bytes of a segment are the octets the Asset actually contains, with no decompression, re-encoding, normalization, reordering, or padding. A Producer whose metadata library returns a parsed or decompressed view cannot satisfy this rule from that library and MUST NOT claim L1 or above.

P2 — One entry per stored segment. Each stored segment is one raw[] entry. A Producer MUST NOT merge two stored segments into one entry, and MUST NOT split one stored segment across two entries.

P3 — File order. Where a family occupies more than one stored segment, its entries MUST appear in raw[] in the order the segments appear in the Asset. VER 1.0 carries no ordinal on raw[] entries, so array order is the only ordering signal a Consumer has, and Producers MUST NOT reorder. (A per-entry index is 1.1 material.)

Digest and encoding. sha256 is the SHA-256 of exactly the preserved octets — the decoded segment bytes, never the base64 text. bytes is the RFC 4648 §4 standard base64 encoding of those same octets: standard alphabet, padded, unwrapped, no line breaks.

No elisions. A truncated, abridged, or otherwise partial copy of a segment is not that segment. A Producer MUST NOT ship an elided segment in a Record claiming L1 or above, and MUST NOT report a family's availability as present (§7.2, “exists in the Asset and is preserved”) on the strength of an elided entry. Where the full stored bytes of a segment present at acquisition cannot be carried, the Record MUST NOT claim L1 or above — with the single exception of a segment deliberately omitted under a redaction claim, which E15 governs. Carriage of a large segment by reference is 1.1 material.

The table below defines the byte range for each container and family this release can define one for. It covers what it covers: where a cell says the range is undefined, that is a normative statement about 1.0.x, not an omission to be filled in by a Producer's judgement.

ContainerFamilyPreserved octets
JPEGallThe APPn marker's payload, including any identifier prefix (Exif\0\0, http://ns.adobe.com/xap/1.0/\0, ICC_PROFILE\0 + the two sequence octets, http://ns.adobe.com/xmp/extension/\0 + its 40-octet header, Photoshop 3.0\0), and excluding the two-octet FFEn marker and the two-octet length field.
PNGallThe full chunk as stored — the 4-octet length, the 4-octet type, the chunk data, and the 4-octet CRC. Compressed chunk data (iCCP, zTXt, compressed iTXt) is preserved compressed.
WebP (RIFF)allThe RIFF chunk payload: the octets after the 4-octet FourCC and the 4-octet size field, excluding any odd-size pad byte.
GIFxmp, icc, otherThe complete Application Extension block as stored, from the 0x21 0xFF introducer through the block-size octet, the 11-octet application identifier and authentication code, every data sub-block, and the 0x00 block terminator. Adobe's XMP-in-GIF 258-octet magic trailer lies inside that range and is preserved with it, unaltered.
BMPiccWhere bV5CSType is 'MBED': exactly bV5ProfileSize octets beginning at bV5ProfileData octets after the first octet of the BITMAPV5HEADER. (bV5ProfileData is an offset from the header's start, not from the file's.) BMP carries no other family in 1.0.x scope.
TIFFicc, xmp, iptc-iim, c2pa, otherThe value bytes of the IFD entry that carries the family, as stored: icc ⇒ tag 34675, xmp ⇒ tag 700, iptc-iim ⇒ tag 33723. The length is the entry's count × its type's octet size. Where that length is ≤ 4 the value is stored inline in the entry's value/offset field and the preserved octets are those first length octets of that field, never the whole 4-octet field; where it exceeds 4 the value/offset field is an offset into the file and the preserved octets begin there.
TIFFexifUndefined in 1.0.x — see below.
HEIC / AVIF (ISOBMFF)exif, xmp, other itemsThe octets of the item, as located by iloc: the item's extents, concatenated in extent order, exactly as stored. For an Exif item this includes the leading 4-octet exif_tiff_header_offset field (ISO/IEC 23008-12 §A.2.1) — it is part of the item as stored, and excluding it would make the preserved bytes something the Asset does not contain.
HEIC / AVIF (ISOBMFF)iccThe ICC profile octets carried in the colr box whose colour_type is prof or rICC: the box payload after the 4-octet colour_type field.
HEIC / AVIF (ISOBMFF)c2pa, other box-carried dataThe box payload: the octets after the box header, where the header is the 4-octet size and 4-octet type, plus the 8-octet largesize where size == 1, plus the 16-octet extended usertype where type == 'uuid'. C2PA manifests in BMFF are carried in a uuid box, so both extensions apply in the common case.

Why TIFF exif is undefined. In JPEG, WebP, HEIF and GIF, EXIF is a self-contained blob that the container stores as one unit, so “the bytes of the EXIF segment” names something. In TIFF it does not: EXIF is the container's own IFD structure. Tag 34665 holds a 4-octet LONG pointer to the Exif IFD, so preserving “the entry's value bytes” would preserve four octets of an offset; and the fields a Consumer wants are spread across IFD0, the Exif IFD, the GPS IFD and the Interoperability IFD, interleaved with the image's own strip and tile pointers. No contiguous byte range contains them and no serialization of them is defined by any specification this release can cite.

Accordingly: byte-exact preservation of the exif family is not defined for TIFF in VER 1.0.x. A Producer processing a TIFF Asset MUST NOT emit a raw[] entry with segment: "exif" — there is no defined range for it to be the bytes of, and a constructed blob would violate P1.

The family is still reported through availability, and the state is pinned here so that two Producers do not choose differently. Where a TIFF Asset carries an Exif IFD, metadata.availability.exif is unknown; where it carries none, absent. It is not present: §7.2 defines present as “exists in the Asset and is preserved”, and nothing is preserved here, so present would be a false statement about the Record's own contents. For this one case unknown is read as the Producer can state no other truthful value, which is the residual role §7.2 gives it. A Producer SHOULD say so in availability_basis. Per-family availability semantics rich enough to say “exists, carriage undefined” are 1.1 material, and this case is a reason for them.

The EXIF octets themselves are not lost: they are part of the Asset, and the Asset is covered byte-for-byte by identity.content_hash. At L2+ the EXIF values still reach the Record through the reconciled normalized view (§7.3), which is a parsed view and never claimed to be byte-exact.

A TIFF Asset may therefore still reach L1, L2 and L3: L1 requires preservation of the segments this release defines a range for, and this release defines ranges for TIFF icc, xmp, iptc-iim, c2pa and other. Defining an EXIF-for-TIFF serialization is 1.1 material.

Reassembly (informative). The rules above say what a Producer stores. They do not say how a Consumer reconstitutes a logical payload from several stored segments, because that reconstruction is container-specific and belongs to the container's own specification, not to VER. Concatenating raw[] entries verbatim is wrong for every multi-segment case in scope; the following notes are offered so that no implementer derives that rule by default.

  • JPEG ICC (APP2). Each entry begins with the 12-octet ICC_PROFILE\0 identifier and two sequence octets (chunk number, chunk count), both of which are part of the stored segment and both of which are part of the preserved bytes. To rebuild the profile a Consumer strips those 14 octets from each entry and concatenates the remainders in ascending chunk-number order, which for a well-formed Asset is also file order.
  • JPEG Extended XMP (APP1). Per the Adobe XMP Specification Part 3, each extended-XMP marker carries the 35-octet namespace http://ns.adobe.com/xmp/extension/\0, a 32-octet ASCII GUID, a 4-octet total length and a 4-octet offset — 75 octets — before its slice of the packet. A Consumer strips those 75 octets, groups slices by GUID, and places each slice at the byte offset its own header names. Concatenation in array order is not equivalent: the offsets are authoritative and the markers need not be stored in offset order.
  • PNG iCCP. There is nothing to reassemble. The PNG specification permits at most one iCCP chunk per datastream and the profile is a single zlib stream inside it. A Consumer strips the chunk framing, then the profile name and compression-method octets, then inflates.
  • GIF Application Extensions. The payload is the concatenation of the data sub-blocks, each stripped of its leading length octet — and for XMP, with Adobe's 258-octet magic trailer removed after the sub-block structure has been unwound.
  • ISOBMFF items. An item's extents are already concatenated into a single raw[] entry by P2, so the entry is the item.

There is deliberately no VER-level “concatenate the entries of a family” rule. A Consumer that needs a reassembled payload uses the container's rules; a Consumer that needs only evidence uses the entries as stored.

Enforcement. The conformance profile validator checks P1–P3 as far as a Record's own contents allow: it recomputes each entry's sha256 over the decoded bytes (VER1302), and it reports, once per family, any family whose metadata.availability state is present at L1+ with no corresponding raw[] entry (VER302, mapping the availability key iptc to the segment family iptc-iim per E21). It cannot check byte-exactness against an Asset it does not have; that is what the conformance corpus is for.

[Classification] patch-correction

[Findings] REC-03, EX-01, SCH-02, SCH-16 (ordinal deferred to 1.1)


E12 — “Conformant decoder”, defined

[1.0.0 text] §4 step 1: “Decode the Asset with a conformant decoder into RGB or RGBA at full resolution. Undecodable inputs MUST be rejected.” §11 makes CPNP-1 mandatory at every conformance level.

[Defect] “Conformant decoder” is used normatively and never defined. Since CPNP-1's output is hashed, decoder disagreement — chroma upsampling, IDCT precision, palette expansion — propagates directly into the identity tier, and a Producer has no way to establish that its decoder qualifies.

[Correction] (disposition source: Studio PRD §16.1)

A decoder is conformant for CPNP-1 with respect to a published implementation profile: a named, versioned pinning of the decode toolchain (library and version, build options that affect pixel output, chroma upsampling and IDCT selections) together with a reference corpus of Assets and their expected Canonical Buffer digests. An implementation is conformant for that profile iff it reproduces every expected digest in the corpus exactly.

An implementation that has not been evaluated against a published profile MAY produce Records, but those Records' identity claims are PROVISIONAL: the Producer MUST NOT advertise cross-producer pixel_hash comparability for them, and a Consumer MUST NOT treat pixel_hash equality across unevaluated implementations as render identity.

Publication of the first reference corpus and implementation profile is a VER 1.1 deliverable. Until it exists, every implementation is PROVISIONAL in this sense, and this annex says so rather than implying otherwise.

[Classification] patch-correction

[Findings] SPEC-07 (standards-fresh-eyes), SPEC-PIXHASH-TOL, E1


E13 — Conformance-table corrections

[1.0.0 text] §11, row 8: “Contextual embeddings w/ source provenance | — | — | — | M”. §8, first sentence: “Contextual embeddings are optional, additive, and provenance-graded.” §11, row 2: “≥ 1 canonical_visual embedding + full space descriptor | M | M | M | M”. §6.1 lists reference as a Space Descriptor Field; the schema does not require it.

[Defect] Two defects in one table. (i) Row 8 mandates at L3 what §8 declares optional; read literally, L3 requires at least one contextual embedding, which no producer of a caption-free asset could ever satisfy. (ii) “Full space descriptor” is mandatory at every level and is never defined; because reference is schema-optional, a descriptor can be “full” while carrying no reference set and no tolerance — which makes §6.2's conformance test (“an implementation conforms to a space iff its mean cosine similarity against the published reference vectors meets the declared tolerance”) unevaluable, and defeats §0's claim that interoperability becomes testable.

[Correction]

Row 8 is corrected to read: “Contextual embeddings w/ source provenance | — | — | — | M\*”, with the footnote below. The alternative reading — that L3 requires a contextual embedding — is rejected: it is a tightening no 1.0.0 Record was written against.

* Contextual embeddings remain optional at every level (§8). Where a Record carries one at L3, its source object MUST carry class and text_sha256, and MUST carry provenance identifying where the text came from, in the form the class determines:

source.classwhat provenance MUST name
ocr_pixelsthe OCR system that read the Canonical Buffer — its space_id where it is declared as a VER space, otherwise a pinned engine name and version
caption_autothe captioner — its space_id where declared as a VER space, otherwise a pinned model identity
caption_human, title, descriptionthe metadata field the text was taken from, named in the vocabulary the normalized view uses (for example dc:description)
alt_text, surrounding_text, page_contextthe acquisition-side locator the text was taken from (for example og:image:alt at source_uri)

lang SHOULD be present wherever the Producer knows it.

“Full space descriptor” means the complete §6.1 field set: space_id, kind, model (with weights_sha256), dim, dtype, normalization, metric, preprocessing, and reference with both set_uri and tolerance_cosine; and a preprocessing block carrying every member §6.1 enumerates for the space's kind:

kindrequired preprocessing members
visualresize, interpolation, antialias, crop, mean, std, channel_order (seven)
texttokenizer, tokenizer_sha256, max_length, truncation, casing (five)
fusednone — a fused space's geometry is fixed by its recipe (E20), not by a preprocessing block

What §11 row 2's M requires, and what it does not. Row 2's mandatory cell is satisfied structurally, by the members the schema already requires of a spaceDescriptor. A Record whose canonical_visual space carries no reference block, or an under-populated preprocessing block, still satisfies §11 row 2 and can claim any conformance level. This is the reading that preserves every already-conformant Record, and this item settles it rather than leaving it open.

What such a descriptor cannot do. A descriptor that is not full in the sense above cannot back a cross-implementation conformance claim. §6.2's conformance test — mean cosine similarity against published reference vectors meeting a declared tolerance — is unevaluable without reference, and the vectors are not reproducible without the full preprocessing. Therefore: the Producer MUST NOT advertise vectors in such a space as interoperable, and a Consumer MUST NOT treat them as comparable with another implementation's vectors in the same nominal space. Such vectors are PROVISIONAL in E12's sense.

These are delivered as profile rules, not as schema required additions: adding reference to the schema's required set would invalidate 1.0.0 Records at structural-validation time, which this release does not do.

Enforcement. The conformance profile validator reports both conditions as warnings, matching the ruling above that they do not defeat §11 row 2: VER404 where a space descriptor carries no reference block, and VER405 where a descriptor's preprocessing omits any member the table above lists for that descriptor's kind — checked per kind, seven members for visual and five for text, with no preprocessing requirement for fused. Whether either becomes an error — which would make a reference-less descriptor non-conformant — is a 1.1 ratification question, not a 1.0.x one.

A standing caveat. E12 records that no reference corpus or implementation profile is yet published, and E26 records that every reference-set URI in every VER example is illustrative and not retrievable. So in practice today a reference block names a set nobody can fetch. That is a real limitation and the reason this item makes reference a warning rather than a requirement: a mandatory pointer to an unpublished artifact would be a conformance bar no honest Producer could clear.

[Classification] patch-correction

[Findings] SPEC-03 (standards-fresh-eyes), SPEC-21, SPEC-09 (embedding-index), SCHEMA-06, SCHEMA-PREPROC-EMPTY


E14 — Reconciliation precedence, completed

[1.0.0 text] §7.3: “Reconciliation precedence, consistent with Metadata Working Group heritage and the C2PA trust model: C2PA-verified assertion > XMP > IPTC-IIM > EXIF.” Schema, metadata.conflicts[].values[].source: ["c2pa", "xmp", "iptc-iim", "exif", "platform"].

[Defect] The schema admits a fifth source, platform, that the precedence order does not rank. A platform-supplied value can therefore participate in a conflict with no defined outcome, and — as the 1.0.0 golden example demonstrates — can be written into the normalized view with no in-asset origin at all.

[Correction]

The reconciliation precedence order is:

C2PA-verified assertion > XMP > IPTC-IIM > EXIF > platform.

platform denotes a value supplied by the acquisition environment rather than by the Asset (an og:image:alt attribute, a CMS field, an API response). A platform value:

  • always carries trust tier unsigned (§7.4);
  • MUST NOT win a conflict against any in-asset source, whatever its apparent quality;
  • MUST be recorded in conflicts[] with source: "platform" when it disagrees with an in-asset value, rather than silently replacing it.

A Record that places a platform-derived value in metadata.normalized without a corresponding trust[] entry at tier unsigned is non-conformant at L2+.

[Classification] patch-correction

[Findings] SPEC-17, EX-03


E15 — Redaction: which shape goes where, and the raw-preservation carve-out

[1.0.0 text] §7.5: “A redacted value is replaced by { redacted: true, reason, salted_sha256 } with the salt held out-of-record.” §7.1: “Producers at conformance level L1+ MUST preserve every metadata segment present at acquisition, byte-exact.” Schema, metadata.redactions[].items: required ["field", "reason"], additionalProperties: false, optional salted_sha256.

[Defect] Two distinct defects, both material.

(i) Shape mismatch. The §7.5 literal is rejected by redactions[]: it lacks the required field and its redacted member is forbidden by the object's closure. The same literal is silently accepted inside metadata.normalized, which is unconstrained. The specification's own normative shape is legal only in the one place the schema does not police.

(ii) Preservation defeats redaction. §7.1's L1+ MUST and §7.5's redaction-with-proof are in direct conflict. GPS coordinates removed from the normalized view are still shipped verbatim inside the preserved raw EXIF bytes. A Record can therefore claim availability.exif: "redacted" while redistributing the exact value it claims to have redacted — a legal exposure under GDPR-class regimes, and the defect §0 claimed to have fixed.

[Correction]

Two shapes, two places. The §7.5 literal { redacted: true, reason, salted_sha256 } describes the in-normalized-view replacement value: the object that stands where the sensitive value would otherwise appear inside metadata.normalized. The metadata.redactions[] entry { field, reason, salted_sha256 } is the ledger: the record-level statement that a redaction occurred, keyed by the field it applies to. The two are complementary and a Record at L2+ that redacts a value SHOULD carry both; neither is a variant spelling of the other.

Preservation is subordinate to redaction. Where a metadata segment carries a §7.5 sensitive-by-default value that the Producer has redacted, the §7.1 L1+ preservation MUST is subordinate to §7.5: the segment MUST be omitted from raw[] entirely. A Record MUST NOT claim the redaction of a value while preserving a raw segment that still contains it, and MUST NOT carry a scrubbed substitute in its place. The corresponding family's availability state MUST be redacted.

Why omission and not in-segment scrubbing. Scrubbing a segment and shipping the remainder is not representable in VER 1.0.x. A raw[] entry has exactly three members — segment, bytes, sha256 — and none of them distinguishes acquired evidence from a Producer-edited derivative. A scrubbed segment would therefore arrive at a Consumer indistinguishable from a byte-exact preserved one, and would masquerade as acquisition evidence while being a Producer artefact: precisely the confusion §7.2 exists to prevent between “we removed it”, “it was never there”, and “the platform destroyed it”. Omission says the same thing without lying about what the bytes are. Scrubbed-segment carriage with an explicit marker — a per-entry flag saying this segment was edited, here is what was removed and its salted proof — is 1.1 ratification material.

Two envelopes. The intended operational pattern is two Records over one Asset: a private evidence Record that preserves every segment byte-exact and never leaves the producing system, and a shareable Record in which the sensitive segments are omitted, the redactions are declared, and the proofs are carried. A single Record cannot be both, and 1.0 provides no way to be both. Partial in-segment redaction — redacted byte ranges plus a salted proof of the removed octets — is 1.1 material.

Compatibility direction. This is the one deliberate relaxation in 1.0.1, and §1's property 3 names it as such. Under a literal reading of 1.0.0 §7.1, an L1+ Producer that dropped a GPS-bearing EXIF segment was non-conformant, because §7.1's MUST admitted no exception. Under this annex that Producer is conformant — indeed required to behave that way. The consequence is stated rather than buried: a Record that is 1.0.1-conformant on these grounds may be judged 1.0.0-non-conformant by a Consumer applying §7.1 literally. No Record becomes invalid: the schema is unchanged in this area and nothing fails structural validation. COMPATIBILITY.md carries this as its one relaxation row.

Enforcement. The conformance profile validator reports a family whose availability state is redacted while a raw[] entry for that same family is still present as an error (VER1005), mapping the availability key iptc to the segment family iptc-iim per E21. Its remediation is “omit the segment”, which is the only remedy this item permits.

Open item. Whether salted_sha256 is REQUIRED on a redactions[] entry is unresolved: §7.5 names the proof as the mechanism's entire point (“redaction-with-proof” without the proof is deletion), while the schema makes it optional. This annex does not decide it. The conformance profile validator emits a warning (VER1002) where it is absent, and the decision is tracked in ADR-0006.

[Classification] patch-correction, with an open-ADR sub-item (ADR-0006)

[Findings] SPEC-02 (standards-fresh-eyes), SCH-14, App. D #8


E16 — record_id and created_at, defined

[1.0.0 text] The specification contains no definition of either field. record_id appears once, inside the §10 abridged example ("record_id": "a3f1c2e4-9b7d-4e21-8c55-2f6a0d9b3e71"), and created_at once ("created_at": "2026-08-21T14:02:11Z"). Both are in the schema's required list.

[Defect] Two of the eight required top-level members have no semantics, uniqueness scope, derivation rule, or stability rule. The ecosystem has already filled the vacuum incompatibly: one implementation derives record_id as uuid5(namespace, pixel_hash), which gives two Records describing the same pixels but different assets, metadata, and producers the same identifier — i.e. it makes record_id an asset identifier, not a record identifier.

[Correction]

record_id identifies the Record document, not the Asset. It is a UUID (RFC 9562) chosen by the Producer and MUST be unique per (producer, record). It MUST NOT be derived from pixel_hash alone, since distinct Records over the same rendered pixels — different producers, different spaces, different metadata, different times — are distinct Records and MUST NOT collide.

Producers SHOULD use UUIDv5 over a producer-scoped stable identity when idempotent re-issue is wanted (so that re-processing the same Asset in the same producer yields the same Record), and UUIDv4 otherwise. A UUIDv5 preimage that includes the producer identity and content_hash satisfies both requirements.

created_at is the instant the Record document was assembled. It SHOULD be greater than or equal to every provenance.chain[].at and every embeddings[].produced_at in the Record. A Record whose created_at precedes its own last chain event is self-inconsistent; the profile validator reports it as a warning (VER1203) rather than an error, because clock skew across a distributed pipeline is a real and benign cause.

Chain ordering. provenance.chain[] SHOULD be chronological: at values non-decreasing, the first no earlier than provenance.acquisition.acquired_at. §9's description of the chain as “append-only” is a statement of producer discipline, not a verifiable property: VER 1.0 provides no hash-linkage, no per-entry identifier, and only a SHOULD-level signature at L3. Append-only proof mechanisms — per-entry prev_sha256, or a mandatory signature — are 1.1 material and cannot be retrofitted by annex.

VER 1.0 defines no cross-producer Record identifier. Two Producers describing the same Asset produce two unrelated record_id values, and that is correct.

[Classification] patch-correction

[Findings] SPEC-15, SPEC-16, REC-09, IMPL-RECORD-ID-COLLIDES


E17 — The signature payload, defined

[1.0.0 text] §9: “Record signing: the VER chain is honest but not self-proving. At L3, Producers SHOULD sign the Record — detached JWS or COSE over the JCS canonical form (RFC 8785) of the JSON.”

[Defect] The signature is stored inside the object it signs (provenance.signature) and the specification never states which subtree is excluded before canonicalization. Whole record? Record minus provenance.signature? Record minus provenance? Each yields a different payload, so no independent implementation can verify anyone else's signature. The feature is unimplementable as written — and, because nothing interoperable exists today, nothing breaks by defining it now.

[Correction]

The signature payload is the Record with the member /provenance/signature removed entirely — not set to null, not set to an empty object, removed — serialized in RFC 8785 JCS canonical form and encoded as UTF-8.

The signature is detached: provenance.signature.value carries the detached JWS — RFC 7515 Appendix F, “Detached Content”, the form in which the payload segment between the two dots is empty — or the COSE_Sign1 structure with a detached payload (RFC 9052 §4.1, payload set to nil).

key_id is REQUIRED for a signature to be verifiable in practice, and a Producer that omits it SHOULD NOT expect verification to succeed. The schema keeps it optional; the profile validator does not fail on its absence.

A Consumer that verifies a signature MUST re-derive the payload by removing /provenance/signature from the received Record and re-canonicalizing. It MUST NOT verify over the received bytes as transmitted, since JSON serialization is not canonical.

On canonicalization (informative). The schema pins provenance.signature.canonicalization as {"const": "rfc8785-jcs"}, so in VER 1.0 a Record carrying any other value fails structural validation and never reaches a Consumer as a valid VER Record. There is therefore no VER 1.0 consumer rule to state about other values: the case cannot arise. The profile validator's VER1401 is the same constraint restated at profile level for Records that reach it without schema validation. Should a later release widen the member beyond rfc8785-jcs, the correct behaviour for a Consumer that does not implement the new canonicalization is to treat the signature as unverifiable rather than as invalid — an unrecognized canonicalization is ignorance, not evidence of tampering. That guidance is recorded here for the 1.1 drafters; it constrains nothing in 1.0.

[Classification] patch-correction

[Findings] SPEC-14


E18 — space_id is unique within a Record

[1.0.0 text] Schema: "spaces": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/spaceDescriptor" } } — no uniqueness constraint. §6.2: “Descriptors are immutable once published. New weights, preprocessing, dtype, or metric ⇒ new space_id. This prevents silent model drift.”

[Defect] A Record may declare two spaces[] entries with the same space_id and contradictory contents — the same identifier with a different weights_sha256, dim, or normalization — and validate cleanly. §6.2's immutability rule is thereby unenforceable inside the one document that carries both copies. Downstream, a mapper that indexes spaces[] by space_id resolves the contradiction by array order, silently.

[Correction]

space_id MUST be unique within a Record's spaces[] array. A Record carrying two entries with the same space_id — identical or not — is non-conformant, and a Consumer MUST reject it rather than resolve it by array order or by last-wins.

A Consumer that encounters two Records presenting different descriptors under the same space_id MUST treat the vectors in those spaces as incomparable, and SHOULD treat the conflict as evidence that at least one Producer has violated §6.2's immutability rule.

The schema cannot express key uniqueness over an array of objects without a rewrite, so this is delivered as a profile rule (VER401). Binding an inline descriptor to its published form by hash — so drift is detectable rather than merely prohibited — is 1.1 material (descriptor_sha256).

[Classification] patch-correction

[Findings] App. D #14, WIRE-32, SPEC-22


E19 — Grade is derived from source.class, not carried

[1.0.0 text] §8: “Consumers MUST be able to filter by role, source class, and grade — grade-aware retrieval is the point of grading”, over a table assigning grades to source classes. §12: “retrieval systems SHOULD weight by grade and trust tier.” The string grade does not appear in the schema.

[Defect] A consumer MUST is placed on a data element that does not exist in the serialization. Worse, the prose table's grade for caption_auto is typeset with U+2212 MINUS SIGN (A−), which is not an interoperable token; implementations silently transliterate it to ASCII A-, and nothing says they should.

[Correction]

Grade is derived, never serialized. VER 1.0 carries no grade member, and a Record MUST NOT invent one. A Consumer computes grade from embeddings[].source.class using this normative mapping:

source.classgrade token
ocr_pixelsA
caption_autoA-
caption_humanB
alt_textB
titleB
descriptionB
surrounding_textC
page_contextC

Grade tokens are ASCII: A, A-, B, C. The A- token uses HYPHEN-MINUS (U+002D). The typographic A− (U+2212) in §8's table is a typesetting artifact and MUST NOT appear in any interface, query parameter, index payload, or API response.

A Consumer satisfies §8's filtering MUST by implementing this table. No new field is required in 1.0, and adding an explicit optional grade — 1.1 material — MUST NOT be permitted to contradict the derivation.

[Classification] patch-correction

[Findings] SPEC-23, SPEC-06 (embedding-index), TEST-GRADE-TOKEN


E20 — Fused recipe inputs, defined

[1.0.0 text] §8: “Fusion is permitted only as a distinct role: "fused" embedding in a kind: "fused" space with a declared recipe { inputs, weights, method }.” Schema, embedding.recipe: inputs is an array of strings with minItems: 2; weights is an unconstrained array of numbers; method is a free string.

[Defect] inputs has no defined referent. Implementations read the entries as role names; space identifiers and per-embedding identifiers are equally plausible readings of the same schema. weights need not match inputs in length, so a two-input recipe with five weights validates. And §6.2's “new X ⇒ new space_id” list omits the recipe, so changing fusion weights keeps the same space_id while producing a different geometry — exactly the silent drift §6.2 exists to prevent.

[Correction]

recipe.inputs are space_id values declared in the same Record's spaces[] array. Every entry MUST resolve to a spaces[].space_id in that Record, and the array MUST name at least two distinct spaces. It is not a list of roles and not a list of free-form labels.

recipe.weights, when present, aligns 1:1 with inputs: same length, same order, weights[i] applying to inputs[i]. A Record whose weights length differs from its inputs length is non-conformant.

The recipe is part of the fused space's identity. §6.2's rule is read as: new weights, preprocessing, dtype, metric, or fusion recipe ⇒ new space_id. Changing method or weights while keeping the space_id constant is non-conformant.

method remains a free string in 1.0. A registry for fusion methods is 1.1 material; until then, Consumers MUST treat an unrecognized method as reason to consider the fused vectors incomparable with their own.

[Classification] patch-correction

[Findings] SPEC-08 (embedding-index), SCRIPT-FUSED-RECIPE-INPUTS


E21 — Availability: family naming, other segments, and basis scope

[1.0.0 text] §7.2: “Each metadata family carries exactly one state.” §7.1 names the families exif|xmp|iptc-iim|icc|c2pa|other. Schema metadata.availability is closed over five keys: exif, xmp, iptc, icc, c2pa. Schema metadata.availability_basis is one record-level string with the description “Required narrative basis when any family is stripped_suspected.”

[Defect] Three inconsistencies. (i) The availability key is iptc while the segment family is iptc-iim; §10's abridged example is the only place iptc appears in the prose, so the naming split is an accident of the example. (ii) other is a legal raw-segment family with no availability key, so §7.2's “exactly one state” is unsatisfiable for any preserved other segment. (iii) availability_basis is a single record-scoped string, so two families stripped for different reasons cannot each state their basis, and nothing conditions its presence on a stripped_suspected value existing.

[Correction]

Naming. The availability key iptc and the raw-segment family iptc-iim denote the same metadata family. A Consumer MUST treat them as equivalent when relating metadata.availability to metadata.raw[]. The naming split is a defect preserved for compatibility; it is not renamed in 1.0.x because adding an iptc-iim availability key would be a loosening.

other segments. VER 1.0 has no availability key for the other family. A preserved other segment is recorded in raw[] and carries no availability state; §7.2's “each family carries exactly one state” is read as applying to the five keyed families only. A Producer MUST NOT invent an availability key for it — metadata.availability is closed. Per-segment and other-family availability states are 1.1 material.

Basis. availability_basis is record-scoped in 1.0: one narrative covering every family that is stripped_suspected in that Record. It is REQUIRED and MUST be non-empty whenever any family's state is stripped_suspected (§7.2's “The basis MUST be stated”), and the profile validator enforces this (VER901). Where two families are stripped for different reasons, the single narrative MUST address both. Per-family basis is 1.1 material.

[Classification] patch-correction, with two sub-items deferred as additive-1.1

[Findings] App. D #9, SCH-12, SCH-13, SPEC-03 (core-pipeline), SPEC-AVAIL-BASIS-SCOPE


E22 — Digest encodings and registered perceptual algorithms

[1.0.0 text] §5: “A Record SHOULD carry at least one perceptual digest. Registered perceptual algorithms in 1.0: pdq/1.0 (256-bit, preferred) and phash-dct/64 (64-bit baseline).” Schema: "value": { "type": "string", "pattern": "^[a-f0-9]+$" } with no length bound.

[Defect] §5 registers two algorithms by name and bit width and specifies nothing else: not the hex case, not the bit order, not the algorithm parameters, not the input the digest is computed over. The schema pins lowercase hex implicitly through its pattern but never bounds the length, so a one-character pdq/1.0 digest validates — and so does the 96-hex digest in the 1.0.0 golden example. The registered widths were unenforceable.

[Correction]

Encoding. Every digest in a Record — content_hash, pixel_hash, weights_sha256, text_sha256, params_sha256, manifest_sha256, salted_sha256, byteRef.sha256, and every perceptual value — is lowercase hexadecimal, unseparated, with no 0x prefix. Uppercase hex is non-conformant.

Widths. pdq/1.0 is a 256-bit digest and its value is exactly 64 hex characters. phash-dct/64 is a 64-bit digest and its value is exactly 16 hex characters. The 1.0.1 schema enforces both, conditioned on alg.

Input. A perceptual digest is computed over the Canonical Buffer (§4), never over the Asset bytes and never over a re-decoded or resized intermediate other than the algorithm's own defined preprocessing.

Parameters. pdq/1.0 is PDQ as published by Meta's ThreatExchange reference implementation, with that implementation's bit order, rendered as 64 lowercase hex characters. phash-dct/64 is the 64-bit DCT perceptual hash in the form produced by the imagehash reference implementation (32×32 luminance DCT, 8×8 low-frequency block, median threshold, row-major, most-significant-bit first), rendered as 16 lowercase hex characters.

Full parameter pinning with published test vectors — the only thing that makes two implementations' digests actually comparable — is deferred to the 1.1 perceptual-algorithm registry. Until those vectors exist, a Consumer SHOULD treat a perceptual digest from an unfamiliar Producer as a within-producer clustering signal only.

Algorithm prefixes. A digest value carries the digest and nothing else. A Producer MUST NOT prefix the value with its algorithm name (pdq/1.0:…); the algorithm is carried in the sibling alg member.

[Classification] patch-correction

[Findings] HASH-02, HASH-01, SCH-07, App. D #1


E23 — Validators MUST assert format and contentEncoding

[1.0.0 text] §10: “A Record is a UTF-8 JSON document conforming to the companion schema ver-record.schema.json (JSON Schema 2020-12).” The schema constrains record_id with format: "uuid" (one), date-times with format: "date-time" (five: created_at, embeddings[].produced_at, spaceDescriptor.provenance.published_at, provenance.acquisition.acquired_at, and provenance.chain[].at), URIs with format: "uri" (three), and payloads with contentEncoding: "base64" (two) — eleven keywords in all, identical in the 1.0.0 and 1.0.1 schemas.

[Defect] JSON Schema 2020-12 defines format and contentEncoding as annotations, not assertions. Under a default 2020-12 validator every one of those eleven constraints is inert: record_id: "not-a-uuid" passes, created_at: "yesterday" passes, acquired_at: "2026-13-45T99:99:99" passes, source_uri: "not a uri at all ::::" passes, and raw[0].bytes: "!!!! not base64 !!!!" passes. The specification's structural guarantees over identifiers, timestamps, and payloads did not exist.

[Correction]

A validator claiming to check VER conformance MUST assert format and contentEncoding, not merely annotate them:

  • format: "uuid" — the value MUST be a well-formed UUID (RFC 9562 textual form).
  • format: "date-time" — the value MUST be a well-formed RFC 3339 date-time. Producers SHOULD emit UTC with a Z designator and second-level or finer precision.
  • format: "uri" — the value MUST be a well-formed URI (RFC 3986), absolute.
  • contentEncoding: "base64" — the value MUST decode as RFC 4648 §4 standard base64: standard alphabet, padded, unwrapped, no line breaks.

This is a profile strictness requirement layered over the schema, not a schema change: the schema's own keywords are unchanged and a generic 2020-12 validator remains free to treat them as annotations. A Record that fails any of these assertions is non-conformant regardless of what a generic validator reports. The conformance profile validator implements them (VER202).

[Classification] patch-correction

[Findings] SCH-01, SCH-02


E24 — Vector carriage: exactly one, dtype inheritance, and byte layout

[1.0.0 text] §6.2: “Vectors MAY be inline (float array), base64 (dtype-tagged, little-endian), or by reference { uri, sha256, byte_length } for large-vector hygiene.” Schema: embedding.oneOf over vector / vector_b64 / vector_ref; dim and dtype optional on the embedding; vector_b64 described as “dtype-tagged via the embedding's dtype field; little-endian”.

[Defect] §6.2's permissive “MAY” reads as a menu from which a Producer may take more than one item, while the schema's oneOf makes the three carriages mutually exclusive — so the prose and the normative schema disagree about a Record that carries both an inline vector and a reference. Separately, a base64 or by-reference vector is literally undecodable as specified: dtype is optional with no stated fallback, byte order is stated for base64 and not for vector_ref, and no rule relates byte_length to dim × sizeof(dtype).

[Correction]

Exactly one carriage. An embedding MUST carry exactly one of vector, vector_b64, or vector_ref. The schema's oneOf is correct and intentional; §6.2's “MAY” selects among the three, it does not permit combining them. Relaxing this would admit Records that a 1.0.0 validator rejects and is therefore not done.

Dtype and dim inheritance. When embeddings[].dim or embeddings[].dtype is present it MUST equal the referenced space descriptor's corresponding value. When absent, the descriptor's value applies. A Record whose embedding contradicts its own descriptor is non-conformant. Whether dim/dtype become mandatory on the embedding is tracked in ADR-0008; the profile validator warns (VER606) when they are omitted, because a Consumer that has not resolved the descriptor cannot decode the payload.

Byte layout. For vector_b64 and for the payload behind vector_ref, multi-byte components are little-endian, packed contiguously with no padding and no header. Component sizes: fp32 4 octets, fp16 2, bf16 2, int8 1.

Binary vectors. For dtype: "binary", dim is the number of bits. Components are packed 8 per octet, most-significant-bit first within each octet, in ascending component order; the payload length is ceil(dim / 8) octets. Note that dtype: "binary" has no usable metric in VER 1.0 — the metric enum offers only cosine, dot, and l2, none of which is meaningful over packed bits — so binary spaces SHOULD NOT be declared until 1.1 registers hamming.

This is a SHOULD NOT, not a MUST NOT: a binary space is structurally valid in 1.0, and the packing and length rules above are stated precisely so that a Record which declares one is still unambiguous. Where a conformance fixture declares a binary space, it is exercising those packing and length rules; it is not an endorsement of the practice this paragraph advises against, and any such fixture's description says so.

Length rules. len(vector) == dim for inline carriage; len(base64_decode(vector_b64)) == dim × component_size (or ceil(dim/8) for binary); vector_ref.byte_length equals the same quantity. All three are enforced by the profile validator (VER603VER605).

[Classification] patch-correction

[Findings] SCH-08, SCH-09, App. D #15, SCRIPT-DTYPE-ASSUMED


E25 — source_bytes_ref points at the Asset content_hash covers

[1.0.0 text] Schema: "source_bytes_ref": { "$ref": "#/$defs/byteRef" } under image, with byteRef requiring uri, sha256, byte_length. The 1.0.0 golden example keeps image.source_bytes_ref.sha256 equal to identity.content_hash.value, so the intended invariant is visible but unstated.

[Defect] Nothing requires the reference to point at the Asset it is a reference to. source_bytes_ref.sha256 may be set to any 64-hex string and the Record validates, which turns the one field that lets a Consumer re-fetch and re-verify the Asset into an unchecked claim.

[Correction]

image.source_bytes_ref, when present, MUST reference the Asset described by the Record. Its sha256 MUST equal identity.content_hash.value, and its byte_length MUST be the Asset's length in octets.

A Consumer that fetches source_bytes_ref.uri MUST verify the retrieved bytes against identity.content_hash.value before using them, and MUST treat a mismatch as a retrieval failure rather than as a new Asset.

Enforced by the profile validator (VER1305).

[Classification] patch-correction

[Findings] SCH-18


E26 — The shipped example artifacts are illustrative, not verified

[1.0.0 text] §10: “A complete valid example ships as example-record.json.”

[Defect] The golden example was read by implementers as a golden artifact, and it is not one. Verified at audit: identity.pixel_hash.value is SHA-256(hello world); metadata.raw[0].sha256 was SHA-256(password); the pdq/1.0 digest was 96 hex characters against §5's 256-bit registration; the preserved ICC payload decodes to 30 octets whose own header declares a 3144-byte profile; the JSON-LD @context did not define the dc: prefix the document uses; and the custody chain recorded one embed event for two embeddings. Every model reference, reference-set URI, and vector URI in the file is fictional.

[Correction] (disposition source: Studio PRD §16.13)

All hashes, URIs, model identifiers, reference-set identifiers, and weight pins appearing in any VER example artifact are illustrative. They are not content-addressed, not retrievable, and not verified. No implementation may derive a conformance claim, an interoperability claim, or a test expectation from them.

Real interoperability requires artifacts the examples do not provide: a published reference corpus with expected Canonical Buffer digests (E12), published reference vector sets with per-space tolerances (§6.2, E13), and content-addressed model bundle manifests (1.1). Until those exist, a cross-producer comparability claim is unsupported.

The 1.0.1 example corrects five defects (§2.2): the PDQ length, the raw-segment digest, the JSON-LD context, the missing chain event, and — edit (e) — the elided ICC payload, which is replaced by a complete 602-octet JPEG APP2 ICC segment. With (e), the example no longer contradicts its own declared conformance_level of L3: the preserved segment is genuinely byte-exact per E11, carries the ICC_PROFILE\0 identifier and sequence octets E11's JPEG row requires, and justifies the present state E21 and §7.2 attach to it.

Everything the example says about the world remains illustrative: content_hash, pixel_hash, the perceptual digests, the model checkpoints and weights_sha256 values, the reference-set URIs, the vector URIs and their digests, and source_bytes_ref. Those cannot be made real without an Asset and a published corpus, neither of which a corrective release can invent. What the example now gets right is everything it says about itself — every internal consistency rule this annex states, it satisfies.

The prohibition on shipping an elided segment at L1 or above is normative and now lives where the byte-exactness rule lives, in E11, rather than inside an item classified editorial.

[Classification] editorial (with the five example corrections classified patch-correction)

[Findings] App. D #13, EX-01, EX-02, EX-03, EX-04


E27 — pixel_hash is a grouping key, never a primary key

[1.0.0 text] §3, identity gradient: the Byte row assigns content_hash the role “dedupe, C2PA binding”; the Pixel row assigns pixel_hash the role “render identity”. §5 requires both.

[Defect] Deployments have read pixel_hash as the global deduplication key for assets. The standard says the opposite — it assigns dedupe to the byte tier — and in fact defines no asset identity at all. Elevating pixel_hash to a primary key silently merges distinct assets that render identically (the same photograph acquired from two sources with different metadata and different provenance) and, per E1, splits assets that differ only in a consumed orientation tag.

[Correction] (disposition source: Studio PRD §16.11)

The first three bullets below are guidance: they describe how the tiers are meant to be used and impose nothing. The closing paragraph is normative and is the reason this item is not classified editorial.

pixel_hash answers one question: do these two Assets render to the same pixels under CPNP-1? It is a grouping key for rendered identity. It is not an asset identifier, not a global primary key, and not a deduplication key across producers.

  • Exact-copy deduplication and C2PA binding belong to content_hash.
  • Rendered-identity grouping across metadata rewrites belongs to pixel_hash.
  • Asset and database identity belong to the deploying system. VER 1.0 defines no cross-producer identifier for an Asset or a Record. A deployment that needs one mints its own, and does well to scope it by producer (for example (producer_did, pixel_hash), which the reference deployment uses) rather than treating a hash as globally authoritative.

Normative. An identifier minted by a deployment in this way is implementation-defined. A Producer or Consumer MUST NOT present such an identifier — or the deduplication behaviour built on it — as VER conformance, as a VER-defined property, or as interoperable with another deployment's identifiers. VER 1.0 makes no claim about it.

[Classification] patch-correction (the closing conformance-claim prohibition), with the tier-usage guidance non-normative

[Findings] SPEC-27, App. D #7, DOCTRINE-PIXHASH-HALF-APPLIED


E28 — C2PA state consistency, and what “MUST be validated” means in 1.0

[1.0.0 text] §9: “C2PA: when a manifest is present it MUST be validated; the Record stores { status: valid|invalid|missing, manifest_sha256 }. content_hash is the binding key between Record and manifest.”

[Defect] Two problems. (i) The Record carries the same fact in two independent places — metadata.availability.c2pa and provenance.c2pa.status — with no consistency rule, so availability.c2pa: "present" alongside c2pa.status: "missing" validates cleanly and asserts both that a manifest exists and that it does not. (ii) §9's unconditional MUST has no vocabulary for its own common outcomes: a manifest that cannot be validated because the trust list is unreachable, the signing certificate has expired, or the validator does not implement the manifest's claim generator is neither valid, nor invalid, nor missing. A Producer facing that case must either lie or refuse to emit a Record.

[Correction]

Consistency. A Record in which metadata.availability.c2pa is present while provenance.c2pa.status is missing — or while provenance.c2pa is absent at L3 — is non-conformant. Likewise a Record carrying a c2pa segment in metadata.raw[] while provenance.c2pa.status is missing. availability.c2pa: "present" and provenance.c2pa.status ∈ {valid, invalid} are two views of one fact and MUST agree.

Enforcement is by the profile validator (VER1103), in two arms that differ in scope because the underlying rules do. The contradiction arm — a Record that asserts a manifest exists (availability.c2pa: "present", or a c2pa entry in raw[]) while provenance.c2pa.status is the literal "missing" — fires at every conformance level, because a Record contradicting itself is wrong at L0 as much as at L3. The absence arm — a Record that asserts a manifest exists while carrying no provenance.c2pa object at all — fires only at effective level L3, because it is L3 that requires provenance.c2pa (§11; VER307). Below L3 a Record is permitted to observe that a manifest is present without having validated it.

Interim reading of the validation MUST. For VER 1.x, §9's “when a manifest is present it MUST be validated” is read as: a Producer MUST NOT report valid, invalid, or missing contrary to fact. Specifically:

  • valid MUST NOT be reported unless validation actually succeeded, and SHOULD be accompanied by manifest_sha256;
  • invalid MUST NOT be reported for a validation that did not complete;
  • missing MUST NOT be reported when a manifest is present in the Asset.

A Producer that cannot complete validation MUST NOT emit provenance.c2pa at all rather than emit a false status; such a Record cannot claim L3, which requires C2PA validation.

A fourth status value unverifiable, with an optional error_detail, is added in 1.1. It cannot be added by a corrective release: it is a new value in a closed enum, i.e. a capability addition, and a 1.0.0 validator would reject it.

[Classification] patch-correction, with the unverifiable status deferred as additive-1.1

[Findings] SCH-17, App. D #12, REC-02


E29 — model.weights_sha256 has no defined preimage in 1.0.x

[1.0.0 text] §6.1, Space Descriptor Fields: “model | family, architecture, checkpoint identifier, and weights_sha256. Weight pinning is mandatory.” Schema, spaceDescriptor.model: "required": ["family", "checkpoint", "weights_sha256"], with weights_sha256 constrained only by ^[a-f0-9]{64}$.

[Defect] The mandatory pin names a digest but never names what is digested. E22 fixes the encoding — lowercase hex, no prefix — and stops there, because encoding was the only part the audit finding it came from covered. The preimage is still undefined:

  • For a single-file checkpoint, sha256(file bytes) is the obvious reading and is what most implementers assume — but nothing in VER 1.0 says so, and the reference implementation does something else: it folds the file's name into the digest before its contents, so its value is not sha256(file bytes) even in this case.
  • For a multi-file checkpoint — sharded safetensors, a checkpoint plus its index manifest, a checkpoint carried in two serialization formats — there is no reading at all. Which files are in the set, in what order, whether names participate, and whether an index manifest counts are four independent choices, and every combination produces a different digest. Two producers pinning the same published checkpoint routinely emit different values.

Consequently the field that §12 relies on to “prevent silent weight swaps and drift” cannot be compared across producers, and a Consumer that treats two different weights_sha256 values as evidence of different weights, or two equal ones as evidence of identical weights, is over-reading it.

[Correction]

What 1.0.x defines. model.weights_sha256 is a SHA-256 digest rendered as 64 lowercase hexadecimal characters (E22). VER 1.0.x defines nothing else about it: no file set, no ordering, no manifest, no preimage. This is stated as a limitation of the release, not repaired by it — a preimage definition invented by annex would be a new construction, and this release adds no capability.

What follows for Consumers. A 1.0.x weights_sha256 is comparable only within one Producer. A Consumer MUST NOT treat equality of weights_sha256 across two Producers as evidence that the same weights were used, and MUST NOT treat inequality across two Producers as evidence that different weights were used. Within one Producer, a change in the value remains a reliable signal that something the Producer hashes has changed, which is what makes the pin useful for drift detection even now.

What follows for Producers. A Producer SHOULD document, outside the Record, which artifact its weights_sha256 covers and how it is constructed — the file set, the ordering, and whether file names participate in the preimage. A Producer MUST NOT advertise its weights_sha256 values as cross-producer comparable while no construction is published. Where the checkpoint is a single file, sha256(file bytes) is the construction Producers SHOULD use, because it is the one a Consumer can independently reproduce.

The interoperable construction is 1.1 material. The VER 1.1 draft — like everything in standards/ver/1.1-draft/, unratified — proposes model.bundle: an explicit manifest of {path, byte_length, sha256} entries whose RFC 8785 JCS canonical form is the preimage of weights_sha256. That makes the digest reproducible by anyone holding the same files, and makes the file set an auditable part of the Record rather than a producer-side convention. The design and its alternatives are ADR-0007; this item is the 1.0.x interpretation ADR-0007 refers back to.

[Classification] patch-correction (a normative limitation note; no schema or example change), with the model.bundle construction deferred as additive-1.1

[Findings] App. D #5, SC-01, SC-02, MOD-04