mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 19:59:26 +00:00
feat(three-stage-scan): select bounded regress candidates with sev-5 extras
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"""Isolated regression snippets and verdicts."""
|
||||
|
||||
from .candidate import INCONCLUSIVE, SAFETY_REJECTED, RegressionRunner, RegressionVerdict
|
||||
from .candidate import INCONCLUSIVE, SAFETY_REJECTED, RegressionRunner, RegressionVerdict, select_candidates
|
||||
from .safety import UnsafeSnippetError, check_snippet_safety
|
||||
|
||||
__all__ = [
|
||||
@@ -10,4 +10,5 @@ __all__ = [
|
||||
"RegressionVerdict",
|
||||
"UnsafeSnippetError",
|
||||
"check_snippet_safety",
|
||||
"select_candidates",
|
||||
]
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
"""Safety-gated regression execution (task 4.3)."""
|
||||
"""Candidate selection and safety-gated regression execution."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping, Sequence
|
||||
from dataclasses import dataclass
|
||||
|
||||
from ..complexity.ledger import hotspot_key, select_forced_candidates
|
||||
from ..complexity.map import Hotspot
|
||||
from ..findings import StaticFinding
|
||||
from ..sandbox import SandboxJob, SandboxResult, SandboxRunner
|
||||
from .safety import UnsafeSnippetError, check_snippet_safety
|
||||
|
||||
@@ -13,6 +17,58 @@ TRIGGERABLE = "triggerable"
|
||||
NOT_TRIGGERABLE = "not_triggerable"
|
||||
|
||||
|
||||
def select_candidates(
|
||||
hotspots: Sequence[Hotspot],
|
||||
findings: Sequence[StaticFinding],
|
||||
*,
|
||||
max_candidates: int,
|
||||
policy_severity_by_id: Mapping[str, object] | None = None,
|
||||
rule_cwe: Mapping[str, str] | None = None,
|
||||
) -> tuple[Hotspot, ...]:
|
||||
"""Select min(max_candidates, ranked hotspots) ∪ sev-5 reachable inventory.
|
||||
|
||||
The cap applies to the ranked hotspot slice. Forced policy-severity-5
|
||||
reachable (or inferred-file-surface) inventory stays in the set even when a
|
||||
ledger overlay demoted its hotspot out of that slice.
|
||||
"""
|
||||
ranked = tuple(sorted(hotspots, key=lambda item: (-item.score, item.path, item.function_name or "")))
|
||||
selected = list(ranked[: max(max_candidates, 0)])
|
||||
seen = {hotspot_key(item.path, item.function_name) for item in selected}
|
||||
extras: list[Hotspot] = []
|
||||
for finding in select_forced_candidates(findings, policy_severity_by_id or {}, rule_cwe=rule_cwe):
|
||||
hotspot = _hotspot_for_finding(finding, ranked) or _hotspot_from_forced_finding(finding)
|
||||
key = hotspot_key(hotspot.path, hotspot.function_name)
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
extras.append(hotspot)
|
||||
return tuple(selected + extras)
|
||||
|
||||
|
||||
def _hotspot_for_finding(finding: StaticFinding, hotspots: Sequence[Hotspot]) -> Hotspot | None:
|
||||
for hotspot in hotspots:
|
||||
if finding.finding_id in hotspot.inventory_finding_ids:
|
||||
return hotspot
|
||||
key = hotspot_key(finding.path, finding.function_name)
|
||||
for hotspot in hotspots:
|
||||
if hotspot_key(hotspot.path, hotspot.function_name) == key:
|
||||
return hotspot
|
||||
return None
|
||||
|
||||
|
||||
def _hotspot_from_forced_finding(finding: StaticFinding) -> Hotspot:
|
||||
return Hotspot(
|
||||
path=finding.path,
|
||||
function_name=finding.function_name,
|
||||
score=0.0,
|
||||
band="low",
|
||||
signals={},
|
||||
rationale="forced reachable severity-5 inventory",
|
||||
test_hint=None,
|
||||
inventory_finding_ids=(finding.finding_id,),
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RegressionVerdict:
|
||||
verdict: str
|
||||
|
||||
Reference in New Issue
Block a user