#!/usr/bin/env python3 """ Mint cat-copy.jpg by running the identity-cat recipe through artdag. Records full provenance: inputs, effects, software, hardware, owner. """ import hashlib import json import shutil import sys from datetime import datetime, timezone from pathlib import Path # Add artdag to path sys.path.insert(0, str(Path(__file__).parent.parent / "artdag")) from artdag.dag import DAG, Node, NodeType from artdag.engine import Engine from artdag import nodes # Register executors def file_hash(path: Path) -> str: """Compute SHA3-256 hash of a file.""" hasher = hashlib.sha3_256() actual_path = path.resolve() if path.is_symlink() else path with open(actual_path, "rb") as f: for chunk in iter(lambda: f.read(65536), b""): hasher.update(chunk) return hasher.hexdigest() def main(): # Registry hashes (from git.rose-ash.com/art-dag/registry) CAT_HASH = "33268b6e167deaf018cc538de12dbe562612b33e89a749391cef855b320a269b" IDENTITY_HASH = "640ea11ee881ebf4101af0a955439105ab11e763682b209e88ea08fc66e1cc03" ARTDAG_HASH = "96a5972de216aee12ec794dcad5f9360da2e676171eabf24a46dfe1ee5fee4b0" WORKSTATION_HASH = "964bf6e69dc4e2493f42375013caffe26404ec3cf8eb5d9bc170cd42a361523b" # Input cat_path = Path("/home/giles/Pictures/cat.jpg") output_path = Path("/home/giles/Pictures/cat-copy.jpg") print("=== Minting cat-copy.jpg via identity-cat recipe ===\n") # Verify input input_hash = file_hash(cat_path) print(f"Input: {cat_path}") print(f"Input hash: {input_hash}") assert input_hash == CAT_HASH, "Input hash mismatch!" # Build DAG: SOURCE -> EFFECT(identity) -> output dag = DAG(metadata={ "recipe": "identity-cat", "recipe_url": "https://git.rose-ash.com/art-dag/recipes", }) source_node = Node( node_type=NodeType.SOURCE, config={"path": str(cat_path)}, inputs=[], name="source_cat", ) source_id = dag.add_node(source_node) effect_node = Node( node_type="EFFECT", config={"effect": "identity"}, inputs=[source_id], name="apply_identity", ) effect_id = dag.add_node(effect_node) dag.set_output(effect_id) # Execute through artdag engine print(f"\nExecuting DAG through artdag engine...") cache_dir = Path.home() / ".artdag" / "cache" engine = Engine(cache_dir) result = engine.execute(dag) if not result.success: print(f"FAILED: {result.error}") sys.exit(1) # Copy output to final location shutil.copy2(result.output_path, output_path) output_hash = file_hash(output_path) print(f"\nOutput: {output_path}") print(f"Output hash: {output_hash}") # Verify identity property assert output_hash == input_hash, "Identity property violated!" print(f"\n✓ Identity property verified: output == input") # Record provenance provenance = { "minted_at": datetime.now(timezone.utc).isoformat(), "minted_by": "@giles@artdag.rose-ash.com", "output": { "name": "cat-copy", "content_hash": output_hash, "local_path": str(output_path), "url": None, # To be filled in after upload }, "inputs": [ {"name": "cat", "content_hash": CAT_HASH} ], "recipe": { "name": "identity-cat", "url": "https://git.rose-ash.com/art-dag/recipes" }, "effects": [ {"name": "effect:identity", "content_hash": IDENTITY_HASH} ], "infrastructure": { "software": {"name": "infra:artdag", "content_hash": ARTDAG_HASH}, "hardware": {"name": "infra:giles-hp", "content_hash": WORKSTATION_HASH} }, "execution": { "time_seconds": result.execution_time, "nodes_executed": result.nodes_executed, "nodes_cached": result.nodes_cached } } # Save provenance provenance_path = output_path.with_suffix(".provenance.json") with open(provenance_path, "w") as f: json.dump(provenance, f, indent=2) print(f"\nProvenance saved: {provenance_path}") print(f"\nUpload {output_path} to rose-ash.com, then provide URL to complete minting.") return provenance if __name__ == "__main__": main()