mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 11:49:28 +00:00
feat(benchmark): Phase 2 (task 4) — actionable per-rule signals, ledger, FP knob
Complete Phase 2: make benchmark output drive per-rule improvement, wire the
loop-owned ledger, and turn a confirmed false positive into a score knob. Zero
new dependencies; detection stays byte-identical (recall 44/47 = 93.6%, FP 0%)
and the 90/10 gate holds.
- Per-rule attribution (4.1): BenchmarkMiss/BenchmarkFalsePositive carry rule_id;
BenchmarkMetrics.per_rule aggregates matched/missed/false_positives per rule.
- Recommendation delta (4.2): evaluate_benchmark emits RuleRecommendation
(keep/shadow/loosen) written to rule_policy_delta.json; the inert
next_improvement_candidate stub is now rule-aware ("rule:<id>" or
"language_hunter").
- Loop ledger (4.3): cli loads the ruleset with the rule_policy.json overlay
(empty -> byte-identical; an entry overlays a rule's status/floors at load);
read_rule_ledger/write_rule_ledger added for the evolve loop. A disabled rule
in the ledger no longer fires (covered by an integration test).
- FP -> reachability knob (4.4): a confirmed false-positive learning lowers a
finding's effective reachability multiplier in the score
(fp_reachability_overrides + scoring reachability_override) instead of deleting
the rule, so recall stays high while the score stops over-penalizing.
93 tests pass; ruff + mypy clean. Spec tasks 4.1-4.4 checked off — Phase 2
(Core: rules-as-data + benchmark signals) is complete. The loop-driven decision
to apply ledger flips remains the gated evolve loop (Phase 3/4).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
444d8c9490
commit
2d7ef48e52
@@ -18,6 +18,7 @@ from .benchmark import (
|
||||
from .calibration import (
|
||||
FalsePositiveLearning,
|
||||
calibrate_rankings,
|
||||
fp_reachability_overrides,
|
||||
learnings_from_verifications,
|
||||
load_false_positive_learnings,
|
||||
merge_false_positive_learnings,
|
||||
@@ -105,7 +106,9 @@ def _run_scan(path: Path, config_path: Path, mode: str, fail_on: str) -> ScanOut
|
||||
write_harness_config(config=config, processors=[], contract_mode="strict", path=run.root / "harness.json")
|
||||
runtime.start(mode=mode, target=run.target)
|
||||
policy = runtime.run_stage("policy_load", load_policy)
|
||||
ruleset = runtime.run_stage("ruleset_load", lambda: load_ruleset(DEFAULT_RULESET_DIR))
|
||||
ledger_path = run.target / CALIBRATION_DIR / "false_positive_learnings.json"
|
||||
rule_ledger_path = run.target / CALIBRATION_DIR / "rule_policy.json"
|
||||
ruleset = runtime.run_stage("ruleset_load", lambda: load_ruleset(DEFAULT_RULESET_DIR, rule_ledger_path))
|
||||
runtime.run_stage("policy_check", lambda: assert_rules_resolve(ruleset, policy))
|
||||
static_hints = runtime.run_stage("static_mapping", lambda: _load_static_hints(config.static_analysis.sarif_paths))
|
||||
write_static_hints(static_hints, run.root / "mapping" / "static_hints.json")
|
||||
@@ -118,7 +121,6 @@ def _run_scan(path: Path, config_path: Path, mode: str, fail_on: str) -> ScanOut
|
||||
targets = attach_reachability_hints(targets, entry_points)
|
||||
write_preprocess_artifact(snapshot, targets, run.root / "preprocess" / "file_targets.json")
|
||||
rankings = runtime.run_stage("rank", lambda: rank_targets(targets))
|
||||
ledger_path = run.target / CALIBRATION_DIR / "false_positive_learnings.json"
|
||||
prior_learnings = load_false_positive_learnings(ledger_path)
|
||||
rankings, calibrations = runtime.run_stage("calibrate", lambda: calibrate_rankings(rankings, prior_learnings))
|
||||
applied_calibrations = [calibration for calibration in calibrations if calibration.applied_learning_ids]
|
||||
@@ -168,6 +170,9 @@ def _run_scan(path: Path, config_path: Path, mode: str, fail_on: str) -> ScanOut
|
||||
)
|
||||
cwe_by_rule = {rule.rule_id: rule.cwe for rule in ruleset}
|
||||
rule_cwe_by_id = {finding.finding_id: cwe_by_rule.get(finding.finding_id.split(":", 1)[0], "") for finding in findings}
|
||||
# A confirmed false positive (prior-scan learning) lowers a finding's effective
|
||||
# reachability multiplier in the score instead of deleting the rule.
|
||||
reachability_override = fp_reachability_overrides(findings, prior_learnings)
|
||||
score_artifact = runtime.run_stage(
|
||||
"score",
|
||||
lambda: build_score_artifact(
|
||||
@@ -178,6 +183,7 @@ def _run_scan(path: Path, config_path: Path, mode: str, fail_on: str) -> ScanOut
|
||||
min_score=config.score.min_score,
|
||||
block_severity_reachable=config.score.block_severity_reachable,
|
||||
blocking=config.score.blocking,
|
||||
reachability_override=reachability_override,
|
||||
),
|
||||
)
|
||||
score_path = run.root / "score.json"
|
||||
|
||||
Reference in New Issue
Block a user