Skip to content

Commit 9ddca83

Browse files
committed
feat(mailbox_soa): W1 — migrate temporal/expert/sigma columns (additive, parity-tested)
First wiring step of the bindspace→mailbox_soa arc. ADDITIVE only — the singleton BindSpace is untouched; nothing deleted (operator: "two paths step by step, never delete the old before testing the new"). - MailboxSoA<N> gains temporal: [u64;N], expert: [u16;N], sigma: [u8;N] (the D-MBX-A2 small columns) + accessors (temporal_at/set_temporal/…) + reset_row. temporal is a standalone column NOT folded into the edge: the v2 CausalEdge64 layout dropped temporal bits (I-LEGACY-API-FEATURE-GATED). sigma is a Σ-codebook index (reference, not content; I-VSA-IDENTITIES). - test_mailbox_soa_column_parity_with_bindspace: writes matched per-row values to a BindSpace window and a MailboxSoA, asserts edges/qualia/meta/entity_type + temporal/expert/sigma read back identically. The "test the new" proof. - 13 mailbox_soa tests green, clippy clean. W1b (dense content/topic/angle hot planes — the driver's resonance read) is the next step; the cycle (Vsa16kF32) plane is never migrated (OQ-1/§2.7). Dependency map + W0–W7 sequence: bindspace-mailbox-soa-dependency-map-v1.md. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent aafef4c commit 9ddca83

2 files changed

Lines changed: 163 additions & 6 deletions

File tree

.claude/plans/bindspace-mailbox-soa-dependency-map-v1.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,21 @@ populated but not yet consumed by the hot path. This is exactly the "two paths"
139139

140140
Each step keeps **both paths live** and adds tests for the new before removing anything.
141141

142-
- **W0 (this doc).** Map ratified. — *here.*
143-
- **W1 — D-MBX-A2 columns (additive, tested).** Add `content`/`topic`/`angle` (dense hot,
144-
`Box<[u64]>`), `sigma`/`temporal`/`expert` to `MailboxSoA<N>` + accessors + `reset_row`.
145-
**Test:** a parity round-trip — write matched per-row values to a `BindSpace` window and a
146-
`MailboxSoA`, assert every migrated column reads back identically (content planes included;
147-
`cycle` deliberately absent). Deletes nothing.
142+
- **W0 (this doc).** Map ratified. — *DONE.*
143+
- **W1 — D-MBX-A2 small columns (additive, tested). DONE.** Added `temporal: [u64;N]`,
144+
`expert: [u16;N]`, `sigma: [u8;N]` to `MailboxSoA<N>` + accessors + `reset_row` +
145+
`test_mailbox_soa_column_parity_with_bindspace` (writes matched per-row values to a
146+
`BindSpace` window and a `MailboxSoA`, asserts `edges`/`qualia`/`meta`/`entity_type` +
147+
`temporal`/`expert`/`sigma` read back identically). 13 mailbox_soa tests green, clippy clean.
148+
Deletes nothing.
149+
- **W1b — D-MBX-A2 dense identity planes (additive, tested).** Add `content`/`topic`/`angle`
150+
(dense, hot, heap `Box<[u64]>` of `N*256`, mirroring `FingerprintColumns` minus `cycle`) to
151+
`MailboxSoA<N>` + a zero-copy `content_row(row)->&[u64]` accessor + parity test vs
152+
`BindSpace.fingerprints`. **Design note:** the mailbox is otherwise all-stack `[T;N]`; the
153+
planes MUST be heap (`N*256` u64 ≈ 2 MB at N=1024 cannot be stack, and `[u64; N*256]` is not
154+
stable) — own them as `Box<[u64]>` fields or a small `MailboxFingerprints` sub-struct. The
155+
`cycle` (`Vsa16kF32`) plane is NEVER added (OQ-1/§2.7). This is the hot-path-critical column
156+
(`driver.rs` resonance read = `content_row`), so it gets its own focused step.
148157
- **W2 — read-parity harness on the hot path.** Add a *read shim* so a `MailboxSoaView` can
149158
serve the columns `driver.rs` reads (content_row, edge, meta, qualia, entity_type). Run the
150159
dispatch resonance read against BOTH the singleton and a mailbox built from the same rows;

crates/cognitive-shader-driver/src/mailbox_soa.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)