test(adr-0182): real guard for exempt commit-ineligibility (was vacuous) (#485)

The wrong=0-critical clause in pool.resolve_pooled (an exempt-only answer
never commits; a complete reading is required to resolve) had no test that
failed when it was removed: the _EXEMPT_ONLY fixture's pool also contained a
complete product (20*3*5=300), so refusal came from the disagreement rule,
not commit-ineligibility. Mutation-disabling the clause left all tests green.

Inject a single-exempt pool directly (the aggressive composers manufacture a
competing complete product for any natural text, so a corpus fixture cannot
isolate the branch). Removing the clause now commits 25 and fails loudly.
Rename the old fixture/test to state honestly that it refuses via disagreement.
This commit is contained in:
Shay 2026-05-29 14:53:52 -07:00 committed by GitHub
parent 6ed8fc21fc
commit d8b37069c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -15,6 +15,7 @@ Sealed lane: ``chat/`` does not import these; serving ``3/47/0`` cannot move.
from __future__ import annotations
from generate.derivation import pool
from generate.derivation.accumulate import accumulation_candidates
from generate.derivation.model import GroundedDerivation, Quantity, Step
from generate.derivation.pool import resolve_pooled
@ -25,9 +26,13 @@ _DISTRACTOR_0014 = (
"Kate has 20 pencils. She studies for 3 hours and then buys 5 more pencils. "
"How many pencils does Kate have?"
)
# A distractor with NO multiplicative cue: the only candidate is the exempt additive
# reading (no complete product exists). The commit-ineligibility rule must refuse it.
_EXEMPT_ONLY = (
# A distractor whose pool contains an exempt additive reading (20+5=25, "3 hours"
# unused) AND a complete product (20*3*5=300) — three distinct answers, so refusal
# here comes from the DISAGREEMENT rule, not commit-ineligibility. (The aggressive
# composers manufacture the product from any such text, which is exactly why a
# natural fixture cannot isolate the commit-ineligibility branch — see
# test_exempt_only_never_commits, which injects a single-exempt pool directly.)
_EXEMPT_PLUS_PRODUCT = (
"Kate has 20 pencils. She rests for 3 hours and buys 5 more pencils."
)
_CLEAN_ACCUMULATION = "Sam has 14 apples. He buys 9 more apples."
@ -86,17 +91,36 @@ class TestResolvePooled:
# product 300 (complete) vs additive 25 (exempt) disagree -> refuse.
assert resolve_pooled(_DISTRACTOR_0014) is None
def test_exempt_only_never_commits(self) -> None:
# THE wrong=0-critical obligation. The only verifying reading here is the
# exempt additive one (no multiplicative cue -> no competing product), so a
# commit would be on an incomplete reading. The commit-ineligibility rule
# must refuse. Flipping `exempt` to commit-eligible makes this commit 25 and
# this test fails loudly.
assert accumulation_candidates(_EXEMPT_ONLY), "expected an exempt candidate"
def test_extra_exempt_readings_do_not_break_refusal(self) -> None:
# A pool carrying an exempt additive reading alongside the complete product
# still refuses (three distinct answers -> disagreement). Guards that the
# exempt class does not accidentally suppress the disagreement rule.
assert accumulation_candidates(_EXEMPT_PLUS_PRODUCT), "expected a candidate"
assert classify_derivation(
accumulation_candidates(_EXEMPT_ONLY)[-1], _EXEMPT_ONLY
accumulation_candidates(_EXEMPT_PLUS_PRODUCT)[-1], _EXEMPT_PLUS_PRODUCT
) == "exempt"
assert resolve_pooled(_EXEMPT_ONLY) is None
assert resolve_pooled(_EXEMPT_PLUS_PRODUCT) is None
def test_exempt_only_never_commits(self, monkeypatch) -> None:
# THE wrong=0-critical obligation, isolated. A pool whose ONLY verifying
# reading is exempt — a single distinct answer with no `complete` reading —
# must refuse on commit-ineligibility (pool.resolve_pooled requires a
# `complete` candidate to commit; an exempt-only answer never commits).
#
# The aggressive composers synthesise a competing `complete` product for any
# natural text (see test_extra_exempt_readings_do_not_break_refusal), so a
# corpus fixture cannot isolate this branch. We inject a single-exempt pool
# directly. Removing the commit-ineligibility clause makes this commit 25 and
# fails loudly; it is otherwise unguarded.
exempt = GroundedDerivation(
start=Quantity(20.0, "pencils", "20"),
steps=(Step(op="add", operand=Quantity(5.0, "pencils", "5"), cue="more"),),
)
assert classify_derivation(exempt, _DISTRACTOR_0014) == "exempt"
monkeypatch.setattr(pool, "pooled_candidates", lambda *_a: [exempt])
# one distinct answer (25), zero `complete` readings -> commit-ineligibility
# is the only clause that can refuse here.
assert resolve_pooled(_DISTRACTOR_0014) is None
def test_deterministic(self) -> None:
assert resolve_pooled(_DISTRACTOR_0014) == resolve_pooled(_DISTRACTOR_0014)