From f327941006548e9adb45f2a164bc1863b121bb78 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 23:00:39 +0000 Subject: [PATCH 1/4] =?UTF-8?q?docs(plan):=20capstone=20validation=20plan?= =?UTF-8?q?=20=E2=80=94=20cognitive-loop=20wiring=20+=20NaN-census?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The measurement companion to the kanban×Rubicon tenant arc. A MEASUREMENT plan, not a build plan — converts the operator's "99% present / 28% wiring gaps / 72% NaN" into three orthogonal measured quantities (piece-presence% / seam-wiring% / run-NaN%) over the phase-aligned cognitive loop, per the measurement-before- synthesis + probe-first iron rules. 7 seams, each a CONJECTURE until its probe runs green: - S1 kanban tenant (field-isolation) - S2 MUL→phase (flow-vs-mismatch GateDecision advances phase) - S3 version-arc→KanbanMove (scheduler) - S4 envelope route via BridgeSlot (surreal plan engaged by Cargo presence) - S5 batch push (measured GAP today: commit is pull-only, notifies optimizer) - S6 timeline zero-copy (GAP: val is opaque variable Binary, not FixedSizeBinary(512)) - S7 SoA self-NaN-census = the "Orchestration meta-awareness" (the capstone) Wave 0 measures the baseline on SHIPPED code — the honest number behind "72% NaN" (a hypothesis until measured). Overclaim guard: AGI-adjacency is the IF the census TESTS, never an asserted THEN; the novel measurable is S7 (a system aware of what it cannot yet do — the φ-1 humility ceiling made a number in the Meta tenant). Records the I-VSA-IDENTITIES decision: thinking-style is ClassView + Meta tenant, NOT a new 128-bit tenant (the 48+80 flat split duplicates Plasticity/Meta/Qualia). Board: AGENT_LOG (cont.16), INTEGRATION_PLANS prepend. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01CcpLeEC3XK8Eye53GKBVvi --- .claude/board/AGENT_LOG.md | 4 + .claude/board/INTEGRATION_PLANS.md | 10 ++ ...one-cognitive-loop-wiring-nan-census-v1.md | 106 ++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 .claude/plans/capstone-cognitive-loop-wiring-nan-census-v1.md diff --git a/.claude/board/AGENT_LOG.md b/.claude/board/AGENT_LOG.md index 5098df77..7dd6f848 100644 --- a/.claude/board/AGENT_LOG.md +++ b/.claude/board/AGENT_LOG.md @@ -1,3 +1,7 @@ +## 2026-06-20 (cont.¹⁶) — capstone validation plan: cognitive-loop wiring + NaN-census + +**Main thread (Opus), autoattended.** Rebased jirak onto main (98c0cf2a = #564 merged: lance-graph-ogar + node_rows_from_le_bytes). Resolved a stop-hook false-positive (98c0cf2a is GitHub's web-flow merge commit, GitHub-verified; fast-forwarded origin/jirak to it, no rewrite). Operator asked whether the kanban/MUL/orchestration wiring deserves a capstone VALIDATION plan that MEASURES the actual wiring ("99% there unused, 28% wiring gaps, 72% NaN"). Agreed — it's mandated by the measurement-before-synthesis + probe-first iron rules. Wrote `.claude/plans/capstone-cognitive-loop-wiring-nan-census-v1.md`: treats the operator's estimate as THREE orthogonal measured quantities (piece-presence% / seam-wiring% / run-NaN%); 7 seams S1..S7 each a CONJECTURE-until-probe-green (S1 kanban tenant, S2 MUL→phase, S3 version→move, S4 envelope route via BridgeSlot, S5 batch push [measured GAP: pull-only], S6 timeline zero-copy [GAP: val is opaque Binary], S7 SoA self-NaN-census = the "Orchestration meta-awareness"); Wave 0 = measure baseline on shipped code (the honest number behind 72% NaN). Overclaim-guard: AGI-adjacency is the IF the census TESTS, never the asserted THEN. Decided (I-VSA-IDENTITIES register-laziness + AGI-glove): thinking-style is ClassView+Meta, NOT a new 128-bit tenant; the 48+80 flat split was rejected (duplicates Plasticity/Meta/Qualia). Kanban tenant stays 8 B; still gated on operator go for the canon-locked region. Plan committed to jirak; INTEGRATION_PLANS prepended. + ## 2026-06-20 (cont.¹⁵) — PR #564 codex P2 fixes (contract-source unification + build-time parity fuse) **Main thread (Opus), autoattended.** Two codex P2 review comments on #564, both correct, both fixed: diff --git a/.claude/board/INTEGRATION_PLANS.md b/.claude/board/INTEGRATION_PLANS.md index b7685bb7..b6ed8482 100644 --- a/.claude/board/INTEGRATION_PLANS.md +++ b/.claude/board/INTEGRATION_PLANS.md @@ -1,3 +1,13 @@ +## 2026-06-20 — capstone-cognitive-loop-wiring-nan-census-v1 (PROPOSED) + +The measurement companion to the kanban×Rubicon tenant arc. **A measurement plan, +not a build plan** — converts the operator's "99% present / 28% wiring gaps / 72% +NaN" into THREE orthogonal measured quantities (piece-presence% / seam-wiring% / +run-NaN%) over the phase-aligned cognitive loop. 7 seams (S1 kanban tenant … S7 +SoA self-NaN-census = "Orchestration meta-awareness"), each a CONJECTURE until its +probe runs green. Wave 0 = measure the baseline on shipped code (the honest number +behind 72% NaN). Plan: `.claude/plans/capstone-cognitive-loop-wiring-nan-census-v1.md`. + ## 2026-06-20 — ogar-vocab ⇄ contract codebook migration (PROPOSED; surfaces a canon conflict) Plan: `.claude/plans/ogar-vocab-contract-codebook-migration-v1.md`. Closes the ontology→contract seam: OGAR `ogar-vocab` already defines the class-identity codebook (`CODEBOOK` domain-encoded `0xDDCC`, `ConceptDomain`, `source_domain_concept`, `canonical_concept_id`, `LabelDTO`) and its own doc says `LabelDTO` "long-term belongs in lance-graph-contract … codebook ids and the NodeGuid.classid u16 low half are wire-compatible." **Conflict surfaced:** merged `CLASSID_OSINT=0x0007` routes to OGAR's *Reserved* domain (OSINT is `0x07XX`), and `CLASSID_FMA=0x0008` sits in OGAR's OCR block (FMA/anatomy ≈ Health `0x09XX`). Target: contract hosts the codebook/`ConceptDomain`/`LabelDTO`, classids follow `0xDDCC` (mint project `0x01XX` + ERP `0x02XX`; realign OSINT→`0x0700`, FMA→Health). D-OVC-1..4. **Gated on operator sign-off (canon realign of merged OSINT/FMA + the OGAR↔contract dependency direction) — see plan §5.** Per-family codebook (D-GV2-2) is the finer scope of the same idea. Cross-ref ISSUES `ISS-CLASSID-OGAR-DRIFT`. diff --git a/.claude/plans/capstone-cognitive-loop-wiring-nan-census-v1.md b/.claude/plans/capstone-cognitive-loop-wiring-nan-census-v1.md new file mode 100644 index 00000000..331b243e --- /dev/null +++ b/.claude/plans/capstone-cognitive-loop-wiring-nan-census-v1.md @@ -0,0 +1,106 @@ +# Capstone Validation Plan — Phase-Aligned Cognitive Loop: Wiring & NaN-Census (v1) + +> **Status:** PROPOSED (2026-06-20). The measurement companion to the kanban×Rubicon +> tenant arc. **Not a build plan — a measurement plan.** Per the workspace +> measurement-before-synthesis iron rule (truth-architect) + probe-first protocol: +> every seam below is a **CONJECTURE until its probe runs green**. The deliverable +> per seam is the probe + the number, never more prose. +> **Operator framing (2026-06-20):** "99% is there unused, 28% wiring gaps +> resulting in 72% NaN" — treated here as THREE orthogonal measured quantities, +> not estimates to assert. + +--- + +## 0 — Why this exists (the anti-synthesis-spiral guard) + +Two Opus mapping passes (AGENT_LOG 2026-06-20) established the qualitative truth: +the substrate has **all the pieces** of a phase-aligned, per-SoA-owned, version-arc- +scheduled cognitive loop (`kanban` Rubicon phases, `MailboxSoaOwner`/`View` split, +`VersionScheduler`/`NextPhaseScheduler`, `CycleAccumulator`, the `Meta`/`Qualia`/ +`Plasticity` tenants, `mul.rs` DK/Trust/Flow/Gate, `OrchestrationBridge`/`BridgeSlot`, +surrealdb `timeline.rs`), **but the runtime loop is not closed** — `UnifiedStep` +carries no SoA pointer, the batch writer pushes nothing, the timeline is unwired +(`#[allow(dead_code)]`), kanban is not yet a value tenant, and no pipelining exists. + +The risk this plan removes: shipping the kanban tenant + calling the loop "done" +by assertion. Instead we **measure** the three quantities below and drive run-NaN +toward 0 seam by seam. + +## 1 — The three measured quantities (the dashboard) + +| Metric | Definition | How measured | Baseline (Wave 0) | +|---|---|---|---| +| **Piece-presence %** | of the N named loop components, how many TYPES exist | static: grep the contract + surrealdb for each named type | ~99% (claim — Wave-0 confirms) | +| **Seam-wiring %** | of the M seams (S1..S7), how many are CONNECTED (caller→callee actually invoked in a non-test path) | runtime-trace: instrument one cycle, count seams that fire | ~72% wired ⇒ ~28% gap (claim) | +| **Run-NaN %** | of the K observable outputs in one end-to-end cycle, fraction that are NaN / `None` / unhandled-`BridgeSlot` / default-fallback | run one cycle on shipped code, count valid vs NaN | ~72% NaN (HYPOTHESIS) | + +**These are distinct.** A piece can be present (99%) yet its seam unwired (28% gap) +yet its output NaN at runtime (72%). The plan's success metric is **run-NaN → <5%** +with **seam-wiring → 100%**, each step backed by a probe number. + +## 2 — The loop under test (one cycle) + +``` +SoA node (Kanban tenant: phase) ── S1 + → MUL reads Qualia(flow/trust/DK)+Meta(NARS/FE)+Plasticity ── S2 + → GateDecision (flow vs mismatch) ── S2 + → owner advances Kanban phase (gated write) ── S1/S2 + → VersionScheduler lowers version-arc → KanbanMove ── S3 + → UnifiedStep routed via BridgeSlot (lance/surreal/lancedb) ── S4 + → batch writer commits → Lance version (push?) ── S5 + → timeline view renders kanbanview (zero-copy?) ── S6 + → SoA self-NaN-census written to Meta (meta-awareness)── S7 + → (next cycle, pipelined against this one's disk-push)── S5/G2 +``` + +## 3 — The seams (each a probe with PASS / KILL) + +| Seam | Claim | Probe (pass/fail) | KILL condition | Status | +|---|---|---|---|---| +| **S1** kanban tenant | `ValueTenant::Kanban` at `[144,152)` carries phase+cycle+exec, layout-preserving | field-isolation matrix: write each tenant, assert all others unchanged; `NodeRowPacket` round-trips kanban byte-exact | stride ≠ 512, or any other tenant perturbed | CONJECTURE | +| **S2** MUL→phase | `MUL::GateDecision(Qualia,Meta,Plasticity)` mismatch ⇒ owner advances phase | feed a known flow-vs-mismatch qualia vector; assert the gate returns the expected `KanbanMove` (or HOLD) | gate ignores qualia (constant output), or reads uninitialized → NaN | CONJECTURE | +| **S3** version→move | `VersionScheduler::on_version` lowers a real Lance version to the next legal `KanbanMove` | drive a 2-version dataset; assert the forward-arc move emitted | no move on a legal transition, or illegal edge emitted | CONJECTURE (type exists, unwired) | +| **S4** envelope route | a kanban `UnifiedStep` reaches the present `BridgeSlot` (surreal plan engaged by Cargo presence) | register a surreal slot; route a `step_type:"kanban.*"`; assert it lands; with slot absent assert graceful unhandled (not panic) | routes to wrong domain, or panics when slot absent | CONJECTURE (UnifiedStep has no SoA pointer — G1) | +| **S5** batch push | commit PUSHES a kanban update to the planner (not pull-only) | commit a row; assert a push/notify carrying the `KanbanMove` is observed | commit only notifies the optimizer (current state) → gap stays | GAP (measured: pull-only) | +| **S6** timeline view | `TimelineView` renders the kanbanview **zero-copy** (`FixedSizeBinary(512)` → `&[NodeRow]`) | store a node as `FixedSizeBinary(512)`; `node_rows_from_le_bytes` over the column buffer; assert ptr-identity (no copy) | `val` is variable `Binary` (current) → cannot zero-copy → NaN/copy | GAP (val is opaque Binary) | +| **S7** meta-awareness | the SoA carries its OWN wiring-completeness census in the `Meta` tenant | compute the run-NaN% of one cycle; write it as a free-energy/awareness field; assert it reads back and reflects the real gap count | census not written, or hard-coded (doesn't track real gaps) | CONJECTURE (the capstone itself) | + +## 4 — Waves (probe-ordered; no brick lands before its probe is green) + +- **Wave 0 — measure the baseline on SHIPPED code (no new code).** Instrument one + end-to-end cycle against `main` as it stands; produce the first real + `(piece-presence%, seam-wiring%, run-NaN%)` triple. **This is the honest + number behind "99/28/72."** Output: a `nan_census` example/bench + a recorded + baseline. Until this runs, "72% NaN" stays a HYPOTHESIS in this doc. +- **Wave 1 — S1 + S2** (the kanban tenant keystone + the MUL trigger). Build the + 8-byte tenant (gated on operator go), field-isolation test (S1 green), wire the + MUL→phase function (S2 green). Re-measure the triple; expect run-NaN to drop. +- **Wave 2 — S4 + S5 + S6** (envelope routing, batch push, `FixedSizeBinary(512)`). + The heavier surrealdb-side + contract envelope work. Each gated on its probe. +- **Wave 3 — S7** the meta-awareness self-census: the SoA writes its own run-NaN% + into `Meta`. The capstone — the system measuring its own wiring. + +## 5 — The honest AGI-adjacency framing (overclaim guard) + +What the green end-state proves: **the substrate can close an active-inference +loop over the SoA, per-SoA-owned, NaN-free, with the system carrying a census of +its own remaining gaps.** That is *adjacency* — the structure of the thing — not a +claim of AGI. The load-bearing word is **IF**: IF all seams go green AND run-NaN→0 +AND S7 self-census tracks real gaps, THEN we are at aspiration-adjacency. The plan +converts the IF into a measured fact; it never asserts the THEN. (truth-architect / +overclaim-auditor: AGI-adjacency is the hypothesis the census tests, not a headline.) + +The genuinely novel measurable: **S7 — a system aware of what it cannot yet do** +(the φ-1 permanent-humility ceiling, made a number in the `Meta` tenant). That +self-NaN-census is the "Orchestration meta-awareness plan" the operator named. + +## 6 — Cross-refs + +`canonical_node.rs` (`ValueTenant`/`VALUE_TENANTS` carve, free `[144,512)`), +`mul.rs` (Dk/Trust/Flow/Gate), `soa_view.rs` (`MailboxSoaOwner`/`View`), +`scheduler.rs` (`VersionScheduler`/`NextPhaseScheduler`), `cycle_accumulator.rs`, +`orchestration.rs` (`UnifiedStep`/`BridgeSlot`), surrealdb `core/src/kvs/lance/ +timeline.rs`; AGENT_LOG 2026-06-20 (cont.¹⁴ the two mapping passes); EPIPHANIES +`E-SURREALDB-SECOND-BRAIN-IS-ZERO-COPY-IFF-FIXEDSIZEBINARY`; the AGI-as-glove +doctrine (four columns ARE the surface); I-VSA-IDENTITIES (register laziness — +why thinking-style is ClassView + Meta, not a new 128-bit tenant). From dcdc1f9dbbffcb2c9f70d777968d9dfcdde4064a Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 23:24:43 +0000 Subject: [PATCH 2/4] =?UTF-8?q?feat(contract):=20kanban=C3=97Rubicon=20SoA?= =?UTF-8?q?=20value=20tenant=20+=20per-tenant=20counters=20(capstone=20S1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Operator-greenlit canon-additive change: make the Rubicon kanban phase a per-node SoA value tenant rather than an envelope field — pinning SoA↔kanban in the LE blob and subsuming the envelope-pointer plan (G1). - ValueTenant::Kanban = 9 at value-slab [112,120) (8 B: phase|exec|reserved|cycle), U64 descriptor @ row_offset 144; added to ValueSchema::{Cognitive,Full}. Reserve-don't-reclaim, layout-preserving: Full 112→120 B, NODE_ROW_STRIDE 512 untouched, no ENVELOPE_LAYOUT_VERSION bump. - KanbanTenant Copy view (phase: KanbanColumn, exec: ExecTarget, cycle: u32) + from_bytes/to_bytes (LE) + NodeRow::{kanban, set_kanban}. set_kanban is owner-only by convention (MailboxSoaOwner/View split); surreal reads, never writes (Rubicon). KanbanColumn::from_u8 + ExecTarget::from_u8. - tenant_counter module + feature `tenant-counters` (default OFF; zero-cost no-op via compile-time dispatch when off, one relaxed atomic per tenant write when on) — the capstone NaN-census / cascade-wiring instrument. set_kanban bumps the Kanban counter as the first wired cascade point. - Updated the 3 byte-budget LOCK tests to the deliberate new layout (Cognitive 58→66, Full 112→120, contiguous carve 112→120) + added Kanban to full-covers; new field-isolation matrix test + schema-membership test. Decisions kept (I-VSA-IDENTITIES register-laziness + AGI-glove): thinking-style is ClassView + the Meta tenant, NOT a new 128-bit tenant (the 48+80 flat split was rejected — duplicates Plasticity/Meta/Qualia); plan-shape stays ClassView-derived; the MUL flow-vs-mismatch trigger is a function over Qualia/Meta/Plasticity → GateDecision → advance phase, not a stored tenant. Verified: contract lib 714 (default) / 715 (tenant-counters) / 720 (guid-v2-tail); clippy -D warnings --all-targets clean all three; fmt clean; Full-carve math 120 B ends row 152 ≤ 512; no downstream crate hardcodes the old tenant count/budget. (cargo-machete not installed in sandbox; the only Cargo.toml change is a dep-less feature, so nothing to flag.) Capstone S1 → green (CONJECTURE→FINDING). Board: AGENT_LOG (cont.17), LATEST_STATE IN-PR entry, EPIPHANIES E-KANBAN-IS-A-VALUE-TENANT-SUBSUMES-G1, plan capstone S1 green. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01CcpLeEC3XK8Eye53GKBVvi --- .claude/board/AGENT_LOG.md | 10 ++ .claude/board/EPIPHANIES.md | 14 ++ .claude/board/LATEST_STATE.md | 2 + ...one-cognitive-loop-wiring-nan-census-v1.md | 2 +- crates/lance-graph-contract/Cargo.toml | 6 + .../src/canonical_node.rs | 150 +++++++++++++++++- crates/lance-graph-contract/src/kanban.rs | 31 ++++ crates/lance-graph-contract/src/lib.rs | 7 +- .../src/tenant_counter.rs | 75 +++++++++ 9 files changed, 287 insertions(+), 10 deletions(-) create mode 100644 crates/lance-graph-contract/src/tenant_counter.rs diff --git a/.claude/board/AGENT_LOG.md b/.claude/board/AGENT_LOG.md index 7dd6f848..e7cf4270 100644 --- a/.claude/board/AGENT_LOG.md +++ b/.claude/board/AGENT_LOG.md @@ -1,3 +1,13 @@ +## 2026-06-20 (cont.¹⁷) — kanban×Rubicon SoA tenant + per-tenant counters (capstone S1 green) + +**Main thread (Opus), autoattended.** Operator go on the canon-locked change ("first the kanban X Rubicon wired inside the SoA" + "add counters for each tenant"). Confirmed aiwar/OSINT/mixin-family on main first (q2 can test: `contract::aiwar` + `soa_graph::project_snapshot(&OSINT_GOTHAM)` → GraphSnapshot, family nodes = categories, cross-category edges → out_family `references` slots = the O(1) mixin model; example `aiwar_family_poc`). Built: +- **`ValueTenant::Kanban = 9`** at value-slab `[112,120)` (8 B, U64 descriptor @ row_offset 144), added to `ValueSchema::{Cognitive,Full}`. Reserve-don't-reclaim, layout-preserving (Full 112→120, stride 512 untouched, no version bump). +- **`KanbanTenant`** Copy view (phase/exec/cycle) + `from_bytes`/`to_bytes` (LE) + `NodeRow::{kanban,set_kanban}`; `KanbanColumn::from_u8` + `ExecTarget::from_u8`. Exported `KanbanTenant` from lib.rs. +- **`tenant_counter`** module + feature `tenant-counters` (default OFF, zero-cost no-op; one relaxed atomic/tenant-write when on) — the capstone NaN-census instrument; `set_kanban` bumps the Kanban counter. +- Updated the 3 byte-budget LOCK tests (their job: catch deliberate layout change) — Cognitive 58→66, Full 112→120, contiguous carve 112→120; added Kanban to the full-covers list. Field-isolation matrix test + schema-membership test added. +Verified: contract lib **714** default / **715** tenant-counters / **720** guid-v2-tail; clippy `-D warnings --all-targets` clean all three; fmt clean; Full-carve math 120 ends row 152. `cargo-machete` not installed in sandbox (only Cargo.toml change is a dep-less feature → nothing to flag). No downstream crate hardcodes the old tenant count/budget (grep clean). **Capstone S1 → green** (CONJECTURE→FINDING). EPIPHANY E-KANBAN-IS-A-VALUE-TENANT-SUBSUMES-G1. +**Deferred (named, NOT this PR):** the Singleton/BindSpace→MailboxSoA rewire (cognitive-shader-driver, W3/W4a `mailbox-thoughtspace` arc); BF16 perturbation-shader cascade (perturbation-sim, excluded crate); capstone Wave-0 NaN baseline. + ## 2026-06-20 (cont.¹⁶) — capstone validation plan: cognitive-loop wiring + NaN-census **Main thread (Opus), autoattended.** Rebased jirak onto main (98c0cf2a = #564 merged: lance-graph-ogar + node_rows_from_le_bytes). Resolved a stop-hook false-positive (98c0cf2a is GitHub's web-flow merge commit, GitHub-verified; fast-forwarded origin/jirak to it, no rewrite). Operator asked whether the kanban/MUL/orchestration wiring deserves a capstone VALIDATION plan that MEASURES the actual wiring ("99% there unused, 28% wiring gaps, 72% NaN"). Agreed — it's mandated by the measurement-before-synthesis + probe-first iron rules. Wrote `.claude/plans/capstone-cognitive-loop-wiring-nan-census-v1.md`: treats the operator's estimate as THREE orthogonal measured quantities (piece-presence% / seam-wiring% / run-NaN%); 7 seams S1..S7 each a CONJECTURE-until-probe-green (S1 kanban tenant, S2 MUL→phase, S3 version→move, S4 envelope route via BridgeSlot, S5 batch push [measured GAP: pull-only], S6 timeline zero-copy [GAP: val is opaque Binary], S7 SoA self-NaN-census = the "Orchestration meta-awareness"); Wave 0 = measure baseline on shipped code (the honest number behind 72% NaN). Overclaim-guard: AGI-adjacency is the IF the census TESTS, never the asserted THEN. Decided (I-VSA-IDENTITIES register-laziness + AGI-glove): thinking-style is ClassView+Meta, NOT a new 128-bit tenant; the 48+80 flat split was rejected (duplicates Plasticity/Meta/Qualia). Kanban tenant stays 8 B; still gated on operator go for the canon-locked region. Plan committed to jirak; INTEGRATION_PLANS prepended. diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 2f121dcc..4248cd8d 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,17 @@ +## 2026-06-20 — E-KANBAN-IS-A-VALUE-TENANT-SUBSUMES-G1 — making kanban×Rubicon a per-node SoA value tenant (`ValueTenant::Kanban`, 8 B at value-slab [112,120)) pins SoA↔kanban in the 512-byte LE blob and dissolves the envelope-pointer gap (G1): the node carries its OWN phase+cycle, so no `UnifiedStep` field is needed + +**Status:** FINDING (operator "brutal version" lock, 2026-06-20; shipped, capstone S1 green). + +The kanban phase was going to be carried by a new `UnifiedStep` pointer field (G1). Putting it IN the SoA instead is cleaner and subsumes G1: +- `ValueTenant::Kanban = 9` at row_offset 144 (value-slab `[112,120)`), 8 B: `phase(KanbanColumn u8) | exec(ExecTarget u8) | reserved(u16) | cycle(u32)`. Reserve-don't-reclaim (368 B were free), **layout-preserving** — Full 112→120 ≤ 480, stride still 512, no `ENVELOPE_LAYOUT_VERSION` bump. Field-isolation matrix test mandatory (I-LEGACY) — green. +- **Pins SoA↔kanban in the LE contract**: kanban is now a fixed byte range in the 512-byte blob → a `FixedSizeBinary(512)` store (surrealdb second brain) reads the kanban state zero-copy at any Lance version; the kanbanview = mailbox rows projected by the tenant's `phase`. +- **Owner-gated write / Rubicon read-only**: `NodeRow::set_kanban` is owner-only by convention (the `MailboxSoaOwner`/`View` split); surreal reads, never writes. +- **Decisions kept (I-VSA-IDENTITIES register-laziness + AGI-glove):** thinking-style is ClassView + the `Meta` tenant, NOT a new 128-bit tenant (the 48+80 flat split was rejected — duplicates Plasticity/Meta/Qualia). plan-shape stays ClassView-derived. The MUL flow-vs-mismatch trigger is a FUNCTION over Qualia/Meta/Plasticity → GateDecision → advance phase, not a stored tenant. + +Companion: `tenant_counter` (feature `tenant-counters`, default OFF, zero-cost no-op when off; one relaxed atomic per tenant write when on) — the per-tenant update-counter instrument the capstone NaN-census reads. `set_kanban` bumps the `Kanban` counter as the first wired cascade point. + +Verified: contract lib 714 (default) / 715 (tenant-counters) / 720 (guid-v2-tail), clippy `-D warnings` clean all three, fmt clean; Full carve math 120 B ends row 152 ≤ 512. Cross-ref: capstone plan S1; `E-SURREALDB-SECOND-BRAIN-IS-ZERO-COPY-IFF-FIXEDSIZEBINARY`; AGENT_LOG 2026-06-20 (cont.¹⁷). + ## 2026-06-20 — E-SURREALDB-SECOND-BRAIN-IS-ZERO-COPY-IFF-FIXEDSIZEBINARY — surrealdb (kv-lance) can become a zero-copy "second brain" inside lance-graph ONLY if it stores each node as an uncompressed `FixedSizeBinary(512)` LE blob; the contract a store satisfies is `node_rows_from_le_bytes(&[u8]) -> Option<&[NodeRow]>` (the inverse of `NodeRowPacket::as_le_bytes`), and a variable-length `Binary` column does NOT qualify **Status:** FINDING (brutal feasibility pass, 2026-06-20; contract primitive shipped, surrealdb side planned). diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 6146c965..22be2f35 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -16,6 +16,8 @@ --- +> **2026-06-20 — IN PR (`claude/jirak-math-theorems-harvest-rfii13`)** — **kanban×Rubicon SoA value tenant + per-tenant counters (capstone S1 green).** NEW `ValueTenant::Kanban = 9` at value-slab `[112,120)` (8 B: `phase|exec|reserved|cycle`), added to `ValueSchema::{Cognitive,Full}` — reserve-don't-reclaim, **layout-preserving** (Full 112→120 B, stride 512 untouched, no version bump). `KanbanTenant` Copy view + `NodeRow::{kanban,set_kanban}` (owner-gated write / surreal read-only / Rubicon); `KanbanColumn`/`ExecTarget` `from_u8`. **Subsumes the envelope-pointer G1** — the node carries its own phase+cycle, pinning SoA↔kanban in the LE blob (a `FixedSizeBinary(512)` store reads kanban zero-copy at any version). NEW `tenant_counter` module + feature `tenant-counters` (default OFF, zero-cost no-op; one relaxed atomic/tenant-write when on) — the capstone NaN-census instrument; `set_kanban` is the first wired cascade point. Decisions kept (I-VSA-IDENTITIES + AGI-glove): thinking-style is ClassView+`Meta`, NOT a 128-bit tenant; plan-shape ClassView-derived; MUL flow-trigger is a function, not a tenant. Contract lib **714**/715(tenant-counters)/720(guid-v2-tail), clippy `-D warnings` + fmt clean all three. Refs: AGENT_LOG (cont.¹⁷), EPIPHANIES `E-KANBAN-IS-A-VALUE-TENANT-SUBSUMES-G1`, plan `capstone-cognitive-loop-wiring-nan-census-v1` (S1 green). +> > **2026-06-20 — IN PR (`claude/jirak-math-theorems-harvest-rfii13`)** — **Zero-copy SoA read contract: `node_rows_from_le_bytes` (the surrealdb "second brain" primitive).** The inverse of `NodeRowPacket::as_le_bytes` (WRITE) — `canonical_node::node_rows_from_le_bytes(&[u8]) -> Option<&[NodeRow]>`, a CHECKED zero-copy cast (`len % 512 == 0` AND `ptr % 64 == 0`, else `None` → caller copies, no UB; empty→Some(empty)). This IS the LE contract a backing store satisfies so its bytes ARE the SoA the cognitive shader reads in place. **Brutal verdict:** lance-graph side now zero-copy-ready end-to-end; surrealdb's kv-lance does NOT qualify as scaffolded (`val: DataType::Binary` variable-length → needs `FixedSizeBinary(512)`), and value zero-copy holds only if stored UNcompressed (key/address always zero-copy). 712 contract lib green, clippy `-D warnings` both configs + fmt clean. Refs: AGENT_LOG 2026-06-20 (cont.¹⁴), EPIPHANIES `E-SURREALDB-SECOND-BRAIN-IS-ZERO-COPY-IFF-FIXEDSIZEBINARY`. > > **2026-06-20 — IN PR (`claude/jirak-math-theorems-harvest-rfii13`)** — **Clean separation: NEW `lance-graph-ogar` activation crate (OGAR Active-Record surface).** The OGAR half of `ontology=OGIT / ogar=OGAR`. OGAR is the AR Core and ALREADY `impl`s the contract: `ogar-class-view::OgarClassView impl lance_graph_contract::ClassView` (32 concepts), `ogar-vocab::Class` = AR shape, `canonical_concept_id == ClassId`. NEW `crates/lance-graph-ogar` (EXCLUDED, own `[workspace]`, git-deps OGAR@main + lance-graph-contract@main = ONE source, no `[patch]`) re-exports the full AR surface (ogar-vocab + ogar-class-view + ogar-ontology + ogar-adapter-surrealql) + a **parity-guard** (`assert_codebook_parity`: bijective `ogar_codebook::CODEBOOK ⇄ ogar_vocab::class_ids::ALL` + domain agreement, FAILS build on drift). Features: `default` (light, emit-only), `surrealql-parser` (parser half), `serde`. **Auto-activation = Cargo presence**: pull the crate → real OGAR AR + drift fuse; don't → contract's zero-dep mirror + bare ClassView trait (OGAR stays headless). `cargo test --manifest-path crates/lance-graph-ogar/Cargo.toml` **3/3** green, clippy + fmt clean, contract = ONE source (git main #ff1a3452). Refs: AGENT_LOG 2026-06-20 (cont.¹³), EPIPHANIES `E-OGAR-IS-AR-CORE-AUTOACTIVATED-BY-CARGO-PRESENCE`, plan D-OVC-5. **(#563 D-OVC contract realign now MERGED to main.)** diff --git a/.claude/plans/capstone-cognitive-loop-wiring-nan-census-v1.md b/.claude/plans/capstone-cognitive-loop-wiring-nan-census-v1.md index 331b243e..d5a99690 100644 --- a/.claude/plans/capstone-cognitive-loop-wiring-nan-census-v1.md +++ b/.claude/plans/capstone-cognitive-loop-wiring-nan-census-v1.md @@ -57,7 +57,7 @@ SoA node (Kanban tenant: phase) ── S1 | Seam | Claim | Probe (pass/fail) | KILL condition | Status | |---|---|---|---|---| -| **S1** kanban tenant | `ValueTenant::Kanban` at `[144,152)` carries phase+cycle+exec, layout-preserving | field-isolation matrix: write each tenant, assert all others unchanged; `NodeRowPacket` round-trips kanban byte-exact | stride ≠ 512, or any other tenant perturbed | CONJECTURE | +| **S1** kanban tenant | `ValueTenant::Kanban` at `[144,152)` carries phase+cycle+exec, layout-preserving | field-isolation matrix: write each tenant, assert all others unchanged; `NodeRowPacket` round-trips kanban byte-exact | stride ≠ 512, or any other tenant perturbed | ✅ **FINDING (green 2026-06-20)** — shipped `ValueTenant::Kanban`/`KanbanTenant`/`NodeRow::{kanban,set_kanban}`; field-isolation + schema-membership tests pass; Full 112→120 ≤ 480, stride 512. + `tenant_counter` instrument. | | **S2** MUL→phase | `MUL::GateDecision(Qualia,Meta,Plasticity)` mismatch ⇒ owner advances phase | feed a known flow-vs-mismatch qualia vector; assert the gate returns the expected `KanbanMove` (or HOLD) | gate ignores qualia (constant output), or reads uninitialized → NaN | CONJECTURE | | **S3** version→move | `VersionScheduler::on_version` lowers a real Lance version to the next legal `KanbanMove` | drive a 2-version dataset; assert the forward-arc move emitted | no move on a legal transition, or illegal edge emitted | CONJECTURE (type exists, unwired) | | **S4** envelope route | a kanban `UnifiedStep` reaches the present `BridgeSlot` (surreal plan engaged by Cargo presence) | register a surreal slot; route a `step_type:"kanban.*"`; assert it lands; with slot absent assert graceful unhandled (not panic) | routes to wrong domain, or panics when slot absent | CONJECTURE (UnifiedStep has no SoA pointer — G1) | diff --git a/crates/lance-graph-contract/Cargo.toml b/crates/lance-graph-contract/Cargo.toml index 58f985dc..7ea09de7 100644 --- a/crates/lance-graph-contract/Cargo.toml +++ b/crates/lance-graph-contract/Cargo.toml @@ -42,3 +42,9 @@ trajectory-audit = [] # (`leaf`/`*_v2`) coexist with v1 until cutover (D-GV2-5). Layout reclaim → # I-LEGACY-API-FEATURE-GATED (field-isolation matrix + version gate). guid-v2-tail = [] + +# tenant-counters — per-ValueTenant update counters for debug instrumentation of +# the SoA write cascade (the capstone NaN-census / seam-wiring measurement). OFF +# by default: `tenant_counter::tenant_update` is a zero-cost no-op unless enabled +# (compile-time dispatch). On: one relaxed atomic increment per tenant write. +tenant-counters = [] diff --git a/crates/lance-graph-contract/src/canonical_node.rs b/crates/lance-graph-contract/src/canonical_node.rs index daeeba41..f79e436c 100644 --- a/crates/lance-graph-contract/src/canonical_node.rs +++ b/crates/lance-graph-contract/src/canonical_node.rs @@ -479,6 +479,7 @@ const _: () = assert!(core::mem::size_of::() == 512); // ── SoaEnvelope binding for [NodeRow] ──────────────────────────────────────── use crate::class_view::FieldMask; +use crate::kanban::{ExecTarget, KanbanColumn}; use crate::soa_envelope::{ColumnDescriptor, ColumnKind, SoaEnvelope}; use std::collections::HashMap; use std::sync::LazyLock; @@ -563,6 +564,13 @@ pub enum ValueTenant { Plasticity = 7, /// OGIT entity-type / class discriminator (`u16`). EntityType = 8, + /// kanban×Rubicon phase cursor (8 B): `phase` (KanbanColumn) + `exec` + /// (ExecTarget) + reserved + `cycle` (u32). The per-node Rubicon lifecycle + /// column — owner-advanced (the `MailboxSoaOwner` split), surreal-read (the + /// Rubicon "view never mutates" projection), q2 renders columns by `phase`. + /// Pins SoA↔kanban in the LE blob at value-slab `[112,120)` (subsumes the + /// envelope-pointer G1: the node carries its own phase + cycle). + Kanban = 9, } impl ValueTenant { @@ -646,6 +654,14 @@ pub const VALUE_TENANTS: &[ColumnDescriptor] = &[ elems_per_row: 1, row_offset: 142, }, + ColumnDescriptor { + // kanban×Rubicon cursor: 8 B contiguous at row_offset 144 (value-slab + // [112,120)); reserve-don't-reclaim, layout-preserving (Full ends 152 ≤ 480). + name_id: ValueTenant::Kanban as u16, + kind: ColumnKind::U64, + elems_per_row: 1, + row_offset: 144, + }, ]; // Compile-time canon: VALUE_TENANTS is discriminant-ordered, contiguous within the @@ -716,6 +732,7 @@ impl ValueSchema { ValueTenant::Energy as u8, ValueTenant::Plasticity as u8, ValueTenant::EntityType as u8, + ValueTenant::Kanban as u8, ]), ValueSchema::Compressed => FieldMask::from_positions(&[ ValueTenant::Fingerprint as u8, @@ -733,6 +750,7 @@ impl ValueSchema { ValueTenant::Energy as u8, ValueTenant::Plasticity as u8, ValueTenant::EntityType as u8, + ValueTenant::Kanban as u8, ]), } } @@ -1016,10 +1034,125 @@ pub fn node_rows_from_le_bytes(bytes: &[u8]) -> Option<&[NodeRow]> { Some(unsafe { core::slice::from_raw_parts(bytes.as_ptr().cast::(), n) }) } +// ── kanban×Rubicon value tenant (the per-node phase cursor) ─────────────────── + +/// The kanban×Rubicon phase cursor stored in [`ValueTenant::Kanban`] — 8 bytes at +/// value-slab `[112, 120)`, LE: `phase(u8) | exec(u8) | reserved(u16) | cycle(u32)`. +/// +/// Per-node Rubicon lifecycle: `phase` advances along the [`KanbanColumn`] DAG +/// (**owner-only** write — the `MailboxSoaOwner`/`View` split is what makes "the +/// view never mutates the SoA" a compile-time guarantee), `exec` names the +/// dispatch backend ([`ExecTarget`]), `cycle` is the owner's `current_cycle` +/// stamp. A `Copy` microcopy: read zero-copy from the slab, written only on phase +/// advance. surrealdb projects it as the kanban view (read-only, Rubicon); q2 +/// renders columns by `phase`. Because it lives IN the node, SoA↔kanban is pinned +/// in the 512-byte LE blob — no separate envelope pointer needed (subsumes G1). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct KanbanTenant { + /// Rubicon lifecycle column. + pub phase: KanbanColumn, + /// Dispatch backend the planner selected. + pub exec: ExecTarget, + /// Owner `current_cycle` stamp at the last phase write. + pub cycle: u32, +} + +impl KanbanTenant { + /// Decode from the 8 tenant bytes (LE; unknown phase/exec discriminants fall + /// back to their zero defaults via [`KanbanColumn::from_u8`]/[`ExecTarget::from_u8`]). + #[inline] + #[must_use] + pub fn from_bytes(b: [u8; 8]) -> Self { + Self { + phase: KanbanColumn::from_u8(b[0]), + exec: ExecTarget::from_u8(b[1]), + // b[2..4] reserved + cycle: u32::from_le_bytes([b[4], b[5], b[6], b[7]]), + } + } + + /// Encode to the 8 tenant bytes (LE). + #[inline] + #[must_use] + pub fn to_bytes(self) -> [u8; 8] { + let c = self.cycle.to_le_bytes(); + [ + self.phase as u8, + self.exec as u8, + 0, + 0, + c[0], + c[1], + c[2], + c[3], + ] + } +} + +impl NodeRow { + /// Read the [`KanbanTenant`] phase cursor from the [`ValueTenant::Kanban`] + /// slab bytes — zero-copy decode, `Copy` result. The per-node Rubicon phase. + #[inline] + #[must_use] + pub fn kanban(&self) -> KanbanTenant { + let o = ValueTenant::Kanban.value_offset(); + let mut b = [0u8; 8]; + b.copy_from_slice(&self.value[o..o + 8]); + KanbanTenant::from_bytes(b) + } + + /// Write the [`KanbanTenant`] into the slab. **Owner-only by convention** (the + /// `MailboxSoaOwner` phase-advance path); reads stay zero-copy. Bumps the + /// `Kanban` per-tenant update counter (no-op unless `tenant-counters`). + #[inline] + pub fn set_kanban(&mut self, k: KanbanTenant) { + let o = ValueTenant::Kanban.value_offset(); + self.value[o..o + 8].copy_from_slice(&k.to_bytes()); + crate::tenant_counter::tenant_update(ValueTenant::Kanban); + } +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn kanban_tenant_round_trip_and_field_isolation() { + let mut row = NodeRow { + key: NodeGuid::local(1), + edges: EdgeBlock::default(), + value: [0xABu8; 480], + }; + let before = row.value; + let k = KanbanTenant { + phase: KanbanColumn::CognitiveWork, + exec: ExecTarget::SurrealQl, + cycle: 0xDEAD_BEEF, + }; + row.set_kanban(k); + assert_eq!(row.kanban(), k, "kanban tenant round-trips"); + let o = ValueTenant::Kanban.value_offset(); + assert_eq!(o, 112, "kanban tenant at value-slab [112,120)"); + for (i, (&now, &was)) in row.value.iter().zip(before.iter()).enumerate() { + if (o..o + 8).contains(&i) { + continue; + } + assert_eq!( + now, was, + "byte {i} outside the kanban tenant must not change" + ); + } + } + + #[test] + fn kanban_in_cognitive_and_full_schemas() { + assert!(ValueSchema::Cognitive.has(ValueTenant::Kanban)); + assert!(ValueSchema::Full.has(ValueTenant::Kanban)); + assert!(!ValueSchema::Bootstrap.has(ValueTenant::Kanban)); + assert!(ValueSchema::Full.tenant_bytes() <= VALUE_SLAB_LEN); + assert_eq!(ValueTenant::Kanban.byte_len(), 8); + } + #[test] fn node_rows_le_bytes_round_trip_zero_copy() { // Build a small SoA, view it as LE bytes (the write path), then read it @@ -1366,8 +1499,8 @@ mod tests { assert!(prev_end <= NODE_ROW_STRIDE); assert_eq!( prev_end - VALUE_SLAB_ROW_OFFSET, - 112, - "current Full carve uses 112 of 480 B (helix right-sized 48→6)" + 120, + "current Full carve uses 120 of 480 B (helix 6 + kanban×Rubicon tenant 8)" ); assert!(prev_end - VALUE_SLAB_ROW_OFFSET <= VALUE_SLAB_LEN); } @@ -1393,6 +1526,7 @@ mod tests { ValueTenant::Energy, ValueTenant::Plasticity, ValueTenant::EntityType, + ValueTenant::Kanban, ] { assert!(full.has(t), "Full must materialise {t:?}"); } @@ -1401,9 +1535,11 @@ mod tests { #[test] fn value_schema_byte_budgets_are_locked() { assert_eq!(ValueSchema::Bootstrap.tenant_bytes(), 0); - assert_eq!(ValueSchema::Cognitive.tenant_bytes(), 58); + // Cognitive 58 + Kanban 8 = 66; Full 112 + Kanban 8 = 120 (kanban×Rubicon + // tenant added — reserve-don't-reclaim, still ≤ 480, stride unchanged). + assert_eq!(ValueSchema::Cognitive.tenant_bytes(), 66); assert_eq!(ValueSchema::Compressed.tenant_bytes(), 56); - assert_eq!(ValueSchema::Full.tenant_bytes(), 112); + assert_eq!(ValueSchema::Full.tenant_bytes(), 120); for s in [ ValueSchema::Bootstrap, ValueSchema::Cognitive, @@ -1511,7 +1647,7 @@ mod tests { #[test] fn default_class_node_materialises_full_slab() { // End-to-end connect: a bootstrap NodeRow → its classid resolves to Full → - // the Full preset covers every tenant and uses the locked 112-byte carve. + // the Full preset covers every tenant and uses the locked 120-byte carve. let row = sample_row(NodeGuid::CLASSID_DEFAULT, 0x00_00CD); let rm = row.key.read_mode(); assert_eq!(rm.value_schema, ValueSchema::Full); @@ -1520,8 +1656,8 @@ mod tests { VALUE_TENANTS.len(), "Full read-mode materialises every value tenant" ); - assert_eq!(rm.value_schema.tenant_bytes(), 112); - // The slab has room (112 ≤ 480) and the choice never grows the stride. + assert_eq!(rm.value_schema.tenant_bytes(), 120); + // The slab has room (120 ≤ 480) and the choice never grows the stride. assert!(rm.value_schema.tenant_bytes() <= VALUE_SLAB_LEN); assert!(rm.is_layout_preserving()); } diff --git a/crates/lance-graph-contract/src/kanban.rs b/crates/lance-graph-contract/src/kanban.rs index cd3eb46c..78aa5f05 100644 --- a/crates/lance-graph-contract/src/kanban.rs +++ b/crates/lance-graph-contract/src/kanban.rs @@ -102,6 +102,22 @@ impl KanbanColumn { pub fn can_transition_to(self, to: KanbanColumn) -> bool { self.next_phases().contains(&to) } + + /// Decode a stored discriminant (e.g. the `ValueTenant::Kanban` phase byte). + /// Unknown values fall back to [`Planning`](KanbanColumn::Planning) — the + /// zero-fallback default, consistent with the canon ladder. + #[inline] + #[must_use] + pub fn from_u8(v: u8) -> Self { + match v { + 1 => Self::CognitiveWork, + 2 => Self::Evaluation, + 3 => Self::Commit, + 4 => Self::Plan, + 5 => Self::Prune, + _ => Self::Planning, + } + } } /// One kanban transition: the planner's output unit and the ractor's lifecycle step. @@ -166,6 +182,21 @@ pub enum ExecTarget { Elixir = 3, } +impl ExecTarget { + /// Decode a stored discriminant (e.g. the `ValueTenant::Kanban` exec byte). + /// Unknown values fall back to [`Native`](ExecTarget::Native), the default. + #[inline] + #[must_use] + pub fn from_u8(v: u8) -> Self { + match v { + 1 => Self::Jit, + 2 => Self::SurrealQl, + 3 => Self::Elixir, + _ => Self::Native, + } + } +} + /// Error from [`crate::soa_view::MailboxSoaOwner::try_advance_phase`] when a requested /// transition is not a legal Rubicon lifecycle edge ([`KanbanColumn::can_transition_to`]). #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/crates/lance-graph-contract/src/lib.rs b/crates/lance-graph-contract/src/lib.rs index 4b2ce8c2..ed0a2fc2 100644 --- a/crates/lance-graph-contract/src/lib.rs +++ b/crates/lance-graph-contract/src/lib.rs @@ -109,6 +109,8 @@ pub mod soa_graph; pub mod soa_view; pub mod splat; pub mod tax; +/// Per-tenant SoA update counters — debug instrumentation (feature `tenant-counters`). +pub mod tenant_counter; pub mod thinking; pub mod unichar; pub mod unicharset; @@ -121,8 +123,9 @@ pub mod world_model; // Re-exports for the most commonly used collapse_gate types. pub use canonical_node::{ - classid_read_mode, node_rows_from_le_bytes, EdgeBlock, EdgeCodecFlavor, GuidParts, NodeGuid, - NodeRow, NodeRowPacket, ReadMode, ValueSchema, ValueTenant, VALUE_TENANTS, + classid_read_mode, node_rows_from_le_bytes, EdgeBlock, EdgeCodecFlavor, GuidParts, + KanbanTenant, NodeGuid, NodeRow, NodeRowPacket, ReadMode, ValueSchema, ValueTenant, + VALUE_TENANTS, }; pub use class_view::{ClassId, ClassProjection, ClassView, FieldMask, RenderRow}; pub use collapse_gate::{GateDecision, MailboxId, MergeMode}; diff --git a/crates/lance-graph-contract/src/tenant_counter.rs b/crates/lance-graph-contract/src/tenant_counter.rs new file mode 100644 index 00000000..b0547939 --- /dev/null +++ b/crates/lance-graph-contract/src/tenant_counter.rs @@ -0,0 +1,75 @@ +//! `tenant_counter` — per-[`ValueTenant`](crate::canonical_node::ValueTenant) +//! update counters for cheap debug instrumentation of the SoA write cascade. +//! +//! The "easy debug" lever for the capstone NaN-census / wiring measurement: each +//! tenant write bumps an atomic counter, so a probe can read which tenants the +//! update cascade actually touched (and how often) in one cycle — the runtime +//! evidence behind seam-wiring% / run-NaN%. +//! +//! **Compile-time dispatch.** Gated behind the `tenant-counters` feature: when +//! off, [`tenant_update`] is a `#[inline]` no-op (the argument is consumed, the +//! whole call optimizes away — zero cost). When on, it is one relaxed atomic +//! increment into a `LazyLock<[AtomicU64; N]>`. Reads ([`tenant_count`], +//! [`snapshot`]) exist only under the feature. +//! +//! Wire pattern: every tenant setter calls `tenant_update(ValueTenant::X)` (e.g. +//! [`NodeRow::set_kanban`](crate::canonical_node::NodeRow::set_kanban)). As more +//! setters are wired, the cascade becomes self-measuring. + +use crate::canonical_node::ValueTenant; + +/// Number of distinct [`ValueTenant`] positions the counter array covers. +/// Must be ≥ the highest `ValueTenant` discriminant + 1. +pub const N_TENANTS: usize = 10; + +#[cfg(feature = "tenant-counters")] +static TENANT_COUNTERS: std::sync::LazyLock<[std::sync::atomic::AtomicU64; N_TENANTS]> = + std::sync::LazyLock::new(|| std::array::from_fn(|_| std::sync::atomic::AtomicU64::new(0))); + +/// Record one update to `tenant`. **No-op unless the `tenant-counters` feature is +/// enabled** (compile-time dispatch — the call optimizes away when off). +#[inline] +pub fn tenant_update(tenant: ValueTenant) { + #[cfg(feature = "tenant-counters")] + { + TENANT_COUNTERS[tenant as usize].fetch_add(1, std::sync::atomic::Ordering::Relaxed); + } + #[cfg(not(feature = "tenant-counters"))] + { + let _ = tenant; + } +} + +/// The current update count for `tenant`. Only available under `tenant-counters`. +#[cfg(feature = "tenant-counters")] +#[must_use] +pub fn tenant_count(tenant: ValueTenant) -> u64 { + TENANT_COUNTERS[tenant as usize].load(std::sync::atomic::Ordering::Relaxed) +} + +/// A snapshot of all per-tenant counters (indexed by `ValueTenant as usize`). +/// Only available under `tenant-counters`. +#[cfg(feature = "tenant-counters")] +#[must_use] +pub fn snapshot() -> [u64; N_TENANTS] { + std::array::from_fn(|i| TENANT_COUNTERS[i].load(std::sync::atomic::Ordering::Relaxed)) +} + +#[cfg(all(test, feature = "tenant-counters"))] +mod tests { + use super::*; + + #[test] + fn counter_increments_for_its_tenant_only() { + let before = tenant_count(ValueTenant::Meta); + tenant_update(ValueTenant::Meta); + tenant_update(ValueTenant::Meta); + assert_eq!(tenant_count(ValueTenant::Meta), before + 2); + // snapshot is consistent with the per-tenant read + let snap = snapshot(); + assert_eq!( + snap[ValueTenant::Meta as usize], + tenant_count(ValueTenant::Meta) + ); + } +} From f045d7fb177e0e1915c58d558facda9f11ac5174 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 23:38:24 +0000 Subject: [PATCH 3/4] fix(symbiont): wire lance-graph-ogar into the golden image (+ contract patch) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The golden image was missing lance-graph-ogar: symbiont git-dep'd raw ogar-vocab/ogar-ontology/ogar-adapter-surrealql but NOT the activation crate, so it never linked ogar-class-view (impl lance_graph_contract::ClassView) or the codebook parity-guard — i.e. the unified binary wasn't proving the OGAR Active-Record surface compiles. - add `lance-graph-ogar = { path = "../lance-graph-ogar", features = ["surrealql-parser"] }` (transitively pulls ogar-class-view + the parity guard). - add `[patch."https://github.com/AdaWorldAPI/lance-graph"] lance-graph-contract = { path = "../lance-graph-contract" }` — REQUIRED (PR #564 CONSUMER REQUIREMENT): symbiont is the workspace root, so lance-graph-ogar's own patch is ignored; ogar-class-view git-deps contract@main and must unify onto symbiont's path copy, else OgarClassView's `impl ClassView` won't typecheck against the engine's. Docker needs no change: the Dockerfile runs `cargo build` on symbiont's manifest and lance-graph-ogar is in-repo (already COPY'd in) — cargo-missing == docker-missing. Verified by resolve (`cargo metadata` exit 0): lance-graph-ogar + ogar-class-view now in the golden-image graph; lance-graph-contract instances = 1 (the path copy) → patch folded the git contract onto one source; no "patch not used" warning. Full compile is the Railway/CI build-validation job (protoc + lance7 + surrealdb). Board: AGENT_LOG (cont.18). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01CcpLeEC3XK8Eye53GKBVvi --- .claude/board/AGENT_LOG.md | 7 +++++++ crates/symbiont/Cargo.toml | 26 +++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/.claude/board/AGENT_LOG.md b/.claude/board/AGENT_LOG.md index e7cf4270..a6eb7ee2 100644 --- a/.claude/board/AGENT_LOG.md +++ b/.claude/board/AGENT_LOG.md @@ -1,3 +1,10 @@ +## 2026-06-20 (cont.¹⁸) — golden image was MISSING lance-graph-ogar — wired it (+ contract [patch]) + +**Main thread (Opus), autoattended.** Operator: "check if the golden image is missing lance-graph-ogar from cargo/docker." **Confirmed missing:** `crates/symbiont/Cargo.toml` git-dep'd raw `ogar-vocab`/`ogar-ontology`/`ogar-adapter-surrealql` but NOT `lance-graph-ogar` (nor `ogar-class-view`, nor the parity-guard) — so the golden image was NOT proving the AR `ClassView` bridge or the codebook drift-guard link in the unified binary. Docker has no separate gap: `Dockerfile` just runs `cargo build --release` on symbiont's manifest (+ clones ndarray sibling); lance-graph-ogar is in-repo (`COPY . /build/lance-graph`), so cargo-missing = docker-missing, one fix. + +**Fix:** added `lance-graph-ogar = { path = "../lance-graph-ogar", features = ["surrealql-parser"] }` + the mandatory `[patch."https://github.com/AdaWorldAPI/lance-graph"] lance-graph-contract = { path = "../lance-graph-contract" }` (the CONSUMER REQUIREMENT from PR #564: symbiont is the workspace root so lance-graph-ogar's own patch is ignored; ogar-class-view git-deps contract@main and must unify onto symbiont's path copy or the impl ClassView won't typecheck). Kept the 3 raw ogar deps (resolve to the same git#main source via the activation crate's re-export — no conflict; collapsing them into the one activation dep is a future cleanup). + +**Verified by resolve** (`cargo metadata --manifest-path crates/symbiont/Cargo.toml`, exit 0): `lance-graph-ogar` + `ogar-class-view` now IN the golden-image graph; **lance-graph-contract instances = 1** (source=None = path copy) → the [patch] folded the git contract onto the one path copy; **no "patch not used" warning** (P0 policy clean). Full compile is the Railway/CI build-validation job (protoc + lance7 + surrealdb tree). symbiont Cargo.lock stays gitignored (living harness). Branch jirak. ## 2026-06-20 (cont.¹⁷) — kanban×Rubicon SoA tenant + per-tenant counters (capstone S1 green) **Main thread (Opus), autoattended.** Operator go on the canon-locked change ("first the kanban X Rubicon wired inside the SoA" + "add counters for each tenant"). Confirmed aiwar/OSINT/mixin-family on main first (q2 can test: `contract::aiwar` + `soa_graph::project_snapshot(&OSINT_GOTHAM)` → GraphSnapshot, family nodes = categories, cross-category edges → out_family `references` slots = the O(1) mixin model; example `aiwar_family_poc`). Built: diff --git a/crates/symbiont/Cargo.toml b/crates/symbiont/Cargo.toml index 1104d67f..1d642bee 100644 --- a/crates/symbiont/Cargo.toml +++ b/crates/symbiont/Cargo.toml @@ -56,8 +56,28 @@ ogar-vocab = { git = "https://github.com/AdaWorldAPI/OGAR", branch = "main" } ogar-ontology = { git = "https://github.com/AdaWorldAPI/OGAR", branch = "main" } ogar-adapter-surrealql = { git = "https://github.com/AdaWorldAPI/OGAR", branch = "main", features = ["surrealdb-parser"] } -# No [patch] needed: this dep and OGAR's transitive surrealdb (ast/parser/core) -# all resolve to https://github.com/AdaWorldAPI/surrealdb#main — ONE source — -# and cargo unions the features so kv-lance is enabled on the shared +# The OGAR activation crate (in-repo, path) — the golden image's whole point is to +# prove the FULL AR surface links: ogar-class-view's `impl lance_graph_contract:: +# ClassView` + the codebook parity-guard, not just raw ogar-vocab. Pulling this +# crate (which transitively pulls ogar-class-view) is what makes the unified binary +# exercise the Active-Record bridge. features=["surrealql-parser"] matches the +# ogar-adapter-surrealql parser pin above (one surrealdb#main source). +lance-graph-ogar = { path = "../lance-graph-ogar", features = ["surrealql-parser"] } + +# ── [patch] — unify lance-graph-contract to the ONE in-repo path copy ── +# CONSUMER REQUIREMENT (codex P2, PR #564): `ogar-class-view` (pulled via +# lance-graph-ogar) hard-git-deps `lance-graph-contract` @ AdaWorldAPI/lance-graph#main, +# while this crate + lance-graph path-dep it. Cargo treats path and git as DISTINCT +# crates even at the same version, so without this patch `OgarClassView` would impl +# a DIFFERENT `ClassView` than the engine expects (won't typecheck). The patch +# folds the git contract onto the path copy = exactly one contract source. +# (Applies here because symbiont is the workspace root; lance-graph-ogar's own +# identical patch is ignored when it is not the root.) +[patch."https://github.com/AdaWorldAPI/lance-graph"] +lance-graph-contract = { path = "../lance-graph-contract" } + +# No surrealdb [patch] needed: this crate's surrealdb-core and OGAR's transitive +# surrealdb (ast/parser/core) all resolve to https://github.com/AdaWorldAPI/surrealdb#main +# — ONE source — and cargo unions the features so kv-lance is enabled on the shared # surrealdb-core. (cargo forbids patching a git URL to the same git URL, which is # why the earlier jirak-redirect patch failed; aligning on main removes the need.) From 379f7ba3828cf03e524e240e3c3fe72693ac5dcf Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 20 Jun 2026 23:56:15 +0000 Subject: [PATCH 4/4] chore(contract): address CodeRabbit review on #565 (3 nits) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tenant_counter: derive N_TENANTS from crate::canonical_node::VALUE_TENANTS.len() instead of the hard-coded 10 — drift-proof, no out-of-bounds on `tenant as usize` if a ValueTenant is later added (CodeRabbit quick-win). - capstone plan: bare ``` fence → ```text (markdownlint MD040). - capstone plan S1: clarify `row_offset 144` (`[112,120)` in the value slab) to remove the row-offset vs value-slab coordinate ambiguity CodeRabbit flagged. Validated: contract lib 714 (default) / 715 (tenant-counters), clippy -D warnings clean. Pure refinements, no behavior change (VALUE_TENANTS.len() == 10 today). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01CcpLeEC3XK8Eye53GKBVvi --- .../capstone-cognitive-loop-wiring-nan-census-v1.md | 4 ++-- crates/lance-graph-contract/src/tenant_counter.rs | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.claude/plans/capstone-cognitive-loop-wiring-nan-census-v1.md b/.claude/plans/capstone-cognitive-loop-wiring-nan-census-v1.md index d5a99690..086cb729 100644 --- a/.claude/plans/capstone-cognitive-loop-wiring-nan-census-v1.md +++ b/.claude/plans/capstone-cognitive-loop-wiring-nan-census-v1.md @@ -40,7 +40,7 @@ with **seam-wiring → 100%**, each step backed by a probe number. ## 2 — The loop under test (one cycle) -``` +```text SoA node (Kanban tenant: phase) ── S1 → MUL reads Qualia(flow/trust/DK)+Meta(NARS/FE)+Plasticity ── S2 → GateDecision (flow vs mismatch) ── S2 @@ -57,7 +57,7 @@ SoA node (Kanban tenant: phase) ── S1 | Seam | Claim | Probe (pass/fail) | KILL condition | Status | |---|---|---|---|---| -| **S1** kanban tenant | `ValueTenant::Kanban` at `[144,152)` carries phase+cycle+exec, layout-preserving | field-isolation matrix: write each tenant, assert all others unchanged; `NodeRowPacket` round-trips kanban byte-exact | stride ≠ 512, or any other tenant perturbed | ✅ **FINDING (green 2026-06-20)** — shipped `ValueTenant::Kanban`/`KanbanTenant`/`NodeRow::{kanban,set_kanban}`; field-isolation + schema-membership tests pass; Full 112→120 ≤ 480, stride 512. + `tenant_counter` instrument. | +| **S1** kanban tenant | `ValueTenant::Kanban` at `row_offset 144` (`[112,120)` in the value slab) carries phase+cycle+exec, layout-preserving | field-isolation matrix: write each tenant, assert all others unchanged; `NodeRowPacket` round-trips kanban byte-exact | stride ≠ 512, or any other tenant perturbed | ✅ **FINDING (green 2026-06-20)** — shipped `ValueTenant::Kanban`/`KanbanTenant`/`NodeRow::{kanban,set_kanban}`; field-isolation + schema-membership tests pass; Full 112→120 ≤ 480, stride 512. + `tenant_counter` instrument. | | **S2** MUL→phase | `MUL::GateDecision(Qualia,Meta,Plasticity)` mismatch ⇒ owner advances phase | feed a known flow-vs-mismatch qualia vector; assert the gate returns the expected `KanbanMove` (or HOLD) | gate ignores qualia (constant output), or reads uninitialized → NaN | CONJECTURE | | **S3** version→move | `VersionScheduler::on_version` lowers a real Lance version to the next legal `KanbanMove` | drive a 2-version dataset; assert the forward-arc move emitted | no move on a legal transition, or illegal edge emitted | CONJECTURE (type exists, unwired) | | **S4** envelope route | a kanban `UnifiedStep` reaches the present `BridgeSlot` (surreal plan engaged by Cargo presence) | register a surreal slot; route a `step_type:"kanban.*"`; assert it lands; with slot absent assert graceful unhandled (not panic) | routes to wrong domain, or panics when slot absent | CONJECTURE (UnifiedStep has no SoA pointer — G1) | diff --git a/crates/lance-graph-contract/src/tenant_counter.rs b/crates/lance-graph-contract/src/tenant_counter.rs index b0547939..0b3ca8f4 100644 --- a/crates/lance-graph-contract/src/tenant_counter.rs +++ b/crates/lance-graph-contract/src/tenant_counter.rs @@ -18,9 +18,13 @@ use crate::canonical_node::ValueTenant; -/// Number of distinct [`ValueTenant`] positions the counter array covers. -/// Must be ≥ the highest `ValueTenant` discriminant + 1. -pub const N_TENANTS: usize = 10; +/// Number of distinct [`ValueTenant`] positions the counter array covers — +/// derived from the canonical carve so it can never drift out of sync with the +/// enum (adding a `ValueTenant` grows `VALUE_TENANTS`, which grows this, which +/// grows the counter array — no hand-maintained constant to forget, no +/// out-of-bounds on `tenant as usize`). Equals the highest discriminant + 1 +/// because `VALUE_TENANTS` is contiguous discriminant-ordered (canon-asserted). +pub const N_TENANTS: usize = crate::canonical_node::VALUE_TENANTS.len(); #[cfg(feature = "tenant-counters")] static TENANT_COUNTERS: std::sync::LazyLock<[std::sync::atomic::AtomicU64; N_TENANTS]> =