44//!
55//! Each segment is a u64 FNV-1a hash of the path component. The 6-tuple
66//! compresses via ZeckBF17→Base17→CAM-PQ→scent (1B, ρ=0.937).
7- //! Phase C wires the full compression chain. Phase A carries the
8- //! parsed address and a stub scent derived by XOR-folding the 6 hashes.
7+ //! Phase C wires the full compression chain. The scent is computed by
8+ //! FNV-1a hashing the canonical hex representation of the 6 segment
9+ //! hashes and XOR-folding the 64-bit digest to 1 byte.
910//!
1011//! Plan: `.claude/plans/callcenter-membrane-v1.md` § 10.12 – § 10.13
1112
@@ -45,21 +46,42 @@ impl DnPath {
4546 } )
4647 }
4748
48- /// Phase-A scent stub: XOR-fold of the 6 segment hashes into 1 byte.
49+ /// Compute the scent of this DN path: FNV-1a hash of the canonical
50+ /// path string, folded to a single `u8`.
4951 ///
50- /// Phase C replaces this with the full ZeckBF17→Base17→CAM-PQ chain
51- /// (16Kbit → 48B → 34B → 6B → 1B, ρ=0.937). Until then, this gives
52- /// a deterministic, stable placeholder that exercises the scent field.
52+ /// The canonical form is the 6 segment hashes rendered as hex and
53+ /// concatenated with `/` separators (deterministic, stable, zero-dep).
54+ /// The full 64-bit FNV-1a digest is XOR-folded into 1 byte, preserving
55+ /// avalanche properties much better than the old XOR-fold of individual
56+ /// segment hashes.
57+ ///
58+ /// Future phases may replace this with ZeckBF17→Base17→CAM-PQ
59+ /// (16Kbit → 48B → 34B → 6B → 1B, ρ=0.937) once bgz-tensor
60+ /// enters the callcenter dep tree.
61+ pub fn scent ( & self ) -> u8 {
62+ use core:: fmt:: Write ;
63+ let mut buf = String :: with_capacity ( 6 * 17 ) ;
64+ let segments = [ self . ns , self . heel , self . hip , self . branch , self . twig , self . leaf ] ;
65+ for ( i, seg) in segments. iter ( ) . enumerate ( ) {
66+ if i > 0 { buf. push ( '/' ) ; }
67+ let _ = write ! ( buf, "{:016x}" , seg) ;
68+ }
69+ let h = fnv1a ( & buf) ;
70+ let folded = h
71+ ^ ( h >> 8 )
72+ ^ ( h >> 16 )
73+ ^ ( h >> 24 )
74+ ^ ( h >> 32 )
75+ ^ ( h >> 40 )
76+ ^ ( h >> 48 )
77+ ^ ( h >> 56 ) ;
78+ folded as u8
79+ }
80+
81+ /// Deprecated alias — use [`scent()`](Self::scent) instead.
82+ #[ deprecated( since = "0.1.1" , note = "renamed to `scent()`; the XOR-fold stub has been replaced with FNV-1a" ) ]
5383 pub fn scent_stub ( & self ) -> u8 {
54- let fold = self . ns ^ self . heel ^ self . hip ^ self . branch ^ self . twig ^ self . leaf ;
55- ( fold
56- ^ ( fold >> 8 )
57- ^ ( fold >> 16 )
58- ^ ( fold >> 24 )
59- ^ ( fold >> 32 )
60- ^ ( fold >> 40 )
61- ^ ( fold >> 48 )
62- ^ ( fold >> 56 ) ) as u8
84+ self . scent ( )
6385 }
6486}
6587
@@ -102,7 +124,7 @@ mod tests {
102124 }
103125
104126 #[ test]
105- fn scent_stub_is_deterministic ( ) {
127+ fn scent_is_deterministic ( ) {
106128 let p1 = DnPath :: parse (
107129 "/tree/ada/heel/callcenter/hip/v1/branch/agents/twig/card/leaf/abc" ,
108130 )
@@ -111,11 +133,11 @@ mod tests {
111133 "/tree/ada/heel/callcenter/hip/v1/branch/agents/twig/card/leaf/abc" ,
112134 )
113135 . unwrap ( ) ;
114- assert_eq ! ( p1. scent_stub ( ) , p2. scent_stub ( ) ) ;
136+ assert_eq ! ( p1. scent ( ) , p2. scent ( ) ) ;
115137 }
116138
117139 #[ test]
118- fn different_paths_typically_differ ( ) {
140+ fn different_paths_different_scents ( ) {
119141 let p1 = DnPath :: parse (
120142 "/tree/ada/heel/callcenter/hip/v1/branch/agents/twig/card/leaf/abc" ,
121143 )
@@ -124,7 +146,24 @@ mod tests {
124146 "/tree/ada/heel/callcenter/hip/v1/branch/agents/twig/card/leaf/xyz" ,
125147 )
126148 . unwrap ( ) ;
127- // leaf differs → scent should differ (not guaranteed but very likely for FNV-1a)
128- assert_ne ! ( p1. leaf, p2. leaf) ;
149+ assert_ne ! ( p1. scent( ) , p2. scent( ) ) ;
150+ }
151+
152+ #[ test]
153+ fn empty_path_scent ( ) {
154+ let p = DnPath :: default ( ) ;
155+ let s1 = p. scent ( ) ;
156+ let s2 = p. scent ( ) ;
157+ assert_eq ! ( s1, s2) ;
158+ }
159+
160+ #[ allow( deprecated) ]
161+ #[ test]
162+ fn scent_stub_alias_matches_scent ( ) {
163+ let p = DnPath :: parse (
164+ "/tree/ada/heel/callcenter/hip/v1/branch/agents/twig/card/leaf/abc" ,
165+ )
166+ . unwrap ( ) ;
167+ assert_eq ! ( p. scent_stub( ) , p. scent( ) ) ;
129168 }
130169}
0 commit comments