mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-07 00:37:36 +00:00
293 lines
10 KiB
Python
293 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""Run machine-declared readiness assertion proof commands."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
from pathlib import Path
|
|
import shlex
|
|
import subprocess
|
|
import sys
|
|
from typing import Any
|
|
|
|
from control_plane import DEFAULT_CONTROL_PLANE, active_target_blocking_levels
|
|
from repo_file_io import REPO_ROOT, load_repo_json, read_repo_text
|
|
|
|
|
|
STATUS_REL = DEFAULT_CONTROL_PLANE["status_rel"]
|
|
|
|
|
|
def load_status_payload(*, staged: bool = False) -> dict[str, Any]:
|
|
return load_repo_json(STATUS_REL, staged=staged)
|
|
|
|
|
|
def _clean_relative_dir(path: str) -> str:
|
|
candidate = Path(path)
|
|
if candidate.is_absolute():
|
|
raise ValueError(f"cwd must be relative, got {path!r}")
|
|
normalized = candidate.as_posix()
|
|
if normalized != path or path.startswith("../") or "/../" in path:
|
|
raise ValueError(f"cwd must be a clean relative path, got {path!r}")
|
|
resolved = REPO_ROOT / path
|
|
if not resolved.exists():
|
|
raise ValueError(f"cwd {path!r} does not exist")
|
|
if not resolved.is_dir():
|
|
raise ValueError(f"cwd {path!r} is not a directory")
|
|
return path
|
|
|
|
|
|
def parse_args(argv: list[str]) -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(
|
|
description="Run proof commands declared in the active release profile status.json."
|
|
)
|
|
parser.add_argument(
|
|
"--blocking-level",
|
|
action="append",
|
|
choices=["repo-ready", "rc-ready", "release-ready"],
|
|
help="Run only assertions matching the given blocking level. Repeat to allow multiple values.",
|
|
)
|
|
parser.add_argument(
|
|
"--active-target",
|
|
action="store_true",
|
|
help=(
|
|
"Resolve blocking levels from the active target completion rule in control_plane.json "
|
|
"and run only proofs relevant to the current target phase."
|
|
),
|
|
)
|
|
parser.add_argument(
|
|
"--proof-type",
|
|
action="append",
|
|
choices=["automated", "manual", "hybrid"],
|
|
help="Run only assertions matching the given proof type. Repeat to allow multiple values.",
|
|
)
|
|
parser.add_argument(
|
|
"--assertion",
|
|
action="append",
|
|
help="Run only the given readiness assertion id. Repeat to allow multiple values.",
|
|
)
|
|
parser.add_argument(
|
|
"--staged",
|
|
action="store_true",
|
|
help="Read the readiness assertion catalog from the git index instead of the working tree.",
|
|
)
|
|
return parser.parse_args(argv)
|
|
|
|
|
|
def selected_proof_commands(
|
|
payload: dict[str, Any],
|
|
*,
|
|
blocking_levels: set[str] | None = None,
|
|
proof_types: set[str] | None = None,
|
|
assertion_ids: set[str] | None = None,
|
|
) -> tuple[list[dict[str, Any]], list[str]]:
|
|
selected: list[dict[str, Any]] = []
|
|
errors: list[str] = []
|
|
assertions = payload.get("readiness_assertions")
|
|
if not isinstance(assertions, list):
|
|
return [], ["status.json missing readiness_assertions list"]
|
|
|
|
for raw in assertions:
|
|
if not isinstance(raw, dict):
|
|
continue
|
|
assertion_id = str(raw.get("id", "")).strip()
|
|
blocking_level = str(raw.get("blocking_level", "")).strip()
|
|
proof_type = str(raw.get("proof_type", "")).strip()
|
|
if assertion_ids and assertion_id not in assertion_ids:
|
|
continue
|
|
if blocking_levels and blocking_level not in blocking_levels:
|
|
continue
|
|
if proof_types and proof_type not in proof_types:
|
|
continue
|
|
|
|
raw_commands = raw.get("proof_commands")
|
|
if raw_commands is None:
|
|
if proof_type == "automated":
|
|
errors.append(f"{assertion_id} is automated but has no proof_commands")
|
|
continue
|
|
if not isinstance(raw_commands, list) or not raw_commands:
|
|
errors.append(f"{assertion_id} proof_commands must be a non-empty list")
|
|
continue
|
|
|
|
for raw_command in raw_commands:
|
|
if not isinstance(raw_command, dict):
|
|
errors.append(f"{assertion_id} proof_commands contains a non-object entry")
|
|
continue
|
|
command_id = str(raw_command.get("id", "")).strip()
|
|
run = raw_command.get("run")
|
|
cwd = raw_command.get("cwd", ".")
|
|
if not command_id:
|
|
errors.append(f"{assertion_id} proof command is missing an id")
|
|
continue
|
|
if not isinstance(run, list) or not run or any(
|
|
not isinstance(entry, str) or not entry.strip() for entry in run
|
|
):
|
|
errors.append(f"{assertion_id}:{command_id} run must be a non-empty list of strings")
|
|
continue
|
|
if not isinstance(cwd, str) or not cwd.strip():
|
|
errors.append(f"{assertion_id}:{command_id} cwd must be a non-empty string when declared")
|
|
continue
|
|
try:
|
|
clean_cwd = _clean_relative_dir(cwd)
|
|
except ValueError as exc:
|
|
errors.append(f"{assertion_id}:{command_id} {exc}")
|
|
continue
|
|
selected.append(
|
|
{
|
|
"assertion_id": assertion_id,
|
|
"command_id": command_id,
|
|
"run": [str(entry) for entry in run],
|
|
"cwd": clean_cwd,
|
|
}
|
|
)
|
|
|
|
return selected, errors
|
|
|
|
|
|
def deduplicated_proof_commands(commands: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
grouped: dict[tuple[str, tuple[str, ...]], dict[str, Any]] = {}
|
|
ordered_keys: list[tuple[str, tuple[str, ...]]] = []
|
|
|
|
for command in commands:
|
|
cwd = str(command["cwd"])
|
|
run = tuple(str(entry) for entry in command["run"])
|
|
key = (cwd, run)
|
|
record = grouped.get(key)
|
|
if record is None:
|
|
record = {
|
|
"assertion_ids": [str(command["assertion_id"])],
|
|
"command_ids": [str(command["command_id"])],
|
|
"cwd": cwd,
|
|
"run": list(run),
|
|
}
|
|
grouped[key] = record
|
|
ordered_keys.append(key)
|
|
continue
|
|
assertion_id = str(command["assertion_id"])
|
|
command_id = str(command["command_id"])
|
|
if assertion_id not in record["assertion_ids"]:
|
|
record["assertion_ids"].append(assertion_id)
|
|
if command_id not in record["command_ids"]:
|
|
record["command_ids"].append(command_id)
|
|
|
|
return [grouped[key] for key in ordered_keys]
|
|
|
|
|
|
def _clean_repo_relative_file(path: str, *, cwd: str) -> str | None:
|
|
candidate = Path(cwd) / path
|
|
normalized = candidate.as_posix()
|
|
if candidate.is_absolute():
|
|
return None
|
|
if normalized.startswith("../") or "/../" in normalized:
|
|
return None
|
|
resolved = (REPO_ROOT / candidate).resolve()
|
|
try:
|
|
return resolved.relative_to(REPO_ROOT.resolve()).as_posix()
|
|
except ValueError:
|
|
return None
|
|
|
|
|
|
def _staged_python_script_run(
|
|
command: dict[str, Any],
|
|
) -> tuple[list[str], str, dict[str, str]] | None:
|
|
run = [str(entry) for entry in command["run"]]
|
|
if len(run) < 2:
|
|
return None
|
|
interpreter = run[0]
|
|
if Path(interpreter).name not in {"python", "python3"}:
|
|
return None
|
|
script_path = run[1]
|
|
if script_path.startswith("-"):
|
|
return None
|
|
rel = _clean_repo_relative_file(script_path, cwd=str(command["cwd"]))
|
|
if rel is None or not rel.endswith(".py"):
|
|
return None
|
|
script_dir = str((REPO_ROOT / rel).parent)
|
|
env = os.environ.copy()
|
|
current_pythonpath = env.get("PYTHONPATH")
|
|
env["PYTHONPATH"] = (
|
|
script_dir if not current_pythonpath else f"{script_dir}{os.pathsep}{current_pythonpath}"
|
|
)
|
|
return [interpreter, "-", *run[2:]], read_repo_text(rel, staged=True), env
|
|
|
|
|
|
def run_selected_proof_commands(commands: list[dict[str, Any]], *, staged: bool = False) -> int:
|
|
unique_commands = deduplicated_proof_commands(commands)
|
|
if not commands:
|
|
print("Readiness assertion guard: no matching proof commands.")
|
|
return 0
|
|
|
|
print(
|
|
"Running readiness assertion guard: "
|
|
f"commands={len(unique_commands)} selected={len(commands)}"
|
|
)
|
|
for command in unique_commands:
|
|
assertion_ids = [str(entry) for entry in command["assertion_ids"]]
|
|
command_ids = [str(entry) for entry in command["command_ids"]]
|
|
run = [str(entry) for entry in command["run"]]
|
|
cwd = REPO_ROOT / str(command["cwd"])
|
|
print(
|
|
f"[{','.join(assertion_ids)}] {','.join(command_ids)}: {shlex.join(run)}"
|
|
)
|
|
staged_python_run = _staged_python_script_run(command) if staged else None
|
|
if staged_python_run is None:
|
|
result = subprocess.run(run, cwd=cwd, check=False)
|
|
else:
|
|
staged_run, script_content, env = staged_python_run
|
|
result = subprocess.run(
|
|
staged_run,
|
|
cwd=cwd,
|
|
env=env,
|
|
input=script_content,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
if result.returncode != 0:
|
|
print(
|
|
"BLOCKED: readiness assertion proof failed for "
|
|
f"{','.join(assertion_ids)}:{','.join(command_ids)} "
|
|
f"(exit {result.returncode})"
|
|
)
|
|
return result.returncode or 1
|
|
|
|
print("Readiness assertion guard passed.")
|
|
return 0
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
args = parse_args(list(argv or []))
|
|
blocking_levels = set(args.blocking_level or [])
|
|
if args.active_target:
|
|
try:
|
|
active_levels = set(active_target_blocking_levels(staged=args.staged))
|
|
except ValueError as exc:
|
|
print(f"BLOCKED: {exc}")
|
|
return 1
|
|
if not active_levels:
|
|
print(
|
|
"Readiness assertion guard: active target declares no machine proof scope; "
|
|
"skipping phase-derived proof selection."
|
|
)
|
|
return 0
|
|
if blocking_levels:
|
|
blocking_levels &= active_levels
|
|
if not blocking_levels:
|
|
print("BLOCKED: explicit --blocking-level filters do not overlap the active target phase")
|
|
return 1
|
|
else:
|
|
blocking_levels = active_levels
|
|
commands, errors = selected_proof_commands(
|
|
load_status_payload(staged=args.staged),
|
|
blocking_levels=blocking_levels,
|
|
proof_types=set(args.proof_type or []),
|
|
assertion_ids=set(args.assertion or []),
|
|
)
|
|
if errors:
|
|
for error in errors:
|
|
print(f"BLOCKED: {error}")
|
|
return 1
|
|
return run_selected_proof_commands(commands, staged=args.staged)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main(sys.argv[1:]))
|