from pathlib import Path from openultrasast.calibration import ( FalsePositiveReason, FindingOutcome, apply_ranking_calibration, calibrate_ranking, calibrate_rankings, learning_from_finding_outcome, record_false_positive_learning, write_false_positive_learnings, ) from openultrasast.findings import StaticFinding from openultrasast.rank import RankingScore def _finding(path: str = "auth/login.py") -> StaticFinding: return StaticFinding( finding_id="python-unsafe-eval:auth/login.py:1", path=path, title="Dynamic Python execution needs review", severity="high", confidence="medium", evidence_level="static_corroboration", rationale="pattern matched", line=1, function_name=None, reachability_status="unknown", reachability_evidence=[], reachability_conditions=[], tags=["syscall_entry"], ranking_priority=4.0, ) def _ranking(path: str) -> RankingScore: return RankingScore( path=path, surface=4, influence=2, reachability=4, priority=3.6, rationale="heuristic", model_id=None, static_boosts=["syscall_entry"], ) def test_false_positive_learning_records_reason_and_scope(tmp_path: Path) -> None: learning = record_false_positive_learning( _finding(), reason=FalsePositiveReason.MISSING_ATTACKER_CONTROL, evidence="Verifier showed input is constant and not attacker-controlled.", ) output = tmp_path / "false-positive-learnings.json" write_false_positive_learnings([learning], output) assert learning.vulnerability_class == "syscall_entry" assert learning.scope == "auth:syscall_entry" assert "missing_attacker_control" in output.read_text() def test_scoped_false_positive_demotion_does_not_suppress_class_globally() -> None: learning = record_false_positive_learning( _finding("auth/login.py"), reason=FalsePositiveReason.STATIC_RULE_MISMATCH, evidence="Rule matched a test-only wrapper in this auth path.", ) same_scope = apply_ranking_calibration(_ranking("auth/login.py"), calibrate_ranking(_ranking("auth/login.py"), [learning])) other_scope = apply_ranking_calibration(_ranking("payments/login.py"), calibrate_ranking(_ranking("payments/login.py"), [learning])) assert same_scope.priority < 3.6 assert other_scope.priority == 3.6 def test_verifier_outcome_feeds_ranking_calibration_and_prompt_constraints() -> None: learning = learning_from_finding_outcome( _finding("auth/login.py"), outcome=FindingOutcome.CONTRADICTED, evidence="Independent verifier showed the risky call is unreachable in production flow.", ) assert learning is not None calibrated, calibrations = calibrate_rankings([_ranking("auth/login.py")], [learning]) assert calibrated[0].priority < 3.6 assert calibrations[0].retrieval_filter_adjustments == {"syscall_entry": "auth:syscall_entry"} assert "without new evidence" in calibrations[0].prompt_constraints[0] def test_verified_outcome_does_not_create_false_positive_learning() -> None: learning = learning_from_finding_outcome( _finding(), outcome=FindingOutcome.VERIFIED, evidence="Verifier confirmed attacker control and impact.", ) assert learning is None