Files
RustRaptor/tools/hd_art_derivation.py

92 lines
5.0 KiB
Python

"""Read-only lineage checks for explicitly cleaned imagegen artwork.
This records how reviewed pixels were made; it does not grant artistic approval
or execute a recipe. Jobs without an artwork_derivation remain unchanged.
"""
import hashlib
import json
from pathlib import Path
import re
def checked_file(root, reference):
if (not isinstance(reference, dict) or set(reference) != {"path", "sha256"}
or not isinstance(reference["path"], str)
or not re.fullmatch(r"[0-9a-f]{64}", str(reference["sha256"]))):
raise ValueError("derivation needs an exact path/SHA-256 reference")
relative = Path(reference["path"])
if relative.is_absolute() or ".." in relative.parts or not relative.parts:
raise ValueError("derivation reference must be a portable relative path")
root = root.resolve()
path = (root / relative).resolve()
if not path.is_relative_to(root) or not path.is_file():
raise ValueError("derivation reference escapes its root or is not a file")
payload = path.read_bytes()
if hashlib.sha256(payload).hexdigest() != reference["sha256"]:
raise ValueError(f"derivation file changed: {relative}")
return payload
def checked_json(root, reference):
value = json.loads(checked_file(root, reference))
if not isinstance(value, dict):
raise ValueError("derivation JSON must contain an object")
return value
def validate_derivation(root, project, candidate, review, rows, qa):
reference = candidate.get("artwork_derivation")
if reference is None:
if "artwork_derivation" in candidate or "artwork_derivation" in review:
raise ValueError("derivation cannot be null or present only in review")
return None
if review.get("artwork_derivation") != reference:
raise ValueError("artistic review does not bind this derivation")
lineage = checked_json(root, reference)
if (type(lineage.get("format_version")) is not int or lineage["format_version"] != 1
or lineage.get("kind") != "deterministic_cleanup"
or not isinstance(lineage.get("method"), str) or not lineage["method"].strip()):
raise ValueError("unsupported or unnamed artwork derivation")
parents = lineage.get("parents")
if not isinstance(parents, list) or not parents:
raise ValueError("derivation needs original imagegen parents")
for parent in parents:
if not isinstance(parent, dict) or set(parent) != {"raw_png", "imagegen_receipt", "prompt"}:
raise ValueError("derivation parent needs raw PNG, imagegen receipt and prompt")
if not checked_file(root, parent["raw_png"]).startswith(b"\x89PNG\r\n\x1a\n"):
raise ValueError("derivation parent is not a PNG")
# Preserve the actual receipt verbatim. Its historical format is not
# rewritten to pretend that cleanup was another generator invocation.
checked_json(root, parent["imagegen_receipt"])
if not checked_file(root, parent["prompt"]).strip():
raise ValueError("derivation parent prompt is empty")
tools = lineage.get("tools")
if not isinstance(tools, list) or not tools:
raise ValueError("derivation needs a checked-in cleanup tool")
for tool in tools:
checked_file(project, tool)
if Path(tool["path"]).parts[0] != "tools" or Path(tool["path"]).suffix != ".py":
raise ValueError("derivation tool must be a project tools Python file")
checked_json(root, lineage.get("recipe"))
execution = checked_json(root, lineage.get("execution_receipt")).get("execution", {})
if not isinstance(execution, dict):
raise ValueError("derivation execution must contain an object")
if lineage.get("sources") != {asset: frame["source_sha256"] for asset, frame in rows.items()}:
raise ValueError("derivation Source inventory differs from selected artwork")
files = {frame["file"] for frame in rows.values()}
outputs = lineage.get("outputs")
if not isinstance(outputs, dict) or set(outputs) != files:
raise ValueError("derivation output inventory differs from selected artwork")
for path, sha in outputs.items():
payload = checked_file(root, {"path": path, "sha256": sha})
if not payload.startswith(b"\x89PNG\r\n\x1a\n") or qa.get("artwork_sha256", {}).get(path) != sha:
raise ValueError("derivation output differs from runtime-reviewed PNG")
if (execution.get("status") != "success" or execution.get("method") != lineage["method"]
or execution.get("recipe_sha256") != lineage["recipe"]["sha256"]
or execution.get("tool_sha256") not in {tool["sha256"] for tool in tools}
or execution.get("source_sha256") != lineage["sources"]
or execution.get("parent_raw_sha256") != sorted({p["raw_png"]["sha256"] for p in parents})
or execution.get("output_sha256") != {asset: outputs[frame["file"]] for asset, frame in rows.items()}):
raise ValueError("derivation execution does not bind actual recipe, tool, inputs and outputs")
return reference