feat(avif): reserve and write a C2PA manifest store, and locate one on read - #506
Open
justin13888 wants to merge 14 commits into
Open
justin13888 wants to merge 14 commits into
justin13888 wants to merge 14 commits into
Conversation
A C2PA manifest store binds to the finished file, so the encoder reserves rather than receives: AvifEncoder::with_c2pa_reserved(len) writes the C2PA 2.4 A.5.1 ContentProvenanceBox - a top-level uuid box with the D8FEC3D6-...-C481 user type, zero FullBox version/flags, box_purpose manifest and a zero 8-byte merkle offset - after ftyp and before meta (A.5.3, via IsoBmffImage::push_top_level_box) around a slot of len zero bytes; with_c2pa(bytes) writes a store the caller computed over this exact output; encode_with_report returns the same bytes encode_to_vec would plus the slot's file range, so a signer knows where to patch before it runs and nothing after the slot moves. The object-safe EncodeImage entry point is unchanged. On read, AvifContainer::c2pa / c2pa_manifest_stores locate every top-level C2PA uuid box and report its box_purpose, slot bytes and file range, in file order. The slot is box-bounded (store plus any A.5.3 padding; no JUMBF LBox trim, unlike gamut-heic - sharing one lens is and the range is documented as observability, not a hash exclusion range (BMFF binds by box path, 18.6 / A.5.6). Tests: exact-byte - two equal-length stores give files differing in exactly the reported span, and patching a reserved file at that range reproduces the directly written file; the crate locates the slot it reserved at the reported range; a mid-update original/update pair is reported in file order from a hand-transcribed A.5.1.2 payload; libavif and dav1d (through the crate's own parse) decode a file carrying the box to the same pixels as one without. Inline: the A.5.1.2 payload layout, the parser's user-type / version-flags / purpose / truncation rules and its offset arithmetic. Refs #444
Adds the ContentProvenanceBox row to section A and the locator row to section L of STATUS.md, a scope paragraph for the reserve/write/report surface, and a note recording the locator's three limits (box-bounded slot rather than an LBox trim, the assumed update framing, and that the range is not an exclusion range) with #505 as the shared-lens follow-up. README gains a content-credentials paragraph. Refs #444
The manifest_stores fixture's only non-uuid box had an empty body, so the uuid type guard could be replaced with true and nothing in the suite noticed (a MISSED mutant in the diff run). Give the free box a body that is byte-for-byte a C2PA payload: C2PA 2.4 A.5.1.1 fixes the box type to uuid, so that box must not be reported, and the guard is now observable. Refs #444
C2paManifestStore was byte-identical in name to gamut-heic's type while promising something different: heic trims to the store's own JUMBF LBox, this crate reports the box-bounded slot, so for one file the two could report different lengths under one name - and both are re-exported from the gamut umbrella. Rename the type to C2paSlot and its field to slot_bytes, and say in the docs what the bound is: the store plus any A.5.3 padding, or all zeros for an unfilled reservation. The bound itself is kept, because an LBox trim cannot locate a reservation - an all-zero slot has no LBox, so the reserve/report/patch flow would have nothing to patch. Unifying the two lenses is #505. The encoder's private slot-source enum takes the name SlotSource, freeing C2paSlot for the public type. Refs #444
C2PA 2.4 A.5.3 states the 8-byte merkle offset for box_purpose manifest and original, and says nothing about the bytes ahead of an update store. Assuming the prefix unconditionally dropped an in-spec prefix-less update store shorter than 8 bytes as absent, and made this crate disagree with gamut-heic about identical bytes. Probe the same [8, 0] candidate list gamut-heic's locator uses, first match winning, for update only. A box-bounded slot has no JUMBF LBox to discriminate on, so the fallback fires exactly when data is too short for the prefix; a longer prefix-less update store is still reported 8 bytes short, which needs the LBox check #505 unifies. Documented on the purpose type. Refs #444
with_c2pa_reserved(len) built a vec![0; len] and copied it into the payload buffer, so reserving n bytes peaked at 2n - reserving 64 MiB touched 128 MiB. Split the payload builder into the A.5.1.2 framing plus a slot, and give the reserved path a resize into the buffer the payload already owns. The equivalence with handing the same zeros to the payload builder is pinned at three lengths and all three purposes. Refs #444
The Av1StillDecoder-over-dav1d bridge was a third copy in the crate. tests/common/mod.rs already carries allow(dead_code) precisely so a binary can use a subset of the fixtures, and tests/c2pa.rs already declares mod common, so the dead-code objection to sharing it does not apply. conformance.rs and high_bitdepth_roundtrip.rs keep their own - moving those is churn beyond this change. Refs #444
The read side reports all three A.5.3 purposes; the encoder writes one. Once a file carries an update box, A.5.3 requires the earlier store to be re-labelled original, so re-encoding such a file through these builders would label it wrongly - producing that pair is a manifest-update operation on an existing file, outside #444. State it on both with_c2pa_reserved and with_c2pa, and record it as a fourth limit in STATUS.md alongside the probe and rename this review settled. Refs #444
This was referenced Sep 9, 2026
Open
`with_c2pa_reserved` is an infallible builder, so an unusable `len` reached the payload builder unchecked and added to the framing length without a guard. Two failures sat next to each other at the top of the range. Nearest `usize::MAX` the addition wrapped, and because a wrapped total is *smaller* than the framing already written, `resize` shrank the 21-byte framing to 20 instead of growing it: in debug that panicked with "attempt to add with overflow", but in release — the profile every downstream consumer builds with — `encode_to_vec` returned `Ok` with a well-formed AVIF whose `uuid` box the crate's own locator then reported as absent. Just below the wrap the sum was representable but larger than a `Vec` can hold, so `with_capacity` panicked. A silent wrong answer about the range a signer binds is the outcome this surface must never produce. Both are now refused at encode as `Error::InvalidInput`, on every entry point including the object-safe `EncodeImage` path, together with a reservation below the 8-byte JUMBF `LBox`/`TBox` header that could not hold a manifest store at all. The read side is deliberately left permissive: a degenerate slot that is genuinely present is still reported with its true range. Strict in what it writes, honest about what it reads, and the asymmetry is documented on both sides. Also in this round, all interlocking with the above: - The C2PA read accessors are renamed for what they report: `AvifContainer::c2pa` -> `c2pa_slot`, `c2pa_manifest_stores` -> `c2pa_slots`, and the private locator `manifest_stores` -> `slots`. `C2paSlot` was already renamed for its box bound, but the accessors kept the old vocabulary and stayed byte-identical in name to `gamut-heic`'s, whose bounds differ, with both re-exported from the umbrella. The doc sentence still opening "Every C2PA manifest store among the top-level boxes" is corrected to say slot. Nothing released carries these names: the whole `c2pa` module is new on this branch and absent from `gamut-avif-v1.1.0`, so this is not a breaking change to any published API and must not bump the major. - `AvifEncodeReport` no longer derives `Default`, which had made a type documented "Construct nothing here" externally constructible. Nothing builds one that way; the encoder uses a struct literal. - The allocation claim on `content_provenance_reserved` is narrowed to the payload builder it is true of: `gamut_isobmff::writer` copies the payload into the output, so the encode as a whole still peaks near 2n. - STATUS.md records the deferral of a range newtype to #505, which until now lived only in the pull request body and would not have survived the merge.
The payload path pre-sized its buffer with `framing_len(purpose) + slot.len()`, but a `Vec` capacity is a hint: replacing that `+` with `*` changes no byte of the output, so the mutation gate reported it missed and no test could have killed it. The arithmetic was also the only place outside the checked reservation where a framing length and a slot length were added. `reserve_exact` asks for the slot's room directly, so the payload path computes no total at all and the single addition of the two lengths is the one `content_provenance_reserved` already checks. The buffer still ends up exactly the size it needs, at the cost of one reallocation that copies the 21 bytes of framing.
This was referenced Sep 10, 2026
Open
The documented refusal set for `with_c2pa_reserved` named two lengths, both `Error::InvalidInput` from this crate, and promised that every `len` between them "is written as asked", with only the allocator's limit beyond. A third refusal sits inside that promised range and comes from another crate. A top-level ISOBMFF box carries a 32-bit size field, so `gamut_isobmff::write` rejects a `ContentProvenanceBox` at or beyond 4 GiB as `Error::Unsupported`. Measured on the `manifest` framing, `len = 4_294_967_251` is refused that way and `4_294_967_250` clears the check, then aborts in the allocator while the writer copies the payload into the output buffer. Say so at every site that states the contract, and name the error type it arrives as. Moving this crate's own ceiling to that bound is a behavioural change, filed as #576. Refs #444
The locator's docs claimed the asymmetry between a strict writer and a permissive reader made "a file this crate would decline to produce still one it will faithfully describe". It does produce it: `with_c2pa(&[0xAB; 3])` encodes, and `AvifContainer::c2pa_slot` finds a 3-byte slot at 77..80, while `with_c2pa_reserved(3)` refuses. The 8-byte minimum bounds the reservation path alone, because a reservation is a bare integer with no other evidence behind it. A supplied slice is carried verbatim, as every other caller-supplied metadata payload in this workspace is. Document that where `with_c2pa` is defined, and delete the false sentence rather than tightening the builder — applying the minimum there would change behaviour and make this the only content judgement the C2PA surface makes, so it is filed as #577 instead. Refs #444
`a_reservation_below_a_jumbf_box_header_is_refused` sweeps `0..MIN_SLOT_LEN` and then accepts `MIN_SLOT_LEN`, so it holds for whatever value the constant carries. Setting it to 7 or 9 leaves all 45 inline tests green, and at 9 the crate would ship a refusal message reading "at least 8 bytes" while refusing 8 — the test asserts the message against a literal but the guard compares against the constant. The mutation gate cannot see this, because it does not mutate constants. Add the two literal-valued assertions the sweep cannot make: 7 is refused with the message that names 8, and 8 is accepted. At `MIN_SLOT_LEN = 9` this is now the only failing test of the 45; at 7 it fails too, from the other side. Refs #444
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #498 (
feat/443-isobmff-top-level-box-list): this PR is opened against that branch and merges only after it. It contains no gamut-isobmff or gamut-heic changes of its own.This is an unattended backlog run. No human approved the plan; the decision record below is what a human reads afterwards.
Summary
Lets
AvifEncoderreserve and write a C2PA manifest store, and lets the AVIF read path locate one and report its byte range — the first write side of epic #239, on the top-level box slice #498 added togamut-isobmff.AvifEncoder::with_c2pa_reserved(len)writes the C2PA 2.4 §A.5.1ContentProvenanceBox— a top-leveluuidbox with user typeD8FEC3D6-1B0E-483C-9297-5828877EC481, zeroFullBoxversion/flags,box_purposemanifest, a zero 8-byte merkle offset — afterftypand beforemeta(§A.5.3, viaIsoBmffImage::push_top_level_box) around a slot oflenzero bytes for an external signer to fill.AvifEncoder::with_c2pa(bytes)writes a store the caller has already computed over this exact output; its doc says so and says the metadata facade never hands one over.AvifEncoder::encode_with_report(image) -> (Vec<u8>, AvifEncodeReport { c2pa: Option<Range<usize>> })returns the same bytesencode_to_vecwould plus the slot's file range, found by the crate's own locator over the finished file.EncodeImageis untouched.AvifContainer::c2pa()/c2pa_manifest_stores()report every top-level C2PAuuidbox as aC2paSlot { slot_bytes, range, purpose: C2paBoxPurpose }, in file order. The type is named for the box-bounded slot it reports — the store plus any §A.5.3 padding, or zeros for an unfilled reservation — becausegamut_heic::C2paManifestStoretrims to the JUMBFLBoxand both are re-exported from the umbrella.updateboxes are probed over the same[8, 0]prefixes gamut-heic uses. New public items:C2PA_UUID,C2paBoxPurpose,C2paSlot,AvifEncodeReport.Files: new
crates/gamut-avif/src/c2pa.rs,crates/gamut-avif/src/encoder.rs,crates/gamut-avif/src/lib.rs, newcrates/gamut-avif/tests/c2pa.rs,crates/gamut-avif/STATUS.md,crates/gamut-avif/README.md.Tests (each names the function whose mutation it kills):
tests/c2pa.rs): two equal-length stores give files differing in exactly the reported span and nowhere else; patching a reserved file at the reported range reproduces the file written with the store outright;encode_with_reportbytes equalencode_to_vecand the range isNonewhen unconfigured.original+updatepair is reported in file order.decode_avif+introspect) and dav1d (throughAvifContainer::parse+decode_primary_rgba8) decode a file carrying the reserved box to the same pixels as one without.src/c2pa.rs,src/encoder.rs): the §A.5.1.2 payload layout exact-byte; the parser's user-type / version-flags / purpose / truncation rules and offset arithmetic; the box is emittedAfterFtypwith no item or property added; last call wins across both knobs;Debugprints the slot by kind and length.Validation
All commands run from the worktree on
feat/444-avif-c2pa-reserve; classification per command.cargo check -p gamut-avif --libcargo clippy -p gamut-avif --all-targets --all-features -- -D warningscargo test -p gamut-avif --all-featurestests/c2pa.rs(libavif + dav1d oracles built from submodules)RUSTDOCFLAGS="-D warnings" cargo doc -p gamut-avif --no-deps --all-featurescargo test -p gamut-avif --all-features --doc__CARGO_TEST_ROOT=<worktree> mise run fmtthen__CARGO_TEST_ROOT=<worktree> mise run fmt-checktooling/*manifest resolutionmise run check-testsmise run check-commits/convco check origin/feat/443-isobmff-top-level-box-list..HEADmise run lint(systemd scope, MemoryMax=16G, CARGO_BUILD_JOBS=2, ulimit -v 12000000)mise run test(same scope)38c7c3e: pass (exit 0, 204 green test binaries, no failures)GAMUT_MUTANTS_BASE=origin/feat/443-isobmff-top-level-box-list mise run mutants-crate gamut-avif --diff --shard 0/1 --budget 16(the runner's own capped scope; the--diffselection is what CI's incremental job blocks on)c2pa.rs:211 replace match guard &ty == b"uuid" with true in manifest_stores. Repaired intest(avif): key the C2PA locator fixture on the box type, not the body(the fixture'sfreebox now carries a C2PA-shaped body, which must not be reported per §A.5.1.1; the exact mutant expression was applied by hand and confirmed to fail that test). Second run on the final head: 41 tested, 36 caught, 5 unviable, 0 missed (exit 0)cargo test -p gamut-avif -p gamut-isobmff --all-features(after merging #498's advanced headc8b87cfby merge commit),mise run fmt-check,mise run check-tests,convco check origin/feat/443-isobmff-top-level-box-list..HEADReview round (five repairs R1–R5 from the read-only review of
38c7c3e, applied as five commits; every gate re-run on32cdc93):cargo test -p gamut-avif --all-featurestests/c2pa.rs)cargo clippy -p gamut-avif --all-targets --all-features -- -D warningsRUSTDOCFLAGS="-D warnings" cargo doc -p gamut-avif --no-deps --all-featurespublic documentation for C2paBoxPurpose links to private item Self::store_prefix_candidates— caused by R2's new doc; link removed, re-run passes.cargo test --doc3 pass__CARGO_TEST_ROOT=<worktree> mise run fmt-check,mise run check-tests,convco check origin/feat/443-isobmff-top-level-box-list..HEAD(8 commits)mise run lint(capped scope)mise run test(capped scope)GAMUT_MUTANTS_BASE=origin/feat/443-isobmff-top-level-box-list mise run mutants-crate gamut-avif --diff --shard 0/1 --budget 16No
Cargo.tomlwas touched, socheck-release-deps/check-ffi-features/check-ffi-headerare not triggered; no C-surface type changed.CI status: red for an infrastructure outage, not for this change (#517). Every required check on
38c7c3ewas green on 2026-09-06. Since 2026-09-09 every job in this repository fails in its first step, Install system build deps:sudo apt-get updateexits 100 withE: Failed to fetch …/chrome-stable/… Hash Sum mismatchfrom the runner image's preinstalled Google Chrome apt repo, so pkg-config/mise/sccache never install and every later step dies withsccache: command not found(127). No gamut code is compiled before the failure, so the runs triggered by the repair push are red for that reason alone — classifiedunavailable/ infrastructure, tracked as #517, and deliberately not "repaired" here. The local gates above, re-run in full on32cdc93, are this change's evidence.Correction (round 3): the paragraph above is superseded. It is false at
32cdc93. #517 was fixed before that head ran, and the required checks did run: Format & Metadata pass, Clippy & Doctests pass, and all four Incremental (PR diff) shards pass (Mutantsrun 34384870602, conclusionsuccess). Coverage (test gate) ran the whole suite to completion —TOTAL 133659 regions, 4641 missed, 96.53%, withcrates/gamut-avif/src/c2pa.rsat436 regions, 5 missed, 98.85%region and26 functions, 0 missed, 100.00%function, both comfortably over the 80 % floor — and then failed in theactions/upload-artifact@v6step alone:That is an artifact-upload timeout after the gate itself had passed, so the correct classification for Coverage at
32cdc93isunavailable(infrastructure, artifact upload), notcausedand not the#517apt outage. The check is re-run at the round-3 head below.Round 3 (head
55bd77d6)Every command below completed in this round; the workspace-wide ones ran under a 16 GiB memory-capped scope with
CARGO_BUILD_JOBS=2.CARGO_BUILD_JOBS=2 cargo test -p gamut-avif --all-featuresCARGO_BUILD_JOBS=2 cargo clippy -p gamut-avif --all-targets --all-features -- -D warnings__CARGO_TEST_ROOT=$(git rev-parse --show-toplevel) mise run fmt-checktooling/*manifests; no manifest was changed)mise run check-testsmodule docs, pinned proptest seeds and oracle filenames all conformconvco check origin/feat/443-isobmff-top-level-box-list..HEADno errors in 10 commitsmise run lintmise run testtest result: okblocks, 0 failedmise run mutants-diffc2pa.rs:348 replace + with * in content_provenance_payload, an unobservableVeccapacity hint. Repaired by removing the arithmetic (reserve_exact) rather than excluding the mutant. Second run on the final head: 85 mutants, 68 caught, 17 unviable, 0 missed (exit 0)The F1 repair, executed in both profiles. A standalone consumer crate outside the workspace, depending on
gamut-avifby path, callingAvifEncoder::new().with_c2pa_reserved(len).encode_to_vec(img):lenusize::MAXattempt to add with overflowatc2pa.rs:287Ok, 345 bytes,c2pa_manifest_stores()finds 0Err(InvalidInput): "the reserved C2PA slot exceeds the largest ContentProvenanceBox that fits in memory"usize::MAX - 21capacity overflowinRawVec(found while repairing; the sum is representable but larger than aVeccan hold)Err(InvalidInput), same message0,7Err(InvalidInput): "a reserved C2PA slot is at least 8 bytes, the size of a JUMBF box header"8Ok, 354 bytes,c2pa_slots()finds 1No
Cargo.tomlwas touched in round 3 either, socheck-release-deps/check-ffi-features/check-ffi-headerremain untriggered; no C-surface type changed.mise run coveragewas not re-run locally — no new module was added and CI's Coverage gate runs the full suite; the round-2 head measured this file at 98.85 % region / 100 % function.CI at
55bd77d6, all required checks terminal and green: Format & Metadatapass(2m06s), Clippy & Doctestspass(21m02s), Incremental (PR diff) shards 0-3pass(4m51s / 5m49s / 5m42s / 5m54s), Coverage (test gate)pass(33m27s). Coverage passing on re-run confirms the round-2 failure was the transient artifact upload and not this change —unavailable, as classified above.Round 4 (closing) — validation
Commits
ec2412a,05f9bbd,c38e64c. The round changes documentation and adds one assertion;no emitted byte and no returned range moves.
__CARGO_TEST_ROOT=$(git rev-parse --show-toplevel) mise run fmt-checkmise run check-testsconvco check origin/master..HEADmise run mutants-diff(capped scope,MemoryMax=16G)cargo test -p gamut-avif --all-features(capped scope, oracles built fromthird_party/)cargo clippy -p gamut-avif --all-targets --all-features -- -D warningsRUSTDOCFLAGS="-D warnings" cargo doc -p gamut-avif --all-features --no-depsWorkspace gates deliberately not re-run this round, with the reason.
mise run lintandmise run testwere skipped: the only compiled files that changed are one crate's doc commentsand one inline test, so the crate-scoped Clippy, rustdoc-with-
-D warningsand full--all-featuressuite above exercise the same lints over exactly the changed files, and theworkspace runs are what CI's Clippy & Doctests and Coverage jobs perform on the pushed head
anyway.
mise run coveragewas skipped: no module was added, and the added assertion onlystrengthens an already-covered function.
check-release-deps/check-ffi-features/check-ffi-headerstay untriggered — noCargo.tomland no C-surface type changed. The AV1/AVIForacle submodules were already present in the worktree, so
mise run fetch-av1-oracleswas notneeded.
Evidence for the new assertion. With
MIN_SLOT_LENset to9,cargo test -p gamut-avif --libreports44 passed; 1 failed, the single failure beingc2pa::tests::a_reservation_below_a_jumbf_box_header_is_refusedat theis_ok()on length 8.With it set to
7, the same test fails at theexpect_erron length 7. At8all 45 pass. Beforethis round both
7and9left all 45 green.Evidence for the two findings, executed at
55bd77d6.with_c2pa_reserved(4_294_967_251)returnsunsupported: ISOBMFF: top-level box at or beyond 4 GiB [origin: gamut-isobmff];4_294_967_250clears that check and then abortswith
memory allocation of 8589934654 bytes failedinside the writer's copy.with_c2pa(&[0xAB; 3])encodesOk, andAvifContainer::c2pa_slotreturnsC2paSlot { slot_bytes: [171, 171, 171], range: 77..80, purpose: Manifest };with_c2pa_reserved(3)returnsinvalid input: AVIF: a reserved C2PA slot is at least 8 bytes, the size of a JUMBF box header [origin: gamut-avif].Correction to a round-3 commit message. The body of
55bd77d6("refactor(avif): ask for thestore's room instead of adding it in") says the buffer ends up exactly the size needed.
Vec::reserve_exactguarantees only at least the requested capacity; it may over-allocate. Thepushed commit is not amended — this entry is the correction. Nothing in the code or its behaviour
depends on the difference: a capacity is a hint, which is why the mutant on that path was
unkillable and the arithmetic was removed rather than excluded.
CI at
c38e64c7, all required checks terminal and green: Format & Metadatapass(41s),Clippy & Doctests
pass(17m35s), Incremental (PR diff) shards 0–3pass(4m49s / 5m50s /5m45s / 5m27s), Coverage (test gate)
pass(23m15s). "Full workspace" isskipping, as it ison every PR head.
Risks and rollout
gamut-avif: new builder methods on the non-CopyAvifEncoder, one new inherent method, four new public items (all#[non_exhaustive]where they are structs/enums). An unconfigured encoder writes byte-identical output (pinned: no item, no property, no top-level box is added).LBox-trimmed asgamut-heic's is, so the two crates report different bounds for the same file today. Recorded inSTATUS.md; the shared lens is gamut-isobmff: share the C2PA uuid lens between gamut-heic and gamut-avif #505.updateframing is assumed to carry the 8-byte merkle offset (§A.5.3 is silent). Recorded.Issue
Closes #444. Follow-up filed: #505 "gamut-isobmff: share the C2PA uuid lens between gamut-heic and gamut-avif".
Decisions taken
Appended by the lane (forks the record did not cover), in the record's shape:
Appended after the read-only review of
38c7c3e(every item below decided by the orchestrator on review, not by the author):Round 3 — decisions carried out
The seven below were taken before this round began and were executed, not reopened. No human approved them: this is an unattended run, and this record is what a human reads afterwards.
Two forks the record did not cover were decided in this round and are appended in the same shape:
Round 4 (closing) decisions, appended in the record's shape. No entry above is edited.
Unresolved review notes
Range<usize>, so nothing in the type system stops a caller feeding it to a hash as an exclusion range. Every doc site says it must not be, and BMFF binds by box path (§18.6/§A.5.6). A newtype that makes that unrepresentable is deferred to gamut-isobmff: share the C2PA uuid lens between gamut-heic and gamut-avif #505, so it can land ingamut-heicandgamut-aviftogether rather than as an AVIF-only spelling that would be renamed on adoption. Round 3: this residual now lives incrates/gamut-avif/STATUS.mdas recorded limit (5), so it survives the merge; the bullet is kept here for the record rather than deleted.third_party/dav1dis anupdate = nonesubmodule and was empty in the reviewer's worktree — so the oracle tests and the 44 caught / 8 unviable / 0 missed diff-mutants result were not independently reproduced by them. CI's fourIncremental (PR diff)shards are the same in-diff mutation gate and were green at38c7c3e, before the repository-wide CI outage (CI: every job fails in "Install system build deps" when apt-get update hits a broken third-party repo #517).32cdc93ran. Format & Metadata, Clippy & Doctests and all four Incremental shards passed there, and Coverage ran the full suite to 96.53 % total before failing atactions/upload-artifact@v6withFailed to CreateArtifact: … ETIMEDOUT— an artifact-upload timeout, classifiedunavailable, notcausedand not CI: every job fails in "Install system build deps" when apt-get update hits a broken third-party repo #517. See the correction in the Validation section.Round 4 (closing).
gamut-avif: should the C2PA reservation ceiling move to the container writer's 4 GiB bound? #576 "gamut-avif: should the C2PA reservation ceiling move to the container writer's 4 GiB
bound?" and gamut-avif: should AvifEncoder::with_c2pa apply the reserved slot's 8-byte minimum? #577 "gamut-avif: should
AvifEncoder::with_c2paapply the reservationminimum?". Each names the executed evidence and both sides of the argument.
changed an emitted byte or a returned range; the four that were actionable are R4-1 to R4-4
above. No finding is left open.
the record is what a human reads before deciding whether this stack merges. That judgement,
including whether to merge at all, is the maintainer's.