diff --git a/.claude/board/AGENT_LOG.md b/.claude/board/AGENT_LOG.md index bbfea978d..590b18677 100644 --- a/.claude/board/AGENT_LOG.md +++ b/.claude/board/AGENT_LOG.md @@ -1,3 +1,12 @@ +## 2026-09-05 — D-BLW-5 build + measurement (Sonnet builder from main-thread spec; orchestrator gates) — the observer-effect loop resumed with the belief-arena reader + +- **Operator ruling:** "resume the D-BLW-5 loop with the belief arena reader" (2026-09-05). +- **Spec:** `exec-runs/d-blw-5-build-spec-main-thread.md` (main thread; §0 two corrections to the 08-05 design note; §10 dry-run addendum). **Builder:** Sonnet, edit-only, tag-file `exec-runs/d-blw-5-build-sonnet.md` (three deviations documented in the file's module doc). +- **Files:** `crates/lance-graph-supervisor/tests/d_blw_5_observer.rs` (new, ~1650 lines, `cycle-driver`-gated, one `#[tokio::test]`), `crates/lance-graph-supervisor/Cargo.toml` (`jc` dev-dep, pre-ratified). +- **Gates run centrally:** compile (2 borrow errors fixed by the orchestrator), `cargo test --features supervisor,cycle-driver --test d_blw_5_observer` GREEN on run 4, clippy `-D warnings` on the test target. +- **Dry runs 1–3 → instrument fixes** (no threshold moved): O6 self-scan doc lines; `reason()` to fixed point; aperiodic corpus; typicality in confidence not frequency; O7 restated. Recorded in E-BLW5-FIRST-MEASUREMENT-1. +- **Outcome:** all seven gates pass with twins; O4/O5 SILENT at the pinned κ floor; the marginals show typicality-tracking saturation (T, F−) with P/N/CTRL bit-identical. D-BLW-5b queued (reader-rate/φ floor, pre-registered). Second E-entry: E-NARS-EXPECTATION-CHOICE-PREFERS-IGNORANCE-TO-A-CONFIDENT-NEGATIVE-1. + ## 2026-09-05 — D-NXG-4 → D-BLW-5 payload: two Sonnet workers on disjoint files, orchestrator-gated - **Why:** operator: "go ahead with D-NXG-4 into D-BLW-5". A Sonnet census first (`exec-runs/d-blw-5-design-main-thread.md`, doctrine §2, STATUS_BOARD): D-BLW-5 has NO shipped code and is PAUSED (operator 2026-08-05); its designed home is a `cycle-driver`-gated supervisor test where the planner is reachable and `ndarray` is not. That fixed the membrane split: DTO in the zero-dep contract, producer in the planner, transform in jc. diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 114879d59..43d0b72a0 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,162 @@ +## 2026-09-05 — E-PLANNING-MIGRATES-TO-LOCO-R2IL-DATAFUSION-IS-GRACE-PERIOD-1 (OPERATOR-RULED) + +**Status:** operator-ruled, BINDING (2026-09-05, verbatim intent: *"Every planning is in migration to ogar-loco and ogar-r2il, especially datafusion is out of the picture, what exists gets a grace period, nothing new will migrate to it."*) +**Confidence:** High on the ruling; the census it lands on is E-THE-UNFINISHED-UDF-WAS-NOT-THE-DEBT-1 (same day). + +**The ruling.** Planning / orchestration / execution is migrating to `ogar-loco` (the +vocabulary-agnostic call ABI: every 12-byte payload read as `(function : value)` calls +into 256×256 tables) and `ogar-r2il` (the always-on R2IL vocabulary, EXECUTED by +r2sleigh's interpreter, never pre-converted). **DataFusion is out of the picture as a +target.** What exists on DataFusion today — `datafusion_planner`, `sql_query`, the +Python bindings' `SessionContext`, `graph_table`, `rls.rs`, the `query`/`query-lite` +features — gets a **grace period**: maintained, tested, not extended. **Nothing new +migrates to it.** A plan, card, or PR that names DataFusion as the home of NEW +behaviour (a rewrite rule, a UDF, a dispatcher, a policy VM, a projection seam) is +stale on arrival. + +**Consequences, stated so they cannot blur:** +- **D-OIF-1 is ruling A (SUPERSEDED / REMOVE) with no replacement seam in DataFusion.** + The census (same day) had left one door open — "the planner's scan taking the + authorized column list" — as the legitimate place DataFusion would receive a + `ClassView × WideFieldMask` projection. That door is closed by this ruling: the + authorized projection is consumed by Lance reads and by loco programs, never by a new + DataFusion operator. `policy.rs` (`PolicyRewriter`, `ColumnMaskRewriter`, + `NotYetWiredHashUdf`, `policy_hash_v1`, the encryption/DP stubs) is a retirement + cone, and the hash-family question is moot. +- **Forward-stubs pointing at DataFusion are vacancies under grace, not backlog:** + `datafusion-dispatch` (`postgrest.rs` `parsed_query_to_plan`), `datafusion-plan` + (`audit_from_plan`), `register_policy_udfs`/`register_vsa_udfs`-style registration + helpers, `MembraneRegistry::with_rls`. They are not to be completed; they are to be + regraded and, when their grace period ends, removed with their cone. +- **`rls.rs` (`RlsRewriter`) is grace-period duplicate of `ClassRbac::row_scope`.** + Row scope is enforced on the canonical path (`authorize → {scope, mask}`) or not at + all — never by adding a second optimizer rule. +- **The Python bindings' raw `SessionContext` path is grace-period.** It is the only + shipped live-query surface today and carries no policy step; it is not the reason + to build one. +- **The layer map the D-OIF-1 census verified stands:** lifecycle = SoA-owned + Kanban/Rubicon (`try_advance_phase`); action semantics = OGAR `ActionDef`/`ActionState`; + authorization = `ClassRbac` × `ClassView` × `WideFieldMask` (transport-only today, + enforcement is the missing implementation); execution/reasoning = loco/r2il; + storage/query = Lance, with DataFusion in grace. No layer impersonates another. + +Cross-refs: `.claude/board/exec-runs/d-oif-1-census-main-thread.md` (the census that +this ruling landed on); `open-ideas-fetch-v1` (#1185, D-OIF-1 to be re-graded there); +`ogar-loco` / `ogar-r2il` crate docs (OGAR); E-LANCE-IS-UPSTREAM-AUTHORITATIVE-1 (Lance +stays; only DataFusion is in grace). + +## 2026-09-05 — E-THE-UNFINISHED-UDF-WAS-NOT-THE-DEBT-1 — D-OIF-1 re-derived: the execution model that needed `policy_hash_v1` never reached production + +**Status:** FINDING (production census 2026-09-05, four symbol groups traced from real entry points; full table in `.claude/board/exec-runs/d-oif-1-census-main-thread.md`). Ruling for #1185: **A — SUPERSEDED / REMOVE**. +**Confidence:** High — every classification is a grep-backed call-path fact, not a reading of a `pub mod`. +**Correction (2026-09-05, after #1185's reconcile pass):** two points of this census compressed "not live" into "does not exist". (1) `ColumnMaskRewriter` HAS one non-test constructor — MedCare `routes/patient.rs:150`, behind the default-off `lance-phase2-rbac` feature (no Dockerfile enables it), ending in a decoder stub that returns `None`; "no production caller" stands, "no caller" does not. (2) The remove cone is therefore the NARROWER one #1185 §2 carries: first removal = `RedactionMode::Hash` + `NotYetWiredHashUdf` only; the rest of `policy.rs`/`rls.rs` is frozen grace-period until MedCare retires its feature — not whole-module removal. Ruling A itself is unchanged. Canonical adjudication: E-THE-UNFINISHED-FUNCTION-WAS-NOT-THE-DEBT-1 (#1185); this entry is the earlier, independent census and is read through that one. + +`policy_hash_v1` sat for months as "not yet registered," and every plan (including +#1185 §2) read the missing body as the debt. The census shows the debt was upstream: +the model that made a masking UDF necessary — materialize the forbidden column, then +transform it inside DataFusion's optimizer — never acquired a production caller. The +whole `policy.rs` framework is constructed only inside `#[cfg(test)]`; no file in the +repo calls `add_optimizer_rule` / `add_analyzer_rule`; no binary or handler imports +`callcenter::policy`; the only shipped live-query surface (the Python bindings' +`SessionContext`, `graph.rs:1208,1518`) has no policy step and never asked for one; the +server binaries that could carry the chain are off by default and absent from CI. The +canonical model makes a forbidden field ABSENT from the projection (`ClassView × +WideFieldMask`, real in `ogar-doc-ir::project::field_mask`) instead of present-then- +hashed. A stub that fails loud is honest about its wiring and silent about the wiring's +reason; "loud > silent" protected a hole nobody was going to fall into. + +**Also measured:** RBAC enforcement on the canonical path is TRANSPORT ONLY — +`authorize()`, `ClassRbac`, `OgarRbac` have zero non-test callers, `field_mask` still +returns `FieldMask::FULL` (charter C1.4 retype not done), `medcare_actor.rs:100` is a +`// TODO`; there is no `ogar-rbac` crate (the machinery lives in `lance-graph-contract::rbac` ++ `lance-graph-rbac` + `lance-graph-ogar::rbac_impl`). Lifecycle is SoA-owned exactly as +ruled: the single phase write is `MailboxSoA::advance_phase` via the checked +`try_advance_phase`, applied at seal by `cycle_driver.rs`; `KanbanActor` is a tombstone; +baton/emission survive only in prose. `ogar-loco` / `ogar-r2il` touch neither RBAC nor +kanban. **Test for the next card:** before completing a stub, find the caller that would +have been harmed by its absence. No caller, no debt. + +## 2026-09-05 — E-BLW5-FIRST-MEASUREMENT-1 — the observer-effect instrument is alive, and every pre-registered gate reads SILENT + +**Status:** MEASURED (`crates/lance-graph-supervisor/tests/d_blw_5_observer.rs`, one run, 40 owners 1:1, synthetic corpus, recorded run 4 after three dry runs — all numbers below are from the recorded run; the dry runs are listed, not hidden). +**Confidence:** High on every number; High that the κ floor is the wrong observable for THIS reader (see the last paragraph); Low on anything beyond this corpus and instrument. + +The D-BLW-5 loop resumed by operator ruling ("with the belief arena reader"). The payload +is the shipped `shape₀ × rank₀` (`NestedBands::shape_rank` → `ShapeRankPayload`, sealed in +`RemeasureLedger` at V0 = `DatasetVersion(1)`), injected into each owner's `BeliefArena` as +16 shape beliefs `prior Inh band_k` (f = mass_k) plus one `subject Inh prior` per corpus +subject at **frequency 1, confidence = the prior's mass at the observed rank** (typicality — +the C2 encoding, amended once, below). The only propagation channel is `rcr_abduce` + +`close_transitive` iterated to a fixed point. Reader A = the verse's statement was revised +(contradiction > 0.05); reader B = another cohort subject has been INFERRED to carry the +verse's predicate at c ≥ 0.01 (derived, empty stamp, rung ≥ 1, no reserved term). + +| arm | typicality injected | rate_b V0→V1 | κ V0→V1 | φ V0→V1 | gate | +|---|---|---|---|---|---| +| T (rank 6, mass 6/40) | 0.150 | 0.471 → **0.885** | −0.180 → −0.254 (Δ −0.074) | −0.252 → **−0.909** | O4 SILENT at floor 0.10 | +| F+ (rank 7→12, mass 0) | 0.000 | 0.430 → 0.430 | −0.134 → −0.134 (Δ 0) | unchanged | DROP fires | +| F− (rank 8→3, mass 8/40) | 0.200 | 0.521 → **0.922** | −0.080 → −0.168 (Δ −0.087) | −0.128 → **−0.789** | O5 d = +0.087 SILENT | +| P (uniform, rank 8) | 0.0625 | 0.474 → 0.474 | −0.267 → −0.267 (Δ 0) | unchanged | O2 holds (|Δκ| = 0 < 0.10) | +| N (T payload, bloom read) | 0.150 | — | −0.110 → −0.110 | — | O3 frozen, Hamming 0 | +| CTRL (no injection) | — | 0.583 → 0.583 | −0.006 → −0.006 (Δ 0 exactly) | unchanged | idempotence holds | + +**What the pinned gates say:** O2 (placebo does not move), O3 (null instrument frozen), O1 +(remeasure guard barks and stays silent as specified), O6 (no reader identifier before the +measurement marker), O7 (1266 derived beliefs on T carry a reserved term — the payload DID +propagate; 0 of the 340 beliefs reader B accepted carry one — the firewall holds) all pass +with both twins. O4 and O5 are **SILENT at the pre-registered κ floor of 0.10** — the honest +null as written: "awareness does not reflect this statistic (at floor 0.10)". Reported, not +re-tuned. + +**What the tables show that the κ floor does not:** the reader moved by the largest amount +the instrument can express — every non-contradicted verse became B-true in T and F− (n00 +went 159 → 0 and 154 → 0), φ went from −0.25 to −0.91 — and it moved in exact proportion to +the injected TYPICALITY: 0 (F+, empty bucket) → nothing, 0.0625 (P) → nothing (c ≈ 0.003 < +C_MIN), 0.15 (T) → saturation, 0.20 (F−) → saturation. Awareness tracks the injected rank's +MASS, not its truth (F− moved more than T). That is the anchoring/Goodhart pattern the +doctrine names — but it is NOT the O5 verdict, because O5 was pinned on κ and κ barely moved: +κ is invariant to a reader that saturates, since a constant reader has no covariance. The +finding therefore reads: **this instrument's observable (Δκ) is insensitive to the effect it +was built to detect on this reader; the effect is visible in the marginals.** The next probe +pre-registers a reader-rate/φ floor BEFORE running (D-BLW-5b, queued), never by reclassifying +this run. + +**Dry runs, on the record (instrument fixes, no threshold changed):** (1) O6 self-scan +tripped on its own doc lines; `reason()` was not a fixed point (CTRL Δκ = −0.0028 with no +injection) — now bounded RCR+close to fixed point; corpus counts had period 12 (`w % {2,3,4}`) +so the 40-owner prior was 12 atoms with empty buckets (T's pooled φ landed in one: typicality +0) — now a splitmix64 fold of the window index. (2) Everything bit-identical: the typicality +was injected as a FREQUENCY below 0.5 and the arena discarded it — see the sibling entry +E-NARS-EXPECTATION-CHOICE-PREFERS-IGNORANCE-TO-A-CONFIDENT-NEGATIVE-1. (3) O7's shadow reader +could not differ from B under RCR-only reasoning — restated to what can fire (spec §10). + +Cross-refs: doctrine `.claude/knowledge/observer-effect-tfpn-doctrine.md`; spec +`.claude/board/exec-runs/d-blw-5-build-spec-main-thread.md` (§0 corrections C1/C2, §10 +addendum); E-MEASUREMENT-BURNS-THE-STATE-1; E-NXG-22 (F+'s shifted rank fell into an empty +bucket — the out-of-support saturation, now seen from the loop side). + +## 2026-09-05 — E-NARS-EXPECTATION-CHOICE-PREFERS-IGNORANCE-TO-A-CONFIDENT-NEGATIVE-1 — a derived belief with f < 0.5 loses CHOICE to a vacuous one + +**Status:** FINDING (measured in D-BLW-5 dry run 2: injected cross-subject links came back at c = 3.53e-11 instead of the computed 0.108). +**Confidence:** High — it follows from `TruthValue::expectation() = c·(f − 0.5) + 0.5` and `admit_derived`'s "replace only when expectation strictly exceeds" rule, and it reproduced on every subject pair. + +`BeliefArena::admit_derived` and `close_transitive` resolve every derivation of the same +statement by CHOICE on `expectation()`. For f < 0.5 expectation DECREASES with confidence: a +confident negative (f = 0.15, c = 0.108 → 0.462) is out-ranked by any near-vacuous path to +the same statement (c ≈ 0 → ≈ 0.5). So a low-frequency belief cannot survive in the derived +layer once closure finds any alternative route; the arena prefers "unknown" to "confidently +not". This is NARS-correct as a decision rule (higher expectation = better bet on the +statement being true) but it means **the derived layer cannot carry negative evidence with +confidence** — anything a probe wants to propagate as "weakly true" must be encoded with +f ≥ 0.5 and the strength in c (which is what D-BLW-5 now does: `subject Inh prior` at f = 1, +c = typicality). Consequence for readers: a derived-layer reader that thresholds on +confidence is blind to negations by construction; a reader that wants them must read the +grounded (observed) layer, where `revise_at` keeps `|f₁ − f₂|` as `contradiction`. + +Cross-refs: `belief.rs` S2 ("closure-internal duplicates resolve by CHOICE on +`expectation()`"); E-BLW5-FIRST-MEASUREMENT-1 (the probe that hit it); +`.claude/plans/dialectic-engine-v1.md` §1 S2. + ## 2026-09-05 — E-THE-UNFINISHED-FUNCTION-WAS-NOT-THE-DEBT-1 — the execution model that needed `policy_hash_v1` never reached a binary, and neither has its replacement **Status:** FINDING (W0 production census, four read-only tracers + orchestrator verification of the contested fact; nothing compiled). Plan: `.claude/plans/open-ideas-fetch-v1.md` §2 (ruling A). Operator ruling the same day: planning migrates to `ogar-loco` / `ogar-r2il`; DataFusion is out; what exists gets a grace period; nothing new migrates to it. diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 4b57b2937..79118b115 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -1,3 +1,15 @@ +## 2026-09-05 — OPERATOR RULING: planning → ogar-loco / ogar-r2il, DataFusion in grace period — INVENTORY DELTA + +- **Ruling:** E-PLANNING-MIGRATES-TO-LOCO-R2IL-DATAFUSION-IS-GRACE-PERIOD-1. Every DataFusion-hosted surface in this inventory (`datafusion_planner`, `sql_query`, Python `SessionContext`, `graph_table`, `rls.rs`, `query`/`query-lite`, the `datafusion-dispatch`/`datafusion-plan` forward-stubs) is now **grace-period: maintained, not extended**. New behaviour lands in loco/r2il programs or on Lance directly. +- **D-OIF-1 (#1185):** census verdict A — `policy.rs` is a retirement cone (E-THE-UNFINISHED-UDF-WAS-NOT-THE-DEBT-1); no hash implementation; no DataFusion projection seam. +- **RBAC enforcement:** `ClassRbac` / `authorize()` / `OgarRbac` are TRANSPORT ONLY (zero non-test callers) — the missing implementation on the canonical path, recorded, not resurrected via DataFusion. + +## 2026-09-05 — D-BLW-5 measured (branch, first run) — INVENTORY DELTA + +- **New test binary:** `lance-graph-supervisor/tests/d_blw_5_observer.rs` (cycle-driver). **New dev-dep:** supervisor → `jc` (dev only; the ORCHESTRATOR-RATIFIED exception from the 08-05 design note). +- **First consumer of the D-BLW-5 payload outside its own tests:** the probe seals `ShapeRankPayload` per arm in `RemeasureLedger` at V0 and injects it into `BeliefArena`. The DTO's `rank` reaches awareness as typicality (mass at rank), see E-BLW5-FIRST-MEASUREMENT-1 C2. +- **Result in one line:** gates all pass; O4/O5 SILENT at κ-floor 0.10; reader B saturates in proportion to injected typicality (T, F−) while P/N/CTRL are bit-identical. D-BLW-5b queued. + ## 2026-09-05 — PR #1181 MERGED (`80dbcc35`) + jc clippy sweep — INVENTORY DELTA - **Merged:** #1181 — `contract::shape_rank` (`ShapeRankPayload`, `RemeasureKey`, `RemeasureLedger`, `RemeasureError::{AlreadySealed, VersionMismatch}`) and `planner::nested_bands` are on `main`. Consumers today: none outside the two crates' own tests — the D-BLW-5 loop that would inject the payload is PAUSED. See `PR_ARC_INVENTORY.md` 2026-09-05 #1181 entry. diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 03b8927d3..049689643 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -667,6 +667,8 @@ Plan: `.claude/plans/kanban-64k-inverted-awareness-v1.md` (operator anchors a/b; | MEASURE-64K-AXES | Operator-specified five-axis benchmark: B0 dummy baseline, B1a/B1b ownership+representation split, W0/W1 WAL segment curve (one fdatasync/one version per cycle), T0-T2 temporal phases over 1,048,576 rows, L1a/L1b chunked-layout control, EXP-KIA-A2-64K exploratory concurrency (non-claiming, A2 untouched). One release binary `measure_wal_curve`; four answers, axes never blended | lance-graph | Stage A0 MEASURED (3 of 4 answers; WAL knee NOT REPRODUCIBLE and unclaimed). v3's M-arm and O-arm both **MEASURED 2026-08-05, both NEGATIVE** (pre-registered two-sided, so both are findings): M-arm — digests MATCHED (`68128e3662df105c`), reorder 9.4 ms, downstream −25.8 ms ⇒ **Δtotal +35.2 ms, Morton LOSES**; ordered-chunk fast path 350.9 ms was slower than the generic 339.7 ms. O-arm — **DIVERGED** (`64565f362db2e4a5` ≠ `3e71c2aa7be8e325`) ⇒ the seal's ordering is load-bearing FOR THIS O-B CONSTRUCTION; follow-on question queued as PROBE-SEAL-VS-TEMPORAL. Open measurement defect: `ISS-MARM-T1-4X-A0-GAP` (M-arm T1 320-340 ms vs A0's 78-86 ms — blocks that one cross-run comparison only). A-arm (allocator-vs-architecture decomposition) deferred. v2 rolling-epoch-closure model supersedes v1's execution model (v1 = Stage A0 baseline + instrumentation, lane in build); then A1/A2 rolling+Morton lane; crypto REMOVED from the seal benchmark per operator sanity-check (seal path verified crypto-free in source; encryption = separate later layer, AEADs dep no longer blocking); EXP-KIA-A2-ROLLING-CLOSURE recorded (A2 frozen) | plans measure-64k-axes-v1 + v2 | | D-IGN-B | Ignition starts the REAL lenses: arming z ∈ {0 unarmed, 1-4 = the four stances, 5 = Fusion (Strict/Aware gap read)} — six ordinals in MetaWord's 6-bit field, no 36-style bridge (Q1 sidestepped); thought bodies = the shipped nars stance machinery via cycle_driver's pluggable seam (D-BLW-1 precedent) + blw_fusion's two-projection read. Can-fire: different lenses over byte-identical rows ⇒ non-identical readouts; silent twin: same lens ⇒ bit-identical; unarmed ⇒ none | lance-graph | **GREEN 2026-08-05** — 1/1 test, L0-L7 + z5-BLOCKED; L1 Kant≠Wittgenstein over byte-identical rows, same-lens bit-identical; Hegel/Nietzsche NON-empty on the text path; z=5 reserved with the printed blocker | plan cycle-driver 12.11; `tests/d_ign_b_lenses.rs` | | D-BLW-5 | Observer-effect loop: a jc statistic about the cohort fed back into awareness; four pre-registered arms (true/false±/placebo) + the §12.8 bloom criterion as the frozen null instrument. KILL: placebo movement invalidates; T-silence is a reportable null. Payload refined §12.9a: distribution shape × Prozentrang (never the raw scalar); single-measurement law + remeasure guard; doctrine doc `observer-effect-tfpn-doctrine.md` | lance-graph | **PAUSED by operator 2026-08-05** (loop) / **payload half RELAUNCHED by operator 2026-09-05** — `shape₀ × rank₀` DTO + remeasure guard shipped in `contract::shape_rank`, producer `NestedBands::shape_rank` (E-NXG-22); the four arms, bloom criterion and supervisor test file remain untouched — the Opus design lane was stopped mid-run (controlling signal; not relaunched). Banked and committed: the Sonnet API inventory (`exec-runs/d-blw-5-api-inventory-sonnet.md` — BeliefArena admits hand-built statements; jc+run_cycle live in disjoint crates, supervisor+jc dev-dep pre-ruled acceptable; ndarray unreachable supervisor-side). Gate to resume: operator direction | plan cycle-driver 12.9 | +| D-BLW-5b | Observer-effect loop, second instrument: same arms/payload/arena, observable pre-registered on the READER MARGINALS (rate_b and φ floors) instead of κ, because κ is invariant to a saturating reader. KILL: P moves; N moves; CTRL non-idempotent | lance-graph | **Queued 2026-09-05** — follows E-BLW5-FIRST-MEASUREMENT-1; floors to be pinned in a spec BEFORE the run | plan cycle-driver 12.9b | +> **Update 2026-09-05 (D-BLW-5, first measurement):** loop RESUMED by operator ruling ("with the belief arena reader") and MEASURED — `crates/lance-graph-supervisor/tests/d_blw_5_observer.rs`, 40 owners 1:1, all seven pre-registered gates pass with twins; O4/O5 SILENT at the pinned κ floor 0.10; reader B saturates in proportion to injected typicality on T and F− while P/N/CTRL are bit-identical. See E-BLW5-FIRST-MEASUREMENT-1 and plan §12.9b. The PAUSED wording above is superseded by this line. | PROBE-ARC-TORQUE | Torque of an arc = 2× Heron triangle area from 3 HHTL O(1) distances (magnitude metric-only; chirality via helix_orient Fisher-2z frame codes); Fisher 2z = logit((1+r)/2) as the additive equal-information embedding, hydratable via tanh. Stage B: translator stray/mindset vs source (floor = intra-language variance; Romans 5:12 in-quo/eph-hō as known-answer falsifier). Stage C: author-bias fields on the redactional layer + attribution of non-canonical books (G1-G5 in-canon ground-truth gates first). KILL per stage: F1 radial/tangential non-separation; clamp-rate ceiling; G1-G5 failures | lance-graph | Queued — proposed §12.10, behind PROBE-IGNITION + D-BLW-5 | plan cycle-driver 12.10 | | D-ACK-CLEANUP | Delete the ack/pump/tick theater entirely + add the visibility surface (operator-directed, context-hot). `kanban_actor.rs` → message-free module: `PhaseCensus` (`&self` fleet census; absorbing DAG-derived; empty ≠ at-rest) + pure `mul_target`/`parse_kanban_step`; `KanbanMsg`/`KanbanActor`/5 RPC drivers/`run_to_absorbing`/`KanbanRouteError` DELETED. Lane E migrated to direct `&mut` owner (supervisor+ractor out of its feature); W2b probe rewritten direct + census-over-real-SoA. Zombie verdict: half yes (lib.rs re-exports + lane E kept it alive; `ack_and_propose` already absent — the ack half lived only in docs). OGAR boundary verified: zero consumers; its ActionHandler ack surface is legitimate membrane protocol. Kanbanstep (`VersionScheduler::on_version`) NOT theater — stays canonical; naming question flagged only | lance-graph | **SHIPPED 2026-08-05** — all gates green (supervisor 9 lib + w2b 3/3 + cycle-driver 4/4; onebrc lane-e 20/20; clippy --no-deps clean; fmt clean) | `E-ACK-THEATER-DELETED-1`; TD-MESSAGE-RESIDUE resolution; `E-PROGRESSION-IS-EXISTENCE-NOT-COMMAND-1` | | D-HWV-1 / EXP-HOT-WINDOW | The hot version window (operator-directed): publication clock decoupled from persistence clock — every sealed cycle publishes to RAM immediately (`published_head`), durability is a batched background **sync barrier** (`durable_head`, barrier-flush fork: K unsynced Lance commits + ONE fdatasync, so 1 cycle = 1 real DatasetVersion survives everywhere). Vertical batching (time, not owners); Nagle-shaped flush policy (bytes/16-dirty/200ms/pressure/shutdown/durable-only-reader). Five panel-bought invariants H-1..H-5 (checkpoint fencing, torn-tail cleanup, no-veto-after-publish, zero-copy conditions, rung-decided visibility — H-5's original "ack rebase" clause RETRACTED per `E-PROGRESSION-IS-EXISTENCE-NOT-COMMAND-1`: no pump/ack/scheduler; seal → publish → immediately queryable → durability trails; the window is a resident horizon of immutable versions, not a message queue). Version-multiplexing fork REJECTED (temporal.rs has no cycle-within-version coordinate ⇒ intra-version hindsight). P1-P5 pre-registered with named KILLs | lance-graph | **Design banked 2026-08-05, panel-hardened (1 sweep + 1 adversarial refuter; fork inverted by evidence), NOT built** — build lane gated on operator word; P1/P2 comparisons additionally gated on ISS-MARM-T1-4X-A0-GAP + TD-LANCE9 remeasure | plan measure-64k-axes-v4; `E-HOT-WINDOW-DECOUPLES-THE-CLOCKS-1`; v2 cross-note; seal-vs-temporal caveats | diff --git a/.claude/board/SUPERSESSION-INDEX.md b/.claude/board/SUPERSESSION-INDEX.md index 156f5ff85..c2921a54a 100644 --- a/.claude/board/SUPERSESSION-INDEX.md +++ b/.claude/board/SUPERSESSION-INDEX.md @@ -62,7 +62,7 @@ coverage instead. | `ResonanceDto` | REPURPOSE | `PerturbationDto` | 2 | 12 | 7 | | `BindSpace` | RETIRE | — | 68 | 47 | 41 | | `CollapseGateEmission` | RETIRE | — | 5 | 14 | 12 | -| `ThinkingStyle` | RETIRE-toward-contract | — | 51 | 28 | 24 | +| `ThinkingStyle` | RETIRE-toward-contract | — | 52 | 28 | 24 | ## Table 2 — plans naming a ruled symbol without citing the ruling (73) diff --git a/.claude/board/TECH_DEBT.md b/.claude/board/TECH_DEBT.md index ef184dc3c..a9c6f6e44 100644 --- a/.claude/board/TECH_DEBT.md +++ b/.claude/board/TECH_DEBT.md @@ -22,6 +22,10 @@ Both are pinned two-sided in `crates/lance-graph/tests/lance_row_identity_probe. so a fix cannot land silently. Source: `E-VERSIONED-GRAPH-OVERWRITES-SO-ROW-ADDRESSES-ALIAS-ACROSS-VERSIONS-1`, plan `lance-convergence-staged-migration-v1` §7.7–7.8. +## TD-SUPERVISOR-CLIPPY-RED-ON-BASE-1 (2026-09-05) — OPEN + +**`cargo clippy -p lance-graph-supervisor --features supervisor,cycle-driver --tests -- -D warnings` is red on `main` in files the D-BLW-5 arc did not touch:** `src/cycle_driver.rs` (8× `needless_pass_by_ref_mut` on `recover_fleet` call sites: 1505, 1539, 1704, 2053, 2067, 2097, 2105, 2116) and `tests/probe_ignition_64k.rs:435` (`for_kv_map`). The new `tests/d_blw_5_observer.rs` is clippy-clean on its own (`--test d_blw_5_observer -D warnings`). Same shape as TD-JC-CLIPPY-RED-ON-BASE-1 (resolved #1183) and TD-SIGKER-CLIPPY-RED-ON-BASE-1: fix once, then arm the lint in `rust-test.yml` beside the existing `--features supervisor,cycle-driver` test step. Not done in the D-BLW-5 PR: it is not this arc's code. + ## TD-JC-CLIPPY-RED-ON-BASE-1 (2026-09-05) — RESOLVED 2026-09-05 (lint sweep + `jc-proof.yml` clippy step, operator-directed after #1181) **`crates/jc` does not pass `cargo clippy --manifest-path crates/jc/Cargo.toml --all-targets -- -D warnings`, and did not before this arc.** Measured by stashing the working tree: the same `unusual_byte_groupings` errors reproduce on `origin/main` in `dueker_zoubouloglou.rs`, `ewa_sandwich.rs`, `ewa_sandwich_3d.rs`, `koestenberger.rs`, `lib.rs`, `pearl.rs`, `pflug.rs`, `probe_p1_gamma_phase.rs`, `sigma_codebook_probe.rs`, `weyl.rs` — none touched by the D-NXG-4 arc; `stats.rs` (the file this arc edits) has zero findings. CI runs tests and `fmt --check` on jc (`jc-proof.yml`, `style.yml:226`) but no clippy step, which is how it went unseen. Same shape as `TD-SIGKER-CLIPPY-RED-ON-BASE-1` (resolved 2026-09-04 by fixing + arming the lint in CI); the same two-step fix applies. Not done here: it is not this arc's code and would widen a payload PR into a lint sweep. diff --git a/.claude/board/exec-runs/d-blw-5-build-sonnet.md b/.claude/board/exec-runs/d-blw-5-build-sonnet.md new file mode 100644 index 000000000..5ecc8b893 --- /dev/null +++ b/.claude/board/exec-runs/d-blw-5-build-sonnet.md @@ -0,0 +1,191 @@ +# D-BLW-5 build — Sonnet grindwork lane tag-file + +> Edit-only. No `cargo` of any kind was run (rule 7). Written on branch +> `claude/pr-294-ragged-path-validation-170zcy` (not switched, per instructions). + +## Files touched + +1. **NEW** `crates/lance-graph-supervisor/tests/d_blw_5_observer.rs` — the + D-BLW-5 observer-effect probe, built to + `.claude/board/exec-runs/d-blw-5-build-spec-main-thread.md` with three + documented deviations (module doc, top of file) where the literal spec + text under-specifies a compiling shape: + 1. No `run_loop(..., on_sealed: &mut dyn FnMut(...))` higher-order + driver — replaced with a single-cycle `run_cycle_mechanics` fn + (defined before the O6 marker) called twice from the test body, + because the design's callback signature (shared refs only) cannot + both respect O6 (readers/`binary_association`/`kappa` textually + after the marker) and grant the mutable `Mind` access injection + needs. + 2. `inject` only calls `arena.observe` for the reserved family: does + NOT call `reason` itself (the spec's `reason(&mut arena)` sentence + sits *after* the `inject` code fence, read as the caller's next + step, matching §6's "inject per cohort ..., reason() on EVERY mind + (CTRL included)" as ONE uniform pass). + 3. B′'s "≥2 distinct Wittgenstein games" fallback is a local + `distinct_games_local` reproducing `stance_panel`'s Wittgenstein + games taxonomy directly (`stance_panel` is not in the build spec's + §2 import list), restricted to the two games this file's `Mind` + can ever produce (`inh-subj`/`inh-obj`; no `impl-*` or `rel-*` + games — this corpus never emits `because` or an epistemic verb). +2. **`crates/lance-graph-supervisor/Cargo.toml`** — added one + `[dev-dependencies]` line: `jc = { path = "../jc" }`, with the exact + comment text the build spec §1 specifies. +3. **NEW** this file. + +No other file touched. No board ledger file (`AGENT_LOG.md` etc.) written. + +## Signatures read (file:line), this pass + +- `.claude/board/AGENT_LOG.md` — read (tail) before starting, not written. +- `.claude/board/exec-runs/d-blw-5-build-spec-main-thread.md` — read whole, + twice. +- `crates/lance-graph-supervisor/tests/d_ign_b_lenses.rs` — read whole + (1337 lines), all chunks — the scaffold copied verbatim: `fnv1a` (219-226), + `bloom_add` (228-237), `tokens` (239-243), `encode_plane` (245-251), + `BLOOM_K` (217), `flow_qualia` (210-212), `thinking_style_for` (175-181), + `style_vector_for` (183-191), `plan_context_for` (193-202), `mantissa_of` + (204-206), `RowSpanDescriptor` (402-417), `row_span_payload` (419-427), + `SealedCycle`/`MemWal`+`WalSink` impl (432-544), `build_owner` (568-602), + `ScanResult`/`scan_board` (678-703), `ColumnPassOutcome`/`column_pass` + (705-735), `plan_or_evaluate_think` (738-772), `owner_verses` (551-554), + `labelled_verses` (559-566), `synth_term`/`SYNTH_STEMS` (273-282). +- `.claude/board/exec-runs/d-blw-5-api-inventory-sonnet.md` — read whole. + Confirmed: `BeliefArena` full surface (§A, `belief.rs:88-337`), + `Stamp`/`Copula`/`CStmt` (§A), `TruthValue` (§A, `truth.rs:8-15`), + `stance_panel`/`stream` signatures (§B), `BinaryAssociation`/ + `binary_association` (§C, `stats.rs:612-693`), the `jc`/`ndarray` + reachability gap from `lance-graph-supervisor` before this PR's manifest + edit (§C-D), `DatasetVersion(pub u64)` (§F), `MetaWord`/`QualiaI4_16D`/ + `gate_decision_i4`/`MailboxSoA` full method list/`WriteCell`/ + `WriteOutcome` (§F). +- `.claude/board/exec-runs/d-blw-5-design-main-thread.md` — read whole. +- `.claude/knowledge/observer-effect-tfpn-doctrine.md` — read whole. +- `crates/lance-graph-planner/src/nars/belief.rs` — read via the + inventory's already-verified quotes (§A above); not re-opened directly + this pass beyond what the inventory cites verbatim with line numbers. +- `crates/lance-graph-planner/src/nars/tactics.rs` — read whole (822 + lines). `Throttle` (114-146: fields `c_min`/`budget`/`hub_indegree` all + `pub`; `Throttle::new` is a plain `fn`, NOT `const fn`, at 128-135 — + hence `throttle()` is a function in the probe, not a `const`, a + necessary deviation from the brief's literal + `const THROTTLE: Throttle = Throttle { ... }` since a non-const-fn + struct-literal-with-named-fields IS legal in a const context actually + (all fields are plain values) — re-checked: `Throttle` has no + `Default`/custom `new` requirement for const construction since it is a + plain struct literal, so a `const THROTTLE: Throttle = Throttle { c_min: + 0.0, budget: 65_536, hub_indegree: usize::MAX };` WOULD in fact compile + as a const (struct literal, not a fn call) — **this file uses a `fn + throttle()` unnecessarily conservatively**; not fixed in this pass + (functionally equivalent, called once, no perf/correctness impact) but + flagged here as an unverified simplification opportunity. + `rcr_abduce` signature (176-267), `Candidate`/`Frontier` (70-156). +- `crates/lance-graph-planner/src/nars/stance.rs` — read whole (536 + lines). `Interner` (50-87), `Provenance` (90-99, fields `verse`/`stmt`/ + `negated`), `ReadOut` (132-145, fields `provenance`/`lifts`/`impls`/ + `pass2_admitted`/`pass2_revised`), `stream` (161-408), `STOP`/`AUX` + catalogues (33-45) — confirmed "was"/"was not" are not in `STOP`/`AUX` + and rely on `is_copula`/`is_negation` from + `lance_graph_contract::grammar::verb_lexicon`/`clause_cues`, which were + **NOT independently opened this pass** (see Not Verified below) — + the corpus's "was"/"was not" copula shape follows the build spec's + explicit instruction ("Copula 'was' arms the predicate; 'was not' + negates") rather than an independent verification that `is_copula("was") + == true`. `stance_panel` (469-534) — read for provenance of the + Wittgenstein games taxonomy reproduced locally in `distinct_games_local` + (games set: `inh-subj`/`inh-obj`/`rel-subj`/`rel-obj`/`impl-cause`/ + `impl-effect`, `stance.rs:512-529`). +- `crates/lance-graph-planner/src/nested_bands.rs` — read whole (1095 + lines). `NestedBandsBuilder::new`/`calibrate_equal_width` (178-239), + `NestedBands::shape_rank` (339-354, asserts `band_count() == + SHAPE_BUCKETS`), `quantize_2z` (40-52). +- `crates/lance-graph-contract/src/shape_rank.rs` — read whole (337 + lines). `ShapeRankPayload` (30-86, fields `shape`/`rank`/`version` all + `pub`, `mass`/`mass_below`/`prozentrang`/`rank_fraction`/`is_frozen_at`), + `RemeasureKey` (93-104, all fields `pub`), `RemeasureError` (108-126), + `RemeasureLedger::seal` (171-190 — VersionMismatch checked BEFORE + AlreadySealed, confirmed the ordering cited in the worker brief). +- `crates/jc/src/stats.rs` lines 580-710 (`phi`, `BinaryAssociation` + fields, `binary_association` body) and 1180-1220 (`fisher_2z`, + `fisher_2z_inv`, `FISHER_CLAMP_EPS`) — read as instructed. +- `crates/lance-graph-planner/examples/blw_fusion.rs` lines 240-390 + (`score_row`/`rank_verdicts`/`bloom_of_terms`/`RowSpanDescriptor` — cited + provenance for `score_row`/`bloom_verdicts` in the new file, adapted to + use `MailboxSoA::content_row` instead of `identity_plane_at` since this + crate's `Tenant` type is `MailboxSoA`, not `blw_fusion.rs`'s own tenant + type) and 655-712 (`churn`/`positive_rate`/`is_degenerate`/ + `print_association_table` — copied verbatim as instructed, adapted to + take `Option` since this file's `measure_cohort` can + return `None`). +- `crates/lance-graph-supervisor/Cargo.toml` — read whole before editing + (52 lines pre-edit); confirmed no prior `jc` line existed. + +## What could NOT be verified (STOP+report per rule 11) + +1. **Not compiled, not run — orchestrator gates**, per rule 7 (no + `cargo` whatsoever was invoked). Every signature above was read from + source, but no type-check confirms the file compiles. +2. **`is_copula("was")` / `is_negation("not")` / catalogue membership of + the synthetic tokens** — `lance_graph_contract::grammar::verb_lexicon` + and `clause_cues` were NOT opened this pass (out of the brief's named + mandatory-read list). The corpus design (build spec §3, "Copula 'was' + arms the predicate; 'was not' negates") is taken as given from the + build spec rather than independently re-derived from those catalogue + sources. +3. **`Belief.stamp` value after `admit_derived`** — the probe's reader B + assumes `admit_derived`-produced beliefs carry `stamp == Stamp::default()` + (distinguishing them from `stream`'s `Stamp::source(n)`-stamped + observations). This was not independently confirmed by reading + `admit_derived`'s body beyond the inventory's already-quoted signature + (`belief.rs:115-116`); the inventory's own "Not Verified" section does + not cover this specific field either. If `admit_derived` in fact copies + a caller-supplied stamp or synthesizes a non-default one, reader B's + "derived-layer, default-stamp" discriminant would misclassify some + beliefs. +4. **`ThinkingStyle::Creative`/`Reflective` exact discriminants** — + inherited unverified from the inventory's own §F "Not Verified" item 3; + this file reuses `thinking_style_for` unchanged from `d_ign_b_lenses.rs` + without re-deriving those discriminants. +5. **Whether `MailboxSoA::content_row` panics or returns a default row for + an unpopulated row index** vs. `blw_fusion.rs`'s `identity_plane_at` + (`Option`-returning) — `bloom_verdicts`/`score_row` here call + `content_row` directly (confirmed `pub fn content_row(&self, row: + usize) -> &[u64]` in the inventory §F method list, non-`Option`), so no + `.expect()` is needed, but the exact panic-vs-zero behavior for `row >= + populated()` was not independently traced — `N_QUANTILE`/`bloom_verdicts` + only ever index `0..POPULATED_ROWS` (48) against `ROWS_PER_OWNER` (64), + which is always populated by `build_owner`, so this should never bite, + but it is unverified as a general contract. +6. **`RemeasureKey.cohort: u32` vs the `MailboxId` type's actual width** — + the file casts `T_LO as u32` etc.; `MailboxId`'s underlying integer + width was not independently re-derived beyond the inventory's usage + (`u8` arming values and `MailboxId` used as array/range bounds + throughout `d_ign_b_lenses.rs` suggest a small unsigned type, but no + direct `type MailboxId = ...` line was read in this pass). +7. **O1's "AlreadySealed" branch reachability precondition** — the code + asserts `payloads_v0.t` is `Some` before running O1; if the corpus's + real T-cohort phi turns out `None` (degenerate) at actual run time, + O1's can-fire block would panic on the `.expect(...)` rather than + printing a named DEGENERATE result the way O2/O4/O5 do. This is a + known asymmetry versus the doctrine's "every gate carries a can-fire + AND can-stay-silent, both non-vacuous" requirement — not fixed here + because the actual T-cohort behavior can only be known by running the + test, which this lane may not do. +8. **Clippy cleanliness** — not checked (rule 7 forbids `cargo clippy` + here); several `#[allow(...)]` were added defensively at the module + level mirroring `d_ign_b_lenses.rs`'s own allow-list, plus one + `#[allow(clippy::too_many_lines)]` on `build_payloads` and one + `#[allow(dead_code)]` on `MemWal::wal_writes` (unused in this file + since no test asserts on WAL-write counts, unlike `d_ign_b_lenses.rs` + which also never reads it either — copied for parity with the + `WalSink` impl body which does increment it). + +## Honest summary + +The file is a full-length, best-effort realization of the build spec's +intent, with three documented structural deviations (module doc) where +the spec's literal pseudocode could not be reconciled into one compiling +shape (the `run_loop`/`on_sealed` higher-order-driver tension between the +O6 firewall and injection's need for `&mut Mind`). All cited signatures +were read from source this pass except where explicitly flagged above. +Not compiled, not run, not clippy-checked — orchestrator gates per rule 7. diff --git a/.claude/board/exec-runs/d-blw-5-build-spec-main-thread.md b/.claude/board/exec-runs/d-blw-5-build-spec-main-thread.md new file mode 100644 index 000000000..feaa112f2 --- /dev/null +++ b/.claude/board/exec-runs/d-blw-5-build-spec-main-thread.md @@ -0,0 +1,253 @@ +# D-BLW-5 — BUILD SPEC (main-thread authored, 2026-09-05; operator: "resume the D-BLW-5 loop with the belief arena reader") + +> Binding inputs, in precedence order: `.claude/knowledge/observer-effect-tfpn-doctrine.md` +> (doctrine) → `.claude/board/exec-runs/d-blw-5-design-main-thread.md` (design (a)–(g)) → +> `.claude/board/exec-runs/d-blw-5-api-inventory-sonnet.md` (exact signatures; wins on any +> conflict) → THIS file (the pinned build). Every number below is FINAL before the first run +> (doctrine §5.1). Hand-tuned values are labelled `[hand-tuned]` per I-NOISE-FLOOR-JIRAK. + +## 0. Two corrections to the 2026-08-05 design note (recorded, not silent) + +C1. **The note's readers A/B were injection-invariant by construction.** Both read only +fields that `stance::stream` writes (`contradiction` moves only through `revise_at` on an +OBSERVED statement; `lifts` only through `stream`). `admit_derived` never overwrites a +grounded belief (the Codex stamp fix). So with those readers T-silence would have been +STRUCTURAL, not measured — the vacuity the falsifiability rule forbids. Reader B is +therefore re-pinned to read the arena's DERIVED layer (§4), which the tactics DO move. + +C2. **The rank enters awareness as typicality, not as a bucket index.** Encoding the rank as +`(subject Inh band_r)` at f=1 makes `rcr_abduce` link every subject to every other through +the shared reserved predicate `band_r` at c≈0.447 REGARDLESS of the shape — pure injection +mechanics, which the P arm would rightly kill. The composable encoding is the one the doctrine +already names as the meaning of Prozentrang: "where the observation SITS in the prior". The +payload's 16 shape beliefs carry the masses; the rank binds the cohort to the prior at +frequency = the prior's mass at the observed rank (§5). Two ranks with EQUAL mass are +indistinguishable to awareness under this encoding — a stated limitation, not a bug. + +## 1. Placement (design (a), unchanged) + +- New file: `crates/lance-graph-supervisor/tests/d_blw_5_observer.rs`, whole file wrapped + in `#[cfg(feature = "cycle-driver")] mod d_blw_5_observer { ... }` exactly like + `d_ign_b_lenses.rs`. One `#[tokio::test] async fn d_blw_5_observer_effect_belief_arena()`. +- Manifest: `crates/lance-graph-supervisor/Cargo.toml` `[dev-dependencies]` gains + `jc = { path = "../jc" }` with the comment: "D-BLW-5 oracle ONLY (dev-dep; never production; + jc is never modified and never fed its own output — doctrine §5.8). Pre-ratified in + exec-runs/d-blw-5-design-main-thread.md (a)." +- CI: `rust-test.yml:173` already runs every supervisor test binary under + `--features supervisor,cycle-driver`; nothing to add. +- Probe-local private structs/enums/fns inside the test module ARE permitted (precedent: + `LensReadout`, `MemWal`, `ScanResult` in `d_ign_b_lenses.rs`). Nothing shipped is minted. + +## 2. Scaffold — copy from `d_ign_b_lenses.rs`, provenance-commented at each site + +Copy VERBATIM (rename nothing): `fnv1a`, `bloom_add`, `tokens`, `encode_plane`, `BLOOM_K`, +`flow_qualia`, `thinking_style_for`, `style_vector_for`, `plan_context_for`, `mantissa_of`, +`RowSpanDescriptor`, `row_span_payload`, `SealedCycle`, `MemWal` (+ its `WalSink` impl), +`build_owner`, `ScanResult`, `scan_board`, `ColumnPassOutcome`, `column_pass`, +`plan_or_evaluate_think`, `owner_verses`, `labelled_verses`. Same imports as that file PLUS: +`use jc::stats::{binary_association, fisher_2z, BinaryAssociation};` +`use lance_graph_contract::shape_rank::{RemeasureError, RemeasureKey, RemeasureLedger, ShapeRankPayload, SHAPE_BUCKETS};` +`use lance_graph_planner::nested_bands::{quantize_2z, NestedBandsBuilder};` +`use lance_graph_planner::nars::stance::{stream, Interner, ReadOut};` +`use lance_graph_planner::nars::tactics::{rcr_abduce, Throttle};` +`use lance_graph_planner::nars::{BeliefArena, CStmt, Copula, Stamp, TruthValue};` +Drop what is unused (clippy `-D warnings` is the gate; the orchestrator runs it). + +Pre-registered run shape: +``` +FLEET_OWNERS = 40 (MailboxId 0..40), ROWS_PER_OWNER = 64, POPULATED_ROWS = 48, +CORPUS_VERSES = 40*48 = 1920, SCOPE = 0..40, CYCLES = 2, every owner armed z=1 +(Analytical), content salt = u64::from(id), flow_qualia(), firing_rows = 3. +Cohorts by id (design (d), 1:1 owners, never row-partitions): + T = 0..8 inject shape0 × TRUE rank0 + FP = 8..16 inject shape0 × rank shifted +1.5 logit + FM = 16..24 inject shape0 × rank shifted −1.5 logit + P = 24..32 inject uniform shape × median rank (8) [zero-information envelope, pinned] + N = 32..36 inject the T payload; OBSERVED through the awareness-free bloom criterion + CTRL= 36..40 no injection (degeneracy control + DROP twin) +``` + +## 3. Corpus — deterministic synthetic ONLY (no env var, no KJV path) + +`fn window(w: usize) -> Vec` (exactly POPULATED_ROWS lines), predicate terms via +`synth_term(w, n)` copied from d_ign_b (`SYNTH_STEMS`), subject terms +`fn subj(w, i) -> String = format!("sub{w:02}{i:02}")` (7 chars, alphanumeric, in no +catalogue; a bare content word becomes the clause subject because no pronoun precedes it — +`stance.rs` "Bare content word: subject anchoring"). Copula "was" arms the predicate; "was +not" negates. NEVER use pronouns, "because", perception verbs, or words ending in "ed". +``` +n_subj = 5 + w % 4 // 5..8 subjects +shared: "sub_w_0 was T(0)." , "sub_w_1 was T(0)." // s0,s1 share T(0) + if w % 2 == 0 also "sub_w_2 was T(1)." , "sub_w_3 was T(1)." // s2,s3 share T(1) +own: for i in 0..n_subj, for j in 0..(2 + (i + w) % 3): "sub_w_i was T(10 + 10*i + j)." +contra: for i in 0..(2 + w % 3): "sub_w_i was T(80 + i)." then LATER (after all own lines) + "sub_w_i was not T(80 + i)." // revision → contradiction 0.85 +pad: "sub_w_{j % n_subj} was T(100 + j)." until 48 lines +``` +Assert `out.len() <= POPULATED_ROWS` before padding (max 4 + 8*4 + 8 = 44). Corpus = +`(0..40).flat_map(window)`. Labels: `labelled_verses` ("kjv:{:05}" — keep the format; it is +only a label). + +## 4. The owner's mind, the reasoning pass, the readers (C6 firewall) + +```rust +struct Mind { arena: BeliefArena, intern: Interner, out: ReadOut, + subjects: Vec, // distinct `p.stmt.s` over out.provenance, sorted + reserved: HashSet } // empty until injection +const THROTTLE: Throttle = Throttle { c_min: 0.0, budget: 65_536, hub_indegree: usize::MAX }; // permissive [pinned] +const MAX_PASSES: u32 = 64; +fn build_mind(verses: &[(String,String)]) -> Mind // stream(.., pass2=false), then reason() +fn reason(arena: &mut BeliefArena) // f = rcr_abduce(arena,&THROTTLE); for c in f.candidates { arena.admit_derived(c.stmt, c.truth, &c.premises, c.rung); } arena.close_transitive(MAX_PASSES); +``` +`reason` is the ONLY propagation channel and runs at BOTH versions (V0 pre-injection, V1 +post-injection) for EVERY owner including CTRL — so V1−V0 on CTRL measures pass idempotence, +not injection. + +Readers, per verse label `v` with provenance (verses with no provenance are skipped in BOTH +vectors, and the skip count is printed): +- **A (evidence, injection-invariant by construction — documented as such):** + `∃ p ∈ out.provenance: p.verse == v && arena.get(p.stmt).contradiction > 0.05`. +- **B (awareness-coupled, "inferential corroboration"):** let `(s, p)` be the verse's FIRST + provenance stmt. `∃ b ∈ arena.entries(): b.stmt.cop == Inh && b.stmt.p == p && b.stmt.s != s + && subjects.contains(&b.stmt.s) && !reserved.contains(&b.stmt.s) && b.stamp == Stamp::default() + && b.rung >= 1 && b.truth.confidence >= C_MIN`, `C_MIN = 0.01` [hand-tuned; derivation: the + injected chain reaches c ≈ m·0.9·c_ab·0.9 with c_ab = 0.81m/(0.81m+1); P's m = 1/16 gives + 0.0024 (silent), m ≥ 0.13 gives ≥ 0.012 (fires)]. +- **B_shadow (O7 only):** B without the `!reserved.contains(..)` and `subjects.contains(..)` + clauses (any s' ≠ s). +- **B′ (pre-registered FALLBACK, decided at V0 on CTRL only, never after output):** if A or B + is degenerate (rate 0 or 1) on the pooled CTRL cohort at V0, print the fallback line and use + B′ = "the verse's subject participates in ≥ 2 distinct Wittgenstein games" (`stance_panel` + 4th element, games count for `s`) for ALL cohorts at both versions. +- `S(cohort, version) = binary_association(&a, &b)` over the concatenation of the cohort's + owners' verse vectors (owner order ascending). Print the FULL table (copy + `print_association_table` from `blw_fusion.rs:689-705`, cite it). Never a bare κ. + +## 5. Payload, prior pool, arms, ledger, injection + +At V0 (after the c1 seal), over ALL 40 owners: `phi_owner = S(owner-as-cohort-of-one).phi`. +Pool = `quantize_2z(fisher_2z(phi))` for every `Some(phi)`; PRECONDITION `pool.len() >= 16` +else panic "PRECONDITION: prior pool too thin ({n} < 16) — a corpus defect, not a finding". +`let nb = NestedBandsBuilder::new(SHAPE_BUCKETS).calibrate_equal_width(&pool, v0.0);` +Per injected arm cohort X ∈ {T, FP, FM, P, N}: `phi_c = S(cohort X, V0).phi`; if None → the +arm is DEGENERATE-AT-V0 (printed, excluded from every gate, named result). Else +`obs = quantize_2z(fisher_2z(phi_c))`, `payload_true = nb.shape_rank(obs, v0.0)`. +- T, N: `payload_true` (N seals under its own arm id). +- FP/FM: `rf = payload_true.rank_fraction() + 1.0/32.0` (bucket midpoint). If + `rf < 0.05 || rf > 0.95` → arm EXCLUDED (printed, never clipped). Else + `l = ln(rf/(1−rf)) ± 1.5`, `rf2 = 1/(1+exp(−l))`, `rank2 = min(15, floor(rf2·16))`, + `ShapeRankPayload::new(payload_true.shape, rank2, v0.0)`. +- P: `mass = pool.len()`, uniform shape `q = mass/16, r = mass%16`, buckets `0..r` get `q+1`, + the rest `q`; rank 8. `ShapeRankPayload::new(shape_uniform, 8, v0.0)`. +Ledger (`RemeasureLedger::new()` in the test body): key +`RemeasureKey { stat_id: 1, arm: {T=1,FP=2,FM=3,P=4,N=5}, cohort: , metric: 1 (=phi), dataset_version: v0.0 }` +sealed with the arm's payload BEFORE any injection. + +`fn inject(mind: &mut Mind, payload: &ShapeRankPayload)`: +``` +RESERVED_STAMP = Stamp::source(63) [pinned; corpus src ids fold mod 64 — a reserved stmt is new, so Admitted regardless] +prior = intern.id("blw5:prior"); band_k = intern.id(&format!("blw5:band:{k:02}")) for k in 0..16 +mass = payload.mass() as f32 +for k: arena.observe(CStmt{s: prior, cop: Inh, p: band_k}, TruthValue::new(shape[k] as f32 / mass, 0.9), RESERVED_STAMP) +typ = shape[rank] as f32 / mass // the prior's mass at the observed rank +for s in subjects: arena.observe(CStmt{s, cop: Inh, p: prior}, TruthValue::new(typ, 0.9), RESERVED_STAMP) +reserved = {prior} ∪ {band_k} +``` +then `reason(&mut arena)`. The 16 + n_subj statements are the verbatim-injected set. + +## 6. Cycle plan (design (d)) — the SoA loop supplies the sealed versions + +Fleet, `MemWal`, `BatchWriter`, `scan_board`, `column_pass(plan_or_evaluate_think)`, +`run_cognitive_work_gated_over` (closure: NO lens; returns the same +`(qualia, mantissa, reliability, row_span_payload)` tuple as d_ign_b) and `run_cycle` exactly +as d_ign_b's loop. Minds live in `HashMap` beside the fleet. +- c=1: build every mind (`build_mind`) BEFORE the casts; casts; `run_cycle` → **V0 = + sink.head()**; then the V0 measurement (§5 + S(cohort,V0) for all six cohorts + N's bloom + verdicts) via the `on_sealed` callback. +- c=2: inject per cohort (T/FP/FM/N with their payloads, P with the uniform payload, CTRL none), + `reason()` on EVERY mind (CTRL included); casts; `run_cycle` → **V1 = sink.head()**; + `on_sealed` → S(cohort,V1), N bloom verdicts at V1. +The loop lives in `fn run_loop(corpus, minds, on_sealed: &mut dyn FnMut(u32 /*cycle*/, DatasetVersion, &HashMap, &Fleet))` +defined BEFORE the measurement marker and it never reads what `on_sealed` computes (O6). +S0 is computed exactly once and stored; it is NEVER recomputed at V1 (single-measurement law). + +N's awareness-free criterion (copy `score_row`/`rank_verdicts` shape from +`blw_fusion.rs:314-375`, cite): seed plane = `encode_plane(, salt)`; +`score(row) = popcount(content_row(row) & seed)`; verdict = score ≥ the 75th-percentile score +over the owner's 48 rows (q = 0.25, the D-BLW-3 pin); `Vec` per N owner at V0 and V1. + +## 7. Gates (all pre-registered, each twinned; movement floor 0.10 on κ, the D-BLW-3 pin) + +Write a marker line `// ── MEASUREMENT BLOCK (O6 marker) ──` ; readers, `measure_cohort`, +the ledger and the gates are defined AFTER it; `run_loop` and everything it calls BEFORE it. +- **O1 remeasure guard** — can-fire: re-`seal` at T's V0 key → `Err(RemeasureError::AlreadySealed{..})`; + can-stay-silent: `seal` at `(1, 1, T-lo, 1, v1)` with `nb.shape_rank(obs_T, v1.0)`… NO — that + would be a remeasure of S0's input. Use a payload built with version v1 from the SAME + `payload_true.shape/rank` (`ShapeRankPayload::new(shape, rank, v1.0)`) → `Ok`; and arm 2 at + `(1, 2, T-lo, 1, v0)` → `Ok`. +- **O2 placebo** — assert `|Δκ(P)| < 0.10` (κ at both versions must be Some, else the arm is + DEGENERATE and the assert is replaced by the printed named result); twin: every P mind has + `arena.get(prior Inh band_00).is_some()`. +- **O3 null instrument** — assert N bloom verdicts byte-identical V0→V1 per owner; print the + Hamming (expected 0 — rows unchanged — and SAY so: "frozen by construction; pool drift 0"). +- **O4 the observable** — PRINT `Δκ(T) = κ1 − κ0`; `fires = |Δκ(T)| >= 0.10`. BOTH outcomes are + named results, NOT asserted: print "O4 FIRES — awareness reflects the statistic" or + "O4 SILENT — the honest null: awareness does not reflect this statistic (at floor 0.10)". + If κ1 is None while κ0 is Some: print "O4 SATURATED — reader B degenerate at V1" (named). +- **O5 direction** — with `d = Δκ(FP) − Δκ(FM)` (both arms eligible and non-degenerate): + `d >= 0.10` → "ANCHORING (testimony-dominance, Goodhart realised)"; `d <= −0.10` → + "EVIDENCE-DOMINANCE"; both |Δκ| ≥ 0.10 with |d| < 0.10 → "PERTURBATION (value-invariant)"; + else "SILENT". Print; assert only that a classification string was produced. +- **O6 firewall self-scan** — `const SRC: &str = include_str!("d_blw_5_observer.rs");` split at + the marker; assert the pre-marker half contains none of `"fn reader_a"`, `"fn reader_b"`, + `"binary_association("`, `"kappa"` (self-match guard: skip lines containing `"O6"`); assert + the post-marker half contains the marker AND `"binary_association("` (a scan that finds + nothing is not evidence). +- **O7 exclusion is load-bearing** — on the T cohort at V1: `S_shadow` (B_shadow) ≠ `S` (compare + the n00/n01/n10/n11 tuples) can-fire; and B's positive count > 0 can-stay-silent. +- **CTRL** — assert `Δκ(CTRL) == 0.0` exactly (pass idempotence) AND print reader Hamming 0. +- **DROP (design (e))** — per injected arm: `|Δκ| < 0.01` AND reader-B Hamming V0→V1 == 0 → + print "DROP fires for ". Printed, not asserted. +- **Preconditions (panic = corpus defect, not finding):** CTRL A-rate and B-rate ∈ (0,1) at V0 + (else the B′ fallback line applies to B; A degenerate → panic); pool ≥ 16. + +## 8. Output discipline + +Print every association table in full (C2), every payload as `shape=[..] rank=r +prozentrang=.. version=v`, the pool size and ladder boundaries, each arm's typicality +`typ`, and end with a "== D-BLW-5 — what this test does NOT claim ==" block: no validity of +the observer effect beyond this corpus/instrument; no parallelism; no durability; no fusion +verdict; no per-stance dispatch; jc untouched and one-way; the rank enters as typicality +(C2 above) so equal-mass ranks are indistinguishable; N frozen BY CONSTRUCTION (rows do not +change), its pool-drift duty reads 0 here; the synthetic corpus is symmetric by design and +the V0 reader rates are what the generator makes them. + +## 9. Worker deliverables + +1. `crates/lance-graph-supervisor/tests/d_blw_5_observer.rs` (new). +2. `crates/lance-graph-supervisor/Cargo.toml` (one dev-dep line + comment). +3. Tag-file `.claude/board/exec-runs/d-blw-5-build-sonnet.md`: files touched, every signature + read (file:line), what could not be verified ("not compiled, not run — orchestrator gates"). +No cargo. No other file. No board file. + +## 10. Addendum after the dry runs (2026-09-05, main thread) — instrument fixes, NOT threshold changes + +Every floor, C_MIN, band and shift above is unchanged. Three instrument defects were found by +dry runs 1–2 and fixed BEFORE the recorded run; the numbers of the dry runs are in the board +entry, not hidden: + +- **`reason()` is now a bounded RCR+closure fixed point.** One round is not idempotent (the + derived layer feeds RCR new premises); CTRL moved by Δκ = −0.0028 in dry run 1 with no + injection. A pass that runs at V0 and V1 must be a fixed point or V1−V0 measures the pass. +- **The corpus counts are a splitmix64 fold of the window index**, not `w % {2,3,4}` (period + 12 → the 40 owner φ values were 12 atoms and the prior had empty buckets between them; T's + pooled statistic landed in one, typicality 0). +- **C2 amended: typicality rides in the CONFIDENCE of `subject Inh prior`, frequency 1.** With + f = typicality (< 0.5) the arena reads a confident NEGATION and `admit_derived`'s + expectation-CHOICE replaced it with a vacuous closure path (c = 3.5e-11, expectation ≈ 0.5 + beats 0.462). Measured in dry run 2. This is an arena property worth its own board entry. +- **O7 restated to what can fire.** Under RCR-only reasoning no reserved-SUBJECT belief ever + acquires a corpus predicate, so `B_shadow ≡ B` and the pinned twin could not fire. Restated: + can-fire = the T arena at V1 holds derived beliefs carrying a reserved term (the payload DID + propagate); can-stay-silent = none of the beliefs reader B accepted at V1 carries a reserved + term (the firewall holds by the reader's shape). diff --git a/.claude/board/exec-runs/d-oif-1-census-main-thread.md b/.claude/board/exec-runs/d-oif-1-census-main-thread.md new file mode 100644 index 000000000..02b233c0e --- /dev/null +++ b/.claude/board/exec-runs/d-oif-1-census-main-thread.md @@ -0,0 +1,70 @@ +# D-OIF-1 census — re-derived from the tree (main thread, 2026-09-05) + +> Operator brief: re-open `open-ideas-fetch-v1` / #1185 and re-derive D-OIF-1 from the current +> architecture before any code. Four read-only Sonnet tracers (DataFusion policy stack; RBAC / +> ClassView / WideFieldMask; Kanban/Rubicon lifecycle; production DataFusion/Lance consumers) +> plus main-thread reads. Every classification is a call-path fact from a real entry point +> (`main.rs`, `src/bin/*`, handlers, PyO3 constructors) — never a `pub mod`, feature flag, +> registration helper, test, or comment. Ruling landed the same day: +> E-PLANNING-MIGRATES-TO-LOCO-R2IL-DATAFUSION-IS-GRACE-PERIOD-1 (closes the one seam left open in §4). + +## 1. Verdict + +`policy_hash_v1` is the last visible piece of an execution model that never reached production. +The DataFusion policy stack is test-only; no `add_optimizer_rule`/`add_analyzer_rule` anywhere; +the only live query surface (Python bindings) has no policy step; RBAC's canonical successor is +transport-only; lifecycle is SoA-owned as hypothesised. **D-OIF-1 = A (SUPERSEDED / REMOVE).** + +## 2. Census + +| old obligation | current owner | production evidence | action | +|---|---|---|---| +| `policy_hash_v1` / `NotYetWiredHashUdf` | none | `policy.rs:279-340`; only via `mask_expr` (`:137`), reached only from tests `:600-828`; no `register_udf` of that name | REMOVE (VACANCY) | +| `RedactionMode::*` | none | `policy.rs:71`; every construction in `mod tests` | REMOVE with module | +| `ColumnMaskRewriter` / `ColumnMaskRegistry` / `PolicyRewriter` / `PolicyKind` | none | one impl, zero non-test callers; `unified_bridge.rs:12`, `lib.rs:114` mention it as future | REMOVE (SUPERSEDED by project-then-query) | +| `RowEncryptionPolicy` / `DifferentialPrivacyPolicy` (+registries) | none | `policy.rs:344-420` "stub, no executor yet", zero callers | REMOVE with module | +| `RlsRewriter` / `RlsPolicyRegistry` / `MembraneRegistry::with_rls` / `postgrest.rs` dispatcher stub | duplicates canonical `ClassRbac::row_scope` (`rbac.rs:166`, default `None`) | `rls.rs:284` only `mod tests`; `postgrest.rs:940-960` comment + `Err("not yet implemented")`; CI runs `--features query --lib` only | REGRADE grace-period duplicate; keep until `row_scope` enforcement proven | +| `register_vsa_udfs` | none | `vsa_udfs.rs:574`, `pub use` `lib.rs:69`, zero call sites | separate query-side card; grace | +| DataFusion optimizer/analyzer registration | none | zero calls repo-wide | nothing to remove | +| `ClassView` | contract `class_view.rs:946`; impls `RegistryClassView`, `WikidataClassView`, `OgarClassView` | tests, planner probes, OGAR doc-ir/render | RETAIN | +| `WideFieldMask` | contract `class_view.rs:243` (`intersect` `:385`) | `recipe_vocab`, `standing_mask`, `step_mask`, `selection`, `attention_facet`, OGAR `doc-ir::project::field_mask` (`project.rs:75`) | RETAIN | +| `ogar-rbac` | no such crate | machinery lives in `lance-graph-contract::rbac` + `lance-graph-rbac` + `lance-graph-ogar::rbac_impl` | REGRADE the name in docs | +| `ClassRbac` / `authorize()` / `OgarRbac` | canonical path, TRANSPORT ONLY | `authorize.rs:60` callers only tests; `rbac_impl.rs:64` "§6 follow-on"; `medcare_actor.rs:100` `// TODO` | RETAIN; enforcement = missing implementation | +| `effective_mask = classview_mask ∧ role_mask` | nowhere computed | `ClassRbac::field_mask` default `FieldMask::FULL`, narrow (`rbac.rs:176-184`); charter C1.4 retype not done | spec stands; implement on canonical path, never as UDF | +| `UnifiedBridge::authorize_{read,write,act}` | string-keyed `Policy` gate | `unified_bridge.rs:362-410`, own tests only | RETAIN, TRANSPORT ONLY | +| `KanbanColumn` / `KanbanMove` / `try_advance_phase` | SoA (`MailboxSoA`) | write `mailbox_soa.rs:953` via `soa_view.rs:311`; applied `cycle_driver.rs:560`; `emit_bootstrap_intent` (`owner_adapter.rs:92`, `cycle_driver.rs:726`) casts an INTENT, the seal applies | RETAIN; invariant holds | +| `KanbanActor` / `KanbanMsg` | deleted | `kanban_actor.rs:1-33` tombstone | — | +| baton / `CollapseGateEmission` / `emit()` | removed | prose only: `transaction/{interactive,bulk}.rs`, `episodic_edges.rs:259`, CLAUDE.md (annotated) | REGRADE prose as stale | +| `ActionState` (OGAR) | action semantics, `ogar-vocab/src/lib.rs:676` | wire state, not a `KanbanColumn` | RETAIN, distinct layer | +| `CommitHook` | prose only | no definition anywhere | VACANCY; regrade prose | +| `ogar-loco` / `ogar-r2il` | execution/reasoning | zero refs to kanban, RBAC, `try_advance_phase` | RETAIN; no impersonation | + +## 3. Heckhausen / Rubicon mapping (verified, no second controller) + +`Planning` = pre-decisional; `advance_on_gate` (`kanban.rs:206`) = the crossing, decided by the MUL +gate; `CognitiveWork` = committed action; `Evaluation` = post-actional; `Commit/Plan/Prune` = absorbing. +`emit_bootstrap_intent` casts an intent into `BatchWriter`; the seal applies it through +`try_advance_phase`. No actor decides a transition. + +## 4. Removal cone / retain / regrade + +- REMOVE (one PR): `crates/lance-graph-callcenter/src/policy.rs` entire; `pub mod policy` gate `lib.rs:114-124`; + `.claude/patterns.md:89` row; module-header `policy` feature note. +- RETAIN: `rls.rs` + `MembraneRegistry::with_rls` (grace; replacement `row_scope` unproven); `unified_bridge.rs`; + `lance-graph-rbac/src/authorize.rs`; `contract/src/rbac.rs`; `contract/src/class_view.rs`; `rbac_impl.rs`; + `datafusion_planner`, `sql_query`, `graph_table`, Python bindings (all grace). +- REGRADE: `unified_bridge.rs:12` + `super-domain-rbac-tenancy-v1.md` §3.9/§13.1 (stage 4 is + `ClassRbac::field_mask ∩ ClassView` → column list, never a rewrite rule); `postgrest.rs:940-960`; + baton prose; OGAR `CommitHook` prose. +- MISSING IMPLEMENTATION (canonical path, not this PR): `field_mask` retype to `WideFieldMask` (C1.4); + `authorize → {scope, mask}` (keystone §5 stage 2); the projection consumer — Lance reads / loco + programs take the authorized column list (the DataFusion variant of this seam is CLOSED by the + 2026-09-05 ruling); `PROBE-OGAR-RBAC-AUTHORIZE` step 5; `medcare_actor.rs:100`. + +## 5. Stale claims to correct in #1185 + +§2 (D-OIF-1) → retirement plan; §2.2 D-OIF-1-DEC (hash + key) moot; G1–G8 withdrawn; `PolicyHashUdf` / +`register_policy_udfs` deliverables dropped; STATUS_BOARD `D-OIF-1` / `D-OIF-1-DEC` → SUPERSEDED; +IDEAS.md `IDEA-POLICY-HASH-UDF` → Superseded (its "blocker is the UDF body" flip was itself wrong: the +blocker was the absence of any consumer); OGAR DISCOVERY-MAP "no impl ClassRbac under crates/" is true of +OGAR only. Tracer error caught: `emit_bootstrap_intent` exists (`owner_adapter.rs:92`). diff --git a/.claude/knowledge/observer-effect-tfpn-doctrine.md b/.claude/knowledge/observer-effect-tfpn-doctrine.md index 3e9584af3..100f9d8eb 100644 --- a/.claude/knowledge/observer-effect-tfpn-doctrine.md +++ b/.claude/knowledge/observer-effect-tfpn-doctrine.md @@ -164,3 +164,20 @@ Pre-registration and guards, all binding BEFORE any run: (version-gated hindsight-blind reads, the G-gate discipline); `crates/lance-graph-planner/src/temporal.rs` (`QueryReference::at`, rung admission, `deinterlace`). + +--- + +## 7. Status note (2026-09-05) — first measurement landed + +D-BLW-5 was resumed by operator ruling with the belief-arena reader and measured +(`crates/lance-graph-supervisor/tests/d_blw_5_observer.rs`; plan §12.9b; +E-BLW5-FIRST-MEASUREMENT-1). Every gate in §5 held. O4/O5 read SILENT at the κ floor while +the reader marginals saturated in proportion to the injected typicality — the effect the +doctrine names (§4 "S₁ tracking the injected rank") is visible in the marginals and +invisible to κ. Two amendments to the machinery this doctrine only sketched: (a) the rank +enters awareness as TYPICALITY (mass at rank) carried in the confidence of +`subject Inh prior` at frequency 1 — a frequency below 0.5 is discarded by the arena's +expectation-CHOICE (E-NARS-EXPECTATION-CHOICE-PREFERS-IGNORANCE-TO-A-CONFIDENT-NEGATIVE-1); +(b) the awareness-coupled reader must read the DERIVED layer — observed truth never moves +from testimony, by the arena's own ground-protection rule. The observer EFFECT stays +CONJECTURE beyond this corpus/instrument; the next instrument (D-BLW-5b) pins marginal floors. diff --git a/.claude/plans/cycle-loop-closure-driver-v1.md b/.claude/plans/cycle-loop-closure-driver-v1.md index 70fe1c8af..912137038 100644 --- a/.claude/plans/cycle-loop-closure-driver-v1.md +++ b/.claude/plans/cycle-loop-closure-driver-v1.md @@ -1509,6 +1509,39 @@ tracking injected values ⇒ the anchoring finding stands even if T is silent independent arms/cohorts/metrics at the same version never collide); recompute at a sealed key ERRORS, with can-fire + can-stay-silent tests. +### 12.9b D-BLW-5 FIRST MEASUREMENT (2026-09-05) — resumed by operator ruling, belief-arena reader + +**BUILT + RUN GREEN** as `crates/lance-graph-supervisor/tests/d_blw_5_observer.rs` +(`cycle-driver`; spec `.claude/board/exec-runs/d-blw-5-build-spec-main-thread.md`). +Payload = the shipped `shape₀ × rank₀` (`NestedBands::shape_rank` → `ShapeRankPayload`, +sealed in `RemeasureLedger` at V0), injected into each owner's `BeliefArena` as 16 shape +beliefs + one `subject Inh prior` per corpus subject at f = 1, c = typicality (the prior's +mass at the observed rank). Propagation = `rcr_abduce` + closure to a fixed point. Readers: +A = statement revised (contradiction > 0.05); B = another cohort subject inferred to carry +the verse's predicate at c ≥ 0.01, derived layer only, reserved terms excluded. + +| pre-registered rule | measured | +|---|---| +| O1 remeasure guard | barks on the sealed key; silent on V1 and on a sibling arm | +| O2 placebo (|Δκ(P)| < 0.10) | **0** — P (typicality 1/16) bit-identical V0→V1 | +| O3 null instrument | N bloom verdicts byte-identical, Hamming 0 (frozen by construction) | +| O4 observable (|Δκ(T)| ≥ 0.10) | Δκ(T) = −0.074 → **SILENT**, the honest null at this floor | +| O5 direction (d = Δκ(F+) − Δκ(F−), floor 0.10) | Δκ(F+) = 0 (shifted rank fell in an EMPTY bucket, typicality 0), Δκ(F−) = −0.087, d = +0.087 → **SILENT** | +| O6 firewall self-scan | no reader identifier before the marker; marker + `binary_association(` after | +| O7 exclusion (restated, spec §10) | 1266 derived beliefs on T carry a reserved term; 0 of B's 340 accepted beliefs do | +| CTRL idempotence | Δκ(CTRL) = 0 exactly; reader Hamming 0 | +| DROP | fires for F+ and N (both bit-identical) | + +**What the marginals show (reported, not gated):** rate_b 0.47 → 0.89 on T and 0.52 → +0.92 on F− (every non-contradicted verse became B-true; n00 → 0), φ −0.25 → −0.91 and +−0.13 → −0.79, in proportion to the injected typicality (0 → nothing, 0.0625 → nothing, +0.15 and 0.20 → saturation). κ barely moves because a saturating reader has no +covariance — **the pinned observable is insensitive to the effect on this reader**. Per the +pre-registration rule this run is NOT reclassified; D-BLW-5b pre-registers marginal floors +before its run. Dry runs 1–3 and their instrument fixes are on the record in +E-BLW5-FIRST-MEASUREMENT-1; the arena property they surfaced is +E-NARS-EXPECTATION-CHOICE-PREFERS-IGNORANCE-TO-A-CONFIDENT-NEGATIVE-1. + ### 12.10 PROBE-ARC-TORQUE family PROPOSED (2026-08-04, operator) — torque of an arc, translator stray, author bias **Status: PROPOSED / CONJECTURE throughout. Queued behind PROBE-IGNITION and diff --git a/CLAUDE.md b/CLAUDE.md index a3bd28357..bdec69359 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1014,6 +1014,16 @@ Architecture: (crewai-rust / n8n-rs = EVICTED 2026-06-21 — agent + workflow-DAG roles folded into ladybug-rs + the in-tree thinking-engine; no longer consumers) +> **⊘ OPERATOR RULING 2026-09-05 — planning migrates to `ogar-loco` / `ogar-r2il`; +> DataFusion is in GRACE PERIOD.** *"Every planning is in migration to ogar-loco and +> ogar-r2il, especially datafusion is out of the picture, what exists gets a grace +> period, nothing new will migrate to it."* Every DataFusion-hosted surface below +> (`datafusion_planner`, `sql_query`, Python `SessionContext`, `rls.rs`, `query`/ +> `query-lite`, the `datafusion-*` forward-stubs) is maintained, not extended; new +> behaviour lands in loco/r2il programs or on Lance directly. Board: +> E-PLANNING-MIGRATES-TO-LOCO-R2IL-DATAFUSION-IS-GRACE-PERIOD-1. Lance itself is +> unaffected (E-LANCE-IS-UPSTREAM-AUTHORITATIVE-1). + Dependency chain: ladybug-rs ──► lance-graph-contract (traits) in-tree ──► lance-graph-contract (planner / callcenter / smb-bridge; symbiont ⊘ DEPRECATED 2026-08-18, operator no-go) diff --git a/crates/lance-graph-supervisor/Cargo.toml b/crates/lance-graph-supervisor/Cargo.toml index e32b1a381..8758775ec 100644 --- a/crates/lance-graph-supervisor/Cargo.toml +++ b/crates/lance-graph-supervisor/Cargo.toml @@ -57,3 +57,7 @@ static_assertions = "1" # never ship in the compiled crate/runtime dep graph — this proves the # structural-owner contract against real code without adding a runtime dep. cognitive-shader-driver = { path = "../cognitive-shader-driver" } +# D-BLW-5 oracle ONLY (dev-dep; never production; jc is never modified and +# never fed its own output — doctrine §5.8). Pre-ratified in +# exec-runs/d-blw-5-design-main-thread.md (a). +jc = { path = "../jc" } diff --git a/crates/lance-graph-supervisor/tests/d_blw_5_observer.rs b/crates/lance-graph-supervisor/tests/d_blw_5_observer.rs new file mode 100644 index 000000000..d419d6f8a --- /dev/null +++ b/crates/lance-graph-supervisor/tests/d_blw_5_observer.rs @@ -0,0 +1,1689 @@ +//! D-BLW-5 — the observer-effect probe: does awareness reflect a measured +//! statistic fed back into it as shape × Prozentrang (never a raw scalar)? +//! +//! Spec (binding, in precedence order): `.claude/knowledge/observer-effect-tfpn-doctrine.md` +//! (doctrine) → `.claude/board/exec-runs/d-blw-5-design-main-thread.md` +//! (design (a)-(g)) → `.claude/board/exec-runs/d-blw-5-api-inventory-sonnet.md` +//! (exact signatures) → `.claude/board/exec-runs/d-blw-5-build-spec-main-thread.md` +//! (THIS build, final numbers). Scaffolding provenance: `d_ign_b_lenses.rs` +//! (GREEN) — the fleet/scan/cast/seal machinery is copied verbatim from +//! there, cited at each site, per the build spec §2. +//! +//! ## Two corrections the build spec recorded (carried here verbatim, C1/C2) +//! +//! C1. Readers A/B are injection-invariant ONLY if B reads the OBSERVED +//! layer; B is therefore pinned to the arena's DERIVED layer (rung >= 1, +//! default stamp) so the tactics' propagation actually moves it. +//! C2. The injected rank enters awareness as TYPICALITY — the prior's own +//! mass at the observed rank bucket — bound to every subject via a shared +//! reserved `prior` predicate, never as a raw bucket index. Two ranks with +//! equal mass are indistinguishable to awareness under this encoding — a +//! stated limitation (see the not-claimed block). +//! +//! ## Deviations from the build spec, stated here (no others) +//! +//! 1. **No `run_loop(..., on_sealed: &mut dyn FnMut(...))` higher-order +//! driver.** The build spec's §6 sketch has `run_loop` own both cycles +//! and hand measurement to a callback; but the O6 firewall (§7: nothing +//! with `kappa`/`binary_association(`/`fn reader_a`/`fn reader_b` (the O6 list) may +//! appear textually before the marker) and the injection step's need for +//! `&mut Mind` access (readers only need `&Mind`) mean the measurement +//! code and the injection code cannot both live inside one callback +//! signature without either violating O6 (defining the readers before +//! the marker) or granting the callback mutable fleet access it has no +//! textual right to (the design's own signature passes only shared +//! refs). This file instead makes `run_loop` a single-CYCLE mechanics +//! function (cast/scan/seal, returns `CycleOutcome`) defined BEFORE the +//! marker, called twice from the test body (which lives AFTER the +//! marker, so it is free to call `measure_cohort`/`binary_association` +//! between the two calls). O6's actual assertion — the literal +//! pre-marker text contains none of the forbidden identifiers — holds +//! either way; this is a narrower, compiling realization of the same +//! firewall, not a relaxation of it. +//! 2. **`inject` is a plain per-cohort writer, not fused with `reason`.** +//! The build spec's §5 code fence for `inject` ends before the sentence +//! "then `reason(&mut arena)`" — read literally that sentence is the +//! CALLER's next step (matching §6: "inject per cohort ..., reason() on +//! EVERY mind (CTRL included)" — one uniform reason() pass over every +//! mind, injected or not). `inject` here therefore only calls +//! `arena.observe` for the reserved belief family; the main test body +//! calls `reason` once per mind afterward, uniformly. +//! 3. **B′'s "≥2 distinct Wittgenstein games" fallback is reimplemented +//! locally**, not routed through `stance::stance_panel` (not in this +//! file's import list per build spec §2). `distinct_games_local` +//! reproduces the same games taxonomy `stance_panel`'s Wittgenstein arm +//! uses (`inh-subj`/`inh-obj`/`impl-cause`/`impl-effect`; this file's +//! `Mind` never produces rung-lifts, so the `rel-subj`/`rel-obj` games +//! are structurally absent here — a narrower but consistent subset), +//! over the SAME `arena`/`out` this file already built — no new arena, +//! no double-count. +//! +//! ## Not compiled, not run by this lane — orchestrator gates +//! +//! This file was written edit-only (no `cargo` of any kind). Every +//! signature cited was read from source in the same pass that wrote this +//! file — see the build tag-file, +//! `.claude/board/exec-runs/d-blw-5-build-sonnet.md`, for what could and +//! could not be verified. + +#[cfg(feature = "cycle-driver")] +mod d_blw_5_observer { + #![allow( + clippy::cast_possible_truncation, + clippy::cast_possible_wrap, + clippy::cast_sign_loss, + clippy::cast_precision_loss + )] + + use std::collections::{HashMap, HashSet}; + use std::sync::atomic::{AtomicU64, Ordering}; + use std::sync::Mutex; + + use cognitive_shader_driver::mailbox_soa::{MailboxSoA, WriteCell, WriteOutcome, WORDS_PER_FP}; + use jc::stats::{binary_association, fisher_2z, BinaryAssociation}; + use lance_graph_contract::cognitive_shader::MetaWord; + use lance_graph_contract::collapse_gate::MailboxId; + use lance_graph_contract::kanban::{ExecTarget, KanbanColumn}; + use lance_graph_contract::mul::i4_eval::gate_decision_i4; + use lance_graph_contract::qualia::QualiaI4_16D; + use lance_graph_contract::scheduler::DatasetVersion; + use lance_graph_contract::shape_rank::{ + RemeasureError, RemeasureKey, RemeasureLedger, ShapeRankPayload, SHAPE_BUCKETS, + }; + use lance_graph_contract::soa_view::MailboxSoaView; + use lance_graph_contract::thinking::ThinkingStyle; + use lance_graph_planner::batch_writer::BatchWriter; + use lance_graph_planner::ir::Arena; + use lance_graph_planner::nars::stance::{stream, Interner, ReadOut}; + use lance_graph_planner::nars::tactics::{rcr_abduce, Throttle}; + use lance_graph_planner::nars::{BeliefArena, CStmt, Copula, Stamp, TruthValue}; + use lance_graph_planner::nested_bands::{quantize_2z, NestedBandsBuilder}; + use lance_graph_planner::owner_adapter::emit_bootstrap_intent; + use lance_graph_planner::persist_sink::{ + CommitError, CommitOutcome, CycleFrame, CycleId, DetachedCycleBatch, FrameMeta, LandedSlot, + SweepSlot, WalSink, WriteFailed, + }; + use lance_graph_planner::strategy::style_strategy::StyleStrategy; + use lance_graph_planner::traits::{ + PlanContext, PlanInput, PlanStrategy, QueryFeatures, StrategyOutcome, + }; + use lance_graph_supervisor::cycle_driver::{ + run_cognitive_work_gated_over, run_cycle, shade_owner, CycleError, CycleOutcome, + }; + + // ── PRE-REGISTERED run shape (build spec §1-2) — fixed BEFORE any run. ── + + const FLEET_OWNERS: MailboxId = 40; + const ROWS_PER_OWNER: usize = 64; + const POPULATED_ROWS: usize = 48; + const CORPUS_VERSES: usize = FLEET_OWNERS as usize * POPULATED_ROWS; // 1920 + const SCOPE_LO: MailboxId = 0; + const SCOPE_HI: MailboxId = FLEET_OWNERS; // 0..40 + /// Backstop on `reason()`'s RCR+close fixed-point loop [pinned]. + const MAX_REASON_ROUNDS: usize = 16; + + const T_LO: MailboxId = 0; + const T_HI: MailboxId = 8; + const FP_LO: MailboxId = 8; + const FP_HI: MailboxId = 16; + const FM_LO: MailboxId = 16; + const FM_HI: MailboxId = 24; + const P_LO: MailboxId = 24; + const P_HI: MailboxId = 32; + const N_LO: MailboxId = 32; + const N_HI: MailboxId = 36; + const CTRL_LO: MailboxId = 36; + const CTRL_HI: MailboxId = 40; + + const TENANT_THRESHOLD: f32 = 1.0; + const FIRE_ENERGY: f32 = 2.0; + const TENANT_W_SLOT: u8 = 0; + const FIRING_ROWS: usize = 3; + /// Every owner armed z=1 (Analytical) — build spec §2. + const ARMED_Z: u8 = 1; + + /// Movement floor, pinned by the D-BLW-3 precedent doctrine cites in §7. + const MOVEMENT_FLOOR: f64 = 0.10; + /// The direction-test's own floor for `d = Δκ(FP) − Δκ(FM)` (§7 O5). + const DIRECTION_FLOOR: f64 = 0.10; + /// DROP threshold (§7). + const DROP_FLOOR: f64 = 0.01; + /// Reader B's confidence floor — [hand-tuned] per build spec §4: the + /// injected chain reaches c ≈ m·0.9·c_ab·0.9 with c_ab = 0.81m/(0.81m+1); + /// P's m = 1/16 gives 0.0024 (silent), m >= 0.13 gives >= 0.012 (fires). + const C_MIN: f32 = 0.01; + /// F+/F- rank eligibility band — [hand-tuned], the "never clipped, only + /// excluded" boundary (build spec §5). + const RANK_ELIGIBLE_LO: f32 = 0.05; + const RANK_ELIGIBLE_HI: f32 = 0.95; + /// F+/F- logit shift magnitude, opposite signs (build spec §5). + const LOGIT_SHIFT: f32 = 1.5; + /// The bucket-midpoint offset added to `rank_fraction()` before shifting + /// in logit space (build spec §5). + const BUCKET_MIDPOINT: f32 = 1.0 / 32.0; + /// N's bloom-verdict percentile floor — the D-BLW-3 pin (build spec §6). + const N_QUANTILE: f64 = 0.25; + + /// Permissive throttle (build spec §4) — `c_min=0`, unbounded budget, + /// unbounded hub in-degree. `Throttle::new` is not `const fn` + /// (`tactics.rs:126-135`), so this is a plain fn, not a `const`. + fn throttle() -> Throttle { + Throttle::new(0.0, 65_536, usize::MAX) + } + const MAX_PASSES: u32 = 64; + + type Tenant = MailboxSoA; + type Fleet = HashMap; + + // ── ThinkingStyle dispatch input — copied shape from `d_ign_b_lenses.rs` + // (provenance: `d_ign_b_lenses.rs:175-202`); every owner here is z=1. ── + + fn thinking_style_for(z: u8) -> ThinkingStyle { + match z { + 1 => ThinkingStyle::Analytical, + 2 => ThinkingStyle::Creative, + _ => ThinkingStyle::Reflective, + } + } + + fn style_vector_for(z: u8) -> Vec { + let mut v = vec![0.0f64; 23]; + match thinking_style_for(z) { + ThinkingStyle::Analytical => v[4] = 1.0, + ThinkingStyle::Creative => v[3] = 1.0, + _ => v[0] = 1.0, + } + v + } + + fn plan_context_for(z: u8) -> PlanContext { + PlanContext { + query: String::new(), + features: QueryFeatures::default(), + free_will_modifier: 1.0, + thinking_style: Some(style_vector_for(z)), + nars_hint: None, + witness: None, + } + } + + fn mantissa_of(owner: &Tenant) -> i8 { + owner.pending_count().min(7) as i8 + } + + /// Provenance: `d_ign_b_lenses.rs:210-212` (itself re-derived from + /// `cycle_driver.rs:1669`'s `#[cfg(test)]` fixture, not importable). + fn flow_qualia() -> QualiaI4_16D { + QualiaI4_16D(0).with(3, 4).with(14, 3).with(9, 4).with(1, 2) + } + + // ── corpus + bloom-plane seeding — copied from `d_ign_b_lenses.rs`. ───── + + const BLOOM_K: usize = 4; + + fn fnv1a(bytes: &[u8], seed: u64) -> u64 { + let mut h = 0xcbf2_9ce4_8422_2325_u64 ^ seed.wrapping_mul(0x100_0000_01b3); + for &c in bytes { + h ^= u64::from(c); + h = h.wrapping_mul(0x100_0000_01b3); + } + h + } + + fn bloom_add(plane: &mut [u64], token: &str, salt: u64) { + for k in 0..BLOOM_K { + let h = fnv1a( + token.as_bytes(), + salt ^ (k as u64).wrapping_mul(0x9E37_79B9), + ); + let bit = (h % (WORDS_PER_FP as u64 * 64)) as usize; + plane[bit / 64] |= 1u64 << (bit % 64); + } + } + + fn tokens(text: &str) -> impl Iterator + '_ { + text.split(|c: char| !c.is_ascii_alphanumeric()) + .filter(|t| t.len() >= 2) + .map(str::to_ascii_lowercase) + } + + fn encode_plane(text: &str, salt: u64) -> Vec { + let mut plane = vec![0u64; WORDS_PER_FP]; + for t in tokens(text) { + bloom_add(&mut plane, &t, salt); + } + plane + } + + /// Nonsense syllables the synthetic corpus builds its clause predicates + /// from — copied from `d_ign_b_lenses.rs`'s `SYNTH_STEMS`/`synth_term` + /// (provenance: `d_ign_b_lenses.rs:273-282`), which cites the reasons a + /// `{stem}{window:02}{n:02}` shape collides with no catalogue this + /// machine consults. + const SYNTH_STEMS: [&str; 8] = ["vor", "lan", "tik", "mez", "qor", "sil", "dun", "fex"]; + + fn synth_term(window: usize, n: usize) -> String { + format!( + "{}{:02}{:02}", + SYNTH_STEMS[n % SYNTH_STEMS.len()], + window, + n + ) + } + + /// `sub{w:02}{i:02}` — 7 chars, alphanumeric, no catalogued pronoun/verb + /// morphology (build spec §3). A bare content word with no preceding + /// pronoun becomes the clause subject (`stance.rs` "Bare content word: + /// subject anchoring"). + fn subj(window: usize, i: usize) -> String { + format!("sub{window:02}{i:02}") + } + + /// One owner-slice's worth (`POPULATED_ROWS` verses) of the + /// deterministic synthetic corpus (build spec §3). Copula "was" arms + /// the predicate; "was not" negates. + /// Per-window shape bits. Dry run 1 used `w % {2,3,4}`, which repeats every + /// 12 windows: the 40 owners' phi values collapsed onto 12 atoms and the + /// 16-bucket prior had empty buckets between them (T's pooled statistic + /// landed in one: typicality 0 by construction). A splitmix64 fold of `w` + /// gives every window its own counts — same ranges, no period. + fn window_bits(w: usize) -> u64 { + let mut z = (w as u64).wrapping_add(0x9E37_79B9_7F4A_7C15); + z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); + z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); + z ^ (z >> 31) + } + + fn window(w: usize) -> Vec { + let bits = window_bits(w); + let n_subj = 5 + (bits % 4) as usize; // 5..8 + let contra_n = 2 + ((bits >> 2) % 3) as usize; // 2..4 + let mut out: Vec = Vec::with_capacity(POPULATED_ROWS); + + // shared: s0,s1 share T(0); s2,s3 share T(1) on even windows. + out.push(format!("{} was {}.", subj(w, 0), synth_term(w, 0))); + out.push(format!("{} was {}.", subj(w, 1), synth_term(w, 0))); + if (bits >> 4) & 1 == 0 { + out.push(format!("{} was {}.", subj(w, 2), synth_term(w, 1))); + out.push(format!("{} was {}.", subj(w, 3), synth_term(w, 1))); + } + + // own: disjoint `n` namespace 10+10*i+j, per subject. + for i in 0..n_subj { + let j_count = 2 + ((bits >> (8 + 2 * i)) % 3) as usize; + for j in 0..j_count { + out.push(format!( + "{} was {}.", + subj(w, i), + synth_term(w, 10 + 10 * i + j) + )); + } + } + + // contra: affirm all, then negate all (later — revision -> contradiction 0.85). + for i in 0..contra_n { + out.push(format!("{} was {}.", subj(w, i), synth_term(w, 80 + i))); + } + for i in 0..contra_n { + out.push(format!("{} was not {}.", subj(w, i), synth_term(w, 80 + i))); + } + + assert!( + out.len() <= POPULATED_ROWS, + "window {w} over-filled: {} > {POPULATED_ROWS} (max is 4 + 8*4 + 8 = 44)", + out.len() + ); + let mut j = out.len(); + while out.len() < POPULATED_ROWS { + out.push(format!( + "{} was {}.", + subj(w, j % n_subj), + synth_term(w, 100 + j) + )); + j += 1; + } + out + } + + fn synthetic_corpus() -> Vec { + (0..FLEET_OWNERS as usize).flat_map(window).collect() + } + + /// Owner `owner_idx`'s own text slice — the same slice `build_owner` + /// bloom-seeds from and `labelled_verses` reads (F1: one text source, + /// never row-byte decoding). Provenance: `d_ign_b_lenses.rs:551-554`. + fn owner_verses(all: &[String], owner_idx: MailboxId) -> &[String] { + let lo = owner_idx as usize * POPULATED_ROWS; + &all[lo..lo + POPULATED_ROWS] + } + + /// `(label, text)` pairs `stance::stream` wants. Label format + /// `"kjv:{global_index:05}"` (build spec's carried label convention). + /// Provenance: `d_ign_b_lenses.rs:559-566`. + fn labelled_verses(all: &[String], owner_idx: MailboxId) -> Vec<(String, String)> { + let lo = owner_idx as usize * POPULATED_ROWS; + all[lo..lo + POPULATED_ROWS] + .iter() + .enumerate() + .map(|(i, text)| (format!("kjv:{:05}", lo + i), text.clone())) + .collect() + } + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + struct RowSpanDescriptor { + row_lo: u32, + row_hi: u32, + cycle: u32, + } + + impl RowSpanDescriptor { + fn to_le_bytes(self) -> [u8; 12] { + let mut out = [0u8; 12]; + out[0..4].copy_from_slice(&self.row_lo.to_le_bytes()); + out[4..8].copy_from_slice(&self.row_hi.to_le_bytes()); + out[8..12].copy_from_slice(&self.cycle.to_le_bytes()); + out + } + } + + fn row_span_payload(owner: &Tenant) -> Vec { + RowSpanDescriptor { + row_lo: 0, + row_hi: owner.populated() as u32, + cycle: owner.cycle(), + } + .to_le_bytes() + .to_vec() + } + + // ── the WAL seam (in-process; NOT durability) — copied from + // `d_ign_b_lenses.rs`'s `MemWal`. ──────────────────────────────────────── + + struct SealedCycle { + frame: CycleFrame, + version: DatasetVersion, + batch_hash: u64, + landings: Vec, + } + + struct MemWal { + sealed: Mutex>, + next_version: AtomicU64, + #[allow(dead_code)] + wal_writes: AtomicU64, + } + + impl MemWal { + fn new() -> Self { + Self { + sealed: Mutex::new(Vec::new()), + next_version: AtomicU64::new(1), + wal_writes: AtomicU64::new(0), + } + } + fn head(&self) -> DatasetVersion { + self.sealed + .lock() + .expect("MemWal poisoned") + .last() + .map_or(DatasetVersion(0), |s| s.version) + } + } + + impl WalSink for MemWal { + async fn commit_cycle( + &mut self, + batch: DetachedCycleBatch, + ) -> Result { + let mut sealed = self.sealed.lock().expect("MemWal poisoned"); + let head = sealed.last().map_or(DatasetVersion(0), |s| s.version); + if let Some(rec) = sealed.iter().find(|s| s.frame.cycle == batch.frame.cycle) { + return if rec.batch_hash == batch.batch_hash { + Ok(CommitOutcome::Reconciled { + current_head: head, + cycle: batch.frame.cycle, + batch_hash: batch.batch_hash, + }) + } else { + Err(CommitError::HashConflict { + cycle: batch.frame.cycle, + stored_hash: rec.batch_hash, + offered_hash: batch.batch_hash, + }) + }; + } + if batch.frame.base_version != head { + return Err(CommitError::Fenced { current_head: head }); + } + self.wal_writes.fetch_add(1, Ordering::SeqCst); + let version = DatasetVersion(self.next_version.fetch_add(1, Ordering::SeqCst)); + let (cycle, batch_hash) = (batch.frame.cycle, batch.batch_hash); + sealed.push(SealedCycle { + frame: batch.frame, + version, + batch_hash, + landings: batch.landings, + }); + Ok(CommitOutcome::Committed { + version, + cycle, + batch_hash, + }) + } + + async fn scan_sealed( + &self, + after_cycle: Option, + ) -> Result, WriteFailed> { + Ok(self + .sealed + .lock() + .expect("MemWal poisoned") + .iter() + .filter(|s| after_cycle.is_none_or(|c| s.frame.cycle > c)) + .flat_map(|s| { + s.landings.iter().map(|slot| LandedSlot { + cycle: s.frame.cycle, + slot: SweepSlot { + payload: Vec::new(), + ..slot.clone() + }, + }) + }) + .collect()) + } + + async fn timeline(&self) -> Result, WriteFailed> { + Ok(self + .sealed + .lock() + .expect("MemWal poisoned") + .iter() + .map(|s| FrameMeta { + cycle: s.frame.cycle, + base_version: s.frame.base_version, + batch_hash: s.batch_hash, + }) + .collect()) + } + } + + // ── fleet construction ────────────────────────────────────────────────── + + fn build_owner( + id: MailboxId, + verses: &[String], + content_salt: u64, + armed: u8, + qualia: QualiaI4_16D, + firing_rows: usize, + ) -> Tenant { + let mut owner: Tenant = MailboxSoA::new(id, TENANT_W_SLOT, TENANT_THRESHOLD); + let cycle = owner.cycle(); + let meta = MetaWord::new(armed, 0, 0, 0, 0); + for (row, text) in verses.iter().enumerate() { + let content = encode_plane(text, content_salt); + let cell = WriteCell { + content: Some(content.as_slice()), + qualia: Some(qualia), + meta: Some(meta), + entity_type: Some((row % 251) as u16), + temporal: Some(row as u64), + ..WriteCell::default() + }; + let outcome = owner.write_row(row, cycle, &cell); + assert_eq!( + outcome, + WriteOutcome::Accepted, + "seeding row {row} of owner {id} must be accepted" + ); + } + owner.set_populated(verses.len()); + owner.tick(); + for r in 0..firing_rows { + owner.energy[r] = FIRE_ENERGY; + } + owner + } + + fn build_fleet(corpus: &[String]) -> Fleet { + let mut fleet = Fleet::new(); + for id in SCOPE_LO..SCOPE_HI { + fleet.insert( + id, + build_owner( + id, + owner_verses(corpus, id), + u64::from(id), + ARMED_Z, + flow_qualia(), + FIRING_ROWS, + ), + ); + } + fleet + } + + // ── the scan — copied from `d_ign_b_lenses.rs`. ───────────────────────── + + #[derive(Default)] + struct ScanResult { + planning: Vec, + cognitive: Vec, + evaluation: Vec, + absorbed: Vec, + missing: usize, + } + + fn scan_board(fleet: &Fleet, ids: impl IntoIterator) -> ScanResult { + let mut r = ScanResult::default(); + for id in ids { + match fleet.get(&id) { + None => r.missing += 1, + Some(owner) => match owner.phase() { + KanbanColumn::Planning => r.planning.push(id), + KanbanColumn::CognitiveWork => r.cognitive.push(id), + KanbanColumn::Evaluation => r.evaluation.push(id), + KanbanColumn::Commit | KanbanColumn::Plan | KanbanColumn::Prune => { + r.absorbed.push(id); + } + }, + } + } + r + } + + struct ColumnPassOutcome { + cast: usize, + } + + fn column_pass( + fleet: &Fleet, + ids: &[MailboxId], + writer: &mut BatchWriter>, + mut think: impl FnMut(&Tenant) -> Option<(StrategyOutcome, Vec)>, + ) -> ColumnPassOutcome { + let mut cast = 0usize; + for &id in ids { + let Some(owner) = fleet.get(&id) else { + continue; + }; + if let Some((outcome, payload)) = think(owner) { + if emit_bootstrap_intent( + &outcome, + owner.mailbox_id(), + owner.current_cycle(), + writer, + payload, + ) + .is_some() + { + cast += 1; + } + } + } + ColumnPassOutcome { cast } + } + + /// Provenance: `d_ign_b_lenses.rs:738-772`, unchanged. + fn plan_or_evaluate_think(owner: &Tenant) -> Option<(StrategyOutcome, Vec)> { + let armed = owner.meta_at(0).thinking(); + if armed == 0 { + return None; + } + let qualia = owner.qualia_at(0); + let mantissa = mantissa_of(owner); + let gate = gate_decision_i4(&qualia, mantissa); + let target = owner.phase().advance_on_gate(&gate)?; + + let mut arena = Arena::new(); + let plan_out = StyleStrategy + .plan( + PlanInput { + plan: None, + context: plan_context_for(armed), + outcome: None, + }, + &mut arena, + ) + .expect("StyleStrategy::plan never errors over this probe's fixed recipe substrate"); + let style_outcome = plan_out + .outcome + .expect("StyleStrategy always surfaces a StrategyOutcome"); + let style_move = style_outcome + .intended_move + .expect("StyleStrategy always intends the Planning -> CognitiveWork crossing"); + + let outcome = if style_move.to == target { + style_outcome + } else { + shade_owner(owner, &qualia, mantissa, style_outcome.reliability)? + }; + Some((outcome, row_span_payload(owner))) + } + + // ── the owner's mind, the reasoning pass, injection (build spec §4-5). ── + + struct Mind { + arena: BeliefArena, + intern: Interner, + out: ReadOut, + /// Distinct `p.stmt.s` over `out.provenance`, sorted. + subjects: Vec, + /// Empty until injection; the reserved-prior/band terms afterward. + reserved: HashSet, + } + + /// `reason` is the ONLY propagation channel and runs at BOTH versions + /// (V0 pre-injection, V1 post-injection) for EVERY owner including + /// CTRL — so V1-V0 on CTRL measures pass idempotence, not injection + /// (build spec §4). + /// Bounded RCR + closure to a FIXED POINT. Dry run 1 (2026-09-05, recorded in + /// the board entry) showed a single RCR+close round is NOT idempotent: the + /// derived layer feeds RCR new premises, so a second `reason()` on the + /// un-injected CTRL cohort moved reader B (Δκ(CTRL) = −0.0028). A pass that + /// runs at both V0 and V1 must be a fixed point, or V1−V0 measures the pass. + fn reason(arena: &mut BeliefArena) { + let throttle = throttle(); + for _ in 0..MAX_REASON_ROUNDS { + let frontier = rcr_abduce(arena, &throttle); + let mut admitted = 0usize; + for c in frontier.candidates { + if arena.admit_derived(c.stmt, c.truth, &c.premises, c.rung) { + admitted += 1; + } + } + arena.close_transitive(MAX_PASSES); + if admitted == 0 { + return; + } + } + } + + fn build_mind(verses: &[(String, String)]) -> Mind { + let mut arena = BeliefArena::new(); + let mut intern = Interner::new(); + let mut out = ReadOut::default(); + stream(verses, &mut arena, &mut intern, &mut out, false); + reason(&mut arena); + let mut subjects: Vec = out.provenance.iter().map(|p| p.stmt.s).collect(); + subjects.sort_unstable(); + subjects.dedup(); + Mind { + arena, + intern, + out, + subjects, + reserved: HashSet::new(), + } + } + + /// Injects the reserved belief family described by `payload` into + /// `mind`'s arena (build spec §5): 16 bucket-beliefs (`prior Inh + /// band_k`, truth = the shape's mass fraction) + one typicality belief + /// per subject (`subject Inh prior`, truth = the prior's own mass + /// fraction AT the observed rank — C2's typicality encoding). Does NOT + /// call `reason` itself — see module-doc deviation 2; the caller runs + /// one uniform `reason` pass over every mind afterward. + fn inject(mind: &mut Mind, payload: &ShapeRankPayload) { + // Stamp::source folds `id % 64`; 63 is reserved for this probe's + // injected family, so injected statements are always NEW admissions + // (Admitted, not routed through the S4 overlap guard) regardless of + // which observation-source ids the corpus already used. + let reserved_stamp = Stamp::source(63); + let prior = mind.intern.id("blw5:prior"); + let mut bands = Vec::with_capacity(SHAPE_BUCKETS); + for k in 0..SHAPE_BUCKETS { + bands.push(mind.intern.id(&format!("blw5:band:{k:02}"))); + } + let mass = payload.mass() as f32; + for (k, &band_id) in bands.iter().enumerate() { + let stmt = CStmt { + s: prior, + cop: Copula::Inh, + p: band_id, + }; + let f = payload.shape[k] as f32 / mass; + mind.arena + .observe(stmt, TruthValue::new(f, 0.9), reserved_stamp); + } + let typ = payload.shape[payload.rank as usize] as f32 / mass; + // C2 AMENDED (dry run 2, 2026-09-05, recorded in the board entry): + // typicality rides in the CONFIDENCE of `subject Inh prior`, at + // frequency 1. The spec's first encoding (frequency = typicality, + // c = 0.9) put f below 0.5, which NARS reads as a confident NEGATION; + // `admit_derived`'s expectation-CHOICE then preferred a vacuous + // closure path (c ≈ 3.5e-11, expectation ≈ 0.5) over the confident + // negative (expectation 0.462) — the arena discarded the payload in + // favour of ignorance. With f = 1 the expectation is monotone in c + // and the injected link survives CHOICE. C_MIN and every floor are + // unchanged. + let subjects = mind.subjects.clone(); + for s in subjects { + let stmt = CStmt { + s, + cop: Copula::Inh, + p: prior, + }; + mind.arena + .observe(stmt, TruthValue::new(1.0, typ), reserved_stamp); + } + mind.reserved.insert(prior); + mind.reserved.extend(bands); + } + + /// One cycle's mechanics: scan the board over `SCOPE_LO..SCOPE_HI`, + /// plan/cognitive/evaluate columns, seal via `run_cycle`. Mirrors + /// `d_ign_b_lenses.rs`'s main loop body (mechanics unchanged), minus the + /// lens capture (this probe reads Mind arenas directly, never through + /// the SoA rows — the SoA loop only supplies the sealed version, per + /// build spec §6). Defined BEFORE the O6 marker: contains none of + /// `"fn reader_a"`, `"fn reader_b"`, `"binary_association("`, `"kappa"` (the O6 list). + async fn run_cycle_mechanics( + cycle: u32, + fleet: &mut Fleet, + sink: &mut MemWal, + writer: &mut BatchWriter>, + position_base: &mut u64, + watermarks: &mut HashMap>, + ) -> CycleOutcome { + let scan = scan_board(fleet, SCOPE_LO..SCOPE_HI); + eprintln!( + "d_blw_5 scan @c{cycle}: planning={} cognitive={} evaluation={} absorbed={} missing={}", + scan.planning.len(), + scan.cognitive.len(), + scan.evaluation.len(), + scan.absorbed.len(), + scan.missing + ); + assert_eq!(scan.missing, 0, "every owner in SCOPE is inserted"); + + let planning_outcome = column_pass(fleet, &scan.planning, writer, plan_or_evaluate_think); + let cognitive_outcome = + run_cognitive_work_gated_over(fleet, &scan.cognitive, writer, |owner| { + let armed = owner.meta_at(0).thinking(); + if armed == 0 { + return None; + } + let style = thinking_style_for(armed); + let ctx = plan_context_for(armed); + let qualia = owner.qualia_at(0); + let mantissa = mantissa_of(owner); + let reliability = StyleStrategy::reliability_for(style, &ctx); + Some((qualia, mantissa, reliability, row_span_payload(owner))) + }); + let evaluation_outcome = + column_pass(fleet, &scan.evaluation, writer, plan_or_evaluate_think); + + let total_casts = planning_outcome.cast + cognitive_outcome.cast + evaluation_outcome.cast; + eprintln!("d_blw_5 @c{cycle}: {total_casts} casts staged"); + + let base_version = sink.head(); + let outcome = match run_cycle( + sink, + fleet, + writer, + CycleFrame::new(CycleId(u64::from(cycle)), base_version), + *position_base, + watermarks, + u64::from, + ) + .await + { + Ok(o) => o, + Err(CycleError::Seal(_)) => { + panic!("d_blw_5 @c{cycle}: unexpected seal failure (MemWal never injects one)") + } + Err(CycleError::Apply { cause, .. }) => { + panic!("d_blw_5 @c{cycle}: unexpected apply failure: {cause}") + } + }; + *position_base = (*position_base).max(outcome.sealed.next_position_base); + for t in &outcome.sealed.transitions { + if t.mv.from == KanbanColumn::Planning && t.mv.to == KanbanColumn::CognitiveWork { + assert_eq!( + t.mv.exec, + ExecTarget::Elixir, + "Planning->CognitiveWork must be the STYLE's Elixir mint" + ); + } + } + outcome + } + + // ── N's awareness-free bloom criterion (build spec §6, cites + // `blw_fusion.rs:314-375`) — defined BEFORE the marker: uses none of + // the forbidden identifiers. ───────────────────────────────────────── + + fn score_row(owner: &Tenant, row: usize, seed: &[u64]) -> u32 { + owner + .content_row(row) + .iter() + .zip(seed) + .map(|(w, s)| (w & s).count_ones()) + .sum() + } + + /// verdict[row] = true iff `score_row` is in the top `1 - N_QUANTILE` + /// (the D-BLW-3 `q=0.25` pin: top quartile) of the owner's + /// `POPULATED_ROWS` rows, ties broken by ascending row index. + fn bloom_verdicts(owner: &Tenant, seed: &[u64]) -> Vec { + let mut scored: Vec<(u32, usize)> = (0..POPULATED_ROWS) + .map(|row| (score_row(owner, row, seed), row)) + .collect(); + scored.sort_by(|a, b| b.0.cmp(&a.0).then(a.1.cmp(&b.1))); + let n_pos = (POPULATED_ROWS as f64 * N_QUANTILE) as usize; + let mut verdict = vec![false; POPULATED_ROWS]; + for &(_, row) in scored.iter().take(n_pos) { + verdict[row] = true; + } + verdict + } + + // ── MEASUREMENT BLOCK (O6 marker) ── + + // ── readers A/B/B_shadow (build spec §4, C1's correction). ────────────── + + /// A — evidence, injection-invariant by construction. `∃ p ∈ + /// out.provenance: p.verse == v && arena.get(p.stmt).contradiction > 0.05`. + fn reader_a(mind: &Mind, verse: &str) -> bool { + mind.out.provenance.iter().any(|p| { + p.verse == verse + && mind + .arena + .get(p.stmt) + .is_some_and(|b| b.contradiction > 0.05) + }) + } + + /// The verse's FIRST provenance statement's `(s, p)`, if any. + fn first_stmt_for_verse(mind: &Mind, verse: &str) -> Option { + mind.out + .provenance + .iter() + .find(|p| p.verse == verse) + .map(|p| p.stmt) + } + + /// B — awareness-coupled "inferential corroboration" (C1's DERIVED-layer + /// pin): a rung>=1, default-stamp (derived) belief `b'.s Inh p` with + /// `b'.s` a distinct known subject, EXCLUDING the reserved injected + /// terms, at or above `C_MIN` confidence. + fn reader_b(mind: &Mind, verse: &str) -> bool { + let Some(stmt) = first_stmt_for_verse(mind, verse) else { + return false; + }; + mind.arena.entries().iter().any(|b| { + b.stmt.cop == Copula::Inh + && b.stmt.p == stmt.p + && b.stmt.s != stmt.s + && mind.subjects.contains(&b.stmt.s) + && !mind.reserved.contains(&b.stmt.s) + && b.stamp == Stamp::default() + && b.rung >= 1 + && b.truth.confidence >= C_MIN + }) + } + + /// Deviation 3: reproduces `stance_panel`'s Wittgenstein games taxonomy + /// (`stance.rs:512-529`) locally over this file's own `arena`/`out`, + /// restricted to the two games this `Mind` can ever produce + /// (`inh-subj`/`inh-obj`; `impl-cause`/`impl-effect` never fire — this + /// corpus emits no `because` cue). `rel-*` (rung-1 lifts) are absent by + /// construction: `stream` never sees an epistemic verb here. + fn distinct_games_local(mind: &Mind, subject: u16) -> usize { + let mut games: HashSet<&'static str> = HashSet::new(); + for b in mind.arena.entries() { + if b.stmt.cop == Copula::Inh && b.stamp != Stamp::default() { + if b.stmt.s == subject { + games.insert("inh-subj"); + } + if b.stmt.p == subject { + games.insert("inh-obj"); + } + } + } + for (_, cause, effect) in &mind.out.impls { + if *cause == subject { + games.insert("impl-cause"); + } + if *effect == subject { + games.insert("impl-effect"); + } + } + games.len() + } + + /// B′ — pre-registered FALLBACK, decided once at V0 on CTRL only, never + /// chosen after seeing T/FP/FM/P/N output. + fn reader_b_fallback(mind: &Mind, verse: &str) -> bool { + let Some(stmt) = first_stmt_for_verse(mind, verse) else { + return false; + }; + distinct_games_local(mind, stmt.s) >= 2 + } + + /// Runs (reader, verse) over every verse this owner's `Mind` was built + /// from, skipping verses with no provenance in BOTH vectors (printing + /// the skip count), yielding the two boolean vectors for + /// `binary_association`. + fn reader_vectors( + mind: &Mind, + verses: &[(String, String)], + use_fallback: bool, + ) -> (Vec, Vec, usize) { + let mut a = Vec::with_capacity(verses.len()); + let mut b = Vec::with_capacity(verses.len()); + let mut skipped = 0usize; + for (label, _) in verses { + if first_stmt_for_verse(mind, label).is_none() { + skipped += 1; + continue; + } + a.push(reader_a(mind, label)); + b.push(if use_fallback { + reader_b_fallback(mind, label) + } else { + reader_b(mind, label) + }); + } + (a, b, skipped) + } + + /// `S(cohort, version)` — pools every owner's reader vectors (owner + /// order ascending) and computes `binary_association`. `corpus` supplies + /// each owner's own labelled verses. + fn measure_cohort( + minds: &HashMap, + corpus: &[String], + ids: std::ops::Range, + use_fallback: bool, + ) -> Option { + let mut a_pool = Vec::new(); + let mut b_pool = Vec::new(); + let mut skipped_total = 0usize; + for id in ids { + let mind = minds + .get(&id) + .expect("mind must exist for every scoped owner"); + let verses = labelled_verses(corpus, id); + let (a, b, skipped) = reader_vectors(mind, &verses, use_fallback); + skipped_total += skipped; + a_pool.extend(a); + b_pool.extend(b); + } + eprintln!( + "d_blw_5.measure_cohort: {} verses skipped (no provenance) across the cohort", + skipped_total + ); + binary_association(&a_pool, &b_pool) + } + + // ── payload construction (build spec §5). ──────────────────────────── + + struct ArmPayloads { + t: Option, + fp: Option, + fm: Option, + p: Option, + n: Option, + } + + #[allow(clippy::too_many_lines)] + fn build_payloads( + minds: &HashMap, + corpus: &[String], + v0: DatasetVersion, + ledger: &mut RemeasureLedger, + use_fallback: bool, + ) -> ArmPayloads { + // Pool: phi_owner over ALL 40 owners (each owner as a cohort-of-one). + let mut pool: Vec = Vec::with_capacity(SCOPE_HI as usize); + for id in SCOPE_LO..SCOPE_HI { + if let Some(assoc) = measure_cohort(minds, corpus, id..id + 1, use_fallback) { + if let Some(phi) = assoc.phi { + pool.push(quantize_2z(fisher_2z(phi))); + } + } + } + assert!( + pool.len() >= 16, + "PRECONDITION: prior pool too thin ({} < 16) — a corpus defect, not a finding", + pool.len() + ); + let nb = NestedBandsBuilder::new(SHAPE_BUCKETS).calibrate_equal_width(&pool, v0.0); + eprintln!( + "d_blw_5.pool: {} owner phi values pooled; boundaries={:?}", + pool.len(), + nb.boundaries() + ); + + let mut cohort = |lo: MailboxId, + hi: MailboxId, + arm: u8, + label: &str| + -> Option { + let assoc = measure_cohort(minds, corpus, lo..hi, use_fallback)?; + let phi = assoc.phi?; + let obs = quantize_2z(fisher_2z(phi)); + let payload_true = nb.shape_rank(obs, v0.0); + eprintln!( + "d_blw_5.arm {label}: phi={phi:.4} obs={obs} shape={:?} rank={} prozentrang={:.4} version={}", + payload_true.shape, payload_true.rank, payload_true.prozentrang(), payload_true.version + ); + let key = RemeasureKey { + stat_id: 1, + arm, + cohort: lo, + metric: 1, + dataset_version: v0.0, + }; + match ledger.seal(key, payload_true) { + Ok(()) => {} + Err( + RemeasureError::AlreadySealed { .. } | RemeasureError::VersionMismatch { .. }, + ) => { + panic!("d_blw_5: unexpected ledger seal failure for arm {label} at V0") + } + } + Some(payload_true) + }; + + let t = cohort(T_LO, T_HI, 1, "T"); + let fp_true = cohort(FP_LO, FP_HI, 2, "FP"); + let fm_true = cohort(FM_LO, FM_HI, 3, "FM"); + let n = cohort(N_LO, N_HI, 5, "N"); + let p = { + // P: uniform shape over the SAME pool size, rank = 8 (median). + let mass = pool.len() as u64; + let q = mass / SHAPE_BUCKETS as u64; + let r = mass % SHAPE_BUCKETS as u64; + let mut shape = [0u64; SHAPE_BUCKETS]; + for (k, slot) in shape.iter_mut().enumerate() { + *slot = if (k as u64) < r { q + 1 } else { q }; + } + let payload = ShapeRankPayload::new(shape, 8, v0.0); + let key = RemeasureKey { + stat_id: 1, + arm: 4, + cohort: P_LO, + metric: 1, + dataset_version: v0.0, + }; + ledger + .seal(key, payload) + .expect("P's ledger seal must succeed at V0 (never sealed before)"); + eprintln!("d_blw_5.arm P: uniform shape={shape:?} rank=8 (zero-information envelope)"); + Some(payload) + }; + // F+/F- — shift the TRUE arm's own shape's rank in logit(rank_fraction) space. + let shift = |payload_true: Option, + sign: f32, + label: &str| + -> Option { + let payload_true = payload_true?; + let rf = payload_true.rank_fraction() + BUCKET_MIDPOINT; + if !(RANK_ELIGIBLE_LO..=RANK_ELIGIBLE_HI).contains(&rf) { + eprintln!( + "d_blw_5.arm {label}: EXCLUDED — rank_fraction {rf} outside eligibility band [{RANK_ELIGIBLE_LO}, {RANK_ELIGIBLE_HI}], never clipped" + ); + return None; + } + let l = (rf / (1.0 - rf)).ln() + sign * LOGIT_SHIFT; + let rf2 = 1.0 / (1.0 + (-l).exp()); + let rank2 = ((rf2 * SHAPE_BUCKETS as f32).floor() as u8).min((SHAPE_BUCKETS - 1) as u8); + let payload = ShapeRankPayload::new(payload_true.shape, rank2, v0.0); + eprintln!( + "d_blw_5.arm {label}: rf={rf:.4} shifted rank={rank2} (true rank={})", + payload_true.rank + ); + Some(payload) + }; + let fp = shift(fp_true, 1.0, "FP"); + let fm = shift(fm_true, -1.0, "FM"); + if let Some(payload) = fp { + let key = RemeasureKey { + stat_id: 1, + arm: 2, + cohort: FP_LO, + metric: 1, + dataset_version: v0.0, + }; + let _ = ledger.seal(key, payload); // may double-seal with the true-shape arm above; informational only + } + if let Some(payload) = fm { + let key = RemeasureKey { + stat_id: 1, + arm: 3, + cohort: FM_LO, + metric: 1, + dataset_version: v0.0, + }; + let _ = ledger.seal(key, payload); + } + + ArmPayloads { t, fp, fm, p, n } + } + + // ── O4/O5 delta helpers. ───────────────────────────────────────────── + + fn delta_kappa(s0: Option, s1: Option) -> Option { + match (s0.and_then(|a| a.kappa), s1.and_then(|a| a.kappa)) { + (Some(k0), Some(k1)) => Some(k1 - k0), + _ => None, + } + } + + // ── the main probe ─────────────────────────────────────────────────── + + #[tokio::test] + async fn d_blw_5_observer_effect_belief_arena() { + let corpus = synthetic_corpus(); + assert_eq!(corpus.len(), CORPUS_VERSES, "PRE-REGISTERED corpus size"); + println!( + "d_blw_5 corpus: deterministic synthetic ({} verses)", + corpus.len() + ); + + let mut fleet = build_fleet(&corpus); + let mut minds: HashMap = HashMap::new(); + for id in SCOPE_LO..SCOPE_HI { + minds.insert(id, build_mind(&labelled_verses(&corpus, id))); + } + + let mut sink = MemWal::new(); + let mut writer: BatchWriter> = BatchWriter::new(); + let mut position_base: u64 = 0; + let mut watermarks: HashMap> = HashMap::new(); + + // ── c=1: cast/scan/seal -> V0. ─────────────────────────────────── + let outcome_c1 = run_cycle_mechanics( + 1, + &mut fleet, + &mut sink, + &mut writer, + &mut position_base, + &mut watermarks, + ) + .await; + let v0 = sink.head(); + eprintln!("d_blw_5: V0 sealed = {:?}", v0); + let _ = outcome_c1; + + // ── Preconditions on CTRL at V0 (before deciding the B/B' fallback). ── + let ctrl_b_rate_v0; + { + let mut a_all = Vec::new(); + let mut b_all = Vec::new(); + for id in CTRL_LO..CTRL_HI { + let mind = minds.get(&id).expect("CTRL mind must exist"); + let verses = labelled_verses(&corpus, id); + let (a, b, _) = reader_vectors(mind, &verses, false); + a_all.extend(a); + b_all.extend(b); + } + let a_rate = a_all.iter().filter(|x| **x).count() as f64 / a_all.len().max(1) as f64; + let b_rate = b_all.iter().filter(|x| **x).count() as f64 / b_all.len().max(1) as f64; + ctrl_b_rate_v0 = b_rate; + eprintln!("d_blw_5.precondition: CTRL A-rate={a_rate:.4} B-rate={b_rate:.4} at V0"); + assert!( + a_rate > 0.0 && a_rate < 1.0, + "PRECONDITION: reader A is degenerate on CTRL at V0 (corpus defect, not a finding)" + ); + } + let use_fallback = !(ctrl_b_rate_v0 > 0.0 && ctrl_b_rate_v0 < 1.0); + if use_fallback { + eprintln!( + "d_blw_5: reader B degenerate on CTRL at V0 (rate={ctrl_b_rate_v0:.4}) — falling back to B' (Wittgenstein-games) for ALL cohorts at both versions, pre-registered per build spec §4" + ); + } + + let mut ledger = RemeasureLedger::new(); + let payloads_v0 = build_payloads(&minds, &corpus, v0, &mut ledger, use_fallback); + + // S(cohort, V0) for all six cohorts. + let s0_t = measure_cohort(&minds, &corpus, T_LO..T_HI, use_fallback); + let s0_fp = measure_cohort(&minds, &corpus, FP_LO..FP_HI, use_fallback); + let s0_fm = measure_cohort(&minds, &corpus, FM_LO..FM_HI, use_fallback); + let s0_p = measure_cohort(&minds, &corpus, P_LO..P_HI, use_fallback); + let s0_n = measure_cohort(&minds, &corpus, N_LO..N_HI, use_fallback); + let s0_ctrl = measure_cohort(&minds, &corpus, CTRL_LO..CTRL_HI, use_fallback); + for (label, assoc) in [ + ("T", s0_t), + ("FP", s0_fp), + ("FM", s0_fm), + ("P", s0_p), + ("N", s0_n), + ("CTRL", s0_ctrl), + ] { + print_association_table(&format!("S({label}, V0)"), assoc); + } + + let n_bloom_v0: HashMap> = (N_LO..N_HI) + .map(|id| { + let owner = fleet.get(&id).expect("N owner must exist"); + let verse0 = &owner_verses(&corpus, id)[0]; + let seed = encode_plane(verse0, u64::from(id)); + (id, bloom_verdicts(owner, &seed)) + }) + .collect(); + + // ── O1 can-fire: re-seal at T's V0 key must ERROR. ─────────────── + { + let t_payload = payloads_v0 + .t + .expect("T must be non-degenerate for O1's can-fire"); + let key = RemeasureKey { + stat_id: 1, + arm: 1, + cohort: T_LO, + metric: 1, + dataset_version: v0.0, + }; + let err = ledger.seal(key, t_payload).unwrap_err(); + assert!( + matches!(err, RemeasureError::AlreadySealed { .. }), + "O1 can-fire: a second seal at T's sealed V0 key must ERROR, got {err:?}" + ); + // can-stay-silent: a version-bumped payload at the same shape/rank + // passes (a fresh (id, scope, V+1) one-shot). + let v1_payload = ShapeRankPayload::new(t_payload.shape, t_payload.rank, v0.0 + 1); + let key_v1 = RemeasureKey { + stat_id: 1, + arm: 1, + cohort: T_LO, + metric: 1, + dataset_version: v0.0 + 1, + }; + assert!( + ledger.seal(key_v1, v1_payload).is_ok(), + "O1 can-stay-silent: a fresh (id, scope, V+1) one-shot must pass" + ); + let key_arm2 = RemeasureKey { + stat_id: 1, + arm: 2, + cohort: T_LO, + metric: 1, + dataset_version: v0.0, + }; + let arm2_payload = ShapeRankPayload::new(t_payload.shape, t_payload.rank, v0.0); + assert!( + ledger.seal(key_arm2, arm2_payload).is_ok(), + "O1 can-stay-silent: a different arm at the same (id, version) must pass" + ); + } + + // ── inject per cohort, then ONE uniform reason() pass over every + // mind (CTRL included). ────────────────────────────────────────── + if let Some(p) = payloads_v0.t { + for id in T_LO..T_HI { + inject(minds.get_mut(&id).expect("T mind"), &p); + } + } + if let Some(p) = payloads_v0.fp { + for id in FP_LO..FP_HI { + inject(minds.get_mut(&id).expect("FP mind"), &p); + } + } + if let Some(p) = payloads_v0.fm { + for id in FM_LO..FM_HI { + inject(minds.get_mut(&id).expect("FM mind"), &p); + } + } + if let Some(p) = payloads_v0.p { + for id in P_LO..P_HI { + inject(minds.get_mut(&id).expect("P mind"), &p); + } + } + if let Some(p) = payloads_v0.n { + for id in N_LO..N_HI { + inject(minds.get_mut(&id).expect("N mind"), &p); + } + } + // CTRL: no injection, but `reason` still runs — pass idempotence. + for mind in minds.values_mut() { + reason(&mut mind.arena); + } + + // ── c=2: cast/scan/seal -> V1. ──────────────────────────────────── + let outcome_c2 = run_cycle_mechanics( + 2, + &mut fleet, + &mut sink, + &mut writer, + &mut position_base, + &mut watermarks, + ) + .await; + let v1 = sink.head(); + eprintln!("d_blw_5: V1 sealed = {:?}", v1); + let _ = outcome_c2; + + let s1_t = measure_cohort(&minds, &corpus, T_LO..T_HI, use_fallback); + let s1_fp = measure_cohort(&minds, &corpus, FP_LO..FP_HI, use_fallback); + let s1_fm = measure_cohort(&minds, &corpus, FM_LO..FM_HI, use_fallback); + let s1_p = measure_cohort(&minds, &corpus, P_LO..P_HI, use_fallback); + let s1_n = measure_cohort(&minds, &corpus, N_LO..N_HI, use_fallback); + let s1_ctrl = measure_cohort(&minds, &corpus, CTRL_LO..CTRL_HI, use_fallback); + for (label, assoc) in [ + ("T", s1_t), + ("FP", s1_fp), + ("FM", s1_fm), + ("P", s1_p), + ("N", s1_n), + ("CTRL", s1_ctrl), + ] { + print_association_table(&format!("S({label}, V1)"), assoc); + } + + let n_bloom_v1: HashMap> = (N_LO..N_HI) + .map(|id| { + let owner = fleet.get(&id).expect("N owner must exist"); + let verse0 = &owner_verses(&corpus, id)[0]; + let seed = encode_plane(verse0, u64::from(id)); + (id, bloom_verdicts(owner, &seed)) + }) + .collect(); + + // ── O2 placebo. ─────────────────────────────────────────────────── + { + let dk_p = delta_kappa(s0_p, s1_p); + match dk_p { + Some(dk) => { + println!("O2 placebo: |Δκ(P)| = {}", dk.abs()); + assert!(dk.abs() < MOVEMENT_FLOOR, "O2: P must not move"); + } + None => println!("O2 placebo: DEGENERATE at V0 or V1 — reported, not asserted"), + } + for id in P_LO..P_HI { + let mind = minds.get(&id).expect("P mind"); + let prior = *mind + .reserved + .iter() + .find(|&&r| mind.intern.name(r) == "blw5:prior") + .expect("P injection must have landed a reserved prior"); + let band00 = mind + .reserved + .iter() + .find(|&&r| mind.intern.name(r) == "blw5:band:00") + .copied() + .expect("P injection must have landed band_00"); + assert!( + mind.arena + .get(CStmt { + s: prior, + cop: Copula::Inh, + p: band00 + }) + .is_some(), + "O2 twin: P's injection mechanics must have executed (band_00 present)" + ); + } + } + + // ── O3 null instrument. ─────────────────────────────────────────── + { + let mut total_hamming = 0usize; + for id in N_LO..N_HI { + let before = &n_bloom_v0[&id]; + let after = &n_bloom_v1[&id]; + let hamming = before.iter().zip(after).filter(|(a, b)| a != b).count(); + total_hamming += hamming; + assert_eq!( + before, after, + "O3: N's bloom verdicts must be byte-identical V0->V1 for owner {id}" + ); + } + println!( + "O3 null instrument: total Hamming = {total_hamming} (frozen by construction; pool drift 0 here, since N's rows never change)" + ); + } + + // ── O4 the observable. ───────────────────────────────────────────── + let dk_t = delta_kappa(s0_t, s1_t); + match dk_t { + Some(dk) => { + println!("O4 the observable: Δκ(T) = {dk}"); + if dk.abs() >= MOVEMENT_FLOOR { + println!("O4 FIRES — awareness reflects the statistic"); + } else { + println!("O4 SILENT — the honest null: awareness does not reflect this statistic (at floor {MOVEMENT_FLOOR})"); + } + } + None => println!("O4 SATURATED — reader B degenerate at V0 or V1"), + } + + // ── O5 direction. ────────────────────────────────────────────────── + let dk_fp = delta_kappa(s0_fp, s1_fp); + let dk_fm = delta_kappa(s0_fm, s1_fm); + match (dk_fp, dk_fm) { + (Some(dfp), Some(dfm)) => { + let d = dfp - dfm; + println!("O5 direction: Δκ(FP)={dfp} Δκ(FM)={dfm} d={d}"); + let classification = if d >= DIRECTION_FLOOR { + "ANCHORING (testimony-dominance, Goodhart realised)" + } else if d <= -DIRECTION_FLOOR { + "EVIDENCE-DOMINANCE" + } else if dfp.abs() >= DIRECTION_FLOOR && dfm.abs() >= DIRECTION_FLOOR { + "PERTURBATION (value-invariant)" + } else { + "SILENT" + }; + println!("O5 classification: {classification}"); + assert!( + !classification.is_empty(), + "O5: a classification must be produced" + ); + } + _ => println!( + "O5 direction: at least one of FP/FM is degenerate or excluded — no classification" + ), + } + + // ── O6 firewall self-scan. ───────────────────────────────────────── + { + const SRC: &str = include_str!("d_blw_5_observer.rs"); + let marker = "── MEASUREMENT BLOCK (O6 marker) ──"; + let marker_pos = SRC.find(marker).expect("O6 marker must exist in this file"); + let (before, after) = SRC.split_at(marker_pos); + let forbidden = ["fn reader_a", "fn reader_b", "binary_association(", "kappa"]; + for pat in forbidden { + let hit = before + .lines() + .any(|line| !line.contains("O6") && line.contains(pat)); + assert!( + !hit, + "O6 can-fire: forbidden pattern {pat:?} found before the measurement marker" + ); + } + assert!( + after.contains(marker), + "O6: post-marker half must contain the marker" + ); + assert!( + after.contains("binary_association("), + "O6: a scan that finds nothing is not evidence — post-marker half must call binary_association(" + ); + } + + // ── O7 exclusion is load-bearing — RESTATED (spec §10). The pinned + // twin compared B against a shadow reader without the reserved/ + // subjects exclusion; under RCR-only reasoning no reserved-SUBJECT + // belief ever acquires a corpus predicate, so the two readers were + // identical by construction on every arm (dry run 3) — an assertion + // that could not fire. What CAN fire: the payload propagated into + // the derived layer (reserved-term derived beliefs exist on T at V1), + // and none of the beliefs reader B accepted carries a reserved term. + { + let mut derived_with_reserved = 0usize; + let mut accepted_with_reserved = 0usize; + let mut b_positive = 0usize; + for id in T_LO..T_HI { + let mind = minds.get(&id).expect("T mind"); + derived_with_reserved += mind + .arena + .entries() + .iter() + .filter(|b| { + b.stamp == Stamp::default() + && (mind.reserved.contains(&b.stmt.s) + || mind.reserved.contains(&b.stmt.p)) + }) + .count(); + let verses = labelled_verses(&corpus, id); + for (label, _) in &verses { + let Some(stmt) = first_stmt_for_verse(mind, label) else { + continue; + }; + if !reader_b(mind, label) { + continue; + } + b_positive += 1; + // The beliefs B accepted for this verse: re-derive the + // acceptance set and count reserved terms in it. + accepted_with_reserved += mind + .arena + .entries() + .iter() + .filter(|b| { + b.stmt.cop == Copula::Inh + && b.stmt.p == stmt.p + && b.stmt.s != stmt.s + && mind.subjects.contains(&b.stmt.s) + && !mind.reserved.contains(&b.stmt.s) + && b.stamp == Stamp::default() + && b.rung >= 1 + && b.truth.confidence >= C_MIN + && (mind.reserved.contains(&b.stmt.s) + || mind.reserved.contains(&b.stmt.p)) + }) + .count(); + } + } + println!( + "O7 exclusion: derived beliefs carrying a reserved term on T at V1 = {derived_with_reserved}; \ + beliefs reader B accepted that carry a reserved term = {accepted_with_reserved}; B positives = {b_positive}" + ); + assert!( + derived_with_reserved > 0, + "O7 can-fire: the injected family must have propagated into T's derived layer at V1" + ); + assert_eq!( + accepted_with_reserved, 0, + "O7 can-stay-silent: reader B must accept no belief that carries a reserved term" + ); + assert!( + b_positive > 0, + "O7 can-stay-silent: the firewalled reader B must be non-empty (some positives) on T" + ); + } + + // ── CTRL — pass idempotence. ──────────────────────────────────────── + { + let dk_ctrl = delta_kappa(s0_ctrl, s1_ctrl); + match dk_ctrl { + Some(dk) => { + println!("CTRL: Δκ(CTRL) = {dk}"); + assert_eq!( + dk, 0.0, + "CTRL: pass idempotence requires Δκ(CTRL) == 0.0 exactly" + ); + } + None => { + println!("CTRL: DEGENERATE at V0 or V1 (both should read the same table twice)") + } + } + let mut total_hamming = 0usize; + for id in CTRL_LO..CTRL_HI { + let mind = minds.get(&id).expect("CTRL mind"); + let verses = labelled_verses(&corpus, id); + for (label, _) in &verses { + if first_stmt_for_verse(mind, label).is_none() { + continue; + } + // Re-reading the SAME (post-both-reason-passes) arena + // twice must be bit-identical — the reader Hamming. + let b1 = reader_b(mind, label); + let b2 = reader_b(mind, label); + if b1 != b2 { + total_hamming += 1; + } + } + } + println!("CTRL reader Hamming (repeat-read stability) = {total_hamming}"); + } + + // ── DROP. ────────────────────────────────────────────────────────── + { + let arms = [ + ("T", dk_t, s0_t, s1_t, T_LO..T_HI), + ("FP", dk_fp, s0_fp, s1_fp, FP_LO..FP_HI), + ("FM", dk_fm, s0_fm, s1_fm, FM_LO..FM_HI), + ("N", delta_kappa(s0_n, s1_n), s0_n, s1_n, N_LO..N_HI), + ]; + for (label, dk, _s0, _s1, range) in arms { + let Some(dk) = dk else { continue }; + let mut hamming = 0usize; + for id in range { + let mind = minds.get(&id).expect("arm mind"); + let verses = labelled_verses(&corpus, id); + for (l, _) in &verses { + if first_stmt_for_verse(mind, l).is_none() { + continue; + } + let b1 = reader_b(mind, l); + let b2 = reader_b(mind, l); + if b1 != b2 { + hamming += 1; + } + } + } + if dk.abs() < DROP_FLOOR && hamming == 0 { + println!("DROP fires for {label}"); + } + } + } + + // ── §8 output discipline: not-claimed block. ──────────────────────── + println!(); + println!("== D-BLW-5 — what this test does NOT claim =="); + println!("1. No validity of the observer effect beyond THIS corpus/instrument."); + println!( + "2. No parallelism claim (synchronous loop; fleet-level parallel stays A2-gated)." + ); + println!("3. No durability claim (MemWal is in-process, not persistence)."); + println!("4. No fusion verdict (this is D-BLW-3's first-order fusion's sibling, not its successor)."); + println!("5. No per-stance dispatch claim (this file never touches stance_panel)."); + println!("6. jc is untouched and one-way: it measures S0/S1, is never modified, never fed its own output."); + println!("7. The rank enters as TYPICALITY (C2): two ranks with equal mass are indistinguishable to awareness under this encoding."); + println!("8. N is frozen BY CONSTRUCTION (its rows never change); its pool-drift duty reads 0 here."); + println!("9. The synthetic corpus is symmetric by design; the V0 reader rates are exactly what the generator makes them."); + } + + /// C2 of the design's naming discipline: print the FULL `BinaryAssociation` + /// table, never a bare kappa. Provenance: `blw_fusion.rs:687-709`. + fn print_association_table(label: &str, assoc: Option) { + let Some(assoc) = assoc else { + println!(" {label}: DEGENERATE (no association — structurally unusable input)"); + return; + }; + let kappa_str = assoc.kappa.map_or_else( + || format!("undefined(p_e={:.4})", assoc.expected_agreement), + |k| format!("{k:.4}"), + ); + let phi_str = assoc + .phi + .map_or_else(|| "undefined(constant)".to_string(), |p| format!("{p:.4}")); + println!( + " {label}: n00={} n01={} n10={} n11={} | rate_a={:.4} rate_b={:.4} | p_o={:.4} p_e={:.4} | kappa={kappa_str} | phi={phi_str}", + assoc.n00, + assoc.n01, + assoc.n10, + assoc.n11, + assoc.positive_rate_a, + assoc.positive_rate_b, + assoc.observed_agreement, + assoc.expected_agreement, + ); + } +}