pre-push(17.2): name every skipped check in plain words

One terminal line per distinct coverage reason (push/explain.py): hex-encoded
paths decoded, per-question sha256 tokens dropped and counted, unknown reasons
still printed; the JSON artifact keeps the raw values. A changed language with
no engine frontend and no quick rules is recorded and printed as "language not
covered: <lang> (...)", in pre-push and in `ousast scan`. The scan report
renders the eight degradations cli.py recorded but never showed, and any
reason without a sentence. The hook forwards OUSAST_COMPARISON_BASE.
Requirement 9.2.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
Marius Ciepluch
2026-10-02 22:10:33 +02:00
co-authored by Claude Opus 5.5
parent bf07cd4d1d
commit f555dee640
11 changed files with 445 additions and 15 deletions
+43
View File
@@ -654,3 +654,46 @@ def test_a_clean_scan_states_no_coverage_gaps(tmp_path) -> None: # type: ignore
degradations=[],
)
assert "### What could not be analysed" not in path.read_text()
def test_every_recorded_scan_degradation_is_rendered(tmp_path) -> None: # type: ignore[no-untyped-def]
"""pre-push 17.2 / Req 9.2: the eight degradations cli.py records and the report used to drop."""
from openultrasast.reports import write_markdown_report
reasons = [
{"stage": "model", "reason": "learning_endpoint_unavailable"},
{"stage": "model", "reason": "cpg_unavailable"},
{"stage": "overlay", "reason": "facts_unavailable", "detail": "missing table"},
{"stage": "obligations", "reason": "facts_unavailable"},
{"stage": "obligations", "reason": "policy_invalid", "detail": "bad toml"},
{"stage": "model", "reason": "model_withheld_execution_budget"},
{"stage": "budget", "reason": "max_findings_exceeded", "requested": 5, "actual": 9},
{"stage": "map", "reason": "hunter_model_unavailable"},
{"stage": "regress", "reason": "sandbox_unavailable"},
{"stage": "x", "reason": "not_yet_described"},
]
path = tmp_path / "report.md"
write_markdown_report([], path, [], degradations=reasons)
text = path.read_text()
for needle in (
"No model endpoint is configured",
"Joern engine is not installed",
"fact tables for the `overlay` stage",
"fact tables for the `obligations` stage",
"`obligations` policy could not be loaded",
"time budget leaves no room",
"first 5 of 9 findings",
"needs a model and none is available",
"Docker is not available",
"`not_yet_described`",
):
assert needle in text, needle
def test_quick_scan_names_a_language_nothing_covers(tmp_path, capsys) -> None: # type: ignore[no-untyped-def]
"""pre-push 17.2 / Req 9.2: a Go-only tree is not a silent zero."""
(tmp_path / "run.go").write_text('package main\n\nimport "os/exec"\n\nfunc run(c string) { exec.Command("sh", "-c", c).Run() }\n')
assert main(["scan", str(tmp_path), "--mode", "quick"]) == 0
assert "notice: language not covered: go (1 files)" in capsys.readouterr().out
report = next((tmp_path / ".openultrasast" / "runs").iterdir()) / "report.md"
assert "Not a clean result for go (1 files)" in report.read_text()