From 52703d566dda4417d1f860eb6a40e1d46f620a54 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 6 Oct 2026 19:04:56 +0000 Subject: [PATCH 1/2] D-SPOG-W-0: the Witness slot as a SPOG sub-context under classid-G A SPOG coordinate read from (classid, CausalEdge64) with no stored column: S/P/O are the resident bytes, G is graph_of(classid) (the existing "g via classid" rule), and the Witness slot is a sub-context inside G for classes that declare that reading. Slot 0 reads as no anchor. Undeclared classes, another reading of the slot, and v1/unknown provenance refuse. Compared with the real MailboxSoA::apply_edges routing: within one graph it accepts exactly the edges in the same SPOG context on all 64 x 64 slot pairs; across graphs it diverges, because routing reads no classid. Recorded as ISS-MAILBOX-ROUTES-WITNESS-WITHOUT-GRAPH; not fixed here. 7 tests, 6 disable runs red, 0 allocations. Board: D-SPOG-W-0 row and entry, ISSUES row; D-GSO-AFF-0 marked Shipped (#1370). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01T8w7Ucm7NhitfTduFKmhYx --- .claude/board/ISSUES.md | 13 + .claude/board/STATUS_BOARD.md | 3 +- .../2026-10-06-spog-witness-sub-context.md | 26 ++ .claude/board/entries/README.md | 3 +- crates/cognitive-shader-driver/Cargo.toml | 5 + .../examples/README.md | 14 + .../examples/spog_witness_probe.rs | 350 ++++++++++++++++++ 7 files changed, 412 insertions(+), 2 deletions(-) create mode 100644 .claude/board/entries/2026-10-06-spog-witness-sub-context.md create mode 100644 crates/cognitive-shader-driver/examples/spog_witness_probe.rs diff --git a/.claude/board/ISSUES.md b/.claude/board/ISSUES.md index 67f67186d..8aa539c51 100644 --- a/.claude/board/ISSUES.md +++ b/.claude/board/ISSUES.md @@ -1,3 +1,16 @@ +## ISS-MAILBOX-ROUTES-WITNESS-WITHOUT-GRAPH — `MailboxSoA` accepts same-slot edges from any graph (2026-10-06) + +**Status:** OPEN. **Basis:** TEST-PINNED in `cognitive-shader-driver/examples/spog_witness_probe.rs` +(`across_graphs_routing_diverges_from_the_spog_reading`). `MailboxSoA::apply_edges` +compares only `edge.w_slot()` with the mailbox's slot and reads no classid. + +- Under the SPOG reading (G = `graph_of(classid)`, Witness = sub-context inside G) a + mailbox accepts an edge from another graph that carries the same slot value. + Within one graph the routing and the reading agree on all 64 × 64 slot pairs. +- **What closes it:** either a documented rule that a mailbox serves exactly one graph, + enforced where edges are delivered, or a graph check in `apply_edges`. Production + change; not made by the probe. + ## ISS-LXA-ALPHA-FIT — known/unknown for the COCA bake vs the alpha channel's own definition (2026-09-30) **Status:** RESOLVED 2026-10-03 — known is NOT alpha: it is the reference set's baked coverage plane (`ReferenceCoverage`, no conversion to/from `AlphaMask`); the attention-recorder half of option (d) is a separate §5 open question. Plan §3.1 DECISION; `.claude/knowledge/reference-frame-vs-motion.md`. The bullets below record the escalation as it stood on 2026-09-30 and are SUPERSEDED: their "uses the alpha split tunnel" premise and the option-(d) recommendation were rejected (known is not alpha; the attention recorder is plan §5 open), and their "What closes it" condition is SATISFIED. D-LXA-3's only remaining blocker is D-LXC-4 (the shared `academic_20k.csv` loader, STATUS_BOARD). **Basis:** VERIFIED-IN-CODE (5+3 council on diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index ccb470927..bdca5cf21 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -35,7 +35,8 @@ Plan: `.claude/plans/2026-10-06-global-sudoku-replayable-orchestration-v1.md`. O | **D-GSO-6** | P6: deterministic, versioned recipe selector | Shipped (#1358, #1362, #1365); `observations_pending` + `frontier_bounded` wired, `new_encounter` → `revision_pending`: In PR | `cognitive-shader-driver/examples/recipe_selector_probe.rs`: `SelectorPolicy::select(state)` is a pure `const fn` over a declared 4-fact `EpistemicState`; V1 = §11 order (fold, bound, local, revise), rests only on the settled state; V2 swaps two steps (differs on 2 of 16 states); a recorded `Selection` (policy + state) replays under its own policy; every cycle rests in one step per open condition and replays identically; the version covers selection only, not recipe implementations (§13 digest open); 5 tests, 5 disable runs red. Follow-up: `new_encounter` is derived from the world (revising would still change a horizon mask; equals `revise` changing it on a 2-bit universe; rootless revisions included) and the `Revision` recipe writes `GadamerRevision`'s `delta.resulting`; dropping that write never rests; `unresolved_tension` cannot drive `local_disagreement` (never cleared); the other three facts are still stand-ins; 14 tests, 10 disable runs red. Follow-up 2: `local_disagreement` = `unresolved_tension \ interrogated`, where `interrogated` is the coverage `MooreInterrogation` writes (tension itself never cleared); a revision adding a new contradiction reopens it; dropping the interrogation write never rests; `observations_pending` and `frontier_bounded` remain stand-ins; 17 tests, 15 disable runs red. Follow-up 3: `new_encounter` renamed `revision_pending` (revising would change the horizon); `observations_pending` = arrived verdicts > verdicts the `Quorum` counted (silence included; `speaking()` would never rest on a silent source); dropping the fold write never rests; coverage stays per tension bit (new evidence on a covered bit does not reopen); `frontier_bounded` = a recorded `Quad8::fold_product` frontier exists for the current candidate space (a changed space reopens it; an empty frontier is bounded); no stand-in fact remains in the wired cycle; 25 tests, 25 disable runs red | | **D-GSO-7** | P7: reasoning-band earning and downgrade | Shipped (#1360) | `cognitive-shader-driver/examples/reasoning_band_probe.rs`: `ReasoningBand` (bits 61..63) read only via `band_reading::project_band`; earned one rung per executed passing proof under the shipped Pearl `CausalMask` (SO majority → Association, PO intervention trial passed → Causal, SPO removal attack passed → Counterfactual), outcomes executed from trial data and never supplied, no skipping, `NotRun` never raises; failed test, contradicting `Quorum` (minority caps at Association, majority drops to Surface) and `simpsons_paradox_risk` lower it; replay gives the same edge bits; unreadable provenance / undeclared / band-absent refuse; ladder and thresholds are policy pins; 7 tests, 10 disable runs red | | **D-GSO-7a** | P7a: the reasoning band as a relational certification (supersedes the meaning of D-GSO-7's rungs; #1360 unchanged) | Shipped (#1369) | `cognitive-shader-driver/examples/relational_certification_probe.rs`: a reading declared per class over bits 61..63 (`0 Open, 1 Associated, 2 Related, 3 Contributes, 4 CausalCandidate, 5 Causes`, 6..7 refuse; a class declared under the historical reading refuses the same bits); contracts are integer folds over unit masks + `causal_audit::SupportLedger` (2 distinct sources; the robustness mask only refutes; `Causes` = 2 distinct `InterventionBacked` sources + executed randomized arms); `entails` is an explicit table equal to `code >=` on 0..=5 only; repetition, observational effect size, the inference mantissa and confounded observational data never raise or demote past their contract; exhaustive 2,500-model family: 0 chain violations with scoped association, 10 with marginal (Simpson); sibling specificity and removal (overdetermination) are shown not to be rungs; only receipts recorded at or before the model's seal count, so a later receipt cannot change an earlier seal's replay; 13 tests, 12 disable runs red | -| **D-GSO-AFF-0** | Affordance measurement: `CausalEdge64 × RecipeLaw[0..63] → EligibleRecipes: u64` | In PR | `cognitive-shader-driver/examples/affordance_measurement_probe.rs`: bits 59..63 read as one EpistemicState5 code (`spare() << 2 \| truth_raw()`, 10 declared codes, non-monotone, 22 refuse) + Pearl3; 7 recipes as `requires`/`forbids` facts + Pearl planes, compiled at build time to `[u64; 32]` × `[u64; 8]`; measure = 2 lookups + AND, 0 allocations; F1 determinism and law-generation dependence, F2 relevant factor flips its bit, F3 S/P/O/F/C/43..45/mantissa/plasticity/witness swept with no change, F4/F5 tables agree bit-for-bit with an independent rule re-derivation that names every refusal, F6/F7 preference only clears, half-field writes to 59..60 yield refusing codes; v1 / unknown provenance refuses (a v1 row with temporal 128 would read as `Causes`); no scheduler/task/capability object; 9 tests, 8 disable runs red | +| **D-GSO-AFF-0** | Affordance measurement: `CausalEdge64 × RecipeLaw[0..63] → EligibleRecipes: u64` | Shipped (#1370) | `cognitive-shader-driver/examples/affordance_measurement_probe.rs`: bits 59..63 read as one EpistemicState5 code (`spare() << 2 \| truth_raw()`, 10 declared codes, non-monotone, 22 refuse) + Pearl3; 7 recipes as `requires`/`forbids` facts + Pearl planes, compiled at build time to `[u64; 32]` × `[u64; 8]`; measure = 2 lookups + AND, 0 allocations; F1 determinism and law-generation dependence, F2 relevant factor flips its bit, F3 S/P/O/F/C/43..45/mantissa/plasticity/witness swept with no change, F4/F5 tables agree bit-for-bit with an independent rule re-derivation that names every refusal, F6/F7 preference only clears, half-field writes to 59..60 yield refusing codes; v1 / unknown provenance refuses (a v1 row with temporal 128 would read as `Causes`); no scheduler/task/capability object; 9 tests, 8 disable runs red | +| **D-SPOG-W-0** | Witness slot as a SPOG sub-context under classid-G | In PR | `cognitive-shader-driver/examples/spog_witness_probe.rs`: SPOG view = (S/P/O blind bytes, G = `graph_of(classid)`, sub = `w_slot()` under a per-class declared reading, slot 0 = no anchor); undeclared class, another slot reading and v1 / unknown provenance refuse; G never moves with the slot; real `MailboxSoA::apply_edges` routing agrees with the reading on all 64 × 64 slot pairs within one graph and diverges across graphs (routing reads no classid; `ISS-MAILBOX-ROUTES-WITNESS-WITHOUT-GRAPH`); 0 allocations; 7 tests, 6 disable runs red | | **D-GSO-8** | P8: seal boundary | Shipped (#1363) | `lance-graph-planner/examples/seal_boundary_probe.rs`: internal events change only working rows; a declared `Boundary` event freezes the changed rows with the shipped `DetachedCycleBatch::freeze` into a local ledger (no `WalSink`); an unchanged boundary writes nothing; replaying the persisted events from each prior seal reproduces the next seal exactly (frame, landings, image, hash); wrong base, dropped event and swapped order each change the seal; tie limit handled by plan option 1: the key is the event's own durable unique `seq`, a tied batch is refused, and a test shows on the real `freeze` that tied keys make the seal arrival-dependent; 6 tests, 5 disable runs red. Follow-up: `apply` refuses a `seq` not greater than the last (a reuse on one row or across seals escaped `first_tie`), `resume_after` restores the top persisted key; 7 tests, 2 more disable runs red | ## D-PLX — Population-law cross-check (2026-10-03) diff --git a/.claude/board/entries/2026-10-06-spog-witness-sub-context.md b/.claude/board/entries/2026-10-06-spog-witness-sub-context.md new file mode 100644 index 000000000..af1a6b7b7 --- /dev/null +++ b/.claude/board/entries/2026-10-06-spog-witness-sub-context.md @@ -0,0 +1,26 @@ +# 2026-10-06 — D-SPOG-W-0: the Witness slot as a SPOG sub-context under classid-G + +## MEASURED + +`crates/cognitive-shader-driver/examples/spog_witness_probe.rs`: 7 tests, +6 disable runs red. + +- A SPOG coordinate is read from `(classid, CausalEdge64)` with no stored + column and no allocation: S/P/O are the resident bytes, G is + `graph_of(classid)` (the existing "g via classid" rule in `spog_tenants.rs`), + and the Witness slot is a sub-context inside G. +- The sub-context reading is declared per class. An undeclared class, a class + declaring another reading of the slot, and v1 / unknown provenance refuse. + Slot 0 reads as no anchor, as the shipped `w_slot` accessor documents. +- G never moves with the slot; the slot never moves with the classid. +- Against the real `MailboxSoA::apply_edges`: within one graph, accept ⟺ same + SPOG context on all 64 × 64 slot pairs (64 accepted, 4032 dropped). Across + graphs they diverge: routing accepts a foreign graph's edge with the same + slot. Recorded as `ISS-MAILBOX-ROUTES-WITNESS-WITHOUT-GRAPH`. + +## OPEN + +- Whether the per-class declaration becomes a contract declaration beside + `band_reading`. +- The cross-graph routing divergence (production change; see ISSUES). +- Sub-context meaning (corpus, frame, schema) stays the consumer's binding. diff --git a/.claude/board/entries/README.md b/.claude/board/entries/README.md index 483c37fbc..273492e43 100644 --- a/.claude/board/entries/README.md +++ b/.claude/board/entries/README.md @@ -25,11 +25,12 @@ index row, (3) no duplicate entry id. Checks 1 and 2 are deliberately opposite directions; the stranding this convention prevents shows up in exactly one of them, never both. -231 entries, 2026-08-06 .. 2026-10-06. +232 entries, 2026-08-06 .. 2026-10-06. | date | entry id | finding | file | |---|---|---|---| | 2026-10-06 | `v3-v4-dual-reading-round3` | | [2026-10-06-v3-v4-dual-reading-round3.md](2026-10-06-v3-v4-dual-reading-round3.md) | +| 2026-10-06 | `D-SPOG-W-0` | | [2026-10-06-spog-witness-sub-context.md](2026-10-06-spog-witness-sub-context.md) | | 2026-10-06 | `spofc-do-learn-as-compile-round6` | | [2026-10-06-spofc-do-learn-as-compile-round6.md](2026-10-06-spofc-do-learn-as-compile-round6.md) | | 2026-10-06 | `D-GSO-6` | | [2026-10-06-selector-revision-wired.md](2026-10-06-selector-revision-wired.md) | | 2026-10-06 | `selector-observations-wired` | | [2026-10-06-selector-observations-wired.md](2026-10-06-selector-observations-wired.md) | diff --git a/crates/cognitive-shader-driver/Cargo.toml b/crates/cognitive-shader-driver/Cargo.toml index a734260c7..3d061ce15 100644 --- a/crates/cognitive-shader-driver/Cargo.toml +++ b/crates/cognitive-shader-driver/Cargo.toml @@ -107,6 +107,11 @@ test = true name = "affordance_measurement_probe" test = true +# D-SPOG-W-0 Witness-as-SPOG-sub-context probe; tests run under `cargo test`. +[[example]] +name = "spog_witness_probe" +test = true + [dependencies] lance-graph-contract = { path = "../lance-graph-contract" } # Read-only ontology registry (Phase 7, v4 plan). default-features = false so the diff --git a/crates/cognitive-shader-driver/examples/README.md b/crates/cognitive-shader-driver/examples/README.md index 2ec7214da..977056978 100644 --- a/crates/cognitive-shader-driver/examples/README.md +++ b/crates/cognitive-shader-driver/examples/README.md @@ -214,6 +214,20 @@ bits. Two law generations show the generation is part of the reading. cargo run -p cognitive-shader-driver --example affordance_measurement_probe ``` +## spog_witness_probe.rs + +D-SPOG-W-0. A SPOG coordinate as a view over resident coordinates: G from +`graph_of(classid)` (the canonical source), the Witness slot as a sub-context +inside G only for classes that declare that reading (slot 0 = no anchor), and +S/P/O passed through as blind bytes. Undeclared classes, another reading of the +slot, and v1 / unknown provenance refuse. Compared with the real +`MailboxSoA::apply_edges` routing: they agree on all 64 × 64 slot pairs within +one graph and diverge across graphs, because routing reads no classid. + +```bash +cargo run -p cognitive-shader-driver --example spog_witness_probe +``` + ## ewa_anisotropic_probe.rs D-CTX-3. Anisotropic EWA: the footprint is each virtual surfel's normalised diff --git a/crates/cognitive-shader-driver/examples/spog_witness_probe.rs b/crates/cognitive-shader-driver/examples/spog_witness_probe.rs new file mode 100644 index 000000000..29c3ed624 --- /dev/null +++ b/crates/cognitive-shader-driver/examples/spog_witness_probe.rs @@ -0,0 +1,350 @@ +//! D-SPOG-W-0: the Witness slot as a SPOG sub-context under classid-G. +//! +//! Claim under test: a SPOG coordinate is a view assembled from coordinates +//! that are already resident, not a storage layout: +//! +//! ```text +//! G = graph_of(classid) the canonical source (spog_tenants: +//! "g via classid"), read from the key +//! sub = w_slot() under a reading a sub-context INSIDE G (corpus, named +//! the class declares frame, schema); 0 = no anchor +//! S/P/O = the three resident bytes passed through blind, no reading +//! ``` +//! +//! Witness refines G; it never replaces it. A class that has not declared the +//! sub-context reading, or declared another reading of the slot, refuses. +//! Bits 53..58 are read only under an asserted v2/V3 provenance (on a v1 row +//! they are old `temporal` bits), the `band_reading` rule. +//! +//! # The routing check +//! +//! The shipped production consumer of the slot is +//! `MailboxSoA::apply_edges`, which drops every edge whose `w_slot()` differs +//! from the mailbox's. The probe compares that real routing with the SPOG +//! reading: +//! +//! - **Within one G they agree** on all 64 × 64 slot pairs, including slot 0 +//! (unanchored mailbox accepts unanchored edges). +//! - **Across graphs they diverge.** `apply_edges` reads no classid, so a +//! mailbox accepts an edge from another graph that happens to carry the same +//! slot value, while the SPOG reading places it in a different context. The +//! agreement therefore holds only for a mailbox scoped to one graph; nothing +//! in `MailboxSoA` enforces that today. Pinned as a test, not fixed here. +//! +//! # Not decided here +//! +//! - The declaration is probe-local and keyed by classid; whether it becomes a +//! `band_reading`-style contract declaration is open. +//! - What a sub-context ordinal means (corpus, frame, schema) is the +//! consumer's binding, not this probe's. +//! - Making `MailboxSoA` graph-aware is a production change and out of scope. +//! +//! Run: `cargo run -p cognitive-shader-driver --example spog_witness_probe` +//! Tests: `cargo test -p cognitive-shader-driver --example spog_witness_probe` + +use std::alloc::{GlobalAlloc, Layout, System}; +use std::cell::Cell; + +use causal_edge::edge::CausalEdge64; +use causal_edge::pearl::CausalMask; +use causal_edge::PlasticityState; +use cognitive_shader_driver::mailbox_soa::MailboxSoA; +use lance_graph_contract::band_reading::EdgeProvenance; +use lance_graph_contract::canonical_node::{NodeGuid, TailVariant}; +use lance_graph_contract::spog_tenants::graph_of; + +// ── allocation counter (the recipe_quartet_probe pattern) ───────────────── + +/// Counts allocations on the current thread; test threads run in parallel. +struct CountingAlloc; + +thread_local! { + static ALLOCATIONS: Cell = const { Cell::new(0) }; +} + +// SAFETY: forwards every call to `System` unchanged; it only adds a counter. +unsafe impl GlobalAlloc for CountingAlloc { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let _ = ALLOCATIONS.try_with(|c| c.set(c.get() + 1)); + unsafe { System.alloc(layout) } + } + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + unsafe { System.dealloc(ptr, layout) } + } + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + let _ = ALLOCATIONS.try_with(|c| c.set(c.get() + 1)); + unsafe { System.realloc(ptr, layout, new_size) } + } +} + +#[global_allocator] +static GLOBAL: CountingAlloc = CountingAlloc; + +/// Run `f` and return its result with the allocations it made on this thread. +fn counting(f: impl FnOnce() -> T) -> (T, usize) { + let before = ALLOCATIONS.with(Cell::get); + let out = f(); + (out, ALLOCATIONS.with(Cell::get) - before) +} + +// ── declared readings of the Witness slot ────────────────────────────────── + +/// How a class reads the six Witness bits. Declared per class; the bits +/// themselves cannot say. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum WitnessReading { + /// A sub-context inside the class's graph (corpus, frame, schema). + GraphSubContext, + /// Some other reading of the same bits (e.g. an evidence family). It is + /// legal for its own consumers and never readable as a SPOG sub-context. + Other, +} + +/// Two graphs (concepts `0x9101`, `0x9102`), two classes in the first, one in +/// the second, and one class that reads the slot otherwise. Probe placeholders, +/// not OGAR mints. +const CLASS_A: u32 = 0x9101_0001; +const CLASS_A2: u32 = 0x9101_0002; +const CLASS_B: u32 = 0x9102_0001; +const CLASS_OTHER: u32 = 0x9101_0003; +const CLASS_UNDECLARED: u32 = 0x9101_0004; + +const DECLARATIONS: [(u32, WitnessReading); 4] = [ + (CLASS_A, WitnessReading::GraphSubContext), + (CLASS_A2, WitnessReading::GraphSubContext), + (CLASS_B, WitnessReading::GraphSubContext), + (CLASS_OTHER, WitnessReading::Other), +]; + +fn reading_of(classid: u32) -> Option { + DECLARATIONS + .iter() + .find(|(c, _)| *c == classid) + .map(|(_, r)| *r) +} + +// ── the SPOG view ─────────────────────────────────────────────────────────── + +/// A SPOG coordinate read from resident state. Transient; never stored. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct Spog { + s: u8, + p: u8, + o: u8, + /// The graph, from the classid. + g: u16, + /// The Witness sub-context inside `g`; `None` = no anchor (slot 0). + sub: Option, +} + +impl Spog { + /// The context half: which graph, and which sub-context inside it. + fn context(self) -> (u16, Option) { + (self.g, self.sub) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Refusal { + /// Bits 53..58 were not asserted to be v2/V3-stamped. + Provenance(EdgeProvenance), + /// The class declared no reading of the slot. + Undeclared(u32), + /// The class reads the slot some other way. + OtherReading(u32), +} + +/// A key carrying `classid`, minted on the sanctioned V3 path. Only the +/// classid matters to G; the tail is zero. +fn key(classid: u32) -> NodeGuid { + NodeGuid::mint_for(TailVariant::V3, classid, 0, 0, 0, 0, 0, 0) +} + +/// Sub-context reading of a slot value. Slot 0 is "no corpus anchor", as the +/// shipped `w_slot` accessor documents. +fn sub_context(w: u8) -> Option { + (w != 0).then_some(w) +} + +/// Assemble the SPOG coordinate from `(classid, edge)`. +fn spog(classid: u32, edge: CausalEdge64, provenance: EdgeProvenance) -> Result { + if !matches!( + provenance, + EdgeProvenance::V2Stamped | EdgeProvenance::V3Register + ) { + return Err(Refusal::Provenance(provenance)); + } + match reading_of(classid) { + None => return Err(Refusal::Undeclared(classid)), + Some(WitnessReading::Other) => return Err(Refusal::OtherReading(classid)), + Some(WitnessReading::GraphSubContext) => {} + } + Ok(Spog { + s: edge.s_idx(), + p: edge.p_idx(), + o: edge.o_idx(), + g: graph_of(key(classid)), + sub: sub_context(edge.w_slot()), + }) +} + +/// An edge this probe stamped itself under the v2 layout. +fn stamped(classid: u32, edge: CausalEdge64) -> Result { + spog(classid, edge, EdgeProvenance::V2Stamped) +} + +/// Does the real mailbox accept this edge? One delivery to row 0. +fn mailbox_accepts(mailbox_slot: u8, edge: CausalEdge64) -> bool { + let mut mb: MailboxSoA<4> = MailboxSoA::new(1, mailbox_slot, 0.5); + mb.apply_edges(&[(0, edge)]) == 1 +} + +fn edge(s: u8, p: u8, o: u8, w: u8) -> CausalEdge64 { + CausalEdge64::pack_v2( + s, + p, + o, + 200, + 180, + CausalMask::SO, + 0b010, + PlasticityState::ALL_HOT, + ) + .with_inference_mantissa(1) + .with_w_slot(w) +} + +fn main() { + let e = edge(0x11, 0x22, 0x33, 23); + for class in [CLASS_A, CLASS_A2, CLASS_B, CLASS_OTHER, CLASS_UNDECLARED] { + println!( + "classid {class:#010x}, w_slot 23 -> {:?}", + stamped(class, e) + ); + } + let (_, allocs) = counting(|| stamped(CLASS_A, e)); + println!("SPOG read allocations: {allocs}"); + let a = stamped(CLASS_A, e).unwrap(); + let b = stamped(CLASS_B, e).unwrap(); + println!( + "mailbox(slot 23) accepts the edge: {}; SPOG contexts {:?} vs {:?} (graphs differ, routing does not see it)", + mailbox_accepts(23, e), + a.context(), + b.context() + ); +} + +#[cfg(test)] +mod tests { + use super::*; + + /// G comes from the classid and only from it: the slot never moves G, the + /// classid never moves the sub-context, and `graph_of` is the concept half. + #[test] + fn g_comes_from_the_classid_never_from_witness() { + for w in 0..64u8 { + let a = stamped(CLASS_A, edge(1, 2, 3, w)).unwrap(); + let b = stamped(CLASS_B, edge(1, 2, 3, w)).unwrap(); + assert_eq!(a.g, 0x9101); + assert_eq!(b.g, 0x9102); + assert_eq!(a.sub, b.sub, "the classid does not move the sub-context"); + assert_ne!(a.context(), b.context(), "same slot, different graphs"); + } + // Two classes of one graph share G. + let a = stamped(CLASS_A, edge(1, 2, 3, 9)).unwrap(); + let a2 = stamped(CLASS_A2, edge(1, 2, 3, 9)).unwrap(); + assert_eq!(a.context(), a2.context()); + } + + /// S/P/O pass through as blind bytes; the context ignores them. + #[test] + fn spo_bytes_pass_through_blind() { + for v in 0..=255u8 { + let c = stamped(CLASS_A, edge(v, 255 - v, v ^ 0x5A, 7)).unwrap(); + assert_eq!((c.s, c.p, c.o), (v, 255 - v, v ^ 0x5A)); + assert_eq!(c.context(), (0x9101, Some(7))); + } + } + + /// Slot 0 reads as "no anchor", every other slot as its own sub-context. + #[test] + fn slot_zero_is_no_anchor() { + assert_eq!(stamped(CLASS_A, edge(1, 2, 3, 0)).unwrap().sub, None); + for w in 1..64u8 { + assert_eq!(stamped(CLASS_A, edge(1, 2, 3, w)).unwrap().sub, Some(w)); + } + } + + /// Classes without the declared reading refuse; so do v1 and unknown + /// provenance. The bits are the same in every case. + #[test] + fn undeclared_other_reading_and_untrusted_provenance_refuse() { + let e = edge(1, 2, 3, 23); + assert_eq!( + stamped(CLASS_UNDECLARED, e), + Err(Refusal::Undeclared(CLASS_UNDECLARED)) + ); + assert_eq!( + stamped(CLASS_OTHER, e), + Err(Refusal::OtherReading(CLASS_OTHER)) + ); + for prov in [EdgeProvenance::V1Legacy, EdgeProvenance::Unknown] { + assert_eq!(spog(CLASS_A, e, prov), Err(Refusal::Provenance(prov))); + } + assert!(spog(CLASS_A, e, EdgeProvenance::V3Register).is_ok()); + } + + /// Within one graph, the real `MailboxSoA` routing and the SPOG reading + /// agree on every one of the 64 × 64 slot pairs: the mailbox accepts an + /// edge exactly when their sub-contexts are equal. + #[test] + fn within_one_graph_routing_agrees_with_the_spog_reading() { + let (mut accepted, mut dropped) = (0, 0); + for mailbox_slot in 0..64u8 { + let mailbox_ctx = (0x9101u16, sub_context(mailbox_slot)); + for w in 0..64u8 { + let e = edge(1, 2, 3, w); + let same_ctx = stamped(CLASS_A, e).unwrap().context() == mailbox_ctx; + let accepts = mailbox_accepts(mailbox_slot, e); + assert_eq!(accepts, same_ctx, "mailbox {mailbox_slot}, edge slot {w}"); + if accepts { + accepted += 1; + } else { + dropped += 1; + } + } + } + assert_eq!((accepted, dropped), (64, 64 * 63), "both outcomes occur"); + } + + /// Across graphs they diverge: the mailbox reads no classid, so it accepts + /// an edge from another graph carrying the same slot, which the SPOG + /// reading places in a different context. Agreement needs a mailbox scoped + /// to one graph. + #[test] + fn across_graphs_routing_diverges_from_the_spog_reading() { + let mailbox_ctx = (0x9101u16, sub_context(23)); + let foreign = edge(1, 2, 3, 23); + assert!( + mailbox_accepts(23, foreign), + "routing accepts the foreign edge" + ); + assert_ne!(stamped(CLASS_B, foreign).unwrap().context(), mailbox_ctx); + // The same edge read under the mailbox's own graph does agree. + assert_eq!(stamped(CLASS_A, foreign).unwrap().context(), mailbox_ctx); + } + + /// The read allocates nothing. + #[test] + fn the_spog_read_allocates_nothing() { + let edges: [CausalEdge64; 64] = core::array::from_fn(|w| edge(w as u8, 2, 3, w as u8)); + let (acc, allocs) = counting(|| { + edges.iter().fold(0u32, |acc, e| { + let c = stamped(CLASS_A, *e).unwrap(); + acc.wrapping_add(u32::from(c.g) ^ u32::from(c.sub.unwrap_or(0)) ^ u32::from(c.s)) + }) + }); + assert_eq!(allocs, 0); + assert_ne!(acc, 0); + } +} From 4cf13d05873793c790bc69fbbd17cd0fc1431489 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 6 Oct 2026 19:29:07 +0000 Subject: [PATCH 2/2] r2il-mask-abi-probe: per-thread heap counter in v3_v4_dual_reading The heap meter kept MEASURING per thread but BYTES global. The test harness runs tests on parallel threads, so loco_execution_needs_a_gathered_copy (which measures its own 364-byte allocation) could land in readings_allocate_nothing's window and fail it with left: 364. BYTES is now thread-local like MEASURING. Measured locally: the old version fails 20/300 runs of the test binary, this one 0/300. The can-fire half (materialize_indices allocates) still passes. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01T8w7Ucm7NhitfTduFKmhYx --- .../r2il-mask-abi-probe/tests/v3_v4_dual_reading.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/r2il-mask-abi-probe/tests/v3_v4_dual_reading.rs b/crates/r2il-mask-abi-probe/tests/v3_v4_dual_reading.rs index 9e6845e09..79f574f0e 100644 --- a/crates/r2il-mask-abi-probe/tests/v3_v4_dual_reading.rs +++ b/crates/r2il-mask-abi-probe/tests/v3_v4_dual_reading.rs @@ -37,7 +37,6 @@ use std::alloc::{GlobalAlloc, Layout, System}; use std::cell::Cell; -use std::sync::atomic::{AtomicUsize, Ordering}; use lance_graph_contract::canonical_node::{node_rows_from_le_bytes, NodeRow}; use lance_graph_contract::facet::FacetCascade; @@ -51,17 +50,19 @@ use ogar_r2il::{project, r2il_mask, R2ILFn, R2IL_BASE}; struct Counting; -static BYTES: AtomicUsize = AtomicUsize::new(0); - thread_local! { static MEASURING: Cell = const { Cell::new(false) }; + // Per thread, like `MEASURING`: the test harness runs tests on parallel + // threads, and a shared counter let one test's allocations land in + // another test's measuring window. + static BYTES: Cell = const { Cell::new(0) }; } // SAFETY: a pure pass-through to `System`; the counter is the only addition. unsafe impl GlobalAlloc for Counting { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { if MEASURING.try_with(Cell::get).unwrap_or(false) { - BYTES.fetch_add(layout.size(), Ordering::Relaxed); + let _ = BYTES.try_with(|b| b.set(b.get() + layout.size())); } // SAFETY: same layout, same contract as the caller's. unsafe { System.alloc(layout) } @@ -76,11 +77,11 @@ unsafe impl GlobalAlloc for Counting { static A: Counting = Counting; fn measure(f: impl FnOnce() -> R) -> (R, usize) { - let before = BYTES.load(Ordering::Relaxed); + let before = BYTES.with(Cell::get); MEASURING.with(|m| m.set(true)); let r = f(); MEASURING.with(|m| m.set(false)); - (r, BYTES.load(Ordering::Relaxed) - before) + (r, BYTES.with(Cell::get) - before) } // ── the resident object ──────────────────────────────────────────────────