Files
Open_Ultra_SAST/src/openultrasast/harness.py
T
Marius CiepluchandClaude Fable 5.1 0d7d3f0dc9 chore: format the whole tree and lint the scratch measurement scripts; CI has been red since 25ae4b4
CI runs ruff format --check and ruff check over the whole repository; 29 files had drifted from the formatter and
four measurement scripts carried lint findings, so every push since 25ae4b4 failed before pytest ran.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-10-14 02:49:44 +02:00

118 lines
3.4 KiB
Python

from __future__ import annotations
import hashlib
import json
from collections.abc import Callable
from dataclasses import asdict, dataclass, field
from datetime import UTC, datetime
from pathlib import Path
from typing import Any, Literal
from .config import ResolvedConfig
EventType = Literal[
"task_start",
"stage_start",
"before_model",
"after_model",
"before_tool",
"after_tool",
"stage_end",
"task_end",
]
@dataclass(frozen=True)
class HarnessEvent:
event_type: EventType
scan_id: str
stage: str | None
timestamp: str
payload: dict[str, Any] = field(default_factory=dict)
@dataclass(frozen=True)
class SerializedHarnessConfig:
model_roles: dict[str, str | None]
prompt_hashes: dict[str, str]
class HarnessTraceWriter:
def __init__(self, path: Path, *, redact: bool = True) -> None:
self.path = path
self.redact = redact
self.path.parent.mkdir(parents=True, exist_ok=True)
def append(self, event: HarnessEvent) -> None:
line = json.dumps(asdict(event), sort_keys=True)
if self.redact:
from .redaction import redact_secrets
line = redact_secrets(line)
with self.path.open("a") as handle:
handle.write(line + "\n")
class HarnessRuntime:
def __init__(
self,
*,
scan_id: str,
config: ResolvedConfig,
trace_writer: HarnessTraceWriter,
) -> None:
self.scan_id = scan_id
self.config = config
self.trace_writer = trace_writer
self.state: dict[str, Any] = {"degradations": []}
def start(self, *, mode: str, target: Path) -> None:
self.emit("task_start", None, {"mode": mode, "target": str(target)})
def finish(self, *, status: str) -> None:
self.emit("task_end", None, {"status": status})
def run_stage(self, name: str, callback: Callable[[], Any]) -> Any:
self.emit("stage_start", name, {})
try:
result = callback()
except Exception as exc:
self.emit("stage_end", name, {"status": "failed", "error": f"{type(exc).__name__}: {exc}"})
raise
self.emit("stage_end", name, {"status": "succeeded"})
return result
def emit(self, event_type: EventType, stage: str | None, payload: dict[str, Any]) -> None:
event = HarnessEvent(
event_type=event_type,
scan_id=self.scan_id,
stage=stage,
timestamp=datetime.now(UTC).isoformat(),
payload=payload,
)
self.trace_writer.append(event)
def write_harness_config(
*,
config: ResolvedConfig,
path: Path,
prompts: dict[str, str] | None = None,
) -> None:
prompts = prompts or {}
serialized = SerializedHarnessConfig(
model_roles={
"ranker": config.models.ranker,
"hunter": config.models.hunter,
"verifier": None, # schema kept byte for byte; LLM verification runs on the plane
"patcher": None, # schema kept; `[models] patcher` was retired 2026-10-02 (never read)
},
prompt_hashes={name: _hash_text(prompt) for name, prompt in sorted(prompts.items())},
)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(asdict(serialized), indent=2, sort_keys=True) + "\n")
def _hash_text(text: str) -> str:
return hashlib.sha256(text.encode()).hexdigest()