merge(learn): harvest signals for the decision engine (decision-engine task 5)

Verify a/b + agree on 150 labelled units ($2.54 attributed), model roles on 799
files ($1.40), engine features for 682 of 686 pair sides; 1,712 feature records.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
Marius Ciepluch
2026-10-01 19:13:20 +02:00
co-authored by Claude Opus 5.5
21 changed files with 1738 additions and 137 deletions
+4 -1
View File
@@ -31,10 +31,13 @@ a model or embedding call.
commits (separate, never merged); a first label snapshot committed as counts only.
- _Requirements: 2.1, 2.2, 2.3, 7.1_
- [ ] 5. Harvest (in progress)
- [x] 5. Harvest (needs budget go-ahead)
- A plane Run of verify a/b on the verify-family pairs and development cases (~$6, ceiling $10), model roles on
the candidate files (ceiling $10), engine features for the pair corpus on the host (frozen source, one
container at a time, background).
- Done 2026-10-01 (`benchmarks/measurements/2026-10-01-decision-engine-harvest/record.json`): verify $2.54 and roles
$1.40 by attribution. The engine job finished the same day (682 of 686 pair sides; the 2 family-unknown pairs
not run) and the feature records were rebuilt; dev-php pins have no engine run (`missing`).
- _Requirements: 1.1_
- [ ] 6. Program, local memory and compilation (design sections 4-5)
+363
View File
@@ -0,0 +1,363 @@
"""Feature records for every labelled pin unit, from the harvested signals (learned-decision-engine task 5, step 3).
Host only. For each labelled pin unit of a label snapshot (``ousast learn labels --out``; conditional rows excluded),
one ``static`` and one ``plane`` record per labelled (candidate, family), built with :mod:`openultrasast.learn.features`
from:
- quick rules and deterministic roles, run here on the unit's tree: a pair side materialised from its excerpt, a
population or recipe pin exported from its clone or checkout (one at a time, deleted after, disk-guarded);
- the engine: ``benchmarks/learn/engine_pairs.py`` records for pairs, the recorded protocol scans
(``~/ousast-results/independent-v{1,2}/scans/<case>--<pin role>.json``) for populations;
- verify passes a/b and ``agree`` from the harvest Run (``harvest-verify``) and, for population v2, the recorded
``validation-46`` Run; model roles from ``harvest-roles``;
- callers and entry-point distance on a repository (``not_applicable`` on a pair excerpt: no repository exists).
An instrument that has not run for a unit yet (the engine job still running, a Run task not done) is recorded as
``failed`` with version ``missing:not-run`` -- null features, never zeros -- and counted as *missing*. Rows go to the
memory store (``OUSAST_MEMORY``, default ``~/ousast-results/plane/memory``) as kind ``features``; a rerun replaces
them by id. ``--counts`` writes the counts-only coverage record (no candidate, path or repository name).
Usage::
python benchmarks/learn/build_features.py --labels labels-<sha>.jsonl --units <harvest plane>/units.json --counts counts.json
"""
from __future__ import annotations
import argparse
import json
import shutil
import subprocess
import tempfile
from collections import Counter, defaultdict
from collections.abc import Mapping
from pathlib import Path
from typing import Any
from openultrasast.findings import quick_scan_findings
from openultrasast.learn.features import (
NONE,
NOT_APPLICABLE,
Part,
SourceIndex,
Vocabulary,
_callers_of,
_signals,
agree_part,
engine_part,
entry_distance,
entry_names,
facts_part,
function_of,
model_sinks_part,
quick_part,
record,
roles_part,
ruleset_digest,
source_part,
verify_part,
)
from openultrasast.learn.roles import RoleSet, from_model_roles, infer_for_checkout
from openultrasast.pairs import DEFAULT_CATALOG, _materialize_side, _targets, load_pair_catalog
from openultrasast.plane import memory
from openultrasast.plane.tasks.alerts import covers
from openultrasast.plane.tasks.alerts import engine_languages as _engine_languages
from openultrasast.plane.tasks.alerts import quick_languages as _quick_languages
from openultrasast.plane.tasks.repo_facts import GLOBAL
from openultrasast.preprocess import detect_language
from openultrasast.rank import rank_targets
from openultrasast.ruleset import DEFAULT_RULESET_DIR, load_ruleset
RESULTS = Path.home() / "ousast-results"
MISSING = Part("failed", version="missing:not-run")
CONDITIONAL_SOURCES = ("benign_control", "assumed_benign")
PASSES = ("a", "b", "c")
QUICK_LANGUAGES = _quick_languages()
ENGINE_LANGUAGES = _engine_languages()
def jsonl(path: Path) -> list[dict[str, Any]]:
return [json.loads(x) for x in path.read_text(encoding="utf-8").splitlines() if x.strip()] if path.is_file() else []
def read_json(path: Path) -> Any:
return json.loads(path.read_text(encoding="utf-8")) if path.is_file() else None
def done(run: Path, entry: str) -> bool:
state = (read_json(run / "state.json") or {}).get("tasks", {})
return state.get(entry, {}).get("status") == "done"
class Signals:
"""The harvested model signals: verify/agree per unit name, model roles per unit name."""
def __init__(self, verify_run: Path, roles_run: Path, v46: Path) -> None:
self.verify_run, self.roles_run, self.v46 = verify_run, roles_run, v46
self.roles: dict[str, tuple[dict[str, Any], set[str]]] = {}
state = (read_json(roles_run / "state.json") or {}).get("tasks", {})
for entry, info in state.items():
if info.get("status") != "done":
continue
payload = read_json(roles_run / entry / "roles.json") or {}
classified = {str(r["path"]) for r in jsonl(roles_run / entry / "units.jsonl") if "error" not in r}
self._index_roles(entry.removesuffix("-roles"), payload, classified)
def _index_roles(self, owner: str, payload: Mapping[str, Any], classified: set[str]) -> None:
grouped = any("/" in p and p.split("/", 1)[0].startswith("h-") for p in classified)
if not grouped: # a pin's Task: the paths are the repository's
self.roles[owner] = (dict(payload), classified)
return
for unit in {p.split("/", 1)[0] for p in classified}:
prefix = unit + "/"
def strip(paths: Any, prefix: str = prefix) -> list[str]:
return [p.removeprefix(prefix) for p in paths if str(p).startswith(prefix)]
rows = [
r | {"path": r["path"].removeprefix(prefix)}
for r in payload.get("roles") or ()
if str(r.get("path", "")).startswith(prefix)
]
unit_payload = {
"model": payload.get("model"),
"roles": rows,
"unclassified_paths": strip(payload.get("unclassified_paths") or ()),
}
self.roles[unit] = (unit_payload, set(strip(classified)))
def passes(self, base: Path, entry_stem: str) -> tuple[dict[str, list], dict[str, str | None], Any] | None:
if not (done(base, f"{entry_stem}-va") and done(base, f"{entry_stem}-vb")):
return None
passes = {p: jsonl(base / f"{entry_stem}-v{p}" / "units.jsonl") for p in PASSES}
models = {p: (read_json(base / f"{entry_stem}-v{p}" / "summary.json") or {}).get("model") for p in PASSES}
final = base / f"{entry_stem}-final" / "agreed.json"
agreed = read_json(final if final.is_file() else base / f"{entry_stem}-agree" / "agreed.json")
return passes, models, agreed
def final_of(agreed: Any, candidate: str) -> str | None:
if not agreed:
return None
disputed = {str(d["candidate"]) for d in agreed.get("disputed") or []}
for row in agreed.get("candidates") or []:
if str(row["candidate"]) == candidate:
return "agreed" if row.get("agreed") else "disputed" if candidate in disputed else "rejected"
return None
def plane_parts(signals: Signals, stems: list[tuple[Path, str]], roles_owner: str | None, candidate: str, line: int,
path: str, function: str,
verify_expected: bool) -> dict[str, Part]: # fmt: skip
"""verify, agree, model_sinks of one candidate: ``stems`` are (run dir, entry stem) that may have asked it."""
parts: dict[str, Part] = {"verify": NONE, "agree": NONE}
pending = False
for base, stem in stems:
got = signals.passes(base, stem)
if got is None:
pending = pending or (base == signals.verify_run and verify_expected)
continue
passes, models, agreed = got
part = verify_part(passes, candidate, line, "+".join(sorted({m for m in models.values() if m})) or None)
if part.state == "ran":
parts = {"verify": part, "agree": agree_part(final_of(agreed, candidate))}
break
else:
if pending:
parts = {"verify": MISSING, "agree": MISSING}
if roles_owner is None or roles_owner not in signals.roles:
parts["model_sinks"] = MISSING
else:
payload, classified = signals.roles[roles_owner]
if path not in classified:
parts["model_sinks"] = MISSING
else:
parts["model_sinks"] = model_sinks_part(from_model_roles(payload), path, function, str(payload.get("model") or "") or None)
return parts
def static_parts(root: Path, index: SourceIndex, quick: Mapping, engine: Mapping, engine_state: Part | None, engine_result: Mapping | None,
inferred: RoleSet, rules: Mapping, quick_version: str, path: str, function: str, family: str, repository: bool,
entries: tuple[set[str], set[str]] | None) -> dict[str, Part]: # fmt: skip
language = detect_language(Path(path)) or "other"
key = (path, function, family)
parts: dict[str, Part] = {}
parts["quick"] = quick_part(quick.get(key, []), rules, quick_version) if covers(QUICK_LANGUAGES, language) else NONE
if language not in ENGINE_LANGUAGES:
parts["engine"] = NONE
elif engine_state is not None:
parts["engine"] = engine_state
else:
result = engine_result or {}
questions, completed = int(result.get("questions") or 0), int(result.get("completed") or 0)
parts["engine"] = engine_part(
engine.get(key, []), completion=completed / questions if questions else None, degraded=bool(result.get("degradations")),
vocabulary=Vocabulary.load(language), version=str(result.get("image") or "recorded"),
) # fmt: skip
lines = index.lines(path)
parts["source"] = source_part(lines, language, function)
parts["roles"] = roles_part(function_of(lines, path, language, function), inferred, language)
parts["delta"] = NOT_APPLICABLE
if repository and entries is not None:
distance = 0 if function == GLOBAL and path in entries[1] else entry_distance(function, entries[0], index.callers())
parts["entry_points"] = Part("ran", {"facts.entry_distance": distance})
parts["facts"] = facts_part(_callers_of(index, index.callers(), path, function), function, "host")
else:
parts["entry_points"] = NOT_APPLICABLE
parts["facts"] = NOT_APPLICABLE
return parts
def engine_for(result: Mapping[str, Any] | None) -> Part | None:
"""None when the engine ran (its findings feed engine_part); else the state Part."""
if result is None:
return MISSING
state = result.get("state", "ran" if result.get("questions") else "failed")
if state == "ran" and int(result.get("questions") or 0) > 0:
return None
if state == "none":
return NONE
return Part("failed", version=str(result.get("image") or "recorded"))
def export(clone: Path, pin: str, target: Path) -> bool:
target.mkdir(parents=True)
archive = target.parent / "pin.tar"
with archive.open("wb") as stream:
ok = subprocess.run(["git", "-C", str(clone), "archive", pin], stdout=stream, check=False).returncode == 0
ok = ok and subprocess.run(["tar", "-xf", str(archive), "-C", str(target)], check=False).returncode == 0
archive.unlink(missing_ok=True)
return ok and any(target.iterdir())
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--labels", type=Path, required=True)
parser.add_argument("--units", type=Path, required=True, help="units.json of the harvest plane")
parser.add_argument("--catalog", type=Path, default=DEFAULT_CATALOG)
parser.add_argument("--cache", type=Path, default=Path.home() / ".cache" / "openultrasast")
parser.add_argument("--engine", type=Path, default=RESULTS / "plane" / "harvest-engine")
parser.add_argument("--verify-run", type=Path, default=RESULTS / "plane" / "harvest-verify")
parser.add_argument("--roles-run", type=Path, default=RESULTS / "plane" / "harvest-roles")
parser.add_argument("--v46", type=Path, default=RESULTS / "plane" / "validation-46")
parser.add_argument("--scratch", type=Path, required=True)
parser.add_argument("--min-free-gb", type=float, default=1.2)
parser.add_argument("--counts", type=Path, required=True)
parser.add_argument("--store", help="OUSAST_MEMORY spec (default: the environment's)")
parser.add_argument("--limit", type=int, default=0, help="stop after this many targets (a test)")
parser.add_argument("--kinds", default="pairs,pin", help="which targets: pairs, pin (population and recipe pins)")
args = parser.parse_args()
labels = [r for r in jsonl(args.labels) if r["unit"] == "pin" and r["source"] not in CONDITIONAL_SOURCES and not r.get("conditional")]
units = json.loads(args.units.read_text(encoding="utf-8"))
by_pair = {(u["ref"], u["side"]): n for n, u in units.items() if u["source"] == "pairs"}
by_pin: dict[tuple[str, str], list[str]] = defaultdict(list)
for n, u in units.items():
if u["source"] != "pairs":
by_pin[(u["repo"], u["pin"])].append(n)
targets: dict[tuple[str, str, str], list[dict[str, Any]]] = defaultdict(list)
for row in labels:
key = ("pairs", row["source_ref"], row["pin_role"]) if row["source"] == "pairs" else ("pin", row["repo"], row["pin"])
targets[key].append(row)
cases = {c.name: c for c in load_pair_catalog(args.catalog)}
loaded = load_ruleset(DEFAULT_RULESET_DIR)
rules = {r.rule_id: r for r in loaded}
quick_version = ruleset_digest(loaded)
signals = Signals(args.verify_run, args.roles_run, args.v46)
store = memory.open_store(args.store)
tally: Counter[tuple[str, ...]] = Counter()
coverage: dict[str, Counter[str]] = defaultdict(Counter)
skipped: Counter[str] = Counter()
args.scratch.mkdir(parents=True, exist_ok=True)
chosen = [(k, r) for k, r in sorted(targets.items()) if k[0] in args.kinds.split(",")]
for number, ((kind, ref, role_or_pin), rows) in enumerate(chosen):
if args.limit and number >= args.limit:
break
with tempfile.TemporaryDirectory(dir=args.scratch) as tmp:
root = Path(tmp) / "tree"
if kind == "pairs":
name = by_pair.get((ref, role_or_pin))
if name is None or ref not in cases:
skipped["pair side not in the harvest units"] += len(rows)
continue
unit = units[name]
_materialize_side(root, cases[ref], side="vuln" if role_or_pin == "vulnerable" else "fixed")
engine_result = read_json(args.engine / f"{ref}--{'vuln' if role_or_pin == 'vulnerable' else 'fixed'}.json")
stems = [(args.verify_run, name)]
owner, repository, repo, pin = name, False, unit["repo"], unit["pin"]
else:
names = by_pin.get((ref, role_or_pin), [])
if not names:
skipped["pin not in the harvest units"] += len(rows)
continue
unit = units[names[0]]
repo, pin, repository = ref, role_or_pin, True
if shutil.disk_usage(args.scratch).free / 1e9 < args.min_free_gb:
skipped["disk guard: pin not exported"] += len(rows)
continue
if unit["source"] == "dev-php":
source_dir = args.cache / "repos" / unit["ref"].split(":")[0] / pin[:12]
shutil.copytree(source_dir, root, symlinks=True, ignore=shutil.ignore_patterns(".git"))
engine_result = None
else:
case_id = unit["ref"]
if not export(args.cache / "independent" / case_id, pin, root):
skipped["pin export failed"] += len(rows)
continue
split = {r["split"] for r in rows} | {unit["source"]}
population = "independent-v1" if "population-v1" in split else "independent-v2"
engine_result = read_json(RESULTS / population / "scans" / f"{case_id}--{rows[0]['pin_role']}.json")
stems = [(args.verify_run, n) for n in names] + [(args.v46, unit["ref"])]
owner = next(
(n for n in names if f"{n}-roles" in {e for e in (read_json(args.roles_run / "state.json") or {}).get("tasks", {})}),
names[0],
)
index = SourceIndex(root)
targets_ = _targets(root)
findings = quick_scan_findings(root, targets_, rank_targets(targets_), None)
engine_state = engine_for(engine_result)
quick, engine = _signals(index, findings, engine_result if engine_state is None else None, rules)
inferred = infer_for_checkout(root)
entries = entry_names(root, {r["candidate"].partition("::")[0] for r in rows}) if repository else None
out_rows: list[dict[str, Any]] = []
seen: set[tuple[str, str]] = set()
for row in rows:
candidate, family = row["candidate"], row["family"]
if (candidate, family) in seen:
continue
seen.add((candidate, family))
path, _, function = candidate.partition("::")
cand_line = next((c[2] for n in ([owner] if kind == "pairs" else by_pin[(repo, pin)]) for c in units[n]["candidates"]
if f"{c[0]}::{c[1]}" == candidate), None) # fmt: skip
parts = static_parts(root, index, quick, engine, engine_state, engine_result, inferred, rules, quick_version, path,
function, family, repository, entries) # fmt: skip
language = detect_language(Path(path)) or "other"
verify_expected = any(units[n]["family"] == family for n in ([owner] if kind == "pairs" else by_pin[(repo, pin)]))
plane = {**parts, **plane_parts(signals, stems, owner, candidate, cand_line or 1, path, function, verify_expected)}
for profile, use in (("static", parts), ("plane", plane)):
rec = record(candidate, family, language, profile, use)
out_rows.append({
"id": memory.row_id("features", "harvest", owner, f"{candidate}:{family}:{profile}"), "kind": "features",
"repo": memory.repo_key(repo), "pin": pin, "run": "harvest", "task": owner, "population": row["source"],
"split": row["split"], "image": str((engine_result or {}).get("image") or "none"), **rec,
}) # fmt: skip
tally[(profile, family)] += 1
if profile == "plane":
for inst, info in rec["instruments"].items():
state = "missing" if info.get("version") == "missing:not-run" else info["state"]
coverage[f"{family}:{inst}"][state] += 1
store.ingest_rows("harvest", f"features:{owner}", out_rows)
print(json.dumps({"target": owner, "rows": len(out_rows)}), flush=True)
counts = {
"records": {f"{p}:{f}": n for (p, f), n in sorted(tally.items())},
"instrument_states_plane": {k: dict(sorted(v.items())) for k, v in sorted(coverage.items())},
"skipped_label_rows": dict(skipped),
"store": store.describe(),
}
args.counts.write_text(json.dumps(counts, indent=1, sort_keys=True) + "\n", encoding="utf-8")
print(json.dumps({"records": sum(tally.values()), "skipped": dict(skipped)}))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+181
View File
@@ -0,0 +1,181 @@
"""Engine signals for the labelled pair corpus, on the host (learned-decision-engine task 5, design "Cost on the 7 GB host").
Per labelled pair (``ousast learn labels`` rows of source ``pairs``) and side (``vuln``, ``fixed``): the side's
excerpt -- with the documents that register it -- is materialised into a temporary directory, and the Joern engine
runs on it in ``openultrasast:dev`` (``benchmarks/push/finding_dump.py`` from a FROZEN source export, ``--network
none --memory 3g``, the pair's labelled families, ONE container at a time). The result is one JSON per side under
``--out`` (``<pair>--<side>.json``), keyed by the side's content sha256 and the engine image id; a rerun skips sides
already recorded, so the job resumes where it stopped.
The instrument must have read its input (:func:`openultrasast.plane.engine_alerts.read_record`): no result file, no
``read N files`` line or ``questions == 0`` is recorded as ``state: failed`` with the reason (retried once), never as
zero findings. A side whose language the engine has no model for is ``state: none`` and is not run.
Guards: free disk is checked before every side and every 60 s while a container runs; below ``--min-free-gb`` the
container is killed by its name, the side is left unrecorded (``missing``) and the job stops. A file ``STOP`` in
``--out`` stops the job cleanly between sides. Progress lines go to ``--out/progress.jsonl``.
Usage::
python benchmarks/learn/engine_pairs.py --labels labels-<sha>.jsonl --source <frozen export> --out <dir>
"""
from __future__ import annotations
import argparse
import hashlib
import json
import re
import shutil
import subprocess
import sys
import tempfile
import threading
from pathlib import Path
from openultrasast.pairs import DEFAULT_CATALOG, _materialize_side, load_pair_catalog
from openultrasast.plane.engine_alerts import EngineAlertsError, docker_command, frozen_source, read_record
from openultrasast.plane.tasks.alerts import engine_languages
from openultrasast.preprocess import detect_language
SIDES = ("vuln", "fixed")
_OFFERED = re.compile(r"^read (\d+) files, \d+ bytes; (\d+) regions offer", re.MULTILINE)
def free_gb(path: Path) -> float:
return shutil.disk_usage(path).free / 1e9
def units(labels: Path, catalog: Path) -> list[tuple[object, str, tuple[str, ...]]]:
"""(pair case, side, families) for every labelled pair, in name order."""
families: dict[str, set[str]] = {}
for line in labels.read_text(encoding="utf-8").splitlines():
row = json.loads(line)
if row["source"] == "pairs" and row["unit"] == "pin" and row["family"] != "unknown":
families.setdefault(row["source_ref"], set()).add(row["family"])
cases = {case.name: case for case in load_pair_catalog(catalog)}
return [(cases[name], side, tuple(sorted(families[name]))) for name in sorted(families) for side in SIDES]
def image_id(image: str) -> str:
done = subprocess.run(["docker", "image", "inspect", "--format", "{{.Id}}", image], capture_output=True, text=True, check=False)
if done.returncode != 0 or not done.stdout.strip():
raise SystemExit(f"no engine image {image}")
return done.stdout.strip()
def run_side(
command: list[str], name: str, timeout: float, out: Path, min_free: float
) -> tuple[subprocess.CompletedProcess[str] | None, str]:
"""Run one container; a watchdog kills it by name when free disk drops below ``min_free`` GB."""
tripped: list[str] = []
done = threading.Event()
def watch() -> None:
while not done.wait(60.0):
if free_gb(out) < min_free:
tripped.append(f"free disk {free_gb(out):.2f} GB < {min_free} GB")
subprocess.run(["docker", "kill", name], capture_output=True, check=False)
return
watcher = threading.Thread(target=watch, daemon=True)
watcher.start()
try:
completed = subprocess.run(command, capture_output=True, text=True, timeout=timeout, check=False)
except subprocess.TimeoutExpired:
subprocess.run(["docker", "kill", name], capture_output=True, check=False)
completed = None
done.set()
return completed, (tripped[0] if tripped else "")
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--labels", type=Path, required=True)
parser.add_argument("--catalog", type=Path, default=DEFAULT_CATALOG)
parser.add_argument("--source", type=Path, required=True, help="frozen export: git archive HEAD src benchmarks/push/finding_dump.py")
parser.add_argument("--out", type=Path, required=True)
parser.add_argument("--image", default="openultrasast:dev")
parser.add_argument("--deadline", type=float, default=600.0)
parser.add_argument("--min-free-gb", type=float, default=1.0)
parser.add_argument("--limit", type=int, default=0, help="stop after this many sides were run (0: all)")
args = parser.parse_args()
source = frozen_source(args.source)
args.out.mkdir(parents=True, exist_ok=True)
image = image_id(args.image)
covered = engine_languages()
progress = (args.out / "progress.jsonl").open("a", encoding="utf-8")
ran = 0
for case, side, families in units(args.labels, args.catalog):
result_path = args.out / f"{case.name}--{side}.json"
if result_path.is_file():
continue
if (args.out / "STOP").exists() or (args.limit and ran >= args.limit):
print("stopped between sides", flush=True)
return 0
if free_gb(args.out) < args.min_free_gb:
print(f"disk guard: {free_gb(args.out):.2f} GB free; stopping", flush=True)
return 4
excerpt = case.vuln_file if side == "vuln" else case.fixed_file
if not excerpt.is_file():
entry = {"state": "failed", "reason": "excerpt not materialised"}
else:
entry = {"sha256": hashlib.sha256(excerpt.read_bytes()).hexdigest()}
language = detect_language(Path(case.relpath)) or "other"
if language not in covered:
entry.update(state="none", reason=f"no engine model for {language}")
else:
entry.update(_scan(case, side, families, source, args, image))
ran += 1
if entry.get("state") == "missing":
print(json.dumps({"pair": case.name, "side": side, **entry}), flush=True)
return 4
record = {"pair": case.name, "side": side, "families": list(families), "image": image, **entry}
result_path.write_text(json.dumps(record, indent=1, sort_keys=True) + "\n", encoding="utf-8")
line = {k: record.get(k) for k in ("pair", "side", "state", "questions", "completed", "seconds", "reason")}
line["findings"] = len(record.get("findings") or [])
progress.write(json.dumps(line) + "\n")
progress.flush()
print(json.dumps(line), flush=True)
return 0
def _scan(case: object, side: str, families: tuple[str, ...], source: Path, args: argparse.Namespace, image: str) -> dict:
reason = ""
for attempt in (1, 2):
with tempfile.TemporaryDirectory(prefix="ep-", dir=args.out) as tmp:
checkout = _materialize_side(Path(tmp) / "case", case, side=side)
out_dir = Path(tmp) / "out"
out_dir.mkdir()
name = f"ousast-ep-{hashlib.sha1(f'{case.name}{side}'.encode()).hexdigest()[:12]}"
command = docker_command(source, checkout, out_dir, "result", ",".join(families), args.deadline, args.image)
command[2:2] = ["--name", name]
completed, tripped = run_side(command, name, args.deadline + 300, args.out, args.min_free_gb)
if tripped:
return {"state": "missing", "reason": tripped}
if completed is None:
reason = f"timeout after {args.deadline + 300:.0f}s"
continue
log = (completed.stdout or "") + "\n" + (completed.stderr or "")
try:
record = read_record(out_dir / "result.json", log, completed.returncode)
except EngineAlertsError as exc:
reason = f"attempt {attempt}: {exc}"[:600]
offered = _OFFERED.search(log)
result = out_dir / "result.json"
if offered is not None and int(offered.group(1)) > 0 and result.is_file():
# It read its input and finished without asking: deterministic (a retry asks nothing either). Still
# `failed` by the design's rule (questions == 0 is never zero findings); the reason keeps it apart
# from a launch failure, because on a fixed side "no question" may mean "no sink left".
recorded = json.loads(result.read_text(encoding="utf-8"))
regions = int(offered.group(2))
why = "no region offers the families" if regions == 0 else f"no question asked for {regions} regions"
return {"state": "failed", "reason": why, "files": int(offered.group(1)), "regions": regions,
"seconds": recorded.get("seconds"), "degradations": recorded.get("degradations")} # fmt: skip
continue
return {"state": "ran", **{k: v for k, v in record.items() if k != "root"}}
return {"state": "failed", "reason": reason}
if __name__ == "__main__":
sys.exit(main())
@@ -1,7 +1,7 @@
{
"modules": 153,
"modules": 154,
"counts": {
"load_bearing": 146,
"load_bearing": 147,
"standalone_capability": 7
},
"orphaned": [],
@@ -362,9 +362,11 @@
"learn.excerpt",
"learn.features",
"learn.folds",
"learn.labels",
"learn.program",
"learn.retrieve",
"learn.roles",
"plane.harvest",
"plane.tasks.features",
"plane.tasks.remember",
"plane.tasks.roles"
@@ -432,7 +434,8 @@
"reason": "reachable from gate, map_gate, pair_gate or the CLI",
"importers": [
"learn.embeddings",
"learn.examples"
"learn.examples",
"learn.labels"
]
},
{
@@ -468,7 +471,8 @@
"learn.evaluate",
"learn.examples",
"learn.excerpt",
"learn.retrieve"
"learn.retrieve",
"plane.harvest"
]
},
{
@@ -831,6 +835,7 @@
"learn.labels",
"model.candidates",
"pair_gate",
"plane.harvest",
"plane.tasks.loop"
]
},
@@ -849,6 +854,7 @@
"plane.egress",
"plane.engine_alerts",
"plane.generate",
"plane.harvest",
"plane.memory",
"plane.reconciler",
"plane.router",
@@ -900,6 +906,15 @@
"module": "plane.generate",
"classification": "load_bearing",
"reason": "ai-service-plane task 7: an increment's Workspaces, per-case Tasks and Run from a recorded set",
"importers": [
"cli",
"plane.harvest"
]
},
{
"module": "plane.harvest",
"classification": "load_bearing",
"reason": "reachable from gate, map_gate, pair_gate or the CLI",
"importers": [
"cli"
]
@@ -912,6 +927,7 @@
"learn.cli",
"plane.egress",
"plane.generate",
"plane.harvest",
"plane.reconciler",
"plane.router",
"plane.runner",
@@ -940,6 +956,7 @@
"reason": "ai-service-plane Req 3.1-3.5, 7.3: ax-backed Run execution, status attribution and doctor; `ousast plane`",
"importers": [
"plane.generate",
"plane.harvest",
"plane.memory",
"plane.workspaces"
]
@@ -971,6 +988,7 @@
"learn.roles",
"plane.engine_alerts",
"plane.generate",
"plane.harvest",
"plane.memory",
"plane.tasks.agree",
"plane.tasks.alerts",
@@ -1027,6 +1045,7 @@
"learn.labels",
"learn.roles",
"plane.generate",
"plane.harvest",
"plane.tasks.alerts",
"plane.tasks.features",
"plane.tasks.remember",
@@ -1037,7 +1056,9 @@
"module": "plane.tasks.roles",
"classification": "load_bearing",
"reason": "learned-decision-engine Req 8.1: model role classification per file chunk (model_sinks.py moved in); its Run template comes with the harvest (task 5)",
"importers": []
"importers": [
"plane.harvest"
]
},
{
"module": "plane.tasks.verify",
@@ -1045,6 +1066,7 @@
"reason": "ai-service-plane Req 5.3/2.1-2.3/7.2: the batched hunt with known callers, metered and resumable per file hunt",
"importers": [
"plane.generate",
"plane.harvest",
"plane.tasks.agree",
"plane.tasks.roles"
]
@@ -1105,6 +1127,7 @@
"model.partitions",
"model.scan",
"pairs",
"plane.harvest",
"plane.tasks.alerts",
"plane.tasks.features",
"plane.tasks.repo_facts",
@@ -0,0 +1,475 @@
{
"balance_usd": {
"after_roles": 18.23,
"after_verify": 18.75,
"before": 19.63
},
"ceilings_usd": {
"roles": 10.0,
"verify": 10.0
},
"created": "2026-10-01",
"engine": {
"frozen_source_commit": "e6ed7854bb8ef17d6a4c39971ea253b4e385af23",
"image": "openultrasast:dev sha256:e6f8dc6cc7547f7118bd058687de676e0fcf741799d1d3839b3e8648ce190fbb",
"pair_sides_total": 686,
"population_engine": "recorded protocol scans of independent-v1/v2 (vulnerable and fixed pins); dev-php not run (missing)",
"sides_missing": 4,
"sides_recorded": {
"failed (read its input, asked no question)": 475,
"none": 8,
"ran": 199
},
"status": "finished 2026-10-01 16:30; the 4 sides of the 2 family-unknown pairs were not run (missing); features rebuilt after"
},
"features": {
"instrument_states_plane": {
"access_control:agree": {
"none": 26
},
"access_control:delta": {
"not_applicable": 26
},
"access_control:engine": {
"failed": 1,
"ran": 25
},
"access_control:entry_points": {
"not_applicable": 26
},
"access_control:facts": {
"not_applicable": 26
},
"access_control:language": {
"ran": 26
},
"access_control:model_sinks": {
"ran": 26
},
"access_control:quick": {
"ran": 26
},
"access_control:roles": {
"failed": 13,
"ran": 13
},
"access_control:source": {
"failed": 13,
"ran": 13
},
"access_control:verify": {
"none": 26
},
"config_secrets:agree": {
"none": 4,
"ran": 31
},
"config_secrets:delta": {
"not_applicable": 35
},
"config_secrets:engine": {
"missing": 4,
"none": 23,
"ran": 8
},
"config_secrets:entry_points": {
"not_applicable": 8,
"ran": 27
},
"config_secrets:facts": {
"not_applicable": 8,
"ran": 27
},
"config_secrets:language": {
"ran": 35
},
"config_secrets:model_sinks": {
"ran": 35
},
"config_secrets:quick": {
"ran": 35
},
"config_secrets:roles": {
"failed": 14,
"ran": 21
},
"config_secrets:source": {
"failed": 4,
"ran": 31
},
"config_secrets:verify": {
"none": 4,
"ran": 31
},
"deserialization:agree": {
"none": 40
},
"deserialization:delta": {
"not_applicable": 40
},
"deserialization:engine": {
"failed": 21,
"ran": 19
},
"deserialization:entry_points": {
"not_applicable": 40
},
"deserialization:facts": {
"not_applicable": 40
},
"deserialization:language": {
"ran": 40
},
"deserialization:model_sinks": {
"ran": 40
},
"deserialization:quick": {
"ran": 40
},
"deserialization:roles": {
"failed": 20,
"ran": 20
},
"deserialization:source": {
"failed": 20,
"ran": 20
},
"deserialization:verify": {
"none": 40
},
"injection:agree": {
"missing": 4,
"none": 25,
"ran": 139
},
"injection:delta": {
"not_applicable": 168
},
"injection:engine": {
"failed": 35,
"missing": 44,
"none": 16,
"ran": 73
},
"injection:entry_points": {
"not_applicable": 70,
"ran": 98
},
"injection:facts": {
"not_applicable": 70,
"ran": 98
},
"injection:language": {
"ran": 168
},
"injection:model_sinks": {
"missing": 4,
"ran": 164
},
"injection:quick": {
"none": 2,
"ran": 166
},
"injection:roles": {
"failed": 44,
"none": 2,
"ran": 122
},
"injection:source": {
"failed": 36,
"none": 2,
"ran": 130
},
"injection:verify": {
"missing": 4,
"none": 25,
"ran": 139
},
"memory:agree": {
"none": 346
},
"memory:delta": {
"not_applicable": 346
},
"memory:engine": {
"failed": 334,
"none": 6,
"ran": 6
},
"memory:entry_points": {
"not_applicable": 346
},
"memory:facts": {
"not_applicable": 346
},
"memory:language": {
"ran": 346
},
"memory:model_sinks": {
"missing": 4,
"ran": 342
},
"memory:quick": {
"none": 4,
"ran": 342
},
"memory:roles": {
"none": 346
},
"memory:source": {
"none": 346
},
"memory:verify": {
"none": 346
},
"output_encoding:agree": {
"none": 112
},
"output_encoding:delta": {
"not_applicable": 112
},
"output_encoding:engine": {
"failed": 26,
"ran": 86
},
"output_encoding:entry_points": {
"not_applicable": 112
},
"output_encoding:facts": {
"not_applicable": 112
},
"output_encoding:language": {
"ran": 112
},
"output_encoding:model_sinks": {
"ran": 112
},
"output_encoding:quick": {
"ran": 112
},
"output_encoding:roles": {
"failed": 16,
"ran": 96
},
"output_encoding:source": {
"failed": 16,
"ran": 96
},
"output_encoding:verify": {
"none": 112
},
"path:agree": {
"none": 29
},
"path:delta": {
"not_applicable": 29
},
"path:engine": {
"failed": 25,
"missing": 1,
"ran": 3
},
"path:entry_points": {
"not_applicable": 28,
"ran": 1
},
"path:facts": {
"not_applicable": 28,
"ran": 1
},
"path:language": {
"ran": 29
},
"path:model_sinks": {
"ran": 29
},
"path:quick": {
"ran": 29
},
"path:roles": {
"failed": 15,
"ran": 14
},
"path:source": {
"failed": 15,
"ran": 14
},
"path:verify": {
"none": 29
},
"unknown:agree": {
"none": 4
},
"unknown:delta": {
"not_applicable": 4
},
"unknown:engine": {
"missing": 2,
"none": 2
},
"unknown:entry_points": {
"not_applicable": 4
},
"unknown:facts": {
"not_applicable": 4
},
"unknown:language": {
"ran": 4
},
"unknown:model_sinks": {
"ran": 4
},
"unknown:quick": {
"ran": 4
},
"unknown:roles": {
"none": 4
},
"unknown:source": {
"none": 4
},
"unknown:verify": {
"none": 4
},
"untrusted_destination:agree": {
"none": 11,
"ran": 85
},
"untrusted_destination:delta": {
"not_applicable": 96
},
"untrusted_destination:engine": {
"failed": 38,
"missing": 12,
"none": 17,
"ran": 29
},
"untrusted_destination:entry_points": {
"not_applicable": 52,
"ran": 44
},
"untrusted_destination:facts": {
"not_applicable": 52,
"ran": 44
},
"untrusted_destination:language": {
"ran": 96
},
"untrusted_destination:model_sinks": {
"ran": 96
},
"untrusted_destination:quick": {
"ran": 96
},
"untrusted_destination:roles": {
"failed": 39,
"ran": 57
},
"untrusted_destination:source": {
"failed": 33,
"ran": 63
},
"untrusted_destination:verify": {
"none": 11,
"ran": 85
}
},
"records": {
"plane:access_control": 26,
"plane:config_secrets": 35,
"plane:deserialization": 40,
"plane:injection": 168,
"plane:memory": 346,
"plane:output_encoding": 112,
"plane:path": 29,
"plane:unknown": 4,
"plane:untrusted_destination": 96,
"static:access_control": 26,
"static:config_secrets": 35,
"static:deserialization": 40,
"static:injection": 168,
"static:memory": 346,
"static:output_encoding": 112,
"static:path": 29,
"static:unknown": 4,
"static:untrusted_destination": 96
},
"skipped_label_rows": {},
"store": "file://~/ousast-results/plane/memory (kind features, run harvest)"
},
"host_peaks": {
"disk_free_mb_min": 960,
"disk_incident": "Substrate kept every golden actor (~24 MB each, deletion failing on a missing snapshot bucket); free disk fell to 0.95 GB, runs were stopped, the bucket created and orphaned actor directories removed",
"mem_available_mb_min": 1990
},
"label_snapshot_sha256": "18337014d8a5c8df67e06f4e9d8e72c1fb8f82b1ed1b9a18836ab37e6cf4fdc9",
"note": "Harvest of the learned decision engine (task 5), COUNTS ONLY. Spend is the plane's attribution table (`ousast plane status`, task meters priced at plane/models/deepseek-flash.yaml); the DeepSeek account moved less (balance below). Feature records are in the local memory store (kind `features`, run `harvest`); the label rows, the units index and the manifests name functions and stay local.",
"roles": {
"attribution": {
"cache_hit_tokens": null,
"calls": 1034,
"output_tokens": null,
"prompt_tokens": null,
"usd": 1.4036
},
"cache_hit_tokens": 329600,
"chunks": 1034,
"files": 799,
"output_tokens": 370155,
"prompt_tokens": 2398669,
"task_budget_sum_usd": 4.917,
"tasks_done": 151,
"unclassified_chunks": 0
},
"runner_image": "localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884",
"verify": {
"attribution": {
"cache_hit_tokens": 6275060,
"calls": 1961,
"output_tokens": 306941,
"prompt_tokens": 10921167,
"usd": 2.5373
},
"file_hunts_done": 364,
"flagged_by_pass_units": {
"dev-php:vulnerable": {
"both": 2
},
"pairs:fixed": {
"both": 4,
"none": 58,
"one": 1
},
"pairs:vulnerable": {
"both": 45,
"none": 11,
"one": 7
},
"population-v1:fixed": {
"both": 5,
"none": 2,
"one": 4
},
"population-v1:vulnerable": {
"both": 8,
"none": 2,
"one": 1
}
},
"hunts_per_pass": 182,
"infrastructure_failures": "ax apply / actor template not found for Workspaces over the inline limit (units then skipped); two tasks interrupted by a disk stop were re-run",
"skipped_units": {
"pairs": 4,
"reason": "excerpt over the 20 KB inline Workspace limit"
},
"task_budget_sum_usd": 8.624,
"tasks_done": 474,
"units": {
"dev-php:vulnerable": 2,
"pairs:fixed": 63,
"pairs:vulnerable": 63,
"population-v1:fixed": 11,
"population-v1:vulnerable": 11
}
}
}
+7
View File
@@ -41,6 +41,13 @@ afterwards (`go clean -cache -modcache`); rerunning the full Substrate install o
only; the gateway dials the address the actor connected to, so the receiver is a Service dialled by ClusterIP
with its name as Host. The Envoy gateway needs a Rust build; the agentgateway variant needs none.
- Apply a Task after the Workspaces it binds; ax copies them at create time.
- Every Task is its own actor template with a golden actor (~24 MB under `/var/lib/ate/actors` on the node). Deleting
them failed while the snapshot bucket ax's templates name (`dberkov-gke-dev3`, from `AX_SNAPSHOTS_BUCKET`) did not
exist in rustfs: golden actors piled up in `DELETING` and filled the disk during a 600-task harvest (2026-10-01).
Create that bucket in rustfs (an aws-cli pod with the `rustfs-bucket-init` env); even then a deleted actor's
directory stays on the node, so long Runs need a janitor that removes directories no live actor owns.
- A Workspace's `files` reach the actor inline in one environment variable: above ~20 KB of content the template
fails with "actor template not found" (an 86 KB and a 31 KB excerpt did; 7 KB ran).
## Moving to a separate Kubernetes cluster (planned)
+1 -1
View File
@@ -7,7 +7,7 @@ metadata:
name: agree
atespace: default
spec:
image: "localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699"
image: "localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884"
command: ["agree"]
resources:
requests:
+1 -1
View File
@@ -7,7 +7,7 @@ metadata:
name: alerts
atespace: default
spec:
image: "localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699"
image: "localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884"
command: ["alerts"]
resources:
requests:
+1 -1
View File
@@ -7,7 +7,7 @@ metadata:
name: features
atespace: default
spec:
image: "localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699"
image: "localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884"
command: ["features"]
resources:
requests:
+1 -1
View File
@@ -7,7 +7,7 @@ metadata:
name: loop
atespace: default
spec:
image: "localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699"
image: "localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884"
command: ["loop"]
resources:
requests:
+1 -1
View File
@@ -7,7 +7,7 @@ metadata:
name: remember
atespace: default
spec:
image: "localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699"
image: "localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884"
command: ["remember"]
resources:
requests:
+1 -1
View File
@@ -8,7 +8,7 @@ metadata:
name: repo-facts
atespace: default
spec:
image: "localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699"
image: "localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884"
command: ["repo-facts"]
resources:
requests:
+22
View File
@@ -0,0 +1,22 @@
# roles (learned-decision-engine design section 2, Req 8.1): the model reads a repository's source files and names
# their roles (sink, source, sanitizer, guard, dispatch), no vocabulary and no framework table. Bound to its Model by
# the `openultrasast.io/model` annotation, which the reconciler strips and renders as OUSAST_MODEL /
# OUSAST_MODEL_PARAMS. The template of the per-pin Tasks of a harvest Run (`ousast plane harvest`), which name the
# files to classify as OUSAST_INPUT_FILES. The image is digest-pinned.
apiVersion: ax.io/v1alpha1
kind: Task
metadata:
name: roles
atespace: default
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: "localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884"
command: ["roles"]
resources:
requests:
cpu: "250m"
memory: "384Mi"
limits:
cpu: "1"
memory: "1Gi"
+120 -120
View File
@@ -5,9 +5,9 @@ metadata:
name: repo-facts-budibase-mongo-template-nosqli
atespace: default
annotations:
openultrasast.io/memory-key: '{"candidates":"b4ef709df34ab08c","image":"sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699","pin":"b7e63df737eede85d951aa9665b0c0778ece7ccb","repo":"github.com/Budibase/budibase"}'
openultrasast.io/memory-key: '{"candidates":"b4ef709df34ab08c","image":"sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884","pin":"b7e63df737eede85d951aa9665b0c0778ece7ccb","repo":"github.com/Budibase/budibase"}'
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- repo-facts
env:
@@ -36,7 +36,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -67,7 +67,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -96,7 +96,7 @@ metadata:
name: agree-budibase-mongo-template-nosqli
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- agree
env:
@@ -123,9 +123,9 @@ metadata:
name: repo-facts-cct-studio-server-cmdi
atespace: default
annotations:
openultrasast.io/memory-key: '{"candidates":"6c9fa4a6e47cda37","image":"sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699","pin":"acdb5893412a81eaec9862ee24a4613bf296ab45","repo":"github.com/davila7/claude-code-templates"}'
openultrasast.io/memory-key: '{"candidates":"6c9fa4a6e47cda37","image":"sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884","pin":"acdb5893412a81eaec9862ee24a4613bf296ab45","repo":"github.com/davila7/claude-code-templates"}'
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- repo-facts
env:
@@ -154,7 +154,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -185,7 +185,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -214,7 +214,7 @@ metadata:
name: agree-cct-studio-server-cmdi
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- agree
env:
@@ -241,9 +241,9 @@ metadata:
name: repo-facts-contextforge-prompt-ssti
atespace: default
annotations:
openultrasast.io/memory-key: '{"candidates":"b272af6456664ebc","image":"sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699","pin":"0f8e0ad679f1fff685f486e17f642ac863c8ca9e","repo":"github.com/IBM/mcp-context-forge"}'
openultrasast.io/memory-key: '{"candidates":"b272af6456664ebc","image":"sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884","pin":"0f8e0ad679f1fff685f486e17f642ac863c8ca9e","repo":"github.com/IBM/mcp-context-forge"}'
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- repo-facts
env:
@@ -272,7 +272,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -303,7 +303,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -332,7 +332,7 @@ metadata:
name: agree-contextforge-prompt-ssti
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- agree
env:
@@ -359,9 +359,9 @@ metadata:
name: repo-facts-froxlor-admin-ip-second-order-sqli
atespace: default
annotations:
openultrasast.io/memory-key: '{"candidates":"c52240d980ef0105","image":"sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699","pin":"9bf03de643a2edee4642bfc6c94db1f05253da02","repo":"github.com/froxlor/froxlor"}'
openultrasast.io/memory-key: '{"candidates":"c52240d980ef0105","image":"sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884","pin":"9bf03de643a2edee4642bfc6c94db1f05253da02","repo":"github.com/froxlor/froxlor"}'
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- repo-facts
env:
@@ -390,7 +390,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -421,7 +421,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -450,7 +450,7 @@ metadata:
name: agree-froxlor-admin-ip-second-order-sqli
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- agree
env:
@@ -477,9 +477,9 @@ metadata:
name: repo-facts-fuxa-tdengine-escape-sqli
atespace: default
annotations:
openultrasast.io/memory-key: '{"candidates":"ec699ee54d38a8ee","image":"sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699","pin":"193a888f8e3f805325072829b1644a2c768c948b","repo":"github.com/frangoteam/FUXA"}'
openultrasast.io/memory-key: '{"candidates":"ec699ee54d38a8ee","image":"sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884","pin":"193a888f8e3f805325072829b1644a2c768c948b","repo":"github.com/frangoteam/FUXA"}'
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- repo-facts
env:
@@ -508,7 +508,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -539,7 +539,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -568,7 +568,7 @@ metadata:
name: agree-fuxa-tdengine-escape-sqli
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- agree
env:
@@ -595,9 +595,9 @@ metadata:
name: repo-facts-lmdeploy-media-url-ssrf
atespace: default
annotations:
openultrasast.io/memory-key: '{"candidates":"1337c9c717f5ac87","image":"sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699","pin":"71648d239c978452720a6d6692fe914efc8a7b94","repo":"github.com/InternLM/lmdeploy"}'
openultrasast.io/memory-key: '{"candidates":"1337c9c717f5ac87","image":"sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884","pin":"71648d239c978452720a6d6692fe914efc8a7b94","repo":"github.com/InternLM/lmdeploy"}'
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- repo-facts
env:
@@ -626,7 +626,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -657,7 +657,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -686,7 +686,7 @@ metadata:
name: agree-lmdeploy-media-url-ssrf
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- agree
env:
@@ -713,9 +713,9 @@ metadata:
name: repo-facts-medplum-external-auth-open-redirect
atespace: default
annotations:
openultrasast.io/memory-key: '{"candidates":"17ba609d5b1a1a00","image":"sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699","pin":"5d4a8654b02aa67c444f96ae1eb5663c09854006","repo":"github.com/medplum/medplum"}'
openultrasast.io/memory-key: '{"candidates":"17ba609d5b1a1a00","image":"sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884","pin":"5d4a8654b02aa67c444f96ae1eb5663c09854006","repo":"github.com/medplum/medplum"}'
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- repo-facts
env:
@@ -744,7 +744,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -775,7 +775,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -804,7 +804,7 @@ metadata:
name: agree-medplum-external-auth-open-redirect
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- agree
env:
@@ -831,9 +831,9 @@ metadata:
name: repo-facts-pgadmin-maintenance-sqli
atespace: default
annotations:
openultrasast.io/memory-key: '{"candidates":"7f1b83a88024c2d4","image":"sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699","pin":"d112dc3b962218efc3613fa727d54f0fc5b1d527","repo":"github.com/pgadmin-org/pgadmin4"}'
openultrasast.io/memory-key: '{"candidates":"7f1b83a88024c2d4","image":"sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884","pin":"d112dc3b962218efc3613fa727d54f0fc5b1d527","repo":"github.com/pgadmin-org/pgadmin4"}'
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- repo-facts
env:
@@ -862,7 +862,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -893,7 +893,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -922,7 +922,7 @@ metadata:
name: agree-pgadmin-maintenance-sqli
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- agree
env:
@@ -949,9 +949,9 @@ metadata:
name: repo-facts-qwed-sympy-eval
atespace: default
annotations:
openultrasast.io/memory-key: '{"candidates":"f071e8db5f5517ab","image":"sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699","pin":"06801f68cbf9c9b5e54cc17f2a61cb2792da7a26","repo":"github.com/QWED-AI/qwed-verification"}'
openultrasast.io/memory-key: '{"candidates":"f071e8db5f5517ab","image":"sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884","pin":"06801f68cbf9c9b5e54cc17f2a61cb2792da7a26","repo":"github.com/QWED-AI/qwed-verification"}'
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- repo-facts
env:
@@ -980,7 +980,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1011,7 +1011,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1040,7 +1040,7 @@ metadata:
name: agree-qwed-sympy-eval
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- agree
env:
@@ -1067,9 +1067,9 @@ metadata:
name: repo-facts-reactpress-findall-column-sqli
atespace: default
annotations:
openultrasast.io/memory-key: '{"candidates":"ecb82a10249c6fed","image":"sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699","pin":"c3dd10681b3d2b5e224f7ac647b3769557603975","repo":"github.com/fecommunity/reactpress"}'
openultrasast.io/memory-key: '{"candidates":"ecb82a10249c6fed","image":"sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884","pin":"c3dd10681b3d2b5e224f7ac647b3769557603975","repo":"github.com/fecommunity/reactpress"}'
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- repo-facts
env:
@@ -1098,7 +1098,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1129,7 +1129,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1158,7 +1158,7 @@ metadata:
name: agree-reactpress-findall-column-sqli
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- agree
env:
@@ -1185,9 +1185,9 @@ metadata:
name: repo-facts-unleash-addon-webhook-ssrf
atespace: default
annotations:
openultrasast.io/memory-key: '{"candidates":"4d3debd51fd86f9c","image":"sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699","pin":"b9f3217d327f24094b4352130baa30387f85cffe","repo":"github.com/Unleash/unleash"}'
openultrasast.io/memory-key: '{"candidates":"4d3debd51fd86f9c","image":"sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884","pin":"b9f3217d327f24094b4352130baa30387f85cffe","repo":"github.com/Unleash/unleash"}'
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- repo-facts
env:
@@ -1216,7 +1216,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1247,7 +1247,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1276,7 +1276,7 @@ metadata:
name: agree-unleash-addon-webhook-ssrf
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- agree
env:
@@ -1303,9 +1303,9 @@ metadata:
name: repo-facts-winml-cli-serve-cors
atespace: default
annotations:
openultrasast.io/memory-key: '{"candidates":"e3b0c44298fc1c14","image":"sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699","pin":"02179d185f0e3ac23518236b5ca1f61fd37ef680","repo":"github.com/microsoft/winml-cli"}'
openultrasast.io/memory-key: '{"candidates":"e3b0c44298fc1c14","image":"sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884","pin":"02179d185f0e3ac23518236b5ca1f61fd37ef680","repo":"github.com/microsoft/winml-cli"}'
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- repo-facts
env:
@@ -1334,7 +1334,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1365,7 +1365,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1394,7 +1394,7 @@ metadata:
name: agree-winml-cli-serve-cors
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- agree
env:
@@ -1421,9 +1421,9 @@ metadata:
name: repo-facts-wp-directorist-avatar-ssrf
atespace: default
annotations:
openultrasast.io/memory-key: '{"candidates":"d6963021f588f39e","image":"sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699","pin":"77cfc9bc8b453f97ab7e720330706fa4ef056867","repo":"github.com/sovware/directorist"}'
openultrasast.io/memory-key: '{"candidates":"d6963021f588f39e","image":"sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884","pin":"77cfc9bc8b453f97ab7e720330706fa4ef056867","repo":"github.com/sovware/directorist"}'
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- repo-facts
env:
@@ -1452,7 +1452,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1483,7 +1483,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1512,7 +1512,7 @@ metadata:
name: agree-wp-directorist-avatar-ssrf
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- agree
env:
@@ -1539,9 +1539,9 @@ metadata:
name: repo-facts-wp-gopay-log-filter-sqli
atespace: default
annotations:
openultrasast.io/memory-key: '{"candidates":"fde4d17144a13d73","image":"sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699","pin":"626bc8b5f8d33de6eefd921e3a05ed52ac320bcd","repo":"github.com/gopaycommunity/gopay-woocommerce-integration"}'
openultrasast.io/memory-key: '{"candidates":"fde4d17144a13d73","image":"sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884","pin":"626bc8b5f8d33de6eefd921e3a05ed52ac320bcd","repo":"github.com/gopaycommunity/gopay-woocommerce-integration"}'
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- repo-facts
env:
@@ -1570,7 +1570,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1601,7 +1601,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1630,7 +1630,7 @@ metadata:
name: agree-wp-gopay-log-filter-sqli
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- agree
env:
@@ -1657,9 +1657,9 @@ metadata:
name: repo-facts-wp-groundhogg-confirm-redirect
atespace: default
annotations:
openultrasast.io/memory-key: '{"candidates":"fde4d17144a13d73","image":"sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699","pin":"9472a1dd79d4556c40eece501a61930793f170d3","repo":"github.com/groundhoggwp/groundhogg"}'
openultrasast.io/memory-key: '{"candidates":"fde4d17144a13d73","image":"sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884","pin":"9472a1dd79d4556c40eece501a61930793f170d3","repo":"github.com/groundhoggwp/groundhogg"}'
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- repo-facts
env:
@@ -1688,7 +1688,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1719,7 +1719,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1748,7 +1748,7 @@ metadata:
name: agree-wp-groundhogg-confirm-redirect
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- agree
env:
@@ -1777,7 +1777,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1808,7 +1808,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1839,7 +1839,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1870,7 +1870,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1901,7 +1901,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1932,7 +1932,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1963,7 +1963,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -1994,7 +1994,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -2025,7 +2025,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -2056,7 +2056,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -2087,7 +2087,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -2118,7 +2118,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -2149,7 +2149,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -2180,7 +2180,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -2211,7 +2211,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- verify
env:
@@ -2240,7 +2240,7 @@ metadata:
name: features-budibase-mongo-template-nosqli
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- features
env:
@@ -2263,7 +2263,7 @@ metadata:
name: remember-budibase-mongo-template-nosqli
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- remember
env:
@@ -2288,7 +2288,7 @@ metadata:
name: features-cct-studio-server-cmdi
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- features
env:
@@ -2311,7 +2311,7 @@ metadata:
name: remember-cct-studio-server-cmdi
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- remember
env:
@@ -2336,7 +2336,7 @@ metadata:
name: features-contextforge-prompt-ssti
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- features
env:
@@ -2359,7 +2359,7 @@ metadata:
name: remember-contextforge-prompt-ssti
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- remember
env:
@@ -2384,7 +2384,7 @@ metadata:
name: features-froxlor-admin-ip-second-order-sqli
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- features
env:
@@ -2407,7 +2407,7 @@ metadata:
name: remember-froxlor-admin-ip-second-order-sqli
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- remember
env:
@@ -2432,7 +2432,7 @@ metadata:
name: features-fuxa-tdengine-escape-sqli
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- features
env:
@@ -2455,7 +2455,7 @@ metadata:
name: remember-fuxa-tdengine-escape-sqli
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- remember
env:
@@ -2480,7 +2480,7 @@ metadata:
name: features-lmdeploy-media-url-ssrf
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- features
env:
@@ -2503,7 +2503,7 @@ metadata:
name: remember-lmdeploy-media-url-ssrf
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- remember
env:
@@ -2528,7 +2528,7 @@ metadata:
name: features-medplum-external-auth-open-redirect
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- features
env:
@@ -2551,7 +2551,7 @@ metadata:
name: remember-medplum-external-auth-open-redirect
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- remember
env:
@@ -2576,7 +2576,7 @@ metadata:
name: features-pgadmin-maintenance-sqli
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- features
env:
@@ -2599,7 +2599,7 @@ metadata:
name: remember-pgadmin-maintenance-sqli
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- remember
env:
@@ -2624,7 +2624,7 @@ metadata:
name: features-qwed-sympy-eval
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- features
env:
@@ -2647,7 +2647,7 @@ metadata:
name: remember-qwed-sympy-eval
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- remember
env:
@@ -2672,7 +2672,7 @@ metadata:
name: features-reactpress-findall-column-sqli
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- features
env:
@@ -2695,7 +2695,7 @@ metadata:
name: remember-reactpress-findall-column-sqli
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- remember
env:
@@ -2720,7 +2720,7 @@ metadata:
name: features-unleash-addon-webhook-ssrf
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- features
env:
@@ -2743,7 +2743,7 @@ metadata:
name: remember-unleash-addon-webhook-ssrf
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- remember
env:
@@ -2768,7 +2768,7 @@ metadata:
name: features-winml-cli-serve-cors
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- features
env:
@@ -2791,7 +2791,7 @@ metadata:
name: remember-winml-cli-serve-cors
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- remember
env:
@@ -2816,7 +2816,7 @@ metadata:
name: features-wp-directorist-avatar-ssrf
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- features
env:
@@ -2839,7 +2839,7 @@ metadata:
name: remember-wp-directorist-avatar-ssrf
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- remember
env:
@@ -2864,7 +2864,7 @@ metadata:
name: features-wp-gopay-log-filter-sqli
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- features
env:
@@ -2887,7 +2887,7 @@ metadata:
name: remember-wp-gopay-log-filter-sqli
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- remember
env:
@@ -2912,7 +2912,7 @@ metadata:
name: features-wp-groundhogg-confirm-redirect
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- features
env:
@@ -2935,7 +2935,7 @@ metadata:
name: remember-wp-groundhogg-confirm-redirect
atespace: default
spec:
image: localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699
image: localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884
command:
- remember
env:
+1 -1
View File
@@ -10,7 +10,7 @@ metadata:
annotations:
openultrasast.io/model: deepseek-flash
spec:
image: "localhost:5001/ousast-runner@sha256:e9964977659c4dcea7d8b25390ca0e16368ac901dd076aa6138df8303a0bb699"
image: "localhost:5001/ousast-runner@sha256:412db0a1504af8f1b23a395af8d1dcbd152c1bb6d16594f2b733067163a31884"
command: ["verify"]
resources:
requests:
+33
View File
@@ -299,6 +299,21 @@ def _main(argv: list[str] | None) -> int:
plane_engine.add_argument("--image", default="openultrasast:dev")
plane_engine.add_argument("--repos", type=Path, help="the case cache (default ~/.cache/openultrasast/independent)")
plane_engine.add_argument("--work", type=Path, help="where pins are exported and results land (default: a temporary directory)")
plane_harvest = plane_sub.add_parser(
"harvest", help="the decision engine's harvest Runs (verify a/b + agree, model roles) over a label snapshot"
)
plane_harvest.add_argument("--labels", type=Path, required=True, help="the full label rows (`ousast learn labels --out`)")
plane_harvest.add_argument(
"--plane", type=Path, required=True, help="where to write runs/, tasks/, workspaces/, models/, units.json (scratch)"
)
plane_harvest.add_argument("--name", default="harvest", help="Run name prefix: <name>-verify and <name>-roles")
plane_harvest.add_argument("--templates", type=Path, default=Path("plane"), help="the plane root holding tasks/ and models/ templates")
plane_harvest.add_argument("--catalog", type=Path, default=Path("benchmarks/pairs/catalog.toml"))
plane_harvest.add_argument(
"--cache", type=Path, default=Path.home() / ".cache" / "openultrasast", help="case clones and recipe checkouts"
)
plane_harvest.add_argument("--ceiling", type=float, default=10.0, help="USD: each Run's task budgets sum to at most this")
plane_harvest.add_argument("--runner-image", type=Path, help="re-pin the task templates to the image in this file first (ops/ax/up.sh)")
plane_ws = plane_sub.add_parser("workspaces", help="one Workspace manifest per case pin of a population file")
plane_ws.add_argument("population", type=Path)
plane_ws.add_argument("--out", type=Path, default=Path("plane/workspaces"))
@@ -1374,6 +1389,8 @@ def _plane(args: argparse.Namespace) -> int:
return 0
if args.plane_command == "alerts-engine":
return _plane_alerts_engine(args)
if args.plane_command == "harvest":
return _plane_harvest(args)
if args.plane_command == "doctor":
checks = reconciler.doctor()
for name, ok, text in checks:
@@ -1388,6 +1405,22 @@ def _plane(args: argparse.Namespace) -> int:
return 0
def _plane_harvest(args: argparse.Namespace) -> int:
from .plane.generate import repin_templates
from .plane.harvest import write_harvest
if args.runner_image is not None:
repin_templates(args.templates, args.runner_image)
command = f"ousast plane harvest --labels {args.labels.name} --name {args.name} --ceiling {args.ceiling:g}"
files = write_harvest(
args.labels, args.plane, name=args.name, command=command, catalog=args.catalog, cache=args.cache, templates=args.templates,
ceiling=args.ceiling,
) # fmt: skip
for relative in sorted(k for k in files if k.startswith("runs/")):
print(f"{args.plane / relative}: {files[relative].splitlines()[1].lstrip('# ')}")
return 0
def _plane_alerts_engine(args: argparse.Namespace) -> int:
from .plane import engine_alerts
+6 -3
View File
@@ -254,7 +254,9 @@ def function_span(lines: Sequence[str], language: str, function: str) -> int | N
def source_part(lines: Sequence[str] | None, language: str, function: str) -> Part:
if lines is None:
"""The function's span; ``none`` without the file or a declaration pattern for its language (C, say: no coverage,
not a failure), ``failed`` when a covered file does not declare the function."""
if lines is None or (function != GLOBAL and language not in DECLARATION):
return NONE
span = function_span(lines, language, function)
return Part("ran", {"facts.function_lines": span}) if span is not None else Part("failed")
@@ -294,8 +296,9 @@ def agree_part(final: str | None) -> Part:
def roles_part(function: Function | None, roles: RoleSet | None, language: str) -> Part:
"""The deterministic roles of the candidate's function (language-level entries plus wrapper inference; priors
off): ``none`` when inference did not run, ``failed`` when the function's body could not be found."""
if roles is None or "inferred_wrapper" not in roles.sources:
off): ``none`` when inference did not run or cannot read the language's declarations, ``failed`` when the
function's body could not be found."""
if roles is None or "inferred_wrapper" not in roles.sources or language not in DECLARATION:
return NONE
if function is None:
return Part("failed", version=VERSION)
+1 -1
View File
@@ -87,7 +87,7 @@ CASE_PATH = "/workspace/case"
INPUTS_PATH = "/workspace/inputs"
FIXED_PATH = "/workspace/fixed"
PROJECT_PATH = "/workspace/project"
TEMPLATES = ("repo-facts", "verify", "agree", "remember", "alerts", "loop", "features")
TEMPLATES = ("repo-facts", "verify", "agree", "remember", "alerts", "loop", "features", "roles")
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"
+379
View File
@@ -0,0 +1,379 @@
"""The harvest Runs of the learned decision engine (design, commit sequence item 5; Req 1.1).
``ousast plane harvest --labels labels-<sha>.jsonl --plane DIR`` writes two Runs over the labelled units of a label
snapshot (``ousast learn labels --out``), with the Workspaces and Tasks they need, under ``DIR`` (a scratch plane
root: the excerpts and candidates are inputs, never committed):
- ``runs/<name>-verify.yaml``: verify passes a and b and ``agree`` (no tie-break) on every verify-family unit
(:data:`.tasks.verify.OPERATIONS`): both sides of each labelled pair, and the development pins -- population v1's
vulnerable and fixed pins, the dev-php recipes -- with ``repo-facts`` first for their callers;
- ``runs/<name>-roles.yaml``: ``roles`` (model roles) on the candidate files of every labelled pin unit: the pair
sides, grouped many to a Workspace (a roles prompt sees one chunk of one file, so grouping changes nothing it
reads), and one Task per population or recipe pin.
Units carry no label. A pair side is a files-only Workspace holding the excerpt (and the documents that register
it) under ``repo/`` and the candidates under ``inputs/`` -- outside the hunt's root, so the tools cannot read them;
its name is opaque (``h-<sha>``): the pair's name says its CWE and its side. ``agree`` gets no declared sites, so
no ``site_match`` is computed. ``units.json`` maps each unit name to its source, reference, side, repository, pin
and candidates for the host-side feature build; it names functions and stays local.
Every Run entry has a budget; the sum over each Run is held under ``ceiling`` (USD) by construction: verify gets
:data:`USD_PER_HUNT` per hunt of an excerpt or a repository (scaled down when the hunts would exceed the ceiling),
roles a per-chunk estimate times a headroom, scaled down to the ceiling.
A pair side's pin is the git blob sha1 of its excerpt (the store keys rows by
a 40-hex pin; an excerpt has no commit of its own).
"""
from __future__ import annotations
import hashlib
import json
import math
from collections.abc import Iterable, Mapping, Sequence
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
import yaml
from ..preprocess import detect_language
from .generate import ATESPACE, CALLS_PER_HUNT, CASE_PATH, INPUTS_PATH, _dump, _json, _task
from .manifests import GIT_COMMITS_ANNOTATION, RUN_API_VERSION, Task
from .reconciler import _ax_name, _doc
from .tasks.repo_facts import DECLARATION, GLOBAL, _declared_name
from .tasks.roles import CHUNK_LINES
from .tasks.verify import OPERATIONS, units_of
__all__ = ["Unit", "declaration_line", "pair_units", "population_units", "render_harvest", "write_harvest"]
Candidate = tuple[str, str, int]
REPO = "repo"
# Per verify hunt (pilot 2026-10-01: an excerpt hunt cost $0.003-0.004, a repository hunt $0.019-0.022): ~3x and ~2.5x.
USD_PER_HUNT = {"excerpt": 0.012, "repository": 0.05}
ROLES_GROUP_FILES = 40
# A Workspace's files travel inline: ax hands them to the actor in one environment variable (AX_WORKSPACES_YAML), which
# Linux caps at 128 KiB (MAX_ARG_STRLEN) after YAML escaping. An 86 KB excerpt failed with "actor template not found"
# (2026-10-01), so did a 31 KB one; 7 KB ran. A unit whose own files exceed the limit is skipped, listed in `units.json`.
MAX_INLINE_BYTES = 20_000
ROLES_OUTPUT_TOKENS = 700 # per chunk, generous: a role list with evidence quotes
ROLES_HEADROOM = 2.5
UNKNOWN_SIZE = (80_000, 2_000) # (characters, lines) assumed for a candidate file the host could not read
PRICES = {"input_per_m": 0.44, "output_per_m": 1.32} # plane/models/deepseek-flash.yaml
POPULATION_SOURCES = ("population-v1", "population-v2", "dev-php", "adjudications")
VERIFY_SOURCES = ("population-v1", "dev-php") # the development cases (design section 3); v2 has its recorded Run
@dataclass(frozen=True)
class Unit:
"""One pin of the harvest: a repository at a commit (``files`` empty) or one side of a pair (``files`` holds the
excerpt). ``family`` is the verify family, ``""`` when the unit is classified by ``roles`` only."""
name: str
source: str
ref: str
side: str
repo: str
pin: str
family: str
candidates: tuple[Candidate, ...]
files: tuple[tuple[str, str], ...] = field(default=())
verify: tuple[Candidate, ...] = field(default=()) # the candidates of ``family`` (all of them when empty)
sizes: tuple[tuple[str, int, int], ...] = field(default=()) # (path, characters, lines) of a pin's candidate files
@property
def paths(self) -> list[str]:
return sorted({c[0] for c in self.candidates})
def index_row(self) -> dict[str, Any]:
return {"source": self.source, "ref": self.ref, "side": self.side, "repo": self.repo, "pin": self.pin,
"family": self.family, "candidates": [list(c) for c in self.candidates]} # fmt: skip
def _opaque(*parts: str) -> str:
return "h-" + hashlib.sha256("\x00".join(parts).encode("utf-8")).hexdigest()[:12]
def blob_sha1(text: str) -> str:
data = text.encode("utf-8")
return hashlib.sha1(b"blob %d\x00" % len(data) + data).hexdigest()
def declaration_line(lines: Sequence[str] | None, path: str, function: str) -> int:
"""1-based line of the first declaration of ``function`` in ``lines``; 1 for ``<global>`` or when not declared."""
pattern = DECLARATION.get(detect_language(Path(path)) or "")
if not lines or pattern is None or function == GLOBAL:
return 1
return next((i + 1 for i, line in enumerate(lines) if _declared_name(pattern, line) == function), 1)
def _read_labels(labels: Path) -> list[dict[str, Any]]:
return [json.loads(line) for line in labels.read_text(encoding="utf-8").splitlines() if line.strip()]
def pair_units(labels: Path, catalog: Path) -> list[Unit]:
"""Both sides of every labelled pair (pin units), each with its labelled candidates of every family; ``family`` is
the pair's verify family when it has one."""
from ..pairs import load_pair_catalog
wanted: dict[str, dict[str, set[str]]] = {}
for row in _read_labels(labels):
if row["source"] == "pairs" and row["unit"] == "pin":
wanted.setdefault(row["source_ref"], {}).setdefault(row["family"], set()).add(row["candidate"])
cases = {case.name: case for case in load_pair_catalog(catalog)}
out: list[Unit] = []
for name in sorted(wanted):
case = cases[name]
families = wanted[name]
verify = sorted(f for f in families if f in OPERATIONS)
for side in ("vuln", "fixed"):
excerpt = case.vuln_file if side == "vuln" else case.fixed_file
if not excerpt.is_file():
raise ValueError(f"{name}: the {side} excerpt is not materialised ({excerpt}); fetch pointer pairs first")
texts = {case.relpath: excerpt.read_text(encoding="utf-8", errors="replace")}
for relpath, vuln_doc, fixed_doc in case.context_files:
texts[relpath] = (vuln_doc if side == "vuln" else fixed_doc).read_text(encoding="utf-8", errors="replace")
lines = {path: text.splitlines() for path, text in texts.items()}
def located(names: Iterable[str], lines: Mapping[str, list[str]] = lines) -> tuple[Candidate, ...]:
return tuple(sorted({(p, fn, declaration_line(lines.get(p), p, fn)) for c in names for p, _, fn in [c.partition("::")]}))
role = "vulnerable" if side == "vuln" else "fixed"
out.append(Unit(
_opaque("pairs", name, side), "pairs", name, role, case.repo or f"pairs/{case.slice}/{name}",
blob_sha1(texts[case.relpath]), verify[0] if verify else "", located(set().union(*families.values())),
tuple(sorted(texts.items())), located(families[verify[0]]) if verify else (),
)) # fmt: skip
return out
def population_units(labels: Path, cache: Path) -> list[Unit]:
"""One unit per (repository, pin, family) of the population, recipe and adjudication pin labels, candidates with
their declaration line at that pin (read from the case clone or the recipe checkout). ``family`` is set for the
development cases' verify families (:data:`VERIFY_SOURCES`, and v1 adjudications), else ``""``."""
groups: dict[tuple[str, str, str], dict[str, Any]] = {}
for row in _read_labels(labels):
if row["unit"] != "pin" or row["source"] not in POPULATION_SOURCES:
continue
key = (row["repo"], row["pin"], row["family"])
group = groups.setdefault(key, {"sources": set(), "refs": set(), "side": row["pin_role"], "candidates": set()})
group["sources"].add(row["split"] if row["source"] == "adjudications" else row["source"])
group["refs"].add(row["source_ref"])
group["candidates"].add(row["candidate"])
case_ids = {r: ref for (r, _, _), g in groups.items() for ref in g["refs"] if ":" not in ref and "#" not in ref}
out: list[Unit] = []
for (repo, pin, family), group in sorted(groups.items()):
source = sorted(group["sources"])[0]
ref = case_ids[repo] if source.startswith("population") and repo in case_ids else str(sorted(group["refs"])[0])
reader = _reader(cache, source, ref, pin)
texts = {p: reader(p) for p in {c.partition("::")[0] for c in group["candidates"]}}
candidates = sorted({(p, fn, declaration_line(texts[p], p, fn)) for c in group["candidates"] for p, _, fn in [c.partition("::")]})
sizes = tuple((p, sum(len(x) + 1 for x in t), len(t)) for p, t in sorted(texts.items()) if t is not None)
verify = family if family in OPERATIONS and source in VERIFY_SOURCES else ""
out.append(Unit(_opaque(repo, pin, family), source, ref, group["side"], repo, pin, verify, tuple(candidates), sizes=sizes))
return out
def _reader(cache: Path, source: str, ref: str, pin: str) -> Any:
"""Lines of a path at the pin: the case clone (populations), else the recipe checkout (dev-php)."""
from ..learn.labels import Clone, LabelSourceError
if source == "dev-php":
root = cache / "repos" / ref.split(":")[0] / pin[:12]
return lambda path: (root / path).read_text(encoding="utf-8", errors="replace").splitlines() if (root / path).is_file() else None
try:
clone = Clone(cache / "independent" / ref)
except LabelSourceError:
return lambda path: None
return lambda path: clone.lines(pin, path)
# --- manifests -------------------------------------------------------------------------------------------------------
def _git_workspace(unit: Unit) -> dict[str, Any]:
doc = _doc("Workspace", _ax_name(unit.name, "pin"), {"git": [{"name": REPO, "repo": unit.repo, "dir": REPO, "depth": 1}]}, ATESPACE)
doc["metadata"]["annotations"] = {GIT_COMMITS_ANNOTATION: f"{REPO}={unit.pin}"}
return doc
def _files_workspace(name: str, files: Mapping[str, str]) -> dict[str, Any]:
return _doc("Workspace", name, {"files": [{"path": k, "content": v} for k, v in sorted(files.items())]}, ATESPACE)
def _verify_docs(unit: Unit, templates: Mapping[str, Task], usd_per_hunt: float) -> tuple[list[dict], list[dict], list[dict]]:
"""(Workspaces, Tasks, Run entries) of one verify unit."""
asked = list(unit.verify or unit.candidates)
candidates = {"family": unit.family, "candidates": [list(c) for c in asked]}
hunts = max(len(units_of(asked)), 1)
budget = {"usd": math.floor(hunts * usd_per_hunt * 1000) / 1000, "calls": hunts * CALLS_PER_HUNT}
on_repo = {"OUSAST_WORKSPACE_DIR": f"{CASE_PATH}/{REPO}"}
workspaces: list[dict] = []
tasks: list[dict] = []
entries: list[dict] = []
if unit.files:
files = {f"{REPO}/{path}": text for path, text in unit.files} | {"inputs/candidates.json": _json(candidates)}
workspace = _files_workspace(_ax_name(unit.name, "side"), files)
workspaces.append(workspace)
bindings = [(workspace["metadata"]["name"], CASE_PATH)]
inputs = f"{CASE_PATH}/inputs/candidates.json"
else:
pinned = _git_workspace(unit)
functions = [{"path": p, "function": fn, "line": ln} for p, fn, ln in asked]
files_doc = _files_workspace(
_ax_name(unit.name, "inputs"), {"candidates.json": _json(candidates), "functions.json": _json(functions)}
)
workspaces += [pinned, files_doc]
bindings = [(pinned["metadata"]["name"], CASE_PATH), (files_doc["metadata"]["name"], INPUTS_PATH)]
inputs = f"{INPUTS_PATH}/candidates.json"
facts_env = {**on_repo, "OUSAST_INPUT_CANDIDATES": f"{INPUTS_PATH}/functions.json"}
tasks.append(_task(templates["repo-facts"], f"repo-facts-{unit.name}", facts_env, bindings))
entries.append({"name": f"{unit.name}-facts", "task": f"repo-facts-{unit.name}", "outputs": ["facts.json", "summary.json"],
"budget": {"usd": 0, "calls": 0}, "serialize": "repo-facts"}) # fmt: skip
for label in ("a", "b"):
env = {**on_repo, "OUSAST_INPUT_CANDIDATES": inputs, "OUSAST_PASS": label}
tasks.append(_task(templates["verify"], f"verify-{label}-{unit.name}", env, bindings))
entry: dict[str, Any] = {"name": f"{unit.name}-v{label}", "task": f"verify-{label}-{unit.name}",
"outputs": ["units.jsonl", "summary.json"], "budget": dict(budget)} # fmt: skip
if not unit.files:
entry["inputs"] = {"facts": f"{unit.name}-facts/facts.json"}
entries.append(entry)
tasks.append(
_task(templates["agree"], f"agree-{unit.name}", {"OUSAST_INPUT_CANDIDATES": inputs}, bindings[-1:] if not unit.files else bindings)
)
entries.append({
"name": f"{unit.name}-agree", "task": f"agree-{unit.name}",
"inputs": {"pass_a": f"{unit.name}-va/units.jsonl", "pass_b": f"{unit.name}-vb/units.jsonl"},
"outputs": ["agreed.json", "disputed.json", "summary.json"], "budget": {"usd": 0, "calls": 0},
}) # fmt: skip
return workspaces, tasks, entries
def _roles_estimate(sizes: Iterable[tuple[int, int]]) -> tuple[float, int]:
"""(usd, chunks) of classifying files of ``(characters, lines)``: input at ~3.5 characters a token plus the
prompt, a generous output."""
usd, chunks = 0.0, 0
for characters, count in sizes:
lines = max(count, 1)
n = math.ceil(lines / CHUNK_LINES)
tokens_in = (characters + 8 * lines) / 3.5 + 400 * n
usd += tokens_in * PRICES["input_per_m"] / 1e6 + n * ROLES_OUTPUT_TOKENS * PRICES["output_per_m"] / 1e6
chunks += n
return usd, chunks
def _kind(unit: Unit) -> str:
return "excerpt" if unit.files else "repository"
def _inline(unit: Unit, *, roles: bool = False) -> int:
"""Bytes of the unit's files a Workspace would carry inline (roles: its candidate files only)."""
return sum(len(text.encode("utf-8")) for path, text in unit.files if not roles or path in unit.paths)
def oversized(unit: Unit) -> str | None:
"""Why a unit cannot run on ax, or None: its files exceed :data:`MAX_INLINE_BYTES`."""
return f"excerpt {_inline(unit)} bytes over the {MAX_INLINE_BYTES}-byte inline limit" if _inline(unit) > MAX_INLINE_BYTES else None
def _roles_groups(units: Sequence[Unit]) -> list[list[Unit]]:
groups: list[list[Unit]] = [[]]
size = 0
for unit in units:
weight = _inline(unit, roles=True)
if groups[-1] and (len(groups[-1]) >= ROLES_GROUP_FILES or size + weight > MAX_INLINE_BYTES):
groups.append([])
size = 0
groups[-1].append(unit)
size += weight
return [g for g in groups if g]
def render_harvest(
units: Sequence[Unit], templates: Mapping[str, Task], name: str, command: str, *, ceiling: float = 10.0
) -> dict[str, str]:
"""Relative path under the plane root -> file text: both Runs, their Workspaces and Tasks, and ``units.json``.
A population file whose size is unknown is estimated at :data:`UNKNOWN_SIZE`."""
header = f"# generated by `{command}`; do not edit, regenerate.\n"
out: dict[str, str] = {}
verify_units = [u for u in units if u.family and oversized(u) is None]
hunts = sum(max(len(units_of(list(u.verify or u.candidates))), 1) for u in verify_units)
wanted = 2 * sum(max(len(units_of(list(u.verify or u.candidates))), 1) * USD_PER_HUNT[_kind(u)] for u in verify_units) # passes a, b
scale = min(1.0, ceiling * 0.98 / wanted) if wanted else 1.0
workspaces: list[dict] = []
tasks: list[dict] = []
entries: list[dict] = []
for unit in verify_units:
w, t, e = _verify_docs(unit, templates, USD_PER_HUNT[_kind(unit)] * scale)
workspaces += w
tasks += t
entries += e
out.update(_run_files(header, f"{name}-verify", workspaces, tasks, entries, f"{len(verify_units)} units, {hunts} hunts per pass"))
emitted = {w["metadata"]["name"] for w in workspaces} # a pin Workspace both Runs bind is written once
workspaces, tasks, entries, estimates = [], [], [], []
for group in _roles_groups([u for u in units if u.files and _inline(u, roles=True) <= MAX_INLINE_BYTES]):
files = {f"{REPO}/{u.name}/{p}": t for u in group for p, t in u.files if p in u.paths}
listing = sorted(f.removeprefix(f"{REPO}/") for f in files)
workspace = _files_workspace(_ax_name(name, "roles", group[0].name), files | {"inputs/files.json": _json(listing)})
workspaces.append(workspace)
estimates.append(_roles_estimate((len(text), len(text.splitlines())) for text in files.values()))
bound = [(workspace["metadata"]["name"], CASE_PATH)]
env = {"OUSAST_WORKSPACE_DIR": f"{CASE_PATH}/{REPO}", "OUSAST_INPUT_FILES": f"{CASE_PATH}/inputs/files.json"}
tasks.append(_task(templates["roles"], f"roles-{group[0].name}", env, bound))
entries.append(
{"name": f"{group[0].name}-roles", "task": f"roles-{group[0].name}", "outputs": ["roles.json", "units.jsonl", "summary.json"]}
)
seen: set[tuple[str, str]] = set()
for unit in (u for u in units if not u.files):
if (unit.repo, unit.pin) in seen:
continue
seen.add((unit.repo, unit.pin))
same = [u for u in units if not u.files and (u.repo, u.pin) == (unit.repo, unit.pin)]
paths = sorted({p for u in same for p in u.paths})
known = {p: (chars, lines) for u in same for p, chars, lines in u.sizes}
pinned = _git_workspace(unit)
files_doc = _files_workspace(_ax_name(unit.name, "roles-in"), {"files.json": _json(paths)})
workspaces += [w for w in (pinned, files_doc) if w["metadata"]["name"] not in emitted]
estimates.append(_roles_estimate(known.get(p, UNKNOWN_SIZE) for p in paths))
bound = [(pinned["metadata"]["name"], CASE_PATH), (files_doc["metadata"]["name"], INPUTS_PATH)]
env = {"OUSAST_WORKSPACE_DIR": f"{CASE_PATH}/{REPO}", "OUSAST_INPUT_FILES": f"{INPUTS_PATH}/files.json"}
tasks.append(_task(templates["roles"], f"roles-{unit.name}", env, bound))
entries.append(
{"name": f"{unit.name}-roles", "task": f"roles-{unit.name}", "outputs": ["roles.json", "units.jsonl", "summary.json"]}
)
wanted = sum(usd for usd, _ in estimates) * ROLES_HEADROOM
scale = min(1.0, ceiling * 0.98 / wanted) if wanted else 1.0
for entry, (usd, chunks) in zip(entries, estimates, strict=True):
entry["budget"] = {"usd": max(math.floor(usd * ROLES_HEADROOM * scale * 1000) / 1000, 0.001), "calls": chunks * 2 + 2}
out.update(_run_files(header, f"{name}-roles", workspaces, tasks, entries, f"{len(entries)} roles tasks"))
index = {u.name: u.index_row() | ({"skipped": oversized(u)} if oversized(u) else {}) for u in units}
out["units.json"] = json.dumps(index, indent=1, sort_keys=True) + "\n"
return out
def _run_files(header: str, run: str, workspaces: list[dict], tasks: list[dict], entries: list[dict], note: str) -> dict[str, str]:
total = sum(float(e["budget"]["usd"]) for e in entries)
metadata = {"name": run, "annotations": {"openultrasast.io/population": "harvest", "openultrasast.io/split": "learn"}}
doc = {"apiVersion": RUN_API_VERSION, "kind": "Run", "metadata": metadata, "spec": {"tasks": entries}}
files = {f"workspaces/{run}.yaml": _dump(header, workspaces), f"tasks/{run}.yaml": _dump(header, tasks)}
files[f"runs/{run}.yaml"] = _dump(header + f"# {note}; usd ceiling {total:.3f}.\n", [doc])
return files
def write_harvest(
labels: Path, plane: Path, *, name: str, command: str, catalog: Path, cache: Path, templates: Path, ceiling: float = 10.0
) -> dict[str, str]:
"""Build the units, render, write under ``plane`` (with the Model manifests copied beside); returns the rendered
files."""
from .manifests import load_manifests
units = pair_units(labels, catalog) + population_units(labels, cache)
loaded = load_manifests([templates / "tasks" / f"{n}.yaml" for n in ("repo-facts", "verify", "agree", "roles")]).tasks
files = render_harvest(units, loaded, name, command, ceiling=ceiling)
for path in sorted((templates / "models").glob("*.yaml")):
files[f"models/{path.name}"] = path.read_text(encoding="utf-8")
for relative, text in files.items():
target = plane / relative
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(text, encoding="utf-8")
yaml.safe_load_all(files[f"runs/{name}-verify.yaml"]) # parse check
return files
+12
View File
@@ -156,6 +156,18 @@ def test_roles_feature_part(tmp_path: Path) -> None:
assert [f.name for f in functions_of("a.php", lines, "php")][:2] == ["run_sql", "fetch_rows"]
def test_a_language_without_declarations_is_none_not_failed() -> None:
"""C has no declaration pattern: the span and wrapper roles cannot run there (no coverage), which is not a broken
instrument (harvest 2026-10-01: every memory pair read as `failed`)."""
from openultrasast.learn.features import source_part
from openultrasast.learn.roles import RoleSet
lines = ["int f(char *s) {", " return strcpy(buf, s);", "}"]
assert source_part(lines, "c", "f").state == "none"
assert roles_part(None, RoleSet(sources=("inferred_wrapper",)), "c").state == "none"
assert source_part(["def g():", " pass"], "python", "missing").state == "failed"
def test_build_for_scan_records_the_roles_instrument(tmp_path: Path) -> None:
from openultrasast.findings import quick_scan_findings
from openultrasast.preprocess import preprocess_repository
+100
View File
@@ -0,0 +1,100 @@
"""The decision engine's harvest Runs (learned-decision-engine task 5): `ousast plane harvest`'s generator."""
from __future__ import annotations
import json
import subprocess
from pathlib import Path
import yaml
from openultrasast.plane import reconciler
from openultrasast.plane.harvest import MAX_INLINE_BYTES, Unit, blob_sha1, declaration_line, render_harvest
from openultrasast.plane.manifests import load_manifests
PLANE = Path("plane")
PIN = "0123456789abcdef0123456789abcdef01234567"
def _templates() -> dict:
return load_manifests([PLANE / "tasks" / f"{n}.yaml" for n in ("repo-facts", "verify", "agree", "roles")]).tasks
def _units() -> list[Unit]:
code = "import os\n\ndef run(cmd):\n os.system(cmd)\n"
excerpt = Unit("h-aaaaaaaaaaaa", "pairs", "x-cwe-078-os-system", "vulnerable", "pairs/github/x", blob_sha1(code), "injection",
(("app.py", "run", 3),), (("app.py", code),)) # fmt: skip
roles_only = Unit("h-bbbbbbbbbbbb", "pairs", "y-cwe-416", "fixed", "pairs/vfc/y", blob_sha1("int f(){}\n"), "",
(("f.c", "f", 1),), (("f.c", "int f(){}\n"),)) # fmt: skip
big = "x = 1\n" * (MAX_INLINE_BYTES // 6 + 10)
oversized = Unit(
"h-cccccccccccc",
"pairs",
"z",
"vulnerable",
"pairs/github/z",
blob_sha1(big),
"injection",
(("big.py", GLOBAL_, 1),),
(("big.py", big),),
)
repo = Unit("h-dddddddddddd", "population-v1", "case", "vulnerable", "https://github.com/o/r", PIN, "injection",
(("a.py", "q", 10), ("b.py", "r", 20)), sizes=(("a.py", 4000, 100), ("b.py", 8000, 900))) # fmt: skip
return [excerpt, roles_only, oversized, repo]
GLOBAL_ = "<global>"
def _render(tmp_path: Path, ceiling: float = 10.0) -> tuple[dict[str, str], Path]:
files = render_harvest(_units(), _templates(), "hv", "test", ceiling=ceiling)
for rel, text in files.items():
(tmp_path / rel).parent.mkdir(parents=True, exist_ok=True)
(tmp_path / rel).write_text(text)
(tmp_path / "models").mkdir()
(tmp_path / "models" / "deepseek-flash.yaml").write_text((PLANE / "models" / "deepseek-flash.yaml").read_text())
return files, tmp_path
def test_verify_run_shape_and_ceiling(tmp_path: Path) -> None:
files, root = _render(tmp_path, ceiling=0.05)
run, manifests = reconciler.load_run(root / "runs" / "hv-verify.yaml")
names = [t.name for t in run.tasks]
assert names == [
"h-aaaaaaaaaaaa-va", "h-aaaaaaaaaaaa-vb", "h-aaaaaaaaaaaa-agree",
"h-dddddddddddd-facts", "h-dddddddddddd-va", "h-dddddddddddd-vb", "h-dddddddddddd-agree",
] # fmt: skip
assert sum(float(t.budget.usd) for t in run.tasks) <= 0.05 # scaled down to the ceiling
assert all(float(t.budget.usd) == 0 for t in run.tasks if t.name.endswith(("-agree", "-facts")))
excerpt = manifests.tasks["verify-a-h-aaaaaaaaaaaa"]
env = {e.name: e.value for e in excerpt.env}
assert env["OUSAST_WORKSPACE_DIR"].endswith("/repo") and "/inputs/" in env["OUSAST_INPUT_CANDIDATES"]
workspace = manifests.workspaces["h-aaaaaaaaaaaa-side"]
paths = {f.path for f in workspace.files}
assert paths == {"repo/app.py", "inputs/candidates.json"} # candidates outside the hunt's root
assert "cwe" not in yaml.safe_dump([workspace.metadata.name, *paths]) # the pair's name never reaches the sandbox
units = json.loads(files["units.json"])
assert "skipped" in units["h-cccccccccccc"] and "skipped" not in units["h-aaaaaaaaaaaa"]
agree = {e.name: e.value for e in manifests.tasks["agree-h-dddddddddddd"].env}
assert "OUSAST_INPUT_SITES" not in agree # no declared sites: no site_match is computed
def test_roles_run_groups_excerpts_and_pins(tmp_path: Path) -> None:
_, root = _render(tmp_path)
run, manifests = reconciler.load_run(root / "runs" / "hv-roles.yaml")
assert len(run.tasks) == 2 and sum(float(t.budget.usd) for t in run.tasks) <= 10.0
grouped = next(w for w in manifests.workspaces.values() if w.metadata.name.startswith("hv-roles"))
listing = json.loads(next(f.content for f in grouped.files if f.path == "inputs/files.json"))
assert listing == ["h-aaaaaaaaaaaa/app.py", "h-bbbbbbbbbbbb/f.c"] # the oversized excerpt is left out
assert sum(len(f.content) for f in grouped.files) <= MAX_INLINE_BYTES + 200
pin = manifests.tasks["roles-h-dddddddddddd"]
assert {e.name: e.value for e in pin.env}["OUSAST_INPUT_FILES"].endswith("/files.json")
def test_blob_sha1_is_gits_and_declaration_lines() -> None:
text = "def a():\n pass\n\ndef b():\n pass\n"
done = subprocess.run(["git", "hash-object", "--stdin"], input=text, capture_output=True, text=True, check=True)
assert blob_sha1(text) == done.stdout.strip()
lines = text.splitlines()
assert declaration_line(lines, "m.py", "b") == 4
assert declaration_line(lines, "m.py", "missing") == 1 and declaration_line(None, "m.py", "a") == 1