zencodec

Executable candidate / review in progress

The checked API,
as it executes today.

Start with one call. Add a requirement when the application needs it. Successful completion establishes the qualified contract; a refusal preserves its cause and decision history.

Partial implementation · review PRsCore 168 · native JPEG 240 · PNG 46 · conversion 92 · transcode 105Unpublished. The complete 0.3 target still has qualification gaps.

Living implementation record / updated with the code

What changed, and why?

Start with the current scope. Expand a change for the consumer, adapter and implementor consequences, exact source signatures and evidence. These entries describe review-PR code; they do not announce a crates.io release.

Admit metadata and publication on the same routeShared admission qualified · native owner experiment pending · TranscodeOffer carries optional OutputScheduleOffer evidence alongside metadata and profile treatment. One policy can require both.

Why this belongs in the API. Separate metadata and scheduling questions cannot establish a complete route when neither carries the other obligation.

Consumer
Keep one PolicySet or BoundPolicy. A required finish-first schedule still refuses on existing routes whose evidence is unknown.
Adapter
Retain the real encoder job, conversion and publication mechanism. Fill output_schedule only for that route. Check output before constructing a publication owner.
Implementor
Supply facts for the actual configuration. Encoder completion alone does not prove that output extras or borrowed input are released.
Bind requirements: metadata and output schedule → Retain one route: configured job and storage → Choose admitted route: one resolver → Verify before publication: unknown retention stays unknownCONSUMERBind requirementsmetadata and output scheduleADAPTERRetain one routeconfigured job and storageCONSUMERChoose admitted routeone resolverADAPTERVerify before publicationunknown retention stays unknown
consumerBind requirementsmetadata and output schedule
adapterRetain one routeconfigured job and storage
consumerChoose admitted routeone resolver
adapterVerify before publicationunknown retention stays unknown
Bind requirements → Retain one route → Choose admitted route → Verify before publication
Exact traits and signatures

Signatures from this revision · bodies omitted

Type definitionzencodec::operation::TranscodeOffer
pub struct TranscodeOffer {
    /// Confirmed configuration of the retained encoder job.
    pub encoder: EncodeOffer,
    /// Samples and interpretation delivered to that encoder after conversion.
    pub output: PixelDescriptor,
    /// Descriptive source metadata treatment.
    pub metadata: MetadataTransfer,
    /// Whether the output retains an opaque source color profile. Preserving
    /// such a profile does not certify its descriptive contents as PII-free.
    /// `None` means uninspected or otherwise unknown; a strict exclusion policy
    /// requires `Some(false)` from the retained executing route.
    pub retains_opaque_profile: Option<bool>,
    /// Managed row/conversion storage retained by this route after selection.
    /// Alternatives may share these same allocations; do not sum their offers.
    /// Excludes common input storage and unqualified native workspace.
    pub admitted_bytes: usize,
    /// Qualified upper bound for additional codec/conversion workspace outside
    /// managed storage, if known. `None` means unknown, never zero. Estimates
    /// are not upper bounds. Encoded staging and caller-owned source storage
    /// must also be included before claiming a whole-operation bound.
    pub workspace_upper_bound: Option<u64>,
    /// Output schedule of this same retained executing route.
    /// `None` remains unknown. Finish-first alone never proves a wait bound.
    /// The adapter must retain the qualified route through execution and check
    /// its output before publication; a decision record is not transferable proof.
    pub output_schedule: Option<super::OutputScheduleOffer>,
}
Read defaults, imports and full contract ↗
Type definitionzencodec::operation::OutputScheduleOffer
pub struct OutputScheduleOffer {
    /// Mechanism executed if this route is selected.
    pub schedule: OutputSchedule,
    /// Actual compressed-output store; None means no staging evidence.
    pub staging: Option<super::StagingFacts>,
    /// Qualified upper bound on all route-retained memory during destination
    /// waits: source, codec, conversion, metadata, scratch and compressed data.
    /// Include caller input retained by the route's borrows. Excludes unrelated
    /// caller state and the destination's own allocations. None is unknown.
    /// This is not peak-operation memory, disk use, or a measured estimate.
    pub wait_retained_upper_bound: Option<u64>,
}
Read defaults, imports and full contract ↗
Inherent methodszencodec::operation::BoundPolicy
impl<R: PolicyResolver> BoundPolicy<R> {
    pub const fn new(requirements: PolicySet, resolver: R) -> Self;

    pub fn into_parts(self) -> (PolicySet, R);
}
Read defaults, imports and full contract ↗

Still open. The fixed-capacity publication owner is a private experiment, not a proposed convenience API. Native Pulse behavior, reusable admitted storage, runtime limits and complete wait retention need qualification. Borrowed per-call controls are optional and preserve retained job controls and required admission. No whole-operation memory or PII claim.

Contract, ownership questions and limits ↗ · Adversarial admission controls ↗ · Executed results and value layouts ↗

Carry requirements outside the callbackImplemented on review API · pinned JPEG/PNG consumer graph qualified · Pass PolicySet directly, or bind it to a callback with BoundPolicy. Admission filters choices before application code and names unmet requirements.

Why this belongs in the API. A preset passed only as a callback could refuse without recording the failed requirement. Callback decorators also had no safe way to forward mandatory settings.

Consumer
Use a preset first. Add BoundPolicy when the route needs a situational decision; keep the same preset. Direct selection cannot bypass required evidence.
Adapter
Accept OperationPolicy at operation entry points. Read requirements before selection and retain the original choice indices and typed failure cause. Compatibility setter requirements remain enforced alongside the binding.
Implementor
Continue supplying evidence for the actual retained route. A binding does not invent workspace, metadata or output guarantees.
State requirements: one policy argument → Admit offers: qualified evidence only → Choose a route: callback or built-in preference → Execute selected route: preserve report and native errorsCONSUMERState requirementsone policy argumentADAPTERAdmit offersqualified evidence onlyCONSUMERChoose a routecallback or built-in preferenceADAPTERExecute selected routepreserve report and native errors
consumerState requirementsone policy argument
adapterAdmit offersqualified evidence only
consumerChoose a routecallback or built-in preference
adapterExecute selected routepreserve report and native errors
State requirements → Admit offers → Choose a route → Execute selected route
Exact traits and signatures

Signatures from this revision · bodies omitted

Trait methodszencodec::operation::OperationPolicy
pub trait OperationPolicy: sealed::Sealed {
    type Error: Error + Send + Sync + 'static;

    fn requirements(&self) -> Option<PolicySet>;

    fn decide(&mut self, question: Question<'_>) -> Result<Decision, At<Self::Error>>;
}

Required methods: requirements, decide. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Inherent methodszencodec::operation::BoundPolicy
impl<R: PolicyResolver> BoundPolicy<R> {
    pub const fn new(requirements: PolicySet, resolver: R) -> Self;

    pub fn into_parts(self) -> (PolicySet, R);
}
Read defaults, imports and full contract ↗
Trait methodszencodec::operation::PolicyResolver
pub trait PolicyResolver {
    type Error: Error + Send + Sync + 'static;

    fn resolve(&mut self, question: Question<'_>) -> Result<Decision, At<Self::Error>>;
}

Required methods: resolve. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Inherent methodszencodec::operation::PolicySet
impl PolicySet {
    pub const fn new() -> Self;

    pub const fn prefer_lossless(mut self, enabled: bool) -> Self;

    pub const fn require_metadata(mut self, treatment: MetadataTransfer) -> Self;

    pub const fn allow_opaque_profiles(mut self, allowed: bool) -> Self;

    pub const fn with_workspace_limit(mut self, bytes: u64) -> Self;

    pub const fn allow_assumed_chroma_siting(mut self, allowed: bool) -> Self;

    pub const fn require_extended_range(mut self, required: bool) -> Self;

    pub const fn require_output_schedule(mut self, schedule: super::OutputSchedule) -> Self;

    pub const fn with_output_wait_limit(mut self, bytes: u64) -> Self;
}

Selected methods · 6 more in the full contract.

Read defaults, imports and full contract ↗
Type definitionzencodec::operation::DecisionRecord
pub struct DecisionRecord {
    /// Globally unique question identity (zero only if identity allocation failed).
    pub question: u64,
    /// What prompted this decision, including on refusal or callback failure.
    pub context: DecisionContext,
    /// Number of executable alternatives supplied before mandatory admission.
    pub offered: u8,
    /// Number of alternatives that passed mandatory admission.
    pub admitted: u8,
    /// Union of missing/contradicted evidence across excluded alternatives.
    /// A selected route satisfies these requirements; rejected alternatives may not.
    pub unmet_requirements: UnmetRequirements,
    /// Selected contract, including its original request preference index.
    pub selected: Option<Selection>,
    /// True when a callback returned `UsePreset`. A `PolicySet` passed directly
    /// as the operation policy selects without delegation and records false.
    pub used_preset: bool,
    /// Selection or refusal category.
    pub code: DecisionCode,
}
Read defaults, imports and full contract ↗

Still open. Requirements cannot yet move safely across unrelated question domains. A decoder still refuses unproven output obligations at final adoption. Pinned zenpipe JPEG/PNG guide integration is qualified; other consumer graphs need separate qualification. The binding is not a pending-obligation ledger.

Canonical binding contract ↗ · Composition controls ↗ · Refusal cause regression ↗

Keep requirements pending until the affected work is allowedNative JPEG experiment qualified · production scope integration open · Source planning can proceed before metadata inspection. PendingPlan retains the requirements independently of the diagnostic report and blocks incomplete execution.

Why this belongs in the API. A metadata requirement should not reject an unrelated source question. It must also survive that question, report wrap and nested operations until the actual output is qualified.

Consumer
State the descriptive metadata requirement once. The native witness uses one callback for execution, metadata and fidelity; conflicting exact-preservation requirements refuse before output.
Adapter
Retain the actual native rewrite plan. Accept only selections with the same operation, requirements and source/route context. Inspect later metadata before emitting its carrier.
Implementor
Qualify effects from the native route and preserve its source ownership. A diagnostic record is not permission to execute; dropping it cannot discharge a requirement.
State requirements: one policy callback → Plan source access: metadata remains pending → Inspect retained input: prepare actual rewrite chunks → Validate complete plan: refuse stale or missing evidence → Receive output: GPS removed; same samplesCONSUMERState requirementsone policy callbackADAPTERPlan source accessmetadata remains pendingIMPLEMENTORInspect retained inputprepare actual rewrite chunksADAPTERValidate complete planrefuse stale or missing evidenceCONSUMERReceive outputGPS removed; same samples
consumerState requirementsone policy callback
adapterPlan source accessmetadata remains pending
implementorInspect retained inputprepare actual rewrite chunks
adapterValidate complete planrefuse stale or missing evidence
consumerReceive outputGPS removed; same samples
State requirements → Plan source access → Inspect retained input → Validate complete plan → Receive output
Exact traits and signatures

Signatures from this revision · bodies omitted

Inherent methodspolicy::plan::PendingPlan (private contract experiment)
impl PendingPlan {
    pub fn new(execution: Selected, current: Context) -> Result<Self, At<PlanError>>;

    pub fn accept(&mut self, selected: Selected, current: Context) -> Result<(), At<PlanError>>;

    pub fn remaining(&self) -> impl Iterator<Item = Kind> + '_;

    pub fn finish(self, current: Context) -> Result<Plan, At<PlanError>>;
}
Read defaults, imports and full contract ↗

Still open. PendingPlan is a private three-stage experiment, not the production scoped policy API. Native planning allocations remain unbounded by this witness; opaque ICC remains retained and uncertified. General effect deadlines and operation-level NotApplicable are still open.

Deadlines, tests and limits ↗ · Native JPEG witness ↗ · Execution tests ↗

Replay the retained pixels, not a transferable promiseNative contract experiment qualified · production integration open · Input::raster holds the actual immutable pixels across every encoder pass. A custom source cannot borrow another raster’s proof token.

Why this belongs in the API. A private token constructor did not bind callback reads to the original pixels. An adversarial source supplied different samples on later passes and still published under an exact-replay requirement.

Consumer
Start with Input::raster(pixels). Use Input::source for a file, network adapter or decoder output. Exact replay then requires admitted owned staging or a refusal.
Adapter
Read the retained raster directly and derive borrowed ownership from that input arm. Keep positional access, source stability, output seeking and retained memory as separate facts.
Implementor
Request each pass into the codec’s own row buffer. The driver rejects incomplete passes, double fills, swallowed source failures and changed replay content. Concrete row sources validate geometry before I/O, preserve typed cancellation and discard padding in bounded chunks.
Retain actual pixels: or supply an external source → Admit replay or staging: pass budget and evidence → Request each pass: codec-owned row buffer → Finish or refuse: preserve errors and output layoutCONSUMERRetain actual pixelsor supply an external sourceADAPTERAdmit replay or stagingpass budget and evidenceIMPLEMENTORRequest each passcodec-owned row bufferADAPTERFinish or refusepreserve errors and output layout
consumerRetain actual pixelsor supply an external source
adapterAdmit replay or stagingpass budget and evidence
implementorRequest each passcodec-owned row buffer
adapterFinish or refusepreserve errors and output layout
Retain actual pixels → Admit replay or staging → Request each pass → Finish or refuse
Exact traits and signatures

Signatures from this revision · bodies omitted

Type definitionmultipass_validation::Input (contract experiment)
pub enum Input<'a, S = NoSource> {
    /// The driver itself reads this immutable view on every pass.
    Raster(zenpixels::PixelSlice<'a>),
    /// An external source; exact replay requires admitted owned staging.
    Source(S),
}
Read defaults, imports and full contract ↗
Functionmultipass_validation::encode_passes (contract experiment)
pub fn encode_passes<E, S, D, R, Q>(
    encoder: E,
    input: Input<'_, S>,
    writer: D,
    resolver: R,
    replay: Q,
    options: Options,
    stop: Option<&dyn zencodec::Pulse>,
) -> Result<Completed, At<Failure>>
where
    E: MultiPassEncoder,
    S: PassSource,
    D: Destination,
    R: OperationPolicy,
    Q: ReplayResolver;

Selected methods · 6 more in the full contract.

Read defaults, imports and full contract ↗
Trait methodsmultipass_validation::MultiPassEncoder (contract experiment)
pub trait MultiPassEncoder: Sized {
    type Error: Error + Send + Sync + 'static;

    fn begin_passes(
        &mut self,
        desc: &Description,
        access: OutputAccess,
    ) -> Result<PassPlan, Self::Error>;

    fn pull_rows(
        &mut self,
        rows: &mut dyn PassRows,
        stop: Option<&dyn zencodec::Pulse>,
    ) -> Result<PassProgress, Self::Error>;

    fn pending_output(&self) -> &[u8];

    fn consume_output(&mut self, n: usize);

    fn finish_output(self) -> Result<OutputFinish<EncodeOutput>, Self::Error>;
}

Required methods: begin_passes, pull_rows, pending_output, consume_output, finish_output. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗

Still open. The experiment still has separate replay and output-policy arguments. Production integration must use one resolver, scoped requirements, a shared checked destination path and bounded tracing. No total-memory bound is claimed.

Caller evolution and remaining contracts ↗ · Detached-token reproduction ↗ · Adversarial replay controls ↗ · Native parity and failure gates ↗

Require compliance before asking the callbackImplemented on review branch · scope composition remains open · A direct Choose can no longer bypass mandatory PolicySet requirements. Only compliant prepared routes reach the callback.

Why this belongs in the API. Tenant privacy and resource requirements must survive application selection logic. Delegating preferences must not be necessary to enforce a requirement.

Consumer
State requirements on the operation policy. Choose among admitted routes or use UsePreset for preferences. Refusals name requirements with Unknown or Contradicted evidence.
Adapter
Keep original indices and retained jobs paired. Admission uses a bounded bit mask and preserves offered versus admitted counts without copying routes.
Implementor
Provide qualified evidence for actual executable effects. Unknown bounds and uninspected opaque profiles cannot satisfy required guarantees.
Prepare executable routes: retain jobs and evidence → Enforce requirements: filter before callback → Choose a compliant route: or delegate preferences → Execute the retained choice: no fallbackADAPTERPrepare executable routesretain jobs and evidenceADAPTEREnforce requirementsfilter before callbackCONSUMERChoose a compliant routeor delegate preferencesADAPTERExecute the retained choiceno fallback
adapterPrepare executable routesretain jobs and evidence
adapterEnforce requirementsfilter before callback
consumerChoose a compliant routeor delegate preferences
adapterExecute the retained choiceno fallback
Prepare executable routes → Enforce requirements → Choose a compliant route → Execute the retained choice
Exact traits and signatures

Signatures from this revision · bodies omitted

Inherent methodszencodec::operation::PolicySet
impl PolicySet {
    pub const fn new() -> Self;

    pub const fn prefer_lossless(mut self, enabled: bool) -> Self;

    pub const fn require_metadata(mut self, treatment: MetadataTransfer) -> Self;

    pub const fn allow_opaque_profiles(mut self, allowed: bool) -> Self;

    pub const fn with_workspace_limit(mut self, bytes: u64) -> Self;

    pub const fn allow_assumed_chroma_siting(mut self, allowed: bool) -> Self;

    pub const fn require_extended_range(mut self, required: bool) -> Self;

    pub const fn require_output_schedule(mut self, schedule: super::OutputSchedule) -> Self;

    pub const fn with_output_wait_limit(mut self, bytes: u64) -> Self;
}

Selected methods · 6 more in the full contract.

Read defaults, imports and full contract ↗
Type definitionzencodec::operation::DecisionRecord
pub struct DecisionRecord {
    /// Globally unique question identity (zero only if identity allocation failed).
    pub question: u64,
    /// What prompted this decision, including on refusal or callback failure.
    pub context: DecisionContext,
    /// Number of executable alternatives supplied before mandatory admission.
    pub offered: u8,
    /// Number of alternatives that passed mandatory admission.
    pub admitted: u8,
    /// Union of missing/contradicted evidence across excluded alternatives.
    /// A selected route satisfies these requirements; rejected alternatives may not.
    pub unmet_requirements: UnmetRequirements,
    /// Selected contract, including its original request preference index.
    pub selected: Option<Selection>,
    /// True when a callback returned `UsePreset`. A `PolicySet` passed directly
    /// as the operation policy selects without delegation and records false.
    pub used_preset: bool,
    /// Selection or refusal category.
    pub code: DecisionCode,
}
Read defaults, imports and full contract ↗

Still open. Reusable policies across unrelated operation stages still need explicit scope and discharge. Keep TranscodeRequest metadata treatment until the single-source requirement migration is qualified. Chroma-siting decisions remain situational.

Admission contract ↗ · Adversarial admission tests ↗ · Remaining scope and discharge ↗

Make ignored policy requests visible to callersChecked replacements implemented · legacy setters deprecated · Typed with_policy and erased set_policy job methods now warn that unsupported fields can be silently ignored.

Why this belongs in the API. An infallible setter looks successful even when a codec does nothing. A caller should not need a capability preflight to require a policy.

Consumer
Use EncodeRequest::with_policy or DecodeRequest::with_policy and execute the request; failures surface as errors. At the job level, use try_with_policy or try_set_policy.
Adapter
Forward the checked method and preserve its original rejection. Required operation requests already use this path; legacy CodecSet conveniences retain their prior semantics.
Implementor
Implement try_set_policy atomically: reject unsupported fields without partial mutation. The default accepts only the empty policy. Implementing the legacy setter does not prove checked support.
Store the requirement: operation request → Apply checked policy: before codec input → Accept or reject atomically: no silent substitution → Receive output or error: original cause retainedCONSUMERStore the requirementoperation requestADAPTERApply checked policybefore codec inputIMPLEMENTORAccept or reject atomicallyno silent substitutionCONSUMERReceive output or errororiginal cause retained
consumerStore the requirementoperation request
adapterApply checked policybefore codec input
implementorAccept or reject atomicallyno silent substitution
consumerReceive output or errororiginal cause retained
Store the requirement → Apply checked policy → Accept or reject atomically → Receive output or error
Exact traits and signatures

Signatures from this revision · bodies omitted

Trait methodszencodec::decode::DecodeJob<'a>
pub trait DecodeJob<'a>: Sized {
    fn try_set_policy(
        &mut self,
        policy: crate::DecodePolicy,
    ) -> Result<(), crate::UnsupportedOperation>;

    fn try_with_policy(
        mut self,
        policy: crate::DecodePolicy,
    ) -> Result<Self, crate::UnsupportedOperation>;
}

Selected methods · 25 more in the full contract.

Required methods: with_pulse, with_limits, probe, output_info, decoder, push_decoder, streaming_decoder, animation_frame_decoder. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Trait methodszencodec::encode::EncodeJob
pub trait EncodeJob: Sized {
    fn try_set_policy(
        &mut self,
        policy: crate::EncodePolicy,
    ) -> Result<(), crate::UnsupportedOperation>;

    fn try_with_policy(
        mut self,
        policy: crate::EncodePolicy,
    ) -> Result<Self, crate::UnsupportedOperation>;
}

Selected methods · 23 more in the full contract.

Required methods: with_pulse, with_limits, with_metadata, encoder, animation_frame_encoder. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗

Still open. Deprecation changes diagnostics, not old behavior. Full configuration and metadata delivery remain native conformance obligations; remaining legacy convenience cleanup is still open.

Canonical migration ↗ · Required-policy conformance ↗

Keep range, precision and pixel storage distinctNative workflow qualified · numerical contract open · Native AV1 component planes now pass through unclipped RGB F32, existing OutputPlan linear-light processing and shared owned-image replacement.

Why this belongs in the API. Float storage does not imply linear light or bit-exact conversion. The deprecated forbid_lossy preset clips by default, and the current continuous sRGB constants differ from the IEC definition.

Consumer
Request the actual descriptor and target color. The executing witness uses retain_channels().with_clip_out_of_gamut(false); callers should eventually receive that guarantee from one operation policy.
Adapter
Retain the selected OutputPlan and its returned color context. Replace packed color for unchanged coordinates; preserve original frame facts and timing separately from current linear RGB.
Implementor
All 54 native presentations and 162 crops pass byte-exact row-batch agreement. Independent double-precision definitions separate continuous-curve arithmetic error from IEC disagreement. Clipped controls expose the legacy preset behavior.
Request linear RGB: explicit range requirement → Prepare the actual transform: descriptor + context + options → Convert strided rows: keep output signaling → Retain the processed image: source timing survivesCONSUMERRequest linear RGBexplicit range requirementADAPTERPrepare the actual transformdescriptor + context + optionsIMPLEMENTORConvert strided rowskeep output signalingCONSUMERRetain the processed imagesource timing survives
consumerRequest linear RGBexplicit range requirement
adapterPrepare the actual transformdescriptor + context + options
implementorConvert strided rowskeep output signaling
consumerRetain the processed imagesource timing survives
Request linear RGB → Prepare the actual transform → Convert strided rows → Retain the processed image
Exact traits and signatures

Signatures from this revision · bodies omitted

Inherent methodszencodec::image::ImageOutput
impl ImageOutput {
    pub fn interpretation(&self) -> &ImageInterpretation;

    pub fn source_frame(&self) -> Option<&Arc<FrameInterpretation>>;

    pub fn map(&self) -> Result<Box<dyn MappedImage + '_>, At<OutputError>>;

    pub fn with_pixels<T>(
        &self,
        consume: impl FnOnce(PixelSlice<'_>) -> T,
    ) -> Result<T, At<OutputError>>;
}

Selected methods · 4 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec::image::ImageOutput
impl ImageOutput {
    pub fn replace_packed_color<P: PackedImageOwner + 'static>(
        self,
        owner: P,
    ) -> Result<Self, At<OutputError>>;
}
Read defaults, imports and full contract ↗
Inherent methods · pinned conversion dependencyzenpixels_convert::OutputPlan

Extracted from f23ac753, matching the compiled native tests. Imports use caller paths; method bodies are omitted. Descriptor/profile conflicts return ConvertError::OutputInterpretationMismatch { requested, profile } before CMS construction. new_preserving_samples instead requires matching profiles and a reversible representation change; it never consults a CMS. retain_channels controls channel/depth restrictions and clipping defaults without claiming exactness. It replaces the removed forbid_lossy spelling. Conversion options source.

use alloc::sync::Arc;
use zenpixels::{ColorContext, ColorOrigin, PixelDescriptor, PixelFormat, PixelSlice, PixelSliceMut};
use zenpixels_convert::{ConvertError, ConvertOptions, OutputPlan, output::{OutputMetadata, OutputProfile}};
use whereat::At;

impl OutputPlan {
    pub fn new(
        source_desc: PixelDescriptor,
        current: Option<Arc<ColorContext>>,
        origin: &ColorOrigin,
        target: OutputProfile,
        pixel_format: PixelFormat,
        options: &ConvertOptions,
        cms: Option<&dyn zenpixels_convert::cms::PluggableCms>,
    ) -> Result<Self, At<ConvertError>>;

    pub fn new_with_descriptor(
        source_desc: PixelDescriptor,
        current: Option<Arc<ColorContext>>,
        origin: &ColorOrigin,
        target: OutputProfile,
        output: PixelDescriptor,
        options: &ConvertOptions,
        cms: Option<&dyn zenpixels_convert::cms::PluggableCms>,
    ) -> Result<Self, At<ConvertError>>;

    pub fn new_preserving_samples(
        source_desc: PixelDescriptor,
        current: Option<Arc<ColorContext>>,
        origin: &ColorOrigin,
        target: OutputProfile,
        output: PixelDescriptor,
    ) -> Result<Self, At<ConvertError>>;

    pub fn output_descriptor(&self) -> PixelDescriptor;

    pub fn metadata(&self) -> &OutputMetadata;

    pub fn prepare(&mut self, max_width: u32) -> Result<(), At<ConvertError>>;

    pub fn borrow_rows<'a>(
        &self,
        source: PixelSlice<'a>,
    ) -> Result<Option<PixelSlice<'a>>, At<ConvertError>>;

    pub fn convert_rows<'a>(
        &mut self,
        source: PixelSlice<'_>,
        mut destination: PixelSliceMut<'a>,
    ) -> Result<PixelSliceMut<'a>, At<ConvertError>>;
}

impl ConvertOptions {
    pub const fn retain_channels() -> Self;

    pub const fn with_clip_out_of_gamut(mut self, clip: bool) -> Self;
}
Read defaults and the full conversion contract ↗

Still open. The shared planar plan is now implemented below. The misleading preset is deprecated in favor of retain_channels; exact samples use new_preserving_samples. The executing Gamma22 route now honors unclipped requests, with native before/after regressions and saved sample traces. The broader numerical model/accuracy contract remains open. This qualifies BT.709/sRGB processing, not HDR rendering or complete workspace bounds. The preset migration changes no option values and adds no dependency package.

Unclipped Gamma22 execution ↗ · Preset migration and native parity ↗ · Native processing scope ↗ · Measured errors and limits ↗ · Every presentation ↗

Resolve color and canvas geometry before publishing an imagePrivate implementation replaced · public renderer qualified · One held-image operation prepares the requested color, projects the oriented canvas region, preserves aligned auxiliary values and resolves publication on the finished owner.

Why this belongs in the API. Composited packed samples can still have unapplied orientation or a different color profile. Assembling those steps exposed a one-bit same-profile TIFF change that isolated color and geometry tests missed.

Consumer
Start with Renderer::new(storage).render_first(&mut images), or render_selected for an explicit selection, for full-canvas oriented sRGB RGBA8. Use with_request for explicit color, descriptor and region, and with_cms when needed. The former private pub struct Request and pub enum Target are now RenderRequest and ColorTarget. Policy sees the final result once.
Adapter
Use CheckedCollection::decode_with to transform before publication under the same cancellation token. Bind OutputPlan to the held descriptor/context, project the requested canvas region and release native color before replacement. Preserve scalar auxiliary values and original spot-color parameters.
Implementor
Native TIFF and BigTIFF exercise all eight orientations with U8/U16/F32 samples. Seventeen TIFF/JXL selections match independent Little CMS plus a separate coordinate table. The resulting zenpixels fix adds no public items, signatures or dependencies.
Require color and region: oriented canvas coordinates → Prepare actual color plan: bind the held source → Project and convert: admit owned samples → Resolve finished output: same policy interfaceCONSUMERRequire color and regionoriented canvas coordinatesADAPTERPrepare actual color planbind the held sourceIMPLEMENTORProject and convertadmit owned samplesCONSUMERResolve finished outputsame policy interface
consumerRequire color and regionoriented canvas coordinates
adapterPrepare actual color planbind the held source
implementorProject and convertadmit owned samples
consumerResolve finished outputsame policy interface
Require color and region → Prepare actual color plan → Project and convert → Resolve finished output
Exact traits and signatures

Signatures from this revision · bodies omitted

Type definitionzencodec_render::ColorTarget
pub enum ColorTarget {
    /// Preserve the current sample color space, not original-file provenance.
    Current,
    /// Convert to this named color space.
    Named(Cicp),
    /// Convert to this ICC profile, requiring a suitable supplied CMS.
    Icc(Arc<[u8]>),
}
Read defaults, imports and full contract ↗
Type definitionzencodec_render::RenderRequest
pub struct RenderRequest {
    /// Required output color space.
    pub target: ColorTarget,
    /// Required packed pixel descriptor, including alpha interpretation.
    pub required: PixelDescriptor,
    /// Oriented canvas coordinates. None requests the full oriented canvas.
    pub region: Option<Rect>,
    /// Explicit conversion behavior, including clipping and channel loss.
    pub conversion: ConvertOptions,
}
Read defaults, imports and full contract ↗
Inherent methodszencodec_render::Renderer
impl<'a> Renderer<'a> {
    pub fn new(storage: StorageBudget) -> Self;

    pub fn with_request(mut self, request: RenderRequest) -> Self;

    pub fn with_cms(mut self, cms: &'a dyn PluggableCms) -> Self;

    pub fn render_first<C: Collection, R: OperationPolicy>(
        &self,
        images: &mut CheckedCollection<C, R>,
    ) -> Result<Option<Outcome<ImageOutput>>, At<CollectionOperationError>>;

    pub fn render_selected<C: Collection, R: OperationPolicy>(
        &self,
        images: &mut CheckedCollection<C, R>,
        image: &ImageId,
    ) -> Result<Outcome<ImageOutput>, At<CollectionOperationError>>;

    pub fn render<R: OperationPolicy + ?Sized>(
        &self,
        image: ImageOutput,
        policy: &mut R,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<Outcome<ImageOutput>, At<RenderFailure>>;
}

Selected methods · 3 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec::operation::CheckedCollection<C, R>
impl<C: Collection, R: OperationPolicy> CheckedCollection<C, R> {
    pub fn open(
        pulse: SharedPulse,
        resolver: R,
        open: impl FnOnce(SharedPulse) -> Result<C, Cause>,
    ) -> Result<Self, At<CollectionOperationError>>;

    pub fn with_policy_set(mut self, preset: PolicySet) -> Self;

    pub fn images(&mut self) -> Result<C::Cursor, At<CollectionOperationError>>;

    pub fn children(&mut self, image: &ImageId) -> Result<C::Cursor, At<CollectionOperationError>>;

    pub fn next(
        &mut self,
        cursor: &mut C::Cursor,
    ) -> Result<Option<ImageId>, At<CollectionOperationError>>;

    pub fn describe(
        &mut self,
        image: &ImageId,
    ) -> Result<ImageDescription, At<CollectionOperationError>>;

    pub fn first_image(
        &mut self,
        request: &ImageRequest,
    ) -> Result<Option<Outcome<ImageOutput>>, At<CollectionOperationError>>;

    pub fn decode(
        &mut self,
        image: &ImageId,
        request: &ImageRequest,
    ) -> Result<Outcome<ImageOutput>, At<CollectionOperationError>>;

    pub fn decode_with<E: Into<Cause>>(
        &mut self,
        image: &ImageId,
        request: &ImageRequest,
        transform: impl FnOnce(ImageOutput, &dyn crate::Pulse) -> Result<ImageOutput, E>,
    ) -> Result<Outcome<ImageOutput>, At<CollectionOperationError>>;

    pub fn report(&self) -> &OperationReport;

    pub fn is_finished(&self) -> bool;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗

Still open. The public companion zencodec-render reuses the validated implementation without adding conversion to core codec dependencies. It still reconstructs the full native selection. Native/CMS workspace, decode ROI planning, planar video, HDR display mapping, resize and privacy remain open. Geometry currently copies even for identity, and descriptor-only normalization can require another color owner; the output pool must admit both.

Implementation, reproduction and limits ↗ · Native evidence and costs ↗ · Same-profile correction ↗ · Shared rendering follow-up ↗

Own preparation and seek-back output in the shared driverPublic review API implemented · native validation passed · zencodec-media::output now owns the actual muxer and writer, validates packet clocks/epochs and applies preflighted finish patches. Exact timing through begin_exact_timing is the default.

Why this belongs in the API. Callers should not rebuild the tested publication, partial-write and relative-seek choreography. Choosing an ordinary muxer must not silently weaken a required timing guarantee.

Consumer
For rows, use row_encode::encode(encoder, source, writer, resolver, options, stop); use encode_to_destination with SeekBack::new(writer) for an actual seekable destination. Raster::new(pixels) adapts an existing image. For finite video, use StagedEncode::new(encoder, muxer, Forward(writer), resolver, storage, stop), push frames and finish. It releases the encoder before final output writes. Use with_staging and Staging::disk for explicit private scratch; disk writes can block during encoding. For already-encoded packets, use Output::new(muxer, Forward(writer), tracks, stop), send packets and finish. For disk, wrap the writer with SeekBack::new; its current position becomes the output origin. Use PreparedOutput::new when policy must run before any header writes, then consume it with publish.
Adapter
Forward and SeekBack now live in zencodec_io::destination and derive access from actual writers; media re-exports the same types. OutputFinish and Patch live in the core, with MuxFinish retained as an alias. The sealed Destination trait prevents independent claims of seek support. Failures retain stage, append progress and the original cause; later packet sends fail terminally.
Implementor
The native qualification imports the shared driver. The owned path also runs the shared real YCbCr converter over changing 8/10/12-bit layouts and ranges into fixed RGB8 AV1/MP4; independent decoded samples match, and required extended range refuses before conversion. Existing three-container disk/forward, timing, cancellation and partial-write checks pass.
Choose actual writer: forward or seek-back → Prepare retained muxer: exact timing or error → Approve publication: consume prepared output → Write and finish: checked clocks and patchesCONSUMERChoose actual writerforward or seek-backADAPTERPrepare retained muxerexact timing or errorCONSUMERApprove publicationconsume prepared outputADAPTERWrite and finishchecked clocks and patches
consumerChoose actual writerforward or seek-back
adapterPrepare retained muxerexact timing or error
consumerApprove publicationconsume prepared output
adapterWrite and finishchecked clocks and patches
Choose actual writer → Prepare retained muxer → Approve publication → Write and finish
Exact traits and signatures

Signatures from this revision · bodies omitted

Trait methodszencodec::encode::Encoder
pub trait Encoder: Sized {
    type Error: core::error::Error + Send + Sync + 'static;

    fn begin_declared(
        &mut self,
        _declaration: &crate::encode::EncodeDeclaration<'_>,
    ) -> Result<crate::encode::EncodePlan, Self::Error>;

    fn begin_output(
        &mut self,
        width: u32,
        height: u32,
        descriptor: PixelDescriptor,
        access: crate::output_patch::OutputAccess,
    ) -> Result<crate::encode::OutputTiming, Self::Error>;

    fn pull_rows(
        &mut self,
        _rows: &mut dyn crate::encode::EncodeRows,
        _pulse: Option<&dyn crate::Pulse>,
    ) -> Result<crate::encode::PullProgress, Self::Error>;

    fn finish_output(self) -> Result<crate::output_patch::OutputFinish<EncodeOutput>, Self::Error>;
}

Selected methods · 11 more in the full contract.

Required methods: reject, encode. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Inherent methodszencodec::encode::OutputTiming
impl OutputTiming {
    pub const fn with_patches(mut self) -> Self;

    pub const fn patches_at_finish(&self) -> bool;
}

Selected methods · 3 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec_media::staged::StagedEncode (std)
impl<E: VideoEncoder, M: Muxer, D: Destination, R: OperationPolicy> StagedEncode<E, M, D, R> {
    pub fn new(
        encoder: E,
        mux: M,
        destination: D,
        resolver: R,
        storage: StorageBudget,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<Self, At<Failure>>;

    pub fn with_staging(
        encoder: E,
        mux: M,
        destination: D,
        resolver: R,
        staging: Staging,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<Self, At<Failure>>;

    pub fn with_policy_set(mut self, preset: PolicySet) -> Self;

    pub fn report(&self) -> &OperationReport;

    pub fn is_failed(&self) -> bool;

    pub fn push(
        &mut self,
        input: Frame,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<(), At<Failure>>;

    pub fn push_converted(
        &mut self,
        input: Frame,
        target: &FrameInterpretation,
        conversion: FrameConversionOffer,
        execute: impl FnOnce(Frame) -> Result<Frame, Cause>,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<(), At<Failure>>;

    pub fn finish(mut self, pulse: Option<&dyn zencodec::Pulse>) -> Result<Completed, At<Failure>>;
}

Selected methods · 2 more in the full contract.

Read defaults, imports and full contract ↗
Function · zencodec-io feature: stdzencodec_io::row_encode::encode (std)
pub fn encode<E: Encoder, S: Source, W: Write, R: OperationPolicy>(
    encoder: E,
    source: S,
    writer: W,
    resolver: R,
    options: Options,
    pulse: Option<&dyn zencodec::Pulse>,
) -> Result<Completed, At<Failure>>;

pub fn encode_to_destination<E: Encoder, S: Source, D: Destination, R: OperationPolicy>(
    encoder: E,
    source: S,
    writer: D,
    resolver: R,
    options: Options,
    pulse: Option<&dyn zencodec::Pulse>,
) -> Result<Completed, At<Failure>>;

Selected methods · 11 more in the full contract.

Read defaults, imports and full contract ↗
Trait methods · zencodec-io feature: stdzencodec_io::row_encode::Source
pub trait Source {
    type Error: Error + Send + Sync + 'static;

    fn description(&self) -> Description;

    fn mechanism(&self) -> OutputSource;

    fn read_rows(
        &mut self,
        first: u32,
        rows: PixelSliceMut<'_>,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<(), Self::Error>;
}

Selected methods · 3 more in the full contract.

Required methods: description, read_rows. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Type definition · zencodec-io feature: stdzencodec_io::row_encode::Description
pub struct Description {
    /// Width in samples.
    pub width: u32,
    /// Total rows.
    pub height: u32,
    /// Complete sample descriptor accepted by the configured encoder.
    pub descriptor: PixelDescriptor,
    /// Original color context carried on every source and encoder row view.
    pub color_context: Option<Arc<ColorContext>>,
    /// Optional assertion about current canonical samples, checked before push.
    /// `Some` is strict: unsupported layouts refuse before startup, including
    /// full-storage CMYK claims. `None` does not restrict sample depth.
    /// It does not prove the encoder preserves samples or bound native memory.
    pub exact_depth: Option<ExactDepth>,
}
Read defaults, imports and full contract ↗
Inherent methods · zencodec-io feature: stdzencodec_io::row_encode::Raster
impl<'a> Raster<'a> {
    pub const fn new(pixels: PixelSlice<'a>) -> Self;
}
Read defaults, imports and full contract ↗
Inherent methods · zencodec-io feature: stdzencodec_io::row_encode::Options
impl Options {
    pub fn new(storage: StorageBudget, max_output_bytes: u64) -> Self;

    pub fn with_batch_rows(mut self, rows: NonZeroU32) -> Self;

    pub fn with_policy_set(mut self, preset: PolicySet) -> Self;

    pub fn with_disk_staging(
        mut self,
        mut file: File,
        replay_bytes: NonZeroUsize,
    ) -> io::Result<Self>;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
Type definition · zencodec-io feature: stdzencodec_io::row_encode::Failure
pub struct Failure {
    /// Boundary that failed.
    pub stage: Stage,
    /// Confirmed destination appends, including partial writes.
    pub emitted: u64,
    /// Confirmed overwritten bytes, separate from appended extent.
    pub patched_bytes: u64,
    /// Bounded policy history, including after later source/codec/I/O failure.
    pub report: Box<OperationReport>,
    cause: Cause,
}
Read defaults, imports and full contract ↗
Functionzencodec::operation::resolve_output_schedule
pub fn resolve_output_schedule<R: OperationPolicy + ?Sized>(
    facts: OutputScheduleFacts,
    offers: &[OutputScheduleOffer],
    resolver: &mut R,
    preset: PolicySet,
    pulse: Option<&dyn crate::Pulse>,
) -> Result<(usize, DecisionRecord), At<PolicyFailure>>;
Read defaults, imports and full contract ↗
Type definitionzencodec::operation::OutputScheduleOffer
pub struct OutputScheduleOffer {
    /// Mechanism executed if this route is selected.
    pub schedule: OutputSchedule,
    /// Actual compressed-output store; None means no staging evidence.
    pub staging: Option<super::StagingFacts>,
    /// Qualified upper bound on all route-retained memory during destination
    /// waits: source, codec, conversion, metadata, scratch and compressed data.
    /// Include caller input retained by the route's borrows. Excludes unrelated
    /// caller state and the destination's own allocations. None is unknown.
    /// This is not peak-operation memory, disk use, or a measured estimate.
    pub wait_retained_upper_bound: Option<u64>,
}
Read defaults, imports and full contract ↗
Type definitionzencodec::operation::OutputScheduleFacts
pub struct OutputScheduleFacts {
    /// Actual configured encoder's byte availability after begin.
    pub timing: OutputTiming,
    /// Actual source acquisition/ownership mechanism.
    pub source: OutputSource,
}
Read defaults, imports and full contract ↗
Inherent methodszencodec::operation::PolicySet
impl PolicySet {
    pub const fn new() -> Self;

    pub const fn prefer_lossless(mut self, enabled: bool) -> Self;

    pub const fn require_metadata(mut self, treatment: MetadataTransfer) -> Self;

    pub const fn allow_opaque_profiles(mut self, allowed: bool) -> Self;

    pub const fn with_workspace_limit(mut self, bytes: u64) -> Self;

    pub const fn allow_assumed_chroma_siting(mut self, allowed: bool) -> Self;

    pub const fn require_extended_range(mut self, required: bool) -> Self;

    pub const fn require_output_schedule(mut self, schedule: super::OutputSchedule) -> Self;

    pub const fn with_output_wait_limit(mut self, bytes: u64) -> Self;
}

Selected methods · 6 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec_media::staging::Staging (std)
impl Staging {
    pub fn memory(budget: StorageBudget) -> Self;

    pub fn disk(mut file: File, limits: DiskLimits, replay: StorageBudget) -> Result<Self, Cause>;

    pub fn facts(&self) -> StagingFacts;
}

Selected methods · 4 more in the full contract.

Read defaults, imports and full contract ↗
Type definitionzencodec_media::staging::DiskLimits
pub struct DiskLimits {
    /// Total packet and addition bytes appended after the scratch origin.
    pub max_bytes: u64,
    /// Maximum number of staged packets.
    pub max_packets: usize,
    /// Maximum side-data entries on each packet, including empty entries.
    pub max_additions_per_packet: usize,
}
Read defaults, imports and full contract ↗
Type definitionzencodec_media::staged::Failure
pub struct Failure {
    /// Failed boundary.
    pub stage: Stage,
    /// Confirmed destination appends, excluding spool writes and patch overwrites.
    /// Failed final patches can also leave partial overwrites; inspect the cause.
    pub emitted: u64,
    /// Decisions up to this failure.
    pub report: Box<OperationReport>,
    cause: Cause,
}
Read defaults, imports and full contract ↗
Trait methodszencodec_media::session::VideoEncoder::output_fidelity
pub trait VideoEncoder {
    type Error: core::error::Error + Send + Sync + 'static;

    fn output_config(&self) -> Option<&CodecConfig>;

    fn output_fidelity(&self) -> Option<zencodec::encode::Fidelity>;
}

Selected methods · 3 more in the full contract.

Required methods: send, receive, end_input, output_config. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Type definitionzencodec_media::staged::Refusal
pub enum Refusal {
    /// Encoder cannot declare its container configuration before input.
    ConfigurationUnavailable,
    /// Configuration does not describe video.
    VideoConfigurationRequired,
    /// Conversion's bounded facts disagree with its full target.
    OfferMismatch,
    /// Conversion returned different interpretation or timing.
    ConversionMismatch,
    /// Encoder promised pending output but did not return a packet.
    ReceivePendingWithoutPacket,
    /// Encoder ended before the caller finished input.
    EarlyEnd,
    /// Encoder changed the configuration or fidelity bound at preparation.
    ConfigurationChanged,
    /// Encoder requested input after end_input.
    InputAfterEnd,
    /// Encoder returned an empty or taken packet.
    EmptyPacket,
    /// Operation previously failed or unwound.
    Terminal,
    /// No input frame completed policy admission. Empty output is unsupported.
    NoAdmittedFrames,
    /// The legacy preset setter was called after input had been accepted.
    PolicyChangedAfterInput,
}
Read defaults, imports and full contract ↗
Trait methodszencodec_media::container::Muxer
pub trait Muxer {
    type Error: core::error::Error + Send + Sync + 'static;

    fn begin_exact_timing(
        &mut self,
        tracks: &[TrackInfo],
        hint: MuxHint,
    ) -> Result<MuxTiming, SessionError<Self::Error>>;

    fn write_packet(
        &mut self,
        packet: Packet,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<(), SessionError<Self::Error>>;
}

Selected methods · 5 more in the full contract.

Required methods: begin, write_packet, pending_output, consume_output, take_output, finish. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Inherent methodszencodec_media::output::PreparedOutput (std)
impl<M: Muxer, D: Destination> PreparedOutput<M, D> {
    pub fn new(
        mux: M,
        destination: D,
        tracks: &[TrackInfo],
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<Self, Failure>;

    pub const fn timing(&self) -> MuxTiming;

    pub fn facts(&self) -> DestinationFacts;

    pub fn publish(self, pulse: Option<&dyn zencodec::Pulse>) -> Result<Output<M, D>, Failure>;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec_media::output::Output (std)
impl<M: Muxer, D: Destination> Output<M, D> {
    pub fn new(
        mux: M,
        destination: D,
        tracks: &[TrackInfo],
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<Self, Failure>;

    pub fn with_container_timing(
        mux: M,
        destination: D,
        tracks: &[TrackInfo],
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<Self, Failure>;

    pub fn facts(&self) -> DestinationFacts;

    pub const fn stats(&self) -> &Stats;

    pub fn send(
        &mut self,
        packet: Packet,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<(), Failure>;

    pub fn finish(mut self, pulse: Option<&dyn zencodec::Pulse>) -> Result<Stats, Failure>;
}

Selected methods · 6 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec_io::destination::SeekBack (std; also re-exported by media)
impl<W: Write + Seek> SeekBack<W> {
    pub fn new(mut writer: W) -> io::Result<Self>;
}
Read defaults, imports and full contract ↗

Still open. This is a synchronous std adapter over the unchanged sans-I/O Muxer. Container-chosen timing requires with_container_timing. It does not establish whole-operation memory bounds or safe live encoder backpressure. StagedEncode now owns a finite fixed-configuration encoder and actual destination, retaining conversion through policy and dropping the encoder before writing compressed output. It stages the full compressed sequence, not native workspace. VideoEncoder::output_fidelity now supplies prepared-instance evidence with unknown as the default; the owned path rejects drift and keeps conversion losses separate. Native adapters must adopt the method before generic callers see qualified fidelity. Explicit disk staging now has separate disk/index caps and admitted replay; policy sees the chosen store. Spool writes may block while the encoder retains raw state. Scratch privacy and deletion remain caller-owned. Late replay errors retain confirmed destination appends and their original cause. The shared schedule resolver now selects retained finish-first/live routes; a native JPEG generated-row witness executes both. StagedEncode automatically supplies finish-first evidence to the same policy. Unknown required wait-memory bounds refuse. The shared row_encode driver now owns fixed-interpretation row input through policy and output, preserving context and failure reports. It revalidates source interpretation and acquisition across policy and writer callbacks, retaining confirmed effects on refusal. Raster adapts existing strided pixels. encode_to_writer is deprecated; one-shot-only codecs use encode followed by writing the result. Raster finish-first now supports Options::with_disk_staging with private caller-owned scratch and admitted chunk replay; the same policy question exposes the actual store. Raster negotiation now preserves finish patches and output metadata through typed/dyn encoders; the row driver preflights patches and separates append/overwrite effects. A private native AV1/IVF single-raster witness qualifies transport while explicitly buffering the input. Production raster codec adoption, repeated input, segmentation and qualified live sequential backpressure remain open.

Native evidence and limits ↗ · Public implementation ↗

Validate every output patch before changing bytesReplacement implemented · apply_finish immediately deprecated · MuxFinish::apply_to validates the complete result and reserves fallibly before mutation. External writers call validate before tail and patch effects.

Why this belongs in the API. The former helper could apply an earlier patch and append a tail before rejecting a later invalid range, leaving changed output behind an error.

Consumer
Replace apply_finish(&mut bytes, finish) with finish.apply_to(&mut bytes). Validation or allocation failure leaves in-memory contents and length unchanged.
Adapter
For external output, validate relative ranges before writes, add the captured stream origin, preserve preceding content and restore the final position. Real I/O failure can leave partial output; retain its cause and confirmed progress.
Implementor
Keep the existing finish payload. Native IVF, WebM and MP4 use the shared zencodec_media::output driver across forward-only and disk destinations; the qualification record retains independent AV1 component readback.
Finish muxing: tail and relative patches → Validate all ranges: before any final effects → Apply or receive an error: in-memory mutation is transactionalIMPLEMENTORFinish muxingtail and relative patchesADAPTERValidate all rangesbefore any final effectsCONSUMERApply or receive an errorin-memory mutation is transactional
implementorFinish muxingtail and relative patches
adapterValidate all rangesbefore any final effects
consumerApply or receive an errorin-memory mutation is transactional
Finish muxing → Validate all ranges → Apply or receive an error
Exact traits and signatures

Signatures from this revision · bodies omitted

Inherent methodszencodec::output_patch::OutputFinish (media alias: MuxFinish)
impl<T: AsRef<[u8]>> OutputFinish<T> {
    pub fn new(tail: T, patches: Vec<Patch>) -> Self;

    pub fn validate(&self, emitted_bytes: u64) -> Result<u64, PatchError>;
}
Read defaults, imports and full contract ↗
Inherent methodszencodec::output_patch::OutputFinish<Vec<u8>>
impl OutputFinish {
    pub fn apply_to(self, file: &mut Vec<u8>) -> Result<(), PatchError>;
}
Read defaults, imports and full contract ↗

Still open. Transport success is not timing preservation: IVF lacks explicit durations, and the tested progressive MP4 route refuses a nonzero decode origin only at finish. Checked timing/layout admission, whole-operation memory, async writer pressure and continuous reconfiguration remain open.

Native matrix and limits ↗ · Qualification receipt ↗ · Shared contract ↗

Decide output transitions before accepting a changed frameShared resolver implemented · native segmented witness remains private · FrameRoute offers Continue or BeginSegment through the existing policy callback. Optional FrameConversionOffer describes the retained converter and actual encoder input, separately from source facts and encoder fidelity.

Why this belongs in the API. A decoder can discover new dimensions, color or clocks after output headers have escaped. Replacing the encoder alone cannot authorize a different destination representation.

Consumer
A fixed destination refuses an unsupported change. An explicitly segmented request can select a prepared replacement. The lossless preference first ranks sample-preserving conversion plus lossless encoding, then encoder-only evidence; required bounds still win. There is no mandatory capability-query dance and no retry after execution failure.
Adapter
Retain the actual new encoder and held frame through policy, drain the old encoder in order, and record the decision. Count header publication as committed output even when no picture bytes have escaped.
Implementor
Existing send, receive, end_input and output_config methods suffice for independent segments. The native AV1 witness preserves exact components through dimension, color, depth/layout and clock changes, checked by FFmpeg/libdav1d. A fixed committed header also refuses equal-size color, depth/layout and clock changes while preserving earlier frames.
Hold the actual frame: source facts and timing → Prepare permitted route: retain real encoder → Resolve or refuse: one fallible callback → Drain then change: respect committed outputIMPLEMENTORHold the actual framesource facts and timingADAPTERPrepare permitted routeretain real encoderCONSUMERResolve or refuseone fallible callbackADAPTERDrain then changerespect committed output
implementorHold the actual framesource facts and timing
adapterPrepare permitted routeretain real encoder
consumerResolve or refuseone fallible callback
adapterDrain then changerespect committed output
Hold the actual frame → Prepare permitted route → Resolve or refuse → Drain then change
Exact traits and signatures

Signatures from this revision · bodies omitted

Functionzencodec::operation::resolve_frame_route
pub fn resolve_frame_route<R: OperationPolicy + ?Sized>(
    interpretation: &FrameInterpretation,
    timing: FrameTiming,
    output: FrameRouteState,
    offers: &[FrameRouteOffer],
    resolver: &mut R,
    preset: PolicySet,
    pulse: Option<&dyn crate::Pulse>,
) -> Result<(usize, DecisionRecord), At<PolicyFailure>>;
Read defaults, imports and full contract ↗
Type definitionzencodec::operation::FrameRouteOffer
pub struct FrameRouteOffer {
    /// Qualified output continuity. Unsupported destination transitions must
    /// not appear in the choices, even when policy would select one.
    pub route: FrameRoute,
    /// Fidelity established by the prepared encoder configuration.
    pub fidelity: Option<Fidelity>,
    /// Effects and target of the retained conversion before this encoder.
    /// None means unqualified, not identity or sample preservation. Encoder
    /// fidelity alone never establishes end-to-end transcode fidelity.
    pub conversion: Option<FrameConversionOffer>,
    /// Actual retained destination evidence. None means unqualified; it does
    /// not imply forward-only, seekable, staged or safe encoder backpressure.
    pub destination: Option<DestinationFacts>,
    /// Actual compressed store; None means unqualified. Disk may block while
    /// raw encoder state is retained, independently of final writer access.
    pub staging: Option<StagingFacts>,
    /// Actual final-destination scheduling guarantee. None is unqualified.
    pub output_schedule: Option<super::OutputScheduleOffer>,
    /// Currently admitted encoded staging held by the operation. This excludes
    /// native input/reference/work buffers and is not a whole-operation bound.
    pub admitted_encoded_bytes: usize,
    /// Qualified additional codec/conversion workspace bound; unknown is not zero.
    pub workspace_upper_bound: Option<u64>,
}
Read defaults, imports and full contract ↗
Type definitionzencodec::operation::DestinationFacts
pub struct DestinationFacts {
    /// The retained writer supports overwriting earlier output positions.
    pub seek_back: bool,
    /// The prepared container makes bytes available before finish.
    pub streams: bool,
    /// Finishing this layout requires overwriting previously emitted bytes.
    pub patches_at_finish: bool,
    /// The retained muxer guarantees exact supplied packet timing or fails.
    /// False means no such guarantee, not permission to discard timing.
    pub exact_timing: bool,
}
Read defaults, imports and full contract ↗
Type definitionzencodec::operation::FrameConversionOffer
pub struct FrameConversionOffer {
    /// Interpretation and timing actually delivered to the encoder. Original
    /// input facts remain in the question; output epochs are independent.
    pub encoder_input: FrameFacts,
    /// True only for a qualified sample-preserving route; false includes
    /// arithmetic, quantization, clipping and unknown preservation.
    pub preserves_samples: bool,
    /// Whether conversion guarantees retention of extended-range values.
    /// This says nothing about the following encoder's precision or fidelity.
    pub preserves_extended_range: bool,
    /// Explicit fallback for undeclared siting. A declaration cannot be replaced.
    pub assumed_chroma_siting: Option<ChromaSiting>,
    /// Managed conversion storage retained/reserved for this selected route.
    /// Shared alternatives must not be summed. Native workspace is separate.
    pub admitted_bytes: usize,
}
Read defaults, imports and full contract ↗
Type definitionzencodec::operation::FrameRouteState
pub struct FrameRouteState {
    /// Last frame accepted by this output operation, if any.
    pub previous: Option<FrameFacts>,
    /// Segments already completed by the adapter, whether staged or published.
    pub completed_segments: u64,
    /// Whether any output representation/header/body has escaped private
    /// staging. Zero body bytes alone must not establish that this is false.
    pub output_committed: bool,
}
Read defaults, imports and full contract ↗

Still open. A new native witness converts changing BT.709/sRGB YCbCr inputs to one committed RGB8 AV1 encoder. Quantization and clipping are disclosed as non-preserving despite lossless encoding. A separate native retiming case shares the source picture, exposes original and requested timing to policy, and preserves variable packet durations; timestamp drift fails terminally. A retained exact MP4 muxer now preserves those native packet timestamps and durations under short writes, with independent file inspection; partial header failure terminates the encoder operation. The private adapter now retains the prepared destination before policy and initial encoding; unsupported destinations and policy rejection write no bytes. PreparedOutput and Output now expose actual retained writer/layout/timing facts; the route callback and selected report retain those facts. A public finite fixed-configuration StagedEncode now retains the encoder, converter and destination across policy. General segmented/live pipeline ownership, HDR, frame-rate conversion, in-band muxer reconfiguration, a general spool and live sequential-output bounds remain open. The finite witness drops native encoders before handing compressed output to the writer; native workspace and input owners remain outside payload admission. Fixed-work profiles show additional record-copy instructions; heap allocation counts stay flat with record count. The elapsed-time comparison remains exploratory because of heavy-process observations.

Measured diagnostic costs and limits ↗ · Coupled conversion and encoding ↗ · Equal-size transition refusals ↗ · Native qualification ↗ · Implementation and limits ↗ · Streaming effect boundary ↗

Establish sample alignment before moving auxiliary planesShared contract and JXL adapter implemented · public projection available · AuxiliaryGrid distinguishes unknown registration from emitted samples aligned with color. Equal dimensions and source dim_shift do not establish alignment.

Why this belongs in the API. Cropping a depth plane or mask along with color is correct only when their coordinates agree. A codec may reconstruct a subsampled source plane onto the color grid; its stored reduction factor then describes different samples.

Consumer
Keep requesting the intended result. The projection adapter returns a typed refusal when alignment is unknown, before admitting output storage; callers need no preparatory capability query.
Adapter
Declare ColorSamples only when each emitted auxiliary sample center matches color at the same row and column. Shared adoption checks dimensions and freezes this fact through retained owners, cloning and color replacement. Unknown is the default.
Implementor
The native JXL adapter declares the reconstructed output grid separately from original source facts. Odd-origin crops retain exact color and auxiliary values for all seven named roles at source shifts 1/2/3.
Reconstruct actual planes: retain source facts separately → Establish emitted registration: ColorSamples or Unknown → Project or refuse: no dimension-based guess → Keep the result: coordinates and owner stay togetherIMPLEMENTORReconstruct actual planesretain source facts separatelyADAPTEREstablish emitted registrationColorSamples or UnknownADAPTERProject or refuseno dimension-based guessCONSUMERKeep the resultcoordinates and owner stay together
implementorReconstruct actual planesretain source facts separately
adapterEstablish emitted registrationColorSamples or Unknown
adapterProject or refuseno dimension-based guess
consumerKeep the resultcoordinates and owner stay together
Reconstruct actual planes → Establish emitted registration → Project or refuse → Keep the result
Exact traits and signatures

Signatures from this revision · bodies omitted

Type definitionzencodec::image::AuxiliaryGrid
pub enum AuxiliaryGrid {
    /// Registration has not been established. Equal dimensions do not change it.
    #[default]
    Unknown,
    /// Each auxiliary sample has the same center as the color sample at the
    /// same row and column, throughout the selected stored region. Dimensions
    /// must match color. This describes emitted samples, including any native
    /// upsampling; it does not authorize changing their numerical values.
    ColorSamples,
}
Read defaults, imports and full contract ↗
Inherent methodszencodec::image::ValueSlice
impl<'a> ValueSlice<'a> {
    pub fn new(
        bytes: &'a [u8],
        width: u32,
        height: u32,
        stride: usize,
        channel: ChannelType,
    ) -> Result<Self, At<OutputError>>;

    pub fn with_grid(mut self, grid: AuxiliaryGrid) -> Self;

    pub fn grid(&self) -> AuxiliaryGrid;

    pub fn as_strided_bytes(&self) -> &'a [u8];
}
Read defaults, imports and full contract ↗
Functionzencodec_render::geometry::project
pub fn project(
    image: &ImageOutput,
    region: Option<Rect>,
    storage: &StorageBudget,
    pulse: Option<&dyn zencodec::Pulse>,
) -> Result<ImageOutput, Cause>;

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗

Still open. General grid origins, phases and resampling are not represented. The public renderer combines qualified canvas projection with required packed-color processing; native planar rendering, HDR display mapping, resizing and whole-operation workspace admission remain unqualified.

Native registration and crop qualification ↗ · Contract and reproduction ↗ · Packaged JXL adapter ↗ · Rendering follow-up ↗

Borrow complete planes without losing their interpretationShared borrowed converter implemented · policy and native ownership qualified · The existing resolver selects an actual conversion route before its destination is allocated. PreparedStorage retains the byte allowance; the source mapping and kernel selection remain unchanged through the callback.

Why this belongs in the API. A caller needs to see chroma assumptions, integer clipping and a large output reservation before paying for that output. Asking after conversion makes refusal too late; checking capability flags does not bind the actual route.

Consumer
Keep native decoding separate from display rendering. A subsampled stream with unknown siting needs an explicit decision before a centered kernel can be used. Packed access alone does not establish a rendered canvas.
Adapter
Honor stride, code bits and bit shift. Validate active words before writes; padding has no sample meaning. Keep coded coordinates through chroma upsampling and crop afterward so odd origins preserve phase. Use prepare_filled for destination/scratch, ask resolve_image_conversion, then allocate and execute the same route. Keep its decision record on failure. Adopt the actual result with from_frame_pixels; source_frame retains native interpretation while packed pixels carry current color. Exact timing/presentation and the destination charge survive publication.
Implementor
Return the checked native span tied to its mapping guard. No new allocation, sample conversion or storage owner is introduced by the accessor.
Map and validate: retain exact source planes → Reserve capacity: destination + row scratch → Choose or refuse: assumption, clipping, cost → Allocate and execute: same route, retained reportIMPLEMENTORMap and validateretain exact source planesADAPTERReserve capacitydestination + row scratchCONSUMERChoose or refuseassumption, clipping, costADAPTERAllocate and executesame route, retained report
implementorMap and validateretain exact source planes
adapterReserve capacitydestination + row scratch
consumerChoose or refuseassumption, clipping, cost
adapterAllocate and executesame route, retained report
Map and validate → Reserve capacity → Choose or refuse → Allocate and execute
Exact traits and signatures

Signatures from this revision · bodies omitted

Functionzencodec::operation::resolve_image_conversion
pub fn resolve_image_conversion<R: OperationPolicy + ?Sized>(
    image: &ImageOutput,
    offers: &[ImageConversionOffer],
    resolver: &mut R,
    preset: PolicySet,
    pulse: Option<&dyn crate::Pulse>,
) -> Result<(usize, DecisionRecord), At<PolicyFailure>>;
Read defaults, imports and full contract ↗
Type definitionzencodec::operation::ImageConversionOffer
pub struct ImageConversionOffer {
    /// Actual output pixel representation and compact color interpretation.
    /// Profiles/context must also satisfy the caller's configured target.
    pub output: PixelDescriptor,
    /// Stored output extent, before residual orientation.
    pub geometry: ImageGeometry,
    /// Residual orientation after this conversion; no inferred rotation.
    pub orientation: Orientation,
    /// A concrete chroma location assumed because the source did not declare
    /// one. None means no such assumption; an existing declaration is never
    /// overridden. Unknown is not a realizable assumed location.
    pub assumed_chroma_siting: Option<ChromaSiting>,
    /// True only when the route guarantees retention of extended-range values.
    /// False includes clipping, integer saturation and an unqualified guarantee;
    /// it is not a lossless-precision or HDR-rendering certificate.
    pub preserves_extended_range: bool,
    /// Managed destination and scratch capacity already reserved for this route.
    /// It need not be allocated or initialized until the callback accepts.
    /// Alternatives may share storage. Source/native workspace is excluded.
    pub admitted_bytes: usize,
    /// Qualified additional conversion workspace bound, if known. Unknown is
    /// not zero; this is not a whole-operation memory bound.
    pub workspace_upper_bound: Option<u64>,
}
Read defaults, imports and full contract ↗
Inherent methodszencodec::operation::PolicySet
impl PolicySet {
    pub const fn new() -> Self;

    pub const fn prefer_lossless(mut self, enabled: bool) -> Self;

    pub const fn require_metadata(mut self, treatment: MetadataTransfer) -> Self;

    pub const fn allow_opaque_profiles(mut self, allowed: bool) -> Self;

    pub const fn with_workspace_limit(mut self, bytes: u64) -> Self;

    pub const fn allow_assumed_chroma_siting(mut self, allowed: bool) -> Self;

    pub const fn require_extended_range(mut self, required: bool) -> Self;

    pub const fn require_output_schedule(mut self, schedule: super::OutputSchedule) -> Self;

    pub const fn with_output_wait_limit(mut self, bytes: u64) -> Self;
}

Selected methods · 6 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec::storage::StorageBudget
impl StorageBudget {
    pub fn new(max_bytes: usize, max_objects: usize) -> Self;

    pub fn prepare_filled<T: Copy>(
        &self,
        len: usize,
        value: T,
    ) -> Result<PreparedStorage<T>, StorageError>;

    pub fn reserved_bytes(&self) -> usize;

    pub fn reserved_objects(&self) -> usize;
}

Selected methods · 7 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec::storage::PreparedStorage<T>
impl<T: Copy> PreparedStorage<T> {
    pub fn allocate(self) -> Result<OwnedStorage<T>, StorageError>;
}
Read defaults, imports and full contract ↗
Inherent methodszencodec::frame::PlaneView
impl<'a> PlaneView<'a> {
    pub fn encoding(&self) -> SampleEncoding;

    pub fn stride_bytes(&self) -> usize;

    pub fn as_strided_samples(&self) -> PlaneSamples<'a>;

    pub fn row(&self, y: usize) -> Option<PlaneSamples<'a>>;

    pub fn code(&self, x: usize, y: usize) -> Option<u16>;
}

Selected methods · 3 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec::image::ImageOutput
impl ImageOutput {
    pub fn from_packed_owner<P: PackedImageOwner + 'static>(
        owner: P,
        auxiliaries: Vec<Auxiliary>,
        interpretation: ImageInterpretation,
    ) -> Result<Self, At<OutputError>>;

    pub fn from_frame_pixels<P: PackedImageOwner + 'static>(
        frame: Frame,
        owner: P,
        orientation: Orientation,
    ) -> Result<Self, At<OutputError>>;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
Shared borrowed conversion · pinned native dependencyCodePlane + YcbcrToRgbF32

Extracted from the exact native AV1 dependency. Enums are non-exhaustive. Construction retains native borrows; execution validates the requested region before writes and returns matching color context.

use zenpixels::{Cicp, PixelDescriptor, PixelSliceMut, sample::{CodePlane, CodePlaneError, SampleEncoding, ChromaSubsampling, ChromaSiting}};
use zenpixels_convert::ycbcr::{YcbcrToRgbF32, YcbcrError};
use whereat::At;

pub enum ChromaSubsampling {
    /// Equal horizontal and vertical resolution.
    Yuv444,
    /// Half horizontal resolution, rounded up at an odd edge.
    Yuv422,
    /// Half horizontal and vertical resolution, rounded up at odd edges.
    Yuv420,
}

pub enum ChromaSiting {
    /// The source did not establish a location.
    Unknown,
    /// Centered horizontally and vertically in the subsampled luma cell.
    Center,
    /// Horizontally co-sited, vertically centered.
    Left,
    /// Co-sited with the top-left luma sample.
    TopLeft,
}

impl<'a> CodePlane<'a> {
    pub fn from_u8(
        words: &'a [u8],
        width: usize,
        height: usize,
        stride_bytes: usize,
        encoding: SampleEncoding,
    ) -> Result<Self, CodePlaneError>;

    pub fn from_u16(
        words: &'a [u16],
        width: usize,
        height: usize,
        stride_bytes: usize,
        encoding: SampleEncoding,
    ) -> Result<Self, CodePlaneError>;

    pub const fn width(self) -> usize;

    pub const fn height(self) -> usize;

    pub const fn encoding(self) -> SampleEncoding;

    pub fn code(self, x: usize, y: usize) -> Option<u16>;
}

impl<'a> YcbcrToRgbF32<'a> {
    pub fn new(
        planes: [CodePlane<'a>; 3],
        sub: ChromaSubsampling,
        siting: ChromaSiting,
        color: Cicp,
    ) -> Result<Self, At<YcbcrError>>;

    pub const fn output_descriptor(&self) -> PixelDescriptor;

    pub fn convert_rows<'b>(
        &self,
        x: usize,
        y: usize,
        mut destination: PixelSliceMut<'b>,
    ) -> Result<PixelSliceMut<'b>, At<YcbcrError>>;
}

pub enum YcbcrError {
    /// Primaries, transfer or matrix is outside the implemented route.
    UnsupportedColor { actual: Cicp },
    /// Bit depth or per-plane sample encodings disagree or are unsupported.
    UnsupportedEncoding {
        plane: usize,
        actual: SampleEncoding,
    },
    /// Chroma sampling/location has no qualified kernel. Unknown is not Center.
    UnsupportedChroma {
        subsampling: ChromaSubsampling,
        siting: ChromaSiting,
    },
    /// Source planes do not describe a nonempty full coded image.
    SourceGeometry {
        plane: usize,
        actual: (usize, usize),
        expected: Option<(usize, usize)>,
    },
    /// Requested output rows/columns extend beyond the complete source image.
    OutputGeometry {
        origin: (usize, usize),
        extent: (usize, usize),
        source: (usize, usize),
    },
    /// Destination does not have the exact promised output descriptor.
    OutputDescriptor { actual: PixelDescriptor },
}
zenpixels/src/sample/plane.rs ↗ zenpixels-convert/src/ycbcr.rs ↗

Still open. The private AV1 route checks 18 depth/subsampling/range combinations, 54 presentations and 162 crop cases. Default policy refuses undeclared subsampled siting. Explicit center permission still refuses signaled Left/TopLeft; range-preservation requirements refuse the clipping integer route. FFmpeg verifies component samples. The pinned public CodePlane and YcbcrToRgbF32 replace the private kernel and produce byte-identical output, passing the unchanged zimg gate. The actual output also passes through OutputPlan. F32 remains sRGB-encoded until that processing stage. Other matrices/siting, integer/mono/GBR output, sample-type unification, HDR, coupled encoder transitions and native workspace bounds remain open. Native preparation scans all active source words.

Shared converter qualification ↗ · Unclipped float qualification ↗ · Independent RGB observations ↗ · Prepared conversion qualification ↗ · Converted output qualification ↗ · Native bridge receipt ↗ · Private implementation and limits ↗ · Canvas rendering gap ↗

Require the pixel interpretation, including alpha associationImplemented on review PR · native premultiplied JXL · OutputPlan::new_with_descriptor accepts the complete required PixelDescriptor. A native premultiplied JXL now retains its samples through both borrowed identity and admitted output rows.

Why this belongs in the API. An editor keeps premultiplied working pixels. Choosing the same ICC profile with only a PixelFormat requests straight alpha, which can introduce unassociation or require a CMS. Profile choice and alpha association are separate requirements.

Consumer
Pass the required descriptor. When samples must stay exact, use OutputPlan::new_preserving_samples with the current profile: it refuses color arithmetic and never consults a CMS. For requested color conversion, use new_with_descriptor and an explicit conversion policy.
Adapter
The executed helper below requires exact samples and selects SameAsOrigin. It needs no clipping option or CMS parameter. Borrow only when the plan accepts the exact source interpretation. Retain the complete ImageOutput only when current signaling also matches; otherwise write to admitted rows and compose the returned color owner.
Implementor
Validate the required descriptor against the resolved profile before constructing a transform. Preserve existing alpha-removal, range and source-binding checks. The existing format-only constructor keeps its straight-alpha behavior.
Require interpretation: profile + full descriptor → Validate and prepare: borrow identity or admit rows → Execute the same plan: exact context or traced refusalCONSUMERRequire interpretationprofile + full descriptorADAPTERValidate and prepareborrow identity or admit rowsIMPLEMENTORExecute the same planexact context or traced refusal
consumerRequire interpretationprofile + full descriptor
adapterValidate and prepareborrow identity or admit rows
implementorExecute the same planexact context or traced refusal
Require interpretation → Validate and prepare → Execute the same plan
Private prototype · Rust
Source ↗
MoreNative tests ↗
Imports & setup9 lines
use zencodec::At;
use zenpixels::{ColorOrigin, PixelDescriptor, PixelSlice};
use zenpixels_convert::{ConvertError, ConvertOptions, OutputPlan, OutputProfile};

fn plan_required_layout(
    source: &PixelSlice<'_>,
    origin: &ColorOrigin,
    required: PixelDescriptor,
) -> Result<OutputPlan, At<ConvertError>> {
    OutputPlan::new_preserving_samples(
        source.descriptor(),
        source.color_context().cloned(),
        origin,
        OutputProfile::SameAsOrigin,
        required,
    )
Return & continuation1 lines
}
Exact traits and signatures

Signatures from this revision · bodies omitted

Inherent methodszencodec::image::ImageOutput
impl ImageOutput {
    pub fn replace_packed_color<P: PackedImageOwner + 'static>(
        self,
        owner: P,
    ) -> Result<Self, At<OutputError>>;
}
Read defaults, imports and full contract ↗
Trait methodszencodec::image::MappedImage
pub trait MappedImage {
    fn plane_count(&self) -> usize;

    fn plane(&self, index: usize) -> Result<Option<Plane<'_>>, At<OutputError>>;
}

Required methods: plane_count, plane. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Inherent methods · pinned conversion dependencyzenpixels_convert::OutputPlan

Extracted from f23ac753, matching the compiled native tests. Imports use caller paths; method bodies are omitted. Descriptor/profile conflicts return ConvertError::OutputInterpretationMismatch { requested, profile } before CMS construction. new_preserving_samples instead requires matching profiles and a reversible representation change; it never consults a CMS. retain_channels controls channel/depth restrictions and clipping defaults without claiming exactness. It replaces the removed forbid_lossy spelling. Conversion options source.

use alloc::sync::Arc;
use zenpixels::{ColorContext, ColorOrigin, PixelDescriptor, PixelFormat, PixelSlice, PixelSliceMut};
use zenpixels_convert::{ConvertError, ConvertOptions, OutputPlan, output::{OutputMetadata, OutputProfile}};
use whereat::At;

impl OutputPlan {
    pub fn new(
        source_desc: PixelDescriptor,
        current: Option<Arc<ColorContext>>,
        origin: &ColorOrigin,
        target: OutputProfile,
        pixel_format: PixelFormat,
        options: &ConvertOptions,
        cms: Option<&dyn zenpixels_convert::cms::PluggableCms>,
    ) -> Result<Self, At<ConvertError>>;

    pub fn new_with_descriptor(
        source_desc: PixelDescriptor,
        current: Option<Arc<ColorContext>>,
        origin: &ColorOrigin,
        target: OutputProfile,
        output: PixelDescriptor,
        options: &ConvertOptions,
        cms: Option<&dyn zenpixels_convert::cms::PluggableCms>,
    ) -> Result<Self, At<ConvertError>>;

    pub fn new_preserving_samples(
        source_desc: PixelDescriptor,
        current: Option<Arc<ColorContext>>,
        origin: &ColorOrigin,
        target: OutputProfile,
        output: PixelDescriptor,
    ) -> Result<Self, At<ConvertError>>;

    pub fn output_descriptor(&self) -> PixelDescriptor;

    pub fn metadata(&self) -> &OutputMetadata;

    pub fn prepare(&mut self, max_width: u32) -> Result<(), At<ConvertError>>;

    pub fn borrow_rows<'a>(
        &self,
        source: PixelSlice<'a>,
    ) -> Result<Option<PixelSlice<'a>>, At<ConvertError>>;

    pub fn convert_rows<'a>(
        &mut self,
        source: PixelSlice<'_>,
        mut destination: PixelSliceMut<'a>,
    ) -> Result<PixelSliceMut<'a>, At<ConvertError>>;
}

impl ConvertOptions {
    pub const fn retain_channels() -> Self;

    pub const fn with_clip_out_of_gamut(mut self, clip: bool) -> Self;
}
Read defaults and the full conversion contract ↗

Still open. This does not make arbitrary ICC premultiplication, narrow/full conversion or CMYK math available. Unsupported transforms still refuse. Auxiliary owners may retain old color payloads. CMS and native workspace bounds remain separate.

Exact output qualification ↗ · Native descriptor qualification ↗ · Conversion contract and regression ↗ · Conversion implementation ↗

Make float color and clipping match the requestNative conversion and retained-auxiliary publication qualified · A real JXL image and five native layers now convert to the selected Display P3 ICC through OutputPlan and admitted destination rows. Independent Little CMS comparison keeps a fixed 0.0001 color tolerance and exact alpha.

Why this belongs in the API. A viewer needs display color, while an editor may retain negative and above-one values for later compositing. Float storage alone did not preserve them: the old backend route clipped values and quantized dark colors. Merely attaching the target ICC would not correct the samples.

Consumer
Choose the required target profile and range behavior. retain_channels controls channel/depth restrictions, and this conversion explicitly disables clipping. Exact carriage instead uses new_preserving_samples. There is no new capability query or separate float-conversion API.
Adapter
Prepare OutputPlan from current sample interpretation, admit destination rows, then execute that same plan. replace_packed_color composes matching output context with the original auxiliary-plane mapping guards. Run the existing resolver before publication; ReturnHeld describes this actual converted image. Errors discard unpublished rows.
Implementor
Use float arithmetic and available analytic curves; apply requested clipping to color without changing alpha. Explicit ICC remains authoritative over embedded CICP. Unqualified partially shared channel curves refuse before writes. JXL now implements retain_auxiliaries over its separate native allocations, so replacing color can release the old raster after original clones drop.
Choose target and range: explicit preservation or clipping → Prepare actual conversion: current samples + selected ICC → Convert or refuse: qualified math + original cause → Compose and resolve: converted color + retained auxiliariesCONSUMERChoose target and rangeexplicit preservation or clippingADAPTERPrepare actual conversioncurrent samples + selected ICCIMPLEMENTORConvert or refusequalified math + original causeADAPTERCompose and resolveconverted color + retained auxiliaries
consumerChoose target and rangeexplicit preservation or clipping
adapterPrepare actual conversioncurrent samples + selected ICC
implementorConvert or refusequalified math + original cause
adapterCompose and resolveconverted color + retained auxiliaries
Choose target and range → Prepare actual conversion → Convert or refuse → Compose and resolve
Private prototype · Rust
Source ↗
MoreNative tests ↗
Imports & setup12 lines
use std::sync::Arc;
use zencodec::At;
use zenpixels::PixelSlice;
use zenpixels_convert::{
    ColorOrigin, ConvertError, ConvertOptions, OutputPlan, OutputProfile, cms_moxcms::MoxCms,
};

pub(super) fn plan_float_export(
    source: PixelSlice<'_>,
    origin: &ColorOrigin,
    target_icc: Arc<[u8]>,
) -> Result<OutputPlan, At<ConvertError>> {
    let options = ConvertOptions::retain_channels().with_clip_out_of_gamut(false);
    let plan = OutputPlan::new(
        source.descriptor(),
        source.color_context().cloned(),
        origin,
        OutputProfile::Icc(target_icc),
        source.descriptor().pixel_format(),
        &options,
        Some(&MoxCms),
    )?;
Return & continuation5 lines
    // The adapter prepares row capacity, admits destination storage, and
    // executes this same plan. Its metadata describes successful output.
    // This color stage does not publish a new auxiliary-plane owner.
    Ok(plan)
}
Exact traits and signatures

Signatures from this revision · bodies omitted

Trait methodscandidate::plan_float_export (private native color-stage prototype)
mod planning {
    pub(super) fn plan_float_export(
        source: PixelSlice<'_>,
        origin: &ColorOrigin,
        target_icc: Arc<[u8]>,
    ) -> Result<OutputPlan, At<ConvertError>>;
}
Read defaults, imports and full contract ↗
Inherent methodszencodec::image::ImageOutput
impl ImageOutput {
    pub fn replace_packed_color<P: PackedImageOwner + 'static>(
        self,
        owner: P,
    ) -> Result<Self, At<OutputError>>;
}
Read defaults, imports and full contract ↗
Trait methodszencodec::image::PackedImageOwner
pub trait PackedImageOwner: Send + Sync {
    fn pixels(&self) -> Result<PixelSlice<'_>, At<OutputError>>;

    fn auxiliary(&self, _index: usize) -> Result<Option<ValueSlice<'_>>, At<OutputError>>;

    fn retain_auxiliaries(&self) -> Result<Option<Arc<dyn AuxiliaryImageOwner>>, At<OutputError>>;
}

Required methods: pixels. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Trait methodszencodec::image::AuxiliaryImageOwner
pub trait AuxiliaryImageOwner: Send + Sync {
    fn auxiliary(&self, index: usize) -> Result<Option<ValueSlice<'_>>, At<OutputError>>;
}

Required methods: auxiliary. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Inherent methodszencodec::storage::StorageBudget
impl StorageBudget {
    pub fn new(max_bytes: usize, max_objects: usize) -> Self;

    pub fn prepare_filled<T: Copy>(
        &self,
        len: usize,
        value: T,
    ) -> Result<PreparedStorage<T>, StorageError>;

    pub fn reserved_bytes(&self) -> usize;

    pub fn reserved_objects(&self) -> usize;
}

Selected methods · 7 more in the full contract.

Read defaults, imports and full contract ↗
Functionzencodec::find_cause
pub fn find_cause<'a, T: core::error::Error + 'static>(
    mut err: &'a (dyn core::error::Error + 'static),
) -> Option<&'a T>;

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methods · pinned conversion dependencyzenpixels_convert::OutputPlan

Extracted from f23ac753, matching the compiled native tests. Imports use caller paths; method bodies are omitted. Descriptor/profile conflicts return ConvertError::OutputInterpretationMismatch { requested, profile } before CMS construction. new_preserving_samples instead requires matching profiles and a reversible representation change; it never consults a CMS. retain_channels controls channel/depth restrictions and clipping defaults without claiming exactness. It replaces the removed forbid_lossy spelling. Conversion options source.

use alloc::sync::Arc;
use zenpixels::{ColorContext, ColorOrigin, PixelDescriptor, PixelFormat, PixelSlice, PixelSliceMut};
use zenpixels_convert::{ConvertError, ConvertOptions, OutputPlan, output::{OutputMetadata, OutputProfile}};
use whereat::At;

impl OutputPlan {
    pub fn new(
        source_desc: PixelDescriptor,
        current: Option<Arc<ColorContext>>,
        origin: &ColorOrigin,
        target: OutputProfile,
        pixel_format: PixelFormat,
        options: &ConvertOptions,
        cms: Option<&dyn zenpixels_convert::cms::PluggableCms>,
    ) -> Result<Self, At<ConvertError>>;

    pub fn new_with_descriptor(
        source_desc: PixelDescriptor,
        current: Option<Arc<ColorContext>>,
        origin: &ColorOrigin,
        target: OutputProfile,
        output: PixelDescriptor,
        options: &ConvertOptions,
        cms: Option<&dyn zenpixels_convert::cms::PluggableCms>,
    ) -> Result<Self, At<ConvertError>>;

    pub fn new_preserving_samples(
        source_desc: PixelDescriptor,
        current: Option<Arc<ColorContext>>,
        origin: &ColorOrigin,
        target: OutputProfile,
        output: PixelDescriptor,
    ) -> Result<Self, At<ConvertError>>;

    pub fn output_descriptor(&self) -> PixelDescriptor;

    pub fn metadata(&self) -> &OutputMetadata;

    pub fn prepare(&mut self, max_width: u32) -> Result<(), At<ConvertError>>;

    pub fn borrow_rows<'a>(
        &self,
        source: PixelSlice<'a>,
    ) -> Result<Option<PixelSlice<'a>>, At<ConvertError>>;

    pub fn convert_rows<'a>(
        &mut self,
        source: PixelSlice<'_>,
        mut destination: PixelSliceMut<'a>,
    ) -> Result<PixelSliceMut<'a>, At<ConvertError>>;
}

impl ConvertOptions {
    pub const fn retain_channels() -> Self;

    pub const fn with_clip_out_of_gamut(mut self, clip: bool) -> Self;
}
Read defaults and the full conversion contract ↗

Still open. The largest tested layer now retains 9,455,616 source bytes instead of 47,222,784; destination payload remains 37,785,600 bytes. These are observed payload charges, not RSS. Source and destination coexist during conversion; inseparable backends retain original color. F32 CMYK arithmetic, general ICC/LUT behavior, CMS workspace and preset naming still need work. No speed claim is made.

Separate auxiliary ownership and measured retention ↗ · Initial converted owner evidence ↗ · Independent float comparison ↗ · Native scope and reproduction ↗ · Conversion implementation ↗ · Output ownership and policy follow-up ↗

Put output admission on the operation requestImplemented on review PR · native JXL and TIFF refusal · The viewer supplies one output pool through OpenRequest. CodecSet passes resolved requirements to one native opening method.

Why this belongs in the API. A shared registry can serve several viewers or tenants with different retained-output allowances. Keeping the pool on JXL configuration forces codec-specific setup and creates competing defaults. The request is the point where the application knows which allowance applies.

Consumer
Add with_output_storage(pool.clone()) to the opening request. Keep using ordinary selection and ImageOutput. Requests can share a pool or use independent pools through one immutable registry. No capability query is required.
Adapter
Implement CollectionFactory::open_with(source, CollectionOpenContext). The context carries format, view, inherited limits, decode policy, shared cancellation and output storage. Honor every applicable requirement or return its typed refusal before affected work.
Implementor
JXL admits color and auxiliary payloads before native writes and keeps the same owner through publication. The private TIFF adapter returns UnsupportedOutputStorage before querying the source. A payload pool does not satisfy max_memory_bytes.
Require an output pool: one opening request → Resolve the operation context: limits + policy + cancellation → Admit or refuse: before native output writes → Keep the image: last owner keeps the chargeCONSUMERRequire an output poolone opening requestADAPTERResolve the operation contextlimits + policy + cancellationIMPLEMENTORAdmit or refusebefore native output writesCONSUMERKeep the imagelast owner keeps the charge
consumerRequire an output poolone opening request
adapterResolve the operation contextlimits + policy + cancellation
implementorAdmit or refusebefore native output writes
consumerKeep the imagelast owner keeps the charge
Require an output pool → Resolve the operation context → Admit or refuse → Keep the image
Implemented · review PRs · Rust
Source ↗
MoreNative tests ↗
Imports & setup18 lines
use zencodec::{
    At, CodecSet, ImageFormat, SharedPulse,
    collection::{Composition, Discovery, ErasedCollection, OpenRequest},
    operation::{CheckedCollection, CollectionOperationError, PolicySet},
    source::DynReadAt,
    storage::StorageBudget,
};

pub fn open_admitted<'a>(
    codecs: &CodecSet,
    source: &'a mut dyn DynReadAt,
    view: Composition,
    discovery_attempts: u64,
    storage: &StorageBudget,
) -> Result<
    CheckedCollection<Discovery<ErasedCollection<'a>>, PolicySet>,
    At<CollectionOperationError>,
> {
    let request =
        OpenRequest::new(view, discovery_attempts).with_output_storage(storage.clone());
    let opened = codecs.open_collection(
        ImageFormat::Jxl,
        source,
        request,
        PolicySet::new(),
        SharedPulse::default(),
    );
Return & continuation5 lines
    // Ordinary selection methods now retain output charges automatically.
    // Unsupported adapters return a typed opening refusal. Native workspace
    // remains outside this allowance, even when reconstruction succeeds.
    opened
}
Exact traits and signatures

Signatures from this revision · bodies omitted

Inherent methodszencodec::collection::OpenRequest
impl OpenRequest {
    pub const fn new(view: Composition, discovery_attempts: u64) -> Self;

    pub fn with_output_storage(mut self, storage: StorageBudget) -> Self;
}
Read defaults, imports and full contract ↗
Type definitionzencodec::collection::CollectionOpenContext
#[non_exhaustive]
pub struct CollectionOpenContext {
    /// The matched registration, without another format-detection pass.
    pub format: ImageFormat,
    /// Fixed view for all identities issued by this opening.
    pub view: Composition,
    /// Inherited resource requirements; `None` keeps the native template.
    pub limits: Option<ResourceLimits>,
    /// Inherited decode requirements; `None` keeps the native template.
    pub policy: Option<DecodePolicy>,
    /// Shared latched cancellation, retained throughout the native operation.
    pub pulse: SharedPulse,
    /// Required pool for retained output payloads, including row padding.
    ///
    /// Admission must precede native output allocation. Published owners and
    /// their clones retain the charge until the last owner drops. This does not
    /// bound native workspace, caller input, metadata or explicit caller copies.
    /// `None` keeps any native template's output-storage configuration.
    pub output_storage: Option<StorageBudget>,
}
Read defaults, imports and full contract ↗
Trait methodszencodec::collection::CollectionFactory
pub trait CollectionFactory: Send + Sync {
    fn formats(&self) -> &[ImageFormat];

    fn open_with<'a>(
        &self,
        source: &'a mut dyn DynReadAt,
        context: CollectionOpenContext,
    ) -> Result<ErasedCollection<'a>, Cause>;
}

Required methods: formats, open_with. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Type definitionzencodec::collection::CollectionOpenError
#[non_exhaustive]
pub enum CollectionOpenError {
    /// No factory serves this format. Still-image decoders are registered separately.
    NotRegistered(ImageFormat),
    /// This adapter needs an already-resident slice; it did not stage the source.
    ResidentInputRequired,
    /// The adapter cannot enforce the supplied resource-limit configuration.
    UnsupportedLimits {
        /// Required fields the adapter cannot enforce. Qualified fields and
        /// fields that do not apply to decoding have their no-op/default values.
        /// This bounded structured data contains no source or metadata payload.
        unsupported: ResourceLimits,
    },
    /// The adapter cannot enforce the supplied decode-policy configuration.
    UnsupportedDecodePolicy {
        /// Explicit requirements the adapter cannot enforce. Handled fields
        /// are `None`; this is not permission to retry with weaker requirements.
        unsupported: DecodePolicy,
    },
    /// The adapter cannot admit returned output payloads to the required pool.
    UnsupportedOutputStorage,
}
Read defaults, imports and full contract ↗
Inherent methodszencodec::storage::StorageBudget
impl StorageBudget {
    pub fn new(max_bytes: usize, max_objects: usize) -> Self;

    pub fn prepare_filled<T: Copy>(
        &self,
        len: usize,
        value: T,
    ) -> Result<PreparedStorage<T>, StorageError>;

    pub fn reserved_bytes(&self) -> usize;

    pub fn reserved_objects(&self) -> usize;
}

Selected methods · 7 more in the full contract.

Read defaults, imports and full contract ↗

Still open. The complete 147-presentation and nine-layer matrix keeps exact native samples and independent-reader gates. Native workspace, metadata, discovery storage and encoded staging still need their own admission. This does not establish that pausing encoder output reduces retained raster memory.

Request-level native matrix ↗ · Opening and ownership contract ↗ · Storage scope ↗

Keep the output charge with the last image ownerShared contract implemented · packaged JXL validation · Native reconstruction can write directly into admitted packed storage. ImageOutput retains that owner, its color facts and auxiliary values without copying samples.

Why this belongs in the API. A decoder can finish while a viewer or publication callback still holds its image. A fresh decoder budget does not account for those retained outputs. Turning admitted storage into an ordinary Vec would lose the charge or require a copy.

Consumer
Keep using ImageOutput, with_pixels and map. Clones share the allocation charge until the last image drops. An allocation refusal is a runtime result; it does not authorize a retry with weaker requirements.
Adapter
Implement PackedImageOwner over the admitted storage and call from_packed_owner with frozen roles and interpretation. ValueSlice borrows strided auxiliary values. JXL consumes the shared OpenRequest output pool; cloned requests and retained results share its charge. Construction and later mappings check layout and color agreement.
Implementor
Admit the output buffers before native reconstruction writes them. Preserve their immutable samples and original error causes. Native workspace needs its own enforced allowance; publication policy still runs on the held result.
Admit output storage: shared byte/object allowance → Reconstruct into it: preserve native samples → Resolve publication: same held owner → Retain or drop: charge follows last cloneADAPTERAdmit output storageshared byte/object allowanceIMPLEMENTORReconstruct into itpreserve native samplesADAPTERResolve publicationsame held ownerCONSUMERRetain or dropcharge follows last clone
adapterAdmit output storageshared byte/object allowance
implementorReconstruct into itpreserve native samples
adapterResolve publicationsame held owner
consumerRetain or dropcharge follows last clone
Admit output storage → Reconstruct into it → Resolve publication → Retain or drop
Exact traits and signatures

Signatures from this revision · bodies omitted

Inherent methodszencodec::storage::StorageBudget
impl StorageBudget {
    pub fn new(max_bytes: usize, max_objects: usize) -> Self;

    pub fn prepare_filled<T: Copy>(
        &self,
        len: usize,
        value: T,
    ) -> Result<PreparedStorage<T>, StorageError>;

    pub fn reserved_bytes(&self) -> usize;

    pub fn reserved_objects(&self) -> usize;
}

Selected methods · 7 more in the full contract.

Read defaults, imports and full contract ↗
Trait methodszencodec::image::PackedImageOwner
pub trait PackedImageOwner: Send + Sync {
    fn pixels(&self) -> Result<PixelSlice<'_>, At<OutputError>>;

    fn auxiliary(&self, _index: usize) -> Result<Option<ValueSlice<'_>>, At<OutputError>>;

    fn retain_auxiliaries(&self) -> Result<Option<Arc<dyn AuxiliaryImageOwner>>, At<OutputError>>;
}

Required methods: pixels. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Inherent methodszencodec::image::ImageOutput
impl ImageOutput {
    pub fn from_packed_owner<P: PackedImageOwner + 'static>(
        owner: P,
        auxiliaries: Vec<Auxiliary>,
        interpretation: ImageInterpretation,
    ) -> Result<Self, At<OutputError>>;

    pub fn from_frame_pixels<P: PackedImageOwner + 'static>(
        frame: Frame,
        owner: P,
        orientation: Orientation,
    ) -> Result<Self, At<OutputError>>;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec::image::ValueSlice
impl<'a> ValueSlice<'a> {
    pub fn new(
        bytes: &'a [u8],
        width: u32,
        height: u32,
        stride: usize,
        channel: ChannelType,
    ) -> Result<Self, At<OutputError>>;

    pub fn with_grid(mut self, grid: AuxiliaryGrid) -> Self;

    pub fn grid(&self) -> AuxiliaryGrid;

    pub fn as_strided_bytes(&self) -> &'a [u8];
}
Read defaults, imports and full contract ↗
Trait methodszencodec::image::MappedImage
pub trait MappedImage {
    fn plane_count(&self) -> usize;

    fn plane(&self, index: usize) -> Result<Option<Plane<'_>>, At<OutputError>>;
}

Required methods: plane_count, plane. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗

Still open. The owned finalizer still requires two copies outside the pool. The existing OutputPlan now borrows native color and writes into admitted storage across all nine raw layers; late CMS error or unwind releases unpublished output. Checked identity borrowing retains all original planes and charges when current signaling also matches. Adding opaque alpha under an unchanged ICC now preserves exact native RGB bits without a CMS. CMYK-to-RGB still refuses with NeedsCms. Converted auxiliary-plane publication is now qualified above, including separate JXL auxiliary retention. CMS scratch, native workspace, input, metadata and encoded staging remain outside this allowance.

Packaged ordinary/admitted matrix ↗ · Native scope and controls ↗ · Current signatures and ownership ↗ · Profile-aware finalization controls ↗ · Admitted OutputPlan color stage ↗ · Retained identity planes ↗ · Native alpha-addition regression ↗ · Exact OutputPlan interface ↗

Still images and video share the native mapping contractImplemented · review PR · Frame owners and borrowed component-plane mappings now live in zencodec::frame. Existing media imports name the same types.

Why this belongs in the API. A selected still image can expose native component planes just like a video frame. Its output must retain the backend owner without depending on video sessions or copying pixels to cross an API boundary.

Consumer
Keep the returned frame owner and borrow its mapped rows. Retain significant code depth, bit placement, crop and timing; converting ten-bit component codes to display RGB is a separate checked operation.
Adapter
Use the shared Frame, Picture and MappedPlanes types. Native TIFF/JXL selected output and AV1 exercise the same implementation; media import paths remain compatible.
Implementor
Implement Picture::map with a guard that keeps native storage borrowed. Core mapping has no dependency on media sessions or I/O drivers. Accounted software allocations remain in media.
Retain native owner: storage survives decoder → Map through core: borrow the backend guard → Read native rows: retain interpretationIMPLEMENTORRetain native ownerstorage survives decoderADAPTERMap through coreborrow the backend guardCONSUMERRead native rowsretain interpretation
implementorRetain native ownerstorage survives decoder
adapterMap through coreborrow the backend guard
consumerRead native rowsretain interpretation
Retain native owner → Map through core → Read native rows
Exact traits and signatures

Signatures from this revision · bodies omitted

Trait methodszencodec::frame::Picture
pub trait Picture: Send + Sync {
    fn map(&self) -> Result<Box<dyn MappedPlanes + '_>, MapError>;
}

Required methods: map. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Trait methodszencodec::frame::MappedPlanes
pub trait MappedPlanes {
    fn plane_count(&self) -> usize;

    fn plane(&self, index: usize) -> Option<PlaneView<'_>>;
}

Required methods: plane_count, plane. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗

Still open. The mapped ImageOutput is now implemented in core and checked by the same native consumers. The shared collection is now implemented on the API review PR. JXL has a concrete factory in its codec review PR; TIFF remains in the native harness. Publication policy, color conversion and workspace bounds remain separate gates.

Shared ownership contract ↗ · Native output qualification ↗

Image identity, containment and composition stay distinctShared contract implemented · JXL codec package in review · Native TIFF tests select nested and shared SubIFDs. Native JXL tests select 147 presentations, including 35 with visible dependency replay. A separate raw-layer adapter now selects nine qualified local layers through the same checked consumer call.

Why this belongs in the API. A SubIFD is not always a reduced image, and a shared child makes a graph rather than a tree. A JXL file can expose one composited presentation or five layers, so a page index cannot describe every selection.

Consumer
Configure the resolver once, choose the view before discovery, then request full pixels or a stored region for a retained image identity. CheckedCollection runs publication policy automatically on every decode. TIFF returns the selected canvas and region origin. The operation owns the source; the identity is not a portable index or timestamp.
Adapter
Keep IDs scoped to the opened operation and bound discovery. TIFF/JXL return one mapped ImageOutput; native AV1 uses the same output without copying planes. Packed color uses zenpixels. JXL retains all seven named auxiliary roles, source integer/float precision, spot/CFA parameters and selected timecodes. Output storage precision and presentation timestamps remain separate facts. Retained spot planes disclose whether they already contributed to output color. JXL exposes JxlCollectionConfig::new(max_images, max_frame_headers): retained identities and hidden header work have separate allowances.
Implementor
Expose actual directory relations and presentation dependencies separately. CheckedCollection freezes the opened view into ImageInterpretation::composition before policy. A changed adapter view or conflicting owner claim returns OutputError::Composition with expected/actual facts. Existing clones and pixel storage stay unchanged. Do not label arbitrary SubIFDs as reduced levels or substitute a flattened canvas for a layer.
Choose the view: presentation or layers → Bind the operation: source + scoped identities → Discover actual items: containment + dependencies → Check the held output: policy accepts or refusesCONSUMERChoose the viewpresentation or layersADAPTERBind the operationsource + scoped identitiesIMPLEMENTORDiscover actual itemscontainment + dependenciesADAPTERCheck the held outputpolicy accepts or refuses
consumerChoose the viewpresentation or layers
adapterBind the operationsource + scoped identities
implementorDiscover actual itemscontainment + dependencies
adapterCheck the held outputpolicy accepts or refuses
Choose the view → Bind the operation → Discover actual items → Check the held output
Private prototype · Rust
Source ↗
MoreNative tests ↗
Imports & setup20 lines
use crate::jxl_items::{JxlItems, Limits};
use zencodec::{
    At, SharedPulse,
    collection::{Composition, ImageRequest},
    image::ImageOutput,
    operation::{CheckedCollection, CollectionOperationError, Outcome, OperationPolicy},
};

// Native factory setup stays in the adapter. Limits are application input:
// images bounds selected-item storage; frame_headers counts hidden work.
// The consumer uses the same checked operation for later selections.
pub fn checked_jxl_first<R: OperationPolicy>(
    bytes: &[u8],
    limits: Limits,
    resolver: R,
    stop: SharedPulse,
) -> Result<Option<Outcome<ImageOutput>>, At<CollectionOperationError>> {
    let mut images = CheckedCollection::open(stop, resolver, |stop| {
        JxlItems::open_with_stop(bytes, Composition::Composited, limits, stop)
    })?;
    let image = images.first_image(&ImageRequest::default())?;
Return & continuation4 lines
    // The result owns its samples after this opened collection is dropped.
    // Policy ran before returning it; no separate authorization call follows.
    Ok(image)
}
Exact traits and signatures

Signatures from this revision · bodies omitted

Inherent methodszencodec::operation::CheckedCollection<C, R>
impl<C: Collection, R: OperationPolicy> CheckedCollection<C, R> {
    pub fn open(
        pulse: SharedPulse,
        resolver: R,
        open: impl FnOnce(SharedPulse) -> Result<C, Cause>,
    ) -> Result<Self, At<CollectionOperationError>>;

    pub fn with_policy_set(mut self, preset: PolicySet) -> Self;

    pub fn images(&mut self) -> Result<C::Cursor, At<CollectionOperationError>>;

    pub fn children(&mut self, image: &ImageId) -> Result<C::Cursor, At<CollectionOperationError>>;

    pub fn next(
        &mut self,
        cursor: &mut C::Cursor,
    ) -> Result<Option<ImageId>, At<CollectionOperationError>>;

    pub fn describe(
        &mut self,
        image: &ImageId,
    ) -> Result<ImageDescription, At<CollectionOperationError>>;

    pub fn first_image(
        &mut self,
        request: &ImageRequest,
    ) -> Result<Option<Outcome<ImageOutput>>, At<CollectionOperationError>>;

    pub fn decode(
        &mut self,
        image: &ImageId,
        request: &ImageRequest,
    ) -> Result<Outcome<ImageOutput>, At<CollectionOperationError>>;

    pub fn decode_with<E: Into<Cause>>(
        &mut self,
        image: &ImageId,
        request: &ImageRequest,
        transform: impl FnOnce(ImageOutput, &dyn crate::Pulse) -> Result<ImageOutput, E>,
    ) -> Result<Outcome<ImageOutput>, At<CollectionOperationError>>;

    pub fn report(&self) -> &OperationReport;

    pub fn is_finished(&self) -> bool;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
Trait methodszencodec::collection::Collection
pub trait Collection {
    type Cursor;

    fn view(&self) -> Composition;

    fn images(&mut self) -> Result<Self::Cursor, At<CollectionError>>;

    fn children(&mut self, image: &ImageId) -> Result<Self::Cursor, At<CollectionError>>;

    fn next(&mut self, cursor: &mut Self::Cursor) -> Result<Option<ImageId>, At<CollectionError>>;

    fn describe(&mut self, image: &ImageId) -> Result<ImageDescription, At<CollectionError>>;

    fn decode(
        &mut self,
        image: &ImageId,
        request: &ImageRequest,
    ) -> Result<ImageOutput, At<CollectionError>>;
}

Required methods: view, images, children, next, describe, decode. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Inherent methodszencodec::collection::Discovery<C>
impl<C> Discovery<C> {
    pub fn new(collection: C, attempts: u64) -> Self;

    pub fn usage(&self) -> DiscoveryUsage;
}

Selected methods · 2 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodscandidate::HttpSource (private HTTP qualification)
impl HttpSource {
    pub fn open(url: String, max_range: usize) -> Result<Self, Failure>;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodscandidate::TiffCollection (private native experiment)
impl<R: Read + Seek> TiffCollection<R> {
    pub fn open(
        input: R,
        view: Composition,
        limits: Allowances,
    ) -> Result<Self, zencodec::At<CollectionError>>;

    pub fn open_with_stop(
        input: R,
        view: Composition,
        limits: Allowances,
        stop: zencodec::SharedPulse,
    ) -> Result<Self, zencodec::At<CollectionError>>;
}

Selected methods · 2 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec::image::ImageOutput
impl ImageOutput {
    pub fn interpretation(&self) -> &ImageInterpretation;

    pub fn source_frame(&self) -> Option<&Arc<FrameInterpretation>>;

    pub fn map(&self) -> Result<Box<dyn MappedImage + '_>, At<OutputError>>;

    pub fn with_pixels<T>(
        &self,
        consume: impl FnOnce(PixelSlice<'_>) -> T,
    ) -> Result<T, At<OutputError>>;
}

Selected methods · 4 more in the full contract.

Read defaults, imports and full contract ↗
Type definitionzencodec::image::ImageInterpretation
pub struct ImageInterpretation {
    /// Returned stored extent within the selected canvas.
    pub geometry: ImageGeometry,
    /// Selected composited or raw-layer view, when established by the adapter.
    /// The checked collection binds this before publication. `None` makes no
    /// claim; packed storage, placement and dimensions cannot establish it.
    /// Composited samples can still require orientation and color conversion.
    pub composition: Option<crate::collection::Composition>,
    /// Placement on a separate presentation canvas, before orientation.
    /// None means this adapter establishes no separate placement contract.
    /// This does not describe blending or permit discarding off-canvas samples.
    pub placement: Option<Placement>,
    /// None means that this adapter does not establish an orientation contract.
    pub orientation: Option<Orientation>,
    /// Retains native component roles, code depth, siting, crop, color and epoch.
    pub signal: Option<Arc<FrameInterpretation>>,
    /// Original component interpretation after explicit conversion to packed
    /// color. This never describes current packed samples or retains pixels.
    /// Keep it when cloning interpretation for a later crop/color transform;
    /// current geometry, descriptor and context describe the transformed output.
    pub source_frame: Option<Arc<FrameInterpretation>>,
    /// Presentation timing, when established; never inferred from item order.
    pub timing: Option<FrameTiming>,
    /// Native temporal presentation facts, if supplied by a frame adapter.
    pub presentation: Option<Presentation>,
    /// Unmodified source facts, not instructions for interpreting transformed
    /// output and not permission to copy opaque metadata into a derivative.
    pub source_details: crate::Extensions,
}
Read defaults, imports and full contract ↗
Trait methodszencodec::image::MappedImage
pub trait MappedImage {
    fn plane_count(&self) -> usize;

    fn plane(&self, index: usize) -> Result<Option<Plane<'_>>, At<OutputError>>;
}

Required methods: plane_count, plane. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Functionzencodec::operation::resolve_image
pub fn resolve_image<R: OperationPolicy + ?Sized>(
    image: ImageOutput,
    resolver: &mut R,
    preset: PolicySet,
    pulse: Option<&dyn crate::Pulse>,
) -> Result<Outcome<ImageOutput>, At<PolicyFailure>>;
Read defaults, imports and full contract ↗
Inherent methodscandidate::TiffItems (private native experiment)
impl<R: Read + Seek> TiffItems<R> {
    pub fn open(input: R, limits: Allowances) -> Result<Self, Failure>;

    pub fn images(&self) -> Cursor;

    pub fn children(&mut self, parent: &ImageId) -> Result<Cursor, Failure>;

    pub fn next(&mut self, cursor: &mut Cursor) -> Result<Option<Relation>, Failure>;

    pub fn decode(
        &mut self,
        image: &ImageId,
        region: impl Into<RegionRequest>,
        composition: Composition,
        mut check: impl FnMut() -> Result<(), Failure>,
    ) -> Result<Region, Failure>;
}

Selected methods · 4 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodscandidate::Region (private TIFF sample handoff)
impl Region {
    pub fn into_pixels(self) -> Result<StoredRaster, Failure>;
}
Read defaults, imports and full contract ↗
Inherent methodscandidate::JxlItems (private native experiment)
impl<'a> JxlItems<'a> {
    pub fn open(
        data: &'a [u8],
        composition: Composition,
        limits: impl Into<Limits>,
    ) -> Result<Self, Failure>;

    pub fn open_with_stop(
        data: &'a [u8],
        composition: Composition,
        limits: impl Into<Limits>,
        stop: zencodec::SharedPulse,
    ) -> Result<Self, Failure>;

    pub fn next(&mut self) -> Result<Option<Presentation>, Failure>;

    pub fn decode(&self, image: &ImageId, padding: usize) -> Result<Vec<Plane>, Failure>;
}

Selected methods · 6 more in the full contract.

Read defaults, imports and full contract ↗

Still open. Collection, Discovery, CheckedCollection and CodecSet registration now live in core. JXL factory packaging is implemented in zenjxl PR31; TIFF packaging remains open. Publication follows reconstruction, so it proves neither preallocation admission nor whole-container privacy. Native JXL header counts and shared discovery-call counts do not bound work inside each parse or total workspace. Broader source snapshots, hard cancellation latency, remaining layer configurations and unknown auxiliary roles remain unqualified.

Shared collection contract and boundaries ↗ · WIC/OpenImageIO review and resulting contracts ↗ · Native matrix and independent discovery oracle ↗ · JXL source details and selected timecodes ↗ · Signed/wide samples and native allocation adoption ↗ · Shared selection discussion ↗ · Native JXL layer support ↗

Inspect a selected image before reconstructing pixelsShared contract implemented · native TIFF and JXL · The viewer can obtain stored dimensions, orientation and placement for an issued identity before asking for pixels.

Why this belongs in the API. An explorer needs page and layer geometry to plan layout and region requests. Decoding a full raster merely to learn its dimensions creates an allocation the caller has not yet chosen.

Consumer
Call images.describe(&id). Keep the opened operation for later decode requests. Unknown orientation remains unknown; a successful description does not promise that decoding or conversion will succeed.
Adapter
Implement Collection::describe using established source facts without reconstructing pixels or advancing cursors. TIFF reads directory tags; JXL retains basic and layer headers. The checked consumer validates geometry, preserves original causes and checks the final cancellation seam.
Implementor
Keep local stored dimensions distinct from a presentation canvas, and preserve the source revision through later reconstruction. Do not infer complete metadata, a rendered pixel format or a workspace estimate from these header facts.
Inspect the selected identity: dimensions + orientation → Validate source geometry: same source revision → Choose a region: decode and resolve publicationCONSUMERInspect the selected identitydimensions + orientationADAPTERValidate source geometrysame source revisionCONSUMERChoose a regiondecode and resolve publication
consumerInspect the selected identitydimensions + orientation
adapterValidate source geometrysame source revision
consumerChoose a regiondecode and resolve publication
Inspect the selected identity → Validate source geometry → Choose a region
Private prototype · Rust
Source ↗
MoreNative tests ↗
Imports & setup15 lines
use zencodec::{
    At,
    collection::{Collection, ImageDescription, ImageId},
    operation::{CheckedCollection, CollectionOperationError, OperationPolicy},
};

// The caller keeps this opened operation, so the returned ID can be used
// for later region requests. No pixel-format capability query is needed.
pub fn checked_first_description<C: Collection, R: OperationPolicy>(
    images: &mut CheckedCollection<C, R>,
) -> Result<Option<(ImageId, ImageDescription)>, At<CollectionOperationError>> {
    let mut roots = images.images()?;
    let Some(id) = images.next(&mut roots)? else {
        return Ok(None);
    };
    let description = images.describe(&id)?;
Return & continuation4 lines
    // Stored geometry can guide layout/region choices. A later decode still
    // checks source consistency and must pass publication policy.
    Ok(Some((id, description)))
}
Exact traits and signatures

Signatures from this revision · bodies omitted

Inherent methodszencodec::collection::ImageDescription
impl ImageDescription {
    pub const fn new(stored_size: (u32, u32)) -> Self;

    pub const fn with_orientation(mut self, orientation: Orientation) -> Self;

    pub const fn with_placement(mut self, placement: Placement) -> Self;

    pub fn validate(&self) -> Result<(), At<CollectionError>>;
}
Read defaults, imports and full contract ↗
Trait methodszencodec::collection::Collection
pub trait Collection {
    type Cursor;

    fn view(&self) -> Composition;

    fn images(&mut self) -> Result<Self::Cursor, At<CollectionError>>;

    fn children(&mut self, image: &ImageId) -> Result<Self::Cursor, At<CollectionError>>;

    fn next(&mut self, cursor: &mut Self::Cursor) -> Result<Option<ImageId>, At<CollectionError>>;

    fn describe(&mut self, image: &ImageId) -> Result<ImageDescription, At<CollectionError>>;

    fn decode(
        &mut self,
        image: &ImageId,
        request: &ImageRequest,
    ) -> Result<ImageOutput, At<CollectionError>>;
}

Required methods: view, images, children, next, describe, decode. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Inherent methodszencodec::operation::CheckedCollection<C, R>
impl<C: Collection, R: OperationPolicy> CheckedCollection<C, R> {
    pub fn open(
        pulse: SharedPulse,
        resolver: R,
        open: impl FnOnce(SharedPulse) -> Result<C, Cause>,
    ) -> Result<Self, At<CollectionOperationError>>;

    pub fn with_policy_set(mut self, preset: PolicySet) -> Self;

    pub fn images(&mut self) -> Result<C::Cursor, At<CollectionOperationError>>;

    pub fn children(&mut self, image: &ImageId) -> Result<C::Cursor, At<CollectionOperationError>>;

    pub fn next(
        &mut self,
        cursor: &mut C::Cursor,
    ) -> Result<Option<ImageId>, At<CollectionOperationError>>;

    pub fn describe(
        &mut self,
        image: &ImageId,
    ) -> Result<ImageDescription, At<CollectionOperationError>>;

    pub fn first_image(
        &mut self,
        request: &ImageRequest,
    ) -> Result<Option<Outcome<ImageOutput>>, At<CollectionOperationError>>;

    pub fn decode(
        &mut self,
        image: &ImageId,
        request: &ImageRequest,
    ) -> Result<Outcome<ImageOutput>, At<CollectionOperationError>>;

    pub fn decode_with<E: Into<Cause>>(
        &mut self,
        image: &ImageId,
        request: &ImageRequest,
        transform: impl FnOnce(ImageOutput, &dyn crate::Pulse) -> Result<ImageOutput, E>,
    ) -> Result<Outcome<ImageOutput>, At<CollectionOperationError>>;

    pub fn report(&self) -> &OperationReport;

    pub fn is_finished(&self) -> bool;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec::collection::Discovery<C>
impl<C> Discovery<C> {
    pub fn new(collection: C, attempts: u64) -> Self;

    pub fn usage(&self) -> DiscoveryUsage;
}

Selected methods · 2 more in the full contract.

Read defaults, imports and full contract ↗

Still open. Native discovery can still traverse prior compressed frames, and transport buffers may read payload bytes. TIFF factory packaging, additional header facts, complete privacy inspection and native workspace admission remain open. Pixel publication still uses the existing resolver after reconstruction.

Selected description contract ↗ · Native qualification and limits ↗ · Shared API qualification ↗ · Initial experiment receipt ↗

Keep format-specific opening out of the viewer loopImplemented on review PR · native adapter packaging open · Register native configurations once, then open the same checked consumer over borrowed bytes or a positional source.

Why this belongs in the API. The explorer now handles both TIFF pages and JXL layers. Native configurations belong in CodecSet; its selection loop should not repeat format-specific construction and error conversion.

Consumer
Choose the format and view, supply one source, resolver and stop token, then use the existing first-image, description and selected-decode methods. Keeping an image does not require keeping the registry or source alive.
Adapter
Erase the collection once at open and owned cursor state once per cursor. ReadAtShim now accepts a local source through the same DynReadAt protocol; require + Send only when the erased object crosses threads. Preserve source lifetimes and errors. A failed first matching factory does not fall back to a different implementation. Inherited limits and decode policy must hold or refuse. Typed refusals retain the specific unhandled settings; all inherited and per-call cancellation sources remain active.
Implementor
TIFF accepts positional reads, including the thread-local HTTP simulator. JXL currently requires an already-resident slice and refuses before reading when one is unavailable. Do not add implicit whole-file buffering to make an opening signature succeed.
Open one checked operation: source + view + policy → Select the registered factory: native limits + source lifetime → Honor the source mode: positional or resident-onlyCONSUMEROpen one checked operationsource + view + policyADAPTERSelect the registered factorynative limits + source lifetimeIMPLEMENTORHonor the source modepositional or resident-only
consumerOpen one checked operationsource + view + policy
adapterSelect the registered factorynative limits + source lifetime
implementorHonor the source modepositional or resident-only
Open one checked operation → Select the registered factory → Honor the source mode
Private prototype · Rust
Source ↗
MoreNative tests ↗
Imports & setup31 lines
use crate::{collection_factories::TiffFactory, tiff_region::Allowances};
use zencodec::{
    At, CodecSet, ImageFormat, SharedPulse,
    collection::{ImageRequest, OpenRequest},
    image::ImageOutput,
    operation::{CollectionOperationError, Outcome, OperationPolicy},
    source::DynReadAt,
};

use zenjxl::collection::JxlCollectionConfig;

// For example: JxlCollectionConfig::new(64, 256) bounds image identities
// and processed frame headers separately. Configure these once.
// JXL uses its real codec package; TIFF
// remains a private adapter candidate. Both use the shared checked operation.
pub fn registered_factories(tiff: Allowances, jxl: JxlCollectionConfig) -> CodecSet {
    CodecSet::new()
        .with_collection(TiffFactory(tiff))
        .with_collection(jxl)
}

// The caller supplies a stable source: immutable bytes, a conditional HTTP
// object, or a file held unchanged. Choose one view and discovery allowance.
pub fn checked_registered_first<R: OperationPolicy>(
    factories: &CodecSet,
    format: ImageFormat,
    source: &mut dyn DynReadAt,
    request: OpenRequest,
    resolver: R,
    stop: SharedPulse,
) -> Result<Option<Outcome<ImageOutput>>, At<CollectionOperationError>> {
    let mut images = factories.open_collection(format, source, request, resolver, stop)?;
    let image = images.first_image(&ImageRequest::new())?;
Return & continuation4 lines
    // The image owns its samples after this operation ends. Applications
    // that retain IDs instead keep the opened operation for later requests.
    Ok(image)
}
Exact traits and signatures

Signatures from this revision · bodies omitted

Inherent methodszencodec::CodecSet
impl CodecSet {
    pub fn with_collection(mut self, factory: impl CollectionFactory + 'static) -> Self;

    pub fn open_collection<'a, R: OperationPolicy>(
        &self,
        format: ImageFormat,
        source: &'a mut dyn DynReadAt,
        request: OpenRequest,
        resolver: R,
        pulse: SharedPulse,
    ) -> Result<CheckedCollection<Discovery<ErasedCollection<'a>>, R>, At<CollectionOperationError>>;
}
Read defaults, imports and full contract ↗
Inherent methodszencodec::collection::OpenRequest
impl OpenRequest {
    pub const fn new(view: Composition, discovery_attempts: u64) -> Self;

    pub fn with_output_storage(mut self, storage: StorageBudget) -> Self;
}
Read defaults, imports and full contract ↗
Type definitionzencodec::collection::CollectionOpenContext
#[non_exhaustive]
pub struct CollectionOpenContext {
    /// The matched registration, without another format-detection pass.
    pub format: ImageFormat,
    /// Fixed view for all identities issued by this opening.
    pub view: Composition,
    /// Inherited resource requirements; `None` keeps the native template.
    pub limits: Option<ResourceLimits>,
    /// Inherited decode requirements; `None` keeps the native template.
    pub policy: Option<DecodePolicy>,
    /// Shared latched cancellation, retained throughout the native operation.
    pub pulse: SharedPulse,
    /// Required pool for retained output payloads, including row padding.
    ///
    /// Admission must precede native output allocation. Published owners and
    /// their clones retain the charge until the last owner drops. This does not
    /// bound native workspace, caller input, metadata or explicit caller copies.
    /// `None` keeps any native template's output-storage configuration.
    pub output_storage: Option<StorageBudget>,
}
Read defaults, imports and full contract ↗
Type definitionzencodec::collection::CollectionOpenError
#[non_exhaustive]
pub enum CollectionOpenError {
    /// No factory serves this format. Still-image decoders are registered separately.
    NotRegistered(ImageFormat),
    /// This adapter needs an already-resident slice; it did not stage the source.
    ResidentInputRequired,
    /// The adapter cannot enforce the supplied resource-limit configuration.
    UnsupportedLimits {
        /// Required fields the adapter cannot enforce. Qualified fields and
        /// fields that do not apply to decoding have their no-op/default values.
        /// This bounded structured data contains no source or metadata payload.
        unsupported: ResourceLimits,
    },
    /// The adapter cannot enforce the supplied decode-policy configuration.
    UnsupportedDecodePolicy {
        /// Explicit requirements the adapter cannot enforce. Handled fields
        /// are `None`; this is not permission to retry with weaker requirements.
        unsupported: DecodePolicy,
    },
    /// The adapter cannot admit returned output payloads to the required pool.
    UnsupportedOutputStorage,
}
Read defaults, imports and full contract ↗
Inherent methodszencodec::CodecSet
impl CodecSet {
    pub fn new() -> Self;

    pub fn with_decoder(mut self, config: impl DecoderConfig + 'static) -> Self;

    pub fn with_encoder<C>(mut self, config: C) -> Self
    where
        C: EncoderConfig + 'static,
        <C::Job as EncodeJob>::Enc: Encoder + Send,
        <C::Job as EncodeJob>::AnimationFrameEnc: AnimationFrameEncoder;

    pub fn with_send_encoder<C>(mut self, config: C) -> Self
    where
        C: EncoderConfig + 'static,
        C::Job: Send,
        <C::Job as EncodeJob>::Enc: Encoder + Send,
        <C::Job as EncodeJob>::AnimationFrameEnc: AnimationFrameEncoder;

    pub fn with_limits(mut self, limits: ResourceLimits) -> Self;

    pub fn with_pulse(mut self, pulse: how_far::SharedPulse) -> Self;

    pub fn pulse(&self) -> &dyn crate::Pulse;
}

Selected methods · 42 more in the full contract.

Read defaults, imports and full contract ↗
Trait methodszencodec::collection::CollectionFactory
pub trait CollectionFactory: Send + Sync {
    fn formats(&self) -> &[ImageFormat];

    fn open_with<'a>(
        &self,
        source: &'a mut dyn DynReadAt,
        context: CollectionOpenContext,
    ) -> Result<ErasedCollection<'a>, Cause>;
}

Required methods: formats, open_with. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Trait methodszencodec::source::DynReadAt
pub trait DynReadAt {
    fn len(&mut self) -> Result<u64, BoxedError>;

    fn len_hint(&self) -> Option<u64>;

    fn read_at(&mut self, offset: u64, out: &mut [u8]) -> Result<usize, BoxedError>;

    fn read_exact_at(&mut self, offset: u64, out: &mut [u8]) -> Result<(), BoxedError>;

    fn read_many_at(&mut self, reqs: &mut [(u64, &mut [u8])]) -> Result<(), BoxedError>;

    fn as_slice(&self) -> Option<&[u8]>;
}

Selected methods · 1 more in the full contract.

Required methods: len, read_exact_at. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Inherent methodszencodec::operation::CheckedCollection<C, R>
impl<C: Collection, R: OperationPolicy> CheckedCollection<C, R> {
    pub fn open(
        pulse: SharedPulse,
        resolver: R,
        open: impl FnOnce(SharedPulse) -> Result<C, Cause>,
    ) -> Result<Self, At<CollectionOperationError>>;

    pub fn with_policy_set(mut self, preset: PolicySet) -> Self;

    pub fn images(&mut self) -> Result<C::Cursor, At<CollectionOperationError>>;

    pub fn children(&mut self, image: &ImageId) -> Result<C::Cursor, At<CollectionOperationError>>;

    pub fn next(
        &mut self,
        cursor: &mut C::Cursor,
    ) -> Result<Option<ImageId>, At<CollectionOperationError>>;

    pub fn describe(
        &mut self,
        image: &ImageId,
    ) -> Result<ImageDescription, At<CollectionOperationError>>;

    pub fn first_image(
        &mut self,
        request: &ImageRequest,
    ) -> Result<Option<Outcome<ImageOutput>>, At<CollectionOperationError>>;

    pub fn decode(
        &mut self,
        image: &ImageId,
        request: &ImageRequest,
    ) -> Result<Outcome<ImageOutput>, At<CollectionOperationError>>;

    pub fn decode_with<E: Into<Cause>>(
        &mut self,
        image: &ImageId,
        request: &ImageRequest,
        transform: impl FnOnce(ImageOutput, &dyn crate::Pulse) -> Result<ImageOutput, E>,
    ) -> Result<Outcome<ImageOutput>, At<CollectionOperationError>>;

    pub fn report(&self) -> &OperationReport;

    pub fn is_finished(&self) -> bool;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec::operation::CollectionOperationError
impl CollectionOperationError {
    pub const fn stop_reason(&self) -> Option<StopReason>;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗

Still open. CodecSet registration is implemented. JXL uses the real zenjxl::collection::JxlCollectionConfig; TIFF remains a private adapter. TIFF enforces stored width, height and pixel caps before compressed-pixel reads, while allowing inspection and smaller children. JXL enforces the actual resident input-byte cap before parsing, regardless of length hints. Both refuse unqualified legacy limit/policy configurations before I/O. Native tests show that a selected layer can fit a pixel cap while its canvas does not, and independently budgeted decoders can retain more output than one decoder budget. Native work and retained ownership need separate enforcement. Transferable opened jobs, automatic detection, native progress, fallible cursor allocation, wider codec coverage and measured erasure costs remain open.

Measured native resource scopes ↗ · Packaged JXL API and qualified limits ↗ · Native opening experiment and limits ↗ · Executing native controls ↗ · Conditional HTTP controls ↗

Layer placement does not weaken pixel boundsShared placement implemented · native raw layers · A layer can extend beyond its presentation canvas. The public native F32 path now returns nine qualified complete local layers. The shared collection binds their placement and composition facts to the sample owner before policy approval.

Why this belongs in the API. The real JXL fixture has a 1024×576 layer at (1860,950) on a 2048×1152 canvas. Its visible rectangle is 188×202. Clipping the stored layer would lose samples; accepting those coordinates as a bounded crop would weaken mapping checks.

Consumer
Choose the layer view at open, then use the same first_image or decode call as for presentations. Read ImageInterpretation::placement from the returned owner and ask for its intersection when drawing. The owner rejects invalid placement before publication. Off-canvas samples remain available; a hidden layer is not end of discovery.
Adapter
Freeze signed placement, native blend instructions and reference slots with the actual local samples. The adapter replays from the start for selection. Native CMYK uses CmykF32Inverted; separate black also declares inverted polarity, so attaching an ICC profile cannot silently change sample meaning.
Implementor
Save local F32 rows before blending and canvas extension while continuing to reconstruct references. Stored orientation, skipped previews, unrendered spots and retained black are required for this native path. Independent libjxl agrees on geometry, blend facts and CMYK/alpha bits; lossy RGB uses the unchanged conformance bound.
Retain local samples: 1024 × 576 layer → Place on the canvas: origin (1860,950) → Draw the intersection: 188 × 202 visibleIMPLEMENTORRetain local samples1024 × 576 layerADAPTERPlace on the canvasorigin (1860,950)CONSUMERDraw the intersection188 × 202 visible
implementorRetain local samples1024 × 576 layer
adapterPlace on the canvasorigin (1860,950)
consumerDraw the intersection188 × 202 visible
Retain local samples → Place on the canvas → Draw the intersection
Private prototype · Rust
Source ↗
MoreNative tests ↗
Imports & setup18 lines
use crate::{jxl_items::Limits, jxl_layers::JxlLayers};
use zencodec::{
    At, SharedPulse,
    collection::ImageRequest,
    image::ImageOutput,
    operation::{CheckedCollection, CollectionOperationError, Outcome, OperationPolicy},
};

// The adapter fixes the uncomposited view before IDs are discovered.
// This qualified F32 path retains stored orientation and unrendered spots.
pub fn checked_jxl_layer_first<R: OperationPolicy>(
    bytes: &[u8],
    limits: Limits,
    resolver: R,
    stop: SharedPulse,
) -> Result<Option<Outcome<ImageOutput>>, At<CollectionOperationError>> {
    let mut images =
        CheckedCollection::open(stop, resolver, |stop| JxlLayers::open(bytes, limits, stop))?;
    let layer = images.first_image(&ImageRequest::default())?;
Return & continuation4 lines
    // The result retains the full local samples, source blend facts, signed
    // canvas placement and explicit CMYK polarity. It is not a flat preview.
    Ok(layer)
}
Exact traits and signatures

Signatures from this revision · bodies omitted

Inherent methodscandidate::JxlLayers (private native F32 adapter)
impl<'a> JxlLayers<'a> {
    pub fn open(bytes: &'a [u8], limits: Limits, stop: SharedPulse) -> Result<Self, Failure>;
}

Selected methods · 3 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec::operation::CheckedCollection<C, R>
impl<C: Collection, R: OperationPolicy> CheckedCollection<C, R> {
    pub fn open(
        pulse: SharedPulse,
        resolver: R,
        open: impl FnOnce(SharedPulse) -> Result<C, Cause>,
    ) -> Result<Self, At<CollectionOperationError>>;

    pub fn with_policy_set(mut self, preset: PolicySet) -> Self;

    pub fn images(&mut self) -> Result<C::Cursor, At<CollectionOperationError>>;

    pub fn children(&mut self, image: &ImageId) -> Result<C::Cursor, At<CollectionOperationError>>;

    pub fn next(
        &mut self,
        cursor: &mut C::Cursor,
    ) -> Result<Option<ImageId>, At<CollectionOperationError>>;

    pub fn describe(
        &mut self,
        image: &ImageId,
    ) -> Result<ImageDescription, At<CollectionOperationError>>;

    pub fn first_image(
        &mut self,
        request: &ImageRequest,
    ) -> Result<Option<Outcome<ImageOutput>>, At<CollectionOperationError>>;

    pub fn decode(
        &mut self,
        image: &ImageId,
        request: &ImageRequest,
    ) -> Result<Outcome<ImageOutput>, At<CollectionOperationError>>;

    pub fn decode_with<E: Into<Cause>>(
        &mut self,
        image: &ImageId,
        request: &ImageRequest,
        transform: impl FnOnce(ImageOutput, &dyn crate::Pulse) -> Result<ImageOutput, E>,
    ) -> Result<Outcome<ImageOutput>, At<CollectionOperationError>>;

    pub fn report(&self) -> &OperationReport;

    pub fn is_finished(&self) -> bool;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec::image::Placement
impl Placement {
    pub fn visible(
        self,
        geometry: ImageGeometry,
    ) -> Result<Option<PlacementOverlap>, PlacementError>;
}
Read defaults, imports and full contract ↗
Inherent methodszencodec::image::ImageOutput
impl ImageOutput {
    pub fn interpretation(&self) -> &ImageInterpretation;

    pub fn source_frame(&self) -> Option<&Arc<FrameInterpretation>>;

    pub fn map(&self) -> Result<Box<dyn MappedImage + '_>, At<OutputError>>;

    pub fn with_pixels<T>(
        &self,
        consume: impl FnOnce(PixelSlice<'_>) -> T,
    ) -> Result<T, At<OutputError>>;
}

Selected methods · 4 more in the full contract.

Read defaults, imports and full contract ↗

Still open. The shared collection is implemented in core; placement is frozen in ImageInterpretation and native raw output is implemented on its review PR. All blend configurations, native negative-origin fixtures, reference-only export, preview/progressive output, raw random seeking, CMYK conversion and workspace bounds remain unqualified. Publication happens after reconstruction. Source blend facts are not a lossless re-encoding contract.

Executing placement contract and limits ↗ · Placement qualification ↗ · Selected raw-output qualification ↗ · Native F32 output and limits ↗ · Native layer contract ↗

Callbacks remain fallible inside the codecImplemented · native review PR · Native JXL color-management failures now return the original traced error through both renderers. They cannot become a panic or an apparently successful fallback image.

Why this belongs in the API. A fallible application resolver is useful only if downstream services also honor refusal. The real-codec loop found RGB callbacks that panicked and CMYK callbacks whose errors produced substitute pixels.

Consumer
Use the normal Result path to inspect the original CMS error and its whereat trace. Native CMYK samples retain their declared inverted ink domain; conversion requires an explicit qualified CMS route.
Adapter
Keep ink-polarity adaptation inside the selected CMS backend. Pass the requested destination profile and preserve the original callback error through the common color stage.
Implementor
Validate the returned transformer count and channel layout before use. Propagate failures from initialization and every row chunk, including parallel rendering. One shared fallible stage now handles RGB, gray and CMYK.
Call the chosen CMS: requested profile + native samples → Preserve its result: same traced error on refusal → Handle the failure: typed cause + original traceIMPLEMENTORCall the chosen CMSrequested profile + native samplesADAPTERPreserve its resultsame traced error on refusalCONSUMERHandle the failuretyped cause + original trace
implementorCall the chosen CMSrequested profile + native samples
adapterPreserve its resultsame traced error on refusal
consumerHandle the failuretyped cause + original trace
Call the chosen CMS → Preserve its result → Handle the failure
Exact traits and signatures

Signatures from this revision · bodies omitted

Functionzencodec::find_cause
pub fn find_cause<'a, T: core::error::Error + 'static>(
    mut err: &'a (dyn core::error::Error + 'static),
) -> Option<&'a T>;

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗

Still open. Qualified native callback cases cover first and later chunks, both renderers and threaded execution. Arbitrary ICC transforms, callback panics, callback workspace/time bounds and recovery through the mutable low-level decoder remain unqualified. This is not a complete CMYK conversion contract.

Native callback contract and qualification ↗ · Native reproducer and implementation ↗ · Downstream TIFF/JXL/HTTP regression receipt ↗

Keep the original failure and the observed stopImplemented · review PR · Collection errors expose the observed stop without discarding a coincident failure. Pump and mapping errors preserve concrete backend causes and location traces.

Why this belongs in the API. A production error often crosses a codec, a pump, conversion and a destination. Losing the cause at any wrapper makes the failure hard to classify or reproduce.

Consumer
On a collection error, use stop_reason() for the first stop observed by its shared token. Use Error::source or zencodec::find_cause for the original failure. The pump still identifies which side failed; retained typed causes require no message parsing.
Adapter
Forward the owned error through PumpError, AdaptError and erasure. Keep traced callback and destination failures intact. An error report adds context without replacing its cause.
Implementor
Pass the original error into MapError::Backend as a boxed Error + Send + Sync. Use Unavailable for storage without a CPU mapping.
Exact traits and signatures

Signatures from this revision · bodies omitted

Functionzencodec::find_cause
pub fn find_cause<'a, T: core::error::Error + 'static>(
    mut err: &'a (dyn core::error::Error + 'static),
) -> Option<&'a T>;

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec::operation::CollectionOperationError
impl CollectionOperationError {
    pub const fn stop_reason(&self) -> Option<StopReason>;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodscore::error::Error for zencodec_media::session::PumpError
impl<U: core::error::Error + 'static, D: core::error::Error + 'static> core::error::Error
    for PumpError<U, D> {
    fn source(&self) -> Option<&(dyn core::error::Error + 'static)>;
}
Read defaults, imports and full contract ↗
Inherent methodscore::error::Error for zencodec::frame::MapError
impl core::error::Error for MapError {
    fn source(&self) -> Option<&(dyn core::error::Error + 'static)>;
}
Read defaults, imports and full contract ↗

Still open. This changes the unpublished MapError payload from String to an owned error. Native AV1 plane-limit refusal still lacks a distinct typed category and stable refusal facts; retaining its cause does not supply those facts.

Collection failure boundaries ↗ · Media error contract ↗ · Mapping and pump regression controls ↗ · Encoder pump and terminal behavior ↗ · Native refusal category gap ↗

Video policy follows each actual presentationPartly implemented · Native AV1 retains samples, color, timing and packet provenance through the shared resolver. Optional native plane admission precedes allocation; full workspace bounds and coupled conversion remain open.

Why this belongs in the API. A later frame can change color interpretation. Publication policy must inspect that frame. A storage requirement must act earlier, before its native planes allocate; a callback after reconstruction cannot enforce it.

Consumer
Set max_picture_bytes when the session needs a native plane allowance, then wrap VideoDecoder in PolicyDecoder. The same fallible resolver checks each presentation. Returned frames keep their original interpretation and storage charge.
Adapter
Hold the actual picture and accepted packet context until the scoped decision succeeds. Native admission failure keeps its traced cause and drops unpublished state. Already returned frames remain valid and charged.
Implementor
Admit a complete picture before its first plane allocation. Carry the charge with native references and retained frames through reset/drop. Preserve send/receive, drain and accepted-packet provenance.
Admit native planes: before first allocation → Reconstruct picture: accepted packet context → Retain exact owner: immutable timing + color → Resolve publication: choose, decline or errorIMPLEMENTORAdmit native planesbefore first allocationIMPLEMENTORReconstruct pictureaccepted packet contextADAPTERRetain exact ownerimmutable timing + colorCONSUMERResolve publicationchoose, decline or error
implementorAdmit native planesbefore first allocation
implementorReconstruct pictureaccepted packet context
adapterRetain exact ownerimmutable timing + color
consumerResolve publicationchoose, decline or error
Admit native planes → Reconstruct picture → Retain exact owner → Resolve publication
Implemented · review PRs · Rust
Source ↗
MoreNative tests ↗
Imports & setup7 lines
use zenavif::media::{Av1SessionError, Av1VideoDecoder};
use zencodec::operation::PolicySet;
use zencodec_media::policy::PolicyDecoder;

pub fn open_video_with_picture_limit(
    picture_bytes: usize,
) -> Result<PolicyDecoder<Av1VideoDecoder, PolicySet>, Av1SessionError> {
    let mut settings = rav1d_safe::Settings::default();
    settings.max_picture_bytes = Some(picture_bytes);
    let native = Av1VideoDecoder::with_settings(settings)?;
    let decoder = PolicyDecoder::new(native, PolicySet::new());
Return & continuation5 lines
    // Native admission precedes reconstruction. Policy precedes publication.
    // The allowance follows retained frames, including after decoder drop.
    // This bounds requested native plane bytes, not workspace or total RAM.
    Ok(decoder)
}
Exact traits and signatures

Signatures from this revision · bodies omitted

Inherent methodszencodec_media::policy::PolicyDecoder
impl<D, R> PolicyDecoder<D, R> {
    pub fn new(decoder: D, resolver: R) -> Self;

    pub fn with_policy_set(mut self, preset: PolicySet) -> Self;

    pub fn report(&self) -> &OperationReport;

    pub const fn is_failed(&self) -> bool;
}
Read defaults, imports and full contract ↗
Functionzencodec::operation::resolve_frame
pub fn resolve_frame<R: OperationPolicy + ?Sized>(
    interpretation: &FrameInterpretation,
    timing: FrameTiming,
    resolver: &mut R,
    preset: PolicySet,
    pulse: Option<&dyn crate::Pulse>,
) -> Result<DecisionRecord, At<PolicyFailure>>;
Read defaults, imports and full contract ↗
Trait methodszencodec_media::session::VideoDecoder
pub trait VideoDecoder {
    type Error: core::error::Error + Send + Sync + 'static;

    fn configure(
        &mut self,
        config: &CodecConfig,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<(), SessionError<Self::Error>>;

    fn send(
        &mut self,
        packet: &mut Packet,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<SendStatus, SessionError<Self::Error>>;

    fn receive(
        &mut self,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<Receive<Frame>, SessionError<Self::Error>>;

    fn end_input(&mut self) -> Result<(), SessionError<Self::Error>>;

    fn reset(&mut self) -> Result<(), SessionError<Self::Error>>;
}

Selected methods · 1 more in the full contract.

Required methods: configure, send, receive, end_input, reset, stream_info. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗

Still open. The native plane byte budget covers requested bytes with padding and alignment. It excludes work buffers, input, metadata, allocator overhead and encoded queues; it is not a total RAM bound. C-FFI refuses this setting. Publication policy still runs after native reconstruction. Structured native refusal facts, coupled planar conversion and reconfiguration remain open.

Native AV1 scope and independent FFmpeg checks ↗ · Executable video examples ↗ · Native allocation contract ↗ · Typed native refusal gap ↗

Required density survives, or encoding refusesImplemented · qualified routes · Generic, erased and registered jobs carry required density. PNG output preserves exactly representable values; JPEG input retains non-square JFIF aspect.

Why this belongs in the API. Pixel equality does not preserve print size or non-square pixel aspect. A successful transcode must not silently lose a reported field that it promised to retain.

Consumer
Use EncodeRequest::with_resolution for existing pixels. Default checked transcodes retain reported density where qualified; no capability-query sequence is required.
Adapter
Apply the requirement to the retained job before joint selection. Compare physical units exactly after encoding, and refuse unqualified EXIF/XMP reconciliation.
Implementor
Implement try_set_resolution only where the carrier can represent the value. A refused setter leaves the job unchanged; an unsupported default returns an error.
Implemented · review PRs · Rust
Source ↗
MoreNative tests ↗
Imports & setup12 lines
use zencodec::{
    At, CodecSet, ImageFormat, Resolution,
    encode::EncodeOutput,
    operation::{EncodeRequest, OperationError, Outcome},
};
use zenpixels::PixelSlice;

pub fn with_density(
    codecs: &CodecSet,
    pixels: PixelSlice<'_>,
    resolution: Resolution,
) -> Result<Outcome<EncodeOutput>, At<OperationError>> {
    let output = EncodeRequest::new(ImageFormat::Png)
        .with_resolution(resolution)
        .encode(codecs, pixels)?;
Return & continuation4 lines
    // Required means exact: 254 DPI fits PNG; 300 DPI refuses.
    // No implicit rounding or unit change may alter the represented density.
    Ok(output)
}
Exact traits and signatures

Signatures from this revision · bodies omitted

Trait methodszencodec::encode::EncodeJob
pub trait EncodeJob: Sized {
    fn try_set_resolution(
        &mut self,
        resolution: crate::Resolution,
    ) -> Result<(), crate::ResolutionError>;

    fn try_with_resolution(
        mut self,
        resolution: crate::Resolution,
    ) -> Result<Self, crate::ResolutionError>;
}

Selected methods · 23 more in the full contract.

Required methods: with_pulse, with_limits, with_metadata, encoder, animation_frame_encoder. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Inherent methodszencodec::Resolution
impl Resolution {
    pub fn validate(&self) -> Result<(), ResolutionError>;

    pub fn is_equivalent_to(&self, other: &Self) -> bool;

    pub fn try_integer_density(
        &self,
        unit: ResolutionUnit,
        max: u32,
    ) -> Result<(u32, u32), ResolutionError>;
}
Read defaults, imports and full contract ↗

Still open. PNG cannot represent exact 300 DPI as integer pixels/metre, so that request refuses. No implicit quantization is offered. Required JPEG output, TIFF rational precision, conflicting metadata and axis-changing transforms remain open.

Exact unit contract and rational oracle ↗ · Native transcode tests ↗ · Remaining resolution contract ↗

One resolver selects a complete executable choiceImplemented · qualified routes · Typed questions cover encoding, interpretation boundaries, selected-image/native-frame publication and coupled pixel transcodes. Configured presets and application code use the same interface.

Why this belongs in the API. Separate color, metadata and encoder choices can each appear supported but fail in combination. The adapter must prepare a coherent combination before asking the application.

Consumer
Start with a preset. Add if/then logic only for a situational rule; return a predefined decision or a traced application error.
Adapter
Retain the selected conversion plan, encoder job and admitted row buffers across the callback. Scope tokens to this question and preserve typed causes and bounded decision history. DecisionContext retains input facts on success and refusal; Selection records the chosen effect without repeating frame or boundary facts.
Implementor
Suspend before affected effects when interpretation changes. Metadata notifications alone do not authorize conversion, encoder initialization or output publication.
Exact traits and signatures

Signatures from this revision · bodies omitted

Trait methodszencodec::operation::PolicyResolver
pub trait PolicyResolver {
    type Error: Error + Send + Sync + 'static;

    fn resolve(&mut self, question: Question<'_>) -> Result<Decision, At<Self::Error>>;
}

Required methods: resolve. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Inherent methodszencodec::operation::PolicySet
impl PolicySet {
    pub const fn new() -> Self;

    pub const fn prefer_lossless(mut self, enabled: bool) -> Self;

    pub const fn require_metadata(mut self, treatment: MetadataTransfer) -> Self;

    pub const fn allow_opaque_profiles(mut self, allowed: bool) -> Self;

    pub const fn with_workspace_limit(mut self, bytes: u64) -> Self;

    pub const fn allow_assumed_chroma_siting(mut self, allowed: bool) -> Self;

    pub const fn require_extended_range(mut self, required: bool) -> Self;

    pub const fn require_output_schedule(mut self, schedule: super::OutputSchedule) -> Self;

    pub const fn with_output_wait_limit(mut self, bytes: u64) -> Self;
}

Selected methods · 6 more in the full contract.

Read defaults, imports and full contract ↗
Trait methodszencodec::decode::ControlledIncrementalDecode
pub trait ControlledIncrementalDecode: IncrementalDecode {
    fn step_controlled(
        &mut self,
        input: Input<'_>,
        sink: &mut dyn DecodeRowSink,
        pulse: Option<&dyn crate::Pulse>,
    ) -> Result<ControlledProgress, StepError<Self::Error>>;

    fn resume(
        &mut self,
        boundary: BoundaryId,
        action: BoundaryAction,
        pulse: Option<&dyn crate::Pulse>,
    ) -> Result<(), StepError<Self::Error>>;
}

Required methods: step_controlled, resume. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗

Still open. Encoded rewrite choices, whole-container privacy and qualified native workspace or encoded staging bounds remain separate qualification work. Unknown evidence refuses a requirement that needs it.

Current contract and implementation record ↗ · Policy history performance and limitations ↗ · Selected-image policy cost: confirmation refused ↗

Disk and network share positional demandImplemented · qualified routes · ReadAtReader bridges an existing synchronous Read + Seek parser; async sources keep byte demand and CPU scheduling separate.

Why this belongs in the API. Disk benefits from positional reads, while a network source needs explicit ranges, bounded holding and suspension. A second core I/O hierarchy would not supply those missing contracts.

Consumer
Keep the checked request when switching among resident bytes, files and async sources. Choose limits for the operation, not a manual codec capability handshake.
Adapter
Own the cursor, source errors, input allowance and scheduling quota. Bind coherent source bytes before native selection: conditional HTTP requests can refuse replacement; a private Linux adapter uses an atomic file clone. Both feed the same ReadAt contract. A seek bridge does not itself provide network suspension or buffering.
Implementor
Request the bytes needed for progress and honor admitted sink row capacity. Native batch size is separate from the size of the consumer’s strip.
Exact traits and signatures

Signatures from this revision · bodies omitted

Inherent methodszencodec_io::ReadAtReader
impl<S> ReadAtReader<S> {
    pub const fn new(source: S) -> Self;

    pub fn into_inner(self) -> S;
}
Read defaults, imports and full contract ↗
Trait methodszencodec::source::ReadAt
pub trait ReadAt {
    type Error: core::error::Error + Send + Sync + 'static;

    fn len(&mut self) -> Result<u64, Self::Error>;

    fn len_hint(&self) -> Option<u64>;

    fn read_exact_at(&mut self, offset: u64, out: &mut [u8]) -> Result<(), Self::Error>;

    fn read_at(&mut self, offset: u64, out: &mut [u8]) -> Result<usize, Self::Error>;

    fn read_many_at(&mut self, reqs: &mut [(u64, &mut [u8])]) -> Result<(), Self::Error>;

    fn as_slice(&self) -> Option<&[u8]>;
}

Selected methods · 1 more in the full contract.

Required methods: len, read_exact_at. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Trait methods · zencodec-io feature: tokiozencodec_io::tokio_io::AsyncSource
pub trait AsyncSource: Send {
    fn read_at(
        &mut self,
        offset: u64,
        out: &mut [u8],
    ) -> impl Future<Output = Result<usize, BoxedError>> + Send;

    fn read_ranges(
        &mut self,
        reqs: &mut [(u64, &mut [u8])],
    ) -> impl Future<Output = Result<(), BoxedError>> + Send;

    fn len_hint(&self) -> Option<u64>;

    fn len(&mut self) -> impl Future<Output = Result<u64, BoxedError>> + Send;
}

Required methods: read_at, len. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Functioncandidate::clone_source (private Linux file snapshot)
pub fn clone_source(source: &File, spool: &File, max_bytes: u64) -> Result<FileReadAt, Error>;

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗

Still open. HTTP qualification covers one conditional object; file-clone qualification covers the tested Linux/XFS path. Multi-object sources, browser transports and other filesystems/platforms remain open. Neither path proves native workspace bounds. Slow-writer backpressure still needs a proven retained-state bound; output queue size alone is insufficient.

Native TIFF disk/range experiments ↗ · Atomic file-source qualification ↗ · Reused IMI transport model ↗ · Encoder retention experiment ↗

The footer identifies this site’s source revision and the native dependency pins. Main’s guide deploys through GitHub Actions; the active implementation is reviewed in the linked PRs.

01 / Consumer

An upload becomes a web image.

These examples use baseline/progressive JPEG and the resulting PNG inputs, with PNG as the destination. The linked native harness records tested revisions and remaining qualification gates. Expand the code above and below each highlighted call to see imports, parameters and the returned value.

Borrowed controls take Option<&dyn Pulse>. Pass None for no extra per-call control; an owned SharedPulse already attached to the set or job stays active. Pass Some(pulse) when adding a borrowed control so reporting, phase planning, sharing and cancellation travel together. Required policy, resource and protocol checks still apply. Optional control contract.

1

Register the accepted formats once.

The service accepts JPEG and PNG and inspects its finished PNG output. Registration retains the concrete encoder job’s thread-transfer guarantee.

Implemented · review PRs · Rust
Source ↗
MoreNative tests ↗
Imports & setup4 lines
use zencodec::CodecSet;
use zencodecs::{JpegDecoderConfig, PngDecoderConfig, PngEncoderConfig};

pub fn gallery_codecs() -> CodecSet {
    CodecSet::new()
        .with_decoder(JpegDecoderConfig::new())
        .with_decoder(PngDecoderConfig::new())
        .with_send_encoder(PngEncoderConfig::new())
Return & continuation1 lines
}
Exact traits and signatures

Signatures from this revision · bodies omitted

Inherent methodszencodec::CodecSet
impl CodecSet {
    pub fn new() -> Self;

    pub fn with_decoder(mut self, config: impl DecoderConfig + 'static) -> Self;

    pub fn with_encoder<C>(mut self, config: C) -> Self
    where
        C: EncoderConfig + 'static,
        <C::Job as EncodeJob>::Enc: Encoder + Send,
        <C::Job as EncodeJob>::AnimationFrameEnc: AnimationFrameEncoder;

    pub fn with_send_encoder<C>(mut self, config: C) -> Self
    where
        C: EncoderConfig + 'static,
        C::Job: Send,
        <C::Job as EncodeJob>::Enc: Encoder + Send,
        <C::Job as EncodeJob>::AnimationFrameEnc: AnimationFrameEncoder;

    pub fn with_limits(mut self, limits: ResourceLimits) -> Self;

    pub fn with_pulse(mut self, pulse: how_far::SharedPulse) -> Self;

    pub fn pulse(&self) -> &dyn crate::Pulse;
}

Selected methods · 42 more in the full contract.

Read defaults, imports and full contract ↗
2

Show a preview.

Bytes are already resident. The default decode preserves the codec’s reported interpretation.

Implemented · review PRs · Rust
Source ↗
MoreNative tests ↗
Imports & setup10 lines
use zencodec::{
    At, CodecSet,
    decode::DecodeOutput,
    operation::{DecodeRequest, OperationError, Outcome},
};

pub fn preview(
    codecs: &CodecSet,
    bytes: &[u8],
) -> Result<Outcome<DecodeOutput>, At<OperationError>> {
    let image = DecodeRequest::new().decode(codecs, bytes)?;
Return & continuation3 lines
    // Keep image.pixels().descriptor() and image.info().source_color with the samples.
    Ok(image)
}

A required descriptor refuses a mismatch; a preference permits fallback. Actual color conversion uses zenpixels-convert. Keep color and orientation with the returned pixels.

Exact traits and signatures

Signatures from this revision · bodies omitted

Inherent methodszencodec::operation::DecodeRequest
impl DecodeRequest {
    pub const fn new() -> Self;

    pub fn with_output(mut self, descriptor: PixelDescriptor) -> Self;

    pub fn preferring_output(mut self, descriptors: impl Into<Vec<PixelDescriptor>>) -> Self;

    pub const fn with_policy(mut self, policy: DecodePolicy) -> Self;

    pub fn decode(
        &self,
        codecs: &CodecSet,
        input: &[u8],
    ) -> Result<Outcome<DecodeOutput>, At<OperationError>>;

    pub fn start_controlled(
        &self,
        codecs: &CodecSet,
        storage: crate::storage::StorageBudget,
    ) -> Result<Box<crate::decode::DynControlledDecoder>, At<OperationError>>;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
3

Create a derivative.

The application now wants a PNG with the reported source metadata retained.

Implemented · review PRs · Rust
Source ↗
MoreNative tests ↗
Imports & setup4 lines
use zencodec::{At, CodecSet, ImageFormat, encode::EncodeOutput, operation::Outcome};
use zencodecs::checked_transcode::{Error, TranscodeRequest};

pub fn derivative(codecs: &CodecSet, bytes: &[u8]) -> Result<Outcome<EncodeOutput>, At<Error>> {
    let output = TranscodeRequest::new(ImageFormat::Png).transcode(codecs, bytes)?;
Return & continuation5 lines
    // The built-in policy selects a permitted route without a custom callback.
    // Default retention can refuse when the target cannot preserve a reported field.
    // No file or HTTP body has been written by this operation.
    Ok(output)
}

The default preserves reported ICC/EXIF/XMP and rendering orientation. Required reported resolution must survive too; unsupported preservation refuses. This does not preserve unknown container packets or certify whole-container privacy.

4

Make the web export explicit.

Source profiles can contain private descriptions. Convert samples to known sRGB while excluding source descriptive metadata.

Implemented · review PRs · Rust
Source ↗
MoreNative tests ↗
Imports & setup9 lines
use zencodec::{
    ImageFormat,
    operation::{MetadataTransfer, PolicySet},
};
use zencodecs::checked_transcode::TranscodeRequest;
use zenpixels::{Cicp, ConvertOptions, PixelFormat};
use zenpixels_convert::{OutputProfile, cms_moxcms::MoxCms};

pub fn web_request() -> (TranscodeRequest<'static>, PolicySet) {
    let request = TranscodeRequest::new(ImageFormat::Png)
        .with_cms(&MoxCms)
        .with_output(
            OutputProfile::Named(Cicp::SRGB),
            PixelFormat::Rgb8,
            ConvertOptions::retain_channels(),
        )
        .with_metadata(MetadataTransfer::DiscardDescriptive);
    let policy = PolicySet::new()
        .require_metadata(MetadataTransfer::DiscardDescriptive)
        .allow_opaque_profiles(false);
    (request, policy)
Return & continuation3 lines
    // Accept JPEG's RGB samples here. Alpha removal and depth loss are not permitted.
    // Current color is converted; orientation needed for rendering is retained.
}

The request is reusable with slice, file or async input. RGB8 fits this JPEG route. Alpha loss and depth reduction remain forbidden. Discarded descriptions do not remove orientation needed to render correctly.

Exact traits and signatures

Signatures from this revision · bodies omitted

Inherent methodszencodec::operation::PolicySet
impl PolicySet {
    pub const fn new() -> Self;

    pub const fn prefer_lossless(mut self, enabled: bool) -> Self;

    pub const fn require_metadata(mut self, treatment: MetadataTransfer) -> Self;

    pub const fn allow_opaque_profiles(mut self, allowed: bool) -> Self;

    pub const fn with_workspace_limit(mut self, bytes: u64) -> Self;

    pub const fn allow_assumed_chroma_siting(mut self, allowed: bool) -> Self;

    pub const fn require_extended_range(mut self, required: bool) -> Self;

    pub const fn require_output_schedule(mut self, schedule: super::OutputSchedule) -> Self;

    pub const fn with_output_wait_limit(mut self, bytes: u64) -> Self;
}

Selected methods · 6 more in the full contract.

Read defaults, imports and full contract ↗
5

Apply a situational tenant rule.

Some tenants require qualified native workspace evidence. Their callback can refuse when the current route cannot provide it.

Implemented · review PRs · Rust
Source ↗
MoreNative tests ↗
Imports & setup23 lines
use zencodec::{
    At, CodecSet,
    encode::EncodeOutput,
    operation::{BoundPolicy, Decision, Outcome, PolicySet, Question},
};
use zencodecs::checked_transcode::{Error, TranscodeRequest};

#[derive(Debug)]
pub struct WorkspaceEvidenceRequired;
impl std::fmt::Display for WorkspaceEvidenceRequired {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("tenant requires a qualified native workspace bound")
    }
}
impl std::error::Error for WorkspaceEvidenceRequired {}

pub fn for_tenant(
    request: &TranscodeRequest<'_>,
    codecs: &CodecSet,
    bytes: &[u8],
    requirements: PolicySet,
    require_workspace_evidence: bool,
) -> Result<Outcome<EncodeOutput>, At<Error>> {
    let mut policy = BoundPolicy::new(
        requirements,
        |question: Question<'_>| -> Result<Decision, At<WorkspaceEvidenceRequired>> {
            if let Question::Transcode { choices, .. } = question
                && require_workspace_evidence
            {
                let choice = choices
                    .iter()
                    .find(|choice| choice.value().workspace_upper_bound.is_some())
                    .ok_or_else(|| At::wrap(WorkspaceEvidenceRequired))?;
                return Ok(Decision::choose(choice));
            }
            Ok(Decision::UsePreset)
        },
    );
    request.transcode_with(codecs, bytes, &mut policy)
Return & continuation3 lines
    // The current native route has an unknown workspace bound, so the strict
    // tenant gets this traced refusal. Managed row storage is not that bound.
}

The native route currently reports an unknown workspace bound, so the strict branch returns the application’s traced error. The preset offers the simpler with_workspace_limit rule. Unknown optional facts alone do not require the default policy to refuse. Later evidence can require another scoped choice before affected effects; a callback can choose a permitted adaptation while hard requirements remain in force. Continue discovery only while those effects remain withheld within admitted storage. Neither managed row bytes nor queue depth proves a whole-operation RAM cap.

Exact traits and signatures

Signatures from this revision · bodies omitted

Trait methodszencodec::operation::OperationPolicy
pub trait OperationPolicy: sealed::Sealed {
    type Error: Error + Send + Sync + 'static;

    fn requirements(&self) -> Option<PolicySet>;

    fn decide(&mut self, question: Question<'_>) -> Result<Decision, At<Self::Error>>;
}

Required methods: requirements, decide. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Inherent methodszencodec::operation::BoundPolicy
impl<R: PolicyResolver> BoundPolicy<R> {
    pub const fn new(requirements: PolicySet, resolver: R) -> Self;

    pub fn into_parts(self) -> (PolicySet, R);
}
Read defaults, imports and full contract ↗
Trait methodszencodec::operation::PolicyResolver
pub trait PolicyResolver {
    type Error: Error + Send + Sync + 'static;

    fn resolve(&mut self, question: Question<'_>) -> Result<Decision, At<Self::Error>>;
}

Required methods: resolve. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Encoding pixels directly

Use EncodeRequest for an existing pixel view. Fidelity alternatives are checked before the resolver sees them. Selection retains the same job, and a native execution failure does not silently retry another choice.

Signatures from this revision · bodies omitted

Inherent methodszencodec::operation::EncodeRequest
impl EncodeRequest {
    pub const fn new(format: ImageFormat) -> Self;

    pub fn with_fidelity(mut self, fidelity: Fidelity) -> Self;

    pub fn preferring_fidelity(mut self, targets: impl Into<Vec<Fidelity>>) -> Self;

    pub const fn with_resolution(mut self, resolution: crate::Resolution) -> Self;

    pub const fn with_orientation_mode(mut self, mode: crate::encode::EncodeOrientation) -> Self;

    pub const fn with_policy(mut self, policy: EncodePolicy) -> Self;

    pub const fn with_policy_set(mut self, preset: PolicySet) -> Self;

    pub fn encode(
        &self,
        codecs: &CodecSet,
        pixels: PixelSlice<'_>,
    ) -> Result<Outcome<EncodeOutput>, At<OperationError>>;

    pub fn encode_with<R: OperationPolicy + ?Sized>(
        &self,
        codecs: &CodecSet,
        pixels: PixelSlice<'_>,
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<OperationError>>;
}

Selected methods · 5 more in the full contract.

Read defaults, imports and full contract ↗

The gallery starts accepting clips.

Keep native planar samples and timing through the existing video session. The same resolver now sees each presentation before the caller, renderer or next encoder receives it.

Implemented · review PRs · Rust
Source ↗
MoreNative tests ↗
Imports & setup5 lines
use zenavif::media::Av1VideoDecoder;
use zencodec::operation::PolicySet;
use zencodec_media::policy::{Error, PolicyDecoder};

pub fn open_video() -> Result<PolicyDecoder<Av1VideoDecoder, PolicySet>, Error> {
    let decoder = PolicyDecoder::try_new(Av1VideoDecoder::new, PolicySet::new(), None)?;
Return & continuation4 lines
    // Configure and drive it through VideoDecoder; None adds no per-call Pulse.
    // Every receive checks policy before returning a native Frame.
    Ok(decoder)
}

The preview renderer accepts declared sRGB transfer. A later frame may carry another interpretation, so an application callback checks the held frame instead of trusting the latest configuration or an earlier capability query. This executable example refuses a later PQ frame and retains the typed application error.

Implemented · review PRs · Rust
Source ↗
MoreNative tests ↗
Imports & setup21 lines
use zenavif::media::{Av1SessionError, Av1VideoDecoder};
use zencodec::{
    At,
    operation::{Decision, OperationPolicy, Question},
};
use zencodec_media::policy::PolicyDecoder;

#[derive(Debug)]
pub struct PreviewTransfer;
impl core::fmt::Display for PreviewTransfer {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str("this preview route requires declared sRGB transfer")
    }
}
impl core::error::Error for PreviewTransfer {}

pub fn open_preview() -> Result<
    PolicyDecoder<Av1VideoDecoder, impl OperationPolicy<Error = PreviewTransfer>>,
    Av1SessionError,
> {
    let native = Av1VideoDecoder::new()?;
    let policy = |question: Question<'_>| -> Result<Decision, At<PreviewTransfer>> {
        if let Question::Frame { interpretation, .. } = question {
            let supported = interpretation
                .color
                .resolved
                .is_some_and(|color| color.transfer_characteristics == 13);
            if !supported {
                return Err(At::wrap(PreviewTransfer).at_named("preview transfer requirement"));
            }
        }
        Ok(Decision::UsePreset)
    };
    let decoder = PolicyDecoder::new(native, policy);
Return & continuation4 lines
    // This gate does not convert YUV/GBR, tone-map or prove a RAM bound.
    // The renderer must still validate its complete conversion route.
    Ok(decoder)
}
Exact traits and signatures

Signatures from this revision · bodies omitted

Inherent methodszencodec_media::policy::PolicyDecoder::try_new
impl<D: VideoDecoder, R: OperationPolicy> PolicyDecoder<D, R> {
    pub fn try_new<E: StdError + Send + Sync + 'static>(
        factory: impl FnOnce() -> Result<D, E>,
        resolver: R,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<Self, Error>;
}

Selected methods · 1 more in the full contract.

Read defaults, imports and full contract ↗
Inherent methodszencodec_media::policy::PolicyDecoder
impl<D, R> PolicyDecoder<D, R> {
    pub fn new(decoder: D, resolver: R) -> Self;

    pub fn with_policy_set(mut self, preset: PolicySet) -> Self;

    pub fn report(&self) -> &OperationReport;

    pub const fn is_failed(&self) -> bool;
}
Read defaults, imports and full contract ↗
Trait methodszencodec::operation::PolicyResolver
pub trait PolicyResolver {
    type Error: Error + Send + Sync + 'static;

    fn resolve(&mut self, question: Question<'_>) -> Result<Decision, At<Self::Error>>;
}

Required methods: resolve. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗

This gate preserves native samples and facts. It does not render them or prove container privacy. Native allocation has already occurred. Preparing a complete planar conversion/encoding route remains separate work.

Put a native picture allowance before reconstruction

A publication callback is too late to prevent the picture allocation. Configure the native decoder first. The allowance follows reference pictures and caller-held frames, including after decoder reset or drop. Native refusal returns its original traced cause without calling publication policy.

Implemented · review PRs · Rust
Source ↗
MoreNative tests ↗
Imports & setup7 lines
use zenavif::media::{Av1SessionError, Av1VideoDecoder};
use zencodec::operation::PolicySet;
use zencodec_media::policy::PolicyDecoder;

pub fn open_video_with_picture_limit(
    picture_bytes: usize,
) -> Result<PolicyDecoder<Av1VideoDecoder, PolicySet>, Av1SessionError> {
    let mut settings = rav1d_safe::Settings::default();
    settings.max_picture_bytes = Some(picture_bytes);
    let native = Av1VideoDecoder::with_settings(settings)?;
    let decoder = PolicyDecoder::new(native, PolicySet::new());
Return & continuation5 lines
    // Native admission precedes reconstruction. Policy precedes publication.
    // The allowance follows retained frames, including after decoder drop.
    // This bounds requested native plane bytes, not workspace or total RAM.
    Ok(decoder)
}

This bounds requested picture-plane bytes, including padding and alignment. Work buffers, input, metadata and allocator overhead are outside the allowance. Bounded mode frees plane buffers instead of caching them. It is not a total-memory promise.

02 / Adapter

Own the connection and the complete route.

The adapter prepares the conversion, metadata handoff, encoder job and admitted row buffers together. It offers those retained objects to policy while the decoder is held at a final interpretation boundary.

Final source facts: decoder remains suspended → Prepared route: conversion + metadata + encoder → Choose or refuse: typed offer + traced resultIMPLEMENTORFinal source factsdecoder remains suspendedADAPTERPrepared routeconversion + metadata + encoderCONSUMERChoose or refusetyped offer + traced result
implementorFinal source factsdecoder remains suspended
adapterPrepared routeconversion + metadata + encoder
consumerChoose or refusetyped offer + traced result
Final source facts → Prepared route → Choose or refuse

The same request, from disk.

FileReadAt uses positional input. It preserves the source’s native error through the operation boundary.

Implemented · review PRs · Rust
Source ↗
MoreNative tests ↗
Imports & setup13 lines
use zencodec::{
    CodecSet,
    encode::EncodeOutput,
    operation::{OperationPolicy, Outcome},
};
use zencodecs::checked_transcode::TranscodeRequest;

pub fn from_file<P: OperationPolicy + ?Sized>(
    request: &TranscodeRequest<'_>,
    codecs: &CodecSet,
    path: &std::path::Path,
    policy: &mut P,
) -> Result<Outcome<EncodeOutput>, Box<dyn std::error::Error + Send + Sync>> {
    let mut source = zencodec_io::FileReadAt::open(path)?;
    let output = request.transcode_from_read_at_with(codecs, &mut source, policy)?;
Return & continuation3 lines
    // FileReadAt keeps positional reads below the codec; the request is unchanged.
    Ok(output)
}

The same request, from a network source.

AsyncSource supplies byte demand. Delayed short reads and unknown lengths use the same policy and output checks.

Implemented · review PRs · Rust
Source ↗
MoreNative tests ↗
Imports & setup15 lines
use zencodec::{
    At, CodecSet,
    encode::EncodeOutput,
    operation::{OperationPolicy, Outcome},
};
use zencodec_io::tokio_io::AsyncSource;
use zencodecs::checked_transcode::Error;
use zencodecs::checked_transcode::TranscodeRequest;

pub async fn from_network<S: AsyncSource + ?Sized, P: OperationPolicy + ?Sized>(
    request: &TranscodeRequest<'_>,
    codecs: &CodecSet,
    source: &mut S,
    policy: &mut P,
) -> Result<Outcome<EncodeOutput>, At<Error>> {
    let output = request.transcode_async_with(codecs, source, policy).await?;
Return & continuation4 lines
    // Send the finished, checked bytes afterwards. Codec CPU work runs on
    // this task: use a suitable worker runtime for expensive configurations.
    Ok(output)
}
Exact traits and signatures

Signatures from this revision · bodies omitted

Trait methodszencodec::source::ReadAt
pub trait ReadAt {
    type Error: core::error::Error + Send + Sync + 'static;

    fn len(&mut self) -> Result<u64, Self::Error>;

    fn len_hint(&self) -> Option<u64>;

    fn read_exact_at(&mut self, offset: u64, out: &mut [u8]) -> Result<(), Self::Error>;

    fn read_at(&mut self, offset: u64, out: &mut [u8]) -> Result<usize, Self::Error>;

    fn read_many_at(&mut self, reqs: &mut [(u64, &mut [u8])]) -> Result<(), Self::Error>;

    fn as_slice(&self) -> Option<&[u8]>;
}

Selected methods · 1 more in the full contract.

Required methods: len, read_exact_at. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Trait methods · zencodec-io feature: tokiozencodec_io::tokio_io::AsyncSource
pub trait AsyncSource: Send {
    fn read_at(
        &mut self,
        offset: u64,
        out: &mut [u8],
    ) -> impl Future<Output = Result<usize, BoxedError>> + Send;

    fn read_ranges(
        &mut self,
        reqs: &mut [(u64, &mut [u8])],
    ) -> impl Future<Output = Result<(), BoxedError>> + Send;

    fn len_hint(&self) -> Option<u64>;

    fn len(&mut self) -> impl Future<Output = Result<u64, BoxedError>> + Send;
}

Required methods: read_at, len. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Inherent methodszencodec::operation::EncodeRequest (adapter)
impl EncodeRequest {
    pub const fn with_policy_defaults(mut self, defaults: EncodePolicy) -> Self;

    pub fn prepare(&self, codecs: &CodecSet) -> Result<Vec<PreparedEncode>, At<OperationError>>;

    pub fn prepare_send(
        &self,
        codecs: &CodecSet,
    ) -> Result<Vec<PreparedSendEncode>, At<OperationError>>;
}

Selected methods · 11 more in the full contract.

Read defaults, imports and full contract ↗
Adapt an existing Read + Seek parser

A native TIFF parser can reuse positional disk or range input through ReadAtReader. This executed example probes the first page’s stored dimensions, applying native limits before directory parsing.

Implemented · review PRs · Rust
Source ↗
MoreNative tests ↗
Imports & setup9 lines
use std::path::Path;
use tiff::decoder::{Decoder, Limits};
use zencodec_io::{FileReadAt, ReadAtReader};

pub fn stored_dimensions(
    path: &Path,
    native_limits: Limits,
) -> Result<(u32, u32), Box<dyn std::error::Error + Send + Sync>> {
    let source = FileReadAt::open(path)?;
    let input = ReadAtReader::new(source);
Return & continuation5 lines
    // TIFF's header-only open precedes directory parsing, so limits apply first.
    let mut decoder = Decoder::open(input)?.with_limits(native_limits);
    decoder.next_image()?;
    Ok(decoder.dimensions()?)
}

Signatures from this revision · bodies omitted

Inherent methodszencodec_io::ReadAtReader
impl<S> ReadAtReader<S> {
    pub const fn new(source: S) -> Self;

    pub fn into_inner(self) -> S;
}
Read defaults, imports and full contract ↗
Inherent methodsstd::io::Seek for zencodec_io::ReadAtReader
impl<S: ReadAt> Seek for ReadAtReader<S> {
    fn seek(&mut self, to: SeekFrom) -> io::Result<u64>;

    fn stream_position(&mut self) -> io::Result<u64>;
}
Read defaults, imports and full contract ↗

The bridge adds no buffer or async suspension. End-relative seeks may consume a sequential source to discover its length. Native TIFF measurements show that adding a fixed buffer reduces source calls but increases transferred bytes; choose that capacity with the transport plan. This geometry probe does not establish color, privacy or a total memory bound. Read the native page, level, region and wide-sample qualification.

Adapter defaults fill unspecified fields. Explicit caller policies take priority, and unsupported requirements refuse before source input. The prepared job receives those checked policies exactly once.

All implemented TranscodeRequest methods

Extracted from the named source declarations at f0322466. The build requires that revision to match the compiled examples.

use zencodec::{
    At, CodecSet, ImageFormat,
    encode::EncodeOutput,
    operation::{self, OperationReport, Outcome, OperationPolicy, PolicySet, MetadataTransfer},
    storage::StorageBudget,
};
use zencodec_io::{DriveOptions, tokio_io::AsyncSource};
use zenpixels::{ConvertOptions, PixelFormat};
use zenpixels_convert::{OutputProfile, cms::PluggableCms};
use zencodecs::checked_transcode::{Error, Stage, TranscodeRequest};

impl<'a> TranscodeRequest<'a> {
    pub fn new(format: ImageFormat) -> Self;

    pub fn preferring_fidelity(mut self, targets: Vec<zencodec::encode::Fidelity>) -> Self;

    pub fn transcode(
        &self,
        codecs: &CodecSet,
        input: &[u8],
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn transcode_from_read_at<S: zencodec::source::ReadAt + ?Sized>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn transcode_from_read_at_with<S, R>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>
    where
        S: zencodec::source::ReadAt + ?Sized,
        R: OperationPolicy + ?Sized;

    pub fn transcode_with<R: OperationPolicy + ?Sized>(
        &self,
        codecs: &CodecSet,
        input: &[u8],
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub fn with_cms(mut self, cms: &'a dyn PluggableCms) -> Self;

    pub fn with_decode(mut self, request: operation::DecodeRequest) -> Self;

    pub fn with_encode_policy(mut self, policy: zencodec::encode::EncodePolicy) -> Self;

    pub fn with_fidelity(mut self, target: zencodec::encode::Fidelity) -> Self;

    pub fn with_io_options(mut self, options: DriveOptions) -> Self;

    pub fn with_metadata(mut self, policy: MetadataTransfer) -> Self;

    pub fn with_output(
        mut self,
        profile: OutputProfile,
        format: PixelFormat,
        options: ConvertOptions,
    ) -> Self;

    pub fn with_output_limit(mut self, bytes: usize) -> Self;

    pub fn with_policy_set(mut self, policy: PolicySet) -> Self;

    pub fn with_storage(mut self, storage: StorageBudget) -> Self;

    pub fn with_strip_rows(mut self, rows: u32) -> Self;
}

impl TranscodeRequest<'_> {
    pub async fn transcode_async<S: AsyncSource + ?Sized>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>;

    pub async fn transcode_async_with<S, R>(
        &self,
        codecs: &CodecSet,
        source: &mut S,
        resolver: &mut R,
    ) -> Result<Outcome<EncodeOutput>, At<Error>>
    where
        S: AsyncSource + ?Sized,
        R: OperationPolicy + ?Sized;
}

impl Error {
    pub fn stage(&self) -> Stage;

    pub fn report(&self) -> &OperationReport;
}
New interpretation factsStop before affected sink, conversion or encoder effects.
Are there final facts and a permitted executable route?
Not yetHold within admitted capacityGather allowed input; return to the decision when relevant facts change.
YesResolve the typed questionChoose a current offer, delegate once to the preset, or return a traced error.
NoRefuse before affected effectsKeep the requirement and concrete cause. Do not weaken it to a preference.
The callback handles relevant choices. It is not invoked for every row, I/O wait or CPU scheduling yield.

Output waiting follows retained ownership.

This transcode completes the encoder, releases its row/native state, inspects the output, and then returns bytes. It never awaits a writer while the encoder holds uncompressed state. Encoded allocation is still unqualified: with_output_limit limits publication, not native allocation. A future truly sequential route needs a proven retained-state bound and pausable upstream input before a slow writer may backpressure it.

Async source I/O and native CPU work run on the polling task. Work quotas yield between complete steps; an expensive codec step needs a suitable worker runtime. The complete default-policy future is Send. No additional general Read/Seek hierarchy is needed for this path.

Video holds the actual presentation during policy.

Reconstruct native frame: retain accepted packet context → Hold picture + facts: same owner, clock and epoch → Approve or refuse: before downstream publicationIMPLEMENTORReconstruct native frameretain accepted packet contextADAPTERHold picture + factssame owner, clock and epochCONSUMERApprove or refusebefore downstream publication
implementorReconstruct native frameretain accepted packet context
adapterHold picture + factssame owner, clock and epoch
consumerApprove or refusebefore downstream publication
Reconstruct native frame → Hold picture + facts → Approve or refuse

Every presentation gets a scoped question, including multiple presentations from one packet. Refusal, cancellation, stale selection or a caught callback panic drops the native decoder and unpublished frames. Earlier returned owners remain valid. This terminal operation requires a new decoder after failure; a healthy session still supports reset.

Exact traits and signatures

Signatures from this revision · bodies omitted

Functionzencodec::operation::resolve_frame
pub fn resolve_frame<R: OperationPolicy + ?Sized>(
    interpretation: &FrameInterpretation,
    timing: FrameTiming,
    resolver: &mut R,
    preset: PolicySet,
    pulse: Option<&dyn crate::Pulse>,
) -> Result<DecisionRecord, At<PolicyFailure>>;
Read defaults, imports and full contract ↗

03 / Implementor

Expose facts and resumable mechanisms.

A codec implements its native progress and exact pre-effect suspension points. It does not own application policy, HTTP caching or the CMS. The existing config/job factory creates a controlled decoder, and the driver carries its byte requests.

Keep the original optional borrowed control through forwarding and erasure. Resolve None only for local checks or private helpers; replacing it with Some(NoPulse) can hide absence from a native adapter. Retained job cancellation remains active even with an explicit per-call no-op. Framework-supplied callback controls and borrowed getters remain resolved controls.

Signatures from this revision · bodies omitted

Trait methodszencodec::decode::ControlledIncrementalDecode
pub trait ControlledIncrementalDecode: IncrementalDecode {
    fn step_controlled(
        &mut self,
        input: Input<'_>,
        sink: &mut dyn DecodeRowSink,
        pulse: Option<&dyn crate::Pulse>,
    ) -> Result<ControlledProgress, StepError<Self::Error>>;

    fn resume(
        &mut self,
        boundary: BoundaryId,
        action: BoundaryAction,
        pulse: Option<&dyn crate::Pulse>,
    ) -> Result<(), StepError<Self::Error>>;
}

Required methods: step_controlled, resume. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Trait methodszencodec::decode::IncrementalDecode
pub trait IncrementalDecode {
    type Error: core::error::Error + Send + Sync + 'static;

    fn step(
        &mut self,
        input: Input<'_>,
        sink: &mut dyn DecodeRowSink,
        pulse: Option<&dyn crate::Pulse>,
    ) -> Result<Progress, StepError<Self::Error>>;

    fn requested(&self) -> &[InputRequest];

    fn info(&self) -> Option<&ImageInfo>;

    fn take_details(&mut self) -> Option<crate::decode::DecodeDetails>;

    fn access_pattern(&self) -> crate::source::AccessPattern;
}

Selected methods · 1 more in the full contract.

Required methods: step, requested, info. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗
Suspend with identity: provisional / final / unavailable → Validate scoped reply: same operation and facts → Resume exact state: rows retain interpretationIMPLEMENTORSuspend with identityprovisional / final / unavailableADAPTERValidate scoped replysame operation and factsIMPLEMENTORResume exact staterows retain interpretation
implementorSuspend with identityprovisional / final / unavailable
adapterValidate scoped replysame operation and facts
implementorResume exact staterows retain interpretation
Suspend with identity → Validate scoped reply → Resume exact state

The current JPEG and PNG implementations retain the entire compressed source before final adoption. Native baseline/progressive JPEG, PNG row decoding and Adam7 output are qualified; native byte-resuming reconstruction is not. PNG gain-map carriers, unknown extensions and APNG refuse this primary-image route. The explicit boundary still prevents premature conversion and encoder initialization. Failed and cancelled sessions release their managed owners.

Video keeps its existing session protocol

Signatures from this revision · bodies omitted

Trait methodszencodec_media::session::VideoDecoder
pub trait VideoDecoder {
    type Error: core::error::Error + Send + Sync + 'static;

    fn configure(
        &mut self,
        config: &CodecConfig,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<(), SessionError<Self::Error>>;

    fn send(
        &mut self,
        packet: &mut Packet,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<SendStatus, SessionError<Self::Error>>;

    fn receive(
        &mut self,
        pulse: Option<&dyn zencodec::Pulse>,
    ) -> Result<Receive<Frame>, SessionError<Self::Error>>;

    fn end_input(&mut self) -> Result<(), SessionError<Self::Error>>;

    fn reset(&mut self) -> Result<(), SessionError<Self::Error>>;
}

Selected methods · 1 more in the full contract.

Required methods: configure, send, receive, end_input, reset, stream_info. Other methods have defaults; read them before relying on their behavior.

Read defaults, imports and full contract ↗

Send/receive progress, drain/reset, immutable frame interpretation and retained old-epoch owners keep their contracts. Native AV1 packet context and policy publication are qualified on zenavif PR 74 and rav1d-safe PR 537. Native picture-plane admission precedes allocation; complete workspace admission and coupled conversion reconfiguration still need implementation. Read the native scope and tests.

Goal / backport boundary

Keep the ideal distinct from the migration.

Ideal contractExecutable todayRemaining gate
Ordinary checked calls; capability queries optionalStored decode/encode requests and coupled pixel transcodeOther content and codec configurations; then retire redundant best-effort coordination
One fallible resolver with typed domain offersEncode, interpretation boundary, native frame publication and complete pixel-transcode choicesEncoded rewrite and whole-container disclosure choices
Transferable jobs on the ordinary async pathAdditive with_send_encoder / prepare_send0.3 can make Send the ordinary registry default; retain an explicit local path where needed
Ownership-based resource guaranteesManaged source/rows, packet/frame owners, input allowances and CPU quotasNative/CMS workspace, encoded staging and qualified sequential publication
Required metadata survives or fails accuratelyReported color, orientation, EXIF/XMP and output checks; exact native PNG density carriageRemaining resolution cases, opaque/nested inspection scope and disclosure proof
Native reconfiguration and spatial demandNative AV1 packet provenance, per-presentation policy, native plane admission, source ranges and retained frame ownershipCoupled video conversion and complete native workspace admission; item + level + region selection bound to a source version

The review manifest is 0.2.0. Required owned SharedPulse job methods intentionally break implementor compatibility with 0.1.26; borrowed per-call controls are optional. This page does not announce a release. Other renames and removals still require coordinated consumer migration.

Argon’s most useful comparisons were libjxl/Wuffs pre-effect suspension, FFmpeg progress and drain, GStreamer/WebCodecs retained ownership, and libvips/OpenImageIO spatial selection. Read the audited review and primary sources. The verified implementation record lives in ideal-api.md.