feat(binding-graph): Phase 2 adapter from MathProblemGraph (ADR-0133) (#174)
Pure-function adapter `bind_math_problem_graph(g) ->
SemanticSymbolicBindingGraph` translating ADR-0115 `MathProblemGraph`
into the ADR-0132 binding-graph data model. Structural translation
only — no I/O, no parser/solver calls, no algebra, no numpy, no
runtime wiring.
Mapping discipline locked as module-level constants:
- each entity -> SymbolBinding(semantic_role="entity")
- each possession -> SymbolBinding(quantity) + BoundFact
- each Operation -> fresh result SymbolBinding + BoundEquation
(operation_kind verbatim passthrough on the
shared closed vocab)
- Unknown -> synthesized SymbolBinding(unknown) + BoundUnknown
Refusal-first: `g: object` boundary accepts any caller input and
refuses non-MathProblemGraph with typed AdapterError (sibling of
BindingGraphError). Cross-collection invariant failures (defensive,
should be unreachable) are re-raised as AdapterError so callers see a
single refusal type.
Phase 2 placeholders (closed in Phase 3+):
- BoundEquation.unit_proof = "deferred_to_phase_3"
- BoundEquation.admissibility_status = "pending"
Phase 3 (ADR-0134 unit-aware admissibility), Phase 4 (question-target
binding refinement), and Phase 5 (bounded-grammar / B3 integration)
explicitly deferred — see ADR.
Evidence:
- generate/binding_graph/adapter.py (pure functions)
- generate/binding_graph/__init__.py (public surface)
- tests/test_binding_graph_adapter.py — 41 tests (refusal-first, all
8 VALID_OPERATION_KINDS round-trip, dep wiring, introduction order,
hash-stability, frozen output, input immutability, placeholder
constants, cross-collection invariants)
- docs/decisions/ADR-0133-binding-graph-adapter.md
Lane: tests/test_binding_graph_model.py + tests/test_binding_graph_adapter.py
-> 110 passed, 0 failed. pyright clean on new files. Runtime
byte-identical to main (no runtime integration yet, by design).