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