Skip to main content

uor_addr/
resolvers.rs

1//! `AddressResolverTuple` — the single eight-resolver tower shared by
2//! every UOR-ADDR realization (ADR-036).
3//!
4//! Under ADR-060 the realization's canonical form flows through the
5//! pipeline as a source-polymorphic [`TermValue`] carrier produced by the
6//! input's `as_binding_value` (see [`crate::common::AddressInput`]). The
7//! ψ-tower is therefore **format-independent**: ψ₁ (nerve) … ψ₈ thread the
8//! carrier through unchanged, and ψ₉ (k-invariants) folds the carrier
9//! through the σ-axis `H` chunk-by-chunk — never materializing it — and
10//! emits the `H::LABEL_BYTES`-wide ASCII κ-label `<algorithm>:<hex>` as an
11//! `Inline` carrier (see [`crate::hash`] for the admissible axes).
12//!
13//! ## Why this is hand-written (not `resolver!`-emitted)
14//!
15//! foundation 0.5.2 generalized the resolver traits to an unbounded `H`
16//! (so a 64-byte `Sha512Hasher` σ-axis composes), but the SDK 0.5.2
17//! `resolver!` macro still emits `H: Hasher` (= `Hasher<32>`) bounds on the
18//! generated tuple impls — which would exclude `Sha512Hasher`. The tower is
19//! therefore written out by hand, bound on the fingerprint-width-erased
20//! [`AddrHash`] façade ([`crate::hash`]) so the single tuple carries every
21//! admissible axis (32- and 64-byte) without a free `FP_MAX` parameter.
22
23use core::marker::PhantomData;
24
25use prism::operation::TermValue;
26use prism::pipeline::{
27    ChainComplexResolver, CochainComplexResolver, CohomologyGroupResolver, HasChainComplexResolver,
28    HasCochainComplexResolver, HasCohomologyGroupResolver, HasHomologyGroupResolver,
29    HasHomotopyGroupResolver, HasKInvariantResolver, HasNerveResolver, HasPostnikovResolver,
30    HomologyGroupResolver, HomotopyGroupResolver, KInvariantResolver, NerveResolver,
31    PostnikovResolver, ResolverCategory, ResolverTuple, ShapeViolation,
32};
33use prism::uor_foundation::pipeline::__sdk_seal::Sealed;
34use prism::uor_foundation::pipeline::shape_iri_registry::EmptyShapeRegistry;
35
36use crate::hash::{AddrHash, MAX_LABEL_BYTES};
37
38const HEX_LOWER: [u8; 16] = *b"0123456789abcdef";
39
40/// Fold the carrier through the σ-axis `H` (streaming, bounded memory) and
41/// format the `H::LABEL_BYTES`-wide κ-label `<prefix>:<hex>` into an
42/// `Inline` carrier. The stack scratch is sized to the widest admissible
43/// axis ([`MAX_LABEL_BYTES`]); only the active axis's bytes are emitted.
44fn kappa_label_carrier<const N: usize, H: AddrHash>(
45    input: &TermValue<'_, N>,
46) -> TermValue<'static, N> {
47    let digest = H::digest_carrier(input);
48    let prefix = H::LABEL_PREFIX.as_bytes();
49    let p = prefix.len();
50    let mut out = [0u8; MAX_LABEL_BYTES];
51    out[..p].copy_from_slice(prefix);
52    out[p] = b':';
53    for (i, byte) in digest.iter().enumerate().take(H::OUTPUT_BYTES) {
54        out[p + 1 + 2 * i] = HEX_LOWER[(byte >> 4) as usize];
55        out[p + 1 + 2 * i + 1] = HEX_LOWER[(byte & 0x0F) as usize];
56    }
57    TermValue::inline_from_slice(&out[..H::LABEL_BYTES])
58}
59
60// ── The eight per-category resolver structs. ψ₁–ψ₈ are pass-throughs;
61//    only ψ₉ (k-invariants) folds the σ-axis. All are generic over an
62//    unbounded `H` (the foundation resolver traits no longer bind it). ──
63
64macro_rules! address_resolver {
65    ($name:ident) => {
66        #[derive(Debug)]
67        pub struct $name<H>(PhantomData<H>);
68        impl<H> Sealed for $name<H> {}
69        impl<H> Default for $name<H> {
70            #[inline]
71            fn default() -> Self {
72                Self(PhantomData)
73            }
74        }
75    };
76}
77
78address_resolver!(AddressNerveResolver);
79address_resolver!(AddressChainComplexResolver);
80address_resolver!(AddressHomologyGroupResolver);
81address_resolver!(AddressCochainComplexResolver);
82address_resolver!(AddressCohomologyGroupResolver);
83address_resolver!(AddressPostnikovResolver);
84address_resolver!(AddressHomotopyGroupResolver);
85address_resolver!(AddressKInvariantResolver);
86
87macro_rules! passthrough_resolver {
88    ($trait:ident, $name:ident) => {
89        impl<const N: usize, H> $trait<N, H> for $name<H> {
90            #[inline]
91            fn resolve<'a>(
92                &self,
93                input: TermValue<'a, N>,
94            ) -> Result<TermValue<'a, N>, ShapeViolation> {
95                Ok(input)
96            }
97        }
98    };
99}
100
101passthrough_resolver!(NerveResolver, AddressNerveResolver);
102passthrough_resolver!(ChainComplexResolver, AddressChainComplexResolver);
103passthrough_resolver!(HomologyGroupResolver, AddressHomologyGroupResolver);
104passthrough_resolver!(CochainComplexResolver, AddressCochainComplexResolver);
105passthrough_resolver!(CohomologyGroupResolver, AddressCohomologyGroupResolver);
106passthrough_resolver!(PostnikovResolver, AddressPostnikovResolver);
107passthrough_resolver!(HomotopyGroupResolver, AddressHomotopyGroupResolver);
108
109impl<const N: usize, H: AddrHash> KInvariantResolver<N, H> for AddressKInvariantResolver<H> {
110    #[inline]
111    fn resolve<'a>(&self, input: TermValue<'a, N>) -> Result<TermValue<'a, N>, ShapeViolation> {
112        // ψ₉ σ-projection: fold the (streamed) canonical carrier through H
113        // and emit the formatted κ-label. The `'static` Inline carrier is
114        // valid for any `'a`.
115        Ok(kappa_label_carrier::<N, H>(&input))
116    }
117}
118
119// ── The tuple. Bound on the fingerprint-width-erased [`AddrHash`] so it
120//    carries every admissible σ-axis (32- and 64-byte). ──
121
122/// The single eight-resolver ψ-tower shared by every realization.
123pub struct AddressResolverTuple<H: AddrHash> {
124    /// ψ₁ Nerve (pass-through).
125    pub nerve: AddressNerveResolver<H>,
126    /// ψ₂ ChainComplex (pass-through).
127    pub chain_complex: AddressChainComplexResolver<H>,
128    /// ψ₃ HomologyGroups (pass-through).
129    pub homology_groups: AddressHomologyGroupResolver<H>,
130    /// ψ₄ CochainComplex (pass-through).
131    pub cochain_complex: AddressCochainComplexResolver<H>,
132    /// ψ₅ CohomologyGroups (pass-through).
133    pub cohomology_groups: AddressCohomologyGroupResolver<H>,
134    /// ψ₇ PostnikovTower (pass-through).
135    pub postnikov: AddressPostnikovResolver<H>,
136    /// ψ₈ HomotopyGroups (pass-through).
137    pub homotopy_groups: AddressHomotopyGroupResolver<H>,
138    /// ψ₉ KInvariants (the σ-projection — emits the κ-label).
139    pub k_invariants: AddressKInvariantResolver<H>,
140    #[doc(hidden)]
141    pub _phantom: PhantomData<H>,
142}
143
144impl<H: AddrHash> Sealed for AddressResolverTuple<H> {}
145
146impl<H: AddrHash> ResolverTuple for AddressResolverTuple<H> {
147    const ARITY: usize = 8;
148    const CATEGORIES: &'static [ResolverCategory] = &[
149        ResolverCategory::Nerve,
150        ResolverCategory::ChainComplex,
151        ResolverCategory::HomologyGroup,
152        ResolverCategory::CochainComplex,
153        ResolverCategory::CohomologyGroup,
154        ResolverCategory::Postnikov,
155        ResolverCategory::HomotopyGroup,
156        ResolverCategory::KInvariant,
157    ];
158    type ShapeRegistry = EmptyShapeRegistry;
159}
160
161impl<H: AddrHash> Default for AddressResolverTuple<H> {
162    fn default() -> Self {
163        Self {
164            nerve: AddressNerveResolver::default(),
165            chain_complex: AddressChainComplexResolver::default(),
166            homology_groups: AddressHomologyGroupResolver::default(),
167            cochain_complex: AddressCochainComplexResolver::default(),
168            cohomology_groups: AddressCohomologyGroupResolver::default(),
169            postnikov: AddressPostnikovResolver::default(),
170            homotopy_groups: AddressHomotopyGroupResolver::default(),
171            k_invariants: AddressKInvariantResolver::default(),
172            _phantom: PhantomData,
173        }
174    }
175}
176
177macro_rules! has_resolver {
178    ($marker:ident, $rtrait:ident, $accessor:ident, $field:ident) => {
179        impl<const N: usize, H: AddrHash> $marker<N, H> for AddressResolverTuple<H> {
180            fn $accessor(&self) -> &dyn $rtrait<N, H> {
181                &self.$field
182            }
183        }
184    };
185}
186
187has_resolver!(HasNerveResolver, NerveResolver, nerve_resolver, nerve);
188has_resolver!(
189    HasChainComplexResolver,
190    ChainComplexResolver,
191    chain_complex_resolver,
192    chain_complex
193);
194has_resolver!(
195    HasHomologyGroupResolver,
196    HomologyGroupResolver,
197    homology_group_resolver,
198    homology_groups
199);
200has_resolver!(
201    HasCochainComplexResolver,
202    CochainComplexResolver,
203    cochain_complex_resolver,
204    cochain_complex
205);
206has_resolver!(
207    HasCohomologyGroupResolver,
208    CohomologyGroupResolver,
209    cohomology_group_resolver,
210    cohomology_groups
211);
212has_resolver!(
213    HasPostnikovResolver,
214    PostnikovResolver,
215    postnikov_resolver,
216    postnikov
217);
218has_resolver!(
219    HasHomotopyGroupResolver,
220    HomotopyGroupResolver,
221    homotopy_group_resolver,
222    homotopy_groups
223);
224has_resolver!(
225    HasKInvariantResolver,
226    KInvariantResolver,
227    k_invariant_resolver,
228    k_invariants
229);