Merge pull request #593 from AssetOverflow/feat/realize-r1c-oov-generalization
feat(realize): R1c + OOV — binding_graph substrate and OOV subjects
This commit is contained in:
commit
7f52be967f
7 changed files with 591 additions and 31 deletions
|
|
@ -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`):
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
130
generate/realize/quantitative.py
Normal file
130
generate/realize/quantitative.py
Normal file
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
119
tests/test_realize_oov.py
Normal file
119
tests/test_realize_oov.py
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
|
|
|||
231
tests/test_realize_r1c_binding_graph.py
Normal file
231
tests/test_realize_r1c_binding_graph.py
Normal file
|
|
@ -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
|
||||
Loading…
Reference in a new issue