"""Replay a checked batch of existing boss cleanup recipes without new pixel math. Each frame is passed to the hash-bound hd_boss_cleanup.run_recipe unchanged. This dispatcher records actual subruns and binds one outer portable recipe. It does not approve artwork or register assets. """ from __future__ import annotations import argparse import hashlib import importlib import json from pathlib import Path import sys METHOD = "derived-generated-boss-source-geometry-batch-v1" def sha(path): return hashlib.sha256(Path(path).read_bytes()).hexdigest() def digest(value): return (type(value) is str and len(value) == 64 and all(c in "0123456789abcdef" for c in value)) def contained(base, value): if type(value) is not str or not value or "\\" in value: raise ValueError("expected portable relative path") path = Path(value) if path.is_absolute() or any(part in (".", "..") for part in path.parts): raise ValueError("expected contained relative path") result = (base / path).resolve() if not result.is_relative_to(base.resolve()) or not result.is_file(): raise ValueError("missing or escaped input") return result def checked_ref(base, value): if type(value) is not dict or set(value) != {"path", "sha256"} or not digest(value["sha256"]): raise ValueError("expected path/sha256 binding") path = contained(base, value["path"]) if sha(path) != value["sha256"]: raise ValueError("input hash mismatch") return path def plan(recipe_path, project): recipe_path = Path(recipe_path).resolve() recipe = json.loads(recipe_path.read_bytes()) if (type(recipe) is not dict or set(recipe) != { "format_version", "kind", "method", "cleanup_tool", "decoder", "frames"} or type(recipe["format_version"]) is not int or recipe["format_version"] != 1 or recipe["kind"] != "boss_cleanup_reproduce_batch" or recipe["method"] != METHOD): raise ValueError("unsupported batch recipe") project = Path(project).resolve() for field, expected in [("cleanup_tool", "tools/hd_boss_cleanup.py"), ("decoder", "tools/hd_frame_policy.py")]: if type(recipe[field]) is not dict or recipe[field].get("path") != expected: raise ValueError("unsupported cleanup implementation") checked_ref(project, recipe[field]) frames = recipe["frames"] if type(frames) is not list or not 1 <= len(frames) <= 32: raise ValueError("expected bounded nonempty frame list") planned, seen = [], set() for frame in frames: if type(frame) is not dict or set(frame) != {"asset", "recipe", "expected_output_sha256"}: raise ValueError("invalid batch frame") asset = frame["asset"] if (type(asset) is not str or not 1 <= len(asset) <= 8 or any(c not in "0123456789abcdef" for c in asset) or asset in seen or not digest(frame["expected_output_sha256"])): raise ValueError("invalid or duplicate asset/output binding") seen.add(asset) subpath = checked_ref(recipe_path.parent, frame["recipe"]) sub = json.loads(subpath.read_bytes()) if type(sub) is not dict or sub.get("asset") != asset: raise ValueError("subrecipe asset mismatch") # Validate all declared input bodies before creating any output directory. for key in ("source", "palette", "parent_raw"): value = sub.get(key) if type(value) is not dict or set(value) != {"path", "sha256"} or not digest(value["sha256"]): raise ValueError("invalid subrecipe input binding") p = Path(value["path"]) if not p.is_absolute(): p = subpath.parent / p if not p.is_file() or sha(p) != value["sha256"]: raise ValueError("subrecipe input hash mismatch") planned.append((frame, subpath, sub)) return recipe, planned def run_batch(recipe_path, output, project): recipe_path, output, project = Path(recipe_path).resolve(), Path(output), Path(project).resolve() if output.exists(): raise ValueError("output exists; do not overwrite old outputs") initial_recipe_sha256 = sha(recipe_path) recipe, planned = plan(recipe_path, project) if sha(recipe_path) != initial_recipe_sha256: raise ValueError("batch recipe changed during planning") sys.path.insert(0, str(project / "tools")) cleanup = importlib.import_module("hd_boss_cleanup") decoder = importlib.import_module("hd_frame_policy") if (Path(cleanup.__file__).resolve() != project / "tools/hd_boss_cleanup.py" or Path(decoder.__file__).resolve() != project / "tools/hd_frame_policy.py" or sha(cleanup.__file__) != recipe["cleanup_tool"]["sha256"] or sha(decoder.__file__) != recipe["decoder"]["sha256"]): raise ValueError("loaded implementation differs from bound tools") output.mkdir(parents=True) subruns, sources, parents, outputs = [], {}, set(), {} def verify_pinned_inputs(): if sha(recipe_path) != initial_recipe_sha256: raise ValueError("batch recipe changed during execution") checked_ref(project, recipe["cleanup_tool"]) checked_ref(project, recipe["decoder"]) for pinned_frame, pinned_path, pinned_sub in planned: if sha(pinned_path) != pinned_frame["recipe"]["sha256"]: raise ValueError("subrecipe changed during execution") for key in ("source", "palette", "parent_raw"): value = pinned_sub[key] path = Path(value["path"]) if not path.is_absolute(): path = pinned_path.parent / path if sha(path) != value["sha256"]: raise ValueError("subrecipe input changed during execution") verify_pinned_inputs() for frame, subpath, sub in planned: verify_pinned_inputs() asset = frame["asset"] destination = output / asset report = cleanup.run_recipe(subpath, destination) png = destination / f"{asset}-derived.png" actual = sha(png) if actual != frame["expected_output_sha256"]: raise ValueError(f"accepted PNG not reproduced: {asset}") expected_execution = {"status": "success", "method": "derived-generated-boss-source-geometry-rgb-preserved-v1", "recipe_sha256": frame["recipe"]["sha256"], "tool_sha256": recipe["cleanup_tool"]["sha256"], "source_sha256": {asset: sub["source"]["sha256"]}, "parent_raw_sha256": [sub["parent_raw"]["sha256"]], "output_sha256": {asset: actual}} if type(report) is not dict or report.get("execution") != expected_execution: raise ValueError("subrun execution receipt differs from pinned inputs") verify_pinned_inputs() sources[asset] = sub["source"]["sha256"] parents.add(sub["parent_raw"]["sha256"]) outputs[asset] = actual provenance = destination / "derived-provenance.json" subruns.append({"asset": asset, "recipe": frame["recipe"], "execution": report["execution"], "provenance": {"path": f"{asset}/derived-provenance.json", "sha256": sha(provenance)}}) verify_pinned_inputs() result = {"format_version": 1, "kind": "boss_cleanup_actual_batch_execution", "execution": {"status": "success", "method": METHOD, "recipe_sha256": initial_recipe_sha256, "tool_sha256": sha(__file__), "source_sha256": sources, "parent_raw_sha256": sorted(parents), "output_sha256": outputs}, "actual_dispatcher_calls": 1, "actual_cleanup_subruns": len(subruns), "subruns": subruns, "cleanup_tool": recipe["cleanup_tool"], "decoder": recipe["decoder"], "scope": "Bookkeeping replay only; existing cleanup pixel algorithm unchanged; no approval/registration."} (output / "batch-execution.json").write_text(json.dumps(result, indent=2, sort_keys=True) + "\n") return result def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("recipe", type=Path) parser.add_argument("output_new_directory", type=Path) parser.add_argument("project", type=Path) args = parser.parse_args() report = run_batch(args.recipe, args.output_new_directory, args.project) print(json.dumps(report["execution"], sort_keys=True)) if __name__ == "__main__": main()