core/generate/realize/quantitative.py
Shay be2d4b487e feat(realize): R1c + OOV — binding_graph substrate and OOV subjects
Generalizes what REALIZE accepts, on the structural-key foundation from #592.

R1c — binding_graph / arithmetic realize:
- `realize_quantitative(QuantComprehension | Refusal, ctx)` realizes a comprehended
  arithmetic structure (its admissibility-checked binding_graph) as a SPECULATIVE
  record with `structure_kind="binding_graph"` and a span-free `structure_key` over
  symbols/facts/equations. The only wrong=0 guard needed is the `Refusal` check —
  factless input is already a Refusal upstream (the fact-count gate would be vacuous).
- Extracts the shared `_realize_structured` write path so meaning_graph and
  binding_graph share ONE wrong=0 dedup/store point (the `.store` call stays in the
  INV-21-allowlisted realize.py).

OOV — lift the in-vocab subject gate:
- R0 declined OOV subjects on the mistaken belief that OOV grounding is
  non-deterministic. It is deterministic and reboot-stable, and #591 makes it
  injective. Correctness rests on the structural key, not the versor, so OOV subjects
  now realize normally. The "side-effect-free" comment is corrected: probe_ingest of
  an OOV token mutates the shared vocab via a session-scoped `insert_transient` that
  is NOT serialized into the snapshot, so reboot-stability rests on the vault record.

Honesty: the binding_graph entities (alice, the synthesized `total`) are symbolic/OOV
so the placement versor is deterministic-GIVEN-session-state, NOT subject-determined;
reboot-stability is carried by the Shape B+ snapshot of the exact bytes, and
distinctness by structural recall — never the (possibly colliding) metric. SPECULATIVE
always; versor_condition<1e-6 + exact CGA recall preserved; no parallel learning path.

Tests: test_realize_r1c_binding_graph.py (8) + test_realize_oov.py (6, incl. a
fresh-vocab reboot proving stability rests on the vault record not the transient) +
test_realize_r0.py oov test flipped to realize. Green: 35 realize + smoke locally.
2026-06-06 06:16:21 -07:00

115 lines
5 KiB
Python

"""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``). 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.
"""
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
# 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()
content_hash = sha256_of(structure_canonical)
structure_key = _binding_graph_structure_key(bg)
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,
)