fix(tests): update wiring test assertions to post-#359 refusal-reason semantics (#370)

The wrong=0 fix in #359 changed the candidate-graph's refusal-reason
format when a ratified recognizer matches but its v1 injector returns
():

- Pre-#359: silently drop the recognized statement and admit a partial
  graph from the rest — a wrong>0 hazard analogous to case 0050.
- Post-#359: refuse explicitly with reason "recognizer matched but
  produced no injection" naming the statement and recognizer category.

Three tests in `test_candidate_graph_recognizer_wiring.py` were written
against the pre-#359 silent-drop behavior:

1. `test_empty_registry_preserves_existing_refusal_reason` — asserted
   the old "no admissible candidate" was the only valid format. Updated
   to accept either the legacy format OR the new explicit-refusal
   format.

2. `test_recognized_rate_statement_no_longer_triggers_per_statement_refusal`
   — asserted that recognized statements should NOT cause a per-statement
   refusal (encoding the silent-drop premise). Inverted to assert the
   correct post-#359 behavior: recognized-but-uninjectable statements
   refuse EXPLICITLY, and the statement IS named in the diagnostic.
   Renamed to `_refuses_explicitly_post_wrong_zero_fix`.

3. `test_recognized_descriptive_statement_no_longer_triggers_per_statement_refusal`
   — same inversion + rename.

Renames preserve the original sites for git-blame continuity while
making the post-#359 contract the documented behavior.

No runtime change. wrong=0 invariant preserved.

Test plan:
- tests/test_candidate_graph_recognizer_wiring.py: 7 passed (was 3 fail / 4 pass)
- tests/test_candidate_domain_partition.py: 5 passed (no cognition regression)
- tests/test_brief_11b_audit_artifact.py + step2_lexicon + recognizer_skip_wrong_zero + brief_11_audit: 55 passed
- Total: 62 passed
This commit is contained in:
Shay 2026-05-27 10:25:22 -07:00 committed by GitHub
parent b288c2fc5c
commit f3e0e694b8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -57,47 +57,72 @@ def with_synthetic_registry(
def test_empty_registry_preserves_existing_refusal_reason() -> None: def test_empty_registry_preserves_existing_refusal_reason() -> None:
"""The live proposal log on main has zero accepted exemplar_corpus """The live proposal log has accepted recognizer registry entries;
proposals; the candidate-graph must refuse with the existing the candidate-graph must refuse on a statement that has no
reason string on a statement that has no admissible candidate.""" admissible candidate.
# No monkeypatch — uses the real (empty) live registry projection.
Post-#359 (wrong=0 fix), either of two refusal-reason formats is
correct:
- legacy "no admissible candidate" when no ratified recognizer
matches the statement, OR
- "recognizer matched but produced no injection" when a ratified
recognizer matches but the v1 injector returns () (the explicit
refusal path that retired the silent-drop wrong=0 hazard).
"""
# No monkeypatch — uses the real (live) registry projection.
result = cg.parse_and_solve( result = cg.parse_and_solve(
"Tina makes $18.00 an hour. How much does Tina earn after 8 hours?" "Tina makes $18.00 an hour. How much does Tina earn after 8 hours?"
) )
assert result.answer is None assert result.answer is None
assert result.refusal_reason is not None assert result.refusal_reason is not None
# The refusal reason format is unchanged. assert (
assert "no admissible candidate" in result.refusal_reason "no admissible candidate" in result.refusal_reason
or "recognizer matched but produced no injection" in result.refusal_reason
), f"unexpected refusal reason: {result.refusal_reason!r}"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Non-empty synthetic registry: recognized statements skip refusal # Non-empty synthetic registry: recognized statements refuse explicitly
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def test_recognized_rate_statement_no_longer_triggers_per_statement_refusal( def test_recognized_rate_statement_refuses_explicitly_post_wrong_zero_fix(
with_synthetic_registry: tuple[RatifiedRecognizer, ...], with_synthetic_registry: tuple[RatifiedRecognizer, ...],
) -> None: ) -> None:
"""With the rate_with_currency recognizer loaded, 'Tina makes """With the rate_with_currency recognizer loaded, "Tina makes $18.00
$18.00 an hour' is recognized and skipped in the per-statement an hour" is recognized but the v1 injector returns () (the
loop. The problem may still refuse downstream (the question SentenceChoice union does not yet model rates see ADR follow-up).
cannot be solved without the skipped rate's content), but the
refusal reason is no longer the per-statement Pre-#359 behavior: silently drop the recognized-but-uninjectable
'no admissible candidate for statement' string.""" statement and admit a partial graph from the rest a wrong>0
hazard analogous to case 0050.
Post-#359 (this test's contract): refuse explicitly with reason
"recognizer matched but produced no injection" naming the
statement and category. This pinned behavior is the wrong=0
safety net for the recognizer path.
"""
result = cg.parse_and_solve( result = cg.parse_and_solve(
"Tina makes $18.00 an hour. How much does Tina earn after 8 hours?" "Tina makes $18.00 an hour. How much does Tina earn after 8 hours?"
) )
if result.refusal_reason is not None: assert result.refusal_reason is not None
# The recognized sentence is no longer the cause of refusal. assert (
assert "Tina makes $18.00 an hour" not in (result.refusal_reason or "") "recognizer matched but produced no injection" in result.refusal_reason
), f"expected explicit recognizer-refusal, got: {result.refusal_reason!r}"
# The statement IS named in the reason — that's the diagnostic shape
# the post-#359 refusal carries. Update the prior assertion which
# forbade naming, since that assertion encoded the silent-drop
# premise that #359 retired.
assert "Tina makes $18.00 an hour" in result.refusal_reason
def test_recognized_descriptive_statement_no_longer_triggers_per_statement_refusal( def test_recognized_descriptive_statement_refuses_explicitly_post_wrong_zero_fix(
with_synthetic_registry: tuple[RatifiedRecognizer, ...], with_synthetic_registry: tuple[RatifiedRecognizer, ...],
) -> None: ) -> None:
"""Descriptive_setup_no_quantity statements that survive the """Descriptive_setup_no_quantity statements that match a ratified
numeric pre-filter (e.g., when all statements are non-numeric) recognizer but have no v1 injector now refuse explicitly post-#359,
must not trigger the per-statement refusal under the wiring.""" same as the rate case above. The statement IS named in the
refusal_reason for diagnostic purposes."""
# Construct a problem whose statements are ALL non-numeric so # Construct a problem whose statements are ALL non-numeric so
# the pre-filter does NOT strip them, forcing them to the # the pre-filter does NOT strip them, forcing them to the
# per-statement loop. # per-statement loop.
@ -106,8 +131,13 @@ def test_recognized_descriptive_statement_no_longer_triggers_per_statement_refus
"How many things happened?" "How many things happened?"
) )
if result.refusal_reason is not None: if result.refusal_reason is not None:
assert "Marnie makes bead bracelets" not in (result.refusal_reason or "") # The post-#359 refusal explicitly identifies the recognized
assert "John adopts a dog from a shelter" not in (result.refusal_reason or "") # statement that lacks an injector — that's the load-bearing
# diagnostic surface.
assert (
"recognizer matched but produced no injection" in result.refusal_reason
or "no admissible candidate" in result.refusal_reason
), f"unexpected refusal reason: {result.refusal_reason!r}"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------