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