core/tests/test_rescan_v4_invariants.py
Shay c655f2fcda fix(rescan-v4): recognize new refusal-reason format + record reader advances
build_rescan's _FIRST_REFUSAL_RE only matched 'no admissible candidate for
...' and required the quote at end-of-string. PR #359 added the
'recognizer matched but produced no injection ... (category=X)' shape, so
extraction returned None and manufactured spurious shifts. Extend the regex
to both shapes and tolerate the trailing (category=...).

With extraction fixed, the live reader legitimately diverges from the v3
baseline on 10 cases (was 2 at the S.4 cut): 5 more first-refusals shifted
one sentence deeper (0019/0023/0025/0027/0047 — overrides added) and 3
refusals became correct admissions (0003/0021/0024 — the +3 that moved
serving 3->6). wrong stays 0; no admission lost. Tests retargeted to the
live counts and artifacts regenerated.

Follow-up: the v4 tests re-run the live reader, so they will drift again on
the next advance; a frozen v5 snapshot (or artifact-derived expectations)
would decouple them.
2026-06-03 01:13:10 -07:00

125 lines
4.7 KiB
Python

"""ADR-0136.S.4-post-rescan — invariant tests for refusal rescan v4.
v4 is the CURRENT snapshot (post-S.4). Unlike v3 (which is now frozen and
asserted against disk), v4 tests re-run build_rescan() to confirm the live
parser still produces the v4 ledger — until S.5 lands and shifts more.
"""
from __future__ import annotations
import json
from pathlib import Path
from evals.gsm8k_math.train_sample.v1.rescan_v4 import build_rescan
_HERE = Path(__file__).resolve().parent.parent
_RESCAN_V4 = _HERE / "evals/gsm8k_math/train_sample/v1/refusal_rescan_v4.json"
_TAXONOMY_V4 = _HERE / "evals/gsm8k_math/train_sample/v1/refusal_taxonomy_v4.json"
def test_wrong_is_zero() -> None:
rescan, _ = build_rescan()
assert rescan["summary"]["wrong"] == 0
def test_admission_set_grew_monotonically_over_v3() -> None:
"""The reader admits the v3 set plus three new *correct* solves.
v3 admitted {0014, 0018, 0042}. Later capability waves added three correct
admissions (0003, 0021, 0024) — the +3 that moved the serving metric from
3 to 6. No v3 admission was lost (monotonic), and wrong stays 0
(test_wrong_is_zero), so every gain is a sound solve.
"""
rescan, _ = build_rescan()
admitted = {
c["case_id"] for c in rescan["per_case"] if c["current_outcome"] == "admitted"
}
v3_admitted = {
"gsm8k-train-sample-v1-0014",
"gsm8k-train-sample-v1-0018",
"gsm8k-train-sample-v1-0042",
}
# No regression: every case v3 solved is still solved.
assert v3_admitted <= admitted
assert admitted == v3_admitted | {
"gsm8k-train-sample-v1-0003",
"gsm8k-train-sample-v1-0021",
"gsm8k-train-sample-v1-0024",
}
def test_barrier_shifts_from_v3_baseline() -> None:
"""The live reader has diverged from the v3 baseline on ten cases.
S.4 shifted two (0038, 0046). Later capability waves (0163-D.2, 0174, 0178,
0191, 0192) advanced the reader past five more v3-era barriers (0019, 0023,
0025, 0027, 0047) — still refusing, one sentence deeper — and turned three
refusals into correct admissions (0003, 0021, 0024). All ten are
soundness-preserving (wrong stays 0); the five still-refusing shifts each
carry an override in _V4_BARRIER_OVERRIDES.
"""
rescan, _ = build_rescan()
# 10 = 7 first-refusal/barrier shifts + 3 refused->admitted transitions
# (0003, 0021, 0024), which also count as shifts (outcome changed).
assert rescan["summary"]["barrier_shifted_v3_to_v4"] == 10
shifted = sorted(
c["case_id"] for c in rescan["per_case"] if c["barrier_shifted"]
)
assert shifted == [
"gsm8k-train-sample-v1-0003",
"gsm8k-train-sample-v1-0019",
"gsm8k-train-sample-v1-0021",
"gsm8k-train-sample-v1-0023",
"gsm8k-train-sample-v1-0024",
"gsm8k-train-sample-v1-0025",
"gsm8k-train-sample-v1-0027",
"gsm8k-train-sample-v1-0038",
"gsm8k-train-sample-v1-0046",
"gsm8k-train-sample-v1-0047",
]
def test_gsm8k_0038_now_refuses_on_sentence_2() -> None:
rescan, _ = build_rescan()
case = next(
c for c in rescan["per_case"]
if c["case_id"] == "gsm8k-train-sample-v1-0038"
)
assert case["previous_first_refusal"].startswith("In a building")
assert case["current_first_refusal"].startswith("There are three times")
assert case["previous_primary_barrier"] == "novel_initial_form"
assert case["current_primary_barrier"] == "compound_comparative"
def test_gsm8k_0046_now_refuses_on_sentence_2() -> None:
rescan, _ = build_rescan()
case = next(
c for c in rescan["per_case"]
if c["case_id"] == "gsm8k-train-sample-v1-0046"
)
assert case["previous_first_refusal"].startswith("A school has 100 students")
assert case["current_first_refusal"].startswith("Half of the students")
assert case["previous_primary_barrier"] == "novel_initial_form"
assert case["current_primary_barrier"] == "fraction_operand"
def test_artifacts_deterministic() -> None:
r1, t1 = build_rescan()
r2, t2 = build_rescan()
assert json.dumps(r1, sort_keys=True) == json.dumps(r2, sort_keys=True)
assert json.dumps(t1, sort_keys=True) == json.dumps(t2, sort_keys=True)
def test_taxonomy_v4_has_50_entries() -> None:
_, taxonomy = build_rescan()
assert len(taxonomy["per_case"]) == 50
def test_disk_artifacts_match_freshly_computed() -> None:
if not _RESCAN_V4.exists() or not _TAXONOMY_V4.exists():
return
rescan, taxonomy = build_rescan()
on_disk_rescan = json.loads(_RESCAN_V4.read_text(encoding="utf-8"))
on_disk_taxonomy = json.loads(_TAXONOMY_V4.read_text(encoding="utf-8"))
assert on_disk_rescan == rescan
assert on_disk_taxonomy == taxonomy