Skip to content

Commit 0ab775a

Browse files
afflomclaude
andcommitted
v0.3.2: foundation 0.5.2 floor — FP_MAX tower generalization (Hasher<64> fix)
Adopts the upstream bugfix in uor-foundation / uor-foundation-sdk 0.5.2. The bug (foundation 0.5.1): the resolver/pipeline tower was pinned to `Hasher<FP_MAX = 32>` — the `AxisTuple` blanket impl and every ψ-stage resolver trait required `FP_MAX = 32` — so a 64-byte-fingerprint hasher (`Sha512Hasher: Hasher<64>`) could not flow through the pipeline at all. A downstream project on prism 0.3.1 / foundation 0.5.1 was blocked. The fix (0.5.2): the tower is generalized over `FP_MAX`. The `AxisTuple` blanket is `impl<INLINE_BYTES, FP_MAX, H: Hasher<FP_MAX>>`, the resolver traits take an unbounded `H`, and `run` / `run_route` / `Grounded` / `PrismModel` / `certify_from_trace` carry `FP_MAX` as a const parameter. prism adoption: - Tests thread the new `FP_MAX` const parameter through every `run` / `run_route` / `PrismModel` / `Grounded` call site (principal_data_path, scaling, large_input_grounding at FP_MAX = 32; prism_model.rs witnesses made generic over `const FP_MAX`). - NEW `tests/wide_hasher_pipeline.rs`: the regression witness — grounds a unit through `run` with `Sha512Hasher` at `FP_MAX = 64` (under a `HostBounds` declaring `FINGERPRINT_MAX_BYTES = 64`), asserts the certificate fingerprint width is the full 64 bytes, and verifies the QS-05 replay round-trip. This is the case 0.5.1 rejected. - AGENTS.md + Cargo.toml: floor 0.5.1 → 0.5.2 with the FP_MAX generalization documented; workspace 0.3.1 → 0.3.2; path-dep + prism-verify pins → 0.3.2; Cargo.lock refreshed. No library source change beyond version pins — the fix is in foundation; prism's adoption is the test-surface FP_MAX threading + the regression test proving Sha512Hasher flows end-to-end. NOT tagged: lands on main only; no v0.3.2 tag, so the release workflow does not fire. CI green: fmt-check, clippy -D warnings, full test suite (28 binaries, incl. the Sha512 pipeline regression), wiki-link-check (139 backlinks, 0 broken), rustdoc, no_std x6, publish-dry x6. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f6ac74d commit 0ab775a

9 files changed

Lines changed: 170 additions & 52 deletions

File tree

AGENTS.md

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -281,13 +281,24 @@ that contribute the built-in axes and built-in types it re-exports.
281281
`prism`'s pin on `uor-foundation` may lag the latest published
282282
version; updates to this repo are demand-driven (a needed surface
283283
change) rather than calendar-driven.
284-
- **`uor-foundation`**: `^0.5` (effective floor 0.5.1 — completes the
285-
ADR-060 input path: `IntoBindingValue` gains a `'a` lifetime and
286-
replaces the `MAX_BYTES` const + `into_binding_bytes` writer with
284+
- **`uor-foundation`**: `^0.5` (effective floor 0.5.2 — generalizes the
285+
resolver/pipeline tower over the fingerprint width `FP_MAX`: the
286+
`AxisTuple` blanket impl is now
287+
`impl<INLINE_BYTES, FP_MAX, H: Hasher<FP_MAX>>`, the eight ψ-stage
288+
resolver traits take an unbounded `H`, and `run` / `run_route` /
289+
`Grounded` / `PrismModel` / `certify_from_trace` carry `FP_MAX` as a
290+
const parameter (`Hasher<const FP_MAX = 32>`). 0.5.1 had pinned the
291+
whole tower to `Hasher<32>`, so a 64-byte-fingerprint hasher
292+
(`Sha512Hasher: Hasher<64>`) could not flow through the pipeline at
293+
all — fixed in 0.5.2 (regression-tested in
294+
`crates/uor-prism/tests/wide_hasher_pipeline.rs`). Earlier floor 0.5.1
295+
completed the ADR-060 input path: `IntoBindingValue` gains a `'a`
296+
lifetime and replaces the `MAX_BYTES` const + `into_binding_bytes`
297+
writer with
287298
`as_binding_value<INLINE_BYTES>(&self) -> TermValue<'a, INLINE_BYTES>`,
288299
returning the source-polymorphic carrier directly so `run_route`
289300
admits arbitrarily large inputs with no byte-width cap;
290-
`PrismModel` / `Grounded` / `run_route` gain the same `'a`. Earlier
301+
`PrismModel` / `Grounded` / `run_route` gain the `'a`. Earlier
291302
floor 0.5.0 introduced the ADR-060 source-polymorphic value carrier:
292303
`TermValue` becomes the const-generic enum
293304
`TermValue<'a, INLINE_BYTES>` with
@@ -341,14 +352,13 @@ that contribute the built-in axes and built-in types it re-exports.
341352
`PrimitiveOp::{Le, Lt, Ge, Gt, Concat}` per ADR-026;
342353
`Output: IntoBindingValue` per ADR-023 value-flow expansion.
343354
`default-features = false`, `no_std`-clean.
344-
- **`uor-foundation-sdk`**: `^0.5` (effective floor 0.5.1 — tracks
345-
the foundation 0.5.1 release completing the ADR-060 input path; the
355+
- **`uor-foundation-sdk`**: `^0.5` (effective floor 0.5.2 — tracks
356+
the foundation 0.5.2 `FP_MAX` tower generalization; the
346357
`axis!` / `verb!` / `prism_model!` / `partition_product!` /
347358
`register_shape!` macro names and grammar are unchanged, but the
348-
`verb!`-emitted `<verb>_term_arena()` accessors and the model/route
349-
surface are const-generic over the ADR-060 `INLINE_BYTES` carrier
350-
width and the `prism_model!`-emitted impls now carry the
351-
`IntoBindingValue<'a>` lifetime. Earlier floors: 0.4.15 added the optional
359+
`prism_model!`-emitted impls now carry the `FP_MAX` const parameter
360+
alongside `INLINE_BYTES` and the `IntoBindingValue<'a>` lifetime
361+
(0.5.1). Earlier floors: 0.4.15 added the optional
352362
`resolver!` macro `shape_registry: MyRegistry` clause that wires an
353363
application's `ShapeRegistryProvider` marker into the emitted
354364
`ResolverTuple` impl as the `ShapeRegistry` associated type;

Cargo.lock

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

Cargo.toml

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ members = [
1111
]
1212

1313
[workspace.package]
14-
version = "0.3.1"
14+
version = "0.3.2"
1515
edition = "2021"
1616
rust-version = "1.83"
1717
license = "MIT"
@@ -24,25 +24,27 @@ readme = "README.md"
2424
# Per wiki ADR-031, the `prism` standard library and its Layer-3
2525
# sub-crates close under the foundation substrate (`uor-foundation`) and
2626
# its SDK macro lane (`uor-foundation-sdk` per ADR-022 / ADR-030 /
27-
# ADR-036). Both are floored at v0.5 (effective floor 0.5.0 — the
28-
# ADR-060 source-polymorphic value carrier: `TermValue` becomes the
29-
# `Inline`/`Borrowed`/`Stream` carrier enum over a `ChunkSource`, the
30-
# fictional 4096 byte-width caps and the foundation-provided
31-
# `DefaultHostBounds` are removed, and per-stage carrier widths derive
32-
# from app-declared structural-count primitives via the foundation
33-
# `*_carrier_bytes::<B>()` const fns. Wire format byte-identical
34-
# (`TRACE_REPLAY_FORMAT_VERSION` stays 10); MSRV stays 1.83).
27+
# ADR-036). Both are floored at v0.5 (effective floor 0.5.2 — the
28+
# resolver/pipeline tower is generalized over the fingerprint width
29+
# `FP_MAX` so a 64-byte hasher (`Sha512Hasher: Hasher<64>`) flows
30+
# through the pipeline; 0.5.1 had pinned it to `Hasher<32>`. Builds on
31+
# the 0.5.0/0.5.1 ADR-060 source-polymorphic value carrier: `TermValue`
32+
# is the `Inline`/`Borrowed`/`Stream` enum over a `ChunkSource`, the
33+
# fictional 4096 byte-width caps and `DefaultHostBounds` are removed,
34+
# and `IntoBindingValue::as_binding_value` returns the carrier directly.
35+
# Wire format byte-identical (`TRACE_REPLAY_FORMAT_VERSION` stays 10);
36+
# MSRV stays 1.83).
3537
uor-foundation = { version = "0.5", default-features = false }
3638
uor-foundation-sdk = { version = "0.5", default-features = false }
3739

3840
# Standard-library sub-crate cross-deps per ADR-031's layered
3941
# consumption pattern (numerics → foundation; crypto → foundation +
4042
# numerics; tensor → foundation + numerics + optionally crypto; fhe →
4143
# foundation). The `prism` façade depends on all four.
42-
uor-prism-numerics = { version = "0.3.1", path = "crates/uor-prism-numerics", default-features = false }
43-
uor-prism-crypto = { version = "0.3.1", path = "crates/uor-prism-crypto", default-features = false }
44-
uor-prism-tensor = { version = "0.3.1", path = "crates/uor-prism-tensor", default-features = false }
45-
uor-prism-fhe = { version = "0.3.1", path = "crates/uor-prism-fhe", default-features = false }
44+
uor-prism-numerics = { version = "0.3.2", path = "crates/uor-prism-numerics", default-features = false }
45+
uor-prism-crypto = { version = "0.3.2", path = "crates/uor-prism-crypto", default-features = false }
46+
uor-prism-tensor = { version = "0.3.2", path = "crates/uor-prism-tensor", default-features = false }
47+
uor-prism-fhe = { version = "0.3.2", path = "crates/uor-prism-fhe", default-features = false }
4648

4749
# Cryptographic substrate dependencies for `prism-crypto`'s canonical
4850
# `HashAxis` impls (ADR-031). These are widely-vetted, no_std-clean

crates/uor-prism-verify/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ keywords = ["prism", "uor", "verify", "replay", "no-std"]
1717
name = "prism_verify"
1818

1919
[dependencies]
20-
uor-prism = { version = "0.3.1", path = "../uor-prism", default-features = false }
20+
uor-prism = { version = "0.3.2", path = "../uor-prism", default-features = false }
2121
uor-foundation = { workspace = true }
2222

2323
[features]

crates/uor-prism/tests/large_input_grounding.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ fn ground_large_input<H: Hasher>(large_input: &[u8]) {
124124
.validate()
125125
.expect("unit well-formed for large input");
126126
let grounded =
127-
run::<ConstrainedTypeInput, _, H, CARRIER>(unit).expect("pipeline admits large input");
127+
run::<ConstrainedTypeInput, _, H, CARRIER, 32>(unit).expect("pipeline admits large input");
128128

129129
// The grounded fingerprint width equals the hasher's output width.
130130
assert_eq!(

crates/uor-prism/tests/principal_data_path.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ fn pipeline_run_then_replay_roundtrip() {
5050

5151
// When: `prism::pipeline::run` consumes the unit with the FNV-1a
5252
// substrate, producing a sealed `Grounded<T>`.
53-
let grounded = run::<ConstrainedTypeInput, _, Fnv16, CARRIER>(unit).expect("pipeline admits");
53+
let grounded =
54+
run::<ConstrainedTypeInput, _, Fnv16, CARRIER, 32>(unit).expect("pipeline admits");
5455

5556
// And: the grounded value's derivation is replayed into a `Trace`
5657
// at the foundation's default `HostBounds` capacity

crates/uor-prism/tests/prism_model.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ const CARRIER: usize = uor_foundation::pipeline::carrier_inline_bytes::<TestHost
5757
// crate fails to compile and the test binary fails to build.
5858

5959
#[allow(dead_code)]
60-
fn _accepts_prism_model<'a, H, M>()
60+
fn _accepts_prism_model<'a, H, M, const FP_MAX: usize>()
6161
where
62-
H: Hasher,
63-
M: PrismModel<'a, DefaultHostTypes, TestHostBounds, H, CARRIER>,
62+
H: Hasher<FP_MAX>,
63+
M: PrismModel<'a, DefaultHostTypes, TestHostBounds, H, CARRIER, FP_MAX>,
6464
{
6565
// `PrismModel`'s fourth generic `R` defaults to `NullResolverTuple`
6666
// per ADR-035/036; the 3-param form below uses that default. Foundation
@@ -70,10 +70,10 @@ where
7070
}
7171

7272
#[allow(dead_code)]
73-
fn _associated_type_bounds<'a, H, M>()
73+
fn _associated_type_bounds<'a, H, M, const FP_MAX: usize>()
7474
where
75-
H: Hasher,
76-
M: PrismModel<'a, DefaultHostTypes, TestHostBounds, H, CARRIER>,
75+
H: Hasher<FP_MAX>,
76+
M: PrismModel<'a, DefaultHostTypes, TestHostBounds, H, CARRIER, FP_MAX>,
7777
// ADR-060: `IntoBindingValue<'a>` now returns a source-polymorphic
7878
// `TermValue` carrier (Inline/Borrowed/Stream) rather than
7979
// serializing into a fixed buffer; the `'a` is the borrowed-input
@@ -85,14 +85,14 @@ where
8585
}
8686

8787
#[allow(dead_code)]
88-
fn _run_route_signature<'a, H, M, R, C>(
88+
fn _run_route_signature<'a, H, M, R, C, const FP_MAX: usize>(
8989
input: M::Input,
9090
resolvers: &R,
9191
commitment: &C,
92-
) -> Result<Grounded<'a, M::Output, CARRIER>, PipelineFailure>
92+
) -> Result<Grounded<'a, M::Output, CARRIER, FP_MAX>, PipelineFailure>
9393
where
94-
H: Hasher + 'a,
95-
M: PrismModel<'a, DefaultHostTypes, TestHostBounds, H, CARRIER, R, C>,
94+
H: Hasher<FP_MAX> + 'a,
95+
M: PrismModel<'a, DefaultHostTypes, TestHostBounds, H, CARRIER, FP_MAX, R, C>,
9696
// ADR-035/036: `R: ResolverTuple` is the substrate parameter for
9797
// the eight categorical-machinery resolvers (Nerve, ChainComplex,
9898
// HomologyGroup, CochainComplex, CohomologyGroup, Postnikov,
@@ -120,7 +120,7 @@ where
120120
// `PrismModel::forward` expands to exactly this call with R / C
121121
// defaulting to `NullResolverTuple` / `EmptyCommitment` when the
122122
// model declares neither resolver use nor a typed commitment.
123-
prism::pipeline::run_route::<DefaultHostTypes, TestHostBounds, H, M, R, C, CARRIER>(
123+
prism::pipeline::run_route::<DefaultHostTypes, TestHostBounds, H, M, R, C, CARRIER, FP_MAX>(
124124
input, resolvers, commitment,
125125
)
126126
}

crates/uor-prism/tests/scaling.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ fn assert_roundtrip<H: Hasher>(witt_ceiling: WittLevel) {
106106
.target_domains(DOMAINS)
107107
.result_type::<ConstrainedTypeInput>();
108108
let unit: Validated<_> = builder.validate().expect("unit well-formed");
109-
let grounded = run::<ConstrainedTypeInput, _, H, CARRIER>(unit).expect("pipeline admits");
109+
let grounded = run::<ConstrainedTypeInput, _, H, CARRIER, 32>(unit).expect("pipeline admits");
110110

111111
// (2) Hasher contract: width recorded on the fingerprint matches the
112112
// hasher's declared `OUTPUT_BYTES`.
@@ -230,9 +230,10 @@ fn fingerprints_at_different_widths_are_distinguishable() {
230230
.expect("unit well-formed")
231231
}
232232

233-
let g16 = run::<ConstrainedTypeInput, _, Fnv16, CARRIER>(fresh_unit()).expect("admits");
234-
let g24 = run::<ConstrainedTypeInput, _, Fnv24, CARRIER>(fresh_unit()).expect("admits");
235-
let g32 = run::<ConstrainedTypeInput, _, Sha256Hasher, CARRIER>(fresh_unit()).expect("admits");
233+
let g16 = run::<ConstrainedTypeInput, _, Fnv16, CARRIER, 32>(fresh_unit()).expect("admits");
234+
let g24 = run::<ConstrainedTypeInput, _, Fnv24, CARRIER, 32>(fresh_unit()).expect("admits");
235+
let g32 =
236+
run::<ConstrainedTypeInput, _, Sha256Hasher, CARRIER, 32>(fresh_unit()).expect("admits");
236237

237238
let f16 = g16.content_fingerprint();
238239
let f24 = g24.content_fingerprint();
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//! Regression: a 64-byte-fingerprint hasher (`Sha512Hasher`, i.e.
2+
//! `Hasher<64>`) must flow through the principal data path.
3+
//!
4+
//! Foundation 0.5.1 pinned the entire resolver/pipeline tower to
5+
//! `Hasher<FP_MAX = 32>` — the `AxisTuple` blanket impl and every ψ-stage
6+
//! resolver trait required `FP_MAX = 32`, so `Sha512Hasher`
7+
//! (`Hasher<64>`) could not be selected as the substrate hasher at all;
8+
//! a downstream project pinned to prism 0.3.1 / foundation 0.5.1 was
9+
//! blocked. Foundation 0.5.2 generalized the tower: the `AxisTuple`
10+
//! blanket is `impl<INLINE_BYTES, FP_MAX, H: Hasher<FP_MAX>>`, the
11+
//! resolver traits take an unbounded `H`, and `run` / `run_route` /
12+
//! `Grounded` / `PrismModel` carry `FP_MAX` as a const parameter.
13+
//!
14+
//! This test grounds a unit through `run` with `Sha512Hasher` at
15+
//! `FP_MAX = 64`, asserts the certificate's fingerprint width is the
16+
//! full 64 bytes, and verifies the QS-05 replay round-trip — the
17+
//! conformance witness that a wide hasher flows end-to-end.
18+
19+
#![allow(clippy::unwrap_used, clippy::expect_used)]
20+
21+
use prism::crypto::Sha512Hasher;
22+
use prism::operation::Term;
23+
use prism::pipeline::run;
24+
use prism::replay::certify_from_trace;
25+
use prism::seal::Validated;
26+
use prism::std_types::ConstrainedTypeInput;
27+
use prism::vocabulary::{CompileUnitBuilder, Hasher, HostBounds, VerificationDomain, WittLevel};
28+
29+
/// An application `HostBounds` whose `FINGERPRINT_MAX_BYTES` is 64,
30+
/// admitting a 64-byte (SHA-512-class) fingerprint. Per ADR-060 the
31+
/// foundation ships no default bounds; this is the test's declaration.
32+
/// Values match the canonical profile except the doubled fingerprint
33+
/// ceiling.
34+
struct Bounds64;
35+
impl HostBounds for Bounds64 {
36+
const FINGERPRINT_MIN_BYTES: usize = 32;
37+
const FINGERPRINT_MAX_BYTES: usize = 64;
38+
const TRACE_MAX_EVENTS: usize = 256;
39+
const WITT_LEVEL_MAX_BITS: u32 = 64;
40+
const FOLD_UNROLL_THRESHOLD: usize = 8;
41+
const BETTI_DIMENSION_MAX: usize = 8;
42+
const NERVE_CONSTRAINTS_MAX: usize = 8;
43+
const NERVE_SITES_MAX: usize = 8;
44+
const JACOBIAN_SITES_MAX: usize = 8;
45+
const RECURSION_TRACE_DEPTH_MAX: usize = 16;
46+
const OP_CHAIN_DEPTH_MAX: usize = 8;
47+
const AFFINE_COEFFS_MAX: usize = 8;
48+
const CONJUNCTION_TERMS_MAX: usize = 8;
49+
const UNFOLD_ITERATIONS_MAX: usize = 256;
50+
}
51+
52+
// The inline carrier width derived from the 64-byte-fingerprint bounds,
53+
// and the matching `FP_MAX`.
54+
const CARRIER: usize = uor_foundation::pipeline::carrier_inline_bytes::<Bounds64>();
55+
const FP_MAX: usize = 64;
56+
57+
const ROOT_TERMS: &[Term<'static, CARRIER>] = &[Term::Literal {
58+
value: prism::operation::TermValue::from_u64_be(7, 1),
59+
level: WittLevel::W8,
60+
}];
61+
static DOMAINS: &[VerificationDomain] = &[VerificationDomain::Enumerative];
62+
63+
#[test]
64+
fn sha512_hasher_flows_through_the_pipeline() {
65+
// `Sha512Hasher: Hasher<64>` — the case foundation 0.5.1 rejected.
66+
let unit: Validated<_> = CompileUnitBuilder::new()
67+
.root_term(ROOT_TERMS)
68+
.witt_level_ceiling(WittLevel::W32)
69+
.thermodynamic_budget(2048)
70+
.target_domains(DOMAINS)
71+
.result_type::<ConstrainedTypeInput>()
72+
.validate()
73+
.expect("unit well-formed");
74+
75+
let grounded = run::<ConstrainedTypeInput, _, Sha512Hasher, CARRIER, FP_MAX>(unit)
76+
.expect("pipeline admits a 64-byte-fingerprint hasher (0.5.2 fix)");
77+
78+
// The full SHA-512 width flows into the certificate fingerprint —
79+
// not truncated to the old FP_MAX = 32 ceiling.
80+
assert_eq!(
81+
usize::from(grounded.content_fingerprint().width_bytes()),
82+
<Sha512Hasher as Hasher<64>>::OUTPUT_BYTES,
83+
"fingerprint width must equal Sha512Hasher::OUTPUT_BYTES (64)",
84+
);
85+
assert_eq!(
86+
usize::from(grounded.content_fingerprint().width_bytes()),
87+
64
88+
);
89+
90+
// QS-05 replay round-trip holds for the wide hasher. `replay`'s
91+
// `TR_MAX` is the trace-buffer bound (the bounds' `TRACE_MAX_EVENTS`);
92+
// its `FP_MAX = 64` flows from the `Derivation`, so
93+
// `certify_from_trace` re-derives a `ContentFingerprint<64>`.
94+
let trace = grounded
95+
.derivation()
96+
.replay::<{ <Bounds64 as HostBounds>::TRACE_MAX_EVENTS }>();
97+
assert!(usize::from(trace.len()) <= <Bounds64 as HostBounds>::TRACE_MAX_EVENTS);
98+
let recertified = certify_from_trace(&trace).expect("trace well-formed");
99+
assert_eq!(
100+
recertified.certificate().content_fingerprint(),
101+
grounded.content_fingerprint(),
102+
"QS-05: re-certified fingerprint must equal source for a 64-byte hasher",
103+
);
104+
}

0 commit comments

Comments
 (0)