refactor(harnessx-removal): retire the HarnessX scan paths (task 5)

The standard scan now runs run_hunter_pool, verify_findings and the
deterministic fuse_findings directly; the HarnessX hunter orchestrator,
the LLM judge dispatcher (verify_judge.py, deleted with its test) and the
LLM fusion panels are gone, and so are their extra-absent degradations.
host_under_harnessx is dropped from stage_processors.

config: [models] verifier and [fusion] panel_model/decider_model raise
RetiredConfigError naming the plane replacement (the CLI prints it and
exits 2); a [harnessx] section loads with one warning and is otherwise
ignored. harness.json keeps "verifier": null. Tests that asserted the
fallback are renamed to the behaviour they assert; the module-audit
manifest drops verify_judge. Nothing outside the two modules deleted in
task 6 imports harness_ext or hunter_harness.

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-09-30 12:13:26 +02:00
co-authored by Claude Opus 5.5
parent b68fe27c74
commit cbadc38d5e
17 changed files with 152 additions and 673 deletions
-65
View File
@@ -1,65 +0,0 @@
"""CLI capability-gated dispatch + graceful degradation (Phase 3 task 6.5).
Simulates the extra-absent environment (via a monkeypatched capability probe so the
behaviour is deterministic whether or not the optional extra is installed): standard
mode with models configured must fall back to the deterministic hunter/structural
verifier and record a degradation; with no models configured the manifest stays
byte-identical.
"""
import json
from pathlib import Path
import pytest
from openultrasast import cli
from openultrasast.cli import main
_VULN = "@app.route('/admin')\ndef admin():\n return eval(request.data)\n"
def _run(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, config_body: str | None) -> dict:
repo = tmp_path / "repo"
repo.mkdir()
(repo / "app.py").write_text(_VULN)
monkeypatch.setattr(cli, "has_harnessx", lambda: False) # simulate the extra being absent
monkeypatch.setenv("OPENULTRASAST_RUNS_DIR", ".runs")
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
monkeypatch.delenv("OPENULTRASAST_HUNTER_CLIENT", raising=False)
argv = ["scan", str(repo), "--mode", "standard"]
if config_body is not None:
cfg = tmp_path / "openultrasast.toml"
cfg.write_text(config_body)
argv += ["--config", str(cfg)]
assert main(argv) == 0
run_dir = sorted((repo / ".runs").iterdir())[-1]
return json.loads((run_dir / "manifest.json").read_text())
def test_degradation_recorded_when_models_set_but_extra_absent(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
manifest = _run(tmp_path, monkeypatch, '[models]\nhunter = "anthropic/claude"\nverifier = "anthropic/claude"\n')
degradations = manifest.get("degradations", [])
stages = {entry["stage"] for entry in degradations}
# `model` is present because the model layer records that no CPG engine was available rather than being
# silently absent (contributor-scan Req 1.3). This test is about the HarnessX extra, so it asserts over
# the HarnessX stages specifically instead of the whole set.
assert stages == {"hunter_pool", "verify", "model"}
assert all(entry["reason"] == "harnessx_extra_unavailable" for entry in degradations if entry["stage"] != "model")
# Fallback still produced findings (deterministic hunter ran).
assert manifest["findings"]
def test_manifest_has_no_harnessx_degradations_without_models(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
manifest = _run(tmp_path, monkeypatch, config_body=None)
degradations = manifest.get("degradations", [])
assert all(entry.get("reason") != "harnessx_extra_unavailable" for entry in degradations)
assert any(entry.get("reason") == "hunter_model_unavailable" for entry in degradations)
def test_standard_scan_falls_back_to_run_hunter_pool(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
calls: list[str] = []
real = cli.run_hunter_pool
monkeypatch.setattr(cli, "run_hunter_pool", lambda *a, **k: calls.append("fallback") or real(*a, **k))
_run(tmp_path, monkeypatch, '[models]\nhunter = "anthropic/claude"\n')
assert calls == ["fallback"]
+47 -27
View File
@@ -1,7 +1,11 @@
import logging
import os
from dataclasses import replace
from pathlib import Path
from openultrasast.config import load_config, load_dotenv, write_resolved_config
import pytest
from openultrasast.config import RetiredConfigError, load_config, load_dotenv, write_resolved_config
def test_load_config_reads_toml(tmp_path: Path) -> None:
@@ -29,48 +33,64 @@ def test_load_config_reads_toml(tmp_path: Path) -> None:
assert config.dynamic.network_scope == ("127.0.0.1:8080",)
def test_harnessx_defaults_when_section_absent() -> None:
config = load_config(None)
assert config.harnessx.provider == "anthropic"
assert config.harnessx.max_cost_usd == 2.0
assert config.harnessx.token_threshold == 120_000
def test_load_config_reads_harnessx_section(tmp_path: Path) -> None:
def test_retired_agentic_section_loads_with_one_warning(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None:
# retired 2026-09-30: a budget-only [harnessx] section loads with one warning naming the plane (Req 3.3)
config_path = tmp_path / "openultrasast.toml"
config_path.write_text(
"\n".join(
[
"[harnessx]",
'provider = "openai"',
"max_cost_usd = 5.0",
"token_threshold = 200000",
]
)
)
config_path.write_text('[harnessx]\nprovider = "openai"\nmax_cost_usd = 5.0\n[score]\nmin_score = 70\n') # retired 2026-09-30
config = load_config(config_path)
with caplog.at_level(logging.WARNING, logger="openultrasast.config"):
config = load_config(config_path)
assert config.harnessx.provider == "openai"
assert config.harnessx.max_cost_usd == 5.0
assert config.harnessx.token_threshold == 200_000
records = [record for record in caplog.records if record.name == "openultrasast.config"]
assert len(records) == 1
assert "ax plane" in records[0].getMessage() and "ops/ax/README.md" in records[0].getMessage()
assert config.score.min_score == 70 # the rest of the file still loads
assert config == replace(load_config(None), score=config.score) # and nothing else is read from the section
@pytest.mark.parametrize(
("body", "key", "replacement"),
[
('[models]\nverifier = "gpt-4o"\n', "`[models] verifier`", "`verify` + `agree`"),
('[fusion]\npanel_model = "gpt-4o"\n', "`[fusion] panel_model`", "`agree` task"),
('[fusion]\ndecider_model = "gpt-4o"\n', "`[fusion] decider_model`", "`agree` task"),
],
)
def test_retired_llm_keys_fail_naming_the_replacement(tmp_path: Path, body: str, key: str, replacement: str) -> None:
config_path = tmp_path / "openultrasast.toml"
config_path.write_text(body)
with pytest.raises(RetiredConfigError) as raised:
load_config(config_path)
message = str(raised.value)
assert key in message and replacement in message and "ousast plane run" in message and "Remove the key" in message
def test_the_cli_exits_2_on_a_retired_key(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
from openultrasast.cli import main
(tmp_path / "repo").mkdir()
config_path = tmp_path / "openultrasast.toml"
config_path.write_text('[models]\nverifier = "gpt-4o"\n')
assert main(["scan", str(tmp_path / "repo"), "--config", str(config_path)]) == 2
assert "`[models] verifier`" in capsys.readouterr().err
def test_fusion_defaults_when_section_absent() -> None:
config = load_config(None)
assert config.fusion.enabled is True
assert config.fusion.panel_model is None
assert config.fusion.high_assurance is False
def test_load_config_reads_fusion_section(tmp_path: Path) -> None:
config_path = tmp_path / "openultrasast.toml"
config_path.write_text('[fusion]\nenabled = true\npanel_model = "gpt-4o"\ndecider_model = "gpt-4o"\nhigh_assurance = true\n')
config_path.write_text("[fusion]\nenabled = false\nhigh_assurance = true\n")
config = load_config(config_path)
assert config.fusion.panel_model == "gpt-4o"
assert config.fusion.decider_model == "gpt-4o"
assert config.fusion.enabled is False
assert config.fusion.high_assurance is True
+2 -11
View File
@@ -9,11 +9,10 @@ from openultrasast.findings import StaticFinding
from openultrasast.fusion import (
FusionDisposition,
fuse_finding,
fuse_findings_dispatch,
fuse_findings,
fusion_triggers,
should_fuse,
)
from openultrasast.harness_ext import has_harnessx
from openultrasast.verification import EvidenceLevel, VerificationResult, VerificationStatus
@@ -135,18 +134,10 @@ def test_dispatch_only_fuses_triggered_findings() -> None:
_finding(finding_id="hot:app.py:3", severity="critical"),
_finding(finding_id="cold:lib.py:9", severity="low", reachability="unknown"),
]
decisions = fuse_findings_dispatch(findings, [])
decisions = fuse_findings(findings, [])
assert [d.finding_id for d in decisions] == ["hot:app.py:3"]
def test_dispatch_records_degradation_when_llm_requested_but_extra_absent() -> None:
if has_harnessx():
pytest.skip("HarnessX extra installed; exercises the extra-absent degradation path")
decisions = fuse_findings_dispatch([_finding()], [], panel_model="gpt-4o", use_harnessx=True)
assert decisions and decisions[0].decision_source == "deterministic-reconciler"
assert any("fusion_llm_unavailable" in note for note in decisions[0].degradations)
def test_decision_serializes_with_full_disclosure() -> None:
decision = fuse_finding(_finding(), _verification(VerificationStatus.ACCEPTED, verified=True), ["severity:critical"])
payload = json.loads(json.dumps(decision.to_dict()))
-1
View File
@@ -141,7 +141,6 @@ def test_deep_scan_unsafe_hunter_snippet_is_safety_rejected_without_docker_job(t
monkeypatch.setenv("OPENULTRASAST_RUNS_DIR", ".runs")
monkeypatch.setenv("OPENULTRASAST_SANDBOX_PROBE", "1")
monkeypatch.setenv("OPENULTRASAST_HUNTER_CLIENT", "unsafe-snippet")
monkeypatch.setattr(cli, "has_harnessx", lambda: False)
monkeypatch.setattr(cli, "resolve_sandbox_runner", lambda: fake)
docker_argv = _guard_docker(monkeypatch)
+23 -40
View File
@@ -1,12 +1,10 @@
"""Scan-pipeline integrity & serialization (Phase 4 task 9).
9.1 — with the extra absent, quick and standard scans run through the existing sync
driver with behaviour unchanged from the deterministic baseline, and a
requested-but-unavailable HarnessX stage records a manifest degradation.
9.1 — quick and standard scans run through the sync driver with the deterministic
hunter, verifier and fusion; a configured hunter model never changes the
deterministic findings.
9.2 — every persisted scan-state slot serializes and restores without loss (guards
against a non-serializable slot silently restoring as empty). This covers the
current artifact-persistence model; the HarnessX processor slot-contract variant
(spec task 9.2 over task 6.3) remains deferred with 6.3.
against a non-serializable slot silently restoring as empty).
"""
import json
@@ -15,7 +13,6 @@ from pathlib import Path
import pytest
from openultrasast import cli
from openultrasast.benchmark import load_findings
from openultrasast.cli import main
from openultrasast.findings import StaticFinding, write_findings
@@ -27,14 +24,13 @@ from openultrasast.verification import EvidenceLevel, VerificationResult, Verifi
_VULN = "@app.route('/admin')\ndef admin():\n return eval(request.data)\n"
# ---- 9.1 fallback parity + degradation --------------------------------------
# ---- 9.1 deterministic parity + degradation --------------------------------------
def _scan(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, *, mode: str, config_body: str | None, harnessx: bool) -> tuple[list, dict]:
def _scan(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, *, mode: str, config_body: str | None) -> tuple[list, dict]:
repo = tmp_path / "repo"
repo.mkdir(parents=True)
(repo / "app.py").write_text(_VULN)
monkeypatch.setattr(cli, "has_harnessx", lambda: harnessx)
monkeypatch.setenv("OPENULTRASAST_RUNS_DIR", ".runs")
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
monkeypatch.delenv("OPENULTRASAST_HUNTER_CLIENT", raising=False)
@@ -50,40 +46,27 @@ def _scan(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, *, mode: str, config_
return findings, manifest
def test_standard_scan_extra_absent_matches_deterministic_baseline(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
# Requested-but-unavailable HarnessX (models set, extra absent) must produce the
# same findings as the plain deterministic path (no models), differing only by
# the recorded degradation.
requested, requested_manifest = _scan(
tmp_path / "a",
monkeypatch,
mode="standard",
config_body='[models]\nhunter = "openai/gpt-4o"\nverifier = "openai/gpt-4o"\n',
harnessx=False,
)
baseline, baseline_manifest = _scan(tmp_path / "b", monkeypatch, mode="standard", config_body=None, harnessx=False)
def test_standard_scan_with_a_hunter_model_keeps_deterministic_findings(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
# A configured hunter model (no client reachable here) must leave the deterministic findings byte-identical.
requested, requested_manifest = _scan(tmp_path / "a", monkeypatch, mode="standard", config_body='[models]\nhunter = "openai/gpt-4o"\n')
baseline, baseline_manifest = _scan(tmp_path / "b", monkeypatch, mode="standard", config_body=None)
assert requested == baseline # byte-identical findings -> sync driver behaviour unchanged
stages = {entry["stage"] for entry in requested_manifest.get("degradations", [])}
# `model` joins the set when the model layer runs without a CPG engine: findings are byte-identical
# (asserted above) and the only difference is that the scan now SAYS the arbiter was unavailable
# (contributor-scan Req 1.3) rather than being silently absent.
assert stages == {"hunter_pool", "verify", "model"}
assert {e["reason"] for e in requested_manifest["degradations"] if e["stage"] == "model"} == {"cpg_unavailable"}
assert all(entry["reason"] == "harnessx_extra_unavailable" for entry in requested_manifest["degradations"] if entry["stage"] != "model")
assert all(entry["reason"] != "hunter_model_unavailable" for entry in requested_manifest["degradations"])
assert [e["reason"] for e in baseline_manifest.get("degradations", []) if e["stage"] != "model"] == ["hunter_model_unavailable"]
assert requested == baseline
# The hunter model runs the tool hunter (its client is absent here, so it contributes nothing) and removes the
# `map` skip; `model` records that no CPG engine was available (contributor-scan Req 1.3).
assert requested_manifest["degradations"] == [{"reason": "cpg_unavailable", "stage": "model"}]
assert {(e["stage"], e["reason"]) for e in baseline_manifest["degradations"]} == {
("model", "cpg_unavailable"),
("map", "hunter_model_unavailable"),
}
def test_quick_scan_is_unaffected_by_the_extra(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
# Quick mode never touches the agentic plane: identical findings whether the
# extra is present or absent, and never a degradation.
present, present_manifest = _scan(tmp_path / "a", monkeypatch, mode="quick", config_body=None, harnessx=True)
absent, _ = _scan(tmp_path / "b", monkeypatch, mode="quick", config_body=None, harnessx=False)
def test_quick_scan_records_no_degradation(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
# Quick mode never touches a model: findings are produced and no degradation is recorded.
findings, manifest = _scan(tmp_path, monkeypatch, mode="quick", config_body=None)
assert present == absent
assert present # findings were produced
assert "degradations" not in present_manifest
assert findings
assert "degradations" not in manifest
# ---- 9.2 per-slot serialization round-trip ----------------------------------
+1 -26
View File
@@ -2,8 +2,7 @@
Proves: deterministic stages carry work in declared slots, a slot-contract violation
is raised on undeclared slot access, the pipeline is model-disabled, the slot pipeline
reproduces the deterministic quick-scan path byte-for-byte, lifecycle hooks fire, and
(when the extra is present) a stage can run under HarnessX with the model disabled.
reproduces the deterministic quick-scan path byte-for-byte, and lifecycle hooks fire.
"""
from collections.abc import Mapping
@@ -13,13 +12,10 @@ from typing import Any
import pytest
from openultrasast.gate import _quick_scan
from openultrasast.harness_ext import has_harnessx
from openultrasast.slot_contract import SlotContractError, SlotContractMixin, SlotPipeline
from openultrasast.stage_processors import (
PreprocessProcessor,
build_deterministic_pipeline,
deterministic_processors,
host_under_harnessx,
run_quick_pipeline,
)
@@ -110,24 +106,3 @@ def test_pipeline_emits_lifecycle_hooks_per_stage() -> None:
for name in names:
assert ("stage_start", name) in recorder.events
assert ("stage_end", name) in recorder.events
# ---- optional: run under HarnessX with the model disabled (1.6) --------------
@pytest.mark.skipif(not has_harnessx(), reason="requires the harnessx extra")
def test_host_under_harnessx_disables_the_model() -> None: # pragma: no cover - needs the extra
import asyncio
from harnessx.core.events import BeforeModelEvent
from harnessx.core.processor import MultiHookProcessor
hosted = host_under_harnessx(PreprocessProcessor())
assert isinstance(hosted, MultiHookProcessor)
async def drive() -> list[Any]:
event = BeforeModelEvent(run_id="test", step_id=0)
return [out async for out in hosted.on_before_model(event)]
emitted = asyncio.run(drive())
assert emitted and emitted[-1].skip_model is True
+2 -4
View File
@@ -219,14 +219,13 @@ def _guard_docker(monkeypatch: pytest.MonkeyPatch) -> list[list[str]]:
def test_standard_scripted_hunter_emits_tool_hunter_suspicion(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
from openultrasast import cli, tool_hunter
from openultrasast import tool_hunter
from openultrasast.cli import main
repo = _standard_repo(tmp_path)
config = _write_hunter_config(tmp_path)
monkeypatch.setenv("OPENULTRASAST_RUNS_DIR", ".runs")
monkeypatch.setenv("OPENULTRASAST_HUNTER_CLIENT", "scripted")
monkeypatch.setattr(cli, "has_harnessx", lambda: False)
docker_argv = _guard_docker(monkeypatch)
hunter_calls: list[int] = []
real_hunter = tool_hunter.run_tool_hunter
@@ -256,14 +255,13 @@ def test_standard_scripted_hunter_emits_tool_hunter_suspicion(tmp_path: Path, mo
def test_dump_only_hunter_client_emits_no_hunter_findings(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
from openultrasast import cli, tool_hunter
from openultrasast import tool_hunter
from openultrasast.cli import main
repo = _standard_repo(tmp_path)
config = _write_hunter_config(tmp_path)
monkeypatch.setenv("OPENULTRASAST_RUNS_DIR", ".runs")
monkeypatch.setenv("OPENULTRASAST_HUNTER_CLIENT", "dump-only")
monkeypatch.setattr(cli, "has_harnessx", lambda: False)
docker_argv = _guard_docker(monkeypatch)
hunter_calls: list[int] = []
real_hunter = tool_hunter.run_tool_hunter
-70
View File
@@ -1,70 +0,0 @@
"""Verifier dispatcher: structural fallback (default) + judge verdict mapping (Phase 3 task 6.4)."""
from openultrasast.findings import StaticFinding
from openultrasast.verification import VerificationStatus, verify_findings
from openultrasast.verify_judge import _verdict_to_verification, verify_findings_dispatch
def _finding(reachability: str = "reachable") -> StaticFinding:
return StaticFinding(
finding_id="python-unsafe-eval:app.py:3",
path="app.py",
title="Dynamic Python execution needs review",
severity="critical",
confidence="medium",
evidence_level="static_corroboration",
rationale="Static pattern matched.",
line=3,
function_name="admin" if reachability == "reachable" else None,
reachability_status=reachability,
reachability_evidence=[{"kind": "route", "access_level": "public", "line": 1, "end_line": 3}]
if reachability == "reachable"
else [],
reachability_conditions=[],
tags=["syscall_entry"],
ranking_priority=3.0,
)
def test_dispatch_falls_back_to_structural_by_default() -> None:
findings = [_finding("reachable"), _finding("unknown")]
assert verify_findings_dispatch(findings) == verify_findings(findings)
def test_dispatch_stays_structural_when_model_unset_or_extra_absent() -> None:
findings = [_finding("reachable")]
# use_harnessx requested but no model -> structural; and no model -> structural.
assert verify_findings_dispatch(findings, verifier_model=None, use_harnessx=True) == verify_findings(findings)
assert verify_findings_dispatch(findings, verifier_model="claude-x", use_harnessx=False) == verify_findings(findings)
def test_dispatch_does_not_import_harnessx_on_fallback(assert_cold_of_harnessx) -> None: # type: ignore[no-untyped-def]
# Even with a model and use_harnessx=True, when the capability is absent the
# dispatch must fall back without importing the extra. Simulate absence so the
# guarantee holds whether or not the extra is installed in this environment.
assert_cold_of_harnessx(
"import openultrasast.verify_judge as vj\n"
"vj.has_harnessx = lambda: False\n"
"from openultrasast.findings import StaticFinding\n"
"f = StaticFinding(finding_id='x:app.py:1', path='app.py', title='t', severity='low',\n"
" confidence='low', evidence_level='static_corroboration', rationale='r', line=1,\n"
" function_name=None, reachability_status='unknown', reachability_evidence=[],\n"
" reachability_conditions=[], tags=[], ranking_priority=1.0)\n"
"vj.verify_findings_dispatch([f], verifier_model='claude-x', use_harnessx=True)\n"
)
def test_verdict_mapping_is_evidence_gated() -> None:
finding = _finding("reachable")
accepted = _verdict_to_verification(finding, {"verdict": "plausible", "confidence": 0.9, "cause": "tainted sink reached"})
low_conf = _verdict_to_verification(finding, {"verdict": "plausible", "confidence": 0.3})
rejected = _verdict_to_verification(finding, {"verdict": "unsupported", "cause": "input is constant"})
hedging = _verdict_to_verification(finding, {"verdict": "hedging"})
infra = _verdict_to_verification(finding, {"verdict": "no_answer"})
assert accepted.status == VerificationStatus.ACCEPTED and accepted.verified
assert accepted.pro_case == "tainted sink reached"
assert low_conf.status == VerificationStatus.NEEDS_EVIDENCE
assert rejected.status == VerificationStatus.REJECTED and rejected.counter_case == "input is constant"
assert hedging.status == VerificationStatus.NEEDS_EVIDENCE
assert infra.status == VerificationStatus.NEEDS_EVIDENCE # infra failure never auto-rejects