Skip to content

Commit dba233d

Browse files
afflomclaude
andcommitted
Bump version to 0.4.8
Implements wiki ADR-055 (Universal substrate-Term verb body discipline, supersedes ADR-054 RA2, amends ADR-010). The `SubstrateTermBody` supertrait makes fold-fusion structurally universal for every axis impl, not just the standard library's canonical impls. Application-author custom axes now structurally carry their substrate-Term decomposition — or the empty-arena primitive-fast- path interpretation that preserves backward-compatibility while still satisfying the type-system bound. Foundation surface additions: - `pub trait SubstrateTermBody: __sdk_seal::Sealed { fn body_arena() -> &'static [Term]; }` on `uor_foundation::pipeline`. Sealed via `__sdk_seal::Sealed`; emission is via the `axis!` SDK macro per ADR-030 + ADR-052. - `pub trait AxisExtension: SubstrateTermBody { ... }` — the supertrait bound is now structural, not opt-in (ADR-054 RA2 reversal). - `AxisTuple::body_arena_at(axis_index) -> &'static [Term]` — per-axis body dispatcher the catamorphism queries to decide between the primitive fast-path and the recursive-fold path. - All AxisTuple impls (Hasher blanket, 1-tuple through 8-tuple) emit `body_arena_at` that delegates to each axis position's `SubstrateTermBody`. Catamorphism fold-rule amendment (ADR-029): - `Term::AxisInvocation` reads `<A as AxisTuple>::body_arena_at(axis_index)`. Empty slice → primitive fast-path via `dispatch_kernel` (existing behavior). Non-empty slice → recursively folds the body's Term arena with the evaluated kernel input bound as `input_bytes`, walking through every axis-surface kernel structurally. SDK macro emission (axis_extension_impl_for_*!): - Both non-generic and `@generic` arms now emit `impl __sdk_seal::Sealed for $struct` and `impl SubstrateTermBody for $struct { fn body_arena() -> &[] }` alongside the existing `impl AxisExtension`. Existing axis impls migrate without source changes (the empty-arena default is the primitive-fast-path interpretation). HashAxis<H> (foundation-built adapter): - Emits `impl SubstrateTermBody for HashAxis<H> { fn body_arena() -> &[] }` + `impl __sdk_seal::Sealed for HashAxis<H>`. The canonical hash fold is the primitive fast-path; `fold_bytes` ∘ `finalize` is byte-output- equivalent to its (empty) substrate-Term decomposition. Substrate amendment: - `TRACE_REPLAY_FORMAT_VERSION` bumped 8 → 9 to reflect the new trait surface. The on-wire shape of `Term::AxisInvocation` is unchanged (body_arena() is a static const, not wire-format state); the version bump records the trait-bound discipline change per ADR-013/TR-08. - `rust/trace_byte_layout_pinned` conformance validator updated to pin format-version 9. Conformance test coverage: - foundation/tests/behavior_adr_055_substrate_term_body.rs (6 tests) pins: SubstrateTermBody public-path reachability, AxisExtension's SubstrateTermBody supertrait bound, HashAxis primitive-fast-path body, AxisTuple body_arena_at dispatch, Hasher-blanket body_arena_at, and the catamorphism's primitive-fast-path branch via evaluate_term_tree. - foundation/tests/public-api.snapshot + .nostd: add `trait SubstrateTermBody`. - conformance/endpoint_coverage.toml: ADR-055 coverage mapping. Future work (not blocking this release): - The `axis!` macro's `body` clause grammar extension — application authors who want recursive fold-fusion through their axis kernel will declare a substrate-Term composition via the `verb!`-style closure- body grammar. The trait surface is in place; the macro emission defaults to empty body_arena (primitive fast-path) until applications opt in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2006bfb commit dba233d

17 files changed

Lines changed: 491 additions & 77 deletions

Cargo.lock

Lines changed: 13 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ members = [
1818
resolver = "2"
1919

2020
[workspace.package]
21-
version = "0.4.7"
21+
version = "0.4.8"
2222
edition = "2021"
2323
rust-version = "1.83"
2424
license = "Apache-2.0"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ structure based on Z/(2^n)Z.
99

1010
## Ontology
1111

12-
Version 0.4.7: 34 namespaces · 473 classes · 948 properties · 3601 named individuals
12+
Version 0.4.8: 34 namespaces · 473 classes · 948 properties · 3601 named individuals
1313

1414
All terms are encoded as typed Rust data in `spec/` (`uor-ontology`) and exported as:
1515
- `foundation/` (`uor-foundation`) — typed Rust traits (published to crates.io)

codegen/src/enforcement.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5440,7 +5440,7 @@ fn generate_grounded_wrapper(f: &mut RustFile) {
54405440
f.doc_comment("Increment when the layout changes (event ordering, trailing fields,");
54415441
f.doc_comment("primitive-op discriminant table, certificate-kind discriminant table).");
54425442
f.doc_comment("Pinned by the `rust/trace_byte_layout_pinned` conformance validator.");
5443-
f.line("pub const TRACE_REPLAY_FORMAT_VERSION: u16 = 8;");
5443+
f.line("pub const TRACE_REPLAY_FORMAT_VERSION: u16 = 9;");
54445444
f.blank();
54455445
f.doc_comment("v0.2.2 T5: pluggable content hasher with parametric output width.");
54465446
f.doc_comment("");
@@ -5587,6 +5587,16 @@ fn generate_grounded_wrapper(f: &mut RustFile) {
55875587
f.line(" pub const KERNEL_HASH: u32 = 0;");
55885588
f.line("}");
55895589
f.blank();
5590+
// ADR-055: HashAxis is a primitive-fast-path axis. Its body is
5591+
// byte-output-equivalent to `fold_bytes` ∘ `finalize` on the wrapped
5592+
// Hasher; the empty arena signals the catamorphism to evaluate via
5593+
// `dispatch_kernel` rather than recursively folding a body.
5594+
f.line("impl<H: Hasher> crate::pipeline::__sdk_seal::Sealed for HashAxis<H> {}");
5595+
f.line("impl<H: Hasher> crate::pipeline::SubstrateTermBody for HashAxis<H> {");
5596+
f.line(" fn body_arena() -> &'static [Term] {");
5597+
f.line(" &[]");
5598+
f.line(" }");
5599+
f.line("}");
55905600
f.line("impl<H: Hasher> crate::pipeline::AxisExtension for HashAxis<H> {");
55915601
f.line(" const AXIS_ADDRESS: &'static str = \"https://uor.foundation/axis/HashAxis\";");
55925602
f.line(" const MAX_OUTPUT_BYTES: usize = <H as Hasher>::OUTPUT_BYTES;");

codegen/src/pipeline.rs

Lines changed: 98 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2209,17 +2209,47 @@ fn emit_axis_extension(f: &mut RustFile) {
22092209
));
22102210
f.blank();
22112211
}
2212+
// ADR-055: Universal substrate-Term verb body discipline. Every
2213+
// `AxisExtension` impl carries a `SubstrateTermBody::body_arena()`
2214+
// describing the kernel's substrate-Term decomposition so the
2215+
// catamorphism's structural reach extends through the axis surface
2216+
// to the leaf level. The trait is sealed via `__sdk_seal::Sealed`;
2217+
// emission is via the `axis!` SDK macro per ADR-030 + ADR-052.
2218+
f.doc_comment("ADR-055: substrate-Term verb body discipline. Every axis impl carries a");
2219+
f.doc_comment("substrate-Term decomposition the catamorphism can fuse through. Sealed —");
2220+
f.doc_comment("the `axis!` SDK macro per ADR-030 + ADR-052 emits the impl.");
2221+
f.doc_comment("");
2222+
f.doc_comment("The catamorphism's `Term::AxisInvocation` fold-rule reads this slice and");
2223+
f.doc_comment("recursively folds the body with the evaluated kernel inputs in scope (per");
2224+
f.doc_comment("ADR-029, amended by ADR-055). The `body_arena()` slice is a static const,");
2225+
f.doc_comment("not wire-format state, so the on-wire shape of `Term::AxisInvocation` is");
2226+
f.doc_comment("preserved.");
2227+
f.line("pub trait SubstrateTermBody: __sdk_seal::Sealed {");
2228+
f.indented_doc_comment("The Term arena the kernel decomposes to. Empty slice signals a");
2229+
f.indented_doc_comment("primitive-fast-path axis whose body the implementation may evaluate");
2230+
f.indented_doc_comment("through `dispatch_kernel` directly per ADR-055's optional fast-path.");
2231+
f.line(" fn body_arena() -> &'static [crate::enforcement::Term];");
2232+
f.line("}");
2233+
f.blank();
2234+
22122235
f.doc_comment("ADR-030: a substrate-extension axis. Each `axis!`-declared");
22132236
f.doc_comment("trait extends this trait via the SDK macro's blanket impl,");
22142237
f.doc_comment("which emits per-method `KERNEL_*` const ids and the");
22152238
f.doc_comment("`dispatch_kernel` router into a fixed-capacity byte buffer.");
22162239
f.doc_comment("");
2240+
f.doc_comment("Per ADR-055, every `AxisExtension` impl also satisfies the");
2241+
f.doc_comment("[`SubstrateTermBody`] supertrait — its kernel decomposes to a");
2242+
f.doc_comment("substrate-Term slice the catamorphism walks structurally. This makes");
2243+
f.doc_comment("the Fold-Fusion Principle (ADR-054) universal across every axis impl,");
2244+
f.doc_comment("not just the standard library's canonical impls.");
2245+
f.doc_comment("");
22172246
f.doc_comment("The catamorphism's `Term::AxisInvocation` fold-rule reads the");
2218-
f.doc_comment("axis position from the application's `AxisTuple` impl and");
2219-
f.doc_comment("calls `dispatch_kernel` with the kernel id and the evaluated");
2220-
f.doc_comment("input bytes; the returned `TermValue` is the axis's");
2221-
f.doc_comment("contribution to the route's evaluation.");
2222-
f.line("pub trait AxisExtension {");
2247+
f.doc_comment("axis position from the application's `AxisTuple` impl, walks the");
2248+
f.doc_comment("axis's `SubstrateTermBody::body_arena()` recursively with the");
2249+
f.doc_comment("evaluated kernel input bound in scope, and emits the resulting");
2250+
f.doc_comment("`TermValue`. The legacy `dispatch_kernel` fast-path remains as an");
2251+
f.doc_comment("optimization for axes whose body is empty (primitive fast-path).");
2252+
f.line("pub trait AxisExtension: SubstrateTermBody {");
22232253
f.indented_doc_comment("ADR-017 content address of this axis trait. The SDK macro");
22242254
f.indented_doc_comment("derives this from the trait name and method signatures.");
22252255
f.line(" const AXIS_ADDRESS: &'static str;");
@@ -2266,6 +2296,13 @@ fn emit_axis_extension(f: &mut RustFile) {
22662296
f.line(" input: &[u8],");
22672297
f.line(" out: &mut [u8],");
22682298
f.line(" ) -> Result<usize, crate::enforcement::ShapeViolation>;");
2299+
f.indented_doc_comment("ADR-055: return the substrate-Term body arena for the axis at");
2300+
f.indented_doc_comment("`axis_index`. An empty slice means the axis is a primitive-fast-path");
2301+
f.indented_doc_comment("axis whose body is byte-output-equivalent to its `dispatch_kernel`.");
2302+
f.indented_doc_comment("Non-empty slices carry the recursive-fold decomposition the");
2303+
f.indented_doc_comment("catamorphism walks per ADR-055's amended `Term::AxisInvocation`");
2304+
f.indented_doc_comment("fold-rule.");
2305+
f.line(" fn body_arena_at(axis_index: u32) -> &'static [crate::enforcement::Term];");
22692306
f.line("}");
22702307
f.blank();
22712308
// ADR-030 blanket: every `Hasher` is an `AxisTuple` of arity 1
@@ -2309,6 +2346,13 @@ fn emit_axis_extension(f: &mut RustFile) {
23092346
f.line(" }");
23102347
f.line(" Ok(n)");
23112348
f.line(" }");
2349+
f.line(" // ADR-055: the Hasher blanket axis is a primitive-fast-path axis. Its");
2350+
f.line(" // body is byte-output-equivalent to `fold_bytes` ∘ `finalize`; the");
2351+
f.line(" // empty arena signals to the catamorphism that dispatch_kernel is the");
2352+
f.line(" // canonical evaluation strategy here.");
2353+
f.line(" fn body_arena_at(_axis_index: u32) -> &'static [crate::enforcement::Term] {");
2354+
f.line(" &[]");
2355+
f.line(" }");
23122356
f.line("}");
23132357
f.blank();
23142358
// 1-tuple impl — the most common case (a single hash axis).
@@ -2335,6 +2379,12 @@ fn emit_axis_extension(f: &mut RustFile) {
23352379
f.line(" }),");
23362380
f.line(" }");
23372381
f.line(" }");
2382+
f.line(" fn body_arena_at(axis_index: u32) -> &'static [crate::enforcement::Term] {");
2383+
f.line(" match axis_index {");
2384+
f.line(" 0 => <A0 as SubstrateTermBody>::body_arena(),");
2385+
f.line(" _ => &[],");
2386+
f.line(" }");
2387+
f.line(" }");
23382388
f.line("}");
23392389
f.blank();
23402390
// 2-tuple impl
@@ -2366,6 +2416,13 @@ fn emit_axis_extension(f: &mut RustFile) {
23662416
f.line(" }),");
23672417
f.line(" }");
23682418
f.line(" }");
2419+
f.line(" fn body_arena_at(axis_index: u32) -> &'static [crate::enforcement::Term] {");
2420+
f.line(" match axis_index {");
2421+
f.line(" 0 => <A0 as SubstrateTermBody>::body_arena(),");
2422+
f.line(" 1 => <A1 as SubstrateTermBody>::body_arena(),");
2423+
f.line(" _ => &[],");
2424+
f.line(" }");
2425+
f.line(" }");
23692426
f.line("}");
23702427
f.blank();
23712428
// ADR-030 tuple impls 3..=MAX_AXIS_TUPLE_ARITY (8).
@@ -2420,6 +2477,16 @@ fn emit_axis_extension(f: &mut RustFile) {
24202477
f.line(" }),");
24212478
f.line(" }");
24222479
f.line(" }");
2480+
f.line(" fn body_arena_at(axis_index: u32) -> &'static [crate::enforcement::Term] {");
2481+
f.line(" match axis_index {");
2482+
for i in 0..arity {
2483+
f.line(&format!(
2484+
" {i} => <A{i} as SubstrateTermBody>::body_arena(),"
2485+
));
2486+
}
2487+
f.line(" _ => &[],");
2488+
f.line(" }");
2489+
f.line(" }");
24232490
f.line("}");
24242491
f.blank();
24252492
}
@@ -4902,27 +4969,36 @@ fn emit_prism_model(f: &mut RustFile) {
49024969
f.line(" }");
49034970
f.line(" }");
49044971
f.line(" crate::enforcement::Term::AxisInvocation { axis_index, kernel_id, input_index } => {");
4905-
f.line(" // ADR-030: dispatch to the application's selected axis at");
4906-
f.line(" // `axis_index`, with `kernel_id` selecting the per-axis kernel.");
4907-
f.line(" // The catamorphism evaluates the input subtree to bytes and");
4908-
f.line(" // hands them to the AxisTuple's dispatch router; the dispatcher");
4909-
f.line(" // writes the kernel's output into a stack-resident buffer.");
4972+
f.line(" // ADR-055: read the axis's SubstrateTermBody::body_arena() via");
4973+
f.line(" // `AxisTuple::body_arena_at`. When non-empty, recursively fold");
4974+
f.line(" // the body with the evaluated kernel input bound as input_bytes;");
4975+
f.line(" // when empty (primitive-fast-path interpretation), dispatch the");
4976+
f.line(" // kernel function directly per the optional fast-path per ADR-055.");
49104977
f.line(" //");
49114978
f.line(" // The foundation-built blanket `impl<H: Hasher> AxisTuple for H`");
4912-
f.line(" // routes the canonical hash dispatch (axis 0, kernel 0)");
4913-
f.line(" // through the legacy Hasher API; user-declared axes via the");
4979+
f.line(" // routes the canonical hash dispatch (axis 0, kernel 0) through");
4980+
f.line(" // the legacy Hasher API (empty body); user-declared axes via the");
49144981
f.line(" // `axis!` SDK macro extend the dispatch surface to additional");
4915-
f.line(" // (axis_index, kernel_id) combinations.");
4982+
f.line(" // (axis_index, kernel_id) combinations and may provide substrate-");
4983+
f.line(" // Term bodies the catamorphism walks structurally.");
49164984
f.line(" let v = evaluate_term_at::<A, R>(arena, input_index as usize, input_bytes, recurse_value, recurse_idx_value, unfold_value, first_admit_idx_value, resolvers)?;");
4917-
f.line(" let mut out = [0u8; AXIS_OUTPUT_BYTES_CEILING];");
4918-
f.line(" let written = match <A as crate::pipeline::AxisTuple>::dispatch(axis_index, kernel_id, v.bytes(), &mut out) {");
4919-
f.line(" Ok(n) => n,");
4920-
f.line(
4921-
" Err(report) => return Err(PipelineFailure::ShapeViolation { report }),",
4922-
);
4923-
f.line(" };");
4924-
f.line(" let width = if written > TERM_VALUE_MAX_BYTES { TERM_VALUE_MAX_BYTES } else { written };");
4925-
f.line(" Ok(TermValue::from_slice(&out[..width]))");
4985+
f.line(" let body = <A as crate::pipeline::AxisTuple>::body_arena_at(axis_index);");
4986+
f.line(" if body.is_empty() {");
4987+
f.line(" // Primitive fast-path: dispatch the kernel function directly.");
4988+
f.line(" let mut out = [0u8; AXIS_OUTPUT_BYTES_CEILING];");
4989+
f.line(" let written = match <A as crate::pipeline::AxisTuple>::dispatch(axis_index, kernel_id, v.bytes(), &mut out) {");
4990+
f.line(" Ok(n) => n,");
4991+
f.line(" Err(report) => return Err(PipelineFailure::ShapeViolation { report }),");
4992+
f.line(" };");
4993+
f.line(" let width = if written > TERM_VALUE_MAX_BYTES { TERM_VALUE_MAX_BYTES } else { written };");
4994+
f.line(" Ok(TermValue::from_slice(&out[..width]))");
4995+
f.line(" } else {");
4996+
f.line(" // ADR-055 recursive-fold path: walk the axis's substrate-Term");
4997+
f.line(" // body with the evaluated kernel input bound in scope. The");
4998+
f.line(" // body's root term is by convention the last entry in the arena.");
4999+
f.line(" let root = body.len() - 1;");
5000+
f.line(" evaluate_term_at::<A, R>(body, root, v.bytes(), recurse_value, recurse_idx_value, unfold_value, first_admit_idx_value, resolvers)");
5001+
f.line(" }");
49265002
f.line(" }");
49275003
f.line(" crate::enforcement::Term::ProjectField { source_index, byte_offset, byte_length } => {");
49285004
f.line(" // ADR-033 G20: evaluate source, slice [byte_offset .. byte_offset+byte_length].");

codegen/src/sdk_macros.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4854,6 +4854,19 @@ pub fn axis(input: TokenStream) -> TokenStream {
48544854
macro_rules! #companion_macro_ident {
48554855
// Non-generic form: simple struct ident.
48564856
($struct_ident:ident) => {
4857+
// ADR-055: SubstrateTermBody supertrait — the default empty
4858+
// body_arena signals the primitive-fast-path interpretation
4859+
// (the kernel-function dispatch path is the byte-output-
4860+
// equivalent realization). Application authors who want
4861+
// recursive fold-fusion through their axis kernel provide
4862+
// an explicit `body` clause via the `axis!` macro's
4863+
// forthcoming body-clause grammar.
4864+
impl ::uor_foundation::pipeline::__sdk_seal::Sealed for $struct_ident {}
4865+
impl ::uor_foundation::pipeline::SubstrateTermBody for $struct_ident {
4866+
fn body_arena() -> &'static [::uor_foundation::enforcement::Term] {
4867+
&[]
4868+
}
4869+
}
48574870
impl ::uor_foundation::pipeline::AxisExtension for $struct_ident {
48584871
const AXIS_ADDRESS: &'static str =
48594872
<$struct_ident as #trait_name>::AXIS_ADDRESS;
@@ -4899,6 +4912,16 @@ pub fn axis(input: TokenStream) -> TokenStream {
48994912
// it would appear after `impl<...>`. The optional `where [...]`
49004913
// group carries any predicates.
49014914
(@generic $struct_ty:ty, [$($generic_params:tt)*] $(, where [$($where_clauses:tt)*])?) => {
4915+
impl<$($generic_params)*> ::uor_foundation::pipeline::__sdk_seal::Sealed for $struct_ty
4916+
$(where $($where_clauses)*)?
4917+
{}
4918+
impl<$($generic_params)*> ::uor_foundation::pipeline::SubstrateTermBody for $struct_ty
4919+
$(where $($where_clauses)*)?
4920+
{
4921+
fn body_arena() -> &'static [::uor_foundation::enforcement::Term] {
4922+
&[]
4923+
}
4924+
}
49024925
impl<$($generic_params)*> ::uor_foundation::pipeline::AxisExtension for $struct_ty
49034926
$(where $($where_clauses)*)?
49044927
{

conformance/endpoint_coverage.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,18 @@ behavior = "behavior_catamorphism_evaluator"
526526
symbols = ["AxisExtension", "AxisTuple", "HashAxis", "MAX_AXIS_TUPLE_ARITY", "AXIS_OUTPUT_BYTES_CEILING"]
527527
behavior = "behavior_catamorphism_evaluator"
528528

529+
# ─── Mapping: ADR-055 universal substrate-Term verb body discipline ────
530+
# ADR-055 commits `SubstrateTermBody` as a supertrait on `AxisExtension`.
531+
# Every axis impl carries a substrate-Term decomposition (or the empty-
532+
# arena primitive-fast-path) the catamorphism walks structurally per the
533+
# amended `Term::AxisInvocation` fold-rule. The behavior file pins:
534+
# the public trait path, the AxisExtension supertrait bound, HashAxis's
535+
# primitive-fast-path body_arena, AxisTuple's body_arena_at dispatcher,
536+
# and the catamorphism's primitive-fast-path branch.
537+
[coverage.adr055_substrate_term_body]
538+
symbols = ["SubstrateTermBody"]
539+
behavior = "behavior_adr_055_substrate_term_body"
540+
529541
# ─── Mapping: ADR-033 G20 field-access projection ──────────────────────
530542
# ADR-033 admits closure-body field access on `partition_product`-shaped
531543
# inputs (`<expr>.<index>` and `<expr>.<field_name>`). Lowered to

0 commit comments

Comments
 (0)