|
| 1 | +# Plan: Singleton → Snapshot Nudge — every shared-mutable singleton becomes a per-owner SoA with Arc-swap COW snapshot |
| 2 | + |
| 3 | +**Version:** v1 |
| 4 | +**Date:** 2026-06-07 |
| 5 | +**Status:** PROPOSAL |
| 6 | +**D-ids:** D-SNGL-1 through D-SNGL-7 |
| 7 | +**Branch:** `claude/stoic-turing-M0Eiq` |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## The thesis |
| 12 | + |
| 13 | +The workspace has one architectural direction for shared state: |
| 14 | + |
| 15 | +> **No shared mutable singleton. State is owned per-mailbox as a `MailboxSoA<N>`, |
| 16 | +> read via cycle-coherent Arc-swap COW snapshots, and calcified to SPO + Lance |
| 17 | +> tombstone. Cross-boundary state is the discrete LE baton, never a materialized |
| 18 | +> singleton.** |
| 19 | +
|
| 20 | +This is already ratified across three epiphanies and two plans: |
| 21 | + |
| 22 | +- `E-MAILBOX-IS-BINDSPACE` — `MailboxSoA<N>` *is* the per-mailbox thoughtspace; |
| 23 | + the singleton `Arc<BindSpace>` is dissolved, not copied. |
| 24 | +- `E-BATON-1` — inter-mailbox state is the `(u16 target, CausalEdge64)` baton; |
| 25 | + no persisted/transmitted singleton. |
| 26 | +- `E-DEINTERLACE-TWO-SCALES` — deinterlace is one operation at two scales; |
| 27 | + byte-scale is the SoA Arc-swap snapshot at `cycle()` granularity. |
| 28 | +- Plan `bindspace-singleton-to-mailbox-soa-v1` — dissolves the `ShaderDriver` |
| 29 | + `Arc<BindSpace>` singleton specifically. |
| 30 | +- Plan `cycle-coherent-soa-snapshot-v1` — the Arc-swap COW snapshot mechanism |
| 31 | + (no-cross-cycle-lag guarantee). |
| 32 | + |
| 33 | +**What is missing:** a single, enumerated audit that nudges *every* |
| 34 | +singleton-shaped construct in the workspace onto this architecture, so the |
| 35 | +migration is exhaustive rather than ad-hoc. This plan is that audit. |
| 36 | + |
| 37 | +--- |
| 38 | + |
| 39 | +## Two kinds of "singleton" — only one is a target |
| 40 | + |
| 41 | +The grep for `LazyLock` / `OnceLock` / `static` / `Arc<…>` returns two |
| 42 | +fundamentally different shapes. The distinction is the whole game: |
| 43 | + |
| 44 | +### NOT a target — read-only immutable codebooks (LEAVE AS-IS) |
| 45 | + |
| 46 | +These are built once and never mutated. They are the I-VSA-IDENTITIES |
| 47 | +Layer-2 role catalogues and Layer-1 codebooks. A `LazyLock` here is correct |
| 48 | +and idiomatic — it is a const table, not shared mutable state. |
| 49 | + |
| 50 | +| Construct | Home | Verdict | |
| 51 | +|---|---|---| |
| 52 | +| `SUBJECT_KEY` / `PREDICATE_KEY` / … role keys | `contract::grammar::role_keys` | ✅ Keep — immutable role identity codebook | |
| 53 | +| `FINNISH_KEYS` / `TENSE_KEYS` / `NARS_KEYS` | `contract::grammar::role_keys` | ✅ Keep — immutable codebook | |
| 54 | +| `KUNDE_KEY` / `RECHNUNG_KEY` / … callcenter keys | `contract::grammar::role_keys` | ✅ Keep — immutable codebook | |
| 55 | +| `VECTOR_DISTANCE_*_UDF` / `HAMMING_*_UDF` | `lance-graph::datafusion_planner::udf` | ✅ Keep — DataFusion UDF registration, immutable | |
| 56 | +| `simd_caps()` singleton | `ndarray::simd_caps` | ✅ Keep — hardware capability probe, immutable | |
| 57 | + |
| 58 | +**Rule:** a `LazyLock<T>` where `T` is never mutated after init is a codebook, |
| 59 | +not a singleton. The data-flow invariant (`ndarray/.claude/rules/data-flow.md` |
| 60 | +§2) already blesses these: "Caches use interior mutability (`RwLock`, |
| 61 | +`LazyLock`) or are built once." |
| 62 | + |
| 63 | +### IS a target — shared mutable runtime state (NUDGE) |
| 64 | + |
| 65 | +These hold mutable runtime state behind a shared handle. They are the |
| 66 | +singletons the architecture dissolves into per-owner SoA + snapshot. |
| 67 | + |
| 68 | +| Construct | Home | Nudge | Owning plan | |
| 69 | +|---|---|---|---| |
| 70 | +| `ShaderDriver.bindspace: Arc<BindSpace>` | cognitive-shader-driver | Dissolve into per-mailbox `MailboxSoA<N>`; driver holds a sea-star of mailboxes | `bindspace-singleton-to-mailbox-soa-v1` (D-MBX-3/5) | |
| 71 | +| `BindSpace::zeros(4096)` in `bin/serve.rs` | cognitive-shader-driver | Delete; mailboxes allocate their own SoA | `bindspace-singleton-to-mailbox-soa-v1` (D-MBX-5) | |
| 72 | +| `AttentionMatrix.gestalt` (shared mutable summary, drifting via `unbundle_from`) | `lance-graph-planner::cache::kv_bundle` | Either rebuild-from-scratch or raw-sum+count; the gestalt is a snapshot read, not an incrementally-mutated singleton | THIS PLAN (D-SNGL-3) + TD-UNBUNDLE-FROM-1 | |
| 73 | +| `ATTENTION_CACHE` / `LINEAR_CACHE` `LazyLock<RwLock<…>>` | `ndarray/crates/burn::ops::matmul` | Audit: is this a JIT-kernel cache (keep, like UDFs) or runtime belief state (nudge)? | THIS PLAN (D-SNGL-4) | |
| 74 | +| any `Arc<RwLock<…>>` / `Arc<Mutex<…>>` runtime caches surfaced by the audit | workspace-wide | Classify codebook-vs-singleton; nudge only the latter | THIS PLAN (D-SNGL-2) | |
| 75 | + |
| 76 | +--- |
| 77 | + |
| 78 | +## Deliverables |
| 79 | + |
| 80 | +### D-SNGL-1 — Workspace-wide singleton census |
| 81 | + |
| 82 | +Grep every crate for `LazyLock` / `OnceLock` / `OnceCell` / `Lazy` / `static …` / |
| 83 | +`Arc<RwLock` / `Arc<Mutex`. Classify each hit into **codebook** (immutable, keep) |
| 84 | +or **singleton** (shared-mutable, nudge). Output: a census table appended to |
| 85 | +`docs/architecture/soa-three-tier-model.md` § "Singleton census" so the |
| 86 | +codebook-vs-singleton verdict is recorded once and not re-litigated. |
| 87 | + |
| 88 | +### D-SNGL-2 — Classification gate (the one rule, codified) |
| 89 | + |
| 90 | +A doc-level decision procedure (mirrors the lab-vs-canonical decision procedure): |
| 91 | +> Is the static ever mutated after init? **No → codebook, keep.** **Yes → is it |
| 92 | +> per-owner runtime state? Yes → nudge to `MailboxSoA<N>` + snapshot. No (truly |
| 93 | +> process-global, e.g. a JIT-kernel cache) → keep behind `RwLock` per data-flow |
| 94 | +> §2, but document why it is not per-owner.** |
| 95 | +
|
| 96 | +### D-SNGL-3 — `AttentionMatrix.gestalt` correctness + snapshot shape |
| 97 | + |
| 98 | +Fix TD-UNBUNDLE-FROM-1: the gestalt is a derived snapshot, not an |
| 99 | +incrementally-subtracted singleton. Switch to raw-sum + count so the gestalt is |
| 100 | +exactly `(sum[d] / count).round()`, OR rebuild on read. Remove the deprecated |
| 101 | +`unbundle_from` once no caller remains. The gestalt then matches the snapshot |
| 102 | +doctrine: a coherent read over the heads at a cycle stamp, not a drifting |
| 103 | +accumulator. |
| 104 | + |
| 105 | +### D-SNGL-4 — burn matmul cache audit |
| 106 | + |
| 107 | +Classify `ATTENTION_CACHE` / `LINEAR_CACHE` in `ndarray/crates/burn`. If they |
| 108 | +cache *compiled kernels* keyed by shape, they are JIT-kernel caches (keep, |
| 109 | +document as process-global per D-SNGL-2). If they cache *runtime activations / |
| 110 | +beliefs*, they are singletons and must move to the SoA. Record the verdict. |
| 111 | + |
| 112 | +### D-SNGL-5 — Snapshot trait adoption checklist |
| 113 | + |
| 114 | +For each nudged singleton, the migration target is the same trait surface from |
| 115 | +`cycle-coherent-soa-snapshot-v1`: implement `SnapshotProvider` (D-SOA-SNAP-2), |
| 116 | +return a `MailboxSoaSnapshot` (D-SOA-SNAP-1) under a cycle stamp. This deliverable |
| 117 | +is the per-crate checklist binding each nudge to the snapshot contract so the |
| 118 | +migration is uniform. |
| 119 | + |
| 120 | +### D-SNGL-6 — No-cross-cycle-lag falsification per nudged crate |
| 121 | + |
| 122 | +Each nudged crate inherits the D-SOA-SNAP-5 test shape: writer thread advancing |
| 123 | +cycles + N reader threads snapshotting; assert every snapshot is single-cycle. |
| 124 | +The test is the merge gate for that crate's nudge. |
| 125 | + |
| 126 | +### D-SNGL-7 — Board hygiene + EPIPHANIES |
| 127 | + |
| 128 | +This plan + INTEGRATION_PLANS prepend + STATUS_BOARD rows + (if the census |
| 129 | +surfaces a genuinely new finding) an EPIPHANIES entry. The candidate epiphany: |
| 130 | +`E-SINGLETON-IS-CODEBOOK-OR-SOA` — every static is exactly one of two things, |
| 131 | +and the test (mutated-after-init?) is mechanical. |
| 132 | + |
| 133 | +--- |
| 134 | + |
| 135 | +## Execution ordering |
| 136 | + |
| 137 | +1. **D-SNGL-1 + D-SNGL-2 first** (census + gate) — settles codebook-vs-singleton |
| 138 | + verdicts before any code moves. Pure doc/audit work. |
| 139 | +2. **D-SNGL-3** (AttentionMatrix) — the one concrete correctness bug; ships with |
| 140 | + the kv_bundle deprecation already landed this session. |
| 141 | +3. **D-SNGL-4** (burn audit) — independent; classify, then keep-or-nudge. |
| 142 | +4. **D-SNGL-5/6** per nudged crate — gated on `cycle-coherent-soa-snapshot-v1` |
| 143 | + D-SOA-SNAP-1/2 landing first (the trait surface must exist to adopt it). |
| 144 | +5. **D-SNGL-7** lands with D-SNGL-1 (board hygiene is same-commit, per CLAUDE.md). |
| 145 | + |
| 146 | +--- |
| 147 | + |
| 148 | +## Non-goals |
| 149 | + |
| 150 | +- Not touching read-only codebooks (role keys, UDFs, simd_caps). Those are |
| 151 | + correct as-is. |
| 152 | +- Not re-deriving the BindSpace dissolution — that is owned by |
| 153 | + `bindspace-singleton-to-mailbox-soa-v1`. This plan references it, does not |
| 154 | + duplicate it. |
| 155 | +- No new snapshot mechanism — reuses `cycle-coherent-soa-snapshot-v1`'s trait. |
| 156 | + |
| 157 | +--- |
| 158 | + |
| 159 | +## Cross-references |
| 160 | + |
| 161 | +- `bindspace-singleton-to-mailbox-soa-v1` — BindSpace singleton dissolution |
| 162 | +- `cycle-coherent-soa-snapshot-v1` — the Arc-swap COW snapshot mechanism reused here |
| 163 | +- `EPIPHANIES.md` E-MAILBOX-IS-BINDSPACE, E-BATON-1, E-DEINTERLACE-TWO-SCALES |
| 164 | +- `TECH_DEBT.md` TD-UNBUNDLE-FROM-1 — the AttentionMatrix gestalt drift |
| 165 | +- `ndarray/.claude/rules/data-flow.md` §2 — the "caches built once" invariant |
| 166 | + that keeps codebooks legal |
| 167 | +- `docs/architecture/soa-three-tier-model.md` — the zero-copy lifecycle target |
0 commit comments