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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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 &'a 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<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<T: crate::enforcement::GroundedShape, const INLINE_BYTES: usize, Tag> WitnessTupleSource
2580 for crate::enforcement::Grounded<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 serialize the
3331/// runtime input value into the `CompileUnit` binding table per wiki
3332/// ADR-023.
3333/// # Implementation contract
3334/// [`into_binding_bytes`] writes the canonical content-addressable byte
3335/// sequence for the value into the caller-provided buffer and returns
3336/// the written length. The serialization MUST be deterministic — two
3337/// values that compare equal MUST produce byte sequences that compare
3338/// equal — so the input's content fingerprint is a function of the
3339/// value alone.
3340/// [`MAX_BYTES`] is the maximum byte length any value of this shape can
3341/// produce. [`run_route`] uses it to size the on-stack buffer and
3342/// rejects inputs whose declared `MAX_BYTES` exceeds the foundation
3343/// ceiling [`INLINE_BYTES`].
3344/// # Sealing
3345/// Sealed via [`__sdk_seal::Sealed`] (the same supertrait as
3346/// [`FoundationClosed`] and [`PrismModel`]): foundation sanctions the
3347/// identity-route impl on [`ConstrainedTypeInput`] directly; the SDK
3348/// shape macros (`product_shape!`, `coproduct_shape!`,
3349/// `cartesian_product_shape!`) emit the impl alongside the
3350/// `ConstrainedTypeShape` impl. Application authors implementing a
3351/// custom `ConstrainedTypeShape` use the `prism_model!` macro's input
3352/// declaration to obtain the impl.
3353pub trait IntoBindingValue: ConstrainedTypeShape + __sdk_seal::Sealed {
3354 /// Maximum byte length any value of this shape can produce when
3355 /// serialized via [`into_binding_bytes`]. Used by [`run_route`] to
3356 /// size the on-stack buffer and reject inputs that would overflow.
3357 const MAX_BYTES: usize;
3358
3359 /// Serialize this input value into the binding-table form. `out` is a
3360 /// fixed-capacity buffer the call-site provides; the implementation
3361 /// writes the canonical content-addressable byte sequence and returns
3362 /// the written length.
3363 /// # Errors
3364 /// Returns [`crate::enforcement::ShapeViolation`] when the canonical
3365 /// serialization cannot be produced (e.g., a coproduct tag is out of
3366 /// range, a constraint cannot be witnessed) or when `out.len()` is
3367 /// smaller than the bytes the value requires.
3368 #[allow(clippy::wrong_self_convention)]
3369 fn into_binding_bytes(
3370 &self,
3371 out: &mut [u8],
3372 ) -> core::result::Result<usize, crate::enforcement::ShapeViolation>;
3373}
3374
3375/// Foundation-fixed threshold for the closure-body grammar `fold_n`'s
3376/// unroll-vs-`Term::Recurse` lowering rule per wiki ADR-026 G14.
3377/// `fold_n` calls with const-literal counts at or below this threshold
3378/// unroll into a sequential `Term::Application` chain; counts above
3379/// (or parametric counts) lower to `Term::Recurse` with a descent-
3380/// measure-bounded fold. The fixed threshold means two implementations
3381/// compiling the same closure body emit the same Term tree.
3382/// Wiki ADR-037: a foundation-fixed conservative default for
3383/// [`HostBounds::FOLD_UNROLL_THRESHOLD`].
3384pub const FOLD_UNROLL_THRESHOLD: usize = 8;
3385
3386/// The application author's typed-iso contract: an `Input` feature type, an
3387/// `Output` label type, and a type-level `Route` witness of the term tree
3388/// mapping one to the other. Per the wiki's ADR-020 — "the model I am
3389/// declaring" — codifies a hylomorphism-with-verifiable-round-trip:
3390/// the catamorphism from `Input` to `Result<Grounded<Output>, PipelineFailure>`
3391/// (see [`run`]) plus the recoverable anamorphism through the trace to
3392/// `Certified<GroundingCertificate>` (see
3393/// [`crate::enforcement::replay::certify_from_trace`]).
3394/// The trait's name derives from the implementation crate, not from the
3395/// categorical Prism optic.
3396/// # Compile-time guarantees
3397/// Implementing `PrismModel` for an application type yields, by virtue of
3398/// the trait's bounds:
3399/// - **Closure under foundation vocabulary**: the `Route` bound
3400/// ([`FoundationClosed`]) is satisfied iff every term in the route witness
3401/// comes from foundation's signature endofunctor F (wiki ADR-019). A
3402/// hand-rolled composition that escapes foundation vocabulary fails to
3403/// compile.
3404/// - **Zero-cost runtime** (TC-01): `forward` is the catamorphism induced
3405/// by initiality of `Term` (ADR-019); the application's compile time
3406/// monomorphizes the catamorphism into native code.
3407/// - **Seal coverage** (TC-02): `forward`'s output is
3408/// `Grounded<Self::Output>` constructed via the seal regime
3409/// ([`crate::enforcement::Grounded`], ADR-011).
3410/// - **Replay equivalence** (TC-05): a `Trace` is recoverable from the
3411/// `Grounded<Output>` via `derivation().replay()`; certifying it via
3412/// [`crate::enforcement::replay::certify_from_trace`] yields a
3413/// `Certified<GroundingCertificate>` whose certificate matches the one
3414/// reachable from `forward`'s output.
3415/// # Authoring
3416/// Application authors do not write `forward`'s body by hand; the
3417/// `prism_model!` macro from `uor-foundation-sdk` derives it from the
3418/// syntactic Route declaration via initiality of `Term` (ADR-019). The
3419/// macro emits both the type-level `Route` witness (which the application's
3420/// `Route` associated type aliases) and the value-level `TermArena` slice
3421/// [`run_route`] traverses (per ADR-022 D2 + D3 + D5).
3422pub trait PrismModel<
3423 H,
3424 B,
3425 A,
3426 const INLINE_BYTES: usize,
3427 R = crate::pipeline::NullResolverTuple,
3428 C = crate::pipeline::EmptyCommitment,
3429>: __sdk_seal::Sealed where
3430 H: crate::HostTypes,
3431 B: crate::HostBounds,
3432 A: crate::pipeline::AxisTuple<INLINE_BYTES> + crate::enforcement::Hasher,
3433 R: crate::pipeline::ResolverTuple,
3434 C: crate::pipeline::TypedCommitment,
3435{
3436 /// Input feature type — a [`ConstrainedTypeShape`] impl declared in
3437 /// foundation vocabulary.
3438 /// Per wiki ADR-023, `Input` is also bound by [`IntoBindingValue`] so
3439 /// [`run_route`] can serialize the runtime input value into the
3440 /// `CompileUnit` binding table for `Term::Variable { name_index: 0 }`
3441 /// (the route's input-parameter slot per ADR-022 D3 G2).
3442 type Input: ConstrainedTypeShape + IntoBindingValue;
3443
3444 /// Output label type — a [`ConstrainedTypeShape`] impl declared in
3445 /// foundation vocabulary that is also a [`crate::enforcement::GroundedShape`].
3446 type Output: ConstrainedTypeShape + crate::enforcement::GroundedShape + IntoBindingValue;
3447
3448 /// Type-level witness of the term tree mapping `Input` to `Output`.
3449 /// Bound by [`FoundationClosed`]: the `prism_model!` macro emits the
3450 /// `FoundationClosed` impl for this witness iff every node is a
3451 /// foundation-vocabulary item, satisfying the closure check at the
3452 /// application's compile time per UORassembly (TC-04).
3453 type Route: FoundationClosed<INLINE_BYTES>;
3454
3455 /// The catamorphism into [`run_route`]'s runtime carrier.
3456 /// Implementations are emitted by the `prism_model!` macro from the
3457 /// syntactic Route declaration; the macro derives the body via
3458 /// initiality of `Term` (wiki ADR-019). The canonical body is
3459 /// `run_route::<H, B, A, Self>(input)` (per ADR-022 D5).
3460 /// # Errors
3461 /// Returns a [`PipelineFailure`] when the input does not satisfy the
3462 /// route's preflight checks (budget solvency, feasibility, package
3463 /// coherence, dispatch coverage, timing) or when reduction stages
3464 /// detect contradiction along the route.
3465 fn forward(
3466 input: Self::Input,
3467 ) -> Result<crate::enforcement::Grounded<Self::Output, INLINE_BYTES>, PipelineFailure>;
3468}
3469
3470/// Higher-level catamorphism entry point — wiki ADR-022 D5.
3471/// `run_route` constructs a `Validated<CompileUnit, FinalPhase>` from the
3472/// model's `Route` (whose const `TermArena` slice carries the term tree)
3473/// plus the input, and invokes [`run`] against it. The macro-emitted
3474/// `PrismModel::forward` body is exactly `run_route::<H, B, A, Self>(input)`.
3475/// Lower-level callers (test harnesses, conformance suites, alternative
3476/// SDK surfaces) use [`run`] directly with a hand-built `CompileUnit`.
3477/// This higher-level form is the canonical model-execution surface the
3478/// wiki commits to.
3479/// # Errors
3480/// Returns [`PipelineFailure`] from the underlying [`run`] call.
3481pub fn run_route<H, B, A, M, R, C, const INLINE_BYTES: usize>(
3482 input: M::Input,
3483 resolvers: &R,
3484 commitment: &C,
3485) -> Result<crate::enforcement::Grounded<M::Output, INLINE_BYTES>, PipelineFailure>
3486where
3487 H: crate::HostTypes,
3488 B: crate::HostBounds,
3489 A: crate::pipeline::AxisTuple<INLINE_BYTES> + crate::enforcement::Hasher,
3490 M: PrismModel<H, B, A, INLINE_BYTES, R, C>,
3491 R: crate::pipeline::ResolverTuple
3492 + crate::pipeline::HasNerveResolver<INLINE_BYTES, A>
3493 + crate::pipeline::HasChainComplexResolver<INLINE_BYTES, A>
3494 + crate::pipeline::HasHomologyGroupResolver<INLINE_BYTES, A>
3495 + crate::pipeline::HasCochainComplexResolver<INLINE_BYTES, A>
3496 + crate::pipeline::HasCohomologyGroupResolver<INLINE_BYTES, A>
3497 + crate::pipeline::HasPostnikovResolver<INLINE_BYTES, A>
3498 + crate::pipeline::HasHomotopyGroupResolver<INLINE_BYTES, A>
3499 + crate::pipeline::HasKInvariantResolver<INLINE_BYTES, A>,
3500 // Wiki ADR-048: 6th type parameter is the model's
3501 // `TypedCommitment` — prism's cost-model commitment surface.
3502 // The catamorphism evaluates `commitment.evaluate(kappa_label)`
3503 // after the resolver-bound κ-label is emitted.
3504 C: crate::pipeline::TypedCommitment,
3505{
3506 // ADR-022 D5: read the route's term-tree arena from the model's
3507 // `Route` (the macro-emitted witness; identity-route returns &[]),
3508 // build a `Validated<CompileUnit, FinalPhase>` whose root_term is
3509 // exactly that arena, and dispatch to `run` (the catamorphism).
3510 let arena_slice = <M::Route as FoundationClosed<INLINE_BYTES>>::arena_slice();
3511 // ADR-023: serialize the runtime input value into a transient
3512 // `Binding` for the route's input-parameter slot
3513 // (`Term::Variable { name_index: 0 }`, ADR-022 D3 G2). The buffer
3514 // ceiling is the foundation-side `INLINE_BYTES`
3515 // (stable-Rust equivalent of nightly's
3516 // `[u8; <M::Input as IntoBindingValue>::MAX_BYTES]` form).
3517 let max_bytes = <M::Input as IntoBindingValue>::MAX_BYTES;
3518 if max_bytes > INLINE_BYTES {
3519 // Per ADR-023: inputs whose declared MAX_BYTES exceeds the
3520 // foundation-side ceiling are rejected — the canonical content
3521 // address cannot be derived without a buffer big enough for
3522 // the value's full byte sequence.
3523 return Err(PipelineFailure::ShapeViolation {
3524 report: crate::enforcement::ShapeViolation {
3525 shape_iri: "https://uor.foundation/pipeline/RouteInputBufferShape",
3526 constraint_iri: "https://uor.foundation/pipeline/RouteInputBufferShape/maxBytes",
3527 property_iri: "https://uor.foundation/pipeline/inputMaxBytes",
3528 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
3529 min_count: 0,
3530 max_count: INLINE_BYTES as u32,
3531 kind: crate::ViolationKind::ValueCheck,
3532 },
3533 });
3534 }
3535 let mut buf = [0u8; INLINE_BYTES];
3536 let written = input
3537 .into_binding_bytes(&mut buf[..max_bytes])
3538 .map_err(|report| PipelineFailure::ShapeViolation { report })?;
3539 // Hash the canonical bytes through the application's selected
3540 // `Hasher` (substitution axis A). The fold output is truncated to
3541 // u64 for the `Binding.content_address` carrier, matching the
3542 // `to_binding_entry` convention foundation already uses for static
3543 // bindings (`ContentAddress::from_u64_fingerprint`).
3544 let mut hasher = <A as crate::enforcement::Hasher>::initial();
3545 hasher = hasher.fold_bytes(&buf[..written]);
3546 let digest = hasher.finalize();
3547 let content_address: u64 = u64::from_be_bytes([
3548 digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
3549 ]);
3550 // Build the transient binding for the route's input slot. The
3551 // `name_index = 0` sentinel is the route-input slot per ADR-022 D3
3552 // G2; `type_index = 0` is the foundation-conventional zero handle
3553 // (the input's `ConstrainedTypeShape::IRI` is foundation-internal
3554 // and not consumed by the binding-signature fold).
3555 let transient_input = [crate::enforcement::Binding {
3556 name_index: 0,
3557 type_index: 0,
3558 value_index: 0,
3559 surface: <M::Input as ConstrainedTypeShape>::IRI,
3560 content_address,
3561 }];
3562 // Foundation defaults for unit-level parameters that are not part
3563 // of the Route's term-tree. The Witt-level ceiling and
3564 // thermodynamic budget come from the application's `HostBounds`
3565 // selection (ADR-018) — `B::WITT_LEVEL_MAX_BITS` caps the level,
3566 // and a budget large enough to admit any in-bounds route avoids
3567 // false-positive solvency rejections. `target_domains` is
3568 // `Enumerative` because the arena is a finite term tree.
3569 static TARGET_DOMAINS: &[crate::VerificationDomain] = &[crate::VerificationDomain::Enumerative];
3570 let level = match B::WITT_LEVEL_MAX_BITS {
3571 bits if bits >= 32 => crate::WittLevel::W32,
3572 bits if bits >= 24 => crate::WittLevel::W24,
3573 bits if bits >= 16 => crate::WittLevel::W16,
3574 _ => crate::WittLevel::W8,
3575 };
3576 let unit = CompileUnitBuilder::new()
3577 .root_term(arena_slice)
3578 .bindings(&transient_input)
3579 .witt_level_ceiling(level)
3580 .thermodynamic_budget(1024)
3581 .target_domains(TARGET_DOMAINS)
3582 .result_type::<M::Output>()
3583 .validate()
3584 .map_err(|report| PipelineFailure::ShapeViolation { report })?;
3585 // ADR-028: reject Output shapes that would overflow the foundation
3586 // ceiling. Parallel to ADR-023's input-side check, but checked
3587 // against the Output-side `IntoBindingValue::MAX_BYTES`.
3588 let out_max = <M::Output as IntoBindingValue>::MAX_BYTES;
3589 if out_max > INLINE_BYTES {
3590 return Err(PipelineFailure::ShapeViolation {
3591 report: crate::enforcement::ShapeViolation {
3592 shape_iri: "https://uor.foundation/pipeline/RouteOutputBufferShape",
3593 constraint_iri: "https://uor.foundation/pipeline/RouteOutputBufferShape/maxBytes",
3594 property_iri: "https://uor.foundation/pipeline/outputMaxBytes",
3595 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
3596 min_count: 0,
3597 max_count: INLINE_BYTES as u32,
3598 kind: crate::ViolationKind::ValueCheck,
3599 },
3600 });
3601 }
3602 // ADR-029: evaluate the route's Term tree as a structural fold.
3603 // The catamorphism's output bytes flow into the Grounded's
3604 // output payload (ADR-028).
3605 let evaluation =
3606 evaluate_term_tree::<A, R, INLINE_BYTES>(arena_slice, &buf[..written], resolvers)?;
3607 // Wiki ADR-048: post-resolver typed-bandwidth admission. The
3608 // catamorphism evaluates the model's `C: TypedCommitment` on the
3609 // κ-label byte sequence (the route's evaluated output, which for the
3610 // canonical k-invariants branch IS the `Term::KInvariants` emission
3611 // per ADR-035). On `evaluate(...) == false`, return
3612 // `PipelineFailure::ShapeViolation` so applications observe the failed-
3613 // commitment branch deterministically (the trace records the
3614 // `CommitmentEvaluated` event with `result: false`; the
3615 // verifier replays the same evaluation).
3616 let kappa_bytes = evaluation.bytes();
3617 if !commitment.evaluate(kappa_bytes) {
3618 return Err(PipelineFailure::ShapeViolation {
3619 report: crate::enforcement::ShapeViolation {
3620 shape_iri: "https://uor.foundation/commitment/TypedCommitment/VIOLATED",
3621 constraint_iri: "https://uor.foundation/commitment/TypedCommitment",
3622 property_iri: "https://uor.foundation/commitment/evaluate",
3623 expected_range: "http://www.w3.org/2001/XMLSchema#boolean",
3624 min_count: 1,
3625 max_count: 1,
3626 kind: crate::ViolationKind::ValueCheck,
3627 },
3628 });
3629 }
3630 let grounded = run::<M::Output, _, A, INLINE_BYTES>(unit)?;
3631 Ok(grounded.with_output_bytes(evaluation.bytes()))
3632}
3633
3634/// ADR-060: foundation-fixed upper bound on a `Hasher`'s ASCII identifier
3635/// width, used in the κ-label inline-width derivation
3636/// ([`carrier_inline_bytes`]). The κ-label ASCII form is
3637/// `identifier ++ ":" ++ hex(digest)`; the hex doubling of the digest
3638/// dominates, but the identifier term is included so the derived inline
3639/// width admits the full κ-label for every conforming `Hasher`. This is a
3640/// wire-format-adjacent constant (ADR-018 carve-out): a single shared
3641/// upper bound, not an application-policy capacity.
3642pub const HASHER_IDENTIFIER_BYTES: usize = 32;
3643
3644/// ADR-060: foundation-fixed per-site wire width for the ψ-stage
3645/// structural serializations (SimplicialComplex / ChainComplex / …).
3646pub const SITE_DESCRIPTOR_BYTES: usize = 8;
3647
3648/// ADR-060: foundation-fixed per-constraint wire width for the ψ-stage
3649/// structural serializations.
3650pub const CONSTRAINT_DESCRIPTOR_BYTES: usize = 16;
3651
3652/// ADR-060: foundation-fixed per-Betti-dimension wire width for the
3653/// homology/cohomology ψ-stage serializations.
3654pub const BETTI_ELEMENT_BYTES: usize = 8;
3655
3656/// ADR-060: foundation-fixed per-stage wire-format header width shared by
3657/// the ψ-stage structural serializations (stage tag + element count).
3658pub const PSI_STAGE_HEADER_BYTES: usize = 16;
3659
3660/// ADR-060: const-fn maximum of three `usize` values. Used by
3661/// [`carrier_inline_bytes`]; admissible in array-length position on stable
3662/// Rust because it is a plain `const fn` (not `generic_const_exprs`).
3663#[must_use]
3664pub const fn max3(a: usize, b: usize, c: usize) -> usize {
3665 let ab = if a > b { a } else { b };
3666 if ab > c {
3667 ab
3668 } else {
3669 c
3670 }
3671}
3672
3673/// ADR-060: foundation-derived inline-carrier width for `TermValue::Inline`,
3674/// computed from the application's `HostBounds` primitives — never declared.
3675/// The inline carrier admits every value class that flows inline: an integer
3676/// literal at the application's maximum Witt level
3677/// (`WITT_LEVEL_MAX_BITS / 8` bytes), a cryptographic digest at the maximum
3678/// fingerprint width (`FINGERPRINT_MAX_BYTES`), and the κ-label ASCII
3679/// serialization (`HASHER_IDENTIFIER_BYTES + 1 + 2 × FINGERPRINT_MAX_BYTES` —
3680/// the hex-encoded digest doubles the fingerprint width). The derived width
3681/// is the maximum over all three; larger structural / unbounded payloads flow
3682/// as `TermValue::Borrowed` / `TermValue::Stream` with no carrier-side ceiling.
3683/// Applications instantiate carrier-bearing types at
3684/// `carrier_inline_bytes::<MyBounds>()` (a concrete `const` at the
3685/// application boundary, where `MyBounds` is concrete — stable Rust, no
3686/// `generic_const_exprs`).
3687#[must_use]
3688pub const fn carrier_inline_bytes<B: crate::HostBounds>() -> usize {
3689 let witt_bytes = B::WITT_LEVEL_MAX_BITS as usize / 8;
3690 let kappa_bytes = HASHER_IDENTIFIER_BYTES + 1 + 2 * B::FINGERPRINT_MAX_BYTES;
3691 max3(witt_bytes, B::FINGERPRINT_MAX_BYTES, kappa_bytes)
3692}
3693
3694/// ADR-060: app-facing carrier-width helper for the Nerve (ψ_1) ψ-stage. An
3695/// application's resolver impl uses it to size its resolver-owned scratch
3696/// (the `Borrowed` carrier's backing) for this stage.
3697/// Structural-element-count × foundation-fixed per-element wire width.
3698#[must_use]
3699pub const fn nerve_carrier_bytes<B: crate::HostBounds>() -> usize {
3700 PSI_STAGE_HEADER_BYTES
3701 + B::NERVE_SITES_MAX * SITE_DESCRIPTOR_BYTES
3702 + B::NERVE_CONSTRAINTS_MAX * CONSTRAINT_DESCRIPTOR_BYTES
3703}
3704
3705/// ADR-060: app-facing carrier-width helper for the ChainComplex (ψ_2) ψ-stage. An
3706/// application's resolver impl uses it to size its resolver-owned scratch
3707/// (the `Borrowed` carrier's backing) for this stage.
3708/// Structural-element-count × foundation-fixed per-element wire width.
3709#[must_use]
3710pub const fn chain_complex_carrier_bytes<B: crate::HostBounds>() -> usize {
3711 PSI_STAGE_HEADER_BYTES + B::BETTI_DIMENSION_MAX * (SITE_DESCRIPTOR_BYTES + BETTI_ELEMENT_BYTES)
3712}
3713
3714/// ADR-060: app-facing carrier-width helper for the HomologyGroups (ψ_3) ψ-stage. An
3715/// application's resolver impl uses it to size its resolver-owned scratch
3716/// (the `Borrowed` carrier's backing) for this stage.
3717/// Structural-element-count × foundation-fixed per-element wire width.
3718#[must_use]
3719pub const fn homology_groups_carrier_bytes<B: crate::HostBounds>() -> usize {
3720 PSI_STAGE_HEADER_BYTES + B::BETTI_DIMENSION_MAX * BETTI_ELEMENT_BYTES
3721}
3722
3723/// ADR-060: app-facing carrier-width helper for the CochainComplex (ψ_5) ψ-stage. An
3724/// application's resolver impl uses it to size its resolver-owned scratch
3725/// (the `Borrowed` carrier's backing) for this stage.
3726/// Structural-element-count × foundation-fixed per-element wire width.
3727#[must_use]
3728pub const fn cochain_complex_carrier_bytes<B: crate::HostBounds>() -> usize {
3729 PSI_STAGE_HEADER_BYTES + B::BETTI_DIMENSION_MAX * (SITE_DESCRIPTOR_BYTES + BETTI_ELEMENT_BYTES)
3730}
3731
3732/// ADR-060: app-facing carrier-width helper for the CohomologyGroups (ψ_6) ψ-stage. An
3733/// application's resolver impl uses it to size its resolver-owned scratch
3734/// (the `Borrowed` carrier's backing) for this stage.
3735/// Structural-element-count × foundation-fixed per-element wire width.
3736#[must_use]
3737pub const fn cohomology_groups_carrier_bytes<B: crate::HostBounds>() -> usize {
3738 PSI_STAGE_HEADER_BYTES + B::BETTI_DIMENSION_MAX * BETTI_ELEMENT_BYTES
3739}
3740
3741/// ADR-060: app-facing carrier-width helper for the PostnikovTower (ψ_7) ψ-stage. An
3742/// application's resolver impl uses it to size its resolver-owned scratch
3743/// (the `Borrowed` carrier's backing) for this stage.
3744/// Structural-element-count × foundation-fixed per-element wire width.
3745#[must_use]
3746pub const fn postnikov_tower_carrier_bytes<B: crate::HostBounds>() -> usize {
3747 PSI_STAGE_HEADER_BYTES + B::BETTI_DIMENSION_MAX * (SITE_DESCRIPTOR_BYTES + BETTI_ELEMENT_BYTES)
3748}
3749
3750/// ADR-060: app-facing carrier-width helper for the HomotopyGroups (ψ_8) ψ-stage. An
3751/// application's resolver impl uses it to size its resolver-owned scratch
3752/// (the `Borrowed` carrier's backing) for this stage.
3753/// Structural-element-count × foundation-fixed per-element wire width.
3754#[must_use]
3755pub const fn homotopy_groups_carrier_bytes<B: crate::HostBounds>() -> usize {
3756 PSI_STAGE_HEADER_BYTES + B::BETTI_DIMENSION_MAX * BETTI_ELEMENT_BYTES
3757}
3758
3759/// ADR-060: app-facing carrier-width helper for the KInvariants (ψ_9) ψ-stage. An
3760/// application's resolver impl uses it to size its resolver-owned scratch
3761/// (the `Borrowed` carrier's backing) for this stage.
3762/// Structural-element-count × foundation-fixed per-element wire width.
3763#[must_use]
3764pub const fn k_invariants_carrier_bytes<B: crate::HostBounds>() -> usize {
3765 carrier_inline_bytes::<B>()
3766}
3767
3768/// Wiki ADR-029: name-index sentinel used by `prism_model!` G7 emission to
3769/// mark `recurse(measure, base, |self| step)`'s self-identifier reference.
3770/// When the catamorphism encounters `Term::Variable { name_index: <this> }`
3771/// during step-body evaluation, it returns the previous iteration's result
3772/// (the `recurse_value` parameter threaded through `evaluate_term_at`) — the
3773/// fresh-name-indexed Variable ADR-029 specifies for the recursive-call
3774/// placeholder.
3775/// Foundation reserves the upper sentinels: `u32::MAX` is the wildcard arm
3776/// for `Term::Match` (ADR-022 D3 G6) and the default-propagation handler for
3777/// `Term::Try` (G9). `RECURSE_PLACEHOLDER_NAME_INDEX = u32::MAX - 1`.
3778pub const RECURSE_PLACEHOLDER_NAME_INDEX: u32 = u32::MAX - 1;
3779
3780/// Wiki ADR-022 D3 G8 + ADR-029: the fresh-name-indexed Variable that
3781/// `prism_model!` emits in place of an `unfold(seed, |state, …| step)`
3782/// closure's state-ident references. The catamorphism's `Term::Unfold`
3783/// fold-rule binds this name to the unfold's current state value
3784/// (threaded through `evaluate_term_at` as the `unfold_value` parameter)
3785/// and iterates step until a Kleene fixpoint or [`UNFOLD_MAX_ITERATIONS`].
3786pub const UNFOLD_PLACEHOLDER_NAME_INDEX: u32 = u32::MAX - 2;
3787
3788/// Wiki ADR-034 Mechanism 1: the foundation-fixed name-index for the
3789/// iteration-counter binding inside `Term::Recurse`'s step body. The
3790/// two-parameter closure form `recurse(measure, base, |self_ident, idx_ident| step)`
3791/// lowers `idx_ident` references to
3792/// `Term::Variable { name_index: RECURSE_IDX_NAME_INDEX }`; the
3793/// catamorphism's per-variant fold-rule binds it to a `TermValue`
3794/// carrying the current measure value at each descent.
3795pub const RECURSE_IDX_NAME_INDEX: u32 = u32::MAX - 3;
3796
3797/// Wiki ADR-034 Mechanism 2: the foundation-fixed name-index for the
3798/// candidate-value binding inside `Term::FirstAdmit`'s predicate body.
3799/// The grammar form `first_admit(<domain>, |idx_ident| <pred>)` lowers
3800/// `idx_ident` references to
3801/// `Term::Variable { name_index: FIRST_ADMIT_IDX_NAME_INDEX }`; the
3802/// catamorphism's per-variant fold-rule binds it to the current
3803/// candidate `idx` (ranging `0..<Domain>::CYCLE_SIZE`).
3804pub const FIRST_ADMIT_IDX_NAME_INDEX: u32 = u32::MAX - 4;
3805
3806/// Wiki ADR-029: bound on the anamorphic fixpoint iteration for
3807/// `Term::Unfold`. The fold rule iterates `step(state)` until either the
3808/// state reaches a Kleene fixpoint (`step(state) == state`) or this
3809/// ceiling is hit, at which point evaluation returns the most-recent
3810/// state. Foundation-fixed (parallel to `FOLD_UNROLL_THRESHOLD`).
3811/// Wiki ADR-037: a foundation-fixed conservative default for
3812/// [`HostBounds::UNFOLD_ITERATIONS_MAX`].
3813pub const UNFOLD_MAX_ITERATIONS: usize = 256;
3814
3815/// ADR-060: a chunk-emitting source for unbounded `TermValue` payloads
3816/// (tensor-data sections, large signing payloads, multi-GB messages). The
3817/// `Stream` carrier references a `&dyn ChunkSource`; its carrier width is the
3818/// dyn-trait descriptor size — payload-independent, with no byte-width ceiling.
3819/// The σ-projection at ψ_9 folds `Stream` chunks directly into the `Hasher`
3820/// via `Hasher::fold_bytes`; structural fold-rules read the chunks as a flat
3821/// byte sequence via [`ChunkSource::for_each_chunk`]. Peak resident memory is
3822/// `carrier_inline_bytes::<B>()` plus the source's internal state — never the
3823/// full canonical byte sequence.
3824pub trait ChunkSource: core::fmt::Debug {
3825 /// Fold each chunk into the closure in canonical order. The total folded
3826 /// byte count is unbounded; each `&[u8]` slice is valid only for the
3827 /// duration of the call (not retained).
3828 fn for_each_chunk(&self, f: &mut dyn FnMut(&[u8]));
3829 /// Total byte count if statically known, else `None`.
3830 fn total_bytes(&self) -> Option<usize> {
3831 None
3832 }
3833}
3834
3835/// Wiki ADR-029 + ADR-060: a single Term variant's evaluated value, carried
3836/// as a **source-polymorphic** carrier const-generic over its inline width.
3837/// ADR-060 replaces the pre-0.5.0 fixed-4096-byte buffer with three variants:
3838/// - `Inline` — a stack buffer of `INLINE_BYTES` (foundation-derived via
3839/// [`carrier_inline_bytes`]) carrying derived structural content: integer
3840/// literals at any admitted Witt level, cryptographic digests at the
3841/// application's hasher output width, the κ-label ASCII form, and
3842/// primitive-op single-value outputs.
3843/// - `Borrowed` — a slice into an upstream byte source (input bytes, a
3844/// sibling ψ-stage's scratch, an axis-kernel output region). Its carrier
3845/// width is `size_of::<&[u8]>()`, independent of payload size.
3846/// - `Stream` — a chunk-emitting source for unbounded payloads, no ceiling.
3847///
3848/// `INLINE_BYTES` is a free const-generic parameter; the application
3849/// instantiates it at the boundary via `carrier_inline_bytes::<MyBounds>()`
3850/// (per the min-const-generics pattern, stable Rust, no `generic_const_exprs`).
3851#[derive(Debug, Clone, Copy)]
3852pub enum TermValue<'a, const INLINE_BYTES: usize> {
3853 /// Stack-allocated inline buffer (zero-padded beyond `len`).
3854 Inline {
3855 /// Fixed-capacity inline byte buffer.
3856 bytes: [u8; INLINE_BYTES],
3857 /// Active prefix length (`<= INLINE_BYTES`).
3858 len: usize,
3859 },
3860 /// Borrowed slice into an upstream byte source — no byte-width ceiling.
3861 Borrowed(&'a [u8]),
3862 /// Chunk-emitting source for unbounded payloads — no byte-width ceiling.
3863 Stream(&'a dyn ChunkSource),
3864}
3865
3866impl<'a, const INLINE_BYTES: usize> PartialEq for TermValue<'a, INLINE_BYTES> {
3867 fn eq(&self, other: &Self) -> bool {
3868 match (self, other) {
3869 (TermValue::Stream(a), TermValue::Stream(b)) => core::ptr::eq(
3870 (*a as *const dyn ChunkSource).cast::<u8>(),
3871 (*b as *const dyn ChunkSource).cast::<u8>(),
3872 ),
3873 (TermValue::Stream(_), _) | (_, TermValue::Stream(_)) => false,
3874 _ => self.as_slice() == other.as_slice(),
3875 }
3876 }
3877}
3878impl<'a, const INLINE_BYTES: usize> Eq for TermValue<'a, INLINE_BYTES> {}
3879
3880impl<'a, const INLINE_BYTES: usize> TermValue<'a, INLINE_BYTES> {
3881 /// Construct an empty inline `TermValue` (length zero).
3882 #[must_use]
3883 pub const fn empty() -> Self {
3884 TermValue::Inline {
3885 bytes: [0u8; INLINE_BYTES],
3886 len: 0,
3887 }
3888 }
3889
3890 /// ADR-060: construct an `Inline` `TermValue` by copying up to
3891 /// `INLINE_BYTES` bytes from `bytes`. For payloads exceeding the inline
3892 /// width use [`TermValue::Borrowed`] / [`TermValue::Stream`] instead.
3893 #[must_use]
3894 pub const fn inline_from_slice(bytes: &[u8]) -> Self {
3895 let mut buf = [0u8; INLINE_BYTES];
3896 let copy_len = if bytes.len() > INLINE_BYTES {
3897 INLINE_BYTES
3898 } else {
3899 bytes.len()
3900 };
3901 let mut i = 0;
3902 while i < copy_len {
3903 buf[i] = bytes[i];
3904 i += 1;
3905 }
3906 TermValue::Inline {
3907 bytes: buf,
3908 len: copy_len,
3909 }
3910 }
3911
3912 /// Construct a zero-copy `Borrowed` `TermValue` referencing `bytes`.
3913 #[must_use]
3914 pub const fn borrowed(bytes: &'a [u8]) -> Self {
3915 TermValue::Borrowed(bytes)
3916 }
3917
3918 /// Construct a `Stream` `TermValue` over an unbounded chunk source.
3919 #[must_use]
3920 pub const fn stream(source: &'a dyn ChunkSource) -> Self {
3921 TermValue::Stream(source)
3922 }
3923
3924 /// ADR-051: construct an `Inline` `TermValue` from a u64 at a declared byte width.
3925 #[must_use]
3926 pub const fn from_u64_be(value: u64, width: usize) -> Self {
3927 let w = if width > 8 { 8 } else { width };
3928 let be = value.to_be_bytes();
3929 let mut buf = [0u8; INLINE_BYTES];
3930 let cap = if w > INLINE_BYTES { INLINE_BYTES } else { w };
3931 let mut i = 0;
3932 while i < cap {
3933 buf[i] = be[8 - w + i];
3934 i += 1;
3935 }
3936 TermValue::Inline {
3937 bytes: buf,
3938 len: cap,
3939 }
3940 }
3941
3942 /// ADR-060: const-constructor from a prepared inline buffer of width `INLINE_BYTES`.
3943 #[must_use]
3944 pub const fn from_inline_const(bytes: &[u8; INLINE_BYTES], len: usize) -> Self {
3945 let l = if len > INLINE_BYTES {
3946 INLINE_BYTES
3947 } else {
3948 len
3949 };
3950 TermValue::Inline {
3951 bytes: *bytes,
3952 len: l,
3953 }
3954 }
3955
3956 /// Returns the active byte prefix for materializable carriers
3957 /// (`Inline` / `Borrowed`), or `None` for `Stream` (which has no single
3958 /// contiguous slice — read it via [`TermValue::for_each_chunk`]).
3959 #[inline]
3960 #[must_use]
3961 pub fn as_slice(&self) -> Option<&[u8]> {
3962 match self {
3963 TermValue::Inline { bytes, len } => Some(&bytes[..*len]),
3964 TermValue::Borrowed(s) => Some(s),
3965 TermValue::Stream(_) => None,
3966 }
3967 }
3968
3969 /// Returns the active byte prefix for `Inline` / `Borrowed`, or the empty
3970 /// slice for `Stream`. Structural fold-rules that never produce `Stream`
3971 /// use this; `Stream`-aware consumers use [`TermValue::for_each_chunk`].
3972 #[inline]
3973 #[must_use]
3974 pub fn bytes(&self) -> &[u8] {
3975 match self {
3976 TermValue::Inline { bytes, len } => &bytes[..*len],
3977 TermValue::Borrowed(s) => s,
3978 TermValue::Stream(_) => &[],
3979 }
3980 }
3981
3982 /// ADR-060: fold every byte of the carrier into `f` in canonical order,
3983 /// dispatching on the variant — `Inline` / `Borrowed` emit a single chunk,
3984 /// `Stream` delegates to [`ChunkSource::for_each_chunk`]. This is the
3985 /// universal reader the σ-projection folds through `Hasher::fold_bytes`.
3986 pub fn for_each_chunk(&self, f: &mut dyn FnMut(&[u8])) {
3987 match self {
3988 TermValue::Inline { bytes, len } => f(&bytes[..*len]),
3989 TermValue::Borrowed(s) => f(s),
3990 TermValue::Stream(src) => src.for_each_chunk(f),
3991 }
3992 }
3993
3994 /// Total byte length if known: `Inline`/`Borrowed` always; `Stream` only
3995 /// when its source reports [`ChunkSource::total_bytes`].
3996 #[must_use]
3997 pub fn len_hint(&self) -> Option<usize> {
3998 match self {
3999 TermValue::Inline { len, .. } => Some(*len),
4000 TermValue::Borrowed(s) => Some(s.len()),
4001 TermValue::Stream(src) => src.total_bytes(),
4002 }
4003 }
4004
4005 /// ADR-060: opt-in, `alloc`-gated materialization into an owned buffer —
4006 /// the only allocation surface, per caller. Folds every chunk into a
4007 /// `Vec<u8>` (works for all three variants, including `Stream`).
4008 #[cfg(feature = "alloc")]
4009 #[must_use]
4010 pub fn to_vec(&self) -> alloc::vec::Vec<u8> {
4011 let mut out = alloc::vec::Vec::new();
4012 self.for_each_chunk(&mut |chunk| out.extend_from_slice(chunk));
4013 out
4014 }
4015}
4016
4017/// ADR-051: construct a `Term::Literal` from a u64 value at a declared Witt level.
4018/// Packs `value` as a big-endian byte sequence whose length equals the level's
4019/// byte width (`level.witt_length() / 8`). For widths > 8 bytes, the high bytes
4020/// are zero-padded.
4021#[must_use]
4022pub const fn literal_u64<const INLINE_BYTES: usize>(
4023 value: u64,
4024 level: crate::WittLevel,
4025) -> crate::enforcement::Term<'static, INLINE_BYTES> {
4026 let mut width = (level.witt_length() / 8) as usize;
4027 if width == 0 {
4028 width = 1;
4029 }
4030 // ADR-060: literals fit inline by construction (carrier_inline_bytes >=
4031 // WITT_LEVEL_MAX_BITS/8); clamp defensively so const eval never indexes
4032 // past the derived inline width.
4033 if width > INLINE_BYTES {
4034 width = INLINE_BYTES;
4035 }
4036 let be = value.to_be_bytes();
4037 let mut buf = [0u8; INLINE_BYTES];
4038 // Pack the u64's big-endian bytes into the low `min(width, 8)` bytes
4039 // of the result, right-aligned. Widths > 8 zero-pad the high portion.
4040 let take = if width > 8 { 8 } else { width };
4041 let mut i = 0;
4042 while i < take {
4043 buf[width - take + i] = be[8 - take + i];
4044 i += 1;
4045 }
4046 crate::enforcement::Term::Literal {
4047 value: TermValue::from_inline_const(&buf, width),
4048 level,
4049 }
4050}
4051
4052/// ADR-051: construct a `Term::Literal` from raw bytes at a declared Witt level.
4053/// `bytes` MUST have exactly `level.witt_length() / 8` bytes; mismatched lengths
4054/// are silently truncated/padded by `TermValue::inline_from_slice`. Use this for
4055/// wide-Witt literals (W128+) that don't fit in a u64 (they fit the
4056/// foundation-derived inline width per ADR-060).
4057#[must_use]
4058pub const fn literal_bytes<const INLINE_BYTES: usize>(
4059 bytes: &[u8],
4060 level: crate::WittLevel,
4061) -> crate::enforcement::Term<'static, INLINE_BYTES> {
4062 crate::enforcement::Term::Literal {
4063 value: TermValue::inline_from_slice(bytes),
4064 level,
4065 }
4066}
4067
4068/// Wiki ADR-029: catamorphism evaluator over the route's Term tree.
4069/// Per-variant fold rules:
4070/// - `Term::Literal { value, level }` — emit `value` as big-endian bytes at the
4071/// byte width of `level`.
4072/// - `Term::Variable { name_index }` — `name_index = 0` returns the route
4073/// input bytes; other indices look up `let`-introduced bindings (current
4074/// iteration: not yet supported, returns the input bytes for any non-zero
4075/// index).
4076/// - `Term::Application { operator, args }` — evaluate each arg and apply
4077/// the `PrimitiveOp` per its algebraic rule.
4078/// - `Term::Lift { operand, target }` — evaluate operand, zero-extend to
4079/// `target` Witt level's byte width.
4080/// - `Term::Project { operand, target }` — evaluate operand, truncate to
4081/// `target` Witt level's byte width.
4082/// - `Term::Match { scrutinee, arms }` — evaluate scrutinee, match arms by
4083/// literal-byte equality (wildcard arm `name_index = u32::MAX` matches
4084/// unconditionally).
4085/// - `Term::Recurse { measure, base, step }` — bounded recursion: evaluate
4086/// measure → n; if n = 0 evaluate base; otherwise iterate step n times with
4087/// the recursive-call placeholder bound to the previous iteration's result.
4088/// - `Term::Unfold { seed, step }` — anamorphism: evaluate seed → state₀;
4089/// iterate step (with the state placeholder bound to the current state)
4090/// until a Kleene fixpoint or `UNFOLD_MAX_ITERATIONS` is reached.
4091/// - `Term::Try { body, handler }` — evaluate body; on failure, propagate
4092/// if `handler_index = u32::MAX`, otherwise evaluate handler.
4093/// - `Term::AxisInvocation { axis_index, kernel_id, input_index }` — dispatch
4094/// the input's bytes to the application's `AxisTuple` (ADR-030); the
4095/// foundation-canonical (axis_index=0, kernel_id=0) folds through the
4096/// selected `Hasher` impl. Replaces the legacy `HasherProjection` variant.
4097/// # Errors
4098/// Returns [`PipelineFailure`] when the term tree is malformed (out-of-bounds
4099/// index, level mismatch, exhausted match without wildcard arm, etc.).
4100pub fn evaluate_term_tree<'a, A, R, const INLINE_BYTES: usize>(
4101 arena: &'a [crate::enforcement::Term<'a, INLINE_BYTES>],
4102 input_bytes: &'a [u8],
4103 resolvers: &'a R,
4104) -> Result<TermValue<'a, INLINE_BYTES>, PipelineFailure>
4105where
4106 A: crate::pipeline::AxisTuple<INLINE_BYTES> + crate::enforcement::Hasher + 'a,
4107 R: crate::pipeline::ResolverTuple
4108 + crate::pipeline::HasNerveResolver<INLINE_BYTES, A>
4109 + crate::pipeline::HasChainComplexResolver<INLINE_BYTES, A>
4110 + crate::pipeline::HasHomologyGroupResolver<INLINE_BYTES, A>
4111 + crate::pipeline::HasCochainComplexResolver<INLINE_BYTES, A>
4112 + crate::pipeline::HasCohomologyGroupResolver<INLINE_BYTES, A>
4113 + crate::pipeline::HasPostnikovResolver<INLINE_BYTES, A>
4114 + crate::pipeline::HasHomotopyGroupResolver<INLINE_BYTES, A>
4115 + crate::pipeline::HasKInvariantResolver<INLINE_BYTES, A>,
4116{
4117 if arena.is_empty() {
4118 // ADR-060: identity route — output is the input bytes by reference
4119 // (zero-copy `Borrowed`, no inline-width ceiling so arbitrarily large
4120 // inputs pass through).
4121 return Ok(TermValue::borrowed(input_bytes));
4122 }
4123 // Canonical convention: the root term is the last entry in the
4124 // arena (the `prism_model!` macro emits in post-order, so the root
4125 // is the final node).
4126 let root_idx = arena.len() - 1;
4127 evaluate_term_at::<A, R, INLINE_BYTES>(
4128 arena,
4129 root_idx,
4130 input_bytes,
4131 None,
4132 None,
4133 None,
4134 None,
4135 resolvers,
4136 )
4137}
4138
4139#[allow(clippy::too_many_arguments)]
4140fn evaluate_term_at<'a, 'b, A, R, const INLINE_BYTES: usize>(
4141 arena: &'a [crate::enforcement::Term<'a, INLINE_BYTES>],
4142 idx: usize,
4143 input_bytes: &'a [u8],
4144 recurse_value: Option<&'b [u8]>,
4145 recurse_idx_value: Option<&'b [u8]>,
4146 unfold_value: Option<&'b [u8]>,
4147 first_admit_idx_value: Option<&'b [u8]>,
4148 resolvers: &'a R,
4149) -> Result<TermValue<'a, INLINE_BYTES>, PipelineFailure>
4150where
4151 A: crate::pipeline::AxisTuple<INLINE_BYTES> + crate::enforcement::Hasher + 'a,
4152 R: crate::pipeline::ResolverTuple
4153 + crate::pipeline::HasNerveResolver<INLINE_BYTES, A>
4154 + crate::pipeline::HasChainComplexResolver<INLINE_BYTES, A>
4155 + crate::pipeline::HasHomologyGroupResolver<INLINE_BYTES, A>
4156 + crate::pipeline::HasCochainComplexResolver<INLINE_BYTES, A>
4157 + crate::pipeline::HasCohomologyGroupResolver<INLINE_BYTES, A>
4158 + crate::pipeline::HasPostnikovResolver<INLINE_BYTES, A>
4159 + crate::pipeline::HasHomotopyGroupResolver<INLINE_BYTES, A>
4160 + crate::pipeline::HasKInvariantResolver<INLINE_BYTES, A>,
4161{
4162 if idx >= arena.len() {
4163 return Err(PipelineFailure::ShapeViolation {
4164 report: crate::enforcement::ShapeViolation {
4165 shape_iri: "https://uor.foundation/pipeline/TermArenaShape",
4166 constraint_iri: "https://uor.foundation/pipeline/TermArenaShape/inBounds",
4167 property_iri: "https://uor.foundation/pipeline/termIndex",
4168 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
4169 min_count: 0,
4170 max_count: arena.len() as u32,
4171 kind: crate::ViolationKind::ValueCheck,
4172 },
4173 });
4174 }
4175 match arena[idx] {
4176 crate::enforcement::Term::Literal { value, level: _ } => {
4177 // ADR-051 + ADR-060: the literal's value IS a source-polymorphic
4178 // carrier (`TermValue<'a, INLINE_BYTES>`, Copy) whose byte length
4179 // matches `level.witt_length() / 8`. The fold-rule returns it
4180 // directly, preserving the carrier variant.
4181 Ok(value)
4182 }
4183 crate::enforcement::Term::Variable { name_index } => {
4184 // ADR-022 D3 G2: name_index = 0 is the route input slot.
4185 // ADR-029: name_index = RECURSE_PLACEHOLDER_NAME_INDEX is the
4186 // recursive-call placeholder bound to `recurse_value`.
4187 // ADR-029: name_index = UNFOLD_PLACEHOLDER_NAME_INDEX is the
4188 // unfold state placeholder bound to `unfold_value` (the
4189 // current iteration's accumulated state — see the
4190 // `Term::Unfold` fold-rule below).
4191 // Other indices reference let-bindings (G10), which the
4192 // current macro emission resolves at expansion time via
4193 // splice into the calling arena (so the binding's value-tree
4194 // root is what the catamorphism actually walks).
4195 if name_index == RECURSE_PLACEHOLDER_NAME_INDEX {
4196 return Ok(TermValue::inline_from_slice(recurse_value.unwrap_or(&[])));
4197 }
4198 // ADR-034 Mechanism 1: iteration-counter binding for Recurse.
4199 if name_index == RECURSE_IDX_NAME_INDEX {
4200 return Ok(TermValue::inline_from_slice(
4201 recurse_idx_value.unwrap_or(&[]),
4202 ));
4203 }
4204 if name_index == UNFOLD_PLACEHOLDER_NAME_INDEX {
4205 return Ok(TermValue::inline_from_slice(unfold_value.unwrap_or(&[])));
4206 }
4207 // ADR-034 Mechanism 2: candidate-value binding for FirstAdmit.
4208 if name_index == FIRST_ADMIT_IDX_NAME_INDEX {
4209 return Ok(TermValue::inline_from_slice(
4210 first_admit_idx_value.unwrap_or(&[]),
4211 ));
4212 }
4213 Ok(TermValue::borrowed(input_bytes))
4214 }
4215 crate::enforcement::Term::Application { operator, args } => {
4216 let start = args.start as usize;
4217 let len = args.len as usize;
4218 apply_primitive_op::<A, R, INLINE_BYTES>(
4219 arena,
4220 operator,
4221 start,
4222 len,
4223 input_bytes,
4224 recurse_value,
4225 recurse_idx_value,
4226 unfold_value,
4227 first_admit_idx_value,
4228 resolvers,
4229 )
4230 }
4231 crate::enforcement::Term::Lift {
4232 operand_index,
4233 target,
4234 } => {
4235 let v = evaluate_term_at::<A, R, INLINE_BYTES>(
4236 arena,
4237 operand_index as usize,
4238 input_bytes,
4239 recurse_value,
4240 recurse_idx_value,
4241 unfold_value,
4242 first_admit_idx_value,
4243 resolvers,
4244 )?;
4245 let target_width = (target.witt_length() / 8) as usize;
4246 let target_width = if target_width > INLINE_BYTES {
4247 INLINE_BYTES
4248 } else if target_width == 0 {
4249 1
4250 } else {
4251 target_width
4252 };
4253 let mut buf = [0u8; INLINE_BYTES];
4254 // Big-endian zero-extend: pad the high bytes with zeros.
4255 let src = v.bytes();
4256 let pad = target_width.saturating_sub(src.len());
4257 let mut i = 0;
4258 while i < src.len() && pad + i < target_width {
4259 buf[pad + i] = src[i];
4260 i += 1;
4261 }
4262 Ok(TermValue::Inline {
4263 bytes: buf,
4264 len: target_width,
4265 })
4266 }
4267 crate::enforcement::Term::Project {
4268 operand_index,
4269 target,
4270 } => {
4271 let v = evaluate_term_at::<A, R, INLINE_BYTES>(
4272 arena,
4273 operand_index as usize,
4274 input_bytes,
4275 recurse_value,
4276 recurse_idx_value,
4277 unfold_value,
4278 first_admit_idx_value,
4279 resolvers,
4280 )?;
4281 let target_width = (target.witt_length() / 8) as usize;
4282 let target_width = if target_width > INLINE_BYTES {
4283 INLINE_BYTES
4284 } else if target_width == 0 {
4285 1
4286 } else {
4287 target_width
4288 };
4289 let src = v.bytes();
4290 // Big-endian truncation: take the trailing `target_width` bytes.
4291 let take_from = src.len().saturating_sub(target_width);
4292 Ok(TermValue::inline_from_slice(&src[take_from..]))
4293 }
4294 crate::enforcement::Term::Match {
4295 scrutinee_index,
4296 arms,
4297 } => {
4298 let scrutinee = evaluate_term_at::<A, R, INLINE_BYTES>(
4299 arena,
4300 scrutinee_index as usize,
4301 input_bytes,
4302 recurse_value,
4303 recurse_idx_value,
4304 unfold_value,
4305 first_admit_idx_value,
4306 resolvers,
4307 )?;
4308 let start = arms.start as usize;
4309 let count = arms.len as usize;
4310 // Arms alternate (pattern, body) per ADR-022 D3 G6.
4311 let mut i = 0usize;
4312 while i + 1 < count {
4313 let pattern_idx = start + i;
4314 let body_idx = start + i + 1;
4315 let is_wildcard = matches!(
4316 arena[pattern_idx],
4317 crate::enforcement::Term::Variable { name_index } if name_index == u32::MAX
4318 );
4319 if is_wildcard {
4320 return evaluate_term_at::<A, R, INLINE_BYTES>(
4321 arena,
4322 body_idx,
4323 input_bytes,
4324 recurse_value,
4325 recurse_idx_value,
4326 unfold_value,
4327 first_admit_idx_value,
4328 resolvers,
4329 );
4330 }
4331 let pattern_val = evaluate_term_at::<A, R, INLINE_BYTES>(
4332 arena,
4333 pattern_idx,
4334 input_bytes,
4335 recurse_value,
4336 recurse_idx_value,
4337 unfold_value,
4338 first_admit_idx_value,
4339 resolvers,
4340 )?;
4341 if pattern_val.bytes() == scrutinee.bytes() {
4342 return evaluate_term_at::<A, R, INLINE_BYTES>(
4343 arena,
4344 body_idx,
4345 input_bytes,
4346 recurse_value,
4347 recurse_idx_value,
4348 unfold_value,
4349 first_admit_idx_value,
4350 resolvers,
4351 );
4352 }
4353 i += 2;
4354 }
4355 // Per ADR-022 D3 G6 the macro enforces wildcard exhaustiveness;
4356 // a well-formed Term tree never reaches this branch.
4357 Err(PipelineFailure::ShapeViolation {
4358 report: crate::enforcement::ShapeViolation {
4359 shape_iri: "https://uor.foundation/pipeline/MatchExhaustivenessShape",
4360 constraint_iri:
4361 "https://uor.foundation/pipeline/MatchExhaustivenessShape/wildcard",
4362 property_iri: "https://uor.foundation/pipeline/matchArms",
4363 expected_range: "http://www.w3.org/2001/XMLSchema#string",
4364 min_count: 1,
4365 max_count: 0,
4366 kind: crate::ViolationKind::Missing,
4367 },
4368 })
4369 }
4370 crate::enforcement::Term::Recurse {
4371 measure_index,
4372 base_index,
4373 step_index,
4374 } => {
4375 // Wiki ADR-029 recursive fold: evaluate measure once to get N;
4376 // if N == 0 evaluate base; else iterate step N times, threading
4377 // each iteration's result as the recurse_value (the recursive-
4378 // call placeholder bound to a fresh-name-indexed Variable per
4379 // ADR-029, with the placeholder's name_index resolving via the
4380 // RECURSE_PLACEHOLDER_NAME_INDEX sentinel handled in the Variable
4381 // arm). The outer recurse_value is preserved for nested Recurse
4382 // forms within the measure/base computations; step body uses the
4383 // iteration's accumulator.
4384 let measure = evaluate_term_at::<A, R, INLINE_BYTES>(
4385 arena,
4386 measure_index as usize,
4387 input_bytes,
4388 recurse_value,
4389 recurse_idx_value,
4390 unfold_value,
4391 first_admit_idx_value,
4392 resolvers,
4393 )?;
4394 let n = bytes_to_u64_be(measure.bytes());
4395 let base_val = evaluate_term_at::<A, R, INLINE_BYTES>(
4396 arena,
4397 base_index as usize,
4398 input_bytes,
4399 recurse_value,
4400 recurse_idx_value,
4401 unfold_value,
4402 first_admit_idx_value,
4403 resolvers,
4404 )?;
4405 if n == 0 {
4406 return Ok(base_val);
4407 }
4408 // Iterate step N times. Each iteration's `current` becomes the
4409 // next iteration's recurse_value. The descent measure is the
4410 // bound; well-foundedness holds by monotonic decrease.
4411 let mut current_buf = [0u8; INLINE_BYTES];
4412 let mut current_len = base_val.bytes().len();
4413 let mut k = 0;
4414 while k < current_len {
4415 current_buf[k] = base_val.bytes()[k];
4416 k += 1;
4417 }
4418 let mut iter = 0u64;
4419 while iter < n {
4420 // ADR-034 Mechanism 1: bind RECURSE_IDX_NAME_INDEX to the
4421 // current measure value (the iteration counter at this
4422 // descent). At iter=0 the descent measure is N; at iter=k
4423 // it is N-k. The byte width follows the measure's BE-truncated
4424 // u64 packing so callers can read it as a u64-shaped value.
4425 let descent_measure: u64 = n - iter;
4426 let descent_bytes = descent_measure.to_be_bytes();
4427 let next = evaluate_term_at::<A, R, INLINE_BYTES>(
4428 arena,
4429 step_index as usize,
4430 input_bytes,
4431 Some(¤t_buf[..current_len]),
4432 Some(&descent_bytes[..]),
4433 unfold_value,
4434 first_admit_idx_value,
4435 resolvers,
4436 )?;
4437 let nb = next.bytes();
4438 let copy_len = if nb.len() > INLINE_BYTES {
4439 INLINE_BYTES
4440 } else {
4441 nb.len()
4442 };
4443 let mut j = 0;
4444 while j < copy_len {
4445 current_buf[j] = nb[j];
4446 j += 1;
4447 }
4448 current_len = copy_len;
4449 iter += 1;
4450 }
4451 Ok(TermValue::inline_from_slice(¤t_buf[..current_len]))
4452 }
4453 crate::enforcement::Term::Unfold {
4454 seed_index,
4455 step_index,
4456 } => {
4457 // ADR-029 anamorphism: evaluate seed → state₀; iterate step
4458 // with the state placeholder (UNFOLD_PLACEHOLDER_NAME_INDEX,
4459 // bound to the current state) until either a Kleene fixpoint
4460 // (step(state) == state) or UNFOLD_MAX_ITERATIONS is reached.
4461 // Well-foundedness: bounded by UNFOLD_MAX_ITERATIONS.
4462 // The outer unfold_value is preserved for nested Unfold forms
4463 // within the seed; step body's state placeholder uses the
4464 // iteration's accumulator.
4465 let seed_val = evaluate_term_at::<A, R, INLINE_BYTES>(
4466 arena,
4467 seed_index as usize,
4468 input_bytes,
4469 recurse_value,
4470 recurse_idx_value,
4471 unfold_value,
4472 first_admit_idx_value,
4473 resolvers,
4474 )?;
4475 let mut state_buf = [0u8; INLINE_BYTES];
4476 let mut state_len = seed_val.bytes().len();
4477 let mut k = 0;
4478 while k < state_len {
4479 state_buf[k] = seed_val.bytes()[k];
4480 k += 1;
4481 }
4482 let mut iter = 0usize;
4483 while iter < UNFOLD_MAX_ITERATIONS {
4484 let next = evaluate_term_at::<A, R, INLINE_BYTES>(
4485 arena,
4486 step_index as usize,
4487 input_bytes,
4488 recurse_value,
4489 recurse_idx_value,
4490 Some(&state_buf[..state_len]),
4491 first_admit_idx_value,
4492 resolvers,
4493 )?;
4494 let nb = next.bytes();
4495 // Kleene fixpoint check: if step(state) == state, return.
4496 if nb.len() == state_len && nb == &state_buf[..state_len] {
4497 return Ok(TermValue::inline_from_slice(&state_buf[..state_len]));
4498 }
4499 let copy_len = if nb.len() > INLINE_BYTES {
4500 INLINE_BYTES
4501 } else {
4502 nb.len()
4503 };
4504 let mut j = 0;
4505 while j < copy_len {
4506 state_buf[j] = nb[j];
4507 j += 1;
4508 }
4509 state_len = copy_len;
4510 iter += 1;
4511 }
4512 Ok(TermValue::inline_from_slice(&state_buf[..state_len]))
4513 }
4514 crate::enforcement::Term::Try {
4515 body_index,
4516 handler_index,
4517 } => {
4518 match evaluate_term_at::<A, R, INLINE_BYTES>(
4519 arena,
4520 body_index as usize,
4521 input_bytes,
4522 recurse_value,
4523 recurse_idx_value,
4524 unfold_value,
4525 first_admit_idx_value,
4526 resolvers,
4527 ) {
4528 Ok(v) => Ok(v),
4529 Err(e) => {
4530 if handler_index == u32::MAX {
4531 Err(e)
4532 } else {
4533 evaluate_term_at::<A, R, INLINE_BYTES>(
4534 arena,
4535 handler_index as usize,
4536 input_bytes,
4537 recurse_value,
4538 recurse_idx_value,
4539 unfold_value,
4540 first_admit_idx_value,
4541 resolvers,
4542 )
4543 }
4544 }
4545 }
4546 }
4547 crate::enforcement::Term::AxisInvocation {
4548 axis_index,
4549 kernel_id,
4550 input_index,
4551 } => {
4552 // ADR-055: read the axis's SubstrateTermBody::body_arena() via
4553 // `AxisTuple::body_arena_at`. When non-empty, recursively fold
4554 // the body with the evaluated kernel input bound as input_bytes;
4555 // when empty (primitive-fast-path interpretation), dispatch the
4556 // kernel function directly per the optional fast-path per ADR-055.
4557 //
4558 // The foundation-built blanket `impl<H: Hasher> AxisTuple for H`
4559 // routes the canonical hash dispatch (axis 0, kernel 0) through
4560 // the legacy Hasher API (empty body); user-declared axes via the
4561 // `axis!` SDK macro extend the dispatch surface to additional
4562 // (axis_index, kernel_id) combinations and may provide substrate-
4563 // Term bodies the catamorphism walks structurally.
4564 let v = evaluate_term_at::<A, R, INLINE_BYTES>(
4565 arena,
4566 input_index as usize,
4567 input_bytes,
4568 recurse_value,
4569 recurse_idx_value,
4570 unfold_value,
4571 first_admit_idx_value,
4572 resolvers,
4573 )?;
4574 let body = <A as crate::pipeline::AxisTuple<INLINE_BYTES>>::body_arena_at(axis_index);
4575 if body.is_empty() {
4576 // Primitive fast-path: dispatch the kernel function directly.
4577 let mut out = [0u8; INLINE_BYTES];
4578 let written = match <A as crate::pipeline::AxisTuple<INLINE_BYTES>>::dispatch(
4579 axis_index,
4580 kernel_id,
4581 v.bytes(),
4582 &mut out,
4583 ) {
4584 Ok(n) => n,
4585 Err(report) => return Err(PipelineFailure::ShapeViolation { report }),
4586 };
4587 let width = if written > INLINE_BYTES {
4588 INLINE_BYTES
4589 } else {
4590 written
4591 };
4592 Ok(TermValue::inline_from_slice(&out[..width]))
4593 } else {
4594 // ADR-055 recursive-fold path: walk the axis's substrate-Term
4595 // body with the evaluated kernel input bound in scope. The
4596 // body's root term is by convention the last entry in the arena.
4597 let root = body.len() - 1;
4598 // ADR-060: the kernel input `v.bytes()` is a local borrow, so the
4599 // recursively-folded body result is materialized into an owned
4600 // `Inline` carrier (it cannot outlive the local `v`).
4601 let folded = evaluate_term_at::<A, R, INLINE_BYTES>(
4602 body,
4603 root,
4604 v.bytes(),
4605 recurse_value,
4606 recurse_idx_value,
4607 unfold_value,
4608 first_admit_idx_value,
4609 resolvers,
4610 )?;
4611 Ok(TermValue::inline_from_slice(folded.bytes()))
4612 }
4613 }
4614 crate::enforcement::Term::ProjectField {
4615 source_index,
4616 byte_offset,
4617 byte_length,
4618 } => {
4619 // ADR-033 G20: evaluate source, slice [byte_offset .. byte_offset+byte_length].
4620 let v = evaluate_term_at::<A, R, INLINE_BYTES>(
4621 arena,
4622 source_index as usize,
4623 input_bytes,
4624 recurse_value,
4625 recurse_idx_value,
4626 unfold_value,
4627 first_admit_idx_value,
4628 resolvers,
4629 )?;
4630 let bytes = v.bytes();
4631 let start = byte_offset as usize;
4632 let end = start.saturating_add(byte_length as usize);
4633 if end > bytes.len() {
4634 return Err(PipelineFailure::ShapeViolation {
4635 report: crate::enforcement::ShapeViolation {
4636 shape_iri: "https://uor.foundation/pipeline/ProjectFieldShape",
4637 constraint_iri:
4638 "https://uor.foundation/pipeline/ProjectFieldShape/inBounds",
4639 property_iri: "https://uor.foundation/pipeline/byteOffset",
4640 expected_range: "https://uor.foundation/pipeline/SourceByteRange",
4641 min_count: 0,
4642 max_count: 1,
4643 kind: crate::ViolationKind::ValueCheck,
4644 },
4645 });
4646 }
4647 Ok(TermValue::inline_from_slice(&bytes[start..end]))
4648 }
4649 crate::enforcement::Term::FirstAdmit {
4650 domain_size_index,
4651 predicate_index,
4652 } => {
4653 // ADR-034 Mechanism 2: bounded search with structural early
4654 // termination. Evaluate the domain size N; iterate idx in 0..N
4655 // ascending; for each idx, evaluate predicate with
4656 // FIRST_ADMIT_IDX_NAME_INDEX bound to idx. Return on the first
4657 // non-zero predicate result (the "found" coproduct value
4658 // 0x01 || idx_bytes); after exhausting the domain return the
4659 // "not-found" coproduct value 0x00 || idx-width zero bytes.
4660 let domain_size = evaluate_term_at::<A, R, INLINE_BYTES>(
4661 arena,
4662 domain_size_index as usize,
4663 input_bytes,
4664 recurse_value,
4665 recurse_idx_value,
4666 unfold_value,
4667 first_admit_idx_value,
4668 resolvers,
4669 )?;
4670 let n = bytes_to_u64_be(domain_size.bytes());
4671 // Determine the idx byte width from the domain size's
4672 // BE-truncated u64 packing (use the smallest non-zero count of
4673 // bytes needed to represent N). For N=0 fall back to 1 byte so
4674 // the not-found sentinel still has the canonical (disc, idx) shape.
4675 let idx_byte_width: usize = if n == 0 {
4676 1
4677 } else {
4678 let mut w = 8usize;
4679 while w > 1 && (n >> ((w - 1) * 8)) == 0 {
4680 w -= 1;
4681 }
4682 w
4683 };
4684 let mut idx_iter: u64 = 0;
4685 while idx_iter < n {
4686 let idx_bytes_full = idx_iter.to_be_bytes();
4687 let idx_bytes = &idx_bytes_full[8 - idx_byte_width..];
4688 let pred_val = evaluate_term_at::<A, R, INLINE_BYTES>(
4689 arena,
4690 predicate_index as usize,
4691 input_bytes,
4692 recurse_value,
4693 recurse_idx_value,
4694 unfold_value,
4695 Some(idx_bytes),
4696 resolvers,
4697 )?;
4698 // ADR-029 zero/non-zero convention: any TermValue whose
4699 // byte sequence has at least one non-zero byte counts as
4700 // "true". An empty TermValue is treated as "false".
4701 let pb = pred_val.bytes();
4702 let mut admitted = false;
4703 let mut bi = 0;
4704 while bi < pb.len() {
4705 if pb[bi] != 0 {
4706 admitted = true;
4707 break;
4708 }
4709 bi += 1;
4710 }
4711 if admitted {
4712 let mut out_buf = [0u8; INLINE_BYTES];
4713 out_buf[0] = 0x01;
4714 let mut k = 0;
4715 while k < idx_byte_width {
4716 out_buf[1 + k] = idx_bytes[k];
4717 k += 1;
4718 }
4719 return Ok(TermValue::inline_from_slice(&out_buf[..1 + idx_byte_width]));
4720 }
4721 idx_iter += 1;
4722 }
4723 // No idx admitted — emit not-found coproduct value.
4724 let mut out_buf = [0u8; INLINE_BYTES];
4725 out_buf[0] = 0x00;
4726 // bytes 1..1+idx_byte_width remain zero (structural padding).
4727 Ok(TermValue::inline_from_slice(&out_buf[..1 + idx_byte_width]))
4728 }
4729 crate::enforcement::Term::Nerve { value_index } => {
4730 // ADR-035 + ADR-036 + ADR-060: resolver-bound ψ-Term variant.
4731 // Evaluate the operand subtree to a source-polymorphic carrier,
4732 // then dispatch it through the application's resolver via the
4733 // `Has<Category>Resolver` accessor. The resolver returns the
4734 // variant's `TermValue` directly (Inline / Borrowed / Stream);
4735 // resolver-side `Err` propagates as
4736 // `PipelineFailure::ShapeViolation` (the Null defaults emit
4737 // `RESOLVER_ABSENT`, recoverable via `Term::Try`).
4738 let operand = evaluate_term_at::<A, R, INLINE_BYTES>(
4739 arena,
4740 value_index as usize,
4741 input_bytes,
4742 recurse_value,
4743 recurse_idx_value,
4744 unfold_value,
4745 first_admit_idx_value,
4746 resolvers,
4747 )?;
4748 match resolvers.nerve_resolver().resolve(operand) {
4749 Ok(tv) => Ok(tv),
4750 Err(report) => Err(PipelineFailure::ShapeViolation { report }),
4751 }
4752 }
4753 crate::enforcement::Term::ChainComplex { simplicial_index } => {
4754 // ADR-035 + ADR-036 + ADR-060: resolver-bound ψ-Term variant.
4755 // Evaluate the operand subtree to a source-polymorphic carrier,
4756 // then dispatch it through the application's resolver via the
4757 // `Has<Category>Resolver` accessor. The resolver returns the
4758 // variant's `TermValue` directly (Inline / Borrowed / Stream);
4759 // resolver-side `Err` propagates as
4760 // `PipelineFailure::ShapeViolation` (the Null defaults emit
4761 // `RESOLVER_ABSENT`, recoverable via `Term::Try`).
4762 let operand = evaluate_term_at::<A, R, INLINE_BYTES>(
4763 arena,
4764 simplicial_index as usize,
4765 input_bytes,
4766 recurse_value,
4767 recurse_idx_value,
4768 unfold_value,
4769 first_admit_idx_value,
4770 resolvers,
4771 )?;
4772 match resolvers.chain_complex_resolver().resolve(operand) {
4773 Ok(tv) => Ok(tv),
4774 Err(report) => Err(PipelineFailure::ShapeViolation { report }),
4775 }
4776 }
4777 crate::enforcement::Term::HomologyGroups { chain_index } => {
4778 // ADR-035 + ADR-036 + ADR-060: resolver-bound ψ-Term variant.
4779 // Evaluate the operand subtree to a source-polymorphic carrier,
4780 // then dispatch it through the application's resolver via the
4781 // `Has<Category>Resolver` accessor. The resolver returns the
4782 // variant's `TermValue` directly (Inline / Borrowed / Stream);
4783 // resolver-side `Err` propagates as
4784 // `PipelineFailure::ShapeViolation` (the Null defaults emit
4785 // `RESOLVER_ABSENT`, recoverable via `Term::Try`).
4786 let operand = evaluate_term_at::<A, R, INLINE_BYTES>(
4787 arena,
4788 chain_index as usize,
4789 input_bytes,
4790 recurse_value,
4791 recurse_idx_value,
4792 unfold_value,
4793 first_admit_idx_value,
4794 resolvers,
4795 )?;
4796 match resolvers.homology_group_resolver().resolve(operand) {
4797 Ok(tv) => Ok(tv),
4798 Err(report) => Err(PipelineFailure::ShapeViolation { report }),
4799 }
4800 }
4801 crate::enforcement::Term::CochainComplex { chain_index } => {
4802 // ADR-035 + ADR-036 + ADR-060: resolver-bound ψ-Term variant.
4803 // Evaluate the operand subtree to a source-polymorphic carrier,
4804 // then dispatch it through the application's resolver via the
4805 // `Has<Category>Resolver` accessor. The resolver returns the
4806 // variant's `TermValue` directly (Inline / Borrowed / Stream);
4807 // resolver-side `Err` propagates as
4808 // `PipelineFailure::ShapeViolation` (the Null defaults emit
4809 // `RESOLVER_ABSENT`, recoverable via `Term::Try`).
4810 let operand = evaluate_term_at::<A, R, INLINE_BYTES>(
4811 arena,
4812 chain_index as usize,
4813 input_bytes,
4814 recurse_value,
4815 recurse_idx_value,
4816 unfold_value,
4817 first_admit_idx_value,
4818 resolvers,
4819 )?;
4820 match resolvers.cochain_complex_resolver().resolve(operand) {
4821 Ok(tv) => Ok(tv),
4822 Err(report) => Err(PipelineFailure::ShapeViolation { report }),
4823 }
4824 }
4825 crate::enforcement::Term::CohomologyGroups { cochain_index } => {
4826 // ADR-035 + ADR-036 + ADR-060: resolver-bound ψ-Term variant.
4827 // Evaluate the operand subtree to a source-polymorphic carrier,
4828 // then dispatch it through the application's resolver via the
4829 // `Has<Category>Resolver` accessor. The resolver returns the
4830 // variant's `TermValue` directly (Inline / Borrowed / Stream);
4831 // resolver-side `Err` propagates as
4832 // `PipelineFailure::ShapeViolation` (the Null defaults emit
4833 // `RESOLVER_ABSENT`, recoverable via `Term::Try`).
4834 let operand = evaluate_term_at::<A, R, INLINE_BYTES>(
4835 arena,
4836 cochain_index as usize,
4837 input_bytes,
4838 recurse_value,
4839 recurse_idx_value,
4840 unfold_value,
4841 first_admit_idx_value,
4842 resolvers,
4843 )?;
4844 match resolvers.cohomology_group_resolver().resolve(operand) {
4845 Ok(tv) => Ok(tv),
4846 Err(report) => Err(PipelineFailure::ShapeViolation { report }),
4847 }
4848 }
4849 crate::enforcement::Term::PostnikovTower { simplicial_index } => {
4850 // ADR-035 + ADR-036 + ADR-060: resolver-bound ψ-Term variant.
4851 // Evaluate the operand subtree to a source-polymorphic carrier,
4852 // then dispatch it through the application's resolver via the
4853 // `Has<Category>Resolver` accessor. The resolver returns the
4854 // variant's `TermValue` directly (Inline / Borrowed / Stream);
4855 // resolver-side `Err` propagates as
4856 // `PipelineFailure::ShapeViolation` (the Null defaults emit
4857 // `RESOLVER_ABSENT`, recoverable via `Term::Try`).
4858 let operand = evaluate_term_at::<A, R, INLINE_BYTES>(
4859 arena,
4860 simplicial_index as usize,
4861 input_bytes,
4862 recurse_value,
4863 recurse_idx_value,
4864 unfold_value,
4865 first_admit_idx_value,
4866 resolvers,
4867 )?;
4868 match resolvers.postnikov_resolver().resolve(operand) {
4869 Ok(tv) => Ok(tv),
4870 Err(report) => Err(PipelineFailure::ShapeViolation { report }),
4871 }
4872 }
4873 crate::enforcement::Term::HomotopyGroups { postnikov_index } => {
4874 // ADR-035 + ADR-036 + ADR-060: resolver-bound ψ-Term variant.
4875 // Evaluate the operand subtree to a source-polymorphic carrier,
4876 // then dispatch it through the application's resolver via the
4877 // `Has<Category>Resolver` accessor. The resolver returns the
4878 // variant's `TermValue` directly (Inline / Borrowed / Stream);
4879 // resolver-side `Err` propagates as
4880 // `PipelineFailure::ShapeViolation` (the Null defaults emit
4881 // `RESOLVER_ABSENT`, recoverable via `Term::Try`).
4882 let operand = evaluate_term_at::<A, R, INLINE_BYTES>(
4883 arena,
4884 postnikov_index as usize,
4885 input_bytes,
4886 recurse_value,
4887 recurse_idx_value,
4888 unfold_value,
4889 first_admit_idx_value,
4890 resolvers,
4891 )?;
4892 match resolvers.homotopy_group_resolver().resolve(operand) {
4893 Ok(tv) => Ok(tv),
4894 Err(report) => Err(PipelineFailure::ShapeViolation { report }),
4895 }
4896 }
4897 crate::enforcement::Term::KInvariants { homotopy_index } => {
4898 // ADR-035 + ADR-036 + ADR-060: resolver-bound ψ-Term variant.
4899 // Evaluate the operand subtree to a source-polymorphic carrier,
4900 // then dispatch it through the application's resolver via the
4901 // `Has<Category>Resolver` accessor. The resolver returns the
4902 // variant's `TermValue` directly (Inline / Borrowed / Stream);
4903 // resolver-side `Err` propagates as
4904 // `PipelineFailure::ShapeViolation` (the Null defaults emit
4905 // `RESOLVER_ABSENT`, recoverable via `Term::Try`).
4906 let operand = evaluate_term_at::<A, R, INLINE_BYTES>(
4907 arena,
4908 homotopy_index as usize,
4909 input_bytes,
4910 recurse_value,
4911 recurse_idx_value,
4912 unfold_value,
4913 first_admit_idx_value,
4914 resolvers,
4915 )?;
4916 match resolvers.k_invariant_resolver().resolve(operand) {
4917 Ok(tv) => Ok(tv),
4918 Err(report) => Err(PipelineFailure::ShapeViolation { report }),
4919 }
4920 }
4921 crate::enforcement::Term::Betti { homology_index } => {
4922 // ADR-035 ψ_4: Betti-number extraction from HomologyGroups.
4923 // Pure byte-projection — no resolver consultation. The
4924 // operand's evaluated bytes (the homology-groups byte
4925 // serialization per the homology bridge's wire format)
4926 // are returned as-is; downstream consumers slice the
4927 // Betti tuple positions out of the result.
4928 let v = evaluate_term_at::<A, R, INLINE_BYTES>(
4929 arena,
4930 homology_index as usize,
4931 input_bytes,
4932 recurse_value,
4933 recurse_idx_value,
4934 unfold_value,
4935 first_admit_idx_value,
4936 resolvers,
4937 )?;
4938 Ok(v)
4939 }
4940 }
4941}
4942
4943#[allow(clippy::too_many_arguments)]
4944fn apply_primitive_op<'a, 'b, A, R, const INLINE_BYTES: usize>(
4945 arena: &'a [crate::enforcement::Term<'a, INLINE_BYTES>],
4946 operator: crate::PrimitiveOp,
4947 args_start: usize,
4948 args_len: usize,
4949 input_bytes: &'a [u8],
4950 recurse_value: Option<&'b [u8]>,
4951 recurse_idx_value: Option<&'b [u8]>,
4952 unfold_value: Option<&'b [u8]>,
4953 first_admit_idx_value: Option<&'b [u8]>,
4954 resolvers: &'a R,
4955) -> Result<TermValue<'a, INLINE_BYTES>, PipelineFailure>
4956where
4957 A: crate::pipeline::AxisTuple<INLINE_BYTES> + crate::enforcement::Hasher + 'a,
4958 R: crate::pipeline::ResolverTuple
4959 + crate::pipeline::HasNerveResolver<INLINE_BYTES, A>
4960 + crate::pipeline::HasChainComplexResolver<INLINE_BYTES, A>
4961 + crate::pipeline::HasHomologyGroupResolver<INLINE_BYTES, A>
4962 + crate::pipeline::HasCochainComplexResolver<INLINE_BYTES, A>
4963 + crate::pipeline::HasCohomologyGroupResolver<INLINE_BYTES, A>
4964 + crate::pipeline::HasPostnikovResolver<INLINE_BYTES, A>
4965 + crate::pipeline::HasHomotopyGroupResolver<INLINE_BYTES, A>
4966 + crate::pipeline::HasKInvariantResolver<INLINE_BYTES, A>,
4967{
4968 // Unary ops: 1 arg. Binary ops: 2 args.
4969 let arity = match operator {
4970 crate::PrimitiveOp::Neg
4971 | crate::PrimitiveOp::Bnot
4972 | crate::PrimitiveOp::Succ
4973 | crate::PrimitiveOp::Pred => 1usize,
4974 crate::PrimitiveOp::Add
4975 | crate::PrimitiveOp::Sub
4976 | crate::PrimitiveOp::Mul
4977 | crate::PrimitiveOp::Xor
4978 | crate::PrimitiveOp::And
4979 | crate::PrimitiveOp::Or
4980 | crate::PrimitiveOp::Le
4981 | crate::PrimitiveOp::Lt
4982 | crate::PrimitiveOp::Ge
4983 | crate::PrimitiveOp::Gt
4984 | crate::PrimitiveOp::Concat
4985 // ADR-053 substrate amendment: ring-axis arithmetic completion.
4986 | crate::PrimitiveOp::Div
4987 | crate::PrimitiveOp::Mod
4988 | crate::PrimitiveOp::Pow => 2usize,
4989 };
4990 if args_len != arity {
4991 return Err(PipelineFailure::ShapeViolation {
4992 report: crate::enforcement::ShapeViolation {
4993 shape_iri: "https://uor.foundation/pipeline/PrimitiveOpArityShape",
4994 constraint_iri: "https://uor.foundation/pipeline/PrimitiveOpArityShape/arity",
4995 property_iri: "https://uor.foundation/pipeline/operatorArity",
4996 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
4997 min_count: arity as u32,
4998 max_count: arity as u32,
4999 kind: crate::ViolationKind::CardinalityViolation,
5000 },
5001 });
5002 }
5003 if arity == 1 {
5004 let v = evaluate_term_at::<A, R, INLINE_BYTES>(
5005 arena,
5006 args_start,
5007 input_bytes,
5008 recurse_value,
5009 recurse_idx_value,
5010 unfold_value,
5011 first_admit_idx_value,
5012 resolvers,
5013 )?;
5014 let x = bytes_to_u64_be(v.bytes());
5015 let r =
5016 match operator {
5017 crate::PrimitiveOp::Neg => x.wrapping_neg(),
5018 crate::PrimitiveOp::Bnot => !x,
5019 crate::PrimitiveOp::Succ => x.wrapping_add(1),
5020 crate::PrimitiveOp::Pred => x.wrapping_sub(1),
5021 _ => return Err(PipelineFailure::ShapeViolation {
5022 report: crate::enforcement::ShapeViolation {
5023 shape_iri: "https://uor.foundation/pipeline/PrimitiveOpArityShape",
5024 constraint_iri:
5025 "https://uor.foundation/pipeline/PrimitiveOpArityShape/binary-as-unary",
5026 property_iri: "https://uor.foundation/pipeline/operatorArity",
5027 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
5028 min_count: 2,
5029 max_count: 2,
5030 kind: crate::ViolationKind::CardinalityViolation,
5031 },
5032 }),
5033 };
5034 let width = v.bytes().len().clamp(1, 8);
5035 let arr = r.to_be_bytes();
5036 Ok(TermValue::inline_from_slice(&arr[8 - width..]))
5037 } else {
5038 let lhs = evaluate_term_at::<A, R, INLINE_BYTES>(
5039 arena,
5040 args_start,
5041 input_bytes,
5042 recurse_value,
5043 recurse_idx_value,
5044 unfold_value,
5045 first_admit_idx_value,
5046 resolvers,
5047 )?;
5048 let rhs = evaluate_term_at::<A, R, INLINE_BYTES>(
5049 arena,
5050 args_start + 1,
5051 input_bytes,
5052 recurse_value,
5053 recurse_idx_value,
5054 unfold_value,
5055 first_admit_idx_value,
5056 resolvers,
5057 )?;
5058 // ADR-013/TR-08 substrate-amendment ops: byte-level Concat and
5059 // comparison primitives bypass the u64 fold and operate on the
5060 // operands' full byte sequences.
5061 match operator {
5062 crate::PrimitiveOp::Concat => {
5063 // Concat: emit lhs.bytes() ⧺ rhs.bytes(), bounded by
5064 // INLINE_BYTES (truncates excess; runtime callers
5065 // declaring shapes whose composite length exceeds the
5066 // ceiling are rejected at validation time per ADR-028's
5067 // symmetric output ceiling check).
5068 let lb = lhs.bytes();
5069 let rb = rhs.bytes();
5070 let total = lb.len() + rb.len();
5071 let cap = if total > INLINE_BYTES {
5072 INLINE_BYTES
5073 } else {
5074 total
5075 };
5076 let mut buf = [0u8; INLINE_BYTES];
5077 let mut i = 0;
5078 while i < lb.len() && i < cap {
5079 buf[i] = lb[i];
5080 i += 1;
5081 }
5082 let mut j = 0;
5083 while j < rb.len() && i < cap {
5084 buf[i] = rb[j];
5085 i += 1;
5086 j += 1;
5087 }
5088 return Ok(TermValue::inline_from_slice(&buf[..cap]));
5089 }
5090 crate::PrimitiveOp::Le
5091 | crate::PrimitiveOp::Lt
5092 | crate::PrimitiveOp::Ge
5093 | crate::PrimitiveOp::Gt => {
5094 // Big-endian byte-level comparison. Both operands are
5095 // padded with leading zeros to the max length so the
5096 // comparison ignores leading-zero stripping differences.
5097 let cmp = byte_compare_be(lhs.bytes(), rhs.bytes());
5098 let result_byte: u8 = match operator {
5099 crate::PrimitiveOp::Le => u8::from(cmp != core::cmp::Ordering::Greater),
5100 crate::PrimitiveOp::Lt => u8::from(cmp == core::cmp::Ordering::Less),
5101 crate::PrimitiveOp::Ge => u8::from(cmp != core::cmp::Ordering::Less),
5102 crate::PrimitiveOp::Gt => u8::from(cmp == core::cmp::Ordering::Greater),
5103 _ => 0,
5104 };
5105 return Ok(TermValue::inline_from_slice(&[result_byte]));
5106 }
5107 // ADR-053 substrate amendment: Div/Mod reject b = 0 with
5108 // a ShapeViolation. Per ADR-050, division semantics are
5109 // Euclidean (q = floor(a / b), r = a − q·b) over the ring
5110 // Z/(2^n)Z where n is max(8·byte-len(a), 8·byte-len(b)).
5111 crate::PrimitiveOp::Div if bytes_all_zero(rhs.bytes()) => {
5112 return Err(PipelineFailure::ShapeViolation {
5113 report: crate::enforcement::ShapeViolation {
5114 shape_iri: "https://uor.foundation/op/Div",
5115 constraint_iri: "https://uor.foundation/op/Div/nonZeroDivisor",
5116 property_iri: "https://uor.foundation/op/arity",
5117 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
5118 min_count: 1,
5119 max_count: 1,
5120 kind: crate::ViolationKind::ValueCheck,
5121 },
5122 });
5123 }
5124 crate::PrimitiveOp::Mod if bytes_all_zero(rhs.bytes()) => {
5125 return Err(PipelineFailure::ShapeViolation {
5126 report: crate::enforcement::ShapeViolation {
5127 shape_iri: "https://uor.foundation/op/Mod",
5128 constraint_iri: "https://uor.foundation/op/Mod/nonZeroDivisor",
5129 property_iri: "https://uor.foundation/op/arity",
5130 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
5131 min_count: 1,
5132 max_count: 1,
5133 kind: crate::ViolationKind::ValueCheck,
5134 },
5135 });
5136 }
5137 _ => {}
5138 }
5139 let width = lhs.bytes().len().max(rhs.bytes().len()).max(1);
5140 // ADR-050 width-parametric dispatch: widths > 8 bytes route
5141 // through the byte-level kernel `byte_arith_be` so wide-Witt
5142 // operands (Limbs<N>-backed levels W160..W32768) compute
5143 // correctly under Z/(2^(8·width))Z without truncating to u64.
5144 if width > 8 {
5145 return byte_arith_be::<INLINE_BYTES>(operator, lhs.bytes(), rhs.bytes(), width);
5146 }
5147 let a = bytes_to_u64_be(lhs.bytes());
5148 let b = bytes_to_u64_be(rhs.bytes());
5149 // ADR-050 width-parametric mask: arithmetic results are reduced
5150 // mod 2^(8·width) before truncation. For the u64 hot path this
5151 // is `(1 << (width·8)) − 1` for width < 8 and u64::MAX for width = 8.
5152 let mask: u64 = if width >= 8 {
5153 u64::MAX
5154 } else {
5155 (1u64 << (width * 8)).wrapping_sub(1)
5156 };
5157 let r =
5158 match operator {
5159 crate::PrimitiveOp::Add => a.wrapping_add(b) & mask,
5160 crate::PrimitiveOp::Sub => a.wrapping_sub(b) & mask,
5161 crate::PrimitiveOp::Mul => a.wrapping_mul(b) & mask,
5162 crate::PrimitiveOp::Xor => (a ^ b) & mask,
5163 crate::PrimitiveOp::And => (a & b) & mask,
5164 crate::PrimitiveOp::Or => (a | b) & mask,
5165 // ADR-053: Euclidean division/remainder over Z/(2^(8·width))Z.
5166 crate::PrimitiveOp::Div => (a & mask) / (b & mask),
5167 crate::PrimitiveOp::Mod => (a & mask) % (b & mask),
5168 // ADR-053: modular exponentiation by repeated squaring.
5169 crate::PrimitiveOp::Pow => u64_modpow(a & mask, b & mask, mask),
5170 _ => return Err(PipelineFailure::ShapeViolation {
5171 report: crate::enforcement::ShapeViolation {
5172 shape_iri: "https://uor.foundation/pipeline/PrimitiveOpArityShape",
5173 constraint_iri:
5174 "https://uor.foundation/pipeline/PrimitiveOpArityShape/unary-as-binary",
5175 property_iri: "https://uor.foundation/pipeline/operatorArity",
5176 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
5177 min_count: 1,
5178 max_count: 1,
5179 kind: crate::ViolationKind::CardinalityViolation,
5180 },
5181 }),
5182 };
5183 let arr = r.to_be_bytes();
5184 Ok(TermValue::inline_from_slice(&arr[8 - width..]))
5185 }
5186}
5187
5188/// ADR-050: byte-level fold-rule dispatch for binary ring-axis primitives over
5189/// widths > 8 bytes. Matches the const-fn `const_ring_eval_w{n}` helpers'
5190/// semantics but operates on the runtime catamorphism's `TermValue` byte slices
5191/// rather than on typed `Limbs<N>` values.
5192#[allow(clippy::too_many_lines)]
5193fn byte_arith_be<'a, const INLINE_BYTES: usize>(
5194 operator: crate::PrimitiveOp,
5195 lhs: &[u8],
5196 rhs: &[u8],
5197 width: usize,
5198) -> Result<TermValue<'a, INLINE_BYTES>, PipelineFailure> {
5199 // Pad both operands to `width` bytes (leading zeros, big-endian).
5200 let mut a = [0u8; INLINE_BYTES];
5201 let mut b = [0u8; INLINE_BYTES];
5202 let w = if width > INLINE_BYTES {
5203 INLINE_BYTES
5204 } else {
5205 width
5206 };
5207 {
5208 let la = lhs.len().min(w);
5209 let dst_off = w - la;
5210 let src_off = lhs.len() - la;
5211 let mut i = 0;
5212 while i < la {
5213 a[dst_off + i] = lhs[src_off + i];
5214 i += 1;
5215 }
5216 }
5217 {
5218 let lb = rhs.len().min(w);
5219 let dst_off = w - lb;
5220 let src_off = rhs.len() - lb;
5221 let mut i = 0;
5222 while i < lb {
5223 b[dst_off + i] = rhs[src_off + i];
5224 i += 1;
5225 }
5226 }
5227 let mut out = [0u8; INLINE_BYTES];
5228 match operator {
5229 crate::PrimitiveOp::And => {
5230 let mut i = 0;
5231 while i < w {
5232 out[i] = a[i] & b[i];
5233 i += 1;
5234 }
5235 }
5236 crate::PrimitiveOp::Or => {
5237 let mut i = 0;
5238 while i < w {
5239 out[i] = a[i] | b[i];
5240 i += 1;
5241 }
5242 }
5243 crate::PrimitiveOp::Xor => {
5244 let mut i = 0;
5245 while i < w {
5246 out[i] = a[i] ^ b[i];
5247 i += 1;
5248 }
5249 }
5250 crate::PrimitiveOp::Add => {
5251 let mut carry: u16 = 0;
5252 let mut i = w;
5253 while i > 0 {
5254 i -= 1;
5255 let s = a[i] as u16 + b[i] as u16 + carry;
5256 out[i] = (s & 0xFF) as u8;
5257 carry = s >> 8;
5258 }
5259 }
5260 crate::PrimitiveOp::Sub => {
5261 let mut borrow: i16 = 0;
5262 let mut i = w;
5263 while i > 0 {
5264 i -= 1;
5265 let d = a[i] as i16 - b[i] as i16 - borrow;
5266 if d < 0 {
5267 out[i] = (d + 256) as u8;
5268 borrow = 1;
5269 } else {
5270 out[i] = d as u8;
5271 borrow = 0;
5272 }
5273 }
5274 }
5275 crate::PrimitiveOp::Mul => {
5276 // Schoolbook multiplication mod 2^(8·w). `a` and `b` are
5277 // big-endian w-byte operands; output is the low w bytes of
5278 // the 2w-byte product, written back big-endian.
5279 //
5280 // Algorithm: walk `i` from LSB to MSB (a[w-1] = LSB), and
5281 // for each `i` walk `j` similarly. The byte product
5282 // a[w-1-i] * b[w-1-j] contributes to output position
5283 // (w-1)-(i+j) when (i+j) < w; otherwise it overflows the
5284 // ring modulus and is discarded.
5285 let mut tmp = [0u32; INLINE_BYTES];
5286 let mut i: usize = 0;
5287 while i < w {
5288 let mut j: usize = 0;
5289 while j < w - i {
5290 let dst = w - 1 - (i + j);
5291 tmp[dst] += a[w - 1 - i] as u32 * b[w - 1 - j] as u32;
5292 j += 1;
5293 }
5294 i += 1;
5295 }
5296 // Carry-propagate within the low w bytes (high bytes are
5297 // discarded — the ring modulus reduces them away).
5298 let mut carry: u32 = 0;
5299 let mut k = w;
5300 while k > 0 {
5301 k -= 1;
5302 let v = tmp[k] + carry;
5303 out[k] = (v & 0xFF) as u8;
5304 carry = v >> 8;
5305 }
5306 }
5307 crate::PrimitiveOp::Div | crate::PrimitiveOp::Mod => {
5308 // Binary long division MSB→LSB over the byte slice.
5309 let mut q = [0u8; INLINE_BYTES];
5310 let mut r = [0u8; INLINE_BYTES];
5311 let total_bits = w * 8;
5312 let mut i = 0;
5313 while i < total_bits {
5314 bytes_shl1(&mut r, w);
5315 let bit_word = i / 8;
5316 let bit_idx = 7 - (i % 8);
5317 let cur_bit = (a[bit_word] >> bit_idx) & 1;
5318 if cur_bit == 1 {
5319 r[w - 1] |= 1;
5320 }
5321 if bytes_le_be(&b[..w], &r[..w]) {
5322 bytes_sub_in_place(&mut r, &b, w);
5323 bytes_shl1(&mut q, w);
5324 q[w - 1] |= 1;
5325 } else {
5326 bytes_shl1(&mut q, w);
5327 }
5328 i += 1;
5329 }
5330 let src = match operator {
5331 crate::PrimitiveOp::Div => &q,
5332 _ => &r,
5333 };
5334 let mut k = 0;
5335 while k < w {
5336 out[k] = src[k];
5337 k += 1;
5338 }
5339 }
5340 crate::PrimitiveOp::Pow => {
5341 // Square-and-multiply over byte-slice operands. Exponent
5342 // bits walked LSB→MSB; each iteration squares the running
5343 // base and conditionally multiplies into the accumulator.
5344 let mut result = [0u8; INLINE_BYTES];
5345 result[w - 1] = 1;
5346 let mut base = [0u8; INLINE_BYTES];
5347 let mut k = 0;
5348 while k < w {
5349 base[k] = a[k];
5350 k += 1;
5351 }
5352 let mut byte = w;
5353 while byte > 0 {
5354 byte -= 1;
5355 let mut bit = 0u32;
5356 while bit < 8 {
5357 if ((b[byte] >> bit) & 1) == 1 {
5358 let prod = byte_arith_be::<INLINE_BYTES>(
5359 crate::PrimitiveOp::Mul,
5360 &result[..w],
5361 &base[..w],
5362 w,
5363 )?;
5364 let pb = prod.bytes();
5365 let dst_off = w - pb.len().min(w);
5366 let mut j = 0;
5367 while j < w {
5368 result[j] = 0;
5369 j += 1;
5370 }
5371 let mut j2 = 0;
5372 while j2 < pb.len().min(w) {
5373 result[dst_off + j2] = pb[j2];
5374 j2 += 1;
5375 }
5376 }
5377 let sq = byte_arith_be::<INLINE_BYTES>(
5378 crate::PrimitiveOp::Mul,
5379 &base[..w],
5380 &base[..w],
5381 w,
5382 )?;
5383 let sb = sq.bytes();
5384 let dst_off = w - sb.len().min(w);
5385 let mut j = 0;
5386 while j < w {
5387 base[j] = 0;
5388 j += 1;
5389 }
5390 let mut j2 = 0;
5391 while j2 < sb.len().min(w) {
5392 base[dst_off + j2] = sb[j2];
5393 j2 += 1;
5394 }
5395 bit += 1;
5396 }
5397 }
5398 let mut k = 0;
5399 while k < w {
5400 out[k] = result[k];
5401 k += 1;
5402 }
5403 }
5404 _ => {
5405 return Err(PipelineFailure::ShapeViolation {
5406 report: crate::enforcement::ShapeViolation {
5407 shape_iri: "https://uor.foundation/pipeline/PrimitiveOpArityShape",
5408 constraint_iri:
5409 "https://uor.foundation/pipeline/PrimitiveOpArityShape/unary-as-binary",
5410 property_iri: "https://uor.foundation/pipeline/operatorArity",
5411 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
5412 min_count: 1,
5413 max_count: 1,
5414 kind: crate::ViolationKind::CardinalityViolation,
5415 },
5416 })
5417 }
5418 }
5419 Ok(TermValue::inline_from_slice(&out[..w]))
5420}
5421
5422fn bytes_shl1(buf: &mut [u8], w: usize) {
5423 let mut carry: u8 = 0;
5424 let mut i = w;
5425 while i > 0 {
5426 i -= 1;
5427 let v = buf[i];
5428 buf[i] = (v << 1) | carry;
5429 carry = v >> 7;
5430 }
5431}
5432
5433fn bytes_le_be(a: &[u8], b: &[u8]) -> bool {
5434 let mut i = 0;
5435 while i < a.len() {
5436 if a[i] < b[i] {
5437 return true;
5438 }
5439 if a[i] > b[i] {
5440 return false;
5441 }
5442 i += 1;
5443 }
5444 true
5445}
5446
5447fn bytes_sub_in_place(dst: &mut [u8], rhs: &[u8], w: usize) {
5448 let mut borrow: i16 = 0;
5449 let mut i = w;
5450 while i > 0 {
5451 i -= 1;
5452 let d = dst[i] as i16 - rhs[i] as i16 - borrow;
5453 if d < 0 {
5454 dst[i] = (d + 256) as u8;
5455 borrow = 1;
5456 } else {
5457 dst[i] = d as u8;
5458 borrow = 0;
5459 }
5460 }
5461}
5462
5463fn byte_compare_be(a: &[u8], b: &[u8]) -> core::cmp::Ordering {
5464 // Pad shorter operand with leading zeros so the comparison treats
5465 // both operands at max(len(a), len(b)) byte width.
5466 let max_len = if a.len() > b.len() { a.len() } else { b.len() };
5467 let mut i = 0;
5468 while i < max_len {
5469 let ai = if i + a.len() >= max_len {
5470 a[i + a.len() - max_len]
5471 } else {
5472 0u8
5473 };
5474 let bi = if i + b.len() >= max_len {
5475 b[i + b.len() - max_len]
5476 } else {
5477 0u8
5478 };
5479 if ai < bi {
5480 return core::cmp::Ordering::Less;
5481 }
5482 if ai > bi {
5483 return core::cmp::Ordering::Greater;
5484 }
5485 i += 1;
5486 }
5487 core::cmp::Ordering::Equal
5488}
5489
5490fn bytes_to_u64_be(bytes: &[u8]) -> u64 {
5491 let take = if bytes.len() > 8 { 8 } else { bytes.len() };
5492 let start = bytes.len() - take;
5493 let mut acc = 0u64;
5494 let mut i = 0;
5495 while i < take {
5496 acc = (acc << 8) | bytes[start + i] as u64;
5497 i += 1;
5498 }
5499 acc
5500}
5501
5502fn bytes_all_zero(bytes: &[u8]) -> bool {
5503 let mut i = 0;
5504 while i < bytes.len() {
5505 if bytes[i] != 0 {
5506 return false;
5507 }
5508 i += 1;
5509 }
5510 true
5511}
5512
5513fn u64_modpow(base: u64, exp: u64, mask: u64) -> u64 {
5514 let mut result: u64 = 1u64 & mask;
5515 let mut b: u64 = base & mask;
5516 let mut e: u64 = exp;
5517 while e > 0 {
5518 if (e & 1) == 1 {
5519 result = result.wrapping_mul(b) & mask;
5520 }
5521 b = b.wrapping_mul(b) & mask;
5522 e >>= 1;
5523 }
5524 result
5525}
5526
5527/// Foundation-sanctioned identity route: `ConstrainedTypeInput` is the
5528/// empty default shape, vacuously closed under foundation vocabulary.
5529/// Application authors with non-trivial routes use the `prism_model!`
5530/// macro from `uor-foundation-sdk`, which emits `FoundationClosed` for
5531/// the witness it generates iff every node is foundation-vocabulary.
5532/// The identity route's `arena_slice()` returns `&[]` — no terms, no
5533/// transformation, input passes through to output unchanged.
5534impl __sdk_seal::Sealed for ConstrainedTypeInput {}
5535impl<const INLINE_BYTES: usize> FoundationClosed<INLINE_BYTES> for ConstrainedTypeInput {
5536 fn arena_slice() -> &'static [crate::enforcement::Term<'static, INLINE_BYTES>] {
5537 &[]
5538 }
5539}
5540impl IntoBindingValue for ConstrainedTypeInput {
5541 const MAX_BYTES: usize = 0;
5542 fn into_binding_bytes(
5543 &self,
5544 _out: &mut [u8],
5545 ) -> core::result::Result<usize, crate::enforcement::ShapeViolation> {
5546 // Identity input carries no bytes — the empty shape's canonical
5547 // serialization is the empty byte sequence.
5548 Ok(0)
5549 }
5550}
5551
5552/// Marker for a `ConstrainedTypeShape` that is the Cartesian product of
5553/// two component shapes. Selecting this trait routes nerve-Betti computation
5554/// through Künneth composition of component Betti profiles rather than
5555/// flat enumeration of (constraint, constraint) pairs. Introduced by the
5556/// Product/Coproduct Completion Amendment §3c for CartesianPartitionProduct
5557/// (CPT_1–CPT_6).
5558pub trait CartesianProductShape: ConstrainedTypeShape {
5559 /// Left operand shape.
5560 type Left: ConstrainedTypeShape;
5561 /// Right operand shape.
5562 type Right: ConstrainedTypeShape;
5563}
5564
5565/// Künneth composition of two Betti profiles.
5566/// Computes `out[k] = Σ_{i + j = k} a[i] · b[j]` over
5567/// `[0, MAX_BETTI_DIMENSION)`. All arithmetic uses saturating operations so the
5568/// function is total on `[u32; MAX_BETTI_DIMENSION]` inputs without panicking.
5569pub const fn kunneth_compose(
5570 a: &[u32; crate::enforcement::MAX_BETTI_DIMENSION],
5571 b: &[u32; crate::enforcement::MAX_BETTI_DIMENSION],
5572) -> [u32; crate::enforcement::MAX_BETTI_DIMENSION] {
5573 let mut out = [0u32; crate::enforcement::MAX_BETTI_DIMENSION];
5574 let mut i: usize = 0;
5575 while i < crate::enforcement::MAX_BETTI_DIMENSION {
5576 let mut j: usize = 0;
5577 while j < crate::enforcement::MAX_BETTI_DIMENSION - i {
5578 let term = a[i].saturating_mul(b[j]);
5579 out[i + j] = out[i + j].saturating_add(term);
5580 j += 1;
5581 }
5582 i += 1;
5583 }
5584 out
5585}
5586
5587/// Cartesian-product nerve Betti via Künneth composition of the component
5588/// shapes' Betti profiles. Used instead of
5589/// `primitive_simplicial_nerve_betti` when a shape declares itself as a
5590/// `CartesianProductShape`. Amendment §3c.
5591/// Phase 1a (orphan-closure): propagates either component's
5592/// `NERVE_CAPACITY_EXCEEDED` via `?`. Dropped `const fn` because
5593/// the `Result` return of the per-component primitive is not `const`-evaluable.
5594/// ADR-057: delegates to [`crate::enforcement::primitive_simplicial_nerve_betti`]
5595/// on each component, which expands `ConstraintRef::Recurse` through
5596/// foundation's built-in shape registry. For application-registry-aware
5597/// expansion, use [`primitive_cartesian_nerve_betti_in`] generic over the
5598/// application's `ShapeRegistryProvider`.
5599/// # Errors
5600/// Returns `NERVE_CAPACITY_EXCEEDED` if either component exceeds caps.
5601/// Returns `RECURSE_SHAPE_UNREGISTERED` when a component's Recurse references
5602/// an IRI not present in the consulted registry.
5603pub fn primitive_cartesian_nerve_betti<S: CartesianProductShape>() -> Result<
5604 [u32; crate::enforcement::MAX_BETTI_DIMENSION],
5605 crate::enforcement::GenericImpossibilityWitness,
5606> {
5607 let left = crate::enforcement::primitive_simplicial_nerve_betti::<S::Left>()?;
5608 let right = crate::enforcement::primitive_simplicial_nerve_betti::<S::Right>()?;
5609 Ok(kunneth_compose(&left, &right))
5610}
5611
5612/// ADR-057: registry-parameterized companion to
5613/// [`primitive_cartesian_nerve_betti`]. Delegates to
5614/// [`crate::enforcement::primitive_simplicial_nerve_betti_in`] on each
5615/// component with `R` as the application's `ShapeRegistryProvider`.
5616/// Recurse entries are expanded through `R::REGISTRY` plus foundation's
5617/// built-in registry.
5618/// # Errors
5619/// Returns `NERVE_CAPACITY_EXCEEDED` if either component's expanded
5620/// constraint set exceeds caps. Returns `RECURSE_SHAPE_UNREGISTERED` when
5621/// a `Recurse` entry references an IRI not present in either registry.
5622pub fn primitive_cartesian_nerve_betti_in<
5623 S: CartesianProductShape,
5624 R: crate::pipeline::shape_iri_registry::ShapeRegistryProvider,
5625>() -> Result<
5626 [u32; crate::enforcement::MAX_BETTI_DIMENSION],
5627 crate::enforcement::GenericImpossibilityWitness,
5628> {
5629 let left = crate::enforcement::primitive_simplicial_nerve_betti_in::<S::Left, R>()?;
5630 let right = crate::enforcement::primitive_simplicial_nerve_betti_in::<S::Right, R>()?;
5631 Ok(kunneth_compose(&left, &right))
5632}
5633
5634/// Shift every site-index reference in a `ConstraintRef` by `offset`.
5635/// Used by the SDK's `product_shape!` / `coproduct_shape!` /
5636/// `cartesian_product_shape!` macros to splice an operand's constraints into
5637/// a combined shape at a post-operand offset.
5638/// **Phase 17: full operand-catalogue support.** Affine and
5639/// Conjunction now shift correctly at const time because the
5640/// variants store fixed-size arrays (no `&'static [i64]` allocation
5641/// required). The pre-Phase-17 `Site { position: u32::MAX }`
5642/// sentinel is removed.
5643/// **Variant coverage.**
5644/// - `Site { position }` → position += offset.
5645/// - `Carry { site }` → site += offset.
5646/// - `Residue`, `Hamming`, `Depth`, `SatClauses`, `Bound`: pass through (no
5647/// site references at this layer).
5648/// - `Affine { coefficients, coefficient_count, bias }`: builds a fresh
5649/// `[i64; AFFINE_MAX_COEFFS]` of zeros, copies the active prefix into
5650/// positions `[offset, offset + coefficient_count)`. If the shift
5651/// would overflow the fixed buffer, returns an Affine with
5652/// `coefficient_count = 0` (vacuously consistent).
5653/// - `Conjunction { conjuncts, conjunct_count }`: builds a fresh
5654/// `[LeafConstraintRef; CONJUNCTION_MAX_TERMS]` and shifts each leaf
5655/// via `shift_leaf_constraint`. One-level depth — leaves cannot be
5656/// Conjunction.
5657pub const fn shift_constraint(c: ConstraintRef, offset: u32) -> ConstraintRef {
5658 match c {
5659 ConstraintRef::Site { position } => ConstraintRef::Site {
5660 position: position.saturating_add(offset),
5661 },
5662 ConstraintRef::Carry { site } => ConstraintRef::Carry {
5663 site: site.saturating_add(offset),
5664 },
5665 ConstraintRef::Residue { modulus, residue } => ConstraintRef::Residue { modulus, residue },
5666 ConstraintRef::Hamming { bound } => ConstraintRef::Hamming { bound },
5667 ConstraintRef::Depth { min, max } => ConstraintRef::Depth { min, max },
5668 ConstraintRef::SatClauses { clauses, num_vars } => {
5669 ConstraintRef::SatClauses { clauses, num_vars }
5670 }
5671 ConstraintRef::Bound {
5672 observable_iri,
5673 bound_shape_iri,
5674 args_repr,
5675 } => ConstraintRef::Bound {
5676 observable_iri,
5677 bound_shape_iri,
5678 args_repr,
5679 },
5680 ConstraintRef::Affine {
5681 coefficients,
5682 coefficient_count,
5683 bias,
5684 } => {
5685 let (out, new_count) =
5686 shift_affine_coefficients(&coefficients, coefficient_count, offset);
5687 ConstraintRef::Affine {
5688 coefficients: out,
5689 coefficient_count: new_count,
5690 bias,
5691 }
5692 }
5693 ConstraintRef::Conjunction {
5694 conjuncts,
5695 conjunct_count,
5696 } => {
5697 let mut out = [LeafConstraintRef::Site { position: 0 }; CONJUNCTION_MAX_TERMS];
5698 let count = conjunct_count as usize;
5699 let mut i = 0;
5700 while i < count && i < CONJUNCTION_MAX_TERMS {
5701 out[i] = shift_leaf_constraint(conjuncts[i], offset);
5702 i += 1;
5703 }
5704 ConstraintRef::Conjunction {
5705 conjuncts: out,
5706 conjunct_count,
5707 }
5708 }
5709 // ADR-057: Recurse references a shape by content-addressed IRI;
5710 // there are no site positions to shift. Pass through unchanged.
5711 ConstraintRef::Recurse {
5712 shape_iri,
5713 descent_bound,
5714 } => ConstraintRef::Recurse {
5715 shape_iri,
5716 descent_bound,
5717 },
5718 }
5719}
5720
5721/// Phase 17 helper: shift the active prefix of an `Affine`
5722/// coefficient array right by `offset`, returning a fresh
5723/// `[i64; AFFINE_MAX_COEFFS]` and the new active count. If the
5724/// shift would overflow the fixed buffer, returns count `0`
5725/// (vacuously consistent — no constraint).
5726#[inline]
5727#[must_use]
5728const fn shift_affine_coefficients(
5729 coefficients: &[i64; AFFINE_MAX_COEFFS],
5730 coefficient_count: u32,
5731 offset: u32,
5732) -> ([i64; AFFINE_MAX_COEFFS], u32) {
5733 let mut out = [0i64; AFFINE_MAX_COEFFS];
5734 let count = coefficient_count as usize;
5735 let off = offset as usize;
5736 if off >= AFFINE_MAX_COEFFS {
5737 return (out, 0);
5738 }
5739 let mut i = 0;
5740 while i < count && i + off < AFFINE_MAX_COEFFS {
5741 out[i + off] = coefficients[i];
5742 i += 1;
5743 }
5744 let new_count = (i + off) as u32;
5745 (out, new_count)
5746}
5747
5748/// Phase 17 helper: same as [`shift_constraint`] but operating on a
5749/// [`LeafConstraintRef`]. Used by Conjunction-shifting paths that
5750/// must preserve the leaf-only depth limit.
5751#[inline]
5752#[must_use]
5753pub const fn shift_leaf_constraint(c: LeafConstraintRef, offset: u32) -> LeafConstraintRef {
5754 match c {
5755 LeafConstraintRef::Site { position } => LeafConstraintRef::Site {
5756 position: position.saturating_add(offset),
5757 },
5758 LeafConstraintRef::Carry { site } => LeafConstraintRef::Carry {
5759 site: site.saturating_add(offset),
5760 },
5761 LeafConstraintRef::Residue { modulus, residue } => {
5762 LeafConstraintRef::Residue { modulus, residue }
5763 }
5764 LeafConstraintRef::Hamming { bound } => LeafConstraintRef::Hamming { bound },
5765 LeafConstraintRef::Depth { min, max } => LeafConstraintRef::Depth { min, max },
5766 LeafConstraintRef::SatClauses { clauses, num_vars } => {
5767 LeafConstraintRef::SatClauses { clauses, num_vars }
5768 }
5769 LeafConstraintRef::Bound {
5770 observable_iri,
5771 bound_shape_iri,
5772 args_repr,
5773 } => LeafConstraintRef::Bound {
5774 observable_iri,
5775 bound_shape_iri,
5776 args_repr,
5777 },
5778 LeafConstraintRef::Affine {
5779 coefficients,
5780 coefficient_count,
5781 bias,
5782 } => {
5783 let (out, new_count) =
5784 shift_affine_coefficients(&coefficients, coefficient_count, offset);
5785 LeafConstraintRef::Affine {
5786 coefficients: out,
5787 coefficient_count: new_count,
5788 bias,
5789 }
5790 }
5791 // ADR-057: Recurse references a shape by content-addressed IRI;
5792 // there are no site positions to shift. Pass through unchanged.
5793 LeafConstraintRef::Recurse {
5794 shape_iri,
5795 descent_bound,
5796 } => LeafConstraintRef::Recurse {
5797 shape_iri,
5798 descent_bound,
5799 },
5800 }
5801}
5802
5803/// SDK support: concatenate two operand constraint arrays into a padded
5804/// fixed-size buffer of length `2 * crate::enforcement::NERVE_CONSTRAINTS_CAP`.
5805/// A's constraints are copied verbatim at indices `[0, A::CONSTRAINTS.len())`;
5806/// B's constraints are copied at `[A::CONSTRAINTS.len(), total)` with each
5807/// entry passed through `shift_constraint(c, A::SITE_COUNT as u32)`.
5808/// Trailing positions are filled with the `Site { position: u32::MAX }`
5809/// sentinel.
5810/// Consumers pair this with `sdk_product_constraints_len` to derive the
5811/// slice length at const-eval time: `&BUF[..LEN]` yields a `&'static
5812/// [ConstraintRef]` of the correct length without `unsafe`.
5813pub const fn sdk_concat_product_constraints<A, B>(
5814) -> [ConstraintRef; 2 * crate::enforcement::NERVE_CONSTRAINTS_CAP]
5815where
5816 A: ConstrainedTypeShape,
5817 B: ConstrainedTypeShape,
5818{
5819 let mut out =
5820 [ConstraintRef::Site { position: u32::MAX }; 2 * crate::enforcement::NERVE_CONSTRAINTS_CAP];
5821 let left = A::CONSTRAINTS;
5822 let right = B::CONSTRAINTS;
5823 let offset = A::SITE_COUNT as u32;
5824 let mut i: usize = 0;
5825 while i < left.len() {
5826 out[i] = left[i];
5827 i += 1;
5828 }
5829 let mut j: usize = 0;
5830 while j < right.len() {
5831 out[i + j] = shift_constraint(right[j], offset);
5832 j += 1;
5833 }
5834 out
5835}
5836
5837/// Companion length helper for `sdk_concat_product_constraints`.
5838pub const fn sdk_product_constraints_len<A, B>() -> usize
5839where
5840 A: ConstrainedTypeShape,
5841 B: ConstrainedTypeShape,
5842{
5843 A::CONSTRAINTS.len() + B::CONSTRAINTS.len()
5844}
5845
5846/// ADR-057 recurse-aware variant of `sdk_concat_product_constraints`.
5847/// Per-operand `Option<u32>` recurse-bound parameter: `None` inlines the
5848/// operand's CONSTRAINTS array as before; `Some(bound)` emits a single
5849/// `ConstraintRef::Recurse { shape_iri: <T>::IRI, descent_bound: bound }`
5850/// at the operand's position. Site offsets for the following operand's
5851/// inline constraints account for the recurse operand's
5852/// `<T>::SITE_COUNT` as if it were inlined.
5853pub const fn sdk_concat_product_constraints_v2<A, B>(
5854 a_recurse_bound: Option<u32>,
5855 b_recurse_bound: Option<u32>,
5856) -> [ConstraintRef; 2 * crate::enforcement::NERVE_CONSTRAINTS_CAP]
5857where
5858 A: ConstrainedTypeShape,
5859 B: ConstrainedTypeShape,
5860{
5861 let mut out =
5862 [ConstraintRef::Site { position: u32::MAX }; 2 * crate::enforcement::NERVE_CONSTRAINTS_CAP];
5863 let offset = A::SITE_COUNT as u32;
5864 let mut i: usize = 0;
5865 // A's contribution: single Recurse entry or inlined constraints.
5866 match a_recurse_bound {
5867 Some(bound) => {
5868 out[0] = ConstraintRef::Recurse {
5869 shape_iri: A::IRI,
5870 descent_bound: bound,
5871 };
5872 i = 1;
5873 }
5874 None => {
5875 let left = A::CONSTRAINTS;
5876 while i < left.len() {
5877 out[i] = left[i];
5878 i += 1;
5879 }
5880 }
5881 }
5882 // B's contribution: single Recurse entry or inlined-and-shifted constraints.
5883 match b_recurse_bound {
5884 Some(bound) => {
5885 out[i] = ConstraintRef::Recurse {
5886 shape_iri: B::IRI,
5887 descent_bound: bound,
5888 };
5889 }
5890 None => {
5891 let right = B::CONSTRAINTS;
5892 let mut j: usize = 0;
5893 while j < right.len() {
5894 out[i + j] = shift_constraint(right[j], offset);
5895 j += 1;
5896 }
5897 }
5898 }
5899 out
5900}
5901
5902/// Companion length helper for `sdk_concat_product_constraints_v2`.
5903pub const fn sdk_product_constraints_v2_len<A, B>(a_recurse: bool, b_recurse: bool) -> usize
5904where
5905 A: ConstrainedTypeShape,
5906 B: ConstrainedTypeShape,
5907{
5908 let a_len = if a_recurse { 1 } else { A::CONSTRAINTS.len() };
5909 let b_len = if b_recurse { 1 } else { B::CONSTRAINTS.len() };
5910 a_len + b_len
5911}
5912
5913/// ADR-057: bounded recursive structural typing — foundation shape-IRI
5914/// registry module. Mirrors the architectural pattern of the
5915/// observable-IRI registry per ADR-038/049 and the substitution-axis
5916/// catalog per ADR-007/030: a closed catalog of shape IRIs admissible
5917/// as `ConstraintRef::Recurse` targets, consulted by ψ_1's NerveResolver
5918/// when expanding recursive references at evaluation time.
5919/// # Architecture
5920/// Foundation is `#![no_std]` with zero dependencies and zero `unsafe`,
5921/// so the registry uses **const-aggregation through a sealed trait**
5922/// rather than a mutable global or link-section symbol resolution.
5923/// Applications declare their shape registry as a single type via the
5924/// SDK `register_shape!(Shape1, Shape2, …)` macro per crate; the type
5925/// implements [`ShapeRegistryProvider`] with the const-aggregated slice.
5926/// ψ_1 NerveResolver consults the registry through [`lookup_shape_in`].
5927/// Foundation's built-in [`lookup_shape`] consults a foundation-owned
5928/// static registry — currently empty; standard-library Layer-3 sub-crates
5929/// per ADR-031 publishing canonical recursive-grammar shapes register
5930/// through this path in future foundation-curated additions.
5931pub mod shape_iri_registry {
5932 use super::ConstraintRef;
5933
5934 /// ADR-057: a registered shape entry. `iri` is the shape's
5935 /// content-addressed identifier per ADR-017; `site_count`,
5936 /// `constraints`, and `cycle_size` mirror the corresponding
5937 /// `ConstrainedTypeShape` associated items so ψ_1's resolver
5938 /// can walk the referenced shape's constraint set without
5939 /// touching the original trait impl.
5940 #[derive(Debug, Clone, Copy)]
5941 pub struct RegisteredShape {
5942 /// Content-addressed IRI of the shape (per ADR-017).
5943 pub iri: &'static str,
5944 /// Mirror of `<T as ConstrainedTypeShape>::SITE_COUNT`.
5945 pub site_count: usize,
5946 /// Mirror of `<T as ConstrainedTypeShape>::CONSTRAINTS`.
5947 pub constraints: &'static [ConstraintRef],
5948 /// Mirror of `<T as ConstrainedTypeShape>::CYCLE_SIZE`.
5949 pub cycle_size: u64,
5950 }
5951
5952 /// ADR-057: sealed trait carrying a crate's registered-shape slice
5953 /// at the const-aggregation surface. The SDK `register_shape!` macro
5954 /// emits an `impl ShapeRegistryProvider` on a marker type whose
5955 /// `REGISTRY` const is the list of registered shapes.
5956 /// Sealed via [`super::__sdk_seal::Sealed`] per ADR-022 D1; only
5957 /// the SDK `register_shape!` macro emits impls (plus foundation's
5958 /// [`EmptyShapeRegistry`] default).
5959 pub trait ShapeRegistryProvider: super::__sdk_seal::Sealed {
5960 /// The crate's registered-shape slice. Walked by
5961 /// [`lookup_shape_in`] when ψ_1 NerveResolver expands
5962 /// `ConstraintRef::Recurse` references at evaluation time.
5963 const REGISTRY: &'static [RegisteredShape];
5964 }
5965
5966 /// ADR-057: the empty-registry default, used by applications that
5967 /// don't carry recursive shapes. Foundation provides this as the
5968 /// baseline `ShapeRegistryProvider` impl; applications with
5969 /// recursive shapes use the SDK `register_shape!` macro to declare
5970 /// their own marker type with a non-empty `REGISTRY`.
5971 #[derive(Debug, Clone, Copy, Default)]
5972 pub struct EmptyShapeRegistry;
5973 impl super::__sdk_seal::Sealed for EmptyShapeRegistry {}
5974 impl ShapeRegistryProvider for EmptyShapeRegistry {
5975 const REGISTRY: &'static [RegisteredShape] = &[];
5976 }
5977
5978 /// Foundation's built-in shape registry. Currently empty —
5979 /// standard-library Layer-3 sub-crates per ADR-031 publishing
5980 /// canonical recursive-grammar shapes (canonical JSON, AST, etc.)
5981 /// register through this path in future foundation-curated additions.
5982 static FOUNDATION_REGISTRY: &[RegisteredShape] = &[];
5983
5984 /// ADR-057: look up a registered shape by IRI in the
5985 /// foundation-built-in registry. Returns `None` if the IRI is
5986 /// not present; applications consulting their own application-
5987 /// registered shapes use [`lookup_shape_in`] generic over their
5988 /// `ShapeRegistryProvider`-implementing marker type.
5989 /// Called by ψ_1's NerveResolver during `ConstraintRef::Recurse`
5990 /// evaluation when no application registry is threaded through.
5991 #[must_use]
5992 pub fn lookup_shape(iri: &str) -> Option<&'static RegisteredShape> {
5993 lookup_in_slice(FOUNDATION_REGISTRY, iri)
5994 }
5995
5996 /// ADR-057: look up a registered shape by IRI in an application's
5997 /// registry plus the foundation-built-in registry. Walks the
5998 /// application's `R::REGISTRY` first, then falls back to the
5999 /// foundation registry; returns `None` if the IRI is in neither.
6000 /// Called by ψ_1's NerveResolver during `ConstraintRef::Recurse`
6001 /// evaluation when an application registry is threaded through
6002 /// (per `pipeline::run_route`'s registry-parameterized path).
6003 #[must_use]
6004 pub fn lookup_shape_in<R: ShapeRegistryProvider>(
6005 iri: &str,
6006 ) -> Option<&'static RegisteredShape> {
6007 if let Some(entry) = lookup_in_slice(R::REGISTRY, iri) {
6008 return Some(entry);
6009 }
6010 lookup_in_slice(FOUNDATION_REGISTRY, iri)
6011 }
6012
6013 /// Linear-scan helper used by both [`lookup_shape`] and
6014 /// [`lookup_shape_in`]. Registry slices are expected to be small
6015 /// (a handful of canonical shapes per standard-library sub-crate
6016 /// or per application), so linear scan dominates startup cost.
6017 fn lookup_in_slice(
6018 slice: &'static [RegisteredShape],
6019 iri: &str,
6020 ) -> Option<&'static RegisteredShape> {
6021 let mut i = 0;
6022 while i < slice.len() {
6023 let entry = &slice[i];
6024 if iri_eq(entry.iri, iri) {
6025 return Some(entry);
6026 }
6027 i += 1;
6028 }
6029 None
6030 }
6031
6032 fn iri_eq(a: &str, b: &str) -> bool {
6033 a.as_bytes() == b.as_bytes()
6034 }
6035}
6036
6037/// ADR-032: per-Witt-level zero-sized marker types implementing
6038/// `ConstrainedTypeShape`. The `prism_model!` proc-macro consumes
6039/// these as the `<DomainTy>` operand of `first_admit(<DomainTy>, |i| …)`
6040/// (G16): `<DomainTy as ConstrainedTypeShape>::CYCLE_SIZE` carries the
6041/// domain's cardinality, which the macro lowers as the descent measure
6042/// for the emitted `Term::Recurse`.
6043/// Each level's `CYCLE_SIZE` is `2^bits_width`, saturated at
6044/// `u64::MAX` for levels whose cardinality exceeds 64-bit range.
6045/// The wiki's normative example `first_admit(WittLevel::W32, |nonce| …)`
6046/// compiles on stable Rust 1.83 as `first_admit(witt_domain::W32, |nonce| …)`
6047/// (`witt_domain::W32::CYCLE_SIZE = 4_294_967_296`).
6048pub mod witt_domain {
6049 use super::{ConstrainedTypeShape, ConstraintRef, PartitionProductFields};
6050 use crate::enforcement::GroundedShape;
6051 use crate::enforcement::ShapeViolation;
6052 use crate::pipeline::__sdk_seal;
6053 use crate::pipeline::IntoBindingValue;
6054
6055 /// ADR-032 Witt-level domain marker for `W8` (8-bit ring).
6056 /// `CYCLE_SIZE = 2^8 = 256`. Used as the `<DomainTy>` operand of
6057 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6058 pub struct W8;
6059 impl ConstrainedTypeShape for W8 {
6060 const IRI: &'static str = "https://uor.foundation/witt/W8";
6061 const SITE_COUNT: usize = 1;
6062 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6063 const CYCLE_SIZE: u64 = 256u64;
6064 }
6065 impl __sdk_seal::Sealed for W8 {}
6066 impl IntoBindingValue for W8 {
6067 const MAX_BYTES: usize = 0;
6068 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6069 Ok(0)
6070 }
6071 }
6072 impl GroundedShape for W8 {}
6073 impl PartitionProductFields for W8 {
6074 const FIELDS: &'static [(u32, u32)] = &[];
6075 const FIELD_NAMES: &'static [&'static str] = &[];
6076 }
6077
6078 /// ADR-032 Witt-level domain marker for `W16` (16-bit ring).
6079 /// `CYCLE_SIZE = 2^16 = 65536`. Used as the `<DomainTy>` operand of
6080 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6081 pub struct W16;
6082 impl ConstrainedTypeShape for W16 {
6083 const IRI: &'static str = "https://uor.foundation/witt/W16";
6084 const SITE_COUNT: usize = 1;
6085 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6086 const CYCLE_SIZE: u64 = 65536u64;
6087 }
6088 impl __sdk_seal::Sealed for W16 {}
6089 impl IntoBindingValue for W16 {
6090 const MAX_BYTES: usize = 0;
6091 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6092 Ok(0)
6093 }
6094 }
6095 impl GroundedShape for W16 {}
6096 impl PartitionProductFields for W16 {
6097 const FIELDS: &'static [(u32, u32)] = &[];
6098 const FIELD_NAMES: &'static [&'static str] = &[];
6099 }
6100
6101 /// ADR-032 Witt-level domain marker for `W24` (24-bit ring).
6102 /// `CYCLE_SIZE = 2^24 = 16777216`. Used as the `<DomainTy>` operand of
6103 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6104 pub struct W24;
6105 impl ConstrainedTypeShape for W24 {
6106 const IRI: &'static str = "https://uor.foundation/witt/W24";
6107 const SITE_COUNT: usize = 1;
6108 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6109 const CYCLE_SIZE: u64 = 16777216u64;
6110 }
6111 impl __sdk_seal::Sealed for W24 {}
6112 impl IntoBindingValue for W24 {
6113 const MAX_BYTES: usize = 0;
6114 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6115 Ok(0)
6116 }
6117 }
6118 impl GroundedShape for W24 {}
6119 impl PartitionProductFields for W24 {
6120 const FIELDS: &'static [(u32, u32)] = &[];
6121 const FIELD_NAMES: &'static [&'static str] = &[];
6122 }
6123
6124 /// ADR-032 Witt-level domain marker for `W32` (32-bit ring).
6125 /// `CYCLE_SIZE = 2^32 = 4294967296`. Used as the `<DomainTy>` operand of
6126 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6127 pub struct W32;
6128 impl ConstrainedTypeShape for W32 {
6129 const IRI: &'static str = "https://uor.foundation/witt/W32";
6130 const SITE_COUNT: usize = 1;
6131 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6132 const CYCLE_SIZE: u64 = 4294967296u64;
6133 }
6134 impl __sdk_seal::Sealed for W32 {}
6135 impl IntoBindingValue for W32 {
6136 const MAX_BYTES: usize = 0;
6137 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6138 Ok(0)
6139 }
6140 }
6141 impl GroundedShape for W32 {}
6142 impl PartitionProductFields for W32 {
6143 const FIELDS: &'static [(u32, u32)] = &[];
6144 const FIELD_NAMES: &'static [&'static str] = &[];
6145 }
6146
6147 /// ADR-032 Witt-level domain marker for `W40` (40-bit ring).
6148 /// `CYCLE_SIZE = 2^40 = 1099511627776`. Used as the `<DomainTy>` operand of
6149 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6150 pub struct W40;
6151 impl ConstrainedTypeShape for W40 {
6152 const IRI: &'static str = "https://uor.foundation/witt/W40";
6153 const SITE_COUNT: usize = 1;
6154 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6155 const CYCLE_SIZE: u64 = 1099511627776u64;
6156 }
6157 impl __sdk_seal::Sealed for W40 {}
6158 impl IntoBindingValue for W40 {
6159 const MAX_BYTES: usize = 0;
6160 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6161 Ok(0)
6162 }
6163 }
6164 impl GroundedShape for W40 {}
6165 impl PartitionProductFields for W40 {
6166 const FIELDS: &'static [(u32, u32)] = &[];
6167 const FIELD_NAMES: &'static [&'static str] = &[];
6168 }
6169
6170 /// ADR-032 Witt-level domain marker for `W48` (48-bit ring).
6171 /// `CYCLE_SIZE = 2^48 = 281474976710656`. Used as the `<DomainTy>` operand of
6172 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6173 pub struct W48;
6174 impl ConstrainedTypeShape for W48 {
6175 const IRI: &'static str = "https://uor.foundation/witt/W48";
6176 const SITE_COUNT: usize = 1;
6177 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6178 const CYCLE_SIZE: u64 = 281474976710656u64;
6179 }
6180 impl __sdk_seal::Sealed for W48 {}
6181 impl IntoBindingValue for W48 {
6182 const MAX_BYTES: usize = 0;
6183 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6184 Ok(0)
6185 }
6186 }
6187 impl GroundedShape for W48 {}
6188 impl PartitionProductFields for W48 {
6189 const FIELDS: &'static [(u32, u32)] = &[];
6190 const FIELD_NAMES: &'static [&'static str] = &[];
6191 }
6192
6193 /// ADR-032 Witt-level domain marker for `W56` (56-bit ring).
6194 /// `CYCLE_SIZE = 2^56 = 72057594037927936`. Used as the `<DomainTy>` operand of
6195 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6196 pub struct W56;
6197 impl ConstrainedTypeShape for W56 {
6198 const IRI: &'static str = "https://uor.foundation/witt/W56";
6199 const SITE_COUNT: usize = 1;
6200 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6201 const CYCLE_SIZE: u64 = 72057594037927936u64;
6202 }
6203 impl __sdk_seal::Sealed for W56 {}
6204 impl IntoBindingValue for W56 {
6205 const MAX_BYTES: usize = 0;
6206 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6207 Ok(0)
6208 }
6209 }
6210 impl GroundedShape for W56 {}
6211 impl PartitionProductFields for W56 {
6212 const FIELDS: &'static [(u32, u32)] = &[];
6213 const FIELD_NAMES: &'static [&'static str] = &[];
6214 }
6215
6216 /// ADR-032 Witt-level domain marker for `W64` (64-bit ring).
6217 /// `CYCLE_SIZE = 2^64 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6218 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6219 pub struct W64;
6220 impl ConstrainedTypeShape for W64 {
6221 const IRI: &'static str = "https://uor.foundation/witt/W64";
6222 const SITE_COUNT: usize = 1;
6223 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6224 const CYCLE_SIZE: u64 = u64::MAX;
6225 }
6226 impl __sdk_seal::Sealed for W64 {}
6227 impl IntoBindingValue for W64 {
6228 const MAX_BYTES: usize = 0;
6229 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6230 Ok(0)
6231 }
6232 }
6233 impl GroundedShape for W64 {}
6234 impl PartitionProductFields for W64 {
6235 const FIELDS: &'static [(u32, u32)] = &[];
6236 const FIELD_NAMES: &'static [&'static str] = &[];
6237 }
6238
6239 /// ADR-032 Witt-level domain marker for `W72` (72-bit ring).
6240 /// `CYCLE_SIZE = 2^72 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6241 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6242 pub struct W72;
6243 impl ConstrainedTypeShape for W72 {
6244 const IRI: &'static str = "https://uor.foundation/witt/W72";
6245 const SITE_COUNT: usize = 1;
6246 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6247 const CYCLE_SIZE: u64 = u64::MAX;
6248 }
6249 impl __sdk_seal::Sealed for W72 {}
6250 impl IntoBindingValue for W72 {
6251 const MAX_BYTES: usize = 0;
6252 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6253 Ok(0)
6254 }
6255 }
6256 impl GroundedShape for W72 {}
6257 impl PartitionProductFields for W72 {
6258 const FIELDS: &'static [(u32, u32)] = &[];
6259 const FIELD_NAMES: &'static [&'static str] = &[];
6260 }
6261
6262 /// ADR-032 Witt-level domain marker for `W80` (80-bit ring).
6263 /// `CYCLE_SIZE = 2^80 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6264 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6265 pub struct W80;
6266 impl ConstrainedTypeShape for W80 {
6267 const IRI: &'static str = "https://uor.foundation/witt/W80";
6268 const SITE_COUNT: usize = 1;
6269 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6270 const CYCLE_SIZE: u64 = u64::MAX;
6271 }
6272 impl __sdk_seal::Sealed for W80 {}
6273 impl IntoBindingValue for W80 {
6274 const MAX_BYTES: usize = 0;
6275 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6276 Ok(0)
6277 }
6278 }
6279 impl GroundedShape for W80 {}
6280 impl PartitionProductFields for W80 {
6281 const FIELDS: &'static [(u32, u32)] = &[];
6282 const FIELD_NAMES: &'static [&'static str] = &[];
6283 }
6284
6285 /// ADR-032 Witt-level domain marker for `W88` (88-bit ring).
6286 /// `CYCLE_SIZE = 2^88 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6287 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6288 pub struct W88;
6289 impl ConstrainedTypeShape for W88 {
6290 const IRI: &'static str = "https://uor.foundation/witt/W88";
6291 const SITE_COUNT: usize = 1;
6292 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6293 const CYCLE_SIZE: u64 = u64::MAX;
6294 }
6295 impl __sdk_seal::Sealed for W88 {}
6296 impl IntoBindingValue for W88 {
6297 const MAX_BYTES: usize = 0;
6298 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6299 Ok(0)
6300 }
6301 }
6302 impl GroundedShape for W88 {}
6303 impl PartitionProductFields for W88 {
6304 const FIELDS: &'static [(u32, u32)] = &[];
6305 const FIELD_NAMES: &'static [&'static str] = &[];
6306 }
6307
6308 /// ADR-032 Witt-level domain marker for `W96` (96-bit ring).
6309 /// `CYCLE_SIZE = 2^96 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6310 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6311 pub struct W96;
6312 impl ConstrainedTypeShape for W96 {
6313 const IRI: &'static str = "https://uor.foundation/witt/W96";
6314 const SITE_COUNT: usize = 1;
6315 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6316 const CYCLE_SIZE: u64 = u64::MAX;
6317 }
6318 impl __sdk_seal::Sealed for W96 {}
6319 impl IntoBindingValue for W96 {
6320 const MAX_BYTES: usize = 0;
6321 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6322 Ok(0)
6323 }
6324 }
6325 impl GroundedShape for W96 {}
6326 impl PartitionProductFields for W96 {
6327 const FIELDS: &'static [(u32, u32)] = &[];
6328 const FIELD_NAMES: &'static [&'static str] = &[];
6329 }
6330
6331 /// ADR-032 Witt-level domain marker for `W104` (104-bit ring).
6332 /// `CYCLE_SIZE = 2^104 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6333 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6334 pub struct W104;
6335 impl ConstrainedTypeShape for W104 {
6336 const IRI: &'static str = "https://uor.foundation/witt/W104";
6337 const SITE_COUNT: usize = 1;
6338 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6339 const CYCLE_SIZE: u64 = u64::MAX;
6340 }
6341 impl __sdk_seal::Sealed for W104 {}
6342 impl IntoBindingValue for W104 {
6343 const MAX_BYTES: usize = 0;
6344 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6345 Ok(0)
6346 }
6347 }
6348 impl GroundedShape for W104 {}
6349 impl PartitionProductFields for W104 {
6350 const FIELDS: &'static [(u32, u32)] = &[];
6351 const FIELD_NAMES: &'static [&'static str] = &[];
6352 }
6353
6354 /// ADR-032 Witt-level domain marker for `W112` (112-bit ring).
6355 /// `CYCLE_SIZE = 2^112 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6356 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6357 pub struct W112;
6358 impl ConstrainedTypeShape for W112 {
6359 const IRI: &'static str = "https://uor.foundation/witt/W112";
6360 const SITE_COUNT: usize = 1;
6361 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6362 const CYCLE_SIZE: u64 = u64::MAX;
6363 }
6364 impl __sdk_seal::Sealed for W112 {}
6365 impl IntoBindingValue for W112 {
6366 const MAX_BYTES: usize = 0;
6367 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6368 Ok(0)
6369 }
6370 }
6371 impl GroundedShape for W112 {}
6372 impl PartitionProductFields for W112 {
6373 const FIELDS: &'static [(u32, u32)] = &[];
6374 const FIELD_NAMES: &'static [&'static str] = &[];
6375 }
6376
6377 /// ADR-032 Witt-level domain marker for `W120` (120-bit ring).
6378 /// `CYCLE_SIZE = 2^120 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6379 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6380 pub struct W120;
6381 impl ConstrainedTypeShape for W120 {
6382 const IRI: &'static str = "https://uor.foundation/witt/W120";
6383 const SITE_COUNT: usize = 1;
6384 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6385 const CYCLE_SIZE: u64 = u64::MAX;
6386 }
6387 impl __sdk_seal::Sealed for W120 {}
6388 impl IntoBindingValue for W120 {
6389 const MAX_BYTES: usize = 0;
6390 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6391 Ok(0)
6392 }
6393 }
6394 impl GroundedShape for W120 {}
6395 impl PartitionProductFields for W120 {
6396 const FIELDS: &'static [(u32, u32)] = &[];
6397 const FIELD_NAMES: &'static [&'static str] = &[];
6398 }
6399
6400 /// ADR-032 Witt-level domain marker for `W128` (128-bit ring).
6401 /// `CYCLE_SIZE = 2^128 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6402 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6403 pub struct W128;
6404 impl ConstrainedTypeShape for W128 {
6405 const IRI: &'static str = "https://uor.foundation/witt/W128";
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 W128 {}
6411 impl IntoBindingValue for W128 {
6412 const MAX_BYTES: usize = 0;
6413 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6414 Ok(0)
6415 }
6416 }
6417 impl GroundedShape for W128 {}
6418 impl PartitionProductFields for W128 {
6419 const FIELDS: &'static [(u32, u32)] = &[];
6420 const FIELD_NAMES: &'static [&'static str] = &[];
6421 }
6422
6423 /// ADR-032 Witt-level domain marker for `W160` (160-bit ring).
6424 /// `CYCLE_SIZE = 2^160 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6425 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6426 pub struct W160;
6427 impl ConstrainedTypeShape for W160 {
6428 const IRI: &'static str = "https://uor.foundation/witt/W160";
6429 const SITE_COUNT: usize = 1;
6430 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6431 const CYCLE_SIZE: u64 = u64::MAX;
6432 }
6433 impl __sdk_seal::Sealed for W160 {}
6434 impl IntoBindingValue for W160 {
6435 const MAX_BYTES: usize = 0;
6436 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6437 Ok(0)
6438 }
6439 }
6440 impl GroundedShape for W160 {}
6441 impl PartitionProductFields for W160 {
6442 const FIELDS: &'static [(u32, u32)] = &[];
6443 const FIELD_NAMES: &'static [&'static str] = &[];
6444 }
6445
6446 /// ADR-032 Witt-level domain marker for `W192` (192-bit ring).
6447 /// `CYCLE_SIZE = 2^192 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6448 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6449 pub struct W192;
6450 impl ConstrainedTypeShape for W192 {
6451 const IRI: &'static str = "https://uor.foundation/witt/W192";
6452 const SITE_COUNT: usize = 1;
6453 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6454 const CYCLE_SIZE: u64 = u64::MAX;
6455 }
6456 impl __sdk_seal::Sealed for W192 {}
6457 impl IntoBindingValue for W192 {
6458 const MAX_BYTES: usize = 0;
6459 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6460 Ok(0)
6461 }
6462 }
6463 impl GroundedShape for W192 {}
6464 impl PartitionProductFields for W192 {
6465 const FIELDS: &'static [(u32, u32)] = &[];
6466 const FIELD_NAMES: &'static [&'static str] = &[];
6467 }
6468
6469 /// ADR-032 Witt-level domain marker for `W224` (224-bit ring).
6470 /// `CYCLE_SIZE = 2^224 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6471 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6472 pub struct W224;
6473 impl ConstrainedTypeShape for W224 {
6474 const IRI: &'static str = "https://uor.foundation/witt/W224";
6475 const SITE_COUNT: usize = 1;
6476 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6477 const CYCLE_SIZE: u64 = u64::MAX;
6478 }
6479 impl __sdk_seal::Sealed for W224 {}
6480 impl IntoBindingValue for W224 {
6481 const MAX_BYTES: usize = 0;
6482 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6483 Ok(0)
6484 }
6485 }
6486 impl GroundedShape for W224 {}
6487 impl PartitionProductFields for W224 {
6488 const FIELDS: &'static [(u32, u32)] = &[];
6489 const FIELD_NAMES: &'static [&'static str] = &[];
6490 }
6491
6492 /// ADR-032 Witt-level domain marker for `W256` (256-bit ring).
6493 /// `CYCLE_SIZE = 2^256 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6494 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6495 pub struct W256;
6496 impl ConstrainedTypeShape for W256 {
6497 const IRI: &'static str = "https://uor.foundation/witt/W256";
6498 const SITE_COUNT: usize = 1;
6499 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6500 const CYCLE_SIZE: u64 = u64::MAX;
6501 }
6502 impl __sdk_seal::Sealed for W256 {}
6503 impl IntoBindingValue for W256 {
6504 const MAX_BYTES: usize = 0;
6505 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6506 Ok(0)
6507 }
6508 }
6509 impl GroundedShape for W256 {}
6510 impl PartitionProductFields for W256 {
6511 const FIELDS: &'static [(u32, u32)] = &[];
6512 const FIELD_NAMES: &'static [&'static str] = &[];
6513 }
6514
6515 /// ADR-032 Witt-level domain marker for `W384` (384-bit ring).
6516 /// `CYCLE_SIZE = 2^384 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6517 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6518 pub struct W384;
6519 impl ConstrainedTypeShape for W384 {
6520 const IRI: &'static str = "https://uor.foundation/witt/W384";
6521 const SITE_COUNT: usize = 1;
6522 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6523 const CYCLE_SIZE: u64 = u64::MAX;
6524 }
6525 impl __sdk_seal::Sealed for W384 {}
6526 impl IntoBindingValue for W384 {
6527 const MAX_BYTES: usize = 0;
6528 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6529 Ok(0)
6530 }
6531 }
6532 impl GroundedShape for W384 {}
6533 impl PartitionProductFields for W384 {
6534 const FIELDS: &'static [(u32, u32)] = &[];
6535 const FIELD_NAMES: &'static [&'static str] = &[];
6536 }
6537
6538 /// ADR-032 Witt-level domain marker for `W448` (448-bit ring).
6539 /// `CYCLE_SIZE = 2^448 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6540 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6541 pub struct W448;
6542 impl ConstrainedTypeShape for W448 {
6543 const IRI: &'static str = "https://uor.foundation/witt/W448";
6544 const SITE_COUNT: usize = 1;
6545 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6546 const CYCLE_SIZE: u64 = u64::MAX;
6547 }
6548 impl __sdk_seal::Sealed for W448 {}
6549 impl IntoBindingValue for W448 {
6550 const MAX_BYTES: usize = 0;
6551 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6552 Ok(0)
6553 }
6554 }
6555 impl GroundedShape for W448 {}
6556 impl PartitionProductFields for W448 {
6557 const FIELDS: &'static [(u32, u32)] = &[];
6558 const FIELD_NAMES: &'static [&'static str] = &[];
6559 }
6560
6561 /// ADR-032 Witt-level domain marker for `W512` (512-bit ring).
6562 /// `CYCLE_SIZE = 2^512 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6563 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6564 pub struct W512;
6565 impl ConstrainedTypeShape for W512 {
6566 const IRI: &'static str = "https://uor.foundation/witt/W512";
6567 const SITE_COUNT: usize = 1;
6568 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6569 const CYCLE_SIZE: u64 = u64::MAX;
6570 }
6571 impl __sdk_seal::Sealed for W512 {}
6572 impl IntoBindingValue for W512 {
6573 const MAX_BYTES: usize = 0;
6574 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6575 Ok(0)
6576 }
6577 }
6578 impl GroundedShape for W512 {}
6579 impl PartitionProductFields for W512 {
6580 const FIELDS: &'static [(u32, u32)] = &[];
6581 const FIELD_NAMES: &'static [&'static str] = &[];
6582 }
6583
6584 /// ADR-032 Witt-level domain marker for `W520` (520-bit ring).
6585 /// `CYCLE_SIZE = 2^520 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6586 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6587 pub struct W520;
6588 impl ConstrainedTypeShape for W520 {
6589 const IRI: &'static str = "https://uor.foundation/witt/W520";
6590 const SITE_COUNT: usize = 1;
6591 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6592 const CYCLE_SIZE: u64 = u64::MAX;
6593 }
6594 impl __sdk_seal::Sealed for W520 {}
6595 impl IntoBindingValue for W520 {
6596 const MAX_BYTES: usize = 0;
6597 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6598 Ok(0)
6599 }
6600 }
6601 impl GroundedShape for W520 {}
6602 impl PartitionProductFields for W520 {
6603 const FIELDS: &'static [(u32, u32)] = &[];
6604 const FIELD_NAMES: &'static [&'static str] = &[];
6605 }
6606
6607 /// ADR-032 Witt-level domain marker for `W528` (528-bit ring).
6608 /// `CYCLE_SIZE = 2^528 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6609 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6610 pub struct W528;
6611 impl ConstrainedTypeShape for W528 {
6612 const IRI: &'static str = "https://uor.foundation/witt/W528";
6613 const SITE_COUNT: usize = 1;
6614 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6615 const CYCLE_SIZE: u64 = u64::MAX;
6616 }
6617 impl __sdk_seal::Sealed for W528 {}
6618 impl IntoBindingValue for W528 {
6619 const MAX_BYTES: usize = 0;
6620 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6621 Ok(0)
6622 }
6623 }
6624 impl GroundedShape for W528 {}
6625 impl PartitionProductFields for W528 {
6626 const FIELDS: &'static [(u32, u32)] = &[];
6627 const FIELD_NAMES: &'static [&'static str] = &[];
6628 }
6629
6630 /// ADR-032 Witt-level domain marker for `W1024` (1024-bit ring).
6631 /// `CYCLE_SIZE = 2^1024 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6632 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6633 pub struct W1024;
6634 impl ConstrainedTypeShape for W1024 {
6635 const IRI: &'static str = "https://uor.foundation/witt/W1024";
6636 const SITE_COUNT: usize = 1;
6637 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6638 const CYCLE_SIZE: u64 = u64::MAX;
6639 }
6640 impl __sdk_seal::Sealed for W1024 {}
6641 impl IntoBindingValue for W1024 {
6642 const MAX_BYTES: usize = 0;
6643 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6644 Ok(0)
6645 }
6646 }
6647 impl GroundedShape for W1024 {}
6648 impl PartitionProductFields for W1024 {
6649 const FIELDS: &'static [(u32, u32)] = &[];
6650 const FIELD_NAMES: &'static [&'static str] = &[];
6651 }
6652
6653 /// ADR-032 Witt-level domain marker for `W2048` (2048-bit ring).
6654 /// `CYCLE_SIZE = 2^2048 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6655 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6656 pub struct W2048;
6657 impl ConstrainedTypeShape for W2048 {
6658 const IRI: &'static str = "https://uor.foundation/witt/W2048";
6659 const SITE_COUNT: usize = 1;
6660 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6661 const CYCLE_SIZE: u64 = u64::MAX;
6662 }
6663 impl __sdk_seal::Sealed for W2048 {}
6664 impl IntoBindingValue for W2048 {
6665 const MAX_BYTES: usize = 0;
6666 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6667 Ok(0)
6668 }
6669 }
6670 impl GroundedShape for W2048 {}
6671 impl PartitionProductFields for W2048 {
6672 const FIELDS: &'static [(u32, u32)] = &[];
6673 const FIELD_NAMES: &'static [&'static str] = &[];
6674 }
6675
6676 /// ADR-032 Witt-level domain marker for `W4096` (4096-bit ring).
6677 /// `CYCLE_SIZE = 2^4096 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6678 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6679 pub struct W4096;
6680 impl ConstrainedTypeShape for W4096 {
6681 const IRI: &'static str = "https://uor.foundation/witt/W4096";
6682 const SITE_COUNT: usize = 1;
6683 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6684 const CYCLE_SIZE: u64 = u64::MAX;
6685 }
6686 impl __sdk_seal::Sealed for W4096 {}
6687 impl IntoBindingValue for W4096 {
6688 const MAX_BYTES: usize = 0;
6689 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6690 Ok(0)
6691 }
6692 }
6693 impl GroundedShape for W4096 {}
6694 impl PartitionProductFields for W4096 {
6695 const FIELDS: &'static [(u32, u32)] = &[];
6696 const FIELD_NAMES: &'static [&'static str] = &[];
6697 }
6698
6699 /// ADR-032 Witt-level domain marker for `W8192` (8192-bit ring).
6700 /// `CYCLE_SIZE = 2^8192 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6701 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6702 pub struct W8192;
6703 impl ConstrainedTypeShape for W8192 {
6704 const IRI: &'static str = "https://uor.foundation/witt/W8192";
6705 const SITE_COUNT: usize = 1;
6706 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6707 const CYCLE_SIZE: u64 = u64::MAX;
6708 }
6709 impl __sdk_seal::Sealed for W8192 {}
6710 impl IntoBindingValue for W8192 {
6711 const MAX_BYTES: usize = 0;
6712 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6713 Ok(0)
6714 }
6715 }
6716 impl GroundedShape for W8192 {}
6717 impl PartitionProductFields for W8192 {
6718 const FIELDS: &'static [(u32, u32)] = &[];
6719 const FIELD_NAMES: &'static [&'static str] = &[];
6720 }
6721
6722 /// ADR-032 Witt-level domain marker for `W12288` (12288-bit ring).
6723 /// `CYCLE_SIZE = 2^12288 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6724 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6725 pub struct W12288;
6726 impl ConstrainedTypeShape for W12288 {
6727 const IRI: &'static str = "https://uor.foundation/witt/W12288";
6728 const SITE_COUNT: usize = 1;
6729 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6730 const CYCLE_SIZE: u64 = u64::MAX;
6731 }
6732 impl __sdk_seal::Sealed for W12288 {}
6733 impl IntoBindingValue for W12288 {
6734 const MAX_BYTES: usize = 0;
6735 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6736 Ok(0)
6737 }
6738 }
6739 impl GroundedShape for W12288 {}
6740 impl PartitionProductFields for W12288 {
6741 const FIELDS: &'static [(u32, u32)] = &[];
6742 const FIELD_NAMES: &'static [&'static str] = &[];
6743 }
6744
6745 /// ADR-032 Witt-level domain marker for `W16384` (16384-bit ring).
6746 /// `CYCLE_SIZE = 2^16384 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6747 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6748 pub struct W16384;
6749 impl ConstrainedTypeShape for W16384 {
6750 const IRI: &'static str = "https://uor.foundation/witt/W16384";
6751 const SITE_COUNT: usize = 1;
6752 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6753 const CYCLE_SIZE: u64 = u64::MAX;
6754 }
6755 impl __sdk_seal::Sealed for W16384 {}
6756 impl IntoBindingValue for W16384 {
6757 const MAX_BYTES: usize = 0;
6758 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6759 Ok(0)
6760 }
6761 }
6762 impl GroundedShape for W16384 {}
6763 impl PartitionProductFields for W16384 {
6764 const FIELDS: &'static [(u32, u32)] = &[];
6765 const FIELD_NAMES: &'static [&'static str] = &[];
6766 }
6767
6768 /// ADR-032 Witt-level domain marker for `W32768` (32768-bit ring).
6769 /// `CYCLE_SIZE = 2^32768 = u64::MAX (saturated)`. Used as the `<DomainTy>` operand of
6770 /// `first_admit(<DomainTy>, |i| …)` (G16) in `prism_model!` closures.
6771 pub struct W32768;
6772 impl ConstrainedTypeShape for W32768 {
6773 const IRI: &'static str = "https://uor.foundation/witt/W32768";
6774 const SITE_COUNT: usize = 1;
6775 const CONSTRAINTS: &'static [ConstraintRef] = &[];
6776 const CYCLE_SIZE: u64 = u64::MAX;
6777 }
6778 impl __sdk_seal::Sealed for W32768 {}
6779 impl IntoBindingValue for W32768 {
6780 const MAX_BYTES: usize = 0;
6781 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
6782 Ok(0)
6783 }
6784 }
6785 impl GroundedShape for W32768 {}
6786 impl PartitionProductFields for W32768 {
6787 const FIELDS: &'static [(u32, u32)] = &[];
6788 const FIELD_NAMES: &'static [&'static str] = &[];
6789 }
6790}
6791
6792/// Admit a downstream [`ConstrainedTypeShape`] into the reduction pipeline.
6793/// Runs the full preflight chain on `T::CONSTRAINTS`:
6794/// [`preflight_feasibility`] and [`preflight_package_coherence`]. On success,
6795/// wraps the supplied `shape` in a [`Validated`] carrying `Runtime` phase.
6796/// # Errors
6797/// Returns [`ShapeViolation`] if any constraint in `T::CONSTRAINTS` fails
6798/// feasibility checking (e.g., residue out of range, depth min > max) or if
6799/// the constraint system is internally incoherent (e.g., contradictory
6800/// residue constraints on the same modulus).
6801/// # Example
6802/// ```
6803/// use uor_foundation::pipeline::{
6804/// ConstrainedTypeShape, ConstraintRef, validate_constrained_type,
6805/// };
6806/// pub struct MyShape;
6807/// impl ConstrainedTypeShape for MyShape {
6808/// const IRI: &'static str = "https://example.org/MyShape";
6809/// const SITE_COUNT: usize = 2;
6810/// const CONSTRAINTS: &'static [ConstraintRef] = &[
6811/// ConstraintRef::Residue { modulus: 5, residue: 2 },
6812/// ];
6813/// const CYCLE_SIZE: u64 = 5; // ADR-032: 5 residue classes mod 5
6814/// }
6815/// let validated = validate_constrained_type(MyShape).unwrap();
6816/// # let _ = validated;
6817/// ```
6818pub fn validate_constrained_type<T: ConstrainedTypeShape>(
6819 shape: T,
6820) -> Result<Validated<T, crate::enforcement::Runtime>, ShapeViolation> {
6821 preflight_feasibility(T::CONSTRAINTS)?;
6822 preflight_package_coherence(T::CONSTRAINTS)?;
6823 Ok(Validated::new(shape))
6824}
6825
6826/// Const-fn companion for [`validate_constrained_type`].
6827/// Admits a downstream [`ConstrainedTypeShape`] at compile time, running the
6828/// same preflight checks as the runtime variant but in `const` context.
6829/// # Scope
6830/// `ConstraintRef::Bound { observable_iri, args_repr, .. }` with
6831/// `observable_iri == "https://uor.foundation/observable/LandauerCost"`
6832/// requires `f64::from_bits` for args parsing, which is stable in `const`
6833/// context only from Rust 1.83. The crate's MSRV is 1.81, so this variant
6834/// rejects const admission of `LandauerCost`-bound constraints with
6835/// [`ShapeViolation`] and recommends the runtime [`validate_constrained_type`]
6836/// for those inputs. All other `ConstraintRef` variants admit at const time.
6837/// # Errors
6838/// Same as [`validate_constrained_type`], plus the const-context rejection
6839/// for `LandauerCost`-bound constraints described above.
6840/// The `T: Copy` bound is required by `const fn` — destructor invocation is
6841/// not yet const-stable, and `Validated<T>` carries `T` by value. Shape
6842/// markers are typically zero-sized types which are trivially `Copy`.
6843pub const fn validate_constrained_type_const<T: ConstrainedTypeShape + Copy>(
6844 shape: T,
6845) -> Result<Validated<T, crate::enforcement::CompileTime>, ShapeViolation> {
6846 // Const-path preflight: walk CONSTRAINTS and apply per-variant const checks.
6847 // Rejects LandauerCost-bound constraints that need non-const f64::from_bits.
6848 let constraints = T::CONSTRAINTS;
6849 let mut i = 0;
6850 while i < constraints.len() {
6851 let ok = match &constraints[i] {
6852 ConstraintRef::SatClauses { clauses, num_vars } => *num_vars != 0 || clauses.is_empty(),
6853 ConstraintRef::Residue { modulus, residue } => *modulus != 0 && *residue < *modulus,
6854 ConstraintRef::Carry { .. } => true,
6855 ConstraintRef::Depth { min, max } => *min <= *max,
6856 ConstraintRef::Hamming { bound } => *bound <= 32_768,
6857 ConstraintRef::Site { .. } => true,
6858 ConstraintRef::Affine {
6859 coefficients,
6860 coefficient_count,
6861 bias,
6862 } => {
6863 // Mirror preflight_feasibility's Affine arm in const context.
6864 let count = *coefficient_count as usize;
6865 if count == 0 {
6866 false
6867 } else {
6868 let mut ok_coeff = true;
6869 let mut idx = 0;
6870 while idx < count && idx < AFFINE_MAX_COEFFS {
6871 if coefficients[idx] == i64::MIN {
6872 ok_coeff = false;
6873 break;
6874 }
6875 idx += 1;
6876 }
6877 ok_coeff && is_affine_consistent(coefficients, *coefficient_count, *bias)
6878 }
6879 }
6880 ConstraintRef::Bound { observable_iri, .. } => {
6881 // const-fn scope: LandauerCost needs f64::from_bits (stable in
6882 // const at 1.83). Reject it here; runtime admission handles it.
6883 !crate::enforcement::str_eq(
6884 observable_iri,
6885 "https://uor.foundation/observable/LandauerCost",
6886 )
6887 }
6888 ConstraintRef::Conjunction {
6889 conjuncts,
6890 conjunct_count,
6891 } => conjunction_all_sat(conjuncts, *conjunct_count),
6892 // ADR-057: const-time validation defers Recurse to runtime
6893 // admission (parallel to ADR-049's LandauerCost deferral).
6894 // The only const check is that `shape_iri` is non-empty —
6895 // the registry lookup happens at the runtime admission path.
6896 ConstraintRef::Recurse { shape_iri, .. } => !shape_iri.is_empty(),
6897 };
6898 if !ok {
6899 return Err(ShapeViolation {
6900 shape_iri: "https://uor.foundation/type/ConstrainedType",
6901 constraint_iri: "https://uor.foundation/type/ConstrainedType_const_constraint",
6902 property_iri: "https://uor.foundation/type/constraints",
6903 expected_range: "https://uor.foundation/type/Constraint",
6904 min_count: 1,
6905 max_count: 1,
6906 kind: ViolationKind::ValueCheck,
6907 });
6908 }
6909 i += 1;
6910 }
6911 Ok(Validated::new(shape))
6912}
6913
6914/// Result of `fragment_classify`: which `predicate:*Shape` fragment the
6915/// input belongs to. Drives `InhabitanceResolver` dispatch routing.
6916#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6917pub enum FragmentKind {
6918 /// `predicate:Is2SatShape` — clauses of width ≤ 2.
6919 TwoSat,
6920 /// `predicate:IsHornShape` — clauses with ≤ 1 positive literal.
6921 Horn,
6922 /// `predicate:IsResidualFragment` — catch-all; no polynomial bound.
6923 Residual,
6924}
6925
6926/// Classify a constraint system into one of the three dispatch fragments.
6927/// The classifier inspects the first `SatClauses` constraint (if any) and
6928/// applies the ontology's shape predicates. Constraint systems with no
6929/// `SatClauses` constraint — e.g., pure residue/hamming constraints — are
6930/// classified as `Residual`: the dispatch table has no polynomial decider
6931/// for them, so they route to the `ResidualVerdictResolver` catch-all.
6932#[must_use]
6933pub const fn fragment_classify(constraints: &[ConstraintRef]) -> FragmentKind {
6934 let mut i = 0;
6935 while i < constraints.len() {
6936 if let ConstraintRef::SatClauses { clauses, .. } = constraints[i] {
6937 // Classify by maximum clause width and positive-literal count.
6938 let mut max_width: usize = 0;
6939 let mut horn: bool = true;
6940 let mut j = 0;
6941 while j < clauses.len() {
6942 let clause = clauses[j];
6943 if clause.len() > max_width {
6944 max_width = clause.len();
6945 }
6946 let mut positives: usize = 0;
6947 let mut k = 0;
6948 while k < clause.len() {
6949 let (_, negated) = clause[k];
6950 if !negated {
6951 positives += 1;
6952 }
6953 k += 1;
6954 }
6955 if positives > 1 {
6956 horn = false;
6957 }
6958 j += 1;
6959 }
6960 if max_width <= 2 {
6961 return FragmentKind::TwoSat;
6962 } else if horn {
6963 return FragmentKind::Horn;
6964 } else {
6965 return FragmentKind::Residual;
6966 }
6967 }
6968 i += 1;
6969 }
6970 // No SAT clauses at all — residual.
6971 FragmentKind::Residual
6972}
6973
6974/// Aspvall-Plass-Tarjan 2-SAT decider: returns `true` iff the clause list
6975/// is satisfiable.
6976/// Builds the implication graph: for each clause `(a | b)`, adds
6977/// `¬a → b` and `¬b → a`. Runs Tarjan's SCC algorithm and checks that
6978/// no variable and its negation share an SCC. O(n+m) via iterative
6979/// Tarjan (the `no_std` path can't recurse freely).
6980/// 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.
6981const TWO_SAT_MAX_VARS: usize = 256;
6982const TWO_SAT_MAX_NODES: usize = TWO_SAT_MAX_VARS * 2;
6983const TWO_SAT_MAX_EDGES: usize = 2048;
6984
6985/// 2-SAT decision result.
6986#[must_use]
6987pub fn decide_two_sat(clauses: &[&[(u32, bool)]], num_vars: u32) -> bool {
6988 if (num_vars as usize) > TWO_SAT_MAX_VARS {
6989 return false;
6990 }
6991 let n = (num_vars as usize) * 2;
6992 // Node index: 2*var is positive literal, 2*var+1 is negated.
6993 let mut adj_starts = [0usize; TWO_SAT_MAX_NODES + 1];
6994 let mut adj_targets = [0usize; TWO_SAT_MAX_EDGES];
6995 // First pass: count out-degrees.
6996 for clause in clauses {
6997 if clause.len() > 2 || clause.is_empty() {
6998 return false;
6999 }
7000 if clause.len() == 1 {
7001 let (v, neg) = clause[0];
7002 let lit = lit_index(v, neg);
7003 let neg_lit = lit_index(v, !neg);
7004 // x ↔ (x ∨ x): ¬x → x (assignment forced)
7005 if neg_lit < n + 1 {
7006 adj_starts[neg_lit + 1] += 1;
7007 }
7008 let _ = lit;
7009 } else {
7010 let (a, an) = clause[0];
7011 let (b, bn) = clause[1];
7012 // ¬a → b, ¬b → a
7013 let na = lit_index(a, !an);
7014 let nb = lit_index(b, !bn);
7015 if na + 1 < n + 1 {
7016 adj_starts[na + 1] += 1;
7017 }
7018 if nb + 1 < n + 1 {
7019 adj_starts[nb + 1] += 1;
7020 }
7021 }
7022 }
7023 // Prefix-sum to get adjacency starts.
7024 let mut i = 1;
7025 while i <= n {
7026 adj_starts[i] += adj_starts[i - 1];
7027 i += 1;
7028 }
7029 let edge_count = adj_starts[n];
7030 if edge_count > TWO_SAT_MAX_EDGES {
7031 return false;
7032 }
7033 let mut fill = [0usize; TWO_SAT_MAX_NODES];
7034 for clause in clauses {
7035 if clause.len() == 1 {
7036 let (v, neg) = clause[0];
7037 let pos_lit = lit_index(v, neg);
7038 let neg_lit = lit_index(v, !neg);
7039 let slot = adj_starts[neg_lit] + fill[neg_lit];
7040 adj_targets[slot] = pos_lit;
7041 fill[neg_lit] += 1;
7042 } else {
7043 let (a, an) = clause[0];
7044 let (b, bn) = clause[1];
7045 let pa = lit_index(a, an);
7046 let na = lit_index(a, !an);
7047 let pb = lit_index(b, bn);
7048 let nb = lit_index(b, !bn);
7049 let s1 = adj_starts[na] + fill[na];
7050 adj_targets[s1] = pb;
7051 fill[na] += 1;
7052 let s2 = adj_starts[nb] + fill[nb];
7053 adj_targets[s2] = pa;
7054 fill[nb] += 1;
7055 }
7056 }
7057 // Iterative Tarjan's SCC.
7058 let mut index_counter: usize = 0;
7059 let mut indices = [usize::MAX; TWO_SAT_MAX_NODES];
7060 let mut lowlinks = [0usize; TWO_SAT_MAX_NODES];
7061 let mut on_stack = [false; TWO_SAT_MAX_NODES];
7062 let mut stack = [0usize; TWO_SAT_MAX_NODES];
7063 let mut stack_top: usize = 0;
7064 let mut scc_id = [usize::MAX; TWO_SAT_MAX_NODES];
7065 let mut scc_count: usize = 0;
7066 let mut call_stack = [(0usize, 0usize); TWO_SAT_MAX_NODES];
7067 let mut call_top: usize = 0;
7068 let mut v = 0;
7069 while v < n {
7070 if indices[v] == usize::MAX {
7071 call_stack[call_top] = (v, adj_starts[v]);
7072 call_top += 1;
7073 indices[v] = index_counter;
7074 lowlinks[v] = index_counter;
7075 index_counter += 1;
7076 stack[stack_top] = v;
7077 stack_top += 1;
7078 on_stack[v] = true;
7079 while call_top > 0 {
7080 let (u, mut next_edge) = call_stack[call_top - 1];
7081 let end_edge = adj_starts[u + 1];
7082 let mut advanced = false;
7083 while next_edge < end_edge {
7084 let w = adj_targets[next_edge];
7085 next_edge += 1;
7086 if indices[w] == usize::MAX {
7087 call_stack[call_top - 1] = (u, next_edge);
7088 indices[w] = index_counter;
7089 lowlinks[w] = index_counter;
7090 index_counter += 1;
7091 stack[stack_top] = w;
7092 stack_top += 1;
7093 on_stack[w] = true;
7094 call_stack[call_top] = (w, adj_starts[w]);
7095 call_top += 1;
7096 advanced = true;
7097 break;
7098 } else if on_stack[w] && indices[w] < lowlinks[u] {
7099 lowlinks[u] = indices[w];
7100 }
7101 }
7102 if !advanced {
7103 call_stack[call_top - 1] = (u, next_edge);
7104 if lowlinks[u] == indices[u] {
7105 loop {
7106 stack_top -= 1;
7107 let w = stack[stack_top];
7108 on_stack[w] = false;
7109 scc_id[w] = scc_count;
7110 if w == u {
7111 break;
7112 }
7113 }
7114 scc_count += 1;
7115 }
7116 call_top -= 1;
7117 if call_top > 0 {
7118 let (parent, _) = call_stack[call_top - 1];
7119 if lowlinks[u] < lowlinks[parent] {
7120 lowlinks[parent] = lowlinks[u];
7121 }
7122 }
7123 }
7124 }
7125 }
7126 v += 1;
7127 }
7128 // Unsatisfiable iff x and ¬x are in the same SCC for any variable.
7129 let mut var = 0u32;
7130 while var < num_vars {
7131 let pos = lit_index(var, false);
7132 let neg = lit_index(var, true);
7133 if scc_id[pos] == scc_id[neg] {
7134 return false;
7135 }
7136 var += 1;
7137 }
7138 true
7139}
7140
7141#[inline]
7142const fn lit_index(var: u32, negated: bool) -> usize {
7143 let base = (var as usize) * 2;
7144 if negated {
7145 base + 1
7146 } else {
7147 base
7148 }
7149}
7150
7151/// Horn-SAT decider via unit propagation. Returns `true` iff the clause
7152/// list is satisfiable.
7153/// Algorithm: start with all variables false. Repeatedly find a clause
7154/// whose negative literals are all satisfied but whose positive literal
7155/// is unassigned/false; set the positive literal true. Fail if a clause
7156/// with no positive literal has all its negatives satisfied.
7157/// Bounds (from `reduction:HornSatBound`): up to 256 variables.
7158const HORN_MAX_VARS: usize = 256;
7159
7160/// Horn-SAT decision result.
7161#[must_use]
7162pub fn decide_horn_sat(clauses: &[&[(u32, bool)]], num_vars: u32) -> bool {
7163 if (num_vars as usize) > HORN_MAX_VARS {
7164 return false;
7165 }
7166 let mut assignment = [false; HORN_MAX_VARS];
7167 let n = num_vars as usize;
7168 loop {
7169 let mut changed = false;
7170 for clause in clauses {
7171 // Count positive literals.
7172 let mut positive: Option<u32> = None;
7173 let mut positive_count = 0;
7174 for (_, negated) in clause.iter() {
7175 if !*negated {
7176 positive_count += 1;
7177 }
7178 }
7179 if positive_count > 1 {
7180 return false;
7181 }
7182 for (var, negated) in clause.iter() {
7183 if !*negated {
7184 positive = Some(*var);
7185 }
7186 }
7187 // Check whether all negative literals are satisfied (var=true).
7188 let mut all_neg_satisfied = true;
7189 for (var, negated) in clause.iter() {
7190 if *negated {
7191 let idx = *var as usize;
7192 if idx >= n {
7193 return false;
7194 }
7195 if !assignment[idx] {
7196 all_neg_satisfied = false;
7197 break;
7198 }
7199 }
7200 }
7201 if all_neg_satisfied {
7202 match positive {
7203 None => return false,
7204 Some(v) => {
7205 let idx = v as usize;
7206 if idx >= n {
7207 return false;
7208 }
7209 if !assignment[idx] {
7210 assignment[idx] = true;
7211 changed = true;
7212 }
7213 }
7214 }
7215 }
7216 }
7217 if !changed {
7218 break;
7219 }
7220 }
7221 // Final verification pass.
7222 for clause in clauses {
7223 let mut satisfied = false;
7224 for (var, negated) in clause.iter() {
7225 let idx = *var as usize;
7226 if idx >= n {
7227 return false;
7228 }
7229 let val = assignment[idx];
7230 if (*negated && !val) || (!*negated && val) {
7231 satisfied = true;
7232 break;
7233 }
7234 }
7235 if !satisfied {
7236 return false;
7237 }
7238 }
7239 true
7240}
7241
7242/// `BudgetSolvencyCheck` (preflightOrder 0): `thermodynamicBudget` must be
7243/// ≥ `bitsWidth(unitWittLevel) × ln 2` per `op:GS_7` / `op:OA_5`.
7244/// Takes the budget in `k_B T · ln 2` units and the target Witt level in
7245/// bit-width. Returns `Ok(())` if solvent, `Err` with the shape violation.
7246pub fn preflight_budget_solvency(budget_units: u64, witt_bits: u32) -> Result<(), ShapeViolation> {
7247 // Landauer bound: one bit requires k_B T · ln 2. Integer form.
7248 let minimum = witt_bits as u64;
7249 if budget_units >= minimum {
7250 Ok(())
7251 } else {
7252 Err(ShapeViolation {
7253 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
7254 constraint_iri:
7255 "https://uor.foundation/conformance/compileUnit_thermodynamicBudget_constraint",
7256 property_iri: "https://uor.foundation/reduction/thermodynamicBudget",
7257 expected_range: "http://www.w3.org/2001/XMLSchema#decimal",
7258 min_count: 1,
7259 max_count: 1,
7260 kind: ViolationKind::ValueCheck,
7261 })
7262 }
7263}
7264
7265/// v0.2.2 Phase F: upper bound on `CarryDepthObservable` depth arguments.
7266/// Matches target §4.5's Witt-level tower ceiling (W16384).
7267pub const WITT_MAX_BITS: u16 = 16_384;
7268
7269/// v0.2.2 Phase F: ASCII parser for a single unsigned decimal `u32`.
7270/// Returns 0 on malformed input; the caller's downstream comparison (`depth <= WITT_MAX_BITS`)
7271/// accepts 0 as the pass-through degenerate depth, so malformed input is rejected
7272/// by the enclosing feasibility check only if the parsed value exceeds the cap.
7273/// For stricter input discipline, the caller pre-validates `args_repr` at builder time.
7274#[must_use]
7275pub fn parse_u32(s: &str) -> u32 {
7276 let bytes = s.as_bytes();
7277 let mut out: u32 = 0;
7278 let mut i = 0;
7279 while i < bytes.len() {
7280 let b = bytes[i];
7281 if !b.is_ascii_digit() {
7282 return 0;
7283 }
7284 out = out.saturating_mul(10).saturating_add((b - b'0') as u32);
7285 i += 1;
7286 }
7287 out
7288}
7289
7290/// v0.2.2 Phase F: ASCII parser for a single unsigned decimal `u64`.
7291#[must_use]
7292pub fn parse_u64(s: &str) -> u64 {
7293 let bytes = s.as_bytes();
7294 let mut out: u64 = 0;
7295 let mut i = 0;
7296 while i < bytes.len() {
7297 let b = bytes[i];
7298 if !b.is_ascii_digit() {
7299 return 0;
7300 }
7301 out = out.saturating_mul(10).saturating_add((b - b'0') as u64);
7302 i += 1;
7303 }
7304 out
7305}
7306
7307/// v0.2.2 Phase F: parser for `"modulus|residue"` decimal pairs.
7308/// Split on the first ASCII `|`; ASCII-digit-parse each half via `parse_u64`.
7309#[must_use]
7310pub fn parse_u64_pair(s: &str) -> (u64, u64) {
7311 let bytes = s.as_bytes();
7312 let mut split = bytes.len();
7313 let mut i = 0;
7314 while i < bytes.len() {
7315 if bytes[i] == b'|' {
7316 split = i;
7317 break;
7318 }
7319 i += 1;
7320 }
7321 if split >= bytes.len() {
7322 return (0, 0);
7323 }
7324 let (left, right_with_pipe) = s.split_at(split);
7325 let (_, right) = right_with_pipe.split_at(1);
7326 (parse_u64(left), parse_u64(right))
7327}
7328
7329/// v0.2.2 Phase F / Phase 9: parse a decimal `u64` representing an
7330/// IEEE-754 bit pattern. The bit pattern is content-deterministic; call sites
7331/// project to `H::Decimal` via `DecimalTranscendental::from_bits`.
7332#[must_use]
7333pub fn parse_u64_bits_str(s: &str) -> u64 {
7334 parse_u64(s)
7335}
7336
7337/// v0.2.2 Phase F: dispatch a `ConstraintRef::Bound` arm on its `observable_iri`.
7338/// Canonical observables: `ValueModObservable`, `CarryDepthObservable`, `LandauerCost`.
7339/// Unknown IRIs are rejected so that an unaudited observable cannot be threaded
7340/// through the preflight surface silently.
7341fn check_bound_feasibility(
7342 observable_iri: &'static str,
7343 bound_shape_iri: &'static str,
7344 args_repr: &'static str,
7345) -> Result<(), ShapeViolation> {
7346 const VALUE_MOD_IRI: &str = "https://uor.foundation/observable/ValueModObservable";
7347 const CARRY_DEPTH_IRI: &str = "https://uor.foundation/observable/CarryDepthObservable";
7348 const LANDAUER_IRI: &str = "https://uor.foundation/observable/LandauerCost";
7349 let ok = if crate::enforcement::str_eq(observable_iri, VALUE_MOD_IRI) {
7350 let (modulus, residue) = parse_u64_pair(args_repr);
7351 modulus != 0 && residue < modulus
7352 } else if crate::enforcement::str_eq(observable_iri, CARRY_DEPTH_IRI) {
7353 let depth = parse_u32(args_repr);
7354 depth <= WITT_MAX_BITS as u32
7355 } else if crate::enforcement::str_eq(observable_iri, LANDAUER_IRI) {
7356 // Project the bit pattern to f64 (default-host) for the
7357 // finite/positive-nats sanity check. Polymorphic consumers
7358 // construct their own H::Decimal via DecimalTranscendental.
7359 let bits = parse_u64_bits_str(args_repr);
7360 let nats = <f64 as crate::DecimalTranscendental>::from_bits(bits);
7361 nats.is_finite() && nats > 0.0
7362 } else {
7363 false
7364 };
7365 if ok {
7366 Ok(())
7367 } else {
7368 Err(ShapeViolation {
7369 shape_iri: bound_shape_iri,
7370 constraint_iri: "https://uor.foundation/type/BoundConstraint",
7371 property_iri: observable_iri,
7372 expected_range: "https://uor.foundation/observable/BaseMetric",
7373 min_count: 1,
7374 max_count: 1,
7375 kind: ViolationKind::ValueCheck,
7376 })
7377 }
7378}
7379
7380/// `FeasibilityCheck`: verify the constraint system isn't trivially
7381/// infeasible. Workstream E (target §1.5 + §4.7, v0.2.2 closure):
7382/// the closed six-kind constraint set is validated by direct per-kind
7383/// satisfiability checks; any variant that fails is rejected here so
7384/// downstream resolvers never see an unsatisfiable constraint system.
7385/// v0.2.2 Phase F: the `Bound` arm dispatches on `observable_iri` to per-observable
7386/// checks via `check_bound_feasibility`; `Carry` and `Site` remain `Ok(())` by
7387/// documented design — their constraint semantics are structural invariants of
7388/// ring arithmetic and site-index bounds respectively, enforced by construction
7389/// rather than by runtime feasibility checks.
7390pub fn preflight_feasibility(constraints: &[ConstraintRef]) -> Result<(), ShapeViolation> {
7391 for c in constraints {
7392 // v0.2.2 Phase F: Bound dispatches to observable-specific checks with its
7393 // own bound_shape_iri; early-return with that shape on failure.
7394 if let ConstraintRef::Bound {
7395 observable_iri,
7396 bound_shape_iri,
7397 args_repr,
7398 } = c
7399 {
7400 check_bound_feasibility(observable_iri, bound_shape_iri, args_repr)?;
7401 continue;
7402 }
7403 let ok = match c {
7404 ConstraintRef::SatClauses { clauses, num_vars } => *num_vars != 0 || clauses.is_empty(),
7405 ConstraintRef::Residue { modulus, residue } => *modulus != 0 && *residue < *modulus,
7406 // Structural invariant of ring arithmetic — carries cannot contradict by construction.
7407 ConstraintRef::Carry { .. } => true,
7408 ConstraintRef::Depth { min, max } => min <= max,
7409 ConstraintRef::Hamming { bound } => *bound <= 32_768,
7410 // Structural invariant of site indexing — bounds enforced by SITE_COUNT typing.
7411 ConstraintRef::Site { .. } => true,
7412 ConstraintRef::Affine {
7413 coefficients,
7414 coefficient_count,
7415 bias,
7416 } => {
7417 let count = *coefficient_count as usize;
7418 if count == 0 {
7419 false
7420 } else {
7421 let mut ok_coeff = true;
7422 let mut idx = 0;
7423 while idx < count && idx < AFFINE_MAX_COEFFS {
7424 if coefficients[idx] == i64::MIN {
7425 ok_coeff = false;
7426 break;
7427 }
7428 idx += 1;
7429 }
7430 ok_coeff && is_affine_consistent(coefficients, *coefficient_count, *bias)
7431 }
7432 }
7433 // Handled above via early `if let`; unreachable here.
7434 ConstraintRef::Bound { .. } => true,
7435 ConstraintRef::Conjunction {
7436 conjuncts,
7437 conjunct_count,
7438 } => conjunction_all_sat(conjuncts, *conjunct_count),
7439 // ADR-057: Recurse defers to runtime ψ_1 NerveResolver
7440 // via the shape-IRI registry. Preflight feasibility
7441 // accepts on non-empty shape_iri; the registry lookup
7442 // happens at runtime admission.
7443 ConstraintRef::Recurse { shape_iri, .. } => !shape_iri.is_empty(),
7444 };
7445 if !ok {
7446 return Err(ShapeViolation {
7447 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
7448 constraint_iri:
7449 "https://uor.foundation/conformance/compileUnit_rootTerm_constraint",
7450 property_iri: "https://uor.foundation/reduction/rootTerm",
7451 expected_range: "https://uor.foundation/schema/Term",
7452 min_count: 1,
7453 max_count: 1,
7454 kind: ViolationKind::ValueCheck,
7455 });
7456 }
7457 }
7458 Ok(())
7459}
7460
7461/// `DispatchCoverageCheck`: verify the inhabitance dispatch table covers the input.
7462/// The table is exhaustive by construction: Rule 3 (IsResidualFragment) is the catch-all.
7463pub fn preflight_dispatch_coverage() -> Result<(), ShapeViolation> {
7464 // Always covered: IsResidualFragment catches everything not in 2-SAT/Horn.
7465 Ok(())
7466}
7467
7468/// `PackageCoherenceCheck`: verify each site's constraints are internally consistent.
7469pub fn preflight_package_coherence(constraints: &[ConstraintRef]) -> Result<(), ShapeViolation> {
7470 // Check residue constraints don't contradict (same modulus, different residues).
7471 let mut i = 0;
7472 while i < constraints.len() {
7473 if let ConstraintRef::Residue {
7474 modulus: m1,
7475 residue: r1,
7476 } = constraints[i]
7477 {
7478 let mut j = i + 1;
7479 while j < constraints.len() {
7480 if let ConstraintRef::Residue {
7481 modulus: m2,
7482 residue: r2,
7483 } = constraints[j]
7484 {
7485 if m1 == m2 && r1 != r2 {
7486 return Err(ShapeViolation {
7487 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
7488 constraint_iri:
7489 "https://uor.foundation/conformance/compileUnit_rootTerm_constraint",
7490 property_iri: "https://uor.foundation/reduction/rootTerm",
7491 expected_range: "https://uor.foundation/schema/Term",
7492 min_count: 1,
7493 max_count: 1,
7494 kind: ViolationKind::ValueCheck,
7495 });
7496 }
7497 }
7498 j += 1;
7499 }
7500 }
7501 i += 1;
7502 }
7503 Ok(())
7504}
7505
7506/// v0.2.2 Phase B: a-priori `UorTime` estimator for preflight timing.
7507/// Derives a content-deterministic upper bound on the `UorTime` a reduction
7508/// over `shape` at `witt_bits` will consume, without a physical clock. The
7509/// bound is `witt_bits × constraint_count` rewrite steps and the matching
7510/// Landauer nats at `ln 2` per step. Preflight compares this via
7511/// [`UorTime::min_wall_clock`](crate::enforcement::UorTime::min_wall_clock) against the policy's Nanos budget — no
7512/// physical clock is consulted.
7513#[must_use]
7514pub fn estimate_preflight_uor_time<T: ConstrainedTypeShape + ?Sized>(
7515 witt_bits: u16,
7516) -> crate::enforcement::UorTime {
7517 let steps = (witt_bits as u64).saturating_mul((T::CONSTRAINTS.len() as u64).max(1));
7518 let nats = (steps as f64) * core::f64::consts::LN_2;
7519 crate::enforcement::UorTime::new(crate::enforcement::LandauerBudget::new(nats), steps)
7520}
7521
7522/// `PreflightTiming`: timing-check preflight. v0.2.2 Phase B: parameterized over
7523/// a [`TimingPolicy`] carrying the Nanos budget and canonical `Calibration`.
7524/// The `expected` UorTime is derived a-priori from input shape via
7525/// [`estimate_preflight_uor_time`] — content-deterministic, no physical
7526/// clock consulted. Rejects when the Nanos lower bound exceeds the budget.
7527/// # Errors
7528/// Returns `ShapeViolation::ValueCheck` when the expected UorTime, converted
7529/// to Nanos under `P::CALIBRATION`, exceeds `P::PREFLIGHT_BUDGET_NS`.
7530#[allow(dead_code)]
7531pub(crate) const CANONICAL_PREFLIGHT_BUDGET_NS: u64 = 10000000;
7532pub fn preflight_timing<P: crate::enforcement::TimingPolicy>(
7533 expected: crate::enforcement::UorTime,
7534) -> Result<(), ShapeViolation> {
7535 let nanos = expected.min_wall_clock(P::CALIBRATION).as_u64();
7536 if nanos <= P::PREFLIGHT_BUDGET_NS {
7537 Ok(())
7538 } else {
7539 Err(ShapeViolation {
7540 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
7541 constraint_iri: "https://uor.foundation/reduction/PreflightTimingBound",
7542 property_iri: "https://uor.foundation/reduction/preflightBudgetNs",
7543 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
7544 min_count: 1,
7545 max_count: 1,
7546 kind: ViolationKind::ValueCheck,
7547 })
7548 }
7549}
7550
7551/// `RuntimeTiming`: runtime timing-check preflight. v0.2.2 Phase B: parameterized
7552/// over a [`TimingPolicy`] carrying the Nanos budget and canonical `Calibration`.
7553/// Identical comparison shape as [`preflight_timing`], against the runtime budget.
7554/// # Errors
7555/// Returns `ShapeViolation::ValueCheck` when the expected UorTime, converted
7556/// to Nanos under `P::CALIBRATION`, exceeds `P::RUNTIME_BUDGET_NS`.
7557#[allow(dead_code)]
7558pub(crate) const CANONICAL_RUNTIME_BUDGET_NS: u64 = 10000000;
7559pub fn runtime_timing<P: crate::enforcement::TimingPolicy>(
7560 expected: crate::enforcement::UorTime,
7561) -> Result<(), ShapeViolation> {
7562 let nanos = expected.min_wall_clock(P::CALIBRATION).as_u64();
7563 if nanos <= P::RUNTIME_BUDGET_NS {
7564 Ok(())
7565 } else {
7566 Err(ShapeViolation {
7567 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
7568 constraint_iri: "https://uor.foundation/reduction/RuntimeTimingBound",
7569 property_iri: "https://uor.foundation/reduction/runtimeBudgetNs",
7570 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
7571 min_count: 1,
7572 max_count: 1,
7573 kind: ViolationKind::ValueCheck,
7574 })
7575 }
7576}
7577
7578/// Reduction stage executor. Takes a classified input and runs the 7 stages
7579/// in order, producing a `StageOutcome` on success.
7580#[derive(Debug, Clone, Copy)]
7581pub struct StageOutcome {
7582 /// Witt level the compile unit was resolved at.
7583 pub witt_bits: u16,
7584 /// Fragment classification decided at `stage_resolve`.
7585 pub fragment: FragmentKind,
7586 /// Whether the input is satisfiable (carrier non-empty).
7587 pub satisfiable: bool,
7588}
7589
7590/// Run the 7 reduction stages on a constrained-type input.
7591///
7592/// v0.2.2 T6.14: the `unit_address` field was removed. The substrate-
7593/// computed `ContentAddress` lives on `Grounded` and is derived at the
7594/// caller from the `H: Hasher` output buffer, not inside this stage
7595/// executor.
7596///
7597/// # Errors
7598///
7599/// Returns `PipelineFailure` with the `reduction:PipelineFailureReason` IRI
7600/// of whichever stage rejected the input.
7601pub fn run_reduction_stages<T: ConstrainedTypeShape + ?Sized>(
7602 witt_bits: u16,
7603) -> Result<StageOutcome, PipelineFailure> {
7604 // Stage 0 (initialization): content-addressed unit-id is computed by
7605 // the caller via the consumer-supplied substrate Hasher; nothing to
7606 // do here.
7607 // Stage 1 (declare): no-op; declarations already captured by the derive macro.
7608 // Stage 2 (factorize): no-op; ring factorization is not required for Boolean fragments.
7609 // Stage 3 (resolve): fragment classification.
7610 let fragment = fragment_classify(T::CONSTRAINTS);
7611 // Stage 4 (attest): run the decider associated with the fragment.
7612 let satisfiable = match fragment {
7613 FragmentKind::TwoSat => {
7614 let mut sat = true;
7615 for c in T::CONSTRAINTS {
7616 if let ConstraintRef::SatClauses { clauses, num_vars } = c {
7617 sat = decide_two_sat(clauses, *num_vars);
7618 break;
7619 }
7620 }
7621 sat
7622 }
7623 FragmentKind::Horn => {
7624 let mut sat = true;
7625 for c in T::CONSTRAINTS {
7626 if let ConstraintRef::SatClauses { clauses, num_vars } = c {
7627 sat = decide_horn_sat(clauses, *num_vars);
7628 break;
7629 }
7630 }
7631 sat
7632 }
7633 FragmentKind::Residual => {
7634 // No polynomial decider available. Residual constraint systems are
7635 // treated as vacuously satisfiable when they carry no SatClauses —
7636 // pure residue/hamming/etc. inputs always have some value satisfying
7637 // at least the trivial case. Non-trivial residuals yield
7638 // ConvergenceStall at stage_convergence below.
7639 let mut has_sat_clauses = false;
7640 for c in T::CONSTRAINTS {
7641 if matches!(c, ConstraintRef::SatClauses { .. }) {
7642 has_sat_clauses = true;
7643 break;
7644 }
7645 }
7646 !has_sat_clauses
7647 }
7648 };
7649 if matches!(fragment, FragmentKind::Residual) && !satisfiable {
7650 return Err(PipelineFailure::ConvergenceStall {
7651 stage_iri: "https://uor.foundation/reduction/stage_convergence",
7652 angle_milliradians: 0,
7653 });
7654 }
7655 // Stage 5 (extract): ConstrainedTypeShape inputs carry no term AST, so no
7656 // bindings flow through this path. CompileUnit-bearing callers retrieve the
7657 // declared bindings directly via `unit.bindings()` (Phase H1); runtime
7658 // `BindingsTable` materialization is not possible because `BindingsTable::entries`
7659 // is `&'static [BindingEntry]` by contract (compile-time-constructed catalogs
7660 // are the sole source of sorted-address binary-search tables).
7661 // Stage 6 (convergence): verify fixpoint reached. Trivially true for
7662 // classified fragments.
7663 Ok(StageOutcome {
7664 witt_bits,
7665 fragment,
7666 satisfiable,
7667 })
7668}
7669
7670/// Run the `TowerCompletenessResolver` pipeline on a `ConstrainedTypeShape`
7671/// input at the requested Witt level. Emits a `LiftChainCertificate` on
7672/// success or a generic `ImpossibilityWitness` on failure.
7673/// # Errors
7674/// Returns `GenericImpossibilityWitness` when the input is unsatisfiable or
7675/// when any preflight / reduction stage rejects it.
7676pub fn run_tower_completeness<T: ConstrainedTypeShape + ?Sized, H: crate::enforcement::Hasher>(
7677 _input: &T,
7678 level: WittLevel,
7679) -> Result<Validated<LiftChainCertificate>, GenericImpossibilityWitness> {
7680 let witt_bits = level.witt_length() as u16;
7681 preflight_budget_solvency(witt_bits as u64, witt_bits as u32)
7682 .map_err(|_| GenericImpossibilityWitness::default())?;
7683 preflight_feasibility(T::CONSTRAINTS).map_err(|_| GenericImpossibilityWitness::default())?;
7684 preflight_package_coherence(T::CONSTRAINTS)
7685 .map_err(|_| GenericImpossibilityWitness::default())?;
7686 preflight_dispatch_coverage().map_err(|_| GenericImpossibilityWitness::default())?;
7687 let expected = estimate_preflight_uor_time::<T>(witt_bits);
7688 preflight_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
7689 .map_err(|_| GenericImpossibilityWitness::default())?;
7690 runtime_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
7691 .map_err(|_| GenericImpossibilityWitness::default())?;
7692 let outcome =
7693 run_reduction_stages::<T>(witt_bits).map_err(|_| GenericImpossibilityWitness::default())?;
7694 if outcome.satisfiable {
7695 // v0.2.2 T6.7: thread H: Hasher through fold_unit_digest to compute
7696 // a real substrate fingerprint. Witt level + budget=0 (no CompileUnit).
7697 let mut hasher = H::initial();
7698 hasher = crate::enforcement::fold_unit_digest(
7699 hasher,
7700 outcome.witt_bits,
7701 0,
7702 T::IRI,
7703 T::SITE_COUNT,
7704 T::CONSTRAINTS,
7705 crate::enforcement::CertificateKind::TowerCompleteness,
7706 );
7707 let buffer = hasher.finalize();
7708 let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
7709 let cert = LiftChainCertificate::with_level_and_fingerprint_const(outcome.witt_bits, fp);
7710 Ok(Validated::new(cert))
7711 } else {
7712 Err(GenericImpossibilityWitness::default())
7713 }
7714}
7715
7716/// Workstream F (target ontology: `resolver:IncrementalCompletenessResolver`):
7717/// sealed `SpectralSequencePage` kernel type recording one step of the
7718/// `Q_n → Q_{n+1}` spectral-sequence walk. Each page carries its index,
7719/// the from/to Witt bit widths, and the differential-vanished flag
7720/// (true ⇒ page is trivial; false ⇒ obstruction present with class IRI).
7721#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7722pub struct SpectralSequencePage {
7723 page_index: u32,
7724 source_bits: u16,
7725 target_bits: u16,
7726 differential_vanished: bool,
7727 obstruction_class_iri: &'static str,
7728 _sealed: (),
7729}
7730
7731impl SpectralSequencePage {
7732 /// Crate-internal constructor. Minted only by the incremental walker.
7733 #[inline]
7734 #[must_use]
7735 pub(crate) const fn from_parts(
7736 page_index: u32,
7737 source_bits: u16,
7738 target_bits: u16,
7739 differential_vanished: bool,
7740 obstruction_class_iri: &'static str,
7741 ) -> Self {
7742 Self {
7743 page_index,
7744 source_bits,
7745 target_bits,
7746 differential_vanished,
7747 obstruction_class_iri,
7748 _sealed: (),
7749 }
7750 }
7751
7752 /// Page index (0, 1, 2, …) along the spectral-sequence walk.
7753 #[inline]
7754 #[must_use]
7755 pub const fn page_index(&self) -> u32 {
7756 self.page_index
7757 }
7758
7759 /// Witt bit width at the page's source level.
7760 #[inline]
7761 #[must_use]
7762 pub const fn source_bits(&self) -> u16 {
7763 self.source_bits
7764 }
7765
7766 /// Witt bit width at the page's target level.
7767 #[inline]
7768 #[must_use]
7769 pub const fn target_bits(&self) -> u16 {
7770 self.target_bits
7771 }
7772
7773 /// True iff the page's differential vanishes (no obstruction).
7774 #[inline]
7775 #[must_use]
7776 pub const fn differential_vanished(&self) -> bool {
7777 self.differential_vanished
7778 }
7779
7780 /// Obstruction class IRI when the differential is non-trivial;
7781 /// empty string when the page is trivial.
7782 #[inline]
7783 #[must_use]
7784 pub const fn obstruction_class_iri(&self) -> &'static str {
7785 self.obstruction_class_iri
7786 }
7787}
7788
7789/// Run the `IncrementalCompletenessResolver` (spectral-sequence walk)
7790/// at `target_level`.
7791///
7792/// Per `spec/src/namespaces/resolver.rs` (`IncrementalCompletenessResolver`),
7793/// the walk proceeds without re-running the full ψ-pipeline from
7794/// scratch. Workstream F (v0.2.2 closure) implements the canonical page
7795/// structure: iterate each `Q_n → Q_{n+1}` step from `W8` up to
7796/// `target_level`, compute each page's differential via
7797/// `run_reduction_stages` at the higher level, and record the
7798/// `SpectralSequencePage`. A non-vanishing differential halts with a
7799/// `GenericImpossibilityWitness` whose obstruction-class IRI is
7800/// `https://uor.foundation/type/LiftObstruction`. All trivial pages
7801/// produce a `LiftChainCertificate` stamped with
7802/// `CertificateKind::IncrementalCompleteness`, discriminable from
7803/// `run_tower_completeness`'s certificate by the kind byte.
7804///
7805/// # Errors
7806///
7807/// Returns `GenericImpossibilityWitness` when any page's differential
7808/// does not vanish, or when preflight checks reject the input.
7809pub fn run_incremental_completeness<
7810 T: ConstrainedTypeShape + ?Sized,
7811 H: crate::enforcement::Hasher,
7812>(
7813 _input: &T,
7814 target_level: WittLevel,
7815) -> Result<Validated<LiftChainCertificate>, GenericImpossibilityWitness> {
7816 let target_bits = target_level.witt_length() as u16;
7817 preflight_budget_solvency(target_bits as u64, target_bits as u32)
7818 .map_err(|_| GenericImpossibilityWitness::default())?;
7819 preflight_feasibility(T::CONSTRAINTS).map_err(|_| GenericImpossibilityWitness::default())?;
7820 preflight_package_coherence(T::CONSTRAINTS)
7821 .map_err(|_| GenericImpossibilityWitness::default())?;
7822 preflight_dispatch_coverage().map_err(|_| GenericImpossibilityWitness::default())?;
7823 let expected = estimate_preflight_uor_time::<T>(target_bits);
7824 preflight_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
7825 .map_err(|_| GenericImpossibilityWitness::default())?;
7826 runtime_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
7827 .map_err(|_| GenericImpossibilityWitness::default())?;
7828
7829 // v0.2.2 Phase H4: Betti-driven spectral-sequence walk. At each page, compute
7830 // the constraint-nerve Betti tuple (via primitive_simplicial_nerve_betti)
7831 // and run reduction at both source and target levels. The differential at
7832 // page r with bidegree (p, q) vanishes iff the bidegree-q projection
7833 // `betti[q]` is unchanged between source and target AND both reductions
7834 // are satisfiable at their levels. A mismatch in any bidegree or a
7835 // non-satisfiable reduction produces a non-trivial differential →
7836 // `LiftObstruction` halt with `GenericImpossibilityWitness`.
7837 //
7838 // Betti-threading also produces content-distinct fingerprints for distinct
7839 // constraint topologies: two input shapes with different Betti profiles will
7840 // produce different certs even if both satisfy at every level.
7841 let betti = crate::enforcement::primitive_simplicial_nerve_betti::<T>()?;
7842 let mut page_index: u32 = 0;
7843 let mut from_bits: u16 = 8;
7844 let mut pages_hasher = H::initial();
7845 while from_bits < target_bits {
7846 let to_bits = if from_bits + 8 > target_bits {
7847 target_bits
7848 } else {
7849 from_bits + 8
7850 };
7851 // Reduce at source and target; both must be satisfiable for the
7852 // differential to have a chance of vanishing.
7853 let outcome_source = run_reduction_stages::<T>(from_bits)
7854 .map_err(|_| GenericImpossibilityWitness::default())?;
7855 let outcome_target = run_reduction_stages::<T>(to_bits)
7856 .map_err(|_| GenericImpossibilityWitness::default())?;
7857 // bidegree q = page_index + 1 (first non-trivial homological degree per page).
7858 let q: usize = ((page_index as usize) + 1).min(crate::enforcement::MAX_BETTI_DIMENSION - 1);
7859 let betti_q = betti[q];
7860 // Differential vanishes iff source ≡ target in Betti bidegree q
7861 // AND both reduction levels are satisfiable. Betti is shape-invariant
7862 // (level-independent); the bidegree check is trivially equal, but the
7863 // satisfiability conjunction captures the level-specific obstruction.
7864 let differential_vanishes = outcome_source.satisfiable && outcome_target.satisfiable;
7865 let page = SpectralSequencePage::from_parts(
7866 page_index,
7867 from_bits,
7868 to_bits,
7869 differential_vanishes,
7870 if differential_vanishes {
7871 ""
7872 } else {
7873 "https://uor.foundation/type/LiftObstruction"
7874 },
7875 );
7876 if !page.differential_vanished() {
7877 return Err(GenericImpossibilityWitness::default());
7878 }
7879 // Fold the per-page Betti/satisfiability contribution so distinct
7880 // constraint shapes yield distinct incremental-completeness certs.
7881 pages_hasher = pages_hasher.fold_bytes(&page_index.to_be_bytes());
7882 pages_hasher = pages_hasher.fold_bytes(&from_bits.to_be_bytes());
7883 pages_hasher = pages_hasher.fold_bytes(&to_bits.to_be_bytes());
7884 pages_hasher = pages_hasher.fold_bytes(&betti_q.to_be_bytes());
7885 page_index += 1;
7886 from_bits = to_bits;
7887 }
7888 // The accumulated pages_hasher is currently unused in the final fold;
7889 // the Betti primitive's full tuple is folded below via fold_betti_tuple
7890 // to keep the cert content-addressed over the whole spectral walk.
7891 let _ = pages_hasher;
7892
7893 // Final reduction at the target level — the walk converges when
7894 // every page's differential has vanished; this guarantees the
7895 // target-level outcome is satisfiable.
7896 let outcome = run_reduction_stages::<T>(target_bits)
7897 .map_err(|_| GenericImpossibilityWitness::default())?;
7898 if !outcome.satisfiable {
7899 return Err(GenericImpossibilityWitness::default());
7900 }
7901 // v0.2.2 Phase H4: fold the Betti tuple into the cert alongside the
7902 // canonical unit digest so distinct constraint topologies produce distinct
7903 // incremental-completeness fingerprints.
7904 let mut hasher = H::initial();
7905 hasher = crate::enforcement::fold_betti_tuple(hasher, &betti);
7906 hasher = crate::enforcement::fold_unit_digest(
7907 hasher,
7908 outcome.witt_bits,
7909 page_index as u64,
7910 T::IRI,
7911 T::SITE_COUNT,
7912 T::CONSTRAINTS,
7913 crate::enforcement::CertificateKind::IncrementalCompleteness,
7914 );
7915 let buffer = hasher.finalize();
7916 let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
7917 let cert = LiftChainCertificate::with_level_and_fingerprint_const(outcome.witt_bits, fp);
7918 Ok(Validated::new(cert))
7919}
7920
7921/// Run the `GroundingAwareResolver` on a `CompileUnit` input at `level`,
7922/// exploiting `state:GroundedContext` bindings for O(1) resolution per
7923/// `op:GS_5`.
7924///
7925/// v0.2.2 Phase H2: walks `unit.root_term()` enumerating every
7926/// `Term::Variable { name_index }` and resolves each via linear search
7927/// over `unit.bindings()`. Unresolved variables (declared in the term AST
7928/// but absent from the bindings slice) trigger a `GenericImpossibilityWitness`
7929/// corresponding to `SC5_UNBOUND_VARIABLE`. Resolved bindings are folded
7930/// into the fingerprint via `primitive_session_binding_signature` so the
7931/// cert content-addresses the full grounding context, not just the unit
7932/// shape — distinct binding sets yield distinct fingerprints.
7933///
7934/// # Errors
7935///
7936/// Returns `GenericImpossibilityWitness` on grounding failure: unresolved
7937/// variables, or any variable reference whose name index is absent from
7938/// `unit.bindings()`.
7939pub fn run_grounding_aware<const INLINE_BYTES: usize, H: crate::enforcement::Hasher>(
7940 unit: &CompileUnit<'_, INLINE_BYTES>,
7941 level: WittLevel,
7942) -> Result<Validated<GroundingCertificate>, GenericImpossibilityWitness> {
7943 let witt_bits = level.witt_length() as u16;
7944 // v0.2.2 Phase H2: walk root_term enumerating Term::Variable name_indices,
7945 // linear-search unit.bindings() for each, reject unresolved variables.
7946 let root_term = unit.root_term();
7947 let bindings = unit.bindings();
7948 let mut ti = 0;
7949 while ti < root_term.len() {
7950 if let crate::enforcement::Term::Variable { name_index } = root_term[ti] {
7951 let mut found = false;
7952 let mut bi = 0;
7953 while bi < bindings.len() {
7954 if bindings[bi].name_index == name_index {
7955 found = true;
7956 break;
7957 }
7958 bi += 1;
7959 }
7960 if !found {
7961 // SC_5 violation: variable referenced but no matching binding.
7962 return Err(GenericImpossibilityWitness::default());
7963 }
7964 }
7965 ti += 1;
7966 }
7967 // Fold: witt_bits / thermodynamic_budget / result_type_iri / session_signature / Grounding kind.
7968 let mut hasher = H::initial();
7969 hasher = hasher.fold_bytes(&witt_bits.to_be_bytes());
7970 hasher = hasher.fold_bytes(&unit.thermodynamic_budget().to_be_bytes());
7971 hasher = hasher.fold_bytes(unit.result_type_iri().as_bytes());
7972 hasher = hasher.fold_byte(0);
7973 let (binding_count, fold_addr) =
7974 crate::enforcement::primitive_session_binding_signature(bindings);
7975 hasher = crate::enforcement::fold_session_signature(hasher, binding_count, fold_addr);
7976 hasher = hasher.fold_byte(crate::enforcement::certificate_kind_discriminant(
7977 crate::enforcement::CertificateKind::Grounding,
7978 ));
7979 let buffer = hasher.finalize();
7980 let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
7981 let cert = GroundingCertificate::with_level_and_fingerprint_const(witt_bits, fp);
7982 Ok(Validated::new(cert))
7983}
7984
7985/// Run the `InhabitanceResolver` dispatch on a `ConstrainedTypeShape`
7986/// input at `level`.
7987///
7988/// Routes to the 2-SAT / Horn-SAT / residual decider via
7989/// `predicate:InhabitanceDispatchTable` rules (ordered by priority).
7990///
7991/// # Errors
7992///
7993/// Returns `InhabitanceImpossibilityWitness` when the input is unsatisfiable.
7994pub fn run_inhabitance<T: ConstrainedTypeShape + ?Sized, H: crate::enforcement::Hasher>(
7995 _input: &T,
7996 level: WittLevel,
7997) -> Result<Validated<InhabitanceCertificate>, InhabitanceImpossibilityWitness> {
7998 let witt_bits = level.witt_length() as u16;
7999 preflight_budget_solvency(witt_bits as u64, witt_bits as u32)
8000 .map_err(|_| InhabitanceImpossibilityWitness::default())?;
8001 preflight_feasibility(T::CONSTRAINTS)
8002 .map_err(|_| InhabitanceImpossibilityWitness::default())?;
8003 preflight_package_coherence(T::CONSTRAINTS)
8004 .map_err(|_| InhabitanceImpossibilityWitness::default())?;
8005 preflight_dispatch_coverage().map_err(|_| InhabitanceImpossibilityWitness::default())?;
8006 let expected = estimate_preflight_uor_time::<T>(witt_bits);
8007 preflight_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
8008 .map_err(|_| InhabitanceImpossibilityWitness::default())?;
8009 runtime_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
8010 .map_err(|_| InhabitanceImpossibilityWitness::default())?;
8011 let outcome = run_reduction_stages::<T>(witt_bits)
8012 .map_err(|_| InhabitanceImpossibilityWitness::default())?;
8013 if outcome.satisfiable {
8014 // v0.2.2 T6.7: thread H: Hasher through fold_unit_digest.
8015 let mut hasher = H::initial();
8016 hasher = crate::enforcement::fold_unit_digest(
8017 hasher,
8018 outcome.witt_bits,
8019 0,
8020 T::IRI,
8021 T::SITE_COUNT,
8022 T::CONSTRAINTS,
8023 crate::enforcement::CertificateKind::Inhabitance,
8024 );
8025 let buffer = hasher.finalize();
8026 let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
8027 let cert = InhabitanceCertificate::with_level_and_fingerprint_const(outcome.witt_bits, fp);
8028 Ok(Validated::new(cert))
8029 } else {
8030 Err(InhabitanceImpossibilityWitness::default())
8031 }
8032}
8033
8034/// v0.2.2 W14: the single typed pipeline entry point producing `Grounded<T>`
8035/// from a validated `CompileUnit`. The caller declares the expected shape `T`;
8036/// the pipeline verifies the unit's root term produces a value of that shape
8037/// and returns `Grounded<T>` on success or a typed `PipelineFailure`.
8038/// Replaces the v0.2.1 `run_pipeline(&datum, level: u8)` form whose bare
8039/// integer level argument was never type-safe.
8040/// # Errors
8041/// Returns `PipelineFailure` on preflight, reduction, or shape-mismatch failure.
8042/// # Example
8043/// ```no_run
8044/// use uor_foundation::enforcement::{
8045/// CompileUnitBuilder, ConstrainedTypeInput, Term,
8046/// };
8047/// use uor_foundation::pipeline::run;
8048/// use uor_foundation::{VerificationDomain, WittLevel};
8049/// # struct Fnv1aHasher16; // downstream substrate; see foundation/examples/custom_hasher_substrate.rs
8050/// # impl uor_foundation::enforcement::Hasher for Fnv1aHasher16 {
8051/// # const OUTPUT_BYTES: usize = 16;
8052/// # fn initial() -> Self { Self }
8053/// # fn fold_byte(self, _: u8) -> Self { self }
8054/// # fn finalize(self) -> [u8; 32] { [0; 32] }
8055/// # }
8056/// // ADR-060: `Term`/`CompileUnit`/`Grounded` carry an `INLINE_BYTES`
8057/// // const-generic the application derives from its `HostBounds`; this
8058/// // example fixes a concrete width and threads it through `run`'s 4th
8059/// // const argument.
8060/// const N: usize = 32;
8061/// let terms: [Term<'static, N>; 1] = [uor_foundation::pipeline::literal_u64(1, WittLevel::W8)];
8062/// let domains: [VerificationDomain; 1] = [VerificationDomain::Enumerative];
8063/// let unit = CompileUnitBuilder::<N>::new()
8064/// .root_term(&terms)
8065/// .witt_level_ceiling(WittLevel::W32)
8066/// .thermodynamic_budget(1024)
8067/// .target_domains(&domains)
8068/// .result_type::<ConstrainedTypeInput>()
8069/// .validate()
8070/// .expect("unit well-formed");
8071/// let grounded = run::<ConstrainedTypeInput, _, Fnv1aHasher16, N>(unit)
8072/// .expect("pipeline admits");
8073/// # let _ = grounded;
8074/// ```
8075pub fn run<T, P, H, const INLINE_BYTES: usize>(
8076 unit: Validated<CompileUnit<'_, INLINE_BYTES>, P>,
8077) -> Result<Grounded<T, INLINE_BYTES>, PipelineFailure>
8078where
8079 T: ConstrainedTypeShape + crate::enforcement::GroundedShape,
8080 P: crate::enforcement::ValidationPhase,
8081 H: crate::enforcement::Hasher,
8082{
8083 let witt_bits = unit.inner().witt_level().witt_length() as u16;
8084 let budget = unit.inner().thermodynamic_budget();
8085 // v0.2.2 T6.11: ShapeMismatch detection. The unit declares its
8086 // result_type_iri at validation time; the caller's `T::IRI` must match.
8087 let unit_iri = unit.inner().result_type_iri();
8088 if !crate::enforcement::str_eq(unit_iri, T::IRI) {
8089 return Err(PipelineFailure::ShapeMismatch {
8090 expected: T::IRI,
8091 got: unit_iri,
8092 });
8093 }
8094 // Preflights. Each maps its ShapeViolation into a PipelineFailure.
8095 preflight_budget_solvency(witt_bits as u64, witt_bits as u32)
8096 .map_err(|report| PipelineFailure::ShapeViolation { report })?;
8097 preflight_feasibility(T::CONSTRAINTS)
8098 .map_err(|report| PipelineFailure::ShapeViolation { report })?;
8099 preflight_package_coherence(T::CONSTRAINTS)
8100 .map_err(|report| PipelineFailure::ShapeViolation { report })?;
8101 preflight_dispatch_coverage().map_err(|report| PipelineFailure::ShapeViolation { report })?;
8102 let expected = estimate_preflight_uor_time::<T>(witt_bits);
8103 preflight_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
8104 .map_err(|report| PipelineFailure::ShapeViolation { report })?;
8105 runtime_timing::<crate::enforcement::CanonicalTimingPolicy>(expected)
8106 .map_err(|report| PipelineFailure::ShapeViolation { report })?;
8107 // v0.2.2 T5 C1: actually call run_reduction_stages and propagate its
8108 // failure. The previous v0.2.2 path skipped this call entirely,
8109 // returning a degenerate Grounded with ContentAddress::zero(). The
8110 // typed `run` entry point now mirrors `run_pipeline`'s reduction-stage
8111 // sequence.
8112 let outcome = run_reduction_stages::<T>(witt_bits)?;
8113 if !outcome.satisfiable {
8114 return Err(PipelineFailure::ContradictionDetected {
8115 at_step: 0,
8116 trace_iri: "https://uor.foundation/trace/InhabitanceSearchTrace",
8117 });
8118 }
8119 // v0.2.2 T5 C3.f: thread the consumer-supplied substrate Hasher through
8120 // the canonical CompileUnit byte layout to compute the parametric
8121 // content fingerprint.
8122 let mut hasher = H::initial();
8123 hasher = crate::enforcement::fold_unit_digest(
8124 hasher,
8125 witt_bits,
8126 budget,
8127 T::IRI,
8128 T::SITE_COUNT,
8129 T::CONSTRAINTS,
8130 crate::enforcement::CertificateKind::Grounding,
8131 );
8132 let buffer = hasher.finalize();
8133 let content_fingerprint =
8134 crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
8135 let unit_address = crate::enforcement::unit_address_from_buffer(&buffer);
8136 let grounding = Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
8137 witt_bits,
8138 content_fingerprint,
8139 ));
8140 let bindings = empty_bindings_table();
8141 Ok(Grounded::<T, INLINE_BYTES>::new_internal(
8142 grounding,
8143 bindings,
8144 outcome.witt_bits,
8145 unit_address,
8146 content_fingerprint,
8147 ))
8148}
8149
8150/// Construct an empty `BindingsTable`.
8151/// v0.2.2 T6.9: the empty slice is vacuously sorted, so direct struct
8152/// construction is sound. Public callers with non-empty entries go
8153/// through `BindingsTable::try_new` (validating).
8154/// # Driver contract
8155/// Every pipeline driver (`run`, `run_const`, `run_parallel`, `run_stream`,
8156/// `run_interactive`, `run_inhabitance`) mints `Grounded<T>` with this
8157/// empty table today: runtime-dynamic binding materialization in
8158/// `Grounded<T>` requires widening `BindingsTable.entries: &'static [...]`
8159/// to a non-`'static` carrier (a separate architectural change).
8160/// Downstream that needs a compile-time binding catalog uses the pattern
8161/// shown in `foundation/examples/static_bindings_catalog.rs`:
8162/// `Binding::to_binding_entry()` (const-fn) + `BindingsTable::try_new(&[...])`.
8163#[must_use]
8164pub const fn empty_bindings_table() -> BindingsTable {
8165 BindingsTable { entries: &[] }
8166}
8167
8168// Suppress warning: BindingEntry is re-exported via use but not used in
8169// this module directly; it's part of the public pipeline surface.
8170#[allow(dead_code)]
8171const _BINDING_ENTRY_REF: Option<BindingEntry> = None;
8172// Same for CompletenessCertificate — the pipeline does not mint this subclass
8173// directly; Phase D resolvers emit the canonical `GroundingCertificate` carrier
8174// and cert-subclass lifts happen in substrate-specific consumers.
8175#[allow(dead_code)]
8176const _COMPLETENESS_CERT_REF: Option<CompletenessCertificate> = None;
8177
8178/// v0.2.2 Phase F / T2.7: parallel-declaration compile unit. Carries the
8179/// declared site partition cardinality plus (Phase A widening) the raw
8180/// partition slice and disjointness-witness IRI from the builder —
8181/// previously these were discarded at validate-time by a shadowed
8182/// enforcement-local `ParallelDeclaration` that nothing consumed.
8183/// v0.2.2 T6.11: also carries `result_type_iri` for ShapeMismatch detection.
8184#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8185pub struct ParallelDeclaration<'a> {
8186 payload: u64,
8187 result_type_iri: &'static str,
8188 /// v0.2.2 Phase A: raw site-partition slice retained from the builder.
8189 /// Empty slice for declarations built via the site-count-only constructor.
8190 site_partition: &'a [u32],
8191 /// v0.2.2 Phase A: disjointness-witness IRI retained from the builder.
8192 /// Empty string for declarations built via the site-count-only constructor.
8193 disjointness_witness: &'a str,
8194 _sealed: (),
8195}
8196
8197impl<'a> ParallelDeclaration<'a> {
8198 /// v0.2.2 Phase H3: construct a parallel declaration carrying the full
8199 /// partition slice and disjointness-witness IRI from the builder.
8200 /// This is the sole public constructor; the v0.2.2 Phase A site-count-only
8201 /// `new::<T>(site_count)` form was deleted in Phase H3 under the "no
8202 /// compatibility" discipline — every caller supplies a real partition.
8203 #[inline]
8204 #[must_use]
8205 pub const fn new_with_partition<T: ConstrainedTypeShape>(
8206 site_partition: &'a [u32],
8207 disjointness_witness: &'a str,
8208 ) -> Self {
8209 Self {
8210 payload: site_partition.len() as u64,
8211 result_type_iri: T::IRI,
8212 site_partition,
8213 disjointness_witness,
8214 _sealed: (),
8215 }
8216 }
8217
8218 /// Returns the declared site partition cardinality.
8219 #[inline]
8220 #[must_use]
8221 pub const fn site_count(&self) -> u64 {
8222 self.payload
8223 }
8224
8225 /// v0.2.2 T6.11: returns the result-type IRI.
8226 #[inline]
8227 #[must_use]
8228 pub const fn result_type_iri(&self) -> &'static str {
8229 self.result_type_iri
8230 }
8231
8232 /// v0.2.2 Phase A: returns the raw site-partition slice.
8233 #[inline]
8234 #[must_use]
8235 pub const fn site_partition(&self) -> &'a [u32] {
8236 self.site_partition
8237 }
8238
8239 /// v0.2.2 Phase A: returns the disjointness-witness IRI.
8240 #[inline]
8241 #[must_use]
8242 pub const fn disjointness_witness(&self) -> &'a str {
8243 self.disjointness_witness
8244 }
8245}
8246
8247/// v0.2.2 Phase F / T2.7: stream-declaration compile unit. Carries a
8248/// payload field encoding the productivity-witness countdown.
8249/// v0.2.2 T6.11: also carries `result_type_iri` for ShapeMismatch detection.
8250/// v0.2.2 Phase A: also retains the builder's seed/step term slices and
8251/// the productivity-witness IRI so stream resolvers can walk declared
8252/// structure. Distinct from the enforcement-local `StreamDeclaration`
8253/// which records only the `StreamShape` validation surface.
8254/// Note: `Hash` is not derived because `Term` does not implement `Hash`;
8255/// downstream code that needs deterministic hashing should fold through
8256/// the substrate `Hasher` via the pipeline's `fold_stream_digest`.
8257#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8258pub struct StreamDeclaration<'a, const INLINE_BYTES: usize> {
8259 payload: u64,
8260 result_type_iri: &'static str,
8261 /// v0.2.2 Phase A: stream seed term slice retained from the builder.
8262 seed: &'a [Term<'a, INLINE_BYTES>],
8263 /// v0.2.2 Phase A: stream step term slice retained from the builder.
8264 step: &'a [Term<'a, INLINE_BYTES>],
8265 /// v0.2.2 Phase A: productivity-witness IRI retained from the builder.
8266 productivity_witness: &'a str,
8267 _sealed: (),
8268}
8269
8270impl<'a, const INLINE_BYTES: usize> StreamDeclaration<'a, INLINE_BYTES> {
8271 /// v0.2.2 T6.11: construct a stream declaration with the given productivity
8272 /// bound and result type. Phase A: leaves seed/step/witness empty; use
8273 /// `new_full` to retain the full structure.
8274 #[inline]
8275 #[must_use]
8276 pub const fn new<T: ConstrainedTypeShape>(
8277 productivity_bound: u64,
8278 ) -> StreamDeclaration<'static, INLINE_BYTES> {
8279 StreamDeclaration {
8280 payload: productivity_bound,
8281 result_type_iri: T::IRI,
8282 seed: &[],
8283 step: &[],
8284 productivity_witness: "",
8285 _sealed: (),
8286 }
8287 }
8288
8289 /// v0.2.2 Phase A: construct a stream declaration carrying the full
8290 /// seed/step/witness structure from the builder.
8291 #[inline]
8292 #[must_use]
8293 pub const fn new_full<T: ConstrainedTypeShape>(
8294 productivity_bound: u64,
8295 seed: &'a [Term<'a, INLINE_BYTES>],
8296 step: &'a [Term<'a, INLINE_BYTES>],
8297 productivity_witness: &'a str,
8298 ) -> Self {
8299 Self {
8300 payload: productivity_bound,
8301 result_type_iri: T::IRI,
8302 seed,
8303 step,
8304 productivity_witness,
8305 _sealed: (),
8306 }
8307 }
8308
8309 /// Returns the declared productivity bound.
8310 #[inline]
8311 #[must_use]
8312 pub const fn productivity_bound(&self) -> u64 {
8313 self.payload
8314 }
8315
8316 /// v0.2.2 T6.11: returns the result-type IRI.
8317 #[inline]
8318 #[must_use]
8319 pub const fn result_type_iri(&self) -> &'static str {
8320 self.result_type_iri
8321 }
8322
8323 /// v0.2.2 Phase A: returns the seed term slice.
8324 #[inline]
8325 #[must_use]
8326 pub const fn seed(&self) -> &'a [Term<'a, INLINE_BYTES>] {
8327 self.seed
8328 }
8329
8330 /// v0.2.2 Phase A: returns the step term slice.
8331 #[inline]
8332 #[must_use]
8333 pub const fn step(&self) -> &'a [Term<'a, INLINE_BYTES>] {
8334 self.step
8335 }
8336
8337 /// v0.2.2 Phase A: returns the productivity-witness IRI.
8338 #[inline]
8339 #[must_use]
8340 pub const fn productivity_witness(&self) -> &'a str {
8341 self.productivity_witness
8342 }
8343}
8344
8345/// v0.2.2 Phase F / T2.7: interaction-declaration compile unit. Carries a
8346/// payload field encoding the convergence-predicate seed.
8347/// v0.2.2 T6.11: also carries `result_type_iri` for ShapeMismatch detection.
8348/// v0.2.2 Phase A: lifetime-parameterized for consistency with the other
8349/// widened runtime carriers. The interaction builder stores scalar fields
8350/// only, so there is no additional borrowed structure to retain; the `'a`
8351/// is vestigial but keeps the carrier signature uniform with
8352/// `ParallelDeclaration<'a>` and `StreamDeclaration<'a>`.
8353#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8354pub struct InteractionDeclaration<'a> {
8355 payload: u64,
8356 result_type_iri: &'static str,
8357 _sealed: (),
8358 _lifetime: core::marker::PhantomData<&'a ()>,
8359}
8360
8361impl<'a> InteractionDeclaration<'a> {
8362 /// v0.2.2 T6.11: construct an interaction declaration with the given
8363 /// convergence-predicate seed and result type.
8364 #[inline]
8365 #[must_use]
8366 pub const fn new<T: ConstrainedTypeShape>(
8367 convergence_seed: u64,
8368 ) -> InteractionDeclaration<'static> {
8369 InteractionDeclaration {
8370 payload: convergence_seed,
8371 result_type_iri: T::IRI,
8372 _sealed: (),
8373 _lifetime: core::marker::PhantomData,
8374 }
8375 }
8376
8377 /// Returns the declared convergence seed.
8378 #[inline]
8379 #[must_use]
8380 pub const fn convergence_seed(&self) -> u64 {
8381 self.payload
8382 }
8383
8384 /// v0.2.2 T6.11: returns the result-type IRI.
8385 #[inline]
8386 #[must_use]
8387 pub const fn result_type_iri(&self) -> &'static str {
8388 self.result_type_iri
8389 }
8390}
8391
8392/// v0.2.2 Phase F: fixed-size inline payload buffer carried by `PeerInput`.
8393/// Sized for the largest `Datum<L>` the foundation supports at this release
8394/// (up to 32 u64 limbs = 2048 bits); smaller levels use the leading bytes.
8395#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8396pub struct PeerPayload {
8397 words: [u64; 32],
8398 bit_width: u16,
8399 _sealed: (),
8400}
8401
8402impl PeerPayload {
8403 /// Construct a zeroed payload of the given bit width.
8404 #[inline]
8405 #[must_use]
8406 pub const fn zero(bit_width: u16) -> Self {
8407 Self {
8408 words: [0u64; 32],
8409 bit_width,
8410 _sealed: (),
8411 }
8412 }
8413
8414 /// Access the underlying limbs.
8415 #[inline]
8416 #[must_use]
8417 pub const fn words(&self) -> &[u64; 32] {
8418 &self.words
8419 }
8420
8421 /// Bit width of the payload's logical Datum.
8422 #[inline]
8423 #[must_use]
8424 pub const fn bit_width(&self) -> u16 {
8425 self.bit_width
8426 }
8427}
8428
8429/// v0.2.2 Phase F: a peer-supplied input to an interaction driver step.
8430/// Fixed-size — holds a `PeerPayload` inline plus the peer's content
8431/// address. No heap, no dynamic dispatch.
8432#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8433pub struct PeerInput {
8434 peer_id: u128,
8435 payload: PeerPayload,
8436 _sealed: (),
8437}
8438
8439impl PeerInput {
8440 /// Construct a new peer input with the given peer id and payload.
8441 #[inline]
8442 #[must_use]
8443 pub const fn new(peer_id: u128, payload: PeerPayload) -> Self {
8444 Self {
8445 peer_id,
8446 payload,
8447 _sealed: (),
8448 }
8449 }
8450
8451 /// Access the peer id.
8452 #[inline]
8453 #[must_use]
8454 pub const fn peer_id(&self) -> u128 {
8455 self.peer_id
8456 }
8457
8458 /// Access the payload.
8459 #[inline]
8460 #[must_use]
8461 pub const fn payload(&self) -> &PeerPayload {
8462 &self.payload
8463 }
8464}
8465
8466/// v0.2.2 Phase F: outcome of a single `InteractionDriver::step` call.
8467#[derive(Debug, Clone)]
8468#[non_exhaustive]
8469pub enum StepResult<T: crate::enforcement::GroundedShape, const INLINE_BYTES: usize> {
8470 /// The step was absorbed; the driver is ready for another peer input.
8471 Continue,
8472 /// The step produced an intermediate grounded output.
8473 Output(Grounded<T, INLINE_BYTES>),
8474 /// The convergence predicate is satisfied; interaction is complete.
8475 Converged(Grounded<T, INLINE_BYTES>),
8476 /// v0.2.2 Phase T.1: the commutator norm failed to decrease for
8477 /// `INTERACTION_DIVERGENCE_BUDGET` consecutive steps — the interaction is
8478 /// non-convergent and the driver is no longer advanceable.
8479 Diverged,
8480 /// The step failed; the driver is no longer advanceable.
8481 Failure(PipelineFailure),
8482}
8483
8484/// v0.2.2 Phase F: sealed commutator-algebra state carried by an
8485/// interaction driver across peer steps.
8486#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8487pub struct CommutatorState<L> {
8488 accumulator: [u64; 4],
8489 _level: core::marker::PhantomData<L>,
8490 _sealed: (),
8491}
8492
8493impl<L> CommutatorState<L> {
8494 /// Construct a zero commutator state.
8495 #[inline]
8496 #[must_use]
8497 pub const fn zero() -> Self {
8498 Self {
8499 accumulator: [0u64; 4],
8500 _level: core::marker::PhantomData,
8501 _sealed: (),
8502 }
8503 }
8504
8505 /// Access the commutator accumulator words.
8506 #[inline]
8507 #[must_use]
8508 pub const fn accumulator(&self) -> &[u64; 4] {
8509 &self.accumulator
8510 }
8511}
8512
8513/// v0.2.2 Phase F / T2.7: sealed iterator driver returned by `run_stream`.
8514/// Carries the productivity countdown initialized from the unit's
8515/// `StreamDeclaration::productivity_bound()`, plus a unit-derived address
8516/// seed for generating distinct `Grounded` outputs per step. Each call to
8517/// `next()` decrements the countdown and yields a `Grounded` whose
8518/// `unit_address` differs from the previous step's.
8519#[derive(Debug, Clone)]
8520pub struct StreamDriver<
8521 T: crate::enforcement::GroundedShape,
8522 P: crate::enforcement::ValidationPhase,
8523 H: crate::enforcement::Hasher,
8524 const INLINE_BYTES: usize,
8525> {
8526 rewrite_steps: u64,
8527 landauer_nats: u64,
8528 productivity_countdown: u64,
8529 seed: u64,
8530 result_type_iri: &'static str,
8531 terminated: bool,
8532 _shape: core::marker::PhantomData<T>,
8533 _phase: core::marker::PhantomData<P>,
8534 _hasher: core::marker::PhantomData<H>,
8535 _sealed: (),
8536}
8537
8538impl<
8539 T: crate::enforcement::GroundedShape,
8540 P: crate::enforcement::ValidationPhase,
8541 H: crate::enforcement::Hasher,
8542 const INLINE_BYTES: usize,
8543 > StreamDriver<T, P, H, INLINE_BYTES>
8544{
8545 /// Crate-internal constructor. Callable only from `pipeline::run_stream`.
8546 #[inline]
8547 #[must_use]
8548 #[allow(dead_code)]
8549 pub(crate) const fn new_internal(
8550 productivity_bound: u64,
8551 seed: u64,
8552 result_type_iri: &'static str,
8553 ) -> Self {
8554 Self {
8555 rewrite_steps: 0,
8556 landauer_nats: 0,
8557 productivity_countdown: productivity_bound,
8558 seed,
8559 result_type_iri,
8560 terminated: false,
8561 _shape: core::marker::PhantomData,
8562 _phase: core::marker::PhantomData,
8563 _hasher: core::marker::PhantomData,
8564 _sealed: (),
8565 }
8566 }
8567
8568 /// Total rewrite steps taken so far.
8569 #[inline]
8570 #[must_use]
8571 pub const fn rewrite_steps(&self) -> u64 {
8572 self.rewrite_steps
8573 }
8574
8575 /// Total Landauer cost accumulated so far.
8576 #[inline]
8577 #[must_use]
8578 pub const fn landauer_nats(&self) -> u64 {
8579 self.landauer_nats
8580 }
8581
8582 /// v0.2.2 T5.10: returns `true` once the driver has stopped producing
8583 /// rewrite steps. A terminated driver is observationally equivalent to
8584 /// one whose next `next()` call returns `None`. Use this when the driver
8585 /// is held inside a larger state machine that needs to decide whether
8586 /// to advance without consuming a step.
8587 /// Parallel to `InteractionDriver::is_converged()`.
8588 #[inline]
8589 #[must_use]
8590 pub const fn is_terminated(&self) -> bool {
8591 self.terminated
8592 }
8593}
8594
8595impl<
8596 T: crate::enforcement::GroundedShape + ConstrainedTypeShape,
8597 P: crate::enforcement::ValidationPhase,
8598 H: crate::enforcement::Hasher,
8599 const INLINE_BYTES: usize,
8600 > Iterator for StreamDriver<T, P, H, INLINE_BYTES>
8601{
8602 type Item = Result<Grounded<T, INLINE_BYTES>, PipelineFailure>;
8603 fn next(&mut self) -> Option<Self::Item> {
8604 if self.terminated || self.productivity_countdown == 0 {
8605 self.terminated = true;
8606 return None;
8607 }
8608 // v0.2.2 T6.11: ShapeMismatch detection — first step only
8609 // (subsequent steps inherit the same result_type_iri).
8610 if self.rewrite_steps == 0 && !crate::enforcement::str_eq(self.result_type_iri, T::IRI) {
8611 self.terminated = true;
8612 return Some(Err(PipelineFailure::ShapeMismatch {
8613 expected: T::IRI,
8614 got: self.result_type_iri,
8615 }));
8616 }
8617 self.productivity_countdown -= 1;
8618 self.rewrite_steps += 1;
8619 self.landauer_nats += 1;
8620 // v0.2.2 T6.1: thread H: Hasher through fold_stream_step_digest
8621 // to compute a real per-step substrate fingerprint.
8622 let mut hasher = H::initial();
8623 hasher = crate::enforcement::fold_stream_step_digest(
8624 hasher,
8625 self.productivity_countdown,
8626 self.rewrite_steps,
8627 self.seed,
8628 self.result_type_iri,
8629 crate::enforcement::CertificateKind::Grounding,
8630 );
8631 let buffer = hasher.finalize();
8632 let content_fingerprint =
8633 crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
8634 let unit_address = crate::enforcement::unit_address_from_buffer(&buffer);
8635 let grounding = Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
8636 32,
8637 content_fingerprint,
8638 ));
8639 let bindings = empty_bindings_table();
8640 Some(Ok(Grounded::<T, INLINE_BYTES>::new_internal(
8641 grounding,
8642 bindings,
8643 32, // default witt level for stream output
8644 unit_address,
8645 content_fingerprint,
8646 )))
8647 }
8648}
8649
8650/// v0.2.2 Phase F / T2.7: sealed state-machine driver returned by
8651/// `run_interactive`. Exposes `step(PeerInput)`, `is_converged()`, and
8652/// `finalize()`. The driver folds each peer input into its
8653/// `commutator_acc` accumulator via XOR; convergence is signalled when
8654/// a peer input arrives with `peer_id == 0` (the closing handshake).
8655#[derive(Debug, Clone)]
8656pub struct InteractionDriver<
8657 T: crate::enforcement::GroundedShape,
8658 P: crate::enforcement::ValidationPhase,
8659 H: crate::enforcement::Hasher,
8660 const INLINE_BYTES: usize,
8661> {
8662 commutator_acc: [u64; 4],
8663 peer_step_count: u64,
8664 converged: bool,
8665 /// Convergence seed read from the source InteractionDeclaration.
8666 /// Available via `seed()` for downstream inspection.
8667 seed: u64,
8668 /// v0.2.2 Phase T.1: previous step's commutator norm (Euclidean-squared
8669 /// over the 4 u64 limbs, saturating). Used to detect divergence.
8670 prev_commutator_norm: u64,
8671 /// v0.2.2 Phase T.1: count of consecutive non-decreasing norm steps.
8672 /// Reset to 0 on any decrease; divergence triggers at `DIVERGENCE_BUDGET`.
8673 consecutive_non_decreasing: u32,
8674 /// v0.2.2 T6.11: result-type IRI from the source InteractionDeclaration.
8675 result_type_iri: &'static str,
8676 _shape: core::marker::PhantomData<T>,
8677 _phase: core::marker::PhantomData<P>,
8678 _hasher: core::marker::PhantomData<H>,
8679 _sealed: (),
8680}
8681
8682/// v0.2.2 Phase T.1: divergence budget — max consecutive non-decreasing commutator-norm
8683/// steps before the interaction driver fails. Foundation-canonical; override at the
8684/// `InteractionDeclaration` level not supported in this release.
8685pub const INTERACTION_DIVERGENCE_BUDGET: u32 = 16;
8686
8687impl<
8688 T: crate::enforcement::GroundedShape,
8689 P: crate::enforcement::ValidationPhase,
8690 H: crate::enforcement::Hasher,
8691 const INLINE_BYTES: usize,
8692 > InteractionDriver<T, P, H, INLINE_BYTES>
8693{
8694 /// Crate-internal constructor. Callable only from `pipeline::run_interactive`.
8695 #[inline]
8696 #[must_use]
8697 #[allow(dead_code)]
8698 pub(crate) const fn new_internal(seed: u64, result_type_iri: &'static str) -> Self {
8699 // Initial commutator seeded from the unit's convergence seed.
8700 Self {
8701 commutator_acc: [seed, 0, 0, 0],
8702 peer_step_count: 0,
8703 converged: false,
8704 seed,
8705 // Initial norm = seed² (saturating) so the first step can only
8706 // decrease the norm via peer input (which is the convergence path).
8707 prev_commutator_norm: seed.saturating_mul(seed),
8708 consecutive_non_decreasing: 0,
8709 result_type_iri,
8710 _shape: core::marker::PhantomData,
8711 _phase: core::marker::PhantomData,
8712 _hasher: core::marker::PhantomData,
8713 _sealed: (),
8714 }
8715 }
8716
8717 /// v0.2.2 Phase T.1: convergence threshold derived from the seed. Termination
8718 /// triggers when the commutator norm falls below this value. Foundation-canonical:
8719 /// `seed.rotate_right(32) ^ 0xDEADBEEF_CAFEBABE`.
8720 #[inline]
8721 #[must_use]
8722 pub const fn convergence_threshold(&self) -> u64 {
8723 self.seed.rotate_right(32) ^ 0xDEAD_BEEF_CAFE_BABE
8724 }
8725
8726 /// Advance the driver by folding in a single peer input (v0.2.2 Phase T.1).
8727 /// Each step XOR-folds the peer payload's first 4 limbs into the
8728 /// commutator accumulator, then recomputes the Euclidean-squared
8729 /// norm over the 4 limbs (saturating `u64`). Termination rules:
8730 /// * **Converged** if the norm falls below `convergence_threshold()`,
8731 /// OR if `peer_id == 0` (explicit closing handshake).
8732 /// * **Diverged** (via `PipelineFailure::ConvergenceStall`) if the norm is
8733 /// non-decreasing for `INTERACTION_DIVERGENCE_BUDGET` consecutive steps.
8734 /// * **Continue** otherwise.
8735 #[must_use]
8736 pub fn step(&mut self, input: PeerInput) -> StepResult<T, INLINE_BYTES>
8737 where
8738 T: ConstrainedTypeShape,
8739 {
8740 self.peer_step_count += 1;
8741 // Fold the first 4 payload words into the accumulator.
8742 let words = input.payload().words();
8743 let mut i = 0usize;
8744 while i < 4 {
8745 self.commutator_acc[i] ^= words[i];
8746 i += 1;
8747 }
8748 // v0.2.2 Phase T.1: compute the Euclidean-squared norm over the 4 limbs.
8749 let mut norm: u64 = 0;
8750 let mut j = 0usize;
8751 while j < 4 {
8752 let limb = self.commutator_acc[j];
8753 norm = norm.saturating_add(limb.saturating_mul(limb));
8754 j += 1;
8755 }
8756 let threshold = self.convergence_threshold();
8757 // v0.2.2 Phase T.1: convergence on norm-below-threshold OR explicit
8758 // handshake (peer_id == 0). Divergence on consecutive non-decreasing norm.
8759 let norm_converged = norm < threshold;
8760 let handshake_close = input.peer_id() == 0;
8761 if norm_converged || handshake_close {
8762 self.converged = true;
8763 // v0.2.2 T6.1: thread H: Hasher through fold_interaction_step_digest
8764 // to compute a real convergence-time substrate fingerprint.
8765 let mut hasher = H::initial();
8766 hasher = crate::enforcement::fold_interaction_step_digest(
8767 hasher,
8768 &self.commutator_acc,
8769 self.peer_step_count,
8770 self.seed,
8771 self.result_type_iri,
8772 crate::enforcement::CertificateKind::Grounding,
8773 );
8774 let buffer = hasher.finalize();
8775 let content_fingerprint =
8776 crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
8777 let unit_address = crate::enforcement::unit_address_from_buffer(&buffer);
8778 let grounding = Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
8779 32,
8780 content_fingerprint,
8781 ));
8782 let bindings = empty_bindings_table();
8783 return StepResult::Converged(Grounded::<T, INLINE_BYTES>::new_internal(
8784 grounding,
8785 bindings,
8786 32,
8787 unit_address,
8788 content_fingerprint,
8789 ));
8790 }
8791 // v0.2.2 Phase T.1: divergence detection — count consecutive
8792 // non-decreasing norm steps. Reset on any decrease.
8793 if norm >= self.prev_commutator_norm {
8794 self.consecutive_non_decreasing = self.consecutive_non_decreasing.saturating_add(1);
8795 } else {
8796 self.consecutive_non_decreasing = 0;
8797 }
8798 self.prev_commutator_norm = norm;
8799 if self.consecutive_non_decreasing >= INTERACTION_DIVERGENCE_BUDGET {
8800 return StepResult::Diverged;
8801 }
8802 StepResult::Continue
8803 }
8804
8805 /// Whether the driver has reached the convergence predicate.
8806 #[inline]
8807 #[must_use]
8808 pub const fn is_converged(&self) -> bool {
8809 self.converged
8810 }
8811
8812 /// Number of peer steps applied so far.
8813 #[inline]
8814 #[must_use]
8815 pub const fn peer_step_count(&self) -> u64 {
8816 self.peer_step_count
8817 }
8818
8819 /// Convergence seed inherited from the source InteractionDeclaration.
8820 #[inline]
8821 #[must_use]
8822 pub const fn seed(&self) -> u64 {
8823 self.seed
8824 }
8825
8826 /// Finalize the interaction, producing a grounded result.
8827 /// Returns a `Grounded<T>` whose `unit_address` is a hash of the
8828 /// accumulated commutator state, so two interaction drivers that
8829 /// processed different peer inputs return distinct grounded values.
8830 /// # Errors
8831 /// Returns a `PipelineFailure::ShapeViolation` if the driver has
8832 /// not converged, or `PipelineFailure::ShapeMismatch` if the source
8833 /// declaration's result_type_iri does not match `T::IRI`.
8834 pub fn finalize(self) -> Result<Grounded<T, INLINE_BYTES>, PipelineFailure>
8835 where
8836 T: ConstrainedTypeShape,
8837 {
8838 // v0.2.2 T6.11: ShapeMismatch detection.
8839 if !crate::enforcement::str_eq(self.result_type_iri, T::IRI) {
8840 return Err(PipelineFailure::ShapeMismatch {
8841 expected: T::IRI,
8842 got: self.result_type_iri,
8843 });
8844 }
8845 if !self.converged {
8846 return Err(PipelineFailure::ShapeViolation {
8847 report: ShapeViolation {
8848 shape_iri: "https://uor.foundation/conformance/InteractionShape",
8849 constraint_iri:
8850 "https://uor.foundation/conformance/InteractionShape#convergence",
8851 property_iri: "https://uor.foundation/conformance/convergencePredicate",
8852 expected_range: "http://www.w3.org/2002/07/owl#Thing",
8853 min_count: 1,
8854 max_count: 1,
8855 kind: ViolationKind::Missing,
8856 },
8857 });
8858 }
8859 // v0.2.2 T6.1: thread H: Hasher through fold_interaction_step_digest.
8860 let mut hasher = H::initial();
8861 hasher = crate::enforcement::fold_interaction_step_digest(
8862 hasher,
8863 &self.commutator_acc,
8864 self.peer_step_count,
8865 self.seed,
8866 self.result_type_iri,
8867 crate::enforcement::CertificateKind::Grounding,
8868 );
8869 let buffer = hasher.finalize();
8870 let content_fingerprint =
8871 crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
8872 let unit_address = crate::enforcement::unit_address_from_buffer(&buffer);
8873 let grounding = Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
8874 32,
8875 content_fingerprint,
8876 ));
8877 let bindings = empty_bindings_table();
8878 Ok(Grounded::<T, INLINE_BYTES>::new_internal(
8879 grounding,
8880 bindings,
8881 32,
8882 unit_address,
8883 content_fingerprint,
8884 ))
8885 }
8886}
8887
8888/// v0.2.2 Phase F / T2.7: parallel driver entry point.
8889/// Consumes a `Validated<ParallelDeclaration, P>` and produces a unified
8890/// `Grounded<T>` whose `unit_address` is derived from the declaration's
8891/// site count via FNV-1a. Two units with different site counts produce
8892/// `Grounded` values with different addresses.
8893/// # Errors
8894/// Returns `PipelineFailure::ShapeMismatch` when the declaration's
8895/// `result_type_iri` does not match `T::IRI` — the caller asked for
8896/// `Grounded<T>` but the declaration was built over a different shape.
8897/// Returns `PipelineFailure::ContradictionDetected` when the declared
8898/// partition cardinality is zero — a parallel composition with no
8899/// sites is inadmissible by construction.
8900/// Success: `run_parallel` folds the declaration's site count through
8901/// `fold_parallel_digest` to produce a content fingerprint; distinct
8902/// partitions produce distinct fingerprints by construction.
8903/// # Example
8904/// ```no_run
8905/// use uor_foundation::enforcement::{ConstrainedTypeInput, Validated};
8906/// use uor_foundation::pipeline::{run_parallel, ParallelDeclaration};
8907/// # use uor_foundation::enforcement::Hasher;
8908/// # struct Fnv1aHasher16;
8909/// # impl Hasher for Fnv1aHasher16 {
8910/// # const OUTPUT_BYTES: usize = 16;
8911/// # fn initial() -> Self { Self }
8912/// # fn fold_byte(self, _: u8) -> Self { self }
8913/// # fn finalize(self) -> [u8; 32] { [0; 32] }
8914/// # }
8915/// # fn wrap<T>(t: T) -> Validated<T> { unimplemented!() /* see uor_foundation_test_helpers */ }
8916/// // 3-component partition over 9 sites.
8917/// static PARTITION: &[u32] = &[0, 0, 0, 1, 1, 1, 2, 2, 2];
8918/// let decl: Validated<ParallelDeclaration> = wrap(
8919/// ParallelDeclaration::new_with_partition::<ConstrainedTypeInput>(
8920/// PARTITION,
8921/// "https://uor.foundation/parallel/ParallelDisjointnessWitness",
8922/// ),
8923/// );
8924/// // ADR-060: `Grounded` carries an `INLINE_BYTES` const-generic the
8925/// // application derives from its `HostBounds`; thread it through
8926/// // `run_parallel`'s 4th const argument.
8927/// let grounded = run_parallel::<ConstrainedTypeInput, _, Fnv1aHasher16, 32>(decl)
8928/// .expect("partition admits");
8929/// # let _ = grounded;
8930/// ```
8931pub fn run_parallel<T, P, H, const INLINE_BYTES: usize>(
8932 unit: Validated<ParallelDeclaration, P>,
8933) -> Result<Grounded<T, INLINE_BYTES>, PipelineFailure>
8934where
8935 T: ConstrainedTypeShape + crate::enforcement::GroundedShape,
8936 P: crate::enforcement::ValidationPhase,
8937 H: crate::enforcement::Hasher,
8938{
8939 let decl = unit.inner();
8940 let site_count = decl.site_count();
8941 let partition = decl.site_partition();
8942 let witness_iri = decl.disjointness_witness();
8943 // Runtime invariants declared in the ParallelDeclaration rustdoc:
8944 // (1) result_type_iri must match T::IRI (target §5 + T6.11);
8945 // (2) site_count > 0 (zero-site parallel composition is vacuous);
8946 // (3) v0.2.2 Phase H3: partition length must equal site_count;
8947 // (4) v0.2.2 Phase H3: partition must be non-empty (only constructor is
8948 // `new_with_partition`, which takes a real partition slice).
8949 if !crate::enforcement::str_eq(decl.result_type_iri(), T::IRI) {
8950 return Err(PipelineFailure::ShapeMismatch {
8951 expected: T::IRI,
8952 got: decl.result_type_iri(),
8953 });
8954 }
8955 if site_count == 0 || partition.is_empty() {
8956 return Err(PipelineFailure::ContradictionDetected {
8957 at_step: 0,
8958 trace_iri: "https://uor.foundation/parallel/ParallelProduct",
8959 });
8960 }
8961 if partition.len() as u64 != site_count {
8962 return Err(PipelineFailure::ShapeMismatch {
8963 expected: T::IRI,
8964 got: decl.result_type_iri(),
8965 });
8966 }
8967 // v0.2.2 Phase H3: walk partition, count sites per component, fold
8968 // per-component into the content fingerprint. Enumerates unique component
8969 // IDs into a fixed stack buffer sized by WITT_MAX_BITS.
8970 let mut hasher = H::initial();
8971 // component_ids: seen component IDs in first-appearance order.
8972 // component_counts: parallel site-count per component.
8973 let mut component_ids = [0u32; WITT_MAX_BITS as usize];
8974 let mut component_counts = [0u32; WITT_MAX_BITS as usize];
8975 let mut n_components: usize = 0;
8976 let mut si = 0;
8977 while si < partition.len() {
8978 let cid = partition[si];
8979 // Find or insert cid.
8980 let mut ci = 0;
8981 let mut found = false;
8982 while ci < n_components {
8983 if component_ids[ci] == cid {
8984 component_counts[ci] = component_counts[ci].saturating_add(1);
8985 found = true;
8986 break;
8987 }
8988 ci += 1;
8989 }
8990 if !found && n_components < (WITT_MAX_BITS as usize) {
8991 component_ids[n_components] = cid;
8992 component_counts[n_components] = 1;
8993 n_components += 1;
8994 }
8995 si += 1;
8996 }
8997 // Fold each component: (component_id, site_count_within) in first-appearance order.
8998 let mut ci = 0;
8999 while ci < n_components {
9000 hasher = hasher.fold_bytes(&component_ids[ci].to_be_bytes());
9001 hasher = hasher.fold_bytes(&component_counts[ci].to_be_bytes());
9002 ci += 1;
9003 }
9004 // Fold disjointness_witness IRI so forgeries yield distinct content fingerprints.
9005 hasher = hasher.fold_bytes(witness_iri.as_bytes());
9006 hasher = hasher.fold_byte(0);
9007 // Canonical ParallelDeclaration tail: site_count + type shape + cert kind.
9008 hasher = crate::enforcement::fold_parallel_digest(
9009 hasher,
9010 site_count,
9011 T::IRI,
9012 T::SITE_COUNT,
9013 T::CONSTRAINTS,
9014 crate::enforcement::CertificateKind::Grounding,
9015 );
9016 let buffer = hasher.finalize();
9017 let content_fingerprint =
9018 crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
9019 let unit_address = crate::enforcement::unit_address_from_buffer(&buffer);
9020 let grounding = Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
9021 32,
9022 content_fingerprint,
9023 ));
9024 let bindings = empty_bindings_table();
9025 Ok(Grounded::<T, INLINE_BYTES>::new_internal(
9026 grounding,
9027 bindings,
9028 32,
9029 unit_address,
9030 content_fingerprint,
9031 ))
9032}
9033
9034/// v0.2.2 Phase F / T2.7: stream driver entry point.
9035/// Consumes a `Validated<StreamDeclaration, P>` and returns a
9036/// `StreamDriver<T, P>` implementing `Iterator`. The driver's productivity
9037/// countdown is initialized from `StreamDeclaration::productivity_bound()`;
9038/// each `next()` call yields a `Grounded` whose `unit_address` differs
9039/// from the previous step's, and the iterator terminates when the
9040/// countdown reaches zero.
9041#[must_use]
9042pub fn run_stream<T, P, H, const INLINE_BYTES: usize>(
9043 unit: Validated<StreamDeclaration<'_, INLINE_BYTES>, P>,
9044) -> StreamDriver<T, P, H, INLINE_BYTES>
9045where
9046 T: crate::enforcement::GroundedShape,
9047 P: crate::enforcement::ValidationPhase,
9048 H: crate::enforcement::Hasher,
9049{
9050 let bound = unit.inner().productivity_bound();
9051 let result_type_iri = unit.inner().result_type_iri();
9052 StreamDriver::new_internal(bound, bound, result_type_iri)
9053}
9054
9055/// v0.2.2 Phase F / T2.7: interaction driver entry point.
9056/// Consumes a `Validated<InteractionDeclaration, P>` and returns an
9057/// `InteractionDriver<T, P, H>` state machine seeded from the declaration's
9058/// `convergence_seed()`. Advance with `step(PeerInput)` until
9059/// `is_converged()` returns `true`, then call `finalize()`.
9060#[must_use]
9061pub fn run_interactive<T, P, H, const INLINE_BYTES: usize>(
9062 unit: Validated<InteractionDeclaration, P>,
9063) -> InteractionDriver<T, P, H, INLINE_BYTES>
9064where
9065 T: crate::enforcement::GroundedShape,
9066 P: crate::enforcement::ValidationPhase,
9067 H: crate::enforcement::Hasher,
9068{
9069 InteractionDriver::new_internal(
9070 unit.inner().convergence_seed(),
9071 unit.inner().result_type_iri(),
9072 )
9073}
9074
9075/// v0.2.2 Phase G / T2.8: const-fn companion for
9076/// `LeaseDeclarationBuilder`. Delegates to the builder's
9077/// `validate_const` method, which validates the `LeaseShape` contract
9078/// (`linear_site` and `scope` required) at compile time.
9079/// # Errors
9080/// Returns `ShapeViolation::Missing` if `linear_site` or `scope` is unset.
9081pub const fn validate_lease_const<'a>(
9082 builder: &LeaseDeclarationBuilder<'a>,
9083) -> Result<Validated<LeaseDeclaration, CompileTime>, ShapeViolation> {
9084 builder.validate_const()
9085}
9086
9087/// v0.2.2 Phase G / T2.8 + T6.13: const-fn companion for `CompileUnitBuilder`.
9088///
9089/// Tightened in T6.13 to enforce the same five required fields as the
9090/// runtime `CompileUnitBuilder::validate()` method:
9091///
9092/// - `root_term`
9093/// - `witt_level_ceiling`
9094/// - `thermodynamic_budget`
9095/// - `target_domains` (non-empty)
9096/// - `result_type_iri`
9097///
9098/// Returns `Result<Validated<CompileUnit, CompileTime>, ShapeViolation>` —
9099/// dual-path consistent with the runtime `validate()` method. Const-eval
9100/// call sites match on the `Result`; the panic only fires at codegen /
9101/// const-eval time, never at runtime.
9102///
9103/// # Errors
9104///
9105/// Returns `ShapeViolation::Missing` for the first unset required field.
9106pub const fn validate_compile_unit_const<'a, const INLINE_BYTES: usize>(
9107 builder: &CompileUnitBuilder<'a, INLINE_BYTES>,
9108) -> Result<Validated<CompileUnit<'a, INLINE_BYTES>, CompileTime>, ShapeViolation> {
9109 if !builder.has_root_term_const() {
9110 return Err(ShapeViolation {
9111 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
9112 constraint_iri: "https://uor.foundation/conformance/compileUnit_rootTerm_constraint",
9113 property_iri: "https://uor.foundation/reduction/rootTerm",
9114 expected_range: "https://uor.foundation/schema/Term",
9115 min_count: 1,
9116 max_count: 1,
9117 kind: ViolationKind::Missing,
9118 });
9119 }
9120 let level = match builder.witt_level_option() {
9121 Some(l) => l,
9122 None => {
9123 return Err(ShapeViolation {
9124 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
9125 constraint_iri:
9126 "https://uor.foundation/conformance/compileUnit_unitWittLevel_constraint",
9127 property_iri: "https://uor.foundation/reduction/unitWittLevel",
9128 expected_range: "https://uor.foundation/schema/WittLevel",
9129 min_count: 1,
9130 max_count: 1,
9131 kind: ViolationKind::Missing,
9132 })
9133 }
9134 };
9135 let budget =
9136 match builder.budget_option() {
9137 Some(b) => b,
9138 None => return Err(ShapeViolation {
9139 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
9140 constraint_iri:
9141 "https://uor.foundation/conformance/compileUnit_thermodynamicBudget_constraint",
9142 property_iri: "https://uor.foundation/reduction/thermodynamicBudget",
9143 expected_range: "http://www.w3.org/2001/XMLSchema#decimal",
9144 min_count: 1,
9145 max_count: 1,
9146 kind: ViolationKind::Missing,
9147 }),
9148 };
9149 if !builder.has_target_domains_const() {
9150 return Err(ShapeViolation {
9151 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
9152 constraint_iri:
9153 "https://uor.foundation/conformance/compileUnit_targetDomains_constraint",
9154 property_iri: "https://uor.foundation/reduction/targetDomains",
9155 expected_range: "https://uor.foundation/op/VerificationDomain",
9156 min_count: 1,
9157 max_count: 0,
9158 kind: ViolationKind::Missing,
9159 });
9160 }
9161 let result_type_iri = match builder.result_type_iri_const() {
9162 Some(iri) => iri,
9163 None => {
9164 return Err(ShapeViolation {
9165 shape_iri: "https://uor.foundation/conformance/CompileUnitShape",
9166 constraint_iri:
9167 "https://uor.foundation/conformance/compileUnit_resultType_constraint",
9168 property_iri: "https://uor.foundation/reduction/resultType",
9169 expected_range: "https://uor.foundation/type/ConstrainedType",
9170 min_count: 1,
9171 max_count: 1,
9172 kind: ViolationKind::Missing,
9173 })
9174 }
9175 };
9176 Ok(Validated::new(CompileUnit::from_parts_const(
9177 level,
9178 budget,
9179 result_type_iri,
9180 builder.root_term_slice_const(),
9181 builder.bindings_slice_const(),
9182 builder.target_domains_slice_const(),
9183 )))
9184}
9185
9186/// v0.2.2 Phase G / T2.8 + T6.11: const-fn companion for
9187/// `ParallelDeclarationBuilder`. Takes a `ConstrainedTypeShape` type parameter
9188/// to set the `result_type_iri` on the produced declaration.
9189/// v0.2.2 Phase A: the produced `ParallelDeclaration<'a>` carries the
9190/// builder's raw site-partition slice and disjointness-witness IRI; the
9191/// lifetime `'a` is the builder's borrow lifetime.
9192#[must_use]
9193pub const fn validate_parallel_const<'a, T: ConstrainedTypeShape>(
9194 builder: &ParallelDeclarationBuilder<'a>,
9195) -> Validated<ParallelDeclaration<'a>, CompileTime> {
9196 Validated::new(ParallelDeclaration::new_with_partition::<T>(
9197 builder.site_partition_slice_const(),
9198 builder.disjointness_witness_const(),
9199 ))
9200}
9201
9202/// v0.2.2 Phase G / T2.8 + T6.11: const-fn companion for
9203/// `StreamDeclarationBuilder`. Takes a `ConstrainedTypeShape` type parameter
9204/// to set the `result_type_iri` on the produced declaration.
9205/// v0.2.2 Phase A: the produced `StreamDeclaration<'a>` retains the
9206/// builder's seed/step term slices and productivity-witness IRI.
9207#[must_use]
9208pub const fn validate_stream_const<'a, const INLINE_BYTES: usize, T: ConstrainedTypeShape>(
9209 builder: &StreamDeclarationBuilder<'a, INLINE_BYTES>,
9210) -> Validated<StreamDeclaration<'a, INLINE_BYTES>, CompileTime> {
9211 let bound = builder.productivity_bound_const();
9212 Validated::new(StreamDeclaration::new_full::<T>(
9213 bound,
9214 builder.seed_slice_const(),
9215 builder.step_slice_const(),
9216 builder.productivity_witness_const(),
9217 ))
9218}
9219
9220/// v0.2.2 T5 C6: const-fn resolver companion for
9221/// `tower_completeness::certify`. Threads the consumer-supplied substrate
9222/// `Hasher` through the canonical CompileUnit byte layout to compute a
9223/// parametric content fingerprint, distinguishing two units that share a
9224/// witt level but differ in budget, IRI, site count, or constraints.
9225#[must_use]
9226pub fn certify_tower_completeness_const<T, H, const INLINE_BYTES: usize>(
9227 unit: &Validated<CompileUnit<'_, INLINE_BYTES>, CompileTime>,
9228) -> Validated<GroundingCertificate, CompileTime>
9229where
9230 T: ConstrainedTypeShape,
9231 H: crate::enforcement::Hasher,
9232{
9233 let level_bits = unit.inner().witt_level().witt_length() as u16;
9234 let budget = unit.inner().thermodynamic_budget();
9235 let mut hasher = H::initial();
9236 hasher = crate::enforcement::fold_unit_digest(
9237 hasher,
9238 level_bits,
9239 budget,
9240 T::IRI,
9241 T::SITE_COUNT,
9242 T::CONSTRAINTS,
9243 crate::enforcement::CertificateKind::TowerCompleteness,
9244 );
9245 let buffer = hasher.finalize();
9246 let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
9247 Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
9248 level_bits, fp,
9249 ))
9250}
9251
9252/// v0.2.2 T5 C6: const-fn resolver companion for
9253/// `incremental_completeness::certify`. Threads `H: Hasher` for the
9254/// parametric fingerprint; uses `CertificateKind::IncrementalCompleteness`
9255/// as the trailing discriminant byte.
9256#[must_use]
9257pub fn certify_incremental_completeness_const<T, H, const INLINE_BYTES: usize>(
9258 unit: &Validated<CompileUnit<'_, INLINE_BYTES>, CompileTime>,
9259) -> Validated<GroundingCertificate, CompileTime>
9260where
9261 T: ConstrainedTypeShape,
9262 H: crate::enforcement::Hasher,
9263{
9264 let level_bits = unit.inner().witt_level().witt_length() as u16;
9265 let budget = unit.inner().thermodynamic_budget();
9266 let mut hasher = H::initial();
9267 hasher = crate::enforcement::fold_unit_digest(
9268 hasher,
9269 level_bits,
9270 budget,
9271 T::IRI,
9272 T::SITE_COUNT,
9273 T::CONSTRAINTS,
9274 crate::enforcement::CertificateKind::IncrementalCompleteness,
9275 );
9276 let buffer = hasher.finalize();
9277 let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
9278 Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
9279 level_bits, fp,
9280 ))
9281}
9282
9283/// v0.2.2 T5 C6: const-fn resolver companion for `inhabitance::certify`.
9284/// Threads `H: Hasher` for the parametric fingerprint; uses
9285/// `CertificateKind::Inhabitance` as the trailing discriminant byte.
9286#[must_use]
9287pub fn certify_inhabitance_const<T, H, const INLINE_BYTES: usize>(
9288 unit: &Validated<CompileUnit<'_, INLINE_BYTES>, CompileTime>,
9289) -> Validated<GroundingCertificate, CompileTime>
9290where
9291 T: ConstrainedTypeShape,
9292 H: crate::enforcement::Hasher,
9293{
9294 let level_bits = unit.inner().witt_level().witt_length() as u16;
9295 let budget = unit.inner().thermodynamic_budget();
9296 let mut hasher = H::initial();
9297 hasher = crate::enforcement::fold_unit_digest(
9298 hasher,
9299 level_bits,
9300 budget,
9301 T::IRI,
9302 T::SITE_COUNT,
9303 T::CONSTRAINTS,
9304 crate::enforcement::CertificateKind::Inhabitance,
9305 );
9306 let buffer = hasher.finalize();
9307 let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
9308 Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
9309 level_bits, fp,
9310 ))
9311}
9312
9313/// v0.2.2 T5 C6: const-fn resolver companion for
9314/// `multiplication::certify`. Threads `H: Hasher` for the parametric
9315/// fingerprint; uses `CertificateKind::Multiplication` as the trailing
9316/// discriminant byte.
9317#[must_use]
9318pub fn certify_multiplication_const<T, H, const INLINE_BYTES: usize>(
9319 unit: &Validated<CompileUnit<'_, INLINE_BYTES>, CompileTime>,
9320) -> Validated<MultiplicationCertificate, CompileTime>
9321where
9322 T: ConstrainedTypeShape,
9323 H: crate::enforcement::Hasher,
9324{
9325 let level_bits = unit.inner().witt_level().witt_length() as u16;
9326 let budget = unit.inner().thermodynamic_budget();
9327 let mut hasher = H::initial();
9328 hasher = crate::enforcement::fold_unit_digest(
9329 hasher,
9330 level_bits,
9331 budget,
9332 T::IRI,
9333 T::SITE_COUNT,
9334 T::CONSTRAINTS,
9335 crate::enforcement::CertificateKind::Multiplication,
9336 );
9337 let buffer = hasher.finalize();
9338 let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
9339 Validated::new(MultiplicationCertificate::with_level_and_fingerprint_const(
9340 level_bits, fp,
9341 ))
9342}
9343
9344/// Phase C.4: const-fn resolver companion for `grounding_aware::certify`.
9345/// Threads `H: Hasher` for the parametric fingerprint; uses
9346/// `CertificateKind::Grounding` as the trailing discriminant byte.
9347#[must_use]
9348pub fn certify_grounding_aware_const<T, H, const INLINE_BYTES: usize>(
9349 unit: &Validated<CompileUnit<'_, INLINE_BYTES>, CompileTime>,
9350) -> Validated<GroundingCertificate, CompileTime>
9351where
9352 T: ConstrainedTypeShape,
9353 H: crate::enforcement::Hasher,
9354{
9355 let level_bits = unit.inner().witt_level().witt_length() as u16;
9356 let budget = unit.inner().thermodynamic_budget();
9357 let mut hasher = H::initial();
9358 hasher = crate::enforcement::fold_unit_digest(
9359 hasher,
9360 level_bits,
9361 budget,
9362 T::IRI,
9363 T::SITE_COUNT,
9364 T::CONSTRAINTS,
9365 crate::enforcement::CertificateKind::Grounding,
9366 );
9367 let buffer = hasher.finalize();
9368 let fp = crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
9369 Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
9370 level_bits, fp,
9371 ))
9372}
9373
9374/// v0.2.2 T5 C6: typed pipeline entry point producing `Grounded<T>` from
9375/// a validated `CompileUnit`. Threads the consumer-supplied substrate
9376/// `Hasher` through `fold_unit_digest` to compute a parametric content
9377/// fingerprint over the unit's full state: `(level_bits, budget, T::IRI,
9378/// T::SITE_COUNT, T::CONSTRAINTS, CertificateKind::Grounding)`.
9379/// Two units differing on **any** of those fields produce `Grounded`
9380/// values with distinct fingerprints (and distinct `unit_address` handles,
9381/// derived from the leading 16 bytes of the fingerprint).
9382/// # Errors
9383/// Returns `PipelineFailure::ShapeMismatch` when the unit's declared
9384/// `result_type_iri` does not match `T::IRI`, or propagates any
9385/// failure from the reduction stage executor.
9386pub fn run_const<T, M, H, const INLINE_BYTES: usize>(
9387 unit: &Validated<CompileUnit<'_, INLINE_BYTES>, CompileTime>,
9388) -> Result<Grounded<T, INLINE_BYTES>, PipelineFailure>
9389where
9390 T: ConstrainedTypeShape + crate::enforcement::GroundedShape,
9391 // Phase C.2 (target §6): const-eval admits only those grounding-map kinds
9392 // that are both Total (defined for all inputs) and Invertible (one-to-one).
9393 // The bound is enforced at the type level via the existing marker tower.
9394 M: crate::enforcement::GroundingMapKind
9395 + crate::enforcement::Total
9396 + crate::enforcement::Invertible,
9397 H: crate::enforcement::Hasher,
9398{
9399 // The marker bound on M is purely type-level — no runtime use.
9400 let _phantom_map: core::marker::PhantomData<M> = core::marker::PhantomData;
9401 let level_bits = unit.inner().witt_level().witt_length() as u16;
9402 let budget = unit.inner().thermodynamic_budget();
9403 // v0.2.2 T6.11: ShapeMismatch detection. The unit declares its
9404 // result_type_iri at validation time; the caller's `T::IRI` must match.
9405 let unit_iri = unit.inner().result_type_iri();
9406 if !crate::enforcement::str_eq(unit_iri, T::IRI) {
9407 return Err(PipelineFailure::ShapeMismatch {
9408 expected: T::IRI,
9409 got: unit_iri,
9410 });
9411 }
9412 // Walk the foundation-locked byte layout via the consumer's hasher.
9413 let mut hasher = H::initial();
9414 hasher = crate::enforcement::fold_unit_digest(
9415 hasher,
9416 level_bits,
9417 budget,
9418 T::IRI,
9419 T::SITE_COUNT,
9420 T::CONSTRAINTS,
9421 crate::enforcement::CertificateKind::Grounding,
9422 );
9423 let buffer = hasher.finalize();
9424 let content_fingerprint =
9425 crate::enforcement::ContentFingerprint::from_buffer(buffer, H::OUTPUT_BYTES as u8);
9426 let unit_address = crate::enforcement::unit_address_from_buffer(&buffer);
9427 let grounding = Validated::new(GroundingCertificate::with_level_and_fingerprint_const(
9428 level_bits,
9429 content_fingerprint,
9430 ));
9431 let bindings = empty_bindings_table();
9432 Ok(Grounded::<T, INLINE_BYTES>::new_internal(
9433 grounding,
9434 bindings,
9435 level_bits,
9436 unit_address,
9437 content_fingerprint,
9438 ))
9439}