From 5b420b4e1f26db0f6ef8de6ae69bd83861f5402e Mon Sep 17 00:00:00 2001 From: gilesb Date: Fri, 9 Jan 2026 12:14:04 +0000 Subject: [PATCH] Add identity-then-dog recipe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chains identity effect followed by dog effect on cat input. Demonstrates effect chaining: SOURCE → EFFECT → EFFECT → output Since identity(cat) = cat, the result is dog(cat) = dog video. Co-Authored-By: Claude Opus 4.5 --- mint_cat_copy.py | 140 ++++++++++++++++++++++++++ recipes/identity-then-dog/recipe.yaml | 53 ++++++++++ render_dog_cat.py | 101 +++++++++++++++++++ 3 files changed, 294 insertions(+) create mode 100644 mint_cat_copy.py create mode 100644 recipes/identity-then-dog/recipe.yaml create mode 100644 render_dog_cat.py diff --git a/mint_cat_copy.py b/mint_cat_copy.py new file mode 100644 index 0000000..f542128 --- /dev/null +++ b/mint_cat_copy.py @@ -0,0 +1,140 @@ +#!/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() diff --git a/recipes/identity-then-dog/recipe.yaml b/recipes/identity-then-dog/recipe.yaml new file mode 100644 index 0000000..630a1e5 --- /dev/null +++ b/recipes/identity-then-dog/recipe.yaml @@ -0,0 +1,53 @@ +# identity-then-dog recipe +# Chains identity effect followed by dog effect +# Demonstrates: SOURCE → EFFECT → EFFECT → output + +name: identity-then-dog +version: "1.0" +description: "Apply identity then dog effect to cat - makes a dog video" + +# Registry references (by content hash) +registry: + assets: + cat: + hash: "33268b6e167deaf018cc538de12dbe562612b33e89a749391cef855b320a269b" + url: "https://rose-ash.com/content/images/2026/01/cat.jpg" + effects: + identity: + hash: "640ea11ee881ebf4101af0a955439105ab11e763682b209e88ea08fc66e1cc03" + url: "https://github.com/gilesbradshaw/art-dag/tree/main/effects/identity" + dog: + hash: "d048fe313433eb4e38f0e24194ffae91b896ca3e6eed3e50b2cc37b7be495555" + url: "https://github.com/gilesbradshaw/art-dag/tree/main/effects/dog" + +# DAG definition +dag: + nodes: + - id: source_cat + type: SOURCE + config: + asset: cat + + - id: apply_identity + type: EFFECT + config: + effect: identity + inputs: + - source_cat + + - id: apply_dog + type: EFFECT + config: + effect: dog + inputs: + - apply_identity + + output: apply_dog + +# Verification +output: + # dog(identity(cat)) = dog(cat) = dog video + expected_hash: "772f26f9b4e80984788bc48f7c6eee0a1974966b2d4ee56a72d7c6586b3ac9d8" + +# Ownership +owner: "@giles@artdag.rose-ash.com" diff --git a/render_dog_cat.py b/render_dog_cat.py new file mode 100644 index 0000000..62119d2 --- /dev/null +++ b/render_dog_cat.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +""" +Render dog(cat) - apply dog effect to cat image. + +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 effect to path +sys.path.insert(0, str(Path(__file__).parent.parent / "artdag-effects" / "dog")) + +from effect import effect_dog, DOG_HASH + + +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 + CAT_HASH = "33268b6e167deaf018cc538de12dbe562612b33e89a749391cef855b320a269b" + DOG_EFFECT_HASH = "d048fe313433eb4e38f0e24194ffae91b896ca3e6eed3e50b2cc37b7be495555" + ARTDAG_HASH = "96a5972de216aee12ec794dcad5f9360da2e676171eabf24a46dfe1ee5fee4b0" + WORKSTATION_HASH = "964bf6e69dc4e2493f42375013caffe26404ec3cf8eb5d9bc170cd42a361523b" + + # Input (cat) + cat_path = Path.home() / "artdag-art" / "cat.jpg" + output_dir = Path.home() / "artdag-art" + output_path = output_dir / "dog-from-cat.mkv" + + print("=== Rendering dog(cat) ===\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, f"Input hash mismatch! Expected {CAT_HASH}" + + # Apply dog effect + print(f"\nApplying dog effect...") + result = effect_dog(cat_path, output_path, {}) + + # Verify output + output_hash = file_hash(result) + print(f"\nOutput: {result}") + print(f"Output hash: {output_hash}") + assert output_hash == DOG_HASH, f"Output hash mismatch! Expected {DOG_HASH}" + + print(f"\n✓ dog(cat) = dog.mkv") + print(f" Cat went in, dog came out!") + + # Record provenance + provenance = { + "minted_at": datetime.now(timezone.utc).isoformat(), + "minted_by": "@giles@artdag.rose-ash.com", + "output": { + "name": "dog-from-cat", + "content_hash": output_hash, + "local_path": str(result), + "url": None, # To be filled after push + }, + "inputs": [ + {"name": "cat", "content_hash": CAT_HASH} + ], + "recipe": { + "name": "dog-cat", + "description": "Apply dog effect to cat" + }, + "effects": [ + {"name": "effect:dog", "content_hash": DOG_EFFECT_HASH} + ], + "infrastructure": { + "software": {"name": "infra:artdag", "content_hash": ARTDAG_HASH}, + "hardware": {"name": "infra:giles-hp", "content_hash": WORKSTATION_HASH} + } + } + + # Save provenance + provenance_path = result.with_suffix(".provenance.json") + with open(provenance_path, "w") as f: + json.dump(provenance, f, indent=2) + + print(f"\nProvenance saved: {provenance_path}") + + return provenance + + +if __name__ == "__main__": + main()