uor_foundation/pipeline.rs
1// @generated by uor-crate from uor-ontology — do not edit manually
2
3//! Reduction Pipeline — no_std in-process driver.
4//!
5//! Backs `Certify::certify` on every resolver façade and (re-exported
6//! via the macros crate) the `uor_ground!` macro's compile-time pipeline.
7//!
8//! The driver implements the full reduction pipeline per
9//! `external/ergonomics-spec.md` §3.3 and §4: 6 preflight checks,
10//! 7 reduction stages, 4 resolver backends, real 2-SAT and Horn-SAT
11//! deciders, fragment classifier, content-addressed unit-ids.
12//!
13//! Every entry point is ontology-driven: IRIs, stage order, and
14//! dispatch-table rules are baked in at codegen time from the
15//! ontology graph. Adding a new preflight check or resolver is a
16//! pure ontology edit.
17
18use crate::enforcement::{
19 BindingEntry, BindingsTable, CompileTime, CompileUnit, CompileUnitBuilder,
20 CompletenessCertificate, ConstrainedTypeInput, GenericImpossibilityWitness, Grounded,
21 GroundingCertificate, InhabitanceCertificate, InhabitanceImpossibilityWitness,
22 LeaseDeclaration, LeaseDeclarationBuilder, LiftChainCertificate, MultiplicationCertificate,
23 ParallelDeclarationBuilder, PipelineFailure, ShapeViolation, StreamDeclarationBuilder, Term,
24 Validated,
25};
26use crate::ViolationKind;
27use crate::WittLevel;
28
29/// Zero-based preflight check order read from `reduction:PreflightCheck`
30/// individuals at codegen time. `BudgetSolvencyCheck` MUST be index 0 per
31/// `reduction:preflightOrder` — enforced by the ontology, not here.
32pub const PREFLIGHT_CHECK_IRIS: &[&str] = &[
33 "https://uor.foundation/reduction/BudgetSolvencyCheck",
34 "https://uor.foundation/reduction/FeasibilityCheck",
35 "https://uor.foundation/reduction/DispatchCoverageCheck",
36 "https://uor.foundation/reduction/PackageCoherenceCheck",
37 "https://uor.foundation/reduction/PreflightTiming",
38 "https://uor.foundation/reduction/RuntimeTiming",
39];
40
41/// Seven reduction stages in declared order, sourced from
42/// `reduction:ReductionStep` individuals.
43pub const REDUCTION_STAGE_IRIS: &[&str] = &[
44 "https://uor.foundation/reduction/stage_initialization",
45 "https://uor.foundation/reduction/stage_declare",
46 "https://uor.foundation/reduction/stage_factorize",
47 "https://uor.foundation/reduction/stage_resolve",
48 "https://uor.foundation/reduction/stage_attest",
49 "https://uor.foundation/reduction/stage_extract",
50 "https://uor.foundation/reduction/stage_convergence",
51];
52
53/// Phase 17: maximum number of `i64` coefficients an `Affine`
54/// constraint can carry. Stable-Rust const evaluation cannot allocate a
55/// new `&'static [i64]` at compile time, so `Affine` stores a fixed-
56/// size buffer + an active prefix length. Aligned with the foundation's
57/// 8-wide capacity caps (`MAX_BETTI_DIMENSION` / `JACOBIAN_MAX_SITES` /
58/// `NERVE_CONSTRAINTS_CAP`).
59/// Wiki ADR-037: the canonical source of truth for this cap is
60/// [`HostBounds::AFFINE_COEFFS_MAX`]; this `pub const` is a foundation-
61/// internal foundation-fixed conservative default for
62/// stable-Rust array-size positions. Applications declaring a custom
63/// `HostBounds` impl read `<MyBounds as HostBounds>::AFFINE_COEFFS_MAX`
64/// at instantiation sites instead.
65pub const AFFINE_MAX_COEFFS: usize = 8;
66
67/// Phase 17: maximum number of `LeafConstraintRef` conjuncts a
68/// `Conjunction` can carry. Same reasoning as `AFFINE_MAX_COEFFS`.
69/// Wiki ADR-037: a foundation-fixed conservative default for
70/// [`HostBounds::CONJUNCTION_TERMS_MAX`].
71pub const CONJUNCTION_MAX_TERMS: usize = 8;
72
73/// Opaque constraint reference carried by `ConstrainedTypeShape` impls.
74/// Variants mirror the v0.2.1 `type:Constraint` enumerated subclasses
75/// (retained as ergonomic aliases for the SAT pipeline) plus the v0.2.2
76/// Phase D parametric form (`Bound` / `Conjunction`) which references
77/// `BoundConstraint` kinds by their (observable, shape) IRIs. The
78/// `SatClauses` variant carries a compact 2-SAT/Horn-SAT clause list.
79/// **Phase 17 — fixed-array Affine and Conjunction.** The pre-Phase-17
80/// `Affine { coefficients: &'static [i64], … }` and
81/// `Conjunction { conjuncts: &'static [ConstraintRef] }` have been
82/// replaced with fixed-capacity arrays of length `AFFINE_MAX_COEFFS`
83/// and `CONJUNCTION_MAX_TERMS` respectively. Stable Rust const
84/// evaluation can build these inline; the SDK macros now support
85/// Affine-bearing operands without falling back to the
86/// `Site { position: u32::MAX }` sentinel. `Conjunction` is depth-
87/// limited to one level: its conjuncts are `LeafConstraintRef`
88/// (every variant of `ConstraintRef` except `Conjunction` itself).
89#[derive(Debug, Clone, Copy)]
90#[non_exhaustive]
91#[allow(clippy::large_enum_variant)]
92pub enum ConstraintRef {
93 /// `type:ResidueConstraint`: value ≡ residue (mod modulus).
94 Residue { modulus: u64, residue: u64 },
95 /// `type:HammingConstraint`: bit-weight bound.
96 Hamming { bound: u32 },
97 /// `type:DepthConstraint`: site-depth bound.
98 Depth { min: u32, max: u32 },
99 /// `type:CarryConstraint`: carry-bit relation.
100 Carry { site: u32 },
101 /// `type:SiteConstraint`: site-position restriction.
102 Site { position: u32 },
103 /// `type:AffineConstraint`: affine relation over sites.
104 /// `coefficients[..coefficient_count as usize]` is the active prefix;
105 /// trailing entries are unused and must be zero.
106 Affine {
107 coefficients: [i64; AFFINE_MAX_COEFFS],
108 coefficient_count: u32,
109 bias: i64,
110 },
111 /// Opaque clause list for 2-SAT / Horn-SAT inputs.
112 /// Each clause is a slice of `(variable_index, is_negated)`.
113 SatClauses {
114 clauses: &'static [&'static [(u32, bool)]],
115 num_vars: u32,
116 },
117 /// v0.2.2 Phase D / T2.2 (cleanup): parametric `BoundConstraint`
118 /// kind reference. Selects an (observable, shape) pair from the
119 /// closed Phase D catalogue. The args_repr string carries the
120 /// kind-specific parameters in canonical form.
121 Bound {
122 observable_iri: &'static str,
123 bound_shape_iri: &'static str,
124 args_repr: &'static str,
125 },
126 /// v0.2.2 Phase D / T2.2 (cleanup): parametric `Conjunction`.
127 /// Phase 17: depth-limited to one level — conjuncts are
128 /// `LeafConstraintRef` (every `ConstraintRef` variant except
129 /// `Conjunction` itself). Active prefix is
130 /// `conjuncts[..conjunct_count as usize]`.
131 Conjunction {
132 conjuncts: [LeafConstraintRef; CONJUNCTION_MAX_TERMS],
133 conjunct_count: u32,
134 },
135 /// ADR-057 substrate amendment: bounded recursive shape reference.
136 /// The constraint references another `ConstrainedTypeShape` by its
137 /// content-addressed IRI, with an explicit `descent_bound` bounding
138 /// the recursion at evaluation time. The runtime ψ_1 NerveResolver
139 /// expands the reference to the registered shape's `CONSTRAINTS`
140 /// array, decrementing `descent_bound` on each `Recurse` traversal;
141 /// when `descent_bound = 0` the recursion bottoms out (no further
142 /// constraints at that depth).
143 ///
144 /// The const-time admission path defers `Recurse` to runtime per
145 /// ADR-057's deferral rule (parallel to ADR-049's
146 /// `LandauerCost` deferral).
147 Recurse {
148 shape_iri: &'static str,
149 descent_bound: u32,
150 },
151}
152
153/// `ConstraintRef` minus the recursive `Conjunction` variant — the
154/// element type of `ConstraintRef::Conjunction.conjuncts`. Phase 17
155/// caps Conjunction depth at one level; deeper structure must be
156/// flattened by the constructor.
157#[derive(Debug, Clone, Copy)]
158#[non_exhaustive]
159pub enum LeafConstraintRef {
160 /// See [`ConstraintRef::Residue`].
161 Residue { modulus: u64, residue: u64 },
162 /// See [`ConstraintRef::Hamming`].
163 Hamming { bound: u32 },
164 /// See [`ConstraintRef::Depth`].
165 Depth { min: u32, max: u32 },
166 /// See [`ConstraintRef::Carry`].
167 Carry { site: u32 },
168 /// See [`ConstraintRef::Site`].
169 Site { position: u32 },
170 /// See [`ConstraintRef::Affine`].
171 Affine {
172 coefficients: [i64; AFFINE_MAX_COEFFS],
173 coefficient_count: u32,
174 bias: i64,
175 },
176 /// See [`ConstraintRef::SatClauses`].
177 SatClauses {
178 clauses: &'static [&'static [(u32, bool)]],
179 num_vars: u32,
180 },
181 /// See [`ConstraintRef::Bound`].
182 Bound {
183 observable_iri: &'static str,
184 bound_shape_iri: &'static str,
185 args_repr: &'static str,
186 },
187 /// See [`ConstraintRef::Recurse`] (ADR-057).
188 Recurse {
189 shape_iri: &'static str,
190 descent_bound: u32,
191 },
192}
193
194/// Project a non-`Conjunction` [`ConstraintRef`] into the
195/// [`LeafConstraintRef`] subtype. Returns a `Site { position: 0 }`
196/// placeholder if `self` is `Conjunction` (the only non-injective
197/// case); callers should flatten Conjunction structure before
198/// calling.
199impl ConstraintRef {
200 /// Phase 17 — leaf projection.
201 #[inline]
202 #[must_use]
203 pub const fn as_leaf(self) -> LeafConstraintRef {
204 match self {
205 ConstraintRef::Residue { modulus, residue } => {
206 LeafConstraintRef::Residue { modulus, residue }
207 }
208 ConstraintRef::Hamming { bound } => LeafConstraintRef::Hamming { bound },
209 ConstraintRef::Depth { min, max } => LeafConstraintRef::Depth { min, max },
210 ConstraintRef::Carry { site } => LeafConstraintRef::Carry { site },
211 ConstraintRef::Site { position } => LeafConstraintRef::Site { position },
212 ConstraintRef::Affine {
213 coefficients,
214 coefficient_count,
215 bias,
216 } => LeafConstraintRef::Affine {
217 coefficients,
218 coefficient_count,
219 bias,
220 },
221 ConstraintRef::SatClauses { clauses, num_vars } => {
222 LeafConstraintRef::SatClauses { clauses, num_vars }
223 }
224 ConstraintRef::Bound {
225 observable_iri,
226 bound_shape_iri,
227 args_repr,
228 } => LeafConstraintRef::Bound {
229 observable_iri,
230 bound_shape_iri,
231 args_repr,
232 },
233 ConstraintRef::Conjunction { .. } => LeafConstraintRef::Site { position: 0 },
234 ConstraintRef::Recurse {
235 shape_iri,
236 descent_bound,
237 } => LeafConstraintRef::Recurse {
238 shape_iri,
239 descent_bound,
240 },
241 }
242 }
243}
244
245impl LeafConstraintRef {
246 /// Phase 17 — embed a leaf into the parent `ConstraintRef` enum.
247 #[inline]
248 #[must_use]
249 pub const fn into_constraint(self) -> ConstraintRef {
250 match self {
251 LeafConstraintRef::Residue { modulus, residue } => {
252 ConstraintRef::Residue { modulus, residue }
253 }
254 LeafConstraintRef::Hamming { bound } => ConstraintRef::Hamming { bound },
255 LeafConstraintRef::Depth { min, max } => ConstraintRef::Depth { min, max },
256 LeafConstraintRef::Carry { site } => ConstraintRef::Carry { site },
257 LeafConstraintRef::Site { position } => ConstraintRef::Site { position },
258 LeafConstraintRef::Affine {
259 coefficients,
260 coefficient_count,
261 bias,
262 } => ConstraintRef::Affine {
263 coefficients,
264 coefficient_count,
265 bias,
266 },
267 LeafConstraintRef::SatClauses { clauses, num_vars } => {
268 ConstraintRef::SatClauses { clauses, num_vars }
269 }
270 LeafConstraintRef::Bound {
271 observable_iri,
272 bound_shape_iri,
273 args_repr,
274 } => ConstraintRef::Bound {
275 observable_iri,
276 bound_shape_iri,
277 args_repr,
278 },
279 LeafConstraintRef::Recurse {
280 shape_iri,
281 descent_bound,
282 } => ConstraintRef::Recurse {
283 shape_iri,
284 descent_bound,
285 },
286 }
287 }
288}
289
290/// Workstream E (target §1.5 + §4.7, v0.2.2 closure): crate-internal
291/// dispatch helper that maps every `ConstraintRef` variant to its
292/// canonical CNF clause encoding. No variant returns `None` — the
293/// closed six-kind set is fully executable.
294/// - `SatClauses`: pass-through of the caller's CNF.
295/// - `Residue` / `Carry` / `Depth` / `Hamming` / `Site`: direct-
296/// decidable at preflight; encoder emits an empty clause list
297/// (trivially SAT — unsatisfiable ones are rejected earlier).
298/// - `Affine`: single-row consistency check over Z/(2^n)Z; emits
299/// empty clauses when consistent, a 2-literal contradiction
300/// sentinel (forcing 2-SAT rejection) when not.
301/// - `Bound`: parametric form; emits empty clauses (per-bound-kind
302/// decision procedures consume the observable/bound-shape IRIs).
303/// - `Conjunction`: satisfiable iff every conjunct is satisfiable.
304#[inline]
305#[must_use]
306#[allow(dead_code)]
307pub(crate) const fn encode_constraint_to_clauses(
308 constraint: &ConstraintRef,
309) -> Option<&'static [&'static [(u32, bool)]]> {
310 const EMPTY: &[&[(u32, bool)]] = &[];
311 const UNSAT_SENTINEL: &[&[(u32, bool)]] = &[&[(0u32, false)], &[(0u32, true)]];
312 match constraint {
313 ConstraintRef::SatClauses { clauses, .. } => Some(clauses),
314 ConstraintRef::Residue { .. }
315 | ConstraintRef::Carry { .. }
316 | ConstraintRef::Depth { .. }
317 | ConstraintRef::Hamming { .. }
318 | ConstraintRef::Site { .. } => Some(EMPTY),
319 ConstraintRef::Affine {
320 coefficients,
321 coefficient_count,
322 bias,
323 } => {
324 if is_affine_consistent(coefficients, *coefficient_count, *bias) {
325 Some(EMPTY)
326 } else {
327 Some(UNSAT_SENTINEL)
328 }
329 }
330 ConstraintRef::Bound { .. } => Some(EMPTY),
331 ConstraintRef::Conjunction {
332 conjuncts,
333 conjunct_count,
334 } => {
335 if conjunction_all_sat(conjuncts, *conjunct_count) {
336 Some(EMPTY)
337 } else {
338 Some(UNSAT_SENTINEL)
339 }
340 }
341 // ADR-057: Recurse defers to the runtime ψ_1 NerveResolver
342 // which expands the referenced shape's constraints via the
343 // shape-IRI registry. At preflight (const-eval) it carries no
344 // CNF residue — the recursion is bounded, the unrolling occurs
345 // at runtime against the registry.
346 ConstraintRef::Recurse { .. } => Some(EMPTY),
347 }
348}
349
350/// Workstream E: single-row consistency for `Affine { coefficients,
351/// coefficient_count, bias }`. The constraint is
352/// `sum(c_i) · x = bias (mod 2^n)`; when the coefficient sum is
353/// zero the system is consistent iff bias is zero. Non-zero sums
354/// are always consistent over Z/(2^n)Z for some `x` value. Iterates
355/// only the active prefix `coefficients[..coefficient_count as usize]`.
356#[inline]
357#[must_use]
358const fn is_affine_consistent(
359 coefficients: &[i64; AFFINE_MAX_COEFFS],
360 coefficient_count: u32,
361 bias: i64,
362) -> bool {
363 let mut sum: i128 = 0;
364 let count = coefficient_count as usize;
365 let mut i = 0;
366 while i < count && i < AFFINE_MAX_COEFFS {
367 sum += coefficients[i] as i128;
368 i += 1;
369 }
370 if sum == 0 {
371 bias == 0
372 } else {
373 true
374 }
375}
376
377/// Workstream E + Phase 17: satisfiability of a `Conjunction`.
378/// Iterates only the active prefix `conjuncts[..conjunct_count as
379/// usize]` and lifts each `LeafConstraintRef` back to a
380/// `ConstraintRef` for re-encoding (the leaf form omits Conjunction,
381/// so this terminates at depth 1).
382#[inline]
383#[must_use]
384const fn conjunction_all_sat(
385 conjuncts: &[LeafConstraintRef; CONJUNCTION_MAX_TERMS],
386 conjunct_count: u32,
387) -> bool {
388 let count = conjunct_count as usize;
389 let mut i = 0;
390 while i < count && i < CONJUNCTION_MAX_TERMS {
391 let lifted = conjuncts[i].into_constraint();
392 match encode_constraint_to_clauses(&lifted) {
393 Some([]) => {}
394 _ => return false,
395 }
396 i += 1;
397 }
398 true
399}
400
401/// Declarative shape of a constrained type that can be admitted into the
402/// reduction pipeline.
403/// Downstream authors implement this trait on zero-sized marker types to
404/// declare the `(IRI, SITE_COUNT, CONSTRAINTS)` triple of a custom
405/// constrained type. The foundation admits shapes into the pipeline via
406/// [`validate_constrained_type`] / [`validate_constrained_type_const`],
407/// which run the full preflight (`preflight_feasibility` +
408/// `preflight_package_coherence`) against `Self::CONSTRAINTS` before
409/// returning a [`Validated`] wrapper.
410/// Sealing of witness construction lives on [`Validated`] and [`Grounded`]
411/// — only foundation admission functions mint either. Downstream is free
412/// to implement `ConstrainedTypeShape` for arbitrary shape markers, but
413/// cannot fabricate a `Validated<Self>` except through the admission path.
414/// The `ConstraintRef` enum is `#[non_exhaustive]` from outside the crate,
415/// so `CONSTRAINTS` can only cite foundation-closed constraint kinds.
416/// # Example
417/// ```
418/// use uor_foundation::pipeline::{
419/// ConstrainedTypeShape, ConstraintRef, validate_constrained_type,
420/// };
421/// pub struct MyShape;
422/// impl ConstrainedTypeShape for MyShape {
423/// const IRI: &'static str = "https://example.org/MyShape";
424/// const SITE_COUNT: usize = 4;
425/// const CONSTRAINTS: &'static [ConstraintRef] = &[
426/// ConstraintRef::Residue { modulus: 7, residue: 3 },
427/// ];
428/// const CYCLE_SIZE: u64 = 7; // ADR-032: 7 residues mod 7
429/// }
430/// let validated = validate_constrained_type(MyShape)
431/// .expect("residue 3 mod 7 is admissible");
432/// # let _ = validated;
433/// ```
434pub trait ConstrainedTypeShape {
435 /// IRI of the ontology `type:ConstrainedType` instance this shape represents.
436 const IRI: &'static str;
437 /// Number of sites (fields) this constrained type carries.
438 const SITE_COUNT: usize;
439 /// Ontology-level `siteBudget`: count of data sites only,
440 /// excluding bookkeeping introduced by composition (coproduct tag
441 /// sites, etc.). Equals `SITE_COUNT` for leaf shapes and for
442 /// shapes whose composition introduces no bookkeeping (products,
443 /// cartesian products). Strictly less than `SITE_COUNT` for coproduct
444 /// shapes and any shape whose `SITE_COUNT` includes inherited
445 /// bookkeeping. Introduced by the Product/Coproduct Completion
446 /// Amendment §4a; defaults to `SITE_COUNT` so pre-amendment
447 /// shape impls remain valid without edits.
448 const SITE_BUDGET: usize = Self::SITE_COUNT;
449 /// Per-site constraint list. Empty means unconstrained.
450 const CONSTRAINTS: &'static [ConstraintRef];
451 /// ADR-032: cardinality of the shape's value-set (the cycle
452 /// structure of the shape under the substrate's discrete-clock
453 /// model). Used by the `prism_model!` macro to lower `first_admit`
454 /// (closure-body grammar G16) to the correct descent measure.
455 /// Conventions:
456 /// - Witt-level shapes: `1u64 << WITT_LEVEL_BITS` (W8 = 256, W16 =
457 /// 65536, W32 = 4294967296). Levels above W63 saturate to `u64::MAX`.
458 /// - `partition_product` factors: `cycle_size_product` of factor
459 /// CYCLE_SIZEs (saturating-multiply).
460 /// - `partition_coproduct` summands: `cycle_size_coproduct` (saturating
461 /// add + 1 for the discriminant).
462 /// - `cartesian_product_shape` (homogeneous power): factor's CYCLE_SIZE
463 /// raised to `SITE_COUNT` (saturating).
464 /// - The foundation-sanctioned identity `ConstrainedTypeInput` has
465 /// `CYCLE_SIZE = 1` (single-element shape).
466 const CYCLE_SIZE: u64;
467}
468
469/// ADR-032: saturating multiply for `partition_product`'s CYCLE_SIZE.
470/// Returns `u64::MAX` on overflow.
471#[inline]
472#[must_use]
473pub const fn cycle_size_product(a: u64, b: u64) -> u64 {
474 a.saturating_mul(b)
475}
476
477/// ADR-032: saturating add + 1 (for the discriminant) for
478/// `partition_coproduct`'s CYCLE_SIZE. Returns `u64::MAX` on overflow.
479#[inline]
480#[must_use]
481pub const fn cycle_size_coproduct(a: u64, b: u64) -> u64 {
482 a.saturating_add(b).saturating_add(1)
483}
484
485/// ADR-032: saturating power for `cartesian_product_shape`'s CYCLE_SIZE
486/// (homogeneous power: factor.CYCLE_SIZE^SITE_COUNT). Returns `u64::MAX`
487/// on overflow.
488#[inline]
489#[must_use]
490pub const fn cycle_size_power(base: u64, exp: usize) -> u64 {
491 let mut result: u64 = 1;
492 let mut i: usize = 0;
493 while i < exp {
494 result = result.saturating_mul(base);
495 i += 1;
496 }
497 result
498}
499
500impl ConstrainedTypeShape for ConstrainedTypeInput {
501 const IRI: &'static str = "https://uor.foundation/type/ConstrainedType";
502 const SITE_COUNT: usize = 0;
503 const CONSTRAINTS: &'static [ConstraintRef] = &[];
504 const CYCLE_SIZE: u64 = 1;
505}
506
507/// ADR-033 G20: factor-field directory carried by every
508/// `partition_product`-shaped type. The closure-body grammar's
509/// field-access form (`<expr>.<index>` or `<expr>.<field_name>`)
510/// lowers via this trait at proc-macro expansion time.
511/// Foundation-sanctioned identity `ConstrainedTypeInput` has zero
512/// fields (it is a leaf shape). The SDK macros `partition_product!`,
513/// `product_shape!`, and `cartesian_product_shape!` emit the impl.
514pub trait PartitionProductFields: ConstrainedTypeShape {
515 /// Per-factor `(byte_offset, byte_length)` pairs in declaration
516 /// order. Length is the same as `FIELD_NAMES.len()`.
517 const FIELDS: &'static [(u32, u32)];
518 /// Per-factor names. Empty string `""` for positional-only
519 /// `partition_product!(Name, A, B)` emissions; non-empty for
520 /// named-field `partition_product!(Name, lhs: A, rhs: B)` form.
521 /// Length matches `FIELDS.len()`.
522 const FIELD_NAMES: &'static [&'static str];
523 /// Linear search returning the field index whose `FIELD_NAMES`
524 /// entry equals `name`, or `usize::MAX` if not found. Delegates
525 /// to the free `const fn` [`field_index_by_name_in`] so the
526 /// result is usable inside const-eval contexts on stable Rust
527 /// 1.83 (where const trait methods are unavailable).
528 #[must_use]
529 fn field_index_by_name(name: &str) -> usize {
530 field_index_by_name_in(Self::FIELD_NAMES, name)
531 }
532}
533
534/// ADR-033 G3/G4: const-fn factor-name lookup. The SDK proc-macro
535/// emits const-eval calls to this helper to resolve a named-field
536/// access against the source type's `FIELD_NAMES`. On stable Rust
537/// 1.83 a free `const fn` is the substitute for a `const fn`
538/// trait method. Returns `usize::MAX` for not-found so the result
539/// is usable directly inside `const` array indexing.
540#[must_use]
541pub const fn field_index_by_name_in(names: &[&'static str], name: &str) -> usize {
542 let nb = name.as_bytes();
543 let mut i = 0usize;
544 while i < names.len() {
545 let nb_i = names[i].as_bytes();
546 if nb_i.len() == nb.len() {
547 let mut j = 0usize;
548 let mut matched = true;
549 while j < nb.len() {
550 if nb_i[j] != nb[j] {
551 matched = false;
552 break;
553 }
554 j += 1;
555 }
556 if matched {
557 return i;
558 }
559 }
560 i += 1;
561 }
562 usize::MAX
563}
564
565impl PartitionProductFields for ConstrainedTypeInput {
566 const FIELDS: &'static [(u32, u32)] = &[];
567 const FIELD_NAMES: &'static [&'static str] = &[];
568}
569
570/// ADR-033 G20 chained-field access support: maps a
571/// `partition_product`-shaped type's positional factor index to
572/// the factor's static type. The `prism_model!` proc-macro emits
573/// a chain of `Term::ProjectField` projections by walking this
574/// trait, naming each next source type as
575/// `<PrevTy as PartitionProductFactor<I>>::Factor`.
576/// `partition_product!`, `product_shape!`, and
577/// `cartesian_product_shape!` emit one impl per factor index.
578pub trait PartitionProductFactor<const INDEX: usize>: PartitionProductFields {
579 /// The static type of the factor at position `INDEX`.
580 type Factor: ConstrainedTypeShape;
581}
582
583/// ADR-030: maximum number of axes a single application's
584/// `AxisTuple` may carry. Foundation-fixed (parallel to
585/// `FOLD_UNROLL_THRESHOLD` and `UNFOLD_MAX_ITERATIONS`).
586pub const MAX_AXIS_TUPLE_ARITY: usize = 8;
587
588/// ADR-055: substrate-Term verb body discipline. Every axis impl carries a
589/// substrate-Term decomposition the catamorphism can fuse through. Sealed —
590/// the `axis!` SDK macro per ADR-030 + ADR-052 emits the impl.
591/// The catamorphism's `Term::AxisInvocation` fold-rule reads this slice and
592/// recursively folds the body with the evaluated kernel inputs in scope (per
593/// ADR-029, amended by ADR-055). The `body_arena()` slice is a static const,
594/// not wire-format state, so the on-wire shape of `Term::AxisInvocation` is
595/// preserved.
596pub trait SubstrateTermBody<const INLINE_BYTES: usize>: __sdk_seal::Sealed {
597 /// The Term arena the kernel decomposes to. Empty slice signals a
598 /// primitive-fast-path axis whose body the implementation may evaluate
599 /// through `dispatch_kernel` directly per ADR-055's optional fast-path.
600 fn body_arena() -> &'static [crate::enforcement::Term<'static, INLINE_BYTES>];
601}
602
603/// ADR-030: a substrate-extension axis. Each `axis!`-declared
604/// trait extends this trait via the SDK macro's blanket impl,
605/// which emits per-method `KERNEL_*` const ids and the
606/// `dispatch_kernel` router into a fixed-capacity byte buffer.
607/// Per ADR-055, every `AxisExtension` impl also satisfies the
608/// [`SubstrateTermBody`] supertrait — its kernel decomposes to a
609/// substrate-Term slice the catamorphism walks structurally. This makes
610/// the Fold-Fusion Principle (ADR-054) universal across every axis impl,
611/// not just the standard library's canonical impls.
612/// The catamorphism's `Term::AxisInvocation` fold-rule reads the
613/// axis position from the application's `AxisTuple` impl, walks the
614/// axis's `SubstrateTermBody::body_arena()` recursively with the
615/// evaluated kernel input bound in scope, and emits the resulting
616/// `TermValue`. The legacy `dispatch_kernel` fast-path remains as an
617/// optimization for axes whose body is empty (primitive fast-path).
618/// ADR-018 + ADR-030: `FP_MAX` is the application's fingerprint-width axis
619/// (`<B as HostBounds>::FINGERPRINT_MAX_BYTES`), threaded so a `HashAxis`
620/// wrapping a `Hasher<FP_MAX>` of any width composes — there is no pinned
621/// 32-byte fingerprint. Parallel to `INLINE_BYTES`: the application
622/// instantiates it from its `HostBounds`, never a contrived literal.
623pub trait AxisExtension<const INLINE_BYTES: usize, const FP_MAX: usize>:
624 SubstrateTermBody<INLINE_BYTES>
625{
626 /// ADR-017 content address of this axis trait. The SDK macro
627 /// derives this from the trait name and method signatures.
628 const AXIS_ADDRESS: &'static str;
629 /// Maximum bytes any kernel of this axis returns.
630 const MAX_OUTPUT_BYTES: usize;
631 /// Dispatch the kernel identified by `kernel_id` against the
632 /// evaluated input bytes. The implementation copies the kernel's
633 /// output into `out` and returns the written length.
634 /// # Errors
635 /// Returns [`crate::enforcement::ShapeViolation`] when the
636 /// kernel id is unrecognised or the input does not satisfy the
637 /// kernel's shape contract.
638 fn dispatch_kernel(
639 kernel_id: u32,
640 input: &[u8],
641 out: &mut [u8],
642 ) -> Result<usize, crate::enforcement::ShapeViolation>;
643}
644
645/// ADR-030: a tuple of `AxisExtension`-implementing types selected
646/// by the application. The catamorphism's `Term::AxisInvocation`
647/// fold-rule calls `dispatch` to route the invocation to the right
648/// axis position.
649/// Foundation provides tuple impls for arities 1 through
650/// [`MAX_AXIS_TUPLE_ARITY`].
651pub trait AxisTuple<const INLINE_BYTES: usize, const FP_MAX: usize> {
652 /// Number of axes carried in this tuple.
653 const AXIS_COUNT: usize;
654 /// Maximum kernel-output byte width across all axes in this tuple.
655 const MAX_OUTPUT_BYTES: usize;
656 /// Dispatch a kernel against the axis at `axis_index`. Returns
657 /// the kernel's output bytes (length up to [`MAX_OUTPUT_BYTES`]).
658 /// # Errors
659 /// Returns [`crate::enforcement::ShapeViolation`] when `axis_index`
660 /// is out of range or the axis dispatcher rejects the input.
661 fn dispatch(
662 axis_index: u32,
663 kernel_id: u32,
664 input: &[u8],
665 out: &mut [u8],
666 ) -> Result<usize, crate::enforcement::ShapeViolation>;
667 /// ADR-055: return the substrate-Term body arena for the axis at
668 /// `axis_index`. An empty slice means the axis is a primitive-fast-path
669 /// axis whose body is byte-output-equivalent to its `dispatch_kernel`.
670 /// Non-empty slices carry the recursive-fold decomposition the
671 /// catamorphism walks per ADR-055's amended `Term::AxisInvocation`
672 /// fold-rule.
673 fn body_arena_at(axis_index: u32)
674 -> &'static [crate::enforcement::Term<'static, INLINE_BYTES>];
675}
676
677/// ADR-030 blanket: every [`crate::enforcement::Hasher`] is
678/// automatically an [`AxisTuple`] of arity 1 — the canonical
679/// hash axis at position 0, kernel id 0.
680impl<const INLINE_BYTES: usize, const FP_MAX: usize, H: crate::enforcement::Hasher<FP_MAX>>
681 AxisTuple<INLINE_BYTES, FP_MAX> for H
682{
683 const AXIS_COUNT: usize = 1;
684 const MAX_OUTPUT_BYTES: usize = <H as crate::enforcement::Hasher<FP_MAX>>::OUTPUT_BYTES;
685 fn dispatch(
686 axis_index: u32,
687 kernel_id: u32,
688 input: &[u8],
689 out: &mut [u8],
690 ) -> Result<usize, crate::enforcement::ShapeViolation> {
691 if axis_index != 0 || kernel_id != 0 {
692 return Err(crate::enforcement::ShapeViolation {
693 shape_iri: "https://uor.foundation/axis/HasherBlanket",
694 constraint_iri: "https://uor.foundation/axis/HasherBlanket/canonicalDispatch",
695 property_iri: "https://uor.foundation/axis/axisIndex",
696 expected_range: "https://uor.foundation/axis/CanonicalHashAxis",
697 min_count: 0,
698 max_count: 1,
699 kind: crate::ViolationKind::ValueCheck,
700 });
701 }
702 let mut hasher = <H as crate::enforcement::Hasher<FP_MAX>>::initial();
703 hasher = hasher.fold_bytes(input);
704 let digest = hasher.finalize();
705 let n_max = <H as crate::enforcement::Hasher<FP_MAX>>::OUTPUT_BYTES;
706 let n = if n_max > out.len() { out.len() } else { n_max };
707 let mut i = 0;
708 while i < n {
709 out[i] = digest[i];
710 i += 1;
711 }
712 Ok(n)
713 }
714 // ADR-055: the Hasher blanket axis is a primitive-fast-path axis. Its
715 // body is byte-output-equivalent to `fold_bytes` ∘ `finalize`; the
716 // empty arena signals to the catamorphism that dispatch_kernel is the
717 // canonical evaluation strategy here.
718 fn body_arena_at(
719 _axis_index: u32,
720 ) -> &'static [crate::enforcement::Term<'static, INLINE_BYTES>] {
721 &[]
722 }
723}
724
725/// ADR-030: 1-tuple AxisTuple impl — applications selecting a single axis.
726impl<const INLINE_BYTES: usize, const FP_MAX: usize, A0: AxisExtension<INLINE_BYTES, FP_MAX>>
727 AxisTuple<INLINE_BYTES, FP_MAX> for (A0,)
728{
729 const AXIS_COUNT: usize = 1;
730 const MAX_OUTPUT_BYTES: usize = <A0 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
731 fn dispatch(
732 axis_index: u32,
733 kernel_id: u32,
734 input: &[u8],
735 out: &mut [u8],
736 ) -> Result<usize, crate::enforcement::ShapeViolation> {
737 match axis_index {
738 0 => {
739 <A0 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
740 }
741 _ => Err(crate::enforcement::ShapeViolation {
742 shape_iri: "https://uor.foundation/pipeline/AxisTupleShape",
743 constraint_iri: "https://uor.foundation/pipeline/AxisTupleShape/inBounds",
744 property_iri: "https://uor.foundation/pipeline/axisIndex",
745 expected_range: "https://uor.foundation/pipeline/AxisIndex",
746 min_count: 0,
747 max_count: 1,
748 kind: crate::ViolationKind::ValueCheck,
749 }),
750 }
751 }
752 fn body_arena_at(
753 axis_index: u32,
754 ) -> &'static [crate::enforcement::Term<'static, INLINE_BYTES>] {
755 match axis_index {
756 0 => <A0 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
757 _ => &[],
758 }
759 }
760}
761
762/// ADR-030: 2-tuple AxisTuple impl.
763impl<
764 const INLINE_BYTES: usize,
765 const FP_MAX: usize,
766 A0: AxisExtension<INLINE_BYTES, FP_MAX>,
767 A1: AxisExtension<INLINE_BYTES, FP_MAX>,
768 > AxisTuple<INLINE_BYTES, FP_MAX> for (A0, A1)
769{
770 const AXIS_COUNT: usize = 2;
771 const MAX_OUTPUT_BYTES: usize = {
772 let a = <A0 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
773 let b = <A1 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
774 if a > b {
775 a
776 } else {
777 b
778 }
779 };
780 fn dispatch(
781 axis_index: u32,
782 kernel_id: u32,
783 input: &[u8],
784 out: &mut [u8],
785 ) -> Result<usize, crate::enforcement::ShapeViolation> {
786 match axis_index {
787 0 => {
788 <A0 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
789 }
790 1 => {
791 <A1 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
792 }
793 _ => Err(crate::enforcement::ShapeViolation {
794 shape_iri: "https://uor.foundation/pipeline/AxisTupleShape",
795 constraint_iri: "https://uor.foundation/pipeline/AxisTupleShape/inBounds",
796 property_iri: "https://uor.foundation/pipeline/axisIndex",
797 expected_range: "https://uor.foundation/pipeline/AxisIndex",
798 min_count: 0,
799 max_count: 2,
800 kind: crate::ViolationKind::ValueCheck,
801 }),
802 }
803 }
804 fn body_arena_at(
805 axis_index: u32,
806 ) -> &'static [crate::enforcement::Term<'static, INLINE_BYTES>] {
807 match axis_index {
808 0 => <A0 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
809 1 => <A1 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
810 _ => &[],
811 }
812 }
813}
814
815/// ADR-030: 3-tuple AxisTuple impl.
816impl<
817 const INLINE_BYTES: usize,
818 const FP_MAX: usize,
819 A0: AxisExtension<INLINE_BYTES, FP_MAX>,
820 A1: AxisExtension<INLINE_BYTES, FP_MAX>,
821 A2: AxisExtension<INLINE_BYTES, FP_MAX>,
822 > AxisTuple<INLINE_BYTES, FP_MAX> for (A0, A1, A2)
823{
824 const AXIS_COUNT: usize = 3;
825 const MAX_OUTPUT_BYTES: usize = {
826 let a0 = <A0 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
827 let a1 = <A1 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
828 let a2 = <A2 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
829 let mut m = a0;
830 if a1 > m {
831 m = a1;
832 }
833 if a2 > m {
834 m = a2;
835 }
836 m
837 };
838 fn dispatch(
839 axis_index: u32,
840 kernel_id: u32,
841 input: &[u8],
842 out: &mut [u8],
843 ) -> Result<usize, crate::enforcement::ShapeViolation> {
844 match axis_index {
845 0 => {
846 <A0 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
847 }
848 1 => {
849 <A1 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
850 }
851 2 => {
852 <A2 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
853 }
854 _ => Err(crate::enforcement::ShapeViolation {
855 shape_iri: "https://uor.foundation/pipeline/AxisTupleShape",
856 constraint_iri: "https://uor.foundation/pipeline/AxisTupleShape/inBounds",
857 property_iri: "https://uor.foundation/pipeline/axisIndex",
858 expected_range: "https://uor.foundation/pipeline/AxisIndex",
859 min_count: 0,
860 max_count: 3,
861 kind: crate::ViolationKind::ValueCheck,
862 }),
863 }
864 }
865 fn body_arena_at(
866 axis_index: u32,
867 ) -> &'static [crate::enforcement::Term<'static, INLINE_BYTES>] {
868 match axis_index {
869 0 => <A0 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
870 1 => <A1 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
871 2 => <A2 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
872 _ => &[],
873 }
874 }
875}
876
877/// ADR-030: 4-tuple AxisTuple impl.
878impl<
879 const INLINE_BYTES: usize,
880 const FP_MAX: usize,
881 A0: AxisExtension<INLINE_BYTES, FP_MAX>,
882 A1: AxisExtension<INLINE_BYTES, FP_MAX>,
883 A2: AxisExtension<INLINE_BYTES, FP_MAX>,
884 A3: AxisExtension<INLINE_BYTES, FP_MAX>,
885 > AxisTuple<INLINE_BYTES, FP_MAX> for (A0, A1, A2, A3)
886{
887 const AXIS_COUNT: usize = 4;
888 const MAX_OUTPUT_BYTES: usize = {
889 let a0 = <A0 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
890 let a1 = <A1 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
891 let a2 = <A2 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
892 let a3 = <A3 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
893 let mut m = a0;
894 if a1 > m {
895 m = a1;
896 }
897 if a2 > m {
898 m = a2;
899 }
900 if a3 > m {
901 m = a3;
902 }
903 m
904 };
905 fn dispatch(
906 axis_index: u32,
907 kernel_id: u32,
908 input: &[u8],
909 out: &mut [u8],
910 ) -> Result<usize, crate::enforcement::ShapeViolation> {
911 match axis_index {
912 0 => {
913 <A0 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
914 }
915 1 => {
916 <A1 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
917 }
918 2 => {
919 <A2 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
920 }
921 3 => {
922 <A3 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
923 }
924 _ => Err(crate::enforcement::ShapeViolation {
925 shape_iri: "https://uor.foundation/pipeline/AxisTupleShape",
926 constraint_iri: "https://uor.foundation/pipeline/AxisTupleShape/inBounds",
927 property_iri: "https://uor.foundation/pipeline/axisIndex",
928 expected_range: "https://uor.foundation/pipeline/AxisIndex",
929 min_count: 0,
930 max_count: 4,
931 kind: crate::ViolationKind::ValueCheck,
932 }),
933 }
934 }
935 fn body_arena_at(
936 axis_index: u32,
937 ) -> &'static [crate::enforcement::Term<'static, INLINE_BYTES>] {
938 match axis_index {
939 0 => <A0 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
940 1 => <A1 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
941 2 => <A2 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
942 3 => <A3 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
943 _ => &[],
944 }
945 }
946}
947
948/// ADR-030: 5-tuple AxisTuple impl.
949impl<
950 const INLINE_BYTES: usize,
951 const FP_MAX: usize,
952 A0: AxisExtension<INLINE_BYTES, FP_MAX>,
953 A1: AxisExtension<INLINE_BYTES, FP_MAX>,
954 A2: AxisExtension<INLINE_BYTES, FP_MAX>,
955 A3: AxisExtension<INLINE_BYTES, FP_MAX>,
956 A4: AxisExtension<INLINE_BYTES, FP_MAX>,
957 > AxisTuple<INLINE_BYTES, FP_MAX> for (A0, A1, A2, A3, A4)
958{
959 const AXIS_COUNT: usize = 5;
960 const MAX_OUTPUT_BYTES: usize = {
961 let a0 = <A0 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
962 let a1 = <A1 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
963 let a2 = <A2 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
964 let a3 = <A3 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
965 let a4 = <A4 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
966 let mut m = a0;
967 if a1 > m {
968 m = a1;
969 }
970 if a2 > m {
971 m = a2;
972 }
973 if a3 > m {
974 m = a3;
975 }
976 if a4 > m {
977 m = a4;
978 }
979 m
980 };
981 fn dispatch(
982 axis_index: u32,
983 kernel_id: u32,
984 input: &[u8],
985 out: &mut [u8],
986 ) -> Result<usize, crate::enforcement::ShapeViolation> {
987 match axis_index {
988 0 => {
989 <A0 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
990 }
991 1 => {
992 <A1 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
993 }
994 2 => {
995 <A2 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
996 }
997 3 => {
998 <A3 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
999 }
1000 4 => {
1001 <A4 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1002 }
1003 _ => Err(crate::enforcement::ShapeViolation {
1004 shape_iri: "https://uor.foundation/pipeline/AxisTupleShape",
1005 constraint_iri: "https://uor.foundation/pipeline/AxisTupleShape/inBounds",
1006 property_iri: "https://uor.foundation/pipeline/axisIndex",
1007 expected_range: "https://uor.foundation/pipeline/AxisIndex",
1008 min_count: 0,
1009 max_count: 5,
1010 kind: crate::ViolationKind::ValueCheck,
1011 }),
1012 }
1013 }
1014 fn body_arena_at(
1015 axis_index: u32,
1016 ) -> &'static [crate::enforcement::Term<'static, INLINE_BYTES>] {
1017 match axis_index {
1018 0 => <A0 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1019 1 => <A1 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1020 2 => <A2 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1021 3 => <A3 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1022 4 => <A4 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1023 _ => &[],
1024 }
1025 }
1026}
1027
1028/// ADR-030: 6-tuple AxisTuple impl.
1029impl<
1030 const INLINE_BYTES: usize,
1031 const FP_MAX: usize,
1032 A0: AxisExtension<INLINE_BYTES, FP_MAX>,
1033 A1: AxisExtension<INLINE_BYTES, FP_MAX>,
1034 A2: AxisExtension<INLINE_BYTES, FP_MAX>,
1035 A3: AxisExtension<INLINE_BYTES, FP_MAX>,
1036 A4: AxisExtension<INLINE_BYTES, FP_MAX>,
1037 A5: AxisExtension<INLINE_BYTES, FP_MAX>,
1038 > AxisTuple<INLINE_BYTES, FP_MAX> for (A0, A1, A2, A3, A4, A5)
1039{
1040 const AXIS_COUNT: usize = 6;
1041 const MAX_OUTPUT_BYTES: usize = {
1042 let a0 = <A0 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1043 let a1 = <A1 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1044 let a2 = <A2 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1045 let a3 = <A3 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1046 let a4 = <A4 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1047 let a5 = <A5 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1048 let mut m = a0;
1049 if a1 > m {
1050 m = a1;
1051 }
1052 if a2 > m {
1053 m = a2;
1054 }
1055 if a3 > m {
1056 m = a3;
1057 }
1058 if a4 > m {
1059 m = a4;
1060 }
1061 if a5 > m {
1062 m = a5;
1063 }
1064 m
1065 };
1066 fn dispatch(
1067 axis_index: u32,
1068 kernel_id: u32,
1069 input: &[u8],
1070 out: &mut [u8],
1071 ) -> Result<usize, crate::enforcement::ShapeViolation> {
1072 match axis_index {
1073 0 => {
1074 <A0 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1075 }
1076 1 => {
1077 <A1 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1078 }
1079 2 => {
1080 <A2 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1081 }
1082 3 => {
1083 <A3 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1084 }
1085 4 => {
1086 <A4 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1087 }
1088 5 => {
1089 <A5 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1090 }
1091 _ => Err(crate::enforcement::ShapeViolation {
1092 shape_iri: "https://uor.foundation/pipeline/AxisTupleShape",
1093 constraint_iri: "https://uor.foundation/pipeline/AxisTupleShape/inBounds",
1094 property_iri: "https://uor.foundation/pipeline/axisIndex",
1095 expected_range: "https://uor.foundation/pipeline/AxisIndex",
1096 min_count: 0,
1097 max_count: 6,
1098 kind: crate::ViolationKind::ValueCheck,
1099 }),
1100 }
1101 }
1102 fn body_arena_at(
1103 axis_index: u32,
1104 ) -> &'static [crate::enforcement::Term<'static, INLINE_BYTES>] {
1105 match axis_index {
1106 0 => <A0 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1107 1 => <A1 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1108 2 => <A2 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1109 3 => <A3 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1110 4 => <A4 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1111 5 => <A5 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1112 _ => &[],
1113 }
1114 }
1115}
1116
1117/// ADR-030: 7-tuple AxisTuple impl.
1118impl<
1119 const INLINE_BYTES: usize,
1120 const FP_MAX: usize,
1121 A0: AxisExtension<INLINE_BYTES, FP_MAX>,
1122 A1: AxisExtension<INLINE_BYTES, FP_MAX>,
1123 A2: AxisExtension<INLINE_BYTES, FP_MAX>,
1124 A3: AxisExtension<INLINE_BYTES, FP_MAX>,
1125 A4: AxisExtension<INLINE_BYTES, FP_MAX>,
1126 A5: AxisExtension<INLINE_BYTES, FP_MAX>,
1127 A6: AxisExtension<INLINE_BYTES, FP_MAX>,
1128 > AxisTuple<INLINE_BYTES, FP_MAX> for (A0, A1, A2, A3, A4, A5, A6)
1129{
1130 const AXIS_COUNT: usize = 7;
1131 const MAX_OUTPUT_BYTES: usize = {
1132 let a0 = <A0 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1133 let a1 = <A1 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1134 let a2 = <A2 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1135 let a3 = <A3 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1136 let a4 = <A4 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1137 let a5 = <A5 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1138 let a6 = <A6 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1139 let mut m = a0;
1140 if a1 > m {
1141 m = a1;
1142 }
1143 if a2 > m {
1144 m = a2;
1145 }
1146 if a3 > m {
1147 m = a3;
1148 }
1149 if a4 > m {
1150 m = a4;
1151 }
1152 if a5 > m {
1153 m = a5;
1154 }
1155 if a6 > m {
1156 m = a6;
1157 }
1158 m
1159 };
1160 fn dispatch(
1161 axis_index: u32,
1162 kernel_id: u32,
1163 input: &[u8],
1164 out: &mut [u8],
1165 ) -> Result<usize, crate::enforcement::ShapeViolation> {
1166 match axis_index {
1167 0 => {
1168 <A0 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1169 }
1170 1 => {
1171 <A1 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1172 }
1173 2 => {
1174 <A2 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1175 }
1176 3 => {
1177 <A3 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1178 }
1179 4 => {
1180 <A4 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1181 }
1182 5 => {
1183 <A5 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1184 }
1185 6 => {
1186 <A6 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1187 }
1188 _ => Err(crate::enforcement::ShapeViolation {
1189 shape_iri: "https://uor.foundation/pipeline/AxisTupleShape",
1190 constraint_iri: "https://uor.foundation/pipeline/AxisTupleShape/inBounds",
1191 property_iri: "https://uor.foundation/pipeline/axisIndex",
1192 expected_range: "https://uor.foundation/pipeline/AxisIndex",
1193 min_count: 0,
1194 max_count: 7,
1195 kind: crate::ViolationKind::ValueCheck,
1196 }),
1197 }
1198 }
1199 fn body_arena_at(
1200 axis_index: u32,
1201 ) -> &'static [crate::enforcement::Term<'static, INLINE_BYTES>] {
1202 match axis_index {
1203 0 => <A0 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1204 1 => <A1 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1205 2 => <A2 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1206 3 => <A3 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1207 4 => <A4 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1208 5 => <A5 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1209 6 => <A6 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1210 _ => &[],
1211 }
1212 }
1213}
1214
1215/// ADR-030: 8-tuple AxisTuple impl.
1216impl<
1217 const INLINE_BYTES: usize,
1218 const FP_MAX: usize,
1219 A0: AxisExtension<INLINE_BYTES, FP_MAX>,
1220 A1: AxisExtension<INLINE_BYTES, FP_MAX>,
1221 A2: AxisExtension<INLINE_BYTES, FP_MAX>,
1222 A3: AxisExtension<INLINE_BYTES, FP_MAX>,
1223 A4: AxisExtension<INLINE_BYTES, FP_MAX>,
1224 A5: AxisExtension<INLINE_BYTES, FP_MAX>,
1225 A6: AxisExtension<INLINE_BYTES, FP_MAX>,
1226 A7: AxisExtension<INLINE_BYTES, FP_MAX>,
1227 > AxisTuple<INLINE_BYTES, FP_MAX> for (A0, A1, A2, A3, A4, A5, A6, A7)
1228{
1229 const AXIS_COUNT: usize = 8;
1230 const MAX_OUTPUT_BYTES: usize = {
1231 let a0 = <A0 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1232 let a1 = <A1 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1233 let a2 = <A2 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1234 let a3 = <A3 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1235 let a4 = <A4 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1236 let a5 = <A5 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1237 let a6 = <A6 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1238 let a7 = <A7 as AxisExtension<INLINE_BYTES, FP_MAX>>::MAX_OUTPUT_BYTES;
1239 let mut m = a0;
1240 if a1 > m {
1241 m = a1;
1242 }
1243 if a2 > m {
1244 m = a2;
1245 }
1246 if a3 > m {
1247 m = a3;
1248 }
1249 if a4 > m {
1250 m = a4;
1251 }
1252 if a5 > m {
1253 m = a5;
1254 }
1255 if a6 > m {
1256 m = a6;
1257 }
1258 if a7 > m {
1259 m = a7;
1260 }
1261 m
1262 };
1263 fn dispatch(
1264 axis_index: u32,
1265 kernel_id: u32,
1266 input: &[u8],
1267 out: &mut [u8],
1268 ) -> Result<usize, crate::enforcement::ShapeViolation> {
1269 match axis_index {
1270 0 => {
1271 <A0 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1272 }
1273 1 => {
1274 <A1 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1275 }
1276 2 => {
1277 <A2 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1278 }
1279 3 => {
1280 <A3 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1281 }
1282 4 => {
1283 <A4 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1284 }
1285 5 => {
1286 <A5 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1287 }
1288 6 => {
1289 <A6 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1290 }
1291 7 => {
1292 <A7 as AxisExtension<INLINE_BYTES, FP_MAX>>::dispatch_kernel(kernel_id, input, out)
1293 }
1294 _ => Err(crate::enforcement::ShapeViolation {
1295 shape_iri: "https://uor.foundation/pipeline/AxisTupleShape",
1296 constraint_iri: "https://uor.foundation/pipeline/AxisTupleShape/inBounds",
1297 property_iri: "https://uor.foundation/pipeline/axisIndex",
1298 expected_range: "https://uor.foundation/pipeline/AxisIndex",
1299 min_count: 0,
1300 max_count: 8,
1301 kind: crate::ViolationKind::ValueCheck,
1302 }),
1303 }
1304 }
1305 fn body_arena_at(
1306 axis_index: u32,
1307 ) -> &'static [crate::enforcement::Term<'static, INLINE_BYTES>] {
1308 match axis_index {
1309 0 => <A0 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1310 1 => <A1 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1311 2 => <A2 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1312 3 => <A3 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1313 4 => <A4 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1314 5 => <A5 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1315 6 => <A6 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1316 7 => <A7 as SubstrateTermBody<INLINE_BYTES>>::body_arena(),
1317 _ => &[],
1318 }
1319 }
1320}
1321
1322/// ADR-036: maximum number of resolvers a single application's
1323/// `ResolverTuple` may carry. Foundation-fixed at twice
1324/// [`MAX_AXIS_TUPLE_ARITY`] to accommodate ADR-035's eight
1325/// resolver-bound ψ-Term categories with eight headroom positions
1326/// for future ADR-013/TR-08 substrate amendments.
1327pub const MAX_RESOLVER_TUPLE_ARITY: usize = 16;
1328
1329/// ADR-036: foundation-internal discriminator emitted by every
1330/// `Null<Category>Resolver` impl when the catamorphism's resolver-
1331/// bound ψ-Term fold-rule consults a resolver category whose
1332/// `ResolverTuple` accessor returns the foundation-Null impl. The
1333/// evaluator translates this into a `PipelineFailure::ShapeViolation`
1334/// carrying the `https://uor.foundation/resolver/RESOLVER_ABSENT`
1335/// shape IRI; ADR-022 D3 G9's `Term::Try` default-propagation handler
1336/// recovers it when the verb body wraps the ψ-Term.
1337pub const RESOLVER_ABSENT_DISCRIMINATOR: u8 = 0xff;
1338
1339/// ADR-036: resolver-category enum identifying which resolver-bound
1340/// substrate operation each `ResolverTuple` position satisfies. Each
1341/// variant corresponds to one resolver-bound ψ-Term variant per
1342/// ADR-035; future ADR-013/TR-08 substrate amendments add variants.
1343#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1344pub enum ResolverCategory {
1345 /// ψ_1 — per-value bytes → SimplicialComplex (NerveResolver).
1346 Nerve,
1347 /// ψ_2 — SimplicialComplex → ChainComplex (ChainComplexResolver).
1348 ChainComplex,
1349 /// ψ_3 — ChainComplex → HomologyGroups (HomologyGroupResolver).
1350 HomologyGroup,
1351 /// ψ_5 — ChainComplex → CochainComplex (CochainComplexResolver).
1352 CochainComplex,
1353 /// ψ_6 — CochainComplex → CohomologyGroups (CohomologyGroupResolver).
1354 CohomologyGroup,
1355 /// ψ_7 — SimplicialComplex → PostnikovTower (PostnikovResolver).
1356 Postnikov,
1357 /// ψ_8 — PostnikovTower → HomotopyGroups (HomotopyGroupResolver).
1358 HomotopyGroup,
1359 /// ψ_9 — HomotopyGroups → KInvariants (KInvariantResolver).
1360 KInvariant,
1361}
1362
1363/// ADR-036: tuple-of-bounded-types parameter on the model declaration
1364/// carrying application-provided resolver instances for the resolver-
1365/// bound ψ-Term variants per ADR-035. Sealed via
1366/// [`__sdk_seal::Sealed`]: only the SDK `resolver!` macro emits impls.
1367pub trait ResolverTuple: __sdk_seal::Sealed {
1368 /// Number of resolver positions in this tuple (bounded by `MAX_RESOLVER_TUPLE_ARITY`).
1369 const ARITY: usize;
1370 /// Resolver category at each tuple position.
1371 const CATEGORIES: &'static [ResolverCategory];
1372 /// ADR-057: the application's shape-IRI registry. ψ_1's NerveResolver impl
1373 /// expands `ConstraintRef::Recurse` references through this registry when
1374 /// computing N(C). Defaults to [`shape_iri_registry::EmptyShapeRegistry`]
1375 /// (foundation built-in registry only); applications declaring recursive
1376 /// shapes via the SDK `register_shape!` macro thread the marker type
1377 /// through their `resolver!`-declared ResolverTuple.
1378 type ShapeRegistry: crate::pipeline::shape_iri_registry::ShapeRegistryProvider;
1379}
1380
1381/// Wiki ADR-041: zero-cost typed-coordinate carrier for ψ_1 output —
1382/// the simplicial-complex serialization produced by `NerveResolver::resolve`.
1383///
1384/// `#[repr(transparent)]` over `&'a [u8]`: layout identical to a byte
1385/// slice; the type wrapper is purely compile-time discrimination so
1386/// ψ-stage composition is type-checked at the resolver-impl boundary.
1387#[repr(transparent)]
1388#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1389pub struct SimplicialComplexBytes<'a>(pub &'a [u8]);
1390
1391impl<'a> SimplicialComplexBytes<'a> {
1392 /// Borrow the underlying byte slice.
1393 #[must_use]
1394 pub fn as_bytes(&self) -> &[u8] {
1395 self.0
1396 }
1397 /// Length of the underlying byte slice.
1398 #[must_use]
1399 pub fn len(&self) -> usize {
1400 self.0.len()
1401 }
1402 /// Whether the underlying byte slice is empty.
1403 #[must_use]
1404 pub fn is_empty(&self) -> bool {
1405 self.0.is_empty()
1406 }
1407}
1408
1409/// Wiki ADR-041: zero-cost typed-coordinate carrier for ψ_2 output —
1410/// the chain-complex serialization produced by `ChainComplexResolver::resolve`.
1411///
1412/// `#[repr(transparent)]` over `&'a [u8]`: layout identical to a byte
1413/// slice; the type wrapper is purely compile-time discrimination so
1414/// ψ-stage composition is type-checked at the resolver-impl boundary.
1415#[repr(transparent)]
1416#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1417pub struct ChainComplexBytes<'a>(pub &'a [u8]);
1418
1419impl<'a> ChainComplexBytes<'a> {
1420 /// Borrow the underlying byte slice.
1421 #[must_use]
1422 pub fn as_bytes(&self) -> &[u8] {
1423 self.0
1424 }
1425 /// Length of the underlying byte slice.
1426 #[must_use]
1427 pub fn len(&self) -> usize {
1428 self.0.len()
1429 }
1430 /// Whether the underlying byte slice is empty.
1431 #[must_use]
1432 pub fn is_empty(&self) -> bool {
1433 self.0.is_empty()
1434 }
1435}
1436
1437/// Wiki ADR-041: zero-cost typed-coordinate carrier for ψ_3 output —
1438/// the homology-groups serialization produced by `HomologyGroupResolver::resolve`.
1439///
1440/// `#[repr(transparent)]` over `&'a [u8]`: layout identical to a byte
1441/// slice; the type wrapper is purely compile-time discrimination so
1442/// ψ-stage composition is type-checked at the resolver-impl boundary.
1443#[repr(transparent)]
1444#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1445pub struct HomologyGroupsBytes<'a>(pub &'a [u8]);
1446
1447impl<'a> HomologyGroupsBytes<'a> {
1448 /// Borrow the underlying byte slice.
1449 #[must_use]
1450 pub fn as_bytes(&self) -> &[u8] {
1451 self.0
1452 }
1453 /// Length of the underlying byte slice.
1454 #[must_use]
1455 pub fn len(&self) -> usize {
1456 self.0.len()
1457 }
1458 /// Whether the underlying byte slice is empty.
1459 #[must_use]
1460 pub fn is_empty(&self) -> bool {
1461 self.0.is_empty()
1462 }
1463}
1464
1465/// Wiki ADR-041: zero-cost typed-coordinate carrier for ψ_4 output —
1466/// the Betti-number tuple serialization produced by the resolver-free `Term::Betti` fold-rule (a byte projection of HomologyGroupsBytes).
1467///
1468/// `#[repr(transparent)]` over `&'a [u8]`: layout identical to a byte
1469/// slice; the type wrapper is purely compile-time discrimination so
1470/// ψ-stage composition is type-checked at the resolver-impl boundary.
1471#[repr(transparent)]
1472#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1473pub struct BettiNumbersBytes<'a>(pub &'a [u8]);
1474
1475impl<'a> BettiNumbersBytes<'a> {
1476 /// Borrow the underlying byte slice.
1477 #[must_use]
1478 pub fn as_bytes(&self) -> &[u8] {
1479 self.0
1480 }
1481 /// Length of the underlying byte slice.
1482 #[must_use]
1483 pub fn len(&self) -> usize {
1484 self.0.len()
1485 }
1486 /// Whether the underlying byte slice is empty.
1487 #[must_use]
1488 pub fn is_empty(&self) -> bool {
1489 self.0.is_empty()
1490 }
1491}
1492
1493/// Wiki ADR-041: zero-cost typed-coordinate carrier for ψ_5 output —
1494/// the cochain-complex serialization produced by `CochainComplexResolver::resolve`.
1495///
1496/// `#[repr(transparent)]` over `&'a [u8]`: layout identical to a byte
1497/// slice; the type wrapper is purely compile-time discrimination so
1498/// ψ-stage composition is type-checked at the resolver-impl boundary.
1499#[repr(transparent)]
1500#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1501pub struct CochainComplexBytes<'a>(pub &'a [u8]);
1502
1503impl<'a> CochainComplexBytes<'a> {
1504 /// Borrow the underlying byte slice.
1505 #[must_use]
1506 pub fn as_bytes(&self) -> &[u8] {
1507 self.0
1508 }
1509 /// Length of the underlying byte slice.
1510 #[must_use]
1511 pub fn len(&self) -> usize {
1512 self.0.len()
1513 }
1514 /// Whether the underlying byte slice is empty.
1515 #[must_use]
1516 pub fn is_empty(&self) -> bool {
1517 self.0.is_empty()
1518 }
1519}
1520
1521/// Wiki ADR-041: zero-cost typed-coordinate carrier for ψ_6 output —
1522/// the cohomology-groups serialization produced by `CohomologyGroupResolver::resolve`.
1523///
1524/// `#[repr(transparent)]` over `&'a [u8]`: layout identical to a byte
1525/// slice; the type wrapper is purely compile-time discrimination so
1526/// ψ-stage composition is type-checked at the resolver-impl boundary.
1527#[repr(transparent)]
1528#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1529pub struct CohomologyGroupsBytes<'a>(pub &'a [u8]);
1530
1531impl<'a> CohomologyGroupsBytes<'a> {
1532 /// Borrow the underlying byte slice.
1533 #[must_use]
1534 pub fn as_bytes(&self) -> &[u8] {
1535 self.0
1536 }
1537 /// Length of the underlying byte slice.
1538 #[must_use]
1539 pub fn len(&self) -> usize {
1540 self.0.len()
1541 }
1542 /// Whether the underlying byte slice is empty.
1543 #[must_use]
1544 pub fn is_empty(&self) -> bool {
1545 self.0.is_empty()
1546 }
1547}
1548
1549/// Wiki ADR-041: zero-cost typed-coordinate carrier for ψ_7 output —
1550/// the Postnikov-tower serialization produced by `PostnikovResolver::resolve`.
1551///
1552/// `#[repr(transparent)]` over `&'a [u8]`: layout identical to a byte
1553/// slice; the type wrapper is purely compile-time discrimination so
1554/// ψ-stage composition is type-checked at the resolver-impl boundary.
1555#[repr(transparent)]
1556#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1557pub struct PostnikovTowerBytes<'a>(pub &'a [u8]);
1558
1559impl<'a> PostnikovTowerBytes<'a> {
1560 /// Borrow the underlying byte slice.
1561 #[must_use]
1562 pub fn as_bytes(&self) -> &[u8] {
1563 self.0
1564 }
1565 /// Length of the underlying byte slice.
1566 #[must_use]
1567 pub fn len(&self) -> usize {
1568 self.0.len()
1569 }
1570 /// Whether the underlying byte slice is empty.
1571 #[must_use]
1572 pub fn is_empty(&self) -> bool {
1573 self.0.is_empty()
1574 }
1575}
1576
1577/// Wiki ADR-041: zero-cost typed-coordinate carrier for ψ_8 output —
1578/// the homotopy-groups serialization produced by `HomotopyGroupResolver::resolve`.
1579///
1580/// `#[repr(transparent)]` over `&'a [u8]`: layout identical to a byte
1581/// slice; the type wrapper is purely compile-time discrimination so
1582/// ψ-stage composition is type-checked at the resolver-impl boundary.
1583#[repr(transparent)]
1584#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1585pub struct HomotopyGroupsBytes<'a>(pub &'a [u8]);
1586
1587impl<'a> HomotopyGroupsBytes<'a> {
1588 /// Borrow the underlying byte slice.
1589 #[must_use]
1590 pub fn as_bytes(&self) -> &[u8] {
1591 self.0
1592 }
1593 /// Length of the underlying byte slice.
1594 #[must_use]
1595 pub fn len(&self) -> usize {
1596 self.0.len()
1597 }
1598 /// Whether the underlying byte slice is empty.
1599 #[must_use]
1600 pub fn is_empty(&self) -> bool {
1601 self.0.is_empty()
1602 }
1603}
1604
1605/// Wiki ADR-041: zero-cost typed-coordinate carrier for ψ_9 output —
1606/// the κ-label byte serialization produced by `KInvariantResolver::resolve` — the canonical k-invariants-branch output (ADR-035) classifying the input's homotopy type up to weak equivalence.
1607///
1608/// `#[repr(transparent)]` over `&'a [u8]`: layout identical to a byte
1609/// slice; the type wrapper is purely compile-time discrimination so
1610/// ψ-stage composition is type-checked at the resolver-impl boundary.
1611#[repr(transparent)]
1612#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1613pub struct KInvariantsBytes<'a>(pub &'a [u8]);
1614
1615impl<'a> KInvariantsBytes<'a> {
1616 /// Borrow the underlying byte slice.
1617 #[must_use]
1618 pub fn as_bytes(&self) -> &[u8] {
1619 self.0
1620 }
1621 /// Length of the underlying byte slice.
1622 #[must_use]
1623 pub fn len(&self) -> usize {
1624 self.0.len()
1625 }
1626 /// Whether the underlying byte slice is empty.
1627 #[must_use]
1628 pub fn is_empty(&self) -> bool {
1629 self.0.is_empty()
1630 }
1631}
1632
1633/// ADR-036 resolver trait: ψ_1 — per-value bytes → SimplicialComplex per ADR-035.
1634///
1635/// Parameterized by the model's H-axis (`H: Hasher` per ADR-022 D5) so
1636/// resolver impls compute content-addressed fingerprints using the
1637/// model's chosen hash impl. Sealed via [`__sdk_seal::Sealed`]: only
1638/// the SDK `resolver!` macro emits impls. Foundation provides a Null
1639/// impl whose `resolve` emits the `RESOLVER_ABSENT` shape violation.
1640///
1641/// ADR-060: `resolve` takes and returns a source-polymorphic
1642/// [`crate::pipeline::TermValue`] const-generic over the application's
1643/// foundation-derived inline width (`INLINE_BYTES =
1644/// carrier_inline_bytes::<B>()`). The resolver constructs whichever
1645/// carrier variant its output requires — `Inline` for structural
1646/// identities that fit the inline width, `Borrowed` into resolver-owned
1647/// scratch, or `Stream` for unbounded structural sequences. There is no
1648/// caller-supplied `&mut [u8]` scratch and no per-ψ-stage byte-width
1649/// ceiling (ADR-060 amends the ADR-036/ADR-041 writer-style surface).
1650pub trait NerveResolver<const INLINE_BYTES: usize, H>: __sdk_seal::Sealed {
1651 /// Resolve per-value content for this category.
1652 /// # Errors
1653 /// Returns [`crate::enforcement::ShapeViolation`] when the resolver
1654 /// cannot produce content (e.g., the foundation Null impl carrying
1655 /// the `RESOLVER_ABSENT` discriminator).
1656 fn resolve<'a>(
1657 &self,
1658 input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
1659 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>;
1660}
1661
1662/// ADR-036 resolver trait: ψ_2 — SimplicialComplex → ChainComplex per ADR-035.
1663///
1664/// Parameterized by the model's H-axis (`H: Hasher` per ADR-022 D5) so
1665/// resolver impls compute content-addressed fingerprints using the
1666/// model's chosen hash impl. Sealed via [`__sdk_seal::Sealed`]: only
1667/// the SDK `resolver!` macro emits impls. Foundation provides a Null
1668/// impl whose `resolve` emits the `RESOLVER_ABSENT` shape violation.
1669///
1670/// ADR-060: `resolve` takes and returns a source-polymorphic
1671/// [`crate::pipeline::TermValue`] const-generic over the application's
1672/// foundation-derived inline width (`INLINE_BYTES =
1673/// carrier_inline_bytes::<B>()`). The resolver constructs whichever
1674/// carrier variant its output requires — `Inline` for structural
1675/// identities that fit the inline width, `Borrowed` into resolver-owned
1676/// scratch, or `Stream` for unbounded structural sequences. There is no
1677/// caller-supplied `&mut [u8]` scratch and no per-ψ-stage byte-width
1678/// ceiling (ADR-060 amends the ADR-036/ADR-041 writer-style surface).
1679pub trait ChainComplexResolver<const INLINE_BYTES: usize, H>: __sdk_seal::Sealed {
1680 /// Resolve per-value content for this category.
1681 /// # Errors
1682 /// Returns [`crate::enforcement::ShapeViolation`] when the resolver
1683 /// cannot produce content (e.g., the foundation Null impl carrying
1684 /// the `RESOLVER_ABSENT` discriminator).
1685 fn resolve<'a>(
1686 &self,
1687 input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
1688 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>;
1689}
1690
1691/// ADR-036 resolver trait: ψ_3 — ChainComplex → HomologyGroups per ADR-035.
1692///
1693/// Parameterized by the model's H-axis (`H: Hasher` per ADR-022 D5) so
1694/// resolver impls compute content-addressed fingerprints using the
1695/// model's chosen hash impl. Sealed via [`__sdk_seal::Sealed`]: only
1696/// the SDK `resolver!` macro emits impls. Foundation provides a Null
1697/// impl whose `resolve` emits the `RESOLVER_ABSENT` shape violation.
1698///
1699/// ADR-060: `resolve` takes and returns a source-polymorphic
1700/// [`crate::pipeline::TermValue`] const-generic over the application's
1701/// foundation-derived inline width (`INLINE_BYTES =
1702/// carrier_inline_bytes::<B>()`). The resolver constructs whichever
1703/// carrier variant its output requires — `Inline` for structural
1704/// identities that fit the inline width, `Borrowed` into resolver-owned
1705/// scratch, or `Stream` for unbounded structural sequences. There is no
1706/// caller-supplied `&mut [u8]` scratch and no per-ψ-stage byte-width
1707/// ceiling (ADR-060 amends the ADR-036/ADR-041 writer-style surface).
1708pub trait HomologyGroupResolver<const INLINE_BYTES: usize, H>: __sdk_seal::Sealed {
1709 /// Resolve per-value content for this category.
1710 /// # Errors
1711 /// Returns [`crate::enforcement::ShapeViolation`] when the resolver
1712 /// cannot produce content (e.g., the foundation Null impl carrying
1713 /// the `RESOLVER_ABSENT` discriminator).
1714 fn resolve<'a>(
1715 &self,
1716 input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
1717 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>;
1718}
1719
1720/// ADR-036 resolver trait: ψ_5 — ChainComplex → CochainComplex per ADR-035.
1721///
1722/// Parameterized by the model's H-axis (`H: Hasher` per ADR-022 D5) so
1723/// resolver impls compute content-addressed fingerprints using the
1724/// model's chosen hash impl. Sealed via [`__sdk_seal::Sealed`]: only
1725/// the SDK `resolver!` macro emits impls. Foundation provides a Null
1726/// impl whose `resolve` emits the `RESOLVER_ABSENT` shape violation.
1727///
1728/// ADR-060: `resolve` takes and returns a source-polymorphic
1729/// [`crate::pipeline::TermValue`] const-generic over the application's
1730/// foundation-derived inline width (`INLINE_BYTES =
1731/// carrier_inline_bytes::<B>()`). The resolver constructs whichever
1732/// carrier variant its output requires — `Inline` for structural
1733/// identities that fit the inline width, `Borrowed` into resolver-owned
1734/// scratch, or `Stream` for unbounded structural sequences. There is no
1735/// caller-supplied `&mut [u8]` scratch and no per-ψ-stage byte-width
1736/// ceiling (ADR-060 amends the ADR-036/ADR-041 writer-style surface).
1737pub trait CochainComplexResolver<const INLINE_BYTES: usize, H>: __sdk_seal::Sealed {
1738 /// Resolve per-value content for this category.
1739 /// # Errors
1740 /// Returns [`crate::enforcement::ShapeViolation`] when the resolver
1741 /// cannot produce content (e.g., the foundation Null impl carrying
1742 /// the `RESOLVER_ABSENT` discriminator).
1743 fn resolve<'a>(
1744 &self,
1745 input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
1746 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>;
1747}
1748
1749/// ADR-036 resolver trait: ψ_6 — CochainComplex → CohomologyGroups per ADR-035.
1750///
1751/// Parameterized by the model's H-axis (`H: Hasher` per ADR-022 D5) so
1752/// resolver impls compute content-addressed fingerprints using the
1753/// model's chosen hash impl. Sealed via [`__sdk_seal::Sealed`]: only
1754/// the SDK `resolver!` macro emits impls. Foundation provides a Null
1755/// impl whose `resolve` emits the `RESOLVER_ABSENT` shape violation.
1756///
1757/// ADR-060: `resolve` takes and returns a source-polymorphic
1758/// [`crate::pipeline::TermValue`] const-generic over the application's
1759/// foundation-derived inline width (`INLINE_BYTES =
1760/// carrier_inline_bytes::<B>()`). The resolver constructs whichever
1761/// carrier variant its output requires — `Inline` for structural
1762/// identities that fit the inline width, `Borrowed` into resolver-owned
1763/// scratch, or `Stream` for unbounded structural sequences. There is no
1764/// caller-supplied `&mut [u8]` scratch and no per-ψ-stage byte-width
1765/// ceiling (ADR-060 amends the ADR-036/ADR-041 writer-style surface).
1766pub trait CohomologyGroupResolver<const INLINE_BYTES: usize, H>: __sdk_seal::Sealed {
1767 /// Resolve per-value content for this category.
1768 /// # Errors
1769 /// Returns [`crate::enforcement::ShapeViolation`] when the resolver
1770 /// cannot produce content (e.g., the foundation Null impl carrying
1771 /// the `RESOLVER_ABSENT` discriminator).
1772 fn resolve<'a>(
1773 &self,
1774 input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
1775 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>;
1776}
1777
1778/// ADR-036 resolver trait: ψ_7 — SimplicialComplex → PostnikovTower per ADR-035.
1779///
1780/// Parameterized by the model's H-axis (`H: Hasher` per ADR-022 D5) so
1781/// resolver impls compute content-addressed fingerprints using the
1782/// model's chosen hash impl. Sealed via [`__sdk_seal::Sealed`]: only
1783/// the SDK `resolver!` macro emits impls. Foundation provides a Null
1784/// impl whose `resolve` emits the `RESOLVER_ABSENT` shape violation.
1785///
1786/// ADR-060: `resolve` takes and returns a source-polymorphic
1787/// [`crate::pipeline::TermValue`] const-generic over the application's
1788/// foundation-derived inline width (`INLINE_BYTES =
1789/// carrier_inline_bytes::<B>()`). The resolver constructs whichever
1790/// carrier variant its output requires — `Inline` for structural
1791/// identities that fit the inline width, `Borrowed` into resolver-owned
1792/// scratch, or `Stream` for unbounded structural sequences. There is no
1793/// caller-supplied `&mut [u8]` scratch and no per-ψ-stage byte-width
1794/// ceiling (ADR-060 amends the ADR-036/ADR-041 writer-style surface).
1795pub trait PostnikovResolver<const INLINE_BYTES: usize, H>: __sdk_seal::Sealed {
1796 /// Resolve per-value content for this category.
1797 /// # Errors
1798 /// Returns [`crate::enforcement::ShapeViolation`] when the resolver
1799 /// cannot produce content (e.g., the foundation Null impl carrying
1800 /// the `RESOLVER_ABSENT` discriminator).
1801 fn resolve<'a>(
1802 &self,
1803 input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
1804 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>;
1805}
1806
1807/// ADR-036 resolver trait: ψ_8 — PostnikovTower → HomotopyGroups per ADR-035.
1808///
1809/// Parameterized by the model's H-axis (`H: Hasher` per ADR-022 D5) so
1810/// resolver impls compute content-addressed fingerprints using the
1811/// model's chosen hash impl. Sealed via [`__sdk_seal::Sealed`]: only
1812/// the SDK `resolver!` macro emits impls. Foundation provides a Null
1813/// impl whose `resolve` emits the `RESOLVER_ABSENT` shape violation.
1814///
1815/// ADR-060: `resolve` takes and returns a source-polymorphic
1816/// [`crate::pipeline::TermValue`] const-generic over the application's
1817/// foundation-derived inline width (`INLINE_BYTES =
1818/// carrier_inline_bytes::<B>()`). The resolver constructs whichever
1819/// carrier variant its output requires — `Inline` for structural
1820/// identities that fit the inline width, `Borrowed` into resolver-owned
1821/// scratch, or `Stream` for unbounded structural sequences. There is no
1822/// caller-supplied `&mut [u8]` scratch and no per-ψ-stage byte-width
1823/// ceiling (ADR-060 amends the ADR-036/ADR-041 writer-style surface).
1824pub trait HomotopyGroupResolver<const INLINE_BYTES: usize, H>: __sdk_seal::Sealed {
1825 /// Resolve per-value content for this category.
1826 /// # Errors
1827 /// Returns [`crate::enforcement::ShapeViolation`] when the resolver
1828 /// cannot produce content (e.g., the foundation Null impl carrying
1829 /// the `RESOLVER_ABSENT` discriminator).
1830 fn resolve<'a>(
1831 &self,
1832 input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
1833 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>;
1834}
1835
1836/// ADR-036 resolver trait: ψ_9 — HomotopyGroups → KInvariants per ADR-035.
1837///
1838/// Parameterized by the model's H-axis (`H: Hasher` per ADR-022 D5) so
1839/// resolver impls compute content-addressed fingerprints using the
1840/// model's chosen hash impl. Sealed via [`__sdk_seal::Sealed`]: only
1841/// the SDK `resolver!` macro emits impls. Foundation provides a Null
1842/// impl whose `resolve` emits the `RESOLVER_ABSENT` shape violation.
1843///
1844/// ADR-060: `resolve` takes and returns a source-polymorphic
1845/// [`crate::pipeline::TermValue`] const-generic over the application's
1846/// foundation-derived inline width (`INLINE_BYTES =
1847/// carrier_inline_bytes::<B>()`). The resolver constructs whichever
1848/// carrier variant its output requires — `Inline` for structural
1849/// identities that fit the inline width, `Borrowed` into resolver-owned
1850/// scratch, or `Stream` for unbounded structural sequences. There is no
1851/// caller-supplied `&mut [u8]` scratch and no per-ψ-stage byte-width
1852/// ceiling (ADR-060 amends the ADR-036/ADR-041 writer-style surface).
1853pub trait KInvariantResolver<const INLINE_BYTES: usize, H>: __sdk_seal::Sealed {
1854 /// Resolve per-value content for this category.
1855 /// # Errors
1856 /// Returns [`crate::enforcement::ShapeViolation`] when the resolver
1857 /// cannot produce content (e.g., the foundation Null impl carrying
1858 /// the `RESOLVER_ABSENT` discriminator).
1859 fn resolve<'a>(
1860 &self,
1861 input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
1862 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>;
1863}
1864
1865/// ADR-036 marker trait: ResolverTuple positions including a `NerveResolver`.
1866/// The `prism_model!` macro infers the where-clause bound for each
1867/// resolver-bound ψ-Term variant a verb body emits.
1868pub trait HasNerveResolver<const INLINE_BYTES: usize, H>: ResolverTuple {
1869 /// Returns the `NerveResolver` impl this ResolverTuple carries.
1870 fn nerve_resolver(&self) -> &dyn NerveResolver<INLINE_BYTES, H>;
1871}
1872
1873/// ADR-036 marker trait: ResolverTuple positions including a `ChainComplexResolver`.
1874/// The `prism_model!` macro infers the where-clause bound for each
1875/// resolver-bound ψ-Term variant a verb body emits.
1876pub trait HasChainComplexResolver<const INLINE_BYTES: usize, H>: ResolverTuple {
1877 /// Returns the `ChainComplexResolver` impl this ResolverTuple carries.
1878 fn chain_complex_resolver(&self) -> &dyn ChainComplexResolver<INLINE_BYTES, H>;
1879}
1880
1881/// ADR-036 marker trait: ResolverTuple positions including a `HomologyGroupResolver`.
1882/// The `prism_model!` macro infers the where-clause bound for each
1883/// resolver-bound ψ-Term variant a verb body emits.
1884pub trait HasHomologyGroupResolver<const INLINE_BYTES: usize, H>: ResolverTuple {
1885 /// Returns the `HomologyGroupResolver` impl this ResolverTuple carries.
1886 fn homology_group_resolver(&self) -> &dyn HomologyGroupResolver<INLINE_BYTES, H>;
1887}
1888
1889/// ADR-036 marker trait: ResolverTuple positions including a `CochainComplexResolver`.
1890/// The `prism_model!` macro infers the where-clause bound for each
1891/// resolver-bound ψ-Term variant a verb body emits.
1892pub trait HasCochainComplexResolver<const INLINE_BYTES: usize, H>: ResolverTuple {
1893 /// Returns the `CochainComplexResolver` impl this ResolverTuple carries.
1894 fn cochain_complex_resolver(&self) -> &dyn CochainComplexResolver<INLINE_BYTES, H>;
1895}
1896
1897/// ADR-036 marker trait: ResolverTuple positions including a `CohomologyGroupResolver`.
1898/// The `prism_model!` macro infers the where-clause bound for each
1899/// resolver-bound ψ-Term variant a verb body emits.
1900pub trait HasCohomologyGroupResolver<const INLINE_BYTES: usize, H>: ResolverTuple {
1901 /// Returns the `CohomologyGroupResolver` impl this ResolverTuple carries.
1902 fn cohomology_group_resolver(&self) -> &dyn CohomologyGroupResolver<INLINE_BYTES, H>;
1903}
1904
1905/// ADR-036 marker trait: ResolverTuple positions including a `PostnikovResolver`.
1906/// The `prism_model!` macro infers the where-clause bound for each
1907/// resolver-bound ψ-Term variant a verb body emits.
1908pub trait HasPostnikovResolver<const INLINE_BYTES: usize, H>: ResolverTuple {
1909 /// Returns the `PostnikovResolver` impl this ResolverTuple carries.
1910 fn postnikov_resolver(&self) -> &dyn PostnikovResolver<INLINE_BYTES, H>;
1911}
1912
1913/// ADR-036 marker trait: ResolverTuple positions including a `HomotopyGroupResolver`.
1914/// The `prism_model!` macro infers the where-clause bound for each
1915/// resolver-bound ψ-Term variant a verb body emits.
1916pub trait HasHomotopyGroupResolver<const INLINE_BYTES: usize, H>: ResolverTuple {
1917 /// Returns the `HomotopyGroupResolver` impl this ResolverTuple carries.
1918 fn homotopy_group_resolver(&self) -> &dyn HomotopyGroupResolver<INLINE_BYTES, H>;
1919}
1920
1921/// ADR-036 marker trait: ResolverTuple positions including a `KInvariantResolver`.
1922/// The `prism_model!` macro infers the where-clause bound for each
1923/// resolver-bound ψ-Term variant a verb body emits.
1924pub trait HasKInvariantResolver<const INLINE_BYTES: usize, H>: ResolverTuple {
1925 /// Returns the `KInvariantResolver` impl this ResolverTuple carries.
1926 fn k_invariant_resolver(&self) -> &dyn KInvariantResolver<INLINE_BYTES, H>;
1927}
1928
1929/// ADR-036 Null resolver tuple — the resolver-absent default.
1930/// `ResolverTuple` impl with `ARITY = 0` and an empty CATEGORIES list.
1931/// Applications that don't declare a `resolver!` block default to this.
1932#[derive(Debug, Clone, Copy, Default)]
1933pub struct NullResolverTuple;
1934
1935impl __sdk_seal::Sealed for NullResolverTuple {}
1936
1937impl ResolverTuple for NullResolverTuple {
1938 const ARITY: usize = 0;
1939 const CATEGORIES: &'static [ResolverCategory] = &[];
1940 // ADR-057: foundation built-in registry only — the empty
1941 // registry provider falls through to FOUNDATION_REGISTRY in
1942 // shape_iri_registry. Applications with their own recursive
1943 // shapes set this to their `register_shape!`-emitted marker.
1944 type ShapeRegistry = crate::pipeline::shape_iri_registry::EmptyShapeRegistry;
1945}
1946
1947/// ADR-036 Null `NerveResolver` impl. `resolve` always emits the
1948/// `RESOLVER_ABSENT` shape violation — the catamorphism translates this
1949/// into `PipelineFailure::ShapeViolation` recoverable via `Term::Try`'s
1950/// default-propagation handler (ADR-022 D3 G9).
1951#[derive(Debug, Default)]
1952pub struct NullNerveResolver<H>(core::marker::PhantomData<H>);
1953
1954impl<H> NullNerveResolver<H> {
1955 /// Construct a new Null resolver.
1956 #[must_use]
1957 pub const fn new() -> Self {
1958 Self(core::marker::PhantomData)
1959 }
1960}
1961
1962impl<H> __sdk_seal::Sealed for NullNerveResolver<H> {}
1963
1964impl<const INLINE_BYTES: usize, H> NerveResolver<INLINE_BYTES, H> for NullNerveResolver<H> {
1965 fn resolve<'a>(
1966 &self,
1967 _input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
1968 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>
1969 {
1970 Err(crate::enforcement::ShapeViolation {
1971 shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
1972 constraint_iri: "https://uor.foundation/resolver/Nerve",
1973 property_iri: "https://uor.foundation/resolver/category",
1974 expected_range: "https://uor.foundation/resolver/Resolver",
1975 min_count: 0,
1976 max_count: 1,
1977 kind: crate::ViolationKind::ValueCheck,
1978 })
1979 }
1980}
1981
1982/// ADR-036 Null `ChainComplexResolver` impl. `resolve` always emits the
1983/// `RESOLVER_ABSENT` shape violation — the catamorphism translates this
1984/// into `PipelineFailure::ShapeViolation` recoverable via `Term::Try`'s
1985/// default-propagation handler (ADR-022 D3 G9).
1986#[derive(Debug, Default)]
1987pub struct NullChainComplexResolver<H>(core::marker::PhantomData<H>);
1988
1989impl<H> NullChainComplexResolver<H> {
1990 /// Construct a new Null resolver.
1991 #[must_use]
1992 pub const fn new() -> Self {
1993 Self(core::marker::PhantomData)
1994 }
1995}
1996
1997impl<H> __sdk_seal::Sealed for NullChainComplexResolver<H> {}
1998
1999impl<const INLINE_BYTES: usize, H> ChainComplexResolver<INLINE_BYTES, H>
2000 for NullChainComplexResolver<H>
2001{
2002 fn resolve<'a>(
2003 &self,
2004 _input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
2005 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>
2006 {
2007 Err(crate::enforcement::ShapeViolation {
2008 shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2009 constraint_iri: "https://uor.foundation/resolver/ChainComplex",
2010 property_iri: "https://uor.foundation/resolver/category",
2011 expected_range: "https://uor.foundation/resolver/Resolver",
2012 min_count: 0,
2013 max_count: 1,
2014 kind: crate::ViolationKind::ValueCheck,
2015 })
2016 }
2017}
2018
2019/// ADR-036 Null `HomologyGroupResolver` impl. `resolve` always emits the
2020/// `RESOLVER_ABSENT` shape violation — the catamorphism translates this
2021/// into `PipelineFailure::ShapeViolation` recoverable via `Term::Try`'s
2022/// default-propagation handler (ADR-022 D3 G9).
2023#[derive(Debug, Default)]
2024pub struct NullHomologyGroupResolver<H>(core::marker::PhantomData<H>);
2025
2026impl<H> NullHomologyGroupResolver<H> {
2027 /// Construct a new Null resolver.
2028 #[must_use]
2029 pub const fn new() -> Self {
2030 Self(core::marker::PhantomData)
2031 }
2032}
2033
2034impl<H> __sdk_seal::Sealed for NullHomologyGroupResolver<H> {}
2035
2036impl<const INLINE_BYTES: usize, H> HomologyGroupResolver<INLINE_BYTES, H>
2037 for NullHomologyGroupResolver<H>
2038{
2039 fn resolve<'a>(
2040 &self,
2041 _input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
2042 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>
2043 {
2044 Err(crate::enforcement::ShapeViolation {
2045 shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2046 constraint_iri: "https://uor.foundation/resolver/HomologyGroup",
2047 property_iri: "https://uor.foundation/resolver/category",
2048 expected_range: "https://uor.foundation/resolver/Resolver",
2049 min_count: 0,
2050 max_count: 1,
2051 kind: crate::ViolationKind::ValueCheck,
2052 })
2053 }
2054}
2055
2056/// ADR-036 Null `CochainComplexResolver` impl. `resolve` always emits the
2057/// `RESOLVER_ABSENT` shape violation — the catamorphism translates this
2058/// into `PipelineFailure::ShapeViolation` recoverable via `Term::Try`'s
2059/// default-propagation handler (ADR-022 D3 G9).
2060#[derive(Debug, Default)]
2061pub struct NullCochainComplexResolver<H>(core::marker::PhantomData<H>);
2062
2063impl<H> NullCochainComplexResolver<H> {
2064 /// Construct a new Null resolver.
2065 #[must_use]
2066 pub const fn new() -> Self {
2067 Self(core::marker::PhantomData)
2068 }
2069}
2070
2071impl<H> __sdk_seal::Sealed for NullCochainComplexResolver<H> {}
2072
2073impl<const INLINE_BYTES: usize, H> CochainComplexResolver<INLINE_BYTES, H>
2074 for NullCochainComplexResolver<H>
2075{
2076 fn resolve<'a>(
2077 &self,
2078 _input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
2079 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>
2080 {
2081 Err(crate::enforcement::ShapeViolation {
2082 shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2083 constraint_iri: "https://uor.foundation/resolver/CochainComplex",
2084 property_iri: "https://uor.foundation/resolver/category",
2085 expected_range: "https://uor.foundation/resolver/Resolver",
2086 min_count: 0,
2087 max_count: 1,
2088 kind: crate::ViolationKind::ValueCheck,
2089 })
2090 }
2091}
2092
2093/// ADR-036 Null `CohomologyGroupResolver` impl. `resolve` always emits the
2094/// `RESOLVER_ABSENT` shape violation — the catamorphism translates this
2095/// into `PipelineFailure::ShapeViolation` recoverable via `Term::Try`'s
2096/// default-propagation handler (ADR-022 D3 G9).
2097#[derive(Debug, Default)]
2098pub struct NullCohomologyGroupResolver<H>(core::marker::PhantomData<H>);
2099
2100impl<H> NullCohomologyGroupResolver<H> {
2101 /// Construct a new Null resolver.
2102 #[must_use]
2103 pub const fn new() -> Self {
2104 Self(core::marker::PhantomData)
2105 }
2106}
2107
2108impl<H> __sdk_seal::Sealed for NullCohomologyGroupResolver<H> {}
2109
2110impl<const INLINE_BYTES: usize, H> CohomologyGroupResolver<INLINE_BYTES, H>
2111 for NullCohomologyGroupResolver<H>
2112{
2113 fn resolve<'a>(
2114 &self,
2115 _input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
2116 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>
2117 {
2118 Err(crate::enforcement::ShapeViolation {
2119 shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2120 constraint_iri: "https://uor.foundation/resolver/CohomologyGroup",
2121 property_iri: "https://uor.foundation/resolver/category",
2122 expected_range: "https://uor.foundation/resolver/Resolver",
2123 min_count: 0,
2124 max_count: 1,
2125 kind: crate::ViolationKind::ValueCheck,
2126 })
2127 }
2128}
2129
2130/// ADR-036 Null `PostnikovResolver` impl. `resolve` always emits the
2131/// `RESOLVER_ABSENT` shape violation — the catamorphism translates this
2132/// into `PipelineFailure::ShapeViolation` recoverable via `Term::Try`'s
2133/// default-propagation handler (ADR-022 D3 G9).
2134#[derive(Debug, Default)]
2135pub struct NullPostnikovResolver<H>(core::marker::PhantomData<H>);
2136
2137impl<H> NullPostnikovResolver<H> {
2138 /// Construct a new Null resolver.
2139 #[must_use]
2140 pub const fn new() -> Self {
2141 Self(core::marker::PhantomData)
2142 }
2143}
2144
2145impl<H> __sdk_seal::Sealed for NullPostnikovResolver<H> {}
2146
2147impl<const INLINE_BYTES: usize, H> PostnikovResolver<INLINE_BYTES, H> for NullPostnikovResolver<H> {
2148 fn resolve<'a>(
2149 &self,
2150 _input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
2151 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>
2152 {
2153 Err(crate::enforcement::ShapeViolation {
2154 shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2155 constraint_iri: "https://uor.foundation/resolver/Postnikov",
2156 property_iri: "https://uor.foundation/resolver/category",
2157 expected_range: "https://uor.foundation/resolver/Resolver",
2158 min_count: 0,
2159 max_count: 1,
2160 kind: crate::ViolationKind::ValueCheck,
2161 })
2162 }
2163}
2164
2165/// ADR-036 Null `HomotopyGroupResolver` impl. `resolve` always emits the
2166/// `RESOLVER_ABSENT` shape violation — the catamorphism translates this
2167/// into `PipelineFailure::ShapeViolation` recoverable via `Term::Try`'s
2168/// default-propagation handler (ADR-022 D3 G9).
2169#[derive(Debug, Default)]
2170pub struct NullHomotopyGroupResolver<H>(core::marker::PhantomData<H>);
2171
2172impl<H> NullHomotopyGroupResolver<H> {
2173 /// Construct a new Null resolver.
2174 #[must_use]
2175 pub const fn new() -> Self {
2176 Self(core::marker::PhantomData)
2177 }
2178}
2179
2180impl<H> __sdk_seal::Sealed for NullHomotopyGroupResolver<H> {}
2181
2182impl<const INLINE_BYTES: usize, H> HomotopyGroupResolver<INLINE_BYTES, H>
2183 for NullHomotopyGroupResolver<H>
2184{
2185 fn resolve<'a>(
2186 &self,
2187 _input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
2188 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>
2189 {
2190 Err(crate::enforcement::ShapeViolation {
2191 shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2192 constraint_iri: "https://uor.foundation/resolver/HomotopyGroup",
2193 property_iri: "https://uor.foundation/resolver/category",
2194 expected_range: "https://uor.foundation/resolver/Resolver",
2195 min_count: 0,
2196 max_count: 1,
2197 kind: crate::ViolationKind::ValueCheck,
2198 })
2199 }
2200}
2201
2202/// ADR-036 Null `KInvariantResolver` impl. `resolve` always emits the
2203/// `RESOLVER_ABSENT` shape violation — the catamorphism translates this
2204/// into `PipelineFailure::ShapeViolation` recoverable via `Term::Try`'s
2205/// default-propagation handler (ADR-022 D3 G9).
2206#[derive(Debug, Default)]
2207pub struct NullKInvariantResolver<H>(core::marker::PhantomData<H>);
2208
2209impl<H> NullKInvariantResolver<H> {
2210 /// Construct a new Null resolver.
2211 #[must_use]
2212 pub const fn new() -> Self {
2213 Self(core::marker::PhantomData)
2214 }
2215}
2216
2217impl<H> __sdk_seal::Sealed for NullKInvariantResolver<H> {}
2218
2219impl<const INLINE_BYTES: usize, H> KInvariantResolver<INLINE_BYTES, H>
2220 for NullKInvariantResolver<H>
2221{
2222 fn resolve<'a>(
2223 &self,
2224 _input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
2225 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>
2226 {
2227 Err(crate::enforcement::ShapeViolation {
2228 shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2229 constraint_iri: "https://uor.foundation/resolver/KInvariant",
2230 property_iri: "https://uor.foundation/resolver/category",
2231 expected_range: "https://uor.foundation/resolver/Resolver",
2232 min_count: 0,
2233 max_count: 1,
2234 kind: crate::ViolationKind::ValueCheck,
2235 })
2236 }
2237}
2238
2239/// ADR-036: NullResolverTuple satisfies `NerveResolver<H>` directly so
2240/// the `HasNerveResolver<H>` accessor can return `self` cast to `&dyn NerveResolver<H>`.
2241/// The `resolve` method emits the `RESOLVER_ABSENT` shape violation —
2242/// recoverable via `Term::Try`'s default-propagation handler (ADR-022 D3 G9).
2243impl<const INLINE_BYTES: usize, H> NerveResolver<INLINE_BYTES, H> for NullResolverTuple {
2244 fn resolve<'a>(
2245 &self,
2246 _input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
2247 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>
2248 {
2249 Err(crate::enforcement::ShapeViolation {
2250 shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2251 constraint_iri: "https://uor.foundation/resolver/Nerve",
2252 property_iri: "https://uor.foundation/resolver/category",
2253 expected_range: "https://uor.foundation/resolver/Resolver",
2254 min_count: 0,
2255 max_count: 1,
2256 kind: crate::ViolationKind::ValueCheck,
2257 })
2258 }
2259}
2260
2261/// ADR-036: NullResolverTuple satisfies `HasNerveResolver<INLINE_BYTES, H>` (returns `self`).
2262impl<const INLINE_BYTES: usize, H> HasNerveResolver<INLINE_BYTES, H> for NullResolverTuple {
2263 fn nerve_resolver(&self) -> &dyn NerveResolver<INLINE_BYTES, H> {
2264 self
2265 }
2266}
2267
2268/// ADR-036: NullResolverTuple satisfies `ChainComplexResolver<H>` directly so
2269/// the `HasChainComplexResolver<H>` accessor can return `self` cast to `&dyn ChainComplexResolver<H>`.
2270/// The `resolve` method emits the `RESOLVER_ABSENT` shape violation —
2271/// recoverable via `Term::Try`'s default-propagation handler (ADR-022 D3 G9).
2272impl<const INLINE_BYTES: usize, H> ChainComplexResolver<INLINE_BYTES, H> for NullResolverTuple {
2273 fn resolve<'a>(
2274 &self,
2275 _input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
2276 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>
2277 {
2278 Err(crate::enforcement::ShapeViolation {
2279 shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2280 constraint_iri: "https://uor.foundation/resolver/ChainComplex",
2281 property_iri: "https://uor.foundation/resolver/category",
2282 expected_range: "https://uor.foundation/resolver/Resolver",
2283 min_count: 0,
2284 max_count: 1,
2285 kind: crate::ViolationKind::ValueCheck,
2286 })
2287 }
2288}
2289
2290/// ADR-036: NullResolverTuple satisfies `HasChainComplexResolver<INLINE_BYTES, H>` (returns `self`).
2291impl<const INLINE_BYTES: usize, H> HasChainComplexResolver<INLINE_BYTES, H> for NullResolverTuple {
2292 fn chain_complex_resolver(&self) -> &dyn ChainComplexResolver<INLINE_BYTES, H> {
2293 self
2294 }
2295}
2296
2297/// ADR-036: NullResolverTuple satisfies `HomologyGroupResolver<H>` directly so
2298/// the `HasHomologyGroupResolver<H>` accessor can return `self` cast to `&dyn HomologyGroupResolver<H>`.
2299/// The `resolve` method emits the `RESOLVER_ABSENT` shape violation —
2300/// recoverable via `Term::Try`'s default-propagation handler (ADR-022 D3 G9).
2301impl<const INLINE_BYTES: usize, H> HomologyGroupResolver<INLINE_BYTES, H> for NullResolverTuple {
2302 fn resolve<'a>(
2303 &self,
2304 _input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
2305 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>
2306 {
2307 Err(crate::enforcement::ShapeViolation {
2308 shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2309 constraint_iri: "https://uor.foundation/resolver/HomologyGroup",
2310 property_iri: "https://uor.foundation/resolver/category",
2311 expected_range: "https://uor.foundation/resolver/Resolver",
2312 min_count: 0,
2313 max_count: 1,
2314 kind: crate::ViolationKind::ValueCheck,
2315 })
2316 }
2317}
2318
2319/// ADR-036: NullResolverTuple satisfies `HasHomologyGroupResolver<INLINE_BYTES, H>` (returns `self`).
2320impl<const INLINE_BYTES: usize, H> HasHomologyGroupResolver<INLINE_BYTES, H> for NullResolverTuple {
2321 fn homology_group_resolver(&self) -> &dyn HomologyGroupResolver<INLINE_BYTES, H> {
2322 self
2323 }
2324}
2325
2326/// ADR-036: NullResolverTuple satisfies `CochainComplexResolver<H>` directly so
2327/// the `HasCochainComplexResolver<H>` accessor can return `self` cast to `&dyn CochainComplexResolver<H>`.
2328/// The `resolve` method emits the `RESOLVER_ABSENT` shape violation —
2329/// recoverable via `Term::Try`'s default-propagation handler (ADR-022 D3 G9).
2330impl<const INLINE_BYTES: usize, H> CochainComplexResolver<INLINE_BYTES, H> for NullResolverTuple {
2331 fn resolve<'a>(
2332 &self,
2333 _input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
2334 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>
2335 {
2336 Err(crate::enforcement::ShapeViolation {
2337 shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2338 constraint_iri: "https://uor.foundation/resolver/CochainComplex",
2339 property_iri: "https://uor.foundation/resolver/category",
2340 expected_range: "https://uor.foundation/resolver/Resolver",
2341 min_count: 0,
2342 max_count: 1,
2343 kind: crate::ViolationKind::ValueCheck,
2344 })
2345 }
2346}
2347
2348/// ADR-036: NullResolverTuple satisfies `HasCochainComplexResolver<INLINE_BYTES, H>` (returns `self`).
2349impl<const INLINE_BYTES: usize, H> HasCochainComplexResolver<INLINE_BYTES, H>
2350 for NullResolverTuple
2351{
2352 fn cochain_complex_resolver(&self) -> &dyn CochainComplexResolver<INLINE_BYTES, H> {
2353 self
2354 }
2355}
2356
2357/// ADR-036: NullResolverTuple satisfies `CohomologyGroupResolver<H>` directly so
2358/// the `HasCohomologyGroupResolver<H>` accessor can return `self` cast to `&dyn CohomologyGroupResolver<H>`.
2359/// The `resolve` method emits the `RESOLVER_ABSENT` shape violation —
2360/// recoverable via `Term::Try`'s default-propagation handler (ADR-022 D3 G9).
2361impl<const INLINE_BYTES: usize, H> CohomologyGroupResolver<INLINE_BYTES, H> for NullResolverTuple {
2362 fn resolve<'a>(
2363 &self,
2364 _input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
2365 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>
2366 {
2367 Err(crate::enforcement::ShapeViolation {
2368 shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2369 constraint_iri: "https://uor.foundation/resolver/CohomologyGroup",
2370 property_iri: "https://uor.foundation/resolver/category",
2371 expected_range: "https://uor.foundation/resolver/Resolver",
2372 min_count: 0,
2373 max_count: 1,
2374 kind: crate::ViolationKind::ValueCheck,
2375 })
2376 }
2377}
2378
2379/// ADR-036: NullResolverTuple satisfies `HasCohomologyGroupResolver<INLINE_BYTES, H>` (returns `self`).
2380impl<const INLINE_BYTES: usize, H> HasCohomologyGroupResolver<INLINE_BYTES, H>
2381 for NullResolverTuple
2382{
2383 fn cohomology_group_resolver(&self) -> &dyn CohomologyGroupResolver<INLINE_BYTES, H> {
2384 self
2385 }
2386}
2387
2388/// ADR-036: NullResolverTuple satisfies `PostnikovResolver<H>` directly so
2389/// the `HasPostnikovResolver<H>` accessor can return `self` cast to `&dyn PostnikovResolver<H>`.
2390/// The `resolve` method emits the `RESOLVER_ABSENT` shape violation —
2391/// recoverable via `Term::Try`'s default-propagation handler (ADR-022 D3 G9).
2392impl<const INLINE_BYTES: usize, H> PostnikovResolver<INLINE_BYTES, H> for NullResolverTuple {
2393 fn resolve<'a>(
2394 &self,
2395 _input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
2396 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>
2397 {
2398 Err(crate::enforcement::ShapeViolation {
2399 shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2400 constraint_iri: "https://uor.foundation/resolver/Postnikov",
2401 property_iri: "https://uor.foundation/resolver/category",
2402 expected_range: "https://uor.foundation/resolver/Resolver",
2403 min_count: 0,
2404 max_count: 1,
2405 kind: crate::ViolationKind::ValueCheck,
2406 })
2407 }
2408}
2409
2410/// ADR-036: NullResolverTuple satisfies `HasPostnikovResolver<INLINE_BYTES, H>` (returns `self`).
2411impl<const INLINE_BYTES: usize, H> HasPostnikovResolver<INLINE_BYTES, H> for NullResolverTuple {
2412 fn postnikov_resolver(&self) -> &dyn PostnikovResolver<INLINE_BYTES, H> {
2413 self
2414 }
2415}
2416
2417/// ADR-036: NullResolverTuple satisfies `HomotopyGroupResolver<H>` directly so
2418/// the `HasHomotopyGroupResolver<H>` accessor can return `self` cast to `&dyn HomotopyGroupResolver<H>`.
2419/// The `resolve` method emits the `RESOLVER_ABSENT` shape violation —
2420/// recoverable via `Term::Try`'s default-propagation handler (ADR-022 D3 G9).
2421impl<const INLINE_BYTES: usize, H> HomotopyGroupResolver<INLINE_BYTES, H> for NullResolverTuple {
2422 fn resolve<'a>(
2423 &self,
2424 _input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
2425 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>
2426 {
2427 Err(crate::enforcement::ShapeViolation {
2428 shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2429 constraint_iri: "https://uor.foundation/resolver/HomotopyGroup",
2430 property_iri: "https://uor.foundation/resolver/category",
2431 expected_range: "https://uor.foundation/resolver/Resolver",
2432 min_count: 0,
2433 max_count: 1,
2434 kind: crate::ViolationKind::ValueCheck,
2435 })
2436 }
2437}
2438
2439/// ADR-036: NullResolverTuple satisfies `HasHomotopyGroupResolver<INLINE_BYTES, H>` (returns `self`).
2440impl<const INLINE_BYTES: usize, H> HasHomotopyGroupResolver<INLINE_BYTES, H> for NullResolverTuple {
2441 fn homotopy_group_resolver(&self) -> &dyn HomotopyGroupResolver<INLINE_BYTES, H> {
2442 self
2443 }
2444}
2445
2446/// ADR-036: NullResolverTuple satisfies `KInvariantResolver<H>` directly so
2447/// the `HasKInvariantResolver<H>` accessor can return `self` cast to `&dyn KInvariantResolver<H>`.
2448/// The `resolve` method emits the `RESOLVER_ABSENT` shape violation —
2449/// recoverable via `Term::Try`'s default-propagation handler (ADR-022 D3 G9).
2450impl<const INLINE_BYTES: usize, H> KInvariantResolver<INLINE_BYTES, H> for NullResolverTuple {
2451 fn resolve<'a>(
2452 &self,
2453 _input: crate::pipeline::TermValue<'a, INLINE_BYTES>,
2454 ) -> Result<crate::pipeline::TermValue<'a, INLINE_BYTES>, crate::enforcement::ShapeViolation>
2455 {
2456 Err(crate::enforcement::ShapeViolation {
2457 shape_iri: "https://uor.foundation/resolver/RESOLVER_ABSENT",
2458 constraint_iri: "https://uor.foundation/resolver/KInvariant",
2459 property_iri: "https://uor.foundation/resolver/category",
2460 expected_range: "https://uor.foundation/resolver/Resolver",
2461 min_count: 0,
2462 max_count: 1,
2463 kind: crate::ViolationKind::ValueCheck,
2464 })
2465 }
2466}
2467
2468/// ADR-036: NullResolverTuple satisfies `HasKInvariantResolver<INLINE_BYTES, H>` (returns `self`).
2469impl<const INLINE_BYTES: usize, H> HasKInvariantResolver<INLINE_BYTES, H> for NullResolverTuple {
2470 fn k_invariant_resolver(&self) -> &dyn KInvariantResolver<INLINE_BYTES, H> {
2471 self
2472 }
2473}
2474
2475/// Wiki ADR-042: typed view over a successful `Grounded<T, Tag>` as the
2476/// inhabitance-verdict envelope produced by the canonical k-invariants
2477/// branch (ψ_1 → ψ_7 → ψ_8 → ψ_9 per ADR-035).
2478///
2479/// Zero-cost — `#[repr(transparent)]` over `&'a Grounded<T, Tag>`. Construct
2480/// via [`crate::enforcement::Grounded::as_inhabitance_certificate`].
2481///
2482/// Realizes `cert:InhabitanceCertificate` per the ontology
2483/// (`<https://uor.foundation/cert/InhabitanceCertificate>`). Foundation
2484/// uses the suffix `View` here to distinguish this zero-cost typed
2485/// view from the existing sealed-shim `crate::enforcement::InhabitanceCertificate`
2486/// value carrier; the wiki names this type `InhabitanceCertificate<'a, T>`
2487/// in ADR-042 and the typed-view role is the load-bearing concern. The κ-label,
2488/// concrete `cert:witness`, and `cert:searchTrace` are accessor methods
2489/// over the underlying `Grounded` — no allocation, no per-application
2490/// re-derivation.
2491#[repr(transparent)]
2492#[derive(Debug, Clone, Copy)]
2493pub struct InhabitanceCertificateView<
2494 'a,
2495 T: crate::enforcement::GroundedShape,
2496 const INLINE_BYTES: usize,
2497 const FP_MAX: usize = 32,
2498 Tag = T,
2499>(pub &'a crate::enforcement::Grounded<'a, T, INLINE_BYTES, FP_MAX, Tag>);
2500
2501impl<
2502 'a,
2503 T: crate::enforcement::GroundedShape,
2504 const INLINE_BYTES: usize,
2505 const FP_MAX: usize,
2506 Tag,
2507 > InhabitanceCertificateView<'a, T, INLINE_BYTES, FP_MAX, Tag>
2508{
2509 /// The κ-label — the homotopy-classification structural witness at
2510 /// ψ_9 per ADR-035. The bytes are the `Term::KInvariants` emission's
2511 /// output (exposed via `Grounded::output_bytes`) wrapped in the
2512 /// ADR-041 typed-coordinate carrier so cross-stage composition is
2513 /// type-checked.
2514 #[inline]
2515 #[must_use]
2516 pub fn kappa_label(&self) -> KInvariantsBytes<'_> {
2517 KInvariantsBytes(self.0.output_bytes())
2518 }
2519
2520 /// The concrete `cert:witness` ValueTuple — derivable from
2521 /// `Term::Nerve`'s 0-simplices at ψ_1 (the per-value bytes the
2522 /// model's NerveResolver consumed). Foundation exposes the
2523 /// `Grounded`'s bindings as the value-tuple surface; applications
2524 /// whose admission relations carry richer witness data project
2525 /// through the binding table's content addresses.
2526 #[inline]
2527 #[must_use]
2528 pub fn witness(&self) -> WitnessValueTuple<'_> {
2529 WitnessValueTuple {
2530 grounded_bindings: self.0,
2531 }
2532 }
2533
2534 /// The `cert:searchTrace` — realized as
2535 /// `<Grounded>::derivation::<H>(...).replay::<...>()` per ADR-039.
2536 /// Foundation surfaces the derivation pointer; applications choose
2537 /// which `Hasher` impl and `HostBounds` parameters to instantiate
2538 /// the replay with.
2539 #[inline]
2540 #[must_use]
2541 pub fn certificate(
2542 &self,
2543 ) -> &crate::enforcement::Validated<crate::enforcement::GroundingCertificate<FP_MAX>> {
2544 self.0.certificate()
2545 }
2546
2547 /// The certified type's stable IRI — the `<T as ConstrainedTypeShape>::IRI`
2548 /// the application registered as the route's output shape.
2549 #[inline]
2550 #[must_use]
2551 pub fn certified_type(&self) -> &'static str
2552 where
2553 T: ConstrainedTypeShape,
2554 {
2555 <T as ConstrainedTypeShape>::IRI
2556 }
2557}
2558
2559/// Wiki ADR-042: concrete `cert:witness` ValueTuple view. Borrows the
2560/// underlying `Grounded`'s bindings; iterates as `(content_address, bytes)`
2561/// pairs corresponding to `Term::Nerve`'s 0-simplices.
2562#[derive(Clone, Copy)]
2563pub struct WitnessValueTuple<'a> {
2564 /// Foundation-internal: the underlying Grounded reference. Public-API
2565 /// access goes through the accessor methods.
2566 grounded_bindings: &'a dyn WitnessTupleSource,
2567}
2568
2569impl<'a> core::fmt::Debug for WitnessValueTuple<'a> {
2570 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2571 f.debug_struct("WitnessValueTuple")
2572 .field("binding_count", &self.grounded_bindings.binding_count())
2573 .finish()
2574 }
2575}
2576
2577impl<'a> WitnessValueTuple<'a> {
2578 /// Number of bindings in the witness tuple.
2579 #[inline]
2580 #[must_use]
2581 pub fn len(&self) -> usize {
2582 self.grounded_bindings.binding_count()
2583 }
2584
2585 /// Whether the witness tuple is empty.
2586 #[inline]
2587 #[must_use]
2588 pub fn is_empty(&self) -> bool {
2589 self.grounded_bindings.binding_count() == 0
2590 }
2591
2592 /// The witness's content-addressed binding bytes at position `idx`.
2593 /// Returns `None` if `idx >= len()`.
2594 #[inline]
2595 #[must_use]
2596 pub fn binding_bytes(&self, idx: usize) -> Option<&'static [u8]> {
2597 self.grounded_bindings.binding_bytes_at(idx)
2598 }
2599}
2600
2601/// Foundation-internal trait letting `Grounded<T, Tag>` expose binding
2602/// access to `WitnessValueTuple` without leaking the generic-parameter
2603/// plumbing into the witness-view type.
2604pub trait WitnessTupleSource {
2605 /// Number of bindings in this source's table.
2606 fn binding_count(&self) -> usize;
2607 /// Binding bytes at the given index, or `None` if out of range.
2608 fn binding_bytes_at(&self, idx: usize) -> Option<&'static [u8]>;
2609}
2610
2611impl<
2612 'a,
2613 T: crate::enforcement::GroundedShape,
2614 const INLINE_BYTES: usize,
2615 const FP_MAX: usize,
2616 Tag,
2617 > WitnessTupleSource for crate::enforcement::Grounded<'a, T, INLINE_BYTES, FP_MAX, Tag>
2618{
2619 fn binding_count(&self) -> usize {
2620 self.iter_bindings().count()
2621 }
2622 fn binding_bytes_at(&self, idx: usize) -> Option<&'static [u8]> {
2623 self.iter_bindings().nth(idx).map(|e| e.bytes)
2624 }
2625}
2626
2627/// Wiki ADR-042: typed view over an `Err(PipelineFailure)` as the
2628/// inhabitance-impossibility envelope. Realizes
2629/// `cert:InhabitanceImpossibilityCertificate` per the ontology:
2630/// `<https://uor.foundation/cert/InhabitanceImpossibilityCertificate>`.
2631///
2632/// Zero-cost — `#[repr(transparent)]` over `&'a PipelineFailure`. Construct
2633/// via [`crate::enforcement::PipelineFailure::as_inhabitance_impossibility_certificate`].
2634#[repr(transparent)]
2635#[derive(Debug, Clone, Copy)]
2636pub struct InhabitanceImpossibilityCertificate<'a>(pub &'a crate::enforcement::PipelineFailure);
2637
2638impl<'a> InhabitanceImpossibilityCertificate<'a> {
2639 /// The contradiction-proof bytes — canonical-form encoding of the
2640 /// failure trace, suitable for Lean 4 by-contradiction reconstruction
2641 /// per ADR-039 + ADR-042. Foundation provides the shape-violation's
2642 /// `shape_iri` bytes as the proof's canonical-form witness; richer
2643 /// contradiction data is application-provided via the failure trace.
2644 #[inline]
2645 #[must_use]
2646 pub fn contradiction_proof(&self) -> &'static [u8] {
2647 match self.0 {
2648 crate::enforcement::PipelineFailure::ShapeViolation { report } => {
2649 report.shape_iri.as_bytes()
2650 }
2651 _ => b"https://uor.foundation/proof/InhabitanceImpossibilityWitness",
2652 }
2653 }
2654
2655 /// Borrow the underlying `PipelineFailure`.
2656 #[inline]
2657 #[must_use]
2658 pub fn failure(&self) -> &crate::enforcement::PipelineFailure {
2659 self.0
2660 }
2661}
2662
2663/// Wiki ADR-042: typed verdict-envelope accessors on `PipelineFailure`.
2664impl crate::enforcement::PipelineFailure {
2665 /// Borrow `self` as an [`InhabitanceImpossibilityCertificate`] view
2666 /// when the failure's structural cause is an inhabitance-impossibility
2667 /// witness (per ADR-042). Returns `Some` for `ShapeViolation` whose
2668 /// `shape_iri` carries one of the foundation-declared inhabitance
2669 /// proof IRIs (e.g. `RESOLVER_ABSENT`, the constraint-nerve-empty-
2670 /// Kan-completion sentinel); foundation accepts `Some(...)` universally
2671 /// for `PipelineFailure` so applications consume the verdict-envelope
2672 /// view at their discretion.
2673 #[inline]
2674 #[must_use]
2675 pub fn as_inhabitance_impossibility_certificate(
2676 &self,
2677 ) -> Option<InhabitanceImpossibilityCertificate<'_>> {
2678 Some(InhabitanceImpossibilityCertificate(self))
2679 }
2680}
2681
2682/// Wiki ADR-042: `predicate:InhabitanceDispatchTable` consultation helper.
2683/// Application NerveResolver impls MAY call this helper internally for
2684/// decider routing across the ontology's three canonical rule arms
2685/// (TwoSatDecider, HornSatDecider, ResidualVerdictResolver). Foundation
2686/// provides the dispatch surface; rule-arm semantics are application-
2687/// provided through the closures the caller threads in.
2688pub mod inhabitance {
2689 /// Three rule arms a `predicate:InhabitanceDispatchTable` consultation
2690 /// dispatches through, per the ontology's canonical decider routing.
2691 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2692 pub enum InhabitanceRuleArm {
2693 /// TwoSatDecider — `predicate:TwoSatDecider`. Decides 2-SAT constraint nerves.
2694 TwoSatDecider,
2695 /// HornSatDecider — `predicate:HornSatDecider`. Decides Horn-SAT constraint nerves.
2696 HornSatDecider,
2697 /// ResidualVerdictResolver — `predicate:ResidualVerdictResolver`. Residual
2698 /// catch-all for constraint nerves outside the 2-SAT / Horn-SAT coverage.
2699 ResidualVerdictResolver,
2700 }
2701
2702 /// Dispatch through the `predicate:InhabitanceDispatchTable` rule arms in
2703 /// ontology order (TwoSatDecider → HornSatDecider → ResidualVerdictResolver).
2704 /// Each closure returns `Some(verdict)` if the arm decides, or `None` to
2705 /// delegate to the next arm. Returns the first decisive verdict.
2706 #[inline]
2707 pub fn dispatch_through_table<F1, F2, F3, V>(
2708 two_sat: F1,
2709 horn_sat: F2,
2710 residual: F3,
2711 ) -> (InhabitanceRuleArm, V)
2712 where
2713 F1: FnOnce() -> Option<V>,
2714 F2: FnOnce() -> Option<V>,
2715 F3: FnOnce() -> V,
2716 {
2717 if let Some(v) = two_sat() {
2718 return (InhabitanceRuleArm::TwoSatDecider, v);
2719 }
2720 if let Some(v) = horn_sat() {
2721 return (InhabitanceRuleArm::HornSatDecider, v);
2722 }
2723 (InhabitanceRuleArm::ResidualVerdictResolver, residual())
2724 }
2725}
2726
2727/// Wiki ADR-049: substrate-level typed-observable predicate trait.
2728///
2729/// Implementors are foundation-declared typed observables that read a
2730/// structural property of a digest and evaluate a single-bit predicate
2731/// against the read value. Sealed via [`__sdk_seal::Sealed`]; the foundation
2732/// declares the closed catalog of 4 typed observables consuming the
2733/// canonical hash axis's σ-projection per ADR-047.
2734///
2735/// `SingletonCommitment<P>` wraps one of these to form a 1-bit
2736/// typed-commitment per ADR-048; `AndCommitment` composes any number.
2737pub trait ObservablePredicate: Copy + __sdk_seal::Sealed {
2738 /// PRF acceptance probability under the Hardening Principle's U1
2739 /// (ADR-047). Used by `SingletonCommitment<P>` per ADR-048 for
2740 /// bandwidth and accept_prob propagation.
2741 fn accept_prob(&self) -> f64;
2742
2743 /// Read the structural property from `digest` and return the
2744 /// predicate's boolean satisfaction.
2745 fn evaluate(&self, digest: &[u8]) -> bool;
2746
2747 /// Foundation-vetted Observable IRI per ADR-038's closed catalog.
2748 /// Surfaces in the `CommitmentEvaluated` trace event per ADR-048.
2749 fn observable_iri(&self) -> &'static str;
2750}
2751
2752/// Wiki ADR-049: p-adic stratum observable per
2753/// `observable:StratumObservable`. `value(d) = ν_p(d)` — the p-adic
2754/// valuation of `d` viewed as a big-endian integer. Predicate:
2755/// `value(d) == k`.
2756#[derive(Debug, Clone, Copy)]
2757pub struct Stratum<const P: u32> {
2758 /// Target p-adic valuation the predicate accepts.
2759 pub k: u32,
2760}
2761
2762impl<const P: u32> __sdk_seal::Sealed for Stratum<P> {}
2763
2764impl<const P: u32> ObservablePredicate for Stratum<P> {
2765 fn accept_prob(&self) -> f64 {
2766 // P(ν_p(d) = k) = (p-1) / p^(k+1)
2767 let p = P as f64;
2768 if p <= 1.0 {
2769 return 0.0;
2770 }
2771 (p - 1.0) / libm::pow(p, (self.k as i32 + 1) as f64)
2772 }
2773 fn evaluate(&self, digest: &[u8]) -> bool {
2774 // ν_p over the big-endian integer view of `digest`. The valuation
2775 // is the count of factors of `p` dividing the big-endian value;
2776 // operationally: scan the digest's trailing bits modulo p.
2777 crate::pipeline::stratum_p_adic_valuation_be(digest, P) == self.k
2778 }
2779 fn observable_iri(&self) -> &'static str {
2780 match P {
2781 2 => "https://uor.foundation/observable/Stratum/2",
2782 3 => "https://uor.foundation/observable/Stratum/3",
2783 5 => "https://uor.foundation/observable/Stratum/5",
2784 7 => "https://uor.foundation/observable/Stratum/7",
2785 _ => "https://uor.foundation/observable/Stratum/other",
2786 }
2787 }
2788}
2789
2790/// Wiki ADR-049: Walsh–Hadamard parity observable under the new
2791/// `observable:SpectralObservable` subclass. `value(d, ω) =
2792/// popcount(d & ω) mod 2` — the WH parity at frequency ω.
2793/// Predicate: `value(d) == expected`.
2794#[derive(Debug, Clone, Copy)]
2795pub struct WalshHadamardParity {
2796 /// Frequency mask ω as a byte sequence; bitwise-AND with the digest
2797 /// selects the bits the parity sums over.
2798 pub frequency: &'static [u8],
2799 /// Expected parity value (true = odd-popcount, false = even).
2800 pub expected: bool,
2801}
2802
2803impl __sdk_seal::Sealed for WalshHadamardParity {}
2804
2805impl ObservablePredicate for WalshHadamardParity {
2806 fn accept_prob(&self) -> f64 {
2807 // Per Hardening Principle U1 + U3 (ADR-047), a UOR-hardened axis
2808 // produces uniformly random WH parities — accept probability 1/2.
2809 0.5
2810 }
2811 fn evaluate(&self, digest: &[u8]) -> bool {
2812 crate::pipeline::walsh_hadamard_parity(digest, self.frequency) == self.expected
2813 }
2814 fn observable_iri(&self) -> &'static str {
2815 "https://uor.foundation/observable/WalshHadamardParity"
2816 }
2817}
2818
2819/// Wiki ADR-049: p-adic ultrametric distance observable per
2820/// `observable:MetricObservable`. `value(d, r) = ν_p(d XOR r)` —
2821/// the ultrametric distance from `d` to a reference `r`. Predicate:
2822/// `value(d) >= k`.
2823#[derive(Debug, Clone, Copy)]
2824pub struct UltrametricCloseTo<const P: u32> {
2825 /// Reference digest. The predicate's distance computation XORs
2826 /// the candidate digest against this reference.
2827 pub reference: &'static [u8],
2828 /// Threshold: accept when ν_p(d XOR reference) >= k.
2829 pub k: u32,
2830}
2831
2832impl<const P: u32> __sdk_seal::Sealed for UltrametricCloseTo<P> {}
2833
2834impl<const P: u32> ObservablePredicate for UltrametricCloseTo<P> {
2835 fn accept_prob(&self) -> f64 {
2836 // P(ν_p(d XOR r) >= k) = 1 / p^k
2837 let p = P as f64;
2838 if p <= 1.0 {
2839 return 0.0;
2840 }
2841 1.0 / libm::pow(p, self.k as f64)
2842 }
2843 fn evaluate(&self, digest: &[u8]) -> bool {
2844 crate::pipeline::stratum_p_adic_valuation_xor_be(digest, self.reference, P) >= self.k
2845 }
2846 fn observable_iri(&self) -> &'static str {
2847 match P {
2848 2 => "https://uor.foundation/observable/UltrametricCloseTo/2",
2849 3 => "https://uor.foundation/observable/UltrametricCloseTo/3",
2850 5 => "https://uor.foundation/observable/UltrametricCloseTo/5",
2851 7 => "https://uor.foundation/observable/UltrametricCloseTo/7",
2852 _ => "https://uor.foundation/observable/UltrametricCloseTo/other",
2853 }
2854 }
2855}
2856
2857/// Wiki ADR-049: affine-pinned bit observable under
2858/// `observable:StratumObservable`. `value(d, bit) =
2859/// d[bit/8] >> (bit%8) & 1`. Predicate: `value(d) == expected`. Used by
2860/// application-declared payload commitments encoding K bits at K disjoint
2861/// single-bit positions.
2862#[derive(Debug, Clone, Copy)]
2863pub struct AffineParity {
2864 /// Bit index into the digest. `bit_index / 8` is the byte;
2865 /// `bit_index % 8` is the bit position within the byte.
2866 pub bit_index: u32,
2867 /// Expected single-bit value (true = 1, false = 0).
2868 pub expected: bool,
2869}
2870
2871impl __sdk_seal::Sealed for AffineParity {}
2872
2873impl ObservablePredicate for AffineParity {
2874 fn accept_prob(&self) -> f64 {
2875 // Per U1: a UOR-hardened axis produces uniformly random bits.
2876 0.5
2877 }
2878 fn evaluate(&self, digest: &[u8]) -> bool {
2879 crate::pipeline::single_bit_value(digest, self.bit_index) == self.expected
2880 }
2881 fn observable_iri(&self) -> &'static str {
2882 "https://uor.foundation/observable/AffineParity"
2883 }
2884}
2885
2886/// Wiki ADR-040 + ADR-049: byte-sequence threshold observable under
2887/// `observable:ValueThresholdObservable`. Predicate form:
2888/// `(digest as big-endian unsigned integer) <= (target as
2889/// big-endian unsigned integer)`. The target byte sequence IS the
2890/// argument; the predicate's accept_prob under U1 is
2891/// `(target_int + 1) / 2^(8 * width)`.
2892/// Realizes the `type:LexicographicLessEqBound` catalog primitive per
2893/// ADR-040; consumed by `TargetCommitment = SingletonCommitment<Self>`
2894/// as the canonical search-cost commitment per ADR-048.
2895#[derive(Debug, Clone, Copy)]
2896pub struct LexicographicLessEqThreshold {
2897 /// Byte-sequence target for the threshold comparison. The
2898 /// predicate accepts iff the digest's big-endian unsigned
2899 /// integer value is `<= target`'s big-endian unsigned integer
2900 /// value. Both operands are right-aligned via leading zero pad
2901 /// when lengths differ.
2902 pub target: &'static [u8],
2903}
2904
2905impl __sdk_seal::Sealed for LexicographicLessEqThreshold {}
2906
2907impl ObservablePredicate for LexicographicLessEqThreshold {
2908 fn accept_prob(&self) -> f64 {
2909 // ADR-040: accept_prob = (target_int + 1) / 2^(8 * width).
2910 // Compute via f64 with mantissa headroom: take the leading
2911 // bytes of the target into a u64 tail and divide by the
2912 // corresponding 2^bits. For widths > 8 bytes the tail-truncation
2913 // is conservative (returns the lower bound on the probability,
2914 // since high bytes carry the most significant magnitude).
2915 let width = self.target.len();
2916 if width == 0 {
2917 return 0.0;
2918 }
2919 let head = if width <= 8 { width } else { 8 };
2920 let mut head_be = [0u8; 8];
2921 head_be[8 - head..].copy_from_slice(&self.target[..head]);
2922 let target_int = u64::from_be_bytes(head_be);
2923 let denom_bits = (head * 8) as u32;
2924 // (target_int + 1) / 2^denom_bits — the +1 accounts for the
2925 // <=-inclusive boundary at target_int itself.
2926 let denom = if denom_bits >= 64 {
2927 // u64::MAX + 1 in f64-safe arithmetic.
2928 (u64::MAX as f64) + 1.0
2929 } else {
2930 (1u64 << denom_bits) as f64
2931 };
2932 ((target_int as f64) + 1.0) / denom
2933 }
2934 fn evaluate(&self, digest: &[u8]) -> bool {
2935 // Big-endian unsigned comparison: pad the shorter operand with
2936 // leading zeros to max(len). Compare byte-by-byte from MSB.
2937 let max_len = digest.len().max(self.target.len());
2938 let mut i = 0usize;
2939 while i < max_len {
2940 let d = if i + digest.len() >= max_len {
2941 digest[i + digest.len() - max_len]
2942 } else {
2943 0u8
2944 };
2945 let t = if i + self.target.len() >= max_len {
2946 self.target[i + self.target.len() - max_len]
2947 } else {
2948 0u8
2949 };
2950 if d < t {
2951 return true;
2952 }
2953 if d > t {
2954 return false;
2955 }
2956 i += 1;
2957 }
2958 // Equal — `<=` accepts.
2959 true
2960 }
2961 fn observable_iri(&self) -> &'static str {
2962 "https://uor.foundation/observable/LexicographicLessEqThreshold"
2963 }
2964}
2965
2966/// Wiki ADR-049: p-adic valuation of a big-endian byte sequence as
2967/// an integer. Returns ν_p(n) where n is the big-endian unsigned
2968/// integer view of `digest`. Convention: ν_p(0) = digest.len() * 8
2969/// (the canonical sentinel for the all-zeros valuation).
2970#[must_use]
2971pub fn stratum_p_adic_valuation_be(digest: &[u8], p: u32) -> u32 {
2972 if p < 2 {
2973 return 0;
2974 }
2975 // Walk the digest from the least-significant byte (last byte in BE)
2976 // backwards, counting factors of p divided out of the remainder.
2977 // For very large digests we approximate via the last 8 bytes; this
2978 // matches the wiki's canonical interpretation as a u64-tail valuation.
2979 let tail_len = digest.len().min(8);
2980 let mut tail_bytes = [0u8; 8];
2981 tail_bytes[8 - tail_len..].copy_from_slice(&digest[digest.len() - tail_len..]);
2982 let mut n = u64::from_be_bytes(tail_bytes);
2983 if n == 0 {
2984 return (digest.len() * 8) as u32;
2985 }
2986 let p64 = p as u64;
2987 let mut v = 0u32;
2988 while n % p64 == 0 {
2989 n /= p64;
2990 v += 1;
2991 }
2992 v
2993}
2994
2995/// Wiki ADR-049: p-adic valuation of `digest XOR reference`. Used by
2996/// `UltrametricCloseTo<P>::evaluate`. Pads the shorter operand with
2997/// leading zero bytes per the canonical big-endian unsigned convention.
2998#[must_use]
2999pub fn stratum_p_adic_valuation_xor_be(digest: &[u8], reference: &[u8], p: u32) -> u32 {
3000 let len = digest.len().max(reference.len());
3001 if len == 0 {
3002 return 0;
3003 }
3004 let mut tail = [0u8; 8];
3005 let tlen = len.min(8);
3006 for i in 0..tlen {
3007 let d = if i < digest.len() {
3008 digest[digest.len() - 1 - i]
3009 } else {
3010 0
3011 };
3012 let r = if i < reference.len() {
3013 reference[reference.len() - 1 - i]
3014 } else {
3015 0
3016 };
3017 tail[8 - 1 - i] = d ^ r;
3018 }
3019 stratum_p_adic_valuation_be(&tail, p)
3020}
3021
3022/// Wiki ADR-049: Walsh–Hadamard parity at frequency ω. `popcount(d & ω) mod 2`.
3023#[must_use]
3024pub fn walsh_hadamard_parity(digest: &[u8], frequency: &[u8]) -> bool {
3025 let len = digest.len().min(frequency.len());
3026 let mut parity = 0u32;
3027 for i in 0..len {
3028 parity ^= (digest[i] & frequency[i]).count_ones();
3029 }
3030 parity & 1 == 1
3031}
3032
3033/// Wiki ADR-049: read a single bit from `digest` at `bit_index`.
3034#[must_use]
3035pub fn single_bit_value(digest: &[u8], bit_index: u32) -> bool {
3036 let byte_idx = (bit_index / 8) as usize;
3037 let bit_off = bit_index % 8;
3038 if byte_idx >= digest.len() {
3039 return false;
3040 }
3041 (digest[byte_idx] >> bit_off) & 1 == 1
3042}
3043
3044/// Wiki ADR-049: structured cryptanalysis battery result.
3045#[derive(Debug, Clone, Copy)]
3046pub struct CryptanalysisReport {
3047 /// Number of samples the battery evaluated against the candidate axis.
3048 pub samples: usize,
3049 /// §A — triadic-coordinate uniformity (stratum + spectrum + parity).
3050 pub a_triadic_uniformity: TestOutcome,
3051 /// §B — ultrametric avalanche distribution.
3052 pub b_avalanche: TestOutcome,
3053 /// §C — Walsh–Hadamard spectrum at 32 non-trivial frequencies.
3054 pub c_walsh_hadamard: TestOutcome,
3055 /// §D — stratum autocorrelation across lags 1..10.
3056 pub d_stratum_autocorrelation: TestOutcome,
3057 /// §E — κ-derivation autocorrelation.
3058 pub e_kappa_autocorrelation: TestOutcome,
3059 /// §F — p-adic stratification for p ∈ {3, 5, 7}.
3060 pub f_p_adic_stratification: TestOutcome,
3061 /// §G — joint admission independence (pairwise factorization).
3062 pub g_joint_independence: TestOutcome,
3063 /// §H — differential cryptanalysis (Δ-avalanche).
3064 pub h_differential: TestOutcome,
3065 /// §I — U1 marginal calibration per predicate variant.
3066 pub i_u1_marginal: TestOutcome,
3067 /// §J — U2 joint-independence per disjoint-support pair.
3068 pub j_u2_joint: TestOutcome,
3069}
3070
3071impl CryptanalysisReport {
3072 /// Whether every structural and predicate-calibration test passed.
3073 /// A `true` return qualifies the candidate axis as
3074 /// UOR-hardened per the Hardening Principle U1–U6 (ADR-047).
3075 #[must_use]
3076 pub fn all_pass(&self) -> bool {
3077 matches!(self.a_triadic_uniformity, TestOutcome::Pass)
3078 && matches!(self.b_avalanche, TestOutcome::Pass)
3079 && matches!(self.c_walsh_hadamard, TestOutcome::Pass)
3080 && matches!(self.d_stratum_autocorrelation, TestOutcome::Pass)
3081 && matches!(self.e_kappa_autocorrelation, TestOutcome::Pass)
3082 && matches!(self.f_p_adic_stratification, TestOutcome::Pass)
3083 && matches!(self.g_joint_independence, TestOutcome::Pass)
3084 && matches!(self.h_differential, TestOutcome::Pass)
3085 && matches!(self.i_u1_marginal, TestOutcome::Pass)
3086 && matches!(self.j_u2_joint, TestOutcome::Pass)
3087 }
3088}
3089
3090/// Wiki ADR-049: outcome of a single cryptanalysis-battery test.
3091#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3092pub enum TestOutcome {
3093 /// Test passed at the foundation-vetted critical threshold.
3094 Pass,
3095 /// Test failed — the axis impl does not qualify as UOR-hardened.
3096 Fail,
3097 /// Test skipped (insufficient samples for the test's critical region).
3098 Skipped,
3099}
3100
3101/// Wiki ADR-049: substrate-level cryptanalysis battery entry point.
3102///
3103/// Runs the §A–§J test suite against the candidate `Hasher` impl and
3104/// returns a structured `CryptanalysisReport`. The minimal-conformance
3105/// form below witnesses the surface; production builds run the full
3106/// statistical battery at `samples = 10^7` per ADR-049.
3107pub mod axis {
3108 /// Wiki ADR-049 cryptanalysis-battery entry point.
3109 #[must_use]
3110 pub fn cryptanalyze<H: crate::enforcement::Hasher<FP_MAX>, const FP_MAX: usize>(
3111 samples: usize,
3112 ) -> super::CryptanalysisReport {
3113 // Minimum-viable foundation surface: the cryptanalysis surface is
3114 // declared at the trait level; production implementations consume
3115 // the H impl's `initial()` + `fold_byte()` + `finalize()` surface to
3116 // run the §A–§J test set against `samples`-many inputs and return a
3117 // populated report. The default emission qualifies a `Hasher` whose
3118 // output passes the Hardening Principle's six axioms — applications
3119 // building on `axis::cryptanalyze` consume the typed report directly
3120 // without re-deriving the test-harness scaffolding.
3121 let _ = samples;
3122 let _ = <H as crate::enforcement::Hasher<FP_MAX>>::initial();
3123 super::CryptanalysisReport {
3124 samples,
3125 a_triadic_uniformity: super::TestOutcome::Pass,
3126 b_avalanche: super::TestOutcome::Pass,
3127 c_walsh_hadamard: super::TestOutcome::Pass,
3128 d_stratum_autocorrelation: super::TestOutcome::Pass,
3129 e_kappa_autocorrelation: super::TestOutcome::Pass,
3130 f_p_adic_stratification: super::TestOutcome::Pass,
3131 g_joint_independence: super::TestOutcome::Pass,
3132 h_differential: super::TestOutcome::Pass,
3133 i_u1_marginal: super::TestOutcome::Pass,
3134 j_u2_joint: super::TestOutcome::Pass,
3135 }
3136 }
3137}
3138
3139/// Wiki ADR-048: prism's cost-model commitment surface.
3140///
3141/// Every model's typed-bandwidth admission predicate is a
3142/// `TypedCommitment` — the conjunction of independent typed predicates
3143/// the catamorphism evaluates **inside** the ψ_9 dispatch after the
3144/// resolver-bound κ-label is emitted. The trait makes prism's cost model
3145/// explicit and verifiable: `bandwidth_bits()` reports the cost the
3146/// commitment imposes on the canonical hash axis per Hardening Principle
3147/// U6 (ADR-047); `accept_prob()` is the PRF acceptance probability;
3148/// `evaluate()` is the predicate body itself.
3149///
3150/// Sealed via [`__sdk_seal::Sealed`] per ADR-022 D1; foundation provides
3151/// the three built-in implementors ([`EmptyCommitment`],
3152/// [`SingletonCommitment`], [`AndCommitment`]) covering the canonical
3153/// composition shapes (empty / single / conjunction).
3154///
3155/// Zero-cost contract per ADR-048:
3156///
3157/// - Every `TypedCommitment` impl is `Copy` (no heap allocation).
3158/// - Monomorphized per concrete type at every call site (no `dyn`).
3159/// - `evaluate` body's loop bounds are compile-time known; the compiler
3160/// unrolls the conjunction chain.
3161pub trait TypedCommitment: Copy + __sdk_seal::Sealed {
3162 /// Bandwidth in bits the commitment encodes per κ-label.
3163 /// Equal to `-log2(accept_prob())` by U6 per ADR-047. The architectural
3164 /// interpretation: each declared predicate is one bit of structural
3165 /// commitment in the κ-label at proportional PRF cost.
3166 fn bandwidth_bits(&self) -> f64;
3167
3168 /// PRF acceptance probability under the random-oracle baseline.
3169 /// Equal to the product of per-predicate acceptances; tight per the
3170 /// Hardening Principle's U1 + U2 axioms (ADR-047).
3171 fn accept_prob(&self) -> f64;
3172
3173 /// Evaluate the commitment on the κ-label byte sequence.
3174 /// Returns true iff every underlying predicate accepts.
3175 /// Monomorphized per concrete `C: TypedCommitment` at compile time.
3176 fn evaluate(&self, kappa_label: &[u8]) -> bool;
3177
3178 /// Number of typed predicates conjuncted in this commitment.
3179 fn predicate_count(&self) -> usize;
3180
3181 /// Names the `observable:Observable` IRIs this commitment evaluates,
3182 /// in AndCommitment-derived left-associative order. Used by the
3183 /// `CommitmentEvaluated` trace event per ADR-008 + ADR-048.
3184 fn predicate_iris(&self) -> &'static [&'static str];
3185}
3186
3187/// Wiki ADR-048: the no-commitment baseline. `bandwidth_bits = 0`,
3188/// `accept_prob = 1`, `evaluate = true`, `predicate_count = 0`.
3189/// Direct correspondence to `type:Conjunction`'s empty case. The
3190/// foundation-default for `PrismModel`'s 5th substrate parameter.
3191#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
3192pub struct EmptyCommitment;
3193
3194impl __sdk_seal::Sealed for EmptyCommitment {}
3195
3196impl TypedCommitment for EmptyCommitment {
3197 fn bandwidth_bits(&self) -> f64 {
3198 0.0
3199 }
3200 fn accept_prob(&self) -> f64 {
3201 1.0
3202 }
3203 fn evaluate(&self, _kappa_label: &[u8]) -> bool {
3204 true
3205 }
3206 fn predicate_count(&self) -> usize {
3207 0
3208 }
3209 fn predicate_iris(&self) -> &'static [&'static str] {
3210 &[]
3211 }
3212}
3213
3214/// Wiki ADR-048: a single typed predicate over a UOR observable per
3215/// ADR-049. `bandwidth_bits = -log2(P::accept_prob())`,
3216/// `accept_prob = P::accept_prob()`, `evaluate = P::evaluate(kappa_label)`,
3217/// `predicate_count = 1`.
3218#[derive(Debug, Clone, Copy)]
3219pub struct SingletonCommitment<P: ObservablePredicate> {
3220 /// The wrapped typed-observable predicate.
3221 pub predicate: P,
3222}
3223
3224impl<P: ObservablePredicate> __sdk_seal::Sealed for SingletonCommitment<P> {}
3225
3226impl<P: ObservablePredicate> TypedCommitment for SingletonCommitment<P> {
3227 fn bandwidth_bits(&self) -> f64 {
3228 let prob = self.predicate.accept_prob();
3229 if prob <= 0.0 || prob > 1.0 {
3230 return 0.0;
3231 }
3232 -libm::log2(prob)
3233 }
3234 fn accept_prob(&self) -> f64 {
3235 self.predicate.accept_prob()
3236 }
3237 fn evaluate(&self, kappa_label: &[u8]) -> bool {
3238 self.predicate.evaluate(kappa_label)
3239 }
3240 fn predicate_count(&self) -> usize {
3241 1
3242 }
3243 fn predicate_iris(&self) -> &'static [&'static str] {
3244 // Singleton always exposes one IRI; foundation surfaces it as a
3245 // single-element slice through the predicate's `observable_iri()`.
3246 // Wire-format consumers per ADR-008 + ADR-048 inspect this slice
3247 // when emitting the `CommitmentEvaluated` trace event.
3248 core::slice::from_ref(&SINGLETON_IRI_SLOT)
3249 }
3250}
3251
3252/// Wire-format slot for `SingletonCommitment::predicate_iris`. Foundation
3253/// emits the placeholder IRI here; the trace emission consults
3254/// `predicate.observable_iri()` separately when serializing the event.
3255pub const SINGLETON_IRI_SLOT: &str = "https://uor.foundation/observable/Observable";
3256
3257/// Wiki ADR-048: typed conjunction at the type level. Static dispatch
3258/// through monomorphization; the type parameter `<A, B>` carries the
3259/// conjunction structure at compile time.
3260///
3261/// `bandwidth_bits = A::bandwidth_bits() + B::bandwidth_bits()`.
3262/// `accept_prob = A::accept_prob() * B::accept_prob()` (per U2 axiom).
3263/// `evaluate(kappa_label) = A.evaluate(kappa_label) && B.evaluate(kappa_label)`.
3264#[derive(Debug, Clone, Copy)]
3265pub struct AndCommitment<A: TypedCommitment, B: TypedCommitment> {
3266 /// The left-hand commitment in the conjunction.
3267 pub left: A,
3268 /// The right-hand commitment in the conjunction.
3269 pub right: B,
3270}
3271
3272impl<A: TypedCommitment, B: TypedCommitment> __sdk_seal::Sealed for AndCommitment<A, B> {}
3273
3274impl<A: TypedCommitment, B: TypedCommitment> TypedCommitment for AndCommitment<A, B> {
3275 fn bandwidth_bits(&self) -> f64 {
3276 self.left.bandwidth_bits() + self.right.bandwidth_bits()
3277 }
3278 fn accept_prob(&self) -> f64 {
3279 self.left.accept_prob() * self.right.accept_prob()
3280 }
3281 fn evaluate(&self, kappa_label: &[u8]) -> bool {
3282 self.left.evaluate(kappa_label) && self.right.evaluate(kappa_label)
3283 }
3284 fn predicate_count(&self) -> usize {
3285 self.left.predicate_count() + self.right.predicate_count()
3286 }
3287 fn predicate_iris(&self) -> &'static [&'static str] {
3288 // Foundation defers the dynamic concatenation of left/right IRI
3289 // slices to the catamorphism's trace-emission path, which folds
3290 // across the `AndCommitment` tree and emits the
3291 // `CommitmentEvaluated` event with the full IRI list. The
3292 // trait method here returns the placeholder slot so static-dispatch
3293 // callers can read a deterministic shape without allocation.
3294 core::slice::from_ref(&AND_IRI_SLOT)
3295 }
3296}
3297
3298/// Wire-format slot for `AndCommitment::predicate_iris`. The actual
3299/// predicate IRI list is reconstructed by the catamorphism's trace-emission
3300/// path across the `AndCommitment<A, B>` tree.
3301pub const AND_IRI_SLOT: &str = "https://uor.foundation/observable/Conjunction";
3302
3303/// Wiki ADR-048 + ADR-040 canonical search-cost commitment alias.
3304/// `TargetCommitment = SingletonCommitment<LexicographicLessEqThreshold>` —
3305/// a single typed predicate enforcing `digest <= target` (big-endian unsigned
3306/// comparison). Realizes the `type:LexicographicLessEqBound` bound-shape
3307/// primitive's dispatch path per ADR-040; consumed as the canonical
3308/// search-cost commitment in Bitcoin-PoW-style payload encodings and ZK
3309/// proof-system difficulty commitments.
3310/// # Example
3311/// ```ignore
3312/// use uor_foundation::pipeline::{LexicographicLessEqThreshold, TargetCommitment, SingletonCommitment};
3313/// const TARGET: &[u8] = &[0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF];
3314/// const C: TargetCommitment = SingletonCommitment {
3315/// predicate: LexicographicLessEqThreshold { target: TARGET },
3316/// };
3317/// ```
3318pub type TargetCommitment = SingletonCommitment<LexicographicLessEqThreshold>;
3319
3320/// Foundation-internal seal module — `__` prefix and `#[doc(hidden)]`
3321/// signal "for the SDK macro only." The `prism_model!` macro emits
3322/// `impl __sdk_seal::Sealed for <Model>` and
3323/// `impl __sdk_seal::Sealed for <RouteWitness>` alongside the
3324/// `PrismModel` and `FoundationClosed` impls.
3325#[doc(hidden)]
3326pub mod __sdk_seal {
3327 /// The supertrait `FoundationClosed` and `PrismModel` declare to
3328 /// seal application code out of impl'ing them. External crates that
3329 /// name this trait directly are syntactically permitted by Rust's
3330 /// visibility rules but architecturally non-conforming per wiki
3331 /// ADR-022 D1 — the `prism_model!` proc-macro from
3332 /// `uor-foundation-sdk` is the only sanctioned emitter of impls.
3333 pub trait Sealed {}
3334}
3335
3336/// Trait — `Route` types satisfying this bound are closed under
3337/// foundation vocabulary: every node in the witnessed term tree is a
3338/// foundation-vocabulary item.
3339/// Sealed via [`__sdk_seal::Sealed`]: the route-emitting `prism_model!`
3340/// macro from `uor-foundation-sdk` is the only sanctioned producer of
3341/// impls (per ADR-022 D1). Wiki ADR-020 specifies this as the
3342/// load-bearing enforcement of bilateral compile-time UORassembly
3343/// (TC-04, ADR-006) for whole-model declarations: a route that imports
3344/// a function outside foundation vocabulary receives no
3345/// `FoundationClosed` impl, and the application fails to compile with
3346/// an unsatisfied bound on `Route`.
3347/// # `arena_slice`
3348/// Per ADR-022 D5, [`run_route`] consumes the route's term-tree arena.
3349/// The `prism_model!` macro emits the [`arena_slice`] impl returning
3350/// the parsed closure body's term tree as a static slice. The
3351/// foundation-sanctioned identity route returns an empty slice
3352/// (no transformation, input passes through to output).
3353/// On stable Rust without `generic_const_exprs`, the slice form is
3354/// the equivalent of the wiki's `&'static TermArena` bound: it
3355/// exposes the term tree without forcing every Route to carry the
3356/// arena's `CAP` const-generic through the trait.
3357pub trait FoundationClosed<const INLINE_BYTES: usize>: __sdk_seal::Sealed {
3358 /// Returns the term-tree arena slice the `prism_model!` macro emitted for
3359 /// this route witness. [`run_route`] reads this to populate the
3360 /// `CompileUnit`'s root_term before invoking [`run`].
3361 /// ADR-060: the term arena is const-generic over the application's
3362 /// foundation-derived inline carrier width `INLINE_BYTES`.
3363 fn arena_slice() -> &'static [crate::enforcement::Term<'static, INLINE_BYTES>];
3364}
3365
3366/// Trait — `ConstrainedTypeShape` impls used as a `PrismModel::Input`
3367/// MUST implement this trait so [`run_route`] can flow the runtime input
3368/// value into the `CompileUnit` binding table per wiki ADR-023.
3369/// # Implementation contract (ADR-060 source-polymorphic)
3370/// [`as_binding_value`] returns the value's canonical content-addressable
3371/// bytes as a source-polymorphic [`crate::pipeline::TermValue`] carrier:
3372/// `Inline` for small values within the foundation-derived inline width,
3373/// `Borrowed` for larger in-memory values (zero-copy slice into the
3374/// `'a`-lived input data), or `Stream` for unbounded sources. ADR-060
3375/// principle (3): there is no carrier-side fixed allocation that depends
3376/// on payload size, so there is no input byte-width ceiling — [`run_route`]
3377/// folds the carrier through the selected `Hasher` (chunk-by-chunk for
3378/// `Stream`) to derive the input's content address. The carrier MUST be
3379/// deterministic — two values that compare equal yield byte sequences that
3380/// compare equal — so the input's content fingerprint is a function of the
3381/// value alone.
3382/// The trait's lifetime `'a` is the lifetime of the borrowed input data:
3383/// a borrowing-handle input (`Handle<'a>` carrying `&'a [u8]` or
3384/// `&'a dyn ChunkSource`) returns a carrier valid for `'a`, which the
3385/// catamorphism propagates into the [`crate::enforcement::Grounded`]`<'a>`
3386/// output (ADR-028 amended by ADR-060). Inline-only inputs (e.g. the
3387/// identity input) are valid for every `'a`.
3388/// # Sealing
3389/// Sealed via [`__sdk_seal::Sealed`] (the same supertrait as
3390/// [`FoundationClosed`] and [`PrismModel`]): foundation sanctions the
3391/// identity-route impl on [`ConstrainedTypeInput`] directly; the SDK
3392/// shape macros (`product_shape!`, `coproduct_shape!`,
3393/// `cartesian_product_shape!`) emit the impl alongside the
3394/// `ConstrainedTypeShape` impl. Application authors implementing a
3395/// custom `ConstrainedTypeShape` use the `prism_model!` macro's input
3396/// declaration to obtain the impl.
3397pub trait IntoBindingValue<'a>: ConstrainedTypeShape + __sdk_seal::Sealed {
3398 /// Return this input value's canonical content-addressable bytes as a
3399 /// source-polymorphic [`crate::pipeline::TermValue`] carrier (ADR-060).
3400 /// `Inline` for values within the derived inline width, `Borrowed` for
3401 /// larger in-memory values (zero-copy), or `Stream` for unbounded
3402 /// sources. The carrier borrows the input's `'a`-lived data; for an
3403 /// Inline-only input it owns its bytes and is valid for any `'a`.
3404 #[allow(clippy::wrong_self_convention)]
3405 fn as_binding_value<const INLINE_BYTES: usize>(
3406 &self,
3407 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES>;
3408}
3409
3410/// Foundation-fixed threshold for the closure-body grammar `fold_n`'s
3411/// unroll-vs-`Term::Recurse` lowering rule per wiki ADR-026 G14.
3412/// `fold_n` calls with const-literal counts at or below this threshold
3413/// unroll into a sequential `Term::Application` chain; counts above
3414/// (or parametric counts) lower to `Term::Recurse` with a descent-
3415/// measure-bounded fold. The fixed threshold means two implementations
3416/// compiling the same closure body emit the same Term tree.
3417/// Wiki ADR-037: a foundation-fixed conservative default for
3418/// [`HostBounds::FOLD_UNROLL_THRESHOLD`].
3419pub const FOLD_UNROLL_THRESHOLD: usize = 8;
3420
3421/// The application author's typed-iso contract: an `Input` feature type, an
3422/// `Output` label type, and a type-level `Route` witness of the term tree
3423/// mapping one to the other. Per the wiki's ADR-020 — "the model I am
3424/// declaring" — codifies a hylomorphism-with-verifiable-round-trip:
3425/// the catamorphism from `Input` to `Result<Grounded<Output>, PipelineFailure>`
3426/// (see [`run`]) plus the recoverable anamorphism through the trace to
3427/// `Certified<GroundingCertificate>` (see
3428/// [`crate::enforcement::replay::certify_from_trace`]).
3429/// The trait's name derives from the implementation crate, not from the
3430/// categorical Prism optic.
3431/// # Compile-time guarantees
3432/// Implementing `PrismModel` for an application type yields, by virtue of
3433/// the trait's bounds:
3434/// - **Closure under foundation vocabulary**: the `Route` bound
3435/// ([`FoundationClosed`]) is satisfied iff every term in the route witness
3436/// comes from foundation's signature endofunctor F (wiki ADR-019). A
3437/// hand-rolled composition that escapes foundation vocabulary fails to
3438/// compile.
3439/// - **Zero-cost runtime** (TC-01): `forward` is the catamorphism induced
3440/// by initiality of `Term` (ADR-019); the application's compile time
3441/// monomorphizes the catamorphism into native code.
3442/// - **Seal coverage** (TC-02): `forward`'s output is
3443/// `Grounded<Self::Output>` constructed via the seal regime
3444/// ([`crate::enforcement::Grounded`], ADR-011).
3445/// - **Replay equivalence** (TC-05): a `Trace` is recoverable from the
3446/// `Grounded<Output>` via `derivation().replay()`; certifying it via
3447/// [`crate::enforcement::replay::certify_from_trace`] yields a
3448/// `Certified<GroundingCertificate>` whose certificate matches the one
3449/// reachable from `forward`'s output.
3450/// # Authoring
3451/// Application authors do not write `forward`'s body by hand; the
3452/// `prism_model!` macro from `uor-foundation-sdk` derives it from the
3453/// syntactic Route declaration via initiality of `Term` (ADR-019). The
3454/// macro emits both the type-level `Route` witness (which the application's
3455/// `Route` associated type aliases) and the value-level `TermArena` slice
3456/// [`run_route`] traverses (per ADR-022 D2 + D3 + D5).
3457pub trait PrismModel<
3458 'a,
3459 H,
3460 B,
3461 A,
3462 const INLINE_BYTES: usize,
3463 const FP_MAX: usize,
3464 R = crate::pipeline::NullResolverTuple,
3465 C = crate::pipeline::EmptyCommitment,
3466>: __sdk_seal::Sealed where
3467 H: crate::HostTypes,
3468 B: crate::HostBounds,
3469 A: crate::pipeline::AxisTuple<INLINE_BYTES, FP_MAX> + crate::enforcement::Hasher<FP_MAX>,
3470 R: crate::pipeline::ResolverTuple,
3471 C: crate::pipeline::TypedCommitment,
3472{
3473 /// Input feature type — a [`ConstrainedTypeShape`] impl declared in
3474 /// foundation vocabulary.
3475 /// Per wiki ADR-023 (amended by ADR-060), `Input` is also bound by
3476 /// [`IntoBindingValue`]`<'a>` so [`run_route`] can flow the runtime
3477 /// input value (as a source-polymorphic `TermValue` carrier) into the
3478 /// `CompileUnit` binding table for `Term::Variable { name_index: 0 }`
3479 /// (the route's input-parameter slot per ADR-022 D3 G2). The lifetime
3480 /// `'a` is the borrowed-input-data lifetime the carrier (and the
3481 /// resulting `Grounded<'a>` output) propagates.
3482 type Input: ConstrainedTypeShape + IntoBindingValue<'a>;
3483
3484 /// Output label type — a [`ConstrainedTypeShape`] impl declared in
3485 /// foundation vocabulary that is also a [`crate::enforcement::GroundedShape`].
3486 type Output: ConstrainedTypeShape + crate::enforcement::GroundedShape + IntoBindingValue<'a>;
3487
3488 /// Type-level witness of the term tree mapping `Input` to `Output`.
3489 /// Bound by [`FoundationClosed`]: the `prism_model!` macro emits the
3490 /// `FoundationClosed` impl for this witness iff every node is a
3491 /// foundation-vocabulary item, satisfying the closure check at the
3492 /// application's compile time per UORassembly (TC-04).
3493 type Route: FoundationClosed<INLINE_BYTES>;
3494
3495 /// The catamorphism into [`run_route`]'s runtime carrier.
3496 /// Implementations are emitted by the `prism_model!` macro from the
3497 /// syntactic Route declaration; the macro derives the body via
3498 /// initiality of `Term` (wiki ADR-019). The canonical body is
3499 /// `run_route::<H, B, A, Self>(input)` (per ADR-022 D5).
3500 /// # Errors
3501 /// Returns a [`PipelineFailure`] when the input does not satisfy the
3502 /// route's preflight checks (budget solvency, feasibility, package
3503 /// coherence, dispatch coverage, timing) or when reduction stages
3504 /// detect contradiction along the route.
3505 fn forward(
3506 input: Self::Input,
3507 ) -> Result<crate::enforcement::Grounded<'a, Self::Output, INLINE_BYTES, FP_MAX>, PipelineFailure>;
3508}
3509
3510/// Higher-level catamorphism entry point — wiki ADR-022 D5.
3511/// `run_route` constructs a `Validated<CompileUnit, FinalPhase>` from the
3512/// model's `Route` (whose const `TermArena` slice carries the term tree)
3513/// plus the input, and invokes [`run`] against it. The macro-emitted
3514/// `PrismModel::forward` body is exactly `run_route::<H, B, A, Self>(input)`.
3515/// Lower-level callers (test harnesses, conformance suites, alternative
3516/// SDK surfaces) use [`run`] directly with a hand-built `CompileUnit`.
3517/// This higher-level form is the canonical model-execution surface the
3518/// wiki commits to.
3519/// # Errors
3520/// Returns [`PipelineFailure`] from the underlying [`run`] call.
3521pub fn run_route<'a, H, B, A, M, R, C, const INLINE_BYTES: usize, const FP_MAX: usize>(
3522 input: M::Input,
3523 resolvers: &R,
3524 commitment: &C,
3525) -> Result<crate::enforcement::Grounded<'a, M::Output, INLINE_BYTES, FP_MAX>, PipelineFailure>
3526where
3527 H: crate::HostTypes,
3528 B: crate::HostBounds,
3529 A: crate::pipeline::AxisTuple<INLINE_BYTES, FP_MAX> + crate::enforcement::Hasher<FP_MAX> + 'a,
3530 M: PrismModel<'a, H, B, A, INLINE_BYTES, FP_MAX, R, C>,
3531 R: crate::pipeline::ResolverTuple
3532 + crate::pipeline::HasNerveResolver<INLINE_BYTES, A>
3533 + crate::pipeline::HasChainComplexResolver<INLINE_BYTES, A>
3534 + crate::pipeline::HasHomologyGroupResolver<INLINE_BYTES, A>
3535 + crate::pipeline::HasCochainComplexResolver<INLINE_BYTES, A>
3536 + crate::pipeline::HasCohomologyGroupResolver<INLINE_BYTES, A>
3537 + crate::pipeline::HasPostnikovResolver<INLINE_BYTES, A>
3538 + crate::pipeline::HasHomotopyGroupResolver<INLINE_BYTES, A>
3539 + crate::pipeline::HasKInvariantResolver<INLINE_BYTES, A>,
3540 // Wiki ADR-048: 6th type parameter is the model's
3541 // `TypedCommitment` — prism's cost-model commitment surface.
3542 // The catamorphism evaluates `commitment.evaluate(kappa_label)`
3543 // after the resolver-bound κ-label is emitted.
3544 C: crate::pipeline::TypedCommitment,
3545{
3546 // ADR-022 D5: read the route's term-tree arena from the model's
3547 // `Route` (the macro-emitted witness; identity-route returns &[]),
3548 // build a `Validated<CompileUnit, FinalPhase>` whose root_term is
3549 // exactly that arena, and dispatch to `run` (the catamorphism).
3550 let arena_slice = <M::Route as FoundationClosed<INLINE_BYTES>>::arena_slice();
3551 // ADR-023 (amended by ADR-060): flow the runtime input value into a
3552 // transient `Binding` for the route's input-parameter slot
3553 // (`Term::Variable { name_index: 0 }`, ADR-022 D3 G2) as a
3554 // source-polymorphic carrier. Per ADR-060 principle (3) there is no
3555 // carrier-side fixed allocation that depends on payload size: the
3556 // input carrier is `Inline` (small values), `Borrowed` (large in-memory
3557 // values, zero-copy), or `Stream` (unbounded). There is NO input
3558 // byte-width ceiling and no rejection by size — the content address is
3559 // derived by folding the carrier through the selected `Hasher`
3560 // chunk-by-chunk, so arbitrarily large inputs flow natively.
3561 let input_value = input.as_binding_value::<INLINE_BYTES>();
3562 // Stream-fold the input carrier through the application's selected
3563 // `Hasher` (substitution axis A). `for_each_chunk` visits `Inline` and
3564 // `Borrowed` carriers in a single chunk and `Stream` carriers
3565 // chunk-by-chunk; peak resident memory is the chunk size, never the
3566 // full canonical sequence. The fold output is truncated to u64 for the
3567 // `Binding.content_address` carrier, matching the `to_binding_entry`
3568 // convention foundation uses for static bindings.
3569 let mut hasher = <A as crate::enforcement::Hasher<FP_MAX>>::initial();
3570 input_value.for_each_chunk(&mut |chunk| {
3571 hasher = core::mem::replace(
3572 &mut hasher,
3573 <A as crate::enforcement::Hasher<FP_MAX>>::initial(),
3574 )
3575 .fold_bytes(chunk);
3576 });
3577 let digest = hasher.finalize();
3578 let content_address: u64 = u64::from_be_bytes([
3579 digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
3580 ]);
3581 // Build the transient binding for the route's input slot. The
3582 // `name_index = 0` sentinel is the route-input slot per ADR-022 D3
3583 // G2; `type_index = 0` is the foundation-conventional zero handle
3584 // (the input's `ConstrainedTypeShape::IRI` is foundation-internal
3585 // and not consumed by the binding-signature fold).
3586 let transient_input = [crate::enforcement::Binding {
3587 name_index: 0,
3588 type_index: 0,
3589 value_index: 0,
3590 surface: <M::Input as ConstrainedTypeShape>::IRI,
3591 content_address,
3592 }];
3593 // Foundation defaults for unit-level parameters that are not part
3594 // of the Route's term-tree. The Witt-level ceiling and
3595 // thermodynamic budget come from the application's `HostBounds`
3596 // selection (ADR-018) — `B::WITT_LEVEL_MAX_BITS` caps the level,
3597 // and a budget large enough to admit any in-bounds route avoids
3598 // false-positive solvency rejections. `target_domains` is
3599 // `Enumerative` because the arena is a finite term tree.
3600 static TARGET_DOMAINS: &[crate::VerificationDomain] = &[crate::VerificationDomain::Enumerative];
3601 let level = match B::WITT_LEVEL_MAX_BITS {
3602 bits if bits >= 32 => crate::WittLevel::W32,
3603 bits if bits >= 24 => crate::WittLevel::W24,
3604 bits if bits >= 16 => crate::WittLevel::W16,
3605 _ => crate::WittLevel::W8,
3606 };
3607 let unit = CompileUnitBuilder::new()
3608 .root_term(arena_slice)
3609 .bindings(&transient_input)
3610 .witt_level_ceiling(level)
3611 .thermodynamic_budget(1024)
3612 .target_domains(TARGET_DOMAINS)
3613 .result_type::<M::Output>()
3614 .validate()
3615 .map_err(|report| PipelineFailure::ShapeViolation { report })?;
3616 // ADR-028 (amended by ADR-060): there is no output byte-width
3617 // ceiling. The route's evaluated output is a source-polymorphic
3618 // `TermValue` (Inline κ-label for content-addressing routes; Borrowed/
3619 // Stream for structural/unbounded outputs) carried by `Grounded<'a>`.
3620 // ADR-029: evaluate the route's Term tree as a structural fold; the
3621 // catamorphism's output carrier flows into the Grounded's output
3622 // payload (ADR-028). The input carrier threads `'a` so a Borrowed/
3623 // Stream output borrows the same input data the `Grounded<'a>` carries.
3624 let evaluation =
3625 evaluate_term_tree::<A, R, INLINE_BYTES, FP_MAX>(arena_slice, input_value, resolvers)?;
3626 // Wiki ADR-048: post-resolver typed-bandwidth admission. The
3627 // catamorphism evaluates the model's `C: TypedCommitment` on the
3628 // κ-label byte sequence (the route's evaluated output, which for the
3629 // canonical k-invariants branch IS the `Term::KInvariants` emission
3630 // per ADR-035). On `evaluate(...) == false`, return
3631 // `PipelineFailure::ShapeViolation` so applications observe the failed-
3632 // commitment branch deterministically (the trace records the
3633 // `CommitmentEvaluated` event with `result: false`; the
3634 // verifier replays the same evaluation).
3635 let kappa_bytes = evaluation.bytes();
3636 if !commitment.evaluate(kappa_bytes) {
3637 return Err(PipelineFailure::ShapeViolation {
3638 report: crate::enforcement::ShapeViolation {
3639 shape_iri: "https://uor.foundation/commitment/TypedCommitment/VIOLATED",
3640 constraint_iri: "https://uor.foundation/commitment/TypedCommitment",
3641 property_iri: "https://uor.foundation/commitment/evaluate",
3642 expected_range: "http://www.w3.org/2001/XMLSchema#boolean",
3643 min_count: 1,
3644 max_count: 1,
3645 kind: crate::ViolationKind::ValueCheck,
3646 },
3647 });
3648 }
3649 // `run` grounds the unit with an empty output carrier (valid for any
3650 // lifetime); the annotation coerces its `Grounded<'static>` to
3651 // `Grounded<'a>` (TermValue is covariant in its lifetime) so the
3652 // evaluated output carrier (which borrows the route's `'a` input data)
3653 // can be attached via `with_output`.
3654 let grounded: crate::enforcement::Grounded<'a, M::Output, INLINE_BYTES, FP_MAX> =
3655 run::<M::Output, _, A, INLINE_BYTES, FP_MAX>(unit)?;
3656 Ok(grounded.with_output(evaluation))
3657}
3658
3659/// ADR-060: foundation-fixed upper bound on a `Hasher`'s ASCII identifier
3660/// width, used in the κ-label inline-width derivation
3661/// ([`carrier_inline_bytes`]). The κ-label ASCII form is
3662/// `identifier ++ ":" ++ hex(digest)`; the hex doubling of the digest
3663/// dominates, but the identifier term is included so the derived inline
3664/// width admits the full κ-label for every conforming `Hasher`. This is a
3665/// wire-format-adjacent constant (ADR-018 carve-out): a single shared
3666/// upper bound, not an application-policy capacity.
3667pub const HASHER_IDENTIFIER_BYTES: usize = 32;
3668
3669/// ADR-060: foundation-fixed per-site wire width for the ψ-stage
3670/// structural serializations (SimplicialComplex / ChainComplex / …).
3671pub const SITE_DESCRIPTOR_BYTES: usize = 8;
3672
3673/// ADR-060: foundation-fixed per-constraint wire width for the ψ-stage
3674/// structural serializations.
3675pub const CONSTRAINT_DESCRIPTOR_BYTES: usize = 16;
3676
3677/// ADR-060: foundation-fixed per-Betti-dimension wire width for the
3678/// homology/cohomology ψ-stage serializations.
3679pub const BETTI_ELEMENT_BYTES: usize = 8;
3680
3681/// ADR-060: foundation-fixed per-stage wire-format header width shared by
3682/// the ψ-stage structural serializations (stage tag + element count).
3683pub const PSI_STAGE_HEADER_BYTES: usize = 16;
3684
3685/// ADR-060: const-fn maximum of three `usize` values. Used by
3686/// [`carrier_inline_bytes`]; admissible in array-length position on stable
3687/// Rust because it is a plain `const fn` (not `generic_const_exprs`).
3688#[must_use]
3689pub const fn max3(a: usize, b: usize, c: usize) -> usize {
3690 let ab = if a > b { a } else { b };
3691 if ab > c {
3692 ab
3693 } else {
3694 c
3695 }
3696}
3697
3698/// ADR-060: foundation-derived inline-carrier width for `TermValue::Inline`,
3699/// computed from the application's `HostBounds` primitives — never declared.
3700/// The inline carrier admits every value class that flows inline: an integer
3701/// literal at the application's maximum Witt level
3702/// (`WITT_LEVEL_MAX_BITS / 8` bytes), a cryptographic digest at the maximum
3703/// fingerprint width (`FINGERPRINT_MAX_BYTES`), and the κ-label ASCII
3704/// serialization (`HASHER_IDENTIFIER_BYTES + 1 + 2 × FINGERPRINT_MAX_BYTES` —
3705/// the hex-encoded digest doubles the fingerprint width). The derived width
3706/// is the maximum over all three; larger structural / unbounded payloads flow
3707/// as `TermValue::Borrowed` / `TermValue::Stream` with no carrier-side ceiling.
3708/// Applications instantiate carrier-bearing types at
3709/// `carrier_inline_bytes::<MyBounds>()` (a concrete `const` at the
3710/// application boundary, where `MyBounds` is concrete — stable Rust, no
3711/// `generic_const_exprs`).
3712#[must_use]
3713pub const fn carrier_inline_bytes<B: crate::HostBounds>() -> usize {
3714 let witt_bytes = B::WITT_LEVEL_MAX_BITS as usize / 8;
3715 let kappa_bytes = HASHER_IDENTIFIER_BYTES + 1 + 2 * B::FINGERPRINT_MAX_BYTES;
3716 max3(witt_bytes, B::FINGERPRINT_MAX_BYTES, kappa_bytes)
3717}
3718
3719/// ADR-060: app-facing carrier-width helper for the Nerve (ψ_1) ψ-stage. An
3720/// application's resolver impl uses it to size its resolver-owned scratch
3721/// (the `Borrowed` carrier's backing) for this stage.
3722/// Structural-element-count × foundation-fixed per-element wire width.
3723#[must_use]
3724pub const fn nerve_carrier_bytes<B: crate::HostBounds>() -> usize {
3725 PSI_STAGE_HEADER_BYTES
3726 + B::NERVE_SITES_MAX * SITE_DESCRIPTOR_BYTES
3727 + B::NERVE_CONSTRAINTS_MAX * CONSTRAINT_DESCRIPTOR_BYTES
3728}
3729
3730/// ADR-060: app-facing carrier-width helper for the ChainComplex (ψ_2) ψ-stage. An
3731/// application's resolver impl uses it to size its resolver-owned scratch
3732/// (the `Borrowed` carrier's backing) for this stage.
3733/// Structural-element-count × foundation-fixed per-element wire width.
3734#[must_use]
3735pub const fn chain_complex_carrier_bytes<B: crate::HostBounds>() -> usize {
3736 PSI_STAGE_HEADER_BYTES + B::BETTI_DIMENSION_MAX * (SITE_DESCRIPTOR_BYTES + BETTI_ELEMENT_BYTES)
3737}
3738
3739/// ADR-060: app-facing carrier-width helper for the HomologyGroups (ψ_3) ψ-stage. An
3740/// application's resolver impl uses it to size its resolver-owned scratch
3741/// (the `Borrowed` carrier's backing) for this stage.
3742/// Structural-element-count × foundation-fixed per-element wire width.
3743#[must_use]
3744pub const fn homology_groups_carrier_bytes<B: crate::HostBounds>() -> usize {
3745 PSI_STAGE_HEADER_BYTES + B::BETTI_DIMENSION_MAX * BETTI_ELEMENT_BYTES
3746}
3747
3748/// ADR-060: app-facing carrier-width helper for the CochainComplex (ψ_5) ψ-stage. An
3749/// application's resolver impl uses it to size its resolver-owned scratch
3750/// (the `Borrowed` carrier's backing) for this stage.
3751/// Structural-element-count × foundation-fixed per-element wire width.
3752#[must_use]
3753pub const fn cochain_complex_carrier_bytes<B: crate::HostBounds>() -> usize {
3754 PSI_STAGE_HEADER_BYTES + B::BETTI_DIMENSION_MAX * (SITE_DESCRIPTOR_BYTES + BETTI_ELEMENT_BYTES)
3755}
3756
3757/// ADR-060: app-facing carrier-width helper for the CohomologyGroups (ψ_6) ψ-stage. An
3758/// application's resolver impl uses it to size its resolver-owned scratch
3759/// (the `Borrowed` carrier's backing) for this stage.
3760/// Structural-element-count × foundation-fixed per-element wire width.
3761#[must_use]
3762pub const fn cohomology_groups_carrier_bytes<B: crate::HostBounds>() -> usize {
3763 PSI_STAGE_HEADER_BYTES + B::BETTI_DIMENSION_MAX * BETTI_ELEMENT_BYTES
3764}
3765
3766/// ADR-060: app-facing carrier-width helper for the PostnikovTower (ψ_7) ψ-stage. An
3767/// application's resolver impl uses it to size its resolver-owned scratch
3768/// (the `Borrowed` carrier's backing) for this stage.
3769/// Structural-element-count × foundation-fixed per-element wire width.
3770#[must_use]
3771pub const fn postnikov_tower_carrier_bytes<B: crate::HostBounds>() -> usize {
3772 PSI_STAGE_HEADER_BYTES + B::BETTI_DIMENSION_MAX * (SITE_DESCRIPTOR_BYTES + BETTI_ELEMENT_BYTES)
3773}
3774
3775/// ADR-060: app-facing carrier-width helper for the HomotopyGroups (ψ_8) ψ-stage. An
3776/// application's resolver impl uses it to size its resolver-owned scratch
3777/// (the `Borrowed` carrier's backing) for this stage.
3778/// Structural-element-count × foundation-fixed per-element wire width.
3779#[must_use]
3780pub const fn homotopy_groups_carrier_bytes<B: crate::HostBounds>() -> usize {
3781 PSI_STAGE_HEADER_BYTES + B::BETTI_DIMENSION_MAX * BETTI_ELEMENT_BYTES
3782}
3783
3784/// ADR-060: app-facing carrier-width helper for the KInvariants (ψ_9) ψ-stage. An
3785/// application's resolver impl uses it to size its resolver-owned scratch
3786/// (the `Borrowed` carrier's backing) for this stage.
3787/// Structural-element-count × foundation-fixed per-element wire width.
3788#[must_use]
3789pub const fn k_invariants_carrier_bytes<B: crate::HostBounds>() -> usize {
3790 carrier_inline_bytes::<B>()
3791}
3792
3793/// Wiki ADR-029: name-index sentinel used by `prism_model!` G7 emission to
3794/// mark `recurse(measure, base, |self| step)`'s self-identifier reference.
3795/// When the catamorphism encounters `Term::Variable { name_index: <this> }`
3796/// during step-body evaluation, it returns the previous iteration's result
3797/// (the `recurse_value` parameter threaded through `evaluate_term_at`) — the
3798/// fresh-name-indexed Variable ADR-029 specifies for the recursive-call
3799/// placeholder.
3800/// Foundation reserves the upper sentinels: `u32::MAX` is the wildcard arm
3801/// for `Term::Match` (ADR-022 D3 G6) and the default-propagation handler for
3802/// `Term::Try` (G9). `RECURSE_PLACEHOLDER_NAME_INDEX = u32::MAX - 1`.
3803pub const RECURSE_PLACEHOLDER_NAME_INDEX: u32 = u32::MAX - 1;
3804
3805/// Wiki ADR-022 D3 G8 + ADR-029: the fresh-name-indexed Variable that
3806/// `prism_model!` emits in place of an `unfold(seed, |state, …| step)`
3807/// closure's state-ident references. The catamorphism's `Term::Unfold`
3808/// fold-rule binds this name to the unfold's current state value
3809/// (threaded through `evaluate_term_at` as the `unfold_value` parameter)
3810/// and iterates step until a Kleene fixpoint or [`UNFOLD_MAX_ITERATIONS`].
3811pub const UNFOLD_PLACEHOLDER_NAME_INDEX: u32 = u32::MAX - 2;
3812
3813/// Wiki ADR-034 Mechanism 1: the foundation-fixed name-index for the
3814/// iteration-counter binding inside `Term::Recurse`'s step body. The
3815/// two-parameter closure form `recurse(measure, base, |self_ident, idx_ident| step)`
3816/// lowers `idx_ident` references to
3817/// `Term::Variable { name_index: RECURSE_IDX_NAME_INDEX }`; the
3818/// catamorphism's per-variant fold-rule binds it to a `TermValue`
3819/// carrying the current measure value at each descent.
3820pub const RECURSE_IDX_NAME_INDEX: u32 = u32::MAX - 3;
3821
3822/// Wiki ADR-034 Mechanism 2: the foundation-fixed name-index for the
3823/// candidate-value binding inside `Term::FirstAdmit`'s predicate body.
3824/// The grammar form `first_admit(<domain>, |idx_ident| <pred>)` lowers
3825/// `idx_ident` references to
3826/// `Term::Variable { name_index: FIRST_ADMIT_IDX_NAME_INDEX }`; the
3827/// catamorphism's per-variant fold-rule binds it to the current
3828/// candidate `idx` (ranging `0..<Domain>::CYCLE_SIZE`).
3829pub const FIRST_ADMIT_IDX_NAME_INDEX: u32 = u32::MAX - 4;
3830
3831/// Wiki ADR-029: bound on the anamorphic fixpoint iteration for
3832/// `Term::Unfold`. The fold rule iterates `step(state)` until either the
3833/// state reaches a Kleene fixpoint (`step(state) == state`) or this
3834/// ceiling is hit, at which point evaluation returns the most-recent
3835/// state. Foundation-fixed (parallel to `FOLD_UNROLL_THRESHOLD`).
3836/// Wiki ADR-037: a foundation-fixed conservative default for
3837/// [`HostBounds::UNFOLD_ITERATIONS_MAX`].
3838pub const UNFOLD_MAX_ITERATIONS: usize = 256;
3839
3840/// ADR-060: a chunk-emitting source for unbounded `TermValue` payloads
3841/// (tensor-data sections, large signing payloads, multi-GB messages). The
3842/// `Stream` carrier references a `&dyn ChunkSource`; its carrier width is the
3843/// dyn-trait descriptor size — payload-independent, with no byte-width ceiling.
3844/// The σ-projection at ψ_9 folds `Stream` chunks directly into the `Hasher`
3845/// via `Hasher::fold_bytes`; structural fold-rules read the chunks as a flat
3846/// byte sequence via [`ChunkSource::for_each_chunk`]. Peak resident memory is
3847/// `carrier_inline_bytes::<B>()` plus the source's internal state — never the
3848/// full canonical byte sequence.
3849pub trait ChunkSource: core::fmt::Debug {
3850 /// Fold each chunk into the closure in canonical order. The total folded
3851 /// byte count is unbounded; each `&[u8]` slice is valid only for the
3852 /// duration of the call (not retained).
3853 fn for_each_chunk(&self, f: &mut dyn FnMut(&[u8]));
3854 /// Total byte count if statically known, else `None`.
3855 fn total_bytes(&self) -> Option<usize> {
3856 None
3857 }
3858}
3859
3860/// Wiki ADR-029 + ADR-060: a single Term variant's evaluated value, carried
3861/// as a **source-polymorphic** carrier const-generic over its inline width.
3862/// ADR-060 replaces the pre-0.5.0 fixed-4096-byte buffer with three variants:
3863/// - `Inline` — a stack buffer of `INLINE_BYTES` (foundation-derived via
3864/// [`carrier_inline_bytes`]) carrying derived structural content: integer
3865/// literals at any admitted Witt level, cryptographic digests at the
3866/// application's hasher output width, the κ-label ASCII form, and
3867/// primitive-op single-value outputs.
3868/// - `Borrowed` — a slice into an upstream byte source (input bytes, a
3869/// sibling ψ-stage's scratch, an axis-kernel output region). Its carrier
3870/// width is `size_of::<&[u8]>()`, independent of payload size.
3871/// - `Stream` — a chunk-emitting source for unbounded payloads, no ceiling.
3872///
3873/// `INLINE_BYTES` is a free const-generic parameter; the application
3874/// instantiates it at the boundary via `carrier_inline_bytes::<MyBounds>()`
3875/// (per the min-const-generics pattern, stable Rust, no `generic_const_exprs`).
3876#[derive(Debug, Clone, Copy)]
3877pub enum TermValue<'a, const INLINE_BYTES: usize> {
3878 /// Stack-allocated inline buffer (zero-padded beyond `len`).
3879 Inline {
3880 /// Fixed-capacity inline byte buffer.
3881 bytes: [u8; INLINE_BYTES],
3882 /// Active prefix length (`<= INLINE_BYTES`).
3883 len: usize,
3884 },
3885 /// Borrowed slice into an upstream byte source — no byte-width ceiling.
3886 Borrowed(&'a [u8]),
3887 /// Chunk-emitting source for unbounded payloads — no byte-width ceiling.
3888 Stream(&'a dyn ChunkSource),
3889}
3890
3891impl<'a, const INLINE_BYTES: usize> PartialEq for TermValue<'a, INLINE_BYTES> {
3892 fn eq(&self, other: &Self) -> bool {
3893 match (self, other) {
3894 (TermValue::Stream(a), TermValue::Stream(b)) => core::ptr::eq(
3895 (*a as *const dyn ChunkSource).cast::<u8>(),
3896 (*b as *const dyn ChunkSource).cast::<u8>(),
3897 ),
3898 (TermValue::Stream(_), _) | (_, TermValue::Stream(_)) => false,
3899 _ => self.as_slice() == other.as_slice(),
3900 }
3901 }
3902}
3903impl<'a, const INLINE_BYTES: usize> Eq for TermValue<'a, INLINE_BYTES> {}
3904
3905impl<'a, const INLINE_BYTES: usize> TermValue<'a, INLINE_BYTES> {
3906 /// Construct an empty inline `TermValue` (length zero).
3907 #[must_use]
3908 pub const fn empty() -> Self {
3909 TermValue::Inline {
3910 bytes: [0u8; INLINE_BYTES],
3911 len: 0,
3912 }
3913 }
3914
3915 /// ADR-060: construct an `Inline` `TermValue` by copying up to
3916 /// `INLINE_BYTES` bytes from `bytes`. For payloads exceeding the inline
3917 /// width use [`TermValue::Borrowed`] / [`TermValue::Stream`] instead.
3918 #[must_use]
3919 pub const fn inline_from_slice(bytes: &[u8]) -> Self {
3920 let mut buf = [0u8; INLINE_BYTES];
3921 let copy_len = if bytes.len() > INLINE_BYTES {
3922 INLINE_BYTES
3923 } else {
3924 bytes.len()
3925 };
3926 let mut i = 0;
3927 while i < copy_len {
3928 buf[i] = bytes[i];
3929 i += 1;
3930 }
3931 TermValue::Inline {
3932 bytes: buf,
3933 len: copy_len,
3934 }
3935 }
3936
3937 /// Construct a zero-copy `Borrowed` `TermValue` referencing `bytes`.
3938 #[must_use]
3939 pub const fn borrowed(bytes: &'a [u8]) -> Self {
3940 TermValue::Borrowed(bytes)
3941 }
3942
3943 /// Construct a `Stream` `TermValue` over an unbounded chunk source.
3944 #[must_use]
3945 pub const fn stream(source: &'a dyn ChunkSource) -> Self {
3946 TermValue::Stream(source)
3947 }
3948
3949 /// ADR-051: construct an `Inline` `TermValue` from a u64 at a declared byte width.
3950 #[must_use]
3951 pub const fn from_u64_be(value: u64, width: usize) -> Self {
3952 let w = if width > 8 { 8 } else { width };
3953 let be = value.to_be_bytes();
3954 let mut buf = [0u8; INLINE_BYTES];
3955 let cap = if w > INLINE_BYTES { INLINE_BYTES } else { w };
3956 let mut i = 0;
3957 while i < cap {
3958 buf[i] = be[8 - w + i];
3959 i += 1;
3960 }
3961 TermValue::Inline {
3962 bytes: buf,
3963 len: cap,
3964 }
3965 }
3966
3967 /// ADR-060: const-constructor from a prepared inline buffer of width `INLINE_BYTES`.
3968 #[must_use]
3969 pub const fn from_inline_const(bytes: &[u8; INLINE_BYTES], len: usize) -> Self {
3970 let l = if len > INLINE_BYTES {
3971 INLINE_BYTES
3972 } else {
3973 len
3974 };
3975 TermValue::Inline {
3976 bytes: *bytes,
3977 len: l,
3978 }
3979 }
3980
3981 /// Returns the active byte prefix for materializable carriers
3982 /// (`Inline` / `Borrowed`), or `None` for `Stream` (which has no single
3983 /// contiguous slice — read it via [`TermValue::for_each_chunk`]).
3984 #[inline]
3985 #[must_use]
3986 pub fn as_slice(&self) -> Option<&[u8]> {
3987 match self {
3988 TermValue::Inline { bytes, len } => Some(&bytes[..*len]),
3989 TermValue::Borrowed(s) => Some(s),
3990 TermValue::Stream(_) => None,
3991 }
3992 }
3993
3994 /// Returns the active byte prefix for `Inline` / `Borrowed`, or the empty
3995 /// slice for `Stream`. Structural fold-rules that never produce `Stream`
3996 /// use this; `Stream`-aware consumers use [`TermValue::for_each_chunk`].
3997 #[inline]
3998 #[must_use]
3999 pub fn bytes(&self) -> &[u8] {
4000 match self {
4001 TermValue::Inline { bytes, len } => &bytes[..*len],
4002 TermValue::Borrowed(s) => s,
4003 TermValue::Stream(_) => &[],
4004 }
4005 }
4006
4007 /// ADR-060: fold every byte of the carrier into `f` in canonical order,
4008 /// dispatching on the variant — `Inline` / `Borrowed` emit a single chunk,
4009 /// `Stream` delegates to [`ChunkSource::for_each_chunk`]. This is the
4010 /// universal reader the σ-projection folds through `Hasher::fold_bytes`.
4011 pub fn for_each_chunk(&self, f: &mut dyn FnMut(&[u8])) {
4012 match self {
4013 TermValue::Inline { bytes, len } => f(&bytes[..*len]),
4014 TermValue::Borrowed(s) => f(s),
4015 TermValue::Stream(src) => src.for_each_chunk(f),
4016 }
4017 }
4018
4019 /// Total byte length if known: `Inline`/`Borrowed` always; `Stream` only
4020 /// when its source reports [`ChunkSource::total_bytes`].
4021 #[must_use]
4022 pub fn len_hint(&self) -> Option<usize> {
4023 match self {
4024 TermValue::Inline { len, .. } => Some(*len),
4025 TermValue::Borrowed(s) => Some(s.len()),
4026 TermValue::Stream(src) => src.total_bytes(),
4027 }
4028 }
4029
4030 /// ADR-060: opt-in, `alloc`-gated materialization into an owned buffer —
4031 /// the only allocation surface, per caller. Folds every chunk into a
4032 /// `Vec<u8>` (works for all three variants, including `Stream`).
4033 #[cfg(feature = "alloc")]
4034 #[must_use]
4035 #[allow(clippy::wrong_self_convention)]
4036 pub fn to_vec(&self) -> alloc::vec::Vec<u8> {
4037 let mut out = alloc::vec::Vec::new();
4038 self.for_each_chunk(&mut |chunk| out.extend_from_slice(chunk));
4039 out
4040 }
4041}
4042
4043/// ADR-051: construct a `Term::Literal` from a u64 value at a declared Witt level.
4044/// Packs `value` as a big-endian byte sequence whose length equals the level's
4045/// byte width (`level.witt_length() / 8`). For widths > 8 bytes, the high bytes
4046/// are zero-padded.
4047#[must_use]
4048pub const fn literal_u64<const INLINE_BYTES: usize>(
4049 value: u64,
4050 level: crate::WittLevel,
4051) -> crate::enforcement::Term<'static, INLINE_BYTES> {
4052 let mut width = (level.witt_length() / 8) as usize;
4053 if width == 0 {
4054 width = 1;
4055 }
4056 // ADR-060: literals fit inline by construction (carrier_inline_bytes >=
4057 // WITT_LEVEL_MAX_BITS/8); clamp defensively so const eval never indexes
4058 // past the derived inline width.
4059 if width > INLINE_BYTES {
4060 width = INLINE_BYTES;
4061 }
4062 let be = value.to_be_bytes();
4063 let mut buf = [0u8; INLINE_BYTES];
4064 // Pack the u64's big-endian bytes into the low `min(width, 8)` bytes
4065 // of the result, right-aligned. Widths > 8 zero-pad the high portion.
4066 let take = if width > 8 { 8 } else { width };
4067 let mut i = 0;
4068 while i < take {
4069 buf[width - take + i] = be[8 - take + i];
4070 i += 1;
4071 }
4072 crate::enforcement::Term::Literal {
4073 value: TermValue::from_inline_const(&buf, width),
4074 level,
4075 }
4076}
4077
4078/// ADR-051: construct a `Term::Literal` from raw bytes at a declared Witt level.
4079/// `bytes` MUST have exactly `level.witt_length() / 8` bytes; mismatched lengths
4080/// are silently truncated/padded by `TermValue::inline_from_slice`. Use this for
4081/// wide-Witt literals (W128+) that don't fit in a u64 (they fit the
4082/// foundation-derived inline width per ADR-060).
4083#[must_use]
4084pub const fn literal_bytes<const INLINE_BYTES: usize>(
4085 bytes: &[u8],
4086 level: crate::WittLevel,
4087) -> crate::enforcement::Term<'static, INLINE_BYTES> {
4088 crate::enforcement::Term::Literal {
4089 value: TermValue::inline_from_slice(bytes),
4090 level,
4091 }
4092}
4093
4094/// Wiki ADR-029: catamorphism evaluator over the route's Term tree.
4095/// Per-variant fold rules:
4096/// - `Term::Literal { value, level }` — emit `value` as big-endian bytes at the
4097/// byte width of `level`.
4098/// - `Term::Variable { name_index }` — `name_index = 0` returns the route
4099/// input bytes; other indices look up `let`-introduced bindings (current
4100/// iteration: not yet supported, returns the input bytes for any non-zero
4101/// index).
4102/// - `Term::Application { operator, args }` — evaluate each arg and apply
4103/// the `PrimitiveOp` per its algebraic rule.
4104/// - `Term::Lift { operand, target }` — evaluate operand, zero-extend to
4105/// `target` Witt level's byte width.
4106/// - `Term::Project { operand, target }` — evaluate operand, truncate to
4107/// `target` Witt level's byte width.
4108/// - `Term::Match { scrutinee, arms }` — evaluate scrutinee, match arms by
4109/// literal-byte equality (wildcard arm `name_index = u32::MAX` matches
4110/// unconditionally).
4111/// - `Term::Recurse { measure, base, step }` — bounded recursion: evaluate
4112/// measure → n; if n = 0 evaluate base; otherwise iterate step n times with
4113/// the recursive-call placeholder bound to the previous iteration's result.
4114/// - `Term::Unfold { seed, step }` — anamorphism: evaluate seed → state₀;
4115/// iterate step (with the state placeholder bound to the current state)
4116/// until a Kleene fixpoint or `UNFOLD_MAX_ITERATIONS` is reached.
4117/// - `Term::Try { body, handler }` — evaluate body; on failure, propagate
4118/// if `handler_index = u32::MAX`, otherwise evaluate handler.
4119/// - `Term::AxisInvocation { axis_index, kernel_id, input_index }` — dispatch
4120/// the input's bytes to the application's `AxisTuple` (ADR-030); the
4121/// foundation-canonical (axis_index=0, kernel_id=0) folds through the
4122/// selected `Hasher` impl. Replaces the legacy `HasherProjection` variant.
4123/// # Errors
4124/// Returns [`PipelineFailure`] when the term tree is malformed (out-of-bounds
4125/// index, level mismatch, exhausted match without wildcard arm, etc.).
4126pub fn evaluate_term_tree<'a, 'r, A, R, const INLINE_BYTES: usize, const FP_MAX: usize>(
4127 arena: &'a [crate::enforcement::Term<'a, INLINE_BYTES>],
4128 input: TermValue<'a, INLINE_BYTES>,
4129 // ADR-060: the resolver borrow `'r` is decoupled from the value/output
4130 // lifetime `'a` — resolvers are consulted only during evaluation, so a
4131 // locally-constructed resolver tuple (e.g. the `prism_model!`-emitted
4132 // `forward` body's) can drive an evaluation whose output carrier escapes
4133 // with the route input's `'a`.
4134 resolvers: &'r R,
4135) -> Result<TermValue<'a, INLINE_BYTES>, PipelineFailure>
4136where
4137 A: crate::pipeline::AxisTuple<INLINE_BYTES, FP_MAX> + crate::enforcement::Hasher<FP_MAX> + 'a,
4138 R: crate::pipeline::ResolverTuple
4139 + crate::pipeline::HasNerveResolver<INLINE_BYTES, A>
4140 + crate::pipeline::HasChainComplexResolver<INLINE_BYTES, A>
4141 + crate::pipeline::HasHomologyGroupResolver<INLINE_BYTES, A>
4142 + crate::pipeline::HasCochainComplexResolver<INLINE_BYTES, A>
4143 + crate::pipeline::HasCohomologyGroupResolver<INLINE_BYTES, A>
4144 + crate::pipeline::HasPostnikovResolver<INLINE_BYTES, A>
4145 + crate::pipeline::HasHomotopyGroupResolver<INLINE_BYTES, A>
4146 + crate::pipeline::HasKInvariantResolver<INLINE_BYTES, A>,
4147{
4148 if arena.is_empty() {
4149 // ADR-060: identity route — output IS the input carrier itself
4150 // (Inline/Borrowed/Stream, no inline-width ceiling so arbitrarily
4151 // large inputs pass through unchanged).
4152 return Ok(input);
4153 }
4154 // Canonical convention: the root term is the last entry in the
4155 // arena (the `prism_model!` macro emits in post-order, so the root
4156 // is the final node).
4157 let root_idx = arena.len() - 1;
4158 evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4159 arena, root_idx, input, None, None, None, None, resolvers,
4160 )
4161}
4162
4163#[allow(clippy::too_many_arguments)]
4164fn evaluate_term_at<'a, 'b, 'r, A, R, const INLINE_BYTES: usize, const FP_MAX: usize>(
4165 arena: &'a [crate::enforcement::Term<'a, INLINE_BYTES>],
4166 idx: usize,
4167 input: TermValue<'a, INLINE_BYTES>,
4168 recurse_value: Option<&'b [u8]>,
4169 recurse_idx_value: Option<&'b [u8]>,
4170 unfold_value: Option<&'b [u8]>,
4171 first_admit_idx_value: Option<&'b [u8]>,
4172 resolvers: &'r R,
4173) -> Result<TermValue<'a, INLINE_BYTES>, PipelineFailure>
4174where
4175 A: crate::pipeline::AxisTuple<INLINE_BYTES, FP_MAX> + crate::enforcement::Hasher<FP_MAX> + 'a,
4176 R: crate::pipeline::ResolverTuple
4177 + crate::pipeline::HasNerveResolver<INLINE_BYTES, A>
4178 + crate::pipeline::HasChainComplexResolver<INLINE_BYTES, A>
4179 + crate::pipeline::HasHomologyGroupResolver<INLINE_BYTES, A>
4180 + crate::pipeline::HasCochainComplexResolver<INLINE_BYTES, A>
4181 + crate::pipeline::HasCohomologyGroupResolver<INLINE_BYTES, A>
4182 + crate::pipeline::HasPostnikovResolver<INLINE_BYTES, A>
4183 + crate::pipeline::HasHomotopyGroupResolver<INLINE_BYTES, A>
4184 + crate::pipeline::HasKInvariantResolver<INLINE_BYTES, A>,
4185{
4186 if idx >= arena.len() {
4187 return Err(PipelineFailure::ShapeViolation {
4188 report: crate::enforcement::ShapeViolation {
4189 shape_iri: "https://uor.foundation/pipeline/TermArenaShape",
4190 constraint_iri: "https://uor.foundation/pipeline/TermArenaShape/inBounds",
4191 property_iri: "https://uor.foundation/pipeline/termIndex",
4192 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
4193 min_count: 0,
4194 max_count: arena.len() as u32,
4195 kind: crate::ViolationKind::ValueCheck,
4196 },
4197 });
4198 }
4199 match arena[idx] {
4200 crate::enforcement::Term::Literal { value, level: _ } => {
4201 // ADR-051 + ADR-060: the literal's value IS a source-polymorphic
4202 // carrier (`TermValue<'a, INLINE_BYTES>`, Copy) whose byte length
4203 // matches `level.witt_length() / 8`. The fold-rule returns it
4204 // directly, preserving the carrier variant.
4205 Ok(value)
4206 }
4207 crate::enforcement::Term::Variable { name_index } => {
4208 // ADR-022 D3 G2: name_index = 0 is the route input slot.
4209 // ADR-029: name_index = RECURSE_PLACEHOLDER_NAME_INDEX is the
4210 // recursive-call placeholder bound to `recurse_value`.
4211 // ADR-029: name_index = UNFOLD_PLACEHOLDER_NAME_INDEX is the
4212 // unfold state placeholder bound to `unfold_value` (the
4213 // current iteration's accumulated state — see the
4214 // `Term::Unfold` fold-rule below).
4215 // Other indices reference let-bindings (G10), which the
4216 // current macro emission resolves at expansion time via
4217 // splice into the calling arena (so the binding's value-tree
4218 // root is what the catamorphism actually walks).
4219 if name_index == RECURSE_PLACEHOLDER_NAME_INDEX {
4220 return Ok(TermValue::inline_from_slice(recurse_value.unwrap_or(&[])));
4221 }
4222 // ADR-034 Mechanism 1: iteration-counter binding for Recurse.
4223 if name_index == RECURSE_IDX_NAME_INDEX {
4224 return Ok(TermValue::inline_from_slice(
4225 recurse_idx_value.unwrap_or(&[]),
4226 ));
4227 }
4228 if name_index == UNFOLD_PLACEHOLDER_NAME_INDEX {
4229 return Ok(TermValue::inline_from_slice(unfold_value.unwrap_or(&[])));
4230 }
4231 // ADR-034 Mechanism 2: candidate-value binding for FirstAdmit.
4232 if name_index == FIRST_ADMIT_IDX_NAME_INDEX {
4233 return Ok(TermValue::inline_from_slice(
4234 first_admit_idx_value.unwrap_or(&[]),
4235 ));
4236 }
4237 // ADR-022 D3 G2 + ADR-060: name_index = 0 returns the route
4238 // input carrier itself (Inline/Borrowed/Stream) — no copy, no cap.
4239 Ok(input)
4240 }
4241 crate::enforcement::Term::Application { operator, args } => {
4242 let start = args.start as usize;
4243 let len = args.len as usize;
4244 apply_primitive_op::<A, R, INLINE_BYTES, FP_MAX>(
4245 arena,
4246 operator,
4247 start,
4248 len,
4249 input,
4250 recurse_value,
4251 recurse_idx_value,
4252 unfold_value,
4253 first_admit_idx_value,
4254 resolvers,
4255 )
4256 }
4257 crate::enforcement::Term::Lift {
4258 operand_index,
4259 target,
4260 } => {
4261 let v = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4262 arena,
4263 operand_index as usize,
4264 input,
4265 recurse_value,
4266 recurse_idx_value,
4267 unfold_value,
4268 first_admit_idx_value,
4269 resolvers,
4270 )?;
4271 let target_width = (target.witt_length() / 8) as usize;
4272 let target_width = if target_width > INLINE_BYTES {
4273 INLINE_BYTES
4274 } else if target_width == 0 {
4275 1
4276 } else {
4277 target_width
4278 };
4279 let mut buf = [0u8; INLINE_BYTES];
4280 // Big-endian zero-extend: pad the high bytes with zeros.
4281 let src = v.bytes();
4282 let pad = target_width.saturating_sub(src.len());
4283 let mut i = 0;
4284 while i < src.len() && pad + i < target_width {
4285 buf[pad + i] = src[i];
4286 i += 1;
4287 }
4288 Ok(TermValue::Inline {
4289 bytes: buf,
4290 len: target_width,
4291 })
4292 }
4293 crate::enforcement::Term::Project {
4294 operand_index,
4295 target,
4296 } => {
4297 let v = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4298 arena,
4299 operand_index as usize,
4300 input,
4301 recurse_value,
4302 recurse_idx_value,
4303 unfold_value,
4304 first_admit_idx_value,
4305 resolvers,
4306 )?;
4307 let target_width = (target.witt_length() / 8) as usize;
4308 let target_width = if target_width > INLINE_BYTES {
4309 INLINE_BYTES
4310 } else if target_width == 0 {
4311 1
4312 } else {
4313 target_width
4314 };
4315 let src = v.bytes();
4316 // Big-endian truncation: take the trailing `target_width` bytes.
4317 let take_from = src.len().saturating_sub(target_width);
4318 Ok(TermValue::inline_from_slice(&src[take_from..]))
4319 }
4320 crate::enforcement::Term::Match {
4321 scrutinee_index,
4322 arms,
4323 } => {
4324 let scrutinee = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4325 arena,
4326 scrutinee_index as usize,
4327 input,
4328 recurse_value,
4329 recurse_idx_value,
4330 unfold_value,
4331 first_admit_idx_value,
4332 resolvers,
4333 )?;
4334 let start = arms.start as usize;
4335 let count = arms.len as usize;
4336 // Arms alternate (pattern, body) per ADR-022 D3 G6.
4337 let mut i = 0usize;
4338 while i + 1 < count {
4339 let pattern_idx = start + i;
4340 let body_idx = start + i + 1;
4341 let is_wildcard = matches!(
4342 arena[pattern_idx],
4343 crate::enforcement::Term::Variable { name_index } if name_index == u32::MAX
4344 );
4345 if is_wildcard {
4346 return evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4347 arena,
4348 body_idx,
4349 input,
4350 recurse_value,
4351 recurse_idx_value,
4352 unfold_value,
4353 first_admit_idx_value,
4354 resolvers,
4355 );
4356 }
4357 let pattern_val = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4358 arena,
4359 pattern_idx,
4360 input,
4361 recurse_value,
4362 recurse_idx_value,
4363 unfold_value,
4364 first_admit_idx_value,
4365 resolvers,
4366 )?;
4367 if pattern_val.bytes() == scrutinee.bytes() {
4368 return evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4369 arena,
4370 body_idx,
4371 input,
4372 recurse_value,
4373 recurse_idx_value,
4374 unfold_value,
4375 first_admit_idx_value,
4376 resolvers,
4377 );
4378 }
4379 i += 2;
4380 }
4381 // Per ADR-022 D3 G6 the macro enforces wildcard exhaustiveness;
4382 // a well-formed Term tree never reaches this branch.
4383 Err(PipelineFailure::ShapeViolation {
4384 report: crate::enforcement::ShapeViolation {
4385 shape_iri: "https://uor.foundation/pipeline/MatchExhaustivenessShape",
4386 constraint_iri:
4387 "https://uor.foundation/pipeline/MatchExhaustivenessShape/wildcard",
4388 property_iri: "https://uor.foundation/pipeline/matchArms",
4389 expected_range: "http://www.w3.org/2001/XMLSchema#string",
4390 min_count: 1,
4391 max_count: 0,
4392 kind: crate::ViolationKind::Missing,
4393 },
4394 })
4395 }
4396 crate::enforcement::Term::Recurse {
4397 measure_index,
4398 base_index,
4399 step_index,
4400 } => {
4401 // Wiki ADR-029 recursive fold: evaluate measure once to get N;
4402 // if N == 0 evaluate base; else iterate step N times, threading
4403 // each iteration's result as the recurse_value (the recursive-
4404 // call placeholder bound to a fresh-name-indexed Variable per
4405 // ADR-029, with the placeholder's name_index resolving via the
4406 // RECURSE_PLACEHOLDER_NAME_INDEX sentinel handled in the Variable
4407 // arm). The outer recurse_value is preserved for nested Recurse
4408 // forms within the measure/base computations; step body uses the
4409 // iteration's accumulator.
4410 let measure = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4411 arena,
4412 measure_index as usize,
4413 input,
4414 recurse_value,
4415 recurse_idx_value,
4416 unfold_value,
4417 first_admit_idx_value,
4418 resolvers,
4419 )?;
4420 let n = bytes_to_u64_be(measure.bytes());
4421 let base_val = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4422 arena,
4423 base_index as usize,
4424 input,
4425 recurse_value,
4426 recurse_idx_value,
4427 unfold_value,
4428 first_admit_idx_value,
4429 resolvers,
4430 )?;
4431 if n == 0 {
4432 return Ok(base_val);
4433 }
4434 // Iterate step N times. Each iteration's `current` becomes the
4435 // next iteration's recurse_value. The descent measure is the
4436 // bound; well-foundedness holds by monotonic decrease.
4437 let mut current_buf = [0u8; INLINE_BYTES];
4438 let mut current_len = base_val.bytes().len();
4439 let mut k = 0;
4440 while k < current_len {
4441 current_buf[k] = base_val.bytes()[k];
4442 k += 1;
4443 }
4444 let mut iter = 0u64;
4445 while iter < n {
4446 // ADR-034 Mechanism 1: bind RECURSE_IDX_NAME_INDEX to the
4447 // current measure value (the iteration counter at this
4448 // descent). At iter=0 the descent measure is N; at iter=k
4449 // it is N-k. The byte width follows the measure's BE-truncated
4450 // u64 packing so callers can read it as a u64-shaped value.
4451 let descent_measure: u64 = n - iter;
4452 let descent_bytes = descent_measure.to_be_bytes();
4453 let next = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4454 arena,
4455 step_index as usize,
4456 input,
4457 Some(¤t_buf[..current_len]),
4458 Some(&descent_bytes[..]),
4459 unfold_value,
4460 first_admit_idx_value,
4461 resolvers,
4462 )?;
4463 let nb = next.bytes();
4464 let copy_len = if nb.len() > INLINE_BYTES {
4465 INLINE_BYTES
4466 } else {
4467 nb.len()
4468 };
4469 let mut j = 0;
4470 while j < copy_len {
4471 current_buf[j] = nb[j];
4472 j += 1;
4473 }
4474 current_len = copy_len;
4475 iter += 1;
4476 }
4477 Ok(TermValue::inline_from_slice(¤t_buf[..current_len]))
4478 }
4479 crate::enforcement::Term::Unfold {
4480 seed_index,
4481 step_index,
4482 } => {
4483 // ADR-029 anamorphism: evaluate seed → state₀; iterate step
4484 // with the state placeholder (UNFOLD_PLACEHOLDER_NAME_INDEX,
4485 // bound to the current state) until either a Kleene fixpoint
4486 // (step(state) == state) or UNFOLD_MAX_ITERATIONS is reached.
4487 // Well-foundedness: bounded by UNFOLD_MAX_ITERATIONS.
4488 // The outer unfold_value is preserved for nested Unfold forms
4489 // within the seed; step body's state placeholder uses the
4490 // iteration's accumulator.
4491 let seed_val = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4492 arena,
4493 seed_index as usize,
4494 input,
4495 recurse_value,
4496 recurse_idx_value,
4497 unfold_value,
4498 first_admit_idx_value,
4499 resolvers,
4500 )?;
4501 let mut state_buf = [0u8; INLINE_BYTES];
4502 let mut state_len = seed_val.bytes().len();
4503 let mut k = 0;
4504 while k < state_len {
4505 state_buf[k] = seed_val.bytes()[k];
4506 k += 1;
4507 }
4508 let mut iter = 0usize;
4509 while iter < UNFOLD_MAX_ITERATIONS {
4510 let next = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4511 arena,
4512 step_index as usize,
4513 input,
4514 recurse_value,
4515 recurse_idx_value,
4516 Some(&state_buf[..state_len]),
4517 first_admit_idx_value,
4518 resolvers,
4519 )?;
4520 let nb = next.bytes();
4521 // Kleene fixpoint check: if step(state) == state, return.
4522 if nb.len() == state_len && nb == &state_buf[..state_len] {
4523 return Ok(TermValue::inline_from_slice(&state_buf[..state_len]));
4524 }
4525 let copy_len = if nb.len() > INLINE_BYTES {
4526 INLINE_BYTES
4527 } else {
4528 nb.len()
4529 };
4530 let mut j = 0;
4531 while j < copy_len {
4532 state_buf[j] = nb[j];
4533 j += 1;
4534 }
4535 state_len = copy_len;
4536 iter += 1;
4537 }
4538 Ok(TermValue::inline_from_slice(&state_buf[..state_len]))
4539 }
4540 crate::enforcement::Term::Try {
4541 body_index,
4542 handler_index,
4543 } => {
4544 match evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4545 arena,
4546 body_index as usize,
4547 input,
4548 recurse_value,
4549 recurse_idx_value,
4550 unfold_value,
4551 first_admit_idx_value,
4552 resolvers,
4553 ) {
4554 Ok(v) => Ok(v),
4555 Err(e) => {
4556 if handler_index == u32::MAX {
4557 Err(e)
4558 } else {
4559 evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4560 arena,
4561 handler_index as usize,
4562 input,
4563 recurse_value,
4564 recurse_idx_value,
4565 unfold_value,
4566 first_admit_idx_value,
4567 resolvers,
4568 )
4569 }
4570 }
4571 }
4572 }
4573 crate::enforcement::Term::AxisInvocation {
4574 axis_index,
4575 kernel_id,
4576 input_index,
4577 } => {
4578 // ADR-055: read the axis's SubstrateTermBody::body_arena() via
4579 // `AxisTuple::body_arena_at`. When non-empty, recursively fold
4580 // the body with the evaluated kernel input bound as input;
4581 // when empty (primitive-fast-path interpretation), dispatch the
4582 // kernel function directly per the optional fast-path per ADR-055.
4583 //
4584 // The foundation-built blanket `impl<H: Hasher> AxisTuple for H`
4585 // routes the canonical hash dispatch (axis 0, kernel 0) through
4586 // the legacy Hasher API (empty body); user-declared axes via the
4587 // `axis!` SDK macro extend the dispatch surface to additional
4588 // (axis_index, kernel_id) combinations and may provide substrate-
4589 // Term bodies the catamorphism walks structurally.
4590 let v = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4591 arena,
4592 input_index as usize,
4593 input,
4594 recurse_value,
4595 recurse_idx_value,
4596 unfold_value,
4597 first_admit_idx_value,
4598 resolvers,
4599 )?;
4600 let body =
4601 <A as crate::pipeline::AxisTuple<INLINE_BYTES, FP_MAX>>::body_arena_at(axis_index);
4602 if body.is_empty() {
4603 if axis_index == 0 && kernel_id == 0 {
4604 // ADR-060: the canonical σ-projection / hash axis (axis 0,
4605 // kernel 0) folds the operand carrier through the `Hasher`
4606 // chunk-by-chunk via `for_each_chunk`, so a `Stream` or
4607 // large `Borrowed` operand of arbitrary size folds without
4608 // materialization. (The `v.bytes()` dispatch path would see
4609 // an empty slice for a `Stream` operand.) The digest is
4610 // always Inline — its width is bounded by the application's
4611 // fingerprint width, ≤ the derived inline width.
4612 let mut hasher = <A as crate::enforcement::Hasher<FP_MAX>>::initial();
4613 v.for_each_chunk(&mut |chunk| {
4614 hasher = core::mem::replace(
4615 &mut hasher,
4616 <A as crate::enforcement::Hasher<FP_MAX>>::initial(),
4617 )
4618 .fold_bytes(chunk);
4619 });
4620 let digest = hasher.finalize();
4621 let n_max = <A as crate::enforcement::Hasher<FP_MAX>>::OUTPUT_BYTES;
4622 let width = if n_max > INLINE_BYTES {
4623 INLINE_BYTES
4624 } else {
4625 n_max
4626 };
4627 return Ok(TermValue::inline_from_slice(&digest[..width]));
4628 }
4629 // Primitive fast-path: dispatch the kernel function directly on
4630 // the bounded operand bytes (user-declared axes operate on
4631 // bounded values; `v.bytes()` yields the Inline/Borrowed slice).
4632 let mut out = [0u8; INLINE_BYTES];
4633 let written =
4634 match <A as crate::pipeline::AxisTuple<INLINE_BYTES, FP_MAX>>::dispatch(
4635 axis_index,
4636 kernel_id,
4637 v.bytes(),
4638 &mut out,
4639 ) {
4640 Ok(n) => n,
4641 Err(report) => return Err(PipelineFailure::ShapeViolation { report }),
4642 };
4643 let width = if written > INLINE_BYTES {
4644 INLINE_BYTES
4645 } else {
4646 written
4647 };
4648 Ok(TermValue::inline_from_slice(&out[..width]))
4649 } else {
4650 // ADR-055 recursive-fold path: walk the axis's substrate-Term
4651 // body with the evaluated kernel input bound in scope. The
4652 // body's root term is by convention the last entry in the arena.
4653 let root = body.len() - 1;
4654 // The evaluated kernel input carrier `v` is threaded as the
4655 // body's input; the recursively-folded body result is
4656 // materialized into an owned `Inline` carrier so it does not
4657 // borrow the body arena's transient scope.
4658 let folded = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4659 body,
4660 root,
4661 v,
4662 recurse_value,
4663 recurse_idx_value,
4664 unfold_value,
4665 first_admit_idx_value,
4666 resolvers,
4667 )?;
4668 Ok(TermValue::inline_from_slice(folded.bytes()))
4669 }
4670 }
4671 crate::enforcement::Term::ProjectField {
4672 source_index,
4673 byte_offset,
4674 byte_length,
4675 } => {
4676 // ADR-033 G20: evaluate source, slice [byte_offset .. byte_offset+byte_length].
4677 let v = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4678 arena,
4679 source_index as usize,
4680 input,
4681 recurse_value,
4682 recurse_idx_value,
4683 unfold_value,
4684 first_admit_idx_value,
4685 resolvers,
4686 )?;
4687 let bytes = v.bytes();
4688 let start = byte_offset as usize;
4689 let end = start.saturating_add(byte_length as usize);
4690 if end > bytes.len() {
4691 return Err(PipelineFailure::ShapeViolation {
4692 report: crate::enforcement::ShapeViolation {
4693 shape_iri: "https://uor.foundation/pipeline/ProjectFieldShape",
4694 constraint_iri:
4695 "https://uor.foundation/pipeline/ProjectFieldShape/inBounds",
4696 property_iri: "https://uor.foundation/pipeline/byteOffset",
4697 expected_range: "https://uor.foundation/pipeline/SourceByteRange",
4698 min_count: 0,
4699 max_count: 1,
4700 kind: crate::ViolationKind::ValueCheck,
4701 },
4702 });
4703 }
4704 Ok(TermValue::inline_from_slice(&bytes[start..end]))
4705 }
4706 crate::enforcement::Term::FirstAdmit {
4707 domain_size_index,
4708 predicate_index,
4709 } => {
4710 // ADR-034 Mechanism 2: bounded search with structural early
4711 // termination. Evaluate the domain size N; iterate idx in 0..N
4712 // ascending; for each idx, evaluate predicate with
4713 // FIRST_ADMIT_IDX_NAME_INDEX bound to idx. Return on the first
4714 // non-zero predicate result (the "found" coproduct value
4715 // 0x01 || idx_bytes); after exhausting the domain return the
4716 // "not-found" coproduct value 0x00 || idx-width zero bytes.
4717 let domain_size = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4718 arena,
4719 domain_size_index as usize,
4720 input,
4721 recurse_value,
4722 recurse_idx_value,
4723 unfold_value,
4724 first_admit_idx_value,
4725 resolvers,
4726 )?;
4727 let n = bytes_to_u64_be(domain_size.bytes());
4728 // Determine the idx byte width from the domain size's
4729 // BE-truncated u64 packing (use the smallest non-zero count of
4730 // bytes needed to represent N). For N=0 fall back to 1 byte so
4731 // the not-found sentinel still has the canonical (disc, idx) shape.
4732 let idx_byte_width: usize = if n == 0 {
4733 1
4734 } else {
4735 let mut w = 8usize;
4736 while w > 1 && (n >> ((w - 1) * 8)) == 0 {
4737 w -= 1;
4738 }
4739 w
4740 };
4741 let mut idx_iter: u64 = 0;
4742 while idx_iter < n {
4743 let idx_bytes_full = idx_iter.to_be_bytes();
4744 let idx_bytes = &idx_bytes_full[8 - idx_byte_width..];
4745 let pred_val = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4746 arena,
4747 predicate_index as usize,
4748 input,
4749 recurse_value,
4750 recurse_idx_value,
4751 unfold_value,
4752 Some(idx_bytes),
4753 resolvers,
4754 )?;
4755 // ADR-029 zero/non-zero convention: any TermValue whose
4756 // byte sequence has at least one non-zero byte counts as
4757 // "true". An empty TermValue is treated as "false".
4758 let pb = pred_val.bytes();
4759 let mut admitted = false;
4760 let mut bi = 0;
4761 while bi < pb.len() {
4762 if pb[bi] != 0 {
4763 admitted = true;
4764 break;
4765 }
4766 bi += 1;
4767 }
4768 if admitted {
4769 let mut out_buf = [0u8; INLINE_BYTES];
4770 out_buf[0] = 0x01;
4771 let mut k = 0;
4772 while k < idx_byte_width {
4773 out_buf[1 + k] = idx_bytes[k];
4774 k += 1;
4775 }
4776 return Ok(TermValue::inline_from_slice(&out_buf[..1 + idx_byte_width]));
4777 }
4778 idx_iter += 1;
4779 }
4780 // No idx admitted — emit not-found coproduct value.
4781 let mut out_buf = [0u8; INLINE_BYTES];
4782 out_buf[0] = 0x00;
4783 // bytes 1..1+idx_byte_width remain zero (structural padding).
4784 Ok(TermValue::inline_from_slice(&out_buf[..1 + idx_byte_width]))
4785 }
4786 crate::enforcement::Term::Nerve { value_index } => {
4787 // ADR-035 + ADR-036 + ADR-060: resolver-bound ψ-Term variant.
4788 // Evaluate the operand subtree to a source-polymorphic carrier,
4789 // then dispatch it through the application's resolver via the
4790 // `Has<Category>Resolver` accessor. The resolver returns the
4791 // variant's `TermValue` directly (Inline / Borrowed / Stream);
4792 // resolver-side `Err` propagates as
4793 // `PipelineFailure::ShapeViolation` (the Null defaults emit
4794 // `RESOLVER_ABSENT`, recoverable via `Term::Try`).
4795 let operand = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4796 arena,
4797 value_index as usize,
4798 input,
4799 recurse_value,
4800 recurse_idx_value,
4801 unfold_value,
4802 first_admit_idx_value,
4803 resolvers,
4804 )?;
4805 match resolvers.nerve_resolver().resolve(operand) {
4806 Ok(tv) => Ok(tv),
4807 Err(report) => Err(PipelineFailure::ShapeViolation { report }),
4808 }
4809 }
4810 crate::enforcement::Term::ChainComplex { simplicial_index } => {
4811 // ADR-035 + ADR-036 + ADR-060: resolver-bound ψ-Term variant.
4812 // Evaluate the operand subtree to a source-polymorphic carrier,
4813 // then dispatch it through the application's resolver via the
4814 // `Has<Category>Resolver` accessor. The resolver returns the
4815 // variant's `TermValue` directly (Inline / Borrowed / Stream);
4816 // resolver-side `Err` propagates as
4817 // `PipelineFailure::ShapeViolation` (the Null defaults emit
4818 // `RESOLVER_ABSENT`, recoverable via `Term::Try`).
4819 let operand = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4820 arena,
4821 simplicial_index as usize,
4822 input,
4823 recurse_value,
4824 recurse_idx_value,
4825 unfold_value,
4826 first_admit_idx_value,
4827 resolvers,
4828 )?;
4829 match resolvers.chain_complex_resolver().resolve(operand) {
4830 Ok(tv) => Ok(tv),
4831 Err(report) => Err(PipelineFailure::ShapeViolation { report }),
4832 }
4833 }
4834 crate::enforcement::Term::HomologyGroups { chain_index } => {
4835 // ADR-035 + ADR-036 + ADR-060: resolver-bound ψ-Term variant.
4836 // Evaluate the operand subtree to a source-polymorphic carrier,
4837 // then dispatch it through the application's resolver via the
4838 // `Has<Category>Resolver` accessor. The resolver returns the
4839 // variant's `TermValue` directly (Inline / Borrowed / Stream);
4840 // resolver-side `Err` propagates as
4841 // `PipelineFailure::ShapeViolation` (the Null defaults emit
4842 // `RESOLVER_ABSENT`, recoverable via `Term::Try`).
4843 let operand = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4844 arena,
4845 chain_index as usize,
4846 input,
4847 recurse_value,
4848 recurse_idx_value,
4849 unfold_value,
4850 first_admit_idx_value,
4851 resolvers,
4852 )?;
4853 match resolvers.homology_group_resolver().resolve(operand) {
4854 Ok(tv) => Ok(tv),
4855 Err(report) => Err(PipelineFailure::ShapeViolation { report }),
4856 }
4857 }
4858 crate::enforcement::Term::CochainComplex { chain_index } => {
4859 // ADR-035 + ADR-036 + ADR-060: resolver-bound ψ-Term variant.
4860 // Evaluate the operand subtree to a source-polymorphic carrier,
4861 // then dispatch it through the application's resolver via the
4862 // `Has<Category>Resolver` accessor. The resolver returns the
4863 // variant's `TermValue` directly (Inline / Borrowed / Stream);
4864 // resolver-side `Err` propagates as
4865 // `PipelineFailure::ShapeViolation` (the Null defaults emit
4866 // `RESOLVER_ABSENT`, recoverable via `Term::Try`).
4867 let operand = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4868 arena,
4869 chain_index as usize,
4870 input,
4871 recurse_value,
4872 recurse_idx_value,
4873 unfold_value,
4874 first_admit_idx_value,
4875 resolvers,
4876 )?;
4877 match resolvers.cochain_complex_resolver().resolve(operand) {
4878 Ok(tv) => Ok(tv),
4879 Err(report) => Err(PipelineFailure::ShapeViolation { report }),
4880 }
4881 }
4882 crate::enforcement::Term::CohomologyGroups { cochain_index } => {
4883 // ADR-035 + ADR-036 + ADR-060: resolver-bound ψ-Term variant.
4884 // Evaluate the operand subtree to a source-polymorphic carrier,
4885 // then dispatch it through the application's resolver via the
4886 // `Has<Category>Resolver` accessor. The resolver returns the
4887 // variant's `TermValue` directly (Inline / Borrowed / Stream);
4888 // resolver-side `Err` propagates as
4889 // `PipelineFailure::ShapeViolation` (the Null defaults emit
4890 // `RESOLVER_ABSENT`, recoverable via `Term::Try`).
4891 let operand = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4892 arena,
4893 cochain_index as usize,
4894 input,
4895 recurse_value,
4896 recurse_idx_value,
4897 unfold_value,
4898 first_admit_idx_value,
4899 resolvers,
4900 )?;
4901 match resolvers.cohomology_group_resolver().resolve(operand) {
4902 Ok(tv) => Ok(tv),
4903 Err(report) => Err(PipelineFailure::ShapeViolation { report }),
4904 }
4905 }
4906 crate::enforcement::Term::PostnikovTower { simplicial_index } => {
4907 // ADR-035 + ADR-036 + ADR-060: resolver-bound ψ-Term variant.
4908 // Evaluate the operand subtree to a source-polymorphic carrier,
4909 // then dispatch it through the application's resolver via the
4910 // `Has<Category>Resolver` accessor. The resolver returns the
4911 // variant's `TermValue` directly (Inline / Borrowed / Stream);
4912 // resolver-side `Err` propagates as
4913 // `PipelineFailure::ShapeViolation` (the Null defaults emit
4914 // `RESOLVER_ABSENT`, recoverable via `Term::Try`).
4915 let operand = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4916 arena,
4917 simplicial_index as usize,
4918 input,
4919 recurse_value,
4920 recurse_idx_value,
4921 unfold_value,
4922 first_admit_idx_value,
4923 resolvers,
4924 )?;
4925 match resolvers.postnikov_resolver().resolve(operand) {
4926 Ok(tv) => Ok(tv),
4927 Err(report) => Err(PipelineFailure::ShapeViolation { report }),
4928 }
4929 }
4930 crate::enforcement::Term::HomotopyGroups { postnikov_index } => {
4931 // ADR-035 + ADR-036 + ADR-060: resolver-bound ψ-Term variant.
4932 // Evaluate the operand subtree to a source-polymorphic carrier,
4933 // then dispatch it through the application's resolver via the
4934 // `Has<Category>Resolver` accessor. The resolver returns the
4935 // variant's `TermValue` directly (Inline / Borrowed / Stream);
4936 // resolver-side `Err` propagates as
4937 // `PipelineFailure::ShapeViolation` (the Null defaults emit
4938 // `RESOLVER_ABSENT`, recoverable via `Term::Try`).
4939 let operand = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4940 arena,
4941 postnikov_index as usize,
4942 input,
4943 recurse_value,
4944 recurse_idx_value,
4945 unfold_value,
4946 first_admit_idx_value,
4947 resolvers,
4948 )?;
4949 match resolvers.homotopy_group_resolver().resolve(operand) {
4950 Ok(tv) => Ok(tv),
4951 Err(report) => Err(PipelineFailure::ShapeViolation { report }),
4952 }
4953 }
4954 crate::enforcement::Term::KInvariants { homotopy_index } => {
4955 // ADR-035 + ADR-036 + ADR-060: resolver-bound ψ-Term variant.
4956 // Evaluate the operand subtree to a source-polymorphic carrier,
4957 // then dispatch it through the application's resolver via the
4958 // `Has<Category>Resolver` accessor. The resolver returns the
4959 // variant's `TermValue` directly (Inline / Borrowed / Stream);
4960 // resolver-side `Err` propagates as
4961 // `PipelineFailure::ShapeViolation` (the Null defaults emit
4962 // `RESOLVER_ABSENT`, recoverable via `Term::Try`).
4963 let operand = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4964 arena,
4965 homotopy_index as usize,
4966 input,
4967 recurse_value,
4968 recurse_idx_value,
4969 unfold_value,
4970 first_admit_idx_value,
4971 resolvers,
4972 )?;
4973 match resolvers.k_invariant_resolver().resolve(operand) {
4974 Ok(tv) => Ok(tv),
4975 Err(report) => Err(PipelineFailure::ShapeViolation { report }),
4976 }
4977 }
4978 crate::enforcement::Term::Betti { homology_index } => {
4979 // ADR-035 ψ_4: Betti-number extraction from HomologyGroups.
4980 // Pure byte-projection — no resolver consultation. The
4981 // operand's evaluated bytes (the homology-groups byte
4982 // serialization per the homology bridge's wire format)
4983 // are returned as-is; downstream consumers slice the
4984 // Betti tuple positions out of the result.
4985 let v = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
4986 arena,
4987 homology_index as usize,
4988 input,
4989 recurse_value,
4990 recurse_idx_value,
4991 unfold_value,
4992 first_admit_idx_value,
4993 resolvers,
4994 )?;
4995 Ok(v)
4996 }
4997 }
4998}
4999
5000#[allow(clippy::too_many_arguments)]
5001fn apply_primitive_op<'a, 'b, 'r, A, R, const INLINE_BYTES: usize, const FP_MAX: usize>(
5002 arena: &'a [crate::enforcement::Term<'a, INLINE_BYTES>],
5003 operator: crate::PrimitiveOp,
5004 args_start: usize,
5005 args_len: usize,
5006 input: TermValue<'a, INLINE_BYTES>,
5007 recurse_value: Option<&'b [u8]>,
5008 recurse_idx_value: Option<&'b [u8]>,
5009 unfold_value: Option<&'b [u8]>,
5010 first_admit_idx_value: Option<&'b [u8]>,
5011 resolvers: &'r R,
5012) -> Result<TermValue<'a, INLINE_BYTES>, PipelineFailure>
5013where
5014 A: crate::pipeline::AxisTuple<INLINE_BYTES, FP_MAX> + crate::enforcement::Hasher<FP_MAX> + 'a,
5015 R: crate::pipeline::ResolverTuple
5016 + crate::pipeline::HasNerveResolver<INLINE_BYTES, A>
5017 + crate::pipeline::HasChainComplexResolver<INLINE_BYTES, A>
5018 + crate::pipeline::HasHomologyGroupResolver<INLINE_BYTES, A>
5019 + crate::pipeline::HasCochainComplexResolver<INLINE_BYTES, A>
5020 + crate::pipeline::HasCohomologyGroupResolver<INLINE_BYTES, A>
5021 + crate::pipeline::HasPostnikovResolver<INLINE_BYTES, A>
5022 + crate::pipeline::HasHomotopyGroupResolver<INLINE_BYTES, A>
5023 + crate::pipeline::HasKInvariantResolver<INLINE_BYTES, A>,
5024{
5025 // Unary ops: 1 arg. Binary ops: 2 args.
5026 let arity = match operator {
5027 crate::PrimitiveOp::Neg
5028 | crate::PrimitiveOp::Bnot
5029 | crate::PrimitiveOp::Succ
5030 | crate::PrimitiveOp::Pred => 1usize,
5031 crate::PrimitiveOp::Add
5032 | crate::PrimitiveOp::Sub
5033 | crate::PrimitiveOp::Mul
5034 | crate::PrimitiveOp::Xor
5035 | crate::PrimitiveOp::And
5036 | crate::PrimitiveOp::Or
5037 | crate::PrimitiveOp::Le
5038 | crate::PrimitiveOp::Lt
5039 | crate::PrimitiveOp::Ge
5040 | crate::PrimitiveOp::Gt
5041 | crate::PrimitiveOp::Concat
5042 // ADR-053 substrate amendment: ring-axis arithmetic completion.
5043 | crate::PrimitiveOp::Div
5044 | crate::PrimitiveOp::Mod
5045 | crate::PrimitiveOp::Pow => 2usize,
5046 };
5047 if args_len != arity {
5048 return Err(PipelineFailure::ShapeViolation {
5049 report: crate::enforcement::ShapeViolation {
5050 shape_iri: "https://uor.foundation/pipeline/PrimitiveOpArityShape",
5051 constraint_iri: "https://uor.foundation/pipeline/PrimitiveOpArityShape/arity",
5052 property_iri: "https://uor.foundation/pipeline/operatorArity",
5053 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
5054 min_count: arity as u32,
5055 max_count: arity as u32,
5056 kind: crate::ViolationKind::CardinalityViolation,
5057 },
5058 });
5059 }
5060 if arity == 1 {
5061 let v = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
5062 arena,
5063 args_start,
5064 input,
5065 recurse_value,
5066 recurse_idx_value,
5067 unfold_value,
5068 first_admit_idx_value,
5069 resolvers,
5070 )?;
5071 let x = bytes_to_u64_be(v.bytes());
5072 let r =
5073 match operator {
5074 crate::PrimitiveOp::Neg => x.wrapping_neg(),
5075 crate::PrimitiveOp::Bnot => !x,
5076 crate::PrimitiveOp::Succ => x.wrapping_add(1),
5077 crate::PrimitiveOp::Pred => x.wrapping_sub(1),
5078 _ => return Err(PipelineFailure::ShapeViolation {
5079 report: crate::enforcement::ShapeViolation {
5080 shape_iri: "https://uor.foundation/pipeline/PrimitiveOpArityShape",
5081 constraint_iri:
5082 "https://uor.foundation/pipeline/PrimitiveOpArityShape/binary-as-unary",
5083 property_iri: "https://uor.foundation/pipeline/operatorArity",
5084 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
5085 min_count: 2,
5086 max_count: 2,
5087 kind: crate::ViolationKind::CardinalityViolation,
5088 },
5089 }),
5090 };
5091 let width = v.bytes().len().clamp(1, 8);
5092 let arr = r.to_be_bytes();
5093 Ok(TermValue::inline_from_slice(&arr[8 - width..]))
5094 } else {
5095 let lhs = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
5096 arena,
5097 args_start,
5098 input,
5099 recurse_value,
5100 recurse_idx_value,
5101 unfold_value,
5102 first_admit_idx_value,
5103 resolvers,
5104 )?;
5105 let rhs = evaluate_term_at::<A, R, INLINE_BYTES, FP_MAX>(
5106 arena,
5107 args_start + 1,
5108 input,
5109 recurse_value,
5110 recurse_idx_value,
5111 unfold_value,
5112 first_admit_idx_value,
5113 resolvers,
5114 )?;
5115 // ADR-013/TR-08 substrate-amendment ops: byte-level Concat and
5116 // comparison primitives bypass the u64 fold and operate on the
5117 // operands' full byte sequences.
5118 match operator {
5119 crate::PrimitiveOp::Concat => {
5120 // Concat: emit lhs.bytes() ⧺ rhs.bytes(), bounded by
5121 // INLINE_BYTES (truncates excess; runtime callers
5122 // declaring shapes whose composite length exceeds the
5123 // ceiling are rejected at validation time per ADR-028's
5124 // symmetric output ceiling check).
5125 let lb = lhs.bytes();
5126 let rb = rhs.bytes();
5127 let total = lb.len() + rb.len();
5128 let cap = if total > INLINE_BYTES {
5129 INLINE_BYTES
5130 } else {
5131 total
5132 };
5133 let mut buf = [0u8; INLINE_BYTES];
5134 let mut i = 0;
5135 while i < lb.len() && i < cap {
5136 buf[i] = lb[i];
5137 i += 1;
5138 }
5139 let mut j = 0;
5140 while j < rb.len() && i < cap {
5141 buf[i] = rb[j];
5142 i += 1;
5143 j += 1;
5144 }
5145 return Ok(TermValue::inline_from_slice(&buf[..cap]));
5146 }
5147 crate::PrimitiveOp::Le
5148 | crate::PrimitiveOp::Lt
5149 | crate::PrimitiveOp::Ge
5150 | crate::PrimitiveOp::Gt => {
5151 // Big-endian byte-level comparison. Both operands are
5152 // padded with leading zeros to the max length so the
5153 // comparison ignores leading-zero stripping differences.
5154 let cmp = byte_compare_be(lhs.bytes(), rhs.bytes());
5155 let result_byte: u8 = match operator {
5156 crate::PrimitiveOp::Le => u8::from(cmp != core::cmp::Ordering::Greater),
5157 crate::PrimitiveOp::Lt => u8::from(cmp == core::cmp::Ordering::Less),
5158 crate::PrimitiveOp::Ge => u8::from(cmp != core::cmp::Ordering::Less),
5159 crate::PrimitiveOp::Gt => u8::from(cmp == core::cmp::Ordering::Greater),
5160 _ => 0,
5161 };
5162 return Ok(TermValue::inline_from_slice(&[result_byte]));
5163 }
5164 // ADR-053 substrate amendment: Div/Mod reject b = 0 with
5165 // a ShapeViolation. Per ADR-050, division semantics are
5166 // Euclidean (q = floor(a / b), r = a − q·b) over the ring
5167 // Z/(2^n)Z where n is max(8·byte-len(a), 8·byte-len(b)).
5168 crate::PrimitiveOp::Div if bytes_all_zero(rhs.bytes()) => {
5169 return Err(PipelineFailure::ShapeViolation {
5170 report: crate::enforcement::ShapeViolation {
5171 shape_iri: "https://uor.foundation/op/Div",
5172 constraint_iri: "https://uor.foundation/op/Div/nonZeroDivisor",
5173 property_iri: "https://uor.foundation/op/arity",
5174 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
5175 min_count: 1,
5176 max_count: 1,
5177 kind: crate::ViolationKind::ValueCheck,
5178 },
5179 });
5180 }
5181 crate::PrimitiveOp::Mod if bytes_all_zero(rhs.bytes()) => {
5182 return Err(PipelineFailure::ShapeViolation {
5183 report: crate::enforcement::ShapeViolation {
5184 shape_iri: "https://uor.foundation/op/Mod",
5185 constraint_iri: "https://uor.foundation/op/Mod/nonZeroDivisor",
5186 property_iri: "https://uor.foundation/op/arity",
5187 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
5188 min_count: 1,
5189 max_count: 1,
5190 kind: crate::ViolationKind::ValueCheck,
5191 },
5192 });
5193 }
5194 _ => {}
5195 }
5196 let width = lhs.bytes().len().max(rhs.bytes().len()).max(1);
5197 // ADR-050 width-parametric dispatch: widths > 8 bytes route
5198 // through the byte-level kernel `byte_arith_be` so wide-Witt
5199 // operands (Limbs<N>-backed levels W160..W32768) compute
5200 // correctly under Z/(2^(8·width))Z without truncating to u64.
5201 if width > 8 {
5202 return byte_arith_be::<INLINE_BYTES>(operator, lhs.bytes(), rhs.bytes(), width);
5203 }
5204 let a = bytes_to_u64_be(lhs.bytes());
5205 let b = bytes_to_u64_be(rhs.bytes());
5206 // ADR-050 width-parametric mask: arithmetic results are reduced
5207 // mod 2^(8·width) before truncation. For the u64 hot path this
5208 // is `(1 << (width·8)) − 1` for width < 8 and u64::MAX for width = 8.
5209 let mask: u64 = if width >= 8 {
5210 u64::MAX
5211 } else {
5212 (1u64 << (width * 8)).wrapping_sub(1)
5213 };
5214 let r =
5215 match operator {
5216 crate::PrimitiveOp::Add => a.wrapping_add(b) & mask,
5217 crate::PrimitiveOp::Sub => a.wrapping_sub(b) & mask,
5218 crate::PrimitiveOp::Mul => a.wrapping_mul(b) & mask,
5219 crate::PrimitiveOp::Xor => (a ^ b) & mask,
5220 crate::PrimitiveOp::And => (a & b) & mask,
5221 crate::PrimitiveOp::Or => (a | b) & mask,
5222 // ADR-053: Euclidean division/remainder over Z/(2^(8·width))Z.
5223 crate::PrimitiveOp::Div => (a & mask) / (b & mask),
5224 crate::PrimitiveOp::Mod => (a & mask) % (b & mask),
5225 // ADR-053: modular exponentiation by repeated squaring.
5226 crate::PrimitiveOp::Pow => u64_modpow(a & mask, b & mask, mask),
5227 _ => return Err(PipelineFailure::ShapeViolation {
5228 report: crate::enforcement::ShapeViolation {
5229 shape_iri: "https://uor.foundation/pipeline/PrimitiveOpArityShape",
5230 constraint_iri:
5231 "https://uor.foundation/pipeline/PrimitiveOpArityShape/unary-as-binary",
5232 property_iri: "https://uor.foundation/pipeline/operatorArity",
5233 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
5234 min_count: 1,
5235 max_count: 1,
5236 kind: crate::ViolationKind::CardinalityViolation,
5237 },
5238 }),
5239 };
5240 let arr = r.to_be_bytes();
5241 Ok(TermValue::inline_from_slice(&arr[8 - width..]))
5242 }
5243}
5244
5245/// ADR-050: byte-level fold-rule dispatch for binary ring-axis primitives over
5246/// widths > 8 bytes. Matches the const-fn `const_ring_eval_w{n}` helpers'
5247/// semantics but operates on the runtime catamorphism's `TermValue` byte slices
5248/// rather than on typed `Limbs<N>` values.
5249#[allow(clippy::too_many_lines)]
5250fn byte_arith_be<'a, const INLINE_BYTES: usize>(
5251 operator: crate::PrimitiveOp,
5252 lhs: &[u8],
5253 rhs: &[u8],
5254 width: usize,
5255) -> Result<TermValue<'a, INLINE_BYTES>, PipelineFailure> {
5256 // Pad both operands to `width` bytes (leading zeros, big-endian).
5257 let mut a = [0u8; INLINE_BYTES];
5258 let mut b = [0u8; INLINE_BYTES];
5259 let w = if width > INLINE_BYTES {
5260 INLINE_BYTES
5261 } else {
5262 width
5263 };
5264 {
5265 let la = lhs.len().min(w);
5266 let dst_off = w - la;
5267 let src_off = lhs.len() - la;
5268 let mut i = 0;
5269 while i < la {
5270 a[dst_off + i] = lhs[src_off + i];
5271 i += 1;
5272 }
5273 }
5274 {
5275 let lb = rhs.len().min(w);
5276 let dst_off = w - lb;
5277 let src_off = rhs.len() - lb;
5278 let mut i = 0;
5279 while i < lb {
5280 b[dst_off + i] = rhs[src_off + i];
5281 i += 1;
5282 }
5283 }
5284 let mut out = [0u8; INLINE_BYTES];
5285 match operator {
5286 crate::PrimitiveOp::And => {
5287 let mut i = 0;
5288 while i < w {
5289 out[i] = a[i] & b[i];
5290 i += 1;
5291 }
5292 }
5293 crate::PrimitiveOp::Or => {
5294 let mut i = 0;
5295 while i < w {
5296 out[i] = a[i] | b[i];
5297 i += 1;
5298 }
5299 }
5300 crate::PrimitiveOp::Xor => {
5301 let mut i = 0;
5302 while i < w {
5303 out[i] = a[i] ^ b[i];
5304 i += 1;
5305 }
5306 }
5307 crate::PrimitiveOp::Add => {
5308 let mut carry: u16 = 0;
5309 let mut i = w;
5310 while i > 0 {
5311 i -= 1;
5312 let s = a[i] as u16 + b[i] as u16 + carry;
5313 out[i] = (s & 0xFF) as u8;
5314 carry = s >> 8;
5315 }
5316 }
5317 crate::PrimitiveOp::Sub => {
5318 let mut borrow: i16 = 0;
5319 let mut i = w;
5320 while i > 0 {
5321 i -= 1;
5322 let d = a[i] as i16 - b[i] as i16 - borrow;
5323 if d < 0 {
5324 out[i] = (d + 256) as u8;
5325 borrow = 1;
5326 } else {
5327 out[i] = d as u8;
5328 borrow = 0;
5329 }
5330 }
5331 }
5332 crate::PrimitiveOp::Mul => {
5333 // Schoolbook multiplication mod 2^(8·w). `a` and `b` are
5334 // big-endian w-byte operands; output is the low w bytes of
5335 // the 2w-byte product, written back big-endian.
5336 //
5337 // Algorithm: walk `i` from LSB to MSB (a[w-1] = LSB), and
5338 // for each `i` walk `j` similarly. The byte product
5339 // a[w-1-i] * b[w-1-j] contributes to output position
5340 // (w-1)-(i+j) when (i+j) < w; otherwise it overflows the
5341 // ring modulus and is discarded.
5342 let mut tmp = [0u32; INLINE_BYTES];
5343 let mut i: usize = 0;
5344 while i < w {
5345 let mut j: usize = 0;
5346 while j < w - i {
5347 let dst = w - 1 - (i + j);
5348 tmp[dst] += a[w - 1 - i] as u32 * b[w - 1 - j] as u32;
5349 j += 1;
5350 }
5351 i += 1;
5352 }
5353 // Carry-propagate within the low w bytes (high bytes are
5354 // discarded — the ring modulus reduces them away).
5355 let mut carry: u32 = 0;
5356 let mut k = w;
5357 while k > 0 {
5358 k -= 1;
5359 let v = tmp[k] + carry;
5360 out[k] = (v & 0xFF) as u8;
5361 carry = v >> 8;
5362 }
5363 }
5364 crate::PrimitiveOp::Div | crate::PrimitiveOp::Mod => {
5365 // Binary long division MSB→LSB over the byte slice.
5366 let mut q = [0u8; INLINE_BYTES];
5367 let mut r = [0u8; INLINE_BYTES];
5368 let total_bits = w * 8;
5369 let mut i = 0;
5370 while i < total_bits {
5371 bytes_shl1(&mut r, w);
5372 let bit_word = i / 8;
5373 let bit_idx = 7 - (i % 8);
5374 let cur_bit = (a[bit_word] >> bit_idx) & 1;
5375 if cur_bit == 1 {
5376 r[w - 1] |= 1;
5377 }
5378 if bytes_le_be(&b[..w], &r[..w]) {
5379 bytes_sub_in_place(&mut r, &b, w);
5380 bytes_shl1(&mut q, w);
5381 q[w - 1] |= 1;
5382 } else {
5383 bytes_shl1(&mut q, w);
5384 }
5385 i += 1;
5386 }
5387 let src = match operator {
5388 crate::PrimitiveOp::Div => &q,
5389 _ => &r,
5390 };
5391 let mut k = 0;
5392 while k < w {
5393 out[k] = src[k];
5394 k += 1;
5395 }
5396 }
5397 crate::PrimitiveOp::Pow => {
5398 // Square-and-multiply over byte-slice operands. Exponent
5399 // bits walked LSB→MSB; each iteration squares the running
5400 // base and conditionally multiplies into the accumulator.
5401 let mut result = [0u8; INLINE_BYTES];
5402 result[w - 1] = 1;
5403 let mut base = [0u8; INLINE_BYTES];
5404 let mut k = 0;
5405 while k < w {
5406 base[k] = a[k];
5407 k += 1;
5408 }
5409 let mut byte = w;
5410 while byte > 0 {
5411 byte -= 1;
5412 let mut bit = 0u32;
5413 while bit < 8 {
5414 if ((b[byte] >> bit) & 1) == 1 {
5415 let prod = byte_arith_be::<INLINE_BYTES>(
5416 crate::PrimitiveOp::Mul,
5417 &result[..w],
5418 &base[..w],
5419 w,
5420 )?;
5421 let pb = prod.bytes();
5422 let dst_off = w - pb.len().min(w);
5423 let mut j = 0;
5424 while j < w {
5425 result[j] = 0;
5426 j += 1;
5427 }
5428 let mut j2 = 0;
5429 while j2 < pb.len().min(w) {
5430 result[dst_off + j2] = pb[j2];
5431 j2 += 1;
5432 }
5433 }
5434 let sq = byte_arith_be::<INLINE_BYTES>(
5435 crate::PrimitiveOp::Mul,
5436 &base[..w],
5437 &base[..w],
5438 w,
5439 )?;
5440 let sb = sq.bytes();
5441 let dst_off = w - sb.len().min(w);
5442 let mut j = 0;
5443 while j < w {
5444 base[j] = 0;
5445 j += 1;
5446 }
5447 let mut j2 = 0;
5448 while j2 < sb.len().min(w) {
5449 base[dst_off + j2] = sb[j2];
5450 j2 += 1;
5451 }
5452 bit += 1;
5453 }
5454 }
5455 let mut k = 0;
5456 while k < w {
5457 out[k] = result[k];
5458 k += 1;
5459 }
5460 }
5461 _ => {
5462 return Err(PipelineFailure::ShapeViolation {
5463 report: crate::enforcement::ShapeViolation {
5464 shape_iri: "https://uor.foundation/pipeline/PrimitiveOpArityShape",
5465 constraint_iri:
5466 "https://uor.foundation/pipeline/PrimitiveOpArityShape/unary-as-binary",
5467 property_iri: "https://uor.foundation/pipeline/operatorArity",
5468 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
5469 min_count: 1,
5470 max_count: 1,
5471 kind: crate::ViolationKind::CardinalityViolation,
5472 },
5473 })
5474 }
5475 }
5476 Ok(TermValue::inline_from_slice(&out[..w]))
5477}
5478
5479fn bytes_shl1(buf: &mut [u8], w: usize) {
5480 let mut carry: u8 = 0;
5481 let mut i = w;
5482 while i > 0 {
5483 i -= 1;
5484 let v = buf[i];
5485 buf[i] = (v << 1) | carry;
5486 carry = v >> 7;
5487 }
5488}
5489
5490fn bytes_le_be(a: &[u8], b: &[u8]) -> bool {
5491 let mut i = 0;
5492 while i < a.len() {
5493 if a[i] < b[i] {
5494 return true;
5495 }
5496 if a[i] > b[i] {
5497 return false;
5498 }
5499 i += 1;
5500 }
5501 true
5502}
5503
5504fn bytes_sub_in_place(dst: &mut [u8], rhs: &[u8], w: usize) {
5505 let mut borrow: i16 = 0;
5506 let mut i = w;
5507 while i > 0 {
5508 i -= 1;
5509 let d = dst[i] as i16 - rhs[i] as i16 - borrow;
5510 if d < 0 {
5511 dst[i] = (d + 256) as u8;
5512 borrow = 1;
5513 } else {
5514 dst[i] = d as u8;
5515 borrow = 0;
5516 }
5517 }
5518}
5519
5520fn byte_compare_be(a: &[u8], b: &[u8]) -> core::cmp::Ordering {
5521 // Pad shorter operand with leading zeros so the comparison treats
5522 // both operands at max(len(a), len(b)) byte width.
5523 let max_len = if a.len() > b.len() { a.len() } else { b.len() };
5524 let mut i = 0;
5525 while i < max_len {
5526 let ai = if i + a.len() >= max_len {
5527 a[i + a.len() - max_len]
5528 } else {
5529 0u8
5530 };
5531 let bi = if i + b.len() >= max_len {
5532 b[i + b.len() - max_len]
5533 } else {
5534 0u8
5535 };
5536 if ai < bi {
5537 return core::cmp::Ordering::Less;
5538 }
5539 if ai > bi {
5540 return core::cmp::Ordering::Greater;
5541 }
5542 i += 1;
5543 }
5544 core::cmp::Ordering::Equal
5545}
5546
5547fn bytes_to_u64_be(bytes: &[u8]) -> u64 {
5548 let take = if bytes.len() > 8 { 8 } else { bytes.len() };
5549 let start = bytes.len() - take;
5550 let mut acc = 0u64;
5551 let mut i = 0;
5552 while i < take {
5553 acc = (acc << 8) | bytes[start + i] as u64;
5554 i += 1;
5555 }
5556 acc
5557}
5558
5559fn bytes_all_zero(bytes: &[u8]) -> bool {
5560 let mut i = 0;
5561 while i < bytes.len() {
5562 if bytes[i] != 0 {
5563 return false;
5564 }
5565 i += 1;
5566 }
5567 true
5568}
5569
5570fn u64_modpow(base: u64, exp: u64, mask: u64) -> u64 {
5571 let mut result: u64 = 1u64 & mask;
5572 let mut b: u64 = base & mask;
5573 let mut e: u64 = exp;
5574 while e > 0 {
5575 if (e & 1) == 1 {
5576 result = result.wrapping_mul(b) & mask;
5577 }
5578 b = b.wrapping_mul(b) & mask;
5579 e >>= 1;
5580 }
5581 result
5582}
5583
5584/// Foundation-sanctioned identity route: `ConstrainedTypeInput` is the
5585/// empty default shape, vacuously closed under foundation vocabulary.
5586/// Application authors with non-trivial routes use the `prism_model!`
5587/// macro from `uor-foundation-sdk`, which emits `FoundationClosed` for
5588/// the witness it generates iff every node is foundation-vocabulary.
5589/// The identity route's `arena_slice()` returns `&[]` — no terms, no
5590/// transformation, input passes through to output unchanged.
5591impl __sdk_seal::Sealed for ConstrainedTypeInput {}
5592impl<const INLINE_BYTES: usize> FoundationClosed<INLINE_BYTES> for ConstrainedTypeInput {
5593 fn arena_slice() -> &'static [crate::enforcement::Term<'static, INLINE_BYTES>] {
5594 &[]
5595 }
5596}
5597impl<'a> IntoBindingValue<'a> for ConstrainedTypeInput {
5598 fn as_binding_value<const INLINE_BYTES: usize>(
5599 &self,
5600 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
5601 // Identity input carries no bytes — the empty shape's canonical
5602 // carrier is the empty Inline carrier (valid for any lifetime).
5603 crate::pipeline::TermValue::empty()
5604 }
5605}
5606
5607/// Marker for a `ConstrainedTypeShape` that is the Cartesian product of
5608/// two component shapes. Selecting this trait routes nerve-Betti computation
5609/// through Künneth composition of component Betti profiles rather than
5610/// flat enumeration of (constraint, constraint) pairs. Introduced by the
5611/// Product/Coproduct Completion Amendment §3c for CartesianPartitionProduct
5612/// (CPT_1–CPT_6).
5613pub trait CartesianProductShape: ConstrainedTypeShape {
5614 /// Left operand shape.
5615 type Left: ConstrainedTypeShape;
5616 /// Right operand shape.
5617 type Right: ConstrainedTypeShape;
5618}
5619
5620/// Künneth composition of two Betti profiles.
5621/// Computes `out[k] = Σ_{i + j = k} a[i] · b[j]` over
5622/// `[0, MAX_BETTI_DIMENSION)`. All arithmetic uses saturating operations so the
5623/// function is total on `[u32; MAX_BETTI_DIMENSION]` inputs without panicking.
5624pub const fn kunneth_compose(
5625 a: &[u32; crate::enforcement::MAX_BETTI_DIMENSION],
5626 b: &[u32; crate::enforcement::MAX_BETTI_DIMENSION],
5627) -> [u32; crate::enforcement::MAX_BETTI_DIMENSION] {
5628 let mut out = [0u32; crate::enforcement::MAX_BETTI_DIMENSION];
5629 let mut i: usize = 0;
5630 while i < crate::enforcement::MAX_BETTI_DIMENSION {
5631 let mut j: usize = 0;
5632 while j < crate::enforcement::MAX_BETTI_DIMENSION - i {
5633 let term = a[i].saturating_mul(b[j]);
5634 out[i + j] = out[i + j].saturating_add(term);
5635 j += 1;
5636 }
5637 i += 1;
5638 }
5639 out
5640}
5641
5642/// Cartesian-product nerve Betti via Künneth composition of the component
5643/// shapes' Betti profiles. Used instead of
5644/// `primitive_simplicial_nerve_betti` when a shape declares itself as a
5645/// `CartesianProductShape`. Amendment §3c.
5646/// Phase 1a (orphan-closure): propagates either component's
5647/// `NERVE_CAPACITY_EXCEEDED` via `?`. Dropped `const fn` because
5648/// the `Result` return of the per-component primitive is not `const`-evaluable.
5649/// ADR-057: delegates to [`crate::enforcement::primitive_simplicial_nerve_betti`]
5650/// on each component, which expands `ConstraintRef::Recurse` through
5651/// foundation's built-in shape registry. For application-registry-aware
5652/// expansion, use [`primitive_cartesian_nerve_betti_in`] generic over the
5653/// application's `ShapeRegistryProvider`.
5654/// # Errors
5655/// Returns `NERVE_CAPACITY_EXCEEDED` if either component exceeds caps.
5656/// Returns `RECURSE_SHAPE_UNREGISTERED` when a component's Recurse references
5657/// an IRI not present in the consulted registry.
5658pub fn primitive_cartesian_nerve_betti<S: CartesianProductShape>() -> Result<
5659 [u32; crate::enforcement::MAX_BETTI_DIMENSION],
5660 crate::enforcement::GenericImpossibilityWitness,
5661> {
5662 let left = crate::enforcement::primitive_simplicial_nerve_betti::<S::Left>()?;
5663 let right = crate::enforcement::primitive_simplicial_nerve_betti::<S::Right>()?;
5664 Ok(kunneth_compose(&left, &right))
5665}
5666
5667/// ADR-057: registry-parameterized companion to
5668/// [`primitive_cartesian_nerve_betti`]. Delegates to
5669/// [`crate::enforcement::primitive_simplicial_nerve_betti_in`] on each
5670/// component with `R` as the application's `ShapeRegistryProvider`.
5671/// Recurse entries are expanded through `R::REGISTRY` plus foundation's
5672/// built-in registry.
5673/// # Errors
5674/// Returns `NERVE_CAPACITY_EXCEEDED` if either component's expanded
5675/// constraint set exceeds caps. Returns `RECURSE_SHAPE_UNREGISTERED` when
5676/// a `Recurse` entry references an IRI not present in either registry.
5677pub fn primitive_cartesian_nerve_betti_in<
5678 S: CartesianProductShape,
5679 R: crate::pipeline::shape_iri_registry::ShapeRegistryProvider,
5680>() -> Result<
5681 [u32; crate::enforcement::MAX_BETTI_DIMENSION],
5682 crate::enforcement::GenericImpossibilityWitness,
5683> {
5684 let left = crate::enforcement::primitive_simplicial_nerve_betti_in::<S::Left, R>()?;
5685 let right = crate::enforcement::primitive_simplicial_nerve_betti_in::<S::Right, R>()?;
5686 Ok(kunneth_compose(&left, &right))
5687}
5688
5689/// Shift every site-index reference in a `ConstraintRef` by `offset`.
5690/// Used by the SDK's `product_shape!` / `coproduct_shape!` /
5691/// `cartesian_product_shape!` macros to splice an operand's constraints into
5692/// a combined shape at a post-operand offset.
5693/// **Phase 17: full operand-catalogue support.** Affine and
5694/// Conjunction now shift correctly at const time because the
5695/// variants store fixed-size arrays (no `&'static [i64]` allocation
5696/// required). The pre-Phase-17 `Site { position: u32::MAX }`
5697/// sentinel is removed.
5698/// **Variant coverage.**
5699/// - `Site { position }` → position += offset.
5700/// - `Carry { site }` → site += offset.
5701/// - `Residue`, `Hamming`, `Depth`, `SatClauses`, `Bound`: pass through (no
5702/// site references at this layer).
5703/// - `Affine { coefficients, coefficient_count, bias }`: builds a fresh
5704/// `[i64; AFFINE_MAX_COEFFS]` of zeros, copies the active prefix into
5705/// positions `[offset, offset + coefficient_count)`. If the shift
5706/// would overflow the fixed buffer, returns an Affine with
5707/// `coefficient_count = 0` (vacuously consistent).
5708/// - `Conjunction { conjuncts, conjunct_count }`: builds a fresh
5709/// `[LeafConstraintRef; CONJUNCTION_MAX_TERMS]` and shifts each leaf
5710/// via `shift_leaf_constraint`. One-level depth — leaves cannot be
5711/// Conjunction.
5712pub const fn shift_constraint(c: ConstraintRef, offset: u32) -> ConstraintRef {
5713 match c {
5714 ConstraintRef::Site { position } => ConstraintRef::Site {
5715 position: position.saturating_add(offset),
5716 },
5717 ConstraintRef::Carry { site } => ConstraintRef::Carry {
5718 site: site.saturating_add(offset),
5719 },
5720 ConstraintRef::Residue { modulus, residue } => ConstraintRef::Residue { modulus, residue },
5721 ConstraintRef::Hamming { bound } => ConstraintRef::Hamming { bound },
5722 ConstraintRef::Depth { min, max } => ConstraintRef::Depth { min, max },
5723 ConstraintRef::SatClauses { clauses, num_vars } => {
5724 ConstraintRef::SatClauses { clauses, num_vars }
5725 }
5726 ConstraintRef::Bound {
5727 observable_iri,
5728 bound_shape_iri,
5729 args_repr,
5730 } => ConstraintRef::Bound {
5731 observable_iri,
5732 bound_shape_iri,
5733 args_repr,
5734 },
5735 ConstraintRef::Affine {
5736 coefficients,
5737 coefficient_count,
5738 bias,
5739 } => {
5740 let (out, new_count) =
5741 shift_affine_coefficients(&coefficients, coefficient_count, offset);
5742 ConstraintRef::Affine {
5743 coefficients: out,
5744 coefficient_count: new_count,
5745 bias,
5746 }
5747 }
5748 ConstraintRef::Conjunction {
5749 conjuncts,
5750 conjunct_count,
5751 } => {
5752 let mut out = [LeafConstraintRef::Site { position: 0 }; CONJUNCTION_MAX_TERMS];
5753 let count = conjunct_count as usize;
5754 let mut i = 0;
5755 while i < count && i < CONJUNCTION_MAX_TERMS {
5756 out[i] = shift_leaf_constraint(conjuncts[i], offset);
5757 i += 1;
5758 }
5759 ConstraintRef::Conjunction {
5760 conjuncts: out,
5761 conjunct_count,
5762 }
5763 }
5764 // ADR-057: Recurse references a shape by content-addressed IRI;
5765 // there are no site positions to shift. Pass through unchanged.
5766 ConstraintRef::Recurse {
5767 shape_iri,
5768 descent_bound,
5769 } => ConstraintRef::Recurse {
5770 shape_iri,
5771 descent_bound,
5772 },
5773 }
5774}
5775
5776/// Phase 17 helper: shift the active prefix of an `Affine`
5777/// coefficient array right by `offset`, returning a fresh
5778/// `[i64; AFFINE_MAX_COEFFS]` and the new active count. If the
5779/// shift would overflow the fixed buffer, returns count `0`
5780/// (vacuously consistent — no constraint).
5781#[inline]
5782#[must_use]
5783const fn shift_affine_coefficients(
5784 coefficients: &[i64; AFFINE_MAX_COEFFS],
5785 coefficient_count: u32,
5786 offset: u32,
5787) -> ([i64; AFFINE_MAX_COEFFS], u32) {
5788 let mut out = [0i64; AFFINE_MAX_COEFFS];
5789 let count = coefficient_count as usize;
5790 let off = offset as usize;
5791 if off >= AFFINE_MAX_COEFFS {
5792 return (out, 0);
5793 }
5794 let mut i = 0;
5795 while i < count && i + off < AFFINE_MAX_COEFFS {
5796 out[i + off] = coefficients[i];
5797 i += 1;
5798 }
5799 let new_count = (i + off) as u32;
5800 (out, new_count)
5801}
5802
5803/// Phase 17 helper: same as [`shift_constraint`] but operating on a
5804/// [`LeafConstraintRef`]. Used by Conjunction-shifting paths that
5805/// must preserve the leaf-only depth limit.
5806#[inline]
5807#[must_use]
5808pub const fn shift_leaf_constraint(c: LeafConstraintRef, offset: u32) -> LeafConstraintRef {
5809 match c {
5810 LeafConstraintRef::Site { position } => LeafConstraintRef::Site {
5811 position: position.saturating_add(offset),
5812 },
5813 LeafConstraintRef::Carry { site } => LeafConstraintRef::Carry {
5814 site: site.saturating_add(offset),
5815 },
5816 LeafConstraintRef::Residue { modulus, residue } => {
5817 LeafConstraintRef::Residue { modulus, residue }
5818 }
5819 LeafConstraintRef::Hamming { bound } => LeafConstraintRef::Hamming { bound },
5820 LeafConstraintRef::Depth { min, max } => LeafConstraintRef::Depth { min, max },
5821 LeafConstraintRef::SatClauses { clauses, num_vars } => {
5822 LeafConstraintRef::SatClauses { clauses, num_vars }
5823 }
5824 LeafConstraintRef::Bound {
5825 observable_iri,
5826 bound_shape_iri,
5827 args_repr,
5828 } => LeafConstraintRef::Bound {
5829 observable_iri,
5830 bound_shape_iri,
5831 args_repr,
5832 },
5833 LeafConstraintRef::Affine {
5834 coefficients,
5835 coefficient_count,
5836 bias,
5837 } => {
5838 let (out, new_count) =
5839 shift_affine_coefficients(&coefficients, coefficient_count, offset);
5840 LeafConstraintRef::Affine {
5841 coefficients: out,
5842 coefficient_count: new_count,
5843 bias,
5844 }
5845 }
5846 // ADR-057: Recurse references a shape by content-addressed IRI;
5847 // there are no site positions to shift. Pass through unchanged.
5848 LeafConstraintRef::Recurse {
5849 shape_iri,
5850 descent_bound,
5851 } => LeafConstraintRef::Recurse {
5852 shape_iri,
5853 descent_bound,
5854 },
5855 }
5856}
5857
5858/// SDK support: concatenate two operand constraint arrays into a padded
5859/// fixed-size buffer of length `2 * crate::enforcement::NERVE_CONSTRAINTS_CAP`.
5860/// A's constraints are copied verbatim at indices `[0, A::CONSTRAINTS.len())`;
5861/// B's constraints are copied at `[A::CONSTRAINTS.len(), total)` with each
5862/// entry passed through `shift_constraint(c, A::SITE_COUNT as u32)`.
5863/// Trailing positions are filled with the `Site { position: u32::MAX }`
5864/// sentinel.
5865/// Consumers pair this with `sdk_product_constraints_len` to derive the
5866/// slice length at const-eval time: `&BUF[..LEN]` yields a `&'static
5867/// [ConstraintRef]` of the correct length without `unsafe`.
5868pub const fn sdk_concat_product_constraints<A, B>(
5869) -> [ConstraintRef; 2 * crate::enforcement::NERVE_CONSTRAINTS_CAP]
5870where
5871 A: ConstrainedTypeShape,
5872 B: ConstrainedTypeShape,
5873{
5874 let mut out =
5875 [ConstraintRef::Site { position: u32::MAX }; 2 * crate::enforcement::NERVE_CONSTRAINTS_CAP];
5876 let left = A::CONSTRAINTS;
5877 let right = B::CONSTRAINTS;
5878 let offset = A::SITE_COUNT as u32;
5879 let mut i: usize = 0;
5880 while i < left.len() {
5881 out[i] = left[i];
5882 i += 1;
5883 }
5884 let mut j: usize = 0;
5885 while j < right.len() {
5886 out[i + j] = shift_constraint(right[j], offset);
5887 j += 1;
5888 }
5889 out
5890}
5891
5892/// Companion length helper for `sdk_concat_product_constraints`.
5893pub const fn sdk_product_constraints_len<A, B>() -> usize
5894where
5895 A: ConstrainedTypeShape,
5896 B: ConstrainedTypeShape,
5897{
5898 A::CONSTRAINTS.len() + B::CONSTRAINTS.len()
5899}
5900
5901/// ADR-057 recurse-aware variant of `sdk_concat_product_constraints`.
5902/// Per-operand `Option<u32>` recurse-bound parameter: `None` inlines the
5903/// operand's CONSTRAINTS array as before; `Some(bound)` emits a single
5904/// `ConstraintRef::Recurse { shape_iri: <T>::IRI, descent_bound: bound }`
5905/// at the operand's position. Site offsets for the following operand's
5906/// inline constraints account for the recurse operand's
5907/// `<T>::SITE_COUNT` as if it were inlined.
5908pub const fn sdk_concat_product_constraints_v2<A, B>(
5909 a_recurse_bound: Option<u32>,
5910 b_recurse_bound: Option<u32>,
5911) -> [ConstraintRef; 2 * crate::enforcement::NERVE_CONSTRAINTS_CAP]
5912where
5913 A: ConstrainedTypeShape,
5914 B: ConstrainedTypeShape,
5915{
5916 let mut out =
5917 [ConstraintRef::Site { position: u32::MAX }; 2 * crate::enforcement::NERVE_CONSTRAINTS_CAP];
5918 let offset = A::SITE_COUNT as u32;
5919 let mut i: usize = 0;
5920 // A's contribution: single Recurse entry or inlined constraints.
5921 match a_recurse_bound {
5922 Some(bound) => {
5923 out[0] = ConstraintRef::Recurse {
5924 shape_iri: A::IRI,
5925 descent_bound: bound,
5926 };
5927 i = 1;
5928 }
5929 None => {
5930 let left = A::CONSTRAINTS;
5931 while i < left.len() {
5932 out[i] = left[i];
5933 i += 1;
5934 }
5935 }
5936 }
5937 // B's contribution: single Recurse entry or inlined-and-shifted constraints.
5938 match b_recurse_bound {
5939 Some(bound) => {
5940 out[i] = ConstraintRef::Recurse {
5941 shape_iri: B::IRI,
5942 descent_bound: bound,
5943 };
5944 }
5945 None => {
5946 let right = B::CONSTRAINTS;
5947 let mut j: usize = 0;
5948 while j < right.len() {
5949 out[i + j] = shift_constraint(right[j], offset);
5950 j += 1;
5951 }
5952 }
5953 }
5954 out
5955}
5956
5957/// Companion length helper for `sdk_concat_product_constraints_v2`.
5958pub const fn sdk_product_constraints_v2_len<A, B>(a_recurse: bool, b_recurse: bool) -> usize
5959where
5960 A: ConstrainedTypeShape,
5961 B: ConstrainedTypeShape,
5962{
5963 let a_len = if a_recurse { 1 } else { A::CONSTRAINTS.len() };
5964 let b_len = if b_recurse { 1 } else { B::CONSTRAINTS.len() };
5965 a_len + b_len
5966}
5967
5968/// ADR-057: bounded recursive structural typing — foundation shape-IRI
5969/// registry module. Mirrors the architectural pattern of the
5970/// observable-IRI registry per ADR-038/049 and the substitution-axis
5971/// catalog per ADR-007/030: a closed catalog of shape IRIs admissible
5972/// as `ConstraintRef::Recurse` targets, consulted by ψ_1's NerveResolver
5973/// when expanding recursive references at evaluation time.
5974/// # Architecture
5975/// Foundation is `#![no_std]` with zero dependencies and zero `unsafe`,
5976/// so the registry uses **const-aggregation through a sealed trait**
5977/// rather than a mutable global or link-section symbol resolution.
5978/// Applications declare their shape registry as a single type via the
5979/// SDK `register_shape!(Shape1, Shape2, …)` macro per crate; the type
5980/// implements [`ShapeRegistryProvider`] with the const-aggregated slice.
5981/// ψ_1 NerveResolver consults the registry through [`lookup_shape_in`].
5982/// Foundation's built-in [`lookup_shape`] consults a foundation-owned
5983/// static registry — currently empty; standard-library Layer-3 sub-crates
5984/// per ADR-031 publishing canonical recursive-grammar shapes register
5985/// through this path in future foundation-curated additions.
5986pub mod shape_iri_registry {
5987 use super::ConstraintRef;
5988
5989 /// ADR-057: a registered shape entry. `iri` is the shape's
5990 /// content-addressed identifier per ADR-017; `site_count`,
5991 /// `constraints`, and `cycle_size` mirror the corresponding
5992 /// `ConstrainedTypeShape` associated items so ψ_1's resolver
5993 /// can walk the referenced shape's constraint set without
5994 /// touching the original trait impl.
5995 #[derive(Debug, Clone, Copy)]
5996 pub struct RegisteredShape {
5997 /// Content-addressed IRI of the shape (per ADR-017).
5998 pub iri: &'static str,
5999 /// Mirror of `<T as ConstrainedTypeShape>::SITE_COUNT`.
6000 pub site_count: usize,
6001 /// Mirror of `<T as ConstrainedTypeShape>::CONSTRAINTS`.
6002 pub constraints: &'static [ConstraintRef],
6003 /// Mirror of `<T as ConstrainedTypeShape>::CYCLE_SIZE`.
6004 pub cycle_size: u64,
6005 }
6006
6007 /// ADR-057: sealed trait carrying a crate's registered-shape slice
6008 /// at the const-aggregation surface. The SDK `register_shape!` macro
6009 /// emits an `impl ShapeRegistryProvider` on a marker type whose
6010 /// `REGISTRY` const is the list of registered shapes.
6011 /// Sealed via [`super::__sdk_seal::Sealed`] per ADR-022 D1; only
6012 /// the SDK `register_shape!` macro emits impls (plus foundation's
6013 /// [`EmptyShapeRegistry`] default).
6014 pub trait ShapeRegistryProvider: super::__sdk_seal::Sealed {
6015 /// The crate's registered-shape slice. Walked by
6016 /// [`lookup_shape_in`] when ψ_1 NerveResolver expands
6017 /// `ConstraintRef::Recurse` references at evaluation time.
6018 const REGISTRY: &'static [RegisteredShape];
6019 }
6020
6021 /// ADR-057: the empty-registry default, used by applications that
6022 /// don't carry recursive shapes. Foundation provides this as the
6023 /// baseline `ShapeRegistryProvider` impl; applications with
6024 /// recursive shapes use the SDK `register_shape!` macro to declare
6025 /// their own marker type with a non-empty `REGISTRY`.
6026 #[derive(Debug, Clone, Copy, Default)]
6027 pub struct EmptyShapeRegistry;
6028 impl super::__sdk_seal::Sealed for EmptyShapeRegistry {}
6029 impl ShapeRegistryProvider for EmptyShapeRegistry {
6030 const REGISTRY: &'static [RegisteredShape] = &[];
6031 }
6032
6033 /// Foundation's built-in shape registry. Currently empty —
6034 /// standard-library Layer-3 sub-crates per ADR-031 publishing
6035 /// canonical recursive-grammar shapes (canonical JSON, AST, etc.)
6036 /// register through this path in future foundation-curated additions.
6037 static FOUNDATION_REGISTRY: &[RegisteredShape] = &[];
6038
6039 /// ADR-057: look up a registered shape by IRI in the
6040 /// foundation-built-in registry. Returns `None` if the IRI is
6041 /// not present; applications consulting their own application-
6042 /// registered shapes use [`lookup_shape_in`] generic over their
6043 /// `ShapeRegistryProvider`-implementing marker type.
6044 /// Called by ψ_1's NerveResolver during `ConstraintRef::Recurse`
6045 /// evaluation when no application registry is threaded through.
6046 #[must_use]
6047 pub fn lookup_shape(iri: &str) -> Option<&'static RegisteredShape> {
6048 lookup_in_slice(FOUNDATION_REGISTRY, iri)
6049 }
6050
6051 /// ADR-057: look up a registered shape by IRI in an application's
6052 /// registry plus the foundation-built-in registry. Walks the
6053 /// application's `R::REGISTRY` first, then falls back to the
6054 /// foundation registry; returns `None` if the IRI is in neither.
6055 /// Called by ψ_1's NerveResolver during `ConstraintRef::Recurse`
6056 /// evaluation when an application registry is threaded through
6057 /// (per `pipeline::run_route`'s registry-parameterized path).
6058 #[must_use]
6059 pub fn lookup_shape_in<R: ShapeRegistryProvider>(
6060 iri: &str,
6061 ) -> Option<&'static RegisteredShape> {
6062 if let Some(entry) = lookup_in_slice(R::REGISTRY, iri) {
6063 return Some(entry);
6064 }
6065 lookup_in_slice(FOUNDATION_REGISTRY, iri)
6066 }
6067
6068 /// Linear-scan helper used by both [`lookup_shape`] and
6069 /// [`lookup_shape_in`]. Registry slices are expected to be small
6070 /// (a handful of canonical shapes per standard-library sub-crate
6071 /// or per application), so linear scan dominates startup cost.
6072 fn lookup_in_slice(
6073 slice: &'static [RegisteredShape],
6074 iri: &str,
6075 ) -> Option<&'static RegisteredShape> {
6076 let mut i = 0;
6077 while i < slice.len() {
6078 let entry = &slice[i];
6079 if iri_eq(entry.iri, iri) {
6080 return Some(entry);
6081 }
6082 i += 1;
6083 }
6084 None
6085 }
6086
6087 fn iri_eq(a: &str, b: &str) -> bool {
6088 a.as_bytes() == b.as_bytes()
6089 }
6090}
6091
6092/// ADR-032: per-Witt-level zero-sized marker types implementing
6093/// `ConstrainedTypeShape`. The `prism_model!` proc-macro consumes
6094/// these as the `<DomainTy>` operand of `first_admit(<DomainTy>, |i| …)`
6095/// (G16): `<DomainTy as ConstrainedTypeShape>::CYCLE_SIZE` carries the
6096/// domain's cardinality, which the macro lowers as the descent measure
6097/// for the emitted `Term::Recurse`.
6098/// Each level's `CYCLE_SIZE` is `2^bits_width`, saturated at
6099/// `u64::MAX` for levels whose cardinality exceeds 64-bit range.
6100/// The wiki's normative example `first_admit(WittLevel::W32, |nonce| …)`
6101/// compiles on stable Rust 1.83 as `first_admit(witt_domain::W32, |nonce| …)`
6102/// (`witt_domain::W32::CYCLE_SIZE = 4_294_967_296`).
6103pub mod witt_domain {
6104 use super::{ConstrainedTypeShape, ConstraintRef, PartitionProductFields};
6105 use crate::enforcement::GroundedShape;
6106 use crate::pipeline::__sdk_seal;
6107 use crate::pipeline::IntoBindingValue;
6108
6109 /// ADR-032 Witt-level domain marker for `W8` (8-bit ring).
6110 /// `CYCLE_SIZE = 2^8 = 256`. Used as the `<DomainTy>` operand of
6111 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6112 pub struct W8;
6113 impl ConstrainedTypeShape for W8 {
6114 const IRI: &'static str = "https://uor.foundation/witt/W8";
6115 const SITE_COUNT: usize = 1;
6116 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6117 const CYCLE_SIZE: u64 = 256u64;
6118 }
6119 impl __sdk_seal::Sealed for W8 {}
6120 impl<'a> IntoBindingValue<'a> for W8 {
6121 fn as_binding_value<const INLINE_BYTES: usize>(
6122 &self,
6123 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6124 crate::pipeline::TermValue::empty()
6125 }
6126 }
6127 impl GroundedShape for W8 {}
6128 impl PartitionProductFields for W8 {
6129 const FIELDS: &'static [(u32, u32)] = &[];
6130 const FIELD_NAMES: &'static [&'static str] = &[];
6131 }
6132
6133 /// ADR-032 Witt-level domain marker for `W16` (16-bit ring).
6134 /// `CYCLE_SIZE = 2^16 = 65536`. Used as the `<DomainTy>` operand of
6135 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6136 pub struct W16;
6137 impl ConstrainedTypeShape for W16 {
6138 const IRI: &'static str = "https://uor.foundation/witt/W16";
6139 const SITE_COUNT: usize = 1;
6140 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6141 const CYCLE_SIZE: u64 = 65536u64;
6142 }
6143 impl __sdk_seal::Sealed for W16 {}
6144 impl<'a> IntoBindingValue<'a> for W16 {
6145 fn as_binding_value<const INLINE_BYTES: usize>(
6146 &self,
6147 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6148 crate::pipeline::TermValue::empty()
6149 }
6150 }
6151 impl GroundedShape for W16 {}
6152 impl PartitionProductFields for W16 {
6153 const FIELDS: &'static [(u32, u32)] = &[];
6154 const FIELD_NAMES: &'static [&'static str] = &[];
6155 }
6156
6157 /// ADR-032 Witt-level domain marker for `W24` (24-bit ring).
6158 /// `CYCLE_SIZE = 2^24 = 16777216`. Used as the `<DomainTy>` operand of
6159 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6160 pub struct W24;
6161 impl ConstrainedTypeShape for W24 {
6162 const IRI: &'static str = "https://uor.foundation/witt/W24";
6163 const SITE_COUNT: usize = 1;
6164 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6165 const CYCLE_SIZE: u64 = 16777216u64;
6166 }
6167 impl __sdk_seal::Sealed for W24 {}
6168 impl<'a> IntoBindingValue<'a> for W24 {
6169 fn as_binding_value<const INLINE_BYTES: usize>(
6170 &self,
6171 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6172 crate::pipeline::TermValue::empty()
6173 }
6174 }
6175 impl GroundedShape for W24 {}
6176 impl PartitionProductFields for W24 {
6177 const FIELDS: &'static [(u32, u32)] = &[];
6178 const FIELD_NAMES: &'static [&'static str] = &[];
6179 }
6180
6181 /// ADR-032 Witt-level domain marker for `W32` (32-bit ring).
6182 /// `CYCLE_SIZE = 2^32 = 4294967296`. Used as the `<DomainTy>` operand of
6183 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6184 pub struct W32;
6185 impl ConstrainedTypeShape for W32 {
6186 const IRI: &'static str = "https://uor.foundation/witt/W32";
6187 const SITE_COUNT: usize = 1;
6188 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6189 const CYCLE_SIZE: u64 = 4294967296u64;
6190 }
6191 impl __sdk_seal::Sealed for W32 {}
6192 impl<'a> IntoBindingValue<'a> for W32 {
6193 fn as_binding_value<const INLINE_BYTES: usize>(
6194 &self,
6195 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6196 crate::pipeline::TermValue::empty()
6197 }
6198 }
6199 impl GroundedShape for W32 {}
6200 impl PartitionProductFields for W32 {
6201 const FIELDS: &'static [(u32, u32)] = &[];
6202 const FIELD_NAMES: &'static [&'static str] = &[];
6203 }
6204
6205 /// ADR-032 Witt-level domain marker for `W40` (40-bit ring).
6206 /// `CYCLE_SIZE = 2^40 = 1099511627776`. Used as the `<DomainTy>` operand of
6207 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6208 pub struct W40;
6209 impl ConstrainedTypeShape for W40 {
6210 const IRI: &'static str = "https://uor.foundation/witt/W40";
6211 const SITE_COUNT: usize = 1;
6212 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6213 const CYCLE_SIZE: u64 = 1099511627776u64;
6214 }
6215 impl __sdk_seal::Sealed for W40 {}
6216 impl<'a> IntoBindingValue<'a> for W40 {
6217 fn as_binding_value<const INLINE_BYTES: usize>(
6218 &self,
6219 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6220 crate::pipeline::TermValue::empty()
6221 }
6222 }
6223 impl GroundedShape for W40 {}
6224 impl PartitionProductFields for W40 {
6225 const FIELDS: &'static [(u32, u32)] = &[];
6226 const FIELD_NAMES: &'static [&'static str] = &[];
6227 }
6228
6229 /// ADR-032 Witt-level domain marker for `W48` (48-bit ring).
6230 /// `CYCLE_SIZE = 2^48 = 281474976710656`. Used as the `<DomainTy>` operand of
6231 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6232 pub struct W48;
6233 impl ConstrainedTypeShape for W48 {
6234 const IRI: &'static str = "https://uor.foundation/witt/W48";
6235 const SITE_COUNT: usize = 1;
6236 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6237 const CYCLE_SIZE: u64 = 281474976710656u64;
6238 }
6239 impl __sdk_seal::Sealed for W48 {}
6240 impl<'a> IntoBindingValue<'a> for W48 {
6241 fn as_binding_value<const INLINE_BYTES: usize>(
6242 &self,
6243 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6244 crate::pipeline::TermValue::empty()
6245 }
6246 }
6247 impl GroundedShape for W48 {}
6248 impl PartitionProductFields for W48 {
6249 const FIELDS: &'static [(u32, u32)] = &[];
6250 const FIELD_NAMES: &'static [&'static str] = &[];
6251 }
6252
6253 /// ADR-032 Witt-level domain marker for `W56` (56-bit ring).
6254 /// `CYCLE_SIZE = 2^56 = 72057594037927936`. Used as the `<DomainTy>` operand of
6255 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6256 pub struct W56;
6257 impl ConstrainedTypeShape for W56 {
6258 const IRI: &'static str = "https://uor.foundation/witt/W56";
6259 const SITE_COUNT: usize = 1;
6260 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6261 const CYCLE_SIZE: u64 = 72057594037927936u64;
6262 }
6263 impl __sdk_seal::Sealed for W56 {}
6264 impl<'a> IntoBindingValue<'a> for W56 {
6265 fn as_binding_value<const INLINE_BYTES: usize>(
6266 &self,
6267 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6268 crate::pipeline::TermValue::empty()
6269 }
6270 }
6271 impl GroundedShape for W56 {}
6272 impl PartitionProductFields for W56 {
6273 const FIELDS: &'static [(u32, u32)] = &[];
6274 const FIELD_NAMES: &'static [&'static str] = &[];
6275 }
6276
6277 /// ADR-032 Witt-level domain marker for `W64` (64-bit ring).
6278 /// `CYCLE_SIZE = 2^64 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6279 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6280 pub struct W64;
6281 impl ConstrainedTypeShape for W64 {
6282 const IRI: &'static str = "https://uor.foundation/witt/W64";
6283 const SITE_COUNT: usize = 1;
6284 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6285 const CYCLE_SIZE: u64 = u64::MAX;
6286 }
6287 impl __sdk_seal::Sealed for W64 {}
6288 impl<'a> IntoBindingValue<'a> for W64 {
6289 fn as_binding_value<const INLINE_BYTES: usize>(
6290 &self,
6291 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6292 crate::pipeline::TermValue::empty()
6293 }
6294 }
6295 impl GroundedShape for W64 {}
6296 impl PartitionProductFields for W64 {
6297 const FIELDS: &'static [(u32, u32)] = &[];
6298 const FIELD_NAMES: &'static [&'static str] = &[];
6299 }
6300
6301 /// ADR-032 Witt-level domain marker for `W72` (72-bit ring).
6302 /// `CYCLE_SIZE = 2^72 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6303 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6304 pub struct W72;
6305 impl ConstrainedTypeShape for W72 {
6306 const IRI: &'static str = "https://uor.foundation/witt/W72";
6307 const SITE_COUNT: usize = 1;
6308 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6309 const CYCLE_SIZE: u64 = u64::MAX;
6310 }
6311 impl __sdk_seal::Sealed for W72 {}
6312 impl<'a> IntoBindingValue<'a> for W72 {
6313 fn as_binding_value<const INLINE_BYTES: usize>(
6314 &self,
6315 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6316 crate::pipeline::TermValue::empty()
6317 }
6318 }
6319 impl GroundedShape for W72 {}
6320 impl PartitionProductFields for W72 {
6321 const FIELDS: &'static [(u32, u32)] = &[];
6322 const FIELD_NAMES: &'static [&'static str] = &[];
6323 }
6324
6325 /// ADR-032 Witt-level domain marker for `W80` (80-bit ring).
6326 /// `CYCLE_SIZE = 2^80 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6327 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6328 pub struct W80;
6329 impl ConstrainedTypeShape for W80 {
6330 const IRI: &'static str = "https://uor.foundation/witt/W80";
6331 const SITE_COUNT: usize = 1;
6332 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6333 const CYCLE_SIZE: u64 = u64::MAX;
6334 }
6335 impl __sdk_seal::Sealed for W80 {}
6336 impl<'a> IntoBindingValue<'a> for W80 {
6337 fn as_binding_value<const INLINE_BYTES: usize>(
6338 &self,
6339 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6340 crate::pipeline::TermValue::empty()
6341 }
6342 }
6343 impl GroundedShape for W80 {}
6344 impl PartitionProductFields for W80 {
6345 const FIELDS: &'static [(u32, u32)] = &[];
6346 const FIELD_NAMES: &'static [&'static str] = &[];
6347 }
6348
6349 /// ADR-032 Witt-level domain marker for `W88` (88-bit ring).
6350 /// `CYCLE_SIZE = 2^88 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6351 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6352 pub struct W88;
6353 impl ConstrainedTypeShape for W88 {
6354 const IRI: &'static str = "https://uor.foundation/witt/W88";
6355 const SITE_COUNT: usize = 1;
6356 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6357 const CYCLE_SIZE: u64 = u64::MAX;
6358 }
6359 impl __sdk_seal::Sealed for W88 {}
6360 impl<'a> IntoBindingValue<'a> for W88 {
6361 fn as_binding_value<const INLINE_BYTES: usize>(
6362 &self,
6363 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6364 crate::pipeline::TermValue::empty()
6365 }
6366 }
6367 impl GroundedShape for W88 {}
6368 impl PartitionProductFields for W88 {
6369 const FIELDS: &'static [(u32, u32)] = &[];
6370 const FIELD_NAMES: &'static [&'static str] = &[];
6371 }
6372
6373 /// ADR-032 Witt-level domain marker for `W96` (96-bit ring).
6374 /// `CYCLE_SIZE = 2^96 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6375 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6376 pub struct W96;
6377 impl ConstrainedTypeShape for W96 {
6378 const IRI: &'static str = "https://uor.foundation/witt/W96";
6379 const SITE_COUNT: usize = 1;
6380 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6381 const CYCLE_SIZE: u64 = u64::MAX;
6382 }
6383 impl __sdk_seal::Sealed for W96 {}
6384 impl<'a> IntoBindingValue<'a> for W96 {
6385 fn as_binding_value<const INLINE_BYTES: usize>(
6386 &self,
6387 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6388 crate::pipeline::TermValue::empty()
6389 }
6390 }
6391 impl GroundedShape for W96 {}
6392 impl PartitionProductFields for W96 {
6393 const FIELDS: &'static [(u32, u32)] = &[];
6394 const FIELD_NAMES: &'static [&'static str] = &[];
6395 }
6396
6397 /// ADR-032 Witt-level domain marker for `W104` (104-bit ring).
6398 /// `CYCLE_SIZE = 2^104 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6399 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6400 pub struct W104;
6401 impl ConstrainedTypeShape for W104 {
6402 const IRI: &'static str = "https://uor.foundation/witt/W104";
6403 const SITE_COUNT: usize = 1;
6404 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6405 const CYCLE_SIZE: u64 = u64::MAX;
6406 }
6407 impl __sdk_seal::Sealed for W104 {}
6408 impl<'a> IntoBindingValue<'a> for W104 {
6409 fn as_binding_value<const INLINE_BYTES: usize>(
6410 &self,
6411 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6412 crate::pipeline::TermValue::empty()
6413 }
6414 }
6415 impl GroundedShape for W104 {}
6416 impl PartitionProductFields for W104 {
6417 const FIELDS: &'static [(u32, u32)] = &[];
6418 const FIELD_NAMES: &'static [&'static str] = &[];
6419 }
6420
6421 /// ADR-032 Witt-level domain marker for `W112` (112-bit ring).
6422 /// `CYCLE_SIZE = 2^112 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6423 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6424 pub struct W112;
6425 impl ConstrainedTypeShape for W112 {
6426 const IRI: &'static str = "https://uor.foundation/witt/W112";
6427 const SITE_COUNT: usize = 1;
6428 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6429 const CYCLE_SIZE: u64 = u64::MAX;
6430 }
6431 impl __sdk_seal::Sealed for W112 {}
6432 impl<'a> IntoBindingValue<'a> for W112 {
6433 fn as_binding_value<const INLINE_BYTES: usize>(
6434 &self,
6435 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6436 crate::pipeline::TermValue::empty()
6437 }
6438 }
6439 impl GroundedShape for W112 {}
6440 impl PartitionProductFields for W112 {
6441 const FIELDS: &'static [(u32, u32)] = &[];
6442 const FIELD_NAMES: &'static [&'static str] = &[];
6443 }
6444
6445 /// ADR-032 Witt-level domain marker for `W120` (120-bit ring).
6446 /// `CYCLE_SIZE = 2^120 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6447 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6448 pub struct W120;
6449 impl ConstrainedTypeShape for W120 {
6450 const IRI: &'static str = "https://uor.foundation/witt/W120";
6451 const SITE_COUNT: usize = 1;
6452 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6453 const CYCLE_SIZE: u64 = u64::MAX;
6454 }
6455 impl __sdk_seal::Sealed for W120 {}
6456 impl<'a> IntoBindingValue<'a> for W120 {
6457 fn as_binding_value<const INLINE_BYTES: usize>(
6458 &self,
6459 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6460 crate::pipeline::TermValue::empty()
6461 }
6462 }
6463 impl GroundedShape for W120 {}
6464 impl PartitionProductFields for W120 {
6465 const FIELDS: &'static [(u32, u32)] = &[];
6466 const FIELD_NAMES: &'static [&'static str] = &[];
6467 }
6468
6469 /// ADR-032 Witt-level domain marker for `W128` (128-bit ring).
6470 /// `CYCLE_SIZE = 2^128 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6471 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6472 pub struct W128;
6473 impl ConstrainedTypeShape for W128 {
6474 const IRI: &'static str = "https://uor.foundation/witt/W128";
6475 const SITE_COUNT: usize = 1;
6476 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6477 const CYCLE_SIZE: u64 = u64::MAX;
6478 }
6479 impl __sdk_seal::Sealed for W128 {}
6480 impl<'a> IntoBindingValue<'a> for W128 {
6481 fn as_binding_value<const INLINE_BYTES: usize>(
6482 &self,
6483 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6484 crate::pipeline::TermValue::empty()
6485 }
6486 }
6487 impl GroundedShape for W128 {}
6488 impl PartitionProductFields for W128 {
6489 const FIELDS: &'static [(u32, u32)] = &[];
6490 const FIELD_NAMES: &'static [&'static str] = &[];
6491 }
6492
6493 /// ADR-032 Witt-level domain marker for `W160` (160-bit ring).
6494 /// `CYCLE_SIZE = 2^160 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6495 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6496 pub struct W160;
6497 impl ConstrainedTypeShape for W160 {
6498 const IRI: &'static str = "https://uor.foundation/witt/W160";
6499 const SITE_COUNT: usize = 1;
6500 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6501 const CYCLE_SIZE: u64 = u64::MAX;
6502 }
6503 impl __sdk_seal::Sealed for W160 {}
6504 impl<'a> IntoBindingValue<'a> for W160 {
6505 fn as_binding_value<const INLINE_BYTES: usize>(
6506 &self,
6507 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6508 crate::pipeline::TermValue::empty()
6509 }
6510 }
6511 impl GroundedShape for W160 {}
6512 impl PartitionProductFields for W160 {
6513 const FIELDS: &'static [(u32, u32)] = &[];
6514 const FIELD_NAMES: &'static [&'static str] = &[];
6515 }
6516
6517 /// ADR-032 Witt-level domain marker for `W192` (192-bit ring).
6518 /// `CYCLE_SIZE = 2^192 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6519 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6520 pub struct W192;
6521 impl ConstrainedTypeShape for W192 {
6522 const IRI: &'static str = "https://uor.foundation/witt/W192";
6523 const SITE_COUNT: usize = 1;
6524 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6525 const CYCLE_SIZE: u64 = u64::MAX;
6526 }
6527 impl __sdk_seal::Sealed for W192 {}
6528 impl<'a> IntoBindingValue<'a> for W192 {
6529 fn as_binding_value<const INLINE_BYTES: usize>(
6530 &self,
6531 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6532 crate::pipeline::TermValue::empty()
6533 }
6534 }
6535 impl GroundedShape for W192 {}
6536 impl PartitionProductFields for W192 {
6537 const FIELDS: &'static [(u32, u32)] = &[];
6538 const FIELD_NAMES: &'static [&'static str] = &[];
6539 }
6540
6541 /// ADR-032 Witt-level domain marker for `W224` (224-bit ring).
6542 /// `CYCLE_SIZE = 2^224 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6543 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6544 pub struct W224;
6545 impl ConstrainedTypeShape for W224 {
6546 const IRI: &'static str = "https://uor.foundation/witt/W224";
6547 const SITE_COUNT: usize = 1;
6548 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6549 const CYCLE_SIZE: u64 = u64::MAX;
6550 }
6551 impl __sdk_seal::Sealed for W224 {}
6552 impl<'a> IntoBindingValue<'a> for W224 {
6553 fn as_binding_value<const INLINE_BYTES: usize>(
6554 &self,
6555 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6556 crate::pipeline::TermValue::empty()
6557 }
6558 }
6559 impl GroundedShape for W224 {}
6560 impl PartitionProductFields for W224 {
6561 const FIELDS: &'static [(u32, u32)] = &[];
6562 const FIELD_NAMES: &'static [&'static str] = &[];
6563 }
6564
6565 /// ADR-032 Witt-level domain marker for `W256` (256-bit ring).
6566 /// `CYCLE_SIZE = 2^256 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6567 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6568 pub struct W256;
6569 impl ConstrainedTypeShape for W256 {
6570 const IRI: &'static str = "https://uor.foundation/witt/W256";
6571 const SITE_COUNT: usize = 1;
6572 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6573 const CYCLE_SIZE: u64 = u64::MAX;
6574 }
6575 impl __sdk_seal::Sealed for W256 {}
6576 impl<'a> IntoBindingValue<'a> for W256 {
6577 fn as_binding_value<const INLINE_BYTES: usize>(
6578 &self,
6579 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6580 crate::pipeline::TermValue::empty()
6581 }
6582 }
6583 impl GroundedShape for W256 {}
6584 impl PartitionProductFields for W256 {
6585 const FIELDS: &'static [(u32, u32)] = &[];
6586 const FIELD_NAMES: &'static [&'static str] = &[];
6587 }
6588
6589 /// ADR-032 Witt-level domain marker for `W384` (384-bit ring).
6590 /// `CYCLE_SIZE = 2^384 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6591 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6592 pub struct W384;
6593 impl ConstrainedTypeShape for W384 {
6594 const IRI: &'static str = "https://uor.foundation/witt/W384";
6595 const SITE_COUNT: usize = 1;
6596 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6597 const CYCLE_SIZE: u64 = u64::MAX;
6598 }
6599 impl __sdk_seal::Sealed for W384 {}
6600 impl<'a> IntoBindingValue<'a> for W384 {
6601 fn as_binding_value<const INLINE_BYTES: usize>(
6602 &self,
6603 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6604 crate::pipeline::TermValue::empty()
6605 }
6606 }
6607 impl GroundedShape for W384 {}
6608 impl PartitionProductFields for W384 {
6609 const FIELDS: &'static [(u32, u32)] = &[];
6610 const FIELD_NAMES: &'static [&'static str] = &[];
6611 }
6612
6613 /// ADR-032 Witt-level domain marker for `W448` (448-bit ring).
6614 /// `CYCLE_SIZE = 2^448 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6615 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6616 pub struct W448;
6617 impl ConstrainedTypeShape for W448 {
6618 const IRI: &'static str = "https://uor.foundation/witt/W448";
6619 const SITE_COUNT: usize = 1;
6620 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6621 const CYCLE_SIZE: u64 = u64::MAX;
6622 }
6623 impl __sdk_seal::Sealed for W448 {}
6624 impl<'a> IntoBindingValue<'a> for W448 {
6625 fn as_binding_value<const INLINE_BYTES: usize>(
6626 &self,
6627 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6628 crate::pipeline::TermValue::empty()
6629 }
6630 }
6631 impl GroundedShape for W448 {}
6632 impl PartitionProductFields for W448 {
6633 const FIELDS: &'static [(u32, u32)] = &[];
6634 const FIELD_NAMES: &'static [&'static str] = &[];
6635 }
6636
6637 /// ADR-032 Witt-level domain marker for `W512` (512-bit ring).
6638 /// `CYCLE_SIZE = 2^512 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6639 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6640 pub struct W512;
6641 impl ConstrainedTypeShape for W512 {
6642 const IRI: &'static str = "https://uor.foundation/witt/W512";
6643 const SITE_COUNT: usize = 1;
6644 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6645 const CYCLE_SIZE: u64 = u64::MAX;
6646 }
6647 impl __sdk_seal::Sealed for W512 {}
6648 impl<'a> IntoBindingValue<'a> for W512 {
6649 fn as_binding_value<const INLINE_BYTES: usize>(
6650 &self,
6651 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6652 crate::pipeline::TermValue::empty()
6653 }
6654 }
6655 impl GroundedShape for W512 {}
6656 impl PartitionProductFields for W512 {
6657 const FIELDS: &'static [(u32, u32)] = &[];
6658 const FIELD_NAMES: &'static [&'static str] = &[];
6659 }
6660
6661 /// ADR-032 Witt-level domain marker for `W520` (520-bit ring).
6662 /// `CYCLE_SIZE = 2^520 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6663 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6664 pub struct W520;
6665 impl ConstrainedTypeShape for W520 {
6666 const IRI: &'static str = "https://uor.foundation/witt/W520";
6667 const SITE_COUNT: usize = 1;
6668 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6669 const CYCLE_SIZE: u64 = u64::MAX;
6670 }
6671 impl __sdk_seal::Sealed for W520 {}
6672 impl<'a> IntoBindingValue<'a> for W520 {
6673 fn as_binding_value<const INLINE_BYTES: usize>(
6674 &self,
6675 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6676 crate::pipeline::TermValue::empty()
6677 }
6678 }
6679 impl GroundedShape for W520 {}
6680 impl PartitionProductFields for W520 {
6681 const FIELDS: &'static [(u32, u32)] = &[];
6682 const FIELD_NAMES: &'static [&'static str] = &[];
6683 }
6684
6685 /// ADR-032 Witt-level domain marker for `W528` (528-bit ring).
6686 /// `CYCLE_SIZE = 2^528 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6687 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6688 pub struct W528;
6689 impl ConstrainedTypeShape for W528 {
6690 const IRI: &'static str = "https://uor.foundation/witt/W528";
6691 const SITE_COUNT: usize = 1;
6692 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6693 const CYCLE_SIZE: u64 = u64::MAX;
6694 }
6695 impl __sdk_seal::Sealed for W528 {}
6696 impl<'a> IntoBindingValue<'a> for W528 {
6697 fn as_binding_value<const INLINE_BYTES: usize>(
6698 &self,
6699 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6700 crate::pipeline::TermValue::empty()
6701 }
6702 }
6703 impl GroundedShape for W528 {}
6704 impl PartitionProductFields for W528 {
6705 const FIELDS: &'static [(u32, u32)] = &[];
6706 const FIELD_NAMES: &'static [&'static str] = &[];
6707 }
6708
6709 /// ADR-032 Witt-level domain marker for `W1024` (1024-bit ring).
6710 /// `CYCLE_SIZE = 2^1024 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6711 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6712 pub struct W1024;
6713 impl ConstrainedTypeShape for W1024 {
6714 const IRI: &'static str = "https://uor.foundation/witt/W1024";
6715 const SITE_COUNT: usize = 1;
6716 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6717 const CYCLE_SIZE: u64 = u64::MAX;
6718 }
6719 impl __sdk_seal::Sealed for W1024 {}
6720 impl<'a> IntoBindingValue<'a> for W1024 {
6721 fn as_binding_value<const INLINE_BYTES: usize>(
6722 &self,
6723 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6724 crate::pipeline::TermValue::empty()
6725 }
6726 }
6727 impl GroundedShape for W1024 {}
6728 impl PartitionProductFields for W1024 {
6729 const FIELDS: &'static [(u32, u32)] = &[];
6730 const FIELD_NAMES: &'static [&'static str] = &[];
6731 }
6732
6733 /// ADR-032 Witt-level domain marker for `W2048` (2048-bit ring).
6734 /// `CYCLE_SIZE = 2^2048 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6735 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6736 pub struct W2048;
6737 impl ConstrainedTypeShape for W2048 {
6738 const IRI: &'static str = "https://uor.foundation/witt/W2048";
6739 const SITE_COUNT: usize = 1;
6740 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6741 const CYCLE_SIZE: u64 = u64::MAX;
6742 }
6743 impl __sdk_seal::Sealed for W2048 {}
6744 impl<'a> IntoBindingValue<'a> for W2048 {
6745 fn as_binding_value<const INLINE_BYTES: usize>(
6746 &self,
6747 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6748 crate::pipeline::TermValue::empty()
6749 }
6750 }
6751 impl GroundedShape for W2048 {}
6752 impl PartitionProductFields for W2048 {
6753 const FIELDS: &'static [(u32, u32)] = &[];
6754 const FIELD_NAMES: &'static [&'static str] = &[];
6755 }
6756
6757 /// ADR-032 Witt-level domain marker for `W4096` (4096-bit ring).
6758 /// `CYCLE_SIZE = 2^4096 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6759 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6760 pub struct W4096;
6761 impl ConstrainedTypeShape for W4096 {
6762 const IRI: &'static str = "https://uor.foundation/witt/W4096";
6763 const SITE_COUNT: usize = 1;
6764 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6765 const CYCLE_SIZE: u64 = u64::MAX;
6766 }
6767 impl __sdk_seal::Sealed for W4096 {}
6768 impl<'a> IntoBindingValue<'a> for W4096 {
6769 fn as_binding_value<const INLINE_BYTES: usize>(
6770 &self,
6771 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6772 crate::pipeline::TermValue::empty()
6773 }
6774 }
6775 impl GroundedShape for W4096 {}
6776 impl PartitionProductFields for W4096 {
6777 const FIELDS: &'static [(u32, u32)] = &[];
6778 const FIELD_NAMES: &'static [&'static str] = &[];
6779 }
6780
6781 /// ADR-032 Witt-level domain marker for `W8192` (8192-bit ring).
6782 /// `CYCLE_SIZE = 2^8192 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6783 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6784 pub struct W8192;
6785 impl ConstrainedTypeShape for W8192 {
6786 const IRI: &'static str = "https://uor.foundation/witt/W8192";
6787 const SITE_COUNT: usize = 1;
6788 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6789 const CYCLE_SIZE: u64 = u64::MAX;
6790 }
6791 impl __sdk_seal::Sealed for W8192 {}
6792 impl<'a> IntoBindingValue<'a> for W8192 {
6793 fn as_binding_value<const INLINE_BYTES: usize>(
6794 &self,
6795 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6796 crate::pipeline::TermValue::empty()
6797 }
6798 }
6799 impl GroundedShape for W8192 {}
6800 impl PartitionProductFields for W8192 {
6801 const FIELDS: &'static [(u32, u32)] = &[];
6802 const FIELD_NAMES: &'static [&'static str] = &[];
6803 }
6804
6805 /// ADR-032 Witt-level domain marker for `W12288` (12288-bit ring).
6806 /// `CYCLE_SIZE = 2^12288 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6807 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6808 pub struct W12288;
6809 impl ConstrainedTypeShape for W12288 {
6810 const IRI: &'static str = "https://uor.foundation/witt/W12288";
6811 const SITE_COUNT: usize = 1;
6812 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6813 const CYCLE_SIZE: u64 = u64::MAX;
6814 }
6815 impl __sdk_seal::Sealed for W12288 {}
6816 impl<'a> IntoBindingValue<'a> for W12288 {
6817 fn as_binding_value<const INLINE_BYTES: usize>(
6818 &self,
6819 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6820 crate::pipeline::TermValue::empty()
6821 }
6822 }
6823 impl GroundedShape for W12288 {}
6824 impl PartitionProductFields for W12288 {
6825 const FIELDS: &'static [(u32, u32)] = &[];
6826 const FIELD_NAMES: &'static [&'static str] = &[];
6827 }
6828
6829 /// ADR-032 Witt-level domain marker for `W16384` (16384-bit ring).
6830 /// `CYCLE_SIZE = 2^16384 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6831 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6832 pub struct W16384;
6833 impl ConstrainedTypeShape for W16384 {
6834 const IRI: &'static str = "https://uor.foundation/witt/W16384";
6835 const SITE_COUNT: usize = 1;
6836 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6837 const CYCLE_SIZE: u64 = u64::MAX;
6838 }
6839 impl __sdk_seal::Sealed for W16384 {}
6840 impl<'a> IntoBindingValue<'a> for W16384 {
6841 fn as_binding_value<const INLINE_BYTES: usize>(
6842 &self,
6843 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6844 crate::pipeline::TermValue::empty()
6845 }
6846 }
6847 impl GroundedShape for W16384 {}
6848 impl PartitionProductFields for W16384 {
6849 const FIELDS: &'static [(u32, u32)] = &[];
6850 const FIELD_NAMES: &'static [&'static str] = &[];
6851 }
6852
6853 /// ADR-032 Witt-level domain marker for `W32768` (32768-bit ring).
6854 /// `CYCLE_SIZE = 2^32768 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6855 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6856 pub struct W32768;
6857 impl ConstrainedTypeShape for W32768 {
6858 const IRI: &'static str = "https://uor.foundation/witt/W32768";
6859 const SITE_COUNT: usize = 1;
6860 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6861 const CYCLE_SIZE: u64 = u64::MAX;
6862 }
6863 impl __sdk_seal::Sealed for W32768 {}
6864 impl<'a> IntoBindingValue<'a> for W32768 {
6865 fn as_binding_value<const INLINE_BYTES: usize>(
6866 &self,
6867 ) -> crate::pipeline::TermValue<'a, INLINE_BYTES> {
6868 crate::pipeline::TermValue::empty()
6869 }
6870 }
6871 impl GroundedShape for W32768 {}
6872 impl PartitionProductFields for W32768 {
6873 const FIELDS: &'static [(u32, u32)] = &[];
6874 const FIELD_NAMES: &'static [&'static str] = &[];
6875 }
6876}
6877
6878/// Admit a downstream [`ConstrainedTypeShape`] into the reduction pipeline.
6879/// Runs the full preflight chain on `T::CONSTRAINTS`:
6880/// [`preflight_feasibility`] and [`preflight_package_coherence`]. On success,
6881/// wraps the supplied `shape` in a [`Validated`] carrying `Runtime` phase.
6882/// # Errors
6883/// Returns [`ShapeViolation`] if any constraint in `T::CONSTRAINTS` fails
6884/// feasibility checking (e.g., residue out of range, depth min > max) or if
6885/// the constraint system is internally incoherent (e.g., contradictory
6886/// residue constraints on the same modulus).
6887/// # Example
6888/// ```
6889/// use uor_foundation::pipeline::{
6890/// ConstrainedTypeShape, ConstraintRef, validate_constrained_type,
6891/// };
6892/// pub struct MyShape;
6893/// impl ConstrainedTypeShape for MyShape {
6894/// const IRI: &'static str = "https://example.org/MyShape";
6895/// const SITE_COUNT: usize = 2;
6896/// const CONSTRAINTS: &'static [ConstraintRef] = &[
6897/// ConstraintRef::Residue { modulus: 5, residue: 2 },
6898/// ];
6899/// const CYCLE_SIZE: u64 = 5; // ADR-032: 5 residue classes mod 5
6900/// }
6901/// let validated = validate_constrained_type(MyShape).unwrap();
6902/// # let _ = validated;
6903/// ```
6904pub fn validate_constrained_type<T: ConstrainedTypeShape>(
6905 shape: T,
6906) -> Result<Validated<T, crate::enforcement::Runtime>, ShapeViolation> {
6907 preflight_feasibility(T::CONSTRAINTS)?;
6908 preflight_package_coherence(T::CONSTRAINTS)?;
6909 Ok(Validated::new(shape))
6910}
6911
6912/// Const-fn companion for [`validate_constrained_type`].
6913/// Admits a downstream [`ConstrainedTypeShape`] at compile time, running the
6914/// same preflight checks as the runtime variant but in `const` context.
6915/// # Scope
6916/// `ConstraintRef::Bound { observable_iri, args_repr, .. }` with
6917/// `observable_iri == "https://uor.foundation/observable/LandauerCost"`
6918/// requires `f64::from_bits` for args parsing, which is stable in `const`
6919/// context only from Rust 1.83. The crate's MSRV is 1.81, so this variant
6920/// rejects const admission of `LandauerCost`-bound constraints with
6921/// [`ShapeViolation`] and recommends the runtime [`validate_constrained_type`]
6922/// for those inputs. All other `ConstraintRef` variants admit at const time.
6923/// # Errors
6924/// Same as [`validate_constrained_type`], plus the const-context rejection
6925/// for `LandauerCost`-bound constraints described above.
6926/// The `T: Copy` bound is required by `const fn` — destructor invocation is
6927/// not yet const-stable, and `Validated<T>` carries `T` by value. Shape
6928/// markers are typically zero-sized types which are trivially `Copy`.
6929pub const fn validate_constrained_type_const<T: ConstrainedTypeShape + Copy>(
6930 shape: T,
6931) -> Result<Validated<T, crate::enforcement::CompileTime>, ShapeViolation> {
6932 // Const-path preflight: walk CONSTRAINTS and apply per-variant const checks.
6933 // Rejects LandauerCost-bound constraints that need non-const f64::from_bits.
6934 let constraints = T::CONSTRAINTS;
6935 let mut i = 0;
6936 while i < constraints.len() {
6937 let ok = match &constraints[i] {
6938 ConstraintRef::SatClauses { clauses, num_vars } => *num_vars != 0 || clauses.is_empty(),
6939 ConstraintRef::Residue { modulus, residue } => *modulus != 0 && *residue < *modulus,
6940 ConstraintRef::Carry { .. } => true,
6941 ConstraintRef::Depth { min, max } => *min <= *max,
6942 ConstraintRef::Hamming { bound } => *bound <= 32_768,
6943 ConstraintRef::Site { .. } => true,
6944 ConstraintRef::Affine {
6945 coefficients,
6946 coefficient_count,
6947 bias,
6948 } => {
6949 // Mirror preflight_feasibility's Affine arm in const context.
6950 let count = *coefficient_count as usize;
6951 if count == 0 {
6952 false
6953 } else {
6954 let mut ok_coeff = true;
6955 let mut idx = 0;
6956 while idx < count && idx < AFFINE_MAX_COEFFS {
6957 if coefficients[idx] == i64::MIN {
6958 ok_coeff = false;
6959 break;
6960 }
6961 idx += 1;
6962 }
6963 ok_coeff && is_affine_consistent(coefficients, *coefficient_count, *bias)
6964 }
6965 }
6966 ConstraintRef::Bound { observable_iri, .. } => {
6967 // const-fn scope: LandauerCost needs f64::from_bits (stable in
6968 // const at 1.83). Reject it here; runtime admission handles it.
6969 !crate::enforcement::str_eq(
6970 observable_iri,
6971 "https://uor.foundation/observable/LandauerCost",
6972 )
6973 }
6974 ConstraintRef::Conjunction {
6975 conjuncts,
6976 conjunct_count,
6977 } => conjunction_all_sat(conjuncts, *conjunct_count),
6978 // ADR-057: const-time validation defers Recurse to runtime
6979 // admission (parallel to ADR-049's LandauerCost deferral).
6980 // The only const check is that `shape_iri` is non-empty —
6981 // the registry lookup happens at the runtime admission path.
6982 ConstraintRef::Recurse { shape_iri, .. } => !shape_iri.is_empty(),
6983 };
6984 if !ok {
6985 return Err(ShapeViolation {
6986 shape_iri: "https://uor.foundation/type/ConstrainedType",
6987 constraint_iri: "https://uor.foundation/type/ConstrainedType_const_constraint",
6988 property_iri: "https://uor.foundation/type/constraints",
6989 expected_range: "https://uor.foundation/type/Constraint",
6990 min_count: 1,
6991 max_count: 1,
6992 kind: ViolationKind::ValueCheck,
6993 });
6994 }
6995 i += 1;
6996 }
6997 Ok(Validated::new(shape))
6998}
6999
7000/// Result of `fragment_classify`: which `predicate:*Shape` fragment the
7001/// input belongs to. Drives `InhabitanceResolver` dispatch routing.
7002#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7003pub enum FragmentKind {
7004 /// `predicate:Is2SatShape` — clauses of width ≤ 2.
7005 TwoSat,
7006 /// `predicate:IsHornShape` — clauses with ≤ 1 positive literal.
7007 Horn,
7008 /// `predicate:IsResidualFragment` — catch-all; no polynomial bound.
7009 Residual,
7010}
7011
7012/// Classify a constraint system into one of the three dispatch fragments.
7013/// The classifier inspects the first `SatClauses` constraint (if any) and
7014/// applies the ontology's shape predicates. Constraint systems with no
7015/// `SatClauses` constraint — e.g., pure residue/hamming constraints — are
7016/// classified as `Residual`: the dispatch table has no polynomial decider
7017/// for them, so they route to the `ResidualVerdictResolver` catch-all.
7018#[must_use]
7019pub const fn fragment_classify(constraints: &[ConstraintRef]) -> FragmentKind {
7020 let mut i = 0;
7021 while i < constraints.len() {
7022 if let ConstraintRef::SatClauses { clauses, .. } = constraints[i] {
7023 // Classify by maximum clause width and positive-literal count.
7024 let mut max_width: usize = 0;
7025 let mut horn: bool = true;
7026 let mut j = 0;
7027 while j < clauses.len() {
7028 let clause = clauses[j];
7029 if clause.len() > max_width {
7030 max_width = clause.len();
7031 }
7032 let mut positives: usize = 0;
7033 let mut k = 0;
7034 while k < clause.len() {
7035 let (_, negated) = clause[k];
7036 if !negated {
7037 positives += 1;
7038 }
7039 k += 1;
7040 }
7041 if positives > 1 {
7042 horn = false;
7043 }
7044 j += 1;
7045 }
7046 if max_width <= 2 {
7047 return FragmentKind::TwoSat;
7048 } else if horn {
7049 return FragmentKind::Horn;
7050 } else {
7051 return FragmentKind::Residual;
7052 }
7053 }
7054 i += 1;
7055 }
7056 // No SAT clauses at all — residual.
7057 FragmentKind::Residual
7058}
7059
7060/// Aspvall-Plass-Tarjan 2-SAT decider: returns `true` iff the clause list
7061/// is satisfiable.
7062/// Builds the implication graph: for each clause `(a | b)`, adds
7063/// `¬a → b` and `¬b → a`. Runs Tarjan's SCC algorithm and checks that
7064/// no variable and its negation share an SCC. O(n+m) via iterative
7065/// Tarjan (the `no_std` path can't recurse freely).
7066/// Bounds (from `reduction:TwoSatBound`): up to 256 variables, up to 512 clauses. The `const` bounds keep the entire decider on the stack — essential for `no_std` and compile-time proc-macro expansion.
7067const TWO_SAT_MAX_VARS: usize = 256;
7068const TWO_SAT_MAX_NODES: usize = TWO_SAT_MAX_VARS * 2;
7069const TWO_SAT_MAX_EDGES: usize = 2048;
7070
7071/// 2-SAT decision result.
7072#[must_use]
7073pub fn decide_two_sat(clauses: &[&[(u32, bool)]], num_vars: u32) -> bool {
7074 if (num_vars as usize) > TWO_SAT_MAX_VARS {
7075 return false;
7076 }
7077 let n = (num_vars as usize) * 2;
7078 // Node index: 2*var is positive literal, 2*var+1 is negated.
7079 let mut adj_starts = [0usize; TWO_SAT_MAX_NODES + 1];
7080 let mut adj_targets = [0usize; TWO_SAT_MAX_EDGES];
7081 // First pass: count out-degrees.
7082 for clause in clauses {
7083 if clause.len() > 2 || clause.is_empty() {
7084 return false;
7085 }
7086 if clause.len() == 1 {
7087 let (v, neg) = clause[0];
7088 let lit = lit_index(v, neg);
7089 let neg_lit = lit_index(v, !neg);
7090 // x ↔ (x ∨ x): ¬x → x (assignment forced)
7091 if neg_lit < n + 1 {
7092 adj_starts[neg_lit + 1] += 1;
7093 }
7094 let _ = lit;
7095 } else {
7096 let (a, an) = clause[0];
7097 let (b, bn) = clause[1];
7098 // ¬a → b, ¬b → a
7099 let na = lit_index(a, !an);
7100 let nb = lit_index(b, !bn);
7101 if na + 1 < n + 1 {
7102 adj_starts[na + 1] += 1;
7103 }
7104 if nb + 1 < n + 1 {
7105 adj_starts[nb + 1] += 1;
7106 }
7107 }
7108 }
7109 // Prefix-sum to get adjacency starts.
7110 let mut i = 1;
7111 while i <= n {
7112 adj_starts[i] += adj_starts[i - 1];
7113 i += 1;
7114 }
7115 let edge_count = adj_starts[n];
7116 if edge_count > TWO_SAT_MAX_EDGES {
7117 return false;
7118 }
7119 let mut fill = [0usize; TWO_SAT_MAX_NODES];
7120 for clause in clauses {
7121 if clause.len() == 1 {
7122 let (v, neg) = clause[0];
7123 let pos_lit = lit_index(v, neg);
7124 let neg_lit = lit_index(v, !neg);
7125 let slot = adj_starts[neg_lit] + fill[neg_lit];
7126 adj_targets[slot] = pos_lit;
7127 fill[neg_lit] += 1;
7128 } else {
7129 let (a, an) = clause[0];
7130 let (b, bn) = clause[1];
7131 let pa = lit_index(a, an);
7132 let na = lit_index(a, !an);
7133 let pb = lit_index(b, bn);
7134 let nb = lit_index(b, !bn);
7135 let s1 = adj_starts[na] + fill[na];
7136 adj_targets[s1] = pb;
7137 fill[na] += 1;
7138 let s2 = adj_starts[nb] + fill[nb];
7139 adj_targets[s2] = pa;
7140 fill[nb] += 1;
7141 }
7142 }
7143 // Iterative Tarjan's SCC.
7144 let mut index_counter: usize = 0;
7145 let mut indices = [usize::MAX; TWO_SAT_MAX_NODES];
7146 let mut lowlinks = [0usize; TWO_SAT_MAX_NODES];
7147 let mut on_stack = [false; TWO_SAT_MAX_NODES];
7148 let mut stack = [0usize; TWO_SAT_MAX_NODES];
7149 let mut stack_top: usize = 0;
7150 let mut scc_id = [usize::MAX; TWO_SAT_MAX_NODES];
7151 let mut scc_count: usize = 0;
7152 let mut call_stack = [(0usize, 0usize); TWO_SAT_MAX_NODES];
7153 let mut call_top: usize = 0;
7154 let mut v = 0;
7155 while v < n {
7156 if indices[v] == usize::MAX {
7157 call_stack[call_top] = (v, adj_starts[v]);
7158 call_top += 1;
7159 indices[v] = index_counter;
7160 lowlinks[v] = index_counter;
7161 index_counter += 1;
7162 stack[stack_top] = v;
7163 stack_top += 1;
7164 on_stack[v] = true;
7165 while call_top > 0 {
7166 let (u, mut next_edge) = call_stack[call_top - 1];
7167 let end_edge = adj_starts[u + 1];
7168 let mut advanced = false;
7169 while next_edge < end_edge {
7170 let w = adj_targets[next_edge];
7171 next_edge += 1;
7172 if indices[w] == usize::MAX {
7173 call_stack[call_top - 1] = (u, next_edge);
7174 indices[w] = index_counter;
7175 lowlinks[w] = index_counter;
7176 index_counter += 1;
7177 stack[stack_top] = w;
7178 stack_top += 1;
7179 on_stack[w] = true;
7180 call_stack[call_top] = (w, adj_starts[w]);
7181 call_top += 1;
7182 advanced = true;
7183 break;
7184 } else if on_stack[w] && indices[w] < lowlinks[u] {
7185 lowlinks[u] = indices[w];
7186 }
7187 }
7188 if !advanced {
7189 call_stack[call_top - 1] = (u, next_edge);
7190 if lowlinks[u] == indices[u] {
7191 loop {
7192 stack_top -= 1;
7193 let w = stack[stack_top];
7194 on_stack[w] = false;
7195 scc_id[w] = scc_count;
7196 if w == u {
7197 break;
7198 }
7199 }
7200 scc_count += 1;
7201 }
7202 call_top -= 1;
7203 if call_top > 0 {
7204 let (parent, _) = call_stack[call_top - 1];
7205 if lowlinks[u] < lowlinks[parent] {
7206 lowlinks[parent] = lowlinks[u];
7207 }
7208 }
7209 }
7210 }
7211 }
7212 v += 1;
7213 }
7214 // Unsatisfiable iff x and ¬x are in the same SCC for any variable.
7215 let mut var = 0u32;
7216 while var < num_vars {
7217 let pos = lit_index(var, false);
7218 let neg = lit_index(var, true);
7219 if scc_id[pos] == scc_id[neg] {
7220 return false;
7221 }
7222 var += 1;
7223 }
7224 true
7225}
7226
7227#[inline]
7228const fn lit_index(var: u32, negated: bool) -> usize {
7229 let base = (var as usize) * 2;
7230 if negated {
7231 base + 1
7232 } else {
7233 base
7234 }
7235}
7236
7237/// Horn-SAT decider via unit propagation. Returns `true` iff the clause
7238/// list is satisfiable.
7239/// Algorithm: start with all variables false. Repeatedly find a clause
7240/// whose negative literals are all satisfied but whose positive literal
7241/// is unassigned/false; set the positive literal true. Fail if a clause
7242/// with no positive literal has all its negatives satisfied.
7243/// Bounds (from `reduction:HornSatBound`): up to 256 variables.
7244const HORN_MAX_VARS: usize = 256;
7245
7246/// Horn-SAT decision result.
7247#[must_use]
7248pub fn decide_horn_sat(clauses: &[&[(u32, bool)]], num_vars: u32) -> bool {
7249 if (num_vars as usize) > HORN_MAX_VARS {
7250 return false;
7251 }
7252 let mut assignment = [false; HORN_MAX_VARS];
7253 let n = num_vars as usize;
7254 loop {
7255 let mut changed = false;
7256 for clause in clauses {
7257 // Count positive literals.
7258 let mut positive: Option<u32> = None;
7259 let mut positive_count = 0;
7260 for (_, negated) in clause.iter() {
7261 if !*negated {
7262 positive_count += 1;
7263 }
7264 }
7265 if positive_count > 1 {
7266 return false;
7267 }
7268 for (var, negated) in clause.iter() {
7269 if !*negated {
7270 positive = Some(*var);
7271 }
7272 }
7273 // Check whether all negative literals are satisfied (var=true).
7274 let mut all_neg_satisfied = true;
7275 for (var, negated) in clause.iter() {
7276 if *negated {
7277 let idx = *var as usize;
7278 if idx >= n {
7279 return false;
7280 }
7281 if !assignment[idx] {
7282 all_neg_satisfied = false;
7283 break;
7284 }
7285 }
7286 }
7287 if all_neg_satisfied {
7288 match positive {
7289 None => return false,
7290 Some(v) => {
7291 let idx = v as usize;
7292 if idx >= n {
7293 return false;
7294 }
7295 if !assignment[idx] {
7296 assignment[idx] = true;
7297 changed = true;
7298 }
7299 }
7300 }
7301 }
7302 }
7303 if !changed {
7304 break;
7305 }
7306 }
7307 // Final verification pass.
7308 for clause in clauses {
7309 let mut satisfied = false;
7310 for (var, negated) in clause.iter() {
7311 let idx = *var as usize;
7312 if idx >= n {
7313 return false;
7314 }
7315 let val = assignment[idx];
7316 if (*negated && !val) || (!*negated && val) {
7317 satisfied = true;
7318 break;
7319 }
7320 }
7321 if !satisfied {
7322 return false;
7323 }
7324 }
7325 true
7326}
7327
7328/// `BudgetSolvencyCheck` (preflightOrder 0): `thermodynamicBudget` must be
7329/// ≥ `bitsWidth(unitWittLevel) × ln 2` per `op:GS_7` / `op:OA_5`.
7330/// Takes the budget in `k_B T · ln 2` units and the target Witt level in
7331/// bit-width. Returns `Ok(())` if solvent, `Err` with the shape violation.
7332pub fn preflight_budget_solvency(budget_units: u64, witt_bits: u32) -> Result<(), ShapeViolation> {
7333 // Landauer bound: one bit requires k_B T · ln 2. Integer form.
7334 let minimum = witt_bits as u64;
7335 if budget_units >= minimum {
7336 Ok(())
7337 } else {
7338 Err(ShapeViolation {
7339 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
7340 constraint_iri:
7341 "https://uor.foundation/conformance/compileUnit_thermodynamicBudget_constraint",
7342 property_iri: "https://uor.foundation/reduction/thermodynamicBudget",
7343 expected_range: "http://www.w3.org/2001/XMLSchema#decimal",
7344 min_count: 1,
7345 max_count: 1,
7346 kind: ViolationKind::ValueCheck,
7347 })
7348 }
7349}
7350
7351/// v0.2.2 Phase F: upper bound on `CarryDepthObservable` depth arguments.
7352/// Matches target §4.5's Witt-level tower ceiling (W16384).
7353pub const WITT_MAX_BITS: u16 = 16_384;
7354
7355/// v0.2.2 Phase F: ASCII parser for a single unsigned decimal `u32`.
7356/// Returns 0 on malformed input; the caller's downstream comparison (`depth <= WITT_MAX_BITS`)
7357/// accepts 0 as the pass-through degenerate depth, so malformed input is rejected
7358/// by the enclosing feasibility check only if the parsed value exceeds the cap.
7359/// For stricter input discipline, the caller pre-validates `args_repr` at builder time.
7360#[must_use]
7361pub fn parse_u32(s: &str) -> u32 {
7362 let bytes = s.as_bytes();
7363 let mut out: u32 = 0;
7364 let mut i = 0;
7365 while i < bytes.len() {
7366 let b = bytes[i];
7367 if !b.is_ascii_digit() {
7368 return 0;
7369 }
7370 out = out.saturating_mul(10).saturating_add((b - b'0') as u32);
7371 i += 1;
7372 }
7373 out
7374}
7375
7376/// v0.2.2 Phase F: ASCII parser for a single unsigned decimal `u64`.
7377#[must_use]
7378pub fn parse_u64(s: &str) -> u64 {
7379 let bytes = s.as_bytes();
7380 let mut out: u64 = 0;
7381 let mut i = 0;
7382 while i < bytes.len() {
7383 let b = bytes[i];
7384 if !b.is_ascii_digit() {
7385 return 0;
7386 }
7387 out = out.saturating_mul(10).saturating_add((b - b'0') as u64);
7388 i += 1;
7389 }
7390 out
7391}
7392
7393/// v0.2.2 Phase F: parser for `"modulus|residue"` decimal pairs.
7394/// Split on the first ASCII `|`; ASCII-digit-parse each half via `parse_u64`.
7395#[must_use]
7396pub fn parse_u64_pair(s: &str) -> (u64, u64) {
7397 let bytes = s.as_bytes();
7398 let mut split = bytes.len();
7399 let mut i = 0;
7400 while i < bytes.len() {
7401 if bytes[i] == b'|' {
7402 split = i;
7403 break;
7404 }
7405 i += 1;
7406 }
7407 if split >= bytes.len() {
7408 return (0, 0);
7409 }
7410 let (left, right_with_pipe) = s.split_at(split);
7411 let (_, right) = right_with_pipe.split_at(1);
7412 (parse_u64(left), parse_u64(right))
7413}
7414
7415/// v0.2.2 Phase F / Phase 9: parse a decimal `u64` representing an
7416/// IEEE-754 bit pattern. The bit pattern is content-deterministic; call sites
7417/// project to `H::Decimal` via `DecimalTranscendental::from_bits`.
7418#[must_use]
7419pub fn parse_u64_bits_str(s: &str) -> u64 {
7420 parse_u64(s)
7421}
7422
7423/// v0.2.2 Phase F: dispatch a `ConstraintRef::Bound` arm on its `observable_iri`.
7424/// Canonical observables: `ValueModObservable`, `CarryDepthObservable`, `LandauerCost`.
7425/// Unknown IRIs are rejected so that an unaudited observable cannot be threaded
7426/// through the preflight surface silently.
7427fn check_bound_feasibility(
7428 observable_iri: &'static str,
7429 bound_shape_iri: &'static str,
7430 args_repr: &'static str,
7431) -> Result<(), ShapeViolation> {
7432 const VALUE_MOD_IRI: &str = "https://uor.foundation/observable/ValueModObservable";
7433 const CARRY_DEPTH_IRI: &str = "https://uor.foundation/observable/CarryDepthObservable";
7434 const LANDAUER_IRI: &str = "https://uor.foundation/observable/LandauerCost";
7435 let ok = if crate::enforcement::str_eq(observable_iri, VALUE_MOD_IRI) {
7436 let (modulus, residue) = parse_u64_pair(args_repr);
7437 modulus != 0 && residue < modulus
7438 } else if crate::enforcement::str_eq(observable_iri, CARRY_DEPTH_IRI) {
7439 let depth = parse_u32(args_repr);
7440 depth <= WITT_MAX_BITS as u32
7441 } else if crate::enforcement::str_eq(observable_iri, LANDAUER_IRI) {
7442 // Project the bit pattern to f64 (default-host) for the
7443 // finite/positive-nats sanity check. Polymorphic consumers
7444 // construct their own H::Decimal via DecimalTranscendental.
7445 let bits = parse_u64_bits_str(args_repr);
7446 let nats = <f64 as crate::DecimalTranscendental>::from_bits(bits);
7447 nats.is_finite() && nats > 0.0
7448 } else {
7449 false
7450 };
7451 if ok {
7452 Ok(())
7453 } else {
7454 Err(ShapeViolation {
7455 shape_iri: bound_shape_iri,
7456 constraint_iri: "https://uor.foundation/type/BoundConstraint",
7457 property_iri: observable_iri,
7458 expected_range: "https://uor.foundation/observable/BaseMetric",
7459 min_count: 1,
7460 max_count: 1,
7461 kind: ViolationKind::ValueCheck,
7462 })
7463 }
7464}
7465
7466/// `FeasibilityCheck`: verify the constraint system isn't trivially
7467/// infeasible. Workstream E (target §1.5 + §4.7, v0.2.2 closure):
7468/// the closed six-kind constraint set is validated by direct per-kind
7469/// satisfiability checks; any variant that fails is rejected here so
7470/// downstream resolvers never see an unsatisfiable constraint system.
7471/// v0.2.2 Phase F: the `Bound` arm dispatches on `observable_iri` to per-observable
7472/// checks via `check_bound_feasibility`; `Carry` and `Site` remain `Ok(())` by
7473/// documented design — their constraint semantics are structural invariants of
7474/// ring arithmetic and site-index bounds respectively, enforced by construction
7475/// rather than by runtime feasibility checks.
7476pub fn preflight_feasibility(constraints: &[ConstraintRef]) -> Result<(), ShapeViolation> {
7477 for c in constraints {
7478 // v0.2.2 Phase F: Bound dispatches to observable-specific checks with its
7479 // own bound_shape_iri; early-return with that shape on failure.
7480 if let ConstraintRef::Bound {
7481 observable_iri,
7482 bound_shape_iri,
7483 args_repr,
7484 } = c
7485 {
7486 check_bound_feasibility(observable_iri, bound_shape_iri, args_repr)?;
7487 continue;
7488 }
7489 let ok = match c {
7490 ConstraintRef::SatClauses { clauses, num_vars } => *num_vars != 0 || clauses.is_empty(),
7491 ConstraintRef::Residue { modulus, residue } => *modulus != 0 && *residue < *modulus,
7492 // Structural invariant of ring arithmetic — carries cannot contradict by construction.
7493 ConstraintRef::Carry { .. } => true,
7494 ConstraintRef::Depth { min, max } => min <= max,
7495 ConstraintRef::Hamming { bound } => *bound <= 32_768,
7496 // Structural invariant of site indexing — bounds enforced by SITE_COUNT typing.
7497 ConstraintRef::Site { .. } => true,
7498 ConstraintRef::Affine {
7499 coefficients,
7500 coefficient_count,
7501 bias,
7502 } => {
7503 let count = *coefficient_count as usize;
7504 if count == 0 {
7505 false
7506 } else {
7507 let mut ok_coeff = true;
7508 let mut idx = 0;
7509 while idx < count && idx < AFFINE_MAX_COEFFS {
7510 if coefficients[idx] == i64::MIN {
7511 ok_coeff = false;
7512 break;
7513 }
7514 idx += 1;
7515 }
7516 ok_coeff && is_affine_consistent(coefficients, *coefficient_count, *bias)
7517 }
7518 }
7519 // Handled above via early `if let`; unreachable here.
7520 ConstraintRef::Bound { .. } => true,
7521 ConstraintRef::Conjunction {
7522 conjuncts,
7523 conjunct_count,
7524 } => conjunction_all_sat(conjuncts, *conjunct_count),
7525 // ADR-057: Recurse defers to runtime ψ_1 NerveResolver
7526 // via the shape-IRI registry. Preflight feasibility
7527 // accepts on non-empty shape_iri; the registry lookup
7528 // happens at runtime admission.
7529 ConstraintRef::Recurse { shape_iri, .. } => !shape_iri.is_empty(),
7530 };
7531 if !ok {
7532 return Err(ShapeViolation {
7533 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
7534 constraint_iri:
7535 "https://uor.foundation/conformance/compileUnit_rootTerm_constraint",
7536 property_iri: "https://uor.foundation/reduction/rootTerm",
7537 expected_range: "https://uor.foundation/schema/Term",
7538 min_count: 1,
7539 max_count: 1,
7540 kind: ViolationKind::ValueCheck,
7541 });
7542 }
7543 }
7544 Ok(())
7545}
7546
7547/// `DispatchCoverageCheck`: verify the inhabitance dispatch table covers the input.
7548/// The table is exhaustive by construction: Rule 3 (IsResidualFragment) is the catch-all.
7549pub fn preflight_dispatch_coverage() -> Result<(), ShapeViolation> {
7550 // Always covered: IsResidualFragment catches everything not in 2-SAT/Horn.
7551 Ok(())
7552}
7553
7554/// `PackageCoherenceCheck`: verify each site's constraints are internally consistent.
7555pub fn preflight_package_coherence(constraints: &[ConstraintRef]) -> Result<(), ShapeViolation> {
7556 // Check residue constraints don't contradict (same modulus, different residues).
7557 let mut i = 0;
7558 while i < constraints.len() {
7559 if let ConstraintRef::Residue {
7560 modulus: m1,
7561 residue: r1,
7562 } = constraints[i]
7563 {
7564 let mut j = i + 1;
7565 while j < constraints.len() {
7566 if let ConstraintRef::Residue {
7567 modulus: m2,
7568 residue: r2,
7569 } = constraints[j]
7570 {
7571 if m1 == m2 && r1 != r2 {
7572 return Err(ShapeViolation {
7573 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
7574 constraint_iri:
7575 "https://uor.foundation/conformance/compileUnit_rootTerm_constraint",
7576 property_iri: "https://uor.foundation/reduction/rootTerm",
7577 expected_range: "https://uor.foundation/schema/Term",
7578 min_count: 1,
7579 max_count: 1,
7580 kind: ViolationKind::ValueCheck,
7581 });
7582 }
7583 }
7584 j += 1;
7585 }
7586 }
7587 i += 1;
7588 }
7589 Ok(())
7590}
7591
7592/// v0.2.2 Phase B: a-priori `UorTime` estimator for preflight timing.
7593/// Derives a content-deterministic upper bound on the `UorTime` a reduction
7594/// over `shape` at `witt_bits` will consume, without a physical clock. The
7595/// bound is `witt_bits × constraint_count` rewrite steps and the matching
7596/// Landauer nats at `ln 2` per step. Preflight compares this via
7597/// [`UorTime::min_wall_clock`](crate::enforcement::UorTime::min_wall_clock) against the policy's Nanos budget — no
7598/// physical clock is consulted.
7599#[must_use]
7600pub fn estimate_preflight_uor_time<T: ConstrainedTypeShape + ?Sized>(
7601 witt_bits: u16,
7602) -> crate::enforcement::UorTime {
7603 let steps = (witt_bits as u64).saturating_mul((T::CONSTRAINTS.len() as u64).max(1));
7604 let nats = (steps as f64) * core::f64::consts::LN_2;
7605 crate::enforcement::UorTime::new(crate::enforcement::LandauerBudget::new(nats), steps)
7606}
7607
7608/// `PreflightTiming`: timing-check preflight. v0.2.2 Phase B: parameterized over
7609/// a [`TimingPolicy`] carrying the Nanos budget and canonical `Calibration`.
7610/// The `expected` UorTime is derived a-priori from input shape via
7611/// [`estimate_preflight_uor_time`] — content-deterministic, no physical
7612/// clock consulted. Rejects when the Nanos lower bound exceeds the budget.
7613/// # Errors
7614/// Returns `ShapeViolation::ValueCheck` when the expected UorTime, converted
7615/// to Nanos under `P::CALIBRATION`, exceeds `P::PREFLIGHT_BUDGET_NS`.
7616#[allow(dead_code)]
7617pub(crate) const CANONICAL_PREFLIGHT_BUDGET_NS: u64 = 10000000;
7618pub fn preflight_timing<P: crate::enforcement::TimingPolicy>(
7619 expected: crate::enforcement::UorTime,
7620) -> Result<(), ShapeViolation> {
7621 let nanos = expected.min_wall_clock(P::CALIBRATION).as_u64();
7622 if nanos <= P::PREFLIGHT_BUDGET_NS {
7623 Ok(())
7624 } else {
7625 Err(ShapeViolation {
7626 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
7627 constraint_iri: "https://uor.foundation/reduction/PreflightTimingBound",
7628 property_iri: "https://uor.foundation/reduction/preflightBudgetNs",
7629 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
7630 min_count: 1,
7631 max_count: 1,
7632 kind: ViolationKind::ValueCheck,
7633 })
7634 }
7635}
7636
7637/// `RuntimeTiming`: runtime timing-check preflight. v0.2.2 Phase B: parameterized
7638/// over a [`TimingPolicy`] carrying the Nanos budget and canonical `Calibration`.
7639/// Identical comparison shape as [`preflight_timing`], against the runtime budget.
7640/// # Errors
7641/// Returns `ShapeViolation::ValueCheck` when the expected UorTime, converted
7642/// to Nanos under `P::CALIBRATION`, exceeds `P::RUNTIME_BUDGET_NS`.
7643#[allow(dead_code)]
7644pub(crate) const CANONICAL_RUNTIME_BUDGET_NS: u64 = 10000000;
7645pub fn runtime_timing<P: crate::enforcement::TimingPolicy>(
7646 expected: crate::enforcement::UorTime,
7647) -> Result<(), ShapeViolation> {
7648 let nanos = expected.min_wall_clock(P::CALIBRATION).as_u64();
7649 if nanos <= P::RUNTIME_BUDGET_NS {
7650 Ok(())
7651 } else {
7652 Err(ShapeViolation {
7653 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
7654 constraint_iri: "https://uor.foundation/reduction/RuntimeTimingBound",
7655 property_iri: "https://uor.foundation/reduction/runtimeBudgetNs",
7656 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
7657 min_count: 1,
7658 max_count: 1,
7659 kind: ViolationKind::ValueCheck,
7660 })
7661 }
7662}
7663
7664/// Reduction stage executor. Takes a classified input and runs the 7 stages
7665/// in order, producing a `StageOutcome` on success.
7666#[derive(Debug, Clone, Copy)]
7667pub struct StageOutcome {
7668 /// Witt level the compile unit was resolved at.
7669 pub witt_bits: u16,
7670 /// Fragment classification decided at `stage_resolve`.
7671 pub fragment: FragmentKind,
7672 /// Whether the input is satisfiable (carrier non-empty).
7673 pub satisfiable: bool,
7674}
7675
7676/// Run the 7 reduction stages on a constrained-type input.
7677///
7678/// v0.2.2 T6.14: the `unit_address` field was removed. The substrate-
7679/// computed `ContentAddress` lives on `Grounded` and is derived at the
7680/// caller from the `H: Hasher` output buffer, not inside this stage
7681/// executor.
7682///
7683/// # Errors
7684///
7685/// Returns `PipelineFailure` with the `reduction:PipelineFailureReason` IRI
7686/// of whichever stage rejected the input.
7687pub fn run_reduction_stages<T: ConstrainedTypeShape + ?Sized>(
7688 witt_bits: u16,
7689) -> Result<StageOutcome, PipelineFailure> {
7690 // Stage 0 (initialization): content-addressed unit-id is computed by
7691 // the caller via the consumer-supplied substrate Hasher; nothing to
7692 // do here.
7693 // Stage 1 (declare): no-op; declarations already captured by the derive macro.
7694 // Stage 2 (factorize): no-op; ring factorization is not required for Boolean fragments.
7695 // Stage 3 (resolve): fragment classification.
7696 let fragment = fragment_classify(T::CONSTRAINTS);
7697 // Stage 4 (attest): run the decider associated with the fragment.
7698 let satisfiable = match fragment {
7699 FragmentKind::TwoSat => {
7700 let mut sat = true;
7701 for c in T::CONSTRAINTS {
7702 if let ConstraintRef::SatClauses { clauses, num_vars } = c {
7703 sat = decide_two_sat(clauses, *num_vars);
7704 break;
7705 }
7706 }
7707 sat
7708 }
7709 FragmentKind::Horn => {
7710 let mut sat = true;
7711 for c in T::CONSTRAINTS {
7712 if let ConstraintRef::SatClauses { clauses, num_vars } = c {
7713 sat = decide_horn_sat(clauses, *num_vars);
7714 break;
7715 }
7716 }
7717 sat
7718 }
7719 FragmentKind::Residual => {
7720 // No polynomial decider available. Residual constraint systems are
7721 // treated as vacuously satisfiable when they carry no SatClauses —
7722 // pure residue/hamming/etc. inputs always have some value satisfying
7723 // at least the trivial case. Non-trivial residuals yield
7724 // ConvergenceStall at stage_convergence below.
7725 let mut has_sat_clauses = false;
7726 for c in T::CONSTRAINTS {
7727 if matches!(c, ConstraintRef::SatClauses { .. }) {
7728 has_sat_clauses = true;
7729 break;
7730 }
7731 }
7732 !has_sat_clauses
7733 }
7734 };
7735 if matches!(fragment, FragmentKind::Residual) && !satisfiable {
7736 return Err(PipelineFailure::ConvergenceStall {
7737 stage_iri: "https://uor.foundation/reduction/stage_convergence",
7738 angle_milliradians: 0,
7739 });
7740 }
7741 // Stage 5 (extract): ConstrainedTypeShape inputs carry no term AST, so no
7742 // bindings flow through this path. CompileUnit-bearing callers retrieve the
7743 // declared bindings directly via `unit.bindings()` (Phase H1); runtime
7744 // `BindingsTable` materialization is not possible because `BindingsTable::entries`
7745 // is `&'static [BindingEntry]` by contract (compile-time-constructed catalogs
7746 // are the sole source of sorted-address binary-search tables).
7747 // Stage 6 (convergence): verify fixpoint reached. Trivially true for
7748 // classified fragments.
7749 Ok(StageOutcome {
7750 witt_bits,
7751 fragment,
7752 satisfiable,
7753 })
7754}
7755
7756/// Run the `TowerCompletenessResolver` pipeline on a `ConstrainedTypeShape`
7757/// input at the requested Witt level. Emits a `LiftChainCertificate` on
7758/// success or a generic `ImpossibilityWitness` on failure.
7759/// # Errors
7760/// Returns `GenericImpossibilityWitness` when the input is unsatisfiable or
7761/// when any preflight / reduction stage rejects it.
7762pub fn run_tower_completeness<
7763 T: ConstrainedTypeShape + ?Sized,
7764 H: crate::enforcement::Hasher<FP_MAX>,
7765 const FP_MAX: usize,
7766>(
7767 _input: &T,
7768 level: WittLevel,
7769) -> Result<Validated<LiftChainCertificate<FP_MAX>>, GenericImpossibilityWitness> {
7770 let witt_bits = level.witt_length() as u16;
7771 preflight_budget_solvency(witt_bits as u64, witt_bits as u32)
7772 .map_err(|_| GenericImpossibilityWitness::default())?;
7773 preflight_feasibility(T::CONSTRAINTS).map_err(|_| GenericImpossibilityWitness::default())?;
7774 preflight_package_coherence(T::CONSTRAINTS)
7775 .map_err(|_| GenericImpossibilityWitness::default())?;
7776 preflight_dispatch_coverage().map_err(|_| GenericImpossibilityWitness::default())?;
7777 let expected = estimate_preflight_uor_time::<T>(witt_bits);
7778 preflight_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
7779 .map_err(|_| GenericImpossibilityWitness::default())?;
7780 runtime_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
7781 .map_err(|_| GenericImpossibilityWitness::default())?;
7782 let outcome =
7783 run_reduction_stages::<T>(witt_bits).map_err(|_| GenericImpossibilityWitness::default())?;
7784 if outcome.satisfiable {
7785 // v0.2.2 T6.7: thread H: Hasher through fold_unit_digest to compute
7786 // a real substrate fingerprint. Witt level + budget=0 (no CompileUnit).
7787 let mut hasher = H::initial();
7788 hasher = crate::enforcement::fold_unit_digest(
7789 hasher,
7790 outcome.witt_bits,
7791 0,
7792 T::IRI,
7793 T::SITE_COUNT,
7794 T::CONSTRAINTS,
7795 crate::enforcement::CertificateKind::TowerCompleteness,
7796 );
7797 let buffer = hasher.finalize();
7798 let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
7799 let cert = LiftChainCertificate::with_level_and_fingerprint_const(outcome.witt_bits, fp);
7800 Ok(Validated::new(cert))
7801 } else {
7802 Err(GenericImpossibilityWitness::default())
7803 }
7804}
7805
7806/// Workstream F (target ontology: `resolver:IncrementalCompletenessResolver`):
7807/// sealed `SpectralSequencePage` kernel type recording one step of the
7808/// `Q_n → Q_{n+1}` spectral-sequence walk. Each page carries its index,
7809/// the from/to Witt bit widths, and the differential-vanished flag
7810/// (true ⇒ page is trivial; false ⇒ obstruction present with class IRI).
7811#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7812pub struct SpectralSequencePage {
7813 page_index: u32,
7814 source_bits: u16,
7815 target_bits: u16,
7816 differential_vanished: bool,
7817 obstruction_class_iri: &'static str,
7818 _sealed: (),
7819}
7820
7821impl SpectralSequencePage {
7822 /// Crate-internal constructor. Minted only by the incremental walker.
7823 #[inline]
7824 #[must_use]
7825 pub(crate) const fn from_parts(
7826 page_index: u32,
7827 source_bits: u16,
7828 target_bits: u16,
7829 differential_vanished: bool,
7830 obstruction_class_iri: &'static str,
7831 ) -> Self {
7832 Self {
7833 page_index,
7834 source_bits,
7835 target_bits,
7836 differential_vanished,
7837 obstruction_class_iri,
7838 _sealed: (),
7839 }
7840 }
7841
7842 /// Page index (0, 1, 2, …) along the spectral-sequence walk.
7843 #[inline]
7844 #[must_use]
7845 pub const fn page_index(&self) -> u32 {
7846 self.page_index
7847 }
7848
7849 /// Witt bit width at the page's source level.
7850 #[inline]
7851 #[must_use]
7852 pub const fn source_bits(&self) -> u16 {
7853 self.source_bits
7854 }
7855
7856 /// Witt bit width at the page's target level.
7857 #[inline]
7858 #[must_use]
7859 pub const fn target_bits(&self) -> u16 {
7860 self.target_bits
7861 }
7862
7863 /// True iff the page's differential vanishes (no obstruction).
7864 #[inline]
7865 #[must_use]
7866 pub const fn differential_vanished(&self) -> bool {
7867 self.differential_vanished
7868 }
7869
7870 /// Obstruction class IRI when the differential is non-trivial;
7871 /// empty string when the page is trivial.
7872 #[inline]
7873 #[must_use]
7874 pub const fn obstruction_class_iri(&self) -> &'static str {
7875 self.obstruction_class_iri
7876 }
7877}
7878
7879/// Run the `IncrementalCompletenessResolver` (spectral-sequence walk)
7880/// at `target_level`.
7881///
7882/// Per `spec/src/namespaces/resolver.rs` (`IncrementalCompletenessResolver`),
7883/// the walk proceeds without re-running the full ψ-pipeline from
7884/// scratch. Workstream F (v0.2.2 closure) implements the canonical page
7885/// structure: iterate each `Q_n → Q_{n+1}` step from `W8` up to
7886/// `target_level`, compute each page's differential via
7887/// `run_reduction_stages` at the higher level, and record the
7888/// `SpectralSequencePage`. A non-vanishing differential halts with a
7889/// `GenericImpossibilityWitness` whose obstruction-class IRI is
7890/// `https://uor.foundation/type/LiftObstruction`. All trivial pages
7891/// produce a `LiftChainCertificate` stamped with
7892/// `CertificateKind::IncrementalCompleteness`, discriminable from
7893/// `run_tower_completeness`'s certificate by the kind byte.
7894///
7895/// # Errors
7896///
7897/// Returns `GenericImpossibilityWitness` when any page's differential
7898/// does not vanish, or when preflight checks reject the input.
7899pub fn run_incremental_completeness<
7900 T: ConstrainedTypeShape + ?Sized,
7901 H: crate::enforcement::Hasher<FP_MAX>,
7902 const FP_MAX: usize,
7903>(
7904 _input: &T,
7905 target_level: WittLevel,
7906) -> Result<Validated<LiftChainCertificate<FP_MAX>>, GenericImpossibilityWitness> {
7907 let target_bits = target_level.witt_length() as u16;
7908 preflight_budget_solvency(target_bits as u64, target_bits as u32)
7909 .map_err(|_| GenericImpossibilityWitness::default())?;
7910 preflight_feasibility(T::CONSTRAINTS).map_err(|_| GenericImpossibilityWitness::default())?;
7911 preflight_package_coherence(T::CONSTRAINTS)
7912 .map_err(|_| GenericImpossibilityWitness::default())?;
7913 preflight_dispatch_coverage().map_err(|_| GenericImpossibilityWitness::default())?;
7914 let expected = estimate_preflight_uor_time::<T>(target_bits);
7915 preflight_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
7916 .map_err(|_| GenericImpossibilityWitness::default())?;
7917 runtime_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
7918 .map_err(|_| GenericImpossibilityWitness::default())?;
7919
7920 // v0.2.2 Phase H4: Betti-driven spectral-sequence walk. At each page, compute
7921 // the constraint-nerve Betti tuple (via primitive_simplicial_nerve_betti)
7922 // and run reduction at both source and target levels. The differential at
7923 // page r with bidegree (p, q) vanishes iff the bidegree-q projection
7924 // `betti[q]` is unchanged between source and target AND both reductions
7925 // are satisfiable at their levels. A mismatch in any bidegree or a
7926 // non-satisfiable reduction produces a non-trivial differential →
7927 // `LiftObstruction` halt with `GenericImpossibilityWitness`.
7928 //
7929 // Betti-threading also produces content-distinct fingerprints for distinct
7930 // constraint topologies: two input shapes with different Betti profiles will
7931 // produce different certs even if both satisfy at every level.
7932 let betti = crate::enforcement::primitive_simplicial_nerve_betti::<T>()?;
7933 let mut page_index: u32 = 0;
7934 let mut from_bits: u16 = 8;
7935 let mut pages_hasher = H::initial();
7936 while from_bits < target_bits {
7937 let to_bits = if from_bits + 8 > target_bits {
7938 target_bits
7939 } else {
7940 from_bits + 8
7941 };
7942 // Reduce at source and target; both must be satisfiable for the
7943 // differential to have a chance of vanishing.
7944 let outcome_source = run_reduction_stages::<T>(from_bits)
7945 .map_err(|_| GenericImpossibilityWitness::default())?;
7946 let outcome_target = run_reduction_stages::<T>(to_bits)
7947 .map_err(|_| GenericImpossibilityWitness::default())?;
7948 // bidegree q = page_index + 1 (first non-trivial homological degree per page).
7949 let q: usize = ((page_index as usize) + 1).min(crate::enforcement::MAX_BETTI_DIMENSION - 1);
7950 let betti_q = betti[q];
7951 // Differential vanishes iff source ≡ target in Betti bidegree q
7952 // AND both reduction levels are satisfiable. Betti is shape-invariant
7953 // (level-independent); the bidegree check is trivially equal, but the
7954 // satisfiability conjunction captures the level-specific obstruction.
7955 let differential_vanishes = outcome_source.satisfiable && outcome_target.satisfiable;
7956 let page = SpectralSequencePage::from_parts(
7957 page_index,
7958 from_bits,
7959 to_bits,
7960 differential_vanishes,
7961 if differential_vanishes {
7962 ""
7963 } else {
7964 "https://uor.foundation/type/LiftObstruction"
7965 },
7966 );
7967 if !page.differential_vanished() {
7968 return Err(GenericImpossibilityWitness::default());
7969 }
7970 // Fold the per-page Betti/satisfiability contribution so distinct
7971 // constraint shapes yield distinct incremental-completeness certs.
7972 pages_hasher = pages_hasher.fold_bytes(&page_index.to_be_bytes());
7973 pages_hasher = pages_hasher.fold_bytes(&from_bits.to_be_bytes());
7974 pages_hasher = pages_hasher.fold_bytes(&to_bits.to_be_bytes());
7975 pages_hasher = pages_hasher.fold_bytes(&betti_q.to_be_bytes());
7976 page_index += 1;
7977 from_bits = to_bits;
7978 }
7979 // The accumulated pages_hasher is currently unused in the final fold;
7980 // the Betti primitive's full tuple is folded below via fold_betti_tuple
7981 // to keep the cert content-addressed over the whole spectral walk.
7982 let _ = pages_hasher;
7983
7984 // Final reduction at the target level — the walk converges when
7985 // every page's differential has vanished; this guarantees the
7986 // target-level outcome is satisfiable.
7987 let outcome = run_reduction_stages::<T>(target_bits)
7988 .map_err(|_| GenericImpossibilityWitness::default())?;
7989 if !outcome.satisfiable {
7990 return Err(GenericImpossibilityWitness::default());
7991 }
7992 // v0.2.2 Phase H4: fold the Betti tuple into the cert alongside the
7993 // canonical unit digest so distinct constraint topologies produce distinct
7994 // incremental-completeness fingerprints.
7995 let mut hasher = H::initial();
7996 hasher = crate::enforcement::fold_betti_tuple(hasher, &betti);
7997 hasher = crate::enforcement::fold_unit_digest(
7998 hasher,
7999 outcome.witt_bits,
8000 page_index as u64,
8001 T::IRI,
8002 T::SITE_COUNT,
8003 T::CONSTRAINTS,
8004 crate::enforcement::CertificateKind::IncrementalCompleteness,
8005 );
8006 let buffer = hasher.finalize();
8007 let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
8008 let cert = LiftChainCertificate::with_level_and_fingerprint_const(outcome.witt_bits, fp);
8009 Ok(Validated::new(cert))
8010}
8011
8012/// Run the `GroundingAwareResolver` on a `CompileUnit` input at `level`,
8013/// exploiting `state:GroundedContext` bindings for O(1) resolution per
8014/// `op:GS_5`.
8015///
8016/// v0.2.2 Phase H2: walks `unit.root_term()` enumerating every
8017/// `Term::Variable { name_index }` and resolves each via linear search
8018/// over `unit.bindings()`. Unresolved variables (declared in the term AST
8019/// but absent from the bindings slice) trigger a `GenericImpossibilityWitness`
8020/// corresponding to `SC5_UNBOUND_VARIABLE`. Resolved bindings are folded
8021/// into the fingerprint via `primitive_session_binding_signature` so the
8022/// cert content-addresses the full grounding context, not just the unit
8023/// shape — distinct binding sets yield distinct fingerprints.
8024///
8025/// # Errors
8026///
8027/// Returns `GenericImpossibilityWitness` on grounding failure: unresolved
8028/// variables, or any variable reference whose name index is absent from
8029/// `unit.bindings()`.
8030pub fn run_grounding_aware<
8031 const INLINE_BYTES: usize,
8032 H: crate::enforcement::Hasher<FP_MAX>,
8033 const FP_MAX: usize,
8034>(
8035 unit: &CompileUnit<'_, INLINE_BYTES>,
8036 level: WittLevel,
8037) -> Result<Validated<GroundingCertificate<FP_MAX>>, GenericImpossibilityWitness> {
8038 let witt_bits = level.witt_length() as u16;
8039 // v0.2.2 Phase H2: walk root_term enumerating Term::Variable name_indices,
8040 // linear-search unit.bindings() for each, reject unresolved variables.
8041 let root_term = unit.root_term();
8042 let bindings = unit.bindings();
8043 let mut ti = 0;
8044 while ti < root_term.len() {
8045 if let crate::enforcement::Term::Variable { name_index } = root_term[ti] {
8046 let mut found = false;
8047 let mut bi = 0;
8048 while bi < bindings.len() {
8049 if bindings[bi].name_index == name_index {
8050 found = true;
8051 break;
8052 }
8053 bi += 1;
8054 }
8055 if !found {
8056 // SC_5 violation: variable referenced but no matching binding.
8057 return Err(GenericImpossibilityWitness::default());
8058 }
8059 }
8060 ti += 1;
8061 }
8062 // Fold: witt_bits / thermodynamic_budget / result_type_iri / session_signature / Grounding kind.
8063 let mut hasher = H::initial();
8064 hasher = hasher.fold_bytes(&witt_bits.to_be_bytes());
8065 hasher = hasher.fold_bytes(&unit.thermodynamic_budget().to_be_bytes());
8066 hasher = hasher.fold_bytes(unit.result_type_iri().as_bytes());
8067 hasher = hasher.fold_byte(0);
8068 let (binding_count, fold_addr) =
8069 crate::enforcement::primitive_session_binding_signature(bindings);
8070 hasher = crate::enforcement::fold_session_signature(hasher, binding_count, fold_addr);
8071 hasher = hasher.fold_byte(crate::enforcement::certificate_kind_discriminant(
8072 crate::enforcement::CertificateKind::Grounding,
8073 ));
8074 let buffer = hasher.finalize();
8075 let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
8076 let cert = GroundingCertificate::with_level_and_fingerprint_const(witt_bits, fp);
8077 Ok(Validated::new(cert))
8078}
8079
8080/// Run the `InhabitanceResolver` dispatch on a `ConstrainedTypeShape`
8081/// input at `level`.
8082///
8083/// Routes to the 2-SAT / Horn-SAT / residual decider via
8084/// `predicate:InhabitanceDispatchTable` rules (ordered by priority).
8085///
8086/// # Errors
8087///
8088/// Returns `InhabitanceImpossibilityWitness` when the input is unsatisfiable.
8089pub fn run_inhabitance<
8090 T: ConstrainedTypeShape + ?Sized,
8091 H: crate::enforcement::Hasher<FP_MAX>,
8092 const FP_MAX: usize,
8093>(
8094 _input: &T,
8095 level: WittLevel,
8096) -> Result<Validated<InhabitanceCertificate<FP_MAX>>, InhabitanceImpossibilityWitness> {
8097 let witt_bits = level.witt_length() as u16;
8098 preflight_budget_solvency(witt_bits as u64, witt_bits as u32)
8099 .map_err(|_| InhabitanceImpossibilityWitness::default())?;
8100 preflight_feasibility(T::CONSTRAINTS)
8101 .map_err(|_| InhabitanceImpossibilityWitness::default())?;
8102 preflight_package_coherence(T::CONSTRAINTS)
8103 .map_err(|_| InhabitanceImpossibilityWitness::default())?;
8104 preflight_dispatch_coverage().map_err(|_| InhabitanceImpossibilityWitness::default())?;
8105 let expected = estimate_preflight_uor_time::<T>(witt_bits);
8106 preflight_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
8107 .map_err(|_| InhabitanceImpossibilityWitness::default())?;
8108 runtime_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
8109 .map_err(|_| InhabitanceImpossibilityWitness::default())?;
8110 let outcome = run_reduction_stages::<T>(witt_bits)
8111 .map_err(|_| InhabitanceImpossibilityWitness::default())?;
8112 if outcome.satisfiable {
8113 // v0.2.2 T6.7: thread H: Hasher through fold_unit_digest.
8114 let mut hasher = H::initial();
8115 hasher = crate::enforcement::fold_unit_digest(
8116 hasher,
8117 outcome.witt_bits,
8118 0,
8119 T::IRI,
8120 T::SITE_COUNT,
8121 T::CONSTRAINTS,
8122 crate::enforcement::CertificateKind::Inhabitance,
8123 );
8124 let buffer = hasher.finalize();
8125 let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
8126 let cert = InhabitanceCertificate::with_level_and_fingerprint_const(outcome.witt_bits, fp);
8127 Ok(Validated::new(cert))
8128 } else {
8129 Err(InhabitanceImpossibilityWitness::default())
8130 }
8131}
8132
8133/// v0.2.2 W14: the single typed pipeline entry point producing `Grounded<T>`
8134/// from a validated `CompileUnit`. The caller declares the expected shape `T`;
8135/// the pipeline verifies the unit's root term produces a value of that shape
8136/// and returns `Grounded<T>` on success or a typed `PipelineFailure`.
8137/// Replaces the v0.2.1 `run_pipeline(&datum, level: u8)` form whose bare
8138/// integer level argument was never type-safe.
8139/// # Errors
8140/// Returns `PipelineFailure` on preflight, reduction, or shape-mismatch failure.
8141/// # Example
8142/// ```no_run
8143/// use uor_foundation::enforcement::{
8144/// CompileUnitBuilder, ConstrainedTypeInput, Term,
8145/// };
8146/// use uor_foundation::pipeline::run;
8147/// use uor_foundation::{VerificationDomain, WittLevel};
8148/// # struct Fnv1aHasher16; // downstream substrate; see foundation/examples/custom_hasher_substrate.rs
8149/// # impl uor_foundation::enforcement::Hasher for Fnv1aHasher16 {
8150/// # const OUTPUT_BYTES: usize = 16;
8151/// # fn initial() -> Self { Self }
8152/// # fn fold_byte(self, _: u8) -> Self { self }
8153/// # fn finalize(self) -> [u8; 32] { [0; 32] }
8154/// # }
8155/// // ADR-060: `Term`/`CompileUnit`/`Grounded` carry an `INLINE_BYTES`
8156/// // const-generic the application derives from its `HostBounds`; this
8157/// // example fixes a concrete width and threads it through `run`'s 4th
8158/// // const argument.
8159/// const N: usize = 32;
8160/// let terms: [Term<'static, N>; 1] = [uor_foundation::pipeline::literal_u64(1, WittLevel::W8)];
8161/// let domains: [VerificationDomain; 1] = [VerificationDomain::Enumerative];
8162/// let unit = CompileUnitBuilder::<N>::new()
8163/// .root_term(&terms)
8164/// .witt_level_ceiling(WittLevel::W32)
8165/// .thermodynamic_budget(1024)
8166/// .target_domains(&domains)
8167/// .result_type::<ConstrainedTypeInput>()
8168/// .validate()
8169/// .expect("unit well-formed");
8170/// let grounded = run::<ConstrainedTypeInput, _, Fnv1aHasher16, N, 32>(unit)
8171/// .expect("pipeline admits");
8172/// # let _ = grounded;
8173/// ```
8174pub fn run<T, P, H, const INLINE_BYTES: usize, const FP_MAX: usize>(
8175 unit: Validated<CompileUnit<'_, INLINE_BYTES>, P>,
8176) -> Result<Grounded<'static, T, INLINE_BYTES, FP_MAX>, PipelineFailure>
8177where
8178 T: ConstrainedTypeShape + crate::enforcement::GroundedShape,
8179 P: crate::enforcement::ValidationPhase,
8180 H: crate::enforcement::Hasher<FP_MAX>,
8181{
8182 let witt_bits = unit.inner().witt_level().witt_length() as u16;
8183 let budget = unit.inner().thermodynamic_budget();
8184 // v0.2.2 T6.11: ShapeMismatch detection. The unit declares its
8185 // result_type_iri at validation time; the caller's `T::IRI` must match.
8186 let unit_iri = unit.inner().result_type_iri();
8187 if !crate::enforcement::str_eq(unit_iri, T::IRI) {
8188 return Err(PipelineFailure::ShapeMismatch {
8189 expected: T::IRI,
8190 got: unit_iri,
8191 });
8192 }
8193 // Preflights. Each maps its ShapeViolation into a PipelineFailure.
8194 preflight_budget_solvency(witt_bits as u64, witt_bits as u32)
8195 .map_err(|report| PipelineFailure::ShapeViolation { report })?;
8196 preflight_feasibility(T::CONSTRAINTS)
8197 .map_err(|report| PipelineFailure::ShapeViolation { report })?;
8198 preflight_package_coherence(T::CONSTRAINTS)
8199 .map_err(|report| PipelineFailure::ShapeViolation { report })?;
8200 preflight_dispatch_coverage().map_err(|report| PipelineFailure::ShapeViolation { report })?;
8201 let expected = estimate_preflight_uor_time::<T>(witt_bits);
8202 preflight_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
8203 .map_err(|report| PipelineFailure::ShapeViolation { report })?;
8204 runtime_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
8205 .map_err(|report| PipelineFailure::ShapeViolation { report })?;
8206 // v0.2.2 T5 C1: actually call run_reduction_stages and propagate its
8207 // failure. The previous v0.2.2 path skipped this call entirely,
8208 // returning a degenerate Grounded with ContentAddress::zero(). The
8209 // typed `run` entry point now mirrors `run_pipeline`'s reduction-stage
8210 // sequence.
8211 let outcome = run_reduction_stages::<T>(witt_bits)?;
8212 if !outcome.satisfiable {
8213 return Err(PipelineFailure::ContradictionDetected {
8214 at_step: 0,
8215 trace_iri: "https://uor.foundation/trace/InhabitanceSearchTrace",
8216 });
8217 }
8218 // v0.2.2 T5 C3.f: thread the consumer-supplied substrate Hasher through
8219 // the canonical CompileUnit byte layout to compute the parametric
8220 // content fingerprint.
8221 let mut hasher = H::initial();
8222 hasher = crate::enforcement::fold_unit_digest(
8223 hasher,
8224 witt_bits,
8225 budget,
8226 T::IRI,
8227 T::SITE_COUNT,
8228 T::CONSTRAINTS,
8229 crate::enforcement::CertificateKind::Grounding,
8230 );
8231 let buffer = hasher.finalize();
8232 let content_fingerprint =
8233 crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
8234 let unit_address = crate::enforcement::unit_address_from_buffer(&buffer);
8235 let grounding = Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
8236 witt_bits,
8237 content_fingerprint,
8238 ));
8239 let bindings = empty_bindings_table();
8240 Ok(Grounded::<T, INLINE_BYTES, FP_MAX>::new_internal(
8241 grounding,
8242 bindings,
8243 outcome.witt_bits,
8244 unit_address,
8245 content_fingerprint,
8246 ))
8247}
8248
8249/// Construct an empty `BindingsTable`.
8250/// v0.2.2 T6.9: the empty slice is vacuously sorted, so direct struct
8251/// construction is sound. Public callers with non-empty entries go
8252/// through `BindingsTable::try_new` (validating).
8253/// # Driver contract
8254/// Every pipeline driver (`run`, `run_const`, `run_parallel`, `run_stream`,
8255/// `run_interactive`, `run_inhabitance`) mints `Grounded<T>` with this
8256/// empty table today: runtime-dynamic binding materialization in
8257/// `Grounded<T>` requires widening `BindingsTable.entries: &'static [...]`
8258/// to a non-`'static` carrier (a separate architectural change).
8259/// Downstream that needs a compile-time binding catalog uses the pattern
8260/// shown in `foundation/examples/static_bindings_catalog.rs`:
8261/// `Binding::to_binding_entry()` (const-fn) + `BindingsTable::try_new(&[...])`.
8262#[must_use]
8263pub const fn empty_bindings_table() -> BindingsTable {
8264 BindingsTable { entries: &[] }
8265}
8266
8267// Suppress warning: BindingEntry is re-exported via use but not used in
8268// this module directly; it's part of the public pipeline surface.
8269#[allow(dead_code)]
8270const _BINDING_ENTRY_REF: Option<BindingEntry> = None;
8271// Same for CompletenessCertificate — the pipeline does not mint this subclass
8272// directly; Phase D resolvers emit the canonical `GroundingCertificate` carrier
8273// and cert-subclass lifts happen in substrate-specific consumers.
8274#[allow(dead_code)]
8275const _COMPLETENESS_CERT_REF: Option<CompletenessCertificate> = None;
8276
8277/// v0.2.2 Phase F / T2.7: parallel-declaration compile unit. Carries the
8278/// declared site partition cardinality plus (Phase A widening) the raw
8279/// partition slice and disjointness-witness IRI from the builder —
8280/// previously these were discarded at validate-time by a shadowed
8281/// enforcement-local `ParallelDeclaration` that nothing consumed.
8282/// v0.2.2 T6.11: also carries `result_type_iri` for ShapeMismatch detection.
8283#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8284pub struct ParallelDeclaration<'a> {
8285 payload: u64,
8286 result_type_iri: &'static str,
8287 /// v0.2.2 Phase A: raw site-partition slice retained from the builder.
8288 /// Empty slice for declarations built via the site-count-only constructor.
8289 site_partition: &'a [u32],
8290 /// v0.2.2 Phase A: disjointness-witness IRI retained from the builder.
8291 /// Empty string for declarations built via the site-count-only constructor.
8292 disjointness_witness: &'a str,
8293 _sealed: (),
8294}
8295
8296impl<'a> ParallelDeclaration<'a> {
8297 /// v0.2.2 Phase H3: construct a parallel declaration carrying the full
8298 /// partition slice and disjointness-witness IRI from the builder.
8299 /// This is the sole public constructor; the v0.2.2 Phase A site-count-only
8300 /// `new::<T>(site_count)` form was deleted in Phase H3 under the "no
8301 /// compatibility" discipline — every caller supplies a real partition.
8302 #[inline]
8303 #[must_use]
8304 pub const fn new_with_partition<T: ConstrainedTypeShape>(
8305 site_partition: &'a [u32],
8306 disjointness_witness: &'a str,
8307 ) -> Self {
8308 Self {
8309 payload: site_partition.len() as u64,
8310 result_type_iri: T::IRI,
8311 site_partition,
8312 disjointness_witness,
8313 _sealed: (),
8314 }
8315 }
8316
8317 /// Returns the declared site partition cardinality.
8318 #[inline]
8319 #[must_use]
8320 pub const fn site_count(&self) -> u64 {
8321 self.payload
8322 }
8323
8324 /// v0.2.2 T6.11: returns the result-type IRI.
8325 #[inline]
8326 #[must_use]
8327 pub const fn result_type_iri(&self) -> &'static str {
8328 self.result_type_iri
8329 }
8330
8331 /// v0.2.2 Phase A: returns the raw site-partition slice.
8332 #[inline]
8333 #[must_use]
8334 pub const fn site_partition(&self) -> &'a [u32] {
8335 self.site_partition
8336 }
8337
8338 /// v0.2.2 Phase A: returns the disjointness-witness IRI.
8339 #[inline]
8340 #[must_use]
8341 pub const fn disjointness_witness(&self) -> &'a str {
8342 self.disjointness_witness
8343 }
8344}
8345
8346/// v0.2.2 Phase F / T2.7: stream-declaration compile unit. Carries a
8347/// payload field encoding the productivity-witness countdown.
8348/// v0.2.2 T6.11: also carries `result_type_iri` for ShapeMismatch detection.
8349/// v0.2.2 Phase A: also retains the builder's seed/step term slices and
8350/// the productivity-witness IRI so stream resolvers can walk declared
8351/// structure. Distinct from the enforcement-local `StreamDeclaration`
8352/// which records only the `StreamShape` validation surface.
8353/// Note: `Hash` is not derived because `Term` does not implement `Hash`;
8354/// downstream code that needs deterministic hashing should fold through
8355/// the substrate `Hasher` via the pipeline's `fold_stream_digest`.
8356#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8357pub struct StreamDeclaration<'a, const INLINE_BYTES: usize> {
8358 payload: u64,
8359 result_type_iri: &'static str,
8360 /// v0.2.2 Phase A: stream seed term slice retained from the builder.
8361 seed: &'a [Term<'a, INLINE_BYTES>],
8362 /// v0.2.2 Phase A: stream step term slice retained from the builder.
8363 step: &'a [Term<'a, INLINE_BYTES>],
8364 /// v0.2.2 Phase A: productivity-witness IRI retained from the builder.
8365 productivity_witness: &'a str,
8366 _sealed: (),
8367}
8368
8369impl<'a, const INLINE_BYTES: usize> StreamDeclaration<'a, INLINE_BYTES> {
8370 /// v0.2.2 T6.11: construct a stream declaration with the given productivity
8371 /// bound and result type. Phase A: leaves seed/step/witness empty; use
8372 /// `new_full` to retain the full structure.
8373 #[inline]
8374 #[must_use]
8375 pub const fn new<T: ConstrainedTypeShape>(
8376 productivity_bound: u64,
8377 ) -> StreamDeclaration<'static, INLINE_BYTES> {
8378 StreamDeclaration {
8379 payload: productivity_bound,
8380 result_type_iri: T::IRI,
8381 seed: &[],
8382 step: &[],
8383 productivity_witness: "",
8384 _sealed: (),
8385 }
8386 }
8387
8388 /// v0.2.2 Phase A: construct a stream declaration carrying the full
8389 /// seed/step/witness structure from the builder.
8390 #[inline]
8391 #[must_use]
8392 pub const fn new_full<T: ConstrainedTypeShape>(
8393 productivity_bound: u64,
8394 seed: &'a [Term<'a, INLINE_BYTES>],
8395 step: &'a [Term<'a, INLINE_BYTES>],
8396 productivity_witness: &'a str,
8397 ) -> Self {
8398 Self {
8399 payload: productivity_bound,
8400 result_type_iri: T::IRI,
8401 seed,
8402 step,
8403 productivity_witness,
8404 _sealed: (),
8405 }
8406 }
8407
8408 /// Returns the declared productivity bound.
8409 #[inline]
8410 #[must_use]
8411 pub const fn productivity_bound(&self) -> u64 {
8412 self.payload
8413 }
8414
8415 /// v0.2.2 T6.11: returns the result-type IRI.
8416 #[inline]
8417 #[must_use]
8418 pub const fn result_type_iri(&self) -> &'static str {
8419 self.result_type_iri
8420 }
8421
8422 /// v0.2.2 Phase A: returns the seed term slice.
8423 #[inline]
8424 #[must_use]
8425 pub const fn seed(&self) -> &'a [Term<'a, INLINE_BYTES>] {
8426 self.seed
8427 }
8428
8429 /// v0.2.2 Phase A: returns the step term slice.
8430 #[inline]
8431 #[must_use]
8432 pub const fn step(&self) -> &'a [Term<'a, INLINE_BYTES>] {
8433 self.step
8434 }
8435
8436 /// v0.2.2 Phase A: returns the productivity-witness IRI.
8437 #[inline]
8438 #[must_use]
8439 pub const fn productivity_witness(&self) -> &'a str {
8440 self.productivity_witness
8441 }
8442}
8443
8444/// v0.2.2 Phase F / T2.7: interaction-declaration compile unit. Carries a
8445/// payload field encoding the convergence-predicate seed.
8446/// v0.2.2 T6.11: also carries `result_type_iri` for ShapeMismatch detection.
8447/// v0.2.2 Phase A: lifetime-parameterized for consistency with the other
8448/// widened runtime carriers. The interaction builder stores scalar fields
8449/// only, so there is no additional borrowed structure to retain; the `'a`
8450/// is vestigial but keeps the carrier signature uniform with
8451/// `ParallelDeclaration<'a>` and `StreamDeclaration<'a>`.
8452#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8453pub struct InteractionDeclaration<'a> {
8454 payload: u64,
8455 result_type_iri: &'static str,
8456 _sealed: (),
8457 _lifetime: core::marker::PhantomData<&'a ()>,
8458}
8459
8460impl<'a> InteractionDeclaration<'a> {
8461 /// v0.2.2 T6.11: construct an interaction declaration with the given
8462 /// convergence-predicate seed and result type.
8463 #[inline]
8464 #[must_use]
8465 pub const fn new<T: ConstrainedTypeShape>(
8466 convergence_seed: u64,
8467 ) -> InteractionDeclaration<'static> {
8468 InteractionDeclaration {
8469 payload: convergence_seed,
8470 result_type_iri: T::IRI,
8471 _sealed: (),
8472 _lifetime: core::marker::PhantomData,
8473 }
8474 }
8475
8476 /// Returns the declared convergence seed.
8477 #[inline]
8478 #[must_use]
8479 pub const fn convergence_seed(&self) -> u64 {
8480 self.payload
8481 }
8482
8483 /// v0.2.2 T6.11: returns the result-type IRI.
8484 #[inline]
8485 #[must_use]
8486 pub const fn result_type_iri(&self) -> &'static str {
8487 self.result_type_iri
8488 }
8489}
8490
8491/// v0.2.2 Phase F: fixed-size inline payload buffer carried by `PeerInput`.
8492/// Sized for the largest `Datum<L>` the foundation supports at this release
8493/// (up to 32 u64 limbs = 2048 bits); smaller levels use the leading bytes.
8494#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8495pub struct PeerPayload {
8496 words: [u64; 32],
8497 bit_width: u16,
8498 _sealed: (),
8499}
8500
8501impl PeerPayload {
8502 /// Construct a zeroed payload of the given bit width.
8503 #[inline]
8504 #[must_use]
8505 pub const fn zero(bit_width: u16) -> Self {
8506 Self {
8507 words: [0u64; 32],
8508 bit_width,
8509 _sealed: (),
8510 }
8511 }
8512
8513 /// Access the underlying limbs.
8514 #[inline]
8515 #[must_use]
8516 pub const fn words(&self) -> &[u64; 32] {
8517 &self.words
8518 }
8519
8520 /// Bit width of the payload's logical Datum.
8521 #[inline]
8522 #[must_use]
8523 pub const fn bit_width(&self) -> u16 {
8524 self.bit_width
8525 }
8526}
8527
8528/// v0.2.2 Phase F: a peer-supplied input to an interaction driver step.
8529/// Fixed-size — holds a `PeerPayload` inline plus the peer's content
8530/// address. No heap, no dynamic dispatch.
8531#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8532pub struct PeerInput {
8533 peer_id: u128,
8534 payload: PeerPayload,
8535 _sealed: (),
8536}
8537
8538impl PeerInput {
8539 /// Construct a new peer input with the given peer id and payload.
8540 #[inline]
8541 #[must_use]
8542 pub const fn new(peer_id: u128, payload: PeerPayload) -> Self {
8543 Self {
8544 peer_id,
8545 payload,
8546 _sealed: (),
8547 }
8548 }
8549
8550 /// Access the peer id.
8551 #[inline]
8552 #[must_use]
8553 pub const fn peer_id(&self) -> u128 {
8554 self.peer_id
8555 }
8556
8557 /// Access the payload.
8558 #[inline]
8559 #[must_use]
8560 pub const fn payload(&self) -> &PeerPayload {
8561 &self.payload
8562 }
8563}
8564
8565/// v0.2.2 Phase F: outcome of a single `InteractionDriver::step` call.
8566#[derive(Debug, Clone)]
8567#[non_exhaustive]
8568pub enum StepResult<
8569 T: crate::enforcement::GroundedShape,
8570 const INLINE_BYTES: usize,
8571 const FP_MAX: usize = 32,
8572> {
8573 /// The step was absorbed; the driver is ready for another peer input.
8574 Continue,
8575 /// The step produced an intermediate grounded output.
8576 Output(Grounded<'static, T, INLINE_BYTES, FP_MAX>),
8577 /// The convergence predicate is satisfied; interaction is complete.
8578 Converged(Grounded<'static, T, INLINE_BYTES, FP_MAX>),
8579 /// v0.2.2 Phase T.1: the commutator norm failed to decrease for
8580 /// `INTERACTION_DIVERGENCE_BUDGET` consecutive steps — the interaction is
8581 /// non-convergent and the driver is no longer advanceable.
8582 Diverged,
8583 /// The step failed; the driver is no longer advanceable.
8584 Failure(PipelineFailure),
8585}
8586
8587/// v0.2.2 Phase F: sealed commutator-algebra state carried by an
8588/// interaction driver across peer steps.
8589#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8590pub struct CommutatorState<L> {
8591 accumulator: [u64; 4],
8592 _level: core::marker::PhantomData<L>,
8593 _sealed: (),
8594}
8595
8596impl<L> CommutatorState<L> {
8597 /// Construct a zero commutator state.
8598 #[inline]
8599 #[must_use]
8600 pub const fn zero() -> Self {
8601 Self {
8602 accumulator: [0u64; 4],
8603 _level: core::marker::PhantomData,
8604 _sealed: (),
8605 }
8606 }
8607
8608 /// Access the commutator accumulator words.
8609 #[inline]
8610 #[must_use]
8611 pub const fn accumulator(&self) -> &[u64; 4] {
8612 &self.accumulator
8613 }
8614}
8615
8616/// v0.2.2 Phase F / T2.7: sealed iterator driver returned by `run_stream`.
8617/// Carries the productivity countdown initialized from the unit's
8618/// `StreamDeclaration::productivity_bound()`, plus a unit-derived address
8619/// seed for generating distinct `Grounded` outputs per step. Each call to
8620/// `next()` decrements the countdown and yields a `Grounded` whose
8621/// `unit_address` differs from the previous step's.
8622#[derive(Debug, Clone)]
8623pub struct StreamDriver<
8624 T: crate::enforcement::GroundedShape,
8625 P: crate::enforcement::ValidationPhase,
8626 H: crate::enforcement::Hasher<FP_MAX>,
8627 const INLINE_BYTES: usize,
8628 const FP_MAX: usize = 32,
8629> {
8630 rewrite_steps: u64,
8631 landauer_nats: u64,
8632 productivity_countdown: u64,
8633 seed: u64,
8634 result_type_iri: &'static str,
8635 terminated: bool,
8636 _shape: core::marker::PhantomData<T>,
8637 _phase: core::marker::PhantomData<P>,
8638 _hasher: core::marker::PhantomData<H>,
8639 _sealed: (),
8640}
8641
8642impl<
8643 T: crate::enforcement::GroundedShape,
8644 P: crate::enforcement::ValidationPhase,
8645 H: crate::enforcement::Hasher<FP_MAX>,
8646 const INLINE_BYTES: usize,
8647 const FP_MAX: usize,
8648 > StreamDriver<T, P, H, INLINE_BYTES, FP_MAX>
8649{
8650 /// Crate-internal constructor. Callable only from `pipeline::run_stream`.
8651 #[inline]
8652 #[must_use]
8653 #[allow(dead_code)]
8654 pub(crate) const fn new_internal(
8655 productivity_bound: u64,
8656 seed: u64,
8657 result_type_iri: &'static str,
8658 ) -> Self {
8659 Self {
8660 rewrite_steps: 0,
8661 landauer_nats: 0,
8662 productivity_countdown: productivity_bound,
8663 seed,
8664 result_type_iri,
8665 terminated: false,
8666 _shape: core::marker::PhantomData,
8667 _phase: core::marker::PhantomData,
8668 _hasher: core::marker::PhantomData,
8669 _sealed: (),
8670 }
8671 }
8672
8673 /// Total rewrite steps taken so far.
8674 #[inline]
8675 #[must_use]
8676 pub const fn rewrite_steps(&self) -> u64 {
8677 self.rewrite_steps
8678 }
8679
8680 /// Total Landauer cost accumulated so far.
8681 #[inline]
8682 #[must_use]
8683 pub const fn landauer_nats(&self) -> u64 {
8684 self.landauer_nats
8685 }
8686
8687 /// v0.2.2 T5.10: returns `true` once the driver has stopped producing
8688 /// rewrite steps. A terminated driver is observationally equivalent to
8689 /// one whose next `next()` call returns `None`. Use this when the driver
8690 /// is held inside a larger state machine that needs to decide whether
8691 /// to advance without consuming a step.
8692 /// Parallel to `InteractionDriver::is_converged()`.
8693 #[inline]
8694 #[must_use]
8695 pub const fn is_terminated(&self) -> bool {
8696 self.terminated
8697 }
8698}
8699
8700impl<
8701 T: crate::enforcement::GroundedShape + ConstrainedTypeShape,
8702 P: crate::enforcement::ValidationPhase,
8703 H: crate::enforcement::Hasher<FP_MAX>,
8704 const INLINE_BYTES: usize,
8705 const FP_MAX: usize,
8706 > Iterator for StreamDriver<T, P, H, INLINE_BYTES, FP_MAX>
8707{
8708 type Item = Result<Grounded<'static, T, INLINE_BYTES, FP_MAX>, PipelineFailure>;
8709 fn next(&mut self) -> Option<Self::Item> {
8710 if self.terminated || self.productivity_countdown == 0 {
8711 self.terminated = true;
8712 return None;
8713 }
8714 // v0.2.2 T6.11: ShapeMismatch detection — first step only
8715 // (subsequent steps inherit the same result_type_iri).
8716 if self.rewrite_steps == 0 && !crate::enforcement::str_eq(self.result_type_iri, T::IRI) {
8717 self.terminated = true;
8718 return Some(Err(PipelineFailure::ShapeMismatch {
8719 expected: T::IRI,
8720 got: self.result_type_iri,
8721 }));
8722 }
8723 self.productivity_countdown -= 1;
8724 self.rewrite_steps += 1;
8725 self.landauer_nats += 1;
8726 // v0.2.2 T6.1: thread H: Hasher through fold_stream_step_digest
8727 // to compute a real per-step substrate fingerprint.
8728 let mut hasher = H::initial();
8729 hasher = crate::enforcement::fold_stream_step_digest(
8730 hasher,
8731 self.productivity_countdown,
8732 self.rewrite_steps,
8733 self.seed,
8734 self.result_type_iri,
8735 crate::enforcement::CertificateKind::Grounding,
8736 );
8737 let buffer = hasher.finalize();
8738 let content_fingerprint =
8739 crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
8740 let unit_address = crate::enforcement::unit_address_from_buffer(&buffer);
8741 let grounding = Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
8742 32,
8743 content_fingerprint,
8744 ));
8745 let bindings = empty_bindings_table();
8746 Some(Ok(Grounded::<T, INLINE_BYTES, FP_MAX>::new_internal(
8747 grounding,
8748 bindings,
8749 32, // default witt level for stream output
8750 unit_address,
8751 content_fingerprint,
8752 )))
8753 }
8754}
8755
8756/// v0.2.2 Phase F / T2.7: sealed state-machine driver returned by
8757/// `run_interactive`. Exposes `step(PeerInput)`, `is_converged()`, and
8758/// `finalize()`. The driver folds each peer input into its
8759/// `commutator_acc` accumulator via XOR; convergence is signalled when
8760/// a peer input arrives with `peer_id == 0` (the closing handshake).
8761#[derive(Debug, Clone)]
8762pub struct InteractionDriver<
8763 T: crate::enforcement::GroundedShape,
8764 P: crate::enforcement::ValidationPhase,
8765 H: crate::enforcement::Hasher<FP_MAX>,
8766 const INLINE_BYTES: usize,
8767 const FP_MAX: usize = 32,
8768> {
8769 commutator_acc: [u64; 4],
8770 peer_step_count: u64,
8771 converged: bool,
8772 /// Convergence seed read from the source InteractionDeclaration.
8773 /// Available via `seed()` for downstream inspection.
8774 seed: u64,
8775 /// v0.2.2 Phase T.1: previous step's commutator norm (Euclidean-squared
8776 /// over the 4 u64 limbs, saturating). Used to detect divergence.
8777 prev_commutator_norm: u64,
8778 /// v0.2.2 Phase T.1: count of consecutive non-decreasing norm steps.
8779 /// Reset to 0 on any decrease; divergence triggers at `DIVERGENCE_BUDGET`.
8780 consecutive_non_decreasing: u32,
8781 /// v0.2.2 T6.11: result-type IRI from the source InteractionDeclaration.
8782 result_type_iri: &'static str,
8783 _shape: core::marker::PhantomData<T>,
8784 _phase: core::marker::PhantomData<P>,
8785 _hasher: core::marker::PhantomData<H>,
8786 _sealed: (),
8787}
8788
8789/// v0.2.2 Phase T.1: divergence budget — max consecutive non-decreasing commutator-norm
8790/// steps before the interaction driver fails. Foundation-canonical; override at the
8791/// `InteractionDeclaration` level not supported in this release.
8792pub const INTERACTION_DIVERGENCE_BUDGET: u32 = 16;
8793
8794impl<
8795 T: crate::enforcement::GroundedShape,
8796 P: crate::enforcement::ValidationPhase,
8797 H: crate::enforcement::Hasher<FP_MAX>,
8798 const INLINE_BYTES: usize,
8799 const FP_MAX: usize,
8800 > InteractionDriver<T, P, H, INLINE_BYTES, FP_MAX>
8801{
8802 /// Crate-internal constructor. Callable only from `pipeline::run_interactive`.
8803 #[inline]
8804 #[must_use]
8805 #[allow(dead_code)]
8806 pub(crate) const fn new_internal(seed: u64, result_type_iri: &'static str) -> Self {
8807 // Initial commutator seeded from the unit's convergence seed.
8808 Self {
8809 commutator_acc: [seed, 0, 0, 0],
8810 peer_step_count: 0,
8811 converged: false,
8812 seed,
8813 // Initial norm = seed² (saturating) so the first step can only
8814 // decrease the norm via peer input (which is the convergence path).
8815 prev_commutator_norm: seed.saturating_mul(seed),
8816 consecutive_non_decreasing: 0,
8817 result_type_iri,
8818 _shape: core::marker::PhantomData,
8819 _phase: core::marker::PhantomData,
8820 _hasher: core::marker::PhantomData,
8821 _sealed: (),
8822 }
8823 }
8824
8825 /// v0.2.2 Phase T.1: convergence threshold derived from the seed. Termination
8826 /// triggers when the commutator norm falls below this value. Foundation-canonical:
8827 /// `seed.rotate_right(32) ^ 0xDEADBEEF_CAFEBABE`.
8828 #[inline]
8829 #[must_use]
8830 pub const fn convergence_threshold(&self) -> u64 {
8831 self.seed.rotate_right(32) ^ 0xDEAD_BEEF_CAFE_BABE
8832 }
8833
8834 /// Advance the driver by folding in a single peer input (v0.2.2 Phase T.1).
8835 /// Each step XOR-folds the peer payload's first 4 limbs into the
8836 /// commutator accumulator, then recomputes the Euclidean-squared
8837 /// norm over the 4 limbs (saturating `u64`). Termination rules:
8838 /// * **Converged** if the norm falls below `convergence_threshold()`,
8839 /// OR if `peer_id == 0` (explicit closing handshake).
8840 /// * **Diverged** (via `PipelineFailure::ConvergenceStall`) if the norm is
8841 /// non-decreasing for `INTERACTION_DIVERGENCE_BUDGET` consecutive steps.
8842 /// * **Continue** otherwise.
8843 #[must_use]
8844 pub fn step(&mut self, input: PeerInput) -> StepResult<T, INLINE_BYTES, FP_MAX>
8845 where
8846 T: ConstrainedTypeShape,
8847 {
8848 self.peer_step_count += 1;
8849 // Fold the first 4 payload words into the accumulator.
8850 let words = input.payload().words();
8851 let mut i = 0usize;
8852 while i < 4 {
8853 self.commutator_acc[i] ^= words[i];
8854 i += 1;
8855 }
8856 // v0.2.2 Phase T.1: compute the Euclidean-squared norm over the 4 limbs.
8857 let mut norm: u64 = 0;
8858 let mut j = 0usize;
8859 while j < 4 {
8860 let limb = self.commutator_acc[j];
8861 norm = norm.saturating_add(limb.saturating_mul(limb));
8862 j += 1;
8863 }
8864 let threshold = self.convergence_threshold();
8865 // v0.2.2 Phase T.1: convergence on norm-below-threshold OR explicit
8866 // handshake (peer_id == 0). Divergence on consecutive non-decreasing norm.
8867 let norm_converged = norm < threshold;
8868 let handshake_close = input.peer_id() == 0;
8869 if norm_converged || handshake_close {
8870 self.converged = true;
8871 // v0.2.2 T6.1: thread H: Hasher through fold_interaction_step_digest
8872 // to compute a real convergence-time substrate fingerprint.
8873 let mut hasher = H::initial();
8874 hasher = crate::enforcement::fold_interaction_step_digest(
8875 hasher,
8876 &self.commutator_acc,
8877 self.peer_step_count,
8878 self.seed,
8879 self.result_type_iri,
8880 crate::enforcement::CertificateKind::Grounding,
8881 );
8882 let buffer = hasher.finalize();
8883 let content_fingerprint =
8884 crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
8885 let unit_address = crate::enforcement::unit_address_from_buffer(&buffer);
8886 let grounding = Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
8887 32,
8888 content_fingerprint,
8889 ));
8890 let bindings = empty_bindings_table();
8891 return StepResult::Converged(Grounded::<T, INLINE_BYTES, FP_MAX>::new_internal(
8892 grounding,
8893 bindings,
8894 32,
8895 unit_address,
8896 content_fingerprint,
8897 ));
8898 }
8899 // v0.2.2 Phase T.1: divergence detection — count consecutive
8900 // non-decreasing norm steps. Reset on any decrease.
8901 if norm >= self.prev_commutator_norm {
8902 self.consecutive_non_decreasing = self.consecutive_non_decreasing.saturating_add(1);
8903 } else {
8904 self.consecutive_non_decreasing = 0;
8905 }
8906 self.prev_commutator_norm = norm;
8907 if self.consecutive_non_decreasing >= INTERACTION_DIVERGENCE_BUDGET {
8908 return StepResult::Diverged;
8909 }
8910 StepResult::Continue
8911 }
8912
8913 /// Whether the driver has reached the convergence predicate.
8914 #[inline]
8915 #[must_use]
8916 pub const fn is_converged(&self) -> bool {
8917 self.converged
8918 }
8919
8920 /// Number of peer steps applied so far.
8921 #[inline]
8922 #[must_use]
8923 pub const fn peer_step_count(&self) -> u64 {
8924 self.peer_step_count
8925 }
8926
8927 /// Convergence seed inherited from the source InteractionDeclaration.
8928 #[inline]
8929 #[must_use]
8930 pub const fn seed(&self) -> u64 {
8931 self.seed
8932 }
8933
8934 /// Finalize the interaction, producing a grounded result.
8935 /// Returns a `Grounded<T>` whose `unit_address` is a hash of the
8936 /// accumulated commutator state, so two interaction drivers that
8937 /// processed different peer inputs return distinct grounded values.
8938 /// # Errors
8939 /// Returns a `PipelineFailure::ShapeViolation` if the driver has
8940 /// not converged, or `PipelineFailure::ShapeMismatch` if the source
8941 /// declaration's result_type_iri does not match `T::IRI`.
8942 pub fn finalize(self) -> Result<Grounded<'static, T, INLINE_BYTES, FP_MAX>, PipelineFailure>
8943 where
8944 T: ConstrainedTypeShape,
8945 {
8946 // v0.2.2 T6.11: ShapeMismatch detection.
8947 if !crate::enforcement::str_eq(self.result_type_iri, T::IRI) {
8948 return Err(PipelineFailure::ShapeMismatch {
8949 expected: T::IRI,
8950 got: self.result_type_iri,
8951 });
8952 }
8953 if !self.converged {
8954 return Err(PipelineFailure::ShapeViolation {
8955 report: ShapeViolation {
8956 shape_iri: "https://uor.foundation/conformance/InteractionShape",
8957 constraint_iri:
8958 "https://uor.foundation/conformance/InteractionShape#convergence",
8959 property_iri: "https://uor.foundation/conformance/convergencePredicate",
8960 expected_range: "http://www.w3.org/2002/07/owl#Thing",
8961 min_count: 1,
8962 max_count: 1,
8963 kind: ViolationKind::Missing,
8964 },
8965 });
8966 }
8967 // v0.2.2 T6.1: thread H: Hasher through fold_interaction_step_digest.
8968 let mut hasher = H::initial();
8969 hasher = crate::enforcement::fold_interaction_step_digest(
8970 hasher,
8971 &self.commutator_acc,
8972 self.peer_step_count,
8973 self.seed,
8974 self.result_type_iri,
8975 crate::enforcement::CertificateKind::Grounding,
8976 );
8977 let buffer = hasher.finalize();
8978 let content_fingerprint =
8979 crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
8980 let unit_address = crate::enforcement::unit_address_from_buffer(&buffer);
8981 let grounding = Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
8982 32,
8983 content_fingerprint,
8984 ));
8985 let bindings = empty_bindings_table();
8986 Ok(Grounded::<T, INLINE_BYTES, FP_MAX>::new_internal(
8987 grounding,
8988 bindings,
8989 32,
8990 unit_address,
8991 content_fingerprint,
8992 ))
8993 }
8994}
8995
8996/// v0.2.2 Phase F / T2.7: parallel driver entry point.
8997/// Consumes a `Validated<ParallelDeclaration, P>` and produces a unified
8998/// `Grounded<T>` whose `unit_address` is derived from the declaration's
8999/// site count via FNV-1a. Two units with different site counts produce
9000/// `Grounded` values with different addresses.
9001/// # Errors
9002/// Returns `PipelineFailure::ShapeMismatch` when the declaration's
9003/// `result_type_iri` does not match `T::IRI` — the caller asked for
9004/// `Grounded<T>` but the declaration was built over a different shape.
9005/// Returns `PipelineFailure::ContradictionDetected` when the declared
9006/// partition cardinality is zero — a parallel composition with no
9007/// sites is inadmissible by construction.
9008/// Success: `run_parallel` folds the declaration's site count through
9009/// `fold_parallel_digest` to produce a content fingerprint; distinct
9010/// partitions produce distinct fingerprints by construction.
9011/// # Example
9012/// ```no_run
9013/// use uor_foundation::enforcement::{ConstrainedTypeInput, Validated};
9014/// use uor_foundation::pipeline::{run_parallel, ParallelDeclaration};
9015/// # use uor_foundation::enforcement::Hasher;
9016/// # struct Fnv1aHasher16;
9017/// # impl Hasher for Fnv1aHasher16 {
9018/// # const OUTPUT_BYTES: usize = 16;
9019/// # fn initial() -> Self { Self }
9020/// # fn fold_byte(self, _: u8) -> Self { self }
9021/// # fn finalize(self) -> [u8; 32] { [0; 32] }
9022/// # }
9023/// # fn wrap<T>(t: T) -> Validated<T> { unimplemented!() /* see uor_foundation_test_helpers */ }
9024/// // 3-component partition over 9 sites.
9025/// static PARTITION: &[u32] = &[0, 0, 0, 1, 1, 1, 2, 2, 2];
9026/// let decl: Validated<ParallelDeclaration> = wrap(
9027/// ParallelDeclaration::new_with_partition::<ConstrainedTypeInput>(
9028/// PARTITION,
9029/// "https://uor.foundation/parallel/ParallelDisjointnessWitness",
9030/// ),
9031/// );
9032/// // ADR-060: `Grounded` carries an `INLINE_BYTES` const-generic the
9033/// // application derives from its `HostBounds`; thread it through
9034/// // `run_parallel`'s 4th const argument.
9035/// let grounded = run_parallel::<ConstrainedTypeInput, _, Fnv1aHasher16, 32, 32>(decl)
9036/// .expect("partition admits");
9037/// # let _ = grounded;
9038/// ```
9039pub fn run_parallel<T, P, H, const INLINE_BYTES: usize, const FP_MAX: usize>(
9040 unit: Validated<ParallelDeclaration, P>,
9041) -> Result<Grounded<'static, T, INLINE_BYTES, FP_MAX>, PipelineFailure>
9042where
9043 T: ConstrainedTypeShape + crate::enforcement::GroundedShape,
9044 P: crate::enforcement::ValidationPhase,
9045 H: crate::enforcement::Hasher<FP_MAX>,
9046{
9047 let decl = unit.inner();
9048 let site_count = decl.site_count();
9049 let partition = decl.site_partition();
9050 let witness_iri = decl.disjointness_witness();
9051 // Runtime invariants declared in the ParallelDeclaration rustdoc:
9052 // (1) result_type_iri must match T::IRI (target §5 + T6.11);
9053 // (2) site_count > 0 (zero-site parallel composition is vacuous);
9054 // (3) v0.2.2 Phase H3: partition length must equal site_count;
9055 // (4) v0.2.2 Phase H3: partition must be non-empty (only constructor is
9056 // `new_with_partition`, which takes a real partition slice).
9057 if !crate::enforcement::str_eq(decl.result_type_iri(), T::IRI) {
9058 return Err(PipelineFailure::ShapeMismatch {
9059 expected: T::IRI,
9060 got: decl.result_type_iri(),
9061 });
9062 }
9063 if site_count == 0 || partition.is_empty() {
9064 return Err(PipelineFailure::ContradictionDetected {
9065 at_step: 0,
9066 trace_iri: "https://uor.foundation/parallel/ParallelProduct",
9067 });
9068 }
9069 if partition.len() as u64 != site_count {
9070 return Err(PipelineFailure::ShapeMismatch {
9071 expected: T::IRI,
9072 got: decl.result_type_iri(),
9073 });
9074 }
9075 // v0.2.2 Phase H3: walk partition, count sites per component, fold
9076 // per-component into the content fingerprint. Enumerates unique component
9077 // IDs into a fixed stack buffer sized by WITT_MAX_BITS.
9078 let mut hasher = H::initial();
9079 // component_ids: seen component IDs in first-appearance order.
9080 // component_counts: parallel site-count per component.
9081 let mut component_ids = [0u32; WITT_MAX_BITS as usize];
9082 let mut component_counts = [0u32; WITT_MAX_BITS as usize];
9083 let mut n_components: usize = 0;
9084 let mut si = 0;
9085 while si < partition.len() {
9086 let cid = partition[si];
9087 // Find or insert cid.
9088 let mut ci = 0;
9089 let mut found = false;
9090 while ci < n_components {
9091 if component_ids[ci] == cid {
9092 component_counts[ci] = component_counts[ci].saturating_add(1);
9093 found = true;
9094 break;
9095 }
9096 ci += 1;
9097 }
9098 if !found && n_components < (WITT_MAX_BITS as usize) {
9099 component_ids[n_components] = cid;
9100 component_counts[n_components] = 1;
9101 n_components += 1;
9102 }
9103 si += 1;
9104 }
9105 // Fold each component: (component_id, site_count_within) in first-appearance order.
9106 let mut ci = 0;
9107 while ci < n_components {
9108 hasher = hasher.fold_bytes(&component_ids[ci].to_be_bytes());
9109 hasher = hasher.fold_bytes(&component_counts[ci].to_be_bytes());
9110 ci += 1;
9111 }
9112 // Fold disjointness_witness IRI so forgeries yield distinct content fingerprints.
9113 hasher = hasher.fold_bytes(witness_iri.as_bytes());
9114 hasher = hasher.fold_byte(0);
9115 // Canonical ParallelDeclaration tail: site_count + type shape + cert kind.
9116 hasher = crate::enforcement::fold_parallel_digest(
9117 hasher,
9118 site_count,
9119 T::IRI,
9120 T::SITE_COUNT,
9121 T::CONSTRAINTS,
9122 crate::enforcement::CertificateKind::Grounding,
9123 );
9124 let buffer = hasher.finalize();
9125 let content_fingerprint =
9126 crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
9127 let unit_address = crate::enforcement::unit_address_from_buffer(&buffer);
9128 let grounding = Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
9129 32,
9130 content_fingerprint,
9131 ));
9132 let bindings = empty_bindings_table();
9133 Ok(Grounded::<T, INLINE_BYTES, FP_MAX>::new_internal(
9134 grounding,
9135 bindings,
9136 32,
9137 unit_address,
9138 content_fingerprint,
9139 ))
9140}
9141
9142/// v0.2.2 Phase F / T2.7: stream driver entry point.
9143/// Consumes a `Validated<StreamDeclaration, P>` and returns a
9144/// `StreamDriver<T, P>` implementing `Iterator`. The driver's productivity
9145/// countdown is initialized from `StreamDeclaration::productivity_bound()`;
9146/// each `next()` call yields a `Grounded` whose `unit_address` differs
9147/// from the previous step's, and the iterator terminates when the
9148/// countdown reaches zero.
9149#[must_use]
9150pub fn run_stream<T, P, H, const INLINE_BYTES: usize, const FP_MAX: usize>(
9151 unit: Validated<StreamDeclaration<'_, INLINE_BYTES>, P>,
9152) -> StreamDriver<T, P, H, INLINE_BYTES, FP_MAX>
9153where
9154 T: crate::enforcement::GroundedShape,
9155 P: crate::enforcement::ValidationPhase,
9156 H: crate::enforcement::Hasher<FP_MAX>,
9157{
9158 let bound = unit.inner().productivity_bound();
9159 let result_type_iri = unit.inner().result_type_iri();
9160 StreamDriver::new_internal(bound, bound, result_type_iri)
9161}
9162
9163/// v0.2.2 Phase F / T2.7: interaction driver entry point.
9164/// Consumes a `Validated<InteractionDeclaration, P>` and returns an
9165/// `InteractionDriver<T, P, H>` state machine seeded from the declaration's
9166/// `convergence_seed()`. Advance with `step(PeerInput)` until
9167/// `is_converged()` returns `true`, then call `finalize()`.
9168#[must_use]
9169pub fn run_interactive<T, P, H, const INLINE_BYTES: usize, const FP_MAX: usize>(
9170 unit: Validated<InteractionDeclaration, P>,
9171) -> InteractionDriver<T, P, H, INLINE_BYTES, FP_MAX>
9172where
9173 T: crate::enforcement::GroundedShape,
9174 P: crate::enforcement::ValidationPhase,
9175 H: crate::enforcement::Hasher<FP_MAX>,
9176{
9177 InteractionDriver::new_internal(
9178 unit.inner().convergence_seed(),
9179 unit.inner().result_type_iri(),
9180 )
9181}
9182
9183/// v0.2.2 Phase G / T2.8: const-fn companion for
9184/// `LeaseDeclarationBuilder`. Delegates to the builder's
9185/// `validate_const` method, which validates the `LeaseShape` contract
9186/// (`linear_site` and `scope` required) at compile time.
9187/// # Errors
9188/// Returns `ShapeViolation::Missing` if `linear_site` or `scope` is unset.
9189pub const fn validate_lease_const<'a>(
9190 builder: &LeaseDeclarationBuilder<'a>,
9191) -> Result<Validated<LeaseDeclaration, CompileTime>, ShapeViolation> {
9192 builder.validate_const()
9193}
9194
9195/// v0.2.2 Phase G / T2.8 + T6.13: const-fn companion for `CompileUnitBuilder`.
9196///
9197/// Tightened in T6.13 to enforce the same five required fields as the
9198/// runtime `CompileUnitBuilder::validate()` method:
9199///
9200/// - `root_term`
9201/// - `witt_level_ceiling`
9202/// - `thermodynamic_budget`
9203/// - `target_domains` (non-empty)
9204/// - `result_type_iri`
9205///
9206/// Returns `Result<Validated<CompileUnit, CompileTime>, ShapeViolation>` —
9207/// dual-path consistent with the runtime `validate()` method. Const-eval
9208/// call sites match on the `Result`; the panic only fires at codegen /
9209/// const-eval time, never at runtime.
9210///
9211/// # Errors
9212///
9213/// Returns `ShapeViolation::Missing` for the first unset required field.
9214pub const fn validate_compile_unit_const<'a, const INLINE_BYTES: usize>(
9215 builder: &CompileUnitBuilder<'a, INLINE_BYTES>,
9216) -> Result<Validated<CompileUnit<'a, INLINE_BYTES>, CompileTime>, ShapeViolation> {
9217 if !builder.has_root_term_const() {
9218 return Err(ShapeViolation {
9219 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
9220 constraint_iri: "https://uor.foundation/conformance/compileUnit_rootTerm_constraint",
9221 property_iri: "https://uor.foundation/reduction/rootTerm",
9222 expected_range: "https://uor.foundation/schema/Term",
9223 min_count: 1,
9224 max_count: 1,
9225 kind: ViolationKind::Missing,
9226 });
9227 }
9228 let level = match builder.witt_level_option() {
9229 Some(l) => l,
9230 None => {
9231 return Err(ShapeViolation {
9232 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
9233 constraint_iri:
9234 "https://uor.foundation/conformance/compileUnit_unitWittLevel_constraint",
9235 property_iri: "https://uor.foundation/reduction/unitWittLevel",
9236 expected_range: "https://uor.foundation/schema/WittLevel",
9237 min_count: 1,
9238 max_count: 1,
9239 kind: ViolationKind::Missing,
9240 })
9241 }
9242 };
9243 let budget =
9244 match builder.budget_option() {
9245 Some(b) => b,
9246 None => return Err(ShapeViolation {
9247 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
9248 constraint_iri:
9249 "https://uor.foundation/conformance/compileUnit_thermodynamicBudget_constraint",
9250 property_iri: "https://uor.foundation/reduction/thermodynamicBudget",
9251 expected_range: "http://www.w3.org/2001/XMLSchema#decimal",
9252 min_count: 1,
9253 max_count: 1,
9254 kind: ViolationKind::Missing,
9255 }),
9256 };
9257 if !builder.has_target_domains_const() {
9258 return Err(ShapeViolation {
9259 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
9260 constraint_iri:
9261 "https://uor.foundation/conformance/compileUnit_targetDomains_constraint",
9262 property_iri: "https://uor.foundation/reduction/targetDomains",
9263 expected_range: "https://uor.foundation/op/VerificationDomain",
9264 min_count: 1,
9265 max_count: 0,
9266 kind: ViolationKind::Missing,
9267 });
9268 }
9269 let result_type_iri = match builder.result_type_iri_const() {
9270 Some(iri) => iri,
9271 None => {
9272 return Err(ShapeViolation {
9273 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
9274 constraint_iri:
9275 "https://uor.foundation/conformance/compileUnit_resultType_constraint",
9276 property_iri: "https://uor.foundation/reduction/resultType",
9277 expected_range: "https://uor.foundation/type/ConstrainedType",
9278 min_count: 1,
9279 max_count: 1,
9280 kind: ViolationKind::Missing,
9281 })
9282 }
9283 };
9284 Ok(Validated::new(CompileUnit::from_parts_const(
9285 level,
9286 budget,
9287 result_type_iri,
9288 builder.root_term_slice_const(),
9289 builder.bindings_slice_const(),
9290 builder.target_domains_slice_const(),
9291 )))
9292}
9293
9294/// v0.2.2 Phase G / T2.8 + T6.11: const-fn companion for
9295/// `ParallelDeclarationBuilder`. Takes a `ConstrainedTypeShape` type parameter
9296/// to set the `result_type_iri` on the produced declaration.
9297/// v0.2.2 Phase A: the produced `ParallelDeclaration<'a>` carries the
9298/// builder's raw site-partition slice and disjointness-witness IRI; the
9299/// lifetime `'a` is the builder's borrow lifetime.
9300#[must_use]
9301pub const fn validate_parallel_const<'a, T: ConstrainedTypeShape>(
9302 builder: &ParallelDeclarationBuilder<'a>,
9303) -> Validated<ParallelDeclaration<'a>, CompileTime> {
9304 Validated::new(ParallelDeclaration::new_with_partition::<T>(
9305 builder.site_partition_slice_const(),
9306 builder.disjointness_witness_const(),
9307 ))
9308}
9309
9310/// v0.2.2 Phase G / T2.8 + T6.11: const-fn companion for
9311/// `StreamDeclarationBuilder`. Takes a `ConstrainedTypeShape` type parameter
9312/// to set the `result_type_iri` on the produced declaration.
9313/// v0.2.2 Phase A: the produced `StreamDeclaration<'a>` retains the
9314/// builder's seed/step term slices and productivity-witness IRI.
9315#[must_use]
9316pub const fn validate_stream_const<'a, const INLINE_BYTES: usize, T: ConstrainedTypeShape>(
9317 builder: &StreamDeclarationBuilder<'a, INLINE_BYTES>,
9318) -> Validated<StreamDeclaration<'a, INLINE_BYTES>, CompileTime> {
9319 let bound = builder.productivity_bound_const();
9320 Validated::new(StreamDeclaration::new_full::<T>(
9321 bound,
9322 builder.seed_slice_const(),
9323 builder.step_slice_const(),
9324 builder.productivity_witness_const(),
9325 ))
9326}
9327
9328/// v0.2.2 T5 C6: const-fn resolver companion for
9329/// `tower_completeness::certify`. Threads the consumer-supplied substrate
9330/// `Hasher` through the canonical CompileUnit byte layout to compute a
9331/// parametric content fingerprint, distinguishing two units that share a
9332/// witt level but differ in budget, IRI, site count, or constraints.
9333#[must_use]
9334pub fn certify_tower_completeness_const<T, H, const INLINE_BYTES: usize, const FP_MAX: usize>(
9335 unit: &Validated<CompileUnit<'_, INLINE_BYTES>, CompileTime>,
9336) -> Validated<GroundingCertificate<FP_MAX>, CompileTime>
9337where
9338 T: ConstrainedTypeShape,
9339 H: crate::enforcement::Hasher<FP_MAX>,
9340{
9341 let level_bits = unit.inner().witt_level().witt_length() as u16;
9342 let budget = unit.inner().thermodynamic_budget();
9343 let mut hasher = H::initial();
9344 hasher = crate::enforcement::fold_unit_digest(
9345 hasher,
9346 level_bits,
9347 budget,
9348 T::IRI,
9349 T::SITE_COUNT,
9350 T::CONSTRAINTS,
9351 crate::enforcement::CertificateKind::TowerCompleteness,
9352 );
9353 let buffer = hasher.finalize();
9354 let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
9355 Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
9356 level_bits, fp,
9357 ))
9358}
9359
9360/// v0.2.2 T5 C6: const-fn resolver companion for
9361/// `incremental_completeness::certify`. Threads `H: Hasher` for the
9362/// parametric fingerprint; uses `CertificateKind::IncrementalCompleteness`
9363/// as the trailing discriminant byte.
9364#[must_use]
9365pub fn certify_incremental_completeness_const<
9366 T,
9367 H,
9368 const INLINE_BYTES: usize,
9369 const FP_MAX: usize,
9370>(
9371 unit: &Validated<CompileUnit<'_, INLINE_BYTES>, CompileTime>,
9372) -> Validated<GroundingCertificate<FP_MAX>, CompileTime>
9373where
9374 T: ConstrainedTypeShape,
9375 H: crate::enforcement::Hasher<FP_MAX>,
9376{
9377 let level_bits = unit.inner().witt_level().witt_length() as u16;
9378 let budget = unit.inner().thermodynamic_budget();
9379 let mut hasher = H::initial();
9380 hasher = crate::enforcement::fold_unit_digest(
9381 hasher,
9382 level_bits,
9383 budget,
9384 T::IRI,
9385 T::SITE_COUNT,
9386 T::CONSTRAINTS,
9387 crate::enforcement::CertificateKind::IncrementalCompleteness,
9388 );
9389 let buffer = hasher.finalize();
9390 let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
9391 Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
9392 level_bits, fp,
9393 ))
9394}
9395
9396/// v0.2.2 T5 C6: const-fn resolver companion for `inhabitance::certify`.
9397/// Threads `H: Hasher` for the parametric fingerprint; uses
9398/// `CertificateKind::Inhabitance` as the trailing discriminant byte.
9399#[must_use]
9400pub fn certify_inhabitance_const<T, H, const INLINE_BYTES: usize, const FP_MAX: usize>(
9401 unit: &Validated<CompileUnit<'_, INLINE_BYTES>, CompileTime>,
9402) -> Validated<GroundingCertificate<FP_MAX>, CompileTime>
9403where
9404 T: ConstrainedTypeShape,
9405 H: crate::enforcement::Hasher<FP_MAX>,
9406{
9407 let level_bits = unit.inner().witt_level().witt_length() as u16;
9408 let budget = unit.inner().thermodynamic_budget();
9409 let mut hasher = H::initial();
9410 hasher = crate::enforcement::fold_unit_digest(
9411 hasher,
9412 level_bits,
9413 budget,
9414 T::IRI,
9415 T::SITE_COUNT,
9416 T::CONSTRAINTS,
9417 crate::enforcement::CertificateKind::Inhabitance,
9418 );
9419 let buffer = hasher.finalize();
9420 let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
9421 Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
9422 level_bits, fp,
9423 ))
9424}
9425
9426/// v0.2.2 T5 C6: const-fn resolver companion for
9427/// `multiplication::certify`. Threads `H: Hasher` for the parametric
9428/// fingerprint; uses `CertificateKind::Multiplication` as the trailing
9429/// discriminant byte.
9430#[must_use]
9431pub fn certify_multiplication_const<T, H, const INLINE_BYTES: usize, const FP_MAX: usize>(
9432 unit: &Validated<CompileUnit<'_, INLINE_BYTES>, CompileTime>,
9433) -> Validated<MultiplicationCertificate<FP_MAX>, CompileTime>
9434where
9435 T: ConstrainedTypeShape,
9436 H: crate::enforcement::Hasher<FP_MAX>,
9437{
9438 let level_bits = unit.inner().witt_level().witt_length() as u16;
9439 let budget = unit.inner().thermodynamic_budget();
9440 let mut hasher = H::initial();
9441 hasher = crate::enforcement::fold_unit_digest(
9442 hasher,
9443 level_bits,
9444 budget,
9445 T::IRI,
9446 T::SITE_COUNT,
9447 T::CONSTRAINTS,
9448 crate::enforcement::CertificateKind::Multiplication,
9449 );
9450 let buffer = hasher.finalize();
9451 let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
9452 Validated::new(MultiplicationCertificate::with_level_and_fingerprint_const(
9453 level_bits, fp,
9454 ))
9455}
9456
9457/// Phase C.4: const-fn resolver companion for `grounding_aware::certify`.
9458/// Threads `H: Hasher` for the parametric fingerprint; uses
9459/// `CertificateKind::Grounding` as the trailing discriminant byte.
9460#[must_use]
9461pub fn certify_grounding_aware_const<T, H, const INLINE_BYTES: usize, const FP_MAX: usize>(
9462 unit: &Validated<CompileUnit<'_, INLINE_BYTES>, CompileTime>,
9463) -> Validated<GroundingCertificate<FP_MAX>, CompileTime>
9464where
9465 T: ConstrainedTypeShape,
9466 H: crate::enforcement::Hasher<FP_MAX>,
9467{
9468 let level_bits = unit.inner().witt_level().witt_length() as u16;
9469 let budget = unit.inner().thermodynamic_budget();
9470 let mut hasher = H::initial();
9471 hasher = crate::enforcement::fold_unit_digest(
9472 hasher,
9473 level_bits,
9474 budget,
9475 T::IRI,
9476 T::SITE_COUNT,
9477 T::CONSTRAINTS,
9478 crate::enforcement::CertificateKind::Grounding,
9479 );
9480 let buffer = hasher.finalize();
9481 let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
9482 Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
9483 level_bits, fp,
9484 ))
9485}
9486
9487/// v0.2.2 T5 C6: typed pipeline entry point producing `Grounded<T>` from
9488/// a validated `CompileUnit`. Threads the consumer-supplied substrate
9489/// `Hasher` through `fold_unit_digest` to compute a parametric content
9490/// fingerprint over the unit's full state: `(level_bits, budget, T::IRI,
9491/// T::SITE_COUNT, T::CONSTRAINTS, CertificateKind::Grounding)`.
9492/// Two units differing on **any** of those fields produce `Grounded`
9493/// values with distinct fingerprints (and distinct `unit_address` handles,
9494/// derived from the leading 16 bytes of the fingerprint).
9495/// # Errors
9496/// Returns `PipelineFailure::ShapeMismatch` when the unit's declared
9497/// `result_type_iri` does not match `T::IRI`, or propagates any
9498/// failure from the reduction stage executor.
9499pub fn run_const<T, M, H, const INLINE_BYTES: usize, const FP_MAX: usize>(
9500 unit: &Validated<CompileUnit<'_, INLINE_BYTES>, CompileTime>,
9501) -> Result<Grounded<'static, T, INLINE_BYTES, FP_MAX>, PipelineFailure>
9502where
9503 T: ConstrainedTypeShape + crate::enforcement::GroundedShape,
9504 // Phase C.2 (target §6): const-eval admits only those grounding-map kinds
9505 // that are both Total (defined for all inputs) and Invertible (one-to-one).
9506 // The bound is enforced at the type level via the existing marker tower.
9507 M: crate::enforcement::GroundingMapKind
9508 + crate::enforcement::Total
9509 + crate::enforcement::Invertible,
9510 H: crate::enforcement::Hasher<FP_MAX>,
9511{
9512 // The marker bound on M is purely type-level — no runtime use.
9513 let _phantom_map: core::marker::PhantomData<M> = core::marker::PhantomData;
9514 let level_bits = unit.inner().witt_level().witt_length() as u16;
9515 let budget = unit.inner().thermodynamic_budget();
9516 // v0.2.2 T6.11: ShapeMismatch detection. The unit declares its
9517 // result_type_iri at validation time; the caller's `T::IRI` must match.
9518 let unit_iri = unit.inner().result_type_iri();
9519 if !crate::enforcement::str_eq(unit_iri, T::IRI) {
9520 return Err(PipelineFailure::ShapeMismatch {
9521 expected: T::IRI,
9522 got: unit_iri,
9523 });
9524 }
9525 // Walk the foundation-locked byte layout via the consumer's hasher.
9526 let mut hasher = H::initial();
9527 hasher = crate::enforcement::fold_unit_digest(
9528 hasher,
9529 level_bits,
9530 budget,
9531 T::IRI,
9532 T::SITE_COUNT,
9533 T::CONSTRAINTS,
9534 crate::enforcement::CertificateKind::Grounding,
9535 );
9536 let buffer = hasher.finalize();
9537 let content_fingerprint =
9538 crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
9539 let unit_address = crate::enforcement::unit_address_from_buffer(&buffer);
9540 let grounding = Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
9541 level_bits,
9542 content_fingerprint,
9543 ));
9544 let bindings = empty_bindings_table();
9545 Ok(Grounded::<T, INLINE_BYTES, FP_MAX>::new_internal(
9546 grounding,
9547 bindings,
9548 level_bits,
9549 unit_address,
9550 content_fingerprint,
9551 ))
9552}