diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index b18d72de..acc340e2 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,78 @@ +## 2026-06-18 — E-OGAR-IS-FOUNDRY — being Palantir Foundry / Gotham reduces to "write the OGAR class schema + inheritance"; everything else (traversal, query, pipelines, actions, low-code apps) is generic machinery over it via the shared AST + +**Status:** FINDING (capstone; operator-stated). The platform-level reading of the whole arc: Foundry/Gotham is not a platform to rebuild — it is an **OGAR class-schema-inheritance exercise**, because every other Foundry/Gotham layer is already generic machinery the workspace ships, parameterized only by `classid`. + +**The reduction (each Foundry/Gotham layer → an OGAR primitive already in code):** + +| Foundry / Gotham layer | OGAR primitive | Shipped surface | +|---|---|---| +| **Ontology** (object types + links) | OGAR **class schema + inheritance** | `classid → ClassView`; `effective_actions` / effective-methods = inheritance | +| **Objects + links** | GUID node + `EdgeBlock` edge | `E-GUID-IS-THE-GRAPH` (key=node, slot=edge, zero-value-decode) | +| **AR object behavior** | **DO vs THINK = Active Record** | THINK = `ClassView`/`MethodSig` (fields+methods, read/compute); DO = `ActionDef`/`ActionInvocation` (actions, gated RBAC→state-guard→MUL) | +| **Pipelines / transforms** | `compute_dag` topological recompute | `ClassView::compute_dag` + `compute_dag_topo_order` (`E-EXCEL`/`E-CHESS`/`E-PERTURBATION`) | +| **Apps / low-code / dashboards** | **Jinja/templates over OGAR classes** | `soa_view.rs:57` — `class_id → OGIT ontology → "label inheritance, column projection, jinja templates"` resolve one layer up | +| **Query / traversal surface** | **Cypher ⇄ SurrealQL, one shared AST** | `E-CYPHER-IS-THE-KANBAN-AST`; polyglot parsers → one `PlanInput` IR; SurrealQL is egress | + +**The headline:** AR-shaped DO/THINK objects + the shared Cypher/SurrealQL AST mean **any low-code or Jinja template runs over OGAR classes** without bespoke per-app wiring — the template binds to the class's fields (THINK) and actions (DO), the AST traverses/queries/mutates them, `compute_dag` recomputes derived fields, the DO-arm gate authorizes writes. So a vertical (medical / work-orders / intelligence cases / project boards) = *write its OGAR classes + inheritance*, and the platform behaviors fall out. That is the Foundry-aspiring superpower: the reduction of "build a platform" to "declare a schema." + +**Scope guard (truth-architect, carried from the 5+3 on the sibling plan):** "everything else is shipped" is the *architecture*, not "all wired today." The honest gaps the council found: (a) `ogar-adapter-surrealql` is NOT a crate — the nearest is `surreal_container::SurrealStore` (a `BLOCKED(C)` stub); the Cypher→SurrealQL lowering rides `lite-unified` (#540, default-OFF). (b) the "odoo ontology traversal already runs through SurrealQL" claim is a tagged `const ActionDef` + `classify_odoo` classification, **not** a running end-to-end traversal — CONJECTURE, not FINDING, until an `ExecTarget::SurrealQl` executor arm exists. (c) Jinja-over-classes is the `class_id→ontology` seam (real hook) + `lance-graph-ontology` resolution — the template *engine* binding is not yet a shipped path. The reduction is the doctrine; the wiring is the `cypher-kanban-ast-unification-v1` increments. Cross-refs: `E-CYPHER-IS-THE-KANBAN-AST`, `E-GUID-IS-THE-GRAPH`, `E-AR-DO-WIRING` (DO/THINK arms), `cypher-kanban-ast-unification-v1`, `canonical_node`/`class_view`/`action`/`soa_view`. + +--- + +## 2026-06-18 — E-CYPHER-IS-THE-KANBAN-AST — a kanban board IS a graph, so Cypher is its AST; board-ops + ontology-traversal + thinking-style dispatch + SurrealQL egress collapse to ONE shared AST + +**Council correction (2026-06-18, 5+3 on `cypher-kanban-ast-unification-v1`):** three legs of this entry were over-stated and are hereby qualified (the CORE — board-is-graph, Cypher-is-graph-AST — stands; convergence-architect confirmed 3 of 4 sides share one shipped `PlanInput` IR): **(1)** "one AST seen from four sides" → say **"one IR, four *relationships*"** (surface = Cypher; egress = SurrealQL; planner-layer = thinking-styles; mutated = board) — they are *is* / *lowers-to* / *plans-over* / *is-mutated-by*, not one identity (dilution-collapse-sentinel). **(2)** "odoo ontology traversal already runs through the SurrealQL AST adapter — existence proof" is **CONJECTURE, not FINDING**: it is a tagged `const ActionDef`(`ExecTarget::SurrealQl`) + `classify_odoo`, with NO executor arm and NO Cypher→SurrealQL lowering in source; `ogar-adapter-surrealql` is not a crate (truth-architect + runtime-archaeologist). **(3)** `from_guid_prefix` lives on **`NiblePath`** (`hhtl.rs:262`), NOT `NodeGuid`; and the wiring is **blocked CATCH-CRITICAL** until `MailboxSoaView` gains a key→row resolver (`row_for_local_key`, now added default-`None`) — the View had only `n_rows`, no way back from the canon address to a row (baton-handoff-auditor). Preserve the distinction `E-GUID` correction #2 drew: mailbox-cycle `KanbanColumn` phase-advance (traversal *dispatched-within* `CognitiveWork`) ≠ domain-board column move (an edge-rewrite) — same AST surface, two semantics (ripple-architect). + +**Status:** FINDING (convergence; odoo is the existence proof — ontology traversal already runs through the SurrealQL AST adapter). Refines `E-GUID-IS-THE-GRAPH` correction #2: "kanban is lifecycle, not traversal" was a false dichotomy — the lifecycle state-machine IS a graph, and that's the whole point. + +**The collapse:** a kanban board is a graph, full stop — +- **card → GUID node** (`E-GUID-IS-THE-GRAPH`), +- **column / phase → state** (a `Column` node the card has an `:IN` edge to, or a `classid`/property), +- **move → edge rewrite** (`(c)-[:IN]->(Doing)` ⇒ `(c)-[:IN]->(Done)`), +- **dependency / blocks → edge**, **WIP limit → a count constraint** (a Cypher `MATCH … RETURN count` guard), **swimlane → basin/label**. +- the Rubicon transition rules (`KanbanColumn::can_transition_to`) **ARE the graph schema** (which edges are legal). + +So the board's queries and mutations are graph patterns, and **Cypher is the AST for graph patterns**. Driving the board with Cypher is not a metaphor — it is the board's native query/mutation language. + +**Why it's the obvious move (the four-into-one):** the planner already holds all the pieces, and they share one AST/IR — +1. **Cypher/Gremlin/SPARQL/GQL → one planner IR** (front-end parsers). +2. **SurrealQL AST** = the shared adapter/egress hub that IR lowers into and the substrate runs (`ExecTarget::SurrealQl` + `ogar-adapter-surrealql`). odoo's **ontology traversal already goes through this adapter** — the existence proof. +3. **Thinking styles + MUL** dispatch over that IR (planner `thinking/`, 12 styles, NARS, sigma chain). +4. **Kanban phases** (`KanbanColumn`) are the lifecycle the cards move through. + +These are not four subsystems to be bridged — they are **one AST seen from four sides**: Cypher is the surface syntax, SurrealQL is the adapter/egress, thinking-styles are the planner layer over it, kanban is the board the patterns mutate. **Cypher AS the kanban-board AST unifies them.** Not doing so means re-expressing board moves in a second language while the graph AST sits right there — the dumb path. + +**Concretely:** a board move = a Cypher mutation → planner IR → thinking-style/MUL plan → `ExecTarget::SurrealQl` (or `Native`/`Jit`) over the GUID-keyed substrate; a board query (cards in a column, blocked cards, WIP count) = a Cypher `MATCH` → prefix-route on `classid` + `EdgeBlock`-slot deref, zero-value-decode (`E-GUID-IS-THE-GRAPH`). The `KanbanColumn` lifecycle is the *mailbox cognitive-cycle* instantiation of the same structure; an odoo project board / woa work-order board / q2 case board are *domain* instantiations — **same AST, same substrate, different `classid` + edge schema.** + +**Scope guard (truth-architect):** "Cypher is the AST" = Cypher is the surface that lowers to the shared IR/SurrealQL AST — NOT a claim that the core's nom Cypher parser already emits SurrealQL (it lowers to DataFusion today; the SurrealQL lowering is the `lite-unified` gate #540, default-OFF, process-not-switch). The unification is the architecture; the wiring is the `Backend::MailboxSoa` router variant + the Cypher→SurrealQL lowering behind `lite-unified`. Cross-refs: `E-GUID-IS-THE-GRAPH`, `E-AR-DO-WIRING` (ontology→DO consumers), `lite-unified-surrealql-lance-v1`, planner `thinking/` + `strategy/cypher_parse.rs`, `kanban::{KanbanColumn, ExecTarget}`. + +--- + +## 2026-06-18 — E-GUID-IS-THE-GRAPH — the substrate IS a graph already: GUID-key = node, EdgeBlock-slot = edge, traversal = prefix-route + slot-deref, all zero-value-decode; SurrealQL is egress, kanban dispatches + +**Status:** FINDING (grounded in shipped `canonical_node.rs` / `soa_view.rs` / `kanban.rs` / planner polyglot strategies). Written to stop the q2-rewire session from hallucinating a node/edge/Cypher layer that already exists. + +**The spine — the GUID is the key (canon, CLAUDE.md CANON + OGAR P0):** a node IS its 16-byte `NodeGuid`. There is no "node table with a guid column" and nothing to "expose" — the key is the address and the prerender: +- `classid (0..4) → ClassView` (type/class/method-resolution), +- `HEEL/HIP/TWIG (4..10) → cascade tier` (HHTL routing), +- `family (10..13) → basin`, `identity (13..16) → instance`; `local_key()` = bytes 10..16. +All of that resolves **off the key alone, zero value decode** ("the key prerenders nodes — in any way — with zero value decode"). The `MailboxSoaView` columns (`energy`/`edges_raw`/`meta_raw`/`entity_type`/`class_id`/content·topic·angle planes) are the **value side** — "everything the key isn't" (deferred 480 B, Lance-compressible). You never look up a row's GUID; the GUID is how you addressed the row. + +**Edges are key-references, not blobs:** an `EdgeBlock` slot is one byte = a basin-local index that resolves to a neighbor's `local_key`. Two coexisting representations: `EdgeBlock` (12 in-family + 4 out-family adjacency-of-keys, in the canonical `NodeRow`) and `edges_raw() -> &[u64]` (`CausalEdge64`, the causal/W-slot variant on the view). Both are references. + +**Therefore the substrate is ALREADY a graph — traversal needs no new layer:** +- `MATCH (n:Person)` → classid prefix binds `ClassView` (a prefix route on the key). +- `(a)-[r]->(b)` → dereference an `EdgeBlock` slot (byte → neighbor `local_key`). +- Both happen on keys, zero value decode — that is the entire point of GUID-as-key: routing + prerender never touch the compressed value. + +**Two corrections to the q2 framing (what it got inverted):** +1. **SurrealQL is EGRESS, not a Cypher/Gremlin front-end.** Front-end parsers are Cypher/Gremlin/SPARQL/GQL → one planner IR. SurrealQL appears only as `ExecTarget::SurrealQl` (lower the parsed plan, run in substrate) + `ogar-adapter-surrealql` schema registration (`DEFINE TABLE`/`knowable_from`). Flow is `Cypher → IR → (optionally) ExecTarget::SurrealQl`, never "SurrealQL adapter as Cypher." (The "DLL AST adapter" phrasing is the OGAR C++→Rust codegen subsystem — a third, unrelated thing.) +2. **Kanban is the mailbox LIFECYCLE, not the traversal.** `KanbanColumn` = `Planning→CognitiveWork→Evaluation→Commit` (+`Plan`/`Prune`), the Rubicon cycle of ONE mailbox (`phase()`, `try_advance_phase`, `KanbanMove`). A traversal is **dispatched as** the `CognitiveWork` action via `ExecTarget` — fine read as *dispatched-within*, wrong read as *kanban-transition = edge-hop*. "16k" is the `Vsa16kF32` fingerprint width (16384 bits/row), a different axis from a `MailboxSoA` row-count. + +**The minimal real wiring (no new layer):** the only missing piece for Cypher-over-the-canonical-substrate is a `Backend::MailboxSoa` variant in `graph_router` whose scan resolves **key → row** (`from_guid_prefix` / `local_key()`) and follows `EdgeBlock`/`edges_raw` slots. Not a guid value-column; not a SurrealQL front-end; not a kanban-as-traversal. Just: route on the key, deref the edge slot. Cross-refs: `canonical_node::{NodeGuid, EdgeBlock, NodeRow}`, `soa_view::MailboxSoaView`, `kanban::{KanbanColumn, ExecTarget}`, planner `strategy/{cypher,gremlin,sparql,gql}_parse.rs`, `E-OGAR-ROUTER-ENCODER` (the key-prefix router is the same address machinery). + +--- + ## 2026-06-18 — E-PERTURBATION-CASCADE-IS-COMPUTE-DAG — the electricity cascade crate is `compute_dag` already running on a real physical field, and it ships the Weyl bound that certifies the incremental recompute **Status:** FINDING (mechanism mapping; no dependency added). The `crates/perturbation-sim` outage simulator is the physical instance of the SAME topological recompute `ClassView::compute_dag` + `compute_dag_topo_order` + `write_row` express abstractly. Doc-level join only (`crates/perturbation-sim/COMPUTE_DAG_MAPPING.md`); the crate stays zero-dep / workspace-excluded — no `lance-graph-contract` import. Grounds: `E-CHESS-TENSOR-PROVEN`, `E-EXCEL-SHADER-PROJECTION`, `E-OGAR-ROUTER-ENCODER`, `probe-excel-compute-dag-v1`. diff --git a/.claude/board/INTEGRATION_PLANS.md b/.claude/board/INTEGRATION_PLANS.md index e28f22c9..51c2c7fd 100644 --- a/.claude/board/INTEGRATION_PLANS.md +++ b/.claude/board/INTEGRATION_PLANS.md @@ -1,3 +1,13 @@ +## 2026-06-18 — cypher-kanban-ast-unification-v1 (Cypher IS the kanban-board AST; the GUID-keyed substrate IS the graph; four subsystems → one AST) + +**Status:** PLAN (pre-5+3-council). **Plan file:** `.claude/plans/cypher-kanban-ast-unification-v1.md`. Grounded by `E-GUID-IS-THE-GRAPH` + `E-CYPHER-IS-THE-KANBAN-AST`; odoo ontology-traversal-through-SurrealQL-AST = existence proof. +**Thesis:** a kanban board is a graph (card=GUID node, column/phase=state, move=edge rewrite, dep=edge, WIP=count guard, `can_transition_to`=schema), so Cypher is its AST; the planner already shares ONE IR across Cypher/Gremlin/SPARQL/GQL front-ends + SurrealQL adapter/egress + thinking-styles/MUL + kanban phases — one AST seen from four sides. +- **Inc 0:** `Backend::MailboxSoa` router variant — key→row via `from_guid_prefix`/`local_key`, edge = `EdgeBlock`-slot deref; `MATCH (n:Label)` = classid prefix-route, `(a)-[r]->(b)` = slot-deref, zero-value-decode. **Inc 1:** Cypher→SurrealQL lowering behind `lite-unified` (#540, default-OFF). **Inc 2:** kanban-board-as-Cypher over the GUID substrate. **Inc 3:** q2 consumer (domain classid+edge schema; consumes lance-graph, never substitutes). +- **Gates:** F1 backend parity vs DataFusion · F2 zero-value-decode 1-hop · F3 illegal-move rejected at plan time · F4 `lite-unified`-OFF byte-identical · F5 no new layer. +**Scope line:** "Cypher is the AST" = surface lowering to the shared IR/SurrealQL AST, NOT the nom parser emitting SurrealQL today. datafusion NOT deprecated. **Repos:** lance-graph (router + planner) + q2 (consumer). Branch `claude/q2-substrate-grounding`. + +--- + ## 2026-06-18 — probe-excel-compute-dag-v1 (land ClassView::compute_dag on the 2-axis grid; the NNUE-incremental existence proof) **Status:** CONJECTURE / probe scope. **Plan file:** `.claude/plans/probe-excel-compute-dag-v1.md`. Grounded by `E-CHESS-TENSOR-PROVEN` + `E-EXCEL-SHADER-PROJECTION` + `E-OGAR-ROUTER-ENCODER`. diff --git a/.claude/plans/cypher-kanban-ast-unification-v1.md b/.claude/plans/cypher-kanban-ast-unification-v1.md new file mode 100644 index 00000000..ad9d215b --- /dev/null +++ b/.claude/plans/cypher-kanban-ast-unification-v1.md @@ -0,0 +1,137 @@ +# cypher-kanban-ast-unification-v1 — Cypher IS the kanban-board AST; the GUID-keyed substrate IS the graph; four subsystems collapse to one + +> **Status:** PLAN (pre-5+3-council). Captures the unification from +> `E-GUID-IS-THE-GRAPH` + `E-CYPHER-IS-THE-KANBAN-AST` before it dilutes, +> and sequences the q2-rewire wiring. **datafusion stays the default query +> engine; the SurrealQL lowering rides the `lite-unified` gate (#540, +> default-OFF) — process, not switch.** + +## The thesis (one AST, four sides) + +A kanban board is a graph. The GUID is the key. Therefore Cypher — the AST for +graph patterns — is the board's native query/mutation language, and the planner +already shares **one IR/AST** across all four sides: + +| Side | Surface | Already shipped | +|---|---|---| +| **Query language** | Cypher / Gremlin / SPARQL / GQL → one IR | planner `strategy/{cypher,gremlin,sparql,gql}_parse.rs` | +| **Adapter / egress** | SurrealQL AST (`DEFINE`/traversal) | `ExecTarget::SurrealQl` enum tag only (no lowering yet; `ogar-adapter-surrealql` is NOT a crate — nearest is `surreal_container::SurrealStore`, a `BLOCKED(C)` stub); odoo path = CONJECTURE, see verdict §5 | +| **Planner layer** | thinking-styles + MUL over the IR | planner `thinking/` (12 styles, NARS, sigma chain), `mul/` | +| **Board** | kanban phases the cards move through | `kanban::{KanbanColumn, ExecTarget}` | + +Board ⇄ graph mapping (no new types): +- **card → GUID node** (`NodeGuid` key; `classid→ClassView`, `local_key`=family++identity) +- **column / phase → state** (a `Column` node via `:IN` edge, or a `classid`/property) +- **move → edge rewrite** (`(c)-[:IN]->(Doing)` ⇒ `(c)-[:IN]->(Done)`) +- **dependency / blocks → edge**; **WIP limit → a Cypher `count` guard**; **swimlane → basin/label** +- **legal moves → the graph schema:** mailbox-lifecycle legality stays with + `KanbanColumn::can_transition_to`; domain-board legality resolves via + `classid → ClassView` (NOT `KanbanColumn` — see verdict §4a) + +The `KanbanColumn` cognitive cycle is the *mailbox* instantiation; an odoo project +board / woa work-order board / q2 case board are *domain* instantiations — +**same AST, same substrate, different `classid` + edge schema.** + +## What's already true (no work) + +- **The substrate is the graph** (`E-GUID-IS-THE-GRAPH`): node = `NodeGuid` key, + edge = `EdgeBlock` slot (byte → neighbor `local_key`) or `CausalEdge64`, + traversal = prefix-route + slot-deref, zero value decode. +- **Cypher parses** (core nom parser, 44 tests: node + relationship patterns, + var-length `*1..2`, WHERE/RETURN/etc). +- **The polyglot front-ends → one IR**; **thinking-styles + MUL** plan over it; + **`ExecTarget::SurrealQl`** is the egress. +- **odoo ontology work suggests the Cypher/ontology-over-SurrealQL path** — but + this is **CONJECTURE, not a proof** (verdict §5): what ships is a tagged + `const ActionDef`(`ExecTarget::SurrealQl`) + `classify_odoo` classification, with + NO executor arm and NO Cypher→SurrealQL lowering in source yet. Treat as a + convergent-but-unwired signal, not a running traversal. + +## The gap (what this plan wires) — additive, layout-preserving + +### Inc 0 — `Backend::MailboxSoa` router variant +`graph_router::Backend` gains a `MailboxSoa` variant whose scan: +- resolves **key → row** via `NiblePath::from_guid_prefix(&guid)` + `NodeGuid::local_key` + + `MailboxSoaView::row_for_local_key` (no guid value-column — the key IS the address; + `from_guid_prefix` is on `NiblePath`, not `NodeGuid` — see verdict §2), +- follows edges via the **`classid`-resolved** representation (verdict §4b) — + `EdgeBlock` slot deref (adjacency, byte → neighbor `local_key`) **XOR** + `MailboxSoaView::edges_raw` (`CausalEdge64`, causal), selected by the class's + `EdgeCodecFlavor`/`ReadMode`, never guessed by availability, +- a Cypher `MATCH (n:Label)` lowers to a **classid prefix route**; `(a)-[r]->(b)` + to an **edge-slot deref** — both zero-value-decode. +Additive to the existing 3-backend router (DataFusion / Blasgraph / Palette); no +`NodeRow`/stride/`ENVELOPE_LAYOUT_VERSION` change. + +### Inc 1 — Cypher → SurrealQL lowering behind `lite-unified` (default-OFF) +The board's mutations/queries lower to SurrealQL (`ExecTarget::SurrealQl`) **only +under `lite-unified`**; datafusion stays the default. This is the egress side of +the AST, gated per the #540 process-not-switch invariant. + +### Inc 2 — kanban-board-as-Cypher over the GUID substrate +Express board ops as Cypher patterns dispatched through the planner with +thinking-styles/MUL: a move = a Cypher edge-rewrite **routed through the DO arm** +(`ActionInvocation` commit gate: def-match → RBAC → state-guard → MUL — NOT a raw +`MATCH…SET`, see verdict §4d); a query (cards-in-column / blocked / WIP-count) = +a Cypher `MATCH`. Legality is `classid → ClassView`-resolved for a domain board; +`KanbanColumn::can_transition_to` governs only the mailbox cognitive cycle. + +### Inc 3 — q2 consumer wiring (the rewire) +q2 (Palantir-Gotham/neo4j-aspiring) cases = a **domain `classid` + edge schema** +over the same substrate + AST. q2 depends on lance-graph as the hot path (NOT a +substitute — `.claude/rules/architectural-compliance.md`); its board/case +traversal is Cypher over `Backend::MailboxSoa`. + +## Falsification gates + +- **F1 (router):** a Cypher `MATCH (n:Label)` over `Backend::MailboxSoa` returns + the same node set as the DataFusion backend on the same data (backend parity). +- **F2 (zero-decode):** a 1-hop traversal touches only key + `EdgeBlock` bytes, + never the 480 B value slab (assert via a value-access counter / borrow check). +- **F3 (board legality, all guard classes — was F6 in the verdict):** an illegal + domain-board move (rejected by the `classid → ClassView` schema) **and** any + DO-arm guard failure (RBAC / state-guard / MUL) are rejected at plan time, not + after the edge rewrite. (Mailbox-cycle legality is the separate + `KanbanColumn::can_transition_to` check.) +- **F4 (gate isolation):** with `lite-unified` OFF, behaviour is byte-identical to + today (datafusion path); the SurrealQL lowering is unreachable. +- **F5 (no new layer):** the unification adds ONE router variant + ONE lowering + path behind a flag — no new node/edge type, no second object model, no + kanban-as-traversal type. + +## Scope guards (truth-architect) + +- "Cypher is the kanban AST" = Cypher is the **surface** that lowers to the shared + IR/SurrealQL AST — NOT a claim the nom parser emits SurrealQL today (it lowers + to DataFusion; SurrealQL lowering is Inc 1 behind `lite-unified`). +- **datafusion is NOT deprecated** (#540). The router gains a backend; it does not + lose one. +- No measured-perf claim without a bench (F1 is a correctness/parity gate, not a + speed gate). +- The mailbox-`KanbanColumn` lifecycle and a domain board are the SAME structure; + do NOT fork a second kanban type per domain — vary `classid` + edge schema. + +## Cross-refs +`E-GUID-IS-THE-GRAPH`, `E-CYPHER-IS-THE-KANBAN-AST`, `E-AR-DO-WIRING` +(ontology→DO consumers), `lite-unified-surrealql-lance-v1` (#540 gate), +`canonical_node::{NodeGuid, EdgeBlock, NodeRow}`, `soa_view::MailboxSoaView`, +`kanban::{KanbanColumn, ExecTarget}`, planner `thinking/` + +`strategy/cypher_parse.rs`, `graph_router::Backend`, +`.claude/rules/architectural-compliance.md` (q2 must consume, not substitute). + +--- + +## 5+3 Council verdict (2026-06-18) — revisions applied + +8/8 reported. Core thesis **SOUND** (convergence: `StepDomain::Kanban` exists; all 4 polyglot parsers already return one `PlanInput`/`QueryFeatures` IR — 3 of 4 sides are genuinely one IR). Revisions: + +1. **Headline demoted** (dilution-sentinel): "four sides of one AST" → **"one IR, four *relationships*"** (surface=Cypher / egress=SurrealQL / planner-layer=styles / mutated=board). +2. **CATCH-CRITICAL fixed first** (baton-auditor): `MailboxSoaView` had no key→row resolver — added `row_for_local_key(local_key) -> Option` default-`None` (deferred binding). `from_guid_prefix` is on **`NiblePath`** (`hhtl.rs:262`), not `NodeGuid` — Inc 0 routes via `NiblePath::from_guid_prefix(&guid)` + `NodeGuid::local_key` + `MailboxSoaView::row_for_local_key`. +3. **Resequenced** (integration-lead): **first shippable = Inc 0 + F1/F2 ALONE**, on the DataFusion-default `ExecTarget::Native` path — zero SurrealQL, zero `lite-unified`, zero q2 coupling. **Inc 1 (Cypher→SurrealQL) is a *dependency-on* `lite-unified-v1`'s OQ-LU-2, NOT a duplicate deliverable** (the two plans were claiming the same lowering). +4. **Three boundaries to pin before Inc 0 lands** (ripple-architect): + - **(b) edge-representation is `classid`-resolved, not query-guessed:** a relationship-type binds to `EdgeBlock` (adjacency) XOR `CausalEdge64` (causal) via the class's `EdgeCodecFlavor`/`ReadMode` — the router must not pick by availability. + - **(a) domain-board transition schema ≠ `KanbanColumn`:** a domain board's legal moves resolve via `classid → ClassView`, NOT `KanbanColumn::can_transition_to` (that encodes the *mailbox* Rubicon DAG only). Same graph shape, different transition algebra. + - **(d) board mutation routes through the DO arm:** a move = `ActionInvocation` through the commit gate (def-match→RBAC→state-guard→MUL), NOT a raw `MATCH…SET` edge-rewrite — otherwise WIP/permission/MUL guards are bypassed. Add F6: an illegal move fails at plan time for ALL guard classes, not just Rubicon legality. +5. **Status downgrades** (truth-architect + archaeologist): the "odoo existence proof" is a tagged `const` + `classify_odoo`, NOT a running traversal → CONJECTURE; "zero-value-decode" (F2) is design-intent until the value-access counter exists; `ogar-adapter-surrealql` is not a crate (it's `surreal_container::SurrealStore`, a `BLOCKED(C)` stub). + +**Net first increment (this PR):** the `row_for_local_key` deferred-binding contract method (the named dropped baton) — additive, zero-dep, testable now. `Backend::MailboxSoa` (Inc 0 proper) follows once the three boundaries above are pinned. diff --git a/crates/lance-graph-contract/src/soa_view.rs b/crates/lance-graph-contract/src/soa_view.rs index b488575d..6a8e6dc8 100644 --- a/crates/lance-graph-contract/src/soa_view.rs +++ b/crates/lance-graph-contract/src/soa_view.rs @@ -68,6 +68,26 @@ pub trait MailboxSoaView { self.entity_type()[row] } + /// Resolve a canonical [`NodeGuid::local_key`](crate::canonical_node::NodeGuid::local_key) + /// (bytes 10..16 = family++identity, the basin-local discriminator) to a row + /// index in this view — the **key→row baton** a `Backend::MailboxSoa` graph + /// router needs to land a Cypher `MATCH`/edge-slot deref on the GUID-keyed + /// substrate (`cypher-kanban-ast-unification-v1` Inc 0; the baton-handoff-auditor's + /// CATCH-CRITICAL — the View previously exposed only `n_rows`, with no way to go + /// from the canon address back to a row). + /// + /// **Default = `None` (zero-fallback, deferred binding).** A view that has NOT + /// materialized a per-row key index returns `None` for every key — the same + /// deferred-accessor discipline as `qualia` / `episodic_witness` below: the + /// resolver contract is declared here so the router signature can name it, and an + /// owner that stores keys (the in-RAM `MailboxSoA`, once it carries a `local_key` + /// column) overrides this. Until then a consumer that gets `None` falls back to + /// the positional `(mailbox_id, row)` address, never a wrong row. + #[inline] + fn row_for_local_key(&self, _local_key: u64) -> Option { + None + } + // NOTE (follow-up): the qualia column (`QualiaI4_16D`) accessor is intentionally omitted — // add `fn qualia(&self) -> &[crate::qualia::QualiaI4_16D]` when the first consumer // (planner strategy selection) needs it; keep the read surface minimal until then. @@ -232,6 +252,17 @@ mod tests { assert_eq!(soa.w_slot(), 7); } + #[test] + fn row_for_local_key_defaults_to_none_until_a_key_index_is_materialized() { + // Deferred-binding default: a view with no per-row key column resolves + // every local_key to None — the consumer falls back to the positional + // (mailbox_id, row) address, never a wrong row. The Backend::MailboxSoa + // router can name this baton; an owner that stores keys overrides it. + let soa = sample(); + assert_eq!(soa.row_for_local_key(0), None); + assert_eq!(soa.row_for_local_key(u64::MAX), None); + } + #[test] fn owner_advances_phase_and_sets_libet_anchor() { let mut soa = sample();