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/__init__.py b/generate/realize/__init__.py index 2ed39792..a2ac0e86 100644 --- a/generate/realize/__init__.py +++ b/generate/realize/__init__.py @@ -1,5 +1,6 @@ """REALIZE — integrate comprehended structure into the held self (roadmap Step 3).""" +from generate.realize.quantitative import realize_quantitative from generate.realize.realize import ( NotRealized, Realized, @@ -13,5 +14,6 @@ __all__ = [ "Realized", "RealizedRecord", "realize_comprehension", + "realize_quantitative", "recall_realized", ] diff --git a/generate/realize/quantitative.py b/generate/realize/quantitative.py new file mode 100644 index 00000000..62b66731 --- /dev/null +++ b/generate/realize/quantitative.py @@ -0,0 +1,130 @@ +"""REALIZE R1c — a comprehended arithmetic structure becomes a realized vault entry. + +The quantitative comprehension path (``generate/quantitative_comprehension.py``) +reads arithmetic prose into a ``SemanticSymbolicBindingGraph`` whose equations were +already admissibility-checked (unreadable input short-circuits to a ``Refusal`` +upstream). This realizes that binding_graph as a SPECULATIVE, structurally-recallable +vault entry — the SECOND substrate behind the shared ``structure_kind`` record (the +first is the meaning_graph relation, R0/R1). + +Correctness rests on the STRUCTURAL key, never the field versor: the binding_graph +entities (``alice``, the synthesized ``total``) are symbolic/OOV, so the placement +versor is deterministic-GIVEN-session-state (and ``total`` is a maximally-colliding +OOV name) — distinctness is carried by ``structure_key`` / ``content_hash`` + +structural recall (``recall.py``), and reboot-stability by the Shape B+ snapshot of +these exact bytes, not by re-deriving the versor. +""" + +from __future__ import annotations + +import numpy as np + +from formation.hashing import sha256_of +from generate.binding_graph.model import SemanticSymbolicBindingGraph +from generate.meaning_graph.reader import Refusal +from generate.quantitative_comprehension import QuantComprehension +from session.context import SessionContext +from teaching.epistemic import EpistemicStatus + +from .realize import _GROUNDING_FAILURES, NotRealized, Realized, _realize_structured + +_STRUCTURE_KIND_BINDING_GRAPH = "binding_graph" + + +def _binding_graph_structure_key(bg: SemanticSymbolicBindingGraph) -> str: + """Span-FREE structural identity of a binding graph: symbols, facts, and + equations with source offsets stripped (the quantitative reader's spans are + name-derived constants, so this is also source-stable). Sorted at every level + for a canonical, order-independent key.""" + return sha256_of( + { + "structure_kind": _STRUCTURE_KIND_BINDING_GRAPH, + "symbols": sorted( + [s.symbol_id, s.semantic_role, s.unit or ""] for s in bg.symbols + ), + "facts": sorted([f.symbol_id, f.value, f.unit or ""] for f in bg.facts), + "equations": sorted( + [e.lhs_symbol_id, e.rhs_canonical, e.operation_kind, sorted(e.dependencies)] + for e in bg.equations + ), + } + ) + + +def realize_quantitative( + comprehension: QuantComprehension | Refusal, ctx: SessionContext +) -> Realized | NotRealized: + """Realize an arithmetic comprehension's binding_graph into ``ctx``'s vault. + + 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") + if not isinstance(comprehension, QuantComprehension): + return NotRealized("not_a_quant_comprehension") + + 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 + # correctness. ``probe_ingest`` of an OOV token mutates the shared vocab via + # insert_transient (session-scoped, excluded from the snapshot); a non-versor + # construction raises and is caught → NotRealized. + try: + field_state = ctx.probe_ingest([comprehension.query.entity]) + except _GROUNDING_FAILURES: + return NotRealized("grounding_failed") + versor = np.asarray(field_state.F, dtype=np.float32) + + structure_canonical = bg.to_canonical_string() + # ``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 + ] + source_span = ";".join(sorted(source_spans)) + replay_hash = sha256_of( + { + "content_hash": content_hash, + "source_span": source_span, + "epistemic_status": status.value, + } + ) + entity_names = tuple(sorted(s.name for s in bg.symbols)) + + return _realize_structured( + ctx, + structure_kind=_STRUCTURE_KIND_BINDING_GRAPH, + structure_canonical=structure_canonical, + relation_predicate="", # meaning_graph-specific; binding graphs are multi-relation + relation_arguments=(), + entity_names=entity_names, + source_span=source_span, + content_hash=content_hash, + structure_key=structure_key, + versor=versor, + replay_hash=replay_hash, + status=status, + ) diff --git a/generate/realize/realize.py b/generate/realize/realize.py index a9528e43..c07a1cde 100644 --- a/generate/realize/realize.py +++ b/generate/realize/realize.py @@ -9,10 +9,12 @@ A realized record is NOT a new store — it is a structured vault entry stamping, and bit-exact Shape B+ persistence for free (see ``docs/analysis/REALIZE-scope-2026-06-06.md``). -Slice R0 was deliberately boring (everything else grows from this without corrupting -the field): one told fact, SPECULATIVE only, in-vocab subject only, single non-negated -declarative relation. Slice R1 (this module + ``recall.py``) adds the relation-space -KEY R0 lacked: ordered ``relation_arguments`` + a span-free ``structure_key`` so +Slice R0 was deliberately boring: one told fact, SPECULATIVE only, in-vocab subject +only, single non-negated declarative relation. The in-vocab restriction is now LIFTED +(OOV subjects realize too): OOV grounding is deterministic, reboot-stable, and injective +(#591), and correctness rests on the structural key, not the versor. Slice R1 (this +module + ``recall.py``) adds the relation-space KEY R0 lacked: ordered +``relation_arguments`` + a span-free ``structure_key`` so distinct facts about one subject stay distinct (they collide on the field versor), and ``recall_realized`` retrieves them by exact structural metadata. Dedup is now span-free (``structure_key``), guarded by an ambiguous-entity-name refusal (wrong=0). Explicitly @@ -129,18 +131,22 @@ def realize_comprehension( return NotRealized("ungrounded_subject") # defensive — graph invariant subject_token = entity.name - # In-vocab gate: OOV grounding is NOT reproducible across reboots, so an OOV - # subject cannot be realized deterministically yet (R0 scope; a substrate gap). - try: - ctx.vocab.index_of(subject_token) - except (KeyError, IndexError): - return NotRealized("oov_subject") - - # Side-effect-free embedding (no state/vault/referent mutation). Closure stays - # algebra/versor.py's job — REALIZE adds no normalization. NOTE: the field point - # composes with the CURRENT session state, so placement is deterministic for a - # GIVEN session state (the fact is realized at the held self's current field - # position), not purely subject-determined. + # OOV subjects ARE realizable (R0's in-vocab gate is lifted). OOV grounding is + # deterministic and reboot-stable, and #591 makes it injective (distinct token + # content -> distinct field point, closure-by-construction). Correctness does not + # rest on the versor anyway: distinct facts stay distinct by their structural key + # + structural recall (recall.py), and reboot-stability rests on the Shape B+ + # snapshot of the exact stored bytes — so even a colliding placement never + # confuses recall. + # + # probe_ingest is side-effect-free for an IN-VOCAB token, but for an OOV token it + # mutates the shared vocab via insert_transient — a SESSION-SCOPED transient that + # is NOT serialized into the snapshot (session/context.py: vocab is a shared + # ratified surface, not session state), so it is re-derived deterministically on + # reboot and never affects reboot stability. The field point still composes with + # the CURRENT session state (deterministic GIVEN that state, not purely + # subject-determined). Closure stays algebra/versor.py's job — REALIZE adds none. + # A degenerate construction raises and is caught below -> NotRealized. try: field_state = ctx.probe_ingest([subject_token]) except _GROUNDING_FAILURES: @@ -176,20 +182,56 @@ def realize_comprehension( ) entity_names = tuple(sorted(e.name for e in graph.entities)) - # Idempotency: a re-told fact (same span-free structure_key) does not grow the - # vault. Refusing duplicate entity names above keeps the name-keyed key - # injective, so dedup only ever collapses the genuinely-same proposition — - # never drops a distinct one. ``iter_metadata`` is the public read-only - # accessor; ``idx`` is the LIVE deque position. + return _realize_structured( + ctx, + structure_kind=_STRUCTURE_KIND_MEANING_GRAPH, + structure_canonical=structure_canonical, + relation_predicate=rel.predicate, + relation_arguments=relation_arguments, + entity_names=entity_names, + source_span=source_span, + content_hash=content_hash, + structure_key=structure_key, + replay_hash=replay_hash, + versor=versor, + status=status, + ) + + +def _realize_structured( + ctx: SessionContext, + *, + structure_kind: str, + structure_canonical: str, + relation_predicate: str, + relation_arguments: tuple[str, ...], + entity_names: tuple[str, ...], + source_span: str, + content_hash: str, + structure_key: str, + replay_hash: str, + versor: np.ndarray, + status: EpistemicStatus, +) -> Realized: + """Dedup-or-store a realized record — the single wrong=0 write path shared by + every substrate (meaning_graph relations, binding_graph quantities). + + Idempotency dedups exactly on the span-free ``structure_key`` (callers refuse + ambiguous identity before getting here, so a hit is always the genuinely-same + proposition, never a distinct one). The ``vault.store`` call is the only + mutation; ``iter_metadata`` is the public read-only accessor and ``idx`` is the + LIVE deque position. The epistemic status is whatever the caller declared — + SPECULATIVE, never COHERENT by default (ADR-0021). + """ for idx, meta in ctx.vault.iter_metadata(): if meta.get("kind") == "realized" and meta.get("structure_key") == structure_key: return Realized(record=_record_from_metadata(meta, idx), created=False) metadata = { "kind": "realized", - "structure_kind": _STRUCTURE_KIND_MEANING_GRAPH, + "structure_kind": structure_kind, "structure_canonical": structure_canonical, - "relation_predicate": rel.predicate, + "relation_predicate": relation_predicate, "relation_arguments": list(relation_arguments), "entity_names": list(entity_names), "source_span": source_span, @@ -201,9 +243,9 @@ def realize_comprehension( vault_index = ctx.vault.store(versor, metadata, epistemic_status=status) return Realized( record=RealizedRecord( - structure_kind=_STRUCTURE_KIND_MEANING_GRAPH, + structure_kind=structure_kind, structure_canonical=structure_canonical, - relation_predicate=rel.predicate, + relation_predicate=relation_predicate, relation_arguments=relation_arguments, entity_names=entity_names, source_span=source_span, diff --git a/tests/test_realize_oov.py b/tests/test_realize_oov.py new file mode 100644 index 00000000..d579a3ed --- /dev/null +++ b/tests/test_realize_oov.py @@ -0,0 +1,119 @@ +"""REALIZE — OOV subjects are realizable (the in-vocab gate is lifted). + +R0 declined OOV subjects on the (mistaken) belief that OOV grounding is +non-deterministic. It is in fact deterministic and reboot-stable, and #591 makes it +injective. These tests pin the consumer half: an OOV subject realizes a normal +SPECULATIVE record, distinct OOV facts stay distinct, and the fact survives reboot — +crucially via the VAULT RECORD, not the (session-scoped, snapshot-excluded) vocab +transient. They assert STRUCTURAL distinctness / stability (names + content_hash + +restored bytes), which holds regardless of whether the versor collides — versor +injectivity itself is #591's own test. +""" + +from __future__ import annotations + +import pytest + +from algebra.versor import versor_condition +from chat.runtime import ChatRuntime +from generate.meaning_graph.reader import comprehend +from generate.realize import NotRealized, Realized, realize_comprehension, recall_realized +from session.context import SessionContext + +_HIGH_INTERVAL = 10**9 + + +@pytest.fixture(scope="module") +def vocab_persona(): + rt = ChatRuntime(no_load_state=True) + return rt._context.vocab, rt._context.persona + + +def _ctx(vocab_persona) -> SessionContext: + vocab, persona = vocab_persona + return SessionContext(vocab=vocab, persona=persona, vault_reproject_interval=_HIGH_INTERVAL) + + +def _realize(text: str, ctx: SessionContext): + return realize_comprehension(comprehend(text), ctx) + + +def test_oov_subject_realizes(vocab_persona) -> None: + ctx = _ctx(vocab_persona) + res = _realize("Rhea is a raven.", ctx) # "rhea"/"raven" are OOV + assert isinstance(res, Realized) and res.created is True + assert res.record.relation_arguments == ("rhea", "raven") + assert res.record.epistemic_status == "speculative" + got = recall_realized(ctx, subject="rhea") + assert len(got) == 1 and got[0].content_hash == res.record.content_hash + + +def test_distinct_oov_subjects_stay_distinct(vocab_persona) -> None: + # Even if the field versor were to collide (pre-#591 substrate), the structural + # key keeps distinct OOV facts distinct — correctness never rests on the versor. + ctx = _ctx(vocab_persona) + _realize("Rhea is a raven.", ctx) + _realize("Zorg is a planet.", ctx) + assert len(ctx.vault._metadata) == 2 + assert len({m["structure_key"] for m in ctx.vault._metadata}) == 2 + assert recall_realized(ctx, subject="rhea")[0].relation_arguments == ("rhea", "raven") + assert recall_realized(ctx, subject="zorg")[0].relation_arguments == ("zorg", "planet") + + +def test_oov_fact_is_idempotent(vocab_persona) -> None: + ctx = _ctx(vocab_persona) + first = _realize("Rhea is a raven.", ctx) + second = _realize("Rhea is a raven.", ctx) + assert isinstance(first, Realized) and first.created is True + assert isinstance(second, Realized) and second.created is False + assert len(ctx.vault._metadata) == 1 + + +def test_oov_placement_deterministic_across_fresh_contexts(vocab_persona) -> None: + # OOV grounding is deterministic: the same OOV fact realized in two independent + # fresh contexts (same shared vocab) yields identical content_hash AND identical + # stored versor bytes. + a = _ctx(vocab_persona) + b = _ctx(vocab_persona) + ra = _realize("Rhea is a raven.", a) + rb = _realize("Rhea is a raven.", b) + assert isinstance(ra, Realized) and isinstance(rb, Realized) + assert ra.record.content_hash == rb.record.content_hash + va = a.vault._versors[ra.record.vault_index].tobytes() + vb = b.vault._versors[rb.record.vault_index].tobytes() + assert va == vb + + +def test_oov_fact_survives_reboot_into_a_FRESH_vocab(vocab_persona) -> None: + # The strongest reboot claim: restore into a context built on a DIFFERENT vocab + # instance (no "rhea" transient present) — the realized fact is still recalled + # structurally and its versor bytes are restored exactly. Proves reboot-stability + # rests on the VAULT RECORD, not on the session-scoped vocab transient. + ctx = _ctx(vocab_persona) + res = _realize("Rhea is a raven.", ctx) + assert isinstance(res, Realized) + pre_versor_bytes = ctx.vault._versors[res.record.vault_index].tobytes() + snap = ctx.snapshot() + + fresh = ChatRuntime(no_load_state=True) # a brand-new vocab/persona surface + rebooted = SessionContext( + vocab=fresh._context.vocab, + persona=fresh._context.persona, + vault_reproject_interval=_HIGH_INTERVAL, + ) + rebooted.restore(snap) + + got = recall_realized(rebooted, subject="rhea") + assert len(got) == 1 + assert got[0].content_hash == res.record.content_hash + post_versor = rebooted.vault._versors[got[0].vault_index] + assert post_versor.tobytes() == pre_versor_bytes # exact restore, no reprojection + assert versor_condition(post_versor) < 1e-6 + + +def test_ineligible_oov_input_still_realizes_nothing(vocab_persona) -> None: + # Lifting the in-vocab gate does NOT loosen the other wrong=0 floors. + ctx = _ctx(vocab_persona) + assert isinstance(_realize("Is rhea a raven?", ctx), NotRealized) # query + assert isinstance(_realize("Rhea is a raven. Zorg is a planet.", ctx), NotRealized) # multi + assert len(ctx.vault._metadata) == 0 diff --git a/tests/test_realize_r0.py b/tests/test_realize_r0.py index 82102099..709568bf 100644 --- a/tests/test_realize_r0.py +++ b/tests/test_realize_r0.py @@ -62,13 +62,16 @@ def test_multi_relation_realizes_nothing(vocab_persona) -> None: assert len(ctx.vault._metadata) == 0 -def test_oov_subject_realizes_nothing(vocab_persona) -> None: - # "rhea" is out-of-vocabulary; OOV grounding is not reproducible across reboots, - # so R0 declines rather than realize a non-deterministic point. +def test_oov_subject_is_realized(vocab_persona) -> None: + # The in-vocab gate is LIFTED: OOV grounding is deterministic, reboot-stable, and + # injective (#591), and correctness rests on the structural key (not the versor), + # so an OOV subject realizes a normal SPECULATIVE record. ctx = _ctx(vocab_persona) res = _realize("Rhea is a raven.", ctx) - assert isinstance(res, NotRealized) and res.reason == "oov_subject" - assert len(ctx.vault._metadata) == 0 + assert isinstance(res, Realized) and res.created is True + assert res.record.relation_arguments[0] == "rhea" + assert res.record.epistemic_status == "speculative" + assert len(ctx.vault._metadata) == 1 def test_single_in_vocab_declarative_is_realized(vocab_persona) -> None: @@ -87,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) # --------------------------------------------------------------------------- # @@ -97,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 new file mode 100644 index 00000000..7b5de9fd --- /dev/null +++ b/tests/test_realize_r1c_binding_graph.py @@ -0,0 +1,231 @@ +"""REALIZE slice R1c — a comprehended arithmetic structure (binding_graph) is +realized as a SPECULATIVE, structurally-recallable vault entry. + +This is the SECOND substrate behind the shared ``structure_kind`` record. Its +entities (``alice``, the synthesized ``total``) are symbolic/OOV, so correctness +rests on the structural key + structural recall, never the (colliding) field +versor — and reboot-stability rests on the Shape B+ snapshot of the exact bytes. +""" + +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, +) +from session.context import SessionContext +from teaching.epistemic import EpistemicStatus + +_HIGH_INTERVAL = 10**9 + +_FACT = "alice has 3 coins. how many coins does alice have?" +_SUM = ( + "alice has 3 coins. bob has 2 more coins than alice. " + "how many coins do alice and bob have?" +) + + +@pytest.fixture(scope="module") +def vocab_persona(): + rt = ChatRuntime(no_load_state=True) + return rt._context.vocab, rt._context.persona + + +def _ctx(vocab_persona) -> SessionContext: + vocab, persona = vocab_persona + return SessionContext(vocab=vocab, persona=persona, vault_reproject_interval=_HIGH_INTERVAL) + + +def _realize(text: str, ctx: SessionContext): + return realize_quantitative(comprehend_quantitative(text), ctx) + + +# --------------------------------------------------------------------------- # +# wrong=0 — a Refusal realizes nothing +# --------------------------------------------------------------------------- # + + +def test_refusal_realizes_nothing(vocab_persona) -> None: + ctx = _ctx(vocab_persona) + res = realize_quantitative(comprehend_quantitative("the weather is nice."), ctx) + assert isinstance(res, NotRealized) and res.reason == "refusal" + assert len(ctx.vault._metadata) == 0 + + +# --------------------------------------------------------------------------- # +# A told arithmetic fact is realized as a binding_graph record +# --------------------------------------------------------------------------- # + + +def test_arithmetic_fact_is_realized(vocab_persona) -> None: + ctx = _ctx(vocab_persona) + res = _realize(_FACT, ctx) + assert isinstance(res, Realized) and res.created is True + r = res.record + assert r.structure_kind == "binding_graph" + assert r.epistemic_status == "speculative" # never COHERENT by default + assert "alice" in r.entity_names + assert r.structure_canonical and r.content_hash and r.structure_key and r.replay_hash + assert r.source_span # provenance present + assert len(ctx.vault._metadata) == 1 + + +def test_sum_problem_is_realized_with_synthesized_total(vocab_persona) -> None: + ctx = _ctx(vocab_persona) + res = _realize(_SUM, ctx) + assert isinstance(res, Realized) and res.created is True + # the synthesized "total" entity and the real ones are all captured + assert set(res.record.entity_names) >= {"alice", "bob", "total"} + + +# --------------------------------------------------------------------------- # +# Structural recall (R1a) finds binding_graph records by kind / entity +# --------------------------------------------------------------------------- # + + +def test_recall_by_structure_kind_and_entity(vocab_persona) -> None: + ctx = _ctx(vocab_persona) + _realize(_FACT, ctx) + got = recall_realized(ctx, structure_kind="binding_graph") + assert len(got) == 1 + assert recall_realized(ctx, structure_kind="binding_graph", entity="alice")[0].content_hash == ( + got[0].content_hash + ) + # the meaning_graph subject filter does not match a binding_graph record + assert recall_realized(ctx, subject="alice") == () + + +# --------------------------------------------------------------------------- # +# Idempotency — same arithmetic structure dedups +# --------------------------------------------------------------------------- # + + +def test_arithmetic_realize_is_idempotent(vocab_persona) -> None: + ctx = _ctx(vocab_persona) + first = _realize(_FACT, ctx) + second = _realize(_FACT, ctx) + assert isinstance(first, Realized) and first.created is True + assert isinstance(second, Realized) and second.created is False + assert second.record.structure_key == first.record.structure_key + assert len(ctx.vault._metadata) == 1 + + +def test_distinct_arithmetic_not_collapsed(vocab_persona) -> None: + ctx = _ctx(vocab_persona) + _realize(_FACT, ctx) + _realize(_SUM, ctx) # different facts/equations -> different structure_key + assert len(ctx.vault._metadata) == 2 + assert len({m["structure_key"] for m in ctx.vault._metadata}) == 2 + + +# --------------------------------------------------------------------------- # +# Exit gate — realize -> snapshot -> reboot -> structural recall, byte-exact +# --------------------------------------------------------------------------- # + + +def test_binding_graph_survives_reboot_and_recalls_structurally(vocab_persona) -> None: + vocab, persona = vocab_persona + ctx = _ctx(vocab_persona) + res = _realize(_FACT, ctx) + assert isinstance(res, Realized) + pre_versor_bytes = ctx.vault._versors[res.record.vault_index].tobytes() + + rebooted = SessionContext(vocab=vocab, persona=persona, vault_reproject_interval=_HIGH_INTERVAL) + rebooted.restore(ctx.snapshot()) + + got = recall_realized(rebooted, structure_kind="binding_graph", entity="alice") + assert len(got) == 1 + assert got[0].content_hash == res.record.content_hash + assert got[0].structure_key == res.record.structure_key + # exact bytes restored (no reprojection on load) and still a valid versor + post_versor = rebooted.vault._versors[got[0].vault_index] + assert post_versor.tobytes() == pre_versor_bytes + assert versor_condition(post_versor) < 1e-6 + + +def test_binding_graph_record_not_admitted_as_evidence(vocab_persona) -> None: + # SPECULATIVE: a realized arithmetic fact is a candidate, not evidence. + ctx = _ctx(vocab_persona) + res = _realize(_FACT, ctx) + assert isinstance(res, Realized) + 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