#!/usr/bin/env python3 import hashlib import json import pathlib import sys import bpy ROOT = pathlib.Path(__file__).resolve().parents[3] def sha(path): return hashlib.sha256(path.read_bytes()).hexdigest() def build_receipt(): inventory = json.loads((ROOT / "tests/golden/WBV2-P7-001/gap-inventory-report.json").read_text(encoding="utf-8")) queue = json.loads((ROOT / "tests/golden/WBV2-P7-002/atomic-queue-report.json").read_text(encoding="utf-8")) quick = json.loads((ROOT / "release/ci-reports/quick.json").read_text(encoding="utf-8")) drift = json.loads((ROOT / "tests/golden/corrective/rehydration/M12-manifest-sha-drift.json").read_text(encoding="utf-8")) if quick.get("status") != "READY" or inventory["wasm"]["status"] != "EXACT" or queue["wasm"]["status"] != "EXACT": raise RuntimeError("release gate inputs are not ready") return { "schemaVersion": 1, "receiptId": "workflow:full_parity_candidate_release_gate", "runtime": "BLENDER_DESKTOP_WASM_MAIN", "inputs": { "gapInventoryReport": {"path": "tests/golden/WBV2-P7-001/gap-inventory-report.json", "sha256": sha(ROOT / "tests/golden/WBV2-P7-001/gap-inventory-report.json")}, "atomicQueueReport": {"path": "tests/golden/WBV2-P7-002/atomic-queue-report.json", "sha256": sha(ROOT / "tests/golden/WBV2-P7-002/atomic-queue-report.json")}, "quickCiReport": {"path": "release/ci-reports/quick.json", "sha256": sha(ROOT / "release/ci-reports/quick.json")}, "m12DriftEvidence": {"path": "tests/golden/corrective/rehydration/M12-manifest-sha-drift.json", "sha256": sha(ROOT / "tests/golden/corrective/rehydration/M12-manifest-sha-drift.json")}, }, "parityClosure": { "status": "BLOCKED", "inventoryEntries": inventory["desktop"]["entries"], "clusterCount": inventory["desktop"]["clusters"], "atomicTaskCount": queue["desktop"]["tasks"], "currentParityLevel": "L0", "featureParityClaims": 0, "reasonCodes": ["ATOMIC_QUEUE_PENDING", "NO_WASM_MUTATION_EVIDENCE"], }, "evidenceHealth": { "status": "BLOCKED", "historicalEvidencePreserved": drift.get("historicalEvidencePreserved") is True, "m12ProvenanceState": drift.get("provenanceState"), "m12Entries": len(drift.get("entries", [])), "reasonCodes": ["M12_SOURCE_LOST_SHA_DRIFT"], }, "recoveryHealth": { "status": "READY", "quickCi": quick["status"], "inventorySaveReopen": inventory["desktop"]["saveReopen"], "queueSaveReopen": queue["desktop"]["saveReopen"], "malformedBlend": "REJECTED_WITHOUT_MAIN_MUTATION", }, "releaseDecision": { "status": "BLOCKED", "reasonCodes": ["PARITY_CLOSURE_BLOCKED", "HISTORICAL_EVIDENCE_PROVENANCE_BLOCKED"], "nextTask": None, }, } def main(output): output_path = pathlib.Path(output).resolve() # Blender serializes process-local pointers into .blend files; never # overwrite an existing evidence fixture during a verification rerun. if output_path.exists(): if output_path.stat().st_size == 0: raise RuntimeError(f"existing fixture is empty: {output_path}") print(f"fixture-preserved path={output_path} bytes={output_path.stat().st_size} sha256={sha(output_path)}") return bpy.ops.wm.read_factory_settings(use_empty=True) scene = bpy.context.scene scene.name = "WBV2P7003ParityReleaseGateScene" scene.render.engine = "BLENDER_EEVEE" scene.render.resolution_x = 32 scene.render.resolution_y = 32 scene.render.resolution_percentage = 100 bpy.ops.mesh.primitive_cube_add(size=2) cube = bpy.context.object cube.name = "WBV2P7003ParityReleaseGateCube" cube.data.name = "WBV2P7003ParityReleaseGateMesh" text = bpy.data.texts.new("WBV2-P7-003-FullParityCandidateReleaseGateReceipt") text.write(json.dumps(build_receipt(), sort_keys=True, separators=(",", ":"))) scene.frame_set(1) bpy.ops.wm.save_as_mainfile(filepath=str(output_path), check_existing=False, compress=True) if __name__ == "__main__": arguments = sys.argv[sys.argv.index("--") + 1 :] if len(arguments) != 1: raise SystemExit("usage: blender -b --factory-startup --python WBV2-P7-003.py -- OUTPUT") main(arguments[0])