Completes the PR-1 migration. The question target now has a single source of truth:
the binding-graph's sole BoundUnknown. The sidecar QuantQuery dataclass + the
QuantComprehension.query field are DELETED.
- New helper single_unknown(graph) -> BoundUnknown | None: returns the sole target, or
None on a graph that does not carry exactly one. Zero unknowns (no question) and
multiple unknowns (ambiguous) both REFUSE — the consumer must never pick one.
- to_relational_metric reads the query from single_unknown(graph) (refuses on None).
- realize_quantitative reads the asked symbol from single_unknown(bg) (NotRealized on None).
- Tests: the .query assertions move to single_unknown; new malformed-graph tests prove
0 and >1 unknowns REFUSE rather than pick one (the wrong=0 boundary).
Byte-identical where it matters: relational_metric answer lane 15/15 wrong=0, setup-oracle
15/15 setup_wrong=0, realize-binding-graph + architectural invariants green. No serving
path touched. No dangling QuantQuery reference remains.
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.
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.