fold/scripts/benchmarks/run_project_eval.py
trvon af66058f3a
Some checks failed
build / build-linux (push) Failing after 7m32s
build / build-macos (push) Has been cancelled
build / release (push) Has been cancelled
bench: wire chromium + sage corpora as stress/diversity manifests
Backlog #5 (operator-confirmed). chromium_source.json and
sage_rust.json were validated but unwired. This adds:

- reviewed dispositions (5 parallel review subagents): chromium 11
  (10 FP/1 accepted-risk — deliberate modular arithmetic in
  base/file_path.cc); sage 2714 (9 actionable uncontained path-join
  reads in skill install/daemon library/model-downloader; 890 FP;
  1815 accepted-risk quality bulk)
- wall/RSS baselines for both corpora

Measured both corpora produce finding drift run-to-run (chromium Z3
fingerprint drifts; sage +5 findings) and chromium's wall swings up
to ~2x under load — so they are wired as STRESS/DIVERSITY corpora:
a new additive evaluation.max_new_findings override lets a manifest
declare its finding-drift tolerance (CLI default unchanged for
fold/yams), allow_finding_drift is on, and chromium's wall/RSS
thresholds are relaxed. Final: both evals pass.

Also fixes a pre-existing /tmp-literal default (now platform tempdir)
in run_project_eval.py.
2026-09-01 19:07:46 -06:00

1618 lines
60 KiB
Python

#!/usr/bin/env python3
"""Lane-generic project evaluation runner driven by a versioned manifest.
One manifest declares corpus, lane (source | rust | binary), analysis
configuration, and evaluation policy; this runner executes Fold, retains
immutable run artifacts, checks finding stability and reliability budgets,
and optionally scores against gold, reviewed dispositions, and a prior
performance baseline. Metric, matching, and gate semantics come from
``fold_eval`` so they cannot drift between entry points.
Literature grounding: ``bench/literature_baselines.json`` group
``profiling-eval-2026-07`` and
``docs/research/project-eval-scaffolding-2026-07-27.md``.
Example:
scripts/benchmarks/run_project_eval.py \
--manifest tests/benchmarks/projects/yams_source.json \
--out-dir /tmp/fold-yams-eval
"""
from __future__ import annotations
import argparse
import hashlib
import importlib.util
import json
import os
import re
import subprocess
import sys
import tempfile
from collections import Counter
from collections.abc import Callable
from pathlib import Path
from typing import Any
from fold_eval.dispositions import (
compare_dispositions,
configuration_fingerprint,
validate_disposition_baseline,
)
from fold_eval.metrics import CLASSIFICATION_THRESHOLD_METRICS, distribution_summary
from fold_eval.performance import compare_performance_lanes, optimization_surface
from fold_eval.project import (
load_project_manifest,
manifest_configuration,
)
from fold_eval.sarif import summarize_sarif_findings
from fold_eval.schema import (
artifact_record,
build_run_artifact,
executable_version,
sha256_file,
utc_now,
validate_run_artifact,
write_json,
)
from fold_eval.scoring import (
negative_cases_from_universe,
score_findings_by_rule,
score_grouped_findings,
score_labeled_cases,
)
from fold_eval.stability import stability_summary
REPO_ROOT = Path(__file__).resolve().parents[2]
RUNNER_COMMON_PATH = REPO_ROOT / "scripts" / "benchmarks" / "runner_common.py"
_spec = importlib.util.spec_from_file_location("runner_common", RUNNER_COMMON_PATH)
if _spec is None or _spec.loader is None:
raise ImportError(f"failed to load {RUNNER_COMMON_PATH}")
common = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(common)
DEFAULT_FOLD = REPO_ROOT / "build" / "release" / "src" / "cli" / "fold"
SUCCESS_EXIT_CODES = (0, 1) # fold returns 1 when findings are present.
SCAN_METRICS = (
"total_wall_ms",
"analyze_ms",
"compile_db_load_ms",
"serial_dispatch_ms",
"function_fingerprint_ms",
"project_index_load_ms",
"project_index_update_ms",
"call_graph_ms",
"project_graph_collect_ms",
"project_graph_merge_ms",
"project_graph_fallback_ms",
"project_index_contribution_update_ms",
"project_index_snapshot_update_ms",
"security_candidate_ms",
"cross_tu_property_ms",
"symbolic_summary_ms",
"frontend_extraction_ms",
"report_ms",
"num_tus",
"num_functions",
"findings_before_filter",
"findings_report_visible",
"source_tus_selected",
"source_tus_excluded",
"source_tus_attempted",
"source_tus_completed",
"source_tus_timed_out",
"source_tus_frontend_failed",
"peak_rss_kb",
)
DETECTOR_METRICS = (
"wall_ms",
"z3_queries",
"z3_timeouts",
"findings_emitted",
"functions_analyzed",
"functions_skipped",
)
# Manifest analysis keys -> fold CLI flags for the source lane. Defaults match
# run_code_quality.py so a manifest with identical settings produces the same
# analysis configuration fingerprint as the pinned Yams baseline.
SOURCE_SETTING_FLAGS = (
("complexity_threshold", "--complexity-threshold", 20),
("min_body_stmts", "--min-body-stmts", 4),
("clone_type", "--clone-type", 2),
("clone_similarity", "--clone-similarity", 0.7),
("clone_min_score", "--clone-min-score", 0.0),
("class_similarity_threshold", "--class-similarity-threshold", 0.7),
("budget_seconds", "--budget-seconds", 60),
("per_query_ms", "--per-query-ms", 2000),
("per_tu_timeout", "--per-tu-timeout", 60),
)
DEFAULT_EXCLUDE_REGEX = r"/(third_party|subprojects|generated)/"
def numeric(value: Any, *, name: str = "value") -> float:
try:
return float(value)
except (TypeError, ValueError) as error:
raise ValueError(f"{name} must be numeric") from error
def integer(value: Any, *, name: str = "value") -> int:
if isinstance(value, bool):
raise ValueError(f"{name} must be an integer")
try:
return int(value)
except (TypeError, ValueError) as error:
raise ValueError(f"{name} must be an integer") from error
def load_json_document(path: Path) -> Any:
try:
return json.loads(path.read_text(encoding="utf-8"))
except OSError as error:
raise ValueError(f"failed to read JSON file {path}: {error}") from error
except json.JSONDecodeError as error:
raise ValueError(f"invalid JSON file {path}: {error}") from error
# ---------------------------------------------------------------------------
# Corpus preparation
# ---------------------------------------------------------------------------
def resolve(base: Path, value: str) -> Path:
candidate = Path(value).expanduser()
return (
candidate.resolve() if candidate.is_absolute() else (base / candidate).resolve()
)
def compile_db_path(value: Path) -> Path:
return value / "compile_commands.json" if value.is_dir() else value
def entry_file(entry: dict[str, Any]) -> Path:
file_path = Path(str(entry.get("file", "")))
directory = Path(str(entry.get("directory", ".")))
return (
(directory / file_path).resolve()
if not file_path.is_absolute()
else file_path.resolve()
)
def filtered_compile_db(
source: Path,
output: Path,
include: re.Pattern[str] | None,
exclude: re.Pattern[str] | None,
max_tus: int,
) -> tuple[Path, list[Path]]:
loaded = load_json_document(source)
if not isinstance(loaded, list):
raise ValueError(f"compile database must contain a JSON array: {source}")
selected: list[tuple[str, dict[str, Any], Path]] = []
seen: set[str] = set()
for raw in loaded:
if not isinstance(raw, dict):
continue
path = entry_file(raw)
key = str(path)
if key in seen or (include and include.search(key) is None):
continue
if exclude and exclude.search(key) is not None:
continue
seen.add(key)
selected.append((key, raw, path))
selected.sort(key=lambda item: item[0])
if max_tus > 0:
selected = selected[:max_tus]
if not selected:
raise ValueError("compile-database filters selected no translation units")
database = output / "compile_commands.filtered.json"
database.write_text(
json.dumps([item[1] for item in selected], indent=2) + "\n",
encoding="utf-8",
)
return database, [item[2] for item in selected]
def project_path(path: Path, project_root: Path) -> str:
try:
return path.resolve().relative_to(project_root).as_posix()
except ValueError:
return path.resolve().as_posix()
def project_revision(project_root: Path) -> str:
proc = subprocess.run(
["git", "-C", str(project_root), "rev-parse", "HEAD"],
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
)
return proc.stdout.strip() if proc.returncode == 0 else "unknown"
def source_kloc(paths: list[Path]) -> float:
lines = 0
for path in paths:
try:
with path.open("r", encoding="utf-8", errors="replace") as stream:
lines += sum(1 for _ in stream)
except OSError:
continue
return lines / 1000.0
def density_scope_paths(
tus: list[Path],
findings: list[dict[str, Any]],
changed_files: list[str],
project_root: Path,
) -> list[Path]:
if changed_files:
return sorted({(project_root / item).resolve() for item in changed_files})
paths = {path.resolve() for path in tus}
for finding in findings:
uri = str(finding.get("uri", ""))
if not uri:
continue
path = Path(uri)
paths.add(
path.resolve() if path.is_absolute() else (project_root / path).resolve()
)
return sorted(paths)
def load_changed_files(path: Path | None, project_root: Path) -> list[str]:
if path is None:
return []
text = path.read_text(encoding="utf-8")
loaded = (
load_json_document(path)
if text.lstrip().startswith("[")
else [line.strip() for line in text.splitlines() if line.strip()]
)
if not isinstance(loaded, list) or not all(
isinstance(item, str) for item in loaded
):
raise ValueError(
"changed-files must be a JSON string array or one path per line"
)
normalized: list[str] = []
for item in loaded:
candidate = Path(item)
normalized.append(
project_path(candidate, project_root)
if candidate.is_absolute()
else candidate.as_posix().removeprefix("./")
)
return sorted(set(normalized))
# ---------------------------------------------------------------------------
# Lane command construction
# ---------------------------------------------------------------------------
def source_scan_settings(analysis: dict[str, Any]) -> dict[str, int | float]:
return {
key: analysis.get(key, default) for key, _flag, default in SOURCE_SETTING_FLAGS
}
def rust_toolchain(analysis: dict[str, Any]) -> str | None:
configured = str(analysis.get("rust_toolchain", "")).strip()
if configured:
return configured
toolchain_file = REPO_ROOT / "tools" / "fold-rust" / "rust-toolchain.toml"
if not toolchain_file.exists():
return None
for line in toolchain_file.read_text(encoding="utf-8").splitlines():
match = re.match(r'\s*channel\s*=\s*["\']([^"\']+)["\']\s*$', line)
if match:
return match.group(1)
return None
def detector_args(command: list[str], analysis: dict[str, Any]) -> None:
detectors = [str(item) for item in analysis.get("detectors", [])]
scopes = [str(item) for item in analysis.get("scopes", [])]
if detectors:
command.extend(["--detectors", ",".join(detectors)])
else:
for scope in scopes:
command.extend(["--scope", scope])
def build_source_command(
fold: Path,
database: Path,
report_path: Path,
profile_path: Path,
analysis: dict[str, Any],
scan_settings: dict[str, int | float],
*,
processes: int,
profiled: bool,
) -> list[str]:
command = [
str(fold),
"--quiet",
"scan",
"--mode",
"source",
"--compile-db",
str(database),
"--format",
"json",
"--output",
str(report_path),
"--processes",
str(processes),
]
for key, flag, _default in SOURCE_SETTING_FLAGS:
command.extend([flag, str(scan_settings[key])])
detector_args(command, analysis)
command.extend(str(arg) for arg in analysis.get("extra_args", []))
if profiled:
command.extend(["--profile", "--profile-output", str(profile_path)])
return command
def build_rust_command(
fold: Path,
corpus: Path,
report_path: Path,
profile_path: Path,
analysis: dict[str, Any],
*,
processes: int,
profiled: bool,
) -> list[str]:
command = [
str(fold),
"--quiet",
"scan",
"--mode=rust",
str(corpus),
"--format",
"json",
"--output",
str(report_path),
"--processes",
str(processes),
]
detector_args(command, analysis)
command.extend(str(arg) for arg in analysis.get("extra_args", []))
if profiled:
command.extend(["--profile", "--profile-output", str(profile_path)])
return command
def build_binary_command(
fold: Path,
binary: Path,
report_path: Path,
profile_path: Path,
analysis: dict[str, Any],
*,
profiled: bool,
) -> list[str]:
command = [
str(fold),
"--quiet",
"scan",
"--mode",
"binary",
"--binary",
str(binary),
"--format",
"json",
"--output",
str(report_path),
]
detector_args(command, analysis)
command.extend(str(arg) for arg in analysis.get("extra_args", []))
if profiled:
command.extend(["--profile", "--profile-output", str(profile_path)])
return command
# ---------------------------------------------------------------------------
# Lane execution
# ---------------------------------------------------------------------------
def scan_operational_evidence(output: str) -> dict[str, Any]:
"""Classify frontend coverage failures without treating them as findings."""
categories: dict[str, list[tuple[str, str]]] = {
"missing_dependencies": [],
"tu_timeouts": [],
"tu_crashes": [],
"frontend_failures": [],
}
for raw_line in output.splitlines():
line = raw_line.strip()
lowered = line.lower()
worker_tu = (
line.split("while processing TU:", 1)[1].strip()
if "while processing TU:" in line
else ""
)
compiler_match = re.match(
r"^(.*?)(?::[0-9]+:[0-9]+)?:\s*(?:fatal )?error:", line
)
compiler_tu = compiler_match.group(1) if compiler_match else ""
if ("fatal error:" in lowered or "error:" in lowered) and (
"file not found" in lowered or "no such file or directory" in lowered
):
categories["missing_dependencies"].append((compiler_tu, line))
if "while processing tu:" in lowered and (
"killed by signal 14" in lowered or "timed out" in lowered
):
categories["tu_timeouts"].append((worker_tu, line))
elif "while processing tu:" in lowered and "killed by signal" in lowered:
categories["tu_crashes"].append((worker_tu, line))
if "while processing tu:" in lowered and "exited with status" in lowered:
categories["frontend_failures"].append((worker_tu, line))
evidence: dict[str, Any] = {}
for name, entries in categories.items():
affected_tus = sorted({tu for tu, _line in entries if tu})
evidence[name] = {
"count": len(affected_tus) if affected_tus else len(entries),
"event_count": len(entries),
"affected_tus": affected_tus,
"examples": [line for _tu, line in entries[:8]],
}
return evidence
def run_lane(
*,
name: str,
fold: Path,
output_dir: Path,
repeats: int,
command_for: Callable[[Path, Path, int], list[str]],
uri_base: Path,
project_root: Path,
timeout: float,
experiment_prefix: str,
target: dict[str, Any],
budgets: dict[str, Any],
profiled: bool,
env: dict[str, str] | None = None,
) -> tuple[list[dict[str, Any]], list[str]]:
"""Execute one lane ``repeats`` times and retain per-run artifacts."""
runs: list[dict[str, Any]] = []
failures: list[str] = []
fold_fingerprint = sha256_file(fold)
fold_version = executable_version(fold)
for index in range(repeats):
report_path = output_dir / f"{name}-{index + 1}.sarif.json"
profile_path = output_dir / f"{name}-{index + 1}.profile.json"
log_path = output_dir / f"{name}-{index + 1}.log"
artifact_path = output_dir / f"{name}-{index + 1}.run.json"
for stale_path in (report_path, profile_path, log_path, artifact_path):
stale_path.unlink(missing_ok=True)
command = command_for(report_path, profile_path, index)
started_at = utc_now()
proc = common.run_measured(command, REPO_ROOT, env=env, timeout_seconds=timeout)
ended_at = utc_now()
log_path.write_text(proc.output, encoding="utf-8")
operational_evidence = scan_operational_evidence(proc.output)
affected_tus = sorted(
{
tu
for evidence in operational_evidence.values()
for tu in evidence["affected_tus"]
}
)
selected_tus = integer(target.get("selected_tus", 0), name="selected TUs")
coverage_reconciliation = {
"selected_tus": selected_tus,
"failed_tus": len(affected_tus),
"completed_tus_lower_bound": max(0, selected_tus - len(affected_tus)),
"affected_tus": affected_tus,
}
run: dict[str, Any] = {
"lane": name,
"iteration": index + 1,
"returncode": proc.returncode,
"output_bytes": len(proc.output.encode("utf-8")),
"operational_evidence": operational_evidence,
"coverage_reconciliation": coverage_reconciliation,
**proc.metrics(),
}
for category, evidence in operational_evidence.items():
if evidence["count"]:
failures.append(
f"{name} run {index + 1} {category.replace('_', ' ')}: "
f"{evidence['count']}"
)
if proc.returncode not in SUCCESS_EXIT_CODES:
failures.append(f"{name} run {index + 1} exited {proc.returncode}")
else:
try:
report = json.loads(report_path.read_text(encoding="utf-8"))
run["findings"] = summarize_sarif_findings(
report, project_root, uri_base
)
if profiled:
if not profile_path.exists():
raise ValueError("profile output is missing")
run["profile"] = json.loads(
profile_path.read_text(encoding="utf-8")
)
scan = run["profile"].get("scan", {})
if not isinstance(scan, dict):
raise ValueError("profile scan metrics are missing")
profile_selected = integer(
scan.get("source_tus_selected"), name="profile selected TUs"
)
profile_attempted = integer(
scan.get("source_tus_attempted"), name="profile attempted TUs"
)
profile_completed = integer(
scan.get("source_tus_completed"), name="profile completed TUs"
)
profile_timed_out = integer(
scan.get("source_tus_timed_out"), name="profile timed-out TUs"
)
profile_frontend_failed = integer(
scan.get("source_tus_frontend_failed"),
name="profile frontend-failed TUs",
)
run["coverage_reconciliation"] = {
"selected_tus": profile_selected,
"attempted_tus": profile_attempted,
"completed_tus": profile_completed,
"timed_out_tus": profile_timed_out,
"frontend_failed_tus": profile_frontend_failed,
"affected_tus": affected_tus,
}
if (
profile_attempted != profile_selected
or profile_completed != profile_attempted
or profile_timed_out != 0
or profile_frontend_failed != 0
):
failures.append(
f"{name} run {index + 1} incomplete profile coverage: "
f"selected={profile_selected} attempted={profile_attempted} "
f"completed={profile_completed} timed_out={profile_timed_out} "
f"frontend_failed={profile_frontend_failed}"
)
except (OSError, ValueError, json.JSONDecodeError) as exc:
failures.append(
f"{name} run {index + 1} produced invalid artifacts: {exc}"
)
artifacts = [artifact_record("log", log_path, "text/plain")]
if report_path.exists():
artifacts.append(
artifact_record("sarif-report", report_path, "application/sarif+json")
)
if profiled and profile_path.exists():
artifacts.append(
artifact_record("profile", profile_path, "application/json")
)
run_artifact = build_run_artifact(
experiment_id=f"{experiment_prefix}:{name}:{index + 1}",
case_id="real-project-evaluation",
tool={
"name": "fold",
"path": str(fold),
"version": fold_version,
"sha256": fold_fingerprint,
},
target=target,
command=command,
started_at=started_at,
ended_at=ended_at,
exit_status=proc.returncode,
budgets=budgets,
artifacts=artifacts,
measurements=proc.metrics(),
observations={
**run.get("findings", {}),
"operational_evidence": operational_evidence,
"coverage_reconciliation": run["coverage_reconciliation"],
},
)
artifact_errors = validate_run_artifact(run_artifact)
if artifact_errors:
failures.extend(
f"{name} run {index + 1} artifact: {error}" for error in artifact_errors
)
write_json(artifact_path, run_artifact)
run["run_artifact"] = str(artifact_path)
runs.append(run)
return runs, failures
def aggregate_profiles(profiles: list[dict[str, Any]]) -> dict[str, Any]:
scan = {
name: distribution_summary(
[
numeric(profile.get("scan", {}).get(name, 0), name=f"scan.{name}")
for profile in profiles
]
)
for name in SCAN_METRICS
}
detector_names = sorted(
{
str(detector.get("name", ""))
for profile in profiles
for detector in profile.get("detectors", [])
if isinstance(detector, dict)
}
)
detectors: dict[str, Any] = {}
for name in detector_names:
rows = [
detector
for profile in profiles
for detector in profile.get("detectors", [])
if isinstance(detector, dict) and str(detector.get("name", "")) == name
]
detectors[name] = {
metric: distribution_summary(
[
numeric(row.get(metric, 0), name=f"detector.{name}.{metric}")
for row in rows
]
)
for metric in DETECTOR_METRICS
}
return {"scan": scan, "detectors": detectors}
# ---------------------------------------------------------------------------
# Baseline comparison
# ---------------------------------------------------------------------------
def rss_regression(
current: dict[str, Any], baseline: dict[str, Any], max_rss_percent: float
) -> list[str]:
failures: list[str] = []
current_rss = (
current.get("performance", {})
.get("profiled_serial", {})
.get("profile", {})
.get("scan", {})
.get("peak_rss_kb", {})
.get("median", 0)
)
baseline_rss = (
baseline.get("performance", {})
.get("profiled_serial", {})
.get("profile", {})
.get("scan", {})
.get("peak_rss_kb", {})
.get("median", 0)
)
baseline_value = numeric(baseline_rss, name="baseline peak RSS")
if baseline_value > 0:
current_value = numeric(current_rss, name="current peak RSS")
rss_delta = 100.0 * (current_value - baseline_value) / baseline_value
if rss_delta > max_rss_percent:
failures.append(
f"peak RSS regressed {rss_delta:.1f}% (limit {max_rss_percent:.1f}%)"
)
return failures
def gold_threshold_failures(
score: dict[str, Any], thresholds: dict[str, Any]
) -> list[str]:
failures: list[str] = []
for metric in CLASSIFICATION_THRESHOLD_METRICS:
if metric not in thresholds:
continue
observed = numeric(score.get(metric, 0.0), name=f"gold {metric}")
minimum = numeric(thresholds[metric], name=f"gold {metric} threshold")
if observed < minimum:
failures.append(
f"gold {metric} {observed:.3f} below threshold {minimum:.3f}"
)
return failures
def per_rule_gold_threshold_failures(
scores: dict[str, dict[str, Any]], thresholds: dict[str, Any]
) -> list[str]:
failures: list[str] = []
for rule, rule_thresholds in sorted(thresholds.items()):
if not isinstance(rule_thresholds, dict):
continue
score = scores.get(rule, {})
for metric in CLASSIFICATION_THRESHOLD_METRICS:
if metric not in rule_thresholds:
continue
observed = numeric(score.get(metric, 0.0), name=f"{rule} {metric}")
minimum = numeric(
rule_thresholds[metric], name=f"{rule} {metric} threshold"
)
if observed < minimum:
failures.append(
f"rule {rule} {metric} {observed:.3f} below threshold {minimum:.3f}"
)
return failures
def stability_by_rule(finding_rows: list[dict[str, Any]]) -> dict[str, dict[str, Any]]:
"""Return deterministic stability summaries for every emitted rule."""
rules = sorted(
{
str(record.get("rule_id", ""))
for row in finding_rows
for record in row.get("records", [])
if record.get("rule_id")
}
)
return {
rule: stability_summary(
[
[
str(record.get("finding_id", ""))
for record in row.get("records", [])
if record.get("rule_id") == rule
]
for row in finding_rows
]
)
for rule in rules
}
def stability_budget_failures(
by_rule: dict[str, dict[str, Any]],
policy: dict[str, Any],
experimental_rules: set[str],
) -> list[str]:
failures: list[str] = []
max_churn = policy.get("max_churn")
min_jaccard = policy.get("min_pairwise_jaccard")
for rule, stability in sorted(by_rule.items()):
if (
rule.removeprefix("fold.") in experimental_rules
or rule in experimental_rules
):
continue
churn = integer(stability.get("churn_count", 0), name=f"{rule} churn")
median = numeric(
stability.get("pairwise_jaccard", {}).get("median", 1.0),
name=f"{rule} pairwise Jaccard",
)
if max_churn is not None and churn > integer(max_churn, name="max churn"):
failures.append(f"rule {rule} churn {churn} exceeds budget {max_churn}")
if min_jaccard is not None:
minimum = numeric(min_jaccard, name="minimum pairwise Jaccard")
if median < minimum:
failures.append(
f"rule {rule} pairwise Jaccard {median:.3f} "
f"below budget {minimum:.3f}"
)
return failures
# ---------------------------------------------------------------------------
# Reporting
# ---------------------------------------------------------------------------
def render_markdown(summary: dict[str, Any]) -> str:
reliability = summary["reliability"]
stability = reliability.get("stability", {})
performance = summary["performance"]
surface = summary["optimization_surface"]
manifest = summary["manifest"]
lines = [
"# Fold project evaluation",
"",
f"- Project: `{manifest['project']}` (lane `{manifest['lane']}`)",
f"- Status: **{summary['status']}**",
f"- Manifest fingerprint: `{manifest['configuration_fingerprint']}`",
f"- Stable findings: `{reliability['stable_findings']}`",
f"- Finding count: `{reliability.get('finding_count', 0)}`",
f"- Finding fingerprint: `{reliability.get('finding_fingerprint', '')}`",
f"- Finding intersection/union: "
f"`{stability.get('intersection_count', 0)}/{stability.get('union_count', 0)}`",
f"- Finding churn: `{stability.get('churn_count', 0)}`",
f"- Pairwise Jaccard median: "
f"`{stability.get('pairwise_jaccard', {}).get('median', 0.0)}`",
f"- Profiled median: `"
f"{performance['profiled_serial']['wall_seconds']['median']} s`",
f"- Peak RSS: `{surface['peak_rss_mb']} MiB`",
"",
"## Ranked optimization surface",
"",
"| Detector | Median ms | Analyze share | Z3 queries | Timeouts |",
"| --- | ---: | ---: | ---: | ---: |",
]
for row in surface["ranked_detectors"]:
lines.append(
f"| `{row['detector']}` | {row['wall_ms']} | {row['analyze_share_pct']}% | "
f"{row['z3_queries']} | {row['z3_timeouts']} |"
)
per_binary = stability.get("per_binary")
if isinstance(per_binary, dict):
lines.extend(
[
"",
"## Per-binary stability",
"",
"| Binary lane | Stable | Findings |",
"| --- | --- | ---: |",
]
)
for name, item in sorted(per_binary.items()):
if not isinstance(item, dict):
continue
counts = item.get("per_run_counts", [])
findings = counts[0] if counts else 0
lines.append(f"| `{name}` | {item.get('exact', False)} | {findings} |")
if surface["ranked_source_phases"]:
lines.extend(
[
"",
"## Pipeline phases",
"",
"| Phase | Median ms | Analyze share |",
"| --- | ---: | ---: |",
]
)
for row in surface["ranked_source_phases"]:
lines.append(
f"| `{row['phase']}` | {row['wall_ms']} | {row['analyze_share_pct']}% |"
)
gold = summary.get("gold")
if isinstance(gold, dict):
lines.extend(
[
"",
"## Gold scoring",
"",
f"- TP/FP/FN: `{gold['true_positive']}/{gold['false_positive']}"
f"/{gold['false_negative']}`",
f"- Precision: `{gold['precision']}`",
f"- Recall: `{gold['recall']}`",
f"- F1: `{gold['f1']}`",
]
)
change_time = summary.get("change_time")
if isinstance(change_time, dict):
counts = change_time["counts"]
lines.extend(
[
"",
"## Reviewed change-time findings",
"",
f"- New: `{counts['new']}`",
f"- Persisting: `{counts['persisting']}`",
f"- Resolved: `{counts['resolved']}`",
f"- Review coverage: `{change_time['review_coverage']}`",
f"- Alert density: `{change_time['alert_density_per_kloc']}` per KLOC",
f"- New alert density: "
f"`{change_time['new_alert_density_per_kloc']}` per KLOC",
]
)
if reliability["failures"]:
lines.extend(["", "## Failures", ""])
lines.extend(f"- {failure}" for failure in reliability["failures"])
return "\n".join(lines) + "\n"
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--manifest", type=Path, required=True)
parser.add_argument("--fold", type=Path, default=DEFAULT_FOLD)
parser.add_argument(
"--out-dir",
type=Path,
default=Path(tempfile.gettempdir()) / "fold-project-eval",
)
parser.add_argument("--timeout", type=float, default=600.0)
parser.add_argument("--changed-files", type=Path)
parser.add_argument("--max-new-findings", type=int, default=0)
parser.add_argument(
"--allow-finding-drift",
action="store_true",
help="Also implied by evaluation.allow_finding_drift in the manifest.",
)
args = parser.parse_args()
fold = args.fold.expanduser().resolve()
if not fold.exists():
print(f"fold binary not found: {fold}", file=sys.stderr)
return 2
manifest_path = args.manifest.expanduser().resolve()
try:
manifest = load_project_manifest(manifest_path)
except (OSError, ValueError, json.JSONDecodeError) as exc:
print(f"{exc}", file=sys.stderr)
return 2
lane = str(manifest["lane"])
manifest_dir = manifest_path.parent
corpus = manifest.get("corpus", {})
analysis = manifest.get("analysis", {})
evaluation = manifest.get("evaluation", {})
missing_policy = str(corpus.get("missing_corpus", "fail"))
# --- corpus resolution -------------------------------------------------
project_root = (
resolve(manifest_dir, str(corpus["path"])) if corpus.get("path") else None
)
binaries: list[Path] = []
database: Path | None = None
if lane == "source":
database = compile_db_path(resolve(manifest_dir, str(corpus["compile_db"])))
if project_root is None:
for parent in (database.parent, *database.parents):
if (parent / ".git").exists():
project_root = parent
break
if project_root is None:
project_root = database.parent
missing = [] if database.exists() else [database]
elif lane == "rust":
missing = [] if project_root and project_root.is_dir() else [project_root]
else:
binaries = [
resolve(manifest_dir, str(item)) for item in corpus.get("binaries", [])
]
missing = [binary for binary in binaries if not binary.exists()]
if project_root is None:
project_root = REPO_ROOT
if missing:
rendered = ", ".join(str(item) for item in missing)
if missing_policy == "skip":
print(f"missing corpus, skipping per manifest policy: {rendered}")
return 0
print(f"missing corpus: {rendered}", file=sys.stderr)
return 2
if project_root is None:
raise ValueError("project root could not be resolved")
args.out_dir.mkdir(parents=True, exist_ok=True)
repeats = integer(evaluation.get("repeats", 2), name="evaluation.repeats")
processes = integer(analysis.get("processes", 1), name="analysis.processes")
allow_drift = bool(evaluation.get("allow_finding_drift", False)) or (
args.allow_finding_drift
)
scan_settings = source_scan_settings(analysis)
effective_configuration = manifest_configuration(manifest)
if lane == "source":
effective_configuration["exclude_regex"] = str(
analysis.get("exclude_regex", DEFAULT_EXCLUDE_REGEX)
)
effective_configuration["source_settings"] = scan_settings
if lane == "rust":
effective_configuration["rust_toolchain"] = rust_toolchain(analysis) or ""
analysis_fingerprint = configuration_fingerprint(effective_configuration)
# --- lane execution ------------------------------------------------------
failures: list[str] = []
all_runs: list[dict[str, Any]] = []
profile_runs: list[dict[str, Any]] = []
target: dict[str, Any] = {}
tus: list[Path] = []
experiment_prefix = f"project-eval:{analysis_fingerprint[:12]}"
try:
if lane == "source":
if database is None:
raise ValueError("source lane requires a compile database")
include = (
re.compile(str(analysis["path_regex"]))
if analysis.get("path_regex")
else None
)
exclude = re.compile(
str(analysis.get("exclude_regex", DEFAULT_EXCLUDE_REGEX))
)
filtered_db, tus = filtered_compile_db(
database,
args.out_dir,
include,
exclude,
integer(analysis.get("max_tus", 0), name="analysis.max_tus"),
)
input_fingerprint = sha256_file(filtered_db)
target = {
"project_root": str(project_root),
"compile_db": str(filtered_db),
"input_fingerprint": input_fingerprint,
"selected_tus": len(tus),
}
budgets = {
"wall_seconds": args.timeout,
"processes": 1,
**scan_settings,
}
profile_runs, failures = run_lane(
name="profiled-serial",
fold=fold,
output_dir=args.out_dir,
repeats=repeats,
command_for=lambda report, profile, _i: build_source_command(
fold,
filtered_db,
report,
profile,
analysis,
scan_settings,
processes=1,
profiled=True,
),
uri_base=database.parent,
project_root=project_root,
timeout=args.timeout,
experiment_prefix=experiment_prefix,
target=target,
budgets=budgets,
profiled=True,
)
all_runs.extend(profile_runs)
if processes > 1:
throughput_runs, lane_failures = run_lane(
name="throughput",
fold=fold,
output_dir=args.out_dir,
repeats=repeats,
command_for=lambda report, profile, _i: build_source_command(
fold,
filtered_db,
report,
profile,
analysis,
scan_settings,
processes=processes,
profiled=False,
),
uri_base=database.parent,
project_root=project_root,
timeout=args.timeout,
experiment_prefix=experiment_prefix,
target=target,
budgets={**budgets, "processes": processes},
profiled=False,
)
failures.extend(lane_failures)
all_runs.extend(throughput_runs)
elif lane == "rust":
input_fingerprint = configuration_fingerprint(
{"corpus": str(project_root), "lane": "rust"}
)
target = {
"project_root": str(project_root),
"input_fingerprint": input_fingerprint,
}
rust_env = os.environ.copy()
if toolchain := rust_toolchain(analysis):
rust_env["RUSTUP_TOOLCHAIN"] = toolchain
profile_runs, failures = run_lane(
name="profiled-rust",
fold=fold,
output_dir=args.out_dir,
repeats=repeats,
command_for=lambda report, profile, _i: build_rust_command(
fold,
project_root,
report,
profile,
analysis,
processes=processes,
profiled=True,
),
uri_base=project_root,
project_root=project_root,
timeout=args.timeout,
experiment_prefix=experiment_prefix,
target=target,
budgets={"wall_seconds": args.timeout, "processes": processes},
profiled=True,
env=rust_env,
)
all_runs.extend(profile_runs)
else: # binary lane
for binary in binaries:
slug = hashlib.sha256(str(binary).encode()).hexdigest()[:8]
input_fingerprint = sha256_file(binary)
target = {
"binary": str(binary),
"input_fingerprint": input_fingerprint,
}
binary_runs, lane_failures = run_lane(
name=f"binary-{binary.stem}-{slug}",
fold=fold,
output_dir=args.out_dir,
repeats=repeats,
command_for=lambda report, profile, _i, b=binary: (
build_binary_command(
fold, b, report, profile, analysis, profiled=True
)
),
uri_base=binary.parent,
project_root=project_root,
timeout=args.timeout,
experiment_prefix=experiment_prefix,
target=target,
budgets={"wall_seconds": args.timeout},
profiled=True,
)
failures.extend(lane_failures)
all_runs.extend(binary_runs)
profile_runs = all_runs
except (OSError, ValueError, json.JSONDecodeError, re.error) as exc:
print(f"benchmark setup failed: {exc}", file=sys.stderr)
return 2
# --- reliability and stability ------------------------------------------
finding_rows = [run["findings"] for run in all_runs if "findings" in run]
if lane == "binary":
runs_by_binary: dict[str, list[dict[str, Any]]] = {}
for run in all_runs:
if "findings" in run:
runs_by_binary.setdefault(str(run["lane"]), []).append(run["findings"])
per_binary_stability = {
name: stability_summary([row.get("identity_keys", []) for row in rows])
for name, rows in sorted(runs_by_binary.items())
}
stable = len(finding_rows) == len(all_runs) and all(
row["exact"] for row in per_binary_stability.values()
)
finding_stability: dict[str, Any] = {
"exact": stable,
"per_binary": per_binary_stability,
}
first_findings_by_binary = {
name: rows[0] for name, rows in sorted(runs_by_binary.items()) if rows
}
first_findings = next(iter(first_findings_by_binary.values()), {})
finding_count = sum(
integer(row.get("count", 0), name="binary finding count")
for row in first_findings_by_binary.values()
)
by_rule_counter: Counter[str] = Counter()
for row in first_findings_by_binary.values():
by_rule_counter.update(row.get("by_rule", {}))
finding_fingerprint = configuration_fingerprint(
{
name: row.get("fingerprint", "")
for name, row in first_findings_by_binary.items()
}
)
else:
finding_stability = stability_summary(
[row.get("identity_keys", []) for row in finding_rows]
)
stable = finding_stability["exact"] and len(finding_rows) == len(all_runs)
first_findings = finding_rows[0] if finding_rows else {}
finding_count = integer(first_findings.get("count", 0), name="finding count")
by_rule_counter = Counter(first_findings.get("by_rule", {}))
finding_fingerprint = str(first_findings.get("fingerprint", ""))
if not stable:
failures.append(
"finding fingerprints differ across repetitions or execution lanes"
)
per_rule_stability = stability_by_rule(finding_rows)
stability_policy = evaluation.get("stability", {})
if isinstance(stability_policy, dict):
failures.extend(
stability_budget_failures(
per_rule_stability,
stability_policy,
{str(rule) for rule in evaluation.get("experimental_rules", [])},
)
)
missing_backends = sum(
integer(row["missing_backend"], name="missing backend count")
for row in finding_rows
)
if missing_backends:
failures.append(
f"reports contain {missing_backends} findings without backend provenance"
)
# Locationless results are fatal on source/rust where every finding must
# carry a file+line; on the binary lane they are identity-bearing heuristic
# observations (artifact+function+kind) and are reported, not failed.
missing_locations = sum(
integer(row["missing_location"], name="missing location count")
for row in finding_rows
)
if missing_locations and lane != "binary":
failures.append(f"reports contain {missing_locations} locationless findings")
profiles = [run["profile"] for run in profile_runs if "profile" in run]
profile_summary = aggregate_profiles(profiles)
z3_timeouts = sum(
integer(metrics["z3_timeouts"]["max"], name="Z3 timeout count")
for metrics in profile_summary["detectors"].values()
)
z3_budget = integer(
evaluation.get("z3_timeout_budget", 0), name="evaluation.z3_timeout_budget"
)
if z3_timeouts > z3_budget:
failures.append(f"Z3 timeouts {z3_timeouts} exceed budget {z3_budget}")
if "min_functions" in evaluation:
functions = numeric(
profile_summary["scan"]["num_functions"].get("median", 0),
name="median function count",
)
functions = max(
[
functions,
*(
numeric(
metrics["functions_analyzed"].get("median", 0),
name="analyzed function count",
)
for metrics in profile_summary["detectors"].values()
),
]
)
minimum = integer(evaluation["min_functions"], name="evaluation.min_functions")
if functions < minimum:
failures.append(
f"functions analyzed {functions:g} below required minimum {minimum}"
)
absolute_budgets = evaluation.get("budgets", {})
if isinstance(absolute_budgets, dict):
median_wall = numeric(
distribution_summary(
[
numeric(run["wall_seconds"], name="wall seconds")
for run in profile_runs
]
).get("median", 0.0),
name="median wall seconds",
)
median_output = numeric(
distribution_summary(
[
numeric(run["output_bytes"], name="output bytes")
for run in profile_runs
]
).get("median", 0.0),
name="median output bytes",
)
peak_rss_mb = (
numeric(
profile_summary["scan"]["peak_rss_kb"].get("median", 0.0),
name="median peak RSS",
)
/ 1024.0
)
frontend_ms = numeric(
profile_summary["scan"]["frontend_extraction_ms"].get("median", 0.0),
name="median frontend extraction time",
)
limits = (
("max_wall_seconds", median_wall, "wall seconds"),
("max_peak_rss_mb", peak_rss_mb, "peak RSS MiB"),
("max_frontend_extraction_ms", frontend_ms, "frontend extraction ms"),
("max_output_bytes", median_output, "output bytes"),
)
for field, observed, label in limits:
if field not in absolute_budgets:
continue
limit = numeric(absolute_budgets[field], name=f"evaluation.budgets.{field}")
if observed > limit:
failures.append(f"{label} {observed:.3f} exceeds budget {limit:.3f}")
target_paths = [project_path(path, project_root) for path in tus]
if lane == "source":
target_hash = hashlib.sha256("\n".join(target_paths).encode()).hexdigest()
elif lane == "rust":
target_hash = configuration_fingerprint(
{"lane": lane, "corpus": str(project_root)}
)
else:
target_hash = configuration_fingerprint(
{
"lane": lane,
"binaries": [
{"path": str(binary), "sha256": sha256_file(binary)}
for binary in binaries
],
}
)
revision = project_revision(project_root)
performance: dict[str, Any] = {
"profiled_serial": {
"wall_samples_seconds": [
numeric(run["wall_seconds"], name="profiled wall seconds")
for run in profile_runs
],
"wall_seconds": distribution_summary(
[
numeric(run["wall_seconds"], name="profiled wall seconds")
for run in profile_runs
]
),
"profile": profile_summary,
}
}
throughput_runs = [run for run in all_runs if run["lane"] == "throughput"]
if throughput_runs:
performance["throughput"] = {
"processes": processes,
"wall_samples_seconds": [
numeric(run["wall_seconds"], name="throughput wall seconds")
for run in throughput_runs
],
"wall_seconds": distribution_summary(
[
numeric(run["wall_seconds"], name="throughput wall seconds")
for run in throughput_runs
]
),
}
operational_by_run = [
{
"lane": run["lane"],
"iteration": run["iteration"],
"reconciliation": run.get("coverage_reconciliation", {}),
**run.get("operational_evidence", {}),
}
for run in all_runs
]
operational_totals = {
category: sum(
integer(
run.get("operational_evidence", {}).get(category, {}).get("count", 0),
name=f"operational evidence {category}",
)
for run in all_runs
)
for category in (
"missing_dependencies",
"tu_timeouts",
"tu_crashes",
"frontend_failures",
)
}
summary: dict[str, Any] = {
"schema_version": 2,
"status": "ok",
"manifest": {
"path": str(manifest_path),
"project": str(manifest["project"]),
"lane": lane,
"configuration_fingerprint": analysis_fingerprint,
"configuration": effective_configuration,
},
"target": {
"project_root": str(project_root),
"lane": lane,
"selected_tus": len(tus),
"tu_set_fingerprint": target_hash,
"revision": revision,
"paths": target_paths,
"binaries": [str(binary) for binary in binaries],
},
"configuration": {
**effective_configuration,
"analysis_fingerprint": analysis_fingerprint,
"repeats": repeats,
"throughput_processes": processes if throughput_runs else 1,
},
"reliability": {
"stable_findings": stable,
"finding_count": finding_count,
"finding_fingerprint": finding_fingerprint,
"stability": finding_stability,
"per_rule_stability": per_rule_stability,
"by_rule": dict(sorted(by_rule_counter.items())),
"missing_location": missing_locations,
"z3_timeouts": z3_timeouts,
"operational_coverage": {
"totals": operational_totals,
"by_run": operational_by_run,
},
"failures": failures,
},
"performance": performance,
"optimization_surface": optimization_surface(profile_summary),
"run_artifacts": [
run["run_artifact"] for run in all_runs if "run_artifact" in run
],
}
# --- optional gold scoring ------------------------------------------------
if evaluation.get("gold"):
gold_path = resolve(manifest_dir, str(evaluation["gold"]))
try:
gold_document = json.loads(gold_path.read_text(encoding="utf-8"))
negative_entries: list[dict[str, Any]] | None = None
gold_entries = gold_document
if isinstance(gold_document, dict):
gold_entries = gold_document.get(
"cases",
gold_document.get("gold", gold_document.get("expected", [])),
)
if "negative" in gold_document:
negative_entries = gold_document["negative"]
elif "universe" in gold_document:
negative_entries = negative_cases_from_universe(
gold_entries, gold_document["universe"]
)
if not isinstance(gold_entries, list):
raise ValueError("gold file must contain a JSON array of cases")
if negative_entries is not None and not isinstance(negative_entries, list):
raise ValueError("gold negative cases must be a JSON array")
score_input = [
{
"id": record.get("finding_id", ""),
"kind": str(record.get("rule_id", "")).removeprefix("fold."),
"file": record.get("uri", ""),
}
for record in first_findings.get("records", [])
]
summary["gold"] = (
score_labeled_cases(score_input, gold_entries, negative_entries)
if negative_entries is not None
else score_grouped_findings(score_input, gold_entries)
)
summary["gold"]["per_rule"] = score_findings_by_rule(
score_input, gold_entries, negative_entries
)
thresholds = evaluation.get("gold_thresholds")
if isinstance(thresholds, dict):
failures.extend(gold_threshold_failures(summary["gold"], thresholds))
per_rule_thresholds = evaluation.get("per_rule_gold_thresholds")
if isinstance(per_rule_thresholds, dict):
failures.extend(
per_rule_gold_threshold_failures(
summary["gold"]["per_rule"], per_rule_thresholds
)
)
except (OSError, ValueError, json.JSONDecodeError) as exc:
failures.append(f"invalid gold file: {exc}")
# --- optional disposition compare ------------------------------------------
changed_files = load_changed_files(args.changed_files, project_root)
if args.changed_files and not evaluation.get("dispositions"):
failures.append("--changed-files requires evaluation.dispositions")
if evaluation.get("dispositions"):
dispositions_path = resolve(manifest_dir, str(evaluation["dispositions"]))
try:
dispositions = json.loads(dispositions_path.read_text(encoding="utf-8"))
disposition_errors = validate_disposition_baseline(dispositions)
if disposition_errors:
raise ValueError("; ".join(disposition_errors))
disposition_target = dispositions["target"]
if dispositions["target_name"] != str(manifest["project"]):
failures.append("disposition baseline targets a different project")
if lane == "source":
if disposition_target["tu_set_fingerprint"] != target_hash:
failures.append(
"disposition baseline uses a different translation-unit set"
)
# Source disposition baselines pin the run_code_quality analysis
# configuration fingerprint; a manifest with matching settings
# reproduces it through the same canonical shape.
cq_configuration = {
"scopes": effective_configuration["scopes"],
"detectors": (
",".join(effective_configuration["detectors"]) or None
),
"scan_settings": scan_settings,
}
if disposition_target[
"configuration_fingerprint"
] != configuration_fingerprint(cq_configuration):
failures.append(
"disposition baseline uses a different analysis configuration"
)
change_time = compare_dispositions(
first_findings.get("records", []),
dispositions,
changed_files=changed_files,
scope_kloc=source_kloc(
density_scope_paths(
tus,
first_findings.get("records", []),
changed_files,
project_root,
)
),
)
change_time["baseline_revision"] = disposition_target["revision"]
change_time["current_revision"] = revision
change_time["reviewed_at"] = dispositions["reviewed_at"]
change_time["reviewer"] = dispositions["reviewer"]
summary["change_time"] = change_time
if change_time["identity_mismatches"]:
failures.append("persisting finding IDs changed rule identity")
if change_time["counts"]["new"] > evaluation.get(
"max_new_findings", args.max_new_findings
):
failures.append(
f"new findings {change_time['counts']['new']} exceed limit "
f"{evaluation.get('max_new_findings', args.max_new_findings)}"
)
except (OSError, ValueError, KeyError, json.JSONDecodeError) as exc:
failures.append(f"invalid disposition baseline: {exc}")
# --- optional baseline compare ---------------------------------------------
if evaluation.get("baseline"):
baseline_path = resolve(manifest_dir, str(evaluation["baseline"]))
try:
baseline = json.loads(baseline_path.read_text(encoding="utf-8"))
if (
baseline.get("target", {}).get("tu_set_fingerprint")
!= summary["target"]["tu_set_fingerprint"]
):
failures.append("baseline uses a different target set")
if (
baseline.get("manifest", {}).get("configuration_fingerprint")
!= summary["manifest"]["configuration_fingerprint"]
):
failures.append("baseline uses a different analysis configuration")
baseline_fingerprint = baseline.get("reliability", {}).get(
"finding_fingerprint"
)
if (
not allow_drift
and baseline_fingerprint
and baseline_fingerprint
!= summary["reliability"]["finding_fingerprint"]
):
failures.append("finding fingerprint differs from baseline")
baseline_comparison, wall_failures = compare_performance_lanes(
summary,
baseline,
max_wall_percent=numeric(
evaluation.get("wall_regression_pct", 20.0),
name="evaluation.wall_regression_pct",
),
)
summary["baseline_comparison"] = baseline_comparison
failures.extend(wall_failures)
failures.extend(
rss_regression(
summary,
baseline,
numeric(
evaluation.get("rss_regression_pct", 20.0),
name="evaluation.rss_regression_pct",
),
)
)
except (OSError, ValueError, json.JSONDecodeError) as exc:
failures.append(f"invalid baseline: {exc}")
summary["status"] = "failed" if failures else "ok"
(args.out_dir / "summary.json").write_text(
json.dumps(summary, indent=2, sort_keys=True) + "\n", encoding="utf-8"
)
(args.out_dir / "summary.md").write_text(render_markdown(summary), encoding="utf-8")
print(json.dumps(summary, indent=2, sort_keys=True))
return 1 if failures else 0
if __name__ == "__main__":
raise SystemExit(main())