feat(three-stage-scan): keep quick as stage-1 inventory

This commit is contained in:
Marius Ciepluch
2026-09-03 23:14:00 +02:00
parent 331b62f1c1
commit 66d985e9e6
6 changed files with 138 additions and 4 deletions
+4
View File
@@ -44,6 +44,7 @@ from .reports import scan_exit_code, write_manifest, write_markdown_report, writ
from .ruleset import DEFAULT_RULESET_DIR, load_ruleset
from .run import ScanRun, create_scan_run
from .scoring import build_score_artifact
from .stages import Stage, plan_for_mode, record_completed, stages_payload
from .verification import VerificationResult, write_verification_results
from .verify_judge import verify_findings_dispatch
@@ -137,6 +138,7 @@ def _run_scan(path: Path, config_path: Path, mode: str, fail_on: str) -> ScanOut
if not path.exists() or not path.is_dir():
raise SystemExit(f"scan path is not a directory: {path}")
plan = plan_for_mode(mode)
config = load_config(config_path if config_path.exists() else None)
# Capability gates for the optional HarnessX agentic plane. When the extra is
# absent (default/CI), both stay False and the deterministic path runs unchanged.
@@ -299,6 +301,7 @@ def _run_scan(path: Path, config_path: Path, mode: str, fail_on: str) -> ScanOut
)
score_path = run.root / "score.json"
score_path.write_text(json.dumps(score_artifact.to_dict(), indent=2, sort_keys=True) + "\n")
plan = record_completed(plan, Stage.STATIC)
runtime.run_stage(
"report", lambda: write_markdown_report(findings, markdown_path, verifications, redact=config.hardening.redact_secrets)
)
@@ -322,6 +325,7 @@ def _run_scan(path: Path, config_path: Path, mode: str, fail_on: str) -> ScanOut
score=score_artifact.to_dict(),
degradations=runtime.state["degradations"] or None,
fusion=[_fusion_summary(decision) for decision in fusion_decisions] or None,
stages=stages_payload(plan),
),
)
runtime.finish(status="succeeded")
+4 -1
View File
@@ -15,7 +15,7 @@ def write_markdown_report(
) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
verification_by_id = _verification_by_id(verifications or [])
lines = ["# OpenUltraSAST Report", "", f"Findings: {len(findings)}", ""]
lines = ["# OpenUltraSAST Report", "", f"Findings: {len(findings)}", "", "## Inventory", ""]
if not findings:
lines.append("No quick-mode findings were emitted.")
for finding in findings:
@@ -82,6 +82,7 @@ def write_manifest(
score: dict[str, object] | None = None,
degradations: list[dict[str, object]] | None = None,
fusion: list[dict[str, object]] | None = None,
stages: dict[str, object] | None = None,
) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
verification_by_id = _verification_by_id(verifications)
@@ -110,6 +111,8 @@ def write_manifest(
payload["degradations"] = degradations
if fusion is not None:
payload["fusion"] = fusion
if stages is not None:
payload["stages"] = stages
path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n")
+12
View File
@@ -35,3 +35,15 @@ def skip_as_degradation(stage: Stage, reason: str) -> dict[str, str]:
def record_skip(plan: StagePlan, stage: Stage, reason: str) -> StagePlan:
return replace(plan, skipped=plan.skipped + (skip_as_degradation(stage, reason),))
def record_completed(plan: StagePlan, stage: Stage) -> StagePlan:
return replace(plan, completed=plan.completed + (stage,))
def stages_payload(plan: StagePlan) -> dict[str, object]:
return {
"requested": [str(stage) for stage in plan.requested],
"completed": [str(stage) for stage in plan.completed],
"skipped": [dict(item) for item in plan.skipped],
}