From 6816e6220c03a1023aa315ffed5e6ae75960165e Mon Sep 17 00:00:00 2001 From: Shay Date: Sat, 6 Jun 2026 06:31:54 -0700 Subject: [PATCH] =?UTF-8?q?fix(realize):=20lookback=20hardening=20?= =?UTF-8?q?=E2=80=94=20defensive=20admissibility=20re-assertion=20+=20cove?= =?UTF-8?q?rage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From the mandated lookback audit of the composed R0→R1→R1c→OOV surface: - wrong=0 HAZARD (medium): realize_quantitative trusted equation admissibility it never checked. `comprehend_quantitative` runs real check_admissibility, but the type does not enforce it, so a future non-reader constructor could slip a dimensionally-incoherent 'pending'/'refused' equation into the held self (then surfaced as-told by DETERMINE). Now RE-ASSERTS admitted-status defensively -> NotRealized("unadmitted_equation"); docstring corrected to match (no longer claims the type guarantees it). Bite test via a hand-built pending-equation graph. - Defensive: wrap binding_graph hashing -> NotRealized("unhashable_structure") so a future numeric field is a clean refusal, not an uncaught TypeError mid-write. - Coverage (the obligations now bite, not decoration): unadmitted_equation, not_a_quant_comprehension, no_bound_fact, grounding_failed (monkeypatched probe), cross-substrate coexistence (meaning_graph + binding_graph in one vault — recall isolates, structure_keys differ), and the negated_relation refusal (hand-built, since the reader encodes declarative negation in the PREDICATE not rel.negated). - Drift: R0 idempotency test now names the actual dedup key (structure_key, not content_hash); scope doc notes #591 made OOV placement injective (the §0/§1 non-injectivity finding describes the pre-#591 substrate). Note: the lookback flagged negated_relation as a high reachable-untested hazard, but verification showed the reader never sets rel.negated=True for declaratives (it refuses "X is not a Y" or uses some_not/disjoint predicates) — so it is a defensive branch, tested via a hand-built graph. Green: 35 realize + ruff clean. --- .../REALIZE-R1-DETERMINE-scope-2026-06-06.md | 8 +++ generate/realize/quantitative.py | 29 ++++++-- tests/test_realize_r0.py | 27 ++++++- tests/test_realize_r1c_binding_graph.py | 71 +++++++++++++++++++ 4 files changed, 127 insertions(+), 8 deletions(-) diff --git a/docs/analysis/REALIZE-R1-DETERMINE-scope-2026-06-06.md b/docs/analysis/REALIZE-R1-DETERMINE-scope-2026-06-06.md index f7a9a010..57b623a6 100644 --- a/docs/analysis/REALIZE-R1-DETERMINE-scope-2026-06-06.md +++ b/docs/analysis/REALIZE-R1-DETERMINE-scope-2026-06-06.md @@ -47,6 +47,14 @@ realized metadata, layered in `generate/realize/`. ## 1. OOV-grounding: the prior claim is refuted — it is deterministic, not non-deterministic +> **Update (#591 shipped):** the §0/§1 non-injectivity finding below +> (`zelophehad`/`photosynthesis`/`unbridgeable` → one root-versor; field-metric +> recall "degenerate") describes the **pre-#591** substrate. #591 (`ingest/gate.py` +> content-derived `_token_spin_delta`, merged) makes OOV placement **injective by +> token content** (verified live: those three now produce three distinct versors), so +> the gate-lift no longer rests only on structural recall — though structural recall +> remains what *carries* correctness. + R0's recorded finding ("OOV grounding is NON-deterministic across reboots") is **wrong** and is corrected here. Evidence (`/tmp/oov_proc.py`, faithful in-tree, `no_load_state`): diff --git a/generate/realize/quantitative.py b/generate/realize/quantitative.py index 5337345f..62b66731 100644 --- a/generate/realize/quantitative.py +++ b/generate/realize/quantitative.py @@ -56,11 +56,13 @@ def realize_quantitative( ) -> Realized | NotRealized: """Realize an arithmetic comprehension's binding_graph into ``ctx``'s vault. - Eligibility: a ``QuantComprehension`` (not a ``Refusal``). Factless / malformed - arithmetic prose is already a ``Refusal`` upstream (``comprehend_quantitative``), - so the only wrong=0 guard needed here is the ``Refusal`` type check — every - ``QuantComprehension`` carries an admissibility-checked binding graph. SPECULATIVE - always (COHERENT is never a default); dedup by the span-free structure_key. + Eligibility: a ``QuantComprehension`` (not a ``Refusal``) whose every equation is + ``admitted``. Every ``QuantComprehension`` PRODUCED BY ``comprehend_quantitative`` + carries an admissibility-checked graph (real ``check_admissibility``; a ``Refusal`` + short-circuits unreadable input) — but the type does NOT enforce it, so this + function RE-ASSERTS admitted-status defensively, keeping the wrong=0 floor + independent of the caller. SPECULATIVE always (COHERENT is never a default); dedup + by the span-free structure_key. """ if isinstance(comprehension, Refusal): return NotRealized("refusal") @@ -70,6 +72,13 @@ def realize_quantitative( bg = comprehension.binding_graph if not bg.facts: return NotRealized("no_bound_fact") # defensive — the reader guarantees >=1 fact + # wrong=0 defense: realize ONLY a fully-admitted binding graph. The model permits a + # structurally-valid graph to carry a 'pending'/'refused' equation, so re-assert + # admitted-status here — a future non-reader constructor cannot slip a + # dimensionally-incoherent equation into the held self (it would otherwise be + # surfaced as-told by DETERMINE). + if any(e.admissibility_status != "admitted" for e in bg.equations): + return NotRealized("unadmitted_equation") # Placement: the asked entity's field point. Symbolic/OOV, so deterministic- # GIVEN-session-state, NOT subject-determined; the structural key carries @@ -83,8 +92,14 @@ def realize_quantitative( versor = np.asarray(field_state.F, dtype=np.float32) structure_canonical = bg.to_canonical_string() - content_hash = sha256_of(structure_canonical) - structure_key = _binding_graph_structure_key(bg) + # ``sha256_of`` rejects floats (canonical-JSON contract). The binding-graph fields + # fed here are str by the model's contract today; wrap defensively so a future + # numeric field is a clean refusal, never an uncaught TypeError mid-write. + try: + content_hash = sha256_of(structure_canonical) + structure_key = _binding_graph_structure_key(bg) + except (TypeError, ValueError): + return NotRealized("unhashable_structure") status = EpistemicStatus.SPECULATIVE source_spans = [f.source_span.to_canonical_string() for f in bg.facts] or [ s.source_span.to_canonical_string() for s in bg.symbols diff --git a/tests/test_realize_r0.py b/tests/test_realize_r0.py index 99363330..709568bf 100644 --- a/tests/test_realize_r0.py +++ b/tests/test_realize_r0.py @@ -90,7 +90,7 @@ def test_single_in_vocab_declarative_is_realized(vocab_persona) -> None: # --------------------------------------------------------------------------- # -# Idempotency — a re-told fact does not grow the vault (dedup by content_hash) +# Idempotency — a re-told fact does not grow the vault (dedup by structure_key) # --------------------------------------------------------------------------- # @@ -100,10 +100,35 @@ def test_retold_fact_is_idempotent(vocab_persona) -> None: second = _realize("Truth is a concept.", ctx) assert isinstance(first, Realized) and first.created is True assert isinstance(second, Realized) and second.created is False # dedup hit + # dedup is on the span-free structure_key (content_hash coincides only because the + # surface is identical here); name the actual dedup key. + assert second.record.structure_key == first.record.structure_key assert second.record.content_hash == first.record.content_hash assert len(ctx.vault._metadata) == 1 # NOT grown +def test_negated_relation_realizes_nothing(vocab_persona) -> None: + # The reader encodes declarative negation in the PREDICATE (some_not/disjoint), so + # rel.negated=True is reachable only via a hand-built graph — but the defensive + # refusal must bite: a negated relation ("X is NOT a Y") must never be realized as + # a positive fact. + from generate.meaning_graph.model import Entity, MeaningGraph, MeaningSpan, Relation + from generate.meaning_graph.reader import Comprehension + + span = MeaningSpan(source_id="input", start=0, end=18, text="truth not concept") + graph = MeaningGraph( + entities=( + Entity(entity_id="truth", name="truth", span=span), + Entity(entity_id="concept", name="concept", span=span), + ), + relations=(Relation(predicate="member", arguments=("truth", "concept"), span=span, negated=True),), + ) + ctx = _ctx(vocab_persona) + res = realize_comprehension(Comprehension(meaning_graph=graph, queries=()), ctx) + assert isinstance(res, NotRealized) and res.reason == "negated_relation" + assert len(ctx.vault._metadata) == 0 + + # --------------------------------------------------------------------------- # # Status firewall — SPECULATIVE is candidate memory, NOT evidence # --------------------------------------------------------------------------- # diff --git a/tests/test_realize_r1c_binding_graph.py b/tests/test_realize_r1c_binding_graph.py index 21432ba2..7b5de9fd 100644 --- a/tests/test_realize_r1c_binding_graph.py +++ b/tests/test_realize_r1c_binding_graph.py @@ -9,14 +9,18 @@ versor — and reboot-stability rests on the Shape B+ snapshot of the exact byte from __future__ import annotations +from dataclasses import replace + import pytest from algebra.versor import versor_condition from chat.runtime import ChatRuntime +from generate.meaning_graph.reader import comprehend from generate.quantitative_comprehension import comprehend_quantitative from generate.realize import ( NotRealized, Realized, + realize_comprehension, realize_quantitative, recall_realized, ) @@ -158,3 +162,70 @@ def test_binding_graph_record_not_admitted_as_evidence(vocab_persona) -> None: query = ctx.vault._versors[res.record.vault_index] coherent = ctx.vault.recall(query, top_k=5, min_status=EpistemicStatus.COHERENT) assert not any(h["metadata"].get("kind") == "realized" for h in coherent) + + +# --------------------------------------------------------------------------- # +# wrong=0 defense — an UNADMITTED equation never enters the held self +# --------------------------------------------------------------------------- # + + +def test_unadmitted_equation_realizes_nothing(vocab_persona) -> None: + # The model permits a structurally-valid graph to carry a 'pending'/'refused' + # equation. realize_quantitative RE-ASSERTS admitted-status, so it cannot be + # slipped a dimensionally-incoherent equation by a non-reader constructor. + ctx = _ctx(vocab_persona) + good = comprehend_quantitative(_SUM) # has admitted equations + pending = tuple(replace(e, admissibility_status="pending") for e in good.binding_graph.equations) + tampered = replace(good, binding_graph=replace(good.binding_graph, equations=pending)) + res = realize_quantitative(tampered, ctx) + assert isinstance(res, NotRealized) and res.reason == "unadmitted_equation" + assert len(ctx.vault._metadata) == 0 + + +def test_not_a_quant_comprehension_realizes_nothing(vocab_persona) -> None: + ctx = _ctx(vocab_persona) + res = realize_quantitative(object(), ctx) # neither Refusal nor QuantComprehension + assert isinstance(res, NotRealized) and res.reason == "not_a_quant_comprehension" + assert len(ctx.vault._metadata) == 0 + + +def test_factless_binding_graph_realizes_nothing(vocab_persona) -> None: + ctx = _ctx(vocab_persona) + good = comprehend_quantitative(_FACT) + factless = replace(good, binding_graph=replace(good.binding_graph, facts=())) + res = realize_quantitative(factless, ctx) + assert isinstance(res, NotRealized) and res.reason == "no_bound_fact" + assert len(ctx.vault._metadata) == 0 + + +def test_grounding_failure_realizes_nothing(vocab_persona) -> None: + # A degenerate placement raises inside probe_ingest; it must be caught -> no write. + ctx = _ctx(vocab_persona) + + def _boom(_tokens): + raise RuntimeError("degenerate grounding") + + ctx.probe_ingest = _boom # type: ignore[method-assign] + res = realize_quantitative(comprehend_quantitative(_FACT), ctx) + assert isinstance(res, NotRealized) and res.reason == "grounding_failed" + assert len(ctx.vault._metadata) == 0 + + +# --------------------------------------------------------------------------- # +# Cross-substrate coexistence — meaning_graph + binding_graph in ONE vault +# --------------------------------------------------------------------------- # + + +def test_meaning_graph_and_binding_graph_coexist_and_recall_isolates(vocab_persona) -> None: + ctx = _ctx(vocab_persona) + realize_comprehension(comprehend("Truth is a concept."), ctx) # meaning_graph member + _realize(_FACT, ctx) # binding_graph (alice) + assert len(ctx.vault._metadata) == 2 + # structure_kind partitions; subject= (a meaning_graph notion) never matches a + # binding_graph record (its relation_arguments is empty). + mg = recall_realized(ctx, structure_kind="meaning_graph") + bg = recall_realized(ctx, structure_kind="binding_graph") + assert len(mg) == 1 and len(bg) == 1 + assert recall_realized(ctx, subject="truth") == mg + assert recall_realized(ctx, subject="alice") == () # binding_graph not matched by subject= + assert mg[0].structure_key != bg[0].structure_key # cross-kind collision-free