Skip to content

Commit 8837884

Browse files
committed
fix(eval): surface incorrectly-denied pairs that aren't also under-grants
A pair that is simultaneously granted AND denied (e.g. a coarse scope split by the auditor into one approved and one best-effort-rejected sub-decision, as hit live by test_e2e_correctness[agent_delegation]) lands in incorrectly_denied without ever appearing in under_grants, since it IS in granted. under_grant_cases only checked under_grants, so that finding silently never became an EvidenceCase and never reached the recommendations section. Checks incorrectly_denied too now. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Amitfre15 <amitfrework@gmail.com>
1 parent 7d0e407 commit 8837884

2 files changed

Lines changed: 54 additions & 3 deletions

File tree

‎eval/recommendations.py‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,17 +129,31 @@ def over_grant_cases(entries: list[tuple[str, str, dict]]) -> list[EvidenceCase]
129129

130130

131131
def under_grant_cases(entries: list[tuple[str, str, dict]]) -> list[EvidenceCase]:
132-
"""Mirrors ``over_grant_cases`` for ``under_grants``."""
132+
"""Mirrors ``over_grant_cases`` for ``under_grants`` -- PLUS ``incorrectly_denied``.
133+
``incorrectly_denied`` (an explicit deny for a pair that should be granted) is normally a
134+
subset of ``under_grants`` (``eval.correctness_scorer``'s own docstring), but a pair that is
135+
simultaneously ``granted`` AND ``denied`` -- e.g. a coarse scope split by the auditor into one
136+
approved sub-decision and one best-effort-rejected sub-decision for the same (role, scope) pair
137+
-- lands in ``incorrectly_denied`` without ever appearing in ``under_grants`` (it IS in
138+
``granted``, so ``expected - granted`` excludes it). Checking both keeps that edge case from
139+
silently vanishing from the recommendations section."""
133140
cases = []
134141
for suite, scenario, props in entries:
135142
under_grants = props.get("under_grants") or {}
136-
if not under_grants:
143+
incorrectly_denied = props.get("incorrectly_denied") or {}
144+
if not under_grants and not incorrectly_denied:
137145
continue
146+
detail_parts = []
147+
if under_grants:
148+
detail_parts.append(f"under-granted pairs -> {_format_pairs(under_grants)}")
149+
if incorrectly_denied:
150+
detail_parts.append(f"explicitly (incorrectly) denied pairs -> {_format_pairs(incorrectly_denied)}")
151+
evidence = f"{suite}/{scenario}: " + "; ".join(detail_parts)
138152
cases.append(
139153
EvidenceCase(
140154
case_id=f"{suite}:{scenario}:under_grant",
141155
finding_type="under_grant",
142-
evidence=f"{suite}/{scenario}: under-granted pairs -> {_format_pairs(under_grants)}",
156+
evidence=evidence,
143157
remedy_menu=_UNDER_GRANT_REMEDY,
144158
fallback_key=suite,
145159
)

‎eval/test_recommendations.py‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ def test_one_case_per_entry_with_correct_fields(self) -> None:
6060
class TestUnderGrantCases:
6161
def test_no_evidence_returns_empty(self) -> None:
6262
assert under_grant_cases([("correctness_prb", "baseline", {"under_grants": {}})]) == []
63+
assert (
64+
under_grant_cases([("correctness_prb", "baseline", {"under_grants": {}, "incorrectly_denied": {}})]) == []
65+
)
6366

6467
def test_one_case_per_entry_with_correct_fields(self) -> None:
6568
entries = [("correctness_prb", "baseline", {"under_grants": {"inbound": [("role-a", "scope-a")]}})]
@@ -69,6 +72,40 @@ def test_one_case_per_entry_with_correct_fields(self) -> None:
6972
assert case.fallback_key == "correctness_prb"
7073
assert "role-a" in case.evidence and "scope-a" in case.evidence
7174

75+
def test_incorrectly_denied_alone_still_produces_a_case(self) -> None:
76+
# The edge case: a pair simultaneously granted AND denied (e.g. a coarse scope split by
77+
# the auditor into one approved and one best-effort-rejected sub-decision) lands in
78+
# incorrectly_denied without ever appearing in under_grants -- must not be silently
79+
# dropped from the recommendations section.
80+
entries = [
81+
(
82+
"correctness_e2e",
83+
"agent_delegation",
84+
{"under_grants": {}, "incorrectly_denied": {"inbound": [("role-dock-worker", "agent-scope-x")]}},
85+
)
86+
]
87+
[case] = under_grant_cases(entries)
88+
assert case.case_id == "correctness_e2e:agent_delegation:under_grant"
89+
assert case.finding_type == "under_grant"
90+
assert case.fallback_key == "correctness_e2e"
91+
assert "role-dock-worker" in case.evidence and "agent-scope-x" in case.evidence
92+
assert "incorrectly) denied" in case.evidence
93+
94+
def test_both_present_combines_into_one_case_not_two(self) -> None:
95+
entries = [
96+
(
97+
"correctness_prb",
98+
"baseline",
99+
{
100+
"under_grants": {"inbound": [("role-a", "scope-a")]},
101+
"incorrectly_denied": {"outbound": [("role-b", "scope-b")]},
102+
},
103+
)
104+
]
105+
cases = under_grant_cases(entries)
106+
assert len(cases) == 1
107+
assert "role-a" in cases[0].evidence and "role-b" in cases[0].evidence
108+
72109

73110
# =========================================================================== #
74111
# sensitivity_cases / invariance_cases #

0 commit comments

Comments
 (0)