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