Skip to content

Commit e9cdc05

Browse files
committed
docs(plan): bindspace→mailbox_soa dependency map + two-path wiring preflight
Read-based map (full reads of every critical-path consumer + two Opus inventory agents for the rest) produced BEFORE any wiring, per operator directive ("map the many dependencies before"; "two paths step by step, never delete the old before testing the new"; "CausalEdge64 is duplicated — no handwaving"). Contents: - §0 the two stores side by side; the D-MBX-A2 gap (content/topic/angle dense hot per OQ-1-resolved, sigma, temporal, expert); cycle (Vsa16kF32) = DROP. - §1 the CausalEdge64 duplication mapped exactly: causal_edge::edge (SPO baton, used everywhere) vs ndarray::hpc::causal_diff (weight-diff codec, NEVER imported into lance-graph). Both repr(transparent)/u64 → silent-corruption hazard at the u64 level. The contract's raw-u64 edges_raw() is the firewall; typed reattach only at MailboxSoA owner + p64-bridge. v2 layout drops temporal. - §2 consumer map: column hot-spots (engine_bridge + driver), the 2 bin construction sites, the 4 Arc::get_mut single-owner hazards, the singleton- bound tests, and the ALREADY-built cold side (surreal_container SurrealMailboxView impls MailboxSoaView; scheduler generic over it; the driver already holds both bindspace + mailboxes). - §3 W0–W7 delete-last wiring sequence (parity test W1, differential test W2, feature-gated engine_bridge/dispatch swap W3/W4, bins W5, tombstone W6, delete BindSpace LAST W7). - §4 dedup hazards checklist. No source wired. Branch off main (post-#516), independent of the open #515. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent e063416 commit e9cdc05

1 file changed

Lines changed: 186 additions & 0 deletions

File tree

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# bindspace → mailbox_soa — dependency map & two-path wiring preflight (v1)
2+
3+
> **Status:** MAP / preflight. No source wired yet. Read-based (full reads of
4+
> every consumer on the critical path; two Opus inventory agents for the rest).
5+
> **Date:** 2026-06-17.
6+
> **Operator constraints (binding):**
7+
> 1. The BindSpace → `MailboxSoA<N>` replacement is a **given** (decided; not re-litigated here).
8+
> 2. **Two parallel paths, step by step** — never delete the old until the new is tested.
9+
> ("better 2 paths step by step than one deleted and 'oops what did that do'.")
10+
> 3. **Map dependencies before wiring/dedup.** Be careful.
11+
> 4. **`CausalEdge64` is duplicated — handle it precisely, no handwaving.**
12+
> **Parent plan:** `bindspace-singleton-to-mailbox-soa-v1.md` (S0–S5 staging; §8 OQ-1 RESOLVED).
13+
> **Companion:** `soa-migration-diff-resolution-2026-06-13.md`.
14+
15+
---
16+
17+
## 0. The two stores, side by side (what migrates)
18+
19+
`BindSpace` (`crates/cognitive-shader-driver/src/bindspace.rs`) — the singleton, **still live**:
20+
21+
| column | type | per-row | → MailboxSoA destination | status |
22+
|---|---|---|---|---|
23+
| `fingerprints.content` | `Box<[u64]>` (256/row) | 2 KB | **own, dense, hot** (OQ-1 RESOLVED §2.7) | **GAP** |
24+
| `fingerprints.topic` | `Box<[u64]>` (256/row) | 2 KB | own, dense, hot | **GAP** |
25+
| `fingerprints.angle` | `Box<[u64]>` (256/row) | 2 KB | own, dense, hot | **GAP** |
26+
| `fingerprints.cycle` | `Box<[f32]>` (16 384/row, `Vsa16kF32`) | **64 KB** | **DROP** — transient local, never a column | n/a |
27+
| `fingerprints.sigma` | `Box<[u8]>` (1/row) | 1 B | own `[u8; N]` (Σ-codebook ref) | **GAP** |
28+
| `edges` | `EdgeColumn(Box<[u64]>)` (**raw u64**) | 8 B | own `[CausalEdge64; N]` (typed) | **SHIPPED** |
29+
| `qualia` | `QualiaI4Column` | 8 B | own `[QualiaI4_16D; N]` | **SHIPPED** |
30+
| `meta` | `MetaColumn(Box<[u32]>)` | 4 B | own `[MetaWord; N]` | **SHIPPED** |
31+
| `temporal` | `Box<[u64]>` | 8 B | own `[u64; N]` (OQ-2 fallback; v2 edge can't carry it) | **GAP** |
32+
| `expert` | `Box<[u16]>` | 2 B | subsume into `mailbox_id`/`w_slot`, or `[u16; N]` | **GAP** |
33+
| `entity_type` | `Box<[u16]>` | 2 B | own `[u16; N]` | **SHIPPED** |
34+
| `ontology` | `Option<Arc<OntologyRegistry>>` | shared | **stays shared** (cold Zone-2, by `&`/`Arc`) | n/a |
35+
36+
`MailboxSoA<N>` (`crates/cognitive-shader-driver/src/mailbox_soa.rs`) — the target, **already shipped (D-MBX-A1)**:
37+
`mailbox_id`, `energy: [f32;N]`, `plasticity_counter: [u8;N]`, `last_active_cycle: [u32;N]`,
38+
`edges: [CausalEdge64;N]`, `qualia: [QualiaI4_16D;N]`, `meta: [MetaWord;N]`,
39+
`entity_type: [u16;N]`, `current_cycle`, `w_slot`, `threshold`, `phase` (Rubicon).
40+
Implements `MailboxSoaView` + `MailboxSoaOwner` (contract), with the `repr(transparent)`
41+
`edges_raw()`/`meta_raw()` zero-copy casts (const-asserted).
42+
43+
**The D-MBX-A2 gap (what S1 still owes):** `content`/`topic`/`angle` (dense, hot — NOT a tiny
44+
ref; OQ-1 resolved), `sigma`, `temporal`, `expert`. Note the content planes are **heap**
45+
(`Box<[u64]>` of `N*256`, like BindSpace) — they cannot be `[u64; N]` stack arrays and
46+
`[u64; N*256]` is not stable; design choice is a parallel `Box<[u64]>` field or a small
47+
`FingerprintColumns`-shaped sub-struct owned by the mailbox.
48+
49+
---
50+
51+
## 1. The `CausalEdge64` duplication (precise — no handwaving)
52+
53+
**Two distinct types, same name, both `#[repr(transparent)]` over `u64`, both with `pub .0`:**
54+
55+
| | `causal_edge::edge::CausalEdge64` | `ndarray::hpc::causal_diff::CausalEdge64` |
56+
|---|---|---|
57+
| file | `crates/causal-edge/src/edge.rs:155` | `/home/user/ndarray/src/hpc/causal_diff.rs:151` |
58+
| semantics | **SPO / thinking edge** — the EdgeColumn baton: S/P/O palette + NARS⟨f,c⟩ + Pearl 2³ + plasticity + (v2) signed inference mantissa + 6-bit W-slot | **weight-diff codec** — which transformer (block, projection) row shifted how far (L1) between two model checkpoints + verb |
59+
| `pack` | 10 scalars | one `&WeightEdge` |
60+
| `frequency`/`confidence` | `/255` | `/1023` |
61+
| `truth()` | `TrustTexture` (2-bit, v2) | `NarsTruth` (f32 pair) |
62+
| `w_slot` / `inference_mantissa` | **yes** | **no** |
63+
| imported into lance-graph? | **yes, everywhere** | **NEVER** (0 imports; only a doc cross-ref at `edge.rs:154`) |
64+
65+
**The hazard:** both are bare `u64` newtypes with public `.0` and overlapping method *names*
66+
carrying *incompatible* semantics. A raw `u64` baton packed by one codec and unpacked by the
67+
other round-trips with **zero compile error and zero runtime signal** — same silent-corruption
68+
class as `I-LEGACY-API-FEATURE-GATED`. Today there is **no** dual-import site; the only vector
69+
is `u64`-level.
70+
71+
**The firewall (already in place — keep it):**
72+
- The contract carries the edge column as **raw `u64`**: `MailboxSoaView::edges_raw() -> &[u64]`
73+
(`soa_view.rs:46`, "kept raw so the contract stays zero-dep — `causal-edge` is not a contract
74+
dep").
75+
- Typed `causal_edge::CausalEdge64` is reattached **only at two trusted boundaries**: the hot
76+
owner `MailboxSoA<N>` (via the const-asserted `repr(transparent)` cast in `edges_raw()`,
77+
`mailbox_soa.rs:377`) and the conversion template `p64-bridge` (`lib.rs:19`, typed).
78+
- The cold view (`surreal_container::SurrealMailboxView`) and `LanceVersionScheduler` stay on
79+
the raw `u64` path and never name the type.
80+
81+
**Safe rule (lock for the migration):** the *only* `CausalEdge64` that may be
82+
`CausalEdge64(raw)`-reconstructed from an `edges_raw()` slice is
83+
`causal_edge::edge::CausalEdge64`; the ndarray twin is **barred** from the mailbox/baton path.
84+
Do not `use ndarray::hpc::causal_diff::CausalEdge64` anywhere in `cognitive-shader-driver`. The
85+
contract's raw-`u64` edge column is the dedup firewall — it keeps the contract zero-dep and
86+
confines the typed reattach to one trusted crate boundary.
87+
88+
**v1/v2 layout note (relevant to `temporal`):** the protocol edge has a feature-gated layout
89+
(`causal-edge-v2-layout`, **default-on** since 0.2.0). v2 **drops `temporal`** (bits 52-63
90+
reclaimed for signed mantissa / plasticity-shift / W-slot / truth-lens / spare; `set_temporal`
91+
is a no-op). ⇒ the BindSpace `temporal` column **cannot** fold into the v2 edge — the `[u64; N]`
92+
fallback (OQ-2) is the correct destination. Any code touching `temporal` must obey
93+
`I-LEGACY-API-FEATURE-GATED` (no v1 temporal accessor under v2).
94+
95+
---
96+
97+
## 2. Consumer dependency map (who touches what)
98+
99+
### 2a. The column hot-spots — `engine_bridge.rs` + `driver.rs` (the bulk)
100+
- **`driver.rs` dispatch hot path** reads the singleton per cycle: `self.bindspace.fingerprints.content_row(row)` (resonance/Hamming search — the heaviest read), `self.bindspace.edges.get(row)``CausalEdge64(raw)`, `self.bindspace.meta.get`, `self.bindspace.qualia.row`, `self.bindspace.entity_type`, `self.bindspace.ontology()`.
101+
- **`engine_bridge.rs`** = the re-encode membrane (S2 dissolve target): `ingest_codebook_indices(&mut BindSpace)` (writes content/meta/temporal), `dispatch_busdto`/`unbind_busdto` (with-engine; cycle/qualia/meta/expert), `persist_cycle(&mut BindSpace)` (cycle/edges/meta), `write_qualia_observed`/`read_qualia_decomposed` (qualia).
102+
103+
### 2b. Other BindSpace consumers (driver crate)
104+
- `serve.rs``bs.fingerprints.set_content` in `encode_handler`; `qualia` read via `read_qualia_decomposed`; `bs.len`.
105+
- `grpc.rs``ingest_codebook_indices`; `qualia` read; `bs.len`.
106+
- **Everything else is comment-only or zero-coupling:** `wire.rs`, `sigma_rosetta.rs`, `cypher_bridge.rs`, `auto_style.rs` (test-only `QUALIA_DIMS`), `codec_*`, `decode_kernel.rs`, `rotation_kernel.rs`, `token_agreement.rs`, `planner_bridge.rs`.
107+
- ⚠️ **`attention_mask.rs` / `attention_mask_actor.rs` define their OWN `AttentionMaskSoA`** — share only the `MailboxId`/`w_slot` vocabulary. **Do NOT conflate** with `MailboxSoA<N>`.
108+
109+
### 2c. Construction (allocation) sites — S3 must change
110+
- `bin/grpc.rs:31``Arc::new(BindSpace::zeros(4096))`
111+
- `bin/serve.rs:31``Arc::new(BindSpace::zeros(4096))`
112+
- (`BindSpaceBuilder` is used only inside `bindspace.rs` tests.)
113+
114+
### 2d. The structural ownership hazard — `Arc::get_mut`
115+
Four sites assume single ownership of the singleton and **break under per-mailbox ownership**:
116+
`grpc.rs:136`, `serve.rs:150`, `serve.rs:601`, `serve.rs:692` (`Arc::get_mut(&mut …bindspace)`
117+
`&mut BindSpace`). Each errors if the `Arc` has >1 ref. These are the write escape hatches
118+
the mailbox model replaces with owned `&mut MailboxSoA`.
119+
120+
### 2e. Tests bound to the singleton (must keep green through both paths)
121+
- `tests/end_to_end.rs``BindSpace::zeros`, `ingest_codebook_indices`, `write_qualia_17d`, `read_qualia_decomposed`, `bindspace(Arc<BindSpace>)`, `persist_cycle`, `bs.meta.get`.
122+
- `tests/busdto_bridge_test.rs` (with-engine) — `BindSpace::zeros` ×4, `dispatch_busdto`/`unbind_busdto`, `bs.meta.get`.
123+
124+
### 2f. The cold side is ALREADY built (the second path's far end)
125+
- `surreal_container::SurrealMailboxView` (`view.rs:159`) **already implements `MailboxSoaView`** (read-only; deliberately NOT `MailboxSoaOwner`). Reads `energy: &[f32]`, **`edges_raw: &[u64]`** (raw path — never names `CausalEdge64`), `meta_raw: &[u32]`, `entity_type: &[u16]` + scalars. The kv-lance projection (`read_via_kv_lance`) is a stub (`BlockedColdBuild`) until the surrealdb fork dep lands; the trait surface + `from_columns` are complete.
126+
- `lance-graph::graph::scheduler::LanceVersionScheduler` is **generic over `MailboxSoaView`** (OUT-direction; "propose, not dispose"); never names `CausalEdge64` or `BindSpace`.
127+
- `p64-bridge` maps **typed** `causal_edge::CausalEdge64` → palette (storage-ward template).
128+
129+
### 2g. The two-path scaffold is ALREADY in `driver.rs`
130+
`ShaderDriver` holds **both**: `bindspace: Arc<BindSpace>` (live) **and**
131+
`mailboxes: HashMap<MailboxId, MailboxSoA<1024>>` (`driver.rs:88`, "transitional per-mailbox
132+
routing surface (slice A2)… purely additive… Removed at cutover (plan S3)"), with `with_mailbox`
133+
builder + `mailbox(id)` accessor. **Dispatch still reads the singleton** — the mailboxes are
134+
populated but not yet consumed by the hot path. This is exactly the "two paths" cradle.
135+
136+
---
137+
138+
## 3. Two-path, step-by-step wiring sequence (delete-last)
139+
140+
Each step keeps **both paths live** and adds tests for the new before removing anything.
141+
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.
148+
- **W2 — read-parity harness on the hot path.** Add a *read shim* so a `MailboxSoaView` can
149+
serve the columns `driver.rs` reads (content_row, edge, meta, qualia, entity_type). Run the
150+
dispatch resonance read against BOTH the singleton and a mailbox built from the same rows;
151+
assert identical hits/resonance. (Differential test — proves the new before any swap.)
152+
- **W3 — `engine_bridge` onto mailbox rows (feature `mailbox-thoughtspace`).** Re-point
153+
`ingest_codebook_indices`/`persist_cycle`/`write_qualia_*` to write a `MailboxSoA` row behind
154+
the feature; v1 `&mut BindSpace` path stays as the default. `cycle` becomes a transient local.
155+
Field-isolation matrix tests on the temporal/expert boundary (`I-LEGACY-API-FEATURE-GATED`).
156+
- **W4 — driver dispatch reads the mailbox (feature-gated).** Behind the feature, the hot path
157+
reads `mailboxes` instead of `bindspace`; the four `Arc::get_mut` write hatches become owned
158+
`&mut MailboxSoA`. Both bins still allocate the singleton when the feature is off.
159+
- **W5 — bins stop allocating the singleton.** `bin/{serve,grpc}.rs` build a mailbox set
160+
(sea-star) instead of `BindSpace::zeros(4096)`, under the feature.
161+
- **W6 — death → SPO-G + Lance tombstone-witness** (gated on `surreal_container` unblock OR the
162+
`lance-graph-callcenter` path). The cold `SurrealMailboxView` is the read end.
163+
- **W7 — delete `BindSpace` + the `cycle` plane; remove the feature gate.** Only after W1–W6 are
164+
green and the singleton has no remaining readers (the two tests in 2e migrated to mailbox).
165+
166+
**Guardrails baked into the order:** the new path is *tested against the old* at W1 (parity) and
167+
W2 (differential) **before** any behaviour swaps at W3/W4; the singleton is removed **last** (W7),
168+
not first. CausalEdge64 stays typed only at the two trusted boundaries; the ndarray twin never
169+
enters. `attention_mask*` SoA is never touched. The content planes stay hot (OQ-1).
170+
171+
---
172+
173+
## 4. Dedup hazards checklist (carry into every wiring PR)
174+
175+
1. **CausalEdge64:** never `use ndarray::hpc::causal_diff::CausalEdge64` in the driver; the
176+
mailbox edge is `causal_edge::edge::CausalEdge64`; cross-boundary stays raw `u64`.
177+
2. **`temporal` under v2:** no v1 temporal accessor; `[u64; N]` column is the home (v2 edge
178+
dropped temporal). `I-LEGACY-API-FEATURE-GATED` field-isolation tests mandatory.
179+
3. **`Arc::get_mut` (4 sites):** each is a single-owner assumption that must become owned
180+
`&mut MailboxSoA` — they are the precise spots that "oops" if the store is shared.
181+
4. **`attention_mask*`:** independent `AttentionMaskSoA`; do not fold into the migration.
182+
5. **`cycle` plane:** never migrate; compute transiently. Dropping it is what makes the 64k–256k
183+
hot working set fit (§2.7).
184+
6. **Content planes stay hot:** `content`/`topic`/`angle` own dense per-row in the mailbox
185+
(~6 KB/thought), not a 6 B ref (OQ-1 resolved). The CAM-PQ/codebook ref is the *cold* form.
186+
7. **Singleton deleted LAST (W7):** the two singleton-bound tests (2e) migrate before deletion.

0 commit comments

Comments
 (0)