diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 72cd7602..839c1568 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,33 @@ +## 2026-07-02 — E-V3-TEMPORAL-DEINTERLACE-1: temporal.rs is the read side of the WAL ruling — replay = a read at a pinned QueryReference +**Status:** FINDING (operator pointer to planner temporal.rs; unifies M24 + M25 with the deinterlace machinery) + +planner/src/temporal.rs already ships the four-clock deinterlace (lance version stream / surrealql knowable_from / ractor per-actor V_ref / thinking trajectory; HLC key (server_id, lance_version, hlc_tick); EpistemicMode rung ladder Strict/Aware/Retro; two-axis dispatchable = time-admitted AND data-ready via DependsClosure). Three consequences for W1: (1) stacked-casts-never-refused is PROVABLE — no reader reads raw state, a Strict reader at V_ref cannot observe unacked/future frames, so write-side gating is structurally unnecessary ("the write masks the thinking and vice versa" = the epistemic classification IS the mask); (2) M24 crash-replay, M25 session-replay, and Lance time-travel are ONE mechanism — QueryReference::at(v, rung) + deinterlace; KanbanSessionStorage implements no new replay machinery; (3) the W1b ack signature carries the assigned LanceVersion — CastId <-> LanceVersion is the join between the WAL board and the temporal classifier; unacked casts = rows on no reader's timeline yet. Bonus: the ractor actor's true payload is its V_ref reading-horizon (helper-scope, again). + +## 2026-07-02 — E-V3-PREFLIGHT-1-CORRECTION: "SoaEnvelope has zero production impls" was FALSE — NodeRowPacket is live +**Status:** CORRECTION (of E-V3-PREFLIGHT-1 delta 2 / M7; caught by codex on #630, verified canonical_node.rs:1275) + +`impl<'a> SoaEnvelope for NodeRowPacket<'a>` ships in production: the Lance-facing zero-copy envelope exposing `&[NodeRow]` as LE bytes (columns = NODE_ROW_COLUMNS, stride = NODE_ROW_STRIDE, as_le_bytes over repr(C, align(64)) with const size asserts). The preflight repeated the fleet M7 row's claim without an impl-grep — the same unverified-coverage-claim failure mode guardrails rule 10 exists for, now demonstrated on the orchestrator itself. Revised M7: SoaEnvelope (storage boundary: certification + canonical Lance byte path) and MailboxSoaView/Owner (runtime read/mutate) are COMPLEMENTARY surfaces; W1 routes storage bytes through the NodeRowPacket envelope path and tests its owner/byte-layout behavior. Process rule reinforced: negative-existence claims in rulings get the same exhaustive-grep declaration as worker reports. + +## 2026-07-02 — E-V3-GRAPHFLOW-BENCH-1: graph-flow measured at ~0.4-0.5 us/step; KanbanSessionStorage needs the delta layer — which ALREADY EXISTS (graph-flow-kanban) +**Status:** FINDING (bench + static inventory, file:line cites in AGENT_LOG; W2e input + M25 feasibility call) + +Measured (release, batch 4096 steady-state, shared container): graph-flow dispatch ~408-471 ns/task-step on the ContinueAndExecute path, ~512-538 ns stepwise — the ~80-100 ns delta IS the per-step SessionStorage get+save round-trip. Verdict for W2e two-speed: graph-flow is a sub-microsecond-ADJACENT orchestration layer, NOT the sub-us hot path (batch-1 noise spans 465-929 ns) — hot dispatch stays with ExecTarget, graph-flow owns the replayable plan/orchestration path. Exactly the operator's two-speed ruling, now with numbers. rig: Task-wrapping is production-proven (recommendation_flow.rs:180-248, generic-over-CompletionModel is the right shape — the openai Client::agent now returns the Responses-API model type); per-call cost = TWO full chat-history clones + a tool-def fetch even with zero tools (agent/completion.rs:571-586, ~200-380) — fine at oracle frequency (once per FailureTicket), prohibitive per kanban transition. rig fork = upstream-faithful 0.39.0 + 3 infra commits (toolchain pin, surrealdb-fork wiring, kv-lance-only features). M25 feasibility: SessionStorage is OVERWRITE semantics (save(Session) by value; all three impls upsert wholesale) — replay-from-moves requires a delta/event layer on top. THE DELTA LAYER ALREADY EXISTS: rs-graph-llm/graph-flow-kanban::KanbanPlanEnvelope accumulates an append-only moves: Vec (lib.rs:89,173-198) consuming lance_graph_contract kanban types + mul::GateDecision — wire, don't invent: KanbanSessionStorage = snapshot upsert (SessionStorage) + move cast through the W1b writer (the envelope's log), replay = snapshot + move log. Build wall recorded: workspace-root cargo in rs-graph-llm/rig 403s on the AdaWorldAPI/burn git submodule (via surreal-lance optional deps) even when the feature is OFF — lockfile-resolution behavior; isolated path-dep crates build clean; affects any sandboxed CI of those roots. + +## 2026-07-02 — E-V3-PLANNER-SOA-AUDIT-1: planner SoA is TYPE-LEVEL reality, wiring-level dormant — the gap is exactly the planner boundary +**Status:** FINDING (grep-verified inventory, file:line cites in AGENT_LOG; answers operator "is the planner SoA already reality?") + +The unified coupling EXISTS at the contract level: KanbanMove (kanban.rs:151) already carries mailbox + witness_chain_position + libet_offset_us + exec:ExecTarget in one <=16B Copy struct, and KanbanColumn::advance_on_gate consumes contract mul::GateDecision. WIRED: supervisor KanbanActor (contract types verbatim, zero duplication), lance-graph core SoA reads (mailbox_scan.rs, markov_soa.rs, scheduler.rs — real generic MailboxSoaView consumers), symbiont POC owner. DORMANT: (a) planner emission — style_strategy.rs is an honest pass-through ("faking a KanbanMove would be theatre"), batch_writer is the W1e todo! skeleton; (b) KanbanActor integration — only ever spawned over its own TestBoard, NEVER over the production MailboxSoA (mailbox_soa.rs:786). ABSENT: (c) lance-graph-planner never references MailboxSoaView/Owner AT ALL (zero grep hits; depends only on contract); (d) OGAR/classid awareness in planner + supervisor — zero hits both; (e) planner mul/gate.rs GateDecision {Proceed,Sandbox,Compass} is a FALSE FRIEND of contract mul::GateDecision {Flow,Hold,Block} (GATE-1/M15) — the planner's gate never reaches advance_on_gate. Consequences folded into the plan: W1b closes (a); W2b gains a spawn-over-real-MailboxSoA integration probe for (b); W2 gains a classid-awareness wiring item for (c)+(d) — MailboxSoaView ALREADY exposes class_id, wire don't invent; M15 rename upgraded to blocking-before-W2 (the collision would silently route the wrong gate into the kanban arc). + +## 2026-07-02 — E-V3-KANBAN-SESSION-1: graph-flow SessionStorage backed by the mailbox kanban board = the replayable langgraph handler +**Status:** FINDING (operator direction + Fable-5 synthesis; M25 + W3b sharpening in .claude/v3/) + +Operator: rs-graph-llm under lance-graph "might need some kanban integration to become the replayable langgraph handler." The convergence: graph-flow persists Session state through a SessionStorage trait; the V3 substrate persists write intent through the kanban board (M24: board = WAL). ONE surface: KanbanSessionStorage — task transitions = KanbanMoves cast through the W1b writer; replay = rebuild the Session from the board. Every langgraph execution becomes replayable by construction, ownership rides the cast pairing (never a DTO field), and the M18 sigma-chain/KanbanColumn mapping gains a concrete consumer. Bench worker measures graph-flow per-step overhead (batch 1/64/4096) for the W2e two-speed decision; rig is the W3c oracle-node client, never hot-path. + +## 2026-07-02 — E-V3-PREFLIGHT-1: Fable-5 ten-point preflight over every V3 layer (operator-requested, pre-W1) +**Status:** FINDING (plan deltas folded into INTEGRATION-PLAN.md Addendum 2026-07-02) + +Headline collapses: (1) the kanban board IS the write-ahead log — cast = move = intent, ack = confirmation; W1b/W1c are one object, crash recovery free (M24). (2) M7 ruling rec: SoaEnvelope re-scoped as spec/certification surface (verify_layout + field-isolation matrix are the value; zero production impls of the trait). (3) Baseline inversion: W6a scanner runs at W1 START — adoption-100% needs a measured t0 denominator. (4) W3 oracle ratchet: oracle-hit rate must trend down vs catalogue size or deterministic-first is silently dead. (5) W2 reorder probe→budget→arms; budget constants measured not guessed; probe at batch 1/64/4096; loser owns slow path. (6) Ractor batching by API shape (Vec per message). (7+8) Pull-forwards: D-PERT-1 and M21 canon-node-bytes ride W1. (9) Gate-run rule: wave PRs end with /v3-audit + touched M-row greps in AGENT_LOG. (10) Supervisor stays thin — the product is the compile-time ownership attestation, not runtime supervision. Test applied throughout: every item is a collapse/reorder of existing machinery, none invents a layer. + ## 2026-07-02 — E-V3-PLANNER-TWO-NATURES-AND-SPEED-PROBE (operator: planner too slow for sub-µs; resonance-based thinking is not DataFusion) **Status:** FINDING (operator-ruled; speed claim probe-gated per truth-architect discipline) diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 29819305..082cc44f 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -100,6 +100,7 @@ Membrane consumers can now pull BOTH halves of a render `classid` BBB-safely fro | PR | Merged | Title | What it added | |---|---|---|---| +| **#629** | 2026-07-02 | V3 SUBSTRATE consolidated entry point (`.claude/v3/`) + ractor ownership attestation | `.claude/v3/` tree shipped: README (orientation), INTEGRATION-PLAN (W0–W6), COMPONENT-MAP (reuse/repurpose/retire), ENTROPY-MILESTONES (N→1 ledger), MODULE-TABLE (per-file census core/contract/planner), soa_layout/ (LE contract, tenant lanes, consumer map, routing), knowledge/ (substrate primer, mailbox-kanban model, sonnet-worker-guardrails), agents/BOOT.md (4 V3 cards); `/v3` skill + `/v3-audit` command; CLAUDE.md/BOOT.md ★ entrypoint. Review sharpenings folded: LE byte-order range-scan caveat, 3-shape legacy corpus scanner (incl. `0xAAAA_DDCC`), ractor helper-scope ruling (NOT messaging — slow; helper only: spawn/supervision/occasional control RPC). Ownership compile attestation: `KanbanActor` `type State = O`, owner MOVES in at pre_start; 22 supervisor tests green on the AdaWorldAPI ractor fork. Merge `28f17cd7`. | | **#628** | 2026-07-02 | classid canon:custom half-order flip EXECUTED (P0+P1+P2) | `CLASSID_ORDER = CanonHigh` live: canon `domain:appid` HIGH / custom LOW (`0x0701_1000` = `0x07:01::1000`); ONE flippable composition + `classid_canon_compat` (mint-forward both-forms reader — RBAC authorizes pre-flip rows, no re-bake); new-form mint constants + `CLASSID_*_LEGACY` aliases; hhtl dual-form fold; OGAR#95 reconciled (prefix = custom half, values unchanged); ogar pin → `19373a2` (OGAR #147 lockstep). Fleet: OGAR #147 + MedCare #180 + woa-rs #177 merged; q2 #71 + op-nexgen #68 open. Merge `6858118b`. | | **#627** | 2026-07-02 | classid canon:custom flip TRIGGERED (doc-only) | Operator ruling recorded + `classid-canon-custom-flip-v1.md` ACTIVE: canon `domain:appid` → hi u16, custom (`0x1000` temporary marker) → lo; `0x0701_1000` / `0x07:01::1000`; OSINT low byte = appid space (zero vocab rows, OGAR #146 67→65 fuse balanced); q2 gate WAIVED; ISSUES ×4 resolved/ruled; codex P2 guards locked (class_id via `classid_canon(id)` never `as u16`; legacy keys demote not retire). Merge `c8e1ec4`. | | **#626** | 2026-07-02 | V3 convergence wiring: tenant-carve certification, RungElevator, P6 wave probe, seam-list plan | "Wire, don't invent": `RungLevel::{from_u8,elevate,de_elevate,pearl_level,causal_mask_bits}` + `RungElevator` (sustained-BLOCK policy over P2/P3-certified masks; converged with `escalation::rung_delta` via `apply_delta` — one ladder, two signal sources) wired through the driver (persistent elevator, `ctx.rung=1` proxy retired, grpc rung saturates-never-wraps per codex P2); BOTH V3 tenant carves matrix-certified (Cognitive + Compressed); P6 probe (wave dist == certified palette read, markov_soa verified); `[patch.crates-io] ndarray` → local sibling path (fetch deadlock gone; first in-sandbox core build, 925/925). Plan `v3-convergence-wiring-v1.md`; worker Rule 7. Branch `claude/v3-substrate-migration-review-o0yoxv`, merge `5aaee33`. | diff --git a/.claude/board/PR_ARC_INVENTORY.md b/.claude/board/PR_ARC_INVENTORY.md index b27bbc64..5fb26428 100644 --- a/.claude/board/PR_ARC_INVENTORY.md +++ b/.claude/board/PR_ARC_INVENTORY.md @@ -35,6 +35,22 @@ --- +## #629 lance-graph: V3 SUBSTRATE consolidated entry point — `.claude/v3/` tree + mailbox-kanban doctrine + ractor ownership attestation + +**Status:** MERGED 2026-07-02 (merge commit `28f17cd7`), branch `claude/v3-substrate-migration-review-o0yoxv`. + +**Added:** `.claude/v3/` — README.md (orientation + doc map, the session entry point), INTEGRATION-PLAN.md (W0–W6 wave plan), COMPONENT-MAP.md (every subsystem: reuse / repurpose / retire), ENTROPY-MILESTONES.md (systematic N→1 collapse ledger), MODULE-TABLE.md (per-file census of core / contract / planner), soa_layout/ (LE contract, tenant lanes, consumer map, routing), knowledge/ (v3-substrate-primer, mailbox-kanban-model, sonnet-worker-guardrails — MANDATORY in every Sonnet worker brief), agents/BOOT.md (the four V3 cards: v3-mailbox-warden, v3-envelope-auditor, v3-kanban-executor-engineer, v3-template-smith); `/v3` skill (bootload) + `/v3-audit` command (mechanical conformance greps); CLAUDE.md + BOOT.md ★ V3 entrypoint sections. + +**Locked:** any session touching SoA rows / tenants / mailbox ownership / kanban / thinking templates / classids / the DTO ladder STARTS at `.claude/v3/README.md`; **no singleton CollapseGate** (one mailbox = one kanban board as tenant); SoaEnvelope LE ownership (`mailbox_owner()`, write-on-behalf iron rule); compiled thinking templates (elixir-template × StepMask, Rig as oracle, compile-down direction only); classid canon-high + the `0x1000` V3-adoption monitor; PerturbationDto/Resonance split (D-PERT-1); **ractor scope (operator-ruled): NOT for messaging (slow) — helper only** (spawn, supervision, occasional serialized control RPC), hot dispatch stays with the D-V3-W2e-probed ExecTarget; mailbox-as-owner is compiler-enforced — `KanbanActor` has `type State = O`, the owner MOVES into the actor at `pre_start` (attested: 22 supervisor tests green, `--features supervisor`, AdaWorldAPI ractor fork per P0). + +**Corrections shipped in-arc (review threads):** LE byte-order caveat — domain range-scans go over the DECODED u32 (or big-endian keys), never raw LE key-byte prefixes (`to_le_bytes` puts the custom byte first); legacy corpus scanner widened to all THREE legacy shapes incl. `0xAAAA_DDCC` render-prefix-high (the `classid_canon_compat` CanonLow set). + +**Deferred:** W1–W6 wave execution (plan is the map, not the work); template-catalogue dispatch (post-P4); `0x1000` marker retirement (P4, operator checkpoint — unchanged from #628). + +**Docs:** the `.claude/v3/` tree IS the doc deliverable; EPIPHANIES + AGENT_LOG entries in-arc (incl. the ractor helper-scope ruling + ownership compile attestation, and the disk-quota ops note). + +**Confidence (2026-07-02):** HIGH — doc/governance arc + supervisor crate attestation; 22 supervisor tests green; review threads resolved + reacted. + ## #628 lance-graph: classid canon:custom half-order flip — P0 route-through + P1 CanonHigh + mint-forward compat reader **Status:** MERGED 2026-07-02 (merge commit `6858118b`), branch `claude/v3-substrate-migration-review-o0yoxv`. Executes plan phases P0+P1 (+P2 by construction); the fleet consumer PRs rode the same arc. diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index f1488fb9..a4e80035 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -7,10 +7,10 @@ Plan: `.claude/v3/INTEGRATION-PLAN.md` (stub: `.claude/plans/v3-substrate-integr | D-V3-W0a | `.claude/v3/` tree (README, plan, COMPONENT-MAP, ENTROPY-MILESTONES, MODULE-TABLE, soa_layout/*) | docs | Shipped (this PR) | complete: 7/7 mappers synthesized; MODULE-TABLE = 304/304 files (21/21 census chunks); soa_layout 5/5 docs | | D-V3-W0b | V3 awareness layer (knowledge docs, v3-* agent cards, /v3 skill, /v3-audit command, CLAUDE.md+BOOT.md entrypoints) | docs | Shipped (this PR) | 4 knowledge docs, 4 cards, skill+command registered | | D-V3-W1a | SoaEnvelope::mailbox_owner() ownership stamp | lance-graph-contract | Shipped | this branch; 775 contract tests green | -| D-V3-W1b | Ahead-firing batch writer (cast pairing + AHEAD KanbanMove at cast) | planner-adjacent | Queued | plan W1 | -| D-V3-W1c | Delegation cache (cast id vs envelope stamp) | batch writer | Queued | plan W1 | -| D-V3-W1d | MailboxId minting path (non-zero owners, uniqueness debug_assert) | contract | Queued | plan W1 | -| D-V3-W1e | Probes: ahead-update ordering + delegation miss | contract/planner | Queued | probe-first gate for W2 | +| D-V3-W1b | Ahead-firing batch writer (cast pairing + AHEAD KanbanMove at cast) | planner-adjacent | In progress | W1 STARTED 2026-07-02; WAL-shaped per preflight addendum (M24: cast = intent record) | +| D-V3-W1c | Delegation cache (cast id vs envelope stamp) | batch writer | In progress | W1 STARTED 2026-07-02; collapses into W1b writer (M24) | +| D-V3-W1d | MailboxId minting path (non-zero owners, uniqueness debug_assert) | contract | In progress | W1 STARTED 2026-07-02 | +| D-V3-W1e | Probes: ahead-update ordering + delegation miss | contract/planner | In PR | W1 STARTED 2026-07-02; probe lands FIRST (probe-first gate) + kill-after-cast replay test (M24) | | D-V3-W2a | Per-mailbox kanban board as TENANT | contract | Queued | field-isolation matrix mandatory | | D-V3-W2b | Supervisor wiring: moves via MailboxSoaOwner::advance_phase | lance-graph-supervisor | Queued | plan W2 | | D-V3-W2c | symbiont SurrealDB-on-kv-lance arm | symbiont | Blocked (kv-lance fork coordinates) | POC = kanban_loop.rs | diff --git a/.claude/v3/ENTROPY-MILESTONES.md b/.claude/v3/ENTROPY-MILESTONES.md index 38d070f5..6eb43e9d 100644 --- a/.claude/v3/ENTROPY-MILESTONES.md +++ b/.claude/v3/ENTROPY-MILESTONES.md @@ -29,7 +29,7 @@ | # | N representations | → Canonical survivor | Mechanical gate | Status | |---|---|---|---|---| -| M7 | 2 column-geometry systems (`SoaEnvelope` trait [zero production impls] vs `VALUE_TENANTS` table + `MailboxSoaView/Owner`) sharing ColumnDescriptor types by convention | ONE: production types implement SoaEnvelope, or SoaEnvelope is re-scoped as the spec/descriptor surface (ruling) | grep `impl SoaEnvelope for` ≥1 production type, or the trait doc names its non-trait role; W1 wiring decides | RULING-NEEDED (feeds W1) | +| M7 | 2 column-geometry systems (`SoaEnvelope` trait [zero production impls] vs `VALUE_TENANTS` table + `MailboxSoaView/Owner`) sharing ColumnDescriptor types by convention | ONE: production types implement SoaEnvelope, or SoaEnvelope is re-scoped as the spec/descriptor surface (ruling) | grep `impl SoaEnvelope for` ≥1 production type, or the trait doc names its non-trait role; W1 wiring decides | CORRECTED 2026-07-02 (codex #630 P2): premise wrong — NodeRowPacket<'a> (canonical_node.rs:1275) IS a production impl (Lance LE byte path). Revised: surfaces are complementary (storage-boundary vs runtime view); gate = roles documented + envelope path tested. RESOLVED | | M8 | 4 near-duplicate thinking engines (u8/BF16/i8/f32 — same 7-method API) | one generic/enum-dispatched engine (BuiltEngine already half-unifies) | the 4 structs become thin type aliases/params; parity suite green across dtypes | QUEUED | | M9 | 5+ `ThinkingStyle` copies (contract 36 canonical; thinking-engine 12 is a NEW uninventoried copy; +3 known ledger entries) | contract `thinking.rs` 36-style taxonomy | grep non-contract ThinkingStyle defs = re-exports only; duplication ledger row closed | QUEUED (blocks StepMask catalogue work) | | M10 | 2 compiled-dispatch stacks (jit.rs n8n-era StyleRegistry [orphaned] vs ExecTarget::Elixir recipe_kernels [exercised]) | the W3 template stack (elixir-template triple + StepMask) | jit.rs either implements against the template stack or retires; ExecTarget::Jit path documented | QUEUED (W3) | @@ -46,6 +46,8 @@ | M21 | 3 dep-free hand-copies of the 16-byte NodeGuid LE encoder (q2 cpic, q2 fma/converge.rs, woa-rs erp/canon.rs — each "byte-identical", zero shared code) | one zero-dep `canon-node-bytes` extraction all three import | byte-parity test vs contract NodeGuid; grep local encoders = imports only | QUEUED (W5) | | M22 | 2 divergent q2 OSINT V3 bakes (crates/osint-bake canon-high 0x0700_0000 vs data/osint-v3 STALE pre-flip 0x1000_0700 dual-GUID scheme) | one canon-high bake against osint_classview.rs's 0x0700/0x0701 reservation | re-bake; grep pre-flip forms in q2 data/ = zero (or dual-alias-read only) | QUEUED (W5; latent until a reader assumes canon-high) | | M23 | 2 write-path doctrines coexisting (owner-stamped V3 writes vs smb-office-rs `LanceConnector::upsert` — the ONE live online consumer write, no stamp/classid/envelope) | all online consumer writes routed through the batch-writer cast | consumer-map §2 table shows zero ORPHAN-WRITE rows; warden green fleet-wide | QUEUED (W5 first live migration; medcare-soa writer BORN stamped as the prevention half) | +| M24 | 2 write-intent bookkeepers (batch-writer internal state vs kanban board AHEAD update) | ONE: the kanban board IS the write-ahead log — cast = move = intent record; ack = confirmation | kill-after-cast-before-ack replay test green; grep writer-internal intent queues = zero; cast carries descriptor never bytes (zero-copy sink through NodeRowPacket); stacked-casts-never-refused test (melden macht frei — Addendum-7; sink coalesces naturally via live-store reads) | QUEUED (shapes W1b from first line; operator ruling Addendum-6: eager drain + mutual masking via phase machine) | +| M25 | 2 persistence surfaces for orchestration state (graph-flow SessionStorage backend vs mailbox kanban board) | ONE: KanbanSessionStorage — Session transitions ARE KanbanMoves via the W1b writer; replay = rebuild from board | kill-mid-graph replay test: session resumes identically from board-recorded moves; grep non-kanban SessionStorage impls in lance-graph = zero | QUEUED (W3b; feeds off M24) | ## The meta-rule (why this ledger exists) diff --git a/.claude/v3/INTEGRATION-PLAN.md b/.claude/v3/INTEGRATION-PLAN.md index 8f4fc4cd..5bef919c 100644 --- a/.claude/v3/INTEGRATION-PLAN.md +++ b/.claude/v3/INTEGRATION-PLAN.md @@ -126,3 +126,205 @@ Gate: replay equivalence green on every template change (template-smith rule). - Adopts (does not duplicate): D-MBX-A6, D-PERT-1, D-CC-RUNTIME/EQUIV/COMPILER rows, D-VCW-3/5/7, D-CCF-4. - Extends: `v3-convergence-wiring-v1` (its seam list is W1–W3's ancestry) and `soa-value-tenant-migration-v2` (Phase-2 tenant shaping proceeds under W2a's tenant discipline). - Supersedes in prose only: any remaining CollapseGate-as-singleton framing in older docs (primer §6 table governs). + +--- + +## Addendum 2026-07-02 — Fable-5 preflight epiphanies (pre-W1, operator-requested) + +Ten-point pass over every layer before phase start (full text: EPIPHANIES +E-V3-PREFLIGHT-1 + session transcript). Plan deltas adopted: + +1. **W1b/W1c collapse (WAL-shaped writer):** the cast IS the kanban move — + the AHEAD update is the write-intent record, Lance ack confirms it. The + board becomes the write-ahead log; crash recovery = replay unacked moves. + New entropy row M24. Gate: kill-after-cast-before-ack replay test. +2. **M7 ruling recommendation:** re-scope `SoaEnvelope` as the spec/descriptor + certification surface (`verify_layout` + field-isolation matrix are the + value; trait polymorphism has zero production impls). Doc-line ruling, + unblocks W1 without refactor. +3. **W6a scanner runs FIRST (baseline inversion):** build the two-metric + range-count tool at W1 start; record t0 old-form counts in the M1 row. + "Adoption 100%" is only falsifiable against a measured denominator. +4. **W3 oracle ratchet metric:** oracle-hit rate per cycle vs catalogue size + must trend DOWN; flat = templates not generalizing = deterministic-first + silently dead. One counter, plotted per replay run. +5. **W2 internal reorder:** W2e (dispatch probe) → W2d (budget) → W2a/b/c. + Budget constants come from measured µs; probe measures batch 1/64/4096 + (sub-µs matters at batch 1); loser owns the slow/plan path (two-speed). +6. **Ractor batching by construction:** actor boundary takes `Vec` + per message, never singles — helper-scope compliance enforced by API shape. +7. **D-PERT-1 rides the first W1 PR** (7 files, mechanical; waiting grows + the blast radius). +8. **M21 pull-forward:** zero-dep `canon-node-bytes` extraction lands in W1 + (same LE work); byte-parity gate vs contract NodeGuid. +9. **Gate-run rule:** every wave PR's final commit runs `/v3-audit` + the + touched M-row greps, results pasted into AGENT_LOG (self-updating ledger). +10. **Supervisor stays thin forever:** the product is the compile-time + ownership attestation; restart policy is the only runtime duty. No + routing/registry/pub-sub creep (the trap arrives dressed as convenience). + +Nothing here invents machinery — every delta is a collapse or reorder of +what the plan already carries (the V3-shape test, passed). + +### Addendum-2 2026-07-02 — operator direction: rs-graph-llm + rig parallel evaluation + +Operator: "test rs-graph-llm + rig in parallel for speed and ergonomics — +under lance-graph it might need some kanban integration to become the +replayable langgraph handler." Folded as: + +- **W3b sharpened → KanbanSessionStorage:** graph-flow's `SessionStorage` + gets a mailbox-kanban-board-backed impl — task transitions persist as + KanbanMoves through the W1b writer, so **replay = rebuild the Session + from the board**. This unifies M24 (board = WAL) with orchestration + persistence: one persistence surface, and every langgraph execution is + replayable by construction (M25). If graph-flow's save() is + whole-session-overwrite, a thin delta layer maps Session diffs to moves + (bench worker reports the trait shape). +- **W2e gains a third measurement:** graph-flow per-step dispatch overhead + at batch 1/64/4096 (bench worker in rs-graph-llm, release mode) sits + beside the SurrealQL-on-kv-lance arm. rig = the oracle-node client (W3c) + — ergonomics assessed for Task-wrapping, never on the hot path. + +### Addendum-3 2026-07-02 — rig backend note (operator) + W1e landed red + +- Operator note folded: **rs-graph-llm vendors rig**, and rig's storage/LLM + backends span lancedb, SurrealDB-on-kv-lance (lance-graph-symbiont), and + Claude/OpenAI/Grok(xAI)/Gemini APIs. Consequence for W3c: the oracle node + is backend-plural behind ONE rig client surface — symbiont (arm #2) can be + BOTH the kanban KV arm and rig's vector store, which would collapse the + oracle's storage to the substrate itself (no second store). Verify when + the bench worker reports rig's provider/store traits. +- W1e status: probes landed RED (probe-first honored); KanbanMove/KanbanColumn + already shipped in contract kanban.rs — the skeleton consumes them, zero mints. + +### Addendum-4 2026-07-02 — planner-SoA reality audit (operator question) → 3 wiring deltas + +Verdict: **type-level reality, wiring-level dormant.** KanbanMove already +unifies mailbox/witness/libet/exec; supervisor + lance-graph core + symbiont +are WIRED; the planner crate is the gap (zero MailboxSoaView references, +zero classid awareness, style_strategy pass-through, mul-gate false friend). +Deltas: +1. **W2b += integration probe**: spawn KanbanActor over the REAL + MailboxSoA (today: TestBoard-only — proven mechanics, unproven + integration). +2. **W2 += classid-awareness wiring**: planner reads class_id through + MailboxSoaView (the getter already exists — wire, don't invent); + supervisor likewise if move-routing needs read modes. +3. **M15 upgraded to BLOCKING-before-W2**: planner mul/gate.rs + GateDecision{Proceed,Sandbox,Compass} vs contract + mul::GateDecision{Flow,Hold,Block} — rename the planner-local one + BEFORE any planner->kanban emission lands, or the wrong gate routes + into advance_on_gate silently. +Full inventory with file:line cites: E-V3-PLANNER-SOA-AUDIT-1 + AGENT_LOG. + +### Addendum-5 2026-07-02 — bench results land the W2e read + M25 design + +- **Numbers (release, steady-state batch 4096):** graph-flow ~408-471 + ns/step (ContinueAndExecute) / ~512-538 ns/step (stepwise; delta = the + storage round-trip). Two-speed CONFIRMED with data: graph-flow = the + replayable orchestration layer; sub-us hot dispatch = ExecTarget. +- **M25 design finalized:** SessionStorage is overwrite-semantics (all 3 + impls upsert the whole Session) -> KanbanSessionStorage = Session + snapshot upsert + KanbanMove cast through the W1b writer; the append-only + move log ALREADY EXISTS as rs-graph-llm/graph-flow-kanban's + KanbanPlanEnvelope (consumes contract kanban types + GateDecision) — + wire it to the W1b writer, invent nothing. Replay = snapshot + move log. +- **rig = oracle-frequency only** (2 full history clones + tool-def fetch + per call): W3c yes, per-transition no. Fork is upstream-faithful. +- **Build wall (ops):** rs-graph-llm/rig workspace-root cargo 403s on the + AdaWorldAPI/burn git submodule via surreal-lance OPTIONAL deps (lock + resolution pulls manifests even when features are off). Sandboxed builds + use isolated path-dep crates until a lockfile/vendor lands. Bench file + committed to rs-graph-llm @ claude/v3-substrate-migration-review-o0yoxv. + +### Correction 2026-07-02 (codex #630 P2) — M7 premise was WRONG: SoaEnvelope HAS a production impl + +`NodeRowPacket<'a>` (canonical_node.rs:1275) implements `SoaEnvelope` in +production — the Lance-facing zero-copy LE byte view over `&[NodeRow]` +(NODE_ROW_COLUMNS / NODE_ROW_STRIDE / as_le_bytes with the repr(C,64) +SAFETY argument). "Zero production impls" (preflight delta 2, inherited +from the fleet's M7 row) is retracted. **Revised M7 ruling:** the two +surfaces are COMPLEMENTARY, not duplicates — `SoaEnvelope` is the +storage-boundary surface (certification + the canonical Lance byte path, +with NodeRowPacket as its live impl); `MailboxSoaView/Owner` is the +runtime read/mutate surface. W1 implementers MUST route storage bytes +through the NodeRowPacket envelope path and preserve/test its +owner/byte-layout behavior — the trait is NOT descriptor-only. M7's gate +re-shapes accordingly (roles documented both ways + envelope path tested, +rather than "≥1 impl or re-scope"). + +### Addendum-6 2026-07-02 — operator ruling: zero-copy sink + mutual masking (W1b design closed) + +Operator: "it was always zerocopy and the write masks the thinking and +vice versa so that the batch writer sinks the deltas asap." Pinned: + +1. **The cast carries a DESCRIPTOR, never bytes:** (mailbox, dirty + row-range, cycle) + intent moves. Deltas stay in the SoA backing + store; the sink reads them through `NodeRowPacket::as_le_bytes` at + flush time (the M7-corrected storage-boundary path). Zero-copy from + creation to Lance tombstone INCLUDING through the writer — the + payload-generic `P` in the skeleton is a descriptor type, never + owned bytes. +2. **Mutual masking via the phase machine, not buffers:** while cycle + N's dirty rows sink, the owner refuses phase re-entry on those rows + (Rubicon arc = the mutation freeze); thinking proceeds on all other + rows/mailboxes. Compute masks I/O and I/O masks compute — the kanban + board IS the scheduler that makes the overlap race-free. No + double-buffering, no copies. +3. **Eager drain:** the sink fires ASAP on cast (background), never + batch-until-full — the unacked window IS the replay surface; keep it + minimal. W2d's 550 ms budget may treat write latency as masked so + long as sink throughput >= delta production rate (instrument both). + +Gate added to W1b: a mutation-freeze test — a row in sink phase rejects +advance_phase until ack (lands with the real-owner wiring, W2b probe +extends it). + +### Addendum-7 2026-07-02 — operator correction of Addendum-6: NO refusal — "melden macht frei" + +The mutation-freeze point in Addendum-6 was over-design and contradicted +the standing rule ("updates reprioritize, never gate"). Corrected: + +1. **Casting IS reporting, and reporting frees the thinker.** The writer + NEVER refuses a cast because earlier casts on the same row/mailbox are + unacked. Stacked writes (>=3) are stacked WAL entries: distinct ids, + full ordered move history, independent acks. +2. **Coalescing is natural, not engineered:** the sink reads the LIVE + backing store at flush, so one physical flush of a row satisfies every + earlier stacked intent for it — last-state-wins is correct because the + replay target is the row's latest state, while the move log preserves + the full ordering history. +3. M24 gate updated: the mutation-freeze test is REPLACED by the + stacked-casts test (probe 4, `probe_stacked_casts_never_refused` — + landed ignored with the other three). + +### Addendum-8 2026-07-02 — temporal.rs is the READ side of the WAL ruling (operator pointer) + +Operator: "check temporal.rs for a deeper understanding." Verified against +`crates/lance-graph-planner/src/temporal.rs` (490 lines, read in full): + +1. **No-refusal is PROVABLE, not just permitted.** No reader ever reads raw + current state — `deinterlace()` classifies every row against the reader's + `QueryReference` (Contemporary/Anachronistic/Spoiler/Unknowable, admitted + per `EpistemicMode` rung policy Strict 0-4 / Aware 5-8 / Retro 9+). A + Strict thinker at `V_ref` cannot see frames past its horizon, so writers + stack freely: coherence is a READ-side property. "The write masks the + thinking and vice versa" — the mask IS the epistemic classification. +2. **Replay = a read at a pinned reference.** `QueryReference::at(v, rung)` + + `deinterlace` IS crash-replay (M24), session-replay (M25), and + time-travel — ONE mechanism. KanbanSessionStorage's replay path + implements nothing new: it pins a Strict reader in the past over the + board + rows. +3. **W1b ack carries the assigned LanceVersion**: `ack(cast, LanceVersion)` + is the CastId <-> LanceVersion join wiring the WAL into the temporal + classifier. Unacked casts = rows on NO reader's timeline yet (exactly + why unacked() is the replay surface). Probe file updates with the + signature in the W1b commit. +4. **The ractor actor's payload is its V_ref** (temporal.rs frame table: + "ractor (awareness) — each actor's own V_ref reading-horizon") — the + helper carries the awareness horizon; one more reason it never needs + to be on the hot path. +5. DATA-causal axis (DependsClosure/NoDeps) composes with the board: + dispatchable = time-admitted AND data-ready — the standing rule + "updates reprioritize, never gate" holds because a data-blocked row is + dropped from the PROJECTION, not refused at the writer. diff --git a/crates/cognitive-shader-driver/src/engine_bridge.rs b/crates/cognitive-shader-driver/src/engine_bridge.rs index c224b1b9..6070e33a 100644 --- a/crates/cognitive-shader-driver/src/engine_bridge.rs +++ b/crates/cognitive-shader-driver/src/engine_bridge.rs @@ -3,7 +3,7 @@ //! Two DTO pipelines exist in isolation: //! //! ```text -//! thinking-engine: Φ StreamDto → Ψ ResonanceDto → B BusDto → Γ ThoughtStruct +//! thinking-engine: Φ StreamDto → Ψ PerturbationDto → B BusDto → Γ ThoughtStruct //! cognitive-shader-driver: Φ ShaderDispatch → Ψ ShaderResonance → B ShaderBus → Γ ShaderCrystal //! ``` //! @@ -12,7 +12,7 @@ //! //! ```text //! [1] StreamDto.codebook_indices → populate BindSpace content fingerprints -//! [2] ResonanceDto.top_k → seed ShaderDispatch.rows (which rows to scan) +//! [2] PerturbationDto.top_k → seed ShaderDispatch.rows (which rows to scan) //! [3] ShaderBus.cycle_fingerprint → produce BusDto (top-1 hit = codebook_index) //! [4] ShaderCrystal → produce ThoughtStruct with sensor provenance //! [5] Qualia17D → fill BindSpace QualiaColumn (17 → 18: pad 0) @@ -88,7 +88,7 @@ pub fn ingest_codebook_indices( } // ═══════════════════════════════════════════════════════════════════════════ -// ResonanceDto → ShaderDispatch (top-k seeds the scan window) +// PerturbationDto → ShaderDispatch (top-k seeds the scan window) // ═══════════════════════════════════════════════════════════════════════════ /// Build a ShaderDispatch from resonance top-k. diff --git a/crates/lance-graph-contract/src/classid_scan.rs b/crates/lance-graph-contract/src/classid_scan.rs new file mode 100644 index 00000000..fe5d5530 --- /dev/null +++ b/crates/lance-graph-contract/src/classid_scan.rs @@ -0,0 +1,456 @@ +//! `classid_scan` — the D-V3-W6a adoption-scan COUNTING LOGIC (zero-dep). +//! +//! Two governance metrics — V3 classid adoption% and the pre-flip corpus-proof +//! count — are, per `.claude/v3/soa_layout/routing.md` §5 ("Monitor routing — +//! adoption is a range count"), **the same key-range scan** over the DECODED +//! `classid: u32`, never a raw LE key-byte prefix walk (routing.md §1's +//! byte-order caveat, codex #629). This module is that scanner's counting +//! logic: [`classify_form`] buckets one decoded classid into a [`ClassidForm`], +//! [`count_adoption`] folds an iterator of classids into [`AdoptionCounts`]. +//! +//! `classify_form` mirrors [`classid_canon_compat`](crate::ogar_codebook::classid_canon_compat)'s +//! own branch structure exactly — same two decisions, in the same order — so a +//! row this scanner buckets as [`ClassidForm::CanonHigh`] is precisely a row +//! `classid_canon_compat` resolves WITHOUT falling back to the legacy +//! [`ClassidOrder::CanonLow`](crate::ogar_codebook::ClassidOrder::CanonLow) +//! split, and a row bucketed as one of the three legacy variants is precisely +//! a row that fallback resolves. No bit math is performed on the composed +//! `u32` here — every classification reads the two `u16` halves returned by +//! [`split_classid`](crate::ogar_codebook::split_classid), the one sanctioned +//! decomposition helper (`ogar_codebook.rs` D-CCF-0). +//! +//! The three legacy shapes counted as `old_form` are exactly the set +//! `classid_canon_compat` routes through its `CanonLow` fallback (per +//! routing.md §5, corpus-proof MUST count all three or it can falsely report +//! a clean corpus while un-rebaked render rows remain): +//! +//! - [`ClassidForm::LegacyZeroPrefixHigh`] — `0x0000_DDCC` (legacy core form; +//! worked example `ogar_codebook::tests::classid_canon_compat_reads_both_stored_forms` +//! line `classid_canon_compat(0x0000_0901)` and `NodeGuid::CLASSID_OSINT_LEGACY` +//! / `CLASSID_FMA_LEGACY` / `CLASSID_PROJECT_LEGACY` / `CLASSID_ERP_LEGACY`). +//! - [`ClassidForm::LegacyV3MarkerHigh`] — `0x1000_DDCC` (pre-flip V3-marker +//! form; worked example `classid_canon_compat(0x1000_0700)` and +//! `NodeGuid::CLASSID_OSINT_V3_LEGACY` / `CLASSID_FMA_V3_LEGACY` / +//! `CLASSID_CPIC_V3_LEGACY`). +//! - [`ClassidForm::LegacyRenderPrefixHigh`] — `0xAAAA_DDCC` (legacy +//! app/render-prefix-high form; worked example +//! `classid_canon_compat(0x0005_0901)` — MedCare's pre-flip Healthcare +//! render pair). + +use crate::ogar_codebook::split_classid; + +/// The decoded shape of a stored `classid: u32`, per the decision procedure +/// in [`classid_canon_compat`](crate::ogar_codebook::classid_canon_compat) +/// (`ogar_codebook.rs` lines 361-387) and the shape catalogue in +/// `.claude/v3/soa_layout/routing.md` §5. +/// +/// `classify_form` mirrors `classid_canon_compat`'s two-branch structure: +/// +/// 1. `canon >= 0x0100 && canon != 0x1000` → the id resolves natively under +/// the active [`ClassidOrder::CanonHigh`](crate::ogar_codebook::ClassidOrder::CanonHigh) +/// order, no fallback needed → [`ClassidForm::CanonHigh`]. The degenerate +/// default-class case (`0x0000_0000`, `canon == 0x0000 && custom == 0x0000`) +/// also resolves without a fallback (`classid_canon_compat`'s final `else` +/// returns `canon` directly, same value either interpretation) and is +/// folded into this variant too — it was never a pre-flip form to migrate +/// away from, so it does not belong in `old_form`. +/// 2. Otherwise, if `custom != 0`, the id needed the legacy `CanonLow` +/// fallback — one of the three [`old_form`](AdoptionCounts::old_form) +/// shapes, distinguished by which value `canon` (the HIGH half) carries. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum ClassidForm { + /// The classid resolves natively under the active `CanonHigh` order + /// (`canon >= 0x0100 && canon != 0x1000`), or is the degenerate default + /// class (`0x0000_0000`) — no legacy fallback consulted. + CanonHigh, + /// `0x0000_DDCC` — legacy core form (pre-flip `compose_classid_with(CanonLow, concept, 0)`). + /// `canon == 0x0000 && custom != 0x0000`. + LegacyZeroPrefixHigh, + /// `0x1000_DDCC` — pre-flip V3-marker-high form (pre-flip + /// `compose_classid_with(CanonLow, concept, 0x1000)`). + /// `canon == 0x1000 && custom != 0x0000`. + /// + /// **Documented ambiguity bound** (`ogar_codebook.rs` lines 372-375, the + /// `classid_canon_compat` doc comment "Documented limitation"): a future + /// GENUINE canon-high id whose concept equals `0x1000` exactly (the + /// domain-root slot of the currently-`Unassigned` domain `0x10`) is + /// bit-identical to this legacy shape and is therefore + /// indistinguishable from it under `classid_canon_compat`'s heuristic — + /// see [`ClassidForm::Ambiguous`]. `classify_form` follows + /// `classid_canon_compat` and routing.md §5's corpus-proof convention: + /// it classifies every `canon == 0x1000 && custom != 0` id as THIS + /// variant (conservatively legacy), never as `Ambiguous`, because no + /// [`CODEBOOK`](crate::ogar_codebook::CODEBOOK) entry occupies domain + /// `0x10` today — see [`ClassidForm::Ambiguous`] for when that changes. + LegacyV3MarkerHigh, + /// `0xAAAA_DDCC` — legacy app/render-prefix-high form (pre-flip + /// `compose_classid_with(CanonLow, concept, app_prefix)`, e.g. MedCare's + /// `0x0005_0901`). `canon` is nonzero, `< 0x0100`, and not `0x1000` + /// (automatically excluded since `0x1000 >= 0x0100`); `custom != 0x0000`. + LegacyRenderPrefixHigh, + /// Reserved for the case documented at [`ClassidForm::LegacyV3MarkerHigh`]: + /// a classid whose true canon is genuinely `0x1000` (domain `0x10` root) + /// composed under the active `CanonHigh` order is bit-for-bit identical + /// to a legacy V3-marker-high id, so no purely bit-level classifier can + /// tell them apart. `classify_form` never constructs this variant today + /// — see the `LegacyV3MarkerHigh` doc comment for why it resolves that + /// bit pattern to `LegacyV3MarkerHigh` instead. Kept as a distinct, + /// `#[non_exhaustive]`-safe variant so a future classifier that DOES gain + /// the information to disambiguate (e.g. cross-checking + /// [`CODEBOOK`](crate::ogar_codebook::CODEBOOK) once domain `0x10` mints + /// a concept) has somewhere to route the genuinely-undecidable case + /// without a breaking enum change. + Ambiguous, +} + +/// Classify a decoded `classid: u32` into its [`ClassidForm`] — the counting +/// primitive both the adoption% and corpus-proof monitors scan with (per +/// `.claude/v3/soa_layout/routing.md` §5, "ONE two-metric scanner"). Reads +/// only the two `u16` halves from [`split_classid`](crate::ogar_codebook::split_classid); +/// performs no bit math on the composed `u32` itself (`/v3-audit` check 1). +#[inline] +#[must_use] +pub fn classify_form(classid: u32) -> ClassidForm { + let (canon, custom) = split_classid(classid); + if canon >= 0x0100 && canon != 0x1000 { + // Mirrors classid_canon_compat's native branch. Also captures the + // degenerate default class (canon == 0x0000, custom == 0x0000) via + // the fallthrough below — see the second branch. + ClassidForm::CanonHigh + } else if custom != 0 { + // Mirrors classid_canon_compat's CanonLow-fallback branch: this id + // needed the legacy split to resolve its true canon. Distinguish the + // three routing.md §5 shapes by which value `canon` (the HIGH half) + // carries. + match canon { + 0x0000 => ClassidForm::LegacyZeroPrefixHigh, + 0x1000 => ClassidForm::LegacyV3MarkerHigh, + _ => ClassidForm::LegacyRenderPrefixHigh, + } + } else { + // custom == 0x0000 && canon < 0x0100: in practice only the default + // class (0x0000_0000) — classid_canon_compat's final `else` returns + // `canon` (0) directly here, identical to the CanonHigh reading, so + // this is not a pre-flip form to migrate away from. + ClassidForm::CanonHigh + } +} + +/// Range-count tallies over a scanned set of classids, per +/// `.claude/v3/soa_layout/routing.md` §5's "ONE two-metric scanner": +/// `canon_high` feeds the adoption% metric, `old_form` feeds the +/// corpus-proof metric (all three legacy shapes summed), `ambiguous` is +/// reserved for [`ClassidForm::Ambiguous`] (currently always `0` — see that +/// variant's doc comment). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct AdoptionCounts { + /// Rows classified as [`ClassidForm::CanonHigh`] (native new form, + /// including the degenerate default class). + pub canon_high: u64, + /// Rows classified as any of the three legacy shapes + /// ([`ClassidForm::LegacyZeroPrefixHigh`], + /// [`ClassidForm::LegacyV3MarkerHigh`], + /// [`ClassidForm::LegacyRenderPrefixHigh`]) — the corpus-proof count. + /// Zero across all three ⇒ alias retirement unlocks (routing.md §5). + pub old_form: u64, + /// Rows classified as [`ClassidForm::Ambiguous`]. Always `0` under the + /// current [`classify_form`] (see that function's doc comment); carried + /// so callers never need to special-case its absence. + pub ambiguous: u64, + /// Total rows scanned (`canon_high + old_form + ambiguous`). + pub total: u64, +} + +impl AdoptionCounts { + /// Adoption percentage: `canon_high / total`, in `[0.0, 1.0]`. `0.0` for + /// an empty scan (`total == 0`) rather than `NaN` — an empty corpus is + /// vacuously not-yet-adopted, not undefined. + #[inline] + #[must_use] + pub fn adoption_pct(&self) -> f64 { + if self.total == 0 { + 0.0 + } else { + self.canon_high as f64 / self.total as f64 + } + } +} + +/// Fold an iterator of decoded `classid: u32` values into [`AdoptionCounts`] +/// by [`classify_form`]. The one counting pass both the adoption% and +/// corpus-proof monitors share (routing.md §5). +#[must_use] +pub fn count_adoption(ids: impl Iterator) -> AdoptionCounts { + let mut counts = AdoptionCounts::default(); + for id in ids { + match classify_form(id) { + ClassidForm::CanonHigh => counts.canon_high += 1, + ClassidForm::LegacyZeroPrefixHigh + | ClassidForm::LegacyV3MarkerHigh + | ClassidForm::LegacyRenderPrefixHigh => counts.old_form += 1, + ClassidForm::Ambiguous => counts.ambiguous += 1, + } + counts.total += 1; + } + counts +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ogar_codebook::{ + canonical_concept_id, classid_canon_compat, compose_classid_with, + render_classid_for_concept, AppPrefix, ClassidOrder, CODEBOOK, + }; + use crate::NodeGuid; + + // Every test classid below is built through a sanctioned composer + // (`compose_classid_with`, `render_classid_for_concept`) or a documented + // public constant (`NodeGuid::CLASSID_*` / `CLASSID_*_LEGACY`) — never a + // hand-rolled composed-`u32` hex literal (footgun F12). Component-level + // literals (the `0x1000` V3 marker, `0x0000` core-form custom, AppPrefix + // values) are used as composer ARGUMENTS, matching the idiom already + // established by `ogar_codebook.rs`'s own tests (e.g. + // `classid_route_through_matrix_under_active_and_legacy_order`'s + // `for prefix in [0x0000u16, 0x0001, 0x0005, 0x1000]`). + // + // `NodeGuid::CLASSID_*_V3` / `CLASSID_*_V3_LEGACY` are gated behind the + // (default-OFF) `guid-v3-tail` feature (Cargo.toml), so this module + // reproduces their bit patterns unconditionally via the composers + // instead of depending on that feature. + + /// A CODEBOOK concept id, used as the CANON half in composed test ids. + fn a_concept() -> u16 { + canonical_concept_id("patient").expect("patient is a CODEBOOK entry") + } + + // ── classify_form: one id at a time, worked examples from ogar_codebook.rs ── + + #[test] + fn classify_form_canon_high_native() { + // NodeGuid post-flip constants — canon in the HIGH half, native. + assert_eq!(classify_form(NodeGuid::CLASSID_OSINT), ClassidForm::CanonHigh); + assert_eq!(classify_form(NodeGuid::CLASSID_FMA), ClassidForm::CanonHigh); + assert_eq!(classify_form(NodeGuid::CLASSID_PROJECT), ClassidForm::CanonHigh); + assert_eq!(classify_form(NodeGuid::CLASSID_ERP), ClassidForm::CanonHigh); + + // Post-flip V3-marked forms: canon high, custom == 0x1000 — still + // native (the marker lives in custom, not canon). Reproduces the + // `CLASSID_*_V3` bit pattern via the unconditional composer instead + // of the `guid-v3-tail`-gated constants. + for &(_, concept) in CODEBOOK { + let v3_marked = compose_classid_with(ClassidOrder::CanonHigh, concept, 0x1000); + assert_eq!(classify_form(v3_marked), ClassidForm::CanonHigh); + } + + // render_classid_for_concept — a real (non-legacy) render classid + // composed via the sanctioned composer, e.g. MedCare Healthcare/patient. + let pat = render_classid_for_concept(AppPrefix::Healthcare, "patient").unwrap(); + assert_eq!(classify_form(pat), ClassidForm::CanonHigh); + } + + #[test] + fn classify_form_default_class_is_canon_high() { + assert_eq!( + classify_form(NodeGuid::CLASSID_DEFAULT), + ClassidForm::CanonHigh + ); + } + + #[test] + fn classify_form_legacy_zero_prefix_high() { + // The documented public legacy aliases (canonical_node.rs) — the + // 0x0000_DDCC shape (worked example: `classid_canon_compat_reads_both_stored_forms` + // pins `classid_canon_compat(0x0000_0901) = 0x0901`, same shape). + assert_eq!( + classify_form(NodeGuid::CLASSID_OSINT_LEGACY), + ClassidForm::LegacyZeroPrefixHigh + ); + assert_eq!( + classify_form(NodeGuid::CLASSID_FMA_LEGACY), + ClassidForm::LegacyZeroPrefixHigh + ); + assert_eq!( + classify_form(NodeGuid::CLASSID_PROJECT_LEGACY), + ClassidForm::LegacyZeroPrefixHigh + ); + assert_eq!( + classify_form(NodeGuid::CLASSID_ERP_LEGACY), + ClassidForm::LegacyZeroPrefixHigh + ); + // Composed directly via the sanctioned CanonLow composer: custom=0, + // canon=concept — reproduces the same shape for any codebook entry. + for &(_, concept) in CODEBOOK { + let legacy = compose_classid_with(ClassidOrder::CanonLow, concept, 0x0000); + assert_eq!(classify_form(legacy), ClassidForm::LegacyZeroPrefixHigh); + } + } + + #[test] + fn classify_form_legacy_v3_marker_high() { + // 0x1000_DDCC shape (worked example: `classid_canon_compat(0x1000_0700) = 0x0700`, + // same shape as `NodeGuid::CLASSID_OSINT_V3_LEGACY`, `guid-v3-tail`-gated). + // Composed via the sanctioned CanonLow composer with custom=0x1000 + // (the pre-flip V3-marker position) for every codebook entry. + for &(_, concept) in CODEBOOK { + let legacy = compose_classid_with(ClassidOrder::CanonLow, concept, 0x1000); + assert_eq!(classify_form(legacy), ClassidForm::LegacyV3MarkerHigh); + } + } + + #[test] + fn classify_form_legacy_render_prefix_high() { + // 0xAAAA_DDCC shape (worked example: `classid_canon_compat(0x0005_0901) = 0x0901`, + // MedCare's pre-flip Healthcare render pair). Every allocated + // AppPrefix, composed via the sanctioned CanonLow composer, against + // every codebook concept. + for app in [ + AppPrefix::OpenProject, + AppPrefix::Odoo, + AppPrefix::Woa, + AppPrefix::Smb, + AppPrefix::Healthcare, + AppPrefix::Redmine, + ] { + for &(_, concept) in CODEBOOK { + let legacy = compose_classid_with(ClassidOrder::CanonLow, concept, app.prefix()); + assert_eq!( + classify_form(legacy), + ClassidForm::LegacyRenderPrefixHigh, + "prefix {:#06x} concept {concept:#06x}", + app.prefix() + ); + } + } + } + + #[test] + fn classify_form_agrees_with_classid_canon_compat_fallback_decision() { + // classify_form's CanonHigh/old_form split must agree exactly with + // whether classid_canon_compat needed the CanonLow fallback: when it + // does NOT need the fallback, canon == classid_canon_compat(id); the + // legacy shapes are exactly where it DOES need the fallback and the + // compat answer differs from the naive canon-high read. + let concept = a_concept(); + let native_ids = [ + NodeGuid::CLASSID_OSINT, + NodeGuid::CLASSID_FMA, + NodeGuid::CLASSID_PROJECT, + NodeGuid::CLASSID_ERP, + NodeGuid::CLASSID_DEFAULT, + compose_classid_with(ClassidOrder::CanonHigh, concept, 0x1000), // V3-marked, native + ]; + for id in native_ids { + assert_eq!(classify_form(id), ClassidForm::CanonHigh); + let (canon, _custom) = split_classid(id); + assert_eq!( + classid_canon_compat(id), + canon, + "CanonHigh-classified id must not need the legacy fallback" + ); + } + + let legacy_ids = [ + ( + compose_classid_with(ClassidOrder::CanonLow, concept, 0x0000), + ClassidForm::LegacyZeroPrefixHigh, + ), + ( + compose_classid_with(ClassidOrder::CanonLow, concept, 0x1000), + ClassidForm::LegacyV3MarkerHigh, + ), + ( + compose_classid_with(ClassidOrder::CanonLow, concept, AppPrefix::Healthcare.prefix()), + ClassidForm::LegacyRenderPrefixHigh, + ), + ]; + for (id, expected_form) in legacy_ids { + assert_eq!(classify_form(id), expected_form); + let (canon, _custom) = split_classid(id); + assert_ne!( + classid_canon_compat(id), + canon, + "old_form-classified id must have needed the legacy fallback \ + (compat answer differs from the naive canon-high read)" + ); + } + } + + // ── count_adoption / AdoptionCounts ── + + #[test] + fn count_adoption_all_canon_high_is_full_adoption() { + let concept = a_concept(); + let ids = [ + NodeGuid::CLASSID_OSINT, + NodeGuid::CLASSID_FMA, + compose_classid_with(ClassidOrder::CanonHigh, concept, 0x1000), // V3-marked, native + ]; + let counts = count_adoption(ids.into_iter()); + assert_eq!( + counts, + AdoptionCounts { + canon_high: 3, + old_form: 0, + ambiguous: 0, + total: 3, + } + ); + assert!((counts.adoption_pct() - 1.0).abs() < f64::EPSILON); + } + + #[test] + fn count_adoption_all_old_form_is_zero_adoption() { + let concept = a_concept(); + let ids = [ + NodeGuid::CLASSID_OSINT_LEGACY, + compose_classid_with(ClassidOrder::CanonLow, concept, 0x1000), + compose_classid_with(ClassidOrder::CanonLow, concept, AppPrefix::Healthcare.prefix()), + ]; + let counts = count_adoption(ids.into_iter()); + assert_eq!( + counts, + AdoptionCounts { + canon_high: 0, + old_form: 3, + ambiguous: 0, + total: 3, + } + ); + assert_eq!(counts.adoption_pct(), 0.0); + } + + #[test] + fn count_adoption_mixed_produces_correct_totals_and_pct() { + // 3 native (one of which is the default class), 3 old_form (one of + // each legacy shape), 0 ambiguous — the mixed-corpus case. + let concept = a_concept(); + let ids = [ + NodeGuid::CLASSID_OSINT, // CanonHigh + compose_classid_with(ClassidOrder::CanonHigh, concept, 0x1000), // CanonHigh (V3-marked) + NodeGuid::CLASSID_DEFAULT, // CanonHigh (degenerate) + NodeGuid::CLASSID_OSINT_LEGACY, // LegacyZeroPrefixHigh + compose_classid_with(ClassidOrder::CanonLow, concept, 0x1000), // LegacyV3MarkerHigh + compose_classid_with(ClassidOrder::CanonLow, concept, AppPrefix::Healthcare.prefix()), // LegacyRenderPrefixHigh + ]; + let counts = count_adoption(ids.into_iter()); + assert_eq!( + counts, + AdoptionCounts { + canon_high: 3, + old_form: 3, + ambiguous: 0, + total: 6, + } + ); + assert!((counts.adoption_pct() - 0.5).abs() < f64::EPSILON); + } + + #[test] + fn count_adoption_empty_iterator_is_zero_not_nan() { + let counts = count_adoption(std::iter::empty()); + assert_eq!(counts, AdoptionCounts::default()); + assert_eq!(counts.adoption_pct(), 0.0); + } +} diff --git a/crates/lance-graph-contract/src/lib.rs b/crates/lance-graph-contract/src/lib.rs index 5860fe6c..504e81bb 100644 --- a/crates/lance-graph-contract/src/lib.rs +++ b/crates/lance-graph-contract/src/lib.rs @@ -54,6 +54,10 @@ pub mod callcenter; pub mod cam; pub mod canonical_node; pub mod class_view; +/// D-V3-W6a — classid adoption-scan counting logic (`ClassidForm`, +/// `classify_form`, `AdoptionCounts`, `count_adoption`). See +/// `.claude/v3/soa_layout/routing.md` §5. +pub mod classid_scan; /// D-GV2-2 — per-family codebook (`family → Codebook`), gated on the v2 tail. #[cfg(feature = "guid-v2-tail")] pub mod codebook; diff --git a/crates/lance-graph-planner/src/batch_writer.rs b/crates/lance-graph-planner/src/batch_writer.rs new file mode 100644 index 00000000..1b1e9b62 --- /dev/null +++ b/crates/lance-graph-planner/src/batch_writer.rs @@ -0,0 +1,117 @@ +//! W1b ahead-firing batch writer — the kanban board IS the write-ahead log (M24). +//! +//! `cast()` records intent moves AHEAD of any storage ack; `ack()` confirms; +//! `unacked()` is the crash-replay surface. Payload-generic: the writer never +//! inspects `P` (DTO purity — ownership rides the cast pairing, never the DTO). +//! +//! **Zero-copy sink (operator ruling, plan Addendum-6):** `P` is a DESCRIPTOR +//! — (mailbox, dirty row-range, cycle) — never owned delta bytes. Deltas stay +//! in the SoA backing store; the sink reads them through +//! `NodeRowPacket::as_le_bytes` at flush time. The sink drains EAGERLY +//! (ASAP on cast, background), and the write masks the thinking and vice +//! versa: the thinker reports (casts) and moves on — "melden macht frei" — +//! it is NEVER refused because earlier casts are unacked. Stacked casts on +//! the same row are stacked WAL entries; the sink reads the LIVE store at +//! flush, so one physical flush coalesces all earlier intents for a row +//! (last-state-wins; the move log keeps the full ordered history). +//! +//! Uses the REAL shipped kanban contract types +//! ([`lance_graph_contract::kanban::KanbanMove`], +//! [`lance_graph_contract::kanban::KanbanColumn`], +//! [`lance_graph_contract::collapse_gate::MailboxId`]) — this module does not +//! mint a parallel `KanbanMove`; see the D-MBX-A6 Outcome adapter context in +//! `crate::strategy::style_strategy`. + +use std::collections::HashMap; + +use lance_graph_contract::collapse_gate::MailboxId; +use lance_graph_contract::kanban::KanbanMove; + +/// Identity of one `cast()` — a write-ahead intent record on the kanban board. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct CastId(pub u64); + +/// Ahead-firing batch writer: intent (`cast`) is visible on the board before +/// any ack; `unacked()` is the M24 crash-replay surface; `resolve_owner` is +/// the W1c delegation cache (resolve-once, cache-hit thereafter). +pub struct BatchWriter

{ + /// Monotonic id generator for the next cast. + next_id: u64, + /// Board: intent moves recorded per cast, keyed by `CastId`, alongside the + /// mailbox the cast was recorded on behalf of. Visible between `cast()` + /// and `ack()` (and beyond, for crash-replay via `unacked()`). + board: HashMap)>, + /// Casts that have been confirmed (acked). A cast present in `board` but + /// absent from `acked` is the crash-replay surface (`unacked()`). + acked: std::collections::HashSet, + /// W1c delegation cache: `on_behalf` mailbox -> resolved owner mailbox. + delegation_cache: HashMap, + /// Payloads recorded per cast (payload-generic; the writer never inspects `P`). + pending_payloads: Vec<(CastId, P)>, +} + +impl

Default for BatchWriter

{ + fn default() -> Self { + Self::new() + } +} + +impl

BatchWriter

{ + /// Construct an empty batch writer. + pub fn new() -> Self { + Self { + next_id: 0, + board: HashMap::new(), + acked: std::collections::HashSet::new(), + delegation_cache: HashMap::new(), + pending_payloads: Vec::new(), + } + } + + /// AHEAD: records the intent (moves visible on the board) BEFORE any ack. + /// Returns the cast id. + /// + /// Probe-first skeleton (D-V3-W1e): body pending — see + /// `tests/w1_probes.rs::probe_ahead_update_ordering` / + /// `probe_kill_after_cast_replay`. + pub fn cast(&mut self, _on_behalf: MailboxId, _moves: Vec, _payload: P) -> CastId { + todo!("W1b") + } + + /// Confirmation — marks the cast acked. + /// + /// Probe-first skeleton (D-V3-W1e): body pending — see + /// `tests/w1_probes.rs::probe_ahead_update_ordering`. + pub fn ack(&mut self, _cast: CastId) { + todo!("W1b") + } + + /// Crash-replay surface (M24): casts recorded but not yet acked. + /// + /// Probe-first skeleton (D-V3-W1e): body pending — see + /// `tests/w1_probes.rs::probe_kill_after_cast_replay`. + pub fn unacked(&self) -> Vec { + todo!("W1b") + } + + /// Board read: intent moves recorded for a cast (visible between cast and ack). + /// + /// Probe-first skeleton (D-V3-W1e): body pending — see + /// `tests/w1_probes.rs::probe_ahead_update_ordering` / + /// `probe_kill_after_cast_replay`. + pub fn intent_moves(&self, _cast: CastId) -> Option<&[KanbanMove]> { + todo!("W1b") + } + + /// W1c delegation cache: resolve an owner once, cache; returns `(owner, was_cache_hit)`. + /// + /// Probe-first skeleton (D-V3-W1e): body pending — see + /// `tests/w1_probes.rs::probe_delegation_miss_then_hit`. + pub fn resolve_owner( + &mut self, + _on_behalf: MailboxId, + _resolver: impl FnOnce(MailboxId) -> MailboxId, + ) -> (MailboxId, bool) { + todo!("W1c") + } +} diff --git a/crates/lance-graph-planner/src/lib.rs b/crates/lance-graph-planner/src/lib.rs index f2a7b8a0..8e291668 100644 --- a/crates/lance-graph-planner/src/lib.rs +++ b/crates/lance-graph-planner/src/lib.rs @@ -72,6 +72,9 @@ pub mod pipeline; // === Autocomplete Cache (VSA superposition KV-cache) === pub mod cache; +// === W1b ahead-firing batch writer (D-V3-W1e probe-first skeleton) === +pub mod batch_writer; + // === Internal API (same-binary, zero-serde) === pub mod api; diff --git a/crates/lance-graph-planner/tests/w1_probes.rs b/crates/lance-graph-planner/tests/w1_probes.rs new file mode 100644 index 00000000..cff4b636 --- /dev/null +++ b/crates/lance-graph-planner/tests/w1_probes.rs @@ -0,0 +1,143 @@ +//! D-V3-W1e probe-first: three failing probes for the W1b ahead-firing batch +//! writer + W1c delegation cache, pinned against +//! `lance_graph_planner::batch_writer::BatchWriter`. +//! +//! All three are `#[ignore]`d — the writer's methods are `todo!()` stubs. +//! Un-ignore in the W1b implementation commit once the bodies are filled in. +//! +//! Uses the REAL shipped kanban contract types +//! (`lance_graph_contract::kanban::{KanbanColumn, KanbanMove, ExecTarget}`, +//! `lance_graph_contract::collapse_gate::MailboxId`) — no hand-rolled +//! composites (F12). + +use lance_graph_contract::kanban::{ExecTarget, KanbanColumn, KanbanMove}; +use lance_graph_planner::batch_writer::BatchWriter; + +/// Build a `KanbanMove` from a `mailbox`/`from`/`to` triple using only public +/// constructors/fields on the shipped contract type — no hand-rolled bit math. +fn make_move(mailbox: u32, from: KanbanColumn, to: KanbanColumn, witness: u32) -> KanbanMove { + KanbanMove { + mailbox, + from, + to, + witness_chain_position: witness, + libet_offset_us: 0, + exec: ExecTarget::Native, + } +} + +/// Probe 1 (W1b): cast() makes intent moves visible on the board AHEAD of any +/// ack; ack() then removes the cast from unacked(). +#[test] +#[ignore = "probe-first: W1b mechanism pending — un-ignore in the W1b implementation commit"] +fn probe_ahead_update_ordering() { + let mut writer: BatchWriter<()> = BatchWriter::new(); + + let moves = vec![ + make_move(7, KanbanColumn::Planning, KanbanColumn::CognitiveWork, 0), + make_move(7, KanbanColumn::CognitiveWork, KanbanColumn::Evaluation, 1), + make_move(7, KanbanColumn::Evaluation, KanbanColumn::Commit, 2), + ]; + + let cast = writer.cast(7, moves.clone(), ()); + + // AHEAD: intent is visible on the board BEFORE any ack. + assert_eq!(writer.intent_moves(cast), Some(moves.as_slice())); + + writer.ack(cast); + + // After ack, the cast is no longer in the unacked (crash-replay) surface. + assert!(!writer.unacked().contains(&cast)); +} + +/// Probe 2 (M24): a cast that is never acked stays on the crash-replay +/// surface (`unacked()`), and its intent moves remain replayable. +#[test] +#[ignore = "probe-first: W1b mechanism pending — un-ignore in the W1b implementation commit"] +fn probe_kill_after_cast_replay() { + let mut writer: BatchWriter<()> = BatchWriter::new(); + + let moves = vec![make_move( + 11, + KanbanColumn::Planning, + KanbanColumn::CognitiveWork, + 0, + )]; + + let cast = writer.cast(11, moves.clone(), ()); + // Deliberately no ack() — simulates a crash between cast and ack. + + let unacked = writer.unacked(); + assert_eq!(unacked, vec![cast]); + + let replayed = writer + .intent_moves(cast) + .expect("unacked cast must still have replayable intent moves"); + assert!(!replayed.is_empty()); + assert_eq!(replayed, moves.as_slice()); +} + +/// Probe 3 (W1c): resolve_owner() calls the resolver on the first lookup for +/// a mailbox (cache miss) and skips it on the second lookup for the same +/// mailbox (cache hit), returning the same owner both times. +#[test] +#[ignore = "probe-first: W1b mechanism pending — un-ignore in the W1b implementation commit"] +fn probe_delegation_miss_then_hit() { + let mut writer: BatchWriter<()> = BatchWriter::new(); + + let on_behalf: u32 = 3; + let mut resolver_calls = 0u32; + + let (owner_first, was_hit_first) = writer.resolve_owner(on_behalf, |mailbox| { + resolver_calls += 1; + mailbox + 1000 // arbitrary deterministic "resolved owner" transform + }); + assert!(!was_hit_first, "first resolve for a mailbox must be a cache miss"); + assert_eq!(resolver_calls, 1); + + let (owner_second, was_hit_second) = writer.resolve_owner(on_behalf, |mailbox| { + resolver_calls += 1; + mailbox + 1000 + }); + assert!(was_hit_second, "second resolve for the same mailbox must be a cache hit"); + assert_eq!(resolver_calls, 1, "resolver must not be called again on cache hit"); + assert_eq!(owner_first, owner_second); +} + +/// Probe 4 (M24 / operator ruling "melden macht frei", plan Addendum-7): +/// casting is REPORTING, and reporting frees the thinker — the writer NEVER +/// refuses a cast because earlier casts on the same mailbox are still +/// unacked. Three stacked casts are three WAL entries: distinct ids, full +/// ordered history retained, acks retire independently. (Physical sink +/// coalescing — one flush of the live store satisfying all earlier intents +/// for a row — is sink-side behavior, exercised in the W1b implementation +/// tests, not at this API surface.) +#[test] +#[ignore = "probe-first: W1b mechanism pending — un-ignore in the W1b implementation commit"] +fn probe_stacked_casts_never_refused() { + let mut writer: BatchWriter<()> = BatchWriter::new(); + + let mv = |w| make_move(7, KanbanColumn::Planning, KanbanColumn::CognitiveWork, w); + + // Three stacked writes on the SAME mailbox, zero acks in between. + let c1 = writer.cast(7, vec![mv(0)], ()); + let c2 = writer.cast(7, vec![mv(1)], ()); + let c3 = writer.cast(7, vec![mv(2)], ()); + + // No refusal: three distinct WAL entries, cast order preserved. + assert_ne!(c1, c2); + assert_ne!(c2, c3); + assert_eq!(writer.unacked(), vec![c1, c2, c3]); + + // Every stacked intent stays independently replayable. + assert!(writer.intent_moves(c1).is_some()); + assert!(writer.intent_moves(c2).is_some()); + assert!(writer.intent_moves(c3).is_some()); + + // Acks retire independently and in any order. + writer.ack(c2); + assert_eq!(writer.unacked(), vec![c1, c3]); + writer.ack(c1); + writer.ack(c3); + assert!(writer.unacked().is_empty()); +} diff --git a/crates/thinking-engine/Cargo.lock b/crates/thinking-engine/Cargo.lock index c6965fff..b81080e7 100644 --- a/crates/thinking-engine/Cargo.lock +++ b/crates/thinking-engine/Cargo.lock @@ -61,9 +61,9 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "arrow-array" -version = "57.3.0" +version = "58.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8955af33b25f3b175ee10af580577280b4bd01f7e823d94c7cdef7cf8c9aef" +checksum = "cfd33d3e92f207444098c75b42de99d329562be0cf686b307b097cc52b4e999e" dependencies = [ "ahash", "arrow-buffer", @@ -71,7 +71,7 @@ dependencies = [ "arrow-schema", "chrono", "half", - "hashbrown", + "hashbrown 0.17.1", "num-complex", "num-integer", "num-traits", @@ -79,9 +79,9 @@ dependencies = [ [[package]] name = "arrow-buffer" -version = "57.3.0" +version = "58.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c697ddca96183182f35b3a18e50b9110b11e916d7b7799cbfd4d34662f2c56c2" +checksum = "0c6cd424c2693bcdbc150d843dc9d4d137dd2de4782ce6df491ad11a3a0416c0" dependencies = [ "bytes", "half", @@ -91,9 +91,9 @@ dependencies = [ [[package]] name = "arrow-data" -version = "57.3.0" +version = "58.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fdd994a9d28e6365aa78e15da3f3950c0fdcea6b963a12fa1c391afb637b304" +checksum = "3c88210023a2bfee1896af366309a3028fc3bcbd6515fa29a7990ee1baa08ee0" dependencies = [ "arrow-buffer", "arrow-schema", @@ -104,9 +104,9 @@ dependencies = [ [[package]] name = "arrow-schema" -version = "57.3.0" +version = "58.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c872d36b7bf2a6a6a2b40de9156265f0242910791db366a2c17476ba8330d68" +checksum = "f633dbfdf39c039ada1bf9e34c694816eb71fbb7dc78f613993b7245e078a1ed" [[package]] name = "atomic-waker" @@ -1011,6 +1011,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + [[package]] name = "heck" version = "0.5.0" @@ -1322,7 +1328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", ] [[package]] @@ -1558,6 +1564,7 @@ dependencies = [ "num-integer", "num-traits", "p64", + "paste", "portable-atomic", "portable-atomic-util", "rawpointer", @@ -2081,7 +2088,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "675656c1eabb620b921efea4f9199f97fc86e36dd6ffd1fbbe48d0f59a4987f5" dependencies = [ - "hashbrown", + "hashbrown 0.16.1", "serde", "serde_json", ] diff --git a/crates/thinking-engine/src/bf16_engine.rs b/crates/thinking-engine/src/bf16_engine.rs index a623b338..19174c79 100644 --- a/crates/thinking-engine/src/bf16_engine.rs +++ b/crates/thinking-engine/src/bf16_engine.rs @@ -12,7 +12,7 @@ //! //! Built from StackedN::cosine() via ClamCodebook, matching bgz-tensor pipeline. -use crate::dto::{BusDto, ResonanceDto}; +use crate::dto::{BusDto, PerturbationDto}; use bgz_tensor::stacked_n::{bf16_to_f32, f32_to_bf16}; use ndarray::hpc::heel_f64x8::cosine_f32_to_f64_simd; use ndarray::simd::F32x16; @@ -283,7 +283,7 @@ impl BF16ThinkingEngine { } /// Think until convergence. - pub fn think(&mut self, max_cycles: usize) -> ResonanceDto { + pub fn think(&mut self, max_cycles: usize) -> PerturbationDto { for _ in 0..max_cycles { let prev = self.energy.clone(); self.cycle(); @@ -297,11 +297,15 @@ impl BF16ThinkingEngine { break; } } - ResonanceDto::from_energy_f32(&self.energy, self.cycles) + PerturbationDto::from_energy_f32(&self.energy, self.cycles) } /// Think with temperature. - pub fn think_with_temperature(&mut self, max_cycles: usize, temperature: f32) -> ResonanceDto { + pub fn think_with_temperature( + &mut self, + max_cycles: usize, + temperature: f32, + ) -> PerturbationDto { for _ in 0..max_cycles { let prev = self.energy.clone(); self.cycle_with_temperature(temperature); @@ -315,7 +319,7 @@ impl BF16ThinkingEngine { break; } } - ResonanceDto::from_energy_f32(&self.energy, self.cycles) + PerturbationDto::from_energy_f32(&self.energy, self.cycles) } /// Perturb with codebook indices. @@ -344,7 +348,7 @@ impl BF16ThinkingEngine { /// Commit dominant peak. pub fn commit(&self) -> BusDto { - let resonance = ResonanceDto::from_energy_f32(&self.energy, self.cycles); + let resonance = PerturbationDto::from_energy_f32(&self.energy, self.cycles); BusDto { codebook_index: resonance.top_k[0].0, energy: resonance.top_k[0].1, diff --git a/crates/thinking-engine/src/composite_engine.rs b/crates/thinking-engine/src/composite_engine.rs index de492be6..80d87253 100644 --- a/crates/thinking-engine/src/composite_engine.rs +++ b/crates/thinking-engine/src/composite_engine.rs @@ -12,7 +12,7 @@ //! ``` use crate::builder::BuiltEngine; -use crate::dto::ResonanceDto; +use crate::dto::PerturbationDto; /// Result of multi-model composition. pub struct CompositeResult { @@ -111,7 +111,7 @@ impl CompositeEngine { for (name, engine) in &mut self.lenses { engine.think(max_cycles); - let res = ResonanceDto::from_energy_f32(engine.energy(), engine.cycles()); + let res = PerturbationDto::from_energy_f32(engine.energy(), engine.cycles()); for &(idx, energy) in &res.top_k { if energy > 1e-10 { diff --git a/crates/thinking-engine/src/dto.rs b/crates/thinking-engine/src/dto.rs index 423bedb8..795d13b9 100644 --- a/crates/thinking-engine/src/dto.rs +++ b/crates/thinking-engine/src/dto.rs @@ -1,9 +1,9 @@ //! DTOs: bus adapters between cognitive speed zones. //! -//! Φ Dispersion: StreamDto — sensor output enters the field -//! Ψ Interference: ResonanceDto — the ripple field IS f64[4096] -//! B Consequence: BusDto — committed thought with provenance -//! Γ Collapse: ThoughtStruct — stabilized, persisted, text is lazy +//! Φ Dispersion: StreamDto — sensor output enters the field +//! Ψ Interference: PerturbationDto — the ripple field IS f64[4096] +//! B Consequence: BusDto — committed thought with provenance +//! Γ Collapse: ThoughtStruct — stabilized, persisted, text is lazy use crate::engine::CODEBOOK_SIZE; @@ -47,16 +47,21 @@ pub struct StreamDto { } // ═══════════════════════════════════════════════════════════════════════════ -// Ψ — ResonanceDto: the ripple field +// Ψ — PerturbationDto: the ripple field // ═══════════════════════════════════════════════════════════════════════════ -/// ResonanceDto IS f64[4096] energy. Not a struct with candidate lists. +/// PerturbationDto IS f64[4096] energy. Not a struct with candidate lists. /// /// High energy at entry 42 = "thought 42 resonates." /// Zero at entry 200 = "thought 200 destructively interfered." /// Spike at entry 7 = "thought 7 crystallizing." +/// +/// D-PERT-1: renamed from `ResonanceDto` — this is the mechanical +/// Morton-tile inverse-pyramid perturbation field (Ψ), distinct from the +/// PERSPECTIVAL (Piaget Three-Mountains) `ResonanceDto` in +/// `awareness_dto.rs`, which keeps the `ResonanceDto` name. #[derive(Clone, Debug)] -pub struct ResonanceDto { +pub struct PerturbationDto { /// Energy distribution. f32 — matches u8 distance table precision. pub energy: Vec, pub cycle_count: u16, @@ -64,7 +69,15 @@ pub struct ResonanceDto { pub top_k: [(u16, f32); 8], } -impl ResonanceDto { +/// D-PERT-1: `ResonanceDto` (mechanical Ψ) renamed to `PerturbationDto`. +/// `ResonanceDto` now names only the perspectival awareness DTO in +/// `awareness_dto.rs`. +#[deprecated( + note = "renamed to PerturbationDto (D-PERT-1); ResonanceDto now names only the perspectival awareness DTO" +)] +pub type ResonanceDto = PerturbationDto; + +impl PerturbationDto { /// Build from f32 energy array (fixed-size legacy compat). pub fn from_energy(energy: &[f32; CODEBOOK_SIZE], cycles: u16) -> Self { Self::from_energy_f32(energy.as_slice(), cycles) @@ -237,7 +250,7 @@ mod tests { energy[100] = 0.3; energy[200] = 0.2; - let res = ResonanceDto::from_energy(&energy, 5); + let res = PerturbationDto::from_energy(&energy, 5); assert_eq!(res.top_k[0].0, 42); assert!((res.top_k[0].1 - 0.5).abs() < 1e-10); assert_eq!(res.top_k[1].0, 100); diff --git a/crates/thinking-engine/src/dual_engine.rs b/crates/thinking-engine/src/dual_engine.rs index bfc38728..636cf04e 100644 --- a/crates/thinking-engine/src/dual_engine.rs +++ b/crates/thinking-engine/src/dual_engine.rs @@ -4,7 +4,7 @@ //! Same input, same perturbation, different encoding → measure disagreement. use crate::builder::BuiltEngine; -use crate::dto::ResonanceDto; +use crate::dto::PerturbationDto; /// Results from running both engines on the same input. pub struct DualResult { @@ -105,8 +105,10 @@ impl DualEngine { self.engine_a.think(max_cycles); self.engine_b.think(max_cycles); - let res_a = ResonanceDto::from_energy_f32(self.engine_a.energy(), self.engine_a.cycles()); - let res_b = ResonanceDto::from_energy_f32(self.engine_b.energy(), self.engine_b.cycles()); + let res_a = + PerturbationDto::from_energy_f32(self.engine_a.energy(), self.engine_a.cycles()); + let res_b = + PerturbationDto::from_energy_f32(self.engine_b.energy(), self.engine_b.cycles()); let a_indices: Vec = res_a .top_k diff --git a/crates/thinking-engine/src/engine.rs b/crates/thinking-engine/src/engine.rs index adc13a26..dfcafa39 100644 --- a/crates/thinking-engine/src/engine.rs +++ b/crates/thinking-engine/src/engine.rs @@ -154,7 +154,7 @@ //! FIX 4: GPU Vulkan compute shader: ~10μs per cycle (see GPU design doc) //! ``` -use crate::dto::{BusDto, ResonanceDto}; +use crate::dto::{BusDto, PerturbationDto}; use ndarray::hpc::heel_f64x8::cosine_f64_simd; use ndarray::simd::F32x16; use ndarray::simd_amx; @@ -383,7 +383,11 @@ impl ThinkingEngine { /// Applies softmax(energy/T) after each cycle. This exponentiates /// small differences into large ones, breaking the attractor collapse /// on uniform tables. T=1.0 ≈ standard normalization. - pub fn think_with_temperature(&mut self, max_cycles: usize, temperature: f32) -> ResonanceDto { + pub fn think_with_temperature( + &mut self, + max_cycles: usize, + temperature: f32, + ) -> PerturbationDto { for _ in 0..max_cycles { let prev = self.energy.clone(); self.cycle(); @@ -415,12 +419,12 @@ impl ThinkingEngine { break; } } - ResonanceDto::from_energy_f32(&self.energy, self.cycles) + PerturbationDto::from_energy_f32(&self.energy, self.cycles) } /// Run until convergence. Returns the resonance state. /// Uses `cycle_auto` which tries VNNI first, falls back to F32x16. - pub fn think(&mut self, max_cycles: usize) -> ResonanceDto { + pub fn think(&mut self, max_cycles: usize) -> PerturbationDto { for _ in 0..max_cycles { let prev = self.energy.clone(); self.cycle(); @@ -435,7 +439,7 @@ impl ThinkingEngine { break; } } - ResonanceDto::from_energy_f32(&self.energy, self.cycles) + PerturbationDto::from_energy_f32(&self.energy, self.cycles) } /// ONE thinking cycle via AMX/VNNI dispatch path. @@ -533,7 +537,7 @@ impl ThinkingEngine { /// Commit: dominant peak → BusDto. pub fn commit(&self) -> BusDto { - let resonance = ResonanceDto::from_energy_f32(&self.energy, self.cycles); + let resonance = PerturbationDto::from_energy_f32(&self.energy, self.cycles); BusDto { codebook_index: resonance.top_k[0].0, energy: resonance.top_k[0].1, diff --git a/crates/thinking-engine/src/f32_engine.rs b/crates/thinking-engine/src/f32_engine.rs index 4db320f2..f7f7b448 100644 --- a/crates/thinking-engine/src/f32_engine.rs +++ b/crates/thinking-engine/src/f32_engine.rs @@ -8,7 +8,7 @@ //! Think cycle: signed MatVec with ReLU + normalization. //! No floor heuristic. No threshold. Full signed accumulation. -use crate::dto::{BusDto, ResonanceDto}; +use crate::dto::{BusDto, PerturbationDto}; /// F32 thinking engine. Distance table at full f32 precision. pub struct F32ThinkingEngine { @@ -206,25 +206,29 @@ impl F32ThinkingEngine { } /// Think until convergence or max_cycles. - pub fn think(&mut self, max_cycles: usize) -> ResonanceDto { + pub fn think(&mut self, max_cycles: usize) -> PerturbationDto { for _ in 0..max_cycles { let delta = self.cycle(); if delta < self.convergence_threshold { break; } } - ResonanceDto::from_energy_f32(&self.energy, self.cycles) + PerturbationDto::from_energy_f32(&self.energy, self.cycles) } /// Think with temperature scaling (1/T applied to MatVec output before normalization). - pub fn think_with_temperature(&mut self, max_cycles: usize, temperature: f32) -> ResonanceDto { + pub fn think_with_temperature( + &mut self, + max_cycles: usize, + temperature: f32, + ) -> PerturbationDto { for _ in 0..max_cycles { let delta = self.cycle_with_temp(temperature); if delta < self.convergence_threshold { break; } } - ResonanceDto::from_energy_f32(&self.energy, self.cycles) + PerturbationDto::from_energy_f32(&self.energy, self.cycles) } /// Access the energy distribution. @@ -263,7 +267,7 @@ impl F32ThinkingEngine { /// Commit dominant peak as BusDto. pub fn commit(&self) -> BusDto { - let resonance = ResonanceDto::from_energy_f32(&self.energy, self.cycles); + let resonance = PerturbationDto::from_energy_f32(&self.energy, self.cycles); BusDto { codebook_index: resonance.top_k[0].0, energy: resonance.top_k[0].1, diff --git a/crates/thinking-engine/src/signed_engine.rs b/crates/thinking-engine/src/signed_engine.rs index f6c79c1d..0a553a8a 100644 --- a/crates/thinking-engine/src/signed_engine.rs +++ b/crates/thinking-engine/src/signed_engine.rs @@ -15,7 +15,7 @@ //! L4 is already i8. With signed L1-L3, the entire stack is uniform. //! VNNI hardware: i8x i8->i32 = VPDPBSSD = native instruction. -use crate::dto::{BusDto, ResonanceDto}; +use crate::dto::{BusDto, PerturbationDto}; use ndarray::hpc::heel_f64x8::cosine_f64_simd; use ndarray::simd::F32x16; @@ -350,7 +350,11 @@ impl SignedThinkingEngine { } /// Think with temperature-as-excitation. Lower T = sharper discrimination. - pub fn think_with_temperature(&mut self, max_cycles: usize, temperature: f32) -> ResonanceDto { + pub fn think_with_temperature( + &mut self, + max_cycles: usize, + temperature: f32, + ) -> PerturbationDto { for _ in 0..max_cycles { let prev = self.energy.clone(); self.cycle_with_temperature(temperature); @@ -365,11 +369,11 @@ impl SignedThinkingEngine { break; } } - ResonanceDto::from_energy_f32(&self.energy, self.cycles) + PerturbationDto::from_energy_f32(&self.energy, self.cycles) } /// Run until convergence. Returns the resonance state. - pub fn think(&mut self, max_cycles: usize) -> ResonanceDto { + pub fn think(&mut self, max_cycles: usize) -> PerturbationDto { for _ in 0..max_cycles { let prev = self.energy.clone(); self.cycle(); @@ -384,7 +388,7 @@ impl SignedThinkingEngine { break; } } - ResonanceDto::from_energy_f32(&self.energy, self.cycles) + PerturbationDto::from_energy_f32(&self.energy, self.cycles) } /// Inject perturbation from sensor output. @@ -413,7 +417,7 @@ impl SignedThinkingEngine { /// Commit: dominant peak -> BusDto. pub fn commit(&self) -> BusDto { - let resonance = ResonanceDto::from_energy_f32(&self.energy, self.cycles); + let resonance = PerturbationDto::from_energy_f32(&self.energy, self.cycles); BusDto { codebook_index: resonance.top_k[0].0, energy: resonance.top_k[0].1,