"""Collect release/restart provenance and enforce audit-checkout clean gate.""" from __future__ import annotations import argparse import json import shlex from datetime import UTC, datetime from pathlib import Path from trex_core.adapters.persistence.ops_checks import ( collect_release_provenance, isolate_dirty_checkout_files, ) _DEFAULT_OUTPUT_DIR = Path("artifacts/validation/release_provenance") _DEFAULT_ISOLATE_DIR = Path("artifacts/validation/release_provenance_isolation") def _record_path(output_dir: Path, *, instance: str, command_class: str) -> Path: stamp = datetime.now(tz=UTC).strftime("%Y%m%dT%H%M%SZ") return output_dir / f"{stamp}--{instance}--{command_class}.json" def _parse_capture_command(raw: str) -> tuple[str, list[str]]: if "=" not in raw: raise ValueError("capture command must be KEY=command") key, command = raw.split("=", 1) key = key.strip() parts = shlex.split(command.strip()) if not key or not parts: raise ValueError("capture command must be KEY=command") return key, parts def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--actor", required=True) parser.add_argument("--reason", required=True) parser.add_argument("--instance", required=True) parser.add_argument("--command-class", required=True) parser.add_argument("--checkout-dir", default=".") parser.add_argument("--expected-oci-revision") parser.add_argument("--image-digest") parser.add_argument("--runtime-file", action="append", default=[]) parser.add_argument("--broker-log-path", default="/tmp/trex-core.log") parser.add_argument("--capture-command", action="append", default=[]) parser.add_argument("--output-dir", default=str(_DEFAULT_OUTPUT_DIR)) parser.add_argument("--isolate-dir", default=str(_DEFAULT_ISOLATE_DIR)) parser.add_argument("--allow-dirty", action="store_true") args = parser.parse_args(argv) capture_commands = {} for raw in args.capture_command: key, command = _parse_capture_command(raw) capture_commands[key] = command report = collect_release_provenance( actor=args.actor, reason=args.reason, instance=args.instance, command_class=args.command_class, checkout_dir=args.checkout_dir, expected_oci_revision=args.expected_oci_revision, image_digest=args.image_digest, runtime_files=args.runtime_file, capture_commands=capture_commands, broker_log_path=args.broker_log_path, ) output_dir = Path(args.output_dir).resolve() output_dir.mkdir(parents=True, exist_ok=True) output_path = _record_path(output_dir, instance=args.instance, command_class=args.command_class) if report["checkout"]["dirty_paths"] and not args.allow_dirty: report["dirty_checkout_isolation"] = isolate_dirty_checkout_files( checkout_dir=args.checkout_dir, isolate_dir=args.isolate_dir, ) output_path.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") print(str(output_path)) if report["checkout"]["dirty_paths"] and not args.allow_dirty: return 2 return 0 if report["gate"]["passed"] else 1 if __name__ == "__main__": raise SystemExit(main())