ThePirateRay/scripts/prepare_m1_acceptance_kit.py
2026-10-21 15:26:33 +02:00

140 lines
4.4 KiB
Python

"""Create a checksummed M1 desktop acceptance kit from public fixtures only."""
from __future__ import annotations
import argparse
import hashlib
import json
import shutil
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
REPORT_TEMPLATE = "tests/fixtures/m1-manual-acceptance-template.json"
KIT_FILES = {
"inputs/synthetic-mopa-import.lbrn2": "tests/fixtures/synthetic-mopa-import.lbrn2",
"inputs/synthetic-jcz-device.lbdev": "tests/fixtures/synthetic-jcz-device.lbdev",
"inputs/synthetic-pirateray-recipe-metadata.json": (
"tests/fixtures/synthetic-pirateray-recipe-metadata.json"
),
}
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_kit(
output: Path,
repository_commit: str | None = None,
artifact_verification: Path | None = None,
) -> dict:
verification_source = None
if artifact_verification is not None:
verification_source = artifact_verification.resolve()
verification = json.loads(
verification_source.read_text(encoding="utf-8-sig")
)
if (
verification.get("status") != "passed"
or verification.get("hardware_access") is not False
or verification.get("executed_binaries") is not False
or verification.get("manual_interaction_required") is not True
):
raise ValueError("artifact-verification report did not pass the no-device gate")
output = output.resolve()
if output.exists() and any(output.iterdir()):
raise ValueError(f"acceptance-kit directory is not empty: {output}")
output.mkdir(parents=True, exist_ok=True)
records = []
for destination, source_name in KIT_FILES.items():
source = ROOT / source_name
target = output / destination
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(source, target)
records.append({
"path": destination,
"source": source_name,
"classification": "synthetic-public",
"bytes": target.stat().st_size,
"sha256": _sha256(target),
})
commit = repository_commit or _repository_commit()
template_path = ROOT / REPORT_TEMPLATE
manifest = {
"schema_version": 2,
"kit": "M1 packaged no-device desktop acceptance",
"repository_commit": commit,
"hardware_access_authorized": False,
"contains_private_evidence": False,
"report_template": {
"source": REPORT_TEMPLATE,
"sha256": _sha256(template_path),
},
"files": records,
}
manifest_path = output / "kit-manifest.json"
manifest_path.write_text(
json.dumps(manifest, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
newline="\n",
)
report = json.loads(template_path.read_text(encoding="utf-8"))
report["acceptance_kit"] = {
"manifest_path": "kit-manifest.json",
"manifest_sha256": _sha256(manifest_path),
"repository_commit": commit,
}
if verification_source is not None:
verification_target = output / "artifact-verification.json"
shutil.copyfile(verification_source, verification_target)
report["artifact_verification"] = {
"status": "passed",
"report_path": verification_target.name,
"report_sha256": _sha256(verification_target),
}
(output / "manual-report.json").write_text(
json.dumps(report, indent=2) + "\n",
encoding="utf-8",
newline="\n",
)
return manifest
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"--output",
type=Path,
default=ROOT / ".build" / "acceptance-kit",
)
parser.add_argument(
"--artifact-verification",
type=Path,
help="passing JSON output from verify_rayforge_windows_checkpoint.py",
)
args = parser.parse_args()
result = create_kit(args.output, artifact_verification=args.artifact_verification)
print(f"kit={args.output.resolve()}")
print(f"files={len(result['files'])}")
print(f"repository_commit={result['repository_commit']}")
return 0
if __name__ == "__main__":
raise SystemExit(main())