test(a2k): prove geometric path refuses out-of-range decrease scales
Regression coverage for the audit repro (5/4), zero/one/gt-one geometric no-bypass, injected Fraction domain mutations, promotable resolver refusal, and flip the PR #87 preexisting gap pin to require operator refuse alongside baseline.
This commit is contained in:
parent
1a6efe0d13
commit
ac4c3e04ad
3 changed files with 136 additions and 10 deletions
|
|
@ -148,6 +148,16 @@ class TestConfuserRefusals:
|
||||||
assert resolve_promotable_fraction_decrease(FINAL_VALUE_CONFUSER) is None
|
assert resolve_promotable_fraction_decrease(FINAL_VALUE_CONFUSER) is None
|
||||||
assert _run(FINAL_VALUE_CONFUSER).answer is None
|
assert _run(FINAL_VALUE_CONFUSER).answer is None
|
||||||
|
|
||||||
|
def test_scale_out_of_range_refuses_fraction_decrease(self):
|
||||||
|
"""Geometric path must not promote scale>1 (negative decrease)."""
|
||||||
|
case = (
|
||||||
|
"In one hour, the river will decrease to 5/4 of its level. "
|
||||||
|
"If the current level is 40 feet, what will the level decrease by?"
|
||||||
|
)
|
||||||
|
assert resolve_promotable_fraction_decrease(case) is None
|
||||||
|
assert compose_fraction_decrease(case) is None
|
||||||
|
assert _run(case).answer is None
|
||||||
|
|
||||||
def test_unequal_split_refuses_percent_partition(self):
|
def test_unequal_split_refuses_percent_partition(self):
|
||||||
assert resolve_promotable_percent_partition(UNEQUAL_SPLIT_CONFUSER) is None
|
assert resolve_promotable_percent_partition(UNEQUAL_SPLIT_CONFUSER) is None
|
||||||
assert _run(UNEQUAL_SPLIT_CONFUSER).answer is None
|
assert _run(UNEQUAL_SPLIT_CONFUSER).answer is None
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,15 @@
|
||||||
|
from fractions import Fraction
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import dataclasses
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from generate.derivation.fraction_decrease import resolve_promotable_fraction_decrease
|
||||||
from generate.problem_frame_builder import build_problem_frame
|
from generate.problem_frame_builder import build_problem_frame
|
||||||
from generate.problem_frame_contracts import (
|
from generate.problem_frame_contracts import (
|
||||||
|
_fraction_decrease_scale_binding,
|
||||||
|
_is_valid_fraction_decrease_scale,
|
||||||
assess_contracts,
|
assess_contracts,
|
||||||
assess_fraction_decrease,
|
assess_fraction_decrease,
|
||||||
assess_geometric_proposals,
|
assess_geometric_proposals,
|
||||||
|
|
@ -177,6 +185,117 @@ def test_fraction_decrease_rejects_scale_greater_than_one() -> None:
|
||||||
assert "scale_out_of_range" in assessment.missing_bindings
|
assert "scale_out_of_range" in assessment.missing_bindings
|
||||||
|
|
||||||
|
|
||||||
|
def test_fraction_decrease_scale_domain_predicate() -> None:
|
||||||
|
"""Single shared domain: finite 0 < k < 1 for Fraction and float."""
|
||||||
|
assert _is_valid_fraction_decrease_scale(Fraction(3, 4))
|
||||||
|
assert _is_valid_fraction_decrease_scale(Fraction(2, 3))
|
||||||
|
assert _is_valid_fraction_decrease_scale(0.5)
|
||||||
|
assert not _is_valid_fraction_decrease_scale(Fraction(0, 4))
|
||||||
|
assert not _is_valid_fraction_decrease_scale(Fraction(4, 4))
|
||||||
|
assert not _is_valid_fraction_decrease_scale(Fraction(5, 4))
|
||||||
|
assert not _is_valid_fraction_decrease_scale(Fraction(-1, 4))
|
||||||
|
assert not _is_valid_fraction_decrease_scale(0.0)
|
||||||
|
assert not _is_valid_fraction_decrease_scale(1.0)
|
||||||
|
assert not _is_valid_fraction_decrease_scale(1.25)
|
||||||
|
assert not _is_valid_fraction_decrease_scale(-0.5)
|
||||||
|
assert not _is_valid_fraction_decrease_scale(float("nan"))
|
||||||
|
assert not _is_valid_fraction_decrease_scale(float("inf"))
|
||||||
|
assert not _is_valid_fraction_decrease_scale(float("-inf"))
|
||||||
|
assert not _is_valid_fraction_decrease_scale("not-a-number")
|
||||||
|
assert not _is_valid_fraction_decrease_scale(None)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"scale_surface",
|
||||||
|
["5/4", "4/4", "0/4"],
|
||||||
|
ids=["gt_one", "one", "zero"],
|
||||||
|
)
|
||||||
|
def test_fraction_decrease_geometric_refuses_out_of_range_scale(scale_surface: str) -> None:
|
||||||
|
"""Geometric admission must agree with obligation scale_out_of_range (no bypass)."""
|
||||||
|
case = (
|
||||||
|
f"In one hour, the river will decrease to {scale_surface} of its level. "
|
||||||
|
"If the current level is 40 feet, what will the level decrease by?"
|
||||||
|
)
|
||||||
|
frame = build_problem_frame(case)
|
||||||
|
obligation = assess_fraction_decrease(frame)
|
||||||
|
assert not obligation.runnable
|
||||||
|
assert "scale_out_of_range" in obligation.missing_bindings
|
||||||
|
assert obligation.bindings == () or not obligation.bindings
|
||||||
|
|
||||||
|
geom = [
|
||||||
|
a
|
||||||
|
for a in assess_geometric_proposals(frame)
|
||||||
|
if a.candidate_organ == "fraction_decrease"
|
||||||
|
]
|
||||||
|
assert geom, "expected a geometric fraction_decrease proposal assessment"
|
||||||
|
for assessment in geom:
|
||||||
|
assert not assessment.runnable
|
||||||
|
assert not assessment.bindings
|
||||||
|
|
||||||
|
assert _fraction_decrease_scale_binding(frame) is None
|
||||||
|
assert resolve_promotable_fraction_decrease(case) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_fraction_decrease_scale_gt_one_live_repro_refuses() -> None:
|
||||||
|
"""Audit repro: 5/4 must not promote a negative decrease answer."""
|
||||||
|
case = (
|
||||||
|
"In one hour, the river will decrease to 5/4 of its level. "
|
||||||
|
"If the current level is 40 feet, what will the level decrease by?"
|
||||||
|
)
|
||||||
|
assert resolve_promotable_fraction_decrease(case) is None
|
||||||
|
frame = build_problem_frame(case)
|
||||||
|
obligation = assess_fraction_decrease(frame)
|
||||||
|
assert "scale_out_of_range" in obligation.missing_bindings
|
||||||
|
geom = assess_geometric_proposals(frame)
|
||||||
|
assert all(
|
||||||
|
not (a.candidate_organ == "fraction_decrease" and a.bindings)
|
||||||
|
for a in geom
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_fraction_decrease_rejects_injected_negative_zero_one_and_gt_one_scale() -> None:
|
||||||
|
"""Domain gate on mutated Fraction scales (GroundedScalar is Fraction-only)."""
|
||||||
|
case = (
|
||||||
|
"In one hour, Addison mountain's temperature will decrease to 3/4 of its temperature. "
|
||||||
|
"If the current temperature of the mountain is 84 degrees, what will the temperature "
|
||||||
|
"decrease by?"
|
||||||
|
)
|
||||||
|
base_frame = build_problem_frame(case)
|
||||||
|
assert assess_fraction_decrease(base_frame).runnable
|
||||||
|
|
||||||
|
# Locate scale quantity fact and replace value.
|
||||||
|
relation = next(
|
||||||
|
r for r in base_frame.bound_relations if r.relation_type == "decrease_to_fraction"
|
||||||
|
)
|
||||||
|
scale_id = next(role.target_id for role in relation.roles if role.role == "scale")
|
||||||
|
mention = next(m for m in base_frame.mentions if m.mention_id == scale_id)
|
||||||
|
fact_id = mention.fact_id
|
||||||
|
assert fact_id is not None
|
||||||
|
|
||||||
|
for bad_value in (
|
||||||
|
Fraction(-1, 4),
|
||||||
|
Fraction(0, 1),
|
||||||
|
Fraction(1, 1),
|
||||||
|
Fraction(5, 4),
|
||||||
|
):
|
||||||
|
new_quantities = []
|
||||||
|
for q in base_frame.quantities:
|
||||||
|
if q.fact_id == fact_id:
|
||||||
|
new_quantities.append(dataclasses.replace(q, value=bad_value))
|
||||||
|
else:
|
||||||
|
new_quantities.append(q)
|
||||||
|
frame = dataclasses.replace(base_frame, quantities=tuple(new_quantities))
|
||||||
|
assessment = assess_fraction_decrease(frame)
|
||||||
|
assert not assessment.runnable, bad_value
|
||||||
|
assert "scale_out_of_range" in assessment.missing_bindings, bad_value
|
||||||
|
assert _fraction_decrease_scale_binding(frame) is None, bad_value
|
||||||
|
geom = assess_geometric_proposals(frame)
|
||||||
|
assert all(
|
||||||
|
not (a.candidate_organ == "fraction_decrease" and a.bindings)
|
||||||
|
for a in geom
|
||||||
|
), bad_value
|
||||||
|
|
||||||
|
|
||||||
def test_multi_base_candidate_is_refused() -> None:
|
def test_multi_base_candidate_is_refused() -> None:
|
||||||
import dataclasses
|
import dataclasses
|
||||||
case = (
|
case = (
|
||||||
|
|
|
||||||
|
|
@ -236,22 +236,19 @@ def test_adversarial_refuse_closed(case: AblationCase) -> None:
|
||||||
assert result.answer is None
|
assert result.answer is None
|
||||||
|
|
||||||
|
|
||||||
def test_preexisting_scale_out_of_range_gap_isolated() -> None:
|
def test_scale_out_of_range_operator_and_baseline_refuse() -> None:
|
||||||
"""Document pre-existing Gate A2k gap: scale>1 may still admit geometrically.
|
"""Gate A2k: scale>1 is fail-closed on both baseline and geometric operator.
|
||||||
|
|
||||||
Frame assess_fraction_decrease marks scale_out_of_range (baseline refuses).
|
Formerly pinned a pre-existing geometric bypass (negative delta). Obligation
|
||||||
Geometric A2k path may still compute a negative delta — not introduced by
|
and geometric admission now share 0 < scale < 1, so operator must refuse.
|
||||||
this ablation. Do not mask; pin the split so a future organ fix flips this.
|
|
||||||
"""
|
"""
|
||||||
base = run_baseline(SCALE_OOR_PREEXISTING)
|
base = run_baseline(SCALE_OOR_PREEXISTING)
|
||||||
op = run_operator(SCALE_OOR_PREEXISTING)
|
op = run_operator(SCALE_OOR_PREEXISTING)
|
||||||
assert base.outcome == "refused"
|
assert base.outcome == "refused"
|
||||||
assert base.answer is None
|
assert base.answer is None
|
||||||
# Pre-existing: operator may wrong-commit. If a future PR fixes A2k, this
|
assert op.outcome == "refused"
|
||||||
# assertion should be updated to require refuse.
|
assert op.answer is None
|
||||||
assert op.outcome in {"refused", "wrong"}
|
assert op.operator_bindings == 0
|
||||||
if op.outcome == "wrong":
|
|
||||||
assert op.answer is not None
|
|
||||||
|
|
||||||
|
|
||||||
def test_malformed_depth_labels_cannot_elevate_runnable() -> None:
|
def test_malformed_depth_labels_cannot_elevate_runnable() -> None:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue