PNG candidate contract
Issue #78 defines
optiflow.png-candidate-contract.v1, a contract-only review packet for a
future lossless PNG provider. Issue
#80 adds a separate read-only
byte validator described below. No encoder is selected or invoked; the
existing CLI and read-only v0.1 authority are unchanged.
The schema, Rust
png_candidate module, and synthetic example
form one specification and conformance boundary. All paths, tools, repeated
hexadecimal content digests, PNG facts, sizes, and resource measurements in the
example are invented test declarations, not observed files or measurements.
Only its request fingerprint is computed from the declared request.
Ownership and compatibility
The packet is separate from scan reports, review opportunities, executable plans, extension SDK v1, and artifact-set v1. It does not add a new extension role or permission. Run v5, report v6, plan v5, command-result v1, effective policy v1, and SQLite are unchanged. OFD-001's review-only boundary remains in force; no accepted architecture decision is superseded.
The provider result and host evidence have separate shapes. A provider may
report its status and claimed candidate content identity. It cannot place
validation or host-observation fields inside that result. A future coordinator
must acquire the host observations itself rather than copying provider stdout.
Deserializing a HostEvidence value does not authenticate its origin.
The packet checker's success result is ConsistentForReview. It is evidence-consistency
feedback, not an accepted media artifact, runtime safety attestation, creative
approval, or permission to execute. No current application path consumes it.
Narrow preservation profile
optiflow.png-idat-preserve.v1 covers static 8-bit RGB or RGBA PNGs only.
Indexed, grayscale, other bit depths, APNG, and unavailable facts are refused.
This is a boundary for initial implementation, not a claim about all PNGs.
An eventual byte validator must establish:
- One complete frame with positive, unchanged dimensions and the same IHDR.
- Full PNG structural/decode validation, including chunk CRCs, critical-chunk validity, stream completion, and no unaccounted trailing bytes.
- No
acTL,fcTL, orfdATanimation chunks. - Exactly equal decoded samples, including otherwise invisible RGB samples beneath zero alpha. Do not premultiply alpha, color-convert, strip precision, or use a perceptual comparison.
- Exact ordered preservation of every non-IDAT chunk, including metadata, color/transfer declarations, transparency, and its placement before or after IDAT. Unknown unsafe-to-copy ancillary chunks must cause refusal before recompression; recognized chunks and unknown safe-to-copy chunks retain their exact bytes and placement.
- Current source identity/content before and after execution and validation; an independently observed regular, non-symlink candidate distinct from the source filesystem object and path.
- A candidate with strictly fewer encoded logical bytes than the source.
The canonical decoded sample stream is the unfiltered, deinterlaced image in
top-to-bottom row order, left-to-right pixel order, with packed 8-bit RGB or
straight RGBA samples and no row padding. Its length must equal width × height
× channels. BLAKE3-256 hashes exactly those bytes. The IHDR digest hashes the
13-byte IHDR payload. The preservation digest hashes the following unambiguous
stream: UTF-8 optiflow.png-preservation.v1 and a zero byte, followed in file
order by tagged records. Each non-IDAT chunk contributes byte 0x00, its
complete chunk byte length as an unsigned 64-bit big-endian integer, and its
original length/type/data/CRC bytes. The one consecutive IDAT run contributes
exactly one byte 0x01 instead of any IDAT bytes. Include IHDR and IEND records.
This preserves the position of chunks relative to IDAT while permitting IDAT
repartitioning/recompression. No other chunk bytes or placement may change.
The selected validator must identify unknown unsafe-to-copy chunks and report
their presence; that observation is always refused by this first profile.
These requirements follow the PNG editing rules.
Digest equality is a compact record; the runtime must
perform actual complete comparisons and validation before reporting a pass.
Keeping the original and candidate increases occupied storage. The contract
reports only reported_encoded_byte_reduction, calculated from the supplied
lengths. It makes no physical-space, reclaimability, or measured-savings claim.
Bindings and checks
The request binds one source observation ID, canonical native path, filesystem identity, observation fingerprint, complete source-content digest, logical length, exact profile, effective policy, producer, validator, and limits. An existing #66 opportunity fingerprint is not a complete content hash and cannot substitute for one.
Tool bindings include name, version, canonical executable path, executable-byte digest, and invocation fingerprint. Future implementations must define and record their immutable argv/configuration/input-binding recipe. A binary digest does not authenticate its publisher, dynamic dependencies, or sandboxing. Host evidence records both tools before and after the observation window.
The provider result and host evidence each bind the full request fingerprint. One host record scopes all eight required checks to exactly its source, candidate, tools, and policy:
| Check | Required result |
|---|---|
source_stable |
Same current source before and after |
complete_decode |
Both complete decodes and structural validation pass |
dimensions |
Same positive dimensions and IHDR |
frame_count |
Exactly one static frame each |
decoded_samples |
Exact canonical sample comparison |
alpha_preserved |
Alpha representation and samples preserved |
color_preserved |
Color representation and declarations preserved |
metadata_preserved |
Ordered non-IDAT bytes preserved |
Each check must occur exactly once and be passed. Failed, unsupported,
not-run, missing, duplicate, and unknown checks cannot satisfy the contract.
The pure checker also compares the supplied PNG facts; passing flags cannot
override contradictory digests, sizes, frames, dimensions, or preservation.
Limits cover input bytes, candidate bytes, decoded bytes per image, peak temporary bytes, peak process memory, and elapsed milliseconds. Declared usage must fit the limits. These are future enforcement requirements, not enforcement provided by this pure checker. A future runner must state its peak-memory measurement/enforcement scope and refuse claims it cannot substantiate.
Deterministic request identity
The request fingerprint is BLAKE3-256 over the UTF-8 domain separator
optiflow.png-candidate-request.v1 followed by a zero byte and the compact
serde_json serialization of the typed CandidateRequest. Struct field order
is fixed by the v1 model and the checked-in example; nested domain structs use
their declared field order. There are no floating values or unordered maps.
This is not a hash of arbitrary incoming JSON formatting. Consumers must parse
and validate first; reordering input JSON keys does not change the identity.
Canonical paths use the existing NativePath encoding, absolute paths without dot segments or repeated separators, and its canonical UTF-8/base64 choice. The checker does not resolve filesystem symlinks. Runtime handle/path binding and complete source/candidate observations remain mandatory later.
Use parse_contract for wire input: it validates the raw schema before typed
deserialization so shared legacy domain types cannot discard unknown fields.
check_candidate_contract checks a typed packet and returns a closed
ContractRefusal code on disagreement. It performs no IO. Invalid-shape and
semantic-refusal tests are separate because a schema-valid claim can still be
inconsistent.
Actual byte validation
The library function png_validation::validate_png_pair accepts immutable
source/candidate byte slices and explicit ByteValidationLimits. It opens no
files, invokes no provider, and creates no output. Its opaque
ValidatedPngPair has private fields and no deserializer: declarations from
the contract example cannot be converted into observed byte evidence.
It uses the pinned png 0.18.1 decoder
(declared MSRV 1.73, below OptiFlow's 1.85), with identity transformations and
checksum checking explicitly enabled. A separate chunk scanner verifies all
CRCs, framing, ordering, and supported metadata before decoding. A bounded
flate2 zlib pass requires the complete stream, correct Adler-32, exact
filtered image length, and consumption of all IDAT data. This compensates for
the decoder's intentional tolerance of unused compressed bytes and malformed
ancillary metadata. PNG permits readers to ignore unused final IDAT bytes;
this validator's refusal is a
stricter profile rule, not a claim that
every refused file violates PNG.
The current implementation supports a subset of
optiflow.png-idat-preserve.v1:
| Input | Current behavior |
|---|---|
| Static, noninterlaced 8-bit RGB/RGBA | Complete decode and exact samples |
PLTE, RGB tRNS, gAMA, sRGB, pHYs |
Check shape, multiplicity and placement; preserve exact bytes |
Uncompressed tEXt |
Check keyword/text structure; preserve exact bytes and order |
| Unknown private safe-to-copy ancillary chunks | Preserve opaque bytes and placement; no semantic interpretation |
| Interlace, grayscale, indexed color, other bit depths, APNG | Explicit refusal |
Other public/registered metadata, including iCCP, zTXt, iTXt, cHRM, eXIf |
Explicit refusal; no compressed-metadata expansion |
| Unknown critical or unsafe-to-copy chunks | Explicit refusal |
Private safe-to-copy chunks have lowercase first, second and fourth type letters, and an uppercase reserved third letter. Supported metadata validation is structural; this is not an ICC/colorimetric or general PNG conformance certifier. Adding metadata/interlace support requires its own bounded tests.
Both actual canonical sample buffers and every ordered preserved raw chunk
are compared directly, with one marker retaining the IDAT run's position.
This catches alpha changes, invisible RGB changes, metadata additions/removals,
and movement across IDAT even when visible rendering is identical. Only then
does the validator expose ContentIdentity, PngFacts and a strictly positive
encoded_byte_reduction, computed from the observed slices with the digest
recipes above. No serialized schema changes are required. The existing pure
contract checker retains its declaration-only semantics.
Limits independently bound source bytes, candidate bytes, decoded bytes per
image and chunks per image. Checked dimensions are refused before image-sized
allocation. Allocation failures in caller-owned sample/chunk/IDAT buffers
return a limit refusal. decoder_allocation_bytes configures the PNG library's
best-effort internal allocation accounting, excluding caller input, output
samples, IDAT copies, zlib state and bookkeeping. Two decoded images are held
for comparison. These ceilings are not peak RSS, wall-clock enforcement,
process isolation or a substitute for CandidateLimits. The API produces no
ResourceUsage measurements or HostEvidence packet.
Run the generated, original synthetic PNG proofs with:
These tests include different compression/filter/IDAT layouts, transparency, metadata placement, changed samples, corrupt chunk CRCs and zlib checksums, truncation, extra compressed/decompressed bytes, unsupported input, and exact budget boundaries. They are functional fixtures, not the broader redistribution corpus tracked by #65.
Next execution checkpoint
After this validator is reviewed, scope one real source-preserving provider using independently produced byte-validation evidence, a complete source-handle window, bounded workspace/process behavior, and candidate-media publication. That checkpoint must explicitly resolve the architecture and release boundary before adding runtime execution. Existing Scan/Plan JSON artifact publication does not already provide a media-output commit protocol.
Keep provider absence/failure, source changes, output corruption, no reduction, budget exhaustion, cancellation, and cleanup as tested refusals. Provider exit zero never substitutes for host validation. Transactional replacement, batch execution, metadata stripping, and lossy/perceptual policies remain separate.