feat(reader-arc): compare compiler-side guards for inverse + chained shapes
Two shapes (PR ruling) pinned compiler-side with tests — reader direction errors surface as refusals, never wrongs: - Chained / ordering: a compare whose reference is neither seeded nor already-defined at that point refuses (compare_reference_not_a_register). Pinned explicitly with a test (story order != dependency order), not left to fall out of existing checks. - Inverse mis-direction: a compare that would DEFINE an already-known (seeded or earlier-defined) register refuses (compare_redefines_register) — the reader must bind the unknown side as the target with a fraction factor; the compiler never overwrites a known quantity. The 5 real compare cases still solve wrong=0. 27/27 (2 new guards + 25). [Verification]: uv run python -m pytest tests/test_adr_0250_compare.py tests/test_adr_0250_multi_register.py tests/test_adr_0250_summation.py -q
This commit is contained in:
parent
cd0df2019f
commit
260e56ca5c
2 changed files with 40 additions and 3 deletions
|
|
@ -208,9 +208,16 @@ def compile_multi_register_program(graph: MathProblemGraph) -> MultiRegisterProg
|
|||
raise MultiRegisterError("compare_factor_not_positive", factor=factor)
|
||||
if op.actor == reference:
|
||||
raise MultiRegisterError("compare_self_reference", actor=op.actor)
|
||||
if op.actor not in units: # the comparison DEFINES the actor register
|
||||
units[op.actor] = units[reference] # unit propagation from the reference
|
||||
order.append(op.actor)
|
||||
# The comparison DEFINES the actor. If the actor is already known
|
||||
# (seeded or defined earlier), this is a redefine — most often a
|
||||
# mis-directed inverse compare (the known side used as the target).
|
||||
# Refuse rather than overwrite: a reader direction error becomes a
|
||||
# refusal, never a wrong (the reader must bind the unknown side as
|
||||
# the target, with a fraction factor).
|
||||
if op.actor in units:
|
||||
raise MultiRegisterError("compare_redefines_register", actor=op.actor)
|
||||
units[op.actor] = units[reference] # unit propagation from the reference
|
||||
order.append(op.actor)
|
||||
turns.append(
|
||||
RegisterTurn(_COMPARE_MULT, op.actor, factor, 0.0, factor, reference=reference)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -153,6 +153,36 @@ def test_summation_record_has_no_source_entity() -> None:
|
|||
# --- Fail-closed refusals ----------------------------------------------------
|
||||
|
||||
|
||||
def test_refuses_chained_reference_before_definition() -> None:
|
||||
# A := 2×B, but B is never seeded or defined at that point (story order ≠
|
||||
# dependency order): fail-closed, don't guess. Pins the ordering rule.
|
||||
graph = MathProblemGraph(
|
||||
entities=("A", "B", "C"),
|
||||
initial_state=(InitialPossession("C", Quantity(5, "x")),),
|
||||
operations=(Operation("A", "compare_multiplicative", Comparison("B", None, 2.0, "times")),),
|
||||
unknown=Unknown("A", "x"),
|
||||
)
|
||||
with pytest.raises(MultiRegisterError):
|
||||
compile_multi_register_program(graph)
|
||||
|
||||
|
||||
def test_refuses_compare_redefining_known_register() -> None:
|
||||
# A seeded; "B is twice A" mis-directed so the target is the KNOWN side —
|
||||
# a redefine of a seeded register. Refuse (the reader must invert to the
|
||||
# unknown side); never overwrite a known quantity.
|
||||
graph = MathProblemGraph(
|
||||
entities=("A", "B"),
|
||||
initial_state=(
|
||||
InitialPossession("A", Quantity(5, "x")),
|
||||
InitialPossession("B", Quantity(9, "x")),
|
||||
),
|
||||
operations=(Operation("B", "compare_multiplicative", Comparison("A", None, 2.0, "times")),),
|
||||
unknown=Unknown(None, "x"),
|
||||
)
|
||||
with pytest.raises(MultiRegisterError):
|
||||
compile_multi_register_program(graph)
|
||||
|
||||
|
||||
def test_refuses_compare_additive() -> None:
|
||||
# compare_additive ("more"/"fewer") is a later increment — out of scope now.
|
||||
graph = MathProblemGraph(
|
||||
|
|
|
|||
Loading…
Reference in a new issue