build: verify portable Windows checkpoints

This commit is contained in:
Codex 2026-10-21 08:33:50 +02:00
parent f8d3c889e3
commit 28de151e65
9 changed files with 251 additions and 8 deletions

View file

@ -129,8 +129,8 @@ likely data leaks. They do not certify hardware safety.
Rayforge source and its Windows build stack are not vendored or downloaded by
these checks. The exact upstream revision is recorded in
`upstreams/rayforge.json`; run `scripts/prepare_rayforge_build.ps1` explicitly
to populate the ignored build cache. Dependency installation and bundle
creation require separate command switches.
to populate the ignored build cache. The repository does not install the
Windows toolchain; bundle creation requires an already reviewed runtime.
## Safety boundary

View file

@ -42,6 +42,21 @@ Before staging, the script rejects transport imports in the addon. The build
manifest records the exact upstream commit and hashes every addon source file,
the portable archive and installer. It does not claim the GUI has been tested.
Copy the ZIP, installer and JSON manifest together, then verify an externally
produced checkpoint without launching either binary:
```powershell
python scripts/verify_rayforge_windows_checkpoint.py `
.build/artifacts/rayforge-windows-checkpoint.json `
--output .build/artifacts/rayforge-windows-verification.json
```
The verifier requires portable artifact names, checks sizes and SHA-256
digests, rejects unsafe ZIP paths, confirms the pinned Rayforge commit and
compares every packaged addon file with its recorded digest. It never executes
the application or installer, and a passing result retains the manual GUI
interaction gate.
## Automated acceptance
Run with the isolated Rayforge Python environment:
@ -65,7 +80,7 @@ blocks encoding.
Use only the isolated no-device profile and an empty work area. Do not connect
or configure a controller.
1. Verify the artifact and addon hashes against the build manifest.
1. Pass the non-executing artifact verifier above.
2. Launch the portable build with a fresh application-data directory.
3. Confirm the UI identifies the state as offline/no-device.
4. Open synthetic geometry and inspect the 110 × 110 mm synthetic field.

View file

@ -321,6 +321,9 @@ The transport-free portions are now consolidated by the
It reruns the public suite and leak guard and hashes the validated regression
reports. The on-demand build entry point fetches pinned source and stages the
addon but deliberately refuses to install a missing Windows toolchain.
An artifact produced elsewhere can be checked locally without execution: the
verifier rejects unsafe paths or changed bytes and confirms the pin plus every
packaged addon source before the manual GUI checklist may begin.
## Decision rules

View file

@ -75,8 +75,11 @@ evidence for:
- independent layer visibility/output state and ordered serialization;
- material-recipe matching plus a fail-closed PirateRay provenance and exact
machine/source/lens/correction compatibility gate;
- one consolidated no-device acceptance report that reruns all 69 public
tests and the leak guard, then hashes the regression evidence.
- one consolidated no-device acceptance report that reruns all 74 public
tests and the leak guard, then hashes the regression evidence;
- a non-executing Windows artifact verifier that checks portable paths,
archive safety, sizes, hashes, the pinned Rayforge revision and every
packaged addon source while retaining the manual GUI gate.
The remaining M1 gates are deliberately explicit:

View file

@ -88,19 +88,21 @@ $addonFiles = Get-ChildItem -LiteralPath $addonSource -Recurse -File |
}
}
$result = [ordered]@{
schema_version = 1
checkpoint = "Rayforge Windows no-device bundle"
rayforge_commit = $verifiedCommit
addon = "the_pirate_ray_jcz"
addon_packaged_root = "rayforge/builtin_addons/rayforge-addon-the-pirate-ray-jcz"
addon_source_files = $addonFiles
hardware_access = $false
transport = $null
bundle = [ordered]@{
path = $archive
path = Split-Path -Leaf $archive
sha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $archive).Hash.ToLowerInvariant()
bytes = (Get-Item -LiteralPath $archive).Length
}
installer = [ordered]@{
path = $installer
path = Split-Path -Leaf $installer
sha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $installer).Hash.ToLowerInvariant()
bytes = (Get-Item -LiteralPath $installer).Length
}

View file

@ -64,7 +64,7 @@ if (-not $BuildWindowsBundle) {
$bash = "C:\msys64\usr\bin\bash.exe"
if (-not (Test-Path -LiteralPath $bash)) {
throw "MSYS2 is required for the upstream Windows build. Install it explicitly before using build flags."
throw "A reviewed MSYS2 Windows build runtime is not available. No dependencies were installed."
}
$msysCheckout = (& $bash -lc "cygpath -u '$checkout'").Trim()

View file

@ -0,0 +1,121 @@
"""Verify a packaged no-device checkpoint without executing its binaries."""
from __future__ import annotations
import argparse
import hashlib
import json
import re
import zipfile
from pathlib import Path, PurePosixPath
ROOT = Path(__file__).resolve().parents[1]
UPSTREAM = json.loads(
(ROOT / "upstreams" / "rayforge.json").read_text(encoding="utf-8")
)
SHA256 = re.compile(r"^[0-9a-f]{64}$")
def _digest(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def _artifact_path(root: Path, value: str) -> Path:
relative = PurePosixPath(value)
if relative.is_absolute() or ".." in relative.parts or len(relative.parts) != 1:
raise ValueError(f"artifact path must be one filename: {value!r}")
return root / relative.name
def _check_file(root: Path, record: dict, label: str) -> Path:
path = _artifact_path(root, record["path"])
data = path.read_bytes()
if record.get("bytes") != len(data):
raise ValueError(f"{label} size mismatch")
digest = record.get("sha256", "")
if not SHA256.fullmatch(digest) or digest != _digest(data):
raise ValueError(f"{label} SHA-256 mismatch")
return path
def verify(manifest_path: Path) -> dict:
manifest_path = manifest_path.resolve()
manifest = json.loads(manifest_path.read_text(encoding="utf-8-sig"))
if manifest.get("schema_version") != 1:
raise ValueError("unsupported checkpoint schema")
if manifest.get("rayforge_commit") != UPSTREAM["commit"]:
raise ValueError("Rayforge commit does not match the repository pin")
if manifest.get("hardware_access") is not False or manifest.get("transport") is not None:
raise ValueError("checkpoint does not declare a no-device boundary")
if manifest.get("manual_interaction_required") is not True:
raise ValueError("checkpoint must retain the manual interaction gate")
artifact_root = manifest_path.parent
bundle = _check_file(artifact_root, manifest["bundle"], "bundle")
installer = _check_file(artifact_root, manifest["installer"], "installer")
packaged_root = PurePosixPath(manifest["addon_packaged_root"])
if packaged_root.is_absolute() or ".." in packaged_root.parts:
raise ValueError("unsafe packaged addon root")
with zipfile.ZipFile(bundle) as archive:
files = {
PurePosixPath(info.filename): info
for info in archive.infolist()
if not info.is_dir()
}
for path in files:
if path.is_absolute() or ".." in path.parts:
raise ValueError("bundle contains an unsafe path")
executable = manifest["bundle"]["path"].removesuffix(".zip") + ".exe"
if not any(path.name == executable for path in files):
raise ValueError("bundle application executable is missing")
verified_addon_files = 0
for record in manifest["addon_source_files"]:
relative = PurePosixPath(record["path"])
if relative.is_absolute() or ".." in relative.parts:
raise ValueError("unsafe addon source path")
matches = [
path for path in files
if tuple(path.parts[-len((packaged_root / relative).parts):])
== (packaged_root / relative).parts
]
if len(matches) != 1:
raise ValueError(f"packaged addon file missing or ambiguous: {relative}")
digest = record.get("sha256", "")
if not SHA256.fullmatch(digest):
raise ValueError(f"invalid addon SHA-256: {relative}")
if _digest(archive.read(files[matches[0]])) != digest:
raise ValueError(f"packaged addon SHA-256 mismatch: {relative}")
verified_addon_files += 1
return {
"status": "passed",
"rayforge_commit": manifest["rayforge_commit"],
"hardware_access": False,
"executed_binaries": False,
"bundle_sha256": manifest["bundle"]["sha256"],
"installer_sha256": manifest["installer"]["sha256"],
"verified_addon_files": verified_addon_files,
"manual_interaction_required": True,
"installer_name": installer.name,
}
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("manifest", type=Path)
parser.add_argument("--output", type=Path)
args = parser.parse_args()
result = verify(args.manifest)
rendered = json.dumps(result, indent=2, sort_keys=True) + "\n"
if args.output:
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(rendered, encoding="utf-8", newline="\n")
print(rendered, end="")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -6,6 +6,7 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
ACCEPTANCE = ROOT / "scripts" / "run_m1_no_device_acceptance.py"
BUILD = ROOT / "scripts" / "build_rayforge_windows_checkpoint.ps1"
VERIFY = ROOT / "scripts" / "verify_rayforge_windows_checkpoint.py"
UPSTREAM = ROOT / "upstreams" / "rayforge.json"
@ -54,6 +55,13 @@ class M1AcceptancePipelineTests(unittest.TestCase):
for forbidden in ("import usb", "import serial", "import socket"):
self.assertIn(forbidden, source)
def test_checkpoint_has_a_non_executing_artifact_verifier(self):
source = VERIFY.read_text(encoding="utf-8")
self.assertIn("zipfile.ZipFile", source)
self.assertIn("Rayforge commit does not match", source)
self.assertIn('"executed_binaries": False', source)
self.assertNotIn("subprocess", source)
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,91 @@
import hashlib
import json
import tempfile
import unittest
import zipfile
from pathlib import Path
from scripts.verify_rayforge_windows_checkpoint import ROOT, UPSTREAM, verify
ADDON_ROOT = "rayforge/builtin_addons/rayforge-addon-the-pirate-ray-jcz"
class WindowsCheckpointVerifierTests(unittest.TestCase):
def _checkpoint(self, root: Path, unsafe_member: str | None = None) -> Path:
bundle_name = f"rayforge-vpirateray-{UPSTREAM['commit'][:12]}.zip"
executable = bundle_name.removesuffix(".zip") + ".exe"
addon_files = {
"rayforge-addon.yaml": b"name: the_pirate_ray_jcz\n",
"pirate_ray_jcz/worker.py": b"TRANSPORT = None\n",
}
bundle = root / bundle_name
with zipfile.ZipFile(bundle, "w") as archive:
archive.writestr(f"{bundle_name[:-4]}/{executable}", b"synthetic")
for relative, data in addon_files.items():
archive.writestr(f"{bundle_name[:-4]}/_internal/{ADDON_ROOT}/{relative}", data)
if unsafe_member:
archive.writestr(unsafe_member, b"unsafe")
installer = root / f"{bundle_name[:-4]}-installer.exe"
installer.write_bytes(b"synthetic installer, never executed")
manifest = {
"schema_version": 1,
"checkpoint": "Rayforge Windows no-device bundle",
"rayforge_commit": UPSTREAM["commit"],
"addon": "the_pirate_ray_jcz",
"addon_packaged_root": ADDON_ROOT,
"addon_source_files": [
{"path": name, "sha256": hashlib.sha256(data).hexdigest()}
for name, data in addon_files.items()
],
"hardware_access": False,
"transport": None,
"bundle": {
"path": bundle.name,
"sha256": hashlib.sha256(bundle.read_bytes()).hexdigest(),
"bytes": bundle.stat().st_size,
},
"installer": {
"path": installer.name,
"sha256": hashlib.sha256(installer.read_bytes()).hexdigest(),
"bytes": installer.stat().st_size,
},
"manual_interaction_required": True,
}
path = root / "rayforge-windows-checkpoint.json"
path.write_text(json.dumps(manifest), encoding="utf-8")
return path
def test_valid_checkpoint_is_verified_without_execution(self):
with tempfile.TemporaryDirectory(dir=ROOT / ".build") as directory:
result = verify(self._checkpoint(Path(directory)))
self.assertEqual(result["status"], "passed")
self.assertFalse(result["executed_binaries"])
self.assertEqual(result["verified_addon_files"], 2)
def test_modified_installer_is_rejected(self):
with tempfile.TemporaryDirectory(dir=ROOT / ".build") as directory:
manifest = self._checkpoint(Path(directory))
data = json.loads(manifest.read_text(encoding="utf-8"))
(manifest.parent / data["installer"]["path"]).write_bytes(b"changed")
with self.assertRaisesRegex(ValueError, "installer (size|SHA-256) mismatch"):
verify(manifest)
def test_bundle_path_traversal_is_rejected(self):
with tempfile.TemporaryDirectory(dir=ROOT / ".build") as directory:
manifest = self._checkpoint(Path(directory), "../escape.txt")
with self.assertRaisesRegex(ValueError, "unsafe path"):
verify(manifest)
def test_manifest_cannot_reference_external_artifacts(self):
with tempfile.TemporaryDirectory(dir=ROOT / ".build") as directory:
manifest = self._checkpoint(Path(directory))
data = json.loads(manifest.read_text(encoding="utf-8"))
data["bundle"]["path"] = "../outside.zip"
manifest.write_text(json.dumps(data), encoding="utf-8")
with self.assertRaisesRegex(ValueError, "one filename"):
verify(manifest)
if __name__ == "__main__":
unittest.main()