Commit graph

4 commits

Author SHA1 Message Date
Shay
3b30eb248a
feat(binding-graph): Phase 4 question-target binding (ADR-0135) (#179)
Refines BoundUnknown from "the symbol whose value the solver determines"
to "the symbol at a specific temporal/state index with a specific
question-form". Two new required fields on BoundUnknown — state_index
(initial/terminal/Operation(operation_index)) and question_form
(count/rate/total/difference/ratio/identity) — populated by the new
pure-function resolver in generate/binding_graph/question_target.py.

The adapter (ADR-0133) now delegates Unknown -> BoundUnknown construction
to bound_unknown_from_math_problem_graph. No runtime wiring, no solver
invocation. Phase 5 (bounded-grammar / B3 integration) remains deferred.

Refusal-first via the new QuestionTargetError (sibling of AdapterError /
AdmissibilityError). Closed reason vocab: not_a_math_problem_graph,
unknown_entity_not_in_entities, apply_rate_unit_mismatch,
unmappable_question_form. Closed precedence rule on question_form
documented in ADR-0135 (compare_multiplicative > compare_additive >
apply_rate{numerator|denominator unit-match} > count); ambiguity refuses.

SemanticSymbolicBindingGraph.__post_init__ gains a cross-collection
guard: Operation(operation_index) must satisfy operation_index <
len(equations). canonical_string emission widened to include
state=... form=... tokens (hash differs from Phase 3 main by design —
not a regression; byte-equal across runs preserved).

Parents: ADR-0132 / ADR-0133 / ADR-0134.

Tests: +70 new (45 unit in test_binding_graph_question_target.py +
25 integration in test_binding_graph_adapter_question_target.py); 5
Phase 1+3 BoundUnknown fixtures migrated. Total binding-graph lane
295/1 pass (1 pre-existing test_symbol_binding_uses_slots failure on
Python 3.14, unrelated to Phase 4 — exists on origin/main). Pyright
clean on new and modified files. No edits to algebra/, chat/, core/,
or runtime hot path. Field invariant untouched.
2026-05-23 11:24:49 -07:00
Shay
6cbaa74076
feat(binding-graph): Phase 3 unit-aware admissibility (ADR-0134) (#176)
Wires deterministic, refusal-first dimensional analysis into the
binding-graph adapter. Every BoundEquation emitted by
bind_math_problem_graph now carries either admissibility_status='admitted'
+ populated unit_proof or admissibility_status='refused' + typed
refusal_reason. No silent coercion; no invented units; no solver.

Adds:
- generate/binding_graph/units.py — pure unit algebra over a 6-dim
  integer exponent vector (length, time, mass, money, count,
  temperature). Closed vocabulary loaded once from en_units_v1
  (ADR-0127) and memoized; composite "<num>_per_<denom>" resolved
  recursively; conservative depluralization; refusal-first.
- generate/binding_graph/admissibility.py — check_admissibility with
  per-operation-kind dispatch over the closed 8-string vocab, typed
  AdmissibilityError (closed reason set), frozen UnitProof.
- ADR-0134 documenting the contract, invariants, and Phase 4-5
  deferrals.

Adapter changes are surgical: synthesizes operand-literal symbols where
the verifier needs them (op<NNN>__multiplicand / __divisor / __rate),
then stamps each equation via check_admissibility. Input/output types
unchanged; bind_math_problem_graph still byte-equal across runs.

Tests: 226 total in the binding-graph lane (110 Phase 1+2 still pass; 47
units + 40 admissibility + 29 adapter-units new). Pyright clean on all
new files. No runtime wiring outside generate/binding_graph/.

Phase 4 (question-target binding) and Phase 5 (B3 / bounded grammar)
remain deferred per the brief.
2026-05-23 11:07:05 -07:00
Shay
5b668cc866
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).
2026-05-23 10:45:15 -07:00
Shay
980213ed62
feat(binding-graph): Phase 1 data model (ADR-0132) (#171)
Frozen dataclasses + deterministic allocator + invariants for the
Semantic-Symbolic Binding Graph proposed in PR #170. Pure data layer:
no parser, no solver, no adapter, no runtime wiring. Phases 2-5
deferred to follow-up PRs.

- generate/binding_graph/model.py: SourceSpanLink, SymbolBinding,
  BoundFact, BoundEquation, BoundUnknown, BoundConstraint, and the
  top-level SemanticSymbolicBindingGraph container. All
  @dataclass(frozen=True, slots=True). Refusal-first construction via
  typed BindingGraphError. Cross-collection referential integrity
  enforced at __post_init__.
- generate/binding_graph/allocation.py: pure deterministic
  allocate_symbols() — same input order yields byte-equal output.
- generate/binding_graph/__init__.py: public API surface.
- tests/test_binding_graph_model.py: 69 tests covering frozen
  invariants, slots enforcement, refusal paths, allocation
  determinism, canonical-string round-trip, cross-collection
  integrity.
- docs/decisions/ADR-0132-binding-graph-data-model.md: ratifies
  Phase 1 only; explicit Phase 2-5 deferred section citing #170.
2026-05-23 10:29:59 -07:00