Skip to main content

uor_foundation/
pipeline.rs

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