Skip to main content

uor_foundation/
pipeline.rs

1// @generated by uor-crate from uor-ontology — do not edit manually
2
3//! Reduction Pipeline — no_std in-process driver.
4//!
5//! Backs `Certify::certify` on every resolver façade and (re-exported
6//! via the macros crate) the `uor_ground!` macro's compile-time pipeline.
7//!
8//! The driver implements the full reduction pipeline per
9//! `external/ergonomics-spec.md` §3.3 and §4: 6 preflight checks,
10//! 7 reduction stages, 4 resolver backends, real 2-SAT and Horn-SAT
11//! deciders, fragment classifier, content-addressed unit-ids.
12//!
13//! Every entry point is ontology-driven: IRIs, stage order, and
14//! dispatch-table rules are baked in at codegen time from the
15//! ontology graph. Adding a new preflight check or resolver is a
16//! pure ontology edit.
17
18use crate::enforcement::{
19    BindingEntry, BindingsTable, CompileTime, CompileUnit, CompileUnitBuilder,
20    CompletenessCertificate, ConstrainedTypeInput, GenericImpossibilityWitness, Grounded,
21    GroundingCertificate, InhabitanceCertificate, InhabitanceImpossibilityWitness,
22    LeaseDeclaration, LeaseDeclarationBuilder, LiftChainCertificate, MultiplicationCertificate,
23    ParallelDeclarationBuilder, PipelineFailure, ShapeViolation, StreamDeclarationBuilder, Term,
24    Validated,
25};
26use crate::ViolationKind;
27use crate::WittLevel;
28
29/// Zero-based preflight check order read from `reduction:PreflightCheck`
30/// individuals at codegen time. `BudgetSolvencyCheck` MUST be index 0 per
31/// `reduction:preflightOrder` — enforced by the ontology, not here.
32pub const PREFLIGHT_CHECK_IRIS: &[&str] = &[
33    "https://uor.foundation/reduction/BudgetSolvencyCheck",
34    "https://uor.foundation/reduction/FeasibilityCheck",
35    "https://uor.foundation/reduction/DispatchCoverageCheck",
36    "https://uor.foundation/reduction/PackageCoherenceCheck",
37    "https://uor.foundation/reduction/PreflightTiming",
38    "https://uor.foundation/reduction/RuntimeTiming",
39];
40
41/// Seven reduction stages in declared order, sourced from
42/// `reduction:ReductionStep` individuals.
43pub const REDUCTION_STAGE_IRIS: &[&str] = &[
44    "https://uor.foundation/reduction/stage_initialization",
45    "https://uor.foundation/reduction/stage_declare",
46    "https://uor.foundation/reduction/stage_factorize",
47    "https://uor.foundation/reduction/stage_resolve",
48    "https://uor.foundation/reduction/stage_attest",
49    "https://uor.foundation/reduction/stage_extract",
50    "https://uor.foundation/reduction/stage_convergence",
51];
52
53/// Phase 17: maximum number of `i64` coefficients an `Affine`
54/// constraint can carry. Stable-Rust const evaluation cannot allocate a
55/// new `&'static [i64]` at compile time, so `Affine` stores a fixed-
56/// size buffer + an active prefix length. Aligned with the foundation's
57/// 8-wide capacity caps (`MAX_BETTI_DIMENSION` / `JACOBIAN_MAX_SITES` /
58/// `NERVE_CONSTRAINTS_CAP`).
59/// Wiki ADR-037: the canonical source of truth for this cap is
60/// [`HostBounds::AFFINE_COEFFS_MAX`]; this `pub const` is a foundation-
61/// internal convenience alias derived from [`DefaultHostBounds`] for
62/// stable-Rust array-size positions. Applications declaring a custom
63/// `HostBounds` impl read `<MyBounds as HostBounds>::AFFINE_COEFFS_MAX`
64/// at instantiation sites instead.
65pub const AFFINE_MAX_COEFFS: usize =
66    <crate::DefaultHostBounds as crate::HostBounds>::AFFINE_COEFFS_MAX;
67
68/// Phase 17: maximum number of `LeafConstraintRef` conjuncts a
69/// `Conjunction` can carry. Same reasoning as `AFFINE_MAX_COEFFS`.
70/// Wiki ADR-037: alias of [`HostBounds::CONJUNCTION_TERMS_MAX`] via
71/// [`DefaultHostBounds`].
72pub const CONJUNCTION_MAX_TERMS: usize =
73    <crate::DefaultHostBounds as crate::HostBounds>::CONJUNCTION_TERMS_MAX;
74
75/// Opaque constraint reference carried by `ConstrainedTypeShape` impls.
76/// Variants mirror the v0.2.1 `type:Constraint` enumerated subclasses
77/// (retained as ergonomic aliases for the SAT pipeline) plus the v0.2.2
78/// Phase D parametric form (`Bound` / `Conjunction`) which references
79/// `BoundConstraint` kinds by their (observable, shape) IRIs. The
80/// `SatClauses` variant carries a compact 2-SAT/Horn-SAT clause list.
81/// **Phase 17 — fixed-array Affine and Conjunction.** The pre-Phase-17
82/// `Affine { coefficients: &'static [i64], … }` and
83/// `Conjunction { conjuncts: &'static [ConstraintRef] }` have been
84/// replaced with fixed-capacity arrays of length `AFFINE_MAX_COEFFS`
85/// and `CONJUNCTION_MAX_TERMS` respectively. Stable Rust const
86/// evaluation can build these inline; the SDK macros now support
87/// Affine-bearing operands without falling back to the
88/// `Site { position: u32::MAX }` sentinel. `Conjunction` is depth-
89/// limited to one level: its conjuncts are `LeafConstraintRef`
90/// (every variant of `ConstraintRef` except `Conjunction` itself).
91#[derive(Debug, Clone, Copy)]
92#[non_exhaustive]
93#[allow(clippy::large_enum_variant)]
94pub enum ConstraintRef {
95    /// `type:ResidueConstraint`: value ≡ residue (mod modulus).
96    Residue { modulus: u64, residue: u64 },
97    /// `type:HammingConstraint`: bit-weight bound.
98    Hamming { bound: u32 },
99    /// `type:DepthConstraint`: site-depth bound.
100    Depth { min: u32, max: u32 },
101    /// `type:CarryConstraint`: carry-bit relation.
102    Carry { site: u32 },
103    /// `type:SiteConstraint`: site-position restriction.
104    Site { position: u32 },
105    /// `type:AffineConstraint`: affine relation over sites.
106    /// `coefficients[..coefficient_count as usize]` is the active prefix;
107    /// trailing entries are unused and must be zero.
108    Affine {
109        coefficients: [i64; AFFINE_MAX_COEFFS],
110        coefficient_count: u32,
111        bias: i64,
112    },
113    /// Opaque clause list for 2-SAT / Horn-SAT inputs.
114    /// Each clause is a slice of `(variable_index, is_negated)`.
115    SatClauses {
116        clauses: &'static [&'static [(u32, bool)]],
117        num_vars: u32,
118    },
119    /// v0.2.2 Phase D / T2.2 (cleanup): parametric `BoundConstraint`
120    /// kind reference. Selects an (observable, shape) pair from the
121    /// closed Phase D catalogue. The args_repr string carries the
122    /// kind-specific parameters in canonical form.
123    Bound {
124        observable_iri: &'static str,
125        bound_shape_iri: &'static str,
126        args_repr: &'static str,
127    },
128    /// v0.2.2 Phase D / T2.2 (cleanup): parametric `Conjunction`.
129    /// Phase 17: depth-limited to one level — conjuncts are
130    /// `LeafConstraintRef` (every `ConstraintRef` variant except
131    /// `Conjunction` itself). Active prefix is
132    /// `conjuncts[..conjunct_count as usize]`.
133    Conjunction {
134        conjuncts: [LeafConstraintRef; CONJUNCTION_MAX_TERMS],
135        conjunct_count: u32,
136    },
137}
138
139/// `ConstraintRef` minus the recursive `Conjunction` variant — the
140/// element type of `ConstraintRef::Conjunction.conjuncts`. Phase 17
141/// caps Conjunction depth at one level; deeper structure must be
142/// flattened by the constructor.
143#[derive(Debug, Clone, Copy)]
144#[non_exhaustive]
145pub enum LeafConstraintRef {
146    /// See [`ConstraintRef::Residue`].
147    Residue { modulus: u64, residue: u64 },
148    /// See [`ConstraintRef::Hamming`].
149    Hamming { bound: u32 },
150    /// See [`ConstraintRef::Depth`].
151    Depth { min: u32, max: u32 },
152    /// See [`ConstraintRef::Carry`].
153    Carry { site: u32 },
154    /// See [`ConstraintRef::Site`].
155    Site { position: u32 },
156    /// See [`ConstraintRef::Affine`].
157    Affine {
158        coefficients: [i64; AFFINE_MAX_COEFFS],
159        coefficient_count: u32,
160        bias: i64,
161    },
162    /// See [`ConstraintRef::SatClauses`].
163    SatClauses {
164        clauses: &'static [&'static [(u32, bool)]],
165        num_vars: u32,
166    },
167    /// See [`ConstraintRef::Bound`].
168    Bound {
169        observable_iri: &'static str,
170        bound_shape_iri: &'static str,
171        args_repr: &'static str,
172    },
173}
174
175/// Project a non-`Conjunction` [`ConstraintRef`] into the
176/// [`LeafConstraintRef`] subtype. Returns a `Site { position: 0 }`
177/// placeholder if `self` is `Conjunction` (the only non-injective
178/// case); callers should flatten Conjunction structure before
179/// calling.
180impl ConstraintRef {
181    /// Phase 17 — leaf projection.
182    #[inline]
183    #[must_use]
184    pub const fn as_leaf(self) -> LeafConstraintRef {
185        match self {
186            ConstraintRef::Residue { modulus, residue } => {
187                LeafConstraintRef::Residue { modulus, residue }
188            }
189            ConstraintRef::Hamming { bound } => LeafConstraintRef::Hamming { bound },
190            ConstraintRef::Depth { min, max } => LeafConstraintRef::Depth { min, max },
191            ConstraintRef::Carry { site } => LeafConstraintRef::Carry { site },
192            ConstraintRef::Site { position } => LeafConstraintRef::Site { position },
193            ConstraintRef::Affine {
194                coefficients,
195                coefficient_count,
196                bias,
197            } => LeafConstraintRef::Affine {
198                coefficients,
199                coefficient_count,
200                bias,
201            },
202            ConstraintRef::SatClauses { clauses, num_vars } => {
203                LeafConstraintRef::SatClauses { clauses, num_vars }
204            }
205            ConstraintRef::Bound {
206                observable_iri,
207                bound_shape_iri,
208                args_repr,
209            } => LeafConstraintRef::Bound {
210                observable_iri,
211                bound_shape_iri,
212                args_repr,
213            },
214            ConstraintRef::Conjunction { .. } => LeafConstraintRef::Site { position: 0 },
215        }
216    }
217}
218
219impl LeafConstraintRef {
220    /// Phase 17 — embed a leaf into the parent `ConstraintRef` enum.
221    #[inline]
222    #[must_use]
223    pub const fn into_constraint(self) -> ConstraintRef {
224        match self {
225            LeafConstraintRef::Residue { modulus, residue } => {
226                ConstraintRef::Residue { modulus, residue }
227            }
228            LeafConstraintRef::Hamming { bound } => ConstraintRef::Hamming { bound },
229            LeafConstraintRef::Depth { min, max } => ConstraintRef::Depth { min, max },
230            LeafConstraintRef::Carry { site } => ConstraintRef::Carry { site },
231            LeafConstraintRef::Site { position } => ConstraintRef::Site { position },
232            LeafConstraintRef::Affine {
233                coefficients,
234                coefficient_count,
235                bias,
236            } => ConstraintRef::Affine {
237                coefficients,
238                coefficient_count,
239                bias,
240            },
241            LeafConstraintRef::SatClauses { clauses, num_vars } => {
242                ConstraintRef::SatClauses { clauses, num_vars }
243            }
244            LeafConstraintRef::Bound {
245                observable_iri,
246                bound_shape_iri,
247                args_repr,
248            } => ConstraintRef::Bound {
249                observable_iri,
250                bound_shape_iri,
251                args_repr,
252            },
253        }
254    }
255}
256
257/// Workstream E (target §1.5 + §4.7, v0.2.2 closure): crate-internal
258/// dispatch helper that maps every `ConstraintRef` variant to its
259/// canonical CNF clause encoding. No variant returns `None` — the
260/// closed six-kind set is fully executable.
261/// - `SatClauses`: pass-through of the caller's CNF.
262/// - `Residue` / `Carry` / `Depth` / `Hamming` / `Site`: direct-
263///   decidable at preflight; encoder emits an empty clause list
264///   (trivially SAT — unsatisfiable ones are rejected earlier).
265/// - `Affine`: single-row consistency check over Z/(2^n)Z; emits
266///   empty clauses when consistent, a 2-literal contradiction
267///   sentinel (forcing 2-SAT rejection) when not.
268/// - `Bound`: parametric form; emits empty clauses (per-bound-kind
269///   decision procedures consume the observable/bound-shape IRIs).
270/// - `Conjunction`: satisfiable iff every conjunct is satisfiable.
271#[inline]
272#[must_use]
273#[allow(dead_code)]
274pub(crate) const fn encode_constraint_to_clauses(
275    constraint: &ConstraintRef,
276) -> Option<&'static [&'static [(u32, bool)]]> {
277    const EMPTY: &[&[(u32, bool)]] = &[];
278    const UNSAT_SENTINEL: &[&[(u32, bool)]] = &[&[(0u32, false)], &[(0u32, true)]];
279    match constraint {
280        ConstraintRef::SatClauses { clauses, .. } => Some(clauses),
281        ConstraintRef::Residue { .. }
282        | ConstraintRef::Carry { .. }
283        | ConstraintRef::Depth { .. }
284        | ConstraintRef::Hamming { .. }
285        | ConstraintRef::Site { .. } => Some(EMPTY),
286        ConstraintRef::Affine {
287            coefficients,
288            coefficient_count,
289            bias,
290        } => {
291            if is_affine_consistent(coefficients, *coefficient_count, *bias) {
292                Some(EMPTY)
293            } else {
294                Some(UNSAT_SENTINEL)
295            }
296        }
297        ConstraintRef::Bound { .. } => Some(EMPTY),
298        ConstraintRef::Conjunction {
299            conjuncts,
300            conjunct_count,
301        } => {
302            if conjunction_all_sat(conjuncts, *conjunct_count) {
303                Some(EMPTY)
304            } else {
305                Some(UNSAT_SENTINEL)
306            }
307        }
308    }
309}
310
311/// Workstream E: single-row consistency for `Affine { coefficients,
312/// coefficient_count, bias }`. The constraint is
313/// `sum(c_i) · x = bias (mod 2^n)`; when the coefficient sum is
314/// zero the system is consistent iff bias is zero. Non-zero sums
315/// are always consistent over Z/(2^n)Z for some `x` value. Iterates
316/// only the active prefix `coefficients[..coefficient_count as usize]`.
317#[inline]
318#[must_use]
319const fn is_affine_consistent(
320    coefficients: &[i64; AFFINE_MAX_COEFFS],
321    coefficient_count: u32,
322    bias: i64,
323) -> bool {
324    let mut sum: i128 = 0;
325    let count = coefficient_count as usize;
326    let mut i = 0;
327    while i < count && i < AFFINE_MAX_COEFFS {
328        sum += coefficients[i] as i128;
329        i += 1;
330    }
331    if sum == 0 {
332        bias == 0
333    } else {
334        true
335    }
336}
337
338/// Workstream E + Phase 17: satisfiability of a `Conjunction`.
339/// Iterates only the active prefix `conjuncts[..conjunct_count as
340/// usize]` and lifts each `LeafConstraintRef` back to a
341/// `ConstraintRef` for re-encoding (the leaf form omits Conjunction,
342/// so this terminates at depth 1).
343#[inline]
344#[must_use]
345const fn conjunction_all_sat(
346    conjuncts: &[LeafConstraintRef; CONJUNCTION_MAX_TERMS],
347    conjunct_count: u32,
348) -> bool {
349    let count = conjunct_count as usize;
350    let mut i = 0;
351    while i < count && i < CONJUNCTION_MAX_TERMS {
352        let lifted = conjuncts[i].into_constraint();
353        match encode_constraint_to_clauses(&lifted) {
354            Some([]) => {}
355            _ => return false,
356        }
357        i += 1;
358    }
359    true
360}
361
362/// Declarative shape of a constrained type that can be admitted into the
363/// reduction pipeline.
364/// Downstream authors implement this trait on zero-sized marker types to
365/// declare the `(IRI, SITE_COUNT, CONSTRAINTS)` triple of a custom
366/// constrained type. The foundation admits shapes into the pipeline via
367/// [`validate_constrained_type`] / [`validate_constrained_type_const`],
368/// which run the full preflight (`preflight_feasibility` +
369/// `preflight_package_coherence`) against `Self::CONSTRAINTS` before
370/// returning a [`Validated`] wrapper.
371/// Sealing of witness construction lives on [`Validated`] and [`Grounded`]
372/// — only foundation admission functions mint either. Downstream is free
373/// to implement `ConstrainedTypeShape` for arbitrary shape markers, but
374/// cannot fabricate a `Validated<Self>` except through the admission path.
375/// The `ConstraintRef` enum is `#[non_exhaustive]` from outside the crate,
376/// so `CONSTRAINTS` can only cite foundation-closed constraint kinds.
377/// # Example
378/// ```
379/// use uor_foundation::pipeline::{
380///     ConstrainedTypeShape, ConstraintRef, validate_constrained_type,
381/// };
382/// pub struct MyShape;
383/// impl ConstrainedTypeShape for MyShape {
384///     const IRI: &'static str = "https://example.org/MyShape";
385///     const SITE_COUNT: usize = 4;
386///     const CONSTRAINTS: &'static [ConstraintRef] = &[
387///         ConstraintRef::Residue { modulus: 7, residue: 3 },
388///     ];
389///     const CYCLE_SIZE: u64 = 7;  // ADR-032: 7 residues mod 7
390/// }
391/// let validated = validate_constrained_type(MyShape)
392///     .expect("residue 3 mod 7 is admissible");
393/// # let _ = validated;
394/// ```
395pub trait ConstrainedTypeShape {
396    /// IRI of the ontology `type:ConstrainedType` instance this shape represents.
397    const IRI: &'static str;
398    /// Number of sites (fields) this constrained type carries.
399    const SITE_COUNT: usize;
400    /// Ontology-level `siteBudget`: count of data sites only,
401    /// excluding bookkeeping introduced by composition (coproduct tag
402    /// sites, etc.). Equals `SITE_COUNT` for leaf shapes and for
403    /// shapes whose composition introduces no bookkeeping (products,
404    /// cartesian products). Strictly less than `SITE_COUNT` for coproduct
405    /// shapes and any shape whose `SITE_COUNT` includes inherited
406    /// bookkeeping. Introduced by the Product/Coproduct Completion
407    /// Amendment §4a; defaults to `SITE_COUNT` so pre-amendment
408    /// shape impls remain valid without edits.
409    const SITE_BUDGET: usize = Self::SITE_COUNT;
410    /// Per-site constraint list. Empty means unconstrained.
411    const CONSTRAINTS: &'static [ConstraintRef];
412    /// ADR-032: cardinality of the shape's value-set (the cycle
413    /// structure of the shape under the substrate's discrete-clock
414    /// model). Used by the `prism_model!` macro to lower `first_admit`
415    /// (closure-body grammar G16) to the correct descent measure.
416    /// Conventions:
417    /// - Witt-level shapes: `1u64 << WITT_LEVEL_BITS` (W8 = 256, W16 =
418    ///   65536, W32 = 4294967296). Levels above W63 saturate to `u64::MAX`.
419    /// - `partition_product` factors: `cycle_size_product` of factor
420    ///   CYCLE_SIZEs (saturating-multiply).
421    /// - `partition_coproduct` summands: `cycle_size_coproduct` (saturating
422    ///   add + 1 for the discriminant).
423    /// - `cartesian_product_shape` (homogeneous power): factor's CYCLE_SIZE
424    ///   raised to `SITE_COUNT` (saturating).
425    /// - The foundation-sanctioned identity `ConstrainedTypeInput` has
426    ///   `CYCLE_SIZE = 1` (single-element shape).
427    const CYCLE_SIZE: u64;
428}
429
430/// ADR-032: saturating multiply for `partition_product`'s CYCLE_SIZE.
431/// Returns `u64::MAX` on overflow.
432#[inline]
433#[must_use]
434pub const fn cycle_size_product(a: u64, b: u64) -> u64 {
435    a.saturating_mul(b)
436}
437
438/// ADR-032: saturating add + 1 (for the discriminant) for
439/// `partition_coproduct`'s CYCLE_SIZE. Returns `u64::MAX` on overflow.
440#[inline]
441#[must_use]
442pub const fn cycle_size_coproduct(a: u64, b: u64) -> u64 {
443    a.saturating_add(b).saturating_add(1)
444}
445
446/// ADR-032: saturating power for `cartesian_product_shape`'s CYCLE_SIZE
447/// (homogeneous power: factor.CYCLE_SIZE^SITE_COUNT). Returns `u64::MAX`
448/// on overflow.
449#[inline]
450#[must_use]
451pub const fn cycle_size_power(base: u64, exp: usize) -> u64 {
452    let mut result: u64 = 1;
453    let mut i: usize = 0;
454    while i < exp {
455        result = result.saturating_mul(base);
456        i += 1;
457    }
458    result
459}
460
461impl ConstrainedTypeShape for ConstrainedTypeInput {
462    const IRI: &'static str = "https://uor.foundation/type/ConstrainedType";
463    const SITE_COUNT: usize = 0;
464    const CONSTRAINTS: &'static [ConstraintRef] = &[];
465    const CYCLE_SIZE: u64 = 1;
466}
467
468/// ADR-033 G20: factor-field directory carried by every
469/// `partition_product`-shaped type. The closure-body grammar's
470/// field-access form (`<expr>.<index>` or `<expr>.<field_name>`)
471/// lowers via this trait at proc-macro expansion time.
472/// Foundation-sanctioned identity `ConstrainedTypeInput` has zero
473/// fields (it is a leaf shape). The SDK macros `partition_product!`,
474/// `product_shape!`, and `cartesian_product_shape!` emit the impl.
475pub trait PartitionProductFields: ConstrainedTypeShape {
476    /// Per-factor `(byte_offset, byte_length)` pairs in declaration
477    /// order. Length is the same as `FIELD_NAMES.len()`.
478    const FIELDS: &'static [(u32, u32)];
479    /// Per-factor names. Empty string `""` for positional-only
480    /// `partition_product!(Name, A, B)` emissions; non-empty for
481    /// named-field `partition_product!(Name, lhs: A, rhs: B)` form.
482    /// Length matches `FIELDS.len()`.
483    const FIELD_NAMES: &'static [&'static str];
484    /// Linear search returning the field index whose `FIELD_NAMES`
485    /// entry equals `name`, or `usize::MAX` if not found. Delegates
486    /// to the free `const fn` [`field_index_by_name_in`] so the
487    /// result is usable inside const-eval contexts on stable Rust
488    /// 1.83 (where const trait methods are unavailable).
489    #[must_use]
490    fn field_index_by_name(name: &str) -> usize {
491        field_index_by_name_in(Self::FIELD_NAMES, name)
492    }
493}
494
495/// ADR-033 G3/G4: const-fn factor-name lookup. The SDK proc-macro
496/// emits const-eval calls to this helper to resolve a named-field
497/// access against the source type's `FIELD_NAMES`. On stable Rust
498/// 1.83 a free `const fn` is the substitute for a `const fn`
499/// trait method. Returns `usize::MAX` for not-found so the result
500/// is usable directly inside `const` array indexing.
501#[must_use]
502pub const fn field_index_by_name_in(names: &[&'static str], name: &str) -> usize {
503    let nb = name.as_bytes();
504    let mut i = 0usize;
505    while i < names.len() {
506        let nb_i = names[i].as_bytes();
507        if nb_i.len() == nb.len() {
508            let mut j = 0usize;
509            let mut matched = true;
510            while j < nb.len() {
511                if nb_i[j] != nb[j] {
512                    matched = false;
513                    break;
514                }
515                j += 1;
516            }
517            if matched {
518                return i;
519            }
520        }
521        i += 1;
522    }
523    usize::MAX
524}
525
526impl PartitionProductFields for ConstrainedTypeInput {
527    const FIELDS: &'static [(u32, u32)] = &[];
528    const FIELD_NAMES: &'static [&'static str] = &[];
529}
530
531/// ADR-033 G20 chained-field access support: maps a
532/// `partition_product`-shaped type's positional factor index to
533/// the factor's static type. The `prism_model!` proc-macro emits
534/// a chain of `Term::ProjectField` projections by walking this
535/// trait, naming each next source type as
536/// `<PrevTy as PartitionProductFactor<I>>::Factor`.
537/// `partition_product!`, `product_shape!`, and
538/// `cartesian_product_shape!` emit one impl per factor index.
539pub trait PartitionProductFactor<const INDEX: usize>: PartitionProductFields {
540    /// The static type of the factor at position `INDEX`.
541    type Factor: ConstrainedTypeShape;
542}
543
544/// ADR-030: maximum number of axes a single application's
545/// `AxisTuple` may carry. Foundation-fixed (parallel to
546/// `FOLD_UNROLL_THRESHOLD` and `UNFOLD_MAX_ITERATIONS`).
547pub const MAX_AXIS_TUPLE_ARITY: usize = 8;
548
549/// ADR-030: the upper byte ceiling on a single axis kernel's
550/// output. Sized to `TERM_VALUE_MAX_BYTES` so any kernel can
551/// populate a `TermValue` directly.
552/// Wiki ADR-037: alias of [`HostBounds::AXIS_OUTPUT_BYTES_MAX`] via
553/// [`DefaultHostBounds`]. Applications declaring a custom `HostBounds`
554/// read `<MyBounds as HostBounds>::AXIS_OUTPUT_BYTES_MAX` instead.
555pub const AXIS_OUTPUT_BYTES_CEILING: usize =
556    <crate::DefaultHostBounds as crate::HostBounds>::AXIS_OUTPUT_BYTES_MAX;
557
558/// Wiki ADR-037: foundation-internal alias of
559/// [`HostBounds::NERVE_OUTPUT_BYTES_MAX`] via [`DefaultHostBounds`] — the ψ_1 (Nerve)
560/// resolver output byte-buffer ceiling.
561pub const NERVE_OUTPUT_BYTES_MAX: usize =
562    <crate::DefaultHostBounds as crate::HostBounds>::NERVE_OUTPUT_BYTES_MAX;
563
564/// Wiki ADR-037: foundation-internal alias of
565/// [`HostBounds::CHAIN_COMPLEX_OUTPUT_BYTES_MAX`] via [`DefaultHostBounds`] — the ψ_2 (ChainComplex)
566/// resolver output byte-buffer ceiling.
567pub const CHAIN_COMPLEX_OUTPUT_BYTES_MAX: usize =
568    <crate::DefaultHostBounds as crate::HostBounds>::CHAIN_COMPLEX_OUTPUT_BYTES_MAX;
569
570/// Wiki ADR-037: foundation-internal alias of
571/// [`HostBounds::HOMOLOGY_GROUPS_OUTPUT_BYTES_MAX`] via [`DefaultHostBounds`] — the ψ_3 (HomologyGroups)
572/// resolver output byte-buffer ceiling.
573pub const HOMOLOGY_GROUPS_OUTPUT_BYTES_MAX: usize =
574    <crate::DefaultHostBounds as crate::HostBounds>::HOMOLOGY_GROUPS_OUTPUT_BYTES_MAX;
575
576/// Wiki ADR-037: foundation-internal alias of
577/// [`HostBounds::COCHAIN_COMPLEX_OUTPUT_BYTES_MAX`] via [`DefaultHostBounds`] — the ψ_5 (CochainComplex)
578/// resolver output byte-buffer ceiling.
579pub const COCHAIN_COMPLEX_OUTPUT_BYTES_MAX: usize =
580    <crate::DefaultHostBounds as crate::HostBounds>::COCHAIN_COMPLEX_OUTPUT_BYTES_MAX;
581
582/// Wiki ADR-037: foundation-internal alias of
583/// [`HostBounds::COHOMOLOGY_GROUPS_OUTPUT_BYTES_MAX`] via [`DefaultHostBounds`] — the ψ_6 (CohomologyGroups)
584/// resolver output byte-buffer ceiling.
585pub const COHOMOLOGY_GROUPS_OUTPUT_BYTES_MAX: usize =
586    <crate::DefaultHostBounds as crate::HostBounds>::COHOMOLOGY_GROUPS_OUTPUT_BYTES_MAX;
587
588/// Wiki ADR-037: foundation-internal alias of
589/// [`HostBounds::POSTNIKOV_TOWER_OUTPUT_BYTES_MAX`] via [`DefaultHostBounds`] — the ψ_7 (PostnikovTower)
590/// resolver output byte-buffer ceiling.
591pub const POSTNIKOV_TOWER_OUTPUT_BYTES_MAX: usize =
592    <crate::DefaultHostBounds as crate::HostBounds>::POSTNIKOV_TOWER_OUTPUT_BYTES_MAX;
593
594/// Wiki ADR-037: foundation-internal alias of
595/// [`HostBounds::HOMOTOPY_GROUPS_OUTPUT_BYTES_MAX`] via [`DefaultHostBounds`] — the ψ_8 (HomotopyGroups)
596/// resolver output byte-buffer ceiling.
597pub const HOMOTOPY_GROUPS_OUTPUT_BYTES_MAX: usize =
598    <crate::DefaultHostBounds as crate::HostBounds>::HOMOTOPY_GROUPS_OUTPUT_BYTES_MAX;
599
600/// Wiki ADR-037: foundation-internal alias of
601/// [`HostBounds::K_INVARIANTS_OUTPUT_BYTES_MAX`] via [`DefaultHostBounds`] — the ψ_9 (KInvariants)
602/// resolver output byte-buffer ceiling.
603pub const K_INVARIANTS_OUTPUT_BYTES_MAX: usize =
604    <crate::DefaultHostBounds as crate::HostBounds>::K_INVARIANTS_OUTPUT_BYTES_MAX;
605
606/// ADR-030: a substrate-extension axis. Each `axis!`-declared
607/// trait extends this trait via the SDK macro's blanket impl,
608/// which emits per-method `KERNEL_*` const ids and the
609/// `dispatch_kernel` router into a fixed-capacity byte buffer.
610/// The catamorphism's `Term::AxisInvocation` fold-rule reads the
611/// axis position from the application's `AxisTuple` impl and
612/// calls `dispatch_kernel` with the kernel id and the evaluated
613/// input bytes; the returned `TermValue` is the axis's
614/// contribution to the route's evaluation.
615pub trait AxisExtension {
616    /// ADR-017 content address of this axis trait. The SDK macro
617    /// derives this from the trait name and method signatures.
618    const AXIS_ADDRESS: &'static str;
619    /// Maximum bytes any kernel of this axis returns.
620    const MAX_OUTPUT_BYTES: usize;
621    /// Dispatch the kernel identified by `kernel_id` against the
622    /// evaluated input bytes. The implementation copies the kernel's
623    /// output into `out` and returns the written length.
624    /// # Errors
625    /// Returns [`crate::enforcement::ShapeViolation`] when the
626    /// kernel id is unrecognised or the input does not satisfy the
627    /// kernel's shape contract.
628    fn dispatch_kernel(
629        kernel_id: u32,
630        input: &[u8],
631        out: &mut [u8],
632    ) -> Result<usize, crate::enforcement::ShapeViolation>;
633}
634
635/// ADR-030: a tuple of `AxisExtension`-implementing types selected
636/// by the application. The catamorphism's `Term::AxisInvocation`
637/// fold-rule calls `dispatch` to route the invocation to the right
638/// axis position.
639/// Foundation provides tuple impls for arities 1 through
640/// [`MAX_AXIS_TUPLE_ARITY`].
641pub trait AxisTuple {
642    /// Number of axes carried in this tuple.
643    const AXIS_COUNT: usize;
644    /// Maximum kernel-output byte width across all axes in this tuple.
645    const MAX_OUTPUT_BYTES: usize;
646    /// Dispatch a kernel against the axis at `axis_index`. Returns
647    /// the kernel's output bytes (length up to [`MAX_OUTPUT_BYTES`]).
648    /// # Errors
649    /// Returns [`crate::enforcement::ShapeViolation`] when `axis_index`
650    /// is out of range or the axis dispatcher rejects the input.
651    fn dispatch(
652        axis_index: u32,
653        kernel_id: u32,
654        input: &[u8],
655        out: &mut [u8],
656    ) -> Result<usize, crate::enforcement::ShapeViolation>;
657}
658
659/// ADR-030 blanket: every [`crate::enforcement::Hasher`] is
660/// automatically an [`AxisTuple`] of arity 1 — the canonical
661/// hash axis at position 0, kernel id 0.
662impl<H: crate::enforcement::Hasher> AxisTuple for H {
663    const AXIS_COUNT: usize = 1;
664    const MAX_OUTPUT_BYTES: usize = <H as crate::enforcement::Hasher>::OUTPUT_BYTES;
665    fn dispatch(
666        axis_index: u32,
667        kernel_id: u32,
668        input: &[u8],
669        out: &mut [u8],
670    ) -> Result<usize, crate::enforcement::ShapeViolation> {
671        if axis_index != 0 || kernel_id != 0 {
672            return Err(crate::enforcement::ShapeViolation {
673                shape_iri: "https://uor.foundation/axis/HasherBlanket",
674                constraint_iri: "https://uor.foundation/axis/HasherBlanket/canonicalDispatch",
675                property_iri: "https://uor.foundation/axis/axisIndex",
676                expected_range: "https://uor.foundation/axis/CanonicalHashAxis",
677                min_count: 0,
678                max_count: 1,
679                kind: crate::ViolationKind::ValueCheck,
680            });
681        }
682        let mut hasher = <H as crate::enforcement::Hasher>::initial();
683        hasher = hasher.fold_bytes(input);
684        let digest = hasher.finalize();
685        let n_max = <H as crate::enforcement::Hasher>::OUTPUT_BYTES;
686        let n = if n_max > out.len() { out.len() } else { n_max };
687        let mut i = 0;
688        while i < n {
689            out[i] = digest[i];
690            i += 1;
691        }
692        Ok(n)
693    }
694}
695
696/// ADR-030: 1-tuple AxisTuple impl — applications selecting a single axis.
697impl<A0: AxisExtension> AxisTuple for (A0,) {
698    const AXIS_COUNT: usize = 1;
699    const MAX_OUTPUT_BYTES: usize = <A0 as AxisExtension>::MAX_OUTPUT_BYTES;
700    fn dispatch(
701        axis_index: u32,
702        kernel_id: u32,
703        input: &[u8],
704        out: &mut [u8],
705    ) -> Result<usize, crate::enforcement::ShapeViolation> {
706        match axis_index {
707            0 => <A0 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
708            _ => Err(crate::enforcement::ShapeViolation {
709                shape_iri: "https://uor.foundation/pipeline/AxisTupleShape",
710                constraint_iri: "https://uor.foundation/pipeline/AxisTupleShape/inBounds",
711                property_iri: "https://uor.foundation/pipeline/axisIndex",
712                expected_range: "https://uor.foundation/pipeline/AxisIndex",
713                min_count: 0,
714                max_count: 1,
715                kind: crate::ViolationKind::ValueCheck,
716            }),
717        }
718    }
719}
720
721/// ADR-030: 2-tuple AxisTuple impl.
722impl<A0: AxisExtension, A1: AxisExtension> AxisTuple for (A0, A1) {
723    const AXIS_COUNT: usize = 2;
724    const MAX_OUTPUT_BYTES: usize = {
725        let a = <A0 as AxisExtension>::MAX_OUTPUT_BYTES;
726        let b = <A1 as AxisExtension>::MAX_OUTPUT_BYTES;
727        if a > b {
728            a
729        } else {
730            b
731        }
732    };
733    fn dispatch(
734        axis_index: u32,
735        kernel_id: u32,
736        input: &[u8],
737        out: &mut [u8],
738    ) -> Result<usize, crate::enforcement::ShapeViolation> {
739        match axis_index {
740            0 => <A0 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
741            1 => <A1 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
742            _ => Err(crate::enforcement::ShapeViolation {
743                shape_iri: "https://uor.foundation/pipeline/AxisTupleShape",
744                constraint_iri: "https://uor.foundation/pipeline/AxisTupleShape/inBounds",
745                property_iri: "https://uor.foundation/pipeline/axisIndex",
746                expected_range: "https://uor.foundation/pipeline/AxisIndex",
747                min_count: 0,
748                max_count: 2,
749                kind: crate::ViolationKind::ValueCheck,
750            }),
751        }
752    }
753}
754
755/// ADR-030: 3-tuple AxisTuple impl.
756impl<A0: AxisExtension, A1: AxisExtension, A2: AxisExtension> AxisTuple for (A0, A1, A2) {
757    const AXIS_COUNT: usize = 3;
758    const MAX_OUTPUT_BYTES: usize = {
759        let a0 = <A0 as AxisExtension>::MAX_OUTPUT_BYTES;
760        let a1 = <A1 as AxisExtension>::MAX_OUTPUT_BYTES;
761        let a2 = <A2 as AxisExtension>::MAX_OUTPUT_BYTES;
762        let mut m = a0;
763        if a1 > m {
764            m = a1;
765        }
766        if a2 > m {
767            m = a2;
768        }
769        m
770    };
771    fn dispatch(
772        axis_index: u32,
773        kernel_id: u32,
774        input: &[u8],
775        out: &mut [u8],
776    ) -> Result<usize, crate::enforcement::ShapeViolation> {
777        match axis_index {
778            0 => <A0 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
779            1 => <A1 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
780            2 => <A2 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
781            _ => Err(crate::enforcement::ShapeViolation {
782                shape_iri: "https://uor.foundation/pipeline/AxisTupleShape",
783                constraint_iri: "https://uor.foundation/pipeline/AxisTupleShape/inBounds",
784                property_iri: "https://uor.foundation/pipeline/axisIndex",
785                expected_range: "https://uor.foundation/pipeline/AxisIndex",
786                min_count: 0,
787                max_count: 3,
788                kind: crate::ViolationKind::ValueCheck,
789            }),
790        }
791    }
792}
793
794/// ADR-030: 4-tuple AxisTuple impl.
795impl<A0: AxisExtension, A1: AxisExtension, A2: AxisExtension, A3: AxisExtension> AxisTuple
796    for (A0, A1, A2, A3)
797{
798    const AXIS_COUNT: usize = 4;
799    const MAX_OUTPUT_BYTES: usize = {
800        let a0 = <A0 as AxisExtension>::MAX_OUTPUT_BYTES;
801        let a1 = <A1 as AxisExtension>::MAX_OUTPUT_BYTES;
802        let a2 = <A2 as AxisExtension>::MAX_OUTPUT_BYTES;
803        let a3 = <A3 as AxisExtension>::MAX_OUTPUT_BYTES;
804        let mut m = a0;
805        if a1 > m {
806            m = a1;
807        }
808        if a2 > m {
809            m = a2;
810        }
811        if a3 > m {
812            m = a3;
813        }
814        m
815    };
816    fn dispatch(
817        axis_index: u32,
818        kernel_id: u32,
819        input: &[u8],
820        out: &mut [u8],
821    ) -> Result<usize, crate::enforcement::ShapeViolation> {
822        match axis_index {
823            0 => <A0 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
824            1 => <A1 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
825            2 => <A2 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
826            3 => <A3 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
827            _ => Err(crate::enforcement::ShapeViolation {
828                shape_iri: "https://uor.foundation/pipeline/AxisTupleShape",
829                constraint_iri: "https://uor.foundation/pipeline/AxisTupleShape/inBounds",
830                property_iri: "https://uor.foundation/pipeline/axisIndex",
831                expected_range: "https://uor.foundation/pipeline/AxisIndex",
832                min_count: 0,
833                max_count: 4,
834                kind: crate::ViolationKind::ValueCheck,
835            }),
836        }
837    }
838}
839
840/// ADR-030: 5-tuple AxisTuple impl.
841impl<
842        A0: AxisExtension,
843        A1: AxisExtension,
844        A2: AxisExtension,
845        A3: AxisExtension,
846        A4: AxisExtension,
847    > AxisTuple for (A0, A1, A2, A3, A4)
848{
849    const AXIS_COUNT: usize = 5;
850    const MAX_OUTPUT_BYTES: usize = {
851        let a0 = <A0 as AxisExtension>::MAX_OUTPUT_BYTES;
852        let a1 = <A1 as AxisExtension>::MAX_OUTPUT_BYTES;
853        let a2 = <A2 as AxisExtension>::MAX_OUTPUT_BYTES;
854        let a3 = <A3 as AxisExtension>::MAX_OUTPUT_BYTES;
855        let a4 = <A4 as AxisExtension>::MAX_OUTPUT_BYTES;
856        let mut m = a0;
857        if a1 > m {
858            m = a1;
859        }
860        if a2 > m {
861            m = a2;
862        }
863        if a3 > m {
864            m = a3;
865        }
866        if a4 > m {
867            m = a4;
868        }
869        m
870    };
871    fn dispatch(
872        axis_index: u32,
873        kernel_id: u32,
874        input: &[u8],
875        out: &mut [u8],
876    ) -> Result<usize, crate::enforcement::ShapeViolation> {
877        match axis_index {
878            0 => <A0 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
879            1 => <A1 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
880            2 => <A2 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
881            3 => <A3 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
882            4 => <A4 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
883            _ => Err(crate::enforcement::ShapeViolation {
884                shape_iri: "https://uor.foundation/pipeline/AxisTupleShape",
885                constraint_iri: "https://uor.foundation/pipeline/AxisTupleShape/inBounds",
886                property_iri: "https://uor.foundation/pipeline/axisIndex",
887                expected_range: "https://uor.foundation/pipeline/AxisIndex",
888                min_count: 0,
889                max_count: 5,
890                kind: crate::ViolationKind::ValueCheck,
891            }),
892        }
893    }
894}
895
896/// ADR-030: 6-tuple AxisTuple impl.
897impl<
898        A0: AxisExtension,
899        A1: AxisExtension,
900        A2: AxisExtension,
901        A3: AxisExtension,
902        A4: AxisExtension,
903        A5: AxisExtension,
904    > AxisTuple for (A0, A1, A2, A3, A4, A5)
905{
906    const AXIS_COUNT: usize = 6;
907    const MAX_OUTPUT_BYTES: usize = {
908        let a0 = <A0 as AxisExtension>::MAX_OUTPUT_BYTES;
909        let a1 = <A1 as AxisExtension>::MAX_OUTPUT_BYTES;
910        let a2 = <A2 as AxisExtension>::MAX_OUTPUT_BYTES;
911        let a3 = <A3 as AxisExtension>::MAX_OUTPUT_BYTES;
912        let a4 = <A4 as AxisExtension>::MAX_OUTPUT_BYTES;
913        let a5 = <A5 as AxisExtension>::MAX_OUTPUT_BYTES;
914        let mut m = a0;
915        if a1 > m {
916            m = a1;
917        }
918        if a2 > m {
919            m = a2;
920        }
921        if a3 > m {
922            m = a3;
923        }
924        if a4 > m {
925            m = a4;
926        }
927        if a5 > m {
928            m = a5;
929        }
930        m
931    };
932    fn dispatch(
933        axis_index: u32,
934        kernel_id: u32,
935        input: &[u8],
936        out: &mut [u8],
937    ) -> Result<usize, crate::enforcement::ShapeViolation> {
938        match axis_index {
939            0 => <A0 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
940            1 => <A1 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
941            2 => <A2 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
942            3 => <A3 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
943            4 => <A4 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
944            5 => <A5 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
945            _ => Err(crate::enforcement::ShapeViolation {
946                shape_iri: "https://uor.foundation/pipeline/AxisTupleShape",
947                constraint_iri: "https://uor.foundation/pipeline/AxisTupleShape/inBounds",
948                property_iri: "https://uor.foundation/pipeline/axisIndex",
949                expected_range: "https://uor.foundation/pipeline/AxisIndex",
950                min_count: 0,
951                max_count: 6,
952                kind: crate::ViolationKind::ValueCheck,
953            }),
954        }
955    }
956}
957
958/// ADR-030: 7-tuple AxisTuple impl.
959impl<
960        A0: AxisExtension,
961        A1: AxisExtension,
962        A2: AxisExtension,
963        A3: AxisExtension,
964        A4: AxisExtension,
965        A5: AxisExtension,
966        A6: AxisExtension,
967    > AxisTuple for (A0, A1, A2, A3, A4, A5, A6)
968{
969    const AXIS_COUNT: usize = 7;
970    const MAX_OUTPUT_BYTES: usize = {
971        let a0 = <A0 as AxisExtension>::MAX_OUTPUT_BYTES;
972        let a1 = <A1 as AxisExtension>::MAX_OUTPUT_BYTES;
973        let a2 = <A2 as AxisExtension>::MAX_OUTPUT_BYTES;
974        let a3 = <A3 as AxisExtension>::MAX_OUTPUT_BYTES;
975        let a4 = <A4 as AxisExtension>::MAX_OUTPUT_BYTES;
976        let a5 = <A5 as AxisExtension>::MAX_OUTPUT_BYTES;
977        let a6 = <A6 as AxisExtension>::MAX_OUTPUT_BYTES;
978        let mut m = a0;
979        if a1 > m {
980            m = a1;
981        }
982        if a2 > m {
983            m = a2;
984        }
985        if a3 > m {
986            m = a3;
987        }
988        if a4 > m {
989            m = a4;
990        }
991        if a5 > m {
992            m = a5;
993        }
994        if a6 > m {
995            m = a6;
996        }
997        m
998    };
999    fn dispatch(
1000        axis_index: u32,
1001        kernel_id: u32,
1002        input: &[u8],
1003        out: &mut [u8],
1004    ) -> Result<usize, crate::enforcement::ShapeViolation> {
1005        match axis_index {
1006            0 => <A0 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
1007            1 => <A1 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
1008            2 => <A2 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
1009            3 => <A3 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
1010            4 => <A4 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
1011            5 => <A5 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
1012            6 => <A6 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
1013            _ => Err(crate::enforcement::ShapeViolation {
1014                shape_iri: "https://uor.foundation/pipeline/AxisTupleShape",
1015                constraint_iri: "https://uor.foundation/pipeline/AxisTupleShape/inBounds",
1016                property_iri: "https://uor.foundation/pipeline/axisIndex",
1017                expected_range: "https://uor.foundation/pipeline/AxisIndex",
1018                min_count: 0,
1019                max_count: 7,
1020                kind: crate::ViolationKind::ValueCheck,
1021            }),
1022        }
1023    }
1024}
1025
1026/// ADR-030: 8-tuple AxisTuple impl.
1027impl<
1028        A0: AxisExtension,
1029        A1: AxisExtension,
1030        A2: AxisExtension,
1031        A3: AxisExtension,
1032        A4: AxisExtension,
1033        A5: AxisExtension,
1034        A6: AxisExtension,
1035        A7: AxisExtension,
1036    > AxisTuple for (A0, A1, A2, A3, A4, A5, A6, A7)
1037{
1038    const AXIS_COUNT: usize = 8;
1039    const MAX_OUTPUT_BYTES: usize = {
1040        let a0 = <A0 as AxisExtension>::MAX_OUTPUT_BYTES;
1041        let a1 = <A1 as AxisExtension>::MAX_OUTPUT_BYTES;
1042        let a2 = <A2 as AxisExtension>::MAX_OUTPUT_BYTES;
1043        let a3 = <A3 as AxisExtension>::MAX_OUTPUT_BYTES;
1044        let a4 = <A4 as AxisExtension>::MAX_OUTPUT_BYTES;
1045        let a5 = <A5 as AxisExtension>::MAX_OUTPUT_BYTES;
1046        let a6 = <A6 as AxisExtension>::MAX_OUTPUT_BYTES;
1047        let a7 = <A7 as AxisExtension>::MAX_OUTPUT_BYTES;
1048        let mut m = a0;
1049        if a1 > m {
1050            m = a1;
1051        }
1052        if a2 > m {
1053            m = a2;
1054        }
1055        if a3 > m {
1056            m = a3;
1057        }
1058        if a4 > m {
1059            m = a4;
1060        }
1061        if a5 > m {
1062            m = a5;
1063        }
1064        if a6 > m {
1065            m = a6;
1066        }
1067        if a7 > m {
1068            m = a7;
1069        }
1070        m
1071    };
1072    fn dispatch(
1073        axis_index: u32,
1074        kernel_id: u32,
1075        input: &[u8],
1076        out: &mut [u8],
1077    ) -> Result<usize, crate::enforcement::ShapeViolation> {
1078        match axis_index {
1079            0 => <A0 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
1080            1 => <A1 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
1081            2 => <A2 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
1082            3 => <A3 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
1083            4 => <A4 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
1084            5 => <A5 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
1085            6 => <A6 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
1086            7 => <A7 as AxisExtension>::dispatch_kernel(kernel_id, input, out),
1087            _ => Err(crate::enforcement::ShapeViolation {
1088                shape_iri: "https://uor.foundation/pipeline/AxisTupleShape",
1089                constraint_iri: "https://uor.foundation/pipeline/AxisTupleShape/inBounds",
1090                property_iri: "https://uor.foundation/pipeline/axisIndex",
1091                expected_range: "https://uor.foundation/pipeline/AxisIndex",
1092                min_count: 0,
1093                max_count: 8,
1094                kind: crate::ViolationKind::ValueCheck,
1095            }),
1096        }
1097    }
1098}
1099
1100/// ADR-036: maximum number of resolvers a single application's
1101/// `ResolverTuple` may carry. Foundation-fixed at twice
1102/// [`MAX_AXIS_TUPLE_ARITY`] to accommodate ADR-035's eight
1103/// resolver-bound ψ-Term categories with eight headroom positions
1104/// for future ADR-013/TR-08 substrate amendments.
1105pub const MAX_RESOLVER_TUPLE_ARITY: usize = 16;
1106
1107/// ADR-036: foundation-internal discriminator emitted by every
1108/// `Null<Category>Resolver` impl when the catamorphism's resolver-
1109/// bound ψ-Term fold-rule consults a resolver category whose
1110/// `ResolverTuple` accessor returns the foundation-Null impl. The
1111/// evaluator translates this into a `PipelineFailure::ShapeViolation`
1112/// carrying the `https://uor.foundation/resolver/RESOLVER_ABSENT`
1113/// shape IRI; ADR-022 D3 G9's `Term::Try` default-propagation handler
1114/// recovers it when the verb body wraps the ψ-Term.
1115pub const RESOLVER_ABSENT_DISCRIMINATOR: u8 = 0xff;
1116
1117/// ADR-036: resolver-category enum identifying which resolver-bound
1118/// substrate operation each `ResolverTuple` position satisfies. Each
1119/// variant corresponds to one resolver-bound ψ-Term variant per
1120/// ADR-035; future ADR-013/TR-08 substrate amendments add variants.
1121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1122pub enum ResolverCategory {
1123    /// ψ_1 — per-value bytes → SimplicialComplex (NerveResolver).
1124    Nerve,
1125    /// ψ_2 — SimplicialComplex → ChainComplex (ChainComplexResolver).
1126    ChainComplex,
1127    /// ψ_3 — ChainComplex → HomologyGroups (HomologyGroupResolver).
1128    HomologyGroup,
1129    /// ψ_5 — ChainComplex → CochainComplex (CochainComplexResolver).
1130    CochainComplex,
1131    /// ψ_6 — CochainComplex → CohomologyGroups (CohomologyGroupResolver).
1132    CohomologyGroup,
1133    /// ψ_7 — SimplicialComplex → PostnikovTower (PostnikovResolver).
1134    Postnikov,
1135    /// ψ_8 — PostnikovTower → HomotopyGroups (HomotopyGroupResolver).
1136    HomotopyGroup,
1137    /// ψ_9 — HomotopyGroups → KInvariants (KInvariantResolver).
1138    KInvariant,
1139}
1140
1141/// ADR-036: tuple-of-bounded-types parameter on the model declaration
1142/// carrying application-provided resolver instances for the resolver-
1143/// bound ψ-Term variants per ADR-035. Sealed via
1144/// [`__sdk_seal::Sealed`]: only the SDK `resolver!` macro emits impls.
1145pub trait ResolverTuple: __sdk_seal::Sealed {
1146    /// Number of resolver positions in this tuple (bounded by `MAX_RESOLVER_TUPLE_ARITY`).
1147    const ARITY: usize;
1148    /// Resolver category at each tuple position.
1149    const CATEGORIES: &'static [ResolverCategory];
1150}
1151
1152/// Wiki ADR-041: zero-cost typed-coordinate carrier for ψ_1 output —
1153/// the simplicial-complex serialization produced by `NerveResolver::resolve`.
1154///
1155/// `#[repr(transparent)]` over `&'a [u8]`: layout identical to a byte
1156/// slice; the type wrapper is purely compile-time discrimination so
1157/// ψ-stage composition is type-checked at the resolver-impl boundary.
1158#[repr(transparent)]
1159#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1160pub struct SimplicialComplexBytes<'a>(pub &'a [u8]);
1161
1162impl<'a> SimplicialComplexBytes<'a> {
1163    /// Borrow the underlying byte slice.
1164    #[must_use]
1165    pub fn as_bytes(&self) -> &[u8] {
1166        self.0
1167    }
1168    /// Length of the underlying byte slice.
1169    #[must_use]
1170    pub fn len(&self) -> usize {
1171        self.0.len()
1172    }
1173    /// Whether the underlying byte slice is empty.
1174    #[must_use]
1175    pub fn is_empty(&self) -> bool {
1176        self.0.is_empty()
1177    }
1178}
1179
1180/// Wiki ADR-041: zero-cost typed-coordinate carrier for ψ_2 output —
1181/// the chain-complex serialization produced by `ChainComplexResolver::resolve`.
1182///
1183/// `#[repr(transparent)]` over `&'a [u8]`: layout identical to a byte
1184/// slice; the type wrapper is purely compile-time discrimination so
1185/// ψ-stage composition is type-checked at the resolver-impl boundary.
1186#[repr(transparent)]
1187#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1188pub struct ChainComplexBytes<'a>(pub &'a [u8]);
1189
1190impl<'a> ChainComplexBytes<'a> {
1191    /// Borrow the underlying byte slice.
1192    #[must_use]
1193    pub fn as_bytes(&self) -> &[u8] {
1194        self.0
1195    }
1196    /// Length of the underlying byte slice.
1197    #[must_use]
1198    pub fn len(&self) -> usize {
1199        self.0.len()
1200    }
1201    /// Whether the underlying byte slice is empty.
1202    #[must_use]
1203    pub fn is_empty(&self) -> bool {
1204        self.0.is_empty()
1205    }
1206}
1207
1208/// Wiki ADR-041: zero-cost typed-coordinate carrier for ψ_3 output —
1209/// the homology-groups serialization produced by `HomologyGroupResolver::resolve`.
1210///
1211/// `#[repr(transparent)]` over `&'a [u8]`: layout identical to a byte
1212/// slice; the type wrapper is purely compile-time discrimination so
1213/// ψ-stage composition is type-checked at the resolver-impl boundary.
1214#[repr(transparent)]
1215#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1216pub struct HomologyGroupsBytes<'a>(pub &'a [u8]);
1217
1218impl<'a> HomologyGroupsBytes<'a> {
1219    /// Borrow the underlying byte slice.
1220    #[must_use]
1221    pub fn as_bytes(&self) -> &[u8] {
1222        self.0
1223    }
1224    /// Length of the underlying byte slice.
1225    #[must_use]
1226    pub fn len(&self) -> usize {
1227        self.0.len()
1228    }
1229    /// Whether the underlying byte slice is empty.
1230    #[must_use]
1231    pub fn is_empty(&self) -> bool {
1232        self.0.is_empty()
1233    }
1234}
1235
1236/// Wiki ADR-041: zero-cost typed-coordinate carrier for ψ_4 output —
1237/// the Betti-number tuple serialization produced by the resolver-free `Term::Betti` fold-rule (a byte projection of HomologyGroupsBytes).
1238///
1239/// `#[repr(transparent)]` over `&'a [u8]`: layout identical to a byte
1240/// slice; the type wrapper is purely compile-time discrimination so
1241/// ψ-stage composition is type-checked at the resolver-impl boundary.
1242#[repr(transparent)]
1243#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1244pub struct BettiNumbersBytes<'a>(pub &'a [u8]);
1245
1246impl<'a> BettiNumbersBytes<'a> {
1247    /// Borrow the underlying byte slice.
1248    #[must_use]
1249    pub fn as_bytes(&self) -> &[u8] {
1250        self.0
1251    }
1252    /// Length of the underlying byte slice.
1253    #[must_use]
1254    pub fn len(&self) -> usize {
1255        self.0.len()
1256    }
1257    /// Whether the underlying byte slice is empty.
1258    #[must_use]
1259    pub fn is_empty(&self) -> bool {
1260        self.0.is_empty()
1261    }
1262}
1263
1264/// Wiki ADR-041: zero-cost typed-coordinate carrier for ψ_5 output —
1265/// the cochain-complex serialization produced by `CochainComplexResolver::resolve`.
1266///
1267/// `#[repr(transparent)]` over `&'a [u8]`: layout identical to a byte
1268/// slice; the type wrapper is purely compile-time discrimination so
1269/// ψ-stage composition is type-checked at the resolver-impl boundary.
1270#[repr(transparent)]
1271#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1272pub struct CochainComplexBytes<'a>(pub &'a [u8]);
1273
1274impl<'a> CochainComplexBytes<'a> {
1275    /// Borrow the underlying byte slice.
1276    #[must_use]
1277    pub fn as_bytes(&self) -> &[u8] {
1278        self.0
1279    }
1280    /// Length of the underlying byte slice.
1281    #[must_use]
1282    pub fn len(&self) -> usize {
1283        self.0.len()
1284    }
1285    /// Whether the underlying byte slice is empty.
1286    #[must_use]
1287    pub fn is_empty(&self) -> bool {
1288        self.0.is_empty()
1289    }
1290}
1291
1292/// Wiki ADR-041: zero-cost typed-coordinate carrier for ψ_6 output —
1293/// the cohomology-groups serialization produced by `CohomologyGroupResolver::resolve`.
1294///
1295/// `#[repr(transparent)]` over `&'a [u8]`: layout identical to a byte
1296/// slice; the type wrapper is purely compile-time discrimination so
1297/// ψ-stage composition is type-checked at the resolver-impl boundary.
1298#[repr(transparent)]
1299#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1300pub struct CohomologyGroupsBytes<'a>(pub &'a [u8]);
1301
1302impl<'a> CohomologyGroupsBytes<'a> {
1303    /// Borrow the underlying byte slice.
1304    #[must_use]
1305    pub fn as_bytes(&self) -> &[u8] {
1306        self.0
1307    }
1308    /// Length of the underlying byte slice.
1309    #[must_use]
1310    pub fn len(&self) -> usize {
1311        self.0.len()
1312    }
1313    /// Whether the underlying byte slice is empty.
1314    #[must_use]
1315    pub fn is_empty(&self) -> bool {
1316        self.0.is_empty()
1317    }
1318}
1319
1320/// Wiki ADR-041: zero-cost typed-coordinate carrier for ψ_7 output —
1321/// the Postnikov-tower serialization produced by `PostnikovResolver::resolve`.
1322///
1323/// `#[repr(transparent)]` over `&'a [u8]`: layout identical to a byte
1324/// slice; the type wrapper is purely compile-time discrimination so
1325/// ψ-stage composition is type-checked at the resolver-impl boundary.
1326#[repr(transparent)]
1327#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1328pub struct PostnikovTowerBytes<'a>(pub &'a [u8]);
1329
1330impl<'a> PostnikovTowerBytes<'a> {
1331    /// Borrow the underlying byte slice.
1332    #[must_use]
1333    pub fn as_bytes(&self) -> &[u8] {
1334        self.0
1335    }
1336    /// Length of the underlying byte slice.
1337    #[must_use]
1338    pub fn len(&self) -> usize {
1339        self.0.len()
1340    }
1341    /// Whether the underlying byte slice is empty.
1342    #[must_use]
1343    pub fn is_empty(&self) -> bool {
1344        self.0.is_empty()
1345    }
1346}
1347
1348/// Wiki ADR-041: zero-cost typed-coordinate carrier for ψ_8 output —
1349/// the homotopy-groups serialization produced by `HomotopyGroupResolver::resolve`.
1350///
1351/// `#[repr(transparent)]` over `&'a [u8]`: layout identical to a byte
1352/// slice; the type wrapper is purely compile-time discrimination so
1353/// ψ-stage composition is type-checked at the resolver-impl boundary.
1354#[repr(transparent)]
1355#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1356pub struct HomotopyGroupsBytes<'a>(pub &'a [u8]);
1357
1358impl<'a> HomotopyGroupsBytes<'a> {
1359    /// Borrow the underlying byte slice.
1360    #[must_use]
1361    pub fn as_bytes(&self) -> &[u8] {
1362        self.0
1363    }
1364    /// Length of the underlying byte slice.
1365    #[must_use]
1366    pub fn len(&self) -> usize {
1367        self.0.len()
1368    }
1369    /// Whether the underlying byte slice is empty.
1370    #[must_use]
1371    pub fn is_empty(&self) -> bool {
1372        self.0.is_empty()
1373    }
1374}
1375
1376/// Wiki ADR-041: zero-cost typed-coordinate carrier for ψ_9 output —
1377/// the κ-label byte serialization produced by `KInvariantResolver::resolve` — the canonical k-invariants-branch output (ADR-035) classifying the input's homotopy type up to weak equivalence.
1378///
1379/// `#[repr(transparent)]` over `&'a [u8]`: layout identical to a byte
1380/// slice; the type wrapper is purely compile-time discrimination so
1381/// ψ-stage composition is type-checked at the resolver-impl boundary.
1382#[repr(transparent)]
1383#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1384pub struct KInvariantsBytes<'a>(pub &'a [u8]);
1385
1386impl<'a> KInvariantsBytes<'a> {
1387    /// Borrow the underlying byte slice.
1388    #[must_use]
1389    pub fn as_bytes(&self) -> &[u8] {
1390        self.0
1391    }
1392    /// Length of the underlying byte slice.
1393    #[must_use]
1394    pub fn len(&self) -> usize {
1395        self.0.len()
1396    }
1397    /// Whether the underlying byte slice is empty.
1398    #[must_use]
1399    pub fn is_empty(&self) -> bool {
1400        self.0.is_empty()
1401    }
1402}
1403
1404/// ADR-036 resolver trait: ψ_1 — per-value bytes → SimplicialComplex per ADR-035.
1405///
1406/// Parameterized by the model's H-axis (`H: Hasher` per ADR-022 D5) so
1407/// resolver impls compute content-addressed fingerprints using the
1408/// model's chosen hash impl. Sealed via [`__sdk_seal::Sealed`]: only
1409/// the SDK `resolver!` macro emits impls. Foundation provides a Null
1410/// impl whose `resolve` emits the `RESOLVER_ABSENT` shape violation.
1411///
1412/// ADR-041: `input` is a zero-cost typed carrier so ψ-stage
1413/// composition is type-checked at the resolver-impl boundary.
1414pub trait NerveResolver<H: crate::enforcement::Hasher>: __sdk_seal::Sealed {
1415    /// Resolve per-value content for this category.
1416    /// # Errors
1417    /// Returns [`crate::enforcement::ShapeViolation`] when the resolver
1418    /// cannot produce content (e.g., the foundation Null impl carrying
1419    /// the `RESOLVER_ABSENT` discriminator).
1420    fn resolve(
1421        &self,
1422        input: &[u8],
1423        out: &mut [u8],
1424    ) -> Result<usize, crate::enforcement::ShapeViolation>;
1425}
1426
1427/// ADR-036 resolver trait: ψ_2 — SimplicialComplex → ChainComplex per ADR-035.
1428///
1429/// Parameterized by the model's H-axis (`H: Hasher` per ADR-022 D5) so
1430/// resolver impls compute content-addressed fingerprints using the
1431/// model's chosen hash impl. Sealed via [`__sdk_seal::Sealed`]: only
1432/// the SDK `resolver!` macro emits impls. Foundation provides a Null
1433/// impl whose `resolve` emits the `RESOLVER_ABSENT` shape violation.
1434///
1435/// ADR-041: `input` is a zero-cost typed carrier so ψ-stage
1436/// composition is type-checked at the resolver-impl boundary.
1437pub trait ChainComplexResolver<H: crate::enforcement::Hasher>: __sdk_seal::Sealed {
1438    /// Resolve per-value content for this category.
1439    /// # Errors
1440    /// Returns [`crate::enforcement::ShapeViolation`] when the resolver
1441    /// cannot produce content (e.g., the foundation Null impl carrying
1442    /// the `RESOLVER_ABSENT` discriminator).
1443    fn resolve(
1444        &self,
1445        input: SimplicialComplexBytes<'_>,
1446        out: &mut [u8],
1447    ) -> Result<usize, crate::enforcement::ShapeViolation>;
1448}
1449
1450/// ADR-036 resolver trait: ψ_3 — ChainComplex → HomologyGroups per ADR-035.
1451///
1452/// Parameterized by the model's H-axis (`H: Hasher` per ADR-022 D5) so
1453/// resolver impls compute content-addressed fingerprints using the
1454/// model's chosen hash impl. Sealed via [`__sdk_seal::Sealed`]: only
1455/// the SDK `resolver!` macro emits impls. Foundation provides a Null
1456/// impl whose `resolve` emits the `RESOLVER_ABSENT` shape violation.
1457///
1458/// ADR-041: `input` is a zero-cost typed carrier so ψ-stage
1459/// composition is type-checked at the resolver-impl boundary.
1460pub trait HomologyGroupResolver<H: crate::enforcement::Hasher>: __sdk_seal::Sealed {
1461    /// Resolve per-value content for this category.
1462    /// # Errors
1463    /// Returns [`crate::enforcement::ShapeViolation`] when the resolver
1464    /// cannot produce content (e.g., the foundation Null impl carrying
1465    /// the `RESOLVER_ABSENT` discriminator).
1466    fn resolve(
1467        &self,
1468        input: ChainComplexBytes<'_>,
1469        out: &mut [u8],
1470    ) -> Result<usize, crate::enforcement::ShapeViolation>;
1471}
1472
1473/// ADR-036 resolver trait: ψ_5 — ChainComplex → CochainComplex per ADR-035.
1474///
1475/// Parameterized by the model's H-axis (`H: Hasher` per ADR-022 D5) so
1476/// resolver impls compute content-addressed fingerprints using the
1477/// model's chosen hash impl. Sealed via [`__sdk_seal::Sealed`]: only
1478/// the SDK `resolver!` macro emits impls. Foundation provides a Null
1479/// impl whose `resolve` emits the `RESOLVER_ABSENT` shape violation.
1480///
1481/// ADR-041: `input` is a zero-cost typed carrier so ψ-stage
1482/// composition is type-checked at the resolver-impl boundary.
1483pub trait CochainComplexResolver<H: crate::enforcement::Hasher>: __sdk_seal::Sealed {
1484    /// Resolve per-value content for this category.
1485    /// # Errors
1486    /// Returns [`crate::enforcement::ShapeViolation`] when the resolver
1487    /// cannot produce content (e.g., the foundation Null impl carrying
1488    /// the `RESOLVER_ABSENT` discriminator).
1489    fn resolve(
1490        &self,
1491        input: ChainComplexBytes<'_>,
1492        out: &mut [u8],
1493    ) -> Result<usize, crate::enforcement::ShapeViolation>;
1494}
1495
1496/// ADR-036 resolver trait: ψ_6 — CochainComplex → CohomologyGroups per ADR-035.
1497///
1498/// Parameterized by the model's H-axis (`H: Hasher` per ADR-022 D5) so
1499/// resolver impls compute content-addressed fingerprints using the
1500/// model's chosen hash impl. Sealed via [`__sdk_seal::Sealed`]: only
1501/// the SDK `resolver!` macro emits impls. Foundation provides a Null
1502/// impl whose `resolve` emits the `RESOLVER_ABSENT` shape violation.
1503///
1504/// ADR-041: `input` is a zero-cost typed carrier so ψ-stage
1505/// composition is type-checked at the resolver-impl boundary.
1506pub trait CohomologyGroupResolver<H: crate::enforcement::Hasher>: __sdk_seal::Sealed {
1507    /// Resolve per-value content for this category.
1508    /// # Errors
1509    /// Returns [`crate::enforcement::ShapeViolation`] when the resolver
1510    /// cannot produce content (e.g., the foundation Null impl carrying
1511    /// the `RESOLVER_ABSENT` discriminator).
1512    fn resolve(
1513        &self,
1514        input: CochainComplexBytes<'_>,
1515        out: &mut [u8],
1516    ) -> Result<usize, crate::enforcement::ShapeViolation>;
1517}
1518
1519/// ADR-036 resolver trait: ψ_7 — SimplicialComplex → PostnikovTower per ADR-035.
1520///
1521/// Parameterized by the model's H-axis (`H: Hasher` per ADR-022 D5) so
1522/// resolver impls compute content-addressed fingerprints using the
1523/// model's chosen hash impl. Sealed via [`__sdk_seal::Sealed`]: only
1524/// the SDK `resolver!` macro emits impls. Foundation provides a Null
1525/// impl whose `resolve` emits the `RESOLVER_ABSENT` shape violation.
1526///
1527/// ADR-041: `input` is a zero-cost typed carrier so ψ-stage
1528/// composition is type-checked at the resolver-impl boundary.
1529pub trait PostnikovResolver<H: crate::enforcement::Hasher>: __sdk_seal::Sealed {
1530    /// Resolve per-value content for this category.
1531    /// # Errors
1532    /// Returns [`crate::enforcement::ShapeViolation`] when the resolver
1533    /// cannot produce content (e.g., the foundation Null impl carrying
1534    /// the `RESOLVER_ABSENT` discriminator).
1535    fn resolve(
1536        &self,
1537        input: SimplicialComplexBytes<'_>,
1538        out: &mut [u8],
1539    ) -> Result<usize, crate::enforcement::ShapeViolation>;
1540}
1541
1542/// ADR-036 resolver trait: ψ_8 — PostnikovTower → HomotopyGroups per ADR-035.
1543///
1544/// Parameterized by the model's H-axis (`H: Hasher` per ADR-022 D5) so
1545/// resolver impls compute content-addressed fingerprints using the
1546/// model's chosen hash impl. Sealed via [`__sdk_seal::Sealed`]: only
1547/// the SDK `resolver!` macro emits impls. Foundation provides a Null
1548/// impl whose `resolve` emits the `RESOLVER_ABSENT` shape violation.
1549///
1550/// ADR-041: `input` is a zero-cost typed carrier so ψ-stage
1551/// composition is type-checked at the resolver-impl boundary.
1552pub trait HomotopyGroupResolver<H: crate::enforcement::Hasher>: __sdk_seal::Sealed {
1553    /// Resolve per-value content for this category.
1554    /// # Errors
1555    /// Returns [`crate::enforcement::ShapeViolation`] when the resolver
1556    /// cannot produce content (e.g., the foundation Null impl carrying
1557    /// the `RESOLVER_ABSENT` discriminator).
1558    fn resolve(
1559        &self,
1560        input: PostnikovTowerBytes<'_>,
1561        out: &mut [u8],
1562    ) -> Result<usize, crate::enforcement::ShapeViolation>;
1563}
1564
1565/// ADR-036 resolver trait: ψ_9 — HomotopyGroups → KInvariants per ADR-035.
1566///
1567/// Parameterized by the model's H-axis (`H: Hasher` per ADR-022 D5) so
1568/// resolver impls compute content-addressed fingerprints using the
1569/// model's chosen hash impl. Sealed via [`__sdk_seal::Sealed`]: only
1570/// the SDK `resolver!` macro emits impls. Foundation provides a Null
1571/// impl whose `resolve` emits the `RESOLVER_ABSENT` shape violation.
1572///
1573/// ADR-041: `input` is a zero-cost typed carrier so ψ-stage
1574/// composition is type-checked at the resolver-impl boundary.
1575pub trait KInvariantResolver<H: crate::enforcement::Hasher>: __sdk_seal::Sealed {
1576    /// Resolve per-value content for this category.
1577    /// # Errors
1578    /// Returns [`crate::enforcement::ShapeViolation`] when the resolver
1579    /// cannot produce content (e.g., the foundation Null impl carrying
1580    /// the `RESOLVER_ABSENT` discriminator).
1581    fn resolve(
1582        &self,
1583        input: HomotopyGroupsBytes<'_>,
1584        out: &mut [u8],
1585    ) -> Result<usize, crate::enforcement::ShapeViolation>;
1586}
1587
1588/// ADR-036 marker trait: ResolverTuple positions including a `NerveResolver`.
1589/// The `prism_model!` macro infers the where-clause bound for each
1590/// resolver-bound ψ-Term variant a verb body emits.
1591pub trait HasNerveResolver<H: crate::enforcement::Hasher>: ResolverTuple {
1592    /// Returns the `NerveResolver` impl this ResolverTuple carries.
1593    fn nerve_resolver(&self) -> &dyn NerveResolver<H>;
1594}
1595
1596/// ADR-036 marker trait: ResolverTuple positions including a `ChainComplexResolver`.
1597/// The `prism_model!` macro infers the where-clause bound for each
1598/// resolver-bound ψ-Term variant a verb body emits.
1599pub trait HasChainComplexResolver<H: crate::enforcement::Hasher>: ResolverTuple {
1600    /// Returns the `ChainComplexResolver` impl this ResolverTuple carries.
1601    fn chain_complex_resolver(&self) -> &dyn ChainComplexResolver<H>;
1602}
1603
1604/// ADR-036 marker trait: ResolverTuple positions including a `HomologyGroupResolver`.
1605/// The `prism_model!` macro infers the where-clause bound for each
1606/// resolver-bound ψ-Term variant a verb body emits.
1607pub trait HasHomologyGroupResolver<H: crate::enforcement::Hasher>: ResolverTuple {
1608    /// Returns the `HomologyGroupResolver` impl this ResolverTuple carries.
1609    fn homology_group_resolver(&self) -> &dyn HomologyGroupResolver<H>;
1610}
1611
1612/// ADR-036 marker trait: ResolverTuple positions including a `CochainComplexResolver`.
1613/// The `prism_model!` macro infers the where-clause bound for each
1614/// resolver-bound ψ-Term variant a verb body emits.
1615pub trait HasCochainComplexResolver<H: crate::enforcement::Hasher>: ResolverTuple {
1616    /// Returns the `CochainComplexResolver` impl this ResolverTuple carries.
1617    fn cochain_complex_resolver(&self) -> &dyn CochainComplexResolver<H>;
1618}
1619
1620/// ADR-036 marker trait: ResolverTuple positions including a `CohomologyGroupResolver`.
1621/// The `prism_model!` macro infers the where-clause bound for each
1622/// resolver-bound ψ-Term variant a verb body emits.
1623pub trait HasCohomologyGroupResolver<H: crate::enforcement::Hasher>: ResolverTuple {
1624    /// Returns the `CohomologyGroupResolver` impl this ResolverTuple carries.
1625    fn cohomology_group_resolver(&self) -> &dyn CohomologyGroupResolver<H>;
1626}
1627
1628/// ADR-036 marker trait: ResolverTuple positions including a `PostnikovResolver`.
1629/// The `prism_model!` macro infers the where-clause bound for each
1630/// resolver-bound ψ-Term variant a verb body emits.
1631pub trait HasPostnikovResolver<H: crate::enforcement::Hasher>: ResolverTuple {
1632    /// Returns the `PostnikovResolver` impl this ResolverTuple carries.
1633    fn postnikov_resolver(&self) -> &dyn PostnikovResolver<H>;
1634}
1635
1636/// ADR-036 marker trait: ResolverTuple positions including a `HomotopyGroupResolver`.
1637/// The `prism_model!` macro infers the where-clause bound for each
1638/// resolver-bound ψ-Term variant a verb body emits.
1639pub trait HasHomotopyGroupResolver<H: crate::enforcement::Hasher>: ResolverTuple {
1640    /// Returns the `HomotopyGroupResolver` impl this ResolverTuple carries.
1641    fn homotopy_group_resolver(&self) -> &dyn HomotopyGroupResolver<H>;
1642}
1643
1644/// ADR-036 marker trait: ResolverTuple positions including a `KInvariantResolver`.
1645/// The `prism_model!` macro infers the where-clause bound for each
1646/// resolver-bound ψ-Term variant a verb body emits.
1647pub trait HasKInvariantResolver<H: crate::enforcement::Hasher>: ResolverTuple {
1648    /// Returns the `KInvariantResolver` impl this ResolverTuple carries.
1649    fn k_invariant_resolver(&self) -> &dyn KInvariantResolver<H>;
1650}
1651
1652/// ADR-036 Null resolver tuple — the resolver-absent default.
1653/// `ResolverTuple` impl with `ARITY = 0` and an empty CATEGORIES list.
1654/// Applications that don't declare a `resolver!` block default to this.
1655#[derive(Debug, Clone, Copy, Default)]
1656pub struct NullResolverTuple;
1657
1658impl __sdk_seal::Sealed for NullResolverTuple {}
1659
1660impl ResolverTuple for NullResolverTuple {
1661    const ARITY: usize = 0;
1662    const CATEGORIES: &'static [ResolverCategory] = &[];
1663}
1664
1665/// ADR-036 Null `NerveResolver` impl. `resolve` always emits the
1666/// `RESOLVER_ABSENT` shape violation — the catamorphism translates this
1667/// into `PipelineFailure::ShapeViolation` recoverable via `Term::Try`'s
1668/// default-propagation handler (ADR-022 D3 G9).
1669#[derive(Debug, Default)]
1670pub struct NullNerveResolver<H: crate::enforcement::Hasher>(core::marker::PhantomData<H>);
1671
1672impl<H: crate::enforcement::Hasher> NullNerveResolver<H> {
1673    /// Construct a new Null resolver.
1674    #[must_use]
1675    pub const fn new() -> Self {
1676        Self(core::marker::PhantomData)
1677    }
1678}
1679
1680impl<H: crate::enforcement::Hasher> __sdk_seal::Sealed for NullNerveResolver<H> {}
1681
1682impl<H: crate::enforcement::Hasher> NerveResolver<H> for NullNerveResolver<H> {
1683    fn resolve(
1684        &self,
1685        _input: &[u8],
1686        _out: &mut [u8],
1687    ) -> Result<usize, crate::enforcement::ShapeViolation> {
1688        Err(crate::enforcement::ShapeViolation {
1689            shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
1690            constraint_iri: "https://uor.foundation/resolver/Nerve",
1691            property_iri: "https://uor.foundation/resolver/category",
1692            expected_range: "https://uor.foundation/resolver/Resolver",
1693            min_count: 0,
1694            max_count: 1,
1695            kind: crate::ViolationKind::ValueCheck,
1696        })
1697    }
1698}
1699
1700/// ADR-036 Null `ChainComplexResolver` impl. `resolve` always emits the
1701/// `RESOLVER_ABSENT` shape violation — the catamorphism translates this
1702/// into `PipelineFailure::ShapeViolation` recoverable via `Term::Try`'s
1703/// default-propagation handler (ADR-022 D3 G9).
1704#[derive(Debug, Default)]
1705pub struct NullChainComplexResolver<H: crate::enforcement::Hasher>(core::marker::PhantomData<H>);
1706
1707impl<H: crate::enforcement::Hasher> NullChainComplexResolver<H> {
1708    /// Construct a new Null resolver.
1709    #[must_use]
1710    pub const fn new() -> Self {
1711        Self(core::marker::PhantomData)
1712    }
1713}
1714
1715impl<H: crate::enforcement::Hasher> __sdk_seal::Sealed for NullChainComplexResolver<H> {}
1716
1717impl<H: crate::enforcement::Hasher> ChainComplexResolver<H> for NullChainComplexResolver<H> {
1718    fn resolve(
1719        &self,
1720        _input: SimplicialComplexBytes<'_>,
1721        _out: &mut [u8],
1722    ) -> Result<usize, crate::enforcement::ShapeViolation> {
1723        Err(crate::enforcement::ShapeViolation {
1724            shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
1725            constraint_iri: "https://uor.foundation/resolver/ChainComplex",
1726            property_iri: "https://uor.foundation/resolver/category",
1727            expected_range: "https://uor.foundation/resolver/Resolver",
1728            min_count: 0,
1729            max_count: 1,
1730            kind: crate::ViolationKind::ValueCheck,
1731        })
1732    }
1733}
1734
1735/// ADR-036 Null `HomologyGroupResolver` impl. `resolve` always emits the
1736/// `RESOLVER_ABSENT` shape violation — the catamorphism translates this
1737/// into `PipelineFailure::ShapeViolation` recoverable via `Term::Try`'s
1738/// default-propagation handler (ADR-022 D3 G9).
1739#[derive(Debug, Default)]
1740pub struct NullHomologyGroupResolver<H: crate::enforcement::Hasher>(core::marker::PhantomData<H>);
1741
1742impl<H: crate::enforcement::Hasher> NullHomologyGroupResolver<H> {
1743    /// Construct a new Null resolver.
1744    #[must_use]
1745    pub const fn new() -> Self {
1746        Self(core::marker::PhantomData)
1747    }
1748}
1749
1750impl<H: crate::enforcement::Hasher> __sdk_seal::Sealed for NullHomologyGroupResolver<H> {}
1751
1752impl<H: crate::enforcement::Hasher> HomologyGroupResolver<H> for NullHomologyGroupResolver<H> {
1753    fn resolve(
1754        &self,
1755        _input: ChainComplexBytes<'_>,
1756        _out: &mut [u8],
1757    ) -> Result<usize, crate::enforcement::ShapeViolation> {
1758        Err(crate::enforcement::ShapeViolation {
1759            shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
1760            constraint_iri: "https://uor.foundation/resolver/HomologyGroup",
1761            property_iri: "https://uor.foundation/resolver/category",
1762            expected_range: "https://uor.foundation/resolver/Resolver",
1763            min_count: 0,
1764            max_count: 1,
1765            kind: crate::ViolationKind::ValueCheck,
1766        })
1767    }
1768}
1769
1770/// ADR-036 Null `CochainComplexResolver` impl. `resolve` always emits the
1771/// `RESOLVER_ABSENT` shape violation — the catamorphism translates this
1772/// into `PipelineFailure::ShapeViolation` recoverable via `Term::Try`'s
1773/// default-propagation handler (ADR-022 D3 G9).
1774#[derive(Debug, Default)]
1775pub struct NullCochainComplexResolver<H: crate::enforcement::Hasher>(core::marker::PhantomData<H>);
1776
1777impl<H: crate::enforcement::Hasher> NullCochainComplexResolver<H> {
1778    /// Construct a new Null resolver.
1779    #[must_use]
1780    pub const fn new() -> Self {
1781        Self(core::marker::PhantomData)
1782    }
1783}
1784
1785impl<H: crate::enforcement::Hasher> __sdk_seal::Sealed for NullCochainComplexResolver<H> {}
1786
1787impl<H: crate::enforcement::Hasher> CochainComplexResolver<H> for NullCochainComplexResolver<H> {
1788    fn resolve(
1789        &self,
1790        _input: ChainComplexBytes<'_>,
1791        _out: &mut [u8],
1792    ) -> Result<usize, crate::enforcement::ShapeViolation> {
1793        Err(crate::enforcement::ShapeViolation {
1794            shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
1795            constraint_iri: "https://uor.foundation/resolver/CochainComplex",
1796            property_iri: "https://uor.foundation/resolver/category",
1797            expected_range: "https://uor.foundation/resolver/Resolver",
1798            min_count: 0,
1799            max_count: 1,
1800            kind: crate::ViolationKind::ValueCheck,
1801        })
1802    }
1803}
1804
1805/// ADR-036 Null `CohomologyGroupResolver` impl. `resolve` always emits the
1806/// `RESOLVER_ABSENT` shape violation — the catamorphism translates this
1807/// into `PipelineFailure::ShapeViolation` recoverable via `Term::Try`'s
1808/// default-propagation handler (ADR-022 D3 G9).
1809#[derive(Debug, Default)]
1810pub struct NullCohomologyGroupResolver<H: crate::enforcement::Hasher>(core::marker::PhantomData<H>);
1811
1812impl<H: crate::enforcement::Hasher> NullCohomologyGroupResolver<H> {
1813    /// Construct a new Null resolver.
1814    #[must_use]
1815    pub const fn new() -> Self {
1816        Self(core::marker::PhantomData)
1817    }
1818}
1819
1820impl<H: crate::enforcement::Hasher> __sdk_seal::Sealed for NullCohomologyGroupResolver<H> {}
1821
1822impl<H: crate::enforcement::Hasher> CohomologyGroupResolver<H> for NullCohomologyGroupResolver<H> {
1823    fn resolve(
1824        &self,
1825        _input: CochainComplexBytes<'_>,
1826        _out: &mut [u8],
1827    ) -> Result<usize, crate::enforcement::ShapeViolation> {
1828        Err(crate::enforcement::ShapeViolation {
1829            shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
1830            constraint_iri: "https://uor.foundation/resolver/CohomologyGroup",
1831            property_iri: "https://uor.foundation/resolver/category",
1832            expected_range: "https://uor.foundation/resolver/Resolver",
1833            min_count: 0,
1834            max_count: 1,
1835            kind: crate::ViolationKind::ValueCheck,
1836        })
1837    }
1838}
1839
1840/// ADR-036 Null `PostnikovResolver` impl. `resolve` always emits the
1841/// `RESOLVER_ABSENT` shape violation — the catamorphism translates this
1842/// into `PipelineFailure::ShapeViolation` recoverable via `Term::Try`'s
1843/// default-propagation handler (ADR-022 D3 G9).
1844#[derive(Debug, Default)]
1845pub struct NullPostnikovResolver<H: crate::enforcement::Hasher>(core::marker::PhantomData<H>);
1846
1847impl<H: crate::enforcement::Hasher> NullPostnikovResolver<H> {
1848    /// Construct a new Null resolver.
1849    #[must_use]
1850    pub const fn new() -> Self {
1851        Self(core::marker::PhantomData)
1852    }
1853}
1854
1855impl<H: crate::enforcement::Hasher> __sdk_seal::Sealed for NullPostnikovResolver<H> {}
1856
1857impl<H: crate::enforcement::Hasher> PostnikovResolver<H> for NullPostnikovResolver<H> {
1858    fn resolve(
1859        &self,
1860        _input: SimplicialComplexBytes<'_>,
1861        _out: &mut [u8],
1862    ) -> Result<usize, crate::enforcement::ShapeViolation> {
1863        Err(crate::enforcement::ShapeViolation {
1864            shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
1865            constraint_iri: "https://uor.foundation/resolver/Postnikov",
1866            property_iri: "https://uor.foundation/resolver/category",
1867            expected_range: "https://uor.foundation/resolver/Resolver",
1868            min_count: 0,
1869            max_count: 1,
1870            kind: crate::ViolationKind::ValueCheck,
1871        })
1872    }
1873}
1874
1875/// ADR-036 Null `HomotopyGroupResolver` impl. `resolve` always emits the
1876/// `RESOLVER_ABSENT` shape violation — the catamorphism translates this
1877/// into `PipelineFailure::ShapeViolation` recoverable via `Term::Try`'s
1878/// default-propagation handler (ADR-022 D3 G9).
1879#[derive(Debug, Default)]
1880pub struct NullHomotopyGroupResolver<H: crate::enforcement::Hasher>(core::marker::PhantomData<H>);
1881
1882impl<H: crate::enforcement::Hasher> NullHomotopyGroupResolver<H> {
1883    /// Construct a new Null resolver.
1884    #[must_use]
1885    pub const fn new() -> Self {
1886        Self(core::marker::PhantomData)
1887    }
1888}
1889
1890impl<H: crate::enforcement::Hasher> __sdk_seal::Sealed for NullHomotopyGroupResolver<H> {}
1891
1892impl<H: crate::enforcement::Hasher> HomotopyGroupResolver<H> for NullHomotopyGroupResolver<H> {
1893    fn resolve(
1894        &self,
1895        _input: PostnikovTowerBytes<'_>,
1896        _out: &mut [u8],
1897    ) -> Result<usize, crate::enforcement::ShapeViolation> {
1898        Err(crate::enforcement::ShapeViolation {
1899            shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
1900            constraint_iri: "https://uor.foundation/resolver/HomotopyGroup",
1901            property_iri: "https://uor.foundation/resolver/category",
1902            expected_range: "https://uor.foundation/resolver/Resolver",
1903            min_count: 0,
1904            max_count: 1,
1905            kind: crate::ViolationKind::ValueCheck,
1906        })
1907    }
1908}
1909
1910/// ADR-036 Null `KInvariantResolver` impl. `resolve` always emits the
1911/// `RESOLVER_ABSENT` shape violation — the catamorphism translates this
1912/// into `PipelineFailure::ShapeViolation` recoverable via `Term::Try`'s
1913/// default-propagation handler (ADR-022 D3 G9).
1914#[derive(Debug, Default)]
1915pub struct NullKInvariantResolver<H: crate::enforcement::Hasher>(core::marker::PhantomData<H>);
1916
1917impl<H: crate::enforcement::Hasher> NullKInvariantResolver<H> {
1918    /// Construct a new Null resolver.
1919    #[must_use]
1920    pub const fn new() -> Self {
1921        Self(core::marker::PhantomData)
1922    }
1923}
1924
1925impl<H: crate::enforcement::Hasher> __sdk_seal::Sealed for NullKInvariantResolver<H> {}
1926
1927impl<H: crate::enforcement::Hasher> KInvariantResolver<H> for NullKInvariantResolver<H> {
1928    fn resolve(
1929        &self,
1930        _input: HomotopyGroupsBytes<'_>,
1931        _out: &mut [u8],
1932    ) -> Result<usize, crate::enforcement::ShapeViolation> {
1933        Err(crate::enforcement::ShapeViolation {
1934            shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
1935            constraint_iri: "https://uor.foundation/resolver/KInvariant",
1936            property_iri: "https://uor.foundation/resolver/category",
1937            expected_range: "https://uor.foundation/resolver/Resolver",
1938            min_count: 0,
1939            max_count: 1,
1940            kind: crate::ViolationKind::ValueCheck,
1941        })
1942    }
1943}
1944
1945/// ADR-036: NullResolverTuple satisfies `NerveResolver<H>` directly so
1946/// the `HasNerveResolver<H>` accessor can return `self` cast to `&dyn NerveResolver<H>`.
1947/// The `resolve` method emits the `RESOLVER_ABSENT` shape violation —
1948/// recoverable via `Term::Try`'s default-propagation handler (ADR-022 D3 G9).
1949impl<H: crate::enforcement::Hasher> NerveResolver<H> for NullResolverTuple {
1950    fn resolve(
1951        &self,
1952        _input: &[u8],
1953        _out: &mut [u8],
1954    ) -> Result<usize, crate::enforcement::ShapeViolation> {
1955        Err(crate::enforcement::ShapeViolation {
1956            shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
1957            constraint_iri: "https://uor.foundation/resolver/Nerve",
1958            property_iri: "https://uor.foundation/resolver/category",
1959            expected_range: "https://uor.foundation/resolver/Resolver",
1960            min_count: 0,
1961            max_count: 1,
1962            kind: crate::ViolationKind::ValueCheck,
1963        })
1964    }
1965}
1966
1967/// ADR-036: NullResolverTuple satisfies `HasNerveResolver<H>` (returns `self`).
1968impl<H: crate::enforcement::Hasher> HasNerveResolver<H> for NullResolverTuple {
1969    fn nerve_resolver(&self) -> &dyn NerveResolver<H> {
1970        self
1971    }
1972}
1973
1974/// ADR-036: NullResolverTuple satisfies `ChainComplexResolver<H>` directly so
1975/// the `HasChainComplexResolver<H>` accessor can return `self` cast to `&dyn ChainComplexResolver<H>`.
1976/// The `resolve` method emits the `RESOLVER_ABSENT` shape violation —
1977/// recoverable via `Term::Try`'s default-propagation handler (ADR-022 D3 G9).
1978impl<H: crate::enforcement::Hasher> ChainComplexResolver<H> for NullResolverTuple {
1979    fn resolve(
1980        &self,
1981        _input: SimplicialComplexBytes<'_>,
1982        _out: &mut [u8],
1983    ) -> Result<usize, crate::enforcement::ShapeViolation> {
1984        Err(crate::enforcement::ShapeViolation {
1985            shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
1986            constraint_iri: "https://uor.foundation/resolver/ChainComplex",
1987            property_iri: "https://uor.foundation/resolver/category",
1988            expected_range: "https://uor.foundation/resolver/Resolver",
1989            min_count: 0,
1990            max_count: 1,
1991            kind: crate::ViolationKind::ValueCheck,
1992        })
1993    }
1994}
1995
1996/// ADR-036: NullResolverTuple satisfies `HasChainComplexResolver<H>` (returns `self`).
1997impl<H: crate::enforcement::Hasher> HasChainComplexResolver<H> for NullResolverTuple {
1998    fn chain_complex_resolver(&self) -> &dyn ChainComplexResolver<H> {
1999        self
2000    }
2001}
2002
2003/// ADR-036: NullResolverTuple satisfies `HomologyGroupResolver<H>` directly so
2004/// the `HasHomologyGroupResolver<H>` accessor can return `self` cast to `&dyn HomologyGroupResolver<H>`.
2005/// The `resolve` method emits the `RESOLVER_ABSENT` shape violation —
2006/// recoverable via `Term::Try`'s default-propagation handler (ADR-022 D3 G9).
2007impl<H: crate::enforcement::Hasher> HomologyGroupResolver<H> for NullResolverTuple {
2008    fn resolve(
2009        &self,
2010        _input: ChainComplexBytes<'_>,
2011        _out: &mut [u8],
2012    ) -> Result<usize, crate::enforcement::ShapeViolation> {
2013        Err(crate::enforcement::ShapeViolation {
2014            shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2015            constraint_iri: "https://uor.foundation/resolver/HomologyGroup",
2016            property_iri: "https://uor.foundation/resolver/category",
2017            expected_range: "https://uor.foundation/resolver/Resolver",
2018            min_count: 0,
2019            max_count: 1,
2020            kind: crate::ViolationKind::ValueCheck,
2021        })
2022    }
2023}
2024
2025/// ADR-036: NullResolverTuple satisfies `HasHomologyGroupResolver<H>` (returns `self`).
2026impl<H: crate::enforcement::Hasher> HasHomologyGroupResolver<H> for NullResolverTuple {
2027    fn homology_group_resolver(&self) -> &dyn HomologyGroupResolver<H> {
2028        self
2029    }
2030}
2031
2032/// ADR-036: NullResolverTuple satisfies `CochainComplexResolver<H>` directly so
2033/// the `HasCochainComplexResolver<H>` accessor can return `self` cast to `&dyn CochainComplexResolver<H>`.
2034/// The `resolve` method emits the `RESOLVER_ABSENT` shape violation —
2035/// recoverable via `Term::Try`'s default-propagation handler (ADR-022 D3 G9).
2036impl<H: crate::enforcement::Hasher> CochainComplexResolver<H> for NullResolverTuple {
2037    fn resolve(
2038        &self,
2039        _input: ChainComplexBytes<'_>,
2040        _out: &mut [u8],
2041    ) -> Result<usize, crate::enforcement::ShapeViolation> {
2042        Err(crate::enforcement::ShapeViolation {
2043            shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2044            constraint_iri: "https://uor.foundation/resolver/CochainComplex",
2045            property_iri: "https://uor.foundation/resolver/category",
2046            expected_range: "https://uor.foundation/resolver/Resolver",
2047            min_count: 0,
2048            max_count: 1,
2049            kind: crate::ViolationKind::ValueCheck,
2050        })
2051    }
2052}
2053
2054/// ADR-036: NullResolverTuple satisfies `HasCochainComplexResolver<H>` (returns `self`).
2055impl<H: crate::enforcement::Hasher> HasCochainComplexResolver<H> for NullResolverTuple {
2056    fn cochain_complex_resolver(&self) -> &dyn CochainComplexResolver<H> {
2057        self
2058    }
2059}
2060
2061/// ADR-036: NullResolverTuple satisfies `CohomologyGroupResolver<H>` directly so
2062/// the `HasCohomologyGroupResolver<H>` accessor can return `self` cast to `&dyn CohomologyGroupResolver<H>`.
2063/// The `resolve` method emits the `RESOLVER_ABSENT` shape violation —
2064/// recoverable via `Term::Try`'s default-propagation handler (ADR-022 D3 G9).
2065impl<H: crate::enforcement::Hasher> CohomologyGroupResolver<H> for NullResolverTuple {
2066    fn resolve(
2067        &self,
2068        _input: CochainComplexBytes<'_>,
2069        _out: &mut [u8],
2070    ) -> Result<usize, crate::enforcement::ShapeViolation> {
2071        Err(crate::enforcement::ShapeViolation {
2072            shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2073            constraint_iri: "https://uor.foundation/resolver/CohomologyGroup",
2074            property_iri: "https://uor.foundation/resolver/category",
2075            expected_range: "https://uor.foundation/resolver/Resolver",
2076            min_count: 0,
2077            max_count: 1,
2078            kind: crate::ViolationKind::ValueCheck,
2079        })
2080    }
2081}
2082
2083/// ADR-036: NullResolverTuple satisfies `HasCohomologyGroupResolver<H>` (returns `self`).
2084impl<H: crate::enforcement::Hasher> HasCohomologyGroupResolver<H> for NullResolverTuple {
2085    fn cohomology_group_resolver(&self) -> &dyn CohomologyGroupResolver<H> {
2086        self
2087    }
2088}
2089
2090/// ADR-036: NullResolverTuple satisfies `PostnikovResolver<H>` directly so
2091/// the `HasPostnikovResolver<H>` accessor can return `self` cast to `&dyn PostnikovResolver<H>`.
2092/// The `resolve` method emits the `RESOLVER_ABSENT` shape violation —
2093/// recoverable via `Term::Try`'s default-propagation handler (ADR-022 D3 G9).
2094impl<H: crate::enforcement::Hasher> PostnikovResolver<H> for NullResolverTuple {
2095    fn resolve(
2096        &self,
2097        _input: SimplicialComplexBytes<'_>,
2098        _out: &mut [u8],
2099    ) -> Result<usize, crate::enforcement::ShapeViolation> {
2100        Err(crate::enforcement::ShapeViolation {
2101            shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2102            constraint_iri: "https://uor.foundation/resolver/Postnikov",
2103            property_iri: "https://uor.foundation/resolver/category",
2104            expected_range: "https://uor.foundation/resolver/Resolver",
2105            min_count: 0,
2106            max_count: 1,
2107            kind: crate::ViolationKind::ValueCheck,
2108        })
2109    }
2110}
2111
2112/// ADR-036: NullResolverTuple satisfies `HasPostnikovResolver<H>` (returns `self`).
2113impl<H: crate::enforcement::Hasher> HasPostnikovResolver<H> for NullResolverTuple {
2114    fn postnikov_resolver(&self) -> &dyn PostnikovResolver<H> {
2115        self
2116    }
2117}
2118
2119/// ADR-036: NullResolverTuple satisfies `HomotopyGroupResolver<H>` directly so
2120/// the `HasHomotopyGroupResolver<H>` accessor can return `self` cast to `&dyn HomotopyGroupResolver<H>`.
2121/// The `resolve` method emits the `RESOLVER_ABSENT` shape violation —
2122/// recoverable via `Term::Try`'s default-propagation handler (ADR-022 D3 G9).
2123impl<H: crate::enforcement::Hasher> HomotopyGroupResolver<H> for NullResolverTuple {
2124    fn resolve(
2125        &self,
2126        _input: PostnikovTowerBytes<'_>,
2127        _out: &mut [u8],
2128    ) -> Result<usize, crate::enforcement::ShapeViolation> {
2129        Err(crate::enforcement::ShapeViolation {
2130            shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2131            constraint_iri: "https://uor.foundation/resolver/HomotopyGroup",
2132            property_iri: "https://uor.foundation/resolver/category",
2133            expected_range: "https://uor.foundation/resolver/Resolver",
2134            min_count: 0,
2135            max_count: 1,
2136            kind: crate::ViolationKind::ValueCheck,
2137        })
2138    }
2139}
2140
2141/// ADR-036: NullResolverTuple satisfies `HasHomotopyGroupResolver<H>` (returns `self`).
2142impl<H: crate::enforcement::Hasher> HasHomotopyGroupResolver<H> for NullResolverTuple {
2143    fn homotopy_group_resolver(&self) -> &dyn HomotopyGroupResolver<H> {
2144        self
2145    }
2146}
2147
2148/// ADR-036: NullResolverTuple satisfies `KInvariantResolver<H>` directly so
2149/// the `HasKInvariantResolver<H>` accessor can return `self` cast to `&dyn KInvariantResolver<H>`.
2150/// The `resolve` method emits the `RESOLVER_ABSENT` shape violation —
2151/// recoverable via `Term::Try`'s default-propagation handler (ADR-022 D3 G9).
2152impl<H: crate::enforcement::Hasher> KInvariantResolver<H> for NullResolverTuple {
2153    fn resolve(
2154        &self,
2155        _input: HomotopyGroupsBytes<'_>,
2156        _out: &mut [u8],
2157    ) -> Result<usize, crate::enforcement::ShapeViolation> {
2158        Err(crate::enforcement::ShapeViolation {
2159            shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2160            constraint_iri: "https://uor.foundation/resolver/KInvariant",
2161            property_iri: "https://uor.foundation/resolver/category",
2162            expected_range: "https://uor.foundation/resolver/Resolver",
2163            min_count: 0,
2164            max_count: 1,
2165            kind: crate::ViolationKind::ValueCheck,
2166        })
2167    }
2168}
2169
2170/// ADR-036: NullResolverTuple satisfies `HasKInvariantResolver<H>` (returns `self`).
2171impl<H: crate::enforcement::Hasher> HasKInvariantResolver<H> for NullResolverTuple {
2172    fn k_invariant_resolver(&self) -> &dyn KInvariantResolver<H> {
2173        self
2174    }
2175}
2176
2177/// Wiki ADR-042: typed view over a successful `Grounded<T, Tag>` as the
2178/// inhabitance-verdict envelope produced by the canonical k-invariants
2179/// branch (ψ_1 → ψ_7 → ψ_8 → ψ_9 per ADR-035).
2180///
2181/// Zero-cost — `#[repr(transparent)]` over `&'a Grounded<T, Tag>`. Construct
2182/// via [`crate::enforcement::Grounded::as_inhabitance_certificate`].
2183///
2184/// Realizes `cert:InhabitanceCertificate` per the ontology
2185/// (`<https://uor.foundation/cert/InhabitanceCertificate>`). Foundation
2186/// uses the suffix `View` here to distinguish this zero-cost typed
2187/// view from the existing sealed-shim `crate::enforcement::InhabitanceCertificate`
2188/// value carrier; the wiki names this type `InhabitanceCertificate<'a, T>`
2189/// in ADR-042 and the typed-view role is the load-bearing concern. The κ-label,
2190/// concrete `cert:witness`, and `cert:searchTrace` are accessor methods
2191/// over the underlying `Grounded` — no allocation, no per-application
2192/// re-derivation.
2193#[repr(transparent)]
2194#[derive(Debug, Clone, Copy)]
2195pub struct InhabitanceCertificateView<'a, T: crate::enforcement::GroundedShape, Tag = T>(
2196    pub &'a crate::enforcement::Grounded<T, Tag>,
2197);
2198
2199impl<'a, T: crate::enforcement::GroundedShape, Tag> InhabitanceCertificateView<'a, T, Tag> {
2200    /// The κ-label — the homotopy-classification structural witness at
2201    /// ψ_9 per ADR-035. The bytes are the `Term::KInvariants` emission's
2202    /// output (exposed via `Grounded::output_bytes`) wrapped in the
2203    /// ADR-041 typed-coordinate carrier so cross-stage composition is
2204    /// type-checked.
2205    #[inline]
2206    #[must_use]
2207    pub fn kappa_label(&self) -> KInvariantsBytes<'_> {
2208        KInvariantsBytes(self.0.output_bytes())
2209    }
2210
2211    /// The concrete `cert:witness` ValueTuple — derivable from
2212    /// `Term::Nerve`'s 0-simplices at ψ_1 (the per-value bytes the
2213    /// model's NerveResolver consumed). Foundation exposes the
2214    /// `Grounded`'s bindings as the value-tuple surface; applications
2215    /// whose admission relations carry richer witness data project
2216    /// through the binding table's content addresses.
2217    #[inline]
2218    #[must_use]
2219    pub fn witness(&self) -> WitnessValueTuple<'_> {
2220        WitnessValueTuple {
2221            grounded_bindings: self.0,
2222        }
2223    }
2224
2225    /// The `cert:searchTrace` — realized as
2226    /// `<Grounded>::derivation::<H>(...).replay::<...>()` per ADR-039.
2227    /// Foundation surfaces the derivation pointer; applications choose
2228    /// which `Hasher` impl and `HostBounds` parameters to instantiate
2229    /// the replay with.
2230    #[inline]
2231    #[must_use]
2232    pub fn certificate(
2233        &self,
2234    ) -> &crate::enforcement::Validated<crate::enforcement::GroundingCertificate> {
2235        self.0.certificate()
2236    }
2237
2238    /// The certified type's stable IRI — the `<T as ConstrainedTypeShape>::IRI`
2239    /// the application registered as the route's output shape.
2240    #[inline]
2241    #[must_use]
2242    pub fn certified_type(&self) -> &'static str
2243    where
2244        T: ConstrainedTypeShape,
2245    {
2246        <T as ConstrainedTypeShape>::IRI
2247    }
2248}
2249
2250/// Wiki ADR-042: concrete `cert:witness` ValueTuple view. Borrows the
2251/// underlying `Grounded`'s bindings; iterates as `(content_address, bytes)`
2252/// pairs corresponding to `Term::Nerve`'s 0-simplices.
2253#[derive(Clone, Copy)]
2254pub struct WitnessValueTuple<'a> {
2255    /// Foundation-internal: the underlying Grounded reference. Public-API
2256    /// access goes through the accessor methods.
2257    grounded_bindings: &'a dyn WitnessTupleSource,
2258}
2259
2260impl<'a> core::fmt::Debug for WitnessValueTuple<'a> {
2261    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2262        f.debug_struct("WitnessValueTuple")
2263            .field("binding_count", &self.grounded_bindings.binding_count())
2264            .finish()
2265    }
2266}
2267
2268impl<'a> WitnessValueTuple<'a> {
2269    /// Number of bindings in the witness tuple.
2270    #[inline]
2271    #[must_use]
2272    pub fn len(&self) -> usize {
2273        self.grounded_bindings.binding_count()
2274    }
2275
2276    /// Whether the witness tuple is empty.
2277    #[inline]
2278    #[must_use]
2279    pub fn is_empty(&self) -> bool {
2280        self.grounded_bindings.binding_count() == 0
2281    }
2282
2283    /// The witness's content-addressed binding bytes at position `idx`.
2284    /// Returns `None` if `idx >= len()`.
2285    #[inline]
2286    #[must_use]
2287    pub fn binding_bytes(&self, idx: usize) -> Option<&'static [u8]> {
2288        self.grounded_bindings.binding_bytes_at(idx)
2289    }
2290}
2291
2292/// Foundation-internal trait letting `Grounded<T, Tag>` expose binding
2293/// access to `WitnessValueTuple` without leaking the generic-parameter
2294/// plumbing into the witness-view type.
2295pub trait WitnessTupleSource {
2296    /// Number of bindings in this source's table.
2297    fn binding_count(&self) -> usize;
2298    /// Binding bytes at the given index, or `None` if out of range.
2299    fn binding_bytes_at(&self, idx: usize) -> Option<&'static [u8]>;
2300}
2301
2302impl<T: crate::enforcement::GroundedShape, Tag> WitnessTupleSource
2303    for crate::enforcement::Grounded<T, Tag>
2304{
2305    fn binding_count(&self) -> usize {
2306        self.iter_bindings().count()
2307    }
2308    fn binding_bytes_at(&self, idx: usize) -> Option<&'static [u8]> {
2309        self.iter_bindings().nth(idx).map(|e| e.bytes)
2310    }
2311}
2312
2313/// Wiki ADR-042: typed view over an `Err(PipelineFailure)` as the
2314/// inhabitance-impossibility envelope. Realizes
2315/// `cert:InhabitanceImpossibilityCertificate` per the ontology:
2316/// `<https://uor.foundation/cert/InhabitanceImpossibilityCertificate>`.
2317///
2318/// Zero-cost — `#[repr(transparent)]` over `&'a PipelineFailure`. Construct
2319/// via [`crate::enforcement::PipelineFailure::as_inhabitance_impossibility_certificate`].
2320#[repr(transparent)]
2321#[derive(Debug, Clone, Copy)]
2322pub struct InhabitanceImpossibilityCertificate<'a>(pub &'a crate::enforcement::PipelineFailure);
2323
2324impl<'a> InhabitanceImpossibilityCertificate<'a> {
2325    /// The contradiction-proof bytes — canonical-form encoding of the
2326    /// failure trace, suitable for Lean 4 by-contradiction reconstruction
2327    /// per ADR-039 + ADR-042. Foundation provides the shape-violation's
2328    /// `shape_iri` bytes as the proof's canonical-form witness; richer
2329    /// contradiction data is application-provided via the failure trace.
2330    #[inline]
2331    #[must_use]
2332    pub fn contradiction_proof(&self) -> &'static [u8] {
2333        match self.0 {
2334            crate::enforcement::PipelineFailure::ShapeViolation { report } => {
2335                report.shape_iri.as_bytes()
2336            }
2337            _ => b"https://uor.foundation/proof/InhabitanceImpossibilityWitness",
2338        }
2339    }
2340
2341    /// Borrow the underlying `PipelineFailure`.
2342    #[inline]
2343    #[must_use]
2344    pub fn failure(&self) -> &crate::enforcement::PipelineFailure {
2345        self.0
2346    }
2347}
2348
2349/// Wiki ADR-042: typed verdict-envelope accessors on `PipelineFailure`.
2350impl crate::enforcement::PipelineFailure {
2351    /// Borrow `self` as an [`InhabitanceImpossibilityCertificate`] view
2352    /// when the failure's structural cause is an inhabitance-impossibility
2353    /// witness (per ADR-042). Returns `Some` for `ShapeViolation` whose
2354    /// `shape_iri` carries one of the foundation-declared inhabitance
2355    /// proof IRIs (e.g. `RESOLVER_ABSENT`, the constraint-nerve-empty-
2356    /// Kan-completion sentinel); foundation accepts `Some(...)` universally
2357    /// for `PipelineFailure` so applications consume the verdict-envelope
2358    /// view at their discretion.
2359    #[inline]
2360    #[must_use]
2361    pub fn as_inhabitance_impossibility_certificate(
2362        &self,
2363    ) -> Option<InhabitanceImpossibilityCertificate<'_>> {
2364        Some(InhabitanceImpossibilityCertificate(self))
2365    }
2366}
2367
2368/// Wiki ADR-042: `predicate:InhabitanceDispatchTable` consultation helper.
2369/// Application NerveResolver impls MAY call this helper internally for
2370/// decider routing across the ontology's three canonical rule arms
2371/// (TwoSatDecider, HornSatDecider, ResidualVerdictResolver). Foundation
2372/// provides the dispatch surface; rule-arm semantics are application-
2373/// provided through the closures the caller threads in.
2374pub mod inhabitance {
2375    /// Three rule arms a `predicate:InhabitanceDispatchTable` consultation
2376    /// dispatches through, per the ontology's canonical decider routing.
2377    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2378    pub enum InhabitanceRuleArm {
2379        ///         TwoSatDecider — `predicate:TwoSatDecider`. Decides 2-SAT constraint nerves.
2380        TwoSatDecider,
2381        ///         HornSatDecider — `predicate:HornSatDecider`. Decides Horn-SAT constraint nerves.
2382        HornSatDecider,
2383        ///         ResidualVerdictResolver — `predicate:ResidualVerdictResolver`. Residual
2384        ///         catch-all for constraint nerves outside the 2-SAT / Horn-SAT coverage.
2385        ResidualVerdictResolver,
2386    }
2387
2388    /// Dispatch through the `predicate:InhabitanceDispatchTable` rule arms in
2389    /// ontology order (TwoSatDecider → HornSatDecider → ResidualVerdictResolver).
2390    /// Each closure returns `Some(verdict)` if the arm decides, or `None` to
2391    /// delegate to the next arm. Returns the first decisive verdict.
2392    #[inline]
2393    pub fn dispatch_through_table<F1, F2, F3, V>(
2394        two_sat: F1,
2395        horn_sat: F2,
2396        residual: F3,
2397    ) -> (InhabitanceRuleArm, V)
2398    where
2399        F1: FnOnce() -> Option<V>,
2400        F2: FnOnce() -> Option<V>,
2401        F3: FnOnce() -> V,
2402    {
2403        if let Some(v) = two_sat() {
2404            return (InhabitanceRuleArm::TwoSatDecider, v);
2405        }
2406        if let Some(v) = horn_sat() {
2407            return (InhabitanceRuleArm::HornSatDecider, v);
2408        }
2409        (InhabitanceRuleArm::ResidualVerdictResolver, residual())
2410    }
2411}
2412
2413/// Foundation-internal seal module — `__` prefix and `#[doc(hidden)]`
2414/// signal "for the SDK macro only." The `prism_model!` macro emits
2415/// `impl __sdk_seal::Sealed for <Model>` and
2416/// `impl __sdk_seal::Sealed for <RouteWitness>` alongside the
2417/// `PrismModel` and `FoundationClosed` impls.
2418#[doc(hidden)]
2419pub mod __sdk_seal {
2420    /// The supertrait `FoundationClosed` and `PrismModel` declare to
2421    /// seal application code out of impl'ing them. External crates that
2422    /// name this trait directly are syntactically permitted by Rust's
2423    /// visibility rules but architecturally non-conforming per wiki
2424    /// ADR-022 D1 — the `prism_model!` proc-macro from
2425    /// `uor-foundation-sdk` is the only sanctioned emitter of impls.
2426    pub trait Sealed {}
2427}
2428
2429/// Trait — `Route` types satisfying this bound are closed under
2430/// foundation vocabulary: every node in the witnessed term tree is a
2431/// foundation-vocabulary item.
2432/// Sealed via [`__sdk_seal::Sealed`]: the route-emitting `prism_model!`
2433/// macro from `uor-foundation-sdk` is the only sanctioned producer of
2434/// impls (per ADR-022 D1). Wiki ADR-020 specifies this as the
2435/// load-bearing enforcement of bilateral compile-time UORassembly
2436/// (TC-04, ADR-006) for whole-model declarations: a route that imports
2437/// a function outside foundation vocabulary receives no
2438/// `FoundationClosed` impl, and the application fails to compile with
2439/// an unsatisfied bound on `Route`.
2440/// # `arena_slice`
2441/// Per ADR-022 D5, [`run_route`] consumes the route's term-tree arena.
2442/// The `prism_model!` macro emits the [`arena_slice`] impl returning
2443/// the parsed closure body's term tree as a static slice. The
2444/// foundation-sanctioned identity route returns an empty slice
2445/// (no transformation, input passes through to output).
2446/// On stable Rust without `generic_const_exprs`, the slice form is
2447/// the equivalent of the wiki's `&'static TermArena` bound: it
2448/// exposes the term tree without forcing every Route to carry the
2449/// arena's `CAP` const-generic through the trait.
2450pub trait FoundationClosed: __sdk_seal::Sealed {
2451    /// Returns the term-tree arena slice the `prism_model!` macro emitted for
2452    /// this route witness. [`run_route`] reads this to populate the
2453    /// `CompileUnit`'s root_term before invoking [`run`].
2454    fn arena_slice() -> &'static [crate::enforcement::Term];
2455}
2456
2457/// Trait — `ConstrainedTypeShape` impls used as a `PrismModel::Input`
2458/// MUST implement this trait so [`run_route`] can serialize the
2459/// runtime input value into the `CompileUnit` binding table per wiki
2460/// ADR-023.
2461/// # Implementation contract
2462/// [`into_binding_bytes`] writes the canonical content-addressable byte
2463/// sequence for the value into the caller-provided buffer and returns
2464/// the written length. The serialization MUST be deterministic — two
2465/// values that compare equal MUST produce byte sequences that compare
2466/// equal — so the input's content fingerprint is a function of the
2467/// value alone.
2468/// [`MAX_BYTES`] is the maximum byte length any value of this shape can
2469/// produce. [`run_route`] uses it to size the on-stack buffer and
2470/// rejects inputs whose declared `MAX_BYTES` exceeds the foundation
2471/// ceiling [`ROUTE_INPUT_BUFFER_BYTES`].
2472/// # Sealing
2473/// Sealed via [`__sdk_seal::Sealed`] (the same supertrait as
2474/// [`FoundationClosed`] and [`PrismModel`]): foundation sanctions the
2475/// identity-route impl on [`ConstrainedTypeInput`] directly; the SDK
2476/// shape macros (`product_shape!`, `coproduct_shape!`,
2477/// `cartesian_product_shape!`) emit the impl alongside the
2478/// `ConstrainedTypeShape` impl. Application authors implementing a
2479/// custom `ConstrainedTypeShape` use the `prism_model!` macro's input
2480/// declaration to obtain the impl.
2481pub trait IntoBindingValue: ConstrainedTypeShape + __sdk_seal::Sealed {
2482    /// Maximum byte length any value of this shape can produce when
2483    /// serialized via [`into_binding_bytes`]. Used by [`run_route`] to
2484    /// size the on-stack buffer and reject inputs that would overflow.
2485    const MAX_BYTES: usize;
2486
2487    /// Serialize this input value into the binding-table form. `out` is a
2488    /// fixed-capacity buffer the call-site provides; the implementation
2489    /// writes the canonical content-addressable byte sequence and returns
2490    /// the written length.
2491    /// # Errors
2492    /// Returns [`crate::enforcement::ShapeViolation`] when the canonical
2493    /// serialization cannot be produced (e.g., a coproduct tag is out of
2494    /// range, a constraint cannot be witnessed) or when `out.len()` is
2495    /// smaller than the bytes the value requires.
2496    #[allow(clippy::wrong_self_convention)]
2497    fn into_binding_bytes(
2498        &self,
2499        out: &mut [u8],
2500    ) -> core::result::Result<usize, crate::enforcement::ShapeViolation>;
2501}
2502
2503/// Foundation-side ceiling for the on-stack buffer [`run_route`] uses to
2504/// materialize an input value's canonical bytes per wiki ADR-023.
2505/// On stable Rust 1.83 we cannot size the buffer with
2506/// `[u8; <T as IntoBindingValue>::MAX_BYTES]` (that requires nightly
2507/// `generic_const_exprs`). This foundation-fixed ceiling is the
2508/// architecturally-equivalent stable-Rust form: inputs declaring
2509/// `MAX_BYTES <= ROUTE_INPUT_BUFFER_BYTES` flow through the catamorphism;
2510/// inputs declaring a larger `MAX_BYTES` are rejected at runtime.
2511/// Wiki ADR-037: alias of [`HostBounds::ROUTE_INPUT_BUFFER_BYTES`] via
2512/// [`DefaultHostBounds`].
2513pub const ROUTE_INPUT_BUFFER_BYTES: usize =
2514    <crate::DefaultHostBounds as crate::HostBounds>::ROUTE_INPUT_BUFFER_BYTES;
2515
2516/// Foundation-side ceiling for the on-stack buffer [`run_route`] uses to
2517/// carry the catamorphism's evaluation result into the `Grounded<T>`'s
2518/// output payload per wiki ADR-028. Parallel to
2519/// [`ROUTE_INPUT_BUFFER_BYTES`].
2520/// Output shapes whose `IntoBindingValue::MAX_BYTES` exceeds this ceiling
2521/// are rejected at runtime by [`run_route`] (the symmetric output-side
2522/// rejection rule paralleling ADR-023's input-side rule).
2523/// Wiki ADR-037: alias of [`HostBounds::ROUTE_OUTPUT_BUFFER_BYTES`] via
2524/// [`DefaultHostBounds`].
2525pub const ROUTE_OUTPUT_BUFFER_BYTES: usize =
2526    <crate::DefaultHostBounds as crate::HostBounds>::ROUTE_OUTPUT_BUFFER_BYTES;
2527
2528/// Foundation-fixed threshold for the closure-body grammar `fold_n`'s
2529/// unroll-vs-`Term::Recurse` lowering rule per wiki ADR-026 G14.
2530/// `fold_n` calls with const-literal counts at or below this threshold
2531/// unroll into a sequential `Term::Application` chain; counts above
2532/// (or parametric counts) lower to `Term::Recurse` with a descent-
2533/// measure-bounded fold. The fixed threshold means two implementations
2534/// compiling the same closure body emit the same Term tree.
2535/// Wiki ADR-037: alias of [`HostBounds::FOLD_UNROLL_THRESHOLD`] via
2536/// [`DefaultHostBounds`].
2537pub const FOLD_UNROLL_THRESHOLD: usize =
2538    <crate::DefaultHostBounds as crate::HostBounds>::FOLD_UNROLL_THRESHOLD;
2539
2540/// The application author's typed-iso contract: an `Input` feature type, an
2541/// `Output` label type, and a type-level `Route` witness of the term tree
2542/// mapping one to the other. Per the wiki's ADR-020 — "the model I am
2543/// declaring" — codifies a hylomorphism-with-verifiable-round-trip:
2544/// the catamorphism from `Input` to `Result<Grounded<Output>, PipelineFailure>`
2545/// (see [`run`]) plus the recoverable anamorphism through the trace to
2546/// `Certified<GroundingCertificate>` (see
2547/// [`crate::enforcement::replay::certify_from_trace`]).
2548/// The trait's name derives from the implementation crate, not from the
2549/// categorical Prism optic.
2550/// # Compile-time guarantees
2551/// Implementing `PrismModel` for an application type yields, by virtue of
2552/// the trait's bounds:
2553/// - **Closure under foundation vocabulary**: the `Route` bound
2554///   ([`FoundationClosed`]) is satisfied iff every term in the route witness
2555///   comes from foundation's signature endofunctor F (wiki ADR-019). A
2556///   hand-rolled composition that escapes foundation vocabulary fails to
2557///   compile.
2558/// - **Zero-cost runtime** (TC-01): `forward` is the catamorphism induced
2559///   by initiality of `Term` (ADR-019); the application's compile time
2560///   monomorphizes the catamorphism into native code.
2561/// - **Seal coverage** (TC-02): `forward`'s output is
2562///   `Grounded<Self::Output>` constructed via the seal regime
2563///   ([`crate::enforcement::Grounded`], ADR-011).
2564/// - **Replay equivalence** (TC-05): a `Trace` is recoverable from the
2565///   `Grounded<Output>` via `derivation().replay()`; certifying it via
2566///   [`crate::enforcement::replay::certify_from_trace`] yields a
2567///   `Certified<GroundingCertificate>` whose certificate matches the one
2568///   reachable from `forward`'s output.
2569/// # Authoring
2570/// Application authors do not write `forward`'s body by hand; the
2571/// `prism_model!` macro from `uor-foundation-sdk` derives it from the
2572/// syntactic Route declaration via initiality of `Term` (ADR-019). The
2573/// macro emits both the type-level `Route` witness (which the application's
2574/// `Route` associated type aliases) and the value-level `TermArena` slice
2575/// [`run_route`] traverses (per ADR-022 D2 + D3 + D5).
2576pub trait PrismModel<H, B, A, R = crate::pipeline::NullResolverTuple>: __sdk_seal::Sealed
2577where
2578    H: crate::HostTypes,
2579    B: crate::HostBounds,
2580    A: crate::pipeline::AxisTuple + crate::enforcement::Hasher,
2581    R: crate::pipeline::ResolverTuple,
2582{
2583    /// Input feature type — a [`ConstrainedTypeShape`] impl declared in
2584    /// foundation vocabulary.
2585    /// Per wiki ADR-023, `Input` is also bound by [`IntoBindingValue`] so
2586    /// [`run_route`] can serialize the runtime input value into the
2587    /// `CompileUnit` binding table for `Term::Variable { name_index: 0 }`
2588    /// (the route's input-parameter slot per ADR-022 D3 G2).
2589    type Input: ConstrainedTypeShape + IntoBindingValue;
2590
2591    /// Output label type — a [`ConstrainedTypeShape`] impl declared in
2592    /// foundation vocabulary that is also a [`crate::enforcement::GroundedShape`].
2593    type Output: ConstrainedTypeShape + crate::enforcement::GroundedShape + IntoBindingValue;
2594
2595    /// Type-level witness of the term tree mapping `Input` to `Output`.
2596    /// Bound by [`FoundationClosed`]: the `prism_model!` macro emits the
2597    /// `FoundationClosed` impl for this witness iff every node is a
2598    /// foundation-vocabulary item, satisfying the closure check at the
2599    /// application's compile time per UORassembly (TC-04).
2600    type Route: FoundationClosed;
2601
2602    /// The catamorphism into [`run_route`]'s runtime carrier.
2603    /// Implementations are emitted by the `prism_model!` macro from the
2604    /// syntactic Route declaration; the macro derives the body via
2605    /// initiality of `Term` (wiki ADR-019). The canonical body is
2606    /// `run_route::<H, B, A, Self>(input)` (per ADR-022 D5).
2607    /// # Errors
2608    /// Returns a [`PipelineFailure`] when the input does not satisfy the
2609    /// route's preflight checks (budget solvency, feasibility, package
2610    /// coherence, dispatch coverage, timing) or when reduction stages
2611    /// detect contradiction along the route.
2612    fn forward(
2613        input: Self::Input,
2614    ) -> Result<crate::enforcement::Grounded<Self::Output>, PipelineFailure>;
2615}
2616
2617/// Higher-level catamorphism entry point — wiki ADR-022 D5.
2618/// `run_route` constructs a `Validated<CompileUnit, FinalPhase>` from the
2619/// model's `Route` (whose const `TermArena` slice carries the term tree)
2620/// plus the input, and invokes [`run`] against it. The macro-emitted
2621/// `PrismModel::forward` body is exactly `run_route::<H, B, A, Self>(input)`.
2622/// Lower-level callers (test harnesses, conformance suites, alternative
2623/// SDK surfaces) use [`run`] directly with a hand-built `CompileUnit`.
2624/// This higher-level form is the canonical model-execution surface the
2625/// wiki commits to.
2626/// # Errors
2627/// Returns [`PipelineFailure`] from the underlying [`run`] call.
2628pub fn run_route<H, B, A, M, R>(
2629    input: M::Input,
2630    resolvers: &R,
2631) -> Result<crate::enforcement::Grounded<M::Output>, PipelineFailure>
2632where
2633    H: crate::HostTypes,
2634    B: crate::HostBounds,
2635    A: crate::pipeline::AxisTuple + crate::enforcement::Hasher,
2636    M: PrismModel<H, B, A, R>,
2637    R: crate::pipeline::ResolverTuple
2638        + crate::pipeline::HasNerveResolver<A>
2639        + crate::pipeline::HasChainComplexResolver<A>
2640        + crate::pipeline::HasHomologyGroupResolver<A>
2641        + crate::pipeline::HasCochainComplexResolver<A>
2642        + crate::pipeline::HasCohomologyGroupResolver<A>
2643        + crate::pipeline::HasPostnikovResolver<A>
2644        + crate::pipeline::HasHomotopyGroupResolver<A>
2645        + crate::pipeline::HasKInvariantResolver<A>,
2646{
2647    // ADR-022 D5: read the route's term-tree arena from the model's
2648    // `Route` (the macro-emitted witness; identity-route returns &[]),
2649    // build a `Validated<CompileUnit, FinalPhase>` whose root_term is
2650    // exactly that arena, and dispatch to `run` (the catamorphism).
2651    let arena_slice = <M::Route as FoundationClosed>::arena_slice();
2652    // ADR-023: serialize the runtime input value into a transient
2653    // `Binding` for the route's input-parameter slot
2654    // (`Term::Variable { name_index: 0 }`, ADR-022 D3 G2). The buffer
2655    // ceiling is the foundation-side `ROUTE_INPUT_BUFFER_BYTES`
2656    // (stable-Rust equivalent of nightly's
2657    // `[u8; <M::Input as IntoBindingValue>::MAX_BYTES]` form).
2658    let max_bytes = <M::Input as IntoBindingValue>::MAX_BYTES;
2659    if max_bytes > ROUTE_INPUT_BUFFER_BYTES {
2660        // Per ADR-023: inputs whose declared MAX_BYTES exceeds the
2661        // foundation-side ceiling are rejected — the canonical content
2662        // address cannot be derived without a buffer big enough for
2663        // the value's full byte sequence.
2664        return Err(PipelineFailure::ShapeViolation {
2665            report: crate::enforcement::ShapeViolation {
2666                shape_iri: "https://uor.foundation/pipeline/RouteInputBufferShape",
2667                constraint_iri: "https://uor.foundation/pipeline/RouteInputBufferShape/maxBytes",
2668                property_iri: "https://uor.foundation/pipeline/inputMaxBytes",
2669                expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
2670                min_count: 0,
2671                max_count: ROUTE_INPUT_BUFFER_BYTES as u32,
2672                kind: crate::ViolationKind::ValueCheck,
2673            },
2674        });
2675    }
2676    let mut buf = [0u8; ROUTE_INPUT_BUFFER_BYTES];
2677    let written = input
2678        .into_binding_bytes(&mut buf[..max_bytes])
2679        .map_err(|report| PipelineFailure::ShapeViolation { report })?;
2680    // Hash the canonical bytes through the application's selected
2681    // `Hasher` (substitution axis A). The fold output is truncated to
2682    // u64 for the `Binding.content_address` carrier, matching the
2683    // `to_binding_entry` convention foundation already uses for static
2684    // bindings (`ContentAddress::from_u64_fingerprint`).
2685    let mut hasher = <A as crate::enforcement::Hasher>::initial();
2686    hasher = hasher.fold_bytes(&buf[..written]);
2687    let digest = hasher.finalize();
2688    let content_address: u64 = u64::from_be_bytes([
2689        digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
2690    ]);
2691    // Build the transient binding for the route's input slot. The
2692    // `name_index = 0` sentinel is the route-input slot per ADR-022 D3
2693    // G2; `type_index = 0` is the foundation-conventional zero handle
2694    // (the input's `ConstrainedTypeShape::IRI` is foundation-internal
2695    // and not consumed by the binding-signature fold).
2696    let transient_input = [crate::enforcement::Binding {
2697        name_index: 0,
2698        type_index: 0,
2699        value_index: 0,
2700        surface: <M::Input as ConstrainedTypeShape>::IRI,
2701        content_address,
2702    }];
2703    // Foundation defaults for unit-level parameters that are not part
2704    // of the Route's term-tree. The Witt-level ceiling and
2705    // thermodynamic budget come from the application's `HostBounds`
2706    // selection (ADR-018) — `B::WITT_LEVEL_MAX_BITS` caps the level,
2707    // and a budget large enough to admit any in-bounds route avoids
2708    // false-positive solvency rejections. `target_domains` is
2709    // `Enumerative` because the arena is a finite term tree.
2710    static TARGET_DOMAINS: &[crate::VerificationDomain] = &[crate::VerificationDomain::Enumerative];
2711    let level = match B::WITT_LEVEL_MAX_BITS {
2712        bits if bits >= 32 => crate::WittLevel::W32,
2713        bits if bits >= 24 => crate::WittLevel::W24,
2714        bits if bits >= 16 => crate::WittLevel::W16,
2715        _ => crate::WittLevel::W8,
2716    };
2717    let unit = CompileUnitBuilder::new()
2718        .root_term(arena_slice)
2719        .bindings(&transient_input)
2720        .witt_level_ceiling(level)
2721        .thermodynamic_budget(1024)
2722        .target_domains(TARGET_DOMAINS)
2723        .result_type::<M::Output>()
2724        .validate()
2725        .map_err(|report| PipelineFailure::ShapeViolation { report })?;
2726    // ADR-028: reject Output shapes that would overflow the foundation
2727    // ceiling. Parallel to ADR-023's input-side check, but checked
2728    // against the Output-side `IntoBindingValue::MAX_BYTES`.
2729    let out_max = <M::Output as IntoBindingValue>::MAX_BYTES;
2730    if out_max > ROUTE_OUTPUT_BUFFER_BYTES {
2731        return Err(PipelineFailure::ShapeViolation {
2732            report: crate::enforcement::ShapeViolation {
2733                shape_iri: "https://uor.foundation/pipeline/RouteOutputBufferShape",
2734                constraint_iri: "https://uor.foundation/pipeline/RouteOutputBufferShape/maxBytes",
2735                property_iri: "https://uor.foundation/pipeline/outputMaxBytes",
2736                expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
2737                min_count: 0,
2738                max_count: ROUTE_OUTPUT_BUFFER_BYTES as u32,
2739                kind: crate::ViolationKind::ValueCheck,
2740            },
2741        });
2742    }
2743    // ADR-029: evaluate the route's Term tree as a structural fold.
2744    // The catamorphism's output bytes flow into the Grounded's
2745    // output payload (ADR-028).
2746    let evaluation = evaluate_term_tree::<A, R>(arena_slice, &buf[..written], resolvers)?;
2747    let grounded = run::<M::Output, _, A>(unit)?;
2748    Ok(grounded.with_output_bytes(evaluation.bytes()))
2749}
2750
2751/// Maximum byte width any single `TermValue` carries during evaluation.
2752/// Foundation-fixed at the maximum of the input/output buffer ceilings so a
2753/// TermValue can carry the catamorphism's evaluation result (per ADR-028) and
2754/// the input bytes a Variable/AxisInvocation consumes (per ADR-023). On stable
2755/// Rust 1.83 we cannot use `max(ROUTE_INPUT_BUFFER_BYTES, ROUTE_OUTPUT_BUFFER_BYTES)`
2756/// as a `const` expression in array-length position without `generic_const_exprs`,
2757/// so foundation pins the value at the architectural maximum (currently 4096 — the
2758/// symmetric value the input/output ceilings already commit to).
2759/// Stack usage during the catamorphism's recursive descent scales as
2760/// `tree_depth × TERM_VALUE_MAX_BYTES`. ADR-024's compile-time inlining bounds
2761/// tree depth by the source's grammar tree depth (verb fragments are inlined at
2762/// compile time, no cross-fragment runtime recursion), keeping stack usage finite.
2763/// Wiki ADR-037: alias of [`HostBounds::TERM_VALUE_MAX_BYTES`] via
2764/// [`DefaultHostBounds`]. Applications declaring a custom `HostBounds`
2765/// read `<MyBounds as HostBounds>::TERM_VALUE_MAX_BYTES` instead.
2766pub const TERM_VALUE_MAX_BYTES: usize =
2767    <crate::DefaultHostBounds as crate::HostBounds>::TERM_VALUE_MAX_BYTES;
2768
2769/// Wiki ADR-029: name-index sentinel used by `prism_model!` G7 emission to
2770/// mark `recurse(measure, base, |self| step)`'s self-identifier reference.
2771/// When the catamorphism encounters `Term::Variable { name_index: <this> }`
2772/// during step-body evaluation, it returns the previous iteration's result
2773/// (the `recurse_value` parameter threaded through `evaluate_term_at`) — the
2774/// fresh-name-indexed Variable ADR-029 specifies for the recursive-call
2775/// placeholder.
2776/// Foundation reserves the upper sentinels: `u32::MAX` is the wildcard arm
2777/// for `Term::Match` (ADR-022 D3 G6) and the default-propagation handler for
2778/// `Term::Try` (G9). `RECURSE_PLACEHOLDER_NAME_INDEX = u32::MAX - 1`.
2779pub const RECURSE_PLACEHOLDER_NAME_INDEX: u32 = u32::MAX - 1;
2780
2781/// Wiki ADR-022 D3 G8 + ADR-029: the fresh-name-indexed Variable that
2782/// `prism_model!` emits in place of an `unfold(seed, |state, …| step)`
2783/// closure's state-ident references. The catamorphism's `Term::Unfold`
2784/// fold-rule binds this name to the unfold's current state value
2785/// (threaded through `evaluate_term_at` as the `unfold_value` parameter)
2786/// and iterates step until a Kleene fixpoint or [`UNFOLD_MAX_ITERATIONS`].
2787pub const UNFOLD_PLACEHOLDER_NAME_INDEX: u32 = u32::MAX - 2;
2788
2789/// Wiki ADR-034 Mechanism 1: the foundation-fixed name-index for the
2790/// iteration-counter binding inside `Term::Recurse`'s step body. The
2791/// two-parameter closure form `recurse(measure, base, |self_ident, idx_ident| step)`
2792/// lowers `idx_ident` references to
2793/// `Term::Variable { name_index: RECURSE_IDX_NAME_INDEX }`; the
2794/// catamorphism's per-variant fold-rule binds it to a `TermValue`
2795/// carrying the current measure value at each descent.
2796pub const RECURSE_IDX_NAME_INDEX: u32 = u32::MAX - 3;
2797
2798/// Wiki ADR-034 Mechanism 2: the foundation-fixed name-index for the
2799/// candidate-value binding inside `Term::FirstAdmit`'s predicate body.
2800/// The grammar form `first_admit(<domain>, |idx_ident| <pred>)` lowers
2801/// `idx_ident` references to
2802/// `Term::Variable { name_index: FIRST_ADMIT_IDX_NAME_INDEX }`; the
2803/// catamorphism's per-variant fold-rule binds it to the current
2804/// candidate `idx` (ranging `0..<Domain>::CYCLE_SIZE`).
2805pub const FIRST_ADMIT_IDX_NAME_INDEX: u32 = u32::MAX - 4;
2806
2807/// Wiki ADR-029: bound on the anamorphic fixpoint iteration for
2808/// `Term::Unfold`. The fold rule iterates `step(state)` until either the
2809/// state reaches a Kleene fixpoint (`step(state) == state`) or this
2810/// ceiling is hit, at which point evaluation returns the most-recent
2811/// state. Foundation-fixed (parallel to `FOLD_UNROLL_THRESHOLD`).
2812/// Wiki ADR-037: alias of [`HostBounds::UNFOLD_ITERATIONS_MAX`] via
2813/// [`DefaultHostBounds`].
2814pub const UNFOLD_MAX_ITERATIONS: usize =
2815    <crate::DefaultHostBounds as crate::HostBounds>::UNFOLD_ITERATIONS_MAX;
2816
2817/// Wiki ADR-029: a single Term variant's evaluated value, carried as a
2818/// fixed-capacity byte buffer with an active-prefix length. The
2819/// catamorphism produces a `TermValue` per variant, propagated up the
2820/// term tree by the per-variant fold rules.
2821#[derive(Debug, Clone, Copy)]
2822pub struct TermValue {
2823    /// Fixed-capacity byte buffer (zero-padded beyond `len`).
2824    bytes: [u8; TERM_VALUE_MAX_BYTES],
2825    /// Active prefix length. `u16` admits the architectural ceiling (4096 < 65536).
2826    len: u16,
2827}
2828
2829impl TermValue {
2830    /// Construct an empty `TermValue` (length zero).
2831    #[must_use]
2832    pub const fn empty() -> Self {
2833        Self {
2834            bytes: [0u8; TERM_VALUE_MAX_BYTES],
2835            len: 0,
2836        }
2837    }
2838
2839    /// Construct a `TermValue` from a slice; copies up to `TERM_VALUE_MAX_BYTES` bytes.
2840    #[must_use]
2841    pub fn from_slice(bytes: &[u8]) -> Self {
2842        let mut buf = [0u8; TERM_VALUE_MAX_BYTES];
2843        let copy_len = if bytes.len() > TERM_VALUE_MAX_BYTES {
2844            TERM_VALUE_MAX_BYTES
2845        } else {
2846            bytes.len()
2847        };
2848        let mut i = 0;
2849        while i < copy_len {
2850            buf[i] = bytes[i];
2851            i += 1;
2852        }
2853        Self {
2854            bytes: buf,
2855            len: copy_len as u16,
2856        }
2857    }
2858
2859    /// Returns the active byte prefix.
2860    #[inline]
2861    #[must_use]
2862    pub fn bytes(&self) -> &[u8] {
2863        &self.bytes[..self.len as usize]
2864    }
2865}
2866
2867/// Wiki ADR-029: catamorphism evaluator over the route's Term tree.
2868/// Per-variant fold rules:
2869/// - `Term::Literal { value, level }` — emit `value` as big-endian bytes at the
2870///   byte width of `level`.
2871/// - `Term::Variable { name_index }` — `name_index = 0` returns the route
2872///   input bytes; other indices look up `let`-introduced bindings (current
2873///   iteration: not yet supported, returns the input bytes for any non-zero
2874///   index).
2875/// - `Term::Application { operator, args }` — evaluate each arg and apply
2876///   the `PrimitiveOp` per its algebraic rule.
2877/// - `Term::Lift { operand, target }` — evaluate operand, zero-extend to
2878///   `target` Witt level's byte width.
2879/// - `Term::Project { operand, target }` — evaluate operand, truncate to
2880///   `target` Witt level's byte width.
2881/// - `Term::Match { scrutinee, arms }` — evaluate scrutinee, match arms by
2882///   literal-byte equality (wildcard arm `name_index = u32::MAX` matches
2883///   unconditionally).
2884/// - `Term::Recurse { measure, base, step }` — bounded recursion: evaluate
2885///   measure → n; if n = 0 evaluate base; otherwise iterate step n times with
2886///   the recursive-call placeholder bound to the previous iteration's result.
2887/// - `Term::Unfold { seed, step }` — anamorphism: evaluate seed → state₀;
2888///   iterate step (with the state placeholder bound to the current state)
2889///   until a Kleene fixpoint or `UNFOLD_MAX_ITERATIONS` is reached.
2890/// - `Term::Try { body, handler }` — evaluate body; on failure, propagate
2891///   if `handler_index = u32::MAX`, otherwise evaluate handler.
2892/// - `Term::AxisInvocation { axis_index, kernel_id, input_index }` — dispatch
2893///   the input's bytes to the application's `AxisTuple` (ADR-030); the
2894///   foundation-canonical (axis_index=0, kernel_id=0) folds through the
2895///   selected `Hasher` impl. Replaces the legacy `HasherProjection` variant.
2896/// # Errors
2897/// Returns [`PipelineFailure`] when the term tree is malformed (out-of-bounds
2898/// index, level mismatch, exhausted match without wildcard arm, etc.).
2899pub fn evaluate_term_tree<A, R>(
2900    arena: &[crate::enforcement::Term],
2901    input_bytes: &[u8],
2902    resolvers: &R,
2903) -> Result<TermValue, PipelineFailure>
2904where
2905    A: crate::pipeline::AxisTuple + crate::enforcement::Hasher,
2906    R: crate::pipeline::ResolverTuple
2907        + crate::pipeline::HasNerveResolver<A>
2908        + crate::pipeline::HasChainComplexResolver<A>
2909        + crate::pipeline::HasHomologyGroupResolver<A>
2910        + crate::pipeline::HasCochainComplexResolver<A>
2911        + crate::pipeline::HasCohomologyGroupResolver<A>
2912        + crate::pipeline::HasPostnikovResolver<A>
2913        + crate::pipeline::HasHomotopyGroupResolver<A>
2914        + crate::pipeline::HasKInvariantResolver<A>,
2915{
2916    if arena.is_empty() {
2917        // Identity route: output equals input bytes.
2918        return Ok(TermValue::from_slice(input_bytes));
2919    }
2920    // Canonical convention: the root term is the last entry in the
2921    // arena (the `prism_model!` macro emits in post-order, so the root
2922    // is the final node).
2923    let root_idx = arena.len() - 1;
2924    evaluate_term_at::<A, R>(
2925        arena,
2926        root_idx,
2927        input_bytes,
2928        None,
2929        None,
2930        None,
2931        None,
2932        resolvers,
2933    )
2934}
2935
2936#[allow(clippy::too_many_arguments)]
2937fn evaluate_term_at<A, R>(
2938    arena: &[crate::enforcement::Term],
2939    idx: usize,
2940    input_bytes: &[u8],
2941    recurse_value: Option<&[u8]>,
2942    recurse_idx_value: Option<&[u8]>,
2943    unfold_value: Option<&[u8]>,
2944    first_admit_idx_value: Option<&[u8]>,
2945    resolvers: &R,
2946) -> Result<TermValue, PipelineFailure>
2947where
2948    A: crate::pipeline::AxisTuple + crate::enforcement::Hasher,
2949    R: crate::pipeline::ResolverTuple
2950        + crate::pipeline::HasNerveResolver<A>
2951        + crate::pipeline::HasChainComplexResolver<A>
2952        + crate::pipeline::HasHomologyGroupResolver<A>
2953        + crate::pipeline::HasCochainComplexResolver<A>
2954        + crate::pipeline::HasCohomologyGroupResolver<A>
2955        + crate::pipeline::HasPostnikovResolver<A>
2956        + crate::pipeline::HasHomotopyGroupResolver<A>
2957        + crate::pipeline::HasKInvariantResolver<A>,
2958{
2959    if idx >= arena.len() {
2960        return Err(PipelineFailure::ShapeViolation {
2961            report: crate::enforcement::ShapeViolation {
2962                shape_iri: "https://uor.foundation/pipeline/TermArenaShape",
2963                constraint_iri: "https://uor.foundation/pipeline/TermArenaShape/inBounds",
2964                property_iri: "https://uor.foundation/pipeline/termIndex",
2965                expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
2966                min_count: 0,
2967                max_count: arena.len() as u32,
2968                kind: crate::ViolationKind::ValueCheck,
2969            },
2970        });
2971    }
2972    match arena[idx] {
2973        crate::enforcement::Term::Literal { value, level } => {
2974            let width = (level.witt_length() / 8) as usize;
2975            let width = if width == 0 {
2976                1
2977            } else if width > 8 {
2978                8
2979            } else {
2980                width
2981            };
2982            let be = value.to_be_bytes();
2983            // Take the trailing `width` bytes (big-endian truncation).
2984            Ok(TermValue::from_slice(&be[8 - width..]))
2985        }
2986        crate::enforcement::Term::Variable { name_index } => {
2987            // ADR-022 D3 G2: name_index = 0 is the route input slot.
2988            // ADR-029: name_index = RECURSE_PLACEHOLDER_NAME_INDEX is the
2989            // recursive-call placeholder bound to `recurse_value`.
2990            // ADR-029: name_index = UNFOLD_PLACEHOLDER_NAME_INDEX is the
2991            // unfold state placeholder bound to `unfold_value` (the
2992            // current iteration's accumulated state — see the
2993            // `Term::Unfold` fold-rule below).
2994            // Other indices reference let-bindings (G10), which the
2995            // current macro emission resolves at expansion time via
2996            // splice into the calling arena (so the binding's value-tree
2997            // root is what the catamorphism actually walks).
2998            if name_index == RECURSE_PLACEHOLDER_NAME_INDEX {
2999                return Ok(TermValue::from_slice(recurse_value.unwrap_or(&[])));
3000            }
3001            // ADR-034 Mechanism 1: iteration-counter binding for Recurse.
3002            if name_index == RECURSE_IDX_NAME_INDEX {
3003                return Ok(TermValue::from_slice(recurse_idx_value.unwrap_or(&[])));
3004            }
3005            if name_index == UNFOLD_PLACEHOLDER_NAME_INDEX {
3006                return Ok(TermValue::from_slice(unfold_value.unwrap_or(&[])));
3007            }
3008            // ADR-034 Mechanism 2: candidate-value binding for FirstAdmit.
3009            if name_index == FIRST_ADMIT_IDX_NAME_INDEX {
3010                return Ok(TermValue::from_slice(first_admit_idx_value.unwrap_or(&[])));
3011            }
3012            Ok(TermValue::from_slice(input_bytes))
3013        }
3014        crate::enforcement::Term::Application { operator, args } => {
3015            let start = args.start as usize;
3016            let len = args.len as usize;
3017            apply_primitive_op::<A, R>(
3018                arena,
3019                operator,
3020                start,
3021                len,
3022                input_bytes,
3023                recurse_value,
3024                recurse_idx_value,
3025                unfold_value,
3026                first_admit_idx_value,
3027                resolvers,
3028            )
3029        }
3030        crate::enforcement::Term::Lift {
3031            operand_index,
3032            target,
3033        } => {
3034            let v = evaluate_term_at::<A, R>(
3035                arena,
3036                operand_index as usize,
3037                input_bytes,
3038                recurse_value,
3039                recurse_idx_value,
3040                unfold_value,
3041                first_admit_idx_value,
3042                resolvers,
3043            )?;
3044            let target_width = (target.witt_length() / 8) as usize;
3045            let target_width = if target_width > TERM_VALUE_MAX_BYTES {
3046                TERM_VALUE_MAX_BYTES
3047            } else if target_width == 0 {
3048                1
3049            } else {
3050                target_width
3051            };
3052            let mut buf = [0u8; TERM_VALUE_MAX_BYTES];
3053            // Big-endian zero-extend: pad the high bytes with zeros.
3054            let src = v.bytes();
3055            let pad = target_width.saturating_sub(src.len());
3056            let mut i = 0;
3057            while i < src.len() && pad + i < target_width {
3058                buf[pad + i] = src[i];
3059                i += 1;
3060            }
3061            Ok(TermValue {
3062                bytes: buf,
3063                len: target_width as u16,
3064            })
3065        }
3066        crate::enforcement::Term::Project {
3067            operand_index,
3068            target,
3069        } => {
3070            let v = evaluate_term_at::<A, R>(
3071                arena,
3072                operand_index as usize,
3073                input_bytes,
3074                recurse_value,
3075                recurse_idx_value,
3076                unfold_value,
3077                first_admit_idx_value,
3078                resolvers,
3079            )?;
3080            let target_width = (target.witt_length() / 8) as usize;
3081            let target_width = if target_width > TERM_VALUE_MAX_BYTES {
3082                TERM_VALUE_MAX_BYTES
3083            } else if target_width == 0 {
3084                1
3085            } else {
3086                target_width
3087            };
3088            let src = v.bytes();
3089            // Big-endian truncation: take the trailing `target_width` bytes.
3090            let take_from = src.len().saturating_sub(target_width);
3091            Ok(TermValue::from_slice(&src[take_from..]))
3092        }
3093        crate::enforcement::Term::Match {
3094            scrutinee_index,
3095            arms,
3096        } => {
3097            let scrutinee = evaluate_term_at::<A, R>(
3098                arena,
3099                scrutinee_index as usize,
3100                input_bytes,
3101                recurse_value,
3102                recurse_idx_value,
3103                unfold_value,
3104                first_admit_idx_value,
3105                resolvers,
3106            )?;
3107            let start = arms.start as usize;
3108            let count = arms.len as usize;
3109            // Arms alternate (pattern, body) per ADR-022 D3 G6.
3110            let mut i = 0usize;
3111            while i + 1 < count {
3112                let pattern_idx = start + i;
3113                let body_idx = start + i + 1;
3114                let is_wildcard = matches!(
3115                    arena[pattern_idx],
3116                    crate::enforcement::Term::Variable { name_index } if name_index == u32::MAX
3117                );
3118                if is_wildcard {
3119                    return evaluate_term_at::<A, R>(
3120                        arena,
3121                        body_idx,
3122                        input_bytes,
3123                        recurse_value,
3124                        recurse_idx_value,
3125                        unfold_value,
3126                        first_admit_idx_value,
3127                        resolvers,
3128                    );
3129                }
3130                let pattern_val = evaluate_term_at::<A, R>(
3131                    arena,
3132                    pattern_idx,
3133                    input_bytes,
3134                    recurse_value,
3135                    recurse_idx_value,
3136                    unfold_value,
3137                    first_admit_idx_value,
3138                    resolvers,
3139                )?;
3140                if pattern_val.bytes() == scrutinee.bytes() {
3141                    return evaluate_term_at::<A, R>(
3142                        arena,
3143                        body_idx,
3144                        input_bytes,
3145                        recurse_value,
3146                        recurse_idx_value,
3147                        unfold_value,
3148                        first_admit_idx_value,
3149                        resolvers,
3150                    );
3151                }
3152                i += 2;
3153            }
3154            // Per ADR-022 D3 G6 the macro enforces wildcard exhaustiveness;
3155            // a well-formed Term tree never reaches this branch.
3156            Err(PipelineFailure::ShapeViolation {
3157                report: crate::enforcement::ShapeViolation {
3158                    shape_iri: "https://uor.foundation/pipeline/MatchExhaustivenessShape",
3159                    constraint_iri:
3160                        "https://uor.foundation/pipeline/MatchExhaustivenessShape/wildcard",
3161                    property_iri: "https://uor.foundation/pipeline/matchArms",
3162                    expected_range: "http://www.w3.org/2001/XMLSchema#string",
3163                    min_count: 1,
3164                    max_count: 0,
3165                    kind: crate::ViolationKind::Missing,
3166                },
3167            })
3168        }
3169        crate::enforcement::Term::Recurse {
3170            measure_index,
3171            base_index,
3172            step_index,
3173        } => {
3174            // Wiki ADR-029 recursive fold: evaluate measure once to get N;
3175            // if N == 0 evaluate base; else iterate step N times, threading
3176            // each iteration's result as the recurse_value (the recursive-
3177            // call placeholder bound to a fresh-name-indexed Variable per
3178            // ADR-029, with the placeholder's name_index resolving via the
3179            // RECURSE_PLACEHOLDER_NAME_INDEX sentinel handled in the Variable
3180            // arm). The outer recurse_value is preserved for nested Recurse
3181            // forms within the measure/base computations; step body uses the
3182            // iteration's accumulator.
3183            let measure = evaluate_term_at::<A, R>(
3184                arena,
3185                measure_index as usize,
3186                input_bytes,
3187                recurse_value,
3188                recurse_idx_value,
3189                unfold_value,
3190                first_admit_idx_value,
3191                resolvers,
3192            )?;
3193            let n = bytes_to_u64_be(measure.bytes());
3194            let base_val = evaluate_term_at::<A, R>(
3195                arena,
3196                base_index as usize,
3197                input_bytes,
3198                recurse_value,
3199                recurse_idx_value,
3200                unfold_value,
3201                first_admit_idx_value,
3202                resolvers,
3203            )?;
3204            if n == 0 {
3205                return Ok(base_val);
3206            }
3207            // Iterate step N times. Each iteration's `current` becomes the
3208            // next iteration's recurse_value. The descent measure is the
3209            // bound; well-foundedness holds by monotonic decrease.
3210            let mut current_buf = [0u8; TERM_VALUE_MAX_BYTES];
3211            let mut current_len = base_val.bytes().len();
3212            let mut k = 0;
3213            while k < current_len {
3214                current_buf[k] = base_val.bytes()[k];
3215                k += 1;
3216            }
3217            let mut iter = 0u64;
3218            while iter < n {
3219                // ADR-034 Mechanism 1: bind RECURSE_IDX_NAME_INDEX to the
3220                // current measure value (the iteration counter at this
3221                // descent). At iter=0 the descent measure is N; at iter=k
3222                // it is N-k. The byte width follows the measure's BE-truncated
3223                // u64 packing so callers can read it as a u64-shaped value.
3224                let descent_measure: u64 = n - iter;
3225                let descent_bytes = descent_measure.to_be_bytes();
3226                let next = evaluate_term_at::<A, R>(
3227                    arena,
3228                    step_index as usize,
3229                    input_bytes,
3230                    Some(&current_buf[..current_len]),
3231                    Some(&descent_bytes[..]),
3232                    unfold_value,
3233                    first_admit_idx_value,
3234                    resolvers,
3235                )?;
3236                let nb = next.bytes();
3237                let copy_len = if nb.len() > TERM_VALUE_MAX_BYTES {
3238                    TERM_VALUE_MAX_BYTES
3239                } else {
3240                    nb.len()
3241                };
3242                let mut j = 0;
3243                while j < copy_len {
3244                    current_buf[j] = nb[j];
3245                    j += 1;
3246                }
3247                current_len = copy_len;
3248                iter += 1;
3249            }
3250            Ok(TermValue::from_slice(&current_buf[..current_len]))
3251        }
3252        crate::enforcement::Term::Unfold {
3253            seed_index,
3254            step_index,
3255        } => {
3256            // ADR-029 anamorphism: evaluate seed → state₀; iterate step
3257            // with the state placeholder (UNFOLD_PLACEHOLDER_NAME_INDEX,
3258            // bound to the current state) until either a Kleene fixpoint
3259            // (step(state) == state) or UNFOLD_MAX_ITERATIONS is reached.
3260            // Well-foundedness: bounded by UNFOLD_MAX_ITERATIONS.
3261            // The outer unfold_value is preserved for nested Unfold forms
3262            // within the seed; step body's state placeholder uses the
3263            // iteration's accumulator.
3264            let seed_val = evaluate_term_at::<A, R>(
3265                arena,
3266                seed_index as usize,
3267                input_bytes,
3268                recurse_value,
3269                recurse_idx_value,
3270                unfold_value,
3271                first_admit_idx_value,
3272                resolvers,
3273            )?;
3274            let mut state_buf = [0u8; TERM_VALUE_MAX_BYTES];
3275            let mut state_len = seed_val.bytes().len();
3276            let mut k = 0;
3277            while k < state_len {
3278                state_buf[k] = seed_val.bytes()[k];
3279                k += 1;
3280            }
3281            let mut iter = 0usize;
3282            while iter < UNFOLD_MAX_ITERATIONS {
3283                let next = evaluate_term_at::<A, R>(
3284                    arena,
3285                    step_index as usize,
3286                    input_bytes,
3287                    recurse_value,
3288                    recurse_idx_value,
3289                    Some(&state_buf[..state_len]),
3290                    first_admit_idx_value,
3291                    resolvers,
3292                )?;
3293                let nb = next.bytes();
3294                // Kleene fixpoint check: if step(state) == state, return.
3295                if nb.len() == state_len && nb == &state_buf[..state_len] {
3296                    return Ok(TermValue::from_slice(&state_buf[..state_len]));
3297                }
3298                let copy_len = if nb.len() > TERM_VALUE_MAX_BYTES {
3299                    TERM_VALUE_MAX_BYTES
3300                } else {
3301                    nb.len()
3302                };
3303                let mut j = 0;
3304                while j < copy_len {
3305                    state_buf[j] = nb[j];
3306                    j += 1;
3307                }
3308                state_len = copy_len;
3309                iter += 1;
3310            }
3311            Ok(TermValue::from_slice(&state_buf[..state_len]))
3312        }
3313        crate::enforcement::Term::Try {
3314            body_index,
3315            handler_index,
3316        } => {
3317            match evaluate_term_at::<A, R>(
3318                arena,
3319                body_index as usize,
3320                input_bytes,
3321                recurse_value,
3322                recurse_idx_value,
3323                unfold_value,
3324                first_admit_idx_value,
3325                resolvers,
3326            ) {
3327                Ok(v) => Ok(v),
3328                Err(e) => {
3329                    if handler_index == u32::MAX {
3330                        Err(e)
3331                    } else {
3332                        evaluate_term_at::<A, R>(
3333                            arena,
3334                            handler_index as usize,
3335                            input_bytes,
3336                            recurse_value,
3337                            recurse_idx_value,
3338                            unfold_value,
3339                            first_admit_idx_value,
3340                            resolvers,
3341                        )
3342                    }
3343                }
3344            }
3345        }
3346        crate::enforcement::Term::AxisInvocation {
3347            axis_index,
3348            kernel_id,
3349            input_index,
3350        } => {
3351            // ADR-030: dispatch to the application's selected axis at
3352            // `axis_index`, with `kernel_id` selecting the per-axis kernel.
3353            // The catamorphism evaluates the input subtree to bytes and
3354            // hands them to the AxisTuple's dispatch router; the dispatcher
3355            // writes the kernel's output into a stack-resident buffer.
3356            //
3357            // The foundation-built blanket `impl<H: Hasher> AxisTuple for H`
3358            // routes the canonical hash dispatch (axis 0, kernel 0)
3359            // through the legacy Hasher API; user-declared axes via the
3360            // `axis!` SDK macro extend the dispatch surface to additional
3361            // (axis_index, kernel_id) combinations.
3362            let v = evaluate_term_at::<A, R>(
3363                arena,
3364                input_index as usize,
3365                input_bytes,
3366                recurse_value,
3367                recurse_idx_value,
3368                unfold_value,
3369                first_admit_idx_value,
3370                resolvers,
3371            )?;
3372            let mut out = [0u8; AXIS_OUTPUT_BYTES_CEILING];
3373            let written = match <A as crate::pipeline::AxisTuple>::dispatch(
3374                axis_index,
3375                kernel_id,
3376                v.bytes(),
3377                &mut out,
3378            ) {
3379                Ok(n) => n,
3380                Err(report) => return Err(PipelineFailure::ShapeViolation { report }),
3381            };
3382            let width = if written > TERM_VALUE_MAX_BYTES {
3383                TERM_VALUE_MAX_BYTES
3384            } else {
3385                written
3386            };
3387            Ok(TermValue::from_slice(&out[..width]))
3388        }
3389        crate::enforcement::Term::ProjectField {
3390            source_index,
3391            byte_offset,
3392            byte_length,
3393        } => {
3394            // ADR-033 G20: evaluate source, slice [byte_offset .. byte_offset+byte_length].
3395            let v = evaluate_term_at::<A, R>(
3396                arena,
3397                source_index as usize,
3398                input_bytes,
3399                recurse_value,
3400                recurse_idx_value,
3401                unfold_value,
3402                first_admit_idx_value,
3403                resolvers,
3404            )?;
3405            let bytes = v.bytes();
3406            let start = byte_offset as usize;
3407            let end = start.saturating_add(byte_length as usize);
3408            if end > bytes.len() {
3409                return Err(PipelineFailure::ShapeViolation {
3410                    report: crate::enforcement::ShapeViolation {
3411                        shape_iri: "https://uor.foundation/pipeline/ProjectFieldShape",
3412                        constraint_iri:
3413                            "https://uor.foundation/pipeline/ProjectFieldShape/inBounds",
3414                        property_iri: "https://uor.foundation/pipeline/byteOffset",
3415                        expected_range: "https://uor.foundation/pipeline/SourceByteRange",
3416                        min_count: 0,
3417                        max_count: 1,
3418                        kind: crate::ViolationKind::ValueCheck,
3419                    },
3420                });
3421            }
3422            Ok(TermValue::from_slice(&bytes[start..end]))
3423        }
3424        crate::enforcement::Term::FirstAdmit {
3425            domain_size_index,
3426            predicate_index,
3427        } => {
3428            // ADR-034 Mechanism 2: bounded search with structural early
3429            // termination. Evaluate the domain size N; iterate idx in 0..N
3430            // ascending; for each idx, evaluate predicate with
3431            // FIRST_ADMIT_IDX_NAME_INDEX bound to idx. Return on the first
3432            // non-zero predicate result (the "found" coproduct value
3433            // 0x01 || idx_bytes); after exhausting the domain return the
3434            // "not-found" coproduct value 0x00 || idx-width zero bytes.
3435            let domain_size = evaluate_term_at::<A, R>(
3436                arena,
3437                domain_size_index as usize,
3438                input_bytes,
3439                recurse_value,
3440                recurse_idx_value,
3441                unfold_value,
3442                first_admit_idx_value,
3443                resolvers,
3444            )?;
3445            let n = bytes_to_u64_be(domain_size.bytes());
3446            // Determine the idx byte width from the domain size's
3447            // BE-truncated u64 packing (use the smallest non-zero count of
3448            // bytes needed to represent N). For N=0 fall back to 1 byte so
3449            // the not-found sentinel still has the canonical (disc, idx) shape.
3450            let idx_byte_width: usize = if n == 0 {
3451                1
3452            } else {
3453                let mut w = 8usize;
3454                while w > 1 && (n >> ((w - 1) * 8)) == 0 {
3455                    w -= 1;
3456                }
3457                w
3458            };
3459            let mut idx_iter: u64 = 0;
3460            while idx_iter < n {
3461                let idx_bytes_full = idx_iter.to_be_bytes();
3462                let idx_bytes = &idx_bytes_full[8 - idx_byte_width..];
3463                let pred_val = evaluate_term_at::<A, R>(
3464                    arena,
3465                    predicate_index as usize,
3466                    input_bytes,
3467                    recurse_value,
3468                    recurse_idx_value,
3469                    unfold_value,
3470                    Some(idx_bytes),
3471                    resolvers,
3472                )?;
3473                // ADR-029 zero/non-zero convention: any TermValue whose
3474                // byte sequence has at least one non-zero byte counts as
3475                // "true". An empty TermValue is treated as "false".
3476                let pb = pred_val.bytes();
3477                let mut admitted = false;
3478                let mut bi = 0;
3479                while bi < pb.len() {
3480                    if pb[bi] != 0 {
3481                        admitted = true;
3482                        break;
3483                    }
3484                    bi += 1;
3485                }
3486                if admitted {
3487                    let mut out_buf = [0u8; TERM_VALUE_MAX_BYTES];
3488                    out_buf[0] = 0x01;
3489                    let mut k = 0;
3490                    while k < idx_byte_width {
3491                        out_buf[1 + k] = idx_bytes[k];
3492                        k += 1;
3493                    }
3494                    return Ok(TermValue::from_slice(&out_buf[..1 + idx_byte_width]));
3495                }
3496                idx_iter += 1;
3497            }
3498            // No idx admitted — emit not-found coproduct value.
3499            let mut out_buf = [0u8; TERM_VALUE_MAX_BYTES];
3500            out_buf[0] = 0x00;
3501            // bytes 1..1+idx_byte_width remain zero (structural padding).
3502            Ok(TermValue::from_slice(&out_buf[..1 + idx_byte_width]))
3503        }
3504        crate::enforcement::Term::Nerve { value_index } => {
3505            // ADR-035 + ADR-036 + ADR-041: resolver-bound ψ-Term variant.
3506            // Evaluate the operand subtree, wrap the resulting bytes in the
3507            // ADR-041 typed-coordinate carrier for this ψ-stage's expected
3508            // input, then dispatch through the application's resolver via the
3509            // `Has<Category>Resolver` accessor. The resolver's output bytes
3510            // populate the variant's `TermValue`; resolver-side `Err`
3511            // propagates as `PipelineFailure::ShapeViolation` (the Null
3512            // defaults emit `RESOLVER_ABSENT`, recoverable via `Term::Try`'s
3513            // default-propagation handler).
3514            //
3515            // ADR-037: scratch buffer sized at `<crate::DefaultHostBounds as crate::HostBounds>::NERVE_OUTPUT_BYTES_MAX`
3516            // — per-ψ-stage cap from the application's selected HostBounds.
3517            let operand = evaluate_term_at::<A, R>(
3518                arena,
3519                value_index as usize,
3520                input_bytes,
3521                recurse_value,
3522                recurse_idx_value,
3523                unfold_value,
3524                first_admit_idx_value,
3525                resolvers,
3526            )?;
3527            let mut out_buf =
3528                [0u8; <crate::DefaultHostBounds as crate::HostBounds>::NERVE_OUTPUT_BYTES_MAX];
3529            match resolvers
3530                .nerve_resolver()
3531                .resolve(operand.bytes(), &mut out_buf)
3532            {
3533                Ok(written) => {
3534                    let width = if written > TERM_VALUE_MAX_BYTES {
3535                        TERM_VALUE_MAX_BYTES
3536                    } else {
3537                        written
3538                    };
3539                    Ok(TermValue::from_slice(&out_buf[..width]))
3540                }
3541                Err(report) => Err(PipelineFailure::ShapeViolation { report }),
3542            }
3543        }
3544        crate::enforcement::Term::ChainComplex { simplicial_index } => {
3545            // ADR-035 + ADR-036 + ADR-041: resolver-bound ψ-Term variant.
3546            // Evaluate the operand subtree, wrap the resulting bytes in the
3547            // ADR-041 typed-coordinate carrier for this ψ-stage's expected
3548            // input, then dispatch through the application's resolver via the
3549            // `Has<Category>Resolver` accessor. The resolver's output bytes
3550            // populate the variant's `TermValue`; resolver-side `Err`
3551            // propagates as `PipelineFailure::ShapeViolation` (the Null
3552            // defaults emit `RESOLVER_ABSENT`, recoverable via `Term::Try`'s
3553            // default-propagation handler).
3554            //
3555            // ADR-037: scratch buffer sized at `<crate::DefaultHostBounds as crate::HostBounds>::CHAIN_COMPLEX_OUTPUT_BYTES_MAX`
3556            // — per-ψ-stage cap from the application's selected HostBounds.
3557            let operand = evaluate_term_at::<A, R>(
3558                arena,
3559                simplicial_index as usize,
3560                input_bytes,
3561                recurse_value,
3562                recurse_idx_value,
3563                unfold_value,
3564                first_admit_idx_value,
3565                resolvers,
3566            )?;
3567            let mut out_buf = [0u8;
3568                <crate::DefaultHostBounds as crate::HostBounds>::CHAIN_COMPLEX_OUTPUT_BYTES_MAX];
3569            match resolvers.chain_complex_resolver().resolve(
3570                crate::pipeline::SimplicialComplexBytes(operand.bytes()),
3571                &mut out_buf,
3572            ) {
3573                Ok(written) => {
3574                    let width = if written > TERM_VALUE_MAX_BYTES {
3575                        TERM_VALUE_MAX_BYTES
3576                    } else {
3577                        written
3578                    };
3579                    Ok(TermValue::from_slice(&out_buf[..width]))
3580                }
3581                Err(report) => Err(PipelineFailure::ShapeViolation { report }),
3582            }
3583        }
3584        crate::enforcement::Term::HomologyGroups { chain_index } => {
3585            // ADR-035 + ADR-036 + ADR-041: resolver-bound ψ-Term variant.
3586            // Evaluate the operand subtree, wrap the resulting bytes in the
3587            // ADR-041 typed-coordinate carrier for this ψ-stage's expected
3588            // input, then dispatch through the application's resolver via the
3589            // `Has<Category>Resolver` accessor. The resolver's output bytes
3590            // populate the variant's `TermValue`; resolver-side `Err`
3591            // propagates as `PipelineFailure::ShapeViolation` (the Null
3592            // defaults emit `RESOLVER_ABSENT`, recoverable via `Term::Try`'s
3593            // default-propagation handler).
3594            //
3595            // ADR-037: scratch buffer sized at `<crate::DefaultHostBounds as crate::HostBounds>::HOMOLOGY_GROUPS_OUTPUT_BYTES_MAX`
3596            // — per-ψ-stage cap from the application's selected HostBounds.
3597            let operand = evaluate_term_at::<A, R>(
3598                arena,
3599                chain_index as usize,
3600                input_bytes,
3601                recurse_value,
3602                recurse_idx_value,
3603                unfold_value,
3604                first_admit_idx_value,
3605                resolvers,
3606            )?;
3607            let mut out_buf = [0u8;
3608                <crate::DefaultHostBounds as crate::HostBounds>::HOMOLOGY_GROUPS_OUTPUT_BYTES_MAX];
3609            match resolvers.homology_group_resolver().resolve(
3610                crate::pipeline::ChainComplexBytes(operand.bytes()),
3611                &mut out_buf,
3612            ) {
3613                Ok(written) => {
3614                    let width = if written > TERM_VALUE_MAX_BYTES {
3615                        TERM_VALUE_MAX_BYTES
3616                    } else {
3617                        written
3618                    };
3619                    Ok(TermValue::from_slice(&out_buf[..width]))
3620                }
3621                Err(report) => Err(PipelineFailure::ShapeViolation { report }),
3622            }
3623        }
3624        crate::enforcement::Term::CochainComplex { chain_index } => {
3625            // ADR-035 + ADR-036 + ADR-041: resolver-bound ψ-Term variant.
3626            // Evaluate the operand subtree, wrap the resulting bytes in the
3627            // ADR-041 typed-coordinate carrier for this ψ-stage's expected
3628            // input, then dispatch through the application's resolver via the
3629            // `Has<Category>Resolver` accessor. The resolver's output bytes
3630            // populate the variant's `TermValue`; resolver-side `Err`
3631            // propagates as `PipelineFailure::ShapeViolation` (the Null
3632            // defaults emit `RESOLVER_ABSENT`, recoverable via `Term::Try`'s
3633            // default-propagation handler).
3634            //
3635            // ADR-037: scratch buffer sized at `<crate::DefaultHostBounds as crate::HostBounds>::COCHAIN_COMPLEX_OUTPUT_BYTES_MAX`
3636            // — per-ψ-stage cap from the application's selected HostBounds.
3637            let operand = evaluate_term_at::<A, R>(
3638                arena,
3639                chain_index as usize,
3640                input_bytes,
3641                recurse_value,
3642                recurse_idx_value,
3643                unfold_value,
3644                first_admit_idx_value,
3645                resolvers,
3646            )?;
3647            let mut out_buf = [0u8;
3648                <crate::DefaultHostBounds as crate::HostBounds>::COCHAIN_COMPLEX_OUTPUT_BYTES_MAX];
3649            match resolvers.cochain_complex_resolver().resolve(
3650                crate::pipeline::ChainComplexBytes(operand.bytes()),
3651                &mut out_buf,
3652            ) {
3653                Ok(written) => {
3654                    let width = if written > TERM_VALUE_MAX_BYTES {
3655                        TERM_VALUE_MAX_BYTES
3656                    } else {
3657                        written
3658                    };
3659                    Ok(TermValue::from_slice(&out_buf[..width]))
3660                }
3661                Err(report) => Err(PipelineFailure::ShapeViolation { report }),
3662            }
3663        }
3664        crate::enforcement::Term::CohomologyGroups { cochain_index } => {
3665            // ADR-035 + ADR-036 + ADR-041: resolver-bound ψ-Term variant.
3666            // Evaluate the operand subtree, wrap the resulting bytes in the
3667            // ADR-041 typed-coordinate carrier for this ψ-stage's expected
3668            // input, then dispatch through the application's resolver via the
3669            // `Has<Category>Resolver` accessor. The resolver's output bytes
3670            // populate the variant's `TermValue`; resolver-side `Err`
3671            // propagates as `PipelineFailure::ShapeViolation` (the Null
3672            // defaults emit `RESOLVER_ABSENT`, recoverable via `Term::Try`'s
3673            // default-propagation handler).
3674            //
3675            // ADR-037: scratch buffer sized at `<crate::DefaultHostBounds as crate::HostBounds>::COHOMOLOGY_GROUPS_OUTPUT_BYTES_MAX`
3676            // — per-ψ-stage cap from the application's selected HostBounds.
3677            let operand = evaluate_term_at::<A, R>(
3678                arena,
3679                cochain_index as usize,
3680                input_bytes,
3681                recurse_value,
3682                recurse_idx_value,
3683                unfold_value,
3684                first_admit_idx_value,
3685                resolvers,
3686            )?;
3687            let mut out_buf = [0u8;
3688                <crate::DefaultHostBounds as crate::HostBounds>::COHOMOLOGY_GROUPS_OUTPUT_BYTES_MAX];
3689            match resolvers.cohomology_group_resolver().resolve(
3690                crate::pipeline::CochainComplexBytes(operand.bytes()),
3691                &mut out_buf,
3692            ) {
3693                Ok(written) => {
3694                    let width = if written > TERM_VALUE_MAX_BYTES {
3695                        TERM_VALUE_MAX_BYTES
3696                    } else {
3697                        written
3698                    };
3699                    Ok(TermValue::from_slice(&out_buf[..width]))
3700                }
3701                Err(report) => Err(PipelineFailure::ShapeViolation { report }),
3702            }
3703        }
3704        crate::enforcement::Term::PostnikovTower { simplicial_index } => {
3705            // ADR-035 + ADR-036 + ADR-041: resolver-bound ψ-Term variant.
3706            // Evaluate the operand subtree, wrap the resulting bytes in the
3707            // ADR-041 typed-coordinate carrier for this ψ-stage's expected
3708            // input, then dispatch through the application's resolver via the
3709            // `Has<Category>Resolver` accessor. The resolver's output bytes
3710            // populate the variant's `TermValue`; resolver-side `Err`
3711            // propagates as `PipelineFailure::ShapeViolation` (the Null
3712            // defaults emit `RESOLVER_ABSENT`, recoverable via `Term::Try`'s
3713            // default-propagation handler).
3714            //
3715            // ADR-037: scratch buffer sized at `<crate::DefaultHostBounds as crate::HostBounds>::POSTNIKOV_TOWER_OUTPUT_BYTES_MAX`
3716            // — per-ψ-stage cap from the application's selected HostBounds.
3717            let operand = evaluate_term_at::<A, R>(
3718                arena,
3719                simplicial_index as usize,
3720                input_bytes,
3721                recurse_value,
3722                recurse_idx_value,
3723                unfold_value,
3724                first_admit_idx_value,
3725                resolvers,
3726            )?;
3727            let mut out_buf = [0u8;
3728                <crate::DefaultHostBounds as crate::HostBounds>::POSTNIKOV_TOWER_OUTPUT_BYTES_MAX];
3729            match resolvers.postnikov_resolver().resolve(
3730                crate::pipeline::SimplicialComplexBytes(operand.bytes()),
3731                &mut out_buf,
3732            ) {
3733                Ok(written) => {
3734                    let width = if written > TERM_VALUE_MAX_BYTES {
3735                        TERM_VALUE_MAX_BYTES
3736                    } else {
3737                        written
3738                    };
3739                    Ok(TermValue::from_slice(&out_buf[..width]))
3740                }
3741                Err(report) => Err(PipelineFailure::ShapeViolation { report }),
3742            }
3743        }
3744        crate::enforcement::Term::HomotopyGroups { postnikov_index } => {
3745            // ADR-035 + ADR-036 + ADR-041: resolver-bound ψ-Term variant.
3746            // Evaluate the operand subtree, wrap the resulting bytes in the
3747            // ADR-041 typed-coordinate carrier for this ψ-stage's expected
3748            // input, then dispatch through the application's resolver via the
3749            // `Has<Category>Resolver` accessor. The resolver's output bytes
3750            // populate the variant's `TermValue`; resolver-side `Err`
3751            // propagates as `PipelineFailure::ShapeViolation` (the Null
3752            // defaults emit `RESOLVER_ABSENT`, recoverable via `Term::Try`'s
3753            // default-propagation handler).
3754            //
3755            // ADR-037: scratch buffer sized at `<crate::DefaultHostBounds as crate::HostBounds>::HOMOTOPY_GROUPS_OUTPUT_BYTES_MAX`
3756            // — per-ψ-stage cap from the application's selected HostBounds.
3757            let operand = evaluate_term_at::<A, R>(
3758                arena,
3759                postnikov_index as usize,
3760                input_bytes,
3761                recurse_value,
3762                recurse_idx_value,
3763                unfold_value,
3764                first_admit_idx_value,
3765                resolvers,
3766            )?;
3767            let mut out_buf = [0u8;
3768                <crate::DefaultHostBounds as crate::HostBounds>::HOMOTOPY_GROUPS_OUTPUT_BYTES_MAX];
3769            match resolvers.homotopy_group_resolver().resolve(
3770                crate::pipeline::PostnikovTowerBytes(operand.bytes()),
3771                &mut out_buf,
3772            ) {
3773                Ok(written) => {
3774                    let width = if written > TERM_VALUE_MAX_BYTES {
3775                        TERM_VALUE_MAX_BYTES
3776                    } else {
3777                        written
3778                    };
3779                    Ok(TermValue::from_slice(&out_buf[..width]))
3780                }
3781                Err(report) => Err(PipelineFailure::ShapeViolation { report }),
3782            }
3783        }
3784        crate::enforcement::Term::KInvariants { homotopy_index } => {
3785            // ADR-035 + ADR-036 + ADR-041: resolver-bound ψ-Term variant.
3786            // Evaluate the operand subtree, wrap the resulting bytes in the
3787            // ADR-041 typed-coordinate carrier for this ψ-stage's expected
3788            // input, then dispatch through the application's resolver via the
3789            // `Has<Category>Resolver` accessor. The resolver's output bytes
3790            // populate the variant's `TermValue`; resolver-side `Err`
3791            // propagates as `PipelineFailure::ShapeViolation` (the Null
3792            // defaults emit `RESOLVER_ABSENT`, recoverable via `Term::Try`'s
3793            // default-propagation handler).
3794            //
3795            // ADR-037: scratch buffer sized at `<crate::DefaultHostBounds as crate::HostBounds>::K_INVARIANTS_OUTPUT_BYTES_MAX`
3796            // — per-ψ-stage cap from the application's selected HostBounds.
3797            let operand = evaluate_term_at::<A, R>(
3798                arena,
3799                homotopy_index as usize,
3800                input_bytes,
3801                recurse_value,
3802                recurse_idx_value,
3803                unfold_value,
3804                first_admit_idx_value,
3805                resolvers,
3806            )?;
3807            let mut out_buf = [0u8;
3808                <crate::DefaultHostBounds as crate::HostBounds>::K_INVARIANTS_OUTPUT_BYTES_MAX];
3809            match resolvers.k_invariant_resolver().resolve(
3810                crate::pipeline::HomotopyGroupsBytes(operand.bytes()),
3811                &mut out_buf,
3812            ) {
3813                Ok(written) => {
3814                    let width = if written > TERM_VALUE_MAX_BYTES {
3815                        TERM_VALUE_MAX_BYTES
3816                    } else {
3817                        written
3818                    };
3819                    Ok(TermValue::from_slice(&out_buf[..width]))
3820                }
3821                Err(report) => Err(PipelineFailure::ShapeViolation { report }),
3822            }
3823        }
3824        crate::enforcement::Term::Betti { homology_index } => {
3825            // ADR-035 ψ_4: Betti-number extraction from HomologyGroups.
3826            // Pure byte-projection — no resolver consultation. The
3827            // operand's evaluated bytes (the homology-groups byte
3828            // serialization per the homology bridge's wire format)
3829            // are returned as-is; downstream consumers slice the
3830            // Betti tuple positions out of the result.
3831            let v = evaluate_term_at::<A, R>(
3832                arena,
3833                homology_index as usize,
3834                input_bytes,
3835                recurse_value,
3836                recurse_idx_value,
3837                unfold_value,
3838                first_admit_idx_value,
3839                resolvers,
3840            )?;
3841            Ok(v)
3842        }
3843    }
3844}
3845
3846#[allow(clippy::too_many_arguments)]
3847fn apply_primitive_op<A, R>(
3848    arena: &[crate::enforcement::Term],
3849    operator: crate::PrimitiveOp,
3850    args_start: usize,
3851    args_len: usize,
3852    input_bytes: &[u8],
3853    recurse_value: Option<&[u8]>,
3854    recurse_idx_value: Option<&[u8]>,
3855    unfold_value: Option<&[u8]>,
3856    first_admit_idx_value: Option<&[u8]>,
3857    resolvers: &R,
3858) -> Result<TermValue, PipelineFailure>
3859where
3860    A: crate::pipeline::AxisTuple + crate::enforcement::Hasher,
3861    R: crate::pipeline::ResolverTuple
3862        + crate::pipeline::HasNerveResolver<A>
3863        + crate::pipeline::HasChainComplexResolver<A>
3864        + crate::pipeline::HasHomologyGroupResolver<A>
3865        + crate::pipeline::HasCochainComplexResolver<A>
3866        + crate::pipeline::HasCohomologyGroupResolver<A>
3867        + crate::pipeline::HasPostnikovResolver<A>
3868        + crate::pipeline::HasHomotopyGroupResolver<A>
3869        + crate::pipeline::HasKInvariantResolver<A>,
3870{
3871    // Unary ops: 1 arg. Binary ops: 2 args.
3872    let arity = match operator {
3873        crate::PrimitiveOp::Neg
3874        | crate::PrimitiveOp::Bnot
3875        | crate::PrimitiveOp::Succ
3876        | crate::PrimitiveOp::Pred => 1usize,
3877        crate::PrimitiveOp::Add
3878        | crate::PrimitiveOp::Sub
3879        | crate::PrimitiveOp::Mul
3880        | crate::PrimitiveOp::Xor
3881        | crate::PrimitiveOp::And
3882        | crate::PrimitiveOp::Or
3883        | crate::PrimitiveOp::Le
3884        | crate::PrimitiveOp::Lt
3885        | crate::PrimitiveOp::Ge
3886        | crate::PrimitiveOp::Gt
3887        | crate::PrimitiveOp::Concat => 2usize,
3888    };
3889    if args_len != arity {
3890        return Err(PipelineFailure::ShapeViolation {
3891            report: crate::enforcement::ShapeViolation {
3892                shape_iri: "https://uor.foundation/pipeline/PrimitiveOpArityShape",
3893                constraint_iri: "https://uor.foundation/pipeline/PrimitiveOpArityShape/arity",
3894                property_iri: "https://uor.foundation/pipeline/operatorArity",
3895                expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
3896                min_count: arity as u32,
3897                max_count: arity as u32,
3898                kind: crate::ViolationKind::CardinalityViolation,
3899            },
3900        });
3901    }
3902    if arity == 1 {
3903        let v = evaluate_term_at::<A, R>(
3904            arena,
3905            args_start,
3906            input_bytes,
3907            recurse_value,
3908            recurse_idx_value,
3909            unfold_value,
3910            first_admit_idx_value,
3911            resolvers,
3912        )?;
3913        let x = bytes_to_u64_be(v.bytes());
3914        let r =
3915            match operator {
3916                crate::PrimitiveOp::Neg => x.wrapping_neg(),
3917                crate::PrimitiveOp::Bnot => !x,
3918                crate::PrimitiveOp::Succ => x.wrapping_add(1),
3919                crate::PrimitiveOp::Pred => x.wrapping_sub(1),
3920                _ => return Err(PipelineFailure::ShapeViolation {
3921                    report: crate::enforcement::ShapeViolation {
3922                        shape_iri: "https://uor.foundation/pipeline/PrimitiveOpArityShape",
3923                        constraint_iri:
3924                            "https://uor.foundation/pipeline/PrimitiveOpArityShape/binary-as-unary",
3925                        property_iri: "https://uor.foundation/pipeline/operatorArity",
3926                        expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
3927                        min_count: 2,
3928                        max_count: 2,
3929                        kind: crate::ViolationKind::CardinalityViolation,
3930                    },
3931                }),
3932            };
3933        let width = v.bytes().len().clamp(1, 8);
3934        let arr = r.to_be_bytes();
3935        Ok(TermValue::from_slice(&arr[8 - width..]))
3936    } else {
3937        let lhs = evaluate_term_at::<A, R>(
3938            arena,
3939            args_start,
3940            input_bytes,
3941            recurse_value,
3942            recurse_idx_value,
3943            unfold_value,
3944            first_admit_idx_value,
3945            resolvers,
3946        )?;
3947        let rhs = evaluate_term_at::<A, R>(
3948            arena,
3949            args_start + 1,
3950            input_bytes,
3951            recurse_value,
3952            recurse_idx_value,
3953            unfold_value,
3954            first_admit_idx_value,
3955            resolvers,
3956        )?;
3957        // ADR-013/TR-08 substrate-amendment ops: byte-level Concat and
3958        // comparison primitives bypass the u64 fold and operate on the
3959        // operands' full byte sequences.
3960        match operator {
3961            crate::PrimitiveOp::Concat => {
3962                // Concat: emit lhs.bytes() ⧺ rhs.bytes(), bounded by
3963                // TERM_VALUE_MAX_BYTES (truncates excess; runtime callers
3964                // declaring shapes whose composite length exceeds the
3965                // ceiling are rejected at validation time per ADR-028's
3966                // symmetric output ceiling check).
3967                let lb = lhs.bytes();
3968                let rb = rhs.bytes();
3969                let total = lb.len() + rb.len();
3970                let cap = if total > TERM_VALUE_MAX_BYTES {
3971                    TERM_VALUE_MAX_BYTES
3972                } else {
3973                    total
3974                };
3975                let mut buf = [0u8; TERM_VALUE_MAX_BYTES];
3976                let mut i = 0;
3977                while i < lb.len() && i < cap {
3978                    buf[i] = lb[i];
3979                    i += 1;
3980                }
3981                let mut j = 0;
3982                while j < rb.len() && i < cap {
3983                    buf[i] = rb[j];
3984                    i += 1;
3985                    j += 1;
3986                }
3987                return Ok(TermValue::from_slice(&buf[..cap]));
3988            }
3989            crate::PrimitiveOp::Le
3990            | crate::PrimitiveOp::Lt
3991            | crate::PrimitiveOp::Ge
3992            | crate::PrimitiveOp::Gt => {
3993                // Big-endian byte-level comparison. Both operands are
3994                // padded with leading zeros to the max length so the
3995                // comparison ignores leading-zero stripping differences.
3996                let cmp = byte_compare_be(lhs.bytes(), rhs.bytes());
3997                let result_byte: u8 = match operator {
3998                    crate::PrimitiveOp::Le => u8::from(cmp != core::cmp::Ordering::Greater),
3999                    crate::PrimitiveOp::Lt => u8::from(cmp == core::cmp::Ordering::Less),
4000                    crate::PrimitiveOp::Ge => u8::from(cmp != core::cmp::Ordering::Less),
4001                    crate::PrimitiveOp::Gt => u8::from(cmp == core::cmp::Ordering::Greater),
4002                    _ => 0,
4003                };
4004                return Ok(TermValue::from_slice(&[result_byte]));
4005            }
4006            _ => {}
4007        }
4008        let a = bytes_to_u64_be(lhs.bytes());
4009        let b = bytes_to_u64_be(rhs.bytes());
4010        let width = lhs.bytes().len().max(rhs.bytes().len()).max(1);
4011        let r =
4012            match operator {
4013                crate::PrimitiveOp::Add => a.wrapping_add(b),
4014                crate::PrimitiveOp::Sub => a.wrapping_sub(b),
4015                crate::PrimitiveOp::Mul => a.wrapping_mul(b),
4016                crate::PrimitiveOp::Xor => a ^ b,
4017                crate::PrimitiveOp::And => a & b,
4018                crate::PrimitiveOp::Or => a | b,
4019                _ => return Err(PipelineFailure::ShapeViolation {
4020                    report: crate::enforcement::ShapeViolation {
4021                        shape_iri: "https://uor.foundation/pipeline/PrimitiveOpArityShape",
4022                        constraint_iri:
4023                            "https://uor.foundation/pipeline/PrimitiveOpArityShape/unary-as-binary",
4024                        property_iri: "https://uor.foundation/pipeline/operatorArity",
4025                        expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
4026                        min_count: 1,
4027                        max_count: 1,
4028                        kind: crate::ViolationKind::CardinalityViolation,
4029                    },
4030                }),
4031            };
4032        let width = if width > 8 { 8 } else { width };
4033        let arr = r.to_be_bytes();
4034        Ok(TermValue::from_slice(&arr[8 - width..]))
4035    }
4036}
4037
4038fn byte_compare_be(a: &[u8], b: &[u8]) -> core::cmp::Ordering {
4039    // Pad shorter operand with leading zeros so the comparison treats
4040    // both operands at max(len(a), len(b)) byte width.
4041    let max_len = if a.len() > b.len() { a.len() } else { b.len() };
4042    let mut i = 0;
4043    while i < max_len {
4044        let ai = if i + a.len() >= max_len {
4045            a[i + a.len() - max_len]
4046        } else {
4047            0u8
4048        };
4049        let bi = if i + b.len() >= max_len {
4050            b[i + b.len() - max_len]
4051        } else {
4052            0u8
4053        };
4054        if ai < bi {
4055            return core::cmp::Ordering::Less;
4056        }
4057        if ai > bi {
4058            return core::cmp::Ordering::Greater;
4059        }
4060        i += 1;
4061    }
4062    core::cmp::Ordering::Equal
4063}
4064
4065fn bytes_to_u64_be(bytes: &[u8]) -> u64 {
4066    let take = if bytes.len() > 8 { 8 } else { bytes.len() };
4067    let start = bytes.len() - take;
4068    let mut acc = 0u64;
4069    let mut i = 0;
4070    while i < take {
4071        acc = (acc << 8) | bytes[start + i] as u64;
4072        i += 1;
4073    }
4074    acc
4075}
4076
4077/// Foundation-sanctioned identity route: `ConstrainedTypeInput` is the
4078/// empty default shape, vacuously closed under foundation vocabulary.
4079/// Application authors with non-trivial routes use the `prism_model!`
4080/// macro from `uor-foundation-sdk`, which emits `FoundationClosed` for
4081/// the witness it generates iff every node is foundation-vocabulary.
4082/// The identity route's `arena_slice()` returns `&[]` — no terms, no
4083/// transformation, input passes through to output unchanged.
4084impl __sdk_seal::Sealed for ConstrainedTypeInput {}
4085impl FoundationClosed for ConstrainedTypeInput {
4086    fn arena_slice() -> &'static [crate::enforcement::Term] {
4087        &[]
4088    }
4089}
4090impl IntoBindingValue for ConstrainedTypeInput {
4091    const MAX_BYTES: usize = 0;
4092    fn into_binding_bytes(
4093        &self,
4094        _out: &mut [u8],
4095    ) -> core::result::Result<usize, crate::enforcement::ShapeViolation> {
4096        // Identity input carries no bytes — the empty shape's canonical
4097        // serialization is the empty byte sequence.
4098        Ok(0)
4099    }
4100}
4101
4102/// Marker for a `ConstrainedTypeShape` that is the Cartesian product of
4103/// two component shapes. Selecting this trait routes nerve-Betti computation
4104/// through Künneth composition of component Betti profiles rather than
4105/// flat enumeration of (constraint, constraint) pairs. Introduced by the
4106/// Product/Coproduct Completion Amendment §3c for CartesianPartitionProduct
4107/// (CPT_1–CPT_6).
4108pub trait CartesianProductShape: ConstrainedTypeShape {
4109    /// Left operand shape.
4110    type Left: ConstrainedTypeShape;
4111    /// Right operand shape.
4112    type Right: ConstrainedTypeShape;
4113}
4114
4115/// Künneth composition of two Betti profiles.
4116/// Computes `out[k] = Σ_{i + j = k} a[i] · b[j]` over
4117/// `[0, MAX_BETTI_DIMENSION)`. All arithmetic uses saturating operations so the
4118/// function is total on `[u32; MAX_BETTI_DIMENSION]` inputs without panicking.
4119pub const fn kunneth_compose(
4120    a: &[u32; crate::enforcement::MAX_BETTI_DIMENSION],
4121    b: &[u32; crate::enforcement::MAX_BETTI_DIMENSION],
4122) -> [u32; crate::enforcement::MAX_BETTI_DIMENSION] {
4123    let mut out = [0u32; crate::enforcement::MAX_BETTI_DIMENSION];
4124    let mut i: usize = 0;
4125    while i < crate::enforcement::MAX_BETTI_DIMENSION {
4126        let mut j: usize = 0;
4127        while j < crate::enforcement::MAX_BETTI_DIMENSION - i {
4128            let term = a[i].saturating_mul(b[j]);
4129            out[i + j] = out[i + j].saturating_add(term);
4130            j += 1;
4131        }
4132        i += 1;
4133    }
4134    out
4135}
4136
4137/// Cartesian-product nerve Betti via Künneth composition of the component
4138/// shapes' Betti profiles. Used instead of
4139/// `primitive_simplicial_nerve_betti` when a shape declares itself as a
4140/// `CartesianProductShape`. Amendment §3c.
4141/// Phase 1a (orphan-closure): propagates either component's
4142/// `NERVE_CAPACITY_EXCEEDED` via `?`. Dropped `const fn` because
4143/// the `Result` return of the per-component primitive is not `const`-evaluable.
4144/// # Errors
4145/// Returns `NERVE_CAPACITY_EXCEEDED` if either component exceeds caps.
4146pub fn primitive_cartesian_nerve_betti<S: CartesianProductShape>() -> Result<
4147    [u32; crate::enforcement::MAX_BETTI_DIMENSION],
4148    crate::enforcement::GenericImpossibilityWitness,
4149> {
4150    let left = crate::enforcement::primitive_simplicial_nerve_betti::<S::Left>()?;
4151    let right = crate::enforcement::primitive_simplicial_nerve_betti::<S::Right>()?;
4152    Ok(kunneth_compose(&left, &right))
4153}
4154
4155/// Shift every site-index reference in a `ConstraintRef` by `offset`.
4156/// Used by the SDK's `product_shape!` / `coproduct_shape!` /
4157/// `cartesian_product_shape!` macros to splice an operand's constraints into
4158/// a combined shape at a post-operand offset.
4159/// **Phase 17: full operand-catalogue support.** Affine and
4160/// Conjunction now shift correctly at const time because the
4161/// variants store fixed-size arrays (no `&'static [i64]` allocation
4162/// required). The pre-Phase-17 `Site { position: u32::MAX }`
4163/// sentinel is removed.
4164/// **Variant coverage.**
4165/// - `Site { position }` → position += offset.
4166/// - `Carry { site }` → site += offset.
4167/// - `Residue`, `Hamming`, `Depth`, `SatClauses`, `Bound`: pass through (no
4168///   site references at this layer).
4169/// - `Affine { coefficients, coefficient_count, bias }`: builds a fresh
4170///   `[i64; AFFINE_MAX_COEFFS]` of zeros, copies the active prefix into
4171///   positions `[offset, offset + coefficient_count)`. If the shift
4172///   would overflow the fixed buffer, returns an Affine with
4173///   `coefficient_count = 0` (vacuously consistent).
4174/// - `Conjunction { conjuncts, conjunct_count }`: builds a fresh
4175///   `[LeafConstraintRef; CONJUNCTION_MAX_TERMS]` and shifts each leaf
4176///   via `shift_leaf_constraint`. One-level depth — leaves cannot be
4177///   Conjunction.
4178pub const fn shift_constraint(c: ConstraintRef, offset: u32) -> ConstraintRef {
4179    match c {
4180        ConstraintRef::Site { position } => ConstraintRef::Site {
4181            position: position.saturating_add(offset),
4182        },
4183        ConstraintRef::Carry { site } => ConstraintRef::Carry {
4184            site: site.saturating_add(offset),
4185        },
4186        ConstraintRef::Residue { modulus, residue } => ConstraintRef::Residue { modulus, residue },
4187        ConstraintRef::Hamming { bound } => ConstraintRef::Hamming { bound },
4188        ConstraintRef::Depth { min, max } => ConstraintRef::Depth { min, max },
4189        ConstraintRef::SatClauses { clauses, num_vars } => {
4190            ConstraintRef::SatClauses { clauses, num_vars }
4191        }
4192        ConstraintRef::Bound {
4193            observable_iri,
4194            bound_shape_iri,
4195            args_repr,
4196        } => ConstraintRef::Bound {
4197            observable_iri,
4198            bound_shape_iri,
4199            args_repr,
4200        },
4201        ConstraintRef::Affine {
4202            coefficients,
4203            coefficient_count,
4204            bias,
4205        } => {
4206            let (out, new_count) =
4207                shift_affine_coefficients(&coefficients, coefficient_count, offset);
4208            ConstraintRef::Affine {
4209                coefficients: out,
4210                coefficient_count: new_count,
4211                bias,
4212            }
4213        }
4214        ConstraintRef::Conjunction {
4215            conjuncts,
4216            conjunct_count,
4217        } => {
4218            let mut out = [LeafConstraintRef::Site { position: 0 }; CONJUNCTION_MAX_TERMS];
4219            let count = conjunct_count as usize;
4220            let mut i = 0;
4221            while i < count && i < CONJUNCTION_MAX_TERMS {
4222                out[i] = shift_leaf_constraint(conjuncts[i], offset);
4223                i += 1;
4224            }
4225            ConstraintRef::Conjunction {
4226                conjuncts: out,
4227                conjunct_count,
4228            }
4229        }
4230    }
4231}
4232
4233/// Phase 17 helper: shift the active prefix of an `Affine`
4234/// coefficient array right by `offset`, returning a fresh
4235/// `[i64; AFFINE_MAX_COEFFS]` and the new active count. If the
4236/// shift would overflow the fixed buffer, returns count `0`
4237/// (vacuously consistent — no constraint).
4238#[inline]
4239#[must_use]
4240const fn shift_affine_coefficients(
4241    coefficients: &[i64; AFFINE_MAX_COEFFS],
4242    coefficient_count: u32,
4243    offset: u32,
4244) -> ([i64; AFFINE_MAX_COEFFS], u32) {
4245    let mut out = [0i64; AFFINE_MAX_COEFFS];
4246    let count = coefficient_count as usize;
4247    let off = offset as usize;
4248    if off >= AFFINE_MAX_COEFFS {
4249        return (out, 0);
4250    }
4251    let mut i = 0;
4252    while i < count && i + off < AFFINE_MAX_COEFFS {
4253        out[i + off] = coefficients[i];
4254        i += 1;
4255    }
4256    let new_count = (i + off) as u32;
4257    (out, new_count)
4258}
4259
4260/// Phase 17 helper: same as [`shift_constraint`] but operating on a
4261/// [`LeafConstraintRef`]. Used by Conjunction-shifting paths that
4262/// must preserve the leaf-only depth limit.
4263#[inline]
4264#[must_use]
4265pub const fn shift_leaf_constraint(c: LeafConstraintRef, offset: u32) -> LeafConstraintRef {
4266    match c {
4267        LeafConstraintRef::Site { position } => LeafConstraintRef::Site {
4268            position: position.saturating_add(offset),
4269        },
4270        LeafConstraintRef::Carry { site } => LeafConstraintRef::Carry {
4271            site: site.saturating_add(offset),
4272        },
4273        LeafConstraintRef::Residue { modulus, residue } => {
4274            LeafConstraintRef::Residue { modulus, residue }
4275        }
4276        LeafConstraintRef::Hamming { bound } => LeafConstraintRef::Hamming { bound },
4277        LeafConstraintRef::Depth { min, max } => LeafConstraintRef::Depth { min, max },
4278        LeafConstraintRef::SatClauses { clauses, num_vars } => {
4279            LeafConstraintRef::SatClauses { clauses, num_vars }
4280        }
4281        LeafConstraintRef::Bound {
4282            observable_iri,
4283            bound_shape_iri,
4284            args_repr,
4285        } => LeafConstraintRef::Bound {
4286            observable_iri,
4287            bound_shape_iri,
4288            args_repr,
4289        },
4290        LeafConstraintRef::Affine {
4291            coefficients,
4292            coefficient_count,
4293            bias,
4294        } => {
4295            let (out, new_count) =
4296                shift_affine_coefficients(&coefficients, coefficient_count, offset);
4297            LeafConstraintRef::Affine {
4298                coefficients: out,
4299                coefficient_count: new_count,
4300                bias,
4301            }
4302        }
4303    }
4304}
4305
4306/// SDK support: concatenate two operand constraint arrays into a padded
4307/// fixed-size buffer of length `2 * crate::enforcement::NERVE_CONSTRAINTS_CAP`.
4308/// A's constraints are copied verbatim at indices `[0, A::CONSTRAINTS.len())`;
4309/// B's constraints are copied at `[A::CONSTRAINTS.len(), total)` with each
4310/// entry passed through `shift_constraint(c, A::SITE_COUNT as u32)`.
4311/// Trailing positions are filled with the `Site { position: u32::MAX }`
4312/// sentinel.
4313/// Consumers pair this with `sdk_product_constraints_len` to derive the
4314/// slice length at const-eval time: `&BUF[..LEN]` yields a `&'static
4315/// [ConstraintRef]` of the correct length without `unsafe`.
4316pub const fn sdk_concat_product_constraints<A, B>(
4317) -> [ConstraintRef; 2 * crate::enforcement::NERVE_CONSTRAINTS_CAP]
4318where
4319    A: ConstrainedTypeShape,
4320    B: ConstrainedTypeShape,
4321{
4322    let mut out =
4323        [ConstraintRef::Site { position: u32::MAX }; 2 * crate::enforcement::NERVE_CONSTRAINTS_CAP];
4324    let left = A::CONSTRAINTS;
4325    let right = B::CONSTRAINTS;
4326    let offset = A::SITE_COUNT as u32;
4327    let mut i: usize = 0;
4328    while i < left.len() {
4329        out[i] = left[i];
4330        i += 1;
4331    }
4332    let mut j: usize = 0;
4333    while j < right.len() {
4334        out[i + j] = shift_constraint(right[j], offset);
4335        j += 1;
4336    }
4337    out
4338}
4339
4340/// Companion length helper for `sdk_concat_product_constraints`.
4341pub const fn sdk_product_constraints_len<A, B>() -> usize
4342where
4343    A: ConstrainedTypeShape,
4344    B: ConstrainedTypeShape,
4345{
4346    A::CONSTRAINTS.len() + B::CONSTRAINTS.len()
4347}
4348
4349/// ADR-032: per-Witt-level zero-sized marker types implementing
4350/// `ConstrainedTypeShape`. The `prism_model!` proc-macro consumes
4351/// these as the `<DomainTy>` operand of `first_admit(<DomainTy>, |i| …)`
4352/// (G16): `<DomainTy as ConstrainedTypeShape>::CYCLE_SIZE` carries the
4353/// domain's cardinality, which the macro lowers as the descent measure
4354/// for the emitted `Term::Recurse`.
4355/// Each level's `CYCLE_SIZE` is `2^bits_width`, saturated at
4356/// `u64::MAX` for levels whose cardinality exceeds 64-bit range.
4357/// The wiki's normative example `first_admit(WittLevel::W32, |nonce| …)`
4358/// compiles on stable Rust 1.83 as `first_admit(witt_domain::W32, |nonce| …)`
4359/// (`witt_domain::W32::CYCLE_SIZE = 4_294_967_296`).
4360pub mod witt_domain {
4361    use super::{ConstrainedTypeShape, ConstraintRef, PartitionProductFields};
4362    use crate::enforcement::GroundedShape;
4363    use crate::enforcement::ShapeViolation;
4364    use crate::pipeline::__sdk_seal;
4365    use crate::pipeline::IntoBindingValue;
4366
4367    /// ADR-032 Witt-level domain marker for `W8` (8-bit ring).
4368    /// `CYCLE_SIZE = 2^8 = 256`. Used as the `<DomainTy>` operand of
4369    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4370    pub struct W8;
4371    impl ConstrainedTypeShape for W8 {
4372        const IRI: &'static str = "https://uor.foundation/witt/W8";
4373        const SITE_COUNT: usize = 1;
4374        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4375        const CYCLE_SIZE: u64 = 256u64;
4376    }
4377    impl __sdk_seal::Sealed for W8 {}
4378    impl IntoBindingValue for W8 {
4379        const MAX_BYTES: usize = 0;
4380        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4381            Ok(0)
4382        }
4383    }
4384    impl GroundedShape for W8 {}
4385    impl PartitionProductFields for W8 {
4386        const FIELDS: &'static [(u32, u32)] = &[];
4387        const FIELD_NAMES: &'static [&'static str] = &[];
4388    }
4389
4390    /// ADR-032 Witt-level domain marker for `W16` (16-bit ring).
4391    /// `CYCLE_SIZE = 2^16 = 65536`. Used as the `<DomainTy>` operand of
4392    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4393    pub struct W16;
4394    impl ConstrainedTypeShape for W16 {
4395        const IRI: &'static str = "https://uor.foundation/witt/W16";
4396        const SITE_COUNT: usize = 1;
4397        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4398        const CYCLE_SIZE: u64 = 65536u64;
4399    }
4400    impl __sdk_seal::Sealed for W16 {}
4401    impl IntoBindingValue for W16 {
4402        const MAX_BYTES: usize = 0;
4403        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4404            Ok(0)
4405        }
4406    }
4407    impl GroundedShape for W16 {}
4408    impl PartitionProductFields for W16 {
4409        const FIELDS: &'static [(u32, u32)] = &[];
4410        const FIELD_NAMES: &'static [&'static str] = &[];
4411    }
4412
4413    /// ADR-032 Witt-level domain marker for `W24` (24-bit ring).
4414    /// `CYCLE_SIZE = 2^24 = 16777216`. Used as the `<DomainTy>` operand of
4415    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4416    pub struct W24;
4417    impl ConstrainedTypeShape for W24 {
4418        const IRI: &'static str = "https://uor.foundation/witt/W24";
4419        const SITE_COUNT: usize = 1;
4420        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4421        const CYCLE_SIZE: u64 = 16777216u64;
4422    }
4423    impl __sdk_seal::Sealed for W24 {}
4424    impl IntoBindingValue for W24 {
4425        const MAX_BYTES: usize = 0;
4426        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4427            Ok(0)
4428        }
4429    }
4430    impl GroundedShape for W24 {}
4431    impl PartitionProductFields for W24 {
4432        const FIELDS: &'static [(u32, u32)] = &[];
4433        const FIELD_NAMES: &'static [&'static str] = &[];
4434    }
4435
4436    /// ADR-032 Witt-level domain marker for `W32` (32-bit ring).
4437    /// `CYCLE_SIZE = 2^32 = 4294967296`. Used as the `<DomainTy>` operand of
4438    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4439    pub struct W32;
4440    impl ConstrainedTypeShape for W32 {
4441        const IRI: &'static str = "https://uor.foundation/witt/W32";
4442        const SITE_COUNT: usize = 1;
4443        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4444        const CYCLE_SIZE: u64 = 4294967296u64;
4445    }
4446    impl __sdk_seal::Sealed for W32 {}
4447    impl IntoBindingValue for W32 {
4448        const MAX_BYTES: usize = 0;
4449        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4450            Ok(0)
4451        }
4452    }
4453    impl GroundedShape for W32 {}
4454    impl PartitionProductFields for W32 {
4455        const FIELDS: &'static [(u32, u32)] = &[];
4456        const FIELD_NAMES: &'static [&'static str] = &[];
4457    }
4458
4459    /// ADR-032 Witt-level domain marker for `W40` (40-bit ring).
4460    /// `CYCLE_SIZE = 2^40 = 1099511627776`. Used as the `<DomainTy>` operand of
4461    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4462    pub struct W40;
4463    impl ConstrainedTypeShape for W40 {
4464        const IRI: &'static str = "https://uor.foundation/witt/W40";
4465        const SITE_COUNT: usize = 1;
4466        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4467        const CYCLE_SIZE: u64 = 1099511627776u64;
4468    }
4469    impl __sdk_seal::Sealed for W40 {}
4470    impl IntoBindingValue for W40 {
4471        const MAX_BYTES: usize = 0;
4472        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4473            Ok(0)
4474        }
4475    }
4476    impl GroundedShape for W40 {}
4477    impl PartitionProductFields for W40 {
4478        const FIELDS: &'static [(u32, u32)] = &[];
4479        const FIELD_NAMES: &'static [&'static str] = &[];
4480    }
4481
4482    /// ADR-032 Witt-level domain marker for `W48` (48-bit ring).
4483    /// `CYCLE_SIZE = 2^48 = 281474976710656`. Used as the `<DomainTy>` operand of
4484    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4485    pub struct W48;
4486    impl ConstrainedTypeShape for W48 {
4487        const IRI: &'static str = "https://uor.foundation/witt/W48";
4488        const SITE_COUNT: usize = 1;
4489        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4490        const CYCLE_SIZE: u64 = 281474976710656u64;
4491    }
4492    impl __sdk_seal::Sealed for W48 {}
4493    impl IntoBindingValue for W48 {
4494        const MAX_BYTES: usize = 0;
4495        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4496            Ok(0)
4497        }
4498    }
4499    impl GroundedShape for W48 {}
4500    impl PartitionProductFields for W48 {
4501        const FIELDS: &'static [(u32, u32)] = &[];
4502        const FIELD_NAMES: &'static [&'static str] = &[];
4503    }
4504
4505    /// ADR-032 Witt-level domain marker for `W56` (56-bit ring).
4506    /// `CYCLE_SIZE = 2^56 = 72057594037927936`. Used as the `<DomainTy>` operand of
4507    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4508    pub struct W56;
4509    impl ConstrainedTypeShape for W56 {
4510        const IRI: &'static str = "https://uor.foundation/witt/W56";
4511        const SITE_COUNT: usize = 1;
4512        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4513        const CYCLE_SIZE: u64 = 72057594037927936u64;
4514    }
4515    impl __sdk_seal::Sealed for W56 {}
4516    impl IntoBindingValue for W56 {
4517        const MAX_BYTES: usize = 0;
4518        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4519            Ok(0)
4520        }
4521    }
4522    impl GroundedShape for W56 {}
4523    impl PartitionProductFields for W56 {
4524        const FIELDS: &'static [(u32, u32)] = &[];
4525        const FIELD_NAMES: &'static [&'static str] = &[];
4526    }
4527
4528    /// ADR-032 Witt-level domain marker for `W64` (64-bit ring).
4529    /// `CYCLE_SIZE = 2^64 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4530    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4531    pub struct W64;
4532    impl ConstrainedTypeShape for W64 {
4533        const IRI: &'static str = "https://uor.foundation/witt/W64";
4534        const SITE_COUNT: usize = 1;
4535        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4536        const CYCLE_SIZE: u64 = u64::MAX;
4537    }
4538    impl __sdk_seal::Sealed for W64 {}
4539    impl IntoBindingValue for W64 {
4540        const MAX_BYTES: usize = 0;
4541        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4542            Ok(0)
4543        }
4544    }
4545    impl GroundedShape for W64 {}
4546    impl PartitionProductFields for W64 {
4547        const FIELDS: &'static [(u32, u32)] = &[];
4548        const FIELD_NAMES: &'static [&'static str] = &[];
4549    }
4550
4551    /// ADR-032 Witt-level domain marker for `W72` (72-bit ring).
4552    /// `CYCLE_SIZE = 2^72 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4553    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4554    pub struct W72;
4555    impl ConstrainedTypeShape for W72 {
4556        const IRI: &'static str = "https://uor.foundation/witt/W72";
4557        const SITE_COUNT: usize = 1;
4558        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4559        const CYCLE_SIZE: u64 = u64::MAX;
4560    }
4561    impl __sdk_seal::Sealed for W72 {}
4562    impl IntoBindingValue for W72 {
4563        const MAX_BYTES: usize = 0;
4564        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4565            Ok(0)
4566        }
4567    }
4568    impl GroundedShape for W72 {}
4569    impl PartitionProductFields for W72 {
4570        const FIELDS: &'static [(u32, u32)] = &[];
4571        const FIELD_NAMES: &'static [&'static str] = &[];
4572    }
4573
4574    /// ADR-032 Witt-level domain marker for `W80` (80-bit ring).
4575    /// `CYCLE_SIZE = 2^80 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4576    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4577    pub struct W80;
4578    impl ConstrainedTypeShape for W80 {
4579        const IRI: &'static str = "https://uor.foundation/witt/W80";
4580        const SITE_COUNT: usize = 1;
4581        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4582        const CYCLE_SIZE: u64 = u64::MAX;
4583    }
4584    impl __sdk_seal::Sealed for W80 {}
4585    impl IntoBindingValue for W80 {
4586        const MAX_BYTES: usize = 0;
4587        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4588            Ok(0)
4589        }
4590    }
4591    impl GroundedShape for W80 {}
4592    impl PartitionProductFields for W80 {
4593        const FIELDS: &'static [(u32, u32)] = &[];
4594        const FIELD_NAMES: &'static [&'static str] = &[];
4595    }
4596
4597    /// ADR-032 Witt-level domain marker for `W88` (88-bit ring).
4598    /// `CYCLE_SIZE = 2^88 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4599    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4600    pub struct W88;
4601    impl ConstrainedTypeShape for W88 {
4602        const IRI: &'static str = "https://uor.foundation/witt/W88";
4603        const SITE_COUNT: usize = 1;
4604        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4605        const CYCLE_SIZE: u64 = u64::MAX;
4606    }
4607    impl __sdk_seal::Sealed for W88 {}
4608    impl IntoBindingValue for W88 {
4609        const MAX_BYTES: usize = 0;
4610        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4611            Ok(0)
4612        }
4613    }
4614    impl GroundedShape for W88 {}
4615    impl PartitionProductFields for W88 {
4616        const FIELDS: &'static [(u32, u32)] = &[];
4617        const FIELD_NAMES: &'static [&'static str] = &[];
4618    }
4619
4620    /// ADR-032 Witt-level domain marker for `W96` (96-bit ring).
4621    /// `CYCLE_SIZE = 2^96 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4622    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4623    pub struct W96;
4624    impl ConstrainedTypeShape for W96 {
4625        const IRI: &'static str = "https://uor.foundation/witt/W96";
4626        const SITE_COUNT: usize = 1;
4627        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4628        const CYCLE_SIZE: u64 = u64::MAX;
4629    }
4630    impl __sdk_seal::Sealed for W96 {}
4631    impl IntoBindingValue for W96 {
4632        const MAX_BYTES: usize = 0;
4633        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4634            Ok(0)
4635        }
4636    }
4637    impl GroundedShape for W96 {}
4638    impl PartitionProductFields for W96 {
4639        const FIELDS: &'static [(u32, u32)] = &[];
4640        const FIELD_NAMES: &'static [&'static str] = &[];
4641    }
4642
4643    /// ADR-032 Witt-level domain marker for `W104` (104-bit ring).
4644    /// `CYCLE_SIZE = 2^104 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4645    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4646    pub struct W104;
4647    impl ConstrainedTypeShape for W104 {
4648        const IRI: &'static str = "https://uor.foundation/witt/W104";
4649        const SITE_COUNT: usize = 1;
4650        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4651        const CYCLE_SIZE: u64 = u64::MAX;
4652    }
4653    impl __sdk_seal::Sealed for W104 {}
4654    impl IntoBindingValue for W104 {
4655        const MAX_BYTES: usize = 0;
4656        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4657            Ok(0)
4658        }
4659    }
4660    impl GroundedShape for W104 {}
4661    impl PartitionProductFields for W104 {
4662        const FIELDS: &'static [(u32, u32)] = &[];
4663        const FIELD_NAMES: &'static [&'static str] = &[];
4664    }
4665
4666    /// ADR-032 Witt-level domain marker for `W112` (112-bit ring).
4667    /// `CYCLE_SIZE = 2^112 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4668    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4669    pub struct W112;
4670    impl ConstrainedTypeShape for W112 {
4671        const IRI: &'static str = "https://uor.foundation/witt/W112";
4672        const SITE_COUNT: usize = 1;
4673        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4674        const CYCLE_SIZE: u64 = u64::MAX;
4675    }
4676    impl __sdk_seal::Sealed for W112 {}
4677    impl IntoBindingValue for W112 {
4678        const MAX_BYTES: usize = 0;
4679        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4680            Ok(0)
4681        }
4682    }
4683    impl GroundedShape for W112 {}
4684    impl PartitionProductFields for W112 {
4685        const FIELDS: &'static [(u32, u32)] = &[];
4686        const FIELD_NAMES: &'static [&'static str] = &[];
4687    }
4688
4689    /// ADR-032 Witt-level domain marker for `W120` (120-bit ring).
4690    /// `CYCLE_SIZE = 2^120 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4691    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4692    pub struct W120;
4693    impl ConstrainedTypeShape for W120 {
4694        const IRI: &'static str = "https://uor.foundation/witt/W120";
4695        const SITE_COUNT: usize = 1;
4696        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4697        const CYCLE_SIZE: u64 = u64::MAX;
4698    }
4699    impl __sdk_seal::Sealed for W120 {}
4700    impl IntoBindingValue for W120 {
4701        const MAX_BYTES: usize = 0;
4702        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4703            Ok(0)
4704        }
4705    }
4706    impl GroundedShape for W120 {}
4707    impl PartitionProductFields for W120 {
4708        const FIELDS: &'static [(u32, u32)] = &[];
4709        const FIELD_NAMES: &'static [&'static str] = &[];
4710    }
4711
4712    /// ADR-032 Witt-level domain marker for `W128` (128-bit ring).
4713    /// `CYCLE_SIZE = 2^128 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4714    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4715    pub struct W128;
4716    impl ConstrainedTypeShape for W128 {
4717        const IRI: &'static str = "https://uor.foundation/witt/W128";
4718        const SITE_COUNT: usize = 1;
4719        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4720        const CYCLE_SIZE: u64 = u64::MAX;
4721    }
4722    impl __sdk_seal::Sealed for W128 {}
4723    impl IntoBindingValue for W128 {
4724        const MAX_BYTES: usize = 0;
4725        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4726            Ok(0)
4727        }
4728    }
4729    impl GroundedShape for W128 {}
4730    impl PartitionProductFields for W128 {
4731        const FIELDS: &'static [(u32, u32)] = &[];
4732        const FIELD_NAMES: &'static [&'static str] = &[];
4733    }
4734
4735    /// ADR-032 Witt-level domain marker for `W160` (160-bit ring).
4736    /// `CYCLE_SIZE = 2^160 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4737    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4738    pub struct W160;
4739    impl ConstrainedTypeShape for W160 {
4740        const IRI: &'static str = "https://uor.foundation/witt/W160";
4741        const SITE_COUNT: usize = 1;
4742        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4743        const CYCLE_SIZE: u64 = u64::MAX;
4744    }
4745    impl __sdk_seal::Sealed for W160 {}
4746    impl IntoBindingValue for W160 {
4747        const MAX_BYTES: usize = 0;
4748        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4749            Ok(0)
4750        }
4751    }
4752    impl GroundedShape for W160 {}
4753    impl PartitionProductFields for W160 {
4754        const FIELDS: &'static [(u32, u32)] = &[];
4755        const FIELD_NAMES: &'static [&'static str] = &[];
4756    }
4757
4758    /// ADR-032 Witt-level domain marker for `W192` (192-bit ring).
4759    /// `CYCLE_SIZE = 2^192 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4760    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4761    pub struct W192;
4762    impl ConstrainedTypeShape for W192 {
4763        const IRI: &'static str = "https://uor.foundation/witt/W192";
4764        const SITE_COUNT: usize = 1;
4765        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4766        const CYCLE_SIZE: u64 = u64::MAX;
4767    }
4768    impl __sdk_seal::Sealed for W192 {}
4769    impl IntoBindingValue for W192 {
4770        const MAX_BYTES: usize = 0;
4771        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4772            Ok(0)
4773        }
4774    }
4775    impl GroundedShape for W192 {}
4776    impl PartitionProductFields for W192 {
4777        const FIELDS: &'static [(u32, u32)] = &[];
4778        const FIELD_NAMES: &'static [&'static str] = &[];
4779    }
4780
4781    /// ADR-032 Witt-level domain marker for `W224` (224-bit ring).
4782    /// `CYCLE_SIZE = 2^224 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4783    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4784    pub struct W224;
4785    impl ConstrainedTypeShape for W224 {
4786        const IRI: &'static str = "https://uor.foundation/witt/W224";
4787        const SITE_COUNT: usize = 1;
4788        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4789        const CYCLE_SIZE: u64 = u64::MAX;
4790    }
4791    impl __sdk_seal::Sealed for W224 {}
4792    impl IntoBindingValue for W224 {
4793        const MAX_BYTES: usize = 0;
4794        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4795            Ok(0)
4796        }
4797    }
4798    impl GroundedShape for W224 {}
4799    impl PartitionProductFields for W224 {
4800        const FIELDS: &'static [(u32, u32)] = &[];
4801        const FIELD_NAMES: &'static [&'static str] = &[];
4802    }
4803
4804    /// ADR-032 Witt-level domain marker for `W256` (256-bit ring).
4805    /// `CYCLE_SIZE = 2^256 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4806    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4807    pub struct W256;
4808    impl ConstrainedTypeShape for W256 {
4809        const IRI: &'static str = "https://uor.foundation/witt/W256";
4810        const SITE_COUNT: usize = 1;
4811        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4812        const CYCLE_SIZE: u64 = u64::MAX;
4813    }
4814    impl __sdk_seal::Sealed for W256 {}
4815    impl IntoBindingValue for W256 {
4816        const MAX_BYTES: usize = 0;
4817        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4818            Ok(0)
4819        }
4820    }
4821    impl GroundedShape for W256 {}
4822    impl PartitionProductFields for W256 {
4823        const FIELDS: &'static [(u32, u32)] = &[];
4824        const FIELD_NAMES: &'static [&'static str] = &[];
4825    }
4826
4827    /// ADR-032 Witt-level domain marker for `W384` (384-bit ring).
4828    /// `CYCLE_SIZE = 2^384 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4829    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4830    pub struct W384;
4831    impl ConstrainedTypeShape for W384 {
4832        const IRI: &'static str = "https://uor.foundation/witt/W384";
4833        const SITE_COUNT: usize = 1;
4834        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4835        const CYCLE_SIZE: u64 = u64::MAX;
4836    }
4837    impl __sdk_seal::Sealed for W384 {}
4838    impl IntoBindingValue for W384 {
4839        const MAX_BYTES: usize = 0;
4840        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4841            Ok(0)
4842        }
4843    }
4844    impl GroundedShape for W384 {}
4845    impl PartitionProductFields for W384 {
4846        const FIELDS: &'static [(u32, u32)] = &[];
4847        const FIELD_NAMES: &'static [&'static str] = &[];
4848    }
4849
4850    /// ADR-032 Witt-level domain marker for `W448` (448-bit ring).
4851    /// `CYCLE_SIZE = 2^448 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4852    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4853    pub struct W448;
4854    impl ConstrainedTypeShape for W448 {
4855        const IRI: &'static str = "https://uor.foundation/witt/W448";
4856        const SITE_COUNT: usize = 1;
4857        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4858        const CYCLE_SIZE: u64 = u64::MAX;
4859    }
4860    impl __sdk_seal::Sealed for W448 {}
4861    impl IntoBindingValue for W448 {
4862        const MAX_BYTES: usize = 0;
4863        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4864            Ok(0)
4865        }
4866    }
4867    impl GroundedShape for W448 {}
4868    impl PartitionProductFields for W448 {
4869        const FIELDS: &'static [(u32, u32)] = &[];
4870        const FIELD_NAMES: &'static [&'static str] = &[];
4871    }
4872
4873    /// ADR-032 Witt-level domain marker for `W512` (512-bit ring).
4874    /// `CYCLE_SIZE = 2^512 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4875    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4876    pub struct W512;
4877    impl ConstrainedTypeShape for W512 {
4878        const IRI: &'static str = "https://uor.foundation/witt/W512";
4879        const SITE_COUNT: usize = 1;
4880        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4881        const CYCLE_SIZE: u64 = u64::MAX;
4882    }
4883    impl __sdk_seal::Sealed for W512 {}
4884    impl IntoBindingValue for W512 {
4885        const MAX_BYTES: usize = 0;
4886        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4887            Ok(0)
4888        }
4889    }
4890    impl GroundedShape for W512 {}
4891    impl PartitionProductFields for W512 {
4892        const FIELDS: &'static [(u32, u32)] = &[];
4893        const FIELD_NAMES: &'static [&'static str] = &[];
4894    }
4895
4896    /// ADR-032 Witt-level domain marker for `W520` (520-bit ring).
4897    /// `CYCLE_SIZE = 2^520 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4898    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4899    pub struct W520;
4900    impl ConstrainedTypeShape for W520 {
4901        const IRI: &'static str = "https://uor.foundation/witt/W520";
4902        const SITE_COUNT: usize = 1;
4903        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4904        const CYCLE_SIZE: u64 = u64::MAX;
4905    }
4906    impl __sdk_seal::Sealed for W520 {}
4907    impl IntoBindingValue for W520 {
4908        const MAX_BYTES: usize = 0;
4909        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4910            Ok(0)
4911        }
4912    }
4913    impl GroundedShape for W520 {}
4914    impl PartitionProductFields for W520 {
4915        const FIELDS: &'static [(u32, u32)] = &[];
4916        const FIELD_NAMES: &'static [&'static str] = &[];
4917    }
4918
4919    /// ADR-032 Witt-level domain marker for `W528` (528-bit ring).
4920    /// `CYCLE_SIZE = 2^528 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4921    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4922    pub struct W528;
4923    impl ConstrainedTypeShape for W528 {
4924        const IRI: &'static str = "https://uor.foundation/witt/W528";
4925        const SITE_COUNT: usize = 1;
4926        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4927        const CYCLE_SIZE: u64 = u64::MAX;
4928    }
4929    impl __sdk_seal::Sealed for W528 {}
4930    impl IntoBindingValue for W528 {
4931        const MAX_BYTES: usize = 0;
4932        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4933            Ok(0)
4934        }
4935    }
4936    impl GroundedShape for W528 {}
4937    impl PartitionProductFields for W528 {
4938        const FIELDS: &'static [(u32, u32)] = &[];
4939        const FIELD_NAMES: &'static [&'static str] = &[];
4940    }
4941
4942    /// ADR-032 Witt-level domain marker for `W1024` (1024-bit ring).
4943    /// `CYCLE_SIZE = 2^1024 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4944    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4945    pub struct W1024;
4946    impl ConstrainedTypeShape for W1024 {
4947        const IRI: &'static str = "https://uor.foundation/witt/W1024";
4948        const SITE_COUNT: usize = 1;
4949        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4950        const CYCLE_SIZE: u64 = u64::MAX;
4951    }
4952    impl __sdk_seal::Sealed for W1024 {}
4953    impl IntoBindingValue for W1024 {
4954        const MAX_BYTES: usize = 0;
4955        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4956            Ok(0)
4957        }
4958    }
4959    impl GroundedShape for W1024 {}
4960    impl PartitionProductFields for W1024 {
4961        const FIELDS: &'static [(u32, u32)] = &[];
4962        const FIELD_NAMES: &'static [&'static str] = &[];
4963    }
4964
4965    /// ADR-032 Witt-level domain marker for `W2048` (2048-bit ring).
4966    /// `CYCLE_SIZE = 2^2048 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4967    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4968    pub struct W2048;
4969    impl ConstrainedTypeShape for W2048 {
4970        const IRI: &'static str = "https://uor.foundation/witt/W2048";
4971        const SITE_COUNT: usize = 1;
4972        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4973        const CYCLE_SIZE: u64 = u64::MAX;
4974    }
4975    impl __sdk_seal::Sealed for W2048 {}
4976    impl IntoBindingValue for W2048 {
4977        const MAX_BYTES: usize = 0;
4978        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
4979            Ok(0)
4980        }
4981    }
4982    impl GroundedShape for W2048 {}
4983    impl PartitionProductFields for W2048 {
4984        const FIELDS: &'static [(u32, u32)] = &[];
4985        const FIELD_NAMES: &'static [&'static str] = &[];
4986    }
4987
4988    /// ADR-032 Witt-level domain marker for `W4096` (4096-bit ring).
4989    /// `CYCLE_SIZE = 2^4096 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
4990    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
4991    pub struct W4096;
4992    impl ConstrainedTypeShape for W4096 {
4993        const IRI: &'static str = "https://uor.foundation/witt/W4096";
4994        const SITE_COUNT: usize = 1;
4995        const CONSTRAINTS: &'static [ConstraintRef] = &[];
4996        const CYCLE_SIZE: u64 = u64::MAX;
4997    }
4998    impl __sdk_seal::Sealed for W4096 {}
4999    impl IntoBindingValue for W4096 {
5000        const MAX_BYTES: usize = 0;
5001        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
5002            Ok(0)
5003        }
5004    }
5005    impl GroundedShape for W4096 {}
5006    impl PartitionProductFields for W4096 {
5007        const FIELDS: &'static [(u32, u32)] = &[];
5008        const FIELD_NAMES: &'static [&'static str] = &[];
5009    }
5010
5011    /// ADR-032 Witt-level domain marker for `W8192` (8192-bit ring).
5012    /// `CYCLE_SIZE = 2^8192 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
5013    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
5014    pub struct W8192;
5015    impl ConstrainedTypeShape for W8192 {
5016        const IRI: &'static str = "https://uor.foundation/witt/W8192";
5017        const SITE_COUNT: usize = 1;
5018        const CONSTRAINTS: &'static [ConstraintRef] = &[];
5019        const CYCLE_SIZE: u64 = u64::MAX;
5020    }
5021    impl __sdk_seal::Sealed for W8192 {}
5022    impl IntoBindingValue for W8192 {
5023        const MAX_BYTES: usize = 0;
5024        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
5025            Ok(0)
5026        }
5027    }
5028    impl GroundedShape for W8192 {}
5029    impl PartitionProductFields for W8192 {
5030        const FIELDS: &'static [(u32, u32)] = &[];
5031        const FIELD_NAMES: &'static [&'static str] = &[];
5032    }
5033
5034    /// ADR-032 Witt-level domain marker for `W12288` (12288-bit ring).
5035    /// `CYCLE_SIZE = 2^12288 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
5036    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
5037    pub struct W12288;
5038    impl ConstrainedTypeShape for W12288 {
5039        const IRI: &'static str = "https://uor.foundation/witt/W12288";
5040        const SITE_COUNT: usize = 1;
5041        const CONSTRAINTS: &'static [ConstraintRef] = &[];
5042        const CYCLE_SIZE: u64 = u64::MAX;
5043    }
5044    impl __sdk_seal::Sealed for W12288 {}
5045    impl IntoBindingValue for W12288 {
5046        const MAX_BYTES: usize = 0;
5047        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
5048            Ok(0)
5049        }
5050    }
5051    impl GroundedShape for W12288 {}
5052    impl PartitionProductFields for W12288 {
5053        const FIELDS: &'static [(u32, u32)] = &[];
5054        const FIELD_NAMES: &'static [&'static str] = &[];
5055    }
5056
5057    /// ADR-032 Witt-level domain marker for `W16384` (16384-bit ring).
5058    /// `CYCLE_SIZE = 2^16384 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
5059    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
5060    pub struct W16384;
5061    impl ConstrainedTypeShape for W16384 {
5062        const IRI: &'static str = "https://uor.foundation/witt/W16384";
5063        const SITE_COUNT: usize = 1;
5064        const CONSTRAINTS: &'static [ConstraintRef] = &[];
5065        const CYCLE_SIZE: u64 = u64::MAX;
5066    }
5067    impl __sdk_seal::Sealed for W16384 {}
5068    impl IntoBindingValue for W16384 {
5069        const MAX_BYTES: usize = 0;
5070        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
5071            Ok(0)
5072        }
5073    }
5074    impl GroundedShape for W16384 {}
5075    impl PartitionProductFields for W16384 {
5076        const FIELDS: &'static [(u32, u32)] = &[];
5077        const FIELD_NAMES: &'static [&'static str] = &[];
5078    }
5079
5080    /// ADR-032 Witt-level domain marker for `W32768` (32768-bit ring).
5081    /// `CYCLE_SIZE = 2^32768 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
5082    /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
5083    pub struct W32768;
5084    impl ConstrainedTypeShape for W32768 {
5085        const IRI: &'static str = "https://uor.foundation/witt/W32768";
5086        const SITE_COUNT: usize = 1;
5087        const CONSTRAINTS: &'static [ConstraintRef] = &[];
5088        const CYCLE_SIZE: u64 = u64::MAX;
5089    }
5090    impl __sdk_seal::Sealed for W32768 {}
5091    impl IntoBindingValue for W32768 {
5092        const MAX_BYTES: usize = 0;
5093        fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
5094            Ok(0)
5095        }
5096    }
5097    impl GroundedShape for W32768 {}
5098    impl PartitionProductFields for W32768 {
5099        const FIELDS: &'static [(u32, u32)] = &[];
5100        const FIELD_NAMES: &'static [&'static str] = &[];
5101    }
5102}
5103
5104/// Admit a downstream [`ConstrainedTypeShape`] into the reduction pipeline.
5105/// Runs the full preflight chain on `T::CONSTRAINTS`:
5106/// [`preflight_feasibility`] and [`preflight_package_coherence`]. On success,
5107/// wraps the supplied `shape` in a [`Validated`] carrying `Runtime` phase.
5108/// # Errors
5109/// Returns [`ShapeViolation`] if any constraint in `T::CONSTRAINTS` fails
5110/// feasibility checking (e.g., residue out of range, depth min > max) or if
5111/// the constraint system is internally incoherent (e.g., contradictory
5112/// residue constraints on the same modulus).
5113/// # Example
5114/// ```
5115/// use uor_foundation::pipeline::{
5116///     ConstrainedTypeShape, ConstraintRef, validate_constrained_type,
5117/// };
5118/// pub struct MyShape;
5119/// impl ConstrainedTypeShape for MyShape {
5120///     const IRI: &'static str = "https://example.org/MyShape";
5121///     const SITE_COUNT: usize = 2;
5122///     const CONSTRAINTS: &'static [ConstraintRef] = &[
5123///         ConstraintRef::Residue { modulus: 5, residue: 2 },
5124///     ];
5125///     const CYCLE_SIZE: u64 = 5;  // ADR-032: 5 residue classes mod 5
5126/// }
5127/// let validated = validate_constrained_type(MyShape).unwrap();
5128/// # let _ = validated;
5129/// ```
5130pub fn validate_constrained_type<T: ConstrainedTypeShape>(
5131    shape: T,
5132) -> Result<Validated<T, crate::enforcement::Runtime>, ShapeViolation> {
5133    preflight_feasibility(T::CONSTRAINTS)?;
5134    preflight_package_coherence(T::CONSTRAINTS)?;
5135    Ok(Validated::new(shape))
5136}
5137
5138/// Const-fn companion for [`validate_constrained_type`].
5139/// Admits a downstream [`ConstrainedTypeShape`] at compile time, running the
5140/// same preflight checks as the runtime variant but in `const` context.
5141/// # Scope
5142/// `ConstraintRef::Bound { observable_iri, args_repr, .. }` with
5143/// `observable_iri == "https://uor.foundation/observable/LandauerCost"`
5144/// requires `f64::from_bits` for args parsing, which is stable in `const`
5145/// context only from Rust 1.83. The crate's MSRV is 1.81, so this variant
5146/// rejects const admission of `LandauerCost`-bound constraints with
5147/// [`ShapeViolation`] and recommends the runtime [`validate_constrained_type`]
5148/// for those inputs. All other `ConstraintRef` variants admit at const time.
5149/// # Errors
5150/// Same as [`validate_constrained_type`], plus the const-context rejection
5151/// for `LandauerCost`-bound constraints described above.
5152/// The `T: Copy` bound is required by `const fn` — destructor invocation is
5153/// not yet const-stable, and `Validated<T>` carries `T` by value. Shape
5154/// markers are typically zero-sized types which are trivially `Copy`.
5155pub const fn validate_constrained_type_const<T: ConstrainedTypeShape + Copy>(
5156    shape: T,
5157) -> Result<Validated<T, crate::enforcement::CompileTime>, ShapeViolation> {
5158    // Const-path preflight: walk CONSTRAINTS and apply per-variant const checks.
5159    // Rejects LandauerCost-bound constraints that need non-const f64::from_bits.
5160    let constraints = T::CONSTRAINTS;
5161    let mut i = 0;
5162    while i < constraints.len() {
5163        let ok = match &constraints[i] {
5164            ConstraintRef::SatClauses { clauses, num_vars } => *num_vars != 0 || clauses.is_empty(),
5165            ConstraintRef::Residue { modulus, residue } => *modulus != 0 && *residue < *modulus,
5166            ConstraintRef::Carry { .. } => true,
5167            ConstraintRef::Depth { min, max } => *min <= *max,
5168            ConstraintRef::Hamming { bound } => *bound <= 32_768,
5169            ConstraintRef::Site { .. } => true,
5170            ConstraintRef::Affine {
5171                coefficients,
5172                coefficient_count,
5173                bias,
5174            } => {
5175                // Mirror preflight_feasibility's Affine arm in const context.
5176                let count = *coefficient_count as usize;
5177                if count == 0 {
5178                    false
5179                } else {
5180                    let mut ok_coeff = true;
5181                    let mut idx = 0;
5182                    while idx < count && idx < AFFINE_MAX_COEFFS {
5183                        if coefficients[idx] == i64::MIN {
5184                            ok_coeff = false;
5185                            break;
5186                        }
5187                        idx += 1;
5188                    }
5189                    ok_coeff && is_affine_consistent(coefficients, *coefficient_count, *bias)
5190                }
5191            }
5192            ConstraintRef::Bound { observable_iri, .. } => {
5193                // const-fn scope: LandauerCost needs f64::from_bits (stable in
5194                // const at 1.83). Reject it here; runtime admission handles it.
5195                !crate::enforcement::str_eq(
5196                    observable_iri,
5197                    "https://uor.foundation/observable/LandauerCost",
5198                )
5199            }
5200            ConstraintRef::Conjunction {
5201                conjuncts,
5202                conjunct_count,
5203            } => conjunction_all_sat(conjuncts, *conjunct_count),
5204        };
5205        if !ok {
5206            return Err(ShapeViolation {
5207                shape_iri: "https://uor.foundation/type/ConstrainedType",
5208                constraint_iri: "https://uor.foundation/type/ConstrainedType_const_constraint",
5209                property_iri: "https://uor.foundation/type/constraints",
5210                expected_range: "https://uor.foundation/type/Constraint",
5211                min_count: 1,
5212                max_count: 1,
5213                kind: ViolationKind::ValueCheck,
5214            });
5215        }
5216        i += 1;
5217    }
5218    Ok(Validated::new(shape))
5219}
5220
5221/// Result of `fragment_classify`: which `predicate:*Shape` fragment the
5222/// input belongs to. Drives `InhabitanceResolver` dispatch routing.
5223#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5224pub enum FragmentKind {
5225    /// `predicate:Is2SatShape` — clauses of width ≤ 2.
5226    TwoSat,
5227    /// `predicate:IsHornShape` — clauses with ≤ 1 positive literal.
5228    Horn,
5229    /// `predicate:IsResidualFragment` — catch-all; no polynomial bound.
5230    Residual,
5231}
5232
5233/// Classify a constraint system into one of the three dispatch fragments.
5234/// The classifier inspects the first `SatClauses` constraint (if any) and
5235/// applies the ontology's shape predicates. Constraint systems with no
5236/// `SatClauses` constraint — e.g., pure residue/hamming constraints — are
5237/// classified as `Residual`: the dispatch table has no polynomial decider
5238/// for them, so they route to the `ResidualVerdictResolver` catch-all.
5239#[must_use]
5240pub const fn fragment_classify(constraints: &[ConstraintRef]) -> FragmentKind {
5241    let mut i = 0;
5242    while i < constraints.len() {
5243        if let ConstraintRef::SatClauses { clauses, .. } = constraints[i] {
5244            // Classify by maximum clause width and positive-literal count.
5245            let mut max_width: usize = 0;
5246            let mut horn: bool = true;
5247            let mut j = 0;
5248            while j < clauses.len() {
5249                let clause = clauses[j];
5250                if clause.len() > max_width {
5251                    max_width = clause.len();
5252                }
5253                let mut positives: usize = 0;
5254                let mut k = 0;
5255                while k < clause.len() {
5256                    let (_, negated) = clause[k];
5257                    if !negated {
5258                        positives += 1;
5259                    }
5260                    k += 1;
5261                }
5262                if positives > 1 {
5263                    horn = false;
5264                }
5265                j += 1;
5266            }
5267            if max_width <= 2 {
5268                return FragmentKind::TwoSat;
5269            } else if horn {
5270                return FragmentKind::Horn;
5271            } else {
5272                return FragmentKind::Residual;
5273            }
5274        }
5275        i += 1;
5276    }
5277    // No SAT clauses at all — residual.
5278    FragmentKind::Residual
5279}
5280
5281/// Aspvall-Plass-Tarjan 2-SAT decider: returns `true` iff the clause list
5282/// is satisfiable.
5283/// Builds the implication graph: for each clause `(a | b)`, adds
5284/// `¬a → b` and `¬b → a`. Runs Tarjan's SCC algorithm and checks that
5285/// no variable and its negation share an SCC. O(n+m) via iterative
5286/// Tarjan (the `no_std` path can't recurse freely).
5287/// Bounds (from `reduction:TwoSatBound`): up to 256 variables, up to 512 clauses. The `const` bounds keep the entire decider on the stack — essential for `no_std` and compile-time proc-macro expansion.
5288const TWO_SAT_MAX_VARS: usize = 256;
5289const TWO_SAT_MAX_NODES: usize = TWO_SAT_MAX_VARS * 2;
5290const TWO_SAT_MAX_EDGES: usize = 2048;
5291
5292/// 2-SAT decision result.
5293#[must_use]
5294pub fn decide_two_sat(clauses: &[&[(u32, bool)]], num_vars: u32) -> bool {
5295    if (num_vars as usize) > TWO_SAT_MAX_VARS {
5296        return false;
5297    }
5298    let n = (num_vars as usize) * 2;
5299    // Node index: 2*var is positive literal, 2*var+1 is negated.
5300    let mut adj_starts = [0usize; TWO_SAT_MAX_NODES + 1];
5301    let mut adj_targets = [0usize; TWO_SAT_MAX_EDGES];
5302    // First pass: count out-degrees.
5303    for clause in clauses {
5304        if clause.len() > 2 || clause.is_empty() {
5305            return false;
5306        }
5307        if clause.len() == 1 {
5308            let (v, neg) = clause[0];
5309            let lit = lit_index(v, neg);
5310            let neg_lit = lit_index(v, !neg);
5311            // x ↔ (x ∨ x): ¬x → x (assignment forced)
5312            if neg_lit < n + 1 {
5313                adj_starts[neg_lit + 1] += 1;
5314            }
5315            let _ = lit;
5316        } else {
5317            let (a, an) = clause[0];
5318            let (b, bn) = clause[1];
5319            // ¬a → b, ¬b → a
5320            let na = lit_index(a, !an);
5321            let nb = lit_index(b, !bn);
5322            if na + 1 < n + 1 {
5323                adj_starts[na + 1] += 1;
5324            }
5325            if nb + 1 < n + 1 {
5326                adj_starts[nb + 1] += 1;
5327            }
5328        }
5329    }
5330    // Prefix-sum to get adjacency starts.
5331    let mut i = 1;
5332    while i <= n {
5333        adj_starts[i] += adj_starts[i - 1];
5334        i += 1;
5335    }
5336    let edge_count = adj_starts[n];
5337    if edge_count > TWO_SAT_MAX_EDGES {
5338        return false;
5339    }
5340    let mut fill = [0usize; TWO_SAT_MAX_NODES];
5341    for clause in clauses {
5342        if clause.len() == 1 {
5343            let (v, neg) = clause[0];
5344            let pos_lit = lit_index(v, neg);
5345            let neg_lit = lit_index(v, !neg);
5346            let slot = adj_starts[neg_lit] + fill[neg_lit];
5347            adj_targets[slot] = pos_lit;
5348            fill[neg_lit] += 1;
5349        } else {
5350            let (a, an) = clause[0];
5351            let (b, bn) = clause[1];
5352            let pa = lit_index(a, an);
5353            let na = lit_index(a, !an);
5354            let pb = lit_index(b, bn);
5355            let nb = lit_index(b, !bn);
5356            let s1 = adj_starts[na] + fill[na];
5357            adj_targets[s1] = pb;
5358            fill[na] += 1;
5359            let s2 = adj_starts[nb] + fill[nb];
5360            adj_targets[s2] = pa;
5361            fill[nb] += 1;
5362        }
5363    }
5364    // Iterative Tarjan's SCC.
5365    let mut index_counter: usize = 0;
5366    let mut indices = [usize::MAX; TWO_SAT_MAX_NODES];
5367    let mut lowlinks = [0usize; TWO_SAT_MAX_NODES];
5368    let mut on_stack = [false; TWO_SAT_MAX_NODES];
5369    let mut stack = [0usize; TWO_SAT_MAX_NODES];
5370    let mut stack_top: usize = 0;
5371    let mut scc_id = [usize::MAX; TWO_SAT_MAX_NODES];
5372    let mut scc_count: usize = 0;
5373    let mut call_stack = [(0usize, 0usize); TWO_SAT_MAX_NODES];
5374    let mut call_top: usize = 0;
5375    let mut v = 0;
5376    while v < n {
5377        if indices[v] == usize::MAX {
5378            call_stack[call_top] = (v, adj_starts[v]);
5379            call_top += 1;
5380            indices[v] = index_counter;
5381            lowlinks[v] = index_counter;
5382            index_counter += 1;
5383            stack[stack_top] = v;
5384            stack_top += 1;
5385            on_stack[v] = true;
5386            while call_top > 0 {
5387                let (u, mut next_edge) = call_stack[call_top - 1];
5388                let end_edge = adj_starts[u + 1];
5389                let mut advanced = false;
5390                while next_edge < end_edge {
5391                    let w = adj_targets[next_edge];
5392                    next_edge += 1;
5393                    if indices[w] == usize::MAX {
5394                        call_stack[call_top - 1] = (u, next_edge);
5395                        indices[w] = index_counter;
5396                        lowlinks[w] = index_counter;
5397                        index_counter += 1;
5398                        stack[stack_top] = w;
5399                        stack_top += 1;
5400                        on_stack[w] = true;
5401                        call_stack[call_top] = (w, adj_starts[w]);
5402                        call_top += 1;
5403                        advanced = true;
5404                        break;
5405                    } else if on_stack[w] && indices[w] < lowlinks[u] {
5406                        lowlinks[u] = indices[w];
5407                    }
5408                }
5409                if !advanced {
5410                    call_stack[call_top - 1] = (u, next_edge);
5411                    if lowlinks[u] == indices[u] {
5412                        loop {
5413                            stack_top -= 1;
5414                            let w = stack[stack_top];
5415                            on_stack[w] = false;
5416                            scc_id[w] = scc_count;
5417                            if w == u {
5418                                break;
5419                            }
5420                        }
5421                        scc_count += 1;
5422                    }
5423                    call_top -= 1;
5424                    if call_top > 0 {
5425                        let (parent, _) = call_stack[call_top - 1];
5426                        if lowlinks[u] < lowlinks[parent] {
5427                            lowlinks[parent] = lowlinks[u];
5428                        }
5429                    }
5430                }
5431            }
5432        }
5433        v += 1;
5434    }
5435    // Unsatisfiable iff x and ¬x are in the same SCC for any variable.
5436    let mut var = 0u32;
5437    while var < num_vars {
5438        let pos = lit_index(var, false);
5439        let neg = lit_index(var, true);
5440        if scc_id[pos] == scc_id[neg] {
5441            return false;
5442        }
5443        var += 1;
5444    }
5445    true
5446}
5447
5448#[inline]
5449const fn lit_index(var: u32, negated: bool) -> usize {
5450    let base = (var as usize) * 2;
5451    if negated {
5452        base + 1
5453    } else {
5454        base
5455    }
5456}
5457
5458/// Horn-SAT decider via unit propagation. Returns `true` iff the clause
5459/// list is satisfiable.
5460/// Algorithm: start with all variables false. Repeatedly find a clause
5461/// whose negative literals are all satisfied but whose positive literal
5462/// is unassigned/false; set the positive literal true. Fail if a clause
5463/// with no positive literal has all its negatives satisfied.
5464/// Bounds (from `reduction:HornSatBound`): up to 256 variables.
5465const HORN_MAX_VARS: usize = 256;
5466
5467/// Horn-SAT decision result.
5468#[must_use]
5469pub fn decide_horn_sat(clauses: &[&[(u32, bool)]], num_vars: u32) -> bool {
5470    if (num_vars as usize) > HORN_MAX_VARS {
5471        return false;
5472    }
5473    let mut assignment = [false; HORN_MAX_VARS];
5474    let n = num_vars as usize;
5475    loop {
5476        let mut changed = false;
5477        for clause in clauses {
5478            // Count positive literals.
5479            let mut positive: Option<u32> = None;
5480            let mut positive_count = 0;
5481            for (_, negated) in clause.iter() {
5482                if !*negated {
5483                    positive_count += 1;
5484                }
5485            }
5486            if positive_count > 1 {
5487                return false;
5488            }
5489            for (var, negated) in clause.iter() {
5490                if !*negated {
5491                    positive = Some(*var);
5492                }
5493            }
5494            // Check whether all negative literals are satisfied (var=true).
5495            let mut all_neg_satisfied = true;
5496            for (var, negated) in clause.iter() {
5497                if *negated {
5498                    let idx = *var as usize;
5499                    if idx >= n {
5500                        return false;
5501                    }
5502                    if !assignment[idx] {
5503                        all_neg_satisfied = false;
5504                        break;
5505                    }
5506                }
5507            }
5508            if all_neg_satisfied {
5509                match positive {
5510                    None => return false,
5511                    Some(v) => {
5512                        let idx = v as usize;
5513                        if idx >= n {
5514                            return false;
5515                        }
5516                        if !assignment[idx] {
5517                            assignment[idx] = true;
5518                            changed = true;
5519                        }
5520                    }
5521                }
5522            }
5523        }
5524        if !changed {
5525            break;
5526        }
5527    }
5528    // Final verification pass.
5529    for clause in clauses {
5530        let mut satisfied = false;
5531        for (var, negated) in clause.iter() {
5532            let idx = *var as usize;
5533            if idx >= n {
5534                return false;
5535            }
5536            let val = assignment[idx];
5537            if (*negated && !val) || (!*negated && val) {
5538                satisfied = true;
5539                break;
5540            }
5541        }
5542        if !satisfied {
5543            return false;
5544        }
5545    }
5546    true
5547}
5548
5549/// `BudgetSolvencyCheck` (preflightOrder 0): `thermodynamicBudget` must be
5550/// ≥ `bitsWidth(unitWittLevel) × ln 2` per `op:GS_7` / `op:OA_5`.
5551/// Takes the budget in `k_B T · ln 2` units and the target Witt level in
5552/// bit-width. Returns `Ok(())` if solvent, `Err` with the shape violation.
5553pub fn preflight_budget_solvency(budget_units: u64, witt_bits: u32) -> Result<(), ShapeViolation> {
5554    // Landauer bound: one bit requires k_B T · ln 2. Integer form.
5555    let minimum = witt_bits as u64;
5556    if budget_units >= minimum {
5557        Ok(())
5558    } else {
5559        Err(ShapeViolation {
5560            shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
5561            constraint_iri:
5562                "https://uor.foundation/conformance/compileUnit_thermodynamicBudget_constraint",
5563            property_iri: "https://uor.foundation/reduction/thermodynamicBudget",
5564            expected_range: "http://www.w3.org/2001/XMLSchema#decimal",
5565            min_count: 1,
5566            max_count: 1,
5567            kind: ViolationKind::ValueCheck,
5568        })
5569    }
5570}
5571
5572/// v0.2.2 Phase F: upper bound on `CarryDepthObservable` depth arguments.
5573/// Matches target §4.5's Witt-level tower ceiling (W16384).
5574pub const WITT_MAX_BITS: u16 = 16_384;
5575
5576/// v0.2.2 Phase F: ASCII parser for a single unsigned decimal `u32`.
5577/// Returns 0 on malformed input; the caller's downstream comparison (`depth <= WITT_MAX_BITS`)
5578/// accepts 0 as the pass-through degenerate depth, so malformed input is rejected
5579/// by the enclosing feasibility check only if the parsed value exceeds the cap.
5580/// For stricter input discipline, the caller pre-validates `args_repr` at builder time.
5581#[must_use]
5582pub fn parse_u32(s: &str) -> u32 {
5583    let bytes = s.as_bytes();
5584    let mut out: u32 = 0;
5585    let mut i = 0;
5586    while i < bytes.len() {
5587        let b = bytes[i];
5588        if !b.is_ascii_digit() {
5589            return 0;
5590        }
5591        out = out.saturating_mul(10).saturating_add((b - b'0') as u32);
5592        i += 1;
5593    }
5594    out
5595}
5596
5597/// v0.2.2 Phase F: ASCII parser for a single unsigned decimal `u64`.
5598#[must_use]
5599pub fn parse_u64(s: &str) -> u64 {
5600    let bytes = s.as_bytes();
5601    let mut out: u64 = 0;
5602    let mut i = 0;
5603    while i < bytes.len() {
5604        let b = bytes[i];
5605        if !b.is_ascii_digit() {
5606            return 0;
5607        }
5608        out = out.saturating_mul(10).saturating_add((b - b'0') as u64);
5609        i += 1;
5610    }
5611    out
5612}
5613
5614/// v0.2.2 Phase F: parser for `"modulus|residue"` decimal pairs.
5615/// Split on the first ASCII `|`; ASCII-digit-parse each half via `parse_u64`.
5616#[must_use]
5617pub fn parse_u64_pair(s: &str) -> (u64, u64) {
5618    let bytes = s.as_bytes();
5619    let mut split = bytes.len();
5620    let mut i = 0;
5621    while i < bytes.len() {
5622        if bytes[i] == b'|' {
5623            split = i;
5624            break;
5625        }
5626        i += 1;
5627    }
5628    if split >= bytes.len() {
5629        return (0, 0);
5630    }
5631    let (left, right_with_pipe) = s.split_at(split);
5632    let (_, right) = right_with_pipe.split_at(1);
5633    (parse_u64(left), parse_u64(right))
5634}
5635
5636/// v0.2.2 Phase F / Phase 9: parse a decimal `u64` representing an
5637/// IEEE-754 bit pattern. The bit pattern is content-deterministic; call sites
5638/// project to `H::Decimal` via `DecimalTranscendental::from_bits`.
5639#[must_use]
5640pub fn parse_u64_bits_str(s: &str) -> u64 {
5641    parse_u64(s)
5642}
5643
5644/// v0.2.2 Phase F: dispatch a `ConstraintRef::Bound` arm on its `observable_iri`.
5645/// Canonical observables: `ValueModObservable`, `CarryDepthObservable`, `LandauerCost`.
5646/// Unknown IRIs are rejected so that an unaudited observable cannot be threaded
5647/// through the preflight surface silently.
5648fn check_bound_feasibility(
5649    observable_iri: &'static str,
5650    bound_shape_iri: &'static str,
5651    args_repr: &'static str,
5652) -> Result<(), ShapeViolation> {
5653    const VALUE_MOD_IRI: &str = "https://uor.foundation/observable/ValueModObservable";
5654    const CARRY_DEPTH_IRI: &str = "https://uor.foundation/observable/CarryDepthObservable";
5655    const LANDAUER_IRI: &str = "https://uor.foundation/observable/LandauerCost";
5656    let ok = if crate::enforcement::str_eq(observable_iri, VALUE_MOD_IRI) {
5657        let (modulus, residue) = parse_u64_pair(args_repr);
5658        modulus != 0 && residue < modulus
5659    } else if crate::enforcement::str_eq(observable_iri, CARRY_DEPTH_IRI) {
5660        let depth = parse_u32(args_repr);
5661        depth <= WITT_MAX_BITS as u32
5662    } else if crate::enforcement::str_eq(observable_iri, LANDAUER_IRI) {
5663        // Project the bit pattern to f64 (default-host) for the
5664        // finite/positive-nats sanity check. Polymorphic consumers
5665        // construct their own H::Decimal via DecimalTranscendental.
5666        let bits = parse_u64_bits_str(args_repr);
5667        let nats = <f64 as crate::DecimalTranscendental>::from_bits(bits);
5668        nats.is_finite() && nats > 0.0
5669    } else {
5670        false
5671    };
5672    if ok {
5673        Ok(())
5674    } else {
5675        Err(ShapeViolation {
5676            shape_iri: bound_shape_iri,
5677            constraint_iri: "https://uor.foundation/type/BoundConstraint",
5678            property_iri: observable_iri,
5679            expected_range: "https://uor.foundation/observable/BaseMetric",
5680            min_count: 1,
5681            max_count: 1,
5682            kind: ViolationKind::ValueCheck,
5683        })
5684    }
5685}
5686
5687/// `FeasibilityCheck`: verify the constraint system isn't trivially
5688/// infeasible. Workstream E (target §1.5 + §4.7, v0.2.2 closure):
5689/// the closed six-kind constraint set is validated by direct per-kind
5690/// satisfiability checks; any variant that fails is rejected here so
5691/// downstream resolvers never see an unsatisfiable constraint system.
5692/// v0.2.2 Phase F: the `Bound` arm dispatches on `observable_iri` to per-observable
5693/// checks via `check_bound_feasibility`; `Carry` and `Site` remain `Ok(())` by
5694/// documented design — their constraint semantics are structural invariants of
5695/// ring arithmetic and site-index bounds respectively, enforced by construction
5696/// rather than by runtime feasibility checks.
5697pub fn preflight_feasibility(constraints: &[ConstraintRef]) -> Result<(), ShapeViolation> {
5698    for c in constraints {
5699        // v0.2.2 Phase F: Bound dispatches to observable-specific checks with its
5700        // own bound_shape_iri; early-return with that shape on failure.
5701        if let ConstraintRef::Bound {
5702            observable_iri,
5703            bound_shape_iri,
5704            args_repr,
5705        } = c
5706        {
5707            check_bound_feasibility(observable_iri, bound_shape_iri, args_repr)?;
5708            continue;
5709        }
5710        let ok = match c {
5711            ConstraintRef::SatClauses { clauses, num_vars } => *num_vars != 0 || clauses.is_empty(),
5712            ConstraintRef::Residue { modulus, residue } => *modulus != 0 && *residue < *modulus,
5713            // Structural invariant of ring arithmetic — carries cannot contradict by construction.
5714            ConstraintRef::Carry { .. } => true,
5715            ConstraintRef::Depth { min, max } => min <= max,
5716            ConstraintRef::Hamming { bound } => *bound <= 32_768,
5717            // Structural invariant of site indexing — bounds enforced by SITE_COUNT typing.
5718            ConstraintRef::Site { .. } => true,
5719            ConstraintRef::Affine {
5720                coefficients,
5721                coefficient_count,
5722                bias,
5723            } => {
5724                let count = *coefficient_count as usize;
5725                if count == 0 {
5726                    false
5727                } else {
5728                    let mut ok_coeff = true;
5729                    let mut idx = 0;
5730                    while idx < count && idx < AFFINE_MAX_COEFFS {
5731                        if coefficients[idx] == i64::MIN {
5732                            ok_coeff = false;
5733                            break;
5734                        }
5735                        idx += 1;
5736                    }
5737                    ok_coeff && is_affine_consistent(coefficients, *coefficient_count, *bias)
5738                }
5739            }
5740            // Handled above via early `if let`; unreachable here.
5741            ConstraintRef::Bound { .. } => true,
5742            ConstraintRef::Conjunction {
5743                conjuncts,
5744                conjunct_count,
5745            } => conjunction_all_sat(conjuncts, *conjunct_count),
5746        };
5747        if !ok {
5748            return Err(ShapeViolation {
5749                shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
5750                constraint_iri:
5751                    "https://uor.foundation/conformance/compileUnit_rootTerm_constraint",
5752                property_iri: "https://uor.foundation/reduction/rootTerm",
5753                expected_range: "https://uor.foundation/schema/Term",
5754                min_count: 1,
5755                max_count: 1,
5756                kind: ViolationKind::ValueCheck,
5757            });
5758        }
5759    }
5760    Ok(())
5761}
5762
5763/// `DispatchCoverageCheck`: verify the inhabitance dispatch table covers the input.
5764/// The table is exhaustive by construction: Rule 3 (IsResidualFragment) is the catch-all.
5765pub fn preflight_dispatch_coverage() -> Result<(), ShapeViolation> {
5766    // Always covered: IsResidualFragment catches everything not in 2-SAT/Horn.
5767    Ok(())
5768}
5769
5770/// `PackageCoherenceCheck`: verify each site's constraints are internally consistent.
5771pub fn preflight_package_coherence(constraints: &[ConstraintRef]) -> Result<(), ShapeViolation> {
5772    // Check residue constraints don't contradict (same modulus, different residues).
5773    let mut i = 0;
5774    while i < constraints.len() {
5775        if let ConstraintRef::Residue {
5776            modulus: m1,
5777            residue: r1,
5778        } = constraints[i]
5779        {
5780            let mut j = i + 1;
5781            while j < constraints.len() {
5782                if let ConstraintRef::Residue {
5783                    modulus: m2,
5784                    residue: r2,
5785                } = constraints[j]
5786                {
5787                    if m1 == m2 && r1 != r2 {
5788                        return Err(ShapeViolation {
5789                            shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
5790                            constraint_iri:
5791                                "https://uor.foundation/conformance/compileUnit_rootTerm_constraint",
5792                            property_iri: "https://uor.foundation/reduction/rootTerm",
5793                            expected_range: "https://uor.foundation/schema/Term",
5794                            min_count: 1,
5795                            max_count: 1,
5796                            kind: ViolationKind::ValueCheck,
5797                        });
5798                    }
5799                }
5800                j += 1;
5801            }
5802        }
5803        i += 1;
5804    }
5805    Ok(())
5806}
5807
5808/// v0.2.2 Phase B: a-priori `UorTime` estimator for preflight timing.
5809/// Derives a content-deterministic upper bound on the `UorTime` a reduction
5810/// over `shape` at `witt_bits` will consume, without a physical clock. The
5811/// bound is `witt_bits × constraint_count` rewrite steps and the matching
5812/// Landauer nats at `ln 2` per step. Preflight compares this via
5813/// [`UorTime::min_wall_clock`](crate::enforcement::UorTime::min_wall_clock) against the policy's Nanos budget — no
5814/// physical clock is consulted.
5815#[must_use]
5816pub fn estimate_preflight_uor_time<T: ConstrainedTypeShape + ?Sized>(
5817    witt_bits: u16,
5818) -> crate::enforcement::UorTime {
5819    let steps = (witt_bits as u64).saturating_mul((T::CONSTRAINTS.len() as u64).max(1));
5820    let nats = (steps as f64) * core::f64::consts::LN_2;
5821    crate::enforcement::UorTime::new(crate::enforcement::LandauerBudget::new(nats), steps)
5822}
5823
5824/// `PreflightTiming`: timing-check preflight. v0.2.2 Phase B: parameterized over
5825/// a [`TimingPolicy`] carrying the Nanos budget and canonical `Calibration`.
5826/// The `expected` UorTime is derived a-priori from input shape via
5827/// [`estimate_preflight_uor_time`] — content-deterministic, no physical
5828/// clock consulted. Rejects when the Nanos lower bound exceeds the budget.
5829/// # Errors
5830/// Returns `ShapeViolation::ValueCheck` when the expected UorTime, converted
5831/// to Nanos under `P::CALIBRATION`, exceeds `P::PREFLIGHT_BUDGET_NS`.
5832#[allow(dead_code)]
5833pub(crate) const CANONICAL_PREFLIGHT_BUDGET_NS: u64 = 10000000;
5834pub fn preflight_timing<P: crate::enforcement::TimingPolicy>(
5835    expected: crate::enforcement::UorTime,
5836) -> Result<(), ShapeViolation> {
5837    let nanos = expected.min_wall_clock(P::CALIBRATION).as_u64();
5838    if nanos <= P::PREFLIGHT_BUDGET_NS {
5839        Ok(())
5840    } else {
5841        Err(ShapeViolation {
5842            shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
5843            constraint_iri: "https://uor.foundation/reduction/PreflightTimingBound",
5844            property_iri: "https://uor.foundation/reduction/preflightBudgetNs",
5845            expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
5846            min_count: 1,
5847            max_count: 1,
5848            kind: ViolationKind::ValueCheck,
5849        })
5850    }
5851}
5852
5853/// `RuntimeTiming`: runtime timing-check preflight. v0.2.2 Phase B: parameterized
5854/// over a [`TimingPolicy`] carrying the Nanos budget and canonical `Calibration`.
5855/// Identical comparison shape as [`preflight_timing`], against the runtime budget.
5856/// # Errors
5857/// Returns `ShapeViolation::ValueCheck` when the expected UorTime, converted
5858/// to Nanos under `P::CALIBRATION`, exceeds `P::RUNTIME_BUDGET_NS`.
5859#[allow(dead_code)]
5860pub(crate) const CANONICAL_RUNTIME_BUDGET_NS: u64 = 10000000;
5861pub fn runtime_timing<P: crate::enforcement::TimingPolicy>(
5862    expected: crate::enforcement::UorTime,
5863) -> Result<(), ShapeViolation> {
5864    let nanos = expected.min_wall_clock(P::CALIBRATION).as_u64();
5865    if nanos <= P::RUNTIME_BUDGET_NS {
5866        Ok(())
5867    } else {
5868        Err(ShapeViolation {
5869            shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
5870            constraint_iri: "https://uor.foundation/reduction/RuntimeTimingBound",
5871            property_iri: "https://uor.foundation/reduction/runtimeBudgetNs",
5872            expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
5873            min_count: 1,
5874            max_count: 1,
5875            kind: ViolationKind::ValueCheck,
5876        })
5877    }
5878}
5879
5880/// Reduction stage executor. Takes a classified input and runs the 7 stages
5881/// in order, producing a `StageOutcome` on success.
5882#[derive(Debug, Clone, Copy)]
5883pub struct StageOutcome {
5884    /// Witt level the compile unit was resolved at.
5885    pub witt_bits: u16,
5886    /// Fragment classification decided at `stage_resolve`.
5887    pub fragment: FragmentKind,
5888    /// Whether the input is satisfiable (carrier non-empty).
5889    pub satisfiable: bool,
5890}
5891
5892/// Run the 7 reduction stages on a constrained-type input.
5893///
5894/// v0.2.2 T6.14: the `unit_address` field was removed. The substrate-
5895/// computed `ContentAddress` lives on `Grounded` and is derived at the
5896/// caller from the `H: Hasher` output buffer, not inside this stage
5897/// executor.
5898///
5899/// # Errors
5900///
5901/// Returns `PipelineFailure` with the `reduction:PipelineFailureReason` IRI
5902/// of whichever stage rejected the input.
5903pub fn run_reduction_stages<T: ConstrainedTypeShape + ?Sized>(
5904    witt_bits: u16,
5905) -> Result<StageOutcome, PipelineFailure> {
5906    // Stage 0 (initialization): content-addressed unit-id is computed by
5907    // the caller via the consumer-supplied substrate Hasher; nothing to
5908    // do here.
5909    // Stage 1 (declare): no-op; declarations already captured by the derive macro.
5910    // Stage 2 (factorize): no-op; ring factorization is not required for Boolean fragments.
5911    // Stage 3 (resolve): fragment classification.
5912    let fragment = fragment_classify(T::CONSTRAINTS);
5913    // Stage 4 (attest): run the decider associated with the fragment.
5914    let satisfiable = match fragment {
5915        FragmentKind::TwoSat => {
5916            let mut sat = true;
5917            for c in T::CONSTRAINTS {
5918                if let ConstraintRef::SatClauses { clauses, num_vars } = c {
5919                    sat = decide_two_sat(clauses, *num_vars);
5920                    break;
5921                }
5922            }
5923            sat
5924        }
5925        FragmentKind::Horn => {
5926            let mut sat = true;
5927            for c in T::CONSTRAINTS {
5928                if let ConstraintRef::SatClauses { clauses, num_vars } = c {
5929                    sat = decide_horn_sat(clauses, *num_vars);
5930                    break;
5931                }
5932            }
5933            sat
5934        }
5935        FragmentKind::Residual => {
5936            // No polynomial decider available. Residual constraint systems are
5937            // treated as vacuously satisfiable when they carry no SatClauses —
5938            // pure residue/hamming/etc. inputs always have some value satisfying
5939            // at least the trivial case. Non-trivial residuals yield
5940            // ConvergenceStall at stage_convergence below.
5941            let mut has_sat_clauses = false;
5942            for c in T::CONSTRAINTS {
5943                if matches!(c, ConstraintRef::SatClauses { .. }) {
5944                    has_sat_clauses = true;
5945                    break;
5946                }
5947            }
5948            !has_sat_clauses
5949        }
5950    };
5951    if matches!(fragment, FragmentKind::Residual) && !satisfiable {
5952        return Err(PipelineFailure::ConvergenceStall {
5953            stage_iri: "https://uor.foundation/reduction/stage_convergence",
5954            angle_milliradians: 0,
5955        });
5956    }
5957    // Stage 5 (extract): ConstrainedTypeShape inputs carry no term AST, so no
5958    // bindings flow through this path. CompileUnit-bearing callers retrieve the
5959    // declared bindings directly via `unit.bindings()` (Phase H1); runtime
5960    // `BindingsTable` materialization is not possible because `BindingsTable::entries`
5961    // is `&'static [BindingEntry]` by contract (compile-time-constructed catalogs
5962    // are the sole source of sorted-address binary-search tables).
5963    // Stage 6 (convergence): verify fixpoint reached. Trivially true for
5964    // classified fragments.
5965    Ok(StageOutcome {
5966        witt_bits,
5967        fragment,
5968        satisfiable,
5969    })
5970}
5971
5972/// Run the `TowerCompletenessResolver` pipeline on a `ConstrainedTypeShape`
5973/// input at the requested Witt level. Emits a `LiftChainCertificate` on
5974/// success or a generic `ImpossibilityWitness` on failure.
5975/// # Errors
5976/// Returns `GenericImpossibilityWitness` when the input is unsatisfiable or
5977/// when any preflight / reduction stage rejects it.
5978pub fn run_tower_completeness<T: ConstrainedTypeShape + ?Sized, H: crate::enforcement::Hasher>(
5979    _input: &T,
5980    level: WittLevel,
5981) -> Result<Validated<LiftChainCertificate>, GenericImpossibilityWitness> {
5982    let witt_bits = level.witt_length() as u16;
5983    preflight_budget_solvency(witt_bits as u64, witt_bits as u32)
5984        .map_err(|_| GenericImpossibilityWitness::default())?;
5985    preflight_feasibility(T::CONSTRAINTS).map_err(|_| GenericImpossibilityWitness::default())?;
5986    preflight_package_coherence(T::CONSTRAINTS)
5987        .map_err(|_| GenericImpossibilityWitness::default())?;
5988    preflight_dispatch_coverage().map_err(|_| GenericImpossibilityWitness::default())?;
5989    let expected = estimate_preflight_uor_time::<T>(witt_bits);
5990    preflight_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
5991        .map_err(|_| GenericImpossibilityWitness::default())?;
5992    runtime_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
5993        .map_err(|_| GenericImpossibilityWitness::default())?;
5994    let outcome =
5995        run_reduction_stages::<T>(witt_bits).map_err(|_| GenericImpossibilityWitness::default())?;
5996    if outcome.satisfiable {
5997        // v0.2.2 T6.7: thread H: Hasher through fold_unit_digest to compute
5998        // a real substrate fingerprint. Witt level + budget=0 (no CompileUnit).
5999        let mut hasher = H::initial();
6000        hasher = crate::enforcement::fold_unit_digest(
6001            hasher,
6002            outcome.witt_bits,
6003            0,
6004            T::IRI,
6005            T::SITE_COUNT,
6006            T::CONSTRAINTS,
6007            crate::enforcement::CertificateKind::TowerCompleteness,
6008        );
6009        let buffer = hasher.finalize();
6010        let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
6011        let cert = LiftChainCertificate::with_level_and_fingerprint_const(outcome.witt_bits, fp);
6012        Ok(Validated::new(cert))
6013    } else {
6014        Err(GenericImpossibilityWitness::default())
6015    }
6016}
6017
6018/// Workstream F (target ontology: `resolver:IncrementalCompletenessResolver`):
6019/// sealed `SpectralSequencePage` kernel type recording one step of the
6020/// `Q_n → Q_{n+1}` spectral-sequence walk. Each page carries its index,
6021/// the from/to Witt bit widths, and the differential-vanished flag
6022/// (true ⇒ page is trivial; false ⇒ obstruction present with class IRI).
6023#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6024pub struct SpectralSequencePage {
6025    page_index: u32,
6026    source_bits: u16,
6027    target_bits: u16,
6028    differential_vanished: bool,
6029    obstruction_class_iri: &'static str,
6030    _sealed: (),
6031}
6032
6033impl SpectralSequencePage {
6034    /// Crate-internal constructor. Minted only by the incremental walker.
6035    #[inline]
6036    #[must_use]
6037    pub(crate) const fn from_parts(
6038        page_index: u32,
6039        source_bits: u16,
6040        target_bits: u16,
6041        differential_vanished: bool,
6042        obstruction_class_iri: &'static str,
6043    ) -> Self {
6044        Self {
6045            page_index,
6046            source_bits,
6047            target_bits,
6048            differential_vanished,
6049            obstruction_class_iri,
6050            _sealed: (),
6051        }
6052    }
6053
6054    /// Page index (0, 1, 2, …) along the spectral-sequence walk.
6055    #[inline]
6056    #[must_use]
6057    pub const fn page_index(&self) -> u32 {
6058        self.page_index
6059    }
6060
6061    /// Witt bit width at the page's source level.
6062    #[inline]
6063    #[must_use]
6064    pub const fn source_bits(&self) -> u16 {
6065        self.source_bits
6066    }
6067
6068    /// Witt bit width at the page's target level.
6069    #[inline]
6070    #[must_use]
6071    pub const fn target_bits(&self) -> u16 {
6072        self.target_bits
6073    }
6074
6075    /// True iff the page's differential vanishes (no obstruction).
6076    #[inline]
6077    #[must_use]
6078    pub const fn differential_vanished(&self) -> bool {
6079        self.differential_vanished
6080    }
6081
6082    /// Obstruction class IRI when the differential is non-trivial;
6083    /// empty string when the page is trivial.
6084    #[inline]
6085    #[must_use]
6086    pub const fn obstruction_class_iri(&self) -> &'static str {
6087        self.obstruction_class_iri
6088    }
6089}
6090
6091/// Run the `IncrementalCompletenessResolver` (spectral-sequence walk)
6092/// at `target_level`.
6093///
6094/// Per `spec/src/namespaces/resolver.rs` (`IncrementalCompletenessResolver`),
6095/// the walk proceeds without re-running the full ψ-pipeline from
6096/// scratch. Workstream F (v0.2.2 closure) implements the canonical page
6097/// structure: iterate each `Q_n → Q_{n+1}` step from `W8` up to
6098/// `target_level`, compute each page's differential via
6099/// `run_reduction_stages` at the higher level, and record the
6100/// `SpectralSequencePage`. A non-vanishing differential halts with a
6101/// `GenericImpossibilityWitness` whose obstruction-class IRI is
6102/// `https://uor.foundation/type/LiftObstruction`. All trivial pages
6103/// produce a `LiftChainCertificate` stamped with
6104/// `CertificateKind::IncrementalCompleteness`, discriminable from
6105/// `run_tower_completeness`'s certificate by the kind byte.
6106///
6107/// # Errors
6108///
6109/// Returns `GenericImpossibilityWitness` when any page's differential
6110/// does not vanish, or when preflight checks reject the input.
6111pub fn run_incremental_completeness<
6112    T: ConstrainedTypeShape + ?Sized,
6113    H: crate::enforcement::Hasher,
6114>(
6115    _input: &T,
6116    target_level: WittLevel,
6117) -> Result<Validated<LiftChainCertificate>, GenericImpossibilityWitness> {
6118    let target_bits = target_level.witt_length() as u16;
6119    preflight_budget_solvency(target_bits as u64, target_bits as u32)
6120        .map_err(|_| GenericImpossibilityWitness::default())?;
6121    preflight_feasibility(T::CONSTRAINTS).map_err(|_| GenericImpossibilityWitness::default())?;
6122    preflight_package_coherence(T::CONSTRAINTS)
6123        .map_err(|_| GenericImpossibilityWitness::default())?;
6124    preflight_dispatch_coverage().map_err(|_| GenericImpossibilityWitness::default())?;
6125    let expected = estimate_preflight_uor_time::<T>(target_bits);
6126    preflight_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
6127        .map_err(|_| GenericImpossibilityWitness::default())?;
6128    runtime_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
6129        .map_err(|_| GenericImpossibilityWitness::default())?;
6130
6131    // v0.2.2 Phase H4: Betti-driven spectral-sequence walk. At each page, compute
6132    // the constraint-nerve Betti tuple (via primitive_simplicial_nerve_betti)
6133    // and run reduction at both source and target levels. The differential at
6134    // page r with bidegree (p, q) vanishes iff the bidegree-q projection
6135    // `betti[q]` is unchanged between source and target AND both reductions
6136    // are satisfiable at their levels. A mismatch in any bidegree or a
6137    // non-satisfiable reduction produces a non-trivial differential →
6138    // `LiftObstruction` halt with `GenericImpossibilityWitness`.
6139    //
6140    // Betti-threading also produces content-distinct fingerprints for distinct
6141    // constraint topologies: two input shapes with different Betti profiles will
6142    // produce different certs even if both satisfy at every level.
6143    let betti = crate::enforcement::primitive_simplicial_nerve_betti::<T>()?;
6144    let mut page_index: u32 = 0;
6145    let mut from_bits: u16 = 8;
6146    let mut pages_hasher = H::initial();
6147    while from_bits < target_bits {
6148        let to_bits = if from_bits + 8 > target_bits {
6149            target_bits
6150        } else {
6151            from_bits + 8
6152        };
6153        // Reduce at source and target; both must be satisfiable for the
6154        // differential to have a chance of vanishing.
6155        let outcome_source = run_reduction_stages::<T>(from_bits)
6156            .map_err(|_| GenericImpossibilityWitness::default())?;
6157        let outcome_target = run_reduction_stages::<T>(to_bits)
6158            .map_err(|_| GenericImpossibilityWitness::default())?;
6159        // bidegree q = page_index + 1 (first non-trivial homological degree per page).
6160        let q: usize = ((page_index as usize) + 1).min(crate::enforcement::MAX_BETTI_DIMENSION - 1);
6161        let betti_q = betti[q];
6162        // Differential vanishes iff source ≡ target in Betti bidegree q
6163        // AND both reduction levels are satisfiable. Betti is shape-invariant
6164        // (level-independent); the bidegree check is trivially equal, but the
6165        // satisfiability conjunction captures the level-specific obstruction.
6166        let differential_vanishes = outcome_source.satisfiable && outcome_target.satisfiable;
6167        let page = SpectralSequencePage::from_parts(
6168            page_index,
6169            from_bits,
6170            to_bits,
6171            differential_vanishes,
6172            if differential_vanishes {
6173                ""
6174            } else {
6175                "https://uor.foundation/type/LiftObstruction"
6176            },
6177        );
6178        if !page.differential_vanished() {
6179            return Err(GenericImpossibilityWitness::default());
6180        }
6181        // Fold the per-page Betti/satisfiability contribution so distinct
6182        // constraint shapes yield distinct incremental-completeness certs.
6183        pages_hasher = pages_hasher.fold_bytes(&page_index.to_be_bytes());
6184        pages_hasher = pages_hasher.fold_bytes(&from_bits.to_be_bytes());
6185        pages_hasher = pages_hasher.fold_bytes(&to_bits.to_be_bytes());
6186        pages_hasher = pages_hasher.fold_bytes(&betti_q.to_be_bytes());
6187        page_index += 1;
6188        from_bits = to_bits;
6189    }
6190    // The accumulated pages_hasher is currently unused in the final fold;
6191    // the Betti primitive's full tuple is folded below via fold_betti_tuple
6192    // to keep the cert content-addressed over the whole spectral walk.
6193    let _ = pages_hasher;
6194
6195    // Final reduction at the target level — the walk converges when
6196    // every page's differential has vanished; this guarantees the
6197    // target-level outcome is satisfiable.
6198    let outcome = run_reduction_stages::<T>(target_bits)
6199        .map_err(|_| GenericImpossibilityWitness::default())?;
6200    if !outcome.satisfiable {
6201        return Err(GenericImpossibilityWitness::default());
6202    }
6203    // v0.2.2 Phase H4: fold the Betti tuple into the cert alongside the
6204    // canonical unit digest so distinct constraint topologies produce distinct
6205    // incremental-completeness fingerprints.
6206    let mut hasher = H::initial();
6207    hasher = crate::enforcement::fold_betti_tuple(hasher, &betti);
6208    hasher = crate::enforcement::fold_unit_digest(
6209        hasher,
6210        outcome.witt_bits,
6211        page_index as u64,
6212        T::IRI,
6213        T::SITE_COUNT,
6214        T::CONSTRAINTS,
6215        crate::enforcement::CertificateKind::IncrementalCompleteness,
6216    );
6217    let buffer = hasher.finalize();
6218    let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
6219    let cert = LiftChainCertificate::with_level_and_fingerprint_const(outcome.witt_bits, fp);
6220    Ok(Validated::new(cert))
6221}
6222
6223/// Run the `GroundingAwareResolver` on a `CompileUnit` input at `level`,
6224/// exploiting `state:GroundedContext` bindings for O(1) resolution per
6225/// `op:GS_5`.
6226///
6227/// v0.2.2 Phase H2: walks `unit.root_term()` enumerating every
6228/// `Term::Variable { name_index }` and resolves each via linear search
6229/// over `unit.bindings()`. Unresolved variables (declared in the term AST
6230/// but absent from the bindings slice) trigger a `GenericImpossibilityWitness`
6231/// corresponding to `SC5_UNBOUND_VARIABLE`. Resolved bindings are folded
6232/// into the fingerprint via `primitive_session_binding_signature` so the
6233/// cert content-addresses the full grounding context, not just the unit
6234/// shape — distinct binding sets yield distinct fingerprints.
6235///
6236/// # Errors
6237///
6238/// Returns `GenericImpossibilityWitness` on grounding failure: unresolved
6239/// variables, or any variable reference whose name index is absent from
6240/// `unit.bindings()`.
6241pub fn run_grounding_aware<H: crate::enforcement::Hasher>(
6242    unit: &CompileUnit,
6243    level: WittLevel,
6244) -> Result<Validated<GroundingCertificate>, GenericImpossibilityWitness> {
6245    let witt_bits = level.witt_length() as u16;
6246    // v0.2.2 Phase H2: walk root_term enumerating Term::Variable name_indices,
6247    // linear-search unit.bindings() for each, reject unresolved variables.
6248    let root_term = unit.root_term();
6249    let bindings = unit.bindings();
6250    let mut ti = 0;
6251    while ti < root_term.len() {
6252        if let crate::enforcement::Term::Variable { name_index } = root_term[ti] {
6253            let mut found = false;
6254            let mut bi = 0;
6255            while bi < bindings.len() {
6256                if bindings[bi].name_index == name_index {
6257                    found = true;
6258                    break;
6259                }
6260                bi += 1;
6261            }
6262            if !found {
6263                // SC_5 violation: variable referenced but no matching binding.
6264                return Err(GenericImpossibilityWitness::default());
6265            }
6266        }
6267        ti += 1;
6268    }
6269    // Fold: witt_bits / thermodynamic_budget / result_type_iri / session_signature / Grounding kind.
6270    let mut hasher = H::initial();
6271    hasher = hasher.fold_bytes(&witt_bits.to_be_bytes());
6272    hasher = hasher.fold_bytes(&unit.thermodynamic_budget().to_be_bytes());
6273    hasher = hasher.fold_bytes(unit.result_type_iri().as_bytes());
6274    hasher = hasher.fold_byte(0);
6275    let (binding_count, fold_addr) =
6276        crate::enforcement::primitive_session_binding_signature(bindings);
6277    hasher = crate::enforcement::fold_session_signature(hasher, binding_count, fold_addr);
6278    hasher = hasher.fold_byte(crate::enforcement::certificate_kind_discriminant(
6279        crate::enforcement::CertificateKind::Grounding,
6280    ));
6281    let buffer = hasher.finalize();
6282    let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
6283    let cert = GroundingCertificate::with_level_and_fingerprint_const(witt_bits, fp);
6284    Ok(Validated::new(cert))
6285}
6286
6287/// Run the `InhabitanceResolver` dispatch on a `ConstrainedTypeShape`
6288/// input at `level`.
6289///
6290/// Routes to the 2-SAT / Horn-SAT / residual decider via
6291/// `predicate:InhabitanceDispatchTable` rules (ordered by priority).
6292///
6293/// # Errors
6294///
6295/// Returns `InhabitanceImpossibilityWitness` when the input is unsatisfiable.
6296pub fn run_inhabitance<T: ConstrainedTypeShape + ?Sized, H: crate::enforcement::Hasher>(
6297    _input: &T,
6298    level: WittLevel,
6299) -> Result<Validated<InhabitanceCertificate>, InhabitanceImpossibilityWitness> {
6300    let witt_bits = level.witt_length() as u16;
6301    preflight_budget_solvency(witt_bits as u64, witt_bits as u32)
6302        .map_err(|_| InhabitanceImpossibilityWitness::default())?;
6303    preflight_feasibility(T::CONSTRAINTS)
6304        .map_err(|_| InhabitanceImpossibilityWitness::default())?;
6305    preflight_package_coherence(T::CONSTRAINTS)
6306        .map_err(|_| InhabitanceImpossibilityWitness::default())?;
6307    preflight_dispatch_coverage().map_err(|_| InhabitanceImpossibilityWitness::default())?;
6308    let expected = estimate_preflight_uor_time::<T>(witt_bits);
6309    preflight_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
6310        .map_err(|_| InhabitanceImpossibilityWitness::default())?;
6311    runtime_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
6312        .map_err(|_| InhabitanceImpossibilityWitness::default())?;
6313    let outcome = run_reduction_stages::<T>(witt_bits)
6314        .map_err(|_| InhabitanceImpossibilityWitness::default())?;
6315    if outcome.satisfiable {
6316        // v0.2.2 T6.7: thread H: Hasher through fold_unit_digest.
6317        let mut hasher = H::initial();
6318        hasher = crate::enforcement::fold_unit_digest(
6319            hasher,
6320            outcome.witt_bits,
6321            0,
6322            T::IRI,
6323            T::SITE_COUNT,
6324            T::CONSTRAINTS,
6325            crate::enforcement::CertificateKind::Inhabitance,
6326        );
6327        let buffer = hasher.finalize();
6328        let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
6329        let cert = InhabitanceCertificate::with_level_and_fingerprint_const(outcome.witt_bits, fp);
6330        Ok(Validated::new(cert))
6331    } else {
6332        Err(InhabitanceImpossibilityWitness::default())
6333    }
6334}
6335
6336/// v0.2.2 W14: the single typed pipeline entry point producing `Grounded<T>`
6337/// from a validated `CompileUnit`. The caller declares the expected shape `T`;
6338/// the pipeline verifies the unit's root term produces a value of that shape
6339/// and returns `Grounded<T>` on success or a typed `PipelineFailure`.
6340/// Replaces the v0.2.1 `run_pipeline(&datum, level: u8)` form whose bare
6341/// integer level argument was never type-safe.
6342/// # Errors
6343/// Returns `PipelineFailure` on preflight, reduction, or shape-mismatch failure.
6344/// # Example
6345/// ```no_run
6346/// use uor_foundation::enforcement::{
6347///     CompileUnitBuilder, ConstrainedTypeInput, Term,
6348/// };
6349/// use uor_foundation::pipeline::run;
6350/// use uor_foundation::{VerificationDomain, WittLevel};
6351/// # struct Fnv1aHasher16; // downstream substrate; see foundation/examples/custom_hasher_substrate.rs
6352/// # impl uor_foundation::enforcement::Hasher for Fnv1aHasher16 {
6353/// #     const OUTPUT_BYTES: usize = 16;
6354/// #     fn initial() -> Self { Self }
6355/// #     fn fold_byte(self, _: u8) -> Self { self }
6356/// #     fn finalize(self) -> [u8; 32] { [0; 32] }
6357/// # }
6358/// static TERMS: &[Term] = &[Term::Literal { value: 1, level: WittLevel::W8 }];
6359/// static DOMAINS: &[VerificationDomain] = &[VerificationDomain::Enumerative];
6360/// let unit = CompileUnitBuilder::new()
6361///     .root_term(TERMS)
6362///     .witt_level_ceiling(WittLevel::W32)
6363///     .thermodynamic_budget(1024)
6364///     .target_domains(DOMAINS)
6365///     .result_type::<ConstrainedTypeInput>()
6366///     .validate()
6367///     .expect("unit well-formed");
6368/// let grounded = run::<ConstrainedTypeInput, _, Fnv1aHasher16>(unit)
6369///     .expect("pipeline admits");
6370/// # let _ = grounded;
6371/// ```
6372pub fn run<T, P, H>(unit: Validated<CompileUnit, P>) -> Result<Grounded<T>, PipelineFailure>
6373where
6374    T: ConstrainedTypeShape + crate::enforcement::GroundedShape,
6375    P: crate::enforcement::ValidationPhase,
6376    H: crate::enforcement::Hasher,
6377{
6378    let witt_bits = unit.inner().witt_level().witt_length() as u16;
6379    let budget = unit.inner().thermodynamic_budget();
6380    // v0.2.2 T6.11: ShapeMismatch detection. The unit declares its
6381    // result_type_iri at validation time; the caller's `T::IRI` must match.
6382    let unit_iri = unit.inner().result_type_iri();
6383    if !crate::enforcement::str_eq(unit_iri, T::IRI) {
6384        return Err(PipelineFailure::ShapeMismatch {
6385            expected: T::IRI,
6386            got: unit_iri,
6387        });
6388    }
6389    // Preflights. Each maps its ShapeViolation into a PipelineFailure.
6390    preflight_budget_solvency(witt_bits as u64, witt_bits as u32)
6391        .map_err(|report| PipelineFailure::ShapeViolation { report })?;
6392    preflight_feasibility(T::CONSTRAINTS)
6393        .map_err(|report| PipelineFailure::ShapeViolation { report })?;
6394    preflight_package_coherence(T::CONSTRAINTS)
6395        .map_err(|report| PipelineFailure::ShapeViolation { report })?;
6396    preflight_dispatch_coverage().map_err(|report| PipelineFailure::ShapeViolation { report })?;
6397    let expected = estimate_preflight_uor_time::<T>(witt_bits);
6398    preflight_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
6399        .map_err(|report| PipelineFailure::ShapeViolation { report })?;
6400    runtime_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
6401        .map_err(|report| PipelineFailure::ShapeViolation { report })?;
6402    // v0.2.2 T5 C1: actually call run_reduction_stages and propagate its
6403    // failure. The previous v0.2.2 path skipped this call entirely,
6404    // returning a degenerate Grounded with ContentAddress::zero(). The
6405    // typed `run` entry point now mirrors `run_pipeline`'s reduction-stage
6406    // sequence.
6407    let outcome = run_reduction_stages::<T>(witt_bits)?;
6408    if !outcome.satisfiable {
6409        return Err(PipelineFailure::ContradictionDetected {
6410            at_step: 0,
6411            trace_iri: "https://uor.foundation/trace/InhabitanceSearchTrace",
6412        });
6413    }
6414    // v0.2.2 T5 C3.f: thread the consumer-supplied substrate Hasher through
6415    // the canonical CompileUnit byte layout to compute the parametric
6416    // content fingerprint.
6417    let mut hasher = H::initial();
6418    hasher = crate::enforcement::fold_unit_digest(
6419        hasher,
6420        witt_bits,
6421        budget,
6422        T::IRI,
6423        T::SITE_COUNT,
6424        T::CONSTRAINTS,
6425        crate::enforcement::CertificateKind::Grounding,
6426    );
6427    let buffer = hasher.finalize();
6428    let content_fingerprint =
6429        crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
6430    let unit_address = crate::enforcement::unit_address_from_buffer(&buffer);
6431    let grounding = Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
6432        witt_bits,
6433        content_fingerprint,
6434    ));
6435    let bindings = empty_bindings_table();
6436    Ok(Grounded::<T>::new_internal(
6437        grounding,
6438        bindings,
6439        outcome.witt_bits,
6440        unit_address,
6441        content_fingerprint,
6442    ))
6443}
6444
6445/// Construct an empty `BindingsTable`.
6446/// v0.2.2 T6.9: the empty slice is vacuously sorted, so direct struct
6447/// construction is sound. Public callers with non-empty entries go
6448/// through `BindingsTable::try_new` (validating).
6449/// # Driver contract
6450/// Every pipeline driver (`run`, `run_const`, `run_parallel`, `run_stream`,
6451/// `run_interactive`, `run_inhabitance`) mints `Grounded<T>` with this
6452/// empty table today: runtime-dynamic binding materialization in
6453/// `Grounded<T>` requires widening `BindingsTable.entries: &'static [...]`
6454/// to a non-`'static` carrier (a separate architectural change).
6455/// Downstream that needs a compile-time binding catalog uses the pattern
6456/// shown in `foundation/examples/static_bindings_catalog.rs`:
6457/// `Binding::to_binding_entry()` (const-fn) + `BindingsTable::try_new(&[...])`.
6458#[must_use]
6459pub const fn empty_bindings_table() -> BindingsTable {
6460    BindingsTable { entries: &[] }
6461}
6462
6463// Suppress warning: BindingEntry is re-exported via use but not used in
6464// this module directly; it's part of the public pipeline surface.
6465#[allow(dead_code)]
6466const _BINDING_ENTRY_REF: Option<BindingEntry> = None;
6467// Same for CompletenessCertificate — the pipeline does not mint this subclass
6468// directly; Phase D resolvers emit the canonical `GroundingCertificate` carrier
6469// and cert-subclass lifts happen in substrate-specific consumers.
6470#[allow(dead_code)]
6471const _COMPLETENESS_CERT_REF: Option<CompletenessCertificate> = None;
6472
6473/// v0.2.2 Phase F / T2.7: parallel-declaration compile unit. Carries the
6474/// declared site partition cardinality plus (Phase A widening) the raw
6475/// partition slice and disjointness-witness IRI from the builder —
6476/// previously these were discarded at validate-time by a shadowed
6477/// enforcement-local `ParallelDeclaration` that nothing consumed.
6478/// v0.2.2 T6.11: also carries `result_type_iri` for ShapeMismatch detection.
6479#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6480pub struct ParallelDeclaration<'a> {
6481    payload: u64,
6482    result_type_iri: &'static str,
6483    /// v0.2.2 Phase A: raw site-partition slice retained from the builder.
6484    /// Empty slice for declarations built via the site-count-only constructor.
6485    site_partition: &'a [u32],
6486    /// v0.2.2 Phase A: disjointness-witness IRI retained from the builder.
6487    /// Empty string for declarations built via the site-count-only constructor.
6488    disjointness_witness: &'a str,
6489    _sealed: (),
6490}
6491
6492impl<'a> ParallelDeclaration<'a> {
6493    /// v0.2.2 Phase H3: construct a parallel declaration carrying the full
6494    /// partition slice and disjointness-witness IRI from the builder.
6495    /// This is the sole public constructor; the v0.2.2 Phase A site-count-only
6496    /// `new::<T>(site_count)` form was deleted in Phase H3 under the "no
6497    /// compatibility" discipline — every caller supplies a real partition.
6498    #[inline]
6499    #[must_use]
6500    pub const fn new_with_partition<T: ConstrainedTypeShape>(
6501        site_partition: &'a [u32],
6502        disjointness_witness: &'a str,
6503    ) -> Self {
6504        Self {
6505            payload: site_partition.len() as u64,
6506            result_type_iri: T::IRI,
6507            site_partition,
6508            disjointness_witness,
6509            _sealed: (),
6510        }
6511    }
6512
6513    /// Returns the declared site partition cardinality.
6514    #[inline]
6515    #[must_use]
6516    pub const fn site_count(&self) -> u64 {
6517        self.payload
6518    }
6519
6520    /// v0.2.2 T6.11: returns the result-type IRI.
6521    #[inline]
6522    #[must_use]
6523    pub const fn result_type_iri(&self) -> &'static str {
6524        self.result_type_iri
6525    }
6526
6527    /// v0.2.2 Phase A: returns the raw site-partition slice.
6528    #[inline]
6529    #[must_use]
6530    pub const fn site_partition(&self) -> &'a [u32] {
6531        self.site_partition
6532    }
6533
6534    /// v0.2.2 Phase A: returns the disjointness-witness IRI.
6535    #[inline]
6536    #[must_use]
6537    pub const fn disjointness_witness(&self) -> &'a str {
6538        self.disjointness_witness
6539    }
6540}
6541
6542/// v0.2.2 Phase F / T2.7: stream-declaration compile unit. Carries a
6543/// payload field encoding the productivity-witness countdown.
6544/// v0.2.2 T6.11: also carries `result_type_iri` for ShapeMismatch detection.
6545/// v0.2.2 Phase A: also retains the builder's seed/step term slices and
6546/// the productivity-witness IRI so stream resolvers can walk declared
6547/// structure. Distinct from the enforcement-local `StreamDeclaration`
6548/// which records only the `StreamShape` validation surface.
6549/// Note: `Hash` is not derived because `Term` does not implement `Hash`;
6550/// downstream code that needs deterministic hashing should fold through
6551/// the substrate `Hasher` via the pipeline's `fold_stream_digest`.
6552#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6553pub struct StreamDeclaration<'a> {
6554    payload: u64,
6555    result_type_iri: &'static str,
6556    /// v0.2.2 Phase A: stream seed term slice retained from the builder.
6557    seed: &'a [Term],
6558    /// v0.2.2 Phase A: stream step term slice retained from the builder.
6559    step: &'a [Term],
6560    /// v0.2.2 Phase A: productivity-witness IRI retained from the builder.
6561    productivity_witness: &'a str,
6562    _sealed: (),
6563}
6564
6565impl<'a> StreamDeclaration<'a> {
6566    /// v0.2.2 T6.11: construct a stream declaration with the given productivity
6567    /// bound and result type. Phase A: leaves seed/step/witness empty; use
6568    /// `new_full` to retain the full structure.
6569    #[inline]
6570    #[must_use]
6571    pub const fn new<T: ConstrainedTypeShape>(
6572        productivity_bound: u64,
6573    ) -> StreamDeclaration<'static> {
6574        StreamDeclaration {
6575            payload: productivity_bound,
6576            result_type_iri: T::IRI,
6577            seed: &[],
6578            step: &[],
6579            productivity_witness: "",
6580            _sealed: (),
6581        }
6582    }
6583
6584    /// v0.2.2 Phase A: construct a stream declaration carrying the full
6585    /// seed/step/witness structure from the builder.
6586    #[inline]
6587    #[must_use]
6588    pub const fn new_full<T: ConstrainedTypeShape>(
6589        productivity_bound: u64,
6590        seed: &'a [Term],
6591        step: &'a [Term],
6592        productivity_witness: &'a str,
6593    ) -> Self {
6594        Self {
6595            payload: productivity_bound,
6596            result_type_iri: T::IRI,
6597            seed,
6598            step,
6599            productivity_witness,
6600            _sealed: (),
6601        }
6602    }
6603
6604    /// Returns the declared productivity bound.
6605    #[inline]
6606    #[must_use]
6607    pub const fn productivity_bound(&self) -> u64 {
6608        self.payload
6609    }
6610
6611    /// v0.2.2 T6.11: returns the result-type IRI.
6612    #[inline]
6613    #[must_use]
6614    pub const fn result_type_iri(&self) -> &'static str {
6615        self.result_type_iri
6616    }
6617
6618    /// v0.2.2 Phase A: returns the seed term slice.
6619    #[inline]
6620    #[must_use]
6621    pub const fn seed(&self) -> &'a [Term] {
6622        self.seed
6623    }
6624
6625    /// v0.2.2 Phase A: returns the step term slice.
6626    #[inline]
6627    #[must_use]
6628    pub const fn step(&self) -> &'a [Term] {
6629        self.step
6630    }
6631
6632    /// v0.2.2 Phase A: returns the productivity-witness IRI.
6633    #[inline]
6634    #[must_use]
6635    pub const fn productivity_witness(&self) -> &'a str {
6636        self.productivity_witness
6637    }
6638}
6639
6640/// v0.2.2 Phase F / T2.7: interaction-declaration compile unit. Carries a
6641/// payload field encoding the convergence-predicate seed.
6642/// v0.2.2 T6.11: also carries `result_type_iri` for ShapeMismatch detection.
6643/// v0.2.2 Phase A: lifetime-parameterized for consistency with the other
6644/// widened runtime carriers. The interaction builder stores scalar fields
6645/// only, so there is no additional borrowed structure to retain; the `'a`
6646/// is vestigial but keeps the carrier signature uniform with
6647/// `ParallelDeclaration<'a>` and `StreamDeclaration<'a>`.
6648#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6649pub struct InteractionDeclaration<'a> {
6650    payload: u64,
6651    result_type_iri: &'static str,
6652    _sealed: (),
6653    _lifetime: core::marker::PhantomData<&'a ()>,
6654}
6655
6656impl<'a> InteractionDeclaration<'a> {
6657    /// v0.2.2 T6.11: construct an interaction declaration with the given
6658    /// convergence-predicate seed and result type.
6659    #[inline]
6660    #[must_use]
6661    pub const fn new<T: ConstrainedTypeShape>(
6662        convergence_seed: u64,
6663    ) -> InteractionDeclaration<'static> {
6664        InteractionDeclaration {
6665            payload: convergence_seed,
6666            result_type_iri: T::IRI,
6667            _sealed: (),
6668            _lifetime: core::marker::PhantomData,
6669        }
6670    }
6671
6672    /// Returns the declared convergence seed.
6673    #[inline]
6674    #[must_use]
6675    pub const fn convergence_seed(&self) -> u64 {
6676        self.payload
6677    }
6678
6679    /// v0.2.2 T6.11: returns the result-type IRI.
6680    #[inline]
6681    #[must_use]
6682    pub const fn result_type_iri(&self) -> &'static str {
6683        self.result_type_iri
6684    }
6685}
6686
6687/// v0.2.2 Phase F: fixed-size inline payload buffer carried by `PeerInput`.
6688/// Sized for the largest `Datum<L>` the foundation supports at this release
6689/// (up to 32 u64 limbs = 2048 bits); smaller levels use the leading bytes.
6690#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6691pub struct PeerPayload {
6692    words: [u64; 32],
6693    bit_width: u16,
6694    _sealed: (),
6695}
6696
6697impl PeerPayload {
6698    /// Construct a zeroed payload of the given bit width.
6699    #[inline]
6700    #[must_use]
6701    pub const fn zero(bit_width: u16) -> Self {
6702        Self {
6703            words: [0u64; 32],
6704            bit_width,
6705            _sealed: (),
6706        }
6707    }
6708
6709    /// Access the underlying limbs.
6710    #[inline]
6711    #[must_use]
6712    pub const fn words(&self) -> &[u64; 32] {
6713        &self.words
6714    }
6715
6716    /// Bit width of the payload's logical Datum.
6717    #[inline]
6718    #[must_use]
6719    pub const fn bit_width(&self) -> u16 {
6720        self.bit_width
6721    }
6722}
6723
6724/// v0.2.2 Phase F: a peer-supplied input to an interaction driver step.
6725/// Fixed-size — holds a `PeerPayload` inline plus the peer's content
6726/// address. No heap, no dynamic dispatch.
6727#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6728pub struct PeerInput {
6729    peer_id: u128,
6730    payload: PeerPayload,
6731    _sealed: (),
6732}
6733
6734impl PeerInput {
6735    /// Construct a new peer input with the given peer id and payload.
6736    #[inline]
6737    #[must_use]
6738    pub const fn new(peer_id: u128, payload: PeerPayload) -> Self {
6739        Self {
6740            peer_id,
6741            payload,
6742            _sealed: (),
6743        }
6744    }
6745
6746    /// Access the peer id.
6747    #[inline]
6748    #[must_use]
6749    pub const fn peer_id(&self) -> u128 {
6750        self.peer_id
6751    }
6752
6753    /// Access the payload.
6754    #[inline]
6755    #[must_use]
6756    pub const fn payload(&self) -> &PeerPayload {
6757        &self.payload
6758    }
6759}
6760
6761/// v0.2.2 Phase F: outcome of a single `InteractionDriver::step` call.
6762#[derive(Debug, Clone)]
6763#[non_exhaustive]
6764pub enum StepResult<T: crate::enforcement::GroundedShape> {
6765    /// The step was absorbed; the driver is ready for another peer input.
6766    Continue,
6767    /// The step produced an intermediate grounded output.
6768    Output(Grounded<T>),
6769    /// The convergence predicate is satisfied; interaction is complete.
6770    Converged(Grounded<T>),
6771    /// v0.2.2 Phase T.1: the commutator norm failed to decrease for
6772    /// `INTERACTION_DIVERGENCE_BUDGET` consecutive steps — the interaction is
6773    /// non-convergent and the driver is no longer advanceable.
6774    Diverged,
6775    /// The step failed; the driver is no longer advanceable.
6776    Failure(PipelineFailure),
6777}
6778
6779/// v0.2.2 Phase F: sealed commutator-algebra state carried by an
6780/// interaction driver across peer steps.
6781#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6782pub struct CommutatorState<L> {
6783    accumulator: [u64; 4],
6784    _level: core::marker::PhantomData<L>,
6785    _sealed: (),
6786}
6787
6788impl<L> CommutatorState<L> {
6789    /// Construct a zero commutator state.
6790    #[inline]
6791    #[must_use]
6792    pub const fn zero() -> Self {
6793        Self {
6794            accumulator: [0u64; 4],
6795            _level: core::marker::PhantomData,
6796            _sealed: (),
6797        }
6798    }
6799
6800    /// Access the commutator accumulator words.
6801    #[inline]
6802    #[must_use]
6803    pub const fn accumulator(&self) -> &[u64; 4] {
6804        &self.accumulator
6805    }
6806}
6807
6808/// v0.2.2 Phase F / T2.7: sealed iterator driver returned by `run_stream`.
6809/// Carries the productivity countdown initialized from the unit's
6810/// `StreamDeclaration::productivity_bound()`, plus a unit-derived address
6811/// seed for generating distinct `Grounded` outputs per step. Each call to
6812/// `next()` decrements the countdown and yields a `Grounded` whose
6813/// `unit_address` differs from the previous step's.
6814#[derive(Debug, Clone)]
6815pub struct StreamDriver<
6816    T: crate::enforcement::GroundedShape,
6817    P: crate::enforcement::ValidationPhase,
6818    H: crate::enforcement::Hasher,
6819> {
6820    rewrite_steps: u64,
6821    landauer_nats: u64,
6822    productivity_countdown: u64,
6823    seed: u64,
6824    result_type_iri: &'static str,
6825    terminated: bool,
6826    _shape: core::marker::PhantomData<T>,
6827    _phase: core::marker::PhantomData<P>,
6828    _hasher: core::marker::PhantomData<H>,
6829    _sealed: (),
6830}
6831
6832impl<
6833        T: crate::enforcement::GroundedShape,
6834        P: crate::enforcement::ValidationPhase,
6835        H: crate::enforcement::Hasher,
6836    > StreamDriver<T, P, H>
6837{
6838    /// Crate-internal constructor. Callable only from `pipeline::run_stream`.
6839    #[inline]
6840    #[must_use]
6841    #[allow(dead_code)]
6842    pub(crate) const fn new_internal(
6843        productivity_bound: u64,
6844        seed: u64,
6845        result_type_iri: &'static str,
6846    ) -> Self {
6847        Self {
6848            rewrite_steps: 0,
6849            landauer_nats: 0,
6850            productivity_countdown: productivity_bound,
6851            seed,
6852            result_type_iri,
6853            terminated: false,
6854            _shape: core::marker::PhantomData,
6855            _phase: core::marker::PhantomData,
6856            _hasher: core::marker::PhantomData,
6857            _sealed: (),
6858        }
6859    }
6860
6861    /// Total rewrite steps taken so far.
6862    #[inline]
6863    #[must_use]
6864    pub const fn rewrite_steps(&self) -> u64 {
6865        self.rewrite_steps
6866    }
6867
6868    /// Total Landauer cost accumulated so far.
6869    #[inline]
6870    #[must_use]
6871    pub const fn landauer_nats(&self) -> u64 {
6872        self.landauer_nats
6873    }
6874
6875    /// v0.2.2 T5.10: returns `true` once the driver has stopped producing
6876    /// rewrite steps. A terminated driver is observationally equivalent to
6877    /// one whose next `next()` call returns `None`. Use this when the driver
6878    /// is held inside a larger state machine that needs to decide whether
6879    /// to advance without consuming a step.
6880    /// Parallel to `InteractionDriver::is_converged()`.
6881    #[inline]
6882    #[must_use]
6883    pub const fn is_terminated(&self) -> bool {
6884        self.terminated
6885    }
6886}
6887
6888impl<
6889        T: crate::enforcement::GroundedShape + ConstrainedTypeShape,
6890        P: crate::enforcement::ValidationPhase,
6891        H: crate::enforcement::Hasher,
6892    > Iterator for StreamDriver<T, P, H>
6893{
6894    type Item = Result<Grounded<T>, PipelineFailure>;
6895    fn next(&mut self) -> Option<Self::Item> {
6896        if self.terminated || self.productivity_countdown == 0 {
6897            self.terminated = true;
6898            return None;
6899        }
6900        // v0.2.2 T6.11: ShapeMismatch detection — first step only
6901        // (subsequent steps inherit the same result_type_iri).
6902        if self.rewrite_steps == 0 && !crate::enforcement::str_eq(self.result_type_iri, T::IRI) {
6903            self.terminated = true;
6904            return Some(Err(PipelineFailure::ShapeMismatch {
6905                expected: T::IRI,
6906                got: self.result_type_iri,
6907            }));
6908        }
6909        self.productivity_countdown -= 1;
6910        self.rewrite_steps += 1;
6911        self.landauer_nats += 1;
6912        // v0.2.2 T6.1: thread H: Hasher through fold_stream_step_digest
6913        // to compute a real per-step substrate fingerprint.
6914        let mut hasher = H::initial();
6915        hasher = crate::enforcement::fold_stream_step_digest(
6916            hasher,
6917            self.productivity_countdown,
6918            self.rewrite_steps,
6919            self.seed,
6920            self.result_type_iri,
6921            crate::enforcement::CertificateKind::Grounding,
6922        );
6923        let buffer = hasher.finalize();
6924        let content_fingerprint =
6925            crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
6926        let unit_address = crate::enforcement::unit_address_from_buffer(&buffer);
6927        let grounding = Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
6928            32,
6929            content_fingerprint,
6930        ));
6931        let bindings = empty_bindings_table();
6932        Some(Ok(Grounded::<T>::new_internal(
6933            grounding,
6934            bindings,
6935            32, // default witt level for stream output
6936            unit_address,
6937            content_fingerprint,
6938        )))
6939    }
6940}
6941
6942/// v0.2.2 Phase F / T2.7: sealed state-machine driver returned by
6943/// `run_interactive`. Exposes `step(PeerInput)`, `is_converged()`, and
6944/// `finalize()`. The driver folds each peer input into its
6945/// `commutator_acc` accumulator via XOR; convergence is signalled when
6946/// a peer input arrives with `peer_id == 0` (the closing handshake).
6947#[derive(Debug, Clone)]
6948pub struct InteractionDriver<
6949    T: crate::enforcement::GroundedShape,
6950    P: crate::enforcement::ValidationPhase,
6951    H: crate::enforcement::Hasher,
6952> {
6953    commutator_acc: [u64; 4],
6954    peer_step_count: u64,
6955    converged: bool,
6956    /// Convergence seed read from the source InteractionDeclaration.
6957    /// Available via `seed()` for downstream inspection.
6958    seed: u64,
6959    /// v0.2.2 Phase T.1: previous step's commutator norm (Euclidean-squared
6960    /// over the 4 u64 limbs, saturating). Used to detect divergence.
6961    prev_commutator_norm: u64,
6962    /// v0.2.2 Phase T.1: count of consecutive non-decreasing norm steps.
6963    /// Reset to 0 on any decrease; divergence triggers at `DIVERGENCE_BUDGET`.
6964    consecutive_non_decreasing: u32,
6965    /// v0.2.2 T6.11: result-type IRI from the source InteractionDeclaration.
6966    result_type_iri: &'static str,
6967    _shape: core::marker::PhantomData<T>,
6968    _phase: core::marker::PhantomData<P>,
6969    _hasher: core::marker::PhantomData<H>,
6970    _sealed: (),
6971}
6972
6973/// v0.2.2 Phase T.1: divergence budget — max consecutive non-decreasing commutator-norm
6974/// steps before the interaction driver fails. Foundation-canonical; override at the
6975/// `InteractionDeclaration` level not supported in this release.
6976pub const INTERACTION_DIVERGENCE_BUDGET: u32 = 16;
6977
6978impl<
6979        T: crate::enforcement::GroundedShape,
6980        P: crate::enforcement::ValidationPhase,
6981        H: crate::enforcement::Hasher,
6982    > InteractionDriver<T, P, H>
6983{
6984    /// Crate-internal constructor. Callable only from `pipeline::run_interactive`.
6985    #[inline]
6986    #[must_use]
6987    #[allow(dead_code)]
6988    pub(crate) const fn new_internal(seed: u64, result_type_iri: &'static str) -> Self {
6989        // Initial commutator seeded from the unit's convergence seed.
6990        Self {
6991            commutator_acc: [seed, 0, 0, 0],
6992            peer_step_count: 0,
6993            converged: false,
6994            seed,
6995            // Initial norm = seed² (saturating) so the first step can only
6996            // decrease the norm via peer input (which is the convergence path).
6997            prev_commutator_norm: seed.saturating_mul(seed),
6998            consecutive_non_decreasing: 0,
6999            result_type_iri,
7000            _shape: core::marker::PhantomData,
7001            _phase: core::marker::PhantomData,
7002            _hasher: core::marker::PhantomData,
7003            _sealed: (),
7004        }
7005    }
7006
7007    /// v0.2.2 Phase T.1: convergence threshold derived from the seed. Termination
7008    /// triggers when the commutator norm falls below this value. Foundation-canonical:
7009    /// `seed.rotate_right(32) ^ 0xDEADBEEF_CAFEBABE`.
7010    #[inline]
7011    #[must_use]
7012    pub const fn convergence_threshold(&self) -> u64 {
7013        self.seed.rotate_right(32) ^ 0xDEAD_BEEF_CAFE_BABE
7014    }
7015
7016    /// Advance the driver by folding in a single peer input (v0.2.2 Phase T.1).
7017    /// Each step XOR-folds the peer payload's first 4 limbs into the
7018    /// commutator accumulator, then recomputes the Euclidean-squared
7019    /// norm over the 4 limbs (saturating `u64`). Termination rules:
7020    /// * **Converged** if the norm falls below `convergence_threshold()`,
7021    ///   OR if `peer_id == 0` (explicit closing handshake).
7022    /// * **Diverged** (via `PipelineFailure::ConvergenceStall`) if the norm is
7023    ///   non-decreasing for `INTERACTION_DIVERGENCE_BUDGET` consecutive steps.
7024    /// * **Continue** otherwise.
7025    #[must_use]
7026    pub fn step(&mut self, input: PeerInput) -> StepResult<T>
7027    where
7028        T: ConstrainedTypeShape,
7029    {
7030        self.peer_step_count += 1;
7031        // Fold the first 4 payload words into the accumulator.
7032        let words = input.payload().words();
7033        let mut i = 0usize;
7034        while i < 4 {
7035            self.commutator_acc[i] ^= words[i];
7036            i += 1;
7037        }
7038        // v0.2.2 Phase T.1: compute the Euclidean-squared norm over the 4 limbs.
7039        let mut norm: u64 = 0;
7040        let mut j = 0usize;
7041        while j < 4 {
7042            let limb = self.commutator_acc[j];
7043            norm = norm.saturating_add(limb.saturating_mul(limb));
7044            j += 1;
7045        }
7046        let threshold = self.convergence_threshold();
7047        // v0.2.2 Phase T.1: convergence on norm-below-threshold OR explicit
7048        // handshake (peer_id == 0). Divergence on consecutive non-decreasing norm.
7049        let norm_converged = norm < threshold;
7050        let handshake_close = input.peer_id() == 0;
7051        if norm_converged || handshake_close {
7052            self.converged = true;
7053            // v0.2.2 T6.1: thread H: Hasher through fold_interaction_step_digest
7054            // to compute a real convergence-time substrate fingerprint.
7055            let mut hasher = H::initial();
7056            hasher = crate::enforcement::fold_interaction_step_digest(
7057                hasher,
7058                &self.commutator_acc,
7059                self.peer_step_count,
7060                self.seed,
7061                self.result_type_iri,
7062                crate::enforcement::CertificateKind::Grounding,
7063            );
7064            let buffer = hasher.finalize();
7065            let content_fingerprint =
7066                crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
7067            let unit_address = crate::enforcement::unit_address_from_buffer(&buffer);
7068            let grounding = Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
7069                32,
7070                content_fingerprint,
7071            ));
7072            let bindings = empty_bindings_table();
7073            return StepResult::Converged(Grounded::<T>::new_internal(
7074                grounding,
7075                bindings,
7076                32,
7077                unit_address,
7078                content_fingerprint,
7079            ));
7080        }
7081        // v0.2.2 Phase T.1: divergence detection — count consecutive
7082        // non-decreasing norm steps. Reset on any decrease.
7083        if norm >= self.prev_commutator_norm {
7084            self.consecutive_non_decreasing = self.consecutive_non_decreasing.saturating_add(1);
7085        } else {
7086            self.consecutive_non_decreasing = 0;
7087        }
7088        self.prev_commutator_norm = norm;
7089        if self.consecutive_non_decreasing >= INTERACTION_DIVERGENCE_BUDGET {
7090            return StepResult::Diverged;
7091        }
7092        StepResult::Continue
7093    }
7094
7095    /// Whether the driver has reached the convergence predicate.
7096    #[inline]
7097    #[must_use]
7098    pub const fn is_converged(&self) -> bool {
7099        self.converged
7100    }
7101
7102    /// Number of peer steps applied so far.
7103    #[inline]
7104    #[must_use]
7105    pub const fn peer_step_count(&self) -> u64 {
7106        self.peer_step_count
7107    }
7108
7109    /// Convergence seed inherited from the source InteractionDeclaration.
7110    #[inline]
7111    #[must_use]
7112    pub const fn seed(&self) -> u64 {
7113        self.seed
7114    }
7115
7116    /// Finalize the interaction, producing a grounded result.
7117    /// Returns a `Grounded<T>` whose `unit_address` is a hash of the
7118    /// accumulated commutator state, so two interaction drivers that
7119    /// processed different peer inputs return distinct grounded values.
7120    /// # Errors
7121    /// Returns a `PipelineFailure::ShapeViolation` if the driver has
7122    /// not converged, or `PipelineFailure::ShapeMismatch` if the source
7123    /// declaration's result_type_iri does not match `T::IRI`.
7124    pub fn finalize(self) -> Result<Grounded<T>, PipelineFailure>
7125    where
7126        T: ConstrainedTypeShape,
7127    {
7128        // v0.2.2 T6.11: ShapeMismatch detection.
7129        if !crate::enforcement::str_eq(self.result_type_iri, T::IRI) {
7130            return Err(PipelineFailure::ShapeMismatch {
7131                expected: T::IRI,
7132                got: self.result_type_iri,
7133            });
7134        }
7135        if !self.converged {
7136            return Err(PipelineFailure::ShapeViolation {
7137                report: ShapeViolation {
7138                    shape_iri: "https://uor.foundation/conformance/InteractionShape",
7139                    constraint_iri:
7140                        "https://uor.foundation/conformance/InteractionShape#convergence",
7141                    property_iri: "https://uor.foundation/conformance/convergencePredicate",
7142                    expected_range: "http://www.w3.org/2002/07/owl#Thing",
7143                    min_count: 1,
7144                    max_count: 1,
7145                    kind: ViolationKind::Missing,
7146                },
7147            });
7148        }
7149        // v0.2.2 T6.1: thread H: Hasher through fold_interaction_step_digest.
7150        let mut hasher = H::initial();
7151        hasher = crate::enforcement::fold_interaction_step_digest(
7152            hasher,
7153            &self.commutator_acc,
7154            self.peer_step_count,
7155            self.seed,
7156            self.result_type_iri,
7157            crate::enforcement::CertificateKind::Grounding,
7158        );
7159        let buffer = hasher.finalize();
7160        let content_fingerprint =
7161            crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
7162        let unit_address = crate::enforcement::unit_address_from_buffer(&buffer);
7163        let grounding = Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
7164            32,
7165            content_fingerprint,
7166        ));
7167        let bindings = empty_bindings_table();
7168        Ok(Grounded::<T>::new_internal(
7169            grounding,
7170            bindings,
7171            32,
7172            unit_address,
7173            content_fingerprint,
7174        ))
7175    }
7176}
7177
7178/// v0.2.2 Phase F / T2.7: parallel driver entry point.
7179/// Consumes a `Validated<ParallelDeclaration, P>` and produces a unified
7180/// `Grounded<T>` whose `unit_address` is derived from the declaration's
7181/// site count via FNV-1a. Two units with different site counts produce
7182/// `Grounded` values with different addresses.
7183/// # Errors
7184/// Returns `PipelineFailure::ShapeMismatch` when the declaration's
7185/// `result_type_iri` does not match `T::IRI` — the caller asked for
7186/// `Grounded<T>` but the declaration was built over a different shape.
7187/// Returns `PipelineFailure::ContradictionDetected` when the declared
7188/// partition cardinality is zero — a parallel composition with no
7189/// sites is inadmissible by construction.
7190/// Success: `run_parallel` folds the declaration's site count through
7191/// `fold_parallel_digest` to produce a content fingerprint; distinct
7192/// partitions produce distinct fingerprints by construction.
7193/// # Example
7194/// ```no_run
7195/// use uor_foundation::enforcement::{ConstrainedTypeInput, Validated};
7196/// use uor_foundation::pipeline::{run_parallel, ParallelDeclaration};
7197/// # use uor_foundation::enforcement::Hasher;
7198/// # struct Fnv1aHasher16;
7199/// # impl Hasher for Fnv1aHasher16 {
7200/// #     const OUTPUT_BYTES: usize = 16;
7201/// #     fn initial() -> Self { Self }
7202/// #     fn fold_byte(self, _: u8) -> Self { self }
7203/// #     fn finalize(self) -> [u8; 32] { [0; 32] }
7204/// # }
7205/// # fn wrap<T>(t: T) -> Validated<T> { unimplemented!() /* see uor_foundation_test_helpers */ }
7206/// // 3-component partition over 9 sites.
7207/// static PARTITION: &[u32] = &[0, 0, 0, 1, 1, 1, 2, 2, 2];
7208/// let decl: Validated<ParallelDeclaration> = wrap(
7209///     ParallelDeclaration::new_with_partition::<ConstrainedTypeInput>(
7210///         PARTITION,
7211///         "https://uor.foundation/parallel/ParallelDisjointnessWitness",
7212///     ),
7213/// );
7214/// let grounded = run_parallel::<ConstrainedTypeInput, _, Fnv1aHasher16>(decl)
7215///     .expect("partition admits");
7216/// # let _ = grounded;
7217/// ```
7218pub fn run_parallel<T, P, H>(
7219    unit: Validated<ParallelDeclaration, P>,
7220) -> Result<Grounded<T>, PipelineFailure>
7221where
7222    T: ConstrainedTypeShape + crate::enforcement::GroundedShape,
7223    P: crate::enforcement::ValidationPhase,
7224    H: crate::enforcement::Hasher,
7225{
7226    let decl = unit.inner();
7227    let site_count = decl.site_count();
7228    let partition = decl.site_partition();
7229    let witness_iri = decl.disjointness_witness();
7230    // Runtime invariants declared in the ParallelDeclaration rustdoc:
7231    // (1) result_type_iri must match T::IRI (target §5 + T6.11);
7232    // (2) site_count > 0 (zero-site parallel composition is vacuous);
7233    // (3) v0.2.2 Phase H3: partition length must equal site_count;
7234    // (4) v0.2.2 Phase H3: partition must be non-empty (only constructor is
7235    //     `new_with_partition`, which takes a real partition slice).
7236    if !crate::enforcement::str_eq(decl.result_type_iri(), T::IRI) {
7237        return Err(PipelineFailure::ShapeMismatch {
7238            expected: T::IRI,
7239            got: decl.result_type_iri(),
7240        });
7241    }
7242    if site_count == 0 || partition.is_empty() {
7243        return Err(PipelineFailure::ContradictionDetected {
7244            at_step: 0,
7245            trace_iri: "https://uor.foundation/parallel/ParallelProduct",
7246        });
7247    }
7248    if partition.len() as u64 != site_count {
7249        return Err(PipelineFailure::ShapeMismatch {
7250            expected: T::IRI,
7251            got: decl.result_type_iri(),
7252        });
7253    }
7254    // v0.2.2 Phase H3: walk partition, count sites per component, fold
7255    // per-component into the content fingerprint. Enumerates unique component
7256    // IDs into a fixed stack buffer sized by WITT_MAX_BITS.
7257    let mut hasher = H::initial();
7258    // component_ids: seen component IDs in first-appearance order.
7259    // component_counts: parallel site-count per component.
7260    let mut component_ids = [0u32; WITT_MAX_BITS as usize];
7261    let mut component_counts = [0u32; WITT_MAX_BITS as usize];
7262    let mut n_components: usize = 0;
7263    let mut si = 0;
7264    while si < partition.len() {
7265        let cid = partition[si];
7266        // Find or insert cid.
7267        let mut ci = 0;
7268        let mut found = false;
7269        while ci < n_components {
7270            if component_ids[ci] == cid {
7271                component_counts[ci] = component_counts[ci].saturating_add(1);
7272                found = true;
7273                break;
7274            }
7275            ci += 1;
7276        }
7277        if !found && n_components < (WITT_MAX_BITS as usize) {
7278            component_ids[n_components] = cid;
7279            component_counts[n_components] = 1;
7280            n_components += 1;
7281        }
7282        si += 1;
7283    }
7284    // Fold each component: (component_id, site_count_within) in first-appearance order.
7285    let mut ci = 0;
7286    while ci < n_components {
7287        hasher = hasher.fold_bytes(&component_ids[ci].to_be_bytes());
7288        hasher = hasher.fold_bytes(&component_counts[ci].to_be_bytes());
7289        ci += 1;
7290    }
7291    // Fold disjointness_witness IRI so forgeries yield distinct content fingerprints.
7292    hasher = hasher.fold_bytes(witness_iri.as_bytes());
7293    hasher = hasher.fold_byte(0);
7294    // Canonical ParallelDeclaration tail: site_count + type shape + cert kind.
7295    hasher = crate::enforcement::fold_parallel_digest(
7296        hasher,
7297        site_count,
7298        T::IRI,
7299        T::SITE_COUNT,
7300        T::CONSTRAINTS,
7301        crate::enforcement::CertificateKind::Grounding,
7302    );
7303    let buffer = hasher.finalize();
7304    let content_fingerprint =
7305        crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
7306    let unit_address = crate::enforcement::unit_address_from_buffer(&buffer);
7307    let grounding = Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
7308        32,
7309        content_fingerprint,
7310    ));
7311    let bindings = empty_bindings_table();
7312    Ok(Grounded::<T>::new_internal(
7313        grounding,
7314        bindings,
7315        32,
7316        unit_address,
7317        content_fingerprint,
7318    ))
7319}
7320
7321/// v0.2.2 Phase F / T2.7: stream driver entry point.
7322/// Consumes a `Validated<StreamDeclaration, P>` and returns a
7323/// `StreamDriver<T, P>` implementing `Iterator`. The driver's productivity
7324/// countdown is initialized from `StreamDeclaration::productivity_bound()`;
7325/// each `next()` call yields a `Grounded` whose `unit_address` differs
7326/// from the previous step's, and the iterator terminates when the
7327/// countdown reaches zero.
7328#[must_use]
7329pub fn run_stream<T, P, H>(unit: Validated<StreamDeclaration, P>) -> StreamDriver<T, P, H>
7330where
7331    T: crate::enforcement::GroundedShape,
7332    P: crate::enforcement::ValidationPhase,
7333    H: crate::enforcement::Hasher,
7334{
7335    let bound = unit.inner().productivity_bound();
7336    let result_type_iri = unit.inner().result_type_iri();
7337    StreamDriver::new_internal(bound, bound, result_type_iri)
7338}
7339
7340/// v0.2.2 Phase F / T2.7: interaction driver entry point.
7341/// Consumes a `Validated<InteractionDeclaration, P>` and returns an
7342/// `InteractionDriver<T, P, H>` state machine seeded from the declaration's
7343/// `convergence_seed()`. Advance with `step(PeerInput)` until
7344/// `is_converged()` returns `true`, then call `finalize()`.
7345#[must_use]
7346pub fn run_interactive<T, P, H>(
7347    unit: Validated<InteractionDeclaration, P>,
7348) -> InteractionDriver<T, P, H>
7349where
7350    T: crate::enforcement::GroundedShape,
7351    P: crate::enforcement::ValidationPhase,
7352    H: crate::enforcement::Hasher,
7353{
7354    InteractionDriver::new_internal(
7355        unit.inner().convergence_seed(),
7356        unit.inner().result_type_iri(),
7357    )
7358}
7359
7360/// v0.2.2 Phase G / T2.8: const-fn companion for
7361/// `LeaseDeclarationBuilder`. Delegates to the builder's
7362/// `validate_const` method, which validates the `LeaseShape` contract
7363/// (`linear_site` and `scope` required) at compile time.
7364/// # Errors
7365/// Returns `ShapeViolation::Missing` if `linear_site` or `scope` is unset.
7366pub const fn validate_lease_const<'a>(
7367    builder: &LeaseDeclarationBuilder<'a>,
7368) -> Result<Validated<LeaseDeclaration, CompileTime>, ShapeViolation> {
7369    builder.validate_const()
7370}
7371
7372/// v0.2.2 Phase G / T2.8 + T6.13: const-fn companion for `CompileUnitBuilder`.
7373///
7374/// Tightened in T6.13 to enforce the same five required fields as the
7375/// runtime `CompileUnitBuilder::validate()` method:
7376///
7377/// - `root_term`
7378/// - `witt_level_ceiling`
7379/// - `thermodynamic_budget`
7380/// - `target_domains` (non-empty)
7381/// - `result_type_iri`
7382///
7383/// Returns `Result<Validated<CompileUnit, CompileTime>, ShapeViolation>` —
7384/// dual-path consistent with the runtime `validate()` method. Const-eval
7385/// call sites match on the `Result`; the panic only fires at codegen /
7386/// const-eval time, never at runtime.
7387///
7388/// # Errors
7389///
7390/// Returns `ShapeViolation::Missing` for the first unset required field.
7391pub const fn validate_compile_unit_const<'a>(
7392    builder: &CompileUnitBuilder<'a>,
7393) -> Result<Validated<CompileUnit<'a>, CompileTime>, ShapeViolation> {
7394    if !builder.has_root_term_const() {
7395        return Err(ShapeViolation {
7396            shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
7397            constraint_iri: "https://uor.foundation/conformance/compileUnit_rootTerm_constraint",
7398            property_iri: "https://uor.foundation/reduction/rootTerm",
7399            expected_range: "https://uor.foundation/schema/Term",
7400            min_count: 1,
7401            max_count: 1,
7402            kind: ViolationKind::Missing,
7403        });
7404    }
7405    let level = match builder.witt_level_option() {
7406        Some(l) => l,
7407        None => {
7408            return Err(ShapeViolation {
7409                shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
7410                constraint_iri:
7411                    "https://uor.foundation/conformance/compileUnit_unitWittLevel_constraint",
7412                property_iri: "https://uor.foundation/reduction/unitWittLevel",
7413                expected_range: "https://uor.foundation/schema/WittLevel",
7414                min_count: 1,
7415                max_count: 1,
7416                kind: ViolationKind::Missing,
7417            })
7418        }
7419    };
7420    let budget =
7421        match builder.budget_option() {
7422            Some(b) => b,
7423            None => return Err(ShapeViolation {
7424                shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
7425                constraint_iri:
7426                    "https://uor.foundation/conformance/compileUnit_thermodynamicBudget_constraint",
7427                property_iri: "https://uor.foundation/reduction/thermodynamicBudget",
7428                expected_range: "http://www.w3.org/2001/XMLSchema#decimal",
7429                min_count: 1,
7430                max_count: 1,
7431                kind: ViolationKind::Missing,
7432            }),
7433        };
7434    if !builder.has_target_domains_const() {
7435        return Err(ShapeViolation {
7436            shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
7437            constraint_iri:
7438                "https://uor.foundation/conformance/compileUnit_targetDomains_constraint",
7439            property_iri: "https://uor.foundation/reduction/targetDomains",
7440            expected_range: "https://uor.foundation/op/VerificationDomain",
7441            min_count: 1,
7442            max_count: 0,
7443            kind: ViolationKind::Missing,
7444        });
7445    }
7446    let result_type_iri = match builder.result_type_iri_const() {
7447        Some(iri) => iri,
7448        None => {
7449            return Err(ShapeViolation {
7450                shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
7451                constraint_iri:
7452                    "https://uor.foundation/conformance/compileUnit_resultType_constraint",
7453                property_iri: "https://uor.foundation/reduction/resultType",
7454                expected_range: "https://uor.foundation/type/ConstrainedType",
7455                min_count: 1,
7456                max_count: 1,
7457                kind: ViolationKind::Missing,
7458            })
7459        }
7460    };
7461    Ok(Validated::new(CompileUnit::from_parts_const(
7462        level,
7463        budget,
7464        result_type_iri,
7465        builder.root_term_slice_const(),
7466        builder.bindings_slice_const(),
7467        builder.target_domains_slice_const(),
7468    )))
7469}
7470
7471/// v0.2.2 Phase G / T2.8 + T6.11: const-fn companion for
7472/// `ParallelDeclarationBuilder`. Takes a `ConstrainedTypeShape` type parameter
7473/// to set the `result_type_iri` on the produced declaration.
7474/// v0.2.2 Phase A: the produced `ParallelDeclaration<'a>` carries the
7475/// builder's raw site-partition slice and disjointness-witness IRI; the
7476/// lifetime `'a` is the builder's borrow lifetime.
7477#[must_use]
7478pub const fn validate_parallel_const<'a, T: ConstrainedTypeShape>(
7479    builder: &ParallelDeclarationBuilder<'a>,
7480) -> Validated<ParallelDeclaration<'a>, CompileTime> {
7481    Validated::new(ParallelDeclaration::new_with_partition::<T>(
7482        builder.site_partition_slice_const(),
7483        builder.disjointness_witness_const(),
7484    ))
7485}
7486
7487/// v0.2.2 Phase G / T2.8 + T6.11: const-fn companion for
7488/// `StreamDeclarationBuilder`. Takes a `ConstrainedTypeShape` type parameter
7489/// to set the `result_type_iri` on the produced declaration.
7490/// v0.2.2 Phase A: the produced `StreamDeclaration<'a>` retains the
7491/// builder's seed/step term slices and productivity-witness IRI.
7492#[must_use]
7493pub const fn validate_stream_const<'a, T: ConstrainedTypeShape>(
7494    builder: &StreamDeclarationBuilder<'a>,
7495) -> Validated<StreamDeclaration<'a>, CompileTime> {
7496    let bound = builder.productivity_bound_const();
7497    Validated::new(StreamDeclaration::new_full::<T>(
7498        bound,
7499        builder.seed_slice_const(),
7500        builder.step_slice_const(),
7501        builder.productivity_witness_const(),
7502    ))
7503}
7504
7505/// v0.2.2 T5 C6: const-fn resolver companion for
7506/// `tower_completeness::certify`. Threads the consumer-supplied substrate
7507/// `Hasher` through the canonical CompileUnit byte layout to compute a
7508/// parametric content fingerprint, distinguishing two units that share a
7509/// witt level but differ in budget, IRI, site count, or constraints.
7510#[must_use]
7511pub fn certify_tower_completeness_const<T, H>(
7512    unit: &Validated<CompileUnit, CompileTime>,
7513) -> Validated<GroundingCertificate, CompileTime>
7514where
7515    T: ConstrainedTypeShape,
7516    H: crate::enforcement::Hasher,
7517{
7518    let level_bits = unit.inner().witt_level().witt_length() as u16;
7519    let budget = unit.inner().thermodynamic_budget();
7520    let mut hasher = H::initial();
7521    hasher = crate::enforcement::fold_unit_digest(
7522        hasher,
7523        level_bits,
7524        budget,
7525        T::IRI,
7526        T::SITE_COUNT,
7527        T::CONSTRAINTS,
7528        crate::enforcement::CertificateKind::TowerCompleteness,
7529    );
7530    let buffer = hasher.finalize();
7531    let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
7532    Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
7533        level_bits, fp,
7534    ))
7535}
7536
7537/// v0.2.2 T5 C6: const-fn resolver companion for
7538/// `incremental_completeness::certify`. Threads `H: Hasher` for the
7539/// parametric fingerprint; uses `CertificateKind::IncrementalCompleteness`
7540/// as the trailing discriminant byte.
7541#[must_use]
7542pub fn certify_incremental_completeness_const<T, H>(
7543    unit: &Validated<CompileUnit, CompileTime>,
7544) -> Validated<GroundingCertificate, CompileTime>
7545where
7546    T: ConstrainedTypeShape,
7547    H: crate::enforcement::Hasher,
7548{
7549    let level_bits = unit.inner().witt_level().witt_length() as u16;
7550    let budget = unit.inner().thermodynamic_budget();
7551    let mut hasher = H::initial();
7552    hasher = crate::enforcement::fold_unit_digest(
7553        hasher,
7554        level_bits,
7555        budget,
7556        T::IRI,
7557        T::SITE_COUNT,
7558        T::CONSTRAINTS,
7559        crate::enforcement::CertificateKind::IncrementalCompleteness,
7560    );
7561    let buffer = hasher.finalize();
7562    let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
7563    Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
7564        level_bits, fp,
7565    ))
7566}
7567
7568/// v0.2.2 T5 C6: const-fn resolver companion for `inhabitance::certify`.
7569/// Threads `H: Hasher` for the parametric fingerprint; uses
7570/// `CertificateKind::Inhabitance` as the trailing discriminant byte.
7571#[must_use]
7572pub fn certify_inhabitance_const<T, H>(
7573    unit: &Validated<CompileUnit, CompileTime>,
7574) -> Validated<GroundingCertificate, CompileTime>
7575where
7576    T: ConstrainedTypeShape,
7577    H: crate::enforcement::Hasher,
7578{
7579    let level_bits = unit.inner().witt_level().witt_length() as u16;
7580    let budget = unit.inner().thermodynamic_budget();
7581    let mut hasher = H::initial();
7582    hasher = crate::enforcement::fold_unit_digest(
7583        hasher,
7584        level_bits,
7585        budget,
7586        T::IRI,
7587        T::SITE_COUNT,
7588        T::CONSTRAINTS,
7589        crate::enforcement::CertificateKind::Inhabitance,
7590    );
7591    let buffer = hasher.finalize();
7592    let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
7593    Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
7594        level_bits, fp,
7595    ))
7596}
7597
7598/// v0.2.2 T5 C6: const-fn resolver companion for
7599/// `multiplication::certify`. Threads `H: Hasher` for the parametric
7600/// fingerprint; uses `CertificateKind::Multiplication` as the trailing
7601/// discriminant byte.
7602#[must_use]
7603pub fn certify_multiplication_const<T, H>(
7604    unit: &Validated<CompileUnit, CompileTime>,
7605) -> Validated<MultiplicationCertificate, CompileTime>
7606where
7607    T: ConstrainedTypeShape,
7608    H: crate::enforcement::Hasher,
7609{
7610    let level_bits = unit.inner().witt_level().witt_length() as u16;
7611    let budget = unit.inner().thermodynamic_budget();
7612    let mut hasher = H::initial();
7613    hasher = crate::enforcement::fold_unit_digest(
7614        hasher,
7615        level_bits,
7616        budget,
7617        T::IRI,
7618        T::SITE_COUNT,
7619        T::CONSTRAINTS,
7620        crate::enforcement::CertificateKind::Multiplication,
7621    );
7622    let buffer = hasher.finalize();
7623    let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
7624    Validated::new(MultiplicationCertificate::with_level_and_fingerprint_const(
7625        level_bits, fp,
7626    ))
7627}
7628
7629/// Phase C.4: const-fn resolver companion for `grounding_aware::certify`.
7630/// Threads `H: Hasher` for the parametric fingerprint; uses
7631/// `CertificateKind::Grounding` as the trailing discriminant byte.
7632#[must_use]
7633pub fn certify_grounding_aware_const<T, H>(
7634    unit: &Validated<CompileUnit, CompileTime>,
7635) -> Validated<GroundingCertificate, CompileTime>
7636where
7637    T: ConstrainedTypeShape,
7638    H: crate::enforcement::Hasher,
7639{
7640    let level_bits = unit.inner().witt_level().witt_length() as u16;
7641    let budget = unit.inner().thermodynamic_budget();
7642    let mut hasher = H::initial();
7643    hasher = crate::enforcement::fold_unit_digest(
7644        hasher,
7645        level_bits,
7646        budget,
7647        T::IRI,
7648        T::SITE_COUNT,
7649        T::CONSTRAINTS,
7650        crate::enforcement::CertificateKind::Grounding,
7651    );
7652    let buffer = hasher.finalize();
7653    let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
7654    Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
7655        level_bits, fp,
7656    ))
7657}
7658
7659/// v0.2.2 T5 C6: typed pipeline entry point producing `Grounded<T>` from
7660/// a validated `CompileUnit`. Threads the consumer-supplied substrate
7661/// `Hasher` through `fold_unit_digest` to compute a parametric content
7662/// fingerprint over the unit's full state: `(level_bits, budget, T::IRI,
7663/// T::SITE_COUNT, T::CONSTRAINTS, CertificateKind::Grounding)`.
7664/// Two units differing on **any** of those fields produce `Grounded`
7665/// values with distinct fingerprints (and distinct `unit_address` handles,
7666/// derived from the leading 16 bytes of the fingerprint).
7667/// # Errors
7668/// Returns `PipelineFailure::ShapeMismatch` when the unit's declared
7669/// `result_type_iri` does not match `T::IRI`, or propagates any
7670/// failure from the reduction stage executor.
7671pub fn run_const<T, M, H>(
7672    unit: &Validated<CompileUnit, CompileTime>,
7673) -> Result<Grounded<T>, PipelineFailure>
7674where
7675    T: ConstrainedTypeShape + crate::enforcement::GroundedShape,
7676    // Phase C.2 (target §6): const-eval admits only those grounding-map kinds
7677    // that are both Total (defined for all inputs) and Invertible (one-to-one).
7678    // The bound is enforced at the type level via the existing marker tower.
7679    M: crate::enforcement::GroundingMapKind
7680        + crate::enforcement::Total
7681        + crate::enforcement::Invertible,
7682    H: crate::enforcement::Hasher,
7683{
7684    // The marker bound on M is purely type-level — no runtime use.
7685    let _phantom_map: core::marker::PhantomData<M> = core::marker::PhantomData;
7686    let level_bits = unit.inner().witt_level().witt_length() as u16;
7687    let budget = unit.inner().thermodynamic_budget();
7688    // v0.2.2 T6.11: ShapeMismatch detection. The unit declares its
7689    // result_type_iri at validation time; the caller's `T::IRI` must match.
7690    let unit_iri = unit.inner().result_type_iri();
7691    if !crate::enforcement::str_eq(unit_iri, T::IRI) {
7692        return Err(PipelineFailure::ShapeMismatch {
7693            expected: T::IRI,
7694            got: unit_iri,
7695        });
7696    }
7697    // Walk the foundation-locked byte layout via the consumer's hasher.
7698    let mut hasher = H::initial();
7699    hasher = crate::enforcement::fold_unit_digest(
7700        hasher,
7701        level_bits,
7702        budget,
7703        T::IRI,
7704        T::SITE_COUNT,
7705        T::CONSTRAINTS,
7706        crate::enforcement::CertificateKind::Grounding,
7707    );
7708    let buffer = hasher.finalize();
7709    let content_fingerprint =
7710        crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
7711    let unit_address = crate::enforcement::unit_address_from_buffer(&buffer);
7712    let grounding = Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
7713        level_bits,
7714        content_fingerprint,
7715    ));
7716    let bindings = empty_bindings_table();
7717    Ok(Grounded::<T>::new_internal(
7718        grounding,
7719        bindings,
7720        level_bits,
7721        unit_address,
7722        content_fingerprint,
7723    ))
7724}