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