diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index 189322d6..63c3bd79 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,3 +1,47 @@ +## 2026-07-30 — E-64K-THOUGHTS-DONT-DO-QUORUM-PLASTICITY-BREATHES-FROM-TENSION-1 — operator ruling: the 64k parallel thoughts do NOT do quorum; inconsistency is plasticity's work; contradictions are the substrate's fuel; and the honesty line runs between EPISODIC and EPISTEMIC causality + +**Status:** OPERATOR-RULED (mid-W7-review, verbatim anchors below). **Confidence:** High as a direction ruling; consequences applied same-commit (the quorum-binder recommendation in `E-A-CHIP-BEARS-LOAD-OR-IT-IS-A-JOKE-1` is withdrawn in place, pre-merge). + +**The verbatim anchors, in arrival order:** +1. *"Quorum suggests we don't have mantissa to hold counterfactual. Our inconsistencies don't lie in quorum. We have 64k thoughts in parallel. They don't do quorum. That's plasticity doing its work."* +2. *"Contradictions is what our substrate thrives on."* +3. *"That's linguistic tension, magnitude of expressivity."* +4. *"Our NARS breathes from these tensions."* +5. *"We just need to be honest between episodic causality, or epistemic causality."* +6. *"Gadamer ist the first step, Kant Wittgenstein Nietzsche and Hegel the next."* + +**What this rules out.** The entropy-work audit had proposed a QUORUM BINDER — write `Locus::Quorum`/`Contradiction` pointers at ingest so grading follows pointers instead of rescanning Θ(N·k) (`TD-LENS-QUORUM-SCANS-THE-WHOLE-LENS`). Withdrawn: that reading treats slot 14 as a consensus mechanism, and the substrate has none. The i4 mantissa was never sized to hold counterfactual agreement state because agreement is not what reconciles the 64k threads — each thought adapts (plasticity tenant, NARS revision) rather than polling its peers. The tech-debt entry survives as SCAN-COST debt; its eventual fix must not smuggle in voting. + +**What this rules in — the slots' honest semantics.** `Quorum` = an OBSERVATION pointer (the window event whose reading matched mine — a fact about the past, episodic), never a mechanism. `Contradiction` = the PRESERVED dissenting peer (Staunen×Wisdom depth) — per the standing doctrine *"opinions are committed contradictions preserved, not resolved."* Contradiction is not an error state awaiting cleanup; it is the free-energy gradient the NARS engine breathes from — no tension, no surprise, no thinking (the shader "can't NOT think while surprise exists"). + +**Linguistic tension = magnitude of expressivity, now measured in one nibble.** W7's divergent chip (stored −7 vs recency's −4 on the interposition fixture) is the same quantity the Babel residual measures at lane scale (1 − amplitude = expressiveness escaping the coordinate system): the gap between what a cheap model can address and what the structure actually says. The chips worth placing sit exactly on these tensions — where the cheap reconstruction and the structural read disagree is where a stored bit carries irreplaceable information, and where they agree the verification (not the value) is the load. + +**The honesty line: episodic vs epistemic causality.** Episodic = what happened, in stream order (Lance versions, `temporal.rs`, "A preceded B"). Epistemic = what grounds belief (the warrant chain, "A is why B is held"). Recency IS episodic proximity deployed as an epistemic proxy — and W7's divergence gates measure precisely where that proxy lies (3:1: the episodically-nearest candidate is not the epistemic ground). The witness register's offsets are episodic ADDRESSES carrying epistemic MEANING; conflating the two axes is the category error this ruling fences. Same fence, corpus scale: `E-WHOLE-BOOK-REASONING-RUN-1`'s caveat — `is_a(god, light)` stores episodic adjacency wearing epistemic inheritance's label. + +**The hermeneutik ladder ordering (stone 3 of the operator's three Rosetta stones):** Gadamer (Horizontverschmelzung — the fusion, B4's circle-termination + B6's both-halves-measured) is the FIRST step, the entry to Erkenntnis; the four-stance panel (Kant a-priori / Wittgenstein Aspektsehen / Nietzsche genealogy / Hegel dialectic) is the NEXT — differentiated reads only possible after fusion. One corpus as crystal, thought as Lichtblitz reflected surface to surface, no surface holding the whole light. + +Cross-ref: `E-A-CHIP-BEARS-LOAD-OR-IT-IS-A-JOKE-1` (corrected in place, same PR), `E-FOUR-LIGHTS-ONE-CRYSTAL-1`, `E-WHOLE-BOOK-REASONING-RUN-1`, `TD-LENS-QUORUM-SCANS-THE-WHOLE-LENS` (re-scoped: scan-cost only), CLAUDE.md § "Meaning = AriGraph facts + resonance + magnitude" (contradictions preserved, not resolved). + +## 2026-07-30 — E-A-CHIP-BEARS-LOAD-OR-IT-IS-A-JOKE-1 — W7 ran: the `Antecedent` chip is placed by BINDING, not recency — proven where the cheap resolver stores a well-formed wrong nibble and the binder refuses to store at all; the operator's entropy-work ruling becomes the mechanical pull-test + +**Status:** SHIPPED (probe `lance-graph-planner/examples/probe_binding_not_heuristic.rs`, 4 gates + 4 unit tests green, clippy `-D warnings` clean, fmt clean). **Confidence:** High on every asserted gate (fixture-pinned); the resolvers are deliberately COARSE (tagger-level features, two rules) — this is the falsifier for the CHIP, not a coreference system. + +**The operator's ruling (verbatim anchors, now canon):** *"if you're on a ladder you should make an effort on behalf of the thought after you"*; *"a little bookkeeping only takes a better algorithm to make it redundant and make it appear as a joke to pretend it was thinking"*; the chips image — the world throws *"space stations full of chips throwing data hoping it sticks"*, we place **data points where the entropy work is load-bearing**. The mechanical form is the **pull-test**: pull the chip — if a cheap resolver reconstructs the same answer everywhere, it was decoration; if somewhere the cheap resolver produces a *different, wrong* answer, the chip carried bits no better algorithm recomputes. + +**What W7 measured (real KJV; gold TYPE-SEPARATED from resolver input — fixtures return `(Vec, gold pairs)` and `Tok` carries no gold field, so a resolver shortcut is a compile error, closing the honesty gap W6 left structurally, per codex/CodeRabbit review on the PR):** +- **B1 divergence (can-fire):** Gen 3:1 — the relative clause *"which the LORD God had made"* interposes; recency finds `god`@15 (d=−4, wrong), structural binding (relative-span skip + subject continuity) finds `serpent`@2 (d=−17, gold). The cheap resolver is wrong exactly where structure is doing work. +- **B2 escalate-not-clamp (the load-bearing REFUSAL):** the heuristic's wrong answer FITS the ±8 window — storable, well-formed, wrong. The binder escalates instead (nibble stays 0). **Knowing when NOT to place a chip is itself entropy work** — the joke-store would have been a syntactically perfect nibble pointing at the wrong god. +- **B3 stay-silent:** Gen 3:7 — both resolvers agree with gold on both `they`s (chips −5, −3, distinct, nonzero). The divergence gate does not fire on everything; a guard that always fires carries no information. +- **B4 pull-test + composition:** inner `they`@12 resolves its ultimate referent by following TWO stored nibbles — `they`@12 → `they`@9 → `them`@4 — via `resolves_to`, each chip a LOCAL warrant, no far verdict cached anywhere. Heuristic-only reconstruction fails on 3:1. **Der Gedanke als Lichtblitz, von verschiedenen Seiten reflektiert** — one flash, each chip a reflecting surface passing it on, no surface holding the whole light. + +**The warrant/verdict split is the general law this instantiates.** All 16 witness loci store i4 pointers-to-evidence, never cached conclusions. A warrant store cannot rot (the verdict is recomputed *through* it) and survives the better-algorithm test: even where a cheap resolver's VALUE coincides (3:7), the chip carries the *verification* — the next thought cannot know recency is trustworthy at 3:7 without running the structure that fails at 3:1. The chip stores the POINTER the verification selected; the answer is recomputed through it — the load it bears is the verification, never a cached guess. + +**The four receipts the operator anchored, one arc:** Horizontverschmelzung (B4 hermeneutic-circle termination + B6 both-halves-measured) is the jump to the intellectual tiers *auf der Schwelle zur Erkenntnis* — the Kant ablation showed the reader's category (knowing > seeing) is CONSTITUTIVE of the 3:7 crown; the Doppelspalt (Wittgenstein/Nietzsche/Hegel/Kant, four stances, one arena, invariant reflexive lift) — **ein Korpus als Quelle von Hermeneutik wird zum Kristall**; and W7 now places its chip exactly on that verge: the reflexive lift verse, bound by structure, refused where structure says refuse. + +**Remaining from the entropy-work audit — ⊘ CORRECTED same day, before merge:** this entry's first draft recommended "a quorum binder in W6/W7's shape" for the never-production-written `Quorum`/`Contradiction` slots. **The operator overruled the direction** (see `E-64K-THOUGHTS-DONT-DO-QUORUM-PLASTICITY-BREATHES-FROM-TENSION-1`, same day): the 64k parallel thoughts don't do quorum; inconsistency is plasticity's work; contradictions are fuel, preserved, never voted away. `TD-LENS-QUORUM-SCANS-THE-WHOLE-LENS` stays filed as scan-cost debt, but its fix is NOT consensus wiring. What survives of this paragraph: `PROBE-RUNG-ELIGIBILITY` (still CONJECTURE in `zero-copy-lens-law.md`) now has its pass/fail — the pull-test, per locus. + +Cross-ref: `E-EYES-OPENED-PRINTS-BLIND-1` (the reflexive signature this binds), `E-FOUR-LIGHTS-ONE-CRYSTAL-1` (the crystal/Doppelspalt), `E-WHOLE-BOOK-WAVE-1` (63.3% beyond ±5 — why escalation is the architecture, not a limitation), `.claude/knowledge/zero-copy-lens-law.md` (the rung-test row this starts promoting), task #60 (W7), `.claude/board/exec-runs/w7-binding-not-heuristic.md`. + ## 2026-07-29 — E-A-CHEAP-PROXY-GETS-PUBLISHED-AS-THE-QUANTITY-IT-RESEMBLES-1 — the doc that named the same-identifier-grep anti-pattern in §9 committed it in §5 **Status:** SHIPPED — first edition merged as #870 (`a50299c`); correction pass merged as **#871** (`f91eeb6`), both on `main`. **Confidence:** High — all four corrections re-measured; two changed the document's conclusions, and the `lance-table` number was produced by a probe that had to leave the workspace to be honest. diff --git a/.claude/board/TECH_DEBT.md b/.claude/board/TECH_DEBT.md index 6043d760..29d1b5d7 100644 --- a/.claude/board/TECH_DEBT.md +++ b/.claude/board/TECH_DEBT.md @@ -1,5 +1,41 @@ # Technical Debt Log — Open + Paid (double-entry, append-only) +## TD-WORKSPACE-FMT-DRIFT (2026-07-30) + +**Measured, not estimated.** `cargo fmt --all -- --check` at +`b9160f8` reports **1,094 hunks / 20,307 diff lines across 64 files in 9 +crates**: + +| crate | files | +|---|---| +| `lance-graph-ontology` | 55 | +| `bgz-tensor` | 2 | +| `causal-edge`, `ogar-emitter`, `ogar-encryption`, `ogar-from-ruff`, `ogar-render-askama`, `sigma-tier-router`, `surreal_container` | 1 each | + +Surfaced by a CodeRabbit finding on #874 asking for the workspace-wide sweep. +The sweep was **deferred, not skipped**, and the reason is the measurement: +attaching a 20k-line reformat of nine untouched crates to a 752-line additive +probe PR makes the diff unreviewable and lands pre-existing drift silently +under an unrelated title. #874's own file is fmt-clean (0 of the 1,094 hunks). + +**The real debt is upstream of the sweep**: `-p`-scoped `cargo fmt` is this +workspace's practice (per `CLAUDE.md` Build Commands and the +agent-cargo-hygiene rule's one-shared-`target/` discipline), so crates only get +formatted when someone edits them — drift accumulates in whatever is not being +worked on. `lance-graph-ontology` holding 86 % of it is the tell: a crate +nobody has touched recently. + +**Paying it** is one mechanical PR (`cargo fmt --all`, no behaviour change) — +best landed when no feature branch is mid-review, since it collides with +everything. Note it is NOT free to defer forever: every deferral makes the +eventual sweep collide with more in-flight work. + +**Companion blocker (separate, do not conflate):** +`clippy --all-targets --all-features` cannot pass at all — the `delta` feature +does not build (`TD-LANCE-GRAPH-ALL-FEATURES-DELTA-BREAK`). Any review asking +for the all-features invocation is blocked on that entry first, independent of +formatting. + ## TD-LENS-QUORUM-SCANS-THE-WHOLE-LENS (2026-07-29) **Codex P2 on #868, verified and MEASURED.** The lens migration changes the diff --git a/.claude/board/exec-runs/w7-binding-not-heuristic.md b/.claude/board/exec-runs/w7-binding-not-heuristic.md new file mode 100644 index 00000000..806fe8a8 --- /dev/null +++ b/.claude/board/exec-runs/w7-binding-not-heuristic.md @@ -0,0 +1,140 @@ +# W7 — binding-not-heuristic falsifier — execution record + +## Files touched +- NEW: `crates/lance-graph-planner/examples/probe_binding_not_heuristic.rs` + (only code file created; no core/contract change). +- Board: `EPIPHANIES.md` prepend (`E-A-CHIP-BEARS-LOAD-OR-IT-IS-A-JOKE-1`), + this file. Same commit, per the board-hygiene rule. + +## What W7 closes that W6 left open +W6's binder took ALREADY-RESOLVED `(pronoun_pos, antecedent_pos)` fixture pairs +— it proved the write/escalate path, never the resolution. W7 supplies the two +RESOLVERS (cheap recency baseline vs. structural binding) over shared +tagger-level features, with gold annotations read ONLY by assert arms, and +makes them disagree on real KJV text. The operator's 2026-07-30 entropy-work +ruling is the spec: a store must be effort on behalf of the thought after you; +bookkeeping a better algorithm makes redundant is a joke pretending to be +thinking. Mechanical form: the pull-test. + +## Fixture honesty +- Features are what a POS tagger + chunker emits (POS, number, animacy, + relative spans, main-clause conjunctions, complementizers). No annotation + encodes an antecedent. +- The structural resolver COMPUTES from those features (R1 complement-subject + → matrix subject; R2 subject continuity with relative-span skip + animacy + repair). Two rules only — deliberately coarse; this falsifies the CHIP, it + is not a coreference system. +- Two load-bearing-annotation tests guard against fixture-supplies-the-answer: + `animacy_check_is_load_bearing` (bare R2 picks `eyes`@2, which is NOT gold) + and `relative_span_skip_is_load_bearing` (without the span, `god`@15 would + be clause-0 subject material). + +## Gates (all falsifiable, all green) +- **B1 divergence** — Gen 3:1: heuristic → `god`@15 (d=−4), binding → + `serpent`@2 (gold). Falsifier: resolvers agreeing, or recency being right. +- **B2 escalate-not-clamp** — heuristic's wrong answer FITS ±8 (the temptation + is real and asserted); binding's right answer is d=−17 → binder escalates, + nibble stays 0. Falsifier: a stored nibble (either clamped or cheap-wrong). +- **B3 stay-silent** — Gen 3:7: both resolvers == gold on both `they`s; chips + bound at −5 and −3 (distinct, nonzero — anti-vacuity). +- **B4 pull-test** — chip chain `they`@12 → `they`@9 → `them`@4 via + `resolves_to` (two nibble follows, no far verdict cached); heuristic-only + reconstruction fails on 3:1. Falsifier: chain not composing, or the cheap + resolver sufficing everywhere. + +## Verification +- `cargo run -p lance-graph-planner --example probe_binding_not_heuristic` + → ALL GATES GREEN (re-run after fmt: still green). +- `cargo test -p lance-graph-planner --example probe_binding_not_heuristic` + → 4/4. +- `cargo clippy -p lance-graph-planner --example probe_binding_not_heuristic + -- -D warnings` → clean (pre-existing unrelated `cognitive-shader-driver` + duplicate-bin-target warning only). +- `cargo fmt -p lance-graph-planner` → ran; gates re-verified green after. + +### Workspace-wide checks — MEASURED, then deferred (not skipped) + +Review asked for `cargo fmt --all` + `cargo clippy --all-targets +--all-features`. Both were investigated rather than argued from practice: + +- **`cargo fmt --all -- --check` → 1,094 hunks / 20,307 diff lines across 64 + files in 9 crates** (55 in `lance-graph-ontology`; also `bgz-tensor`, + `causal-edge`, `ogar-emitter`, `ogar-encryption`, `ogar-from-ruff`, + `ogar-render-askama`, `sigma-tier-router`, `surreal_container`). + **Zero hunks in this PR's file** — `probe_binding_not_heuristic.rs` is + fmt-clean under the workspace-wide config, which is the property the + guideline protects. Running the sweep here would attach a 20k-line reformat + of nine untouched crates to a 752-line additive probe; the diff stops being + reviewable and pre-existing drift lands silently under an unrelated title. + Deferred to its own PR — logged as `TD-WORKSPACE-FMT-DRIFT`. +- **`clippy --all-targets --all-features`** — blocked, not declined by + preference: all-features is the known `TD-LANCE-GRAPH-ALL-FEATURES-DELTA-BREAK` + surface (the `delta` feature does not build), so the invocation cannot pass + regardless of this diff. The scoped `-D warnings` run above covers every line + the PR adds. + +All nine review threads (codex ×2, CodeRabbit ×7) addressed and resolved. + +## Honest boundaries +- Escalation is side-band (the binder's return), not row state — a 0 nibble + alone is indistinguishable from never-attempted. Same boundary W6 recorded. +- The resolvers are two rules over annotated features; real-corpus coreference + lives in `probe_eyes_opened.rs` / deepnsm. W7's claim is exactly: the chip's + placement algorithm is structural, and its refusal on 3:1 is a choice + (the wrong answer was storable), not a range limitation. + +## Follow-ons surfaced (not done here) +- Quorum binder: `Locus::Quorum`/`Contradiction` slots are never + production-written while grading rescans Θ(N·k) + (`TD-LENS-QUORUM-SCANS-THE-WHOLE-LENS`) — one defect, two views; W6/W7's + binder shape retires both. +- `PROBE-RUNG-ELIGIBILITY` (CONJECTURE row in `zero-copy-lens-law.md`): the + pull-test is now its defined pass/fail; run per locus. + +## Post-review hardening (codex 2 findings + CodeRabbit 7 comments, same PR) + +- **Gold type-separated (codex #1, CodeRabbit ×3):** `Tok` no longer carries a + `gold` field; fixtures return `(Vec, Gold)` where `Gold` is a separate + `(pronoun_pos, antecedent_pos)` list read only by assert arms. A future + `toks[p].gold` shortcut is now a COMPILE ERROR — closed by type, not + convention (strictly stronger than the suggested invariance test). +- **B2 exercises the binder (codex #2, CodeRabbit ×2):** the heuristic's wrong + target is bound into scratch rows; the gate asserts `Ok(-4)` AND stored + nibble `-4` — "storable-but-wrong" proven at the binder, not by re-deriving + its range predicate. New unit test `binder_accepts_the_tempting_wrong_target`. +- **Relative-span test was VACUOUS (codex P2, CodeRabbit Major):** confirmed — + `subject_of_clause` scans forward and English is head-first, so `serpent`@2 + wins with or without the filter; the old "load-bearing" claim was false for + this rule. Replaced with the honest pair: + `clause_segmentation_is_load_bearing` (erase the `and` boundary → + resolution FAILS — the true 3:1 counterfactual) and + `relative_span_filter_can_fire` (synthetic clause where the first candidate + is in-relative — filter changes the outcome, asserted against the + unfiltered pick). The earlier claim in this file's "Fixture honesty" section + is superseded by this addendum. +- **Pull-test had no divergent STORED chip (codex P1):** confirmed — 3:1 + escalates (no chip) and 3:7's chips match the heuristic, so stored state was + entirely heuristic-reconstructible. Added the constructed in-range + interposition fixture ("the man which the boy saw slept, and he smiled", + labelled built-English, not KJV): recency → boy (d=−4), binding → man + (d=−7, gold, BINDS). New gate B4 asserts the STORED nibble (−7) differs + from the heuristic reconstruction (−4); old composition gate renumbered B5. + New unit test `divergent_chip_differs_from_heuristic_reconstruction`. +- **EPIPHANIES wording (CodeRabbit Minor):** "chip stores the verified answer" + → "stores the POINTER the verification selected; the answer is recomputed + through it" — pointer-not-answer kept consistent. +- **Declined, with reasons:** workspace-wide `cargo fmt --all` / `clippy + --all-targets --all-features` (repo practice is scoped `-p` runs — a + workspace-wide sweep would touch unrelated crates/files and the + all-features build is the known `TD-LANCE-GRAPH-ALL-FEATURES-DELTA-BREAK` + surface); methods-on-carrier nitpick (the carrier litmus targets cognitive + state carriers, not probe fixtures — two flat resolver functions keep the + falsifier legible; noted, not a doctrine violation). +- **Operator ruling landed mid-review** (see + `E-64K-THOUGHTS-DONT-DO-QUORUM-PLASTICITY-BREATHES-FROM-TENSION-1`): the + quorum-binder follow-on this file proposed is WITHDRAWN — 64k thoughts + don't do quorum; plasticity does that work; contradictions are fuel. + `TD-LENS-QUORUM` survives as scan-cost debt only. + +Re-verified after all changes: 5 gates green (B1–B5), 7 unit tests green, +clippy `-D warnings` clean, fmt clean. diff --git a/crates/lance-graph-planner/examples/probe_binding_not_heuristic.rs b/crates/lance-graph-planner/examples/probe_binding_not_heuristic.rs new file mode 100644 index 00000000..ae5877b6 --- /dev/null +++ b/crates/lance-graph-planner/examples/probe_binding_not_heuristic.rs @@ -0,0 +1,591 @@ +//! `probe_binding_not_heuristic` — W7: the falsifier W6 deferred. Prove the +//! `Locus::Antecedent` chip is placed by structural BINDING, not by a distance +//! heuristic — i.e. that the stored nibble carries bits a cheap algorithm +//! cannot recompute, so the chip bears load instead of decorating. +//! +//! **The operator's criterion (2026-07-30), which is this probe's spec:** a +//! store must be *effort on behalf of the thought after you*; bookkeeping that +//! a better algorithm makes redundant is "a joke to pretend it was thinking." +//! The pull-test follows: pull the chip — if a cheap resolver reconstructs the +//! same answer everywhere, the chip was decoration; if somewhere the cheap +//! resolver produces a *different, wrong* answer, the chip was load-bearing. +//! +//! **W6 bound fixture-supplied `(pronoun, antecedent)` pairs — it proved the +//! write path, not the resolution.** This probe supplies the two RESOLVERS and +//! makes them disagree on real KJV text: +//! +//! - **Heuristic** (the cheap baseline, deliberately strong): nearest +//! preceding number-agreeing referential token. This is the resolver a +//! "better algorithm makes it redundant" claim would deploy. +//! - **Binding** (structural): complement-clause subjects bind the MATRIX +//! subject (the reflexive signature from `E-EYES-OPENED-PRINTS-BLIND-1`); +//! main-clause subject pronouns bind the PREVIOUS main clause's subject, +//! skipping relative-clause material, with an animacy check against the +//! pronoun's own verb. +//! +//! Both resolvers read the same tagger-level token features (POS, number, +//! animacy, clause boundaries, relative spans, complementizers). Gold +//! annotations are TYPE-SEPARATED from resolver input (CodeRabbit hardening on +//! this PR): fixtures return `(Vec, gold pairs)`, and `Tok` carries no +//! gold field at all — a future `toks[p].gold` shortcut is a compile error, +//! not a convention. This closes the honesty gap W6 left open structurally. +//! +//! **Three fixtures, three roles** (the third added after codex's P1 on this +//! PR, which caught that the original two left the pull-test's stored state +//! heuristic-reconstructible): +//! +//! - **Gen 3:1** — the refusal text: recency finds `god` at in-window distance +//! −4 (wrong); binding finds `serpent` at −17 — **outside the ±8 chip range, +//! so the binder ESCALATES rather than store anything**. The refusal is a +//! choice, not a range limitation: B2 proves the binder ACCEPTS the tempting +//! wrong target on scratch rows. +//! - **A constructed in-range interposition** (*"the man which the boy saw +//! slept, and he smiled"* — built English, labelled as such, not KJV): both +//! answers are in-window, they DIVERGE (recency → `boy`@−4 wrong, binding → +//! `man`@−7 gold), and the structural answer BINDS — so the stored nibble +//! itself differs from what recency would reconstruct. This is the chip that +//! carries bits the cheap algorithm cannot recompute, IN STORED STATE. +//! - **Gen 3:7** — the stay-silent half (both resolvers agree on both `they`s) +//! plus the chip-composition payoff: inner `they`@12 → matrix `they`@9 → +//! `them`@4 resolves by following two stored nibbles — chips COMPOSE, each +//! carrying its local warrant, never a cached far verdict. +//! +//! **Honesty note on the relative span (codex P2 on this PR):** in head-first +//! English the subject precedes its relative clause, so forward subject +//! selection excludes `god` by ORDER — the `!in_relative` filter is redundant +//! for these fixtures' subject picks. What actually bears load on 3:1 is +//! CLAUSE SEGMENTATION (delete the `and` boundary and resolution fails +//! entirely — asserted as a counterfactual test), and the span filter's +//! discriminative power is proven on a synthetic clause where order does NOT +//! protect it (`relative_span_filter_can_fire`). +//! +//! Escalation is side-band (a record, not row state): a `0` nibble alone reads +//! as *unbound*, indistinguishable from never-attempted — same honest boundary +//! W6 documented. +//! +//! Usage: `cargo run -p lance-graph-planner --example probe_binding_not_heuristic` + +use lance_graph_contract::canonical_node::{EdgeBlock, NodeGuid, NodeRow}; +use lance_graph_contract::causal_witness::{CausalWitnessFacet, Locus}; +use lance_graph_contract::witness_fabric::WitnessLens; + +/// Part-of-speech class at tagger granularity — the features both resolvers +/// share. Nothing here encodes an antecedent. +#[derive(Clone, Copy, PartialEq, Eq)] +enum Pos { + /// Referential nominal — an antecedent candidate. + Noun, + /// Referential pronoun — a candidate AND (possibly) a resolvable site. + Pron, + /// Verb. `animate` on a verb token means "requires an animate subject". + Verb, + /// Everything else (particles, adjectives, conjunctions, complementizers). + Other, +} + +/// One fixture token: surface features only (what a POS tagger + chunker +/// emits). **Deliberately contains NO gold field** — the referee's answers +/// live in the separate gold list a fixture returns, so the resolvers (which +/// receive only `&[Tok]`) cannot reach them even by a future shortcut. +struct Tok { + label: &'static str, + pos: Pos, + plural: bool, + /// Noun/Pron: referent animacy. Verb: requires an animate subject. + animate: bool, + /// Token sits inside a relative-clause span ("which … made"). + in_relative: bool, + /// Token is a conjunction opening a NEW main clause. + new_main: bool, + /// Token is a complementizer ("that") introducing a complement clause. + comp: bool, +} + +/// The referee's answer sheet: `(pronoun_pos, gold_antecedent_pos)` pairs. +/// Read ONLY by assert arms; never passed to a resolver. +type Gold = Vec<(usize, usize)>; + +/// Look up the gold antecedent for a pronoun position. +fn gold_of(gold: &Gold, p: usize) -> Option { + gold.iter().find(|&&(q, _)| q == p).map(|&(_, t)| t) +} + +impl Tok { + fn t(label: &'static str) -> Self { + Tok { + label, + pos: Pos::Other, + plural: false, + animate: false, + in_relative: false, + new_main: false, + comp: false, + } + } + fn noun(label: &'static str, plural: bool, animate: bool) -> Self { + Tok { + pos: Pos::Noun, + plural, + animate, + ..Tok::t(label) + } + } + fn pron(label: &'static str, plural: bool) -> Self { + Tok { + pos: Pos::Pron, + plural, + animate: true, + ..Tok::t(label) + } + } + fn verb(label: &'static str, requires_animate: bool) -> Self { + Tok { + pos: Pos::Verb, + animate: requires_animate, + ..Tok::t(label) + } + } + fn conj(label: &'static str) -> Self { + Tok { + new_main: true, + ..Tok::t(label) + } + } + fn rel(mut self) -> Self { + self.in_relative = true; + self + } + fn is_candidate(&self) -> bool { + matches!(self.pos, Pos::Noun | Pos::Pron) + } +} + +/// Gen 3:1 (KJV, lowercased, trimmed): *"now the serpent was more subtil than +/// any beast of the field which the LORD God had made: and he said unto the +/// woman"*. The relative span covers "which … made". Gold (returned separately, +/// never inside `Tok`): `he`@19 → serpent@2. +fn genesis_3_1() -> (Vec, Gold) { + let toks = vec![ + Tok::t("now"), // 0 + Tok::t("the"), // 1 + Tok::noun("serpent", false, true), // 2 + Tok::verb("was", false), // 3 + Tok::t("more"), // 4 + Tok::t("subtil"), // 5 + Tok::t("than"), // 6 + Tok::t("any"), // 7 + Tok::noun("beast", false, true), // 8 + Tok::t("of"), // 9 + Tok::t("the"), // 10 + Tok::noun("field", false, false), // 11 + Tok::t("which").rel(), // 12 + Tok::t("the").rel(), // 13 + Tok::t("lord").rel(), // 14 + Tok::noun("god", false, true).rel(), // 15 + Tok::t("had").rel(), // 16 + Tok::verb("made", false).rel(), // 17 + Tok::conj("and"), // 18 + Tok::pron("he", false), // 19 → serpent@2 (d = -17) + Tok::verb("said", true), // 20 + Tok::t("unto"), // 21 + Tok::t("the"), // 22 + Tok::noun("woman", false, true), // 23 + ]; + (toks, vec![(19, 2)]) +} + +/// Gen 3:7 (KJV, lowercased, trimmed): *"and the eyes of them both were +/// opened, and they knew that they were naked"*. Gold (returned separately, +/// never inside `Tok`): matrix `they`@9 → `them`@4 (the pair, not the eyes); +/// inner `they`@12 → matrix `they`@9 (the reflexive signature — knower == +/// overt inner subject). +fn genesis_3_7() -> (Vec, Gold) { + let toks = vec![ + Tok::t("and"), // 0 + Tok::t("the"), // 1 + Tok::noun("eyes", true, false), // 2 (plural, inanimate) + Tok::t("of"), // 3 + Tok::noun("them", true, true), // 4 (referential pronoun-as-candidate) + Tok::t("both"), // 5 + Tok::verb("were", false), // 6 + Tok::verb("opened", false), // 7 + Tok::conj("and"), // 8 + Tok::pron("they", true), // 9 → them@4 (d = -5) + Tok::verb("knew", true), // 10 (knowing requires an animate subject) + Tok { + comp: true, + ..Tok::t("that") + }, // 11 + Tok::pron("they", true), // 12 → matrix they@9 (d = -3) + Tok::verb("were", false), // 13 + Tok::t("naked"), // 14 + ]; + (toks, vec![(9, 4), (12, 9)]) +} + +/// A constructed in-range interposition (built English, NOT KJV — labelled +/// honestly): *"the man which the boy saw slept, and he smiled"*. Added for +/// codex's P1: both resolvers' answers are in-window and DIVERGE — recency → +/// `boy`@4 (d = −4, wrong), binding → `man`@1 (d = −7, gold) — and the +/// structural answer BINDS, so the STORED nibble itself differs from what +/// recency would reconstruct. +fn interposed_in_range() -> (Vec, Gold) { + let toks = vec![ + Tok::t("the"), // 0 + Tok::noun("man", false, true), // 1 + Tok::t("which").rel(), // 2 + Tok::t("the").rel(), // 3 + Tok::noun("boy", false, true).rel(), // 4 + Tok::verb("saw", true).rel(), // 5 + Tok::verb("slept", true), // 6 + Tok::conj("and"), // 7 + Tok::pron("he", false), // 8 → man@1 (d = -7) + Tok::verb("smiled", true), // 9 + ]; + (toks, vec![(8, 1)]) +} + +/// The cheap baseline: nearest preceding number-agreeing referential token. +/// Deliberately ignores clause structure and animacy — that IS its cheapness. +fn heuristic_resolve(toks: &[Tok], p: usize) -> Option { + (0..p) + .rev() + .find(|&i| toks[i].is_candidate() && toks[i].plural == toks[p].plural) +} + +/// Main-clause id per token (increments at each `new_main` conjunction). +fn clause_of(toks: &[Tok], i: usize) -> usize { + toks[..=i].iter().filter(|t| t.new_main).count() +} + +/// The subject of main clause `k`: its first candidate token outside any +/// relative span. +fn subject_of_clause(toks: &[Tok], k: usize) -> Option { + (0..toks.len()) + .find(|&i| clause_of(toks, i) == k && toks[i].is_candidate() && !toks[i].in_relative) +} + +/// The structural resolver. Two rules, both computed from features: +/// +/// - **R1 (complement subject):** a pronoun immediately after a +/// complementizer binds the subject of the clause whose verb takes the +/// complement (the matrix subject) — the reflexive signature. +/// - **R2 (subject continuity, animacy-checked):** a subject pronoun opening +/// a main clause binds the previous main clause's subject — unless the +/// pronoun's own verb requires an animate subject and that subject is +/// inanimate, in which case it binds the nearest preceding number-agreeing +/// ANIMATE candidate. +fn structural_resolve(toks: &[Tok], p: usize) -> Option { + // R1: complement-clause subject → matrix subject. + if p > 0 && toks[p - 1].comp { + let matrix_verb = (0..p - 1).rev().find(|&i| toks[i].pos == Pos::Verb)?; + return subject_of_clause(toks, clause_of(toks, matrix_verb)); + } + // R2: subject pronoun of main clause k → subject of clause k-1. + let k = clause_of(toks, p); + if k == 0 { + return None; + } + let prev_subj = subject_of_clause(toks, k - 1)?; + let own_verb = (p + 1..toks.len()).find(|&i| toks[i].pos == Pos::Verb); + let needs_animate = own_verb.is_some_and(|v| toks[v].animate); + if needs_animate && !toks[prev_subj].animate { + // Animacy repair: nearest preceding number-agreeing animate candidate. + return (0..p).rev().find(|&i| { + toks[i].is_candidate() && toks[i].plural == toks[p].plural && toks[i].animate + }); + } + Some(prev_subj) +} + +/// W6's binder semantics, verbatim: in-range displacement binds the nibble; +/// `0` or out-of-`±8` escalates — the row stays unbound, never clamped. +fn bind(rows: &mut [NodeRow], pronoun: usize, target: usize) -> Result { + let d = target as isize - pronoun as isize; + if d == 0 || !(-8..=7).contains(&d) { + return Err(d); + } + let facet = CausalWitnessFacet::ZERO.with(Locus::Antecedent, d as i8); + WitnessLens::write_register(&mut rows[pronoun], &facet); + Ok(d as i8) +} + +fn fresh_rows(n: usize) -> Vec { + (0..n) + .map(|i| NodeRow { + key: NodeGuid::local(i as u32), + edges: EdgeBlock::default(), + value: [0u8; 480], + }) + .collect() +} + +fn main() { + let mut green = true; + let mut gate = |name: &str, pass: bool, detail: String| { + println!("[{}] {name} — {detail}", if pass { "PASS" } else { "FAIL" }); + green &= pass; + }; + + // ── Fixture A: Gen 3:1 — the divergence text ───────────────────────────── + let (a, gold_a) = genesis_3_1(); + let he = 19; + let h_a = heuristic_resolve(&a, he); + let s_a = structural_resolve(&a, he); + + // B1 — can-fire: the resolvers genuinely disagree, and gold sides with + // binding. Falsifier: agreement, or the heuristic being right. + gate( + "B1 divergence", + h_a == Some(15) && s_a == Some(2) && gold_of(&gold_a, he) == Some(2) && h_a != s_a, + format!( + "heuristic(he@19) = {:?} ({}), binding = {:?} ({}), gold = serpent@2", + h_a, + h_a.map_or("-", |i| a[i].label), + s_a, + s_a.map_or("-", |i| a[i].label), + ), + ); + + // B2 — the temptation is real, and the chip refuses it. Proven at the + // BINDER, not by re-deriving its range predicate (CodeRabbit hardening): + // binding the heuristic's target into scratch rows must SUCCEED — the + // binder accepts and stores the wrong-but-in-window nibble (-4) — while + // binding the structural answer (d = -17) must escalate, leaving the real + // rows unbound. The chip's refusal is a choice, not a range limitation. + let mut rows_tempted = fresh_rows(a.len()); + let tempted = bind(&mut rows_tempted, he, h_a.unwrap()); + let tempted_nibble = WitnessLens::new(&rows_tempted) + .at(he) + .map(|f| f.at(Locus::Antecedent)); + let mut rows_a = fresh_rows(a.len()); + let bound = bind(&mut rows_a, he, s_a.unwrap()); + let lens_a = WitnessLens::new(&rows_a); + let nibble = lens_a.at(he).map(|f| f.at(Locus::Antecedent)); + gate( + "B2 escalate-not-clamp", + tempted == Ok(-4) + && tempted_nibble == Some(-4) + && bound == Err(-17) + && nibble == Some(0), + format!( + "binder ACCEPTS the heuristic's wrong target ({tempted:?}, nibble {tempted_nibble:?} on scratch rows) — storable-but-wrong is real; binding d=-17 → escalated, nibble={nibble:?}" + ), + ); + + // ── Fixture B: Gen 3:7 — the stay-silent text + chip composition ───────── + let (b, gold_b) = genesis_3_7(); + let (matrix, inner) = (9, 12); + let mut rows_b = fresh_rows(b.len()); + let mut agree = true; + let mut displacements = Vec::new(); + for &p in &[matrix, inner] { + let h = heuristic_resolve(&b, p); + let s = structural_resolve(&b, p); + agree &= h == s && s == gold_of(&gold_b, p); + if let Some(t) = s { + if let Ok(d) = bind(&mut rows_b, p, t) { + displacements.push(d); + } + } + } + + // B3 — can-stay-silent: on the reflexive verse both resolvers agree with + // gold and with each other; the divergence gate does not fire on + // everything (a guard that always fires carries no information). The two + // chips hold distinct nonzero displacements (anti-vacuity). + gate( + "B3 stay-silent", + agree && displacements == vec![-5, -3], + format!("both they@9/they@12 agree across resolvers; chips = {displacements:?}"), + ); + + // ── Fixture C: constructed in-range interposition — the divergent CHIP ─── + // (codex P1 on this PR: with only fixtures A and B, every STORED chip came + // from the agreeing verse, so stored state was heuristic-reconstructible + // and the pull-test could not establish its central claim.) + let (c, gold_c) = interposed_in_range(); + let hep = 8; + let h_c = heuristic_resolve(&c, hep); + let s_c = structural_resolve(&c, hep); + let mut rows_c = fresh_rows(c.len()); + let chip_c = bind(&mut rows_c, hep, s_c.unwrap()); + let stored_c = WitnessLens::new(&rows_c) + .at(hep) + .map(|f| f.at(Locus::Antecedent)); + let heuristic_offset = h_c.map(|t| t as i8 - hep as i8); + + // B4 — the pull-test on a DIVERGENT stored chip: both answers in-window, + // resolvers disagree, gold sides with binding, and the STORED nibble (-7) + // differs from what recency would reconstruct (-4). Pull this chip and the + // cheap resolver rebuilds the WRONG antecedent — the stored state itself + // carries bits no better cheap algorithm recomputes. + gate( + "B4 divergent chip", + h_c == Some(4) + && s_c == Some(1) + && gold_of(&gold_c, hep) == Some(1) + && chip_c == Ok(-7) + && stored_c == Some(-7) + && heuristic_offset == Some(-4) + && stored_c != heuristic_offset, + format!( + "recency → boy@4 (would store {heuristic_offset:?}), binding → man@1; STORED nibble {stored_c:?} ≠ heuristic reconstruction — divergence lives in stored state" + ), + ); + + // B5 — chips COMPOSE: the inner pronoun's ultimate referent resolves by + // following two stored nibbles (they@12 → they@9 → them@4) — each chip a + // local warrant, no far verdict cached anywhere. + let lens_b = WitnessLens::new(&rows_b); + let hop1 = lens_b + .at(inner) + .and_then(|f| f.resolves_to(Locus::Antecedent, inner, lens_b.len())); + let hop2 = hop1.and_then(|p| { + lens_b + .at(p) + .and_then(|f| f.resolves_to(Locus::Antecedent, p, lens_b.len())) + }); + gate( + "B5 chips compose", + hop1 == Some(matrix) && hop2 == Some(4), + format!("chip chain they@12 → {hop1:?} → {hop2:?} (gold them@4) — two local warrants"), + ); + + println!(); + if green { + println!("ALL GATES GREEN"); + } else { + println!("GATES FAILED"); + std::process::exit(1); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// The probe's own gates, runnable under `cargo test --example`. + #[test] + fn divergence_fires_on_3_1_and_stays_silent_on_3_7() { + let (a, gold_a) = genesis_3_1(); + assert_eq!(heuristic_resolve(&a, 19), Some(15), "recency finds god"); + assert_eq!(structural_resolve(&a, 19), Some(2), "binding finds serpent"); + assert_eq!(gold_of(&gold_a, 19), Some(2)); + + let (b, gold_b) = genesis_3_7(); + for p in [9, 12] { + assert_eq!(heuristic_resolve(&b, p), structural_resolve(&b, p)); + assert_eq!(structural_resolve(&b, p), gold_of(&gold_b, p)); + } + } + + /// Deleting the animacy check must break 3:7's matrix resolution (the + /// rule is load-bearing, not decoration): without it, R2 returns the + /// previous clause's subject verbatim — `eyes`@2, which is wrong. + #[test] + fn animacy_check_is_load_bearing() { + let (b, gold_b) = genesis_3_7(); + let k = clause_of(&b, 9); + let bare_r2 = subject_of_clause(&b, k - 1); + assert_eq!(bare_r2, Some(2), "bare subject-continuity picks eyes@2"); + assert_ne!( + bare_r2, + gold_of(&gold_b, 9), + "…which is NOT the gold answer" + ); + assert_eq!( + structural_resolve(&b, 9), + gold_of(&gold_b, 9), + "the check repairs it" + ); + } + + /// What ACTUALLY bears load on 3:1 is clause segmentation, not the span + /// filter (codex P2: in head-first English `serpent`@2 precedes `god`@15, + /// so subject selection excludes `god` by ORDER — clearing `in_relative` + /// changes nothing on this fixture). The honest counterfactual: delete the + /// clause boundary and structural resolution FAILS entirely. + #[test] + fn clause_segmentation_is_load_bearing() { + let (mut a, gold_a) = genesis_3_1(); + assert_eq!(structural_resolve(&a, 19), gold_of(&gold_a, 19)); + a[18].new_main = false; // erase the "and" boundary + assert_eq!( + structural_resolve(&a, 19), + None, + "without segmentation the resolver has no previous clause to bind" + ); + assert_ne!(None, gold_of(&gold_a, 19), "…and gold still exists"); + } + + /// The span filter's discriminative power, proven where ORDER does not + /// protect it: a synthetic clause whose FIRST candidate sits inside a + /// relative span. With the filter the subject is the second candidate; + /// the unfiltered first-candidate pick differs. Can-fire, non-vacuous. + #[test] + fn relative_span_filter_can_fire() { + let toks = vec![ + Tok::noun("ghost", false, true).rel(), // 0 — in-relative candidate FIRST + Tok::noun("host", false, true), // 1 — the real subject + ]; + let filtered = subject_of_clause(&toks, 0); + let unfiltered = + (0..toks.len()).find(|&i| clause_of(&toks, i) == 0 && toks[i].is_candidate()); + assert_eq!(filtered, Some(1), "filter skips the in-relative candidate"); + assert_eq!(unfiltered, Some(0), "without it, the wrong token wins"); + assert_ne!(filtered, unfiltered, "the filter changes the outcome"); + } + + /// codex P1's demanded artifact: a DIVERGENT chip in stored state. The + /// in-range interposition binds -7 while recency would reconstruct -4 — + /// the stored nibble itself differs from the cheap reconstruction. + #[test] + fn divergent_chip_differs_from_heuristic_reconstruction() { + let (c, gold_c) = interposed_in_range(); + let h = heuristic_resolve(&c, 8); + let s = structural_resolve(&c, 8); + assert_eq!(h, Some(4), "recency finds boy (wrong)"); + assert_eq!(s, Some(1), "binding finds man (gold)"); + assert_eq!(s, gold_of(&gold_c, 8)); + + let mut rows = fresh_rows(c.len()); + assert_eq!(bind(&mut rows, 8, s.unwrap()), Ok(-7)); + let stored = WitnessLens::new(&rows) + .at(8) + .map(|f| f.at(Locus::Antecedent)); + assert_eq!(stored, Some(-7)); + let heuristic_reconstruction = h.map(|t| t as i8 - 8); + assert_ne!( + stored, heuristic_reconstruction, + "the stored bit is exactly what recency cannot recompute" + ); + } + + /// Out-of-window escalation leaves the nibble unbound — never a clamp. + #[test] + fn escalation_leaves_nibble_unbound() { + let (a, _) = genesis_3_1(); + let mut rows = fresh_rows(a.len()); + assert_eq!(bind(&mut rows, 19, 2), Err(-17)); + let lens = WitnessLens::new(&rows); + assert_eq!(lens.at(19).map(|f| f.at(Locus::Antecedent)), Some(0)); + } + + /// The binder ACCEPTS the heuristic's wrong-but-in-window target — the + /// "storable-but-wrong" half of B2 proven at the binder itself, not by + /// re-deriving its range predicate in the gate. + #[test] + fn binder_accepts_the_tempting_wrong_target() { + let (a, gold_a) = genesis_3_1(); + let mut rows = fresh_rows(a.len()); + assert_eq!(bind(&mut rows, 19, 15), Ok(-4), "god@15 binds cleanly"); + let lens = WitnessLens::new(&rows); + assert_eq!(lens.at(19).map(|f| f.at(Locus::Antecedent)), Some(-4)); + assert_ne!( + Some(15), + gold_of(&gold_a, 19), + "…and it is the WRONG target" + ); + } +}