mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-11-13 01:48:17 +00:00
feat(detection-core): migrate benchmark readiness and admission consumers
This commit is contained in:
+69
-42
@@ -19,11 +19,13 @@ from collections import Counter, deque
|
||||
from concurrent.futures import FIRST_COMPLETED, ThreadPoolExecutor, wait
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import timedelta
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
import yaml
|
||||
|
||||
from benchmarks.valid.artifact_contract import read_benchmark_artifact, write_benchmark_artifact
|
||||
from openultrasast.config import load_dotenv
|
||||
from openultrasast.plane.memory import PRESIGNED_ARCHIVE_SUFFIX, S3Store, open_store
|
||||
from openultrasast.search.task_storage import diagnostic, exception_reason
|
||||
@@ -495,21 +497,21 @@ def schedule(pending, lanes, out, execute, complete, *, fallbacks=(), overflow=T
|
||||
return failures
|
||||
|
||||
|
||||
def dispatch(items, workload, out, executors, *, lanes=("ax", "ax", "docker"), overflow=True, max_attempts=2):
|
||||
"""Digest checkpoints, one instrument rerun, sandbox retry, priority VM fallback."""
|
||||
if not isinstance(max_attempts, int) or not 1 <= max_attempts <= 2:
|
||||
raise ValueError("batch attempts must be one or two")
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
items = list(items)
|
||||
if len({item.id for item in items}) != len(items):
|
||||
raise ValueError("duplicate dispatch item id")
|
||||
def _dispatch_path(out, item):
|
||||
return out / (hashlib.sha256(item.id.encode()).hexdigest() + ".json")
|
||||
|
||||
|
||||
def _read_dispatch_record(out, item, record_kind):
|
||||
path = _dispatch_path(out, item)
|
||||
if not path.exists():
|
||||
return {}
|
||||
return read_benchmark_artifact(path, record_kind) if record_kind is not None else json.loads(path.read_text())
|
||||
|
||||
|
||||
def _dispatch_queue(items, out, record_kind):
|
||||
pending, fallbacks = [], []
|
||||
|
||||
def path(item):
|
||||
return out / (hashlib.sha256(item.id.encode()).hexdigest() + ".json")
|
||||
|
||||
for item in items:
|
||||
previous = json.loads(path(item).read_text()) if path(item).exists() else {}
|
||||
previous = _read_dispatch_record(out, item, record_kind)
|
||||
if previous.get("input_digest") == item.input_digest:
|
||||
if previous.get("fallback_pending"):
|
||||
fallbacks.append((item, previous))
|
||||
@@ -517,37 +519,62 @@ def dispatch(items, workload, out, executors, *, lanes=("ax", "ax", "docker"), o
|
||||
if previous.get("done"):
|
||||
continue
|
||||
pending.append(item)
|
||||
return pending, fallbacks
|
||||
|
||||
def execute(item, lane, previous):
|
||||
output = out / hashlib.sha256(item.id.encode()).hexdigest()
|
||||
output.mkdir(exist_ok=True)
|
||||
attempts = []
|
||||
for attempt in range(max_attempts):
|
||||
attempt_output = output / ("attempt-" + uuid.uuid4().hex)
|
||||
attempt_output.mkdir()
|
||||
record = executors[lane](item, attempt_output, attempt)
|
||||
for artifact in attempt_output.iterdir():
|
||||
if artifact.is_file():
|
||||
shutil.copyfile(artifact, output / artifact.name)
|
||||
attempts.append(
|
||||
{
|
||||
"lane": lane,
|
||||
"status": record.get("status"),
|
||||
"attempt": attempt,
|
||||
"transport": record.get("ax_attempts", []),
|
||||
"docker_errors": record.get("docker_errors", []),
|
||||
}
|
||||
)
|
||||
if record.get("status") not in {"sandbox_failure", "instrument_failure"}:
|
||||
break
|
||||
return {**record, "attempts": (previous or {}).get("attempts", []) + attempts, "input_digest": item.input_digest, "done": True}
|
||||
|
||||
def complete(item, lane, record, progress):
|
||||
fallback = lane in {"ax", "kind"} and record.get("status") in {"sandbox_failure", "oom"} and "docker" in lanes
|
||||
record["fallback_pending"] = fallback
|
||||
write_json(path(item), record)
|
||||
progress["last_item"] = hashlib.sha256(item.id.encode()).hexdigest()
|
||||
return record.get("status") not in {"ok", "unanalysable", "quick_only", "engine_covered"}, record if fallback else None
|
||||
def _dispatch_attempt(item, lane, output, executors, attempt):
|
||||
attempt_output = output / ("attempt-" + uuid.uuid4().hex)
|
||||
attempt_output.mkdir()
|
||||
record = executors[lane](item, attempt_output, attempt)
|
||||
for artifact in attempt_output.iterdir():
|
||||
if artifact.is_file():
|
||||
shutil.copyfile(artifact, output / artifact.name)
|
||||
summary = {
|
||||
"lane": lane,
|
||||
"status": record.get("status"),
|
||||
"attempt": attempt,
|
||||
"transport": record.get("ax_attempts", []),
|
||||
"docker_errors": record.get("docker_errors", []),
|
||||
}
|
||||
return record, summary
|
||||
|
||||
|
||||
def _execute_dispatch(item, lane, previous, *, out, executors, max_attempts):
|
||||
output = out / hashlib.sha256(item.id.encode()).hexdigest()
|
||||
output.mkdir(exist_ok=True)
|
||||
attempts = []
|
||||
for attempt in range(max_attempts):
|
||||
record, summary = _dispatch_attempt(item, lane, output, executors, attempt)
|
||||
attempts.append(summary)
|
||||
if record.get("status") not in {"sandbox_failure", "instrument_failure"}:
|
||||
break
|
||||
return {**record, "attempts": (previous or {}).get("attempts", []) + attempts, "input_digest": item.input_digest, "done": True}
|
||||
|
||||
|
||||
def _complete_dispatch(item, lane, record, progress, *, out, lanes, record_kind):
|
||||
fallback = lane in {"ax", "kind"} and record.get("status") in {"sandbox_failure", "oom"} and "docker" in lanes
|
||||
record["fallback_pending"] = fallback
|
||||
path = _dispatch_path(out, item)
|
||||
if record_kind is None:
|
||||
write_json(path, record)
|
||||
else:
|
||||
write_benchmark_artifact(path, record_kind, record, replace=True)
|
||||
progress["last_item"] = hashlib.sha256(item.id.encode()).hexdigest()
|
||||
failed = record.get("status") not in {"ok", "unanalysable", "quick_only", "engine_covered"}
|
||||
return failed, record if fallback else None
|
||||
|
||||
|
||||
def dispatch(items, workload, out, executors, *, lanes=("ax", "ax", "docker"), overflow=True, max_attempts=2, record_kind=None):
|
||||
"""Digest checkpoints, one instrument rerun, sandbox retry, priority VM fallback."""
|
||||
if not isinstance(max_attempts, int) or not 1 <= max_attempts <= 2:
|
||||
raise ValueError("batch attempts must be one or two")
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
items = list(items)
|
||||
if len({item.id for item in items}) != len(items):
|
||||
raise ValueError("duplicate dispatch item id")
|
||||
pending, fallbacks = _dispatch_queue(items, out, record_kind)
|
||||
execute = partial(_execute_dispatch, out=out, executors=executors, max_attempts=max_attempts)
|
||||
complete = partial(_complete_dispatch, out=out, lanes=lanes, record_kind=record_kind)
|
||||
|
||||
return schedule(pending, lanes, out, execute, complete, fallbacks=fallbacks, overflow=overflow)
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
from benchmarks.valid.artifact_contract import write_benchmark_artifact
|
||||
|
||||
|
||||
class TaskEntryError(RuntimeError):
|
||||
"""A task-entry diagnostic containing only locally controlled text."""
|
||||
@@ -91,68 +93,94 @@ def failure_record(pin, reason):
|
||||
}
|
||||
|
||||
|
||||
def main(*, work_root=Path("/tmp"), worker_script=None, readiness_budget=60):
|
||||
pin = {}
|
||||
failed = False
|
||||
def _read_worker_result(path, pin):
|
||||
"""Validate the engine worker's raw handoff before benchmark-owned persistence."""
|
||||
try:
|
||||
value = json.loads(path.read_text())
|
||||
except (OSError, json.JSONDecodeError) as error:
|
||||
raise TaskEntryError("worker result is unreadable") from error
|
||||
if not isinstance(value, dict) or {"schema_version", "artifact_kind", "artifact_digest"}.intersection(value):
|
||||
raise TaskEntryError("worker result has an unsupported raw schema")
|
||||
units = value.get("units")
|
||||
expected = [unit.get("unit") for unit in pin.get("units", [])]
|
||||
if value.get("done") is not True or not isinstance(units, list):
|
||||
raise TaskEntryError("worker did not write a completed result")
|
||||
actual = [unit.get("unit") for unit in units if isinstance(unit, dict)]
|
||||
if len(actual) != len(units) or actual != expected:
|
||||
raise TaskEntryError("worker result units do not match the prepared pin")
|
||||
return value
|
||||
|
||||
|
||||
def _produce_result(work_root, worker_script, readiness_budget, state):
|
||||
output = work_root / "out"
|
||||
result = output / "result.json"
|
||||
step = "prepare output"
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
result.unlink(missing_ok=True)
|
||||
questions = work_root / "questions.json"
|
||||
state["step"] = "GET questions"
|
||||
download(os.environ["QUESTIONS_URL"], questions, readiness_budget=readiness_budget)
|
||||
state["step"] = "parse questions"
|
||||
pin = json.loads(questions.read_text())
|
||||
if not isinstance(pin, dict):
|
||||
raise ValueError("questions object must contain a prepared pin")
|
||||
state["pin"] = pin
|
||||
source, case = work_root / "source.tar", work_root / "case"
|
||||
state["step"] = "GET source"
|
||||
download(os.environ["SOURCE_URL"], source)
|
||||
state["step"] = "unpack source"
|
||||
case.mkdir(parents=True, exist_ok=True)
|
||||
unpack(source, case)
|
||||
deadline = int(os.environ.get("DEADLINE", "900"))
|
||||
command = _worker_command(worker_script, questions, case, result, deadline)
|
||||
state["step"] = "run worker"
|
||||
completed = subprocess.run(command, timeout=deadline + 5, check=False)
|
||||
if completed.returncode:
|
||||
raise TaskEntryError(f"worker exited {completed.returncode}")
|
||||
state["step"] = "read worker result"
|
||||
worker_result = _read_worker_result(result, pin) if result.is_file() else {}
|
||||
if not worker_result:
|
||||
raise TaskEntryError("worker did not write a completed result")
|
||||
write_benchmark_artifact(result, "engine-task-result", worker_result, replace=True)
|
||||
return output, result
|
||||
|
||||
|
||||
def _worker_command(worker_script, questions, case, result, deadline):
|
||||
return [
|
||||
sys.executable,
|
||||
str(worker_script or Path(__file__).with_name("engine_trace_worker.py")),
|
||||
"--worker",
|
||||
str(questions),
|
||||
"--root",
|
||||
str(case),
|
||||
"--output",
|
||||
str(result),
|
||||
"--heap-profile",
|
||||
"cluster",
|
||||
"--deadline",
|
||||
str(deadline),
|
||||
"--question-deadline",
|
||||
os.environ.get("QUESTION_DEADLINE", "120"),
|
||||
]
|
||||
|
||||
|
||||
def _failure_detail(error):
|
||||
if isinstance(error, urllib.error.HTTPError):
|
||||
return f"HTTP {error.code}"
|
||||
return str(error) if isinstance(error, TaskEntryError) else type(error).__name__
|
||||
|
||||
|
||||
def _persist_failure(work_root, state, error):
|
||||
output, result = work_root / "out", work_root / "out/result.json"
|
||||
reason = f"{state['step']}: {_failure_detail(error)}"
|
||||
try:
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
result.unlink(missing_ok=True)
|
||||
questions = work_root / "questions.json"
|
||||
step = "GET questions"
|
||||
download(os.environ["QUESTIONS_URL"], questions, readiness_budget=readiness_budget)
|
||||
step = "parse questions"
|
||||
pin = json.loads(questions.read_text())
|
||||
if not isinstance(pin, dict):
|
||||
pin = {}
|
||||
raise ValueError("questions object must contain a prepared pin")
|
||||
source = work_root / "source.tar"
|
||||
step = "GET source"
|
||||
download(os.environ["SOURCE_URL"], source)
|
||||
step = "unpack source"
|
||||
case = work_root / "case"
|
||||
case.mkdir(parents=True, exist_ok=True)
|
||||
unpack(source, case)
|
||||
step = "configure worker"
|
||||
deadline = int(os.environ.get("DEADLINE", "900"))
|
||||
command = [
|
||||
sys.executable,
|
||||
str(worker_script or Path(__file__).with_name("engine_trace_worker.py")),
|
||||
"--worker",
|
||||
str(questions),
|
||||
"--root",
|
||||
str(case),
|
||||
"--output",
|
||||
str(result),
|
||||
"--heap-profile",
|
||||
"cluster",
|
||||
"--deadline",
|
||||
str(deadline),
|
||||
"--question-deadline",
|
||||
os.environ.get("QUESTION_DEADLINE", "120"),
|
||||
]
|
||||
step = "run worker"
|
||||
completed = subprocess.run(command, timeout=deadline + 5, check=False)
|
||||
if completed.returncode:
|
||||
raise TaskEntryError(f"worker exited {completed.returncode}")
|
||||
step = "read worker result"
|
||||
if not result.is_file() or not json.loads(result.read_text()).get("done"):
|
||||
raise TaskEntryError("worker did not write a completed result")
|
||||
except Exception as exc:
|
||||
failed = True
|
||||
# Avoid including exception text containing a presigned URL in durable output.
|
||||
if isinstance(exc, urllib.error.HTTPError):
|
||||
detail = f"HTTP {exc.code}"
|
||||
else:
|
||||
detail = str(exc) if isinstance(exc, TaskEntryError) else type(exc).__name__
|
||||
reason = f"{step}: {detail}"
|
||||
try:
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
result.write_text(json.dumps(failure_record(pin, reason)) + "\n")
|
||||
except Exception:
|
||||
print("task entry could not write failure result", file=sys.stderr)
|
||||
write_benchmark_artifact(result, "engine-task-failure", failure_record(state["pin"], reason), replace=True)
|
||||
except Exception:
|
||||
print("task entry could not write failure result", file=sys.stderr)
|
||||
return output
|
||||
|
||||
|
||||
def _pack_result(work_root, output, readiness_budget, failed):
|
||||
step = "pack result"
|
||||
try:
|
||||
archive = work_root / "result.tar"
|
||||
@@ -166,6 +194,17 @@ def main(*, work_root=Path("/tmp"), worker_script=None, readiness_budget=60):
|
||||
except Exception as exc:
|
||||
detail = f"HTTP {exc.code}" if isinstance(exc, urllib.error.HTTPError) else type(exc).__name__
|
||||
print(f"{step}: {detail}; dispatcher deadline remains authoritative", file=sys.stderr)
|
||||
|
||||
|
||||
def main(*, work_root=Path("/tmp"), worker_script=None, readiness_budget=60):
|
||||
state = {"pin": {}, "step": "prepare output"}
|
||||
failed = False
|
||||
try:
|
||||
output, _result = _produce_result(work_root, worker_script, readiness_budget, state)
|
||||
except Exception as error:
|
||||
failed = True
|
||||
output = _persist_failure(work_root, state, error)
|
||||
_pack_result(work_root, output, readiness_budget, failed)
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,13 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
from benchmarks.unseen import score
|
||||
from benchmarks.unseen.replay import load_slice
|
||||
from benchmarks.valid.artifact_contract import write_benchmark_jsonl
|
||||
from benchmarks.valid.hunt_handoff import (
|
||||
HUNT_RUNNER_PRODUCER,
|
||||
discover_hunt_runner_context,
|
||||
persist_hunt_runner_records,
|
||||
)
|
||||
from openultrasast import stats
|
||||
from openultrasast.model.endpoint import _PRICES as PRICES
|
||||
|
||||
@@ -130,12 +137,25 @@ def cost(log: Path) -> dict | None:
|
||||
def main(root: Path) -> dict:
|
||||
out: dict = {}
|
||||
all_suspicions = []
|
||||
repo = Path(__file__).resolve().parents[3]
|
||||
manifest = repo / "benchmarks/valid/train/pool-train.toml"
|
||||
freeze = json.loads((repo / "benchmarks/valid/train/freeze-train.json").read_text())
|
||||
changes = load_slice(manifest, "1")
|
||||
for arm, folder in (("dspy", "dspy-1"), ("hunter", "hunter-1")):
|
||||
records = score.load_records(root / folder)
|
||||
source = root / folder
|
||||
destination = root / "benchmark-v2" / arm
|
||||
context = discover_hunt_runner_context(
|
||||
source,
|
||||
manifest,
|
||||
producer=HUNT_RUNNER_PRODUCER,
|
||||
freeze_digest=freeze["freeze_digest"],
|
||||
)
|
||||
persist_hunt_runner_records(source, destination, context=context, changes=changes)
|
||||
records = score.load_records(destination)
|
||||
out[arm], suspicions = arm_summary(records, arm)
|
||||
all_suspicions.extend(suspicions)
|
||||
out["dspy"]["cost"] = cost(root / "dspy-1.log")
|
||||
(root / "suspicions.jsonl").write_text("".join(json.dumps(s) + "\n" for s in all_suspicions))
|
||||
write_benchmark_jsonl(root / "suspicions.jsonl", "access-control-suspicion", all_suspicions, replace=True)
|
||||
return out
|
||||
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ import statistics
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
from benchmarks.ax.batch import write_json
|
||||
from benchmarks.unseen.replay import canonical, load_slice, run_replays, sample_changes
|
||||
from benchmarks.unseen.score import coverage, load_records
|
||||
from benchmarks.valid.artifact_contract import write_benchmark_artifact
|
||||
|
||||
|
||||
def stage_times(record):
|
||||
@@ -120,7 +120,7 @@ def main():
|
||||
result.update(
|
||||
image=args.image, deadline_seconds=args.deadline, sample_digest=hashlib.sha256(canonical([c["id"] for c in changes])).hexdigest()
|
||||
)
|
||||
write_json(args.record, result)
|
||||
write_benchmark_artifact(args.record, "deadline-calibration", result, replace=True)
|
||||
return 0 if result["status"] == "ok" else 2
|
||||
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ from pathlib import Path
|
||||
|
||||
from benchmarks.ax.batch import AXLane, DockerLane, Item, Workload, dispatch, write_json
|
||||
from benchmarks.unseen.score import coverage
|
||||
from benchmarks.valid.artifact_contract import read_benchmark_artifact, write_benchmark_sequence
|
||||
|
||||
|
||||
def make_item(change, arm, image, *, deadline_scale=1.0, lane="ax", deadline=None):
|
||||
@@ -180,7 +181,7 @@ def main():
|
||||
changes = load_slice(args.manifest, args.slice, args.private_manifest)
|
||||
factor = args.deadline_scale
|
||||
if args.calibration:
|
||||
calibration = json.loads(args.calibration.read_text())
|
||||
calibration = read_benchmark_artifact(args.calibration, "deadline-calibration")
|
||||
if calibration.get("status") != "ok" or calibration.get("image") != args.image:
|
||||
parser.error("calibration must be successful and match the replay image")
|
||||
factor = calibration["quick_total"]["factor"]
|
||||
@@ -213,9 +214,11 @@ def run_replays(changes, args, out, lane, *, deadline_scale=1.0, deadline=None):
|
||||
if out.resolve().is_relative_to(Path(__file__).resolve().parents[2]):
|
||||
raise ValueError("raw replay output must be outside the repository")
|
||||
items = [make_item(c, args.arm, args.image, deadline_scale=deadline_scale, lane=lane, deadline=deadline) for c in changes]
|
||||
write_json(
|
||||
write_benchmark_sequence(
|
||||
out / "inputs.json",
|
||||
"benchmark-arm-inputs",
|
||||
[{"item_id": item.id, "change": change, "input_digest": item.input_digest} for item, change in zip(items, changes, strict=True)],
|
||||
replace=True,
|
||||
)
|
||||
maximum = max(
|
||||
max(json.loads(i.inputs["SPEC_URL"])["deadline_seconds"], json.loads(i.inputs["SPEC_URL"])["base_deadline_seconds"]) for i in items
|
||||
@@ -240,7 +243,7 @@ def run_replays(changes, args, out, lane, *, deadline_scale=1.0, deadline=None):
|
||||
return record
|
||||
|
||||
executors[name] = execute
|
||||
return dispatch(items, workload, out, executors, lanes=lanes)
|
||||
return dispatch(items, workload, out, executors, lanes=lanes, record_kind="benchmark-arm-record")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -9,7 +9,13 @@ import math
|
||||
import random
|
||||
from collections import Counter, defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
from benchmarks.valid.artifact_contract import (
|
||||
read_benchmark_artifact,
|
||||
read_benchmark_sequence,
|
||||
write_benchmark_artifact,
|
||||
)
|
||||
from openultrasast.model.taxonomy import load_families
|
||||
from openultrasast.push.report import CAPABILITY_VETOES
|
||||
|
||||
@@ -324,13 +330,17 @@ def gate(a, b, *, slice_id=None, informed=False, binary=True, threshold=None, th
|
||||
}
|
||||
|
||||
|
||||
def load_records(path):
|
||||
def load_records(path: Path) -> list[dict[str, Any]]:
|
||||
"""Load only canonical benchmark v2 records; producer handoffs are explicit adapters."""
|
||||
path = Path(path)
|
||||
if path.is_file():
|
||||
return json.loads(path.read_text())
|
||||
rows = []
|
||||
for entry in json.loads((path / "inputs.json").read_text()):
|
||||
return [cast(dict[str, Any], row) for row in read_benchmark_sequence(path, "benchmark-arm-records")]
|
||||
rows: list[dict[str, Any]] = []
|
||||
entries = read_benchmark_sequence(path / "inputs.json", "benchmark-arm-inputs")
|
||||
for raw_entry in entries:
|
||||
entry = cast(dict[str, Any], raw_entry)
|
||||
result = path / (hashlib.sha256(entry["item_id"].encode()).hexdigest() + ".json")
|
||||
record = json.loads(result.read_text()) if result.exists() else {}
|
||||
record: dict[str, Any] = cast(dict[str, Any], read_benchmark_artifact(result, "benchmark-arm-record")) if result.exists() else {}
|
||||
if record.get("input_digest") != entry["input_digest"]:
|
||||
record = {}
|
||||
rows.append({**entry["change"], **record, "id": entry["change"]["id"]})
|
||||
@@ -356,7 +366,7 @@ def main():
|
||||
result["unseen"] = gate(load_records(args.against) if args.against else [], current, slice_id=args.slice_id, informed=args.informed)
|
||||
if native and result["flagged"]:
|
||||
result["unseen"] = {"gate": "not_run", "reason": "native_agreement"}
|
||||
args.out.write_text(json.dumps(result, sort_keys=True, indent=2) + "\n")
|
||||
write_benchmark_artifact(args.out, "unseen-score", result, replace=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -15,34 +15,22 @@ mutates or re-labels the manifest it consumes.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import tomllib
|
||||
from collections.abc import Callable, Sequence
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from benchmarks.unseen.replay import canonical
|
||||
from benchmarks.valid.admission_scoring import AdmissionCase, OrdinaryChange, admission_report, score_v2_admission
|
||||
from benchmarks.valid.arm_regression import BundleDigests, _input_digest, _prove_one
|
||||
from benchmarks.valid.admission_scoring import AdmissionCase, OrdinaryChange, score_v2_admission
|
||||
from benchmarks.valid.population_custody import AdmissionSpendAuthority, GuardedPopulationCustodian, ReleaseReview
|
||||
from benchmarks.valid.prover_admission import AdmissionManifest, validate_admission_manifest
|
||||
from openultrasast.core.admission_case import AdmissionCaseResult
|
||||
from openultrasast.core.admission_report import AdmissionReplay
|
||||
from openultrasast.core.admission_spend import authorize_admission
|
||||
from openultrasast.preprocess import detect_language
|
||||
from openultrasast.source import canonical_language, ranges
|
||||
|
||||
__all__ = [
|
||||
"admission_ordinary",
|
||||
"admission_pair_cases",
|
||||
"assemble_admission_report",
|
||||
"coverage_share",
|
||||
"fixed_side_change",
|
||||
"load_admission_population",
|
||||
"locate_function",
|
||||
"run_admission",
|
||||
"run_v2_admission",
|
||||
"V2AdmissionRunInput",
|
||||
"site_judgment",
|
||||
@@ -225,156 +213,3 @@ def coverage_share(records: tuple[dict[str, Any], ...]) -> float:
|
||||
return 0.0
|
||||
covered = sum(1 for total in per_repository.values() if total > 0)
|
||||
return covered / len(per_repository)
|
||||
|
||||
|
||||
def load_admission_population(
|
||||
payload: dict[str, object], pool_path: Path, freeze_path: Path
|
||||
) -> tuple[tuple[dict[str, Any], ...], tuple[dict[str, Any], ...]]:
|
||||
"""Validate the manifest and the reviewed freeze before opening labels (Req 12.1)."""
|
||||
manifest = validate_admission_manifest(payload)
|
||||
pool = tomllib.loads(Path(pool_path).read_text(encoding="utf-8"))
|
||||
freeze = json.loads(Path(freeze_path).read_text(encoding="utf-8"))
|
||||
digest = hashlib.sha256(canonical(pool)).hexdigest()
|
||||
if digest != freeze.get("freeze_digest"):
|
||||
raise ValueError(f"population: pool digest {digest[:12]} does not match the reviewed freeze")
|
||||
if freeze.get("guard_result") != 0:
|
||||
raise ValueError("guard: held-out guard failed for this population")
|
||||
if manifest.population_digest != digest:
|
||||
raise ValueError("population: manifest names a different population than the reviewed freeze")
|
||||
changes = tuple(
|
||||
{**change, "id": _change_identity(repository, change), "repo": str(repository["id"])}
|
||||
for repository in pool.get("repository", [])
|
||||
for change in repository.get("changes", [])
|
||||
)
|
||||
vulnerable = tuple(change for change in changes if change.get("kind") == "vulnerability")
|
||||
ordinary = tuple(change for change in changes if change.get("kind") == "ordinary")
|
||||
return vulnerable, ordinary
|
||||
|
||||
|
||||
def _change_identity(repository: dict[str, Any], change: dict[str, Any]) -> str:
|
||||
"""The pool's canonical change identity, as ``replay.load_slice`` derives it."""
|
||||
payload = [repository["id"], change["kind"], change["base"], change["head"]]
|
||||
return hashlib.sha256(canonical(payload)).hexdigest()
|
||||
|
||||
|
||||
def assemble_admission_report(
|
||||
manifest: AdmissionManifest,
|
||||
vulnerable: tuple[dict[str, Any], ...],
|
||||
fixed: tuple[dict[str, Any], ...],
|
||||
ordinary: tuple[dict[str, Any], ...],
|
||||
*,
|
||||
criteria: dict[str, float] | None,
|
||||
budget_seconds: float = 120.0,
|
||||
) -> dict[str, Any]:
|
||||
"""Bridge measured records to the paired admission report (Reqs 12.7, 12.8).
|
||||
|
||||
Every change is budget-gated: pairs by their combined seconds, ordinary
|
||||
changes individually (design.md:370 "fails any change over 120 seconds").
|
||||
"""
|
||||
failures = sum(1 for record in (*vulnerable, *fixed, *ordinary) if record.get("status") == "instrument_failure")
|
||||
report = admission_report(
|
||||
manifest,
|
||||
admission_pair_cases(vulnerable, fixed),
|
||||
admission_ordinary(ordinary),
|
||||
coverage=coverage_share((*vulnerable, *fixed, *ordinary)),
|
||||
instrument_failures=failures,
|
||||
budget_seconds=budget_seconds,
|
||||
criteria=criteria,
|
||||
)
|
||||
overruns = [
|
||||
f"budget:{record['id']}:{float(record.get('seconds', 0.0)):g}s>{budget_seconds:g}s"
|
||||
for record in ordinary
|
||||
if float(record.get("seconds", 0.0)) > budget_seconds
|
||||
]
|
||||
if overruns:
|
||||
report["failed_gates"] = [*report["failed_gates"], *overruns]
|
||||
report["decision"] = "NO-GO"
|
||||
return report
|
||||
|
||||
|
||||
def _resume_or_prove(
|
||||
bundle: BundleDigests,
|
||||
population: str,
|
||||
change: dict[str, Any],
|
||||
repo_path: Path,
|
||||
side_dir: Path,
|
||||
) -> dict[str, Any]:
|
||||
from benchmarks.valid.prover_arm import _write_json
|
||||
|
||||
side_dir.mkdir(parents=True, exist_ok=True)
|
||||
path = side_dir / f"{change['id']}.json"
|
||||
if path.is_file():
|
||||
previous: dict[str, Any] = json.loads(path.read_text(encoding="utf-8"))
|
||||
if previous.get("input_digest") == _input_digest(change, ARM_NAME):
|
||||
return previous
|
||||
raise ValueError(f"existing record for {change['id']} does not match this arm's inputs")
|
||||
record = _prove_one(bundle, ARM_NAME, population, change, repo_path)
|
||||
_write_json(path, record)
|
||||
return record
|
||||
|
||||
|
||||
def run_admission(
|
||||
*,
|
||||
payload: dict[str, object],
|
||||
pool_path: Path,
|
||||
freeze_path: Path,
|
||||
repos: dict[str, str],
|
||||
bundle: BundleDigests,
|
||||
out_dir: Path,
|
||||
criteria: dict[str, float] | None,
|
||||
budget_seconds: float = 120.0,
|
||||
) -> dict[str, Any]:
|
||||
"""Consume the reviewed manifest, prove the population, return GO/NO-GO."""
|
||||
vulnerable, ordinary = load_admission_population(payload, pool_path, freeze_path)
|
||||
manifest = validate_admission_manifest(payload)
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
vulnerable_records = tuple(
|
||||
_resume_or_prove(bundle, manifest.population_digest, change, Path(repos[change["repo"]]).expanduser(), out_dir / "vulnerable")
|
||||
for change in vulnerable
|
||||
)
|
||||
fixed_records = tuple(
|
||||
_resume_or_prove(
|
||||
bundle,
|
||||
manifest.population_digest,
|
||||
_fixed_side_change(Path(repos[change["repo"]]).expanduser(), change),
|
||||
Path(repos[change["repo"]]).expanduser(),
|
||||
out_dir / "fixed",
|
||||
)
|
||||
for change in vulnerable
|
||||
)
|
||||
ordinary_records = tuple(
|
||||
_resume_or_prove(bundle, manifest.population_digest, change, Path(repos[change["repo"]]).expanduser(), out_dir / "ordinary")
|
||||
for change in ordinary
|
||||
)
|
||||
return assemble_admission_report(
|
||||
manifest, vulnerable_records, fixed_records, ordinary_records, criteria=criteria, budget_seconds=budget_seconds
|
||||
)
|
||||
|
||||
|
||||
def _fixed_side_change(repo_path: Path, change: dict[str, Any]) -> dict[str, Any]:
|
||||
lines, operation = _fixed_site(repo_path, change)
|
||||
return fixed_side_change(change, fixed_lines=lines, fixed_operation=operation)
|
||||
|
||||
|
||||
def _fixed_site(repo_path: Path, change: dict[str, Any]) -> tuple[tuple[int, int] | None, str]:
|
||||
"""(lines, operation) for the labelled function at the fixed revision.
|
||||
|
||||
A locator miss (the name is present but ``ranges`` could not name it) is
|
||||
"missed" and can never become a clean judgment (design.md:371).
|
||||
"""
|
||||
from benchmarks.valid.catalog import local_git
|
||||
|
||||
git = local_git(repo_path)
|
||||
text = git.runner(
|
||||
["git", "-C", str(repo_path), "show", f"{change['base']}:{change['path']}"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
).stdout
|
||||
name = str(change.get("function"))
|
||||
try:
|
||||
functions = ranges(str(change["path"]), text, canonical_language(detect_language(repo_path / change["path"])))
|
||||
except ValueError:
|
||||
functions = ()
|
||||
lines = locate_function(functions, name=name)
|
||||
return lines, classify_fixed_site(lines, name_in_text=name in text)
|
||||
|
||||
@@ -10,12 +10,13 @@ indeterminate and never converted to clean results; ordinary changes whose
|
||||
review found a real missing-authorization defect are refused before scoring.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from typing import Any, Protocol
|
||||
|
||||
from benchmarks.unseen import score
|
||||
from benchmarks.valid.prover_admission import AdmissionManifest
|
||||
from openultrasast.core import canonical
|
||||
from openultrasast.core.admission_case import AdmissionCaseRelease, AdmissionCaseResult
|
||||
from openultrasast.core.admission_context import AdmissionValidationContext
|
||||
@@ -31,6 +32,18 @@ __all__ = ["AdmissionCase", "AdmissionScoringError", "OrdinaryChange", "admissio
|
||||
_JUDGMENTS = ("unguarded", "discharged", "indeterminate")
|
||||
|
||||
|
||||
class AdmissionManifest(Protocol):
|
||||
"""Structural shape used only by the parked legacy scoring helpers."""
|
||||
|
||||
population_role: str
|
||||
analyzer_revision: str
|
||||
fact_set_digest: str
|
||||
acquisition_record_digest: str
|
||||
configuration_digest: str
|
||||
population_digest: str
|
||||
families: tuple[str, ...]
|
||||
|
||||
|
||||
class AdmissionScoringError(ValueError):
|
||||
"""Raised when a population cannot be scored; the message names the reason."""
|
||||
|
||||
|
||||
@@ -23,11 +23,18 @@ from typing import Any
|
||||
|
||||
from benchmarks.unseen.extract import InstrumentFailure
|
||||
from benchmarks.unseen.replay import canonical
|
||||
from benchmarks.valid.artifact_contract import read_benchmark_artifact, write_benchmark_artifact
|
||||
from benchmarks.valid.catalog import local_git
|
||||
from benchmarks.valid.development_runner import AnalyzerBundle, stage_funnel
|
||||
from benchmarks.valid.prover_arm import _write_json
|
||||
from openultrasast.core.legacy_v1 import InstrumentFailure as CoreInstrumentFailure
|
||||
from openultrasast.core.legacy_v1 import check_reads
|
||||
from openultrasast.core.coverage import (
|
||||
InputManifest,
|
||||
InputReadReceipt,
|
||||
derive_input_manifest_digest,
|
||||
validate_instrument,
|
||||
)
|
||||
from openultrasast.core.coverage import (
|
||||
InstrumentFailure as CoreInstrumentFailure,
|
||||
)
|
||||
from openultrasast.hunt.producers import Context, _Absent, _safe_path
|
||||
from openultrasast.hunt.prover import prove_change
|
||||
|
||||
@@ -119,25 +126,8 @@ def _population_digest(changes: tuple[dict[str, Any], ...]) -> str:
|
||||
return hashlib.sha256(joined.encode()).hexdigest()[:12]
|
||||
|
||||
|
||||
def _prove_one(
|
||||
bundle: BundleDigests,
|
||||
arm_name: str,
|
||||
population: str,
|
||||
change: dict[str, Any],
|
||||
repo_path: Path,
|
||||
) -> dict[str, Any]:
|
||||
started = time.monotonic()
|
||||
instrument: dict[str, Any] = dict(
|
||||
hook_exit=0,
|
||||
hook_bytes_read=0,
|
||||
changed_bytes={},
|
||||
file_bytes={},
|
||||
head_verified=False,
|
||||
base_verified=False,
|
||||
suspect_fast=False,
|
||||
failure=None,
|
||||
)
|
||||
record: dict[str, Any] = {
|
||||
def _arm_record(bundle: BundleDigests, arm_name: str, population: str, change: dict[str, Any], instrument: dict[str, Any]):
|
||||
return {
|
||||
**change,
|
||||
"arm": arm_name,
|
||||
"analyzer_revision": bundle.analyzer_revision,
|
||||
@@ -157,40 +147,71 @@ def _prove_one(
|
||||
"stage_funnel": {},
|
||||
"push": {"admission": {"defects": [], "dispositions": []}, "quick_tier": []},
|
||||
}
|
||||
|
||||
|
||||
def _apply_prover_result(record: dict[str, Any], result: Any, change: dict[str, Any], instrument: dict[str, Any]) -> None:
|
||||
funnel = stage_funnel(result)
|
||||
record.update(
|
||||
findings=list(result.findings),
|
||||
traces=list(result.traces),
|
||||
coverage=asdict(result.coverage),
|
||||
degradations=list(result.degradations),
|
||||
stage_funnel=funnel,
|
||||
)
|
||||
record["complexity"] = {
|
||||
"regions": len(change.get("files", ())),
|
||||
"calls": sum(funnel.get("resolution", {}).values()),
|
||||
"changed_bytes": sum(instrument.get("changed_bytes", {}).values()),
|
||||
}
|
||||
record["push"]["admission"]["defects"] = [
|
||||
{**finding, "defect_id": finding["id"], "source": "prover", "locations": [[finding["path"], finding["line"]]]}
|
||||
for finding in result.findings
|
||||
]
|
||||
|
||||
|
||||
def _prove_checkout(record: dict[str, Any], change: dict[str, Any], repo_path: Path, instrument: dict[str, Any]) -> None:
|
||||
git = local_git(repo_path)
|
||||
git.run("cat-file", "-e", change["base"] + "^{commit}")
|
||||
instrument["base_verified"] = True
|
||||
with tempfile.TemporaryDirectory(prefix="ousast-arm-") as temporary:
|
||||
root = Path(temporary) / "head"
|
||||
git.run("-c", "core.hooksPath=/dev/null", "worktree", "add", "--detach", str(root), change["head"])
|
||||
try:
|
||||
_verify_checkout(root)
|
||||
ctx = Context(root, change)
|
||||
ctx.verify_head()
|
||||
instrument["head_verified"] = True
|
||||
_instrument_reads(ctx, root, change, instrument)
|
||||
result = prove_change(root, change, ctx, judgment_mode=True)
|
||||
instrument["hook_bytes_read"] = sum(instrument["file_bytes"].values())
|
||||
_apply_prover_result(record, result, change, instrument)
|
||||
finally:
|
||||
git.run("worktree", "remove", "--force", str(root))
|
||||
|
||||
|
||||
def _prove_one(
|
||||
bundle: BundleDigests,
|
||||
arm_name: str,
|
||||
population: str,
|
||||
change: dict[str, Any],
|
||||
repo_path: Path,
|
||||
) -> dict[str, Any]:
|
||||
started = time.monotonic()
|
||||
instrument: dict[str, Any] = dict(
|
||||
hook_exit=0,
|
||||
hook_bytes_read=0,
|
||||
changed_bytes={},
|
||||
changed_digests={},
|
||||
file_bytes={},
|
||||
file_digests={},
|
||||
head_verified=False,
|
||||
base_verified=False,
|
||||
suspect_fast=False,
|
||||
failure=None,
|
||||
)
|
||||
record = _arm_record(bundle, arm_name, population, change, instrument)
|
||||
try:
|
||||
git = local_git(repo_path)
|
||||
git.run("cat-file", "-e", change["base"] + "^{commit}")
|
||||
instrument["base_verified"] = True
|
||||
with tempfile.TemporaryDirectory(prefix="ousast-arm-") as temporary:
|
||||
root = Path(temporary) / "head"
|
||||
git.run("-c", "core.hooksPath=/dev/null", "worktree", "add", "--detach", str(root), change["head"])
|
||||
try:
|
||||
_verify_checkout(root)
|
||||
ctx = Context(root, change)
|
||||
ctx.verify_head()
|
||||
instrument["head_verified"] = True
|
||||
_instrument_reads(ctx, root, change, instrument)
|
||||
result = prove_change(root, change, ctx, judgment_mode=True)
|
||||
instrument["hook_bytes_read"] = sum(instrument["file_bytes"].values())
|
||||
funnel = stage_funnel(result)
|
||||
record.update(
|
||||
findings=list(result.findings),
|
||||
traces=list(result.traces),
|
||||
coverage=asdict(result.coverage),
|
||||
degradations=list(result.degradations),
|
||||
stage_funnel=funnel,
|
||||
)
|
||||
record["complexity"] = {
|
||||
"regions": len(change.get("files", ())),
|
||||
"calls": sum(funnel.get("resolution", {}).values()),
|
||||
"changed_bytes": sum(instrument.get("changed_bytes", {}).values()),
|
||||
}
|
||||
record["push"]["admission"]["defects"] = [
|
||||
{**finding, "defect_id": finding["id"], "source": "prover", "locations": [[finding["path"], finding["line"]]]}
|
||||
for finding in result.findings
|
||||
]
|
||||
finally:
|
||||
git.run("worktree", "remove", "--force", str(root))
|
||||
_prove_checkout(record, change, repo_path, instrument)
|
||||
except Exception as error: # noqa: BLE001 - the record must survive any instrument failure
|
||||
instrument.update(hook_exit=2, failure=type(error).__name__)
|
||||
record.update(status="instrument_failure", findings=[])
|
||||
@@ -223,12 +244,19 @@ def _instrument_reads(ctx: Context, root: Path, change: dict[str, Any], instrume
|
||||
held = local_git(root).runner(["git", "-C", str(root), "cat-file", "-s", f"HEAD:{relative}"], capture_output=True, text=True)
|
||||
if getattr(error, "reason", "") == "empty_source" and held.returncode == 0 and int(held.stdout.strip() or 0) == 0:
|
||||
instrument["empty_files"] = instrument.get("empty_files", 0) + 1
|
||||
continue
|
||||
instrument["file_bytes"][relative] = 0
|
||||
instrument["file_digests"][relative] = hashlib.sha256(b"").hexdigest()
|
||||
if relative in changed:
|
||||
instrument["changed_bytes"][relative] = 0
|
||||
instrument["changed_digests"][relative] = instrument["file_digests"][relative]
|
||||
raise InstrumentFailure("zero-byte read") from error
|
||||
raise InstrumentFailure("unread tracked file") from error
|
||||
size = len(text.encode("utf-8"))
|
||||
instrument["file_bytes"][relative] = size
|
||||
instrument["file_digests"][relative] = hashlib.sha256(text.encode("utf-8")).hexdigest()
|
||||
if relative in changed:
|
||||
instrument["changed_bytes"][relative] = size
|
||||
instrument["changed_digests"][relative] = instrument["file_digests"][relative]
|
||||
if not size:
|
||||
raise InstrumentFailure("zero-byte read")
|
||||
|
||||
@@ -248,13 +276,13 @@ def run_arm(
|
||||
for change in changes:
|
||||
path = out_dir / f"{change['id']}.json"
|
||||
if path.is_file():
|
||||
previous = json.loads(path.read_text(encoding="utf-8"))
|
||||
previous = read_benchmark_artifact(path, "development-arm-record")
|
||||
if previous.get("input_digest") == _input_digest(change, arm_name):
|
||||
records.append(previous)
|
||||
continue
|
||||
raise ValueError(f"existing record for {change['id']} does not match this arm's inputs")
|
||||
record = _prove_one(bundle, arm_name, population, change, Path(repos[change["repo"]]).expanduser())
|
||||
_write_json(path, record)
|
||||
write_benchmark_artifact(path, "development-arm-record", record)
|
||||
records.append(record)
|
||||
return tuple(records)
|
||||
|
||||
@@ -263,17 +291,25 @@ def _masked(repo: str) -> str:
|
||||
return hashlib.sha256(repo.encode()).hexdigest()[:12]
|
||||
|
||||
|
||||
def _validate_record_instrument(instrument: dict[str, Any]) -> None:
|
||||
sizes = {**instrument.get("file_bytes", {}), **instrument.get("changed_bytes", {})}
|
||||
digests = {**instrument.get("file_digests", {}), **instrument.get("changed_digests", {})}
|
||||
input_ids = tuple(sorted(sizes))
|
||||
try:
|
||||
manifest = InputManifest(input_ids, derive_input_manifest_digest(input_ids))
|
||||
receipts = tuple(InputReadReceipt(path, path, True, int(sizes[path]), digests.get(path)) for path in input_ids)
|
||||
validate_instrument(manifest, receipts)
|
||||
except (CoreInstrumentFailure, TypeError, ValueError) as error:
|
||||
raise InstrumentFailure(str(error)) from error
|
||||
|
||||
|
||||
def summarize_arm(records: tuple[dict[str, Any], ...]) -> dict[str, Any]:
|
||||
"""Aggregate one arm; refuse any zero-byte read, publish budget and masked coverage."""
|
||||
judgment = {"unguarded": 0, "discharged": 0, "indeterminate": 0}
|
||||
per_repository: dict[str, dict[str, Any]] = {}
|
||||
for record in records:
|
||||
instrument = record.get("instrument", {})
|
||||
merged = {**instrument.get("file_bytes", {}), **instrument.get("changed_bytes", {})}
|
||||
try:
|
||||
check_reads(merged) # core instrument refusal: a zero-byte read invalidates the run
|
||||
except CoreInstrumentFailure as error:
|
||||
raise InstrumentFailure(str(error)) from error
|
||||
_validate_record_instrument(instrument)
|
||||
funnel = record.get("stage_funnel", {})
|
||||
for kind, count in funnel.get("judgment", {}).items():
|
||||
judgment[kind] += int(count)
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
"""Strict benchmark-owned persistence for canonical v2 artifacts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import fcntl
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
from collections.abc import Iterator, Mapping, Sequence
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from typing import TypeAlias, cast
|
||||
|
||||
from openultrasast.core.canonical import JsonValue, content_digest
|
||||
|
||||
BENCHMARK_ARTIFACT_SCHEMA = "openultrasast.benchmark-artifact.v2"
|
||||
ArtifactPayload: TypeAlias = Mapping[str, JsonValue]
|
||||
ArtifactRows: TypeAlias = Sequence[Mapping[str, JsonValue]]
|
||||
_RESERVED = frozenset({"schema_version", "artifact_kind", "artifact_digest"})
|
||||
_SEQUENCE_KEYS = frozenset({"artifact_mode", "sequence_count", "sequence_digest", "rows"})
|
||||
_JSONL_HEADER_KEYS = frozenset({"artifact_mode", "sequence_count", "sequence_digest"})
|
||||
_JSONL_ROW_KEYS = frozenset({"sequence_index", "sequence_count", "sequence_digest", "artifact"})
|
||||
|
||||
|
||||
def _canonical_value(value: object) -> JsonValue:
|
||||
if value is None or isinstance(value, (str, bool, int, float)):
|
||||
return value
|
||||
if isinstance(value, Mapping):
|
||||
return {str(key): _canonical_value(item) for key, item in value.items()}
|
||||
if isinstance(value, Sequence) and not isinstance(value, (str, bytes)):
|
||||
return tuple(_canonical_value(item) for item in value)
|
||||
raise ValueError(f"unsupported benchmark artifact value: {type(value).__name__}")
|
||||
|
||||
|
||||
def _kind(kind: str) -> str:
|
||||
if not isinstance(kind, str) or not kind:
|
||||
raise ValueError("benchmark artifact kind must be nonempty text")
|
||||
return kind
|
||||
|
||||
|
||||
def _artifact_digest(kind: str, payload: Mapping[str, JsonValue]) -> str:
|
||||
return content_digest(
|
||||
{
|
||||
"schema_version": BENCHMARK_ARTIFACT_SCHEMA,
|
||||
"artifact_kind": kind,
|
||||
"payload": _canonical_value(payload),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def benchmark_artifact(kind: str, payload: ArtifactPayload) -> dict[str, JsonValue]:
|
||||
"""Create one v2 artifact from an unwrapped payload."""
|
||||
kind = _kind(kind)
|
||||
if not isinstance(payload, Mapping):
|
||||
raise ValueError("benchmark artifact payload must be a mapping")
|
||||
if _RESERVED.intersection(payload):
|
||||
raise ValueError("benchmark artifact payload uses reserved metadata")
|
||||
value = {str(key): _canonical_value(item) for key, item in payload.items()}
|
||||
return {
|
||||
**value,
|
||||
"schema_version": BENCHMARK_ARTIFACT_SCHEMA,
|
||||
"artifact_kind": kind,
|
||||
"artifact_digest": _artifact_digest(kind, value),
|
||||
}
|
||||
|
||||
|
||||
def benchmark_payload(value: object, *, expected_kind: str) -> dict[str, JsonValue]:
|
||||
"""Strictly validate one v2 artifact and return its domain payload."""
|
||||
expected_kind = _kind(expected_kind)
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError("benchmark artifact metadata is missing")
|
||||
if _RESERVED.intersection(value) != _RESERVED:
|
||||
raise ValueError("benchmark artifact metadata is missing or incomplete")
|
||||
raw = {str(key): item for key, item in value.items() if key not in _RESERVED}
|
||||
canonical_raw = cast(dict[str, JsonValue], _canonical_value(raw))
|
||||
if (
|
||||
value["schema_version"] != BENCHMARK_ARTIFACT_SCHEMA
|
||||
or value["artifact_kind"] != expected_kind
|
||||
or value["artifact_digest"] != _artifact_digest(expected_kind, canonical_raw)
|
||||
):
|
||||
raise ValueError("benchmark artifact metadata does not match payload")
|
||||
return cast(dict[str, JsonValue], raw)
|
||||
|
||||
|
||||
def encode_benchmark_artifact(kind: str, payload: ArtifactPayload, *, indent: int | None = 2) -> str:
|
||||
"""Encode one canonical v2 artifact."""
|
||||
return json.dumps(benchmark_artifact(kind, payload), sort_keys=True, indent=indent, ensure_ascii=False)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _target_lock(path: Path) -> Iterator[None]:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
descriptor = os.open(path.with_name(f".{path.name}.lock"), os.O_CREAT | os.O_RDWR, 0o600)
|
||||
try:
|
||||
fcntl.flock(descriptor, fcntl.LOCK_EX)
|
||||
yield
|
||||
finally:
|
||||
fcntl.flock(descriptor, fcntl.LOCK_UN)
|
||||
os.close(descriptor)
|
||||
|
||||
|
||||
def _write_locked(path: Path, content: bytes, *, replace: bool) -> None:
|
||||
if path.exists():
|
||||
existing = path.read_bytes()
|
||||
if existing == content:
|
||||
return
|
||||
if not replace:
|
||||
raise ValueError(f"benchmark artifact conflict: {path}")
|
||||
temporary_name: str | None = None
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(mode="wb", dir=path.parent, prefix=f".{path.name}.", suffix=".tmp", delete=False) as temporary:
|
||||
temporary_name = temporary.name
|
||||
temporary.write(content)
|
||||
temporary.flush()
|
||||
os.fsync(temporary.fileno())
|
||||
os.replace(temporary_name, path)
|
||||
temporary_name = None
|
||||
finally:
|
||||
if temporary_name is not None:
|
||||
Path(temporary_name).unlink(missing_ok=True)
|
||||
|
||||
|
||||
def _write_bytes(path: Path, content: bytes, *, replace: bool) -> None:
|
||||
path = Path(path)
|
||||
with _target_lock(path):
|
||||
_write_locked(path, content, replace=replace)
|
||||
|
||||
|
||||
def write_benchmark_artifact(path: Path, kind: str, payload: ArtifactPayload, *, replace: bool = False) -> None:
|
||||
"""Atomically create one artifact; identical writes are idempotent."""
|
||||
_write_bytes(path, (encode_benchmark_artifact(kind, payload) + "\n").encode(), replace=replace)
|
||||
|
||||
|
||||
def read_benchmark_artifact(path: Path, expected_kind: str) -> dict[str, JsonValue]:
|
||||
"""Read and strictly validate one artifact from disk."""
|
||||
try:
|
||||
value = json.loads(Path(path).read_text())
|
||||
except (OSError, json.JSONDecodeError) as error:
|
||||
raise ValueError(f"benchmark artifact unreadable: {path}") from error
|
||||
return benchmark_payload(value, expected_kind=expected_kind)
|
||||
|
||||
|
||||
def _ordered_digest(artifacts: Sequence[Mapping[str, JsonValue]]) -> str:
|
||||
return content_digest(
|
||||
tuple(
|
||||
{"sequence_index": index, "artifact_digest": cast(str, artifact["artifact_digest"])} for index, artifact in enumerate(artifacts)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _sequence_artifact(kind: str, rows: ArtifactRows) -> dict[str, JsonValue]:
|
||||
artifacts = tuple(benchmark_artifact(f"{kind}.item", row) for row in rows)
|
||||
members: tuple[JsonValue, ...] = tuple({"sequence_index": index, "artifact": artifact} for index, artifact in enumerate(artifacts))
|
||||
return benchmark_artifact(
|
||||
kind,
|
||||
{
|
||||
"artifact_mode": "ordered-sequence",
|
||||
"sequence_count": len(artifacts),
|
||||
"sequence_digest": _ordered_digest(artifacts),
|
||||
"rows": members,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def write_benchmark_sequence(path: Path, kind: str, rows: ArtifactRows, *, replace: bool = False) -> None:
|
||||
"""Persist an exact ordered collection with count, indices, and aggregate digest."""
|
||||
value = _sequence_artifact(_kind(kind), rows)
|
||||
content = (json.dumps(value, sort_keys=True, indent=2, ensure_ascii=False) + "\n").encode()
|
||||
_write_bytes(path, content, replace=replace)
|
||||
|
||||
|
||||
def _sequence_payload(value: object, kind: str) -> tuple[dict[str, JsonValue], ...]:
|
||||
payload = benchmark_payload(value, expected_kind=kind)
|
||||
if set(payload) != _SEQUENCE_KEYS or payload.get("artifact_mode") != "ordered-sequence":
|
||||
raise ValueError("benchmark sequence metadata does not match contract")
|
||||
count = payload.get("sequence_count")
|
||||
sequence_digest = payload.get("sequence_digest")
|
||||
members = payload.get("rows")
|
||||
if (
|
||||
not isinstance(count, int)
|
||||
or count < 0
|
||||
or not isinstance(sequence_digest, str)
|
||||
or not isinstance(members, Sequence)
|
||||
or isinstance(members, (str, bytes))
|
||||
):
|
||||
raise ValueError("benchmark sequence metadata is invalid")
|
||||
if len(members) != count:
|
||||
raise ValueError("benchmark sequence count mismatch")
|
||||
rows: list[dict[str, JsonValue]] = []
|
||||
artifacts: list[dict[str, JsonValue]] = []
|
||||
for index, member in enumerate(members):
|
||||
if not isinstance(member, dict) or set(member) != {"sequence_index", "artifact"} or member.get("sequence_index") != index:
|
||||
raise ValueError("benchmark sequence index mismatch")
|
||||
artifact = member.get("artifact")
|
||||
rows.append(benchmark_payload(artifact, expected_kind=f"{kind}.item"))
|
||||
artifacts.append(cast(dict[str, JsonValue], artifact))
|
||||
digests = tuple(cast(str, artifact["artifact_digest"]) for artifact in artifacts)
|
||||
if len(set(digests)) != len(digests) or sequence_digest != _ordered_digest(artifacts):
|
||||
raise ValueError("benchmark sequence digest mismatch or duplicate row")
|
||||
return tuple(rows)
|
||||
|
||||
|
||||
def read_benchmark_sequence(path: Path, expected_kind: str) -> tuple[dict[str, JsonValue], ...]:
|
||||
"""Read and validate a complete ordered collection."""
|
||||
try:
|
||||
value = json.loads(Path(path).read_text())
|
||||
except (OSError, json.JSONDecodeError) as error:
|
||||
raise ValueError(f"benchmark sequence unreadable: {path}") from error
|
||||
return _sequence_payload(value, _kind(expected_kind))
|
||||
|
||||
|
||||
def _jsonl_bytes(kind: str, rows: ArtifactRows) -> bytes:
|
||||
items = tuple(benchmark_artifact(f"{kind}.item", row) for row in rows)
|
||||
sequence_digest = _ordered_digest(items)
|
||||
header = benchmark_artifact(
|
||||
f"{kind}.sequence",
|
||||
{"artifact_mode": "ordered-jsonl", "sequence_count": len(items), "sequence_digest": sequence_digest},
|
||||
)
|
||||
encoded = [json.dumps(header, sort_keys=True, ensure_ascii=False)]
|
||||
for index, artifact in enumerate(items):
|
||||
encoded.append(
|
||||
json.dumps(
|
||||
benchmark_artifact(
|
||||
f"{kind}.row",
|
||||
{
|
||||
"sequence_index": index,
|
||||
"sequence_count": len(items),
|
||||
"sequence_digest": sequence_digest,
|
||||
"artifact": artifact,
|
||||
},
|
||||
),
|
||||
sort_keys=True,
|
||||
ensure_ascii=False,
|
||||
)
|
||||
)
|
||||
return ("\n".join(encoded) + "\n").encode()
|
||||
|
||||
|
||||
def _jsonl_rows(content: bytes, kind: str) -> tuple[dict[str, JsonValue], ...]:
|
||||
try:
|
||||
values = tuple(json.loads(line) for line in content.decode().splitlines())
|
||||
except (UnicodeDecodeError, json.JSONDecodeError) as error:
|
||||
raise ValueError("benchmark JSONL is unreadable") from error
|
||||
if not values:
|
||||
raise ValueError("benchmark JSONL metadata is missing")
|
||||
header = benchmark_payload(values[0], expected_kind=f"{kind}.sequence")
|
||||
if set(header) != _JSONL_HEADER_KEYS or header.get("artifact_mode") != "ordered-jsonl":
|
||||
raise ValueError("benchmark JSONL metadata does not match contract")
|
||||
count = header.get("sequence_count")
|
||||
sequence_digest = header.get("sequence_digest")
|
||||
if not isinstance(count, int) or count < 0 or not isinstance(sequence_digest, str) or len(values) != count + 1:
|
||||
raise ValueError("benchmark JSONL count mismatch")
|
||||
rows: list[dict[str, JsonValue]] = []
|
||||
items: list[dict[str, JsonValue]] = []
|
||||
for index, value in enumerate(values[1:]):
|
||||
member = benchmark_payload(value, expected_kind=f"{kind}.row")
|
||||
if set(member) != _JSONL_ROW_KEYS:
|
||||
raise ValueError("benchmark JSONL row metadata does not match contract")
|
||||
if (
|
||||
member.get("sequence_index") != index
|
||||
or member.get("sequence_count") != count
|
||||
or member.get("sequence_digest") != sequence_digest
|
||||
):
|
||||
raise ValueError("benchmark JSONL sequence binding mismatch")
|
||||
artifact = member.get("artifact")
|
||||
rows.append(benchmark_payload(artifact, expected_kind=f"{kind}.item"))
|
||||
items.append(cast(dict[str, JsonValue], artifact))
|
||||
digests = tuple(cast(str, item["artifact_digest"]) for item in items)
|
||||
if len(set(digests)) != len(digests) or sequence_digest != _ordered_digest(items):
|
||||
raise ValueError("benchmark JSONL digest mismatch or duplicate row")
|
||||
return tuple(rows)
|
||||
|
||||
|
||||
def write_benchmark_jsonl(path: Path, kind: str, rows: ArtifactRows, *, replace: bool = False) -> None:
|
||||
"""Atomically persist an ordered JSONL manifest and its bound rows."""
|
||||
_write_bytes(path, _jsonl_bytes(_kind(kind), rows), replace=replace)
|
||||
|
||||
|
||||
def read_benchmark_jsonl(path: Path, expected_kind: str) -> tuple[dict[str, JsonValue], ...]:
|
||||
"""Read and validate a complete ordered JSONL artifact."""
|
||||
try:
|
||||
content = Path(path).read_bytes()
|
||||
except OSError as error:
|
||||
raise ValueError(f"benchmark JSONL unreadable: {path}") from error
|
||||
return _jsonl_rows(content, _kind(expected_kind))
|
||||
|
||||
|
||||
def append_benchmark_jsonl(path: Path, kind: str, row: ArtifactPayload) -> None:
|
||||
"""Append one row under the target lock and rewrite the bound sequence atomically."""
|
||||
path = Path(path)
|
||||
kind = _kind(kind)
|
||||
with _target_lock(path):
|
||||
rows = _jsonl_rows(path.read_bytes(), kind) if path.exists() else ()
|
||||
_write_locked(path, _jsonl_bytes(kind, (*rows, row)), replace=True)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"BENCHMARK_ARTIFACT_SCHEMA",
|
||||
"append_benchmark_jsonl",
|
||||
"benchmark_artifact",
|
||||
"benchmark_payload",
|
||||
"encode_benchmark_artifact",
|
||||
"read_benchmark_artifact",
|
||||
"read_benchmark_jsonl",
|
||||
"read_benchmark_sequence",
|
||||
"write_benchmark_artifact",
|
||||
"write_benchmark_jsonl",
|
||||
"write_benchmark_sequence",
|
||||
]
|
||||
@@ -22,6 +22,7 @@ from typing import TYPE_CHECKING, Any, TypeVar
|
||||
if TYPE_CHECKING:
|
||||
from openultrasast.hunt.prover import ProverResult
|
||||
|
||||
from benchmarks.valid.artifact_contract import write_benchmark_artifact
|
||||
from benchmarks.valid.readiness_contract import require_stage_authorized
|
||||
from benchmarks.valid.run_manifests import AnalyzerBundle
|
||||
from openultrasast.core.readiness import AnalysisIdentity, ReadinessResult, ReadinessStage
|
||||
@@ -115,10 +116,7 @@ def run_development(
|
||||
seconds=seconds,
|
||||
)
|
||||
records.append(record)
|
||||
(out_dir / f"{record.change_id}.json").write_text(
|
||||
json.dumps(asdict(record), indent=2, sort_keys=True) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
write_benchmark_artifact(out_dir / f"{record.change_id}.json", "development-observation", asdict(record))
|
||||
return tuple(records)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
"""Explicit, non-mutating handoff from the hunt runner to benchmark v2 storage."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from collections.abc import Mapping, Sequence
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
from benchmarks.unseen.replay import canonical
|
||||
from benchmarks.valid.artifact_contract import (
|
||||
read_benchmark_artifact,
|
||||
write_benchmark_artifact,
|
||||
write_benchmark_sequence,
|
||||
)
|
||||
from openultrasast.hunt.artifact import MAX_ATTEMPT_ORDINAL, RunArtifact
|
||||
from openultrasast.hunt.runner import input_digest, record_path
|
||||
|
||||
HUNT_RUNNER_PRODUCER = "openultrasast.hunt.runner.v1"
|
||||
_RESERVED = frozenset({"schema_version", "artifact_kind", "artifact_digest"})
|
||||
_RECORD_REQUIRED = frozenset(
|
||||
{"id", "done", "status", "instrument", "push", "rows", "program_id", "mask", "inputs_present", "seconds", "input_digest", "usd"}
|
||||
)
|
||||
_RECORD_OPTIONAL = frozenset({"parse_failed", "loop_parse_failed"})
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class HuntRunContext:
|
||||
producer: str
|
||||
program_id: str
|
||||
mask: tuple[str, ...]
|
||||
manifest_digest: str
|
||||
freeze_digest: str
|
||||
only: str
|
||||
slice_id: str
|
||||
artifact_ids: tuple[str, ...]
|
||||
versions: tuple[tuple[str, str], ...]
|
||||
|
||||
|
||||
def _hex_digest(value: object, field: str) -> str:
|
||||
if not isinstance(value, str) or len(value) != 64 or any(character not in "0123456789abcdef" for character in value):
|
||||
raise ValueError(f"hunt handoff {field} must be a full lowercase digest")
|
||||
return value
|
||||
|
||||
|
||||
def _validate_context(context: HuntRunContext) -> None:
|
||||
if context.producer != HUNT_RUNNER_PRODUCER:
|
||||
raise ValueError(f"unsupported hunt producer: {context.producer}")
|
||||
_hex_digest(context.program_id, "program identity")
|
||||
_hex_digest(context.manifest_digest, "manifest identity")
|
||||
_hex_digest(context.freeze_digest, "freeze identity")
|
||||
if tuple(sorted(set(context.mask))) != context.mask:
|
||||
raise ValueError("hunt handoff mask must be sorted and unique")
|
||||
if not context.only or not context.slice_id:
|
||||
raise ValueError("hunt handoff runner context is incomplete")
|
||||
if not context.artifact_ids or tuple(sorted(set(context.artifact_ids))) != context.artifact_ids:
|
||||
raise ValueError("hunt handoff attempt identities must be nonempty, sorted, and unique")
|
||||
for artifact_id in context.artifact_ids:
|
||||
_hex_digest(artifact_id, "attempt identity")
|
||||
if tuple(sorted(set(context.versions))) != context.versions:
|
||||
raise ValueError("hunt handoff versions must be sorted and unique")
|
||||
|
||||
|
||||
def _read_json(path: Path, label: str) -> object:
|
||||
try:
|
||||
data = path.read_bytes()
|
||||
if not data:
|
||||
raise ValueError(f"{label} is empty")
|
||||
return json.loads(data)
|
||||
except (OSError, json.JSONDecodeError) as error:
|
||||
raise ValueError(f"{label} is unreadable") from error
|
||||
|
||||
|
||||
def _has_reserved(value: object) -> bool:
|
||||
if isinstance(value, Mapping):
|
||||
return bool(_RESERVED.intersection(value)) or any(_has_reserved(item) for item in value.values())
|
||||
if isinstance(value, Sequence) and not isinstance(value, (str, bytes)):
|
||||
return any(_has_reserved(item) for item in value)
|
||||
return False
|
||||
|
||||
|
||||
def _expected_entries(changes: Sequence[Mapping[str, Any]], context: HuntRunContext) -> tuple[dict[str, Any], ...]:
|
||||
if not changes or len({change.get("id") for change in changes}) != len(changes):
|
||||
raise ValueError("hunt handoff changes must be nonempty with unique identities")
|
||||
return tuple(
|
||||
{
|
||||
"item_id": change["id"],
|
||||
"change": dict(change),
|
||||
"input_digest": input_digest(change, context.program_id, context.mask),
|
||||
}
|
||||
for change in changes
|
||||
if context.only == "all" or change.get("kind") == context.only
|
||||
)
|
||||
|
||||
|
||||
def _raw_entries(source: Path, expected: tuple[dict[str, Any], ...]) -> tuple[dict[str, Any], ...]:
|
||||
value = _read_json(source / "inputs.json", "hunt input index")
|
||||
if not isinstance(value, list) or _has_reserved(value):
|
||||
raise ValueError("hunt input index has reserved metadata or an unsupported schema")
|
||||
if not all(isinstance(row, dict) and set(row) == {"item_id", "change", "input_digest"} for row in value):
|
||||
raise ValueError("hunt input index has an unsupported row shape")
|
||||
rows = cast(tuple[dict[str, Any], ...], tuple(value))
|
||||
if rows != expected[: len(rows)]:
|
||||
raise ValueError("hunt input identity, ordering, or input digest mismatch")
|
||||
return rows
|
||||
|
||||
|
||||
def _artifact_paths(source: Path, record_names: set[str]) -> tuple[Path, ...]:
|
||||
return tuple(sorted(path for path in source.glob("*.json") if path.name != "inputs.json" and path.name not in record_names))
|
||||
|
||||
|
||||
def _artifact_projection(artifact: RunArtifact) -> tuple[object, ...]:
|
||||
return (
|
||||
artifact.schema_version,
|
||||
artifact.program_id,
|
||||
tuple(artifact.mask),
|
||||
tuple(sorted(artifact.data_digests.items())),
|
||||
tuple(json.dumps(value, sort_keys=True) for value in artifact.configurations),
|
||||
tuple(sorted(artifact.versions.items())),
|
||||
)
|
||||
|
||||
|
||||
def _read_attempts(paths: Sequence[Path]) -> tuple[RunArtifact, ...]:
|
||||
artifacts: list[RunArtifact] = []
|
||||
for path in paths:
|
||||
try:
|
||||
artifact = RunArtifact.read(path)
|
||||
except (OSError, TypeError, ValueError) as error:
|
||||
raise ValueError("hunt runner context artifact is invalid") from error
|
||||
if path.stem != artifact.artifact_id:
|
||||
raise ValueError("hunt runner context artifact filename does not match its identity")
|
||||
artifacts.append(artifact)
|
||||
if not artifacts:
|
||||
raise ValueError("hunt runner context artifact collection is empty")
|
||||
ordinals = [artifact.attempt_ordinal for artifact in artifacts]
|
||||
if any(type(ordinal) is not int or not 0 < ordinal <= MAX_ATTEMPT_ORDINAL for ordinal in ordinals) or len(set(ordinals)) != len(
|
||||
ordinals
|
||||
):
|
||||
raise ValueError("hunt runner attempt ordinals must be positive and unique")
|
||||
projection = _artifact_projection(artifacts[0])
|
||||
if any(_artifact_projection(artifact) != projection for artifact in artifacts[1:]):
|
||||
raise ValueError("hunt runner attempt invariant mismatch")
|
||||
return tuple(artifacts)
|
||||
|
||||
|
||||
def _validate_run_artifact(source: Path, context: HuntRunContext, expected: tuple[dict[str, Any], ...]) -> None:
|
||||
record_names = {record_path(source, cast(str, entry["item_id"])).name for entry in expected}
|
||||
artifacts = _read_attempts(_artifact_paths(source, record_names))
|
||||
artifact = artifacts[0]
|
||||
if tuple(sorted(item.artifact_id for item in artifacts)) != context.artifact_ids:
|
||||
raise ValueError("hunt runner attempt identities do not match handoff context")
|
||||
if artifact.schema_version != 1:
|
||||
raise ValueError("hunt runner schema is unsupported")
|
||||
if artifact.program_id != context.program_id or tuple(artifact.mask) != context.mask:
|
||||
raise ValueError("hunt runner program identity does not match handoff context")
|
||||
if artifact.data_digests != {"manifest": context.manifest_digest}:
|
||||
raise ValueError("hunt runner manifest identity does not match handoff context")
|
||||
expected_configuration = [{"only": context.only, "slice": context.slice_id, "changes": len(expected)}]
|
||||
if artifact.configurations != expected_configuration:
|
||||
raise ValueError("hunt runner configuration identity does not match handoff context")
|
||||
if tuple(sorted(artifact.versions.items())) != context.versions:
|
||||
raise ValueError("hunt runner version identity does not match handoff context")
|
||||
|
||||
|
||||
def _raw_record(source: Path, entry: Mapping[str, Any], context: HuntRunContext) -> dict[str, Any] | None:
|
||||
path = record_path(source, cast(str, entry["item_id"]))
|
||||
if not path.exists():
|
||||
return None
|
||||
value = _read_json(path, "hunt record")
|
||||
if not isinstance(value, dict) or _has_reserved(value):
|
||||
raise ValueError("hunt record uses reserved metadata or an unsupported schema")
|
||||
keys = frozenset(value)
|
||||
if not _RECORD_REQUIRED.issubset(keys) or not keys.issubset(_RECORD_REQUIRED | _RECORD_OPTIONAL):
|
||||
raise ValueError("hunt record has an unsupported shape")
|
||||
if value["id"] != entry["item_id"]:
|
||||
raise ValueError("hunt record identity mismatch")
|
||||
if value["input_digest"] != entry["input_digest"]:
|
||||
raise ValueError("hunt record input digest mismatch")
|
||||
if value["program_id"] != context.program_id or tuple(value["mask"]) != context.mask:
|
||||
raise ValueError("hunt record program or configuration identity mismatch")
|
||||
if value["done"] is not True or not isinstance(value["instrument"], dict) or not isinstance(value["push"], dict):
|
||||
raise ValueError("hunt record completion schema is invalid")
|
||||
return value
|
||||
|
||||
|
||||
def read_hunt_runner_records(
|
||||
source: Path,
|
||||
*,
|
||||
context: HuntRunContext,
|
||||
changes: Sequence[Mapping[str, Any]],
|
||||
) -> tuple[dict[str, Any], ...]:
|
||||
"""Validate a named raw hunt run and return normalized rows without writing to it."""
|
||||
source = Path(source)
|
||||
_validate_context(context)
|
||||
expected = _expected_entries(changes, context)
|
||||
_validate_run_artifact(source, context, expected)
|
||||
entries = _raw_entries(source, expected)
|
||||
rows: list[dict[str, Any]] = []
|
||||
for entry in entries:
|
||||
record = _raw_record(source, entry, context) or {}
|
||||
change = cast(dict[str, Any], entry["change"])
|
||||
rows.append({**change, **record, "id": change["id"]})
|
||||
return tuple(rows)
|
||||
|
||||
|
||||
def _handoff_payload(context: HuntRunContext, entries: Sequence[Mapping[str, Any]]) -> dict[str, Any]:
|
||||
return {
|
||||
**asdict(context),
|
||||
"mask": list(context.mask),
|
||||
"input_count": len(entries),
|
||||
"input_sequence_digest": hashlib.sha256(canonical(entries)).hexdigest(),
|
||||
}
|
||||
|
||||
|
||||
def persist_hunt_runner_records(
|
||||
source: Path,
|
||||
destination: Path,
|
||||
*,
|
||||
context: HuntRunContext,
|
||||
changes: Sequence[Mapping[str, Any]],
|
||||
) -> tuple[dict[str, Any], ...]:
|
||||
"""Copy a validated raw run into distinct benchmark-owned canonical v2 files."""
|
||||
source, destination = Path(source), Path(destination)
|
||||
if source.resolve() == destination.resolve():
|
||||
raise ValueError("hunt producer and benchmark destination must be distinct")
|
||||
rows = read_hunt_runner_records(source, context=context, changes=changes)
|
||||
expected = _expected_entries(changes, context)
|
||||
entries = _raw_entries(source, expected)
|
||||
write_benchmark_artifact(destination / "handoff.json", "hunt-runner-handoff", _handoff_payload(context, entries))
|
||||
write_benchmark_sequence(destination / "inputs.json", "benchmark-arm-inputs", entries)
|
||||
for entry in entries:
|
||||
record = _raw_record(source, entry, context)
|
||||
if record is not None:
|
||||
write_benchmark_artifact(record_path(destination, cast(str, entry["item_id"])), "benchmark-arm-record", record)
|
||||
return rows
|
||||
|
||||
|
||||
def read_hunt_handoff_context(path: Path) -> HuntRunContext:
|
||||
"""Read the bound context of an already-normalized benchmark handoff."""
|
||||
payload = read_benchmark_artifact(Path(path) / "handoff.json", "hunt-runner-handoff")
|
||||
context = HuntRunContext(
|
||||
producer=cast(str, payload["producer"]),
|
||||
program_id=cast(str, payload["program_id"]),
|
||||
mask=tuple(cast(Sequence[str], payload["mask"])),
|
||||
manifest_digest=cast(str, payload["manifest_digest"]),
|
||||
freeze_digest=cast(str, payload["freeze_digest"]),
|
||||
only=cast(str, payload["only"]),
|
||||
slice_id=cast(str, payload["slice_id"]),
|
||||
artifact_ids=tuple(cast(Sequence[str], payload["artifact_ids"])),
|
||||
versions=tuple(tuple(cast(Sequence[str], row)) for row in cast(Sequence[Sequence[str]], payload["versions"])),
|
||||
)
|
||||
_validate_context(context)
|
||||
return context
|
||||
|
||||
|
||||
def discover_hunt_runner_context(
|
||||
source: Path,
|
||||
manifest: Path,
|
||||
*,
|
||||
producer: str,
|
||||
freeze_digest: str,
|
||||
) -> HuntRunContext:
|
||||
"""Resolve the content-addressed runner identity for a documented raw-run handoff."""
|
||||
if producer != HUNT_RUNNER_PRODUCER:
|
||||
raise ValueError(f"unsupported hunt producer: {producer}")
|
||||
manifest_bytes = Path(manifest).read_bytes()
|
||||
if not manifest_bytes:
|
||||
raise ValueError("hunt handoff manifest is empty")
|
||||
manifest_digest = hashlib.sha256(manifest_bytes).hexdigest()
|
||||
paths: list[Path] = []
|
||||
for path in Path(source).glob("*.json"):
|
||||
value = _read_json(path, "hunt runner candidate")
|
||||
if isinstance(value, dict) and "artifact_id" in value:
|
||||
paths.append(path)
|
||||
artifacts = _read_attempts(tuple(sorted(paths)))
|
||||
artifact = artifacts[0]
|
||||
if len(artifact.configurations) != 1:
|
||||
raise ValueError("hunt runner configuration artifact is ambiguous")
|
||||
if artifact.data_digests != {"manifest": manifest_digest}:
|
||||
raise ValueError("hunt runner manifest identity does not match supplied manifest")
|
||||
configuration = artifact.configurations[0]
|
||||
context = HuntRunContext(
|
||||
producer=producer,
|
||||
program_id=artifact.program_id,
|
||||
mask=tuple(artifact.mask),
|
||||
manifest_digest=manifest_digest,
|
||||
freeze_digest=freeze_digest,
|
||||
only=cast(str, configuration.get("only")),
|
||||
slice_id=cast(str, configuration.get("slice")),
|
||||
artifact_ids=tuple(sorted(item.artifact_id for item in artifacts)),
|
||||
versions=tuple(sorted(artifact.versions.items())),
|
||||
)
|
||||
_validate_context(context)
|
||||
return context
|
||||
|
||||
|
||||
__all__ = [
|
||||
"HUNT_RUNNER_PRODUCER",
|
||||
"HuntRunContext",
|
||||
"discover_hunt_runner_context",
|
||||
"persist_hunt_runner_records",
|
||||
"read_hunt_handoff_context",
|
||||
"read_hunt_runner_records",
|
||||
]
|
||||
@@ -27,6 +27,12 @@ if __package__ in {None, ""}:
|
||||
from benchmarks.unseen.extract import SHA, InstrumentFailure
|
||||
from benchmarks.unseen.replay import canonical, load_slice
|
||||
from benchmarks.unseen.score import load_records
|
||||
from benchmarks.valid.artifact_contract import (
|
||||
read_benchmark_artifact,
|
||||
read_benchmark_sequence,
|
||||
write_benchmark_artifact,
|
||||
write_benchmark_sequence,
|
||||
)
|
||||
from benchmarks.valid.catalog import local_git
|
||||
|
||||
# These names describe findings, never benchmark labels. The taxonomy owns the
|
||||
@@ -154,11 +160,12 @@ def finding_defect(finding, classification=None):
|
||||
}
|
||||
|
||||
|
||||
def _write_json(path, value):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = path.with_suffix(".json.tmp")
|
||||
temporary.write_text(json.dumps(value, sort_keys=True, indent=2) + "\n")
|
||||
temporary.replace(path)
|
||||
def _write_json(path, value, *, kind=None, replace=False):
|
||||
kind = kind or ("benchmark-arm-inputs" if Path(path).name == "inputs.json" else "benchmark-arm-record")
|
||||
if isinstance(value, list):
|
||||
write_benchmark_sequence(path, kind, value, replace=replace)
|
||||
else:
|
||||
write_benchmark_artifact(path, kind, value, replace=replace)
|
||||
|
||||
|
||||
def _record_path(out, identity):
|
||||
@@ -167,7 +174,7 @@ def _record_path(out, identity):
|
||||
|
||||
def _inputs(out):
|
||||
path = out / "inputs.json"
|
||||
entries = json.loads(path.read_text()) if path.exists() else []
|
||||
entries = list(read_benchmark_sequence(path, "benchmark-arm-inputs")) if path.exists() else []
|
||||
for key in (lambda entry: entry["item_id"], lambda entry: entry["change"]["id"]):
|
||||
if len({key(entry) for entry in entries}) != len(entries):
|
||||
raise ValueError(f"duplicate ids in {path}")
|
||||
@@ -277,21 +284,13 @@ def _hunt_change(change, repos, *, client, model, max_steps, no_classify=False):
|
||||
return record
|
||||
|
||||
|
||||
def run(
|
||||
manifest, repos_path, out, *, slice_id="1", max_steps=tool_hunter.DEFAULT_MAX_STEPS, only="all", client_mode=None, no_classify=False
|
||||
):
|
||||
if max_steps < 1:
|
||||
raise ValueError("max_steps must be positive")
|
||||
changes = load_slice(manifest, slice_id)
|
||||
if len({change["id"] for change in changes}) != len(changes):
|
||||
raise ValueError("duplicate change ids")
|
||||
changes = [change for change in changes if only == "all" or change["kind"] == only]
|
||||
def _pending_hunter_entries(changes, out):
|
||||
entries = _inputs(out)
|
||||
indexed = {entry["change"]["id"]: entry for entry in entries}
|
||||
pending = []
|
||||
for change in changes:
|
||||
path = _record_path(out, change["id"])
|
||||
previous = json.loads(path.read_text()) if path.exists() else {}
|
||||
previous = read_benchmark_artifact(path, "benchmark-arm-record") if path.exists() else {}
|
||||
entry = indexed.get(change["id"])
|
||||
if entry is None:
|
||||
entry = {
|
||||
@@ -306,31 +305,51 @@ def run(
|
||||
print(f"{change['id']}: skipped (done)", flush=True)
|
||||
continue
|
||||
pending.append(entry)
|
||||
if not pending:
|
||||
_write_json(out / "inputs.json", entries)
|
||||
return 0
|
||||
return entries, pending
|
||||
|
||||
|
||||
def _hunter_client(client_mode, out):
|
||||
config_path = Path("openultrasast.toml")
|
||||
config = load_config(config_path if config_path.exists() else None, dotenv=client_mode != "scripted")
|
||||
model = resolve_models(config)[0]
|
||||
client = tool_hunter.scripted_hunter_client() if client_mode == "scripted" else tool_hunter.resolve_hunter_client()
|
||||
if client is None:
|
||||
print("No hunter client resolved. Configure a hunter provider or use --client scripted for offline tests.", file=sys.stderr)
|
||||
return 2
|
||||
repos = json.loads(repos_path.read_text())
|
||||
_write_json(out / "inputs.json", entries)
|
||||
return client, model
|
||||
|
||||
|
||||
def _run_hunter_entries(pending, repos, out, client, model, client_mode, max_steps, no_classify):
|
||||
failed = False
|
||||
for entry in pending:
|
||||
# The built-in script is a finite sequence, so each change needs a fresh
|
||||
# sequence. Live clients remain shared and are resolved only once.
|
||||
if client_mode == "scripted":
|
||||
client = tool_hunter.scripted_hunter_client()
|
||||
record = _hunt_change(entry["change"], repos, client=client, model=model, max_steps=max_steps, no_classify=no_classify)
|
||||
current_client = tool_hunter.scripted_hunter_client() if client_mode == "scripted" else client
|
||||
record = _hunt_change(entry["change"], repos, client=current_client, model=model, max_steps=max_steps, no_classify=no_classify)
|
||||
record["input_digest"] = entry["input_digest"]
|
||||
_write_json(_record_path(out, entry["item_id"]), record)
|
||||
failed |= record["status"] == "instrument_failure"
|
||||
return 2 if failed else 0
|
||||
|
||||
|
||||
def run(
|
||||
manifest, repos_path, out, *, slice_id="1", max_steps=tool_hunter.DEFAULT_MAX_STEPS, only="all", client_mode=None, no_classify=False
|
||||
):
|
||||
if max_steps < 1:
|
||||
raise ValueError("max_steps must be positive")
|
||||
changes = load_slice(manifest, slice_id)
|
||||
if len({change["id"] for change in changes}) != len(changes):
|
||||
raise ValueError("duplicate change ids")
|
||||
changes = [change for change in changes if only == "all" or change["kind"] == only]
|
||||
entries, pending = _pending_hunter_entries(changes, out)
|
||||
if not pending:
|
||||
_write_json(out / "inputs.json", entries, kind="benchmark-arm-inputs", replace=True)
|
||||
return 0
|
||||
client, model = _hunter_client(client_mode, out)
|
||||
if client is None:
|
||||
return 2
|
||||
repos = json.loads(repos_path.read_text())
|
||||
_write_json(out / "inputs.json", entries, kind="benchmark-arm-inputs", replace=True)
|
||||
return _run_hunter_entries(pending, repos, out, client, model, client_mode, max_steps, no_classify)
|
||||
|
||||
|
||||
def _joined(out):
|
||||
_inputs(out)
|
||||
rows = load_records(out)
|
||||
@@ -393,7 +412,7 @@ def combine(replay_out, hunter_out, out, *, keep_verdicts=None):
|
||||
"b": right.get("status", "missing") if has_b else "missing",
|
||||
}
|
||||
combined.append(merged)
|
||||
_write_json(out, combined)
|
||||
_write_json(out, combined, kind="benchmark-arm-records", replace=True)
|
||||
return combined
|
||||
|
||||
|
||||
|
||||
@@ -19,6 +19,14 @@ import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from benchmarks.valid.artifact_contract import (
|
||||
append_benchmark_jsonl,
|
||||
encode_benchmark_artifact,
|
||||
read_benchmark_artifact,
|
||||
read_benchmark_jsonl,
|
||||
write_benchmark_artifact,
|
||||
)
|
||||
|
||||
JUDGE_QUESTION = (
|
||||
"A static analyzer flagged this code path as a possible access-control "
|
||||
"vulnerability: an attacker-reachable entry point reaches a protected "
|
||||
@@ -51,13 +59,13 @@ def _judge_input(repo: str, finding: dict) -> str:
|
||||
"operation": finding.get("operation", {}),
|
||||
"missing": finding.get("missing"),
|
||||
}
|
||||
return JUDGE_QUESTION + "\n\n" + json.dumps(payload, indent=2, default=str)
|
||||
return JUDGE_QUESTION + "\n\n" + encode_benchmark_artifact("judge-pass-input", payload)
|
||||
|
||||
|
||||
def _iter_findings(prover_out: Path):
|
||||
"""Every finding of every completed prover record, with its push context."""
|
||||
for path in sorted(prover_out.glob("[0-9a-f]*.json")):
|
||||
record = json.loads(path.read_text())
|
||||
record = read_benchmark_artifact(path, "benchmark-arm-record")
|
||||
if not record.get("done"):
|
||||
continue
|
||||
for finding in record.get("findings") or []:
|
||||
@@ -68,20 +76,19 @@ def _load_done(out: Path) -> dict:
|
||||
done = {}
|
||||
judged = out / "judgments.jsonl"
|
||||
if judged.is_file():
|
||||
for line in judged.read_text().splitlines():
|
||||
if line.strip():
|
||||
row = json.loads(line)
|
||||
done[row["key"]] = row
|
||||
for row in read_benchmark_jsonl(judged, "judge-pass"):
|
||||
key = row["key"]
|
||||
if key in done:
|
||||
raise ValueError(f"duplicate judge key: {key}")
|
||||
done[key] = row
|
||||
return done
|
||||
|
||||
|
||||
def run(args) -> int:
|
||||
def _judge_program(out: Path):
|
||||
import dspy
|
||||
|
||||
from openultrasast.hunt.program import build_lm
|
||||
|
||||
out = Path(args.out)
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
lm = build_lm(cache_dir=out / "dspy-cache")
|
||||
dspy.configure(lm=lm, adapter=dspy.ChatAdapter())
|
||||
|
||||
@@ -91,52 +98,59 @@ def run(args) -> int:
|
||||
case: str = dspy.InputField()
|
||||
answer: str = dspy.OutputField()
|
||||
|
||||
judge = dspy.Predict(Judge)
|
||||
done = _load_done(out)
|
||||
judgments_path = out / "judgments.jsonl"
|
||||
return dspy, lm, dspy.Predict(Judge)
|
||||
|
||||
todo = 0
|
||||
failures = []
|
||||
started = time.monotonic()
|
||||
for record, finding in _iter_findings(Path(args.prover_out)):
|
||||
key = _finding_key(record["id"], finding.get("id") or "")
|
||||
if key in done:
|
||||
|
||||
def _judgment_payload(record, finding, model):
|
||||
return {
|
||||
"key": _finding_key(record["id"], finding.get("id") or ""),
|
||||
"record_id": record["id"],
|
||||
"repo": record["repo"],
|
||||
"kind": record.get("kind"),
|
||||
"finding_id": finding.get("id"),
|
||||
"path": finding.get("path"),
|
||||
"line": finding.get("line"),
|
||||
"model": model,
|
||||
}
|
||||
|
||||
|
||||
def _apply_judgment(payload, judge, record, finding):
|
||||
result = judge(case=_judge_input(record["repo"], finding))
|
||||
text = (result.answer or "").strip()
|
||||
body = text.strip("`")
|
||||
if body.lower().startswith("json"):
|
||||
body = body[4:]
|
||||
parsed = json.loads(body[body.index("{") : body.rindex("}") + 1])
|
||||
if not isinstance(parsed.get("public"), bool):
|
||||
raise ValueError("public is not a boolean")
|
||||
payload.update(public=parsed["public"], confidence=parsed.get("confidence"), reason=parsed.get("reason"), status="ok")
|
||||
|
||||
|
||||
def _judge_findings(prover_out, out, lm, judge, done):
|
||||
todo, failures = 0, []
|
||||
for record, finding in _iter_findings(prover_out):
|
||||
payload = _judgment_payload(record, finding, lm.model)
|
||||
if payload["key"] in done:
|
||||
continue
|
||||
todo += 1
|
||||
payload = {
|
||||
"key": key,
|
||||
"record_id": record["id"],
|
||||
"repo": record["repo"],
|
||||
"kind": record.get("kind"),
|
||||
"finding_id": finding.get("id"),
|
||||
"path": finding.get("path"),
|
||||
"line": finding.get("line"),
|
||||
"model": lm.model,
|
||||
}
|
||||
try:
|
||||
result = judge(case=_judge_input(record["repo"], finding))
|
||||
text = (result.answer or "").strip()
|
||||
# Tolerate a fenced JSON block, then parse strictly.
|
||||
body = text.strip("`")
|
||||
if body.lower().startswith("json"):
|
||||
body = body[4:]
|
||||
parsed = json.loads(body[body.index("{") : body.rindex("}") + 1])
|
||||
if not isinstance(parsed.get("public"), bool):
|
||||
raise ValueError("public is not a boolean")
|
||||
payload["public"] = parsed["public"]
|
||||
payload["confidence"] = parsed.get("confidence")
|
||||
payload["reason"] = parsed.get("reason")
|
||||
payload["status"] = "ok"
|
||||
except Exception as error: # noqa: BLE001 (recorded, never swallowed silently)
|
||||
payload["status"] = "judge_error"
|
||||
payload["error_type"] = type(error).__name__
|
||||
payload["error"] = str(error)[:500]
|
||||
_apply_judgment(payload, judge, record, finding)
|
||||
except Exception as error: # noqa: BLE001
|
||||
payload.update(status="judge_error", error_type=type(error).__name__, error=str(error)[:500])
|
||||
failures.append(payload)
|
||||
with judgments_path.open("a") as handle:
|
||||
handle.write(json.dumps(payload, default=str) + "\n")
|
||||
append_benchmark_jsonl(out / "judgments.jsonl", "judge-pass", payload)
|
||||
if not todo % 25:
|
||||
print(f"{todo} judged, {len(failures)} failures", flush=True)
|
||||
return todo, failures
|
||||
|
||||
|
||||
def run(args) -> int:
|
||||
out = Path(args.out)
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
dspy, lm, judge = _judge_program(out)
|
||||
done = _load_done(out)
|
||||
started = time.monotonic()
|
||||
todo, failures = _judge_findings(Path(args.prover_out), out, lm, judge, done)
|
||||
seconds = time.monotonic() - started
|
||||
meta = {
|
||||
"model": lm.model,
|
||||
@@ -147,7 +161,7 @@ def run(args) -> int:
|
||||
"seconds": round(seconds, 1),
|
||||
"note": "zero-shot: no examples, no thresholds; question fixed before any answer was seen",
|
||||
}
|
||||
(out / "run.json").write_text(json.dumps(meta, indent=2, default=str) + "\n")
|
||||
write_benchmark_artifact(out / "run.json", "judge-pass-run", meta, replace=True)
|
||||
print(json.dumps(meta))
|
||||
return 2 if failures and todo else 0
|
||||
|
||||
@@ -203,10 +217,10 @@ def summarize(args) -> int:
|
||||
"headline": (
|
||||
f"Judge over prover findings: keeps {tp_kept}/{len(vuln)} true flags, "
|
||||
f"drops {fa_dropped}/{fa_total} false flags "
|
||||
f"(recall {rate(tp_kept, 23)*100:.1f}%, FA {(fa_total - fa_dropped) / 460 * 100:.1f}/100)"
|
||||
f"(recall {rate(tp_kept, 23) * 100:.1f}%, FA {(fa_total - fa_dropped) / 460 * 100:.1f}/100)"
|
||||
),
|
||||
}
|
||||
(out / "summary.json").write_text(json.dumps(summary, indent=2, default=str) + "\n")
|
||||
write_benchmark_artifact(out / "summary.json", "judge-pass-summary", summary, replace=True)
|
||||
print(json.dumps(summary, indent=2, default=str))
|
||||
return 0
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import json
|
||||
from pathlib import Path
|
||||
|
||||
from benchmarks.unseen import score
|
||||
from benchmarks.valid.artifact_contract import read_benchmark_artifact, write_benchmark_artifact
|
||||
|
||||
|
||||
def demonstrated(proof: dict) -> bool:
|
||||
@@ -104,10 +105,10 @@ def main(argv=None):
|
||||
args = parser.parse_args(argv)
|
||||
result = summarize(
|
||||
score.load_records(args.records),
|
||||
scored=json.loads(args.scored.read_text()) if args.scored else None,
|
||||
scored=read_benchmark_artifact(args.scored, "unseen-score") if args.scored else None,
|
||||
proofs=json.loads(args.proofs.read_text()) if args.proofs else [],
|
||||
)
|
||||
args.out.write_text(json.dumps(result, sort_keys=True, indent=2) + "\n")
|
||||
write_benchmark_artifact(args.out, "valid-benchmark-metrics", result, replace=True)
|
||||
print(result["headline"])
|
||||
return 0
|
||||
|
||||
|
||||
@@ -1,23 +1,7 @@
|
||||
"""Admission manifest consumption for the access-control prover arm.
|
||||
"""Retired benchmark v1 admission seam.
|
||||
|
||||
Task 12.5 of the access-control-prover specification (Reqs 12.1, 12.2, 12.3,
|
||||
12.6, 12.8). The admission runner only CONSUMES manifests that valid-detection-benchmark
|
||||
produces and reviews; it never produces, mutates or re-labels one.
|
||||
|
||||
Since detection-core task 4.1 this module is a thin compatibility wrapper: the
|
||||
generalized, family-agnostic contract lives in ``openultrasast.core.admission``.
|
||||
Live admission is exclusively ``admission_run.run_v2_admission``. Historical
|
||||
v1 bytes remain readable only through ``openultrasast.core.legacy``.
|
||||
"""
|
||||
|
||||
from openultrasast.core.admission import (
|
||||
REPORT_SCHEMA_KEYS,
|
||||
AdmissionManifest,
|
||||
AdmissionManifestError,
|
||||
validate_admission_manifest,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"REPORT_SCHEMA_KEYS",
|
||||
"AdmissionManifest",
|
||||
"AdmissionManifestError",
|
||||
"validate_admission_manifest",
|
||||
]
|
||||
__all__: list[str] = []
|
||||
|
||||
@@ -16,16 +16,23 @@ from benchmarks.unseen import score
|
||||
from benchmarks.unseen.extract import SHA, InstrumentFailure
|
||||
from benchmarks.unseen.replay import canonical, load_slice
|
||||
from benchmarks.valid import metrics
|
||||
from benchmarks.valid.artifact_contract import (
|
||||
read_benchmark_artifact,
|
||||
read_benchmark_sequence,
|
||||
write_benchmark_artifact,
|
||||
write_benchmark_sequence,
|
||||
)
|
||||
from benchmarks.valid.catalog import local_git
|
||||
from openultrasast.hunt.producers import Context, _Absent, _safe_path
|
||||
from openultrasast.hunt.prover import prove_change
|
||||
|
||||
|
||||
def _write_json(path, value):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = path.with_suffix(".json.tmp")
|
||||
temporary.write_text(json.dumps(value, sort_keys=True, indent=2) + "\n")
|
||||
temporary.replace(path)
|
||||
def _write_json(path, value, *, kind=None, replace=False):
|
||||
kind = kind or ("benchmark-arm-inputs" if Path(path).name == "inputs.json" else "benchmark-arm-record")
|
||||
if isinstance(value, list):
|
||||
write_benchmark_sequence(path, kind, value, replace=replace)
|
||||
else:
|
||||
write_benchmark_artifact(path, kind, value, replace=replace)
|
||||
|
||||
|
||||
def _record_path(out, identity):
|
||||
@@ -144,7 +151,7 @@ def run(manifest, repos_path, out, *, slice_id="1", only="all", max_depth=3):
|
||||
raise ValueError("duplicate change ids")
|
||||
changes = [change for change in changes if only == "all" or change["kind"] == only]
|
||||
index = out / "inputs.json"
|
||||
entries = json.loads(index.read_text()) if index.exists() else []
|
||||
entries = list(read_benchmark_sequence(index, "benchmark-arm-inputs")) if index.exists() else []
|
||||
if len({entry["item_id"] for entry in entries}) != len(entries):
|
||||
raise ValueError("duplicate inputs")
|
||||
indexed = {entry["item_id"]: entry for entry in entries}
|
||||
@@ -158,14 +165,14 @@ def run(manifest, repos_path, out, *, slice_id="1", only="all", max_depth=3):
|
||||
if not previous_entry:
|
||||
entries.append(entry)
|
||||
path = _record_path(out, change["id"])
|
||||
previous = json.loads(path.read_text()) if path.exists() else {}
|
||||
previous = read_benchmark_artifact(path, "benchmark-arm-record") if path.exists() else {}
|
||||
if previous and previous.get("input_digest") != digest:
|
||||
raise ValueError("existing prover inputs do not match")
|
||||
if previous.get("done"):
|
||||
print(f"{change['id']}: skipped (done)", flush=True)
|
||||
else:
|
||||
pending.append(entry)
|
||||
_write_json(index, entries)
|
||||
_write_json(index, entries, kind="benchmark-arm-inputs", replace=True)
|
||||
repos = json.loads(repos_path.read_text())
|
||||
failed = False
|
||||
for entry in pending:
|
||||
@@ -176,84 +183,101 @@ def run(manifest, repos_path, out, *, slice_id="1", only="all", max_depth=3):
|
||||
return 2 if failed else 0
|
||||
|
||||
|
||||
def summarize(out, *, seed=0):
|
||||
out = Path(out)
|
||||
records = score.load_records(out)
|
||||
def _summary_records(out):
|
||||
records = score.load_records(Path(out))
|
||||
if not records or len({row["id"] for row in records}) != len(records):
|
||||
raise ValueError("missing or duplicate prover records")
|
||||
for record in records:
|
||||
instrument = record.get("instrument", {})
|
||||
if any(size == 0 for field in ("changed_bytes", "file_bytes") for size in instrument.get(field, {}).values()):
|
||||
raise ValueError("zero-byte read of a tracked file; summary refused")
|
||||
result = metrics.summarize(records, seed=seed)
|
||||
usable = [row for row in records if score.coverage(row) != "instrument_failure"]
|
||||
locations = [
|
||||
return records
|
||||
|
||||
|
||||
def _target_locations(usable):
|
||||
return [
|
||||
bool(score.catches({**row, **target, "files": [{"path": target["path"], "head_lines": [target["function_lines"]]}]}))
|
||||
for row in usable
|
||||
if row["kind"] == "vulnerability"
|
||||
for target in row.get("targets") or [row]
|
||||
]
|
||||
repositories = {}
|
||||
framework_rows = {}
|
||||
|
||||
|
||||
def _coverage_slot():
|
||||
fields = ("entry_points", "reached", "unreached", "operations_checked", "operations_discharged")
|
||||
return {
|
||||
**dict.fromkeys(fields, 0),
|
||||
"files_by_language": Counter(),
|
||||
"frameworks_recognised": Counter(),
|
||||
"unchecked_by_reason": Counter(),
|
||||
"changes": 0,
|
||||
}
|
||||
|
||||
|
||||
def _add_coverage(target, coverage, fields):
|
||||
target["changes"] += 1
|
||||
for field in fields:
|
||||
target[field] += coverage.get(field, 0)
|
||||
for field in ("files_by_language", "frameworks_recognised", "unchecked_by_reason"):
|
||||
if field in target:
|
||||
target[field].update(coverage.get(field, {}))
|
||||
|
||||
|
||||
def _coverage_tables(records):
|
||||
fields = ("entry_points", "reached", "unreached", "operations_checked", "operations_discharged")
|
||||
repositories, framework_rows = {}, {}
|
||||
for row in records:
|
||||
cov = row.get("coverage", {})
|
||||
repository = repositories.setdefault(
|
||||
row["repo"],
|
||||
{
|
||||
**dict.fromkeys(fields, 0),
|
||||
"files_by_language": Counter(),
|
||||
"frameworks_recognised": Counter(),
|
||||
"unchecked_by_reason": Counter(),
|
||||
"changes": 0,
|
||||
},
|
||||
)
|
||||
repository["changes"] += 1
|
||||
for field in fields:
|
||||
repository[field] += cov.get(field, 0)
|
||||
for field in ("files_by_language", "frameworks_recognised", "unchecked_by_reason"):
|
||||
repository[field].update(cov.get(field, {}))
|
||||
for framework, count in cov.get("frameworks_recognised", {}).items():
|
||||
coverage = row.get("coverage", {})
|
||||
_add_coverage(repositories.setdefault(row["repo"], _coverage_slot()), coverage, fields)
|
||||
for framework, count in coverage.get("frameworks_recognised", {}).items():
|
||||
table = framework_rows.setdefault(
|
||||
framework, {"files": 0, "changes": 0, **dict.fromkeys(fields, 0), "unchecked_by_reason": Counter()}
|
||||
)
|
||||
table["files"] += count
|
||||
table["changes"] += 1
|
||||
for field in fields:
|
||||
table[field] += cov.get(field, 0)
|
||||
table["unchecked_by_reason"].update(cov.get("unchecked_by_reason", {}))
|
||||
uncovered = sorted(repo for repo, cov in repositories.items() if not cov["frameworks_recognised"])
|
||||
for repo, cov in repositories.items():
|
||||
cov["status"] = "uncovered" if repo in uncovered else "covered"
|
||||
_add_coverage(table, coverage, fields)
|
||||
return repositories, framework_rows
|
||||
|
||||
|
||||
def _annotate_repositories(repositories, usable):
|
||||
uncovered = sorted(repo for repo, coverage in repositories.items() if not coverage["frameworks_recognised"])
|
||||
for repo, coverage in repositories.items():
|
||||
coverage["status"] = "uncovered" if repo in uncovered else "covered"
|
||||
positives = [row for row in usable if row["repo"] == repo and row["kind"] == "vulnerability"]
|
||||
cov["recall"] = score.rate([bool(score.catches(row)) for row in metrics.scoring_records(positives)])
|
||||
coverage["recall"] = score.rate([bool(score.catches(row)) for row in metrics.scoring_records(positives)])
|
||||
return uncovered
|
||||
|
||||
|
||||
def _scope_recall(usable, uncovered, *, include_uncovered):
|
||||
values = [
|
||||
bool(score.catches(row))
|
||||
for row in metrics.scoring_records(usable)
|
||||
if row["kind"] == "vulnerability" and ((row["repo"] in uncovered) is include_uncovered)
|
||||
]
|
||||
return score.rate(values)
|
||||
|
||||
|
||||
def summarize(out, *, seed=0):
|
||||
out = Path(out)
|
||||
records = _summary_records(out)
|
||||
result = metrics.summarize(records, seed=seed)
|
||||
usable = [row for row in records if score.coverage(row) != "instrument_failure"]
|
||||
repositories, framework_rows = _coverage_tables(records)
|
||||
uncovered = _annotate_repositories(repositories, usable)
|
||||
seconds = [row["seconds"] for row in records]
|
||||
result.update(
|
||||
location_recall=score.rate(locations),
|
||||
location_recall=score.rate(_target_locations(usable)),
|
||||
per_repository=repositories,
|
||||
per_framework=framework_rows,
|
||||
uncovered_repositories=uncovered,
|
||||
covered_recall=score.rate(
|
||||
[
|
||||
bool(score.catches(row))
|
||||
for row in metrics.scoring_records(usable)
|
||||
if row["kind"] == "vulnerability" and row["repo"] not in uncovered
|
||||
]
|
||||
),
|
||||
uncovered_recall=score.rate(
|
||||
[
|
||||
bool(score.catches(row))
|
||||
for row in metrics.scoring_records(usable)
|
||||
if row["kind"] == "vulnerability" and row["repo"] in uncovered
|
||||
]
|
||||
),
|
||||
covered_recall=_scope_recall(usable, uncovered, include_uncovered=False),
|
||||
uncovered_recall=_scope_recall(usable, uncovered, include_uncovered=True),
|
||||
instrument_failures=sum(score.coverage(row) == "instrument_failure" for row in records),
|
||||
seconds_per_change=dict(n=len(seconds), mean=statistics.mean(seconds), median=statistics.median(seconds), maximum=max(seconds)),
|
||||
changes_over_120_seconds=[{"id": row["id"], "seconds": row["seconds"]} for row in records if row["seconds"] > 120],
|
||||
)
|
||||
# This is a training pool; the generic metric headline refers to unseen repositories.
|
||||
result["headline"] = result["headline"].replace("On unseen app repos:", "On the training pool:")
|
||||
_write_json(out / "summary.json", result)
|
||||
_write_json(out / "summary.json", result, kind="benchmark-arm-summary", replace=True)
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -35,6 +35,12 @@ if __package__ in {None, ""}:
|
||||
|
||||
from benchmarks.unseen.extract import SHA, InstrumentFailure
|
||||
from benchmarks.unseen.replay import canonical, load_slice
|
||||
from benchmarks.valid.artifact_contract import (
|
||||
read_benchmark_artifact,
|
||||
read_benchmark_sequence,
|
||||
write_benchmark_artifact,
|
||||
write_benchmark_sequence,
|
||||
)
|
||||
from benchmarks.valid.catalog import local_git
|
||||
|
||||
# Our language ids to Semgrep's. Groovy has no Semgrep parser: `generic` plus a path filter.
|
||||
@@ -427,11 +433,12 @@ def result_defect(result):
|
||||
}
|
||||
|
||||
|
||||
def _write_json(path, value):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = path.with_suffix(".json.tmp")
|
||||
temporary.write_text(json.dumps(value, sort_keys=True, indent=2) + "\n")
|
||||
temporary.replace(path)
|
||||
def _write_json(path, value, *, kind=None, replace=False):
|
||||
kind = kind or ("benchmark-arm-inputs" if Path(path).name == "inputs.json" else "benchmark-arm-record")
|
||||
if isinstance(value, list):
|
||||
write_benchmark_sequence(path, kind, value, replace=replace)
|
||||
else:
|
||||
write_benchmark_artifact(path, kind, value, replace=replace)
|
||||
|
||||
|
||||
def _record_path(out, identity):
|
||||
@@ -440,7 +447,7 @@ def _record_path(out, identity):
|
||||
|
||||
def _inputs(out):
|
||||
path = out / "inputs.json"
|
||||
entries = json.loads(path.read_text()) if path.exists() else []
|
||||
entries = list(read_benchmark_sequence(path, "benchmark-arm-inputs")) if path.exists() else []
|
||||
for key in (lambda entry: entry["item_id"], lambda entry: entry["change"]["id"]):
|
||||
if len({key(entry) for entry in entries}) != len(entries):
|
||||
raise ValueError(f"duplicate ids in {path}")
|
||||
@@ -568,7 +575,7 @@ def run(manifest, repos_path, rules, out, *, slice_id="1", only="all", semgrep=N
|
||||
pending = []
|
||||
for change in changes:
|
||||
path = _record_path(out, change["id"])
|
||||
previous = json.loads(path.read_text()) if path.exists() else {}
|
||||
previous = read_benchmark_artifact(path, "benchmark-arm-record") if path.exists() else {}
|
||||
entry = indexed.get(change["id"])
|
||||
if entry is None:
|
||||
entry = {
|
||||
@@ -583,7 +590,7 @@ def run(manifest, repos_path, rules, out, *, slice_id="1", only="all", semgrep=N
|
||||
print(f"{change['id']}: skipped (done)", flush=True)
|
||||
continue
|
||||
pending.append(entry)
|
||||
_write_json(out / "inputs.json", entries)
|
||||
_write_json(out / "inputs.json", entries, kind="benchmark-arm-inputs", replace=True)
|
||||
if not pending:
|
||||
return 0
|
||||
repos = json.loads(Path(repos_path).expanduser().read_text())
|
||||
|
||||
Reference in New Issue
Block a user