Skip to content

11 Technical Risks

Alex Flom edited this page May 12, 2026 · 9 revisions

Risks and Technical Debts

This chapter enumerates known risks and technical debts in Prism’s architecture and in conforming implementations. An entry listed here is a structural property an implementation must navigate; it is not implementation discretion. Every entry has a name, a description, an architectural impact, and a mitigation posture.

The risks are not exhaustive. Operational risks (build pipeline failures, CI infrastructure outages, dependency supply-chain compromise) are out of scope; this chapter covers risks intrinsic to Prism’s architecture as specified in this wiki.

TR-01 — generic_const_exprs incompleteness on Rust nightly

Description: uor-foundation’s internal proc-macro sub-crate enables `#![feature(generic_const_exprs)] because the shape proc-macros (product_shape!, coproduct_shape!, cartesian_product_shape!) emit ConstrainedTypeShape impls that reference <H as HostBounds>::CAP at array-size positions. The feature is incomplete in Rust nightly and has been so for an extended period; specific complex const-expression patterns can fail to type-check or fail to monomorphize even when the pattern is logically well-formed. The macros are reached by application-author code through `prism’s vocabulary re-exports of `uor-foundation’s internal proc-macro sub-crate.

Architectural impact: An application author who writes a ConstrainedTypeShape declaration that exercises a const-expression pattern the compiler cannot yet resolve will see a compilation error that does not point at a UORassembly violation but at a toolchain limitation. This blurs the diagnostic distinction QS-04 specifies: the author cannot tell from the error whether the contract is violated or the toolchain is incomplete.

Mitigation posture: The shape proc-macros emit const-expression patterns the toolchain handles correctly today. The set of expressible shapes is bounded by what the toolchain supports; new shape variants prism might want to expose are gated both by uor-foundation’s proc-macro emissions (where the feature opt-in lives) and by toolchain progress. The wiki’s UORassembly contract (section 8) is specified independently of the toolchain’s current feature implementation; if a future Rust release stabilizes `generic_const_exprs or removes its incompleteness, the contract does not change. The risk is therefore one of time-bounded toolchain quality, not of architectural drift.

TR-02 — Gauge-level fixity at compile time

Description: Prism’s substitution axes are fixed at the application author’s compile time. An author cannot change the HostTypes, HostBounds, or AxisTuple selection (per ADR-030; the third position originally Hasher per ADR-007, generalized to a tuple of axis trait selections) at runtime; nor can they support multiple selections in a single executable without pre-compiling each variant separately. This is a direct consequence of constraint TC-01 (zero-cost runtime) and ADR-007 (substitution-axis allocation).

Architectural impact: An application that needs to operate against multiple substitution-axis configurations — for example, a service that accepts inputs hashed under SHA-256 from some clients and BLAKE3 from others — cannot be a single Prism executable. It must be either multiple executables or a non-Prism executable that delegates to Prism executables per-input.

Mitigation posture: This is a deliberate architectural property, not a defect. The mitigation, if an application’s domain requires multi-substrate operation, is at the application architecture level: compose multiple Prism executables, or use a meta-tool that selects among them. The wiki does not specify the meta-tool.

TR-03 — Host-platform feature-flag combinatorics

Description: All three crates — uor-foundation, prism, and prism-verify — expose feature flags alloc, std, serde, observability (section 8). prism’s flags are consistent with `uor-foundation’s (additive across the dependency graph). The author selects feature flags as part of their crate’s `Cargo.toml. Feature combinations now interact across two crate boundaries: a crate that depends on prism with alloc enabled and on a third-party crate that depends on uor-foundation (or another prism consumer) with std enabled triggers Cargo’s feature-unification, producing a build with both features active in both prism and uor-foundation. The unified build’s behavior is normatively specified — feature flags are additive (section 8) — but the compile-time and binary-size consequences vary across combinations and span both crates.

Architectural impact: An author whose crate composes with other crates that depend on prism or uor-foundation may not have full control over which features are active at build time. The author’s intended #![no_std] posture, for example, may be silently overridden if any transitive dependency enables std on either crate. The combinatoric space is now the cross-product of features at the uor-foundation and prism boundaries; with prism-verify participating in some dependency graphs, the cross-product extends to three boundaries.

Mitigation posture: Authors who require a specific feature posture can pin their dependency on prism (and indirectly uor-foundation) to a build profile that disables default features and explicitly enables only the flags they require (default-features = false plus an explicit features = […​] list, in Cargo.toml, for each Prism crate the author depends on). Cargo’s feature-unification still operates, so a transitive enabler of std will still cause std to be enabled, but this is detectable through cargo tree and similar inspection. The wiki specifies the additive property of features; the application author’s responsibility is to manage the feature graph across all three Prism crates for their crate’s intended deployment.

TR-04 — Distribution channel selection is out of architectural scope

Description: Prism is silent on the distribution channel by which the executable reaches the user (ADR-004). This is an architectural property — Prism’s specification does not constrain the channel — but it is also a risk: the absence of a normative channel means the channel’s properties (integrity, authenticity, availability) are application-author concerns that the architecture does not address.

Architectural impact: A compromised distribution channel can deliver a different executable than the author published. The user’s verification of an output (Scenario 2) confirms the output’s claims given the trace; it does not confirm the executable that produced the trace was the one the author intended. These are orthogonal properties; Scenario 4 in section 6 calls this out explicitly.

Mitigation posture: Application authors who require executable-authenticity guarantees adopt a supply-chain integrity mechanism out-of-band: code signing, content-addressable distribution (e.g., signed Git tags, content-addressed package registries, blockchain-anchored references), reproducible builds, attested build pipelines. Prism does not specify any of these; the application author selects whichever mechanism their threat model requires. The wiki acknowledges the orthogonality; conforming implementations are not responsible for solving it.

TR-05 — Hasher selection mismatch produces verification failure indistinguishable from data corruption

Description: The verifier rejects a trace with ReplayError::HasherMismatch if the supplied hasher_instance’s identifier does not match the trace’s `hasher_identifier. The user, faced with this error, cannot distinguish from the error alone whether: (a) they have the wrong Hasher instantiated; (b) the trace was produced with a Hasher whose identifier conflicts with another Hasher in the wider ecosystem; (c) the trace was tampered with to alter the identifier.

Architectural impact: Diagnosing why a verification fails is harder for the user than diagnosing why an execution fails. The execution’s error model (section 8) returns typed impossibility witnesses with positional information; the verification’s error model returns identifier-level errors that do not localize within the trace.

Mitigation posture: The application author’s documentation should communicate the Hasher identifier alongside any distributed traces, in a form that is not itself the trace (e.g., human-readable text the user can confirm matches what their verification environment expects). Prism’s wiki does not specify this communication format; it is the application author’s responsibility. The risk is that an author who skips this step leaves users unable to diagnose verification failures.

TR-06 — Trace format evolution requires version coordination

Description: The trace wire format carries a format_version field equal to TRACE_REPLAY_FORMAT_VERSION. The verifier rejects traces whose format version it does not recognize (section 8). If the wiki’s normative specification of the trace format is revised in a future revision of this wiki, the resulting format will have a different version number; existing traces will not be verifiable by the new verifier and new traces will not be verifiable by old verifiers. The format is defined in uor-foundation (data definitions in bridge); produced by prism’s pipeline; consumed by `prism-verify (façade) re-exporting prism’s replay implementation. Format-version evolution therefore spans two repositories (`UOR-Foundation/UOR-Framework for uor-foundation; UOR-Foundation/Prism for prism and prism-verify).

Architectural impact: Verification is bound to the trace format version, not just the trace content. Long-lived traces — produced today, verified years from now — require the verifier in use at verification time to recognize the format version the trace was produced with. Cross-repo coordination is now required for any format-version bump: uor-foundation is amended first (the data definitions change), then prism is repinned against the new uor-foundation and amended for the new producer/consumer paths, then prism-verify follows automatically as a thin façade.

Mitigation posture: The wire format’s length-prefix-on-events discipline (section 8) provides forward compatibility for trace producers: a future format version may add new event payload data without breaking old verifiers, provided the variant discriminants remain unchanged. Larger format changes — adding new variant discriminants, changing the top-level structure — are normative architectural changes that require their own ADR and a version bump. Conforming implementations preserve old format versions' verification capability when bumping the format version, so that traces produced under any historic format remain verifiable. The cross-repo coordination is sequenced: uor-foundation release → prism repin and release → prism-verify follows; any out-of-order release produces a prism build failure (the foundation-side UORassembly check, per ADR-006) and is detected at compile time.

TR-07 — Compile-time content-address stability across toolchain versions

Description: The ContentFingerprint carried by a Certified<GroundingCertificate> is computed by the author’s selected Hasher over data produced by pipeline::run. The data the hasher receives includes the trace’s TraceEvent sequence; the byte representation of the events is stable per the wire format specification (section 8). However, if a future Rust toolchain version changes the layout of intermediate values that contribute to a TraceEvent payload — for instance, if a new toolchain version’s Rust ABI changes the layout of an enum the foundation uses internally — the resulting trace bytes could differ for the same input.

Architectural impact: Bit-identical reproducibility of Grounded<T> and Trace values across toolchain versions is not normatively guaranteed by the wiki. Two builds of the same author’s crate against different toolchain versions could produce traces that verify against the same Hasher but produce different ContentFingerprint values, because the input bytes the Hasher saw were different.

Mitigation posture: The wire format specification (section 8) defines the byte layout of TraceEvent payloads independently of any toolchain version. Conforming implementations of uor-foundation SHOULD produce identical trace bytes across toolchain versions for identical inputs; this is not a normative MUST because it depends on toolchain ABI stability the foundation does not control. Application authors who require cross-toolchain reproducibility pin a specific toolchain version in their crate’s rust-toolchain.toml.

TR-08 — Vocabulary insufficiency in uor-foundation forces cross-repo amendment cadence

Description: prism is closed under uor-foundation’s vocabulary (ADR-013): every type `prism exposes is derived from uor-foundation’s type-declaration vocabulary; every operation declaration composes `uor-foundation::PrimitiveOp discriminants. Consequently, a prism amendment that needs a new primitive type (a domain shape uor-foundation’s vocabulary cannot express) or a new `PrimitiveOp discriminant cannot proceed without first amending uor-foundation. The two crates live in separate repositories (UOR-Foundation/UOR-Framework for uor-foundation; UOR-Foundation/Prism for prism and prism-verify) per ADR-015.

Architectural impact: When prism needs to expand its surface in a way that requires substrate amendment, the amendment cadence is sequenced: identify the substrate gap; amend uor-foundation; release uor-foundation; repin prism; amend prism; release prism. Each step is a cross-repo coordination point. The window between the uor-foundation release and the prism release is one in which prism consumers see a uor-foundation whose vocabulary is wider than what prism exposes; this is benign (the wider vocabulary is just unused in `prism’s surface) but is observable.

Mitigation posture: The substrate is designed to be wide enough that most prism amendments are additive — they expose a new combination of existing uor-foundation vocabulary, not a new primitive. The closure property in ADR-013 is the architectural commitment that makes this possible; if prism ever needs to introduce a primitive of its own, that is the signal that the substrate’s vocabulary is insufficient and the proper amendment site is uor-foundation. Substrate stability is the architectural goal; cross-repo coordination is the cost of the substrate being a separate, slower-moving layer.

TR-09 — prism version pin lag against uor-foundation

Description: Per the cross-repo split (ADR-015), prism pins a specific uor-foundation version in its Cargo.toml. When uor-foundation releases a new version (e.g., to add a new primitive per TR-08, or to fix a substrate-level bug), prism does not automatically pick up the new version — the prism repository must be amended to repin against the new release. There may be a window in which the latest uor-foundation release contains an amendment that prism does not yet pick up.

Architectural impact: Application authors who require a uor-foundation amendment that has been released but not yet picked up by prism must wait for the prism repin. Cargo’s feature-unification will not silently bridge this gap: the author depends on prism, which pins uor-foundation@X.Y.Z; a new uor-foundation@X.Y.Z+1 is unreachable from the author’s crate until prism repins. The author cannot work around this by declaring their own uor-foundation@X.Y.Z+1 dependency, because Cargo will reject the version conflict.

Mitigation posture: prism’s release cadence is expected to follow `uor-foundation’s closely for substrate-driven amendments (per TR-08). For substrate-level bug fixes, the `prism repository’s CI should track uor-foundation releases and produce a repin PR within a short window. The architectural commitment is that prism follows uor-foundation; the operational discipline is that the follow happens promptly. A prism release that lags uor-foundation by a substantial period is an operational concern, not an architectural one, but it surfaces as the practical bottleneck for application authors awaiting substrate amendments.

TR-10 — Foundation-fixed buffer ceilings reject oversized application inputs and outputs at runtime

Description: pipeline::run_route allocates fixed-size stack buffers of capacity <B as HostBounds>::ROUTE_INPUT_BUFFER_BYTES (per ADR-023 reconciliation, migrated to HostBounds per ADR-037) and <B as HostBounds>::ROUTE_OUTPUT_BUFFER_BYTES (per ADR-028, migrated to HostBounds per ADR-037) to serialize the input value (via IntoBindingValue::into_binding_bytes) and to accumulate the catamorphism’s output bytes (per ADR-029). Inputs whose <M::Input as IntoBindingValue>::MAX_BYTES exceeds the input ceiling, or models whose <M::Output as IntoBindingValue>::MAX_BYTES exceeds the output ceiling, are rejected at runtime with a PipelineFailure::ShapeViolation. The ceilings are HostBounds associated constants per ADR-037; applications declare their own HostBounds impl to raise the ceilings for their typed feature hierarchies, or select DefaultHostBounds which carries foundation’s prior fixed values as defaults.

Architectural impact: An application whose domain requires inputs or outputs larger than the foundation-fixed ceilings cannot run on the current foundation release; the author must either restructure the application’s input/output shapes to fit under the ceilings (e.g., compose multiple smaller models that pipeline through partition_product shapes) or wait for a foundation parameter change to raise the ceiling. The runtime-rejection point is the buffer-size check inside run_route, which is later than the type-system would catch the analogous violation under generic_const_exprs; the failure is structurally identical (buffer too small) but is relocated from compile-time to runtime under the stable-Rust form (per ADR-023 Rejected alternative 4).

Mitigation posture: The architectural commitment is to the foundation-fixed ceiling form, not to specific numeric values. Foundation chooses ceiling values that accommodate typical applications (typical input/output shapes well under the ceiling); applications with outsized shapes flag this as a foundation parameter request rather than a workaround. The structural commitment that the ceilings exist is deliberate: it bounds the executable’s stack usage at compile time, which is required by the #![no_std] posture and the zero-cost runtime constraint. The risk surfaces as a one-time application-foundation coordination cost when an application’s shapes exceed the ceiling.

TR-11 — Verb-graph cycle detection at compile time may produce confusing error spans for complex implementations

Description: ADR-024’s verb-closure check requires the verb-reference graph through non-recurse operators to be acyclic. The verb! macro performs a per-verb local check at each invocation site; the prism_model! macro performs a final whole-implementation cycle check over the verb-reference graph reachable from the model’s route. A cycle is a closure violation surfaced as a Rust compile error.

Architectural impact: Implementations with many cross-referencing verbs and verb imports (via use_verbs!) produce a verb-reference graph whose cycle structure may be non-obvious from any single verb! invocation. The macro’s compile error must point at a span the author can act on: ideally the verb that closes the cycle, but in practice the error may point at the model declaration (where the whole-implementation check runs) rather than the offending verb pair. Authors with complex verb hierarchies may find diagnosing cycles harder than diagnosing closure-under-foundation-vocabulary errors (which always point at the offending function-call span per ADR-022 D3).

Mitigation posture: The macro implementation should produce structured error output: a primary span at the model declaration (where the cycle was detected) plus a sequence of secondary spans at each verb! invocation in the cycle (so the author sees the cycle’s full path). Foundation does not normatively prescribe the diagnostic format; conformant macro implementations include cycle-path information sufficient for the author to break the cycle. Authors writing complex verb hierarchies use the foundation’s cycle-detection diagnostic format as their disambiguator.

TR-12 — prism_model! macro identifier-resolution order has shadowing implications for verbs

Description: ADR-026’s G3-disambiguation rule specifies the macro’s identifier-resolution order: (1) reserved macro-vocabulary identifiers (parallel, fold_n, tree_fold, first_admit, partition_product, partition_coproduct, hash, recurse, unfold, lift, project, nerve, chain_complex, homology_groups, betti, cochain_complex, cohomology_groups, postnikov_tower, homotopy_groups, k_invariants); (2) lowercase PrimitiveOp identifiers from foundation’s canonical PrimitiveOp enum at any committed snapshot per ADR-022 D3 G3a (foundation 0.3.6: add, sub, mul, xor, and, or, neg, bnot, succ, pred, le, lt, ge, gt, concat); (3) axis-trait method identifiers from the model’s declared AxisTuple per ADR-030; (4) own-implementation verbs from verb!-emitted set; (5) imported verbs via use_verbs!. An identifier matching none is a closure violation. Binary-operator forms per ADR-022 D3 G3b (+, -, *, ^, &, |, , <, >=, >) are recognized at expression syntax level before identifier resolution.

Architectural impact: An implementation that declares a verb! named add (the same identifier as the binary PrimitiveOp::Add) cannot use that verb in route bodies because the resolution order resolves the call to PrimitiveOp::Add first. The author must rename the verb to disambiguate. This is a strict ordering: verbs are resolved later than primitives, never earlier. The risk is that authors who name verbs after intuitive English verbs (add, multiply, compare) collide silently with primitive identifiers and find their verbs unreachable from route bodies.

Mitigation posture: The reserved macro-vocabulary identifiers and the PrimitiveOp identifiers are documented (ADR-022 D3 G3 and ADR-026 G12); authors check their verb names against this list before declaring. The macro MAY emit a warning (not an error, since the call is well-typed and the primitive is reachable) when a verb declaration shadows a PrimitiveOp identifier. Authors writing implementations with overlapping vocabulary use namespace-style verb names (my_add, domain_compare, precision_multiply) to avoid shadowing.

TR-13 — Catamorphism evaluation cost may surprise authors of deeply-nested term trees

Description: ADR-029 specifies per-variant fold-rules for the catamorphism. The fold is structurally recursive: each Term::Application evaluates its args first, each Term::Match evaluates the scrutinee then dispatches to an arm body, each Term::Recurse runs descent-measure-bounded iterations of step bodies, etc. The Term tree’s structural depth determines the call-stack depth of the catamorphism’s evaluation; deeply-nested term trees produce deep recursion in the runtime evaluator.

Architectural impact: Authors writing routes with extensive composition (many nested compose calls, deeply nested tree_fold reductions, Term::Recurse with large descent measures producing many recursive applications of the step body) may produce term trees whose evaluation exhausts the stack on resource-constrained targets (thumbv7em-none-eabihf and similar). Foundation’s #![no_std] posture forbids heap allocation in the carrier path, so the catamorphism’s recursion lives on the stack; stack-overflow on small-stack targets is an architectural concern. The behavior is deterministic (same Term tree → same stack depth) but may not match the author’s intuition about where memory pressure arises.

Mitigation posture: The closure-body grammar’s fold_n form (G14) lowers to either an unrolled chain or Term::Recurse based on <B as HostBounds>::FOLD_UNROLL_THRESHOLD per ADR-037 (ADR-026 G14); above the threshold, recursion is iterative-via-recurse rather than directly nested, which keeps the stack depth bounded. Authors targeting small-stack platforms structure their routes around fold_n and Term::Recurse rather than deep direct composition; foundation’s documentation calls out the trade-off. The architectural commitment is to the catamorphism’s correctness, not to a specific stack budget; conformant implementations document their stack profile against representative term trees.

TR-14 — ψ-residuals discipline rejects naturally search-shaped verb bodies at proc-macro expansion

Description: ADR-035’s ψ-residuals discipline forbids Term::FirstAdmit, Term::AxisInvocation, and byte-comparison/concat PrimitiveOp emissions in verb-body terms. The SDK’s verb! and prism_model! macros walk the verb body’s term arena at proc-macro expansion and reject ψ-residual emissions with a closure-violation error citing the ψ-residual category and the verb’s name. Application authors whose domain logic feels naturally search-shaped (e.g., constraint-satisfaction problems whose admission seems to be a predicate over a value-bytes domain) hit the discipline at compile time and must restructure their verb body around the canonical ψ-chain (typically the k-invariants branch per ADR-035) instead.

Architectural impact: The discipline is load-bearing for the framework’s verifiability commitment — admission is a structural property of the value’s k-invariant signature, not a search predicate over byte values. But authors who reach for first_admit over a parametric domain, or for hash(input) as a verb-body call, find themselves rejected at proc-macro expansion. The error message must localize to the offending span (the rejected emission site) and cite the ψ-residual category and the architectural reason; without that, authors face a cryptic "verb body emitted a ψ-residual" failure.

Mitigation posture: The SDK’s proc-macro emits structured error output naming (a) which Term variant or PrimitiveOp emission was rejected, (b) the offending span in the verb body, (c) the ψ-residual category from ADR-035 (FirstAdmit-enumeration / AxisInvocation-dispatch / byte-comparison / byte-concat), (d) the architectural reason (admission must be structural; the canonical k-invariants branch is the maximum-discriminating witness; resolvers consume the canonical hash axis internally rather than verb-body terms), (e) the recommended restructuring path (move axis dispatch into a resolver impl; express admission as a ψ-Term composition over the value’s structural decomposition; if the domain truly is search-shaped, use the substrate’s Term::FirstAdmit outside the verb-body lane in a conformance-corpus generator or foundation-internal trace-replay context). Foundation does not relax the discipline for application convenience; the architectural commitment is calibrated against the verifiability commitment per ADR-001 + ADR-019.

TR-15 — RESOLVER_ABSENT runtime path is the only mechanism reporting resolver-absent at evaluation time

Description: ADR-036’s NullResolverTuple is the default for the R: ResolverTuple substrate parameter on PrismModel. When the model author omits the R parameter at the impl site (or selects NullResolverTuple explicitly) but the verb body emits a resolver-bound ψ-Term variant per ADR-035, the catamorphism dispatches to the corresponding Null<Category>Resolver<H> impl at evaluation time, which returns a ShapeViolation carrying the https://uor.foundation/resolver/RESOLVER_ABSENT shape IRI and the foundation-internal RESOLVER_ABSENT_DISCRIMINATOR byte. The catamorphism propagates the shape-violation through Term::Try per ADR-022 D3 G9 — the verb body’s outer Term::Try handler may recover the resolver-absent case, but absent that handler the propagation reaches pipeline::run_route’s `Result<Grounded<Output>, PipelineFailure> as a PipelineFailure::ShapeViolation.

Architectural impact: Resolver-absent is a runtime failure mode, not a compile-time bound check. Authors who emit a Term::Nerve (or other resolver-bound ψ-Term) in their verb body but forget to declare the corresponding resolver field in their resolver! impl find the model compiles successfully (the resolver! macro’s "emit all eight impls" pattern satisfies run_route’s where-clause unconditionally) but fails at evaluation time on the first per-value invocation. The diagnostic signal at runtime is the `PipelineFailure::ShapeViolation carrying the RESOLVER_ABSENT IRI; from the author’s perspective this is structurally similar to other runtime shape-violations (e.g., capacity-exceeded) but rooted in a missing resolver declaration rather than a value’s runtime properties.

Mitigation posture: The SDK’s verb! and prism_model! macros perform a proc-macro-time analysis paralleling the ψ-residuals walk: each verb body’s emitted ψ-Term variants determine the required resolver categories; if any resolver-bound ψ-Term is emitted and the model declaration’s R is NullResolverTuple (or any concrete ResolverTuple whose declared field set does not cover the required categories), the macro emits a warning (or, optionally per the macro implementation’s configuration, a compile error) naming the missing resolver category and the verb that requires it. The warning is non-blocking by default (since the Null* impls satisfy the type-system contract), but conformant macro implementations include this diagnostic so authors discover resolver-absent at compile time rather than at evaluation time. The architectural commitment is to the runtime RESOLVER_ABSENT mechanism (the foundation-internal byte discriminator + the shape IRI) as the load-bearing path; the proc-macro warning is a diagnostic convenience. Authors building applications under the canonical k-invariants branch declare the four required resolver fields (nerve, postnikov, homotopy_groups, k_invariants) in their resolver! invocation to avoid resolver-absent at evaluation time.

Clone this wiki locally