fix(realize): lookback hardening — defensive admissibility re-assertion + coverage

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.
This commit is contained in:
Shay 2026-06-06 06:31:54 -07:00
parent be2d4b487e
commit 6816e6220c
4 changed files with 127 additions and 8 deletions

View file

@ -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`):

View file

@ -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

View file

@ -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
# --------------------------------------------------------------------------- #

View file

@ -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