Skip to main content

uor_foundation/
pipeline.rs

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