Errata ledgerstable
§10 Owner review (23 August 2026)
The standard's owner reviewed the full branch and issued rulings. Every row below was verified at source before it was ruled on; none is a re-litigation. Three columns matter here:
- Classification is the change class, as everywhere else in this ledger.
- Disposition names the ruling and the artifact or track that carries it.
VAL*,P*,F*andG*are the review's own ruling identifiers for the validator, producer, federation and governance tracks. - Status is
Closedwhere the remediation is in this branch's standards artifacts,Ruled — <track>where the ruling is made and the remediation belongs to another track's files, andDeferred (planned)where the review deliberately logged a deliverable rather than producing it.
Where a row supersedes an earlier disposition, it says so and names the finding.
10.1 Verified conditions in the implementation
| ID | Title | Source | Classification | Disposition | Status |
|---|---|---|---|---|---|
REV-01 | validator.py's rule runner swallows rule exceptions into an empty result, so an internal failure can report ok: true — fail-open | audit/owner | implementation-only | VAL1 — a raising rule emits VER001 validator-internal-error (error, stage 0); the report gains status: conformant | nonconformant | indeterminate; any VER001 ⇒ indeterminate; ok = (status == "conformant") | Ruled — validator track |
REV-02 | cli.py's --ignore filter drops ignored codes and recomputes ok, so a CLI flag redefines the standards result | audit/owner | implementation-only | VAL2 — ok and status are ALWAYS unfiltered; the report gains ok_after_policy_exceptions and ignored_codes; the CLI exit code follows the policy-filtered result (that is the flag's purpose) and the text output prints both | Ruled — validator track |
REV-03 | api.py writes uploads to Path(tmp) / upload.filename — filename traversal — and its path-mode ingest walks arbitrary server paths unauthenticated | audit/owner | implementation-only (security) | F1 — server-generated names (uuid + sanitized suffix); path-mode gated behind VERD_API_ALLOW_PATHS and resolved strictly beneath an allowlisted root; optional bearer auth; streaming per-file and file-count limits; clamped limit params | Ruled — federation track |
REV-04 | jetstream.py logs a handler exception and saves the cursor anyway — permanent silent event loss; delete commits ignored; update commits defeated by an early (producer, pixel_hash) dedup | audit/owner | implementation-only | F3 — cursor advances only on success or a successful dead-letter write; deletes remove the matching points and write a tombstone; updates re-process instead of dedup-skipping. Supersedes WIRE-03/WIRE-04's documented — not patched and WIRE-05's Deferred | Ruled — federation track |
REV-05 | The 1.1 draft's bundle path pattern accepted ..\evil, C:\evil and a\..\evil — it constrained a segment's shape and never its alphabet | audit/owner | additive-1.1 (draft defect) | fixed in this branch — $defs/portablePath: printable portable charset per segment, / only, no empty/./.. segment, no leading /, plus a complement-class not so a trailing newline cannot slip past $. Draft §4.2, ADR-0007 revision 3 | Closed |
REV-06 | The 1.1 draft's size limits were incoherent: a 16 MiB decoded-inline-raw cap is unreachable inside a 10 MiB serialized cap (base64 ≈ 21.3 MiB). The same incoherence sat in the validator's VER104 defaults | audit/owner | additive-1.1 (draft defect) | fixed in this branch — draft §7's three-quantity model: serialized record ≤ 10 MiB, decoded inline raw ≤ 6 MiB, decoded single vector ≤ 6 MiB, each individually reachable inside the one that contains it; referenced bytes governed separately. VAL5 aligns VER104 | Closed (standards half); Ruled — validator track (VER104) |
REV-07 | tests/test_cpnp_determinism.py::test_broken_icc_profile_does_not_kill_ingest protects the fail-open ICC behaviour annex E8 forbids | audit/owner | implementation-only | P1 — ICC fails closed per E8: a present-but-unusable profile raises CpnpError, no icc_error fallback, an absent profile still assumes sRGB; the test is replaced with fail-closed assertions | Ruled — producer track |
REV-08 | The validator wheel ships only py.typed — no schemas — so an installed wheel depends on the repository layout | audit/owner | process | VAL4 — the 1.0.0 and 1.0.1 schema JSONs become build-synced package data with a sync-check test; loader order VER_STANDARDS_ROOT → repo-tree walk → packaged resources | Ruled — validator track |
REV-09 | recordio.py and atp.py hardcode fp16 regardless of the declared dtype | audit/owner | implementation-only | P5 — dtype-aware encode and decode, byte length asserted against dim × sizeof(dtype) before write. Supersedes REC-07's and WIRE-11's documented — not patched | Ruled — producer track |
REV-10 | Records are stored at records/<pixel_hash>.json; record_id is uuid5(content:pixel) with no producer scope; the index point id collapses record variants; rkey is pixel_hash[:13] | audit/owner | implementation-only | P3 + F4 — verd/identity.py with a producer DID; record_id = uuid5(ns, producer_did | content_hash | pixel_hash); storage keyed by record_id with an atomic pixel_hash → [record_id] sidecar index; rkey = the record_id UUID hex. Applies annex E27 in code, which DOCTRINE-PIXHASH-HALF-APPLIED said was only half-applied | Ruled — producer / federation tracks |
REV-11 | hashes_only publishes the embedding unless --no-vector is passed separately, so the mode's name promises more than it delivers | audit/owner | implementation-only | P8 + F4 — privacy becomes public | public_vector | hashes_only, where hashes_only omits vector AND thumb; contradictory CLI combinations are rejected. Matches draft §14.5's normative definition | Ruled — producer / federation tracks |
REV-12 | qd.ensure_collection checks dimension only and ignores space_id; queries never filter space_id; must_tags is an OR; the migration script deletes the live collection and restores only locally-held vectors | audit/owner | implementation-only | F2 + F5 — collections bound to a space and refused on space_id mismatch; every query filters space_id; must_tags becomes a true AND with any_tags beside it; the migration script is dry-run by default, never deletes, and verifies before cutover. Supersedes EMB-02, QD-01, QD-02, MIG-01–MIG-04 | Ruled — federation track |
REV-13 | Raw-segment capture via Pillow's info dict is not byte-exact under annex E11, and the producer claims L1 anyway | audit/owner | implementation-only | P2 — stop emitting Pillow-derived raw[] as byte-exact evidence; records claim L0; availability still reported where detection is honest, with availability_basis naming the method. L1 returns only with a container-level segment extractor. Supersedes REC-01/IMPL-CONFORMANCE-SELF-DECLARED's partial fix | Ruled — producer track |
REV-14 | Emitted space descriptors carry preprocessing: {"processor", "note"} and no reference block, so they make no conformance claim (annex E13) | audit/owner | implementation-only | P7 — real preprocessing fields extracted from the processor where available; preprocessing.declared = false plus a logged conformance warning where extraction fails; no fabricated reference block — its absence is honest and VER404 is correct to warn | Ruled — producer track |
REV-15 | recordio.py writes records non-atomically | audit/owner | implementation-only | P6 — temp file plus os.replace throughout recordio, recordio_segments and space_cache; append_provenance becomes read-modify-replace. Supersedes REC-12's documented — not patched | Ruled — producer track |
10.2 Standards-track rulings
| ID | Title | Source | Classification | Disposition | Status |
|---|---|---|---|---|---|
REV-16 | ADR-0004 opens a value space with no governed registry behind it, does not bind conformance to a snapshot, does not say which tokens a consumer must understand, cannot distinguish an Asset event from a Record event, and lets sign be claimed without a signature | audit/owner | additive-1.1 | ADR-0004 Accepted — conditionally, C1–C5. C4/C5 encoded (chain target, uri, sign ⇒ signature); C2 encoded as provenance.registry; C3 stated as draft §2.3's three-tier table; C1 is a blocking deliverable | Closed (draft); conditions blocking |
REV-17 | ADR-0005's parent reference could be satisfied by identifiers that resolve to nothing (a bare record_id, annex E16) or to a group (pixel_hash, annex E27); recipe_uri needed no digest; the recipe document had no defined format | audit/owner | additive-1.1 | ADR-0005 Accepted — model adopted, shape revised. At least one globally resolvable identifier (record_uri, or {producer_id, record_id}); content_hash SHOULD-with-warning; pixel_hash as {value, cpnp_version}; recipe_uri ⇒ recipe_sha256; the recipe envelope specified (draft §3.4); eight semantic fixtures required | Closed (draft); fixtures blocking |
REV-18 | ADR-0006 was the only open decision on the standards track, and it blocked 1.1 ratification | audit/owner | open-ADR → additive-1.1 | ADR-0006 Accepted — revised Option A, version-gated. A producer_performed redaction REQUIRES a commitment; upstream_withheld names the never-held case in both the ledger and the availability enum; salted_sha256 → commitment_sha256 + commitment_alg with the ver-redaction-commitment/1 construction; the "proves a value existed" overclaim is retired and the salt-disclosure protocol is declared out of scope. 1.0.x unchanged; VER1002 stays a warning there | Closed |
REV-19 | ADR-0007 redefined a published field's preimage — weights_sha256 — which silently changes the meaning of values already in circulation | audit/owner | additive-1.1 | ADR-0007 Accepted — principle adopted, construction revised. weights_sha256 keeps annex E29's reading forever; the 1.1 pin is model.bundle.manifest_sha256 with the construction intrinsic to bundle_digest_alg; the decorative canonicalization member is dropped; model.artifacts[] and checkpoint_uri ⇒ revision are added | Closed (draft) |
REV-20 | ADR-0008's binary rules — metric, normalization, carriage, packing — were prose the schema did not carry, so a binary space with metric: "cosine" and an inline float array was valid | audit/owner | additive-1.1 | ADR-0008 Accepted as written, and the binary rules are encoded: binary ⇔ hamming, normalization: "none", no inline vector, and tolerance_hamming_max in place of the withdrawn ±1-expansion cosine reading | Closed (draft) |
REV-21 | The 1.1 draft suppressed encodable rules and left seven objects open in order to preserve a schema-superset property, so the standard's own machine-checkable requirements went unenforced | audit/owner | additive-1.1 | S1 + S2 — the superset goal is withdrawn. The seven objects are closed (draft §15.2); every JSON-Schema-encodable rule is encoded; the migration is enumerated with real failure pointers (draft §12.1); VERSIONING.md §2.2 replaces the superset requirement with an enumerated-migration requirement | Closed |
REV-22 | The corrected 1.0 reading is fragmented across a frozen specification, an errata annex, this ledger, a compatibility matrix and a draft, so no single document states what VER 1.0 currently requires | audit/owner | process | A consolidated corrected 1.0 reading is a planned P2 deliverable. It is logged, not produced on this branch: assembling one is a substantial editorial act with its own review, and shipping a half-consolidated reading beside five authoritative sources would add a sixth. The precedence rule stands meanwhile — the annex governs 1.0.x, this draft governs 1.1 | Deferred (planned) |
REV-23 | VER 1.0 was published as a standard with no reference corpus, no conformance runner, no second implementation and no governance process — the condition that produced the 189-finding audit | audit/owner | process | S8 — VERSIONING.md §3.4 states a six-condition publication bar for every release from 1.1 onward. 1.0.0 and 1.0.1 predate it and are not held to it retroactively — they were preservation acts, and unfreezing them to meet a later bar would destroy the property the freeze provides. 1.1 is explicitly held to it, and five of its checklist rows are that bar | Closed |
REV-24 | The repository has no LICENSE, no security policy, no contribution guide, no code owners and no CI | audit/owner | process | G1–G4 — Apache-2.0 at the root and in the validator package; SECURITY.md with a reporting contact, a supported-versions table and the credential-rotation note; CONTRIBUTING.md covering the gates and the standards-track rules (frozen directories, code-registry stability, the ADR process, fixture authoring); a CODEOWNERS stub; a pinned four-job CI workflow | Ruled — governance track |
REV-25 | The 1.1 draft made error_detail — free human-readable text — REQUIRED and load-bearing for c2pa.status: "unverifiable", putting a machine obligation on a string written for people | audit/owner | additive-1.1 | S6 — a machine-readable reason is REQUIRED for unverifiable and forbidden otherwise, over a six-token enum plus an <authority>/x-… escape; error_detail becomes optional and non-normative in every state | Closed (draft) |
REV-26 | Integrity-relevant URIs in the 1.1 draft could be carried without the digest that makes fetching them safe | audit/owner | additive-1.1 | S4 — descriptor_uri ⇒ descriptor_sha256, recipe_uri ⇒ recipe_sha256, set_uri ⇒ set_manifest_sha256 (unconditionally: a reference block is a tolerance claim), checkpoint_uri ⇒ revision; all schema-encoded. Draft §15.3 also states the two URIs deliberately left uncoupled, and why | Closed (draft) |
REV-27 | A binary space had no native tolerance: conformance was expressed as a cosine floor over the ±1 expansion of the packed bits | audit/owner | additive-1.1 | S5 — tolerance_hamming_max is REQUIRED for a binary space's reference, and the cosine tolerances MUST NOT appear on it. The ±1-expansion reading is withdrawn as a conformance basis | Closed (draft) |
REV-28 | The 1.1 draft's wire position was to open every lexicon object with a MUST-ignore-unknown rule, which would let an unreviewed member sit beside pixelSha256 on a public firehose | audit/owner | additive-1.1 (wire profile) | S7 — the hybrid: a stable collection NSID across 1.x; verVersion and cpnpVersion REQUIRED on the wire as the negotiation signal; identity- and security-critical structures closed; extensibility through exactly one namespaced ext map that consumers MUST ignore when unrecognised; a new NSID only for a breaking wire major. Draft §14.1 | Closed (draft) |
10.3 Raised after the review
Rows here are not owner-review dispositions: they were raised after 23 August, while building the 1.1 deliverables, and they continue the REV-nn numbering because the namespace is one sequence. Status carries §1's ledger-wide meaning, so Open here means a decision is required and has not been made.
| ID | Title | Source | Classification | Disposition | Status |
|---|---|---|---|---|---|
REV-29 | Annex E7 requires every logical-screen pixel that frame 0's rectangle does not cover to be RGBA (0,0,0,0) entering §4 step 5, and therefore opaque white in the Canonical Buffer. verd.cpnp honours this where the GIF declares a transparency index; where it declares none, the uncovered region decodes to the colour of palette index 0 instead. Found during decoder-corpus construction (25 August 2026) and reproduced against services/verd/src/verd/cpnp.py | decoder corpus | implementation-only | documented — not patched; owner ruling requested before decoder-corpus release 2. The divergence is recorded in standards/decoder-corpus/1/PROFILE.md §3.2 and §6, and corpus release 1 deliberately carries no digest for the divergent case: a reference corpus must not publish an expected digest for behaviour a normative errata item forbids, because doing so would freeze the divergence into a conformance target. The transparency-index case is pinned, by corpus/gif-frame0-subrectangle.gif. Either outcome — an implementation fix, or an errata amendment — is a ruling this branch did not take, and release 2 cannot carry a digest for the case in either direction until it is taken | Open |
REV-30 | The 1.1 draft opens an other key in metadata.availability (§10.3) and the 1.1 schema's raw[].segment enum carries other, but the profile validator's family universe was still the five 1.0 keys, so every availability-versus-carriage rule was blind to the sixth family: a 1.1 Record declaring availability.other: "redacted" while still preserving the other raw segment validated conformant — the exact annotated leak VER1005 exists to reject. VER1001's pairing arms and VER901's basis requirement were equally blind. Found by the 30–31 August repository audit (adversarially verified, reproduced empirically) | repository audit | implementation-only | fixed at source, version-gated. levels.py gains FAMILIES_1_1/FAMILY_SEGMENT_1_1 (six families, E21 mapping extended identity-spelled for other); metadata_rules.py selects the universe by schema version, so VER1005, both VER1001 pairings and VER901 now see other under --schema 1.1-draft while every 1.0.x result stays byte-identical (the L1 completeness gate VER303 deliberately keeps counting five). Four regression tests pin the leak, the safe pattern, the unledgered case and the basis requirement | Closed |
REV-31 | Every availability-versus-carriage rule keyed on metadata.availability alone, so a producer that redacted a field but forgot or mis-stated the family's state slipped VER1001 and VER1005 both; a pointerless producer_performed entry whose stand-in object was absent skipped VER1009 entirely; and --registry verdicts were computed against whatever snapshot the operator supplied, never checked against the snapshot the Record names (ADR-0004 C2) | repository audit | implementation-only | fixed at source, version-gated (31 August). Entry-level arms of VER1001/VER1005/VER1010 via the conservative PREFIX_FAMILY map; VER1009's retention arm; VER1208 registry-snapshot-mismatch with membership left unverified on a digest disagreement. All 1.1-draft-gated; 1.0.x byte-identical | Closed |
REV-32 | Nothing connected metadata.redactions[] to embeddings[]: a Record could redact a caption with a proper §5.3 commitment while carrying a contextual embedding computed from that caption — a vector that inverts to the text — beside source.text_sha256, an unsalted, undomained digest of the withheld value that functions as the confirmation oracle §5.3's framed salted construction exists to prevent. Fused vectors carry the same leak one recipe hop away | repository audit | draft-normative gap | ADR-0009, drafted-in-1.1 §5.4 (normative MUST: derived artifacts are withdrawn with the value), VER1011 for the exact-field map (dc:title/dc:description/Iptc4xmpExt:AltTextAccessibility → contextual source classes), §5.5 privacy considerations (informative), §15.4 rows recording what no validator can reach. Fixture invalid/ver1011-… pins the coexistence defect | Closed (draft) |
REV-33 | The wire profile's ext rules were all consumer-side (MUST-ignore, MUST-preserve, MUST-NOT-index) with no producer-side prohibition — an open smuggling channel for content the declared privacy mode withholds; tags is REQUIRED on every wire record including hashes_only, whose definition is "the identity tier only"; and a retraction reason was free text on a permanent public log with no rule against restating the sensitive content it retracts | repository audit | draft-normative gap | ADR-0009, drafted-in-1.1 §14.5/§14.6: a record MUST NOT carry in ext content its privacy mode withholds (salt prohibition included); a hashes_only record's tags MUST be empty; a retraction reason MUST NOT carry personal data or retracted content. Mirrored into both draft lexicons and the wire README | Closed (draft) |
REV-34 | §5.2/E15's omission remedy and §10.1's index-contiguity requirement contradicted each other for a multi-segment family: omitting the one segment carrying the redacted value leaves a legitimately gapped family that VER902 must reject, so the only conformant execution of the carve-out was un-writable for exactly the families (Extended XMP) most likely to need it | repository audit | draft-normative defect | drafted-in-1.1 §5.2: omission is family-granular — a redaction that requires omitting any segment of a family omits every segment of it, because partially omitted families re-assemble to bytes the producer never held; §10.1's contiguity rule therefore never meets a legitimately gapped family | Closed (draft) |
REV-35 | The wire trust-tier vocabulary (signed_verified/signed_unverified/unsigned) had no name for a signature that was checked and FAILED — the most security-relevant outcome it can observe — so a failed verification either read as merely unchecked or as no claim at all | repository audit | draft-normative gap | ADR-0009, drafted-in-1.1 §14.7: fourth tier signed_invalid; MUST NOT be served as verified under any filter; SHOULD be retained rather than retried into unsigned | Closed (draft) |
REV-36 | ADR-0006 part 2 and the migration guide tell a producer that lost the salt to restate the entry as state: "upstream_withheld", but draft §5.2 defines that state as "the Producer never received the value" — a producer that HELD the value and lost the salt would be making a false statement of provenance to escape an impossible commitment. The honest alternative ADR-0006 also names (the Record stays at 1.0) has no such cost | repository audit | draft-normative tension | documented — owner ruling requested. Options: (a) bless the restatement by widening §5.2's definition to "cannot commit" (weakens the state's meaning for every honest use); (b) require stay-at-1.0 for lost-salt entries and supersede ADR-0006's restatement sentence (ADR-0010 material); (c) a third state. The migration guide now presents stay-at-1.0 as the primary lost-salt path pending the ruling | Open |
