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