@@ -94,6 +94,31 @@ pub struct MailboxSoA<const N: usize> {
9494 /// The registry itself stays `Arc<OntologyRegistry>` (cold Zone-2, not owned here).
9595 pub entity_type : [ u16 ; N ] ,
9696
97+ // ── NEW: D-MBX-A2 migrated columns (W1 — temporal / expert / sigma) ──
98+ /// Per-row temporal stamp (`u64`, 8 B/row). Migrated from `BindSpace.temporal`.
99+ ///
100+ /// Kept as a standalone column (OQ-2 fallback), NOT folded into the edge: the
101+ /// v2 `causal_edge::CausalEdge64` layout reclaimed the old temporal bits
102+ /// (`I-LEGACY-API-FEATURE-GATED`), so the edge cannot carry it. `current_cycle`
103+ /// is the mailbox-level clock; this is the per-row event stamp.
104+ pub temporal : [ u64 ; N ] ,
105+
106+ /// Per-row expert/corpus id (`u16`, 2 B/row). Migrated from `BindSpace.expert`.
107+ ///
108+ /// Often subsumed by `mailbox_id` / `w_slot` (the mailbox *is* an expert), but
109+ /// kept per-row for multi-expert mailboxes during the migration.
110+ pub expert : [ u16 ; N ] ,
111+
112+ /// Per-row Σ-codebook index (`u8`, 1 B/row). Migrated from
113+ /// `BindSpace.fingerprints.sigma`.
114+ ///
115+ /// A *reference* (1-byte index) into the 256-entry Σ codebook owned by
116+ /// `lance-graph-contract::sigma_propagation` — content, like the ontology and
117+ /// the CAM-PQ codebook, stays shared/cold and is NOT copied per row
118+ /// (`I-VSA-IDENTITIES`: indices, not content). The dense content/topic/angle
119+ /// identity planes are a separate W1b step.
120+ pub sigma : [ u8 ; N ] ,
121+
97122 /// Monotonic cycle stamp; advanced by `tick()`.
98123 pub current_cycle : u32 ,
99124
@@ -152,6 +177,10 @@ impl<const N: usize> MailboxSoA<N> {
152177 qualia : [ QualiaI4_16D :: ZERO ; N ] ,
153178 meta : [ MetaWord ( 0 ) ; N ] ,
154179 entity_type : [ 0u16 ; N ] ,
180+ // ── NEW D-MBX-A2 columns — zero-initialised (W1) ──
181+ temporal : [ 0u64 ; N ] ,
182+ expert : [ 0u16 ; N ] ,
183+ sigma : [ 0u8 ; N ] ,
155184 // Pre-Rubicon: every mailbox starts in deliberation.
156185 phase : KanbanColumn :: Planning ,
157186 }
@@ -242,6 +271,10 @@ impl<const N: usize> MailboxSoA<N> {
242271 self . qualia [ row] = QualiaI4_16D :: ZERO ;
243272 self . meta [ row] = MetaWord ( 0 ) ;
244273 self . entity_type [ row] = 0 ;
274+ // ── NEW D-MBX-A2 columns reset (W1) ──
275+ self . temporal [ row] = 0 ;
276+ self . expert [ row] = 0 ;
277+ self . sigma [ row] = 0 ;
245278 }
246279
247280 // ── Read-only inspectors ──────────────────────────────────────────────────
@@ -336,6 +369,46 @@ impl<const N: usize> MailboxSoA<N> {
336369 pub fn set_entity_type ( & mut self , row : usize , t : u16 ) {
337370 self . entity_type [ row] = t;
338371 }
372+
373+ // ── D-MBX-A2 column accessors (W1: temporal / expert / sigma) ────────────
374+
375+ /// Per-row temporal stamp for `row`.
376+ #[ inline]
377+ pub fn temporal_at ( & self , row : usize ) -> u64 {
378+ self . temporal [ row]
379+ }
380+
381+ /// Set the per-row temporal stamp for `row`. (Distinct from the v2
382+ /// `CausalEdge64::set_temporal` no-op — this is the mailbox's standalone
383+ /// temporal column, the legitimate home per `I-LEGACY-API-FEATURE-GATED`.)
384+ #[ inline]
385+ pub fn set_temporal ( & mut self , row : usize , t : u64 ) {
386+ self . temporal [ row] = t;
387+ }
388+
389+ /// Per-row expert/corpus id for `row`.
390+ #[ inline]
391+ pub fn expert_at ( & self , row : usize ) -> u16 {
392+ self . expert [ row]
393+ }
394+
395+ /// Set the per-row expert/corpus id for `row`.
396+ #[ inline]
397+ pub fn set_expert ( & mut self , row : usize , e : u16 ) {
398+ self . expert [ row] = e;
399+ }
400+
401+ /// Per-row Σ-codebook index for `row`.
402+ #[ inline]
403+ pub fn sigma_at ( & self , row : usize ) -> u8 {
404+ self . sigma [ row]
405+ }
406+
407+ /// Set the per-row Σ-codebook index for `row`.
408+ #[ inline]
409+ pub fn set_sigma ( & mut self , row : usize , s : u8 ) {
410+ self . sigma [ row] = s;
411+ }
339412}
340413
341414// ── Contract trait impls: MailboxSoA IS the in-RAM Rubicon owner ──────────────
@@ -749,4 +822,79 @@ mod tests {
749822 "meta_raw is zero-copy (same backing pointer)"
750823 ) ;
751824 }
825+
826+ // ── test 13: W1 column parity — MailboxSoA carries what BindSpace carries ──
827+
828+ /// **The "test the new before deleting the old" proof (W1).** Write distinct
829+ /// per-row values to a `BindSpace` window AND mirror them into a `MailboxSoA`,
830+ /// then assert every migrated LE-contract column reads back identically:
831+ /// `edges` / `qualia` / `meta` / `entity_type` (D-MBX-A1) plus the new
832+ /// `temporal` / `expert` / `sigma` (D-MBX-A2, W1). This proves the mailbox is a
833+ /// faithful carrier for these columns — it deletes nothing and touches no
834+ /// dispatch path. The dense `content`/`topic`/`angle` identity planes are the
835+ /// separate W1b step; the deprecated `cycle` (Vsa16kF32) plane is never migrated.
836+ #[ test]
837+ fn test_mailbox_soa_column_parity_with_bindspace ( ) {
838+ use crate :: bindspace:: BindSpace ;
839+ use lance_graph_contract:: cognitive_shader:: MetaWord ;
840+ use lance_graph_contract:: qualia:: QualiaI4_16D ;
841+
842+ const N : usize = 8 ;
843+ let mut bs = BindSpace :: zeros ( N ) ;
844+ let mut mb: MailboxSoA < N > = MailboxSoA :: new ( 1 , 0 , 1.0 ) ;
845+
846+ for row in 0 ..N {
847+ let edge = 0xABCD_0000u64 | row as u64 ;
848+ let q = QualiaI4_16D :: ZERO
849+ . with ( 0 , ( row % 7 ) as i8 )
850+ . with ( 7 , -( ( row % 8 ) as i8 ) ) ;
851+ let m = MetaWord :: new (
852+ ( row % 12 ) as u8 ,
853+ 1 ,
854+ ( row * 10 ) as u8 ,
855+ ( row * 7 ) as u8 ,
856+ ( row % 6 ) as u8 ,
857+ ) ;
858+ let etype = ( 100 + row) as u16 ;
859+ let temporal = 0xDEAD_0000u64 | row as u64 ;
860+ let expert = ( 200 + row) as u16 ;
861+ let sigma = ( row * 3 ) as u8 ;
862+
863+ // BindSpace side (the singleton, source).
864+ bs. edges . set ( row, edge) ;
865+ bs. qualia . set ( row, q) ;
866+ bs. meta . set ( row, m) ;
867+ bs. entity_type [ row] = etype;
868+ bs. temporal [ row] = temporal;
869+ bs. expert [ row] = expert;
870+ bs. fingerprints . write_sigma ( row, sigma) ;
871+
872+ // MailboxSoA side (the migrated owner).
873+ mb. set_edge ( row, CausalEdge64 ( edge) ) ;
874+ mb. set_qualia ( row, q) ;
875+ mb. set_meta ( row, m) ;
876+ mb. set_entity_type ( row, etype) ;
877+ mb. set_temporal ( row, temporal) ;
878+ mb. set_expert ( row, expert) ;
879+ mb. set_sigma ( row, sigma) ;
880+ }
881+
882+ for row in 0 ..N {
883+ assert_eq ! ( mb. edge( row) . 0 , bs. edges. get( row) , "edges[{row}]" ) ;
884+ assert_eq ! ( mb. qualia_at( row) , bs. qualia. row( row) , "qualia[{row}]" ) ;
885+ assert_eq ! ( mb. meta_at( row) . 0 , bs. meta. get( row) . 0 , "meta[{row}]" ) ;
886+ assert_eq ! (
887+ mb. entity_type_at( row) ,
888+ bs. entity_type[ row] ,
889+ "entity_type[{row}]"
890+ ) ;
891+ assert_eq ! ( mb. temporal_at( row) , bs. temporal[ row] , "temporal[{row}]" ) ;
892+ assert_eq ! ( mb. expert_at( row) , bs. expert[ row] , "expert[{row}]" ) ;
893+ assert_eq ! (
894+ mb. sigma_at( row) ,
895+ bs. fingerprints. sigma_at( row) ,
896+ "sigma[{row}]"
897+ ) ;
898+ }
899+ }
752900}
0 commit comments