mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-11-13 17:58:18 +00:00
feat(detection-core): anti-shortcut probes and fitted-component admission gate
This commit is contained in:
@@ -87,13 +87,13 @@ _Requirement IDs use N.M where N is the requirement number in requirements.md an
|
|||||||
- _Depends: 4.2, 3.2_
|
- _Depends: 4.2, 3.2_
|
||||||
|
|
||||||
- [ ] 5. Anti-shortcut probes for any fitted component
|
- [ ] 5. Anti-shortcut probes for any fitted component
|
||||||
- [ ] 5.1 (P) Reversed-pair probe harness
|
- [x] 5.1 (P) Reversed-pair probe harness
|
||||||
- Build the fix/reversed-fix probe; the deletion-count shape feature must fail it (control test).
|
- Build the fix/reversed-fix probe; the deletion-count shape feature must fail it (control test).
|
||||||
- Done when the control probe fails a shape-only component and passes a relation-based one.
|
- Done when the control probe fails a shape-only component and passes a relation-based one.
|
||||||
- _Requirements: 8.1, 8.2_
|
- _Requirements: 8.1, 8.2_
|
||||||
- _Boundary: core probes_
|
- _Boundary: core probes_
|
||||||
- _Depends: 1.2_
|
- _Depends: 1.2_
|
||||||
- [ ] 5.2 Fitted-component gate
|
- [x] 5.2 Fitted-component gate
|
||||||
- Require per-repository holdout and untouched-population admission before any fitted component makes a claim; BLOCK reserved for demonstrated proofs.
|
- Require per-repository holdout and untouched-population admission before any fitted component makes a claim; BLOCK reserved for demonstrated proofs.
|
||||||
- Done when a fitted component without admission evidence is refused by the gate in a test.
|
- Done when a fitted component without admission evidence is refused by the gate in a test.
|
||||||
- _Requirements: 8.2, 9.2_
|
- _Requirements: 8.2, 9.2_
|
||||||
|
|||||||
@@ -4,31 +4,55 @@ Owns contracts only -- no analysis logic. Arms implement the trace contract;
|
|||||||
runners and consumers adopt coverage, budget, provenance, and admission contracts.
|
runners and consumers adopt coverage, budget, provenance, and admission contracts.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from openultrasast.core.admission import (
|
||||||
|
REPORT_SCHEMA_KEYS,
|
||||||
|
AdmissionManifest,
|
||||||
|
AdmissionManifestError,
|
||||||
|
validate_admission_manifest,
|
||||||
|
)
|
||||||
from openultrasast.core.budget import Budget, BudgetGateFailure, Complexity, check_budget, derive_budget
|
from openultrasast.core.budget import Budget, BudgetGateFailure, Complexity, check_budget, derive_budget
|
||||||
from openultrasast.core.coverage import Coverage, InstrumentFailure, ResolutionOutcome, check_reads
|
from openultrasast.core.coverage import Coverage, InstrumentFailure, ResolutionOutcome, check_reads
|
||||||
|
from openultrasast.core.probes import (
|
||||||
|
FittedComponentBlocked,
|
||||||
|
ProbeResult,
|
||||||
|
ShortcutDetected,
|
||||||
|
assert_no_shortcut,
|
||||||
|
require_admission_evidence,
|
||||||
|
reversed_pair_probe,
|
||||||
|
)
|
||||||
from openultrasast.core.provenance import ProvenanceMismatch, stamp, verify_stamp
|
from openultrasast.core.provenance import ProvenanceMismatch, stamp, verify_stamp
|
||||||
from openultrasast.core.trace import Guard, Hop, TraceRecord, Witness
|
from openultrasast.core.trace import Guard, Hop, TraceRecord, Witness
|
||||||
from openultrasast.core.verdict import BoundedSilence, Judgment, ProvenClaim, as_clean
|
from openultrasast.core.verdict import BoundedSilence, Judgment, ProvenClaim, as_clean
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
"REPORT_SCHEMA_KEYS",
|
||||||
|
"AdmissionManifest",
|
||||||
|
"AdmissionManifestError",
|
||||||
"BoundedSilence",
|
"BoundedSilence",
|
||||||
"Budget",
|
"Budget",
|
||||||
"BudgetGateFailure",
|
"BudgetGateFailure",
|
||||||
"Complexity",
|
"Complexity",
|
||||||
"Coverage",
|
"Coverage",
|
||||||
|
"FittedComponentBlocked",
|
||||||
"Guard",
|
"Guard",
|
||||||
"Hop",
|
"Hop",
|
||||||
"InstrumentFailure",
|
"InstrumentFailure",
|
||||||
"Judgment",
|
"Judgment",
|
||||||
|
"ProbeResult",
|
||||||
"ProvenClaim",
|
"ProvenClaim",
|
||||||
"ProvenanceMismatch",
|
"ProvenanceMismatch",
|
||||||
"ResolutionOutcome",
|
"ResolutionOutcome",
|
||||||
|
"ShortcutDetected",
|
||||||
"TraceRecord",
|
"TraceRecord",
|
||||||
"Witness",
|
"Witness",
|
||||||
"as_clean",
|
"as_clean",
|
||||||
|
"assert_no_shortcut",
|
||||||
"check_budget",
|
"check_budget",
|
||||||
"check_reads",
|
"check_reads",
|
||||||
"derive_budget",
|
"derive_budget",
|
||||||
|
"require_admission_evidence",
|
||||||
|
"reversed_pair_probe",
|
||||||
"stamp",
|
"stamp",
|
||||||
|
"validate_admission_manifest",
|
||||||
"verify_stamp",
|
"verify_stamp",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
"""Anti-shortcut probes for fitted components (Req 8, 9).
|
||||||
|
|
||||||
|
A fitted prompt, threshold, or model must not separate a fix from its reversed fix
|
||||||
|
using shape features (diff size, deletion count, line counts): that is pool-specific
|
||||||
|
shortcut learning, the 2026-10-14 deletion-count 0.90 finding. A component making any
|
||||||
|
detection claim must also carry per-repository holdout evidence and a GO admission on
|
||||||
|
an untouched population.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable, Mapping
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
class ShortcutDetected(ValueError):
|
||||||
|
"""A component separates a fix from its reversed fix by its decision features."""
|
||||||
|
|
||||||
|
|
||||||
|
class FittedComponentBlocked(ValueError):
|
||||||
|
"""A fitted component claims detection without holdout + admission evidence."""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ProbeResult:
|
||||||
|
"""Outcome of one reversed-pair probe."""
|
||||||
|
|
||||||
|
separated: bool
|
||||||
|
fix_score: float
|
||||||
|
reversed_score: float
|
||||||
|
|
||||||
|
|
||||||
|
def reversed_pair_probe(
|
||||||
|
score_fn: Callable[[Mapping[str, Any]], float], fix_change: Mapping[str, Any], reversed_change: Mapping[str, Any]
|
||||||
|
) -> ProbeResult:
|
||||||
|
"""Score a fix and its reversed fix; separated=True means the features shortcut."""
|
||||||
|
fix_score = float(score_fn(fix_change))
|
||||||
|
reversed_score = float(score_fn(reversed_change))
|
||||||
|
return ProbeResult(separated=fix_score != reversed_score, fix_score=fix_score, reversed_score=reversed_score)
|
||||||
|
|
||||||
|
|
||||||
|
def assert_no_shortcut(
|
||||||
|
score_fn: Callable[[Mapping[str, Any]], float], fix_change: Mapping[str, Any], reversed_change: Mapping[str, Any]
|
||||||
|
) -> None:
|
||||||
|
"""Raise ShortcutDetected when the component separates fix from reversed fix."""
|
||||||
|
result = reversed_pair_probe(score_fn, fix_change, reversed_change)
|
||||||
|
if result.separated:
|
||||||
|
raise ShortcutDetected(f"shape shortcut: fix={result.fix_score} vs reversed={result.reversed_score}")
|
||||||
|
|
||||||
|
|
||||||
|
def require_admission_evidence(*, holdout_passed: bool, admission_decision: str) -> None:
|
||||||
|
"""Block any fitted-component claim without holdout and an admission GO (Req 8.2)."""
|
||||||
|
if not holdout_passed:
|
||||||
|
raise FittedComponentBlocked("fitted component lacks per-repository holdout evidence")
|
||||||
|
if admission_decision != "GO":
|
||||||
|
raise FittedComponentBlocked(f"fitted component admission decision is {admission_decision!r}, not GO")
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"FittedComponentBlocked",
|
||||||
|
"ProbeResult",
|
||||||
|
"ShortcutDetected",
|
||||||
|
"assert_no_shortcut",
|
||||||
|
"require_admission_evidence",
|
||||||
|
"reversed_pair_probe",
|
||||||
|
]
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
"""Anti-shortcut probe tests for detection-core tasks 5.1-5.2 (Req 8, 9)."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from openultrasast.core.probes import ( # noqa: E402
|
||||||
|
FittedComponentBlocked,
|
||||||
|
ShortcutDetected,
|
||||||
|
assert_no_shortcut,
|
||||||
|
require_admission_evidence,
|
||||||
|
reversed_pair_probe,
|
||||||
|
)
|
||||||
|
|
||||||
|
FIX: dict[str, Any] = {"id": "fix", "files": [{"path": "a.py", "head_lines": [[1, 10]]}], "seconds": 1.0}
|
||||||
|
REVERSED: dict[str, Any] = {"id": "reversed", "files": [{"path": "a.py", "head_lines": [[1, 50]]}], "seconds": 1.0}
|
||||||
|
|
||||||
|
|
||||||
|
def test_reversed_pair_probe_detects_shape_shortcut() -> None:
|
||||||
|
# deletion-count style component: score = lines removed -> separates fix from reversed
|
||||||
|
def shape_score(change: Mapping[str, Any]) -> float:
|
||||||
|
return float(50.0 - change["files"][0]["head_lines"][0][1])
|
||||||
|
|
||||||
|
result = reversed_pair_probe(shape_score, FIX, REVERSED)
|
||||||
|
assert result.separated is True
|
||||||
|
try:
|
||||||
|
assert_no_shortcut(shape_score, FIX, REVERSED)
|
||||||
|
raise AssertionError("shape shortcut must be detected")
|
||||||
|
except ShortcutDetected:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_reversed_pair_probe_passes_relation_based_component() -> None:
|
||||||
|
def relation_score(change: Mapping[str, Any]) -> float:
|
||||||
|
return float(bool(change.get("has_guard")))
|
||||||
|
|
||||||
|
assert reversed_pair_probe(relation_score, {**FIX, "has_guard": True}, {**REVERSED, "has_guard": True}).separated is False
|
||||||
|
assert_no_shortcut(relation_score, {**FIX, "has_guard": True}, {**REVERSED, "has_guard": True})
|
||||||
|
|
||||||
|
|
||||||
|
def test_fitted_component_requires_holdout_and_admission_go() -> None:
|
||||||
|
require_admission_evidence(holdout_passed=True, admission_decision="GO")
|
||||||
|
for holdout, decision in ((False, "GO"), (True, "NO-GO"), (False, "NO-GO")):
|
||||||
|
try:
|
||||||
|
require_admission_evidence(holdout_passed=holdout, admission_decision=decision)
|
||||||
|
raise AssertionError(f"holdout={holdout} decision={decision} must block the claim")
|
||||||
|
except FittedComponentBlocked:
|
||||||
|
pass
|
||||||
Reference in New Issue
Block a user