uor_addr/outcome.rs
1//! Pipeline-output carrier — the shape every realization's `address()`
2//! entry point returns.
3//!
4//! All realizations produce the same triple: the ASCII κ-label
5//! ([`crate::KappaLabel`]) plus a replayable TC-05 witness. The κ-label
6//! width `N` is the selected σ-axis's [`AddrHash::LABEL_BYTES`](crate::hash::AddrHash::LABEL_BYTES)
7//! (71 sha256/blake3, 73 sha3-256, 74 keccak256, 135 sha512) and the
8//! fingerprint width `FP` is the axis's `FINGERPRINT_MAX_BYTES` (32, or 64
9//! for sha512) — both carried in the type. Under ADR-060 the model's
10//! `forward()` yields a `Grounded<'a, S, NIN, FP>` whose `'a` borrows the
11//! input carrier; [`AddressOutcome`] extracts the **owned** witness data
12//! (the κ-label, the replay [`Trace`], and the σ-projection fingerprint)
13//! from it so the outcome carries no borrow and the input may be dropped.
14
15use prism::replay::{certify_from_trace, Trace};
16use prism::seal::Grounded;
17
18use crate::label::{KappaLabel, LabelDecodeError};
19
20/// Trace event capacity for the address realizations. The κ-derivation's
21/// canonical k-invariants branch emits a short, bounded trace; 256 is the
22/// foundation's default `Trace` width (`HostBounds::TRACE_MAX_EVENTS`).
23pub const ADDRESS_TRACE_EVENTS: usize = 256;
24
25/// **The result of a successful `address()` invocation.** Generic over the
26/// κ-label byte width `N` and the fingerprint byte width `FP` (the selected
27/// σ-axis's `LABEL_BYTES` / `FINGERPRINT_MAX_BYTES`; `FP` defaults to 32).
28#[derive(Debug)]
29pub struct AddressOutcome<const N: usize, const FP: usize = 32> {
30 /// The replayable TC-05 witness (owns its trace + fingerprint).
31 pub witness: AddressWitness<N, FP>,
32 /// The ASCII wire-format κ-label, `<algorithm>:<lowercase-hex>`.
33 pub address: KappaLabel<N>,
34}
35
36impl<const N: usize, const FP: usize> AddressOutcome<N, FP> {
37 /// Extract the owned outcome from a model's `forward()` result. Reads
38 /// the κ-label output bytes, replays the derivation into an owned
39 /// [`Trace`], and snapshots the σ-projection fingerprint — none of
40 /// which borrow the (about-to-be-dropped) input carrier.
41 ///
42 /// `N` must equal the grounded output shape's `SITE_COUNT` (the κ-label
43 /// byte width); the per-axis entry points supply the matching literal.
44 ///
45 /// # Errors
46 ///
47 /// [`LabelDecodeError`] if the grounded output is not a well-formed
48 /// `N`-byte ASCII κ-label (unreachable for the address realizations'
49 /// ψ₉ output; defensive against substrate corruption).
50 pub fn from_grounded<S, const NIN: usize>(
51 grounded: &Grounded<'_, S, NIN, FP>,
52 ) -> Result<Self, LabelDecodeError>
53 where
54 S: prism::std_types::GroundedShape,
55 {
56 let address = KappaLabel::<N>::from_bytes(grounded.output_bytes())?;
57 let trace: Trace<ADDRESS_TRACE_EVENTS, FP> = grounded.derivation().replay();
58 let mut fingerprint = [0u8; FP];
59 let fp = grounded.content_fingerprint();
60 let fp_bytes = fp.as_bytes();
61 let n = fp_bytes.len().min(FP);
62 fingerprint[..n].copy_from_slice(&fp_bytes[..n]);
63 Ok(Self {
64 witness: AddressWitness {
65 address,
66 trace,
67 fingerprint,
68 },
69 address,
70 })
71 }
72}
73
74/// A replayable TC-05 witness. Holds the owned replay [`Trace`], the
75/// σ-projection fingerprint, and the κ-label. [`verify`](Self::verify)
76/// re-certifies the trace through `prism::replay::certify_from_trace`
77/// without re-invoking the σ-axis, and confirms the re-derived
78/// fingerprint equals the source's (QS-05 replay equivalence).
79pub struct AddressWitness<const N: usize, const FP: usize = 32> {
80 address: KappaLabel<N>,
81 trace: Trace<ADDRESS_TRACE_EVENTS, FP>,
82 fingerprint: [u8; FP],
83}
84
85impl<const N: usize, const FP: usize> AddressWitness<N, FP> {
86 /// The κ-label this witness attests.
87 #[must_use]
88 pub fn kappa_label(&self) -> KappaLabel<N> {
89 self.address
90 }
91
92 /// The `FP`-byte σ-projection content fingerprint (32 for the
93 /// `Hasher<32>` axes, 64 for sha512).
94 #[must_use]
95 pub fn content_fingerprint(&self) -> &[u8; FP] {
96 &self.fingerprint
97 }
98
99 /// Replay the derivation through `certify_from_trace` (no σ-axis
100 /// re-invocation) and confirm the re-derived fingerprint matches.
101 /// Returns the attested κ-label on success.
102 ///
103 /// # Errors
104 ///
105 /// [`VerifyError`] if the trace is malformed or the re-derived
106 /// fingerprint diverges from the source (QS-05 violation).
107 pub fn verify(&self) -> Result<KappaLabel<N>, VerifyError> {
108 let certified = certify_from_trace(&self.trace).map_err(|_| VerifyError::ReplayFailed)?;
109 if certified.certificate().content_fingerprint().as_bytes()[..] != self.fingerprint[..] {
110 return Err(VerifyError::FingerprintMismatch);
111 }
112 Ok(self.address)
113 }
114}
115
116impl<const N: usize, const FP: usize> core::fmt::Debug for AddressWitness<N, FP> {
117 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
118 f.debug_struct("AddressWitness")
119 .field("address", &self.address)
120 .finish_non_exhaustive()
121 }
122}
123
124/// TC-05 replay-verification failures from [`AddressWitness::verify`].
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub enum VerifyError {
127 /// `certify_from_trace` rejected the trace (malformed / out-of-order).
128 ReplayFailed,
129 /// The re-derived fingerprint diverged from the source (QS-05).
130 FingerprintMismatch,
131}