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