diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index d0905268..b18d72de 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,158 @@ +## 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`. + +**The mapping (mechanism, not rhyme):** + +| `perturbation-sim` | `compute_dag` substrate | +|---|---| +| `simulate_outage` round loop (trip → recompute survivors → re-trip) | `compute_dag_topo_order` dispatch (edit → dirty-set → recompute dependents) | +| `PerturbationShape::trip_round[e]` (round each line tripped, `0`=seed) | the topological generation in `compute_dag_topo_order` | +| seed trip = rank-1 `E` on Laplacian `L` | the dirty seed `write_row(seed, cycle)` | +| `spectral_perturbation` Weyl `\|λᵢ(L′)−λᵢ(L)\|≤‖E‖₂` + Davis–Kahan | the **NNUE incremental ≡ full** invariant: bounded local edit → bounded global perturbation → dirty-set is the complete support | +| `PerturbationShape::node_field` | the wave/field readoff (`E-OGAR-ROUTER-ENCODER` field side) | +| `splat::morton2` | the 2-axis router ADDRESS (256×256 tile, GREEN case) | +| `sketch::fwht` + `walsh_pyramid_energy` | the deterministic field ENCODER (Walsh pyramid) | +| `witness::particle_equals_wave` (Parseval over FWHT) | particle (`∑field·arc`) ≡ wave (transform once, many arcs) — proven on a real field | + +**The headline (truth-architect honesty):** the two crates are deliberate **complementary halves**. `perturbation-sim` does the EXACT FULL recompute each round (robust, no LODF drift) but ships the **certification apparatus** — its Weyl/Davis–Kahan bounds are precisely the inequalities an incremental scheme needs to prove it equals the full recompute. `compute_dag` is the INCREMENTAL dispatch (recompute only dirty dependents in topo order, gated by `write_row`); the Weyl bound this crate certifies is *why* that incremental recompute is sound. **This crate = proof + full reference + router/field/witness encoders; the `compute_dag` harness = the incremental consumer whose equivalence the bound certifies.** Stockfish NNUE proves the incremental side at scale; perturbation-sim proves the bound holding it together is real. + +**Why it strengthens the arc without diluting:** `E-OGAR-ROUTER-ENCODER` flagged OSM → splat → electricity as the 3D *sibling* cascade (N=3 Hilbert, distinct from the 2-axis centroid tile). This says: that 3D cascade is a legitimate `compute_dag` consumer — its recompute order IS `simulate_outage`'s round structure, its incremental-equivalence IS Weyl-bounded. The crate already self-guards every scope line we'd draw: no measured speed claim (`witness.rs`), the numeric witness arc explicitly NOT the contract `WitnessTable` (`I-VSA-IDENTITIES`), significance via Jirak not IID Berry–Esseen (`I-NOISE-FLOOR-JIRAK`). Nothing new to strip; the costume was never put on here. + +**Scope guard (unchanged):** per-bus/per-cell evaluation semantics are general compute via the DO arm / `UnifiedStep` — the *recompute structure* transfers, the *formula/injection content* does not. Same line `E-EXCEL` / `E-CHESS` drew. Cross-refs: `crates/perturbation-sim/COMPUTE_DAG_MAPPING.md`, `class_view::{compute_dag, compute_dag_topo_order, compute_dag_is_acyclic}`. + +--- + +## 2026-06-18 — E-CHESS-TENSOR-PROVEN — chess AI is the proven-at-scale instance of router(grid)+field(planes)+incremental-recompute(NNUE)+search(MCTS); its 4 species = the session's 4 facets + +**Status:** FINDING (grounding/validation). Makes `E-EXCEL-SHADER-PROJECTION` / `PROBE-EXCEL-COMPUTE-DAG` non-speculative: the architecture it proposes is what production chess engines already run. + +**The mapping (chess AI → OGAR stack), each piece an anchor not a rhyme:** + +| Chess AI | OGAR stack | session anchor | +|---|---|---| +| 8×8 board (rank×file) | **2-axis router grid** | `E-OGAR-ROUTER-ENCODER` (2-axis = the GREEN case) | +| `C` feature planes (12 piece + history + reps + counters; AlphaZero `N×N×(MT+L)`, Leela 112 planes of 8×8) | **value tenants / the field over the grid** | the "wave"; `Energy`/`HelixResidue` tenants | +| AlphaZero/Leela `8×8×C → CNN → policy/value` | **`CognitiveShader` dispatch over the SoA grid** (the field read) | "shader projection" | +| **Stockfish NNUE** — *"efficiently updatable… only small input changes between neighboring positions"* | **the cycle-aware INCREMENTAL recompute = `compute_dag` dirty-propagation gated by `write_row`** | `E-SOA-CYCLE-OWNERSHIP` + `E-EXCEL` — **the exact match** | +| transformer chess (64 squares as tokens, attention; arXiv 2406.00877 learned look-ahead) | **`EdgeBlock` coupling / attention-as-table-lookup (`bgz-tensor`)** | the coupling | +| MCTS / alpha-beta search | **the dispatch cycle** ("can't NOT think while surprise exists") + scenario/counterfactual branching | the cycle; `ScenarioBranch` | +| classical bitboards + handcrafted eval | the **particle register** (discrete piece positions) | `I-VSA-IDENTITIES` Test-0 register | + +**The headline:** the four "species of chess AI" are the session's four facets of ONE node, each proven independently at the highest level — **bitboards = particle register, AlphaZero/Leela CNN = field/wave (shader projection), Stockfish NNUE = the incremental cycle-aware recompute (the `compute_dag`), transformer = the `EdgeBlock`/attention coupling.** Critically, **NNUE is `PROBE-EXCEL-COMPUTE-DAG` already proven at world-champion strength**: "only small input changes between neighboring positions, incrementally update" *is* the `compute_dag` dirty-propagation + the cycle-aware `write_row`. The probe re-derives NNUE's incrementality as the `compute_dag`/`write_row` contract on a 2-axis grid. + +**Why this strengthens, not dilutes (truth-architect):** the chess board is the same 2-axis grid (rank×file = x/y) the cascade-architect certified GREEN — so chess validates the router+field+incremental+search architecture *without* touching the open 3D-Hilbert / bit-partition questions (`E-OGAR-ROUTER-ENCODER`). A chess board is an even cleaner 2-axis demonstrator than a sheet; **Excel stays the consumer-facing demonstrator** (odoo/medcare/woa/q2 are spreadsheet-shaped per `E-AR-DO-WIRING`), **chess is the architecture validator + the feature-plane / NNUE-incremental design reference.** Honest scope unchanged: the *recompute structure* (planes + incremental update + search) transfers; per-cell/per-square *evaluation semantics* are general compute via the DO arm / `UnifiedStep`, not the Walsh-Hadamard field — never the physics costume that was stripped. + +Cross-refs: `E-EXCEL-SHADER-PROJECTION` (the probe), `E-OGAR-ROUTER-ENCODER` (router/encoder), `E-AR-DO-WIRING` (consumers), `bgz-tensor` (attention-as-lookup = the transformer-chess facet, already shipped). + +--- + +## 2026-06-18 — E-EXCEL-SHADER-PROJECTION — a spreadsheet projected as a shader is the clean 2-axis existence proof for the router + DO arm + `compute_dag` + cycle-aware write + +**Status:** CONJECTURE / proposed probe. The buildable demonstrator of `E-OGAR-ROUTER-ENCODER` in its provable 2-axis form, and the named first proof for the one Core gap (`ClassView::compute_dag`). Builds on (do NOT lose): `E-OGAR-ROUTER-ENCODER` (the router/encoder click + the strip-the-physics verdict), `E-AR-DO-WIRING` (ruff+ARM+AR→OGAR consumer landing), `E-SOA-CYCLE-OWNERSHIP` (cycle-aware write), and the `mailbox-cycle-aware-write-contract` / DO-arm `action.rs` work. + +**The mapping (every piece lands on a shipped or named primitive — mechanism, not rhyme):** + +| Excel | OGAR stack | +|---|---| +| cell `(row, col)` | node, addressed by the **2-axis router** (`HEEL/HIP/TWIG` 256×256 tile, x/y binding — the cascade-architect's GREEN 2-axis case) | +| cell value | value tenant (`Energy`=number, content=text) — the **particle** | +| formula `=A1+B2` | **`ActionInvocation`** (DO arm, `action.rs`): predicate=formula, `depends_on`=precedents, `exec`=Native/Jit | +| the dependency DAG | **`ClassView::compute_dag`** — the *one named Core gap* (core-gap-auditor), landed here | +| recompute-on-edit (topological) | the **cycle-aware write (`write_row`) + the field-wave** — dirty-set propagates through the DAG; the write gates the recompute-generation | +| `SUM(A1:A10)` | tier-table reduction / `vsa_bundle` | +| the sheet *projected* | **`CognitiveShader` dispatch over the SoA grid** — "can't NOT recompute while dirty" IS the active-inference dispatch (F>floor→fire) | + +**Why it's the ideal first proof (carries the prior-iteration verdict):** +1. **2-axis = the router's PROVEN home.** A sheet is inherently row/col — it sidesteps the 3D-Hilbert axis-count crack (`E-OGAR-ROUTER-ENCODER`: OSM→splat→electricity is the 3D *sibling* cascade; Excel is the 2-axis case `cam_pq::DistanceTables` already ships GREEN). +2. **Formulas ARE the `compute_dag`** — building Excel-as-shader lands that Core extension on the cleanest case. +3. **Universal.** Odoo `@api.depends`, medcare lab-trends, woa calculations, q2 computed cells all **reduce to a sheet** — so this is the consumer-agnostic proof the AR consumers (`E-AR-DO-WIRING`) all collapse to. +4. The recompute **is** the cycle-aware field-wave — it exercises the `write_row` contract end-to-end. + +**Honest scope line (truth-architect):** a formula's *semantics* (`=VLOOKUP`, `=IF`) are general compute dispatched per-cell via the DO arm / `UnifiedStep` — **NOT** the Walsh-Hadamard deterministic-phase field. "Shader projection" = the **dependency-driven recompute dispatch** (`compute_dag` + cycle), not field *synthesis*. Correct, buildable scope — exactly the gap already named, never the physics costume `E-OGAR-ROUTER-ENCODER` stripped. + +**The probe (`PROBE-EXCEL-COMPUTE-DAG`, compute_dag existence proof):** a minimal sheet — cells = SoA rows, 2-axis `(row,col)` addressed; formulas = `ActionDef`s with `depends_on` edges; `ClassView::compute_dag` topological recompute on cell-edit, each recompute gated by `write_row(cycle)`. **Success:** editing `A1` dirties + recomputes dependents in topological order through the shader dispatch, every recompute cycle-stamped; cycle rejects (registry-build) on a formula loop; `ValueSchema::Cognitive ∩ Compressed = {Fingerprint, EntityType}` disjointness holds (no over-collapse). **Lands:** `ClassView::compute_dag` on the 2-axis case — the prerequisite the core-gap-auditor flagged for *every* computed-field AR consumer (odoo/medcare/woa/q2). Better first proof than any single consumer because they all reduce to it. + +**Carries the converged deliverables (unchanged, all layout-preserving):** `ClassView::{axis_binding, centroid_codebook}` (the wave/spatial selector — the sheet uses `axis_binding=Spatial(x/y)`); the `Field` `ValueSchema` preset; the Jirak reword. Cross-refs: `E-OGAR-ROUTER-ENCODER`, `E-AR-DO-WIRING`, `lite-unified-surrealql-lance-v1`. + +--- + +## 2026-06-18 — E-OGAR-ROUTER-ENCODER — the "particle/wave click" is a domain-agnostic ADDRESS ROUTER + a deterministic FIELD ENCODER over one classid-dispatched node; the physics-duality framing is a costume to strip + +**Status:** FINDING (8-agent 5+3, unanimous; cascade-architect grounded in shipped ndarray code). The click is REAL and mostly in code — but NOT where the physics vocabulary put it. + +**What the 5+3 was asked:** make the "API Class inherited-view + edges + particle/wave" click *hard*. **What it found:** the click splits into two real mechanisms welded to one costume. + +### COLLAPSES — legitimate, at the ADDRESS only (the hard click) +- **Address ROUTER [H]** — one shift/mask prefix-distance over the 128-bit `NodeGuid` (`classid·HEEL·HIP·TWIG`, 6 bytes = the CAM-PQ 6×256 code, 3 tier-table lookups, O(1)) serves BOTH a 2-axis spatial mipmap (OSM x/y) AND a semantic centroid cascade (AR class). `classid` selects the codebook; the domain binds the axes. **Semantic side is GREEN-shipped** (`ndarray::hpc::cam_pq::DistanceTables::distance` = 6 lookups + 5 adds). D-BOTHCASC is real AT THE KEY. +- **Field ENCODER [H]** — the bipolar-phase Walsh-Hadamard pyramid synthesizes the field from the address (phase, *never stored*, CurveRuler stride-4-over-17) + the magnitude tenant. The TWO-ALGEBRA rule (sign = XOR = `vsa_bind`; magnitude = `vsa_bundle`, NEVER raw-XOR) is correctly Markov-fenced. + +### MUST STAY DISTINCT — do NOT flatten (value + lifecycle) +It is **allocation, not conservation**: the 480 B value slab holds the discrete-identity tenants (Class) AND the field-residue tenants in *different byte ranges* — a byte-budget partition, not a conjugate-basis duality. +- `ValueSchema::Cognitive` (particle: Meta/Qualia/Energy/Plasticity/EntityType) vs `Compressed` (wave: HelixResidue/Turbovec) — disjoint presets over one slab, by classid `ReadMode`. +- `EdgeBlock` `CoarseOnly` (particle FK adjacency, ≤16 discrete) vs `Pq32x4`/residue (wave continuous coupling) — distinct `EdgeCodecFlavor`, same 16 bytes. +- DO-arm: `ActionInvocation` (RBAC+MUL **Rubicon commit gate**, discrete, egress) vs the **deterministic phase recurrence** (ungated, reversible, WHT self-inverse, never commits). Two physics on opposite sides of the firewall (ADR-022). + +### STRIP — rhyme/costume (DROP-DO-NOT-BUILD the physics framing) +- **"particle/wave duality" as physics** — there is no norm-preserving transform `T(Class)↔field` (the residue is lossy-for-analysis → one direction lossy → not a basis change). Build nothing assuming a Class↔field conjugacy. +- **"simultaneously spatial AND semantic"** — a node inhabits ONE domain (classid dispatch), never both at once; it's a dispatch, not a superposition. +- **"Heisenberg / uncertainty principle"** — `N ≤ √d/4 ≈ 32` is a VSA capacity/SNR floor, NOT Δx·Δp. The number survives; demote the physics label to [S]. + +### THE TWO CRACKS for the named 3D consumer (cascade-architect, shipped-code-grounded) — load-bearing +**OpenStreetMap → gaussian splat → electricity perturbation is 3D, and the shipped spatial substrate is 3D Hilbert (`ndarray::hpc::linalg::hilbert.rs`, N=3) — which does NOT factor into the 2-axis 256×256 centroid tile.** So: +1. **Axis count:** the unification holds for **2D-spatial (OSM x/y) ≡ 2-PQ-semantic**; the **3D gaussian-splat grid is a RELATED-BUT-DISTINCT 3-axis Hilbert cascade**, not the same byte-per-axis Morton stride. Either pin spatial to 2D x/y (defer z to a 4th tier / the value plane) OR formally admit two stride families. **The user's exact consumer is the 3D case → it is a sibling cascade, not the 2-axis spine.** +2. **Spatial distance is not LUT-based yet** — `splat3d::depth_cascade::cascade_block` gates on screen-space-error + depth certificate (live floats), not `table[s][byte]` sums. "3 tier lookups" is realized for semantic, CONJECTURE for spatial until a splat position is encoded to centroid bytes through `DistanceTables`. + +### The single proof that makes it "hard" (theorem-checker) +The **bit-partition identity:** the base-4 depth-4 centroid-tree decomposition (the router's tree) must be the *same* partition of the tier-byte that the WHT sign-pyramid sums over (the encoder's basis). The canon's "byte's **nibbles** are the centroid's ancestry" is an arithmetic slip — a depth-4 4-ary tree needs **four 2-bit crumbs**, not two base-16 nibbles; the 16-ary GUID tree and the 4-ary centroid tree are different trees over the same 8 bits. Pin them to ONE partition → router+encoder harden [H]→[G]. It does NOT rescue the physics-duality claim (separate, false). + +### Falsifiable obligations + Core deliverables (all layout-preserving) +- Probes: **bit-partition identity**; **PROBE-CODEBOOK-44** (semantic hierarchical-4⁴ VQ ρ vs 0.9973/0.965); **pin-spatial-to-2-axis** (or admit the 3D-Hilbert sibling); **norm-preserving-T test** (predicted FAIL → confirms strip the physics); **disjoint-schema regression** (`Cognitive ∩ Compressed = {Fingerprint, EntityType}` only); **revert `ReadMode::DEFAULT=Full` POC → Bootstrap** before claiming the click below the key (`read_mode_default_is_full_poc` is the tripwire). +- Core (converged across agents): `ClassView::{axis_binding, centroid_codebook}` (per-class spatial/wave selector); a `Field` `ValueSchema` preset (Energy in the compressed set); the Jirak reword of the "top gaussian preserved" Parseval line. + +**Consumer landing split:** PARTICLE consumers (woa-rs, medcare-rs, q2-as-gotham/neo4j, odoo-rs, openproject) = AR Class via `classid→ClassView` + `EdgeBlock(CoarseOnly)` + `Cognitive` schema + `ActionInvocation`(RBAC+MUL). WAVE consumer (OSM→splat→electricity, **3D → sibling cascade**) = same `NodeGuid` router family + `Compressed`/`Field` schema (Helix/Turbovec/Energy) + PQ coupling + deterministic recurrence (no RBAC). They SHARE the address-router + codebook-selection-by-classid; they do NOT share the value slab or the lifecycle. + +--- + +## 2026-06-18 — E-AR-DO-WIRING — ruff (static) + ARM (dynamic) meet at SPO; AR Class splits along DOLCE into THINK+DO; consumers land off the harvest + +**Status:** FINDING (cross-session converged: odoo-rs + OGAR-contract + openproject-nexgen-rs blast-radius surveys all confirm additive convergence onto the #534 keystone). Landing recipe: `docs/OGAR_CONSUMER_API.md`. + +**One frame — how a domain becomes an OGAR node, and where each consumer plugs in:** + +``` +ruff_cpp_spo / ruff-Rails harvest (STATIC: has_function, reads_field, inherits_from, validation_kind) + + lance-graph-arm-discovery / ARM (DYNAMIC: runtime (X→Y) rules, codebook-deterministic) + └──────────────► MEET at SPO {s,p,o,f,c} ──► the OGAR Core (classid → ClassView) + │ + AR Class splits along DOLCE (one node, two arms): │ + • fields → Endurant → THINK : ValueTenant cols + MethodSig (ClassMethods/methods_for) + • methods → Perdurant → DO : ActionDef (ClassActions/actions_for) + ActionInvocation + • inheritance → classid → ClassView (one mechanism, both arms; child overrides parent by key) + • relations → EdgeBlock (12 in-family + 4 out) + ▼ + consumer generates `const ClassMethods` (THINK) + `const ClassActions` (DO) from ITS harvest, + binds classid OGAR-side, writes THIN classid-keyed adapters that ASSUME the Core, + commits through the gate: def-match → RBAC (ActorContext) → state-guard → MUL (GateDecision) + → ExecTarget::SurrealQl (the AR-shaped API surface; NOT a per-crate endpoint). +``` + +**The three claims that let a consumer land NOW (no waiting):** +1. **The harvest IS the manifest.** Don't hand-author the object model — generate the `const` tables. `has_function`→`ActionDef.predicate`; `inherits_from`→`overrides` (filled at codegen by `mro::resolve_overrides`); `reads_field`→`ValueSchema`/`FieldMask`. (odoo-rs: `mro` gains a downstream consumer, no change.) +2. **Static + dynamic are one SPO.** ruff = the static skeleton; ARM = dynamic enrichment; both emit `{s,p,o,f,c}`. A consumer with only a static harvest lands the full THINK+DO shape today; ARM refines `(f,c)` later. +3. **DOLCE draws the THINK/DO line, not the consumer.** Perdurant (methods/events) = DO; Endurant/Quality (fields/state) = THINK. `aerial/ontology.rs` already emits the `dolce_id`. + +**Per-consumer entry points (from the 3 blast-radius surveys):** +- **odoo-rs:** `od-posting::_post` IS an `ActionInvocation` (ExecTarget::SurrealQl) — the runtime DO consumer. `od-ontology` stays zero-dep (NO `RegistryClassView` dep without a council). `mro::resolve_overrides` = the `overrides` source at codegen. +- **openproject-nexgen-rs:** `op-surreal-ast` imports only `codegen_spine::Triple` (unchanged). Two net-new lifts: `ogar-from-ruff` `Model::functions → ClassActions/ActionDef` (DO producer); a registration hop `Class → register_class_path` + classid bind (THINK next-hop). +- **tesseract-rs:** the cheapest existence proof — its pipeline is already byte-parity green; generate one class's `ClassActions` from `has_function`. + +**Convergence flagged (3 instances now → unify):** override-resolution appears as `effective_actions` (DO, per-class) + `mro::resolve_overrides` (whole-manifest) + `ClassView` inherit — same child-overrides-parent-by-key relation. Crosses the abstraction threshold → an `Overridable`/keyed-merge primitive (council-gated). + +**Guards (every consumer holds):** ENVELOPE_LAYOUT_VERSION=2 / NODE_ROW_STRIDE=512 must not move; `from_guid_prefix` high-classid-u16→None refusal + 16M-per-basin ceiling are now addressing-load-bearing; `ClassView::{compute_dag, constraints}` (computed-field recompute/validation) grows the EXISTING ClassView, never a new layer. + +--- + ## 2026-06-18 — E-SOA-CYCLE-OWNERSHIP — cycle is per-mailbox + per-cycle, LE-contract-enforced; multi-mailbox interlaces; non-fitting consumers get OGAR classid→schema **Status:** FINDING (operator-ratified architecture sync; the wiring itself is 5+3-gated before code). Repo brought in sync via the `bindspace-singleton-to-mailbox-soa-v1` ERRATA ADDENDUM 2026-06-18c. diff --git a/.claude/board/INTEGRATION_PLANS.md b/.claude/board/INTEGRATION_PLANS.md index e6e62812..e28f22c9 100644 --- a/.claude/board/INTEGRATION_PLANS.md +++ b/.claude/board/INTEGRATION_PLANS.md @@ -1,3 +1,14 @@ +## 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`. +**Owns:** the named first proof for the one Core gap `ClassView::compute_dag` — a spreadsheet (≡ Stockfish-NNUE's incremental eval) projected onto the shader: cells=SoA rows (2-axis (row,col)), formulas=`ActionDef`+`depends_on`, edit→topological recompute gated per-cell by the cycle-aware `write_row`. +- **Inc 0:** `ClassView::compute_dag(classid)` — per-class topological recompute manifest from `depends_on`+`emitted_by`; registry-build rejects cycles; layout-preserving default-method (mirrors `value_schema`). **Inc 1:** minimal sheet harness (cells/formulas/tenants). **Inc 2:** topological recompute on edit, each a `write_row(cycle)`. **Inc 3:** success = cycle-stamped dirty-propagation in topological order + loop-rejected-at-build + Cognitive∩Compressed disjoint + incremental≡full-recompute parity (the NNUE invariant). +- **Why first:** lands the gap for EVERY computed-field AR consumer at once (odoo @api.depends/medcare/woa/q2 all reduce to a sheet); 2-axis dodges the 3D-Hilbert + bit-partition open questions. +**Scope line:** recompute STRUCTURE transfers; per-cell formula semantics are general compute via DO arm/UnifiedStep, not the WHT field. Independent of lite-unified / surreal-kv-lance / the 3D-spatial thread. +**Repos:** lance-graph (contract `ClassView` extension). Branch `claude/particle-wave-click-epiphany`. + +--- + ## 2026-06-18 — lite-unified-surrealql-lance-v1 (one store + one query surface, feature-gated; CONJECTURE, test-don't-commit) **Status:** CONJECTURE / design — feature-gated test path, NOT a default-build change. **Plan file:** `.claude/plans/lite-unified-surrealql-lance-v1.md`. diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index d2f92444..34101d14 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -89,6 +89,8 @@ ## Current Contract Inventory (lance-graph-contract) +> **2026-06-18 — ADDED (probe-excel-compute-dag-v1 Inc 0, the `compute_dag` Core gap)**: `lance_graph_contract::class_view::{ComputeEdge, compute_dag_is_acyclic}` + `ClassView::compute_dag(class) -> &[ComputeEdge]` (default `&[]`, zero-fallback). `ComputeEdge {target: u8, inputs: &'static [u8]}` is the harvest-sourced recompute edge (`emitted_by` target ← `depends_on` inputs; field positions index the class `FieldMask`), `const`-constructible like `MethodSig`/`ActionDef` (the harvest IS the manifest). `compute_dag_is_acyclic` is the **registry-build gate** — a cyclic recompute DAG (formula loop / `@api.depends` cycle / self-loop) is rejected at build (Kahn over ≤64 positions, allocation-free; out-of-range positions ignored, no panic, mirrors `FieldMask::from_positions`). This is the Core home for computed-field recompute *dispatch* that EVERY computed-field AR consumer needs (Odoo `@api.depends`, Excel formulas, medcare lab-trends, woa calc, q2 cells — they reduce to a sheet; `E-EXCEL-SHADER-PROJECTION`) and the NNUE-incremental existence-proof shape (`E-CHESS-TENSOR-PROVEN`). **Layout-preserving**: a default trait method + a free fn, resolution metadata ABOVE the SoA, stores nothing on the row, zero `NODE_ROW_STRIDE`/`ENVELOPE_LAYOUT_VERSION` impact (core-gap-auditor's EXTEND-CORE, never an adapter-state hack). The instance recompute that consumes it is gated per-cell by the cycle-aware `write_row` (`E-SOA-CYCLE-OWNERSHIP`). Additive, zero-dep; +4 tests (default-empty, acyclic-chain, cycle/self-loop/3-cycle rejected, out-of-range ignored); 10/10 class_view, clippy/fmt clean. Sibling `ClassView::constraints` (`validation_kind`-sourced) deferred to Inc-follow-up. Plan: `.claude/plans/probe-excel-compute-dag-v1.md`. Branch `claude/particle-wave-click-epiphany`. + > **2026-06-18 — ADDED (D-DO-ARM-1, the OGAR DO arm)**: `lance_graph_contract::action::{ActionState, StateGuard, ActionDef, ClassActions, actions_for, effective_actions, ActionInvocation}` — the Perdurant DO arm completing the OGAR IR (the action-axis sibling of `codegen_manifest`'s `MethodSig`/THINK). Both the 4-agent `sale_order` AR→DO probe (runtime-archaeologist) AND the merged cross-repo PR survey (ruff/OGAR/lance-graph/openproject/tesseract) agreed this was the ONE missing wire: the THINK arm (`classid → ClassView`, `has_function → MethodSig`) is converged + merged; the DO-arm `ActionInvocation`/`ActionDef` type was ABSENT. **`ActionDef`** (static, `const`-constructible, all `&'static`/`Copy`): `predicate` (= harvested `has_function` method), `object_class` (classid), `exec` (`ExecTarget` incl `SurrealQl`), `guard` (`StateGuard` = KausalSpec field==value), `required_role` (RBAC), `overrides` (OGAR `classid→ClassView` inheritance). **`ClassActions`+`actions_for`** (zero-fallback) mirror `ClassMethods`/`methods_for`. **`effective_actions(parent, child)`** = OGAR inheritance on the action axis (child overrides parent by predicate). **`ActionInvocation`** (dynamic, `Copy`): lifecycle `ActionState{Pending→Committed|Failed|Cancelled}` (sticky terminals), S2.5 `cycle` stamp, idempotency/trace keys, HLC `emitted_at_millis`. **`ActionInvocation::commit(def, actor, impact, now)`** is the gated egress — RBAC FIRST (`auth::ActorContext` must hold `required_role` or be admin → else `Failed`), THEN MUL impact (`mul::GateDecision`: `Flow→Committed`+stamped, `Hold→`Pending/escalate, `Block→Cancelled`). This IS "commit to the external consumer (odoo/openproject/woa/tesseract) after the cycle decides sound." Dispatched via `UnifiedStep`/`ExecTarget`, NOT a per-crate endpoint. Additive, zero-dep. +5 tests green. Consumer reference: `docs/OGAR_CONSUMER_API.md`. Branch `claude/soa-write-deinterlace-inc2`. > **2026-06-18 — ADDED (D-UNICHARSET-KEYSTONE, classid → ClassView → adapter wiring)**: `lance_graph_contract::unicharset_adapter::{UniCharSetStore, UniCharCall, UniCharOut, DispatchError, invoke_unicharset}` — steps 2–3 of `PROBE-OGAR-ADAPTER-UNICHARSET`, the keystone composing the proven `UniCharSet` adapter through the OGAR Core's three movable parts. `invoke_unicharset(registry, store, classid, call)`: (1) **ClassView composition gate** — `codegen_manifest::methods_for(registry, classid)` must list the call's method (the harvested `has_function` manifest), else `MethodNotComposed` (zero-fallback: an unconfigured classid composes nothing); (2) **content-store tier** — `UniCharSetStore::unicharset(classid)`, a consumer-provided trait (dependency-inverted like `ClassView`/`PlannerContract`; the adapter holds NO state — `I-VSA-IDENTITIES`); (3) **adapter leaf** — routes to `UniCharSet::{id_to_unichar, unichar_to_id}`. DO-in (`UniCharCall`) / DO-out (`UniCharOut`, zero-copy borrow). **Byte-parity inherited** from `UniCharSet` (112/112); the keystone proves the dispatch path is faithful (the `NULL`→space edge survives it), the gate works, and there is **no Core gap** (the doctrine's iron guard holds — the variable-length bijection rides the content tier cleanly). NOT routed through the heavy `OrchestrationBridge` (cross-subsystem router); this is the adapter-invocation primitive a `UnifiedStep` calls. Additive, zero-dep. +5 tests; clippy `--all-targets -D warnings` + fmt clean. Completes the core-first doctrine END-TO-END for the unicharset leaf (`E-CPP-KEYSTONE-1`). diff --git a/.claude/plans/probe-excel-compute-dag-v1.md b/.claude/plans/probe-excel-compute-dag-v1.md new file mode 100644 index 00000000..5f62b258 --- /dev/null +++ b/.claude/plans/probe-excel-compute-dag-v1.md @@ -0,0 +1,76 @@ +# probe-excel-compute-dag-v1 — land `ClassView::compute_dag` on the clean 2-axis grid (the NNUE-incremental existence proof) + +> **Status:** CONJECTURE / probe scope. The named first proof for the one Core gap +> (`ClassView::compute_dag`), on the case that can't hit the 3D-Hilbert crack. +> **Grounded by:** `E-CHESS-TENSOR-PROVEN` (Stockfish NNUE *is* this, at +> world-champion strength), `E-EXCEL-SHADER-PROJECTION` (the demonstrator), +> `E-OGAR-ROUTER-ENCODER` (2-axis = GREEN), `E-SOA-CYCLE-OWNERSHIP` + +> `mailbox-cycle-aware-write-contract` (`write_row`), the DO-arm `action.rs`. + +## The claim being proven + +A spreadsheet (≡ a chess board's incremental-eval loop) projected onto the +shader: **cells = SoA rows (2-axis `(row,col)` router address); formulas = +`ActionDef`s with `depends_on` edges; editing a cell triggers a topological +recompute of dependents, gated per-cell by the cycle-aware `write_row`.** This +is NNUE's "only small input changes between neighboring positions → incrementally +update," re-expressed as `ClassView::compute_dag` + `write_row`. + +## Why this probe (not a bigger build) + +- **Lands the one Core gap** (`ClassView::{compute_dag, constraints}`, core-gap-auditor) + that EVERY computed-field AR consumer (odoo `@api.depends`, medcare lab-trends, + woa calc, q2 cells) needs — they all reduce to a sheet. +- **2-axis → dodges the open questions:** no 3D-Hilbert axis-count crack, no + bit-partition proof needed (those gate the *spatial-wave* `E-OGAR-ROUTER-ENCODER` + side, not this recompute structure). +- **Proven shape:** NNUE/AlphaZero are the existence proof; this is re-derivation, + not invention. + +## Increments + +0. **`ClassView::compute_dag(classid) -> &[ComputeEdge]`** (the Core extension) — + a per-class topological recompute manifest sourced from `depends_on` + + `emitted_by` (harvest-shaped); registry-build **rejects cycles**. Mirrors the + existing `ClassView::{fields, value_schema}` resolvers; stores nothing on the + row (the harvest IS the manifest). Layout-preserving (no `NodeRow`/stride/ + `ENVELOPE_LAYOUT_VERSION` change). Sibling: `ClassView::constraints` (deferred + to a follow-up; `validation_kind`-sourced). +1. **Minimal sheet harness** — cells = `MailboxSoA` rows, addressed `(row,col)` + (the 2-axis `axis_binding=Spatial(x/y)` from `ClassView::axis_binding`); + each cell's value in a value tenant (`Energy` numeric); formulas as + `ActionDef`s (`predicate`=op, `depends_on`=precedent cells). +2. **Topological recompute on edit** — edit a cell → dirty-set → recompute + dependents in `compute_dag` topological order, each recompute a `write_row(cycle)` + so it carries the recompute-generation; the dispatch is the `CognitiveShader` + "can't-NOT-recompute-while-dirty" loop. +3. **Success / falsifier criteria:** + - editing `A1` dirties + recomputes its transitive dependents in topological + order, each cycle-stamped (`last_write_cycle`); a non-dependent cell is NOT + recomputed. + - a formula loop is **rejected at registry-build** (cycle in `compute_dag`). + - `ValueSchema::Cognitive ∩ Compressed = {Fingerprint, EntityType}` disjointness + holds (no over-collapse, ripple guard). + - incremental-update parity: recomputing only the dirty-set yields the same + result as a full recompute (the NNUE incremental ≡ full-eval invariant). + +## Scope line (do not over-claim) + +"Shader projection" = the **dependency-driven recompute dispatch** (`compute_dag` ++ cycle), NOT Walsh-Hadamard field synthesis. Per-cell formula *semantics* +(`=VLOOKUP`, `=IF`) are general compute dispatched via the DO arm / `UnifiedStep` +— exactly as a chess engine's per-square eval is general, not the field encoder. + +## Deliverables carried (all layout-preserving) + +`ClassView::compute_dag` (this probe) → unblocks computed-field adoption for all +AR consumers. Pairs with the already-converged `ClassView::axis_binding` +(Spatial for the sheet) + the `Field` `ValueSchema` preset. Chess-side design +reference: NNUE incremental update + AlphaZero `8×8×C` planes (`E-CHESS-TENSOR-PROVEN`). + +## Blockers / sequencing + +- `compute_dag` is a `lance-graph-contract::ClassView` trait extension — additive, + default-method, mirrors `value_schema`. No stride/version impact. +- Independent of the 3D-spatial / `lite-unified` / surreal-kv-lance threads. +- Best first proof: it lands the gap for every computed-field consumer at once. diff --git a/crates/lance-graph-contract/src/class_view.rs b/crates/lance-graph-contract/src/class_view.rs index fecf8b1e..68f1f0c5 100644 --- a/crates/lance-graph-contract/src/class_view.rs +++ b/crates/lance-graph-contract/src/class_view.rs @@ -138,6 +138,130 @@ impl FieldMask { } } +/// One recompute edge in a class's **compute DAG**: field position `target` is +/// (re)computed from the field positions in `inputs`. +/// +/// Harvest-sourced — `target` is the `emitted_by` field, `inputs` are its +/// `depends_on` precedents (Odoo `@api.depends`, an Excel formula's referenced +/// cells, a chess-eval feature's inputs). All fields are `&'static` so a +/// generated `const DAG: &[ComputeEdge] = &[..]` compiles (the harvest IS the +/// manifest — mirrors [`crate::codegen_manifest::MethodSig`] / +/// [`crate::action::ActionDef`]). Positions index the class's [`FieldMask`] +/// (0..[`FieldMask::MAX_FIELDS`]), matching [`ClassView::fields`]. +/// +/// This is the Core home for recompute *dispatch* (`E-EXCEL-SHADER-PROJECTION` / +/// `probe-excel-compute-dag-v1`): the manifest lives ABOVE the SoA (resolution +/// metadata, stores nothing on the row); no adapter carries its own `@api.depends` +/// table (`core-first-transcode-doctrine` — that would be the Adapter-State-Leak). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ComputeEdge { + /// Field position this edge recomputes (the `emitted_by` target). + pub target: u8, + /// Field positions this target reads (its `depends_on` precedents). + pub inputs: &'static [u8], +} + +/// Whether a class's `compute_dag` is **acyclic** — the registry-build gate. +/// +/// A cyclic recompute DAG (a formula loop `A=B+1, B=A+1`, or a `@api.depends` +/// cycle) MUST be rejected at registry-build: it has no topological order and +/// would never converge. Returns `false` on any cycle (incl. a self-loop +/// `target ∈ inputs`). Considers only positions `< FieldMask::MAX_FIELDS`; +/// out-of-range targets/inputs are ignored (no panic, mirrors +/// [`FieldMask::from_positions`]). Allocation-free (≤ 64 positions). +#[must_use] +pub fn compute_dag_is_acyclic(edges: &[ComputeEdge]) -> bool { + const N: usize = FieldMask::MAX_FIELDS as usize; // 64 + // deps[t] = bitmask of in-range positions that target `t` depends on. + let mut deps = [0u64; N]; + let mut is_target = 0u64; + for e in edges { + if (e.target as usize) >= N { + continue; + } + is_target |= 1u64 << e.target; + for &inp in e.inputs { + if (inp as usize) < N { + deps[e.target as usize] |= 1u64 << inp; + } + } + } + // Kahn: leaves (non-targets) are resolved; peel any target whose deps are all + // resolved. If a round makes no progress with targets remaining → cycle. + let mut resolved = !is_target; + let mut remaining = is_target; + loop { + if remaining == 0 { + return true; + } + let mut progressed = false; + let mut r = remaining; + while r != 0 { + let t = r.trailing_zeros(); + r &= r - 1; + if deps[t as usize] & !resolved == 0 { + resolved |= 1u64 << t; + remaining &= !(1u64 << t); + progressed = true; + } + } + if !progressed { + return false; // stuck with targets remaining → cycle + } + } +} + +/// A valid **recompute order** for a class's `compute_dag` — the target field +/// positions in an order where every target appears after all targets it +/// (transitively) depends on. `None` if the DAG is cyclic (no topological order). +/// +/// Leaves (positions that are only inputs, never targets) are not included — they +/// are the already-present values a recompute reads. Within one Kahn round the +/// resolved targets are mutually independent, so any order among them is valid. +/// The consumer recomputes targets in this order, each gated by the cycle-aware +/// `write_row` (`probe-excel-compute-dag-v1` Inc 2). Allocation = one `Vec` of +/// the target positions (≤ 64). +#[must_use] +pub fn compute_dag_topo_order(edges: &[ComputeEdge]) -> Option> { + const N: usize = FieldMask::MAX_FIELDS as usize; // 64 + let mut deps = [0u64; N]; + let mut is_target = 0u64; + for e in edges { + if (e.target as usize) >= N { + continue; + } + is_target |= 1u64 << e.target; + for &inp in e.inputs { + if (inp as usize) < N { + deps[e.target as usize] |= 1u64 << inp; + } + } + } + let mut resolved = !is_target; + let mut remaining = is_target; + let mut order = Vec::with_capacity(is_target.count_ones() as usize); + loop { + if remaining == 0 { + return Some(order); + } + let mut progressed = false; + let mut r = remaining; + while r != 0 { + let t = r.trailing_zeros(); + r &= r - 1; + if deps[t as usize] & !resolved == 0 { + resolved |= 1u64 << t; + remaining &= !(1u64 << t); + order.push(t as u8); + progressed = true; + } + } + if !progressed { + return None; // cycle + } + } +} + /// The class as a **meta lookup that flies above the SoA** — the resolver trait. /// /// An implementor (in `lance-graph-ontology`, over the OGIT cache) is the @@ -246,6 +370,24 @@ pub trait ClassView { // signed / fingerprint / …), it adds no new property. crate::canonical_node::ValueSchema::Full } + + /// The class's **recompute DAG** — the topological manifest of which fields + /// recompute from which (the `emitted_by` + `depends_on` harvest), the Core + /// home for computed-field dispatch (Odoo `@api.depends`, Excel formulas, + /// chess-eval features; `probe-excel-compute-dag-v1`). + /// + /// Default `&[]` — the zero-fallback: an unconfigured class has no computed + /// fields (mirrors `compute_dag`'s no-panic siblings). An implementor returns + /// a generated `const &[ComputeEdge]`; the registry MUST validate it with + /// [`compute_dag_is_acyclic`] at build (a cyclic DAG is rejected, never + /// recomputed). Layout-preserving: resolution metadata above the SoA, stores + /// nothing on the row, never a `NODE_ROW_STRIDE`/`ENVELOPE_LAYOUT_VERSION` + /// change. The instance recompute that consumes this is gated per-cell by the + /// cycle-aware `write_row` (`E-SOA-CYCLE-OWNERSHIP`). + #[inline] + fn compute_dag(&self, _class: ClassId) -> &[ComputeEdge] { + &[] + } } /// One populated field to render — the late-resolved `label` + its `predicate` key. @@ -319,6 +461,151 @@ mod tests { } } + // ── compute_dag (probe-excel-compute-dag-v1, Inc 0) ────────────────────── + + /// Default `compute_dag` is the zero-fallback empty manifest (no computed + /// fields for an unconfigured class). + #[test] + fn compute_dag_default_is_empty() { + let c = FakeClasses { invoice: vec![] }; + assert!(c.compute_dag(7).is_empty()); + assert!(c.compute_dag(0).is_empty()); + } + + /// `const`-constructible manifest — the exact shape a generated + /// `const DAG: &[ComputeEdge]` emits (a chain: f2 = g(f1), f1 = h(f0)). + const SAMPLE_DAG: &[ComputeEdge] = &[ + ComputeEdge { + target: 1, + inputs: &[0], + }, + ComputeEdge { + target: 2, + inputs: &[1], + }, + ]; + + #[test] + fn compute_dag_acyclic_chain_passes() { + assert!( + compute_dag_is_acyclic(SAMPLE_DAG), + "a dependency chain f0→f1→f2 is acyclic" + ); + assert!(compute_dag_is_acyclic(&[]), "empty dag is acyclic"); + // a target reading a non-computed leaf is fine + assert!(compute_dag_is_acyclic(&[ComputeEdge { + target: 5, + inputs: &[3, 4] + }])); + } + + #[test] + fn compute_dag_cycle_is_rejected() { + // f0 = g(f1), f1 = h(f0) — a 2-cycle, no topological order. + let two_cycle = &[ + ComputeEdge { + target: 0, + inputs: &[1], + }, + ComputeEdge { + target: 1, + inputs: &[0], + }, + ]; + assert!( + !compute_dag_is_acyclic(two_cycle), + "a formula loop must be rejected at registry-build" + ); + // self-loop f0 = g(f0) + assert!(!compute_dag_is_acyclic(&[ComputeEdge { + target: 0, + inputs: &[0] + }])); + // 3-cycle f0→f1→f2→f0 + assert!(!compute_dag_is_acyclic(&[ + ComputeEdge { + target: 1, + inputs: &[0] + }, + ComputeEdge { + target: 2, + inputs: &[1] + }, + ComputeEdge { + target: 0, + inputs: &[2] + }, + ])); + } + + #[test] + fn compute_dag_out_of_range_positions_ignored() { + // target/inputs >= MAX_FIELDS (64) are ignored, never folded → no panic, + // no false cycle (mirrors FieldMask::from_positions). + assert!(compute_dag_is_acyclic(&[ + ComputeEdge { + target: 64, + inputs: &[0] + }, // ignored target + ComputeEdge { + target: 5, + inputs: &[200] + }, // input ignored → leaf-only target + ])); + } + + #[test] + fn compute_dag_topo_order_respects_dependencies() { + // chain f0→f1→f2: f1 must come before f2; f0 is a leaf, not emitted. + let order = compute_dag_topo_order(SAMPLE_DAG).expect("acyclic has an order"); + assert_eq!(order.len(), 2, "two targets (f1, f2); f0 is a read-only leaf"); + let pos1 = order.iter().position(|&t| t == 1).unwrap(); + let pos2 = order.iter().position(|&t| t == 2).unwrap(); + assert!(pos1 < pos2, "f1 recomputed before its dependent f2"); + // empty manifest → empty order, not None. + assert_eq!(compute_dag_topo_order(&[]), Some(vec![])); + } + + #[test] + fn compute_dag_topo_order_none_on_cycle() { + // a 2-cycle has no topological order — None, matching is_acyclic == false. + let two_cycle = &[ + ComputeEdge { + target: 0, + inputs: &[1], + }, + ComputeEdge { + target: 1, + inputs: &[0], + }, + ]; + assert!(compute_dag_topo_order(two_cycle).is_none()); + assert!(!compute_dag_is_acyclic(two_cycle)); + } + + #[test] + fn compute_dag_topo_order_diamond() { + // f3 = g(f1, f2); f1 = h(f0); f2 = k(f0). f0 leaf. f1,f2 before f3. + let diamond = &[ + ComputeEdge { + target: 1, + inputs: &[0], + }, + ComputeEdge { + target: 2, + inputs: &[0], + }, + ComputeEdge { + target: 3, + inputs: &[1, 2], + }, + ]; + let order = compute_dag_topo_order(diamond).expect("acyclic"); + let p = |t: u8| order.iter().position(|&x| x == t).unwrap(); + assert!(p(1) < p(3) && p(2) < p(3), "both precedents before the join"); + assert_eq!(order.len(), 3); + } + #[test] fn field_mask_is_presence_bits() { let m = FieldMask::from_positions(&[0, 2]); // amount + partner populated, tax absent diff --git a/crates/perturbation-sim/COMPUTE_DAG_MAPPING.md b/crates/perturbation-sim/COMPUTE_DAG_MAPPING.md new file mode 100644 index 00000000..9d154e73 --- /dev/null +++ b/crates/perturbation-sim/COMPUTE_DAG_MAPPING.md @@ -0,0 +1,78 @@ +# perturbation-sim ≡ `compute_dag` — the electricity cascade IS the topological recompute + +> **Status:** FINDING (conceptual mapping; no code dependency added). `perturbation-sim` +> stays zero-dep / workspace-excluded — this is a doc-level join, not a `lance-graph-contract` +> import. The two crates share a *mechanism*, proven on opposite ends: this crate ships the +> certified physics + full recompute + the router/field/witness encoders; `compute_dag` +> (`lance-graph-contract::class_view`) ships the incremental dispatch the physics bound certifies. +> +> **Grounds:** `E-CHESS-TENSOR-PROVEN`, `E-EXCEL-SHADER-PROJECTION`, `E-OGAR-ROUTER-ENCODER`, +> `probe-excel-compute-dag-v1`. **Cross-ref iron rule:** `I-NOISE-FLOOR-JIRAK` (significance +> uses Jirak 2016, not IID Berry–Esseen), `I-VSA-IDENTITIES` (the numeric witness arc is NOT +> the contract's identity `WitnessTable` — this crate's own `witness.rs` already guards that). + +## The one-line claim + +A cascading grid outage and a spreadsheet recompute are **the same dependency-driven +topological recompute**. Trip a line → redistribute flow → re-trip whatever now overloads +is, *structurally*, edit a cell → dirty its dependents → recompute them in topological order. +`perturbation-sim::simulate_outage` is the physical instance; `ClassView::compute_dag` + +`compute_dag_topo_order` + `write_row` is the abstract substrate. NNUE proves the same shape +at world-champion strength (`E-CHESS-TENSOR-PROVEN`). + +## The mapping (each row a mechanism, not a rhyme) + +| `perturbation-sim` (physical grid) | `compute_dag` / OGAR substrate | what it is | +|---|---|---| +| `simulate_outage` round loop (trip → recompute survivors → re-trip) | `compute_dag_topo_order` recompute dispatch (edit → dirty-set → recompute dependents) | **the cascade ≡ the topological recompute** | +| `PerturbationShape::trip_round[e]` (round each line tripped; `0` = seed) | the topological *generation* of each recomputed field = its position in `compute_dag_topo_order` | **trip generation = topo generation** | +| seed `alive[seed_line]=false` → rank-1 `E` on Laplacian `L` | the dirty seed: `write_row(seed_cell, cycle)` | **the trip = the gated edit** | +| `spectral_perturbation`: Weyl `\|λᵢ(L′)−λᵢ(L)\| ≤ ‖E‖₂`, Davis–Kahan `sinθ ≤ ‖E‖₂/gap` | the **NNUE incremental ≡ full** invariant: a bounded local edit perturbs the global field by a bounded amount, so recomputing only the dirty-set provably equals a full recompute | **the bound that certifies incrementality** | +| `PerturbationShape::node_field` (per-bus magnitude, the red footprint) | the wave/field readoff over the grid (`E-OGAR-ROUTER-ENCODER` field side) | **the wave** | +| `splat::morton2` (x/y nibble-interleave) | the 2-axis router ADDRESS (`HEEL/HIP/TWIG` 256×256 tile) | **the router** (`E-OGAR-ROUTER-ENCODER` GREEN 2-axis case) | +| `sketch::fwht` + `walsh_pyramid_energy` | the deterministic FIELD ENCODER (Walsh–Hadamard pyramid) | **the encoder** | +| `witness::particle_equals_wave` (Parseval over FWHT) | particle (pointer-chase `∑field·arc`) ≡ wave (one transform, many arcs) | **the particle/wave click, proven on a real field** | + +## The crucial honesty (why this is a join, not a merge) + +The two ends are **complementary halves**, deliberately: + +- **`perturbation-sim` does the EXACT FULL recompute each round** — `simulate_outage` recomputes + DC flows on the *surviving* network from scratch every round ("robust where iterated single-line + LODF would drift", per `cascade.rs`). It is NOT incremental. What it ships *alongside* is the + **certification apparatus**: `spectral_perturbation`'s Weyl/Davis–Kahan bounds are exactly the + inequalities an incremental scheme needs to prove it equals the full recompute. +- **`compute_dag` is the INCREMENTAL dispatch** — recompute only the dirty dependents, in + `compute_dag_topo_order`, each gated by the cycle-aware `write_row`. The Weyl bound this crate + certifies is *why* that incremental recompute is sound (a bounded local change → bounded global + perturbation → the dirty-set is the complete support of the change). + +So: **this crate = the proof + the full reference + the router/field/witness encoders; the +`compute_dag` harness = the incremental consumer whose equivalence this crate's Weyl bound +certifies.** Stockfish NNUE is the existence proof that the incremental side works at scale; +this crate is the existence proof that the bound holding it together is real. + +## What this does NOT claim (scope guard) + +- **No speed claim.** `witness.rs` already states the particle/wave win is "one field, many arcs + whose spectra are reusable", not a measured single-arc speedup. This doc inherits that honesty. +- **No new dependency.** `perturbation-sim` remains zero-dep, workspace-excluded, standalone. + This is a conceptual bridge; the only wiring is the optional `ndarray-simd` git feature that + already existed for the eigensolver/reliability path. +- **The numeric witness arc ≠ the contract `WitnessTable`.** Already guarded in `witness.rs` + (`I-VSA-IDENTITIES` register-loss / Frankenstein hazard). The mapping above pairs *mechanisms* + (Parseval ≡ particle/wave), never the value categories (`&[f64]` field vs 6-bit W-slot identity). +- **Per-node/per-cell evaluation semantics are general compute**, dispatched via the DO arm / + `UnifiedStep` — not the Walsh field. The *recompute structure* transfers; the *formula content* + (`=VLOOKUP`, a per-square chess eval, a per-bus injection) does not. Same line `E-EXCEL` / + `E-CHESS` already drew. + +## Consumer payoff + +The probe `probe-excel-compute-dag-v1` lands `ClassView::compute_dag` on the clean 2-axis sheet. +This doc shows the *same* `compute_dag` already has a physical, certified instance here: an OSM → +gaussian-splat → electricity-perturbation consumer (the 3D sibling cascade of `E-OGAR-ROUTER-ENCODER`) +is a `compute_dag` consumer whose recompute order is `simulate_outage`'s round structure and whose +incremental-equivalence is Weyl-bounded. Every computed-field AR consumer (odoo `@api.depends`, +medcare lab-trends, woa calc, q2 cells, **and** the electricity-cascade analytics) reduces to the +same `compute_dag_topo_order` dispatch.