mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-18 12:39:28 +00:00
fix(learning-harness): the round-4 review findings
Three defects, two of them the same shape as ones already rejected once: - The meta-agent proposer still could not propose. `MetaAgent(inner_model=)` takes a ModelConfig; it was handed a model name, a `# type: ignore` hid that from mypy, and a bare `except Exception` turned the resulting AttributeError into `meta_agent_failed` -- which reads exactly like the model declining. The composition is now a seam a test can pin, and a construction error is re-raised rather than reported as a quiet round. - `ousast pairs --hunter` crashed in its documented form: the K floor landed with `--k-runs` still defaulting to 1. The covering test always passed `--k-runs 4`, which is why nothing noticed. - 28 rows (12% of the corpus) resolved a span on the vulnerable side and none on the fixed side, so every fixed-side finding read as outside the labeled function and the pair scored as silence whatever the detector said. `fix_function` now reaches the catalog and the scorer, and the generator derives it from the fixed excerpt for the 25 trap rows that never declared one. Measured after: 0 rows. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
This commit is contained in:
co-authored by
Claude Opus 5
parent
75ae9ee47b
commit
2dad25d312
@@ -93,20 +93,31 @@ def evaluate_loo(cases: Sequence[PairCase], *, facts: SemanticFacts | None = Non
|
||||
targets: list[PairCase] = []
|
||||
skipped: list[tuple[str, str]] = []
|
||||
for case in cases:
|
||||
if case.known_limit:
|
||||
skipped.append((case.name, f"known_limit:{case.known_limit}"))
|
||||
if case.unscorable or case.known_limit:
|
||||
# `PairCase.unscorable` verbatim when the loader computed one: one spelling per corpus fact, and it
|
||||
# already carries the `known_limit:` prefix when the limit is a maintainer's own word rather than one
|
||||
# of the vocabulary's. A case built by hand may carry only the declaration.
|
||||
skipped.append((case.name, case.unscorable or f"known_limit:{case.known_limit}"))
|
||||
elif not case.vuln_file.is_file() or not case.fixed_file.is_file():
|
||||
skipped.append((case.name, "pointer_pair_not_cached" if not case.vendored else "excerpt_missing"))
|
||||
else:
|
||||
targets.append(case)
|
||||
from ..learning.split import is_teacher, refuse_if_holdout
|
||||
from ..learning.split import GATING_TIERS, holdout_names, is_teacher, refuse_if_holdout
|
||||
|
||||
# learning-harness Req 5.1: every pair is still scored, but only a train-split teacher may seed a store.
|
||||
lessons = {case.name: pair_lessons(case, loaded) for case in targets if is_teacher(case)}
|
||||
outcomes = [_hold_out(case, targets, lessons, loaded) for case in targets]
|
||||
# Req 5.2: the pairs that were kept out of the teaching set are named, not silently dropped.
|
||||
refusal = refuse_if_holdout([case.name for case in targets if case.name not in lessons], targets)
|
||||
# Req 5.2: the holdout pairs kept out of the teaching set are named, not silently dropped. Non-teachers held
|
||||
# back for their tier or their unscorable reason are already in `skipped` with that reason.
|
||||
excluded = [case.name for case in targets if case.name not in lessons]
|
||||
refusal = refuse_if_holdout(excluded, targets)
|
||||
refusals = (refusal.degradation(),) if refusal is not None else ()
|
||||
withheld = tuple(
|
||||
(case.name, "tier" if case.review_tier not in GATING_TIERS else "unscorable")
|
||||
for case in targets
|
||||
if case.name in set(excluded) - set(holdout_names(targets))
|
||||
)
|
||||
skipped.extend((name, f"not_a_teacher:{why}") for name, why in withheld)
|
||||
return LooResult(
|
||||
outcomes=tuple(outcomes),
|
||||
per_slice=_group(outcomes, lambda item: (item.slice,)),
|
||||
|
||||
@@ -197,12 +197,12 @@ def _skip_reason(case: PairCase) -> str | None:
|
||||
|
||||
if case.review_tier not in GATING_TIERS:
|
||||
return f"tier:{case.review_tier}"
|
||||
if case.unscorable:
|
||||
# One spelling per corpus fact. `identical_twin` declared in the catalog and `identical_twin` computed from
|
||||
# the bytes are the same reason, and calling one of them `split:train` is simply untrue (Req 5.1, 10.3).
|
||||
return f"unscorable:{case.unscorable}"
|
||||
if case.known_limit:
|
||||
return f"known_limit:{case.known_limit}"
|
||||
if case.unscorable or case.known_limit:
|
||||
# `PairCase.unscorable` verbatim when the loader computed one, the same string every other path reports:
|
||||
# `identical_twin` declared in the catalog and `identical_twin` computed from the bytes are one fact, and
|
||||
# calling either of them `split:train` is simply untrue. A case built by hand may carry only the
|
||||
# declaration (Req 5.1, 10.3).
|
||||
return case.unscorable or f"known_limit:{case.known_limit}"
|
||||
if not is_teacher(case) and case.vendored:
|
||||
return f"split:{case.split}"
|
||||
if not case.vuln_file.is_file() or not case.fixed_file.is_file():
|
||||
|
||||
Reference in New Issue
Block a user