"""Broad-quantifier reminder text + governance hash binding. Ticket #000008 Phase 3. Default-OFF mechanism — operator opts in per-call via ``quantifier_reminder_enabled=True`` in policy. Tests pin the reminder generator's behavior and the policy field's inclusion in ``_VERIFIER_POLICY_FIELDS`` so flipping it bumps ``governance_policy_hash``. Bench-first per §10.8 decision tree: enable default-on only after a bench cycle shows ≥5pp delta on FORMAT_COLLAPSED or pointer-loss rate. """ from __future__ import annotations import pytest from arborist.qa.keys import _VERIFIER_POLICY_FIELDS, verifier_policy_hash from arborist.qa.quantifier_reminder import broad_quantifier_reminder # ---------------------------------------------------------------- gating @pytest.mark.parametrize("intensity", [ "SINGULAR", "PROPORTIONAL", "FEW", "MANY", "SMALL_NUM_EXPLICIT", "COMPARATIVE_BOUND", "ABSENT", ]) def test_non_broad_intensity_returns_none(intensity): """Reminder only fires for ALL/COMPREHENSIVE/OPEN_REQUEST.""" out = broad_quantifier_reminder( intensity=intensity, cap=8, scope_bound_hint="unbounded", ) assert out is None @pytest.mark.parametrize("intensity", ["ALL", "COMPREHENSIVE", "OPEN_REQUEST"]) def test_broad_intensity_with_cap_produces_reminder(intensity): out = broad_quantifier_reminder( intensity=intensity, cap=8, scope_bound_hint="unbounded", ) assert out is not None assert "8" in out assert "broad-quantifier query" in out def test_none_cap_returns_none(): """Defensive — guard upstream may fail to extract a cap; reminder declines rather than render an ill-formed 'at most None'.""" out = broad_quantifier_reminder( intensity="ALL", cap=None, scope_bound_hint="unbounded", ) assert out is None def test_zero_or_negative_cap_returns_none(): """Same defensive — non-positive caps shouldn't render.""" assert broad_quantifier_reminder( intensity="ALL", cap=0, scope_bound_hint="unbounded", ) is None assert broad_quantifier_reminder( intensity="ALL", cap=-1, scope_bound_hint="unbounded", ) is None def test_none_intensity_returns_none(): out = broad_quantifier_reminder( intensity=None, cap=8, scope_bound_hint="unbounded", ) assert out is None # ---------------------------------------------------------------- bounded vs unbounded def test_bounded_universe_uses_bounded_template(): """Bounded universals (e.g. all members of the Beatles, year- anchored) get a softer reminder — no 'do not enumerate from training prior' clause, since the corpus has the answer set.""" bounded = broad_quantifier_reminder( intensity="ALL", cap=4, scope_bound_hint="bounded", ) unbounded = broad_quantifier_reminder( intensity="ALL", cap=4, scope_bound_hint="unbounded", ) assert "bounded universe" in bounded assert "training prior" not in bounded # Unbounded reminder is the stricter shape with the no-prior # clause and the under-specified-scope warning. assert "training prior" in unbounded assert "under-specified" in unbounded def test_unknown_scope_treated_as_unbounded(): """When the classifier can't decide bounded vs unbounded, default to the stricter (unbounded) reminder. Better to over-warn than under-warn on a broad-intensity question.""" out = broad_quantifier_reminder( intensity="ALL", cap=8, scope_bound_hint="unknown", ) assert "training prior" in out # ---------------------------------------------------------------- cap interpolation def test_cap_appears_verbatim_in_reminder(): """The cap N must appear in the reminder text so the model knows its budget. Pin the literal cap string so a typo in the template surfaces here.""" for cap in (1, 5, 8, 12, 50): out = broad_quantifier_reminder( intensity="ALL", cap=cap, scope_bound_hint="unbounded", ) assert str(cap) in out def test_reminder_demands_bracket_citations(): """The reminder must restate the [E\\d+] citation rule so the model doesn't drop pointer tags under enumeration pressure.""" out = broad_quantifier_reminder( intensity="ALL", cap=8, scope_bound_hint="unbounded", ) assert "[E5]" in out # template uses [E5] as the example # ---------------------------------------------------------------- governance binding def test_quantifier_reminder_enabled_is_in_verifier_policy_fields(): """Flipping the reminder switch must invalidate prior cache records — without this binding an operator could enable the reminder and silently re-use cached pre-reminder verdicts.""" assert "quantifier_reminder_enabled" in _VERIFIER_POLICY_FIELDS def test_governance_hash_changes_when_reminder_flips(): base_policy = dict.fromkeys(_VERIFIER_POLICY_FIELDS, "default") base_policy["quantifier_reminder_enabled"] = False h_off = verifier_policy_hash(base_policy) base_policy["quantifier_reminder_enabled"] = True h_on = verifier_policy_hash(base_policy) assert h_off != h_on