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