ThePirateRay/scripts/prepare_m3_review_packet.py
2026-10-21 21:55:28 +02:00

130 lines
3.6 KiB
Python

"""Freeze a review-ready M3 plan into a non-authorizing review packet."""
from __future__ import annotations
import argparse
import hashlib
import json
import shutil
import subprocess
from pathlib import Path
if __package__:
from scripts.validate_m3_hardware_protocol import validate
else:
from validate_m3_hardware_protocol import validate
ROOT = Path(__file__).resolve().parents[1]
def _sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def _repository_commit() -> str:
result = subprocess.run(
["git", "rev-parse", "HEAD"],
cwd=ROOT,
check=True,
capture_output=True,
text=True,
)
return result.stdout.strip()
def create_packet(
plan_path: Path,
output: Path,
repository_commit: str | None = None,
) -> dict:
source = plan_path.resolve()
validation = validate(source)
if validation["status"] != "ready_for_review":
raise ValueError("M3 plan is not ready for review")
output = output.resolve()
if output.exists() and any(output.iterdir()):
raise ValueError(f"review-packet directory is not empty: {output}")
output.mkdir(parents=True, exist_ok=True)
frozen_plan = output / "plan.json"
shutil.copyfile(source, frozen_plan)
plan = json.loads(frozen_plan.read_text(encoding="utf-8-sig"))
commit = repository_commit or _repository_commit()
review = {
"schema_version": 1,
"review": "M3 hardware research session plan",
"binding": {
"plan_path": frozen_plan.name,
"plan_sha256": _sha256(frozen_plan),
"repository_commit": commit,
},
"session_window": {
"starts_at": plan["session"]["planned_at"],
"ends_at": plan["session"]["ends_at"],
},
"decision": "not_reviewed",
"reviewer": None,
"reviewed_at": None,
"notes": "",
"hardware_access_authorized": False,
"separate_human_approval_required": True,
}
review_path = output / "approval-record-template.json"
review_path.write_text(
json.dumps(review, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
newline="\n",
)
manifest = {
"schema_version": 1,
"packet": "M3 hardware research plan review",
"repository_commit": commit,
"contains_private_evidence": False,
"hardware_access_authorized": False,
"files": [
{
"path": frozen_plan.name,
"bytes": frozen_plan.stat().st_size,
"sha256": _sha256(frozen_plan),
},
{
"path": review_path.name,
"bytes": review_path.stat().st_size,
"sha256": _sha256(review_path),
},
],
}
manifest_path = output / "packet-manifest.json"
manifest_path.write_text(
json.dumps(manifest, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
newline="\n",
)
return manifest
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("plan", type=Path)
parser.add_argument(
"--output",
type=Path,
default=ROOT / ".build" / "m3-review-packet",
)
args = parser.parse_args()
try:
result = create_packet(args.plan, args.output)
except ValueError as error:
parser.error(str(error))
print(f"packet={args.output.resolve()}")
print(f"plan_sha256={result['files'][0]['sha256']}")
print("hardware_access_authorized=false")
return 0
if __name__ == "__main__":
raise SystemExit(main())