"""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, single_unknown 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") # The question target is the graph's sole BoundUnknown (PR-1/PR-3); refuse a graph # that does not carry exactly one rather than picking one. target = single_unknown(bg) if target is None: return NotRealized("no_single_question_target") # 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([target.symbol_id]) 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, )