mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
unseen-eval: calibrated replay deadline + native agreement check, docker-lane diagnostics, proof 4.4 record; engine runner cleans up fetched sources
gVisor replays exhausted the hook's 30 s deadline in the snapshot step on a 1,725-file repository; cluster replays now use 30 s x a measured slowdown factor, verified on a native sample. The engine runner deletes each pin's fetched source (1.8 GB had accumulated), caps the cache at 500 MB and refuses below 1.5 GB free. Proof 4.4: two engine pins via the generic dispatcher match the VM baseline exactly. Implemented with Codex. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5dc86c2f3f
commit
6d27c9bfcf
@@ -219,6 +219,22 @@ what the user sees (30 s) and what the engine could catch given time (300 s), so
|
|||||||
told apart from a miss by the engine. It is a measurement arm, not a proposed default: adopting a longer hook
|
told apart from a miss by the engine. It is a measurement arm, not a proposed default: adopting a longer hook
|
||||||
deadline would be a separate decision under 7.2.
|
deadline would be a separate decision under 7.2.
|
||||||
|
|
||||||
|
**Decision 4.0a: calibrated cluster deadline and native check** (maintainer, 2026-10-05).
|
||||||
|
The gVisor proof exhausted 30 seconds in snapshot preparation on a 1,725-file repository.
|
||||||
|
Cluster replays therefore scale the 30/300-second arms by the median paired cluster/native
|
||||||
|
time through the quick tier, rounded to integer seconds. Native runc replays retain 30/300 seconds.
|
||||||
|
A deterministic sample (default 20, or all of a smaller proof manifest) runs both lanes at
|
||||||
|
600 seconds for calibration. Report per-change stage times and median slowdown with bootstrap
|
||||||
|
95% intervals for time through quick, engine build and engine queries separately. Incomplete
|
||||||
|
timings or degraded scans invalidate calibration. Records contain counts, times and opaque digests.
|
||||||
|
|
||||||
|
A deterministic native sample accompanies the cluster slice. Compare whether each push flags
|
||||||
|
anything, with a Wilson 95% interval. If its lower bound is below 95%, or any sample pair is
|
||||||
|
missing/failed, flag the run and exclude cluster results from the baseline. Disagreements name
|
||||||
|
change IDs only. The scorer reports applied deadlines and factors. Docker pulls explicitly after
|
||||||
|
checking its data-root free space against an unpacked image size upper bound plus 1 GiB; failures
|
||||||
|
retain sanitized stderr tails and exit codes. See the unseen README for coordinator commands.
|
||||||
|
|
||||||
**Decision 4.1: catch, per family.** A finding counts if its family is the change's family. Engine streams carry a
|
**Decision 4.1: catch, per family.** A finding counts if its family is the change's family. Engine streams carry a
|
||||||
family; a quick-rule match maps its CWE through the family CWE table. Its head location must also be in the
|
family; a quick-rule match maps its CWE through the family CWE table. Its head location must also be in the
|
||||||
vulnerable function or within 10 lines of the change's head-side hunks (protocol v1's window).
|
vulnerable function or within 10 lines of the change's head-side hunks (protocol v1's window).
|
||||||
|
|||||||
+106
-5
@@ -387,7 +387,15 @@ def dispatch(items, workload, out, executors, *, lanes=("ax", "ax", "docker"), o
|
|||||||
for artifact in attempt_output.iterdir():
|
for artifact in attempt_output.iterdir():
|
||||||
if artifact.is_file():
|
if artifact.is_file():
|
||||||
shutil.copyfile(artifact, output / artifact.name)
|
shutil.copyfile(artifact, output / artifact.name)
|
||||||
attempts.append({"lane": lane, "status": record.get("status"), "attempt": attempt, "transport": record.get("ax_attempts", [])})
|
attempts.append(
|
||||||
|
{
|
||||||
|
"lane": lane,
|
||||||
|
"status": record.get("status"),
|
||||||
|
"attempt": attempt,
|
||||||
|
"transport": record.get("ax_attempts", []),
|
||||||
|
"docker_errors": record.get("docker_errors", []),
|
||||||
|
}
|
||||||
|
)
|
||||||
if record.get("status") not in {"sandbox_failure", "instrument_failure"}:
|
if record.get("status") not in {"sandbox_failure", "instrument_failure"}:
|
||||||
break
|
break
|
||||||
return {**record, "attempts": (previous or {}).get("attempts", []) + attempts, "input_digest": item.input_digest, "done": True}
|
return {**record, "attempts": (previous or {}).get("attempts", []) + attempts, "input_digest": item.input_digest, "done": True}
|
||||||
@@ -402,9 +410,76 @@ def dispatch(items, workload, out, executors, *, lanes=("ax", "ax", "docker"), o
|
|||||||
return schedule(pending, lanes, out, execute, complete, fallbacks=fallbacks, overflow=overflow)
|
return schedule(pending, lanes, out, execute, complete, fallbacks=fallbacks, overflow=overflow)
|
||||||
|
|
||||||
|
|
||||||
|
def docker_diagnostic(text):
|
||||||
|
"""Bound diagnostics and remove bearer URLs, environment secrets and assignments."""
|
||||||
|
text = str(text or "")
|
||||||
|
for key, value in os.environ.items():
|
||||||
|
if len(value) >= 4 and any(word in key.upper() for word in ("TOKEN", "SECRET", "PASSWORD", "CREDENTIAL", "KEY")):
|
||||||
|
text = text.replace(value, "[redacted]")
|
||||||
|
text = re.sub(r"https?://[^\s\"']+", "[redacted-url]", text)
|
||||||
|
text = re.sub(
|
||||||
|
r"(?i)(authorization|bearer|password|token|secret|signature|credential|[\w-]*key)\s*[:= ]\s*[^\s,;]+", r"\1=[redacted]", text
|
||||||
|
)
|
||||||
|
return "\n".join(text.splitlines()[-10:])[-4000:]
|
||||||
|
|
||||||
|
|
||||||
class DockerLane(AXLane):
|
class DockerLane(AXLane):
|
||||||
"""VM lane uses the same presigned-object protocol and pinned replay image."""
|
"""VM lane uses the same presigned-object protocol and pinned replay image."""
|
||||||
|
|
||||||
|
def __init__(self, *args, runner=None, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.runner = runner or subprocess.run
|
||||||
|
self._prepared = False
|
||||||
|
|
||||||
|
def __call__(self, item, output, attempt):
|
||||||
|
self._diagnostics = []
|
||||||
|
try:
|
||||||
|
if not self._prepared:
|
||||||
|
self._prepare_image()
|
||||||
|
self._prepared = True
|
||||||
|
except (ValueError, OSError, subprocess.SubprocessError) as exc:
|
||||||
|
if not self._diagnostics:
|
||||||
|
self._diagnostics.append({"step": "preflight", "exit_code": None, "stderr": docker_diagnostic(str(exc))})
|
||||||
|
record = {"status": "instrument_failure", "docker_errors": self._diagnostics}
|
||||||
|
write_json(output / "docker-error.json", record)
|
||||||
|
return record
|
||||||
|
try:
|
||||||
|
record = super().__call__(item, output, attempt)
|
||||||
|
finally:
|
||||||
|
if self._diagnostics:
|
||||||
|
write_json(output / "docker-error.json", {"docker_errors": self._diagnostics})
|
||||||
|
if self._diagnostics:
|
||||||
|
record["docker_errors"] = self._diagnostics
|
||||||
|
return record
|
||||||
|
|
||||||
|
def _run_docker(self, command, timeout):
|
||||||
|
try:
|
||||||
|
done = self.runner(command, capture_output=True, text=True, timeout=timeout)
|
||||||
|
except (OSError, subprocess.SubprocessError) as exc:
|
||||||
|
self._diagnostics.append(
|
||||||
|
{"step": command[1], "exit_code": None, "stderr": docker_diagnostic(getattr(exc, "stderr", "") or type(exc).__name__)}
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
if done.returncode:
|
||||||
|
self._diagnostics.append({"step": command[1], "exit_code": done.returncode, "stderr": docker_diagnostic(done.stderr)})
|
||||||
|
raise ValueError(f"docker {command[1]} failed (exit {done.returncode})")
|
||||||
|
return done
|
||||||
|
|
||||||
|
def _prepare_image(self):
|
||||||
|
# Docker's data root, not the coordinator checkout, is where layers consume space.
|
||||||
|
root = self._run_docker(["docker", "info", "--format", "{{.DockerRootDir}}"], 30).stdout.strip()
|
||||||
|
size = getattr(self.args, "docker_image_bytes", None)
|
||||||
|
if not isinstance(size, int) or size <= 0:
|
||||||
|
raise ValueError("set --docker-image-bytes to the unpacked image size upper bound before pulling")
|
||||||
|
free = self.runner(["df", "-B1", "--output=avail", root], capture_output=True, text=True, timeout=30)
|
||||||
|
if free.returncode:
|
||||||
|
raise ValueError("cannot check free space on Docker data root")
|
||||||
|
available = int(free.stdout.splitlines()[-1].strip())
|
||||||
|
required = size + 1024**3
|
||||||
|
if available < required:
|
||||||
|
raise ValueError(f"refusing Docker pull: free bytes {available} below image + 1 GiB ({required})")
|
||||||
|
self._run_docker(["docker", "pull", self.workload.image], 900)
|
||||||
|
|
||||||
def _execute_attempt(self, item, output, attempt, attempts):
|
def _execute_attempt(self, item, output, attempt, attempts):
|
||||||
# Reuse all object/validation/cleanup logic, replacing only the task runner.
|
# Reuse all object/validation/cleanup logic, replacing only the task runner.
|
||||||
import copy
|
import copy
|
||||||
@@ -417,18 +492,44 @@ class DockerLane(AXLane):
|
|||||||
def _docker_command(self, *args, manifest=None, timeout=30):
|
def _docker_command(self, *args, manifest=None, timeout=30):
|
||||||
if args[0] == "apply":
|
if args[0] == "apply":
|
||||||
self._docker_name = manifest["metadata"]["name"]
|
self._docker_name = manifest["metadata"]["name"]
|
||||||
command = ["docker", "run", "-d", "--name", self._docker_name, "--entrypoint", manifest["spec"]["command"][0]]
|
self._docker_created = True # Even a failed run can leave a created container.
|
||||||
|
command = [
|
||||||
|
"docker",
|
||||||
|
"run",
|
||||||
|
"-d",
|
||||||
|
"--runtime",
|
||||||
|
"runc",
|
||||||
|
"--pull",
|
||||||
|
"never",
|
||||||
|
"--name",
|
||||||
|
self._docker_name,
|
||||||
|
"--entrypoint",
|
||||||
|
manifest["spec"]["command"][0],
|
||||||
|
]
|
||||||
for env in manifest["spec"]["env"]:
|
for env in manifest["spec"]["env"]:
|
||||||
command += ["-e", env["name"] + "=" + env["value"]]
|
command += ["-e", env["name"] + "=" + env["value"]]
|
||||||
command += [manifest["spec"]["image"], *manifest["spec"]["command"][1:], "--heap-profile", "vm"]
|
command += [manifest["spec"]["image"], *manifest["spec"]["command"][1:], "--heap-profile", "vm"]
|
||||||
elif args[0] == "get":
|
elif args[0] == "get":
|
||||||
command = ["docker", "inspect", "--format", "{{.State.Running}} {{.State.ExitCode}}", self._docker_name]
|
command = ["docker", "inspect", "--format", "{{.State.Running}} {{.State.ExitCode}}", self._docker_name]
|
||||||
else:
|
else:
|
||||||
|
if not getattr(self, "_docker_created", False):
|
||||||
|
return subprocess.CompletedProcess([], 0, "", "")
|
||||||
command = ["docker", "rm", "-f", self._docker_name or args[2]]
|
command = ["docker", "rm", "-f", self._docker_name or args[2]]
|
||||||
done = subprocess.run(command, capture_output=True, text=True, timeout=timeout)
|
try:
|
||||||
if done.returncode:
|
done = self._run_docker(command, timeout)
|
||||||
raise ValueError("docker command failed")
|
except ValueError:
|
||||||
|
if args[0] == "delete" and "No such container" in self._diagnostics[-1]["stderr"]:
|
||||||
|
self._diagnostics.pop()
|
||||||
|
return subprocess.CompletedProcess(command, 0, "", "")
|
||||||
|
raise
|
||||||
|
if args[0] == "apply":
|
||||||
|
self._docker_created = True
|
||||||
if args[0] == "get":
|
if args[0] == "get":
|
||||||
phase = "Running" if done.stdout.startswith("true") else "Failed"
|
phase = "Running" if done.stdout.startswith("true") else "Failed"
|
||||||
|
if phase == "Failed":
|
||||||
|
logs = self._run_docker(["docker", "logs", "--tail", "10", self._docker_name], timeout)
|
||||||
|
self._diagnostics.append(
|
||||||
|
{"step": "container", "exit_code": int(done.stdout.split()[-1]), "stderr": docker_diagnostic(logs.stderr)}
|
||||||
|
)
|
||||||
done.stdout = json.dumps({"metadata": {"name": self._docker_name}, "status": {"phase": phase}})
|
done.stdout = json.dumps({"metadata": {"name": self._docker_name}, "status": {"phase": phase}})
|
||||||
return done
|
return done
|
||||||
|
|||||||
@@ -179,9 +179,11 @@ def make_plan(
|
|||||||
class Inputs:
|
class Inputs:
|
||||||
"""Resolve spent corpora; fetch pair source commits into the host cache."""
|
"""Resolve spent corpora; fetch pair source commits into the host cache."""
|
||||||
|
|
||||||
def __init__(self, root, cache, allow_excerpt_fallback=False):
|
def __init__(self, root, cache, allow_excerpt_fallback=False, *, keep_sources=False, source_cache_mb=500):
|
||||||
self.cache = cache
|
self.cache = cache
|
||||||
self.allow_excerpt_fallback = allow_excerpt_fallback
|
self.allow_excerpt_fallback = allow_excerpt_fallback
|
||||||
|
self.keep_sources = keep_sources
|
||||||
|
self.source_cache_bytes = source_cache_mb * 1024 * 1024
|
||||||
self.catalog_rows = defaultdict(list)
|
self.catalog_rows = defaultdict(list)
|
||||||
for catalog in (root / "benchmarks/pairs").rglob("catalog.toml"):
|
for catalog in (root / "benchmarks/pairs").rglob("catalog.toml"):
|
||||||
for item in tomllib.loads(catalog.read_text()).get("pair", []):
|
for item in tomllib.loads(catalog.read_text()).get("pair", []):
|
||||||
@@ -259,8 +261,30 @@ class Inputs:
|
|||||||
raise ValueError(f"missing catalog repository for {case.name}")
|
raise ValueError(f"missing catalog repository for {case.name}")
|
||||||
return repo, parent if side == "vuln" else commit
|
return repo, parent if side == "vuln" else commit
|
||||||
|
|
||||||
|
def source_path(self, repo, commit, roots):
|
||||||
|
return self.cache / "trace-sources" / digest([repo, commit, roots])
|
||||||
|
|
||||||
|
def prepare_source_cache(self):
|
||||||
|
cache = self.cache / "trace-sources"
|
||||||
|
cache.mkdir(parents=True, exist_ok=True)
|
||||||
|
entries = []
|
||||||
|
for entry in cache.iterdir():
|
||||||
|
if entry.is_dir() and not entry.is_symlink():
|
||||||
|
size = sum(p.stat().st_size for p in entry.rglob("*") if not p.is_symlink() and p.is_file())
|
||||||
|
entries.append((entry.stat().st_mtime_ns, entry, size))
|
||||||
|
total = sum(size for _, _, size in entries)
|
||||||
|
for _, entry, size in sorted(entries):
|
||||||
|
if total <= self.source_cache_bytes:
|
||||||
|
break
|
||||||
|
shutil.rmtree(entry)
|
||||||
|
total -= size
|
||||||
|
free = shutil.disk_usage(cache).free
|
||||||
|
if free < 1.5 * 1024**3:
|
||||||
|
raise OSError(f"refusing source fetch: {free / 1024**3:.2f} GB free at {cache}; at least 1.5 GB required")
|
||||||
|
|
||||||
def fetch_source(self, repo, commit, roots):
|
def fetch_source(self, repo, commit, roots):
|
||||||
clone = self.cache / "trace-sources" / digest([repo, commit, roots])
|
self.prepare_source_cache()
|
||||||
|
clone = self.source_path(repo, commit, roots)
|
||||||
clone.mkdir(parents=True, exist_ok=True)
|
clone.mkdir(parents=True, exist_ok=True)
|
||||||
url = repo if repo.startswith("https://") else f"https://github.com/{repo.removesuffix('.git')}.git"
|
url = repo if repo.startswith("https://") else f"https://github.com/{repo.removesuffix('.git')}.git"
|
||||||
env = {**os.environ, "GIT_TERMINAL_PROMPT": "0"}
|
env = {**os.environ, "GIT_TERMINAL_PROMPT": "0"}
|
||||||
@@ -348,6 +372,13 @@ class Inputs:
|
|||||||
for case, side in resolved:
|
for case, side in resolved:
|
||||||
_materialize_side(target, case, side=side)
|
_materialize_side(target, case, side=side)
|
||||||
return {"mode": "excerpt-fallback", "reason": str(exc), "repo": repo, "commit": commit}
|
return {"mode": "excerpt-fallback", "reason": str(exc), "repo": repo, "commit": commit}
|
||||||
|
finally:
|
||||||
|
# Only the exported copy is used by executors. Drop the clone even
|
||||||
|
# on a partial fetch, timeout, failed export or excerpt fallback.
|
||||||
|
if not self.keep_sources:
|
||||||
|
clone = self.source_path(repo, commit, roots)
|
||||||
|
if clone.exists():
|
||||||
|
shutil.rmtree(clone)
|
||||||
verify_functions(target, rows)
|
verify_functions(target, rows)
|
||||||
return {**proof, "mode": "source", "repo": repo, "commit": commit}
|
return {**proof, "mode": "source", "repo": repo, "commit": commit}
|
||||||
if all(r["source"] == "dev-php" for r in rows):
|
if all(r["source"] == "dev-php" for r in rows):
|
||||||
@@ -845,6 +876,8 @@ def main():
|
|||||||
parser.add_argument("--examples", type=Path, help="frozen example JSONL instead of the local memory store")
|
parser.add_argument("--examples", type=Path, help="frozen example JSONL instead of the local memory store")
|
||||||
parser.add_argument("--memory", type=Path, default=Path.home() / "ousast-results/plane/memory")
|
parser.add_argument("--memory", type=Path, default=Path.home() / "ousast-results/plane/memory")
|
||||||
parser.add_argument("--cache", type=Path, default=Path.home() / ".cache/openultrasast")
|
parser.add_argument("--cache", type=Path, default=Path.home() / ".cache/openultrasast")
|
||||||
|
parser.add_argument("--keep-sources", action="store_true", help="retain fetched sources after export (still subject to cache eviction)")
|
||||||
|
parser.add_argument("--source-cache-mb", type=int, default=500, help="maximum retained source MB before each fetch (default: 500)")
|
||||||
parser.add_argument("--out", type=Path, default=DEFAULT_OUT)
|
parser.add_argument("--out", type=Path, default=DEFAULT_OUT)
|
||||||
parser.add_argument("--image", help="default: openultrasast:dev for Docker; public 2.0.1 image for k8s/mixed")
|
parser.add_argument("--image", help="default: openultrasast:dev for Docker; public 2.0.1 image for k8s/mixed")
|
||||||
parser.add_argument("--executor", choices=("docker", "k8s", "k8s-jobs", "mixed", "queue", "ax", "mixed-ax"), default="docker")
|
parser.add_argument("--executor", choices=("docker", "k8s", "k8s-jobs", "mixed", "queue", "ax", "mixed-ax"), default="docker")
|
||||||
@@ -872,6 +905,8 @@ def main():
|
|||||||
parser.add_argument("--dry-run", action="store_true")
|
parser.add_argument("--dry-run", action="store_true")
|
||||||
parser.add_argument("--summary", action="store_true")
|
parser.add_argument("--summary", action="store_true")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
if args.source_cache_mb < 0:
|
||||||
|
parser.error("--source-cache-mb must be nonnegative")
|
||||||
if args.parallel is None:
|
if args.parallel is None:
|
||||||
args.parallel = 2 if args.executor in {"ax", "mixed-ax"} else 1
|
args.parallel = 2 if args.executor in {"ax", "mixed-ax"} else 1
|
||||||
if args.executor in {"ax", "mixed-ax"}:
|
if args.executor in {"ax", "mixed-ax"}:
|
||||||
@@ -912,7 +947,7 @@ def main():
|
|||||||
units = join_units(read_jsonl(args.units), examples, args.include_typescript)
|
units = join_units(read_jsonl(args.units), examples, args.include_typescript)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
parser.error(str(exc))
|
parser.error(str(exc))
|
||||||
inputs = Inputs(ROOT, args.cache, args.allow_excerpt_fallback)
|
inputs = Inputs(ROOT, args.cache, args.allow_excerpt_fallback, keep_sources=args.keep_sources, source_cache_mb=args.source_cache_mb)
|
||||||
# Resolve pair source names for leak-audit coverage before calculating the resume key.
|
# Resolve pair source names for leak-audit coverage before calculating the resume key.
|
||||||
for unit in units:
|
for unit in units:
|
||||||
if unit.get("excerpt") or unit["source"].startswith("advisory-fixes"):
|
if unit.get("excerpt") or unit["source"].startswith("advisory-fixes"):
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
{
|
||||||
|
"cleanup": "completed-before-checkpoint",
|
||||||
|
"commit": "5dc86c2f3f54b4dfb59060c88fb3ab5b307ef448",
|
||||||
|
"differences": 0,
|
||||||
|
"image": "ghcr.io/norandom/ousast-engine-task@sha256:b73c9bbd48fbf39c14ac013e5b0ff0a9d536b3d6388b50b1bd4cddfa22264b91",
|
||||||
|
"pins": [
|
||||||
|
{
|
||||||
|
"baseline_digest": "c12c675f816a24ab33a63336a21efdc93db10a37dcf83437d91bec6f728cef1f",
|
||||||
|
"equal": true,
|
||||||
|
"instrument": [
|
||||||
|
{
|
||||||
|
"bytes": 1761,
|
||||||
|
"cpg_bytes": 138723,
|
||||||
|
"jvm": [
|
||||||
|
{
|
||||||
|
"exit_code": null,
|
||||||
|
"heap_mb": 1700,
|
||||||
|
"seconds": 11.88592994,
|
||||||
|
"success": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exit_code": null,
|
||||||
|
"heap_mb": 1700,
|
||||||
|
"seconds": 35.821929360999995,
|
||||||
|
"success": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exit_code": null,
|
||||||
|
"heap_mb": 1700,
|
||||||
|
"seconds": 7.4493522819999995,
|
||||||
|
"success": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exit_code": null,
|
||||||
|
"heap_mb": 1700,
|
||||||
|
"seconds": 2.9803447330000026,
|
||||||
|
"success": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exit_code": null,
|
||||||
|
"heap_mb": 1700,
|
||||||
|
"seconds": 0.5151375319999971,
|
||||||
|
"success": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exit_code": null,
|
||||||
|
"heap_mb": 1700,
|
||||||
|
"seconds": 9.691500790999996,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pin_digest": "63c992b500590a9d4b643c9458df1d6cf71d770b06cc70594acbdba99b06c00b",
|
||||||
|
"record_digest": "c12c675f816a24ab33a63336a21efdc93db10a37dcf83437d91bec6f728cef1f",
|
||||||
|
"seconds": 158.85258329089265
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baseline_digest": "5a60ed069d99f9325a1d3f3a7612fcf0d853d4fabfd53add81151b051132a5ee",
|
||||||
|
"equal": true,
|
||||||
|
"instrument": [
|
||||||
|
{
|
||||||
|
"bytes": 1791,
|
||||||
|
"cpg_bytes": 138886,
|
||||||
|
"jvm": [
|
||||||
|
{
|
||||||
|
"exit_code": null,
|
||||||
|
"heap_mb": 1700,
|
||||||
|
"seconds": 7.656527941999999,
|
||||||
|
"success": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exit_code": null,
|
||||||
|
"heap_mb": 1700,
|
||||||
|
"seconds": 27.532885242,
|
||||||
|
"success": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exit_code": null,
|
||||||
|
"heap_mb": 1700,
|
||||||
|
"seconds": 9.524187851,
|
||||||
|
"success": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exit_code": null,
|
||||||
|
"heap_mb": 1700,
|
||||||
|
"seconds": 5.0155780199999995,
|
||||||
|
"success": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exit_code": null,
|
||||||
|
"heap_mb": 1700,
|
||||||
|
"seconds": 0.6809096819999993,
|
||||||
|
"success": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exit_code": null,
|
||||||
|
"heap_mb": 1700,
|
||||||
|
"seconds": 13.824248304000001,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pin_digest": "8bd2f48dfb2d00cfc5b60a5dee3a8c87e6970671121caa17221f2449c1254fb2",
|
||||||
|
"record_digest": "5a60ed069d99f9325a1d3f3a7612fcf0d853d4fabfd53add81151b051132a5ee",
|
||||||
|
"seconds": 142.5083134449087
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -289,3 +289,84 @@ proof is missing, or quick-tier findings differ. It does not convert a quick-onl
|
|||||||
without a JVM into an engine proof. Pool replays use `--lane mixed` (default) for the two
|
without a JVM into an engine proof. Pool replays use `--lane mixed` (default) for the two
|
||||||
AX slots and single VM overflow/fallback lane; `--arm long_deadline` changes only the
|
AX slots and single VM overflow/fallback lane; `--arm long_deadline` changes only the
|
||||||
hook deadline to 300 seconds and is a separate input digest.
|
hook deadline to 300 seconds and is a separate input digest.
|
||||||
|
|
||||||
|
### Deadline calibration and native agreement
|
||||||
|
|
||||||
|
The 2026-10-05 maintainer decision supersedes the unscaled cluster deadline above.
|
||||||
|
gVisor snapshot preparation can consume 30 seconds before any analysis. Calibrate
|
||||||
|
the pinned replay image against the VM's native Docker **runc** runtime first.
|
||||||
|
The default deterministic sample is the first 20 changes ordered by SHA-256 of
|
||||||
|
change ID; a smaller manifest (including the five-change proof) uses all changes.
|
||||||
|
Sampling never depends on manifest order.
|
||||||
|
|
||||||
|
`calibrate` runs both lanes with a 600-second hook deadline (override `--deadline`
|
||||||
|
if stages still time out). It reads resolution, provenance, preparation, snapshot,
|
||||||
|
and quick timings from the push artifact; snapshot is nested in preparation.
|
||||||
|
`quick_total` sums resolution + provenance + preparation + quick, without counting
|
||||||
|
snapshot twice. Engine build and query times sum across the hook's scans. Hook
|
||||||
|
wall time comes from the instrument block. Missing/nonpositive timings, deadline
|
||||||
|
exhaustion, degraded engine scans or failed instruments prevent a successful
|
||||||
|
calibration; failed changes are never silently dropped. Each factor is the median
|
||||||
|
of paired cluster/native ratios with a deterministic 2,000-resample percentile
|
||||||
|
bootstrap 95% interval. The measurement record contains opaque change digests,
|
||||||
|
counts and timings, with no repository names. Raw artifacts remain outside the repo.
|
||||||
|
|
||||||
|
Set `REPLAY_IMAGE` to the newly built replay image's pinned digest and
|
||||||
|
`REPLAY_IMAGE_BYTES` to an **unpacked image size upper bound in bytes** from the
|
||||||
|
image build. Do not use compressed registry layer size. Docker refuses to pull
|
||||||
|
without this bound or when `df` at its data root reports less than that bound plus
|
||||||
|
1 GiB free. It then explicitly pulls the image before issuing presigned task URLs.
|
||||||
|
Supply `--docker-image-bytes "$REPLAY_IMAGE_BYTES"` to VM and mixed replay commands,
|
||||||
|
including the earlier VM proof command.
|
||||||
|
Launch/pull failures retain exit codes and the last ten sanitized stderr lines in
|
||||||
|
`docker-error.json`, the result record and attempt history. URLs, known environment
|
||||||
|
secrets and credential assignments are redacted. The original VM failure's cause
|
||||||
|
remains unconfirmed until this diagnostic is captured; the old transport discarded it.
|
||||||
|
|
||||||
|
Coordinator commands (these run networked workloads; offline tests use fakes):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export PATH=/home/mc/Source/OpenUltraSAST/.venv/bin:$PATH
|
||||||
|
export PYTHONPATH=$PWD/src
|
||||||
|
export S3_ENDPOINT=https://files.because-security.com
|
||||||
|
CALIBRATION_RECORD="benchmarks/measurements/$(date +%F)-unseen-calibration/record.json"
|
||||||
|
python -m benchmarks.unseen.calibrate \
|
||||||
|
--manifest "$HOME/ousast-results/unseen/proof-05-inputs.json" \
|
||||||
|
--sample 20 --deadline 600 --image "$REPLAY_IMAGE" \
|
||||||
|
--docker-image-bytes "$REPLAY_IMAGE_BYTES" --kubeconfig "$AX_KUBECONFIG" \
|
||||||
|
--out "$HOME/ousast-results/unseen/calibration" --record "$CALIBRATION_RECORD"
|
||||||
|
|
||||||
|
python -m benchmarks.unseen.replay --manifest benchmarks/unseen/pool-p1.toml \
|
||||||
|
--pool p1 --slice 1 --arm default --lane ax --image "$REPLAY_IMAGE" \
|
||||||
|
--calibration "$CALIBRATION_RECORD" --native-sample 100 \
|
||||||
|
--docker-image-bytes "$REPLAY_IMAGE_BYTES" --kubeconfig "$AX_KUBECONFIG" \
|
||||||
|
--out "$HOME/ousast-results/unseen/p1/1/calibrated-default"
|
||||||
|
python -m benchmarks.unseen.score "$HOME/ousast-results/unseen/p1/1/calibrated-default" \
|
||||||
|
--out "$HOME/ousast-results/unseen/p1/1/calibrated-default-score.json"
|
||||||
|
```
|
||||||
|
|
||||||
|
For a 20-change slice calibration, replace the proof manifest with
|
||||||
|
`benchmarks/unseen/pool-p1.toml --slice 1`; both commands accept `--private-manifest`.
|
||||||
|
For the long arm use `--arm long_deadline` and a separate output root. Calibration
|
||||||
|
requires the same image digest as replay. An explicit `--deadline-scale FACTOR`
|
||||||
|
is an alternative to `--calibration`, not an additional multiplier.
|
||||||
|
|
||||||
|
Cluster deadlines are `round(30 * factor)` or `round(300 * factor)` seconds;
|
||||||
|
native deadlines remain 30 or 300, including VM fallback in mixed runs.
|
||||||
|
Records carry configured and applied scale, base/applied deadline and actual lane;
|
||||||
|
the scorer reports their counts. Task, subprocess and presigned-object budgets
|
||||||
|
grow with the applied deadline.
|
||||||
|
|
||||||
|
Use `--lane ax` (or `kind`) with `--native-sample K` for an agreement run, so every
|
||||||
|
sampled change has a cluster counterpart. The native sample lives in `OUT/native`;
|
||||||
|
the scorer detects it automatically, or accepts `--native PATH`. Missing, failed,
|
||||||
|
wrong-lane or mismatched-image/arm pairs are unknown, never quiet agreements.
|
||||||
|
The decision is whether any of the three finding streams flags the push, not
|
||||||
|
whether the finding matches the vulnerability label.
|
||||||
|
|
||||||
|
**Decision rule:** if the Wilson 95% agreement lower bound is below 95%, do not use
|
||||||
|
the cluster results for the baseline. The score sets `flagged=true`,
|
||||||
|
`baseline_eligible=false` and the adoption gate to `not_run`. Unknown pairs also
|
||||||
|
flag the run. Disagreements are listed by change ID only, alongside per-change
|
||||||
|
flagged/not-flagged decisions. Even perfect agreement needs at least 73 pairs to
|
||||||
|
clear this bound; the five-change proof or a 20-change sample cannot qualify it.
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
"""Paired native/cluster deadline calibration; publish only opaque IDs and numbers."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import statistics
|
||||||
|
from datetime import date
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from benchmarks.ax.batch import write_json
|
||||||
|
from benchmarks.unseen.replay import canonical, load_slice, run_replays, sample_changes
|
||||||
|
from benchmarks.unseen.score import coverage, load_records
|
||||||
|
|
||||||
|
|
||||||
|
def stage_times(record):
|
||||||
|
if coverage(record) == "instrument_failure":
|
||||||
|
raise ValueError("calibration instrument failed")
|
||||||
|
push = record["push"]
|
||||||
|
if "deadline_exhausted" in json.dumps(push) or any(t.get("status") == "failed" for t in push.get("quick_tier", [])):
|
||||||
|
raise ValueError("calibration deadline exhausted or quick tier failed; increase deadline")
|
||||||
|
raw = push.get("timings", {})
|
||||||
|
timings = {}
|
||||||
|
for key, value in raw.items():
|
||||||
|
key = re.sub(r"^comparison_\d+_", "", key)
|
||||||
|
if not isinstance(value, (int, float)) or not math.isfinite(value) or value < 0:
|
||||||
|
raise ValueError("invalid stage timing")
|
||||||
|
timings[key] = timings.get(key, 0) + value
|
||||||
|
required = ("resolution_seconds", "provenance_seconds", "preparation_seconds", "quick_seconds", "snapshot_seconds")
|
||||||
|
if any(key not in timings for key in required):
|
||||||
|
raise ValueError("missing completed quick-stage timings")
|
||||||
|
scans = [r["scan"] for r in push.get("scans", [])]
|
||||||
|
if any(s.get("degradations") for s in scans):
|
||||||
|
raise ValueError("incomplete engine stages; calibration cannot use degraded scans")
|
||||||
|
if not scans or any(k not in s for s in scans for k in ("build_seconds", "query_seconds")):
|
||||||
|
raise ValueError("missing engine timings")
|
||||||
|
result = {
|
||||||
|
"snapshot": timings["snapshot_seconds"], # Nested inside preparation; never counted twice.
|
||||||
|
"preparation": timings["preparation_seconds"],
|
||||||
|
"quick": timings["quick_seconds"],
|
||||||
|
"quick_total": sum(timings[k] for k in required if k != "snapshot_seconds"),
|
||||||
|
"engine_build": sum(s["build_seconds"] for s in scans),
|
||||||
|
"engine_queries": sum(s["query_seconds"] for s in scans),
|
||||||
|
"hook": record["instrument"].get("timings", {}).get("pre-push"),
|
||||||
|
}
|
||||||
|
if any(not isinstance(v, (int, float)) or not math.isfinite(v) or v <= 0 for v in result.values()):
|
||||||
|
raise ValueError("missing or nonpositive stage timing")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def slowdown(native, cluster, *, seed=0, draws=2000):
|
||||||
|
if not native or len(native) != len(cluster):
|
||||||
|
raise ValueError("calibration needs paired timings")
|
||||||
|
if any(not math.isfinite(v) or v <= 0 for v in [*native, *cluster]):
|
||||||
|
raise ValueError("timings must be finite and positive")
|
||||||
|
ratios = [b / a for a, b in zip(native, cluster, strict=True)]
|
||||||
|
rng = random.Random(seed)
|
||||||
|
bootstrap = sorted(statistics.median(rng.choices(ratios, k=len(ratios))) for _ in range(draws))
|
||||||
|
return {
|
||||||
|
"n": len(ratios),
|
||||||
|
"factor": statistics.median(ratios),
|
||||||
|
"bootstrap95": [bootstrap[int(0.025 * (draws - 1))], bootstrap[int(0.975 * (draws - 1))]],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def calibration_record(native, cluster):
|
||||||
|
nn, cc = ({r["id"]: r for r in rows} for rows in (native, cluster))
|
||||||
|
if not nn or nn.keys() != cc.keys() or len(nn) != len(native) or len(cc) != len(cluster):
|
||||||
|
raise ValueError("calibration requires exactly paired unique changes")
|
||||||
|
pairs = []
|
||||||
|
for key in sorted(nn):
|
||||||
|
if nn[key].get("lane") != "docker" or cc[key].get("lane") != "ax":
|
||||||
|
raise ValueError("calibration requires native docker and cluster ax lanes")
|
||||||
|
pairs.append(
|
||||||
|
{"change_id": hashlib.sha256(str(key).encode()).hexdigest(), "native": stage_times(nn[key]), "cluster": stage_times(cc[key])}
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"status": "ok",
|
||||||
|
"changes": len(pairs),
|
||||||
|
"pairs": pairs,
|
||||||
|
**{
|
||||||
|
stage: slowdown([p["native"][stage] for p in pairs], [p["cluster"][stage] for p in pairs])
|
||||||
|
for stage in ("quick_total", "engine_build", "engine_queries")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--manifest", type=Path, required=True)
|
||||||
|
parser.add_argument("--private-manifest", type=Path)
|
||||||
|
parser.add_argument("--slice", default="1")
|
||||||
|
parser.add_argument("--sample", type=int, default=20)
|
||||||
|
parser.add_argument("--deadline", type=int, default=600)
|
||||||
|
parser.add_argument("--image", required=True)
|
||||||
|
parser.add_argument("--docker-image-bytes", type=int, required=True, help="unpacked image size upper bound")
|
||||||
|
parser.add_argument("--ax-bin", default="ax")
|
||||||
|
parser.add_argument("--kubeconfig", type=Path)
|
||||||
|
parser.add_argument("--atespace", default="default")
|
||||||
|
parser.add_argument("--out", type=Path, default=Path.home() / "ousast-results/unseen/calibration")
|
||||||
|
parser.add_argument("--record", type=Path, default=Path("benchmarks/measurements") / f"{date.today()}-unseen-calibration/record.json")
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.deadline < 1 or args.sample < 1:
|
||||||
|
parser.error("deadline and sample must be positive")
|
||||||
|
args.arm = "default"
|
||||||
|
changes = sample_changes(load_slice(args.manifest, args.slice, args.private_manifest), args.sample)
|
||||||
|
failed = False
|
||||||
|
for lane, directory in (("docker", "native"), ("ax", "cluster")):
|
||||||
|
failed |= run_replays(changes, args, args.out / directory, lane, deadline=args.deadline)
|
||||||
|
result = {"status": "failed", "changes": len(changes)}
|
||||||
|
if not failed:
|
||||||
|
try:
|
||||||
|
result = calibration_record(load_records(args.out / "native"), load_records(args.out / "cluster"))
|
||||||
|
except ValueError as exc:
|
||||||
|
result["reason"] = str(exc) # Only locally controlled diagnostics.
|
||||||
|
result.update(
|
||||||
|
image=args.image, deadline_seconds=args.deadline, sample_digest=hashlib.sha256(canonical([c["id"] for c in changes])).hexdigest()
|
||||||
|
)
|
||||||
|
write_json(args.record, result)
|
||||||
|
return 0 if result["status"] == "ok" else 2
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -57,7 +57,7 @@ def replay_proof(roots):
|
|||||||
def engine_proof(root, baseline):
|
def engine_proof(root, baseline):
|
||||||
current = records(root)
|
current = records(root)
|
||||||
old = {digest([r["repo"], r["pin"]]): r for r in records(baseline) if "repo" in r and "pin" in r}
|
old = {digest([r["repo"], r["pin"]]): r for r in records(baseline) if "repo" in r and "pin" in r}
|
||||||
current = [r for r in current if "repo" in r and "pin" in r]
|
current = [r for r in current if "repo" in r and "pin" in r and not all(u.get("status") == "unsupported" for u in r["units"])]
|
||||||
if len(current) != 2:
|
if len(current) != 2:
|
||||||
raise ValueError("engine proof requires exactly two pins")
|
raise ValueError("engine proof requires exactly two pins")
|
||||||
evidence = []
|
evidence = []
|
||||||
|
|||||||
+68
-14
@@ -6,6 +6,7 @@ import argparse
|
|||||||
import hashlib
|
import hashlib
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
|
import math
|
||||||
import subprocess
|
import subprocess
|
||||||
import tarfile
|
import tarfile
|
||||||
import tomllib
|
import tomllib
|
||||||
@@ -15,11 +16,26 @@ from benchmarks.ax.batch import AXLane, DockerLane, Item, Workload, dispatch, wr
|
|||||||
from benchmarks.unseen.score import coverage
|
from benchmarks.unseen.score import coverage
|
||||||
|
|
||||||
|
|
||||||
def make_item(change, arm, image):
|
def make_item(change, arm, image, *, deadline_scale=1.0, lane="ax", deadline=None):
|
||||||
if arm not in {"default", "long_deadline"}:
|
if arm not in {"default", "long_deadline"}:
|
||||||
raise ValueError("unknown replay arm")
|
raise ValueError("unknown replay arm")
|
||||||
identity = hashlib.sha256(str(change["id"]).encode()).hexdigest()[:14]
|
identity = hashlib.sha256(str(change["id"]).encode()).hexdigest()[:14]
|
||||||
spec = {**change, "id": identity, "hook_flags": [] if arm == "default" else ["--deadline", "300"], "image": image}
|
if not math.isfinite(deadline_scale) or deadline_scale <= 0:
|
||||||
|
raise ValueError("deadline scale must be finite and positive")
|
||||||
|
base = deadline if deadline is not None else 30 if arm == "default" else 300
|
||||||
|
applied = round(base * (1 if lane == "docker" else deadline_scale))
|
||||||
|
if applied < 1:
|
||||||
|
raise ValueError("scaled deadline must be at least one second")
|
||||||
|
spec = {
|
||||||
|
**change,
|
||||||
|
"id": identity,
|
||||||
|
"hook_flags": ["--deadline", str(applied)],
|
||||||
|
"image": image,
|
||||||
|
"base_deadline_seconds": base,
|
||||||
|
"deadline_seconds": applied,
|
||||||
|
"deadline_scale": deadline_scale,
|
||||||
|
"applied_deadline_scale": 1.0 if lane == "docker" else deadline_scale,
|
||||||
|
}
|
||||||
data = json.dumps(spec, sort_keys=True).encode()
|
data = json.dumps(spec, sort_keys=True).encode()
|
||||||
digest = hashlib.sha256(data).hexdigest()
|
digest = hashlib.sha256(data).hexdigest()
|
||||||
return Item(
|
return Item(
|
||||||
@@ -146,6 +162,11 @@ def main():
|
|||||||
parser.add_argument("--slice", default="1")
|
parser.add_argument("--slice", default="1")
|
||||||
parser.add_argument("--out", type=Path)
|
parser.add_argument("--out", type=Path)
|
||||||
parser.add_argument("--lane", choices=("ax", "kind", "docker", "mixed"), default="mixed")
|
parser.add_argument("--lane", choices=("ax", "kind", "docker", "mixed"), default="mixed")
|
||||||
|
group = parser.add_mutually_exclusive_group()
|
||||||
|
group.add_argument("--deadline-scale", type=float, default=1.0)
|
||||||
|
group.add_argument("--calibration", type=Path)
|
||||||
|
parser.add_argument("--native-sample", type=int, default=0)
|
||||||
|
parser.add_argument("--docker-image-bytes", type=int, help="unpacked image size upper bound, required for VM pull")
|
||||||
parser.add_argument("--ax-bin", default="ax")
|
parser.add_argument("--ax-bin", default="ax")
|
||||||
parser.add_argument("--kubeconfig", type=Path)
|
parser.add_argument("--kubeconfig", type=Path)
|
||||||
parser.add_argument("--atespace", default="default")
|
parser.add_argument("--atespace", default="default")
|
||||||
@@ -157,7 +178,15 @@ def main():
|
|||||||
if not args.image:
|
if not args.image:
|
||||||
parser.error("--image digest is required")
|
parser.error("--image digest is required")
|
||||||
changes = load_slice(args.manifest, args.slice, args.private_manifest)
|
changes = load_slice(args.manifest, args.slice, args.private_manifest)
|
||||||
items = [make_item(change, args.arm, args.image) for change in changes]
|
factor = args.deadline_scale
|
||||||
|
if args.calibration:
|
||||||
|
calibration = json.loads(args.calibration.read_text())
|
||||||
|
if calibration.get("status") != "ok" or calibration.get("image") != args.image:
|
||||||
|
parser.error("calibration must be successful and match the replay image")
|
||||||
|
factor = calibration["quick_total"]["factor"]
|
||||||
|
if args.native_sample < 0 or (args.native_sample and args.lane not in {"ax", "kind"}):
|
||||||
|
parser.error("--native-sample requires an ax or kind cluster run and a positive K")
|
||||||
|
items = [make_item(change, args.arm, args.image, deadline_scale=factor, lane=args.lane) for change in changes]
|
||||||
if len({i.id for i in items}) != len(items):
|
if len({i.id for i in items}) != len(items):
|
||||||
parser.error("duplicate change identity")
|
parser.error("duplicate change identity")
|
||||||
args.out = args.out or Path.home() / "ousast-results/unseen" / args.pool / args.slice / args.arm
|
args.out = args.out or Path.home() / "ousast-results/unseen" / args.pool / args.slice / args.arm
|
||||||
@@ -166,27 +195,52 @@ def main():
|
|||||||
return 0
|
return 0
|
||||||
if args.out.resolve().is_relative_to(Path(__file__).resolve().parents[2]):
|
if args.out.resolve().is_relative_to(Path(__file__).resolve().parents[2]):
|
||||||
parser.error("raw replay output must be outside the repository")
|
parser.error("raw replay output must be outside the repository")
|
||||||
|
failed = run_replays(changes, args, args.out, args.lane, deadline_scale=factor)
|
||||||
|
if args.native_sample:
|
||||||
|
failed |= run_replays(sample_changes(changes, args.native_sample), args, args.out / "native", "docker", deadline_scale=factor)
|
||||||
|
return 2 if failed else 0
|
||||||
|
|
||||||
|
|
||||||
|
def sample_changes(changes, count):
|
||||||
|
if count < 1:
|
||||||
|
raise ValueError("sample size must be positive")
|
||||||
|
if len({str(c["id"]) for c in changes}) != len(changes):
|
||||||
|
raise ValueError("duplicate change identity")
|
||||||
|
return sorted(changes, key=lambda c: hashlib.sha256(str(c["id"]).encode()).digest())[:count]
|
||||||
|
|
||||||
|
|
||||||
|
def run_replays(changes, args, out, lane, *, deadline_scale=1.0, deadline=None):
|
||||||
|
if out.resolve().is_relative_to(Path(__file__).resolve().parents[2]):
|
||||||
|
raise ValueError("raw replay output must be outside the repository")
|
||||||
|
items = [make_item(c, args.arm, args.image, deadline_scale=deadline_scale, lane=lane, deadline=deadline) for c in changes]
|
||||||
write_json(
|
write_json(
|
||||||
args.out / "inputs.json",
|
out / "inputs.json",
|
||||||
[{"item_id": item.id, "change": change, "input_digest": item.input_digest} for item, change in zip(items, changes, strict=True)],
|
[{"item_id": item.id, "change": change, "input_digest": item.input_digest} for item, change in zip(items, changes, strict=True)],
|
||||||
)
|
)
|
||||||
workload = Workload(args.image, frozenset({"SPEC_URL", "RESULT_URL"}), 600 if args.arm == "default" else 900, validate_result)
|
maximum = max(
|
||||||
lanes = ("ax", "ax", "docker") if args.lane == "mixed" else (args.lane,) * (2 if args.lane in {"ax", "kind"} else 1)
|
max(json.loads(i.inputs["SPEC_URL"])["deadline_seconds"], json.loads(i.inputs["SPEC_URL"])["base_deadline_seconds"]) for i in items
|
||||||
|
)
|
||||||
|
workload = Workload(args.image, frozenset({"SPEC_URL", "RESULT_URL"}), maximum + 600, validate_result)
|
||||||
|
lanes = ("ax", "ax", "docker") if lane == "mixed" else (lane,) * (2 if lane in {"ax", "kind"} else 1)
|
||||||
|
original = {item.id: change for item, change in zip(items, changes, strict=True)}
|
||||||
executors = {}
|
executors = {}
|
||||||
for lane in set(lanes):
|
for name in set(lanes):
|
||||||
transport = (DockerLane if lane == "docker" else AXLane)(args, workload)
|
transport = (DockerLane if name == "docker" else AXLane)(args, workload)
|
||||||
|
|
||||||
def execute(item, output, attempt, transport=transport):
|
def execute(item, output, attempt, transport=transport, name=name):
|
||||||
record = {**json.loads(item.inputs["SPEC_URL"]), **transport(item, output, attempt)}
|
spec = original[item.id]
|
||||||
|
# Re-render on placement, including VM fallback: native is always unscaled.
|
||||||
|
placed = make_item(spec, args.arm, args.image, deadline_scale=deadline_scale, lane=name, deadline=deadline)
|
||||||
|
placed.id = item.id
|
||||||
|
record = {**json.loads(placed.inputs["SPEC_URL"]), "lane": name, **transport(placed, output, attempt)}
|
||||||
if (output / "result.dat").exists():
|
if (output / "result.dat").exists():
|
||||||
# Durable archive lives separately from attempt staging objects, which are deleted.
|
key = f"unseen/{placed.input_digest}/{name}/result.dat"
|
||||||
key = f"unseen/{item.input_digest}/result.dat"
|
|
||||||
transport.store._put(key, (output / "result.dat").read_bytes())
|
transport.store._put(key, (output / "result.dat").read_bytes())
|
||||||
record["result_object"] = key
|
record["result_object"] = key
|
||||||
return record
|
return record
|
||||||
|
|
||||||
executors[lane] = execute
|
executors[name] = execute
|
||||||
return 2 if dispatch(items, workload, args.out, executors, lanes=lanes) else 0
|
return dispatch(items, workload, out, executors, lanes=lanes)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -52,7 +52,8 @@ def validate_spec(spec):
|
|||||||
raise ReplayFailure("repository must use public DNS")
|
raise ReplayFailure("repository must use public DNS")
|
||||||
if any(not re.fullmatch("[0-9a-f]{40,64}", spec[key]) for key in ("base", "head")):
|
if any(not re.fullmatch("[0-9a-f]{40,64}", spec[key]) for key in ("base", "head")):
|
||||||
raise ReplayFailure("invalid revision")
|
raise ReplayFailure("invalid revision")
|
||||||
if spec.get("hook_flags", []) not in ([], ["--deadline", "300"]):
|
flags = spec.get("hook_flags", [])
|
||||||
|
if flags and (len(flags) != 2 or flags[0] != "--deadline" or not re.fullmatch(r"[1-9][0-9]*", str(flags[1]))):
|
||||||
raise ReplayFailure("unsupported hook flags")
|
raise ReplayFailure("unsupported hook flags")
|
||||||
|
|
||||||
|
|
||||||
@@ -92,6 +93,8 @@ def main(*, work_root=Path("/tmp"), runner=subprocess.run, heap_profile="cluster
|
|||||||
spec = json.loads(spec_path.read_text())
|
spec = json.loads(spec_path.read_text())
|
||||||
instrument["image"] = spec.get("image")
|
instrument["image"] = spec.get("image")
|
||||||
validate_spec(spec)
|
validate_spec(spec)
|
||||||
|
instrument["deadline_seconds"] = int(spec["hook_flags"][1]) if spec.get("hook_flags") else 30
|
||||||
|
instrument["deadline_scale"] = spec.get("deadline_scale", 1.0)
|
||||||
case = work_root / "case"
|
case = work_root / "case"
|
||||||
case.mkdir()
|
case.mkdir()
|
||||||
git = ["git", "-c", "core.hooksPath=/dev/null", "-c", "protocol.file.allow=never", "-C", str(case)]
|
git = ["git", "-c", "core.hooksPath=/dev/null", "-c", "protocol.file.allow=never", "-C", str(case)]
|
||||||
@@ -148,7 +151,9 @@ def main(*, work_root=Path("/tmp"), runner=subprocess.run, heap_profile="cluster
|
|||||||
*spec.get("hook_flags", []),
|
*spec.get("hook_flags", []),
|
||||||
]
|
]
|
||||||
before = clock()
|
before = clock()
|
||||||
completed = runner(command, capture_output=True, text=True, timeout=330 if spec.get("hook_flags") else 60, env=env)
|
completed = runner(
|
||||||
|
command, capture_output=True, text=True, timeout=int(spec["hook_flags"][1]) + 30 if spec.get("hook_flags") else 60, env=env
|
||||||
|
)
|
||||||
instrument["timings"][step] = clock() - before
|
instrument["timings"][step] = clock() - before
|
||||||
instrument["hook_exit"] = completed.returncode
|
instrument["hook_exit"] = completed.returncode
|
||||||
(output / "push.txt").write_text(completed.stdout)
|
(output / "push.txt").write_text(completed.stdout)
|
||||||
|
|||||||
@@ -199,12 +199,59 @@ def score(records, *, seed=0):
|
|||||||
return {
|
return {
|
||||||
**metrics(usable),
|
**metrics(usable),
|
||||||
"total": len(records),
|
"total": len(records),
|
||||||
|
"deadlines": [
|
||||||
|
{"lane": lane, "deadline_seconds": deadline, "deadline_scale": factor, "applied_deadline_scale": applied, "count": count}
|
||||||
|
for (lane, deadline, factor, applied), count in sorted(
|
||||||
|
Counter(
|
||||||
|
(r.get("lane", "unknown"), r.get("deadline_seconds"), r.get("deadline_scale"), r.get("applied_deadline_scale"))
|
||||||
|
for r in records
|
||||||
|
).items(),
|
||||||
|
key=lambda pair: str(pair[0]),
|
||||||
|
)
|
||||||
|
],
|
||||||
"coverage": {state: counts[state] for state in STATES},
|
"coverage": {state: counts[state] for state in STATES},
|
||||||
"breakdowns": breakdown,
|
"breakdowns": breakdown,
|
||||||
"confirmed": metrics([r for r in usable if r.get("label_check", r.get("confirmed")) in ("confirmed", True)]),
|
"confirmed": metrics([r for r in usable if r.get("label_check", r.get("confirmed")) in ("confirmed", True)]),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def agreement(cluster, native):
|
||||||
|
"""Missing, failed or wrong-lane records are unknown, never matching quiet pushes."""
|
||||||
|
cc = {r["id"]: r for r in cluster}
|
||||||
|
if len(cc) != len(cluster) or len({r["id"] for r in native}) != len(native):
|
||||||
|
raise ValueError("duplicate agreement identities")
|
||||||
|
pairs, unknown, disagreements = [], [], []
|
||||||
|
for n in sorted(native, key=lambda r: r["id"]):
|
||||||
|
c = cc.get(n["id"])
|
||||||
|
if (
|
||||||
|
c is None
|
||||||
|
or c.get("lane") not in {"ax", "kind"}
|
||||||
|
or n.get("lane") != "docker"
|
||||||
|
or c.get("image") != n.get("image")
|
||||||
|
or c.get("base_deadline_seconds") != n.get("base_deadline_seconds")
|
||||||
|
or coverage(c) == "instrument_failure"
|
||||||
|
or coverage(n) == "instrument_failure"
|
||||||
|
):
|
||||||
|
unknown.append(n["id"])
|
||||||
|
continue
|
||||||
|
cluster_flag, native_flag = false_alarm(c), false_alarm(n)
|
||||||
|
same = cluster_flag == native_flag
|
||||||
|
pairs.append({"change_id": n["id"], "cluster_flagged": cluster_flag, "native_flagged": native_flag, "same": same})
|
||||||
|
if not same:
|
||||||
|
disagreements.append(n["id"])
|
||||||
|
report = rate([p["same"] for p in pairs])
|
||||||
|
eligible = bool(pairs) and not unknown and report["wilson95"][0] >= 0.95
|
||||||
|
return {
|
||||||
|
**report,
|
||||||
|
"requested": len(native),
|
||||||
|
"pairs": pairs,
|
||||||
|
"unknown": unknown,
|
||||||
|
"disagreements": disagreements,
|
||||||
|
"baseline_eligible": eligible,
|
||||||
|
"flagged": not eligible,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def budgets(rows, *, thresholds=None, binary=False):
|
def budgets(rows, *, thresholds=None, binary=False):
|
||||||
ordinary = [r for r in rows if r["kind"] == "ordinary"]
|
ordinary = [r for r in rows if r["kind"] == "ordinary"]
|
||||||
vulnerable = [r for r in rows if r["kind"] != "ordinary"]
|
vulnerable = [r for r in rows if r["kind"] != "ordinary"]
|
||||||
@@ -295,12 +342,20 @@ def main():
|
|||||||
parser.add_argument("records", type=Path, help="JSON array of joined change/instrument/push records")
|
parser.add_argument("records", type=Path, help="JSON array of joined change/instrument/push records")
|
||||||
parser.add_argument("--out", type=Path, required=True)
|
parser.add_argument("--out", type=Path, required=True)
|
||||||
parser.add_argument("--against", type=Path, help="production-arm records for a paired adoption gate")
|
parser.add_argument("--against", type=Path, help="production-arm records for a paired adoption gate")
|
||||||
|
parser.add_argument("--native", type=Path, help="native sample root; auto-detected under replay root")
|
||||||
parser.add_argument("--slice-id")
|
parser.add_argument("--slice-id")
|
||||||
parser.add_argument("--informed", action="store_true")
|
parser.add_argument("--informed", action="store_true")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
current = load_records(args.records)
|
current = load_records(args.records)
|
||||||
result = score(current)
|
result = score(current)
|
||||||
|
native = args.native or (args.records / "native" if args.records.is_dir() and (args.records / "native").exists() else None)
|
||||||
|
if native:
|
||||||
|
result["native_agreement"] = agreement(current, load_records(native))
|
||||||
|
result["baseline_eligible"] = result["native_agreement"]["baseline_eligible"]
|
||||||
|
result["flagged"] = result["native_agreement"]["flagged"]
|
||||||
result["unseen"] = gate(load_records(args.against) if args.against else [], current, slice_id=args.slice_id, informed=args.informed)
|
result["unseen"] = gate(load_records(args.against) if args.against else [], current, slice_id=args.slice_id, informed=args.informed)
|
||||||
|
if native and result["flagged"]:
|
||||||
|
result["unseen"] = {"gate": "not_run", "reason": "native_agreement"}
|
||||||
args.out.write_text(json.dumps(result, sort_keys=True, indent=2) + "\n")
|
args.out.write_text(json.dumps(result, sort_keys=True, indent=2) + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1719,3 +1719,104 @@ def test_worker_session_question_deadline_keeps_markers_and_does_not_replay(tmp_
|
|||||||
assert backend.timed_out and backend.asked == ["a"]
|
assert backend.timed_out and backend.asked == ["a"]
|
||||||
assert not backend.answers and not backend.session_fallback
|
assert not backend.answers and not backend.session_fallback
|
||||||
assert closed == [True]
|
assert closed == [True]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("keep_sources", [False, True])
|
||||||
|
@pytest.mark.parametrize("outcome", ["success", "failure", "timeout", "fetch-failure", "fetch-timeout", "export-failure"])
|
||||||
|
def test_finished_pin_source_lifetime(tmp_path, monkeypatch, keep_sources, outcome):
|
||||||
|
inputs, case = pair_inputs(tmp_path)
|
||||||
|
inputs.keep_sources = keep_sources
|
||||||
|
pin = trace.make_plan([unit(file=case.relpath)], tmp_path)[0]
|
||||||
|
clone = inputs.source_path(case.repo, "a" * 40, ["pkg"])
|
||||||
|
monkeypatch.setattr(inputs, "pair_for", lambda row: (case, "vuln"))
|
||||||
|
|
||||||
|
def git(command, **kwargs):
|
||||||
|
(clone / "pkg").mkdir(exist_ok=True)
|
||||||
|
(clone / case.relpath).write_text("def run(): pass\n")
|
||||||
|
if outcome == "fetch-failure":
|
||||||
|
raise subprocess.CalledProcessError(1, command)
|
||||||
|
if outcome == "fetch-timeout":
|
||||||
|
raise subprocess.TimeoutExpired(command, 300)
|
||||||
|
if outcome == "export-failure":
|
||||||
|
(clone / case.relpath).unlink()
|
||||||
|
return subprocess.CompletedProcess(command, 0, "a" * 40 + "\n", "")
|
||||||
|
|
||||||
|
class Dispatcher:
|
||||||
|
def execute(self, checkout, output, prepared, name):
|
||||||
|
assert (checkout / case.relpath).read_text() == "def run(): pass\n"
|
||||||
|
if outcome == "timeout":
|
||||||
|
return None, "worker"
|
||||||
|
if outcome == "failure":
|
||||||
|
raise OSError("executor failed")
|
||||||
|
trace.write_json(
|
||||||
|
output / "result.json",
|
||||||
|
{**prepared, "done": True, "units": [worker.unit_record(u, "asked-nothing") for u in prepared["units"]]},
|
||||||
|
)
|
||||||
|
return subprocess.CompletedProcess([], 0, "", ""), "worker"
|
||||||
|
|
||||||
|
monkeypatch.setattr(trace.subprocess, "run", git)
|
||||||
|
monkeypatch.setattr(trace, "prepare_questions", lambda *_: {"a": {}})
|
||||||
|
args = k8s_args(tmp_path, executor="ax", ax_dispatcher=Dispatcher())
|
||||||
|
assert trace.run_pin(pin, None, {}, inputs, tmp_path, args) is (outcome != "success")
|
||||||
|
assert clone.exists() is keep_sources
|
||||||
|
|
||||||
|
|
||||||
|
def test_source_cache_evicts_oldest_before_fetch(tmp_path, monkeypatch):
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
inputs, _ = pair_inputs(tmp_path)
|
||||||
|
inputs.source_cache_bytes = 8
|
||||||
|
cache = inputs.cache / "trace-sources"
|
||||||
|
for name, modified in (("oldest", 10), ("newest", 30), ("middle", 20)):
|
||||||
|
entry = cache / name
|
||||||
|
entry.mkdir(parents=True)
|
||||||
|
(entry / "data").write_bytes(b"123456")
|
||||||
|
trace.os.utime(entry, (modified, modified))
|
||||||
|
# Other caches and symlink targets must remain untouched.
|
||||||
|
other = inputs.cache / "repos"
|
||||||
|
other.mkdir()
|
||||||
|
(other / "data").write_bytes(b"keep")
|
||||||
|
(cache / "link").symlink_to(other, target_is_directory=True)
|
||||||
|
monkeypatch.setattr(trace.shutil, "disk_usage", lambda path: SimpleNamespace(free=2 * 1024**3))
|
||||||
|
commands = []
|
||||||
|
|
||||||
|
def git(command, **kwargs):
|
||||||
|
assert not (cache / "oldest").exists()
|
||||||
|
assert not (cache / "middle").exists()
|
||||||
|
assert (cache / "newest/data").read_bytes() == b"123456"
|
||||||
|
assert (other / "data").read_bytes() == b"keep"
|
||||||
|
commands.append(command)
|
||||||
|
return subprocess.CompletedProcess(command, 0, "a" * 40 + "\n", "")
|
||||||
|
|
||||||
|
monkeypatch.setattr(trace.subprocess, "run", git)
|
||||||
|
inputs.fetch_source("owner/repo", "a" * 40, ["pkg"])
|
||||||
|
assert any("fetch" in command for command in commands)
|
||||||
|
|
||||||
|
|
||||||
|
def test_source_fetch_refuses_low_disk_before_git(tmp_path, monkeypatch):
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
inputs, _ = pair_inputs(tmp_path)
|
||||||
|
monkeypatch.setattr(trace.shutil, "disk_usage", lambda path: SimpleNamespace(free=int(1.5 * 1024**3) - 1))
|
||||||
|
monkeypatch.setattr(trace.subprocess, "run", lambda *a, **kw: pytest.fail("low disk must refuse before git"))
|
||||||
|
with pytest.raises(OSError, match="refusing source fetch: .*at least 1.5 GB required"):
|
||||||
|
inputs.fetch_source("owner/repo", "a" * 40, ["pkg"])
|
||||||
|
assert not inputs.source_path("owner/repo", "a" * 40, ["pkg"]).exists()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("flags,keep,cap", [([], False, 500), (["--keep-sources", "--source-cache-mb", "7"], True, 7)])
|
||||||
|
def test_source_cache_cli_options(tmp_path, monkeypatch, flags, keep, cap):
|
||||||
|
original = trace.Inputs
|
||||||
|
created = []
|
||||||
|
|
||||||
|
def inputs(*args, **kwargs):
|
||||||
|
result = original(*args, **kwargs)
|
||||||
|
created.append(result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
monkeypatch.setattr(trace, "Inputs", inputs)
|
||||||
|
monkeypatch.setattr(trace, "read_jsonl", lambda path: [])
|
||||||
|
monkeypatch.setattr(sys, "argv", [str(SCRIPT), "--dry-run", "--examples", "unused", "--cache", str(tmp_path), *flags])
|
||||||
|
assert trace.main() == 0
|
||||||
|
assert created[0].keep_sources is keep
|
||||||
|
assert created[0].source_cache_bytes == cap * 1024 * 1024
|
||||||
|
|||||||
@@ -0,0 +1,235 @@
|
|||||||
|
"""Offline calibration, placement, agreement and Docker diagnostic controls."""
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||||
|
c = importlib.import_module("benchmarks.unseen.calibrate")
|
||||||
|
r = importlib.import_module("benchmarks.unseen.replay")
|
||||||
|
s = importlib.import_module("benchmarks.unseen.score")
|
||||||
|
b = importlib.import_module("benchmarks.ax.batch")
|
||||||
|
|
||||||
|
|
||||||
|
def timing_record(identity="opaque", factor=1, lane="docker"):
|
||||||
|
return {
|
||||||
|
"id": identity,
|
||||||
|
"lane": lane,
|
||||||
|
"repo": "never-publish",
|
||||||
|
"kind": "ordinary",
|
||||||
|
"instrument": {
|
||||||
|
"head_verified": True,
|
||||||
|
"base_verified": True,
|
||||||
|
"changed_bytes": {"app.py": 10},
|
||||||
|
"hook_exit": 0,
|
||||||
|
"hook_bytes_read": 10,
|
||||||
|
"timings": {"pre-push": 60 * factor},
|
||||||
|
},
|
||||||
|
"push": {
|
||||||
|
"timings": {
|
||||||
|
f"comparison_0_{k}_seconds": v * factor
|
||||||
|
for k, v in (("resolution", 1), ("provenance", 2), ("snapshot", 4), ("preparation", 6), ("quick", 1))
|
||||||
|
},
|
||||||
|
"quick_tier": [{"bytes_read": 10, "findings": []}],
|
||||||
|
"scans": [{"scan": {"build_seconds": 20 * factor, "query_seconds": 30 * factor}}],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_calibration_median_bootstrap_and_privacy():
|
||||||
|
result = c.slowdown([1, 10, 100], [2, 20, 200])
|
||||||
|
assert result == {"n": 3, "factor": 2, "bootstrap95": [2, 2]}
|
||||||
|
varied = c.slowdown([1] * 5, [1, 2, 3, 4, 5])
|
||||||
|
assert varied["factor"] == 3
|
||||||
|
assert varied["bootstrap95"] == [1, 5]
|
||||||
|
assert varied == c.slowdown([1] * 5, [1, 2, 3, 4, 5])
|
||||||
|
record = c.calibration_record([timing_record()], [timing_record(factor=3, lane="ax")])
|
||||||
|
assert record["quick_total"]["factor"] == 3
|
||||||
|
assert record["pairs"][0]["native"]["quick_total"] == 10 # Snapshot is nested, not added twice.
|
||||||
|
assert record["engine_build"]["factor"] == record["engine_queries"]["factor"] == 3
|
||||||
|
assert "never-publish" not in json.dumps(record)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
c.slowdown([0], [2])
|
||||||
|
bad = timing_record()
|
||||||
|
bad["push"]["snapshots"] = [{"boundaries": [{"reason": "deadline_exhausted"}]}]
|
||||||
|
with pytest.raises(ValueError, match="deadline"):
|
||||||
|
c.stage_times(bad)
|
||||||
|
bad = timing_record()
|
||||||
|
del bad["push"]["timings"]["comparison_0_quick_seconds"]
|
||||||
|
with pytest.raises(ValueError, match="missing"):
|
||||||
|
c.stage_times(bad)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"lane,arm,expected",
|
||||||
|
[
|
||||||
|
("ax", "default", 75),
|
||||||
|
("ax", "long_deadline", 750),
|
||||||
|
("docker", "default", 30),
|
||||||
|
("docker", "long_deadline", 300),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_scaled_task_contract(lane, arm, expected):
|
||||||
|
item = r.make_item({"id": "a"}, arm, "image", deadline_scale=2.5, lane=lane)
|
||||||
|
spec = json.loads(item.inputs["SPEC_URL"])
|
||||||
|
assert spec["hook_flags"] == ["--deadline", str(expected)]
|
||||||
|
assert spec["deadline_seconds"] == expected and spec["deadline_scale"] == 2.5
|
||||||
|
entry = importlib.import_module("benchmarks.unseen.replay_entry")
|
||||||
|
entry.validate_spec({**spec, "repository_url": "https://public.example/test", "base": "a" * 40, "head": "b" * 40})
|
||||||
|
|
||||||
|
|
||||||
|
def test_native_sample_and_agreement():
|
||||||
|
native = [timing_record(str(i)) for i in range(100)]
|
||||||
|
cluster = [timing_record(str(i), lane="ax") for i in range(100)]
|
||||||
|
assert r.sample_changes(native, 20) == r.sample_changes(list(reversed(native)), 20)
|
||||||
|
report = s.agreement(cluster, native)
|
||||||
|
assert report["rate"] == 1 and report["baseline_eligible"]
|
||||||
|
assert s.agreement(cluster[:20], native[:20])["flagged"] # Perfect small sample is insufficient.
|
||||||
|
cluster[0]["push"]["quick_tier"][0]["findings"] = [{"cwe": "CWE-89"}]
|
||||||
|
report = s.agreement(cluster, native)
|
||||||
|
assert report["disagreements"] == ["0"] and report["rate"] == 0.99
|
||||||
|
assert report["flagged"]
|
||||||
|
cluster[0]["instrument"] = {}
|
||||||
|
report = s.agreement(cluster, native)
|
||||||
|
assert report["unknown"] == ["0"] and report["flagged"]
|
||||||
|
assert s.agreement([], [])["flagged"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_docker_error_capture_and_preflight(tmp_path, monkeypatch):
|
||||||
|
calls = []
|
||||||
|
monkeypatch.setenv("TEST_SECRET", "hidden-value")
|
||||||
|
lane = object.__new__(b.DockerLane)
|
||||||
|
lane.args = SimpleNamespace(docker_image_bytes=100)
|
||||||
|
lane.workload = SimpleNamespace(image="pinned-image")
|
||||||
|
lane._prepared = False
|
||||||
|
|
||||||
|
def runner(command, **kwargs):
|
||||||
|
calls.append(command)
|
||||||
|
if command[:2] == ["docker", "info"]:
|
||||||
|
return subprocess.CompletedProcess(command, 0, "/docker-data\n", "")
|
||||||
|
if command[0] == "df":
|
||||||
|
return subprocess.CompletedProcess(command, 0, "Avail\n2000000000\n", "")
|
||||||
|
return subprocess.CompletedProcess(command, 125, "", "pull denied hidden-value https://host.example/?signature=private\nlast error")
|
||||||
|
|
||||||
|
lane.runner = runner
|
||||||
|
record = lane(None, tmp_path, 0)
|
||||||
|
assert record["status"] == "instrument_failure"
|
||||||
|
assert record["docker_errors"][0]["exit_code"] == 125
|
||||||
|
assert record["docker_errors"][0]["stderr"].endswith("last error")
|
||||||
|
assert "hidden-value" not in json.dumps(record) and "private" not in json.dumps(record)
|
||||||
|
assert [cmd[0] for cmd in calls] == ["docker", "df", "docker"]
|
||||||
|
assert calls[-1] == ["docker", "pull", "pinned-image"]
|
||||||
|
assert (tmp_path / "docker-error.json").exists()
|
||||||
|
lane.args.docker_image_bytes = 2000000000
|
||||||
|
calls.clear()
|
||||||
|
record = lane(None, tmp_path, 1)
|
||||||
|
assert "refusing Docker pull" in record["docker_errors"][0]["stderr"]
|
||||||
|
assert len(calls) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_docker_launch_error_survives_transport(tmp_path, monkeypatch):
|
||||||
|
monkeypatch.setenv("S3_ENDPOINT", "https://store.example")
|
||||||
|
|
||||||
|
class Store:
|
||||||
|
def _put(self, *args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _delete(self, *args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def presign_get(self, *args):
|
||||||
|
return "https://store.example/secret"
|
||||||
|
|
||||||
|
presign_put = presign_get
|
||||||
|
|
||||||
|
image = "registry/image@sha256:" + "a" * 64
|
||||||
|
lane = b.DockerLane(
|
||||||
|
SimpleNamespace(atespace="default"),
|
||||||
|
b.Workload(image, frozenset({"SPEC_URL", "RESULT_URL"}), 60, None),
|
||||||
|
store=Store(),
|
||||||
|
runner=lambda command, **kw: subprocess.CompletedProcess(command, 0 if command[1] == "rm" else 125, "", "runtime missing"),
|
||||||
|
)
|
||||||
|
lane._prepared = True
|
||||||
|
item = r.make_item({"id": "x"}, "default", image)
|
||||||
|
record = lane(item, tmp_path, 0)
|
||||||
|
assert record["docker_errors"] == [{"step": "run", "exit_code": 125, "stderr": "runtime missing"}]
|
||||||
|
assert record["status"] == "instrument_failure"
|
||||||
|
|
||||||
|
|
||||||
|
def test_placement_scales_cluster_but_not_vm_fallback(tmp_path, monkeypatch):
|
||||||
|
observed = []
|
||||||
|
|
||||||
|
class Transport:
|
||||||
|
def __init__(self, args, workload):
|
||||||
|
assert workload.deadline >= 750 + 600
|
||||||
|
|
||||||
|
def __call__(self, item, output, attempt):
|
||||||
|
spec = json.loads(item.inputs["SPEC_URL"])
|
||||||
|
observed.append(spec)
|
||||||
|
return {"status": "quick_only"}
|
||||||
|
|
||||||
|
def dispatch(items, workload, out, executors, **kwargs):
|
||||||
|
for lane in ("ax", "docker"):
|
||||||
|
record = executors[lane](items[0], out, 0)
|
||||||
|
assert record["lane"] == lane
|
||||||
|
assert record["deadline_seconds"] == (750 if lane == "ax" else 300)
|
||||||
|
return False
|
||||||
|
|
||||||
|
monkeypatch.setattr(r, "AXLane", Transport)
|
||||||
|
monkeypatch.setattr(r, "DockerLane", Transport)
|
||||||
|
monkeypatch.setattr(r, "dispatch", dispatch)
|
||||||
|
args = SimpleNamespace(arm="long_deadline", image="test-image")
|
||||||
|
assert not r.run_replays([{"id": "opaque"}], args, tmp_path, "mixed", deadline_scale=2.5)
|
||||||
|
assert observed[0]["id"] == observed[1]["id"]
|
||||||
|
assert observed[0]["hook_flags"] == ["--deadline", "750"]
|
||||||
|
assert observed[1]["hook_flags"] == ["--deadline", "300"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_calibration_cli_writes_anonymous_record(tmp_path, monkeypatch):
|
||||||
|
manifest, record = tmp_path / "manifest.json", tmp_path / "record.json"
|
||||||
|
changes = [{"id": str(i), "repo": "do-not-publish"} for i in range(25)]
|
||||||
|
monkeypatch.setattr(c, "load_slice", lambda *args: changes)
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
def replay(selected, args, out, lane, **kwargs):
|
||||||
|
calls.append((selected, lane, kwargs))
|
||||||
|
return False
|
||||||
|
|
||||||
|
monkeypatch.setattr(c, "run_replays", replay)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
c,
|
||||||
|
"load_records",
|
||||||
|
lambda path: [
|
||||||
|
timing_record(str(i), factor=3 if path.name == "cluster" else 1, lane="ax" if path.name == "cluster" else "docker")
|
||||||
|
for i in range(20)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
sys,
|
||||||
|
"argv",
|
||||||
|
[
|
||||||
|
"calibrate",
|
||||||
|
"--manifest",
|
||||||
|
str(manifest),
|
||||||
|
"--image",
|
||||||
|
"digest",
|
||||||
|
"--docker-image-bytes",
|
||||||
|
"100",
|
||||||
|
"--out",
|
||||||
|
str(tmp_path / "raw"),
|
||||||
|
"--record",
|
||||||
|
str(record),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert c.main() == 0
|
||||||
|
assert len(calls[0][0]) == 20 and calls[0][0] == calls[1][0]
|
||||||
|
assert [call[1] for call in calls] == ["docker", "ax"]
|
||||||
|
assert all(call[2]["deadline"] == 600 for call in calls)
|
||||||
|
payload = record.read_text()
|
||||||
|
assert "do-not-publish" not in payload and "never-publish" not in payload
|
||||||
|
assert json.loads(payload)["quick_total"]["factor"] == 3
|
||||||
@@ -54,3 +54,33 @@ def test_engine_proof_counts_differences_without_names(tmp_path):
|
|||||||
(baseline / f"{i}.json").write_text(json.dumps(record))
|
(baseline / f"{i}.json").write_text(json.dumps(record))
|
||||||
proof = p.engine_proof(current, baseline)
|
proof = p.engine_proof(current, baseline)
|
||||||
assert proof["differences"] == 0 and "synthetic" not in json.dumps(proof)
|
assert proof["differences"] == 0 and "synthetic" not in json.dumps(proof)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("executed", [2, 3])
|
||||||
|
def test_engine_proof_counts_only_executed_records(tmp_path, executed):
|
||||||
|
current, baseline = tmp_path / "current", tmp_path / "baseline"
|
||||||
|
current.mkdir()
|
||||||
|
baseline.mkdir()
|
||||||
|
(current / "progress.json").write_text(json.dumps({"completed": executed}))
|
||||||
|
for i in range(executed + 4):
|
||||||
|
record = {
|
||||||
|
"repo": "synthetic",
|
||||||
|
"pin": str(i),
|
||||||
|
"done": True,
|
||||||
|
"seconds": 20 if i < executed else 0,
|
||||||
|
"units": [{"unit": "unsupported", "status": "unsupported"}],
|
||||||
|
}
|
||||||
|
if i < executed:
|
||||||
|
record["units"].append(
|
||||||
|
{"unit": "executed", "status": "path", "instrument": {"bytes": 20, "jvm": [{"seconds": 8, "success": True}]}}
|
||||||
|
)
|
||||||
|
(baseline / f"{i}.json").write_text(json.dumps(record))
|
||||||
|
(current / f"{i}.json").write_text(json.dumps(record))
|
||||||
|
|
||||||
|
if executed == 3:
|
||||||
|
with pytest.raises(ValueError, match="engine proof requires exactly two pins"):
|
||||||
|
p.engine_proof(current, baseline)
|
||||||
|
else:
|
||||||
|
proof = p.engine_proof(current, baseline)
|
||||||
|
assert len(proof["pins"]) == 2
|
||||||
|
assert proof["differences"] == 0
|
||||||
|
|||||||
Reference in New Issue
Block a user