Files
govoplan/tools/devkit/govoplan_devkit/runner.py
T
zemion 2ffdb23f69
Dependency Audit / dependency-audit (push) Successful in 1m45s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Successful in 11m30s
feat(devkit): add resumable workspace automation and UI review tooling
Verified with the coordinated workspace changes by devkit full run
2026-09-08T225814-186389-0000-3e3ed7cd (all seven phases passed).
This shared UI pass does not mark the individual module reviews complete.
2026-09-09 02:03:17 +02:00

1035 lines
40 KiB
Python
Executable File

"""Resource-aware checks with bounded logs and resumable, source-bound receipts."""
from __future__ import annotations
from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED
from contextlib import ExitStack
from copy import deepcopy
import os
import hashlib
from pathlib import Path
import signal
import subprocess
import threading
import time
import uuid
from .common import (
atomic_json,
atomic_text,
digest,
identifier,
now,
private_directory,
read_bounded_bytes,
read_json,
redact,
redact_argv,
resource_lock,
state_root,
)
from .environment import environment_fingerprint, execution_environment, resolve_tools
from .workspace import load_project
from .checkpoints import Checkpoints, FINGERPRINT_VERSION, validate_checkpoint_receipt
from .inputs import InputSnapshotter
from .monitoring import (
bounded_display,
capture_text,
elapsed_seconds,
latest_run,
list_runs,
read_live,
write_live,
)
MAX_LOG_BYTES = 8 * 1024 * 1024
TERMINAL = {
"passed",
"failed",
"timed_out",
"blocked",
"skipped",
"interrupted",
"stale",
}
def validate_stages(
stages: list[dict], workspace_root: Path, tools: dict[str, str]
) -> list[dict]:
if not isinstance(stages, list) or len(stages) > 512:
raise ValueError("A check plan must contain at most 512 stages")
result, ids = [], set()
for source in stages:
stage = deepcopy(source)
stage_id = identifier(stage.get("id"))
if stage_id in ids:
raise ValueError("Duplicate stage identifier")
ids.add(stage_id)
argv = stage.get("argv")
if (
not isinstance(argv, list)
or not argv
or len(argv) > 256
or any(
not isinstance(value, str) or "\0" in value or len(value) > 8192
for value in argv
)
):
raise ValueError(f"Invalid command for {stage_id}")
resolved = []
for value in argv:
for name, executable in tools.items():
value = value.replace("{" + name + "}", executable)
value = value.replace("{workspace}", str(workspace_root))
resolved.append(value)
cwd = Path(stage.get("cwd", str(workspace_root)))
if not cwd.is_absolute():
cwd = workspace_root / cwd
cwd = cwd.resolve()
if not cwd.is_relative_to(workspace_root.resolve()):
raise ValueError("Check working directory escapes the workspace")
timeout = stage.get("timeout_seconds", 3600)
if (
isinstance(timeout, bool)
or not isinstance(timeout, (int, float))
or not 0 < timeout <= 43200
):
raise ValueError("Check timeout must be between 0 and 43200 seconds")
for field in ("deps", "after", "resources"):
values = stage.get(field, [])
if not isinstance(values, list) or any(
not isinstance(value, str) or not value or len(value) > 256
for value in values
):
raise ValueError(f"Invalid stage {field}")
stage[field] = list(dict.fromkeys(values))
if stage.get("reuse", "verified") not in {"verified", "never"}:
raise ValueError("Stage reuse must be verified or never")
stage.setdefault("reuse", "verified")
stage.update(
id=stage_id,
argv=resolved,
cwd=str(cwd),
timeout_seconds=timeout,
title=str(stage.get("title", stage_id)),
reason=str(stage.get("reason", "Explicitly selected")),
)
result.append(stage)
for stage in result:
dependencies = set(stage["deps"]) | set(stage["after"])
if dependencies - ids or stage["id"] in dependencies:
raise ValueError("Stage has an unknown or self dependency")
if set(stage["deps"]) & set(stage["after"]):
raise ValueError(
"A dependency cannot be both data-dependent and order-only"
)
remaining = {item["id"]: set(item["deps"]) | set(item["after"]) for item in result}
while remaining:
ready = {key for key, deps in remaining.items() if not deps}
if not ready:
raise ValueError("Check dependencies contain a cycle")
remaining = {
key: deps - ready for key, deps in remaining.items() if key not in ready
}
return result
def _seal(receipt: dict) -> dict:
receipt["integrity_sha256"] = digest(
{key: value for key, value in receipt.items() if key != "integrity_sha256"}
)
return receipt
def read_receipt(workspace_root: Path, state_dir: Path | None, run_id: str) -> dict:
identifier(run_id)
path = state_root(workspace_root, state_dir) / "runs" / run_id / "receipt.json"
receipt = read_json(path)
if (
not isinstance(receipt, dict)
or receipt.get("schema_version") != 1
or receipt.get("run_id") != run_id
):
raise ValueError("Invalid check receipt")
if receipt.get("workspace_root") != str(workspace_root.resolve()):
raise ValueError("Check receipt belongs to a different workspace")
expected = digest(
{key: value for key, value in receipt.items() if key != "integrity_sha256"}
)
if receipt.get("integrity_sha256") != expected:
raise ValueError("Check receipt integrity mismatch")
if not isinstance(receipt.get("stages"), list) or len(receipt["stages"]) > 512:
raise ValueError("Invalid receipt stages")
if receipt.get("status") not in {
"running",
"passed",
"failed",
"interrupted",
"stale",
}:
raise ValueError("Invalid receipt run state")
if receipt["status"] == "passed" and (
receipt.get("snapshot_verified") is not True
or not receipt["stages"]
or any(
stage.get("status") != "passed"
for stage in receipt["stages"]
if isinstance(stage, dict)
)
):
raise ValueError(
"Passing receipt requires successful stages and a verified final snapshot"
)
if type(receipt.get("snapshot_verified")) is not bool:
raise ValueError("Receipt snapshot verification must be boolean")
stage_ids = set()
for stage in receipt["stages"]:
if not isinstance(stage, dict) or stage.get("status") not in TERMINAL | {
"pending",
"running",
}:
raise ValueError("Invalid receipt stage state")
identifier(stage.get("id"))
if stage["id"] in stage_ids:
raise ValueError("Duplicate receipt stage ID")
stage_ids.add(stage["id"])
if stage.get("exit_code") is not None and type(stage["exit_code"]) is not int:
raise ValueError("Check exit code must be an integer or null")
if stage["status"] == "passed" and stage.get("exit_code") != 0:
raise ValueError("Passing check receipt must have exit code zero")
validate_checkpoint_receipt(receipt)
return receipt
def _verified_log_bytes(stage: dict, root: Path) -> bytes:
raw = stage.get("log_path")
if not isinstance(raw, str) or not isinstance(stage.get("log_sha256"), str):
raise ValueError("Cached stage has no verifiable log; rerun without --resume")
path = Path(raw)
from .common import reject_symlinks
reject_symlinks(path)
if (
not path.is_absolute()
or not path.resolve().is_relative_to(root / "runs")
or not path.is_file()
):
raise ValueError("Cached log does not belong to this workspace's check runs")
encoded = read_bounded_bytes(path, MAX_LOG_BYTES + 65536)
if hashlib.sha256(encoded).hexdigest() != stage["log_sha256"]:
raise ValueError("Cached log integrity mismatch; rerun without --resume")
return encoded
def _verify_cached_log(stage: dict, root: Path) -> None:
_verified_log_bytes(stage, root)
def execute_stage(
stage: dict,
log_path: Path,
locks: Path,
env: dict[str, str],
cancelled: threading.Event,
checkpoints=None,
prior=None,
allow_reuse=True,
on_checkpoint=None,
verify_log=None,
) -> dict:
try:
with ExitStack() as stack:
for resource in sorted(stage["resources"]):
directory = (
locks.parent.parent / "host-resource-locks"
if resource.startswith(("browser:", "port:", "host:"))
else locks
)
stack.enter_context(resource_lock(directory, resource))
if cancelled.is_set():
return {
"status": "interrupted",
"exit_code": None,
"duration_seconds": 0,
"log_path": None,
"checkpoint_verified": False,
}
before = None
if checkpoints:
before, cached = checkpoints.prepare(
stage, prior, allow_reuse, verify_log
)
if cached:
cached["reused_from"] = stage["candidate_run_id"]
on_checkpoint(cached)
return cached
result = _execute_stage_command(stage, log_path, env, cancelled)
if checkpoints:
try:
result = checkpoints.finish(stage, before, result)
except InterruptedError as exc:
result.update(
status="interrupted",
checkpoint_verified=False,
error=redact(str(exc)),
)
except (
OSError,
ValueError,
RuntimeError,
subprocess.SubprocessError,
) as exc:
# Keep the actual completed command/log visible even when
# its post-execution input probe cannot certify a checkpoint.
result.update(
status="stale",
checkpoint_verified=False,
error="Stage checkpoint could not be verified: "
+ redact(str(exc)),
)
if result.get("checkpoint_verified"):
verify_log(result)
# Persist while the resource locks are still held, before another
# phase/process can consume or replace this phase's state.
on_checkpoint(result)
return result
except InterruptedError:
return {
"status": "interrupted",
"exit_code": None,
"duration_seconds": 0,
"log_path": None,
"checkpoint_verified": False,
}
except RuntimeError as exc:
return {
"status": "blocked",
"exit_code": None,
"error": redact(str(exc)),
"checkpoint_verified": False,
"log_path": None,
}
def _execute_stage_command(stage, log_path, env, cancelled) -> dict:
from .process import run_captured
started = time.monotonic()
state, code, truncated, output = "failed", None, False, b""
omitted_bytes, rendered = 0, None
try:
result = run_captured(
stage["argv"],
cwd=stage["cwd"],
env=env,
timeout=stage["timeout_seconds"],
max_stdout=MAX_LOG_BYTES,
cancelled=cancelled,
merge_stderr=True,
terminate_on_limit=False,
capture_mode="head_tail",
on_output=lambda snapshot: write_live(
log_path, stage["id"], snapshot, started
),
)
state = result.status if result.status in TERMINAL else "failed"
code, truncated, output = result.returncode, result.truncated, result.stdout
omitted_bytes = result.omitted_stdout_bytes
rendered = capture_text(result.snapshot())
if result.status == "leaked_process":
rendered += "\nA descendant outlived the command; the owned process group was terminated.\n"
except RuntimeError as exc:
state, output = "blocked", str(exc).encode()
except (OSError, subprocess.SubprocessError, ValueError) as exc:
output = f"{type(exc).__name__}: {exc}".encode()
text = (
rendered
if rendered is not None
else redact(output.decode("utf-8", errors="replace"))
)
encoded = text.encode("utf-8")
if len(encoded) > MAX_LOG_BYTES:
truncated = True
text = bounded_display(text, MAX_LOG_BYTES)
if truncated:
text += "\n[Output truncated at configured log bound; exit status is independent.]\n"
atomic_text(log_path, text, max_bytes=MAX_LOG_BYTES + 65536)
return {
"status": state,
"exit_code": code,
"duration_seconds": round(time.monotonic() - started, 3),
"log_path": str(log_path),
"output_truncated": truncated,
"omitted_output_bytes": omitted_bytes,
"log_sha256": hashlib.sha256(log_path.read_bytes()).hexdigest(),
}
def summarize(receipt: dict) -> dict:
counts = {}
for stage in receipt["stages"]:
counts[stage["status"]] = counts.get(stage["status"], 0) + 1
lines = [
f"Check run {receipt['run_id']}: {receipt['status']}",
", ".join(f"{count} {state}" for state, count in sorted(counts.items()))
or "No checks selected",
]
elapsed = elapsed_seconds(receipt)
lines.insert(
1,
f"Phase: {receipt.get('phase', 'checking' if receipt['status'] == 'running' else 'finished')}; elapsed: {elapsed}s",
)
if receipt.get("fingerprint_version") == FINGERPRINT_VERSION:
reused = sum(bool(stage.get("reused_from")) for stage in receipt["stages"])
verified = sum(
stage.get("checkpoint_verified") is True for stage in receipt["stages"]
)
lines.append(f"Verified phase checkpoints: {verified}; reused: {reused}")
if receipt.get("error") or receipt.get("invalidated_reason"):
lines.append(redact(str(receipt.get("error") or receipt["invalidated_reason"])))
for stage in receipt["stages"]:
if stage["status"] not in {"passed", "pending"}:
lines.append(
f"{stage['id']}: {stage['status']}"
+ (f" — {stage['log_path']}" if stage.get("log_path") else "")
)
notes = list(
dict.fromkeys(
note
for stage in receipt["stages"]
for note in stage.get("coverage_notes", [])
)
)
lines.extend(notes[:8])
if len(notes) > 8:
lines.append(
f"{len(notes) - 8} additional coverage notes are in the JSON receipt."
)
lines.append(
"Receipts describe local checks, not a signed security certification or approval to publish."
)
return {
**receipt,
"counts": counts,
"elapsed_seconds": elapsed,
"summary": lines,
"_exit_code": 0 if receipt["status"] in {"passed", "running", "planned"} else 1,
}
def input_scope_label(stage: dict) -> str:
if "inputs" not in stage:
return "whole workspace (conservative fallback)"
repos = stage["inputs"]["repos"]
if len(repos) <= 4:
return ", ".join(repos)
return f"{len(repos)} declared repositories (complete list in --json)"
def run_checks(args, stages: list[dict]) -> dict:
workspace_root = Path(args.workspace_root).resolve()
project = load_project(workspace_root, getattr(args, "project", None))
tools = resolve_tools(workspace_root, project)
env = execution_environment(workspace_root, project, tools)
plan = validate_stages(stages, workspace_root, tools)
input_validator = InputSnapshotter(project, workspace_root=workspace_root)
for stage in plan:
input_validator.scope(stage)
if getattr(args, "dry_run", False):
notes = list(
dict.fromkeys(
note for stage in plan for note in stage.get("coverage_notes", [])
)
)
return {
"status": "planned",
"stages": plan,
"coverage": getattr(args, "check_coverage", None),
"summary": (
[
f"{stage['id']}: {stage['title']}{stage['reason']}"
+ "; inputs: "
+ input_scope_label(stage)
for stage in plan
]
or ["No checks selected"]
)
+ notes[:8],
}
if not plan:
return {
"status": "not_run",
"stages": [],
"summary": ["No checks selected; this is not a passing verification."],
"_exit_code": 0,
}
jobs = getattr(args, "jobs", 2)
if not 1 <= jobs <= 8:
raise ValueError("jobs must be between 1 and 8")
source = environment = None
base = state_root(workspace_root, getattr(args, "state_dir", None))
run_id = (
now().replace(":", "").replace("+", "-").replace(".", "-")
+ "-"
+ uuid.uuid4().hex[:8]
)
previous_id = getattr(args, "resume", None)
previous = (
read_receipt(workspace_root, getattr(args, "state_dir", None), previous_id)
if previous_id
else None
)
if previous:
if previous["status"] == "running":
raise ValueError(
"Run still reports running; use status to inspect it and explicitly recover interruption first"
)
run_dir = base / "runs" / run_id
private_directory(run_dir)
stages_state = []
previous_stages = (
{stage["id"]: stage for stage in previous["stages"]}
if previous and previous.get("fingerprint_version") == FINGERPRINT_VERSION
else {}
)
for stage in plan:
state = {
**stage,
"status": "pending",
"exit_code": None,
"duration_seconds": None,
"log_path": None,
"checkpoint_verified": False,
"candidate_run_id": previous_id,
}
stages_state.append(state)
receipt = {
"schema_version": 1,
"fingerprint_version": FINGERPRINT_VERSION,
"run_id": run_id,
"workspace_root": str(workspace_root),
"project_file": str(args.project.resolve())
if getattr(args, "project", None)
else None,
"source_fingerprint": source,
"environment_fingerprint": environment,
"plan_fingerprint": digest(plan),
"status": "running",
"phase": "preparing",
"stages": stages_state,
"generated_at": now(),
"finished_at": None,
"owner_pid": os.getpid(),
"resumed_from": previous_id,
"profile": getattr(args, "profile", None),
"selection": {
"repos": getattr(args, "repo", []),
"changed": getattr(args, "changed", False),
},
"snapshot_verified": False,
"coverage": getattr(args, "check_coverage", None),
}
saved_digest = None
receipt_lock = threading.RLock()
last_progress = 0.0
last_progress_key = None
def progress(force=False):
nonlocal last_progress, last_progress_key
callback = getattr(args, "on_progress", None)
if callback is None:
return
key = (receipt["phase"], tuple(stage["status"] for stage in stages_state))
elapsed = time.monotonic() - last_progress
if not force and (elapsed < 1 or (key == last_progress_key and elapsed < 15)):
return
counts = {}
for stage in stages_state:
counts[stage["status"]] = counts.get(stage["status"], 0) + 1
callback(
{
"event": "check_progress",
"run_id": run_id,
"phase": receipt["phase"],
"status": receipt["status"],
"counts": counts,
"total_stages": len(stages_state),
"elapsed_seconds": elapsed_seconds(receipt),
"active_stages": [
stage["id"]
for stage in stages_state
if stage["status"] == "running"
],
"receipt_path": str(run_dir / "receipt.json"),
}
)
last_progress, last_progress_key = time.monotonic(), key
def save():
with receipt_lock:
save_locked()
def save_locked():
nonlocal saved_digest
# Execution retains argv in memory; receipts never persist known credentials.
public = deepcopy(receipt)
for item in public["stages"]:
item["argv"] = redact_argv(item["argv"])
for field in ("title", "reason", "error"):
if field in item:
item[field] = redact(str(item[field]))
if "error" in public:
public["error"] = redact(str(public["error"]))
_seal(public)
if saved_digest != public["integrity_sha256"]:
atomic_json(run_dir / "receipt.json", public)
saved_digest = public["integrity_sha256"]
cancelled = threading.Event()
checkpoints = Checkpoints(
project,
workspace_root,
plan,
lambda: environment_fingerprint(workspace_root, project, tools, env),
cancelled,
)
def persist_checkpoint(stage, result):
with receipt_lock:
before = dict(stage)
try:
stage.update(result)
save()
except BaseException:
stage.clear()
stage.update(before)
raise
old_handlers = {}
if threading.current_thread() is threading.main_thread():
for sig in (signal.SIGINT, signal.SIGTERM):
old_handlers[sig] = signal.signal(sig, lambda *_: cancelled.set())
try:
with resource_lock(base / "locks", "run:" + run_id):
try:
with ThreadPoolExecutor(max_workers=jobs) as pool:
try:
save()
progress(force=True)
snapshot, environment = checkpoints.initialize()
source = snapshot["observed_source_fingerprint"]
receipt.update(
source_fingerprint=source,
environment_fingerprint=environment,
source_scope={
"repos": sorted(snapshot["repository_fingerprints"]),
"complete_workspace": snapshot["complete_workspace"],
},
input_scan_stats=snapshot["scan_stats"],
)
receipt["phase"] = "checking"
save()
progress(force=True)
active = {}
while True:
by_id = {stage["id"]: stage for stage in stages_state}
active_ids = {stage["id"] for stage in active.values()}
active_resources = {
resource
for stage in active.values()
for resource in stage["resources"]
}
for stage in stages_state:
if stage["status"] != "pending":
continue
deps = [
by_id[dep]["status"]
for dep in stage["deps"] + stage["after"]
]
if cancelled.is_set() or any(
state in TERMINAL - {"passed"} for state in deps
):
with receipt_lock:
stage["status"] = (
"interrupted"
if cancelled.is_set()
else "skipped"
)
elif (
len(active) < jobs
and all(state == "passed" for state in deps)
# A checkpoint callback may be publishing its
# result. Consumers wait for the future to
# complete, including successful persistence.
and not active_ids.intersection(
stage["deps"] + stage["after"]
)
and not active_resources.intersection(
stage["resources"]
)
):
with receipt_lock:
stage["status"] = "running"
stage["started_at"] = now()
# Locks remain shared across different evidence directories.
future = pool.submit(
execute_stage,
stage,
run_dir / (stage["id"] + ".log"),
state_root(workspace_root) / "resource-locks",
env,
cancelled,
checkpoints,
previous_stages.get(stage["id"]),
all(
by_id[dep].get("reused_from")
for dep in stage["deps"]
),
lambda result, current=stage: (
persist_checkpoint(current, result)
),
lambda result: _verify_cached_log(result, base),
)
active[future] = stage
active_ids.add(stage["id"])
active_resources.update(stage["resources"])
save()
progress()
if not active:
break
done, _ = wait(
active, timeout=0.5, return_when=FIRST_COMPLETED
)
for future in done:
stage = active.pop(future)
try:
with receipt_lock:
stage.update(future.result())
except Exception as exc:
with receipt_lock:
stage.update(
status="failed",
checkpoint_verified=False,
error=f"{type(exc).__name__}: {redact(str(exc))}",
)
terminal_status = (
"interrupted"
if cancelled.is_set()
else "passed"
if all(
stage["status"] == "passed" for stage in stages_state
)
else "failed"
)
receipt["phase"] = "finalizing"
save()
progress(force=True)
try:
matches, final_snapshot, _ = checkpoints.finalize(
stages_state
)
receipt["final_input_scan_stats"] = final_snapshot[
"scan_stats"
]
except (
OSError,
ValueError,
RuntimeError,
subprocess.SubprocessError,
) as exc:
matches = False
receipt["invalidated_reason"] = (
"Final snapshot could not be verified: "
+ redact(str(exc))
)
if cancelled.is_set():
receipt.update(
status="interrupted", snapshot_verified=matches
)
elif matches:
receipt.update(
status=terminal_status, snapshot_verified=True
)
else:
receipt["status"] = "stale"
receipt.setdefault(
"invalidated_reason",
"Source or environment changed during verification; rerun checks against the final state",
)
finally:
# Signal owned processes before the executor waits for them, even on persistence errors.
cancelled.set()
except InterruptedError as exc:
receipt.update(status="interrupted", error=redact(str(exc)))
except Exception as exc:
receipt.update(
status="failed", error=f"{type(exc).__name__}: {redact(str(exc))}"
)
raise
finally:
if receipt["status"] == "running":
receipt["status"] = "interrupted"
for stage in stages_state:
if stage["status"] in {"pending", "running"}:
stage["status"] = "interrupted"
receipt["finished_at"] = now()
receipt["phase"] = "finished"
save() # The run lock is held through final persistence and cleanup.
progress(force=True)
finally:
for sig, handler in old_handlers.items():
signal.signal(sig, handler)
result = summarize(
read_receipt(workspace_root, getattr(args, "state_dir", None), run_id)
)
result["receipt_path"] = str(run_dir / "receipt.json")
result["summary"].append("Receipt: " + result["receipt_path"])
return result
def register(subparsers):
check = subparsers.add_parser(
"check", help="Run registered checks, keeping bounded logs and evidence"
)
check.add_argument(
"--profile", choices=("quick", "ui", "backend", "full"), default="quick"
)
check.add_argument("--repo", action="append", default=[])
check.add_argument("--changed", action="store_true")
check.add_argument("--jobs", type=int, default=2)
check.add_argument("--dry-run", action="store_true")
check.add_argument(
"--resume",
metavar="RUN",
help="Reuse verified phase checkpoints whose declared inputs and environment still match",
)
def handle(args):
from .catalog import build_stages, build_coverage
stages = build_stages(
args.workspace_root, args.profile, args.repo, args.changed, args.project
)
args.check_coverage = build_coverage(
args.workspace_root,
args.profile,
args.repo,
args.changed,
args.project,
stages=stages,
)
if stages:
stages[0]["coverage_notes"] = list(
dict.fromkeys(
[
*stages[0].get("coverage_notes", []),
*args.check_coverage["notes"],
]
)
)
return run_checks(args, stages)
check.set_defaults(handler=handle)
coverage = subparsers.add_parser(
"coverage",
help="Inventory declared suites, planned coverage, exclusions and unsupported commands",
)
coverage.add_argument(
"--profile", choices=("quick", "ui", "backend", "full"), default="quick"
)
coverage.add_argument("--repo", action="append", default=[])
coverage.add_argument("--changed", action="store_true")
def coverage_handler(args):
from .catalog import build_coverage
result = build_coverage(
args.workspace_root, args.profile, args.repo, args.changed, args.project
)
omitted = [
item
for item in result["suites"]
if item["disposition"] in {"excluded", "unsupported"}
]
result["summary"] = [
"Declared-suite coverage for "
+ args.profile
+ " (plan only, not execution evidence)",
", ".join(
f"{count} {name}" for name, count in sorted(result["counts"].items())
),
*result["notes"],
*[
f"{item['repo']} / {item['name']}: {item['disposition']}{item['reason']}"
for item in omitted[:12]
],
]
if len(omitted) > 12:
result["summary"].append(
f"{len(omitted) - 12} additional exclusions/unsupported suites are listed with --json."
)
return result
coverage.set_defaults(handler=coverage_handler)
runs = subparsers.add_parser(
"runs", help="List recent local runs with bounded cursor pagination"
)
runs.add_argument("--limit", type=int, default=10)
runs.add_argument(
"--before", help="Exclusive run-ID cursor from the preceding page"
)
runs.set_defaults(handler=list_runs)
latest = subparsers.add_parser(
"latest", help="Read the newest matching run; never substitute an older pass"
)
latest.set_defaults(handler=latest_run)
for name in ("status", "summary"):
parser = subparsers.add_parser(
name, help="Read a check run without reading or replaying its full logs"
)
parser.add_argument("run_id")
parser.set_defaults(
handler=lambda args: summarize(
read_receipt(args.workspace_root, args.state_dir, args.run_id)
)
)
resume = subparsers.add_parser(
"resume",
help="Replan a previous check selection and reuse only verified unchanged results",
)
resume.add_argument("run_id")
resume.add_argument("--jobs", type=int, default=2)
resume.add_argument("--dry-run", action="store_true")
def resume_handler(args):
previous = read_receipt(args.workspace_root, args.state_dir, args.run_id)
if previous.get("profile") not in {"quick", "ui", "backend", "full"}:
raise ValueError(
"This run has no reusable check profile; repeat its owning audit command"
)
previous_project = previous.get("project_file")
if args.project and str(args.project.resolve()) != previous_project:
raise ValueError("Resume project differs from the recorded project")
args.project = Path(previous_project) if previous_project else None
args.profile = previous["profile"]
args.repo = previous.get("selection", {}).get("repos", [])
args.changed = previous.get("selection", {}).get("changed", False)
args.resume = args.run_id
return handle(args)
resume.set_defaults(handler=resume_handler)
recover = subparsers.add_parser(
"recover", help="Mark an abandoned check run interrupted; never rerun a process"
)
recover.add_argument("run_id")
recover.add_argument("--apply", action="store_true")
recover.add_argument(
"--confirm-processes-stopped",
action="store_true",
help="Attest that surviving commands from the abandoned run were manually checked and stopped",
)
def recover_handler(args):
base = state_root(args.workspace_root, args.state_dir)
previous = read_receipt(args.workspace_root, args.state_dir, args.run_id)
if previous["status"] != "running":
return {
"status": "unchanged",
"summary": ["Run does not require interrupted-owner recovery."],
}
if not args.apply:
return {
"status": "planned",
"summary": [
"Abandoned run owner PID: " + str(previous.get("owner_pid")),
"A free run lock does not prove its child processes stopped after a hard kill. Inspect the recorded commands and stop any survivors before using --apply --confirm-processes-stopped. No checks will execute during recovery.",
],
}
if not args.confirm_processes_stopped:
raise ValueError(
"Hard-crash recovery requires manual verification that the recorded commands and surviving child processes stopped; then use --apply --confirm-processes-stopped"
)
with resource_lock(base / "locks", "run:" + args.run_id):
previous = read_receipt(args.workspace_root, args.state_dir, args.run_id)
if previous["status"] != "running":
return {
"status": "unchanged",
"summary": [
"Run completed before recovery acquired its lock; no state was changed."
],
}
previous.update(
status="interrupted",
phase="finished",
finished_at=now(),
snapshot_verified=False,
)
for item in previous["stages"]:
if item["status"] in {"running", "pending"}:
item["status"] = "interrupted"
atomic_json(base / "runs" / args.run_id / "receipt.json", _seal(previous))
result = summarize(previous)
result["summary"].append(
"The interrupted aggregate is not passing evidence. Resume may reuse independently verified matching phase checkpoints; other stages run again."
)
return result
recover.set_defaults(handler=recover_handler)
logs = subparsers.add_parser(
"logs",
help="Read bounded provisional output or a hash-verified final stage log",
)
logs.add_argument("run_id")
logs.add_argument("--stage", required=True)
logs.add_argument("--tail", type=int, default=40)
logs.add_argument(
"--final-only",
action="store_true",
help="Require a finalized, hash-verified stage log",
)
def logs_handler(args):
if not 1 <= args.tail <= 200:
raise ValueError("Log tail must be between 1 and 200 lines")
receipt = read_receipt(args.workspace_root, args.state_dir, args.run_id)
selected = next(
(stage for stage in receipt["stages"] if stage["id"] == args.stage), None
)
if not selected:
raise ValueError("Stage does not belong to this run")
if selected.get("log_sha256"):
text = _verified_log_bytes(
selected, state_root(args.workspace_root, args.state_dir)
).decode("utf-8", errors="replace")
details = {"provisional": False, "log_verified": True}
else:
if args.final_only:
raise ValueError(
"No finalized stage log is available yet; live output is provisional"
)
details = read_live(
args.workspace_root, args.state_dir, args.run_id, args.stage
)
text = details.pop("excerpt")
details["log_verified"] = False
excerpt = bounded_display(
redact("\n".join(text.splitlines()[-args.tail :])), 16384, tail_only=True
)
label = (
"Provisional live output — not verified evidence."
if details["provisional"]
else "Final stage log — hash verified; consult run status for overall verification."
)
return {
**details,
"run_id": args.run_id,
"stage": args.stage,
"run_status": receipt["status"],
"snapshot_verified": receipt["snapshot_verified"],
"excerpt": excerpt,
"summary": [label, excerpt],
}
logs.set_defaults(handler=logs_handler)