diff --git a/evals/multi_register_program.py b/evals/multi_register_program.py index 4191bfcc..8878dc33 100644 --- a/evals/multi_register_program.py +++ b/evals/multi_register_program.py @@ -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) ) diff --git a/tests/test_adr_0250_compare.py b/tests/test_adr_0250_compare.py index e960e33d..57b9cfae 100644 --- a/tests/test_adr_0250_compare.py +++ b/tests/test_adr_0250_compare.py @@ -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(