92 lines
5.0 KiB
Python
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
|