Files
Open_Ultra_SAST/src/openultrasast/plane/generate.py
T
Marius CiepluchandClaude Opus 5.5 45d02ecf20 feat(plane): honest coverage in alerts; PHP alerts from the engine on the host
Quick mode has rules only for c/cpp, groovy, java, javascript/typescript and
python, so every PHP case's "0 alerts" read as clean. The `alerts` task now
records per language of the files read whether quick mode covers it
(derived from the bundled ruleset, not a list): coverage quick | none, with
file counts per pin, and `uncovered`. `remember` turns that into `coverage`
memory rows (a new kind; the Run passes the alerts summary.json as input),
loop-measure reports coverage per language, and improve/memory.py's M1/M2
skip a repository+pin whose coverage for the rule's language is only
`none` (counted under `coverage_none` in the proposal's excluded).

`ousast plane alerts-engine <Run.yaml> --source <frozen export>`
(plane/engine_alerts.py) does the alerts task on the host for cases whose
files include a language the engine covers but quick mode does not (PHP):
exports both pins from the case cache, runs the quick scan and the
openultrasast:dev engine container (finding_dump.py, --network none,
--memory 3g, one at a time, per-pin deadline), writes engine findings in
the alerts.jsonl row shape (rule_id engine:<family>, source engine,
in_fix_range from case.json) with a summary (files read, questions,
completed, seconds, degradations per pin), and marks the task done via
reconciler.mark_done. No result, no files read, or questions == 0 fails
loudly and leaves the run untouched. Refuses a git checkout as source.

Runner image rebuilt and re-pinned (sha256:ebb40a9a...); validation-46
regenerated (digest only). Live, recorded in
benchmarks/measurements/2026-09-30-php-engine-alerts/: froxlor 366 files,
130/130 questions per pin, 6 alerts per pin, 0 in the fix range; wp-gopay
19 files, 101/101 questions, 0 alerts. The loop: 32 run rows (coverage 8,
php=engine), 0 proposals, gate no_proposals, $0.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
2026-09-30 13:44:32 +02:00

506 lines
29 KiB
Python

"""Manifests of a recorded measurement as a Run (ai-service-plane task 7).
``ousast plane workspaces <population.toml> --validation-set <set.json> --candidates <dir> --triage <dir>`` writes,
under ``--plane`` (default ``plane/``), everything a Run over the set needs beside the hand-written templates
(``tasks/repo-facts.yaml``, ``tasks/verify.yaml``, ``tasks/agree.yaml``, ``models/*.yaml``):
- ``workspaces/<case>-vulnerable.yaml``: the case's repository at its vulnerable pin (commit in the
``openultrasast.io/git-commits`` annotation, as :mod:`.workspaces` writes it);
- ``workspaces/<case>-inputs.yaml``: a files-only Workspace with the inputs no task produces -- the recorded
candidates (``candidates.json`` in the ``independent-v2-modelsinks`` shape for ``verify`` and ``agree``,
``functions.json`` as ``{path, function, line}`` rows for ``repo-facts``), everything ``agree`` scores by
(``case.json``: the declared sites, the old-side fix ranges exactly as ``evaluate.hunks(case, "old")`` computes
them from ``--repos``, the declared sites among the set's candidates -- the reference's "of 20" -- and the
reference's cost: candidates before triage, recorded usd, and the triage share of it) and the recorded triage
(``triage.json``, for the measurement);
- ``tasks/<run>.yaml``: per case, one Task per template (two for ``verify``: ``OUSAST_PASS=a|b``) binding the two
Workspaces and naming the files as env;
- ``runs/<run>.yaml``: the Run, per case ``facts -> verify a, verify b -> agree -> verify c -> final -> remember``,
with budgets, annotated with the population and split (``openultrasast.io/population``/``split``: the files'
stems) the memory rows carry.
``--loop`` (harnessx-removal design section 6) appends the improvement loop: per case ``alerts`` (quick-mode rules
on the vulnerable and the fixed pin, which gets its Workspace ``<case>-fixed``; ``case.json`` then also carries the
fix's new-side ranges) feeding ``remember``, and once ``memory-snapshot`` (the store's rows after the train-on-test
guard, written on the host by ``memory.seed``) -> ``loop-measure`` -> ``loop-propose`` -> ``loop-improve``, the last
two bound to this repository at the commit under test. All of them model-free with budget ``{usd: 0, calls: 0}``;
the gate's verdict is ``<run>/loop-improve/gate.json``, and adopting an accepted ledger stays a maintainer commit.
Each ``repo-facts`` Task carries ``openultrasast.io/memory-key`` (repository, pin, candidates digest, runner image
digest): the key under which stored facts are reused instead of recomputed (``plane/memory.py``, ``seed``).
Why per-case Tasks rather than one Task per step: an ax Task binds its Workspaces by name and a Run entry adds
neither a binding nor env (it names a Task, artifacts and a budget), and ``Run.inputs`` accepts only
``<task>/<artifact>``. So an input that no task produces is a Workspace ``files`` entry, and the case a step works
on is fixed by the Task it runs. One Run covers every case so the measurement has one state and one attribution
table.
Candidates are the set's ``(path, function)`` pairs with the line of the case record, deduplicated the way
``benchmarks/independent/verify_batched.py`` does (sorted, the last line of a repeated pair wins), and filtered
by the recorded triage (the ``kept`` lists of the batched-check scans): ``verify`` has no triage stage, so the
hunts see exactly the candidates the reference hunts saw. Output is a pure function of the inputs.
The recorded triage cost is derived per file: a batched-check record's ``usd`` paid for the triage call and the
hunts, and its ``usage`` counts only the hunts (``verify_sinks._Recording`` slices the client's usage from the
first hunt on), so the triage share is ``usd`` minus that usage priced at the reference model's list price.
``--runner-image FILE`` (``~/.cache/ousast/ax-src/runner-image``, written by ``ops/ax/up.sh``) re-pins the
templates' ``image:`` to the digest in that file before generating, so a rebuilt image is one command.
"""
from __future__ import annotations
import json
import math
import re
import subprocess
import tomllib
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import yaml
from ..model.endpoint import price_of
from .budget import cost_of
from .manifests import GIT_COMMITS_ANNOTATION, RUN_API_VERSION, Task, load_manifests
from .memory import MEMORY_KEY_ANNOTATION, POPULATION_ANNOTATION, SNAPSHOT_ANNOTATION, SPLIT_ANNOTATION, memory_key
from .reconciler import _ax_name, _doc
from .tasks.repo_facts import _digest
from .tasks.verify import MAX_STEPS, units_of
__all__ = ["CaseInputs", "Loop", "fix_ranges", "increment", "read_cases", "render", "repin_templates"]
REFERENCE_USD_PER_CANDIDATE = 0.022 # two passes: verifier-batched-check-2026-09-29.json cost.usd_per_candidate_two_passes
REFERENCE_MODEL = "deepseek-flash" # verify_batched.py's default (DEFAULT_DETECTOR_MODEL) in the batched check
TRIAGE_BASIS = (
"derived per file from the batched-check scans: recorded usd (triage + hunts) minus the recorded hunt usage "
f"priced at {REFERENCE_MODEL} list prices; the triage call's own usage was not recorded"
)
RANGES_BASIS = "git diff -U0 <vulnerable> <fixed>, old side: benchmarks/independent/evaluate.hunks(case, 'old')"
HEADROOM = 1.5
CALLS_PER_HUNT = MAX_STEPS + 2 # every tool step, the answer, one empty-content retry
ATESPACE = "default"
CASE_PATH = "/workspace/case"
INPUTS_PATH = "/workspace/inputs"
FIXED_PATH = "/workspace/fixed"
PROJECT_PATH = "/workspace/project"
TEMPLATES = ("repo-facts", "verify", "agree", "remember", "alerts", "loop")
FIXED_RANGES_BASIS = "git diff -U0 <vulnerable> <fixed>, new side: the fix's lines at the fixed pin"
LOOP_MANIFEST = "benchmarks/manifests/java-spring-boot-vulnerable.toml" # the equality baseline's `improve` manifest
LOOP_CATALOG = "benchmarks/pairs/catalog.toml"
Candidate = tuple[str, str, int]
@dataclass(frozen=True)
class CaseInputs:
case: Mapping[str, Any]
candidates: tuple[Candidate, ...]
kept: tuple[Candidate, ...]
triage: Mapping[str, str]
recorded_usd: float
triage_usd: float = 0.0
ranges: Mapping[str, Sequence[tuple[int, int]]] | None = None
fixed_ranges: Mapping[str, Sequence[tuple[int, int]]] | None = None
@dataclass(frozen=True)
class Loop:
"""The improvement loop appended to a Run (``--loop``, design section 6): ``repo`` at ``commit`` is this
repository at the commit under test, where ``manifest`` and ``catalog`` (paths inside it) gate the proposals;
rows of ``populations`` never propose (the train-on-test guard)."""
repo: str
commit: str
manifest: str = LOOP_MANIFEST
catalog: str = LOOP_CATALOG
populations: tuple[str, ...] = ()
def fix_ranges(repo: Path, vulnerable: str, fixed: str, side: str = "old") -> dict[str, list[tuple[int, int]]]:
"""Changed line ranges per file on the vulnerable side of the fix: `evaluate.hunks(case, "old")`, line for
line (including its attribution of a hunk under ``--- /dev/null`` to the file before it). ``side="new"`` is
the same on the fixed side (``+++ b/`` paths, the ``+`` spans): where a fixed-pin alert sits inside the fix."""
if not (repo / ".git").exists() and not (repo / "HEAD").is_file():
raise ValueError(f"no repository at {repo}: the fix ranges cannot be computed")
diff = subprocess.run(["git", "-C", str(repo), "diff", "-U0", vulnerable, fixed], capture_output=True, text=True, check=True).stdout
ranges: dict[str, list[tuple[int, int]]] = {}
current = ""
header, group = ("--- a/", 1) if side == "old" else ("+++ b/", 3)
for line in diff.splitlines():
if line.startswith(header):
current = line[6:]
elif line.startswith("@@") and current:
match = re.search(r"-(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))?", line)
if match:
span = match.group(group + 1)
first, length = int(match.group(group)), int(span) if span is not None else 1
ranges.setdefault(current, []).append((first, first + max(length, 1) - 1))
if not ranges:
raise ValueError(f"{repo}: the diff {vulnerable[:12]}..{fixed[:12]} changed no line on the vulnerable side")
return ranges
def _triage_usd(rows: Sequence[Mapping[str, Any]]) -> float:
"""The triage share of the recorded spend: per file, usd minus the recorded hunt usage at list price."""
prices = price_of(REFERENCE_MODEL)
if prices is None:
raise ValueError(f"no recorded price for {REFERENCE_MODEL}")
return round(sum(max(float(r.get("usd") or 0.0) - cost_of(r.get("usage") or {}, prices), 0.0) for r in rows), 6)
def _scan_rows(triage_dir: Path, case_id: str) -> list[dict[str, Any]]:
path = triage_dir / f"{case_id}--vulnerable_a.jsonl"
if not path.is_file():
raise ValueError(f"{case_id}: no recorded triage at {path}")
rows = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip()]
rows = [r for r in rows if "unverified" not in r]
if not rows:
raise ValueError(f"{path}: no completed file record")
return rows
def read_cases(
validation_set: Path, candidates_dir: Path, triage_dir: Path, population: Path, repos: Path | None = None, *, fixed_side: bool = False
) -> list[CaseInputs]:
"""The set's cases in id order, each with its candidates, the triage-kept subset, the recorded cost and its
triage share, and -- with ``repos`` (one clone per case id) -- the fix's old-side ranges (and, with
``fixed_side``, its new-side ranges, which the loop's `alerts` task marks fixed-pin alerts by)."""
subset: dict[str, list[list[str]]] = json.loads(validation_set.read_text(encoding="utf-8"))
records = {c["id"]: c for c in tomllib.loads(population.read_text(encoding="utf-8"))["case"]}
cases: list[CaseInputs] = []
for case_id in sorted(subset):
if case_id not in records:
raise ValueError(f"{validation_set}: case {case_id!r} is not in {population}")
record = json.loads((candidates_dir / f"{case_id}.json").read_text(encoding="utf-8"))
unique = {(str(p), str(fn)): (str(p), str(fn), int(ln)) for p, fn, ln in sorted(record["candidates"])}
wanted = [(str(p), str(fn)) for p, fn, *_ in subset[case_id]]
missing = [f"{p}::{fn}" for p, fn in wanted if (p, fn) not in unique]
if missing:
raise ValueError(f"{case_id}: set candidates absent from {candidates_dir / case_id}.json: {missing[:5]}")
wanted_keys = set(wanted)
chosen = [c for key, c in unique.items() if key in wanted_keys]
rows = _scan_rows(triage_dir, case_id)
asked = {(str(c[0]), str(c[1])): (str(c[0]), str(c[1]), int(c[2])) for r in rows for c in r["candidates"]}
if sorted(asked.values()) != sorted(chosen):
raise ValueError(f"{case_id}: the recorded scans asked {sorted(asked.values())[:3]}..., the set gives {chosen[:3]}...")
kept_keys = {(str(r["path"]), str(fn)) for r in rows for fn in r.get("kept", [c[1] for c in r["candidates"]])}
triage = {f"{r['path']}::{fn}": str(label) for r in rows for fn, label in sorted((r.get("triage") or {}).items())}
cases.append(
CaseInputs(
case=records[case_id],
candidates=tuple(chosen),
kept=tuple(c for c in chosen if (c[0], c[1]) in kept_keys),
triage=dict(sorted(triage.items())),
recorded_usd=round(sum(float(r.get("usd") or 0.0) for r in rows), 6),
triage_usd=_triage_usd(rows),
ranges=None if repos is None else fix_ranges(repos / case_id, records[case_id]["vulnerable"], records[case_id]["fixed"]),
fixed_ranges=(
fix_ranges(repos / case_id, records[case_id]["vulnerable"], records[case_id]["fixed"], side="new")
if repos is not None and fixed_side
else None
),
)
)
return cases
def _dump(header: str, docs: Sequence[Mapping[str, Any]]) -> str:
return header + str(yaml.safe_dump_all(list(docs), sort_keys=False, width=1000))
def _json(payload: object) -> str:
return json.dumps(payload, indent=1, sort_keys=True) + "\n"
def _case_record(item: CaseInputs) -> dict[str, Any]:
"""What `agree` scores by: sites, fix ranges (absent without ``--repos``), sites in the set, reference cost."""
case = item.case
sites = [str(s) for s in case.get("sites", [])]
in_set = {f"{p}::{fn}" for p, fn, _ in item.candidates}
record: dict[str, Any] = {"id": case["id"], "family": case["family"], "sites": sites}
if item.ranges is not None:
record["ranges"] = {path: [list(span) for span in spans] for path, spans in item.ranges.items()}
record["ranges_basis"] = RANGES_BASIS
if item.fixed_ranges is not None:
record["fixed_ranges"] = {path: [list(span) for span in spans] for path, spans in item.fixed_ranges.items()}
record["fixed_ranges_basis"] = FIXED_RANGES_BASIS
record["sites_in_set"] = [s for s in sites if s in in_set]
record["cost"] = {
"candidates_before_triage": len(item.candidates), "recorded_usd": item.recorded_usd,
"recorded_triage_usd": item.triage_usd, "recorded_triage_basis": TRIAGE_BASIS,
} # fmt: skip
return record
def _workspaces(item: CaseInputs) -> tuple[dict[str, Any], dict[str, Any]]:
case = item.case
git = [{"name": "repo", "repo": case["repo"], "dir": "repo", "depth": 1}]
pinned = _doc("Workspace", _ax_name(case["id"], "vulnerable"), {"git": git}, ATESPACE)
pinned["metadata"]["annotations"] = {GIT_COMMITS_ANNOTATION: f"repo={case['vulnerable']}"}
files = {
"candidates.json": _json({"id": case["id"], "family": case["family"], "candidates": [list(c) for c in item.kept]}),
"functions.json": _json([{"path": p, "function": fn, "line": ln} for p, fn, ln in item.kept]),
"case.json": _json(_case_record(item)),
"triage.json": _json(
{"candidates": [list(c) for c in item.candidates], "kept": [list(c) for c in item.kept], "triage": dict(item.triage)}
),
}
inputs = _doc("Workspace", _ax_name(case["id"], "inputs"), {"files": [{"path": k, "content": v} for k, v in files.items()]}, ATESPACE)
return pinned, inputs
def _task(
template: Task, name: str, env: Mapping[str, str], bindings: Sequence[tuple[str, str]], command: Sequence[str] = ()
) -> dict[str, Any]:
spec: dict[str, Any] = {}
if template.image:
spec["image"] = template.image
spec["command"] = [*template.command, *command]
spec["env"] = [{"name": e.name, "value": e.value} for e in template.env] + [{"name": k, "value": v} for k, v in env.items()]
if template.resources:
kinds = {kind: values for kind, values in vars(template.resources).items() if values is not None}
spec["resources"] = {kind: {k: v for k, v in vars(values).items() if v is not None} for kind, values in kinds.items()}
spec["workspaces"] = [{"name": n, "path": p} for n, p in bindings]
if template.debug:
spec["debug"] = True
doc = _doc("Task", name, spec, template.metadata.atespace)
if template.metadata.annotations:
doc["metadata"]["annotations"] = dict(template.metadata.annotations)
return doc
def verify_budget(item: CaseInputs) -> dict[str, float | int]:
"""One verify pass: 1.5x its share of the case's recorded two-pass cost (at least the set's per-candidate
average times the case's candidates), rounded up to a cent; calls: every hunt at its step ceiling."""
share = max(item.recorded_usd, REFERENCE_USD_PER_CANDIDATE * len(item.candidates)) / 2
hunts = len(units_of(list(item.kept)))
return {"usd": math.ceil(HEADROOM * share * 100 - 1e-9) / 100, "calls": max(hunts, 1) * CALLS_PER_HUNT}
def _tiebreak_entries(item: CaseInputs) -> list[dict[str, Any]]:
"""The tie-break (Requirement 6.4): verify pass c on the candidates a and b disputed, then agree 2-of-3. Fresh
budget dicts, so YAML adds no anchor to the entries before them."""
case_id = item.case["id"]
return [
{
"name": f"{case_id}-vc", "task": f"verify-c-{case_id}", "dependsOn": [f"{case_id}-agree"],
"inputs": {"facts": f"{case_id}-facts/facts.json", "only": f"{case_id}-agree/disputed.json"},
"outputs": ["units.jsonl", "summary.json"], "budget": verify_budget(item),
},
{
"name": f"{case_id}-final", "task": f"agree-{case_id}",
"inputs": {pass_: f"{case_id}-v{pass_[-1]}/units.jsonl" for pass_ in ("pass_a", "pass_b", "pass_c")},
"outputs": ["agreed.json", "disputed.json"], "budget": {"usd": 0, "calls": 0},
},
] # fmt: skip
def _free() -> dict[str, int]:
return {"usd": 0, "calls": 0} # model-free: budget.py refuses any call, so a stray one fails loudly
def _remember(item: CaseInputs, templates: Mapping[str, Task], population: str, split: str, alerts: bool) -> tuple[dict, dict]:
"""(Task, Run entry) of the case's `remember` (design section 4): bound to the vulnerable Workspace for the
repository and pin its rows carry; every artifact of the case's chain (and, in a loop, its alerts) as input."""
case_id = item.case["id"]
env = {"OUSAST_POPULATION": population or "unknown", "OUSAST_SPLIT": split or "unknown"}
task = _task(templates["remember"], f"remember-{case_id}", env, [(_ax_name(case_id, "vulnerable"), CASE_PATH)])
inputs = {"facts": f"{case_id}-facts/facts.json", "facts_summary": f"{case_id}-facts/summary.json"}
for label in ("a", "b", "c"):
inputs.update({f"pass_{label}": f"{case_id}-v{label}/units.jsonl", f"summary_{label}": f"{case_id}-v{label}/summary.json"})
inputs["agreed"] = f"{case_id}-final/agreed.json"
if alerts:
inputs["alerts"] = f"{case_id}-alerts/alerts.jsonl"
inputs["alerts_summary"] = f"{case_id}-alerts/summary.json" # the coverage: whether a zero could mean clean
entry = {"name": f"{case_id}-remember", "task": f"remember-{case_id}", "inputs": inputs, "outputs": ["memory.jsonl"], "budget": _free()}
return task, entry
def _alerts(item: CaseInputs, templates: Mapping[str, Task]) -> tuple[dict, dict, dict]:
"""(fixed-pin Workspace, Task, Run entry) of the case's `alerts`: quick-mode rules on both pins."""
case = item.case
git = [{"name": "repo", "repo": case["repo"], "dir": "repo", "depth": 1}]
fixed = _doc("Workspace", _ax_name(case["id"], "fixed"), {"git": git}, ATESPACE)
fixed["metadata"]["annotations"] = {GIT_COMMITS_ANNOTATION: f"repo={case['fixed']}"}
bindings = [(_ax_name(case["id"], "vulnerable"), CASE_PATH), (fixed["metadata"]["name"], FIXED_PATH)]
bindings.append((_ax_name(case["id"], "inputs"), INPUTS_PATH))
env = {
"OUSAST_WORKSPACE_DIR": f"{CASE_PATH}/repo", "OUSAST_FIXED_DIR": f"{FIXED_PATH}/repo",
"OUSAST_INPUT_CASE": f"{INPUTS_PATH}/case.json", "OUSAST_VULNERABLE_PIN": str(case["vulnerable"]),
"OUSAST_FIXED_PIN": str(case["fixed"]),
} # fmt: skip
task = _task(templates["alerts"], f"alerts-{case['id']}", env, bindings)
entry = {"name": f"{case['id']}-alerts", "task": f"alerts-{case['id']}", "outputs": ["alerts.jsonl", "summary.json"], "budget": _free()}
entry["serialize"] = "engine"
return fixed, task, entry
def _loop(cases: Sequence[CaseInputs], templates: Mapping[str, Task], loop: Loop) -> tuple[dict, list[dict], list[dict]]:
"""(project Workspace, Tasks, Run entries) of the loop's singletons: snapshot -> measure -> propose -> improve."""
git = [{"name": "repo", "repo": loop.repo, "dir": "repo", "depth": 1}]
project = _doc("Workspace", _ax_name("openultrasast", loop.commit[:12]), {"git": git}, ATESPACE)
project["metadata"]["annotations"] = {GIT_COMMITS_ANNOTATION: f"repo={loop.commit}"}
bound = [(project["metadata"]["name"], PROJECT_PATH)]
env = {
"OUSAST_PROJECT_DIR": f"{PROJECT_PATH}/repo", "OUSAST_LOOP_MANIFEST": loop.manifest, "OUSAST_LOOP_CATALOG": loop.catalog,
"OUSAST_QUALIFY_POPULATIONS": json.dumps(sorted(loop.populations)),
} # fmt: skip
snapshot = _task(templates["loop"], "memory-snapshot", {}, [], ["snapshot"])
guard = {"catalog": loop.catalog, "manifest": loop.manifest, "populations": sorted(loop.populations)}
snapshot["metadata"].setdefault("annotations", {})[SNAPSHOT_ANNOTATION] = json.dumps(guard, sort_keys=True, separators=(",", ":"))
tasks = [
snapshot, _task(templates["loop"], "loop-measure", {}, [], ["measure"]),
_task(templates["loop"], "loop-propose", env, bound, ["propose"]),
_task(templates["loop"], "loop-improve", env, bound, ["improve"]),
] # fmt: skip
remembered = {f"remember_{c.case['id']}": f"{c.case['id']}-remember/memory.jsonl" for c in cases}
entries = [
{"name": "memory-snapshot", "task": "memory-snapshot", "outputs": ["rows.jsonl", "index.json"], "budget": _free()},
{"name": "loop-measure", "task": "loop-measure", "inputs": {**remembered, "snapshot": "memory-snapshot/rows.jsonl"},
"outputs": ["measure.json", "rows.jsonl"], "budget": _free()},
{"name": "loop-propose", "task": "loop-propose",
"inputs": {"measure": "loop-measure/measure.json", "rows": "loop-measure/rows.jsonl", "index": "memory-snapshot/index.json"},
"outputs": ["proposals.jsonl", "memory_proposals.jsonl", "signals.json"], "budget": _free()},
{"name": "loop-improve", "task": "loop-improve", "inputs": {"proposals": "loop-propose/proposals.jsonl"},
"outputs": ["gate.json", "journal.json"], "budget": _free(), "serialize": "engine"},
] # fmt: skip
return project, tasks, entries
def render(
cases: Sequence[CaseInputs],
templates: Mapping[str, Task],
run_name: str,
command: str,
*,
population: str = "",
split: str = "",
loop: Loop | None = None,
) -> dict[str, str]:
"""Relative path under the plane root -> file text, for every generated manifest. ``population`` and ``split``
(the population file's and the set's stems) become the Run's annotations, which the memory rows carry. Each
case ends in `remember`; ``loop`` adds per case `alerts` (and the fixed-pin Workspace), and once the
snapshot, `loop-measure`, `loop-propose` and `loop-improve` (design section 6), all model-free."""
header = f"# generated by `{command}`; do not edit, regenerate.\n"
out: dict[str, str] = {}
tasks: list[dict[str, Any]] = []
entries: list[dict[str, Any]] = []
tiebreak: list[dict[str, Any]] = []
for item in cases:
case_id = item.case["id"]
pinned, inputs = _workspaces(item)
for doc in (pinned, inputs):
out[f"workspaces/{doc['metadata']['name']}.yaml"] = _dump(header, [doc])
repo = [(pinned["metadata"]["name"], CASE_PATH), (inputs["metadata"]["name"], INPUTS_PATH)]
on_repo = {"OUSAST_WORKSPACE_DIR": f"{CASE_PATH}/repo"}
facts_env = {**on_repo, "OUSAST_INPUT_CANDIDATES": f"{INPUTS_PATH}/functions.json"}
facts_task = _task(templates["repo-facts"], f"repo-facts-{case_id}", facts_env, repo)
digest = _digest(sorted({fn for _, fn, _ in item.kept}))
key = memory_key(str(item.case["repo"]), str(item.case["vulnerable"]), digest, templates["repo-facts"].image or "")
facts_task["metadata"].setdefault("annotations", {})[MEMORY_KEY_ANNOTATION] = key
tasks.append(facts_task)
for label in ("a", "b"):
env = {**on_repo, "OUSAST_INPUT_CANDIDATES": f"{INPUTS_PATH}/candidates.json", "OUSAST_PASS": label}
tasks.append(_task(templates["verify"], f"verify-{label}-{case_id}", env, repo))
agree_env = {
"OUSAST_INPUT_CANDIDATES": f"{INPUTS_PATH}/candidates.json",
"OUSAST_INPUT_SITES": f"{INPUTS_PATH}/case.json",
"OUSAST_CASE_ID": case_id,
}
tasks.append(_task(templates["agree"], f"agree-{case_id}", agree_env, repo[1:]))
free = {"usd": 0, "calls": 0}
facts = {"name": f"{case_id}-facts", "task": f"repo-facts-{case_id}", "outputs": ["facts.json"], "budget": free}
entries.append({**facts, "serialize": "repo-facts"})
for label in ("a", "b"):
entries.append({
"name": f"{case_id}-v{label}", "task": f"verify-{label}-{case_id}", "inputs": {"facts": f"{case_id}-facts/facts.json"},
"outputs": ["units.jsonl", "summary.json"], "budget": verify_budget(item),
}) # fmt: skip
entries.append({
"name": f"{case_id}-agree", "task": f"agree-{case_id}",
"inputs": {"pass_a": f"{case_id}-va/units.jsonl", "pass_b": f"{case_id}-vb/units.jsonl"},
"outputs": ["agreed.json", "disputed.json"], "budget": free,
}) # fmt: skip
env = {**on_repo, "OUSAST_INPUT_CANDIDATES": f"{INPUTS_PATH}/candidates.json", "OUSAST_PASS": "c"}
tasks.append(_task(templates["verify"], f"verify-c-{case_id}", env, repo))
tiebreak.extend(_tiebreak_entries(item))
tasks.sort(key=lambda doc: 1 if doc["metadata"]["name"].startswith("verify-c-") else 0) # stable: the old Tasks first
entries.extend(tiebreak) # appended: the first Run's entries stay byte-identical, so a rerun keeps their state
for item in cases: # appended after the tie-break for the same reason
if loop is not None:
fixed, alerts_task, alerts_entry = _alerts(item, templates)
out[f"workspaces/{fixed['metadata']['name']}.yaml"] = _dump(header, [fixed])
tasks.append(alerts_task)
entries.append(alerts_entry)
remember_task, remember_entry = _remember(item, templates, population, split, loop is not None)
tasks.append(remember_task)
entries.append(remember_entry)
if loop is not None:
project, loop_tasks, loop_entries = _loop(cases, templates, loop)
out[f"workspaces/{project['metadata']['name']}.yaml"] = _dump(header, [project])
tasks.extend(loop_tasks)
entries.extend(loop_entries)
out[f"tasks/{run_name}.yaml"] = _dump(header, tasks)
annotations = {k: v for k, v in ((POPULATION_ANNOTATION, population), (SPLIT_ANNOTATION, split)) if v}
metadata: dict[str, Any] = {"name": run_name, **({"annotations": annotations} if annotations else {})}
run = {"apiVersion": RUN_API_VERSION, "kind": "Run", "metadata": metadata, "spec": {"tasks": entries}}
total = sum(float(e["budget"]["usd"]) for e in entries)
candidates = sum(len(c.candidates) for c in cases)
kept = sum(len(c.kept) for c in cases)
note = f"# {len(cases)} cases, {candidates} candidates ({kept} kept by the recorded triage); usd ceiling {total:.2f}.\n"
out[f"runs/{run_name}.yaml"] = _dump(header + note, [run])
return out
_IMAGE_LINE = re.compile(r'^(\s*image:\s*)"?[^"\s]+"?\s*$', re.MULTILINE)
def repin_templates(plane: Path, image_file: Path) -> str:
"""Set every template's ``image:`` to the digest-pinned reference in ``image_file`` (as ``ops/ax/up.sh``
writes it); comments and layout are kept. Returns the reference."""
image = image_file.read_text(encoding="utf-8").strip()
if not re.fullmatch(r"\S+@sha256:[0-9a-f]{64}", image):
raise ValueError(f"{image_file}: expected a digest-pinned image reference, got {image!r}")
for name in TEMPLATES:
path = plane / "tasks" / f"{name}.yaml"
text = path.read_text(encoding="utf-8")
pinned, count = _IMAGE_LINE.subn(lambda m: f'{m.group(1)}"{image}"', text)
if count != 1:
raise ValueError(f"{path}: {count} image lines, expected one")
path.write_text(pinned, encoding="utf-8")
return image
def increment(
population: Path,
validation_set: Path,
candidates_dir: Path,
triage_dir: Path,
*,
plane: Path,
run_name: str,
command: str,
repos: Path | None = None,
runner_image: Path | None = None,
loop: Loop | None = None,
) -> list[Path]:
"""Write the generated manifests under ``plane`` from the templates in ``plane/tasks`` (re-pinned first when
``runner_image`` is given), with the improvement loop when ``loop`` is given; returns the paths."""
if loop is not None and repos is None:
raise ValueError("--loop needs --repos: the alerts task marks fixed-pin alerts by the fix's new-side ranges")
if runner_image is not None:
repin_templates(plane, runner_image)
loaded = load_manifests([plane / "tasks" / f"{name}.yaml" for name in TEMPLATES])
cases = read_cases(validation_set, candidates_dir, triage_dir, population, repos, fixed_side=loop is not None)
files = render(cases, loaded.tasks, run_name, command, population=population.stem, split=validation_set.stem, loop=loop)
written: list[Path] = []
for relative, text in files.items():
path = plane / relative
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
written.append(path)
return written