Add identity-then-dog recipe
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 <noreply@anthropic.com>
This commit is contained in:
140
mint_cat_copy.py
Normal file
140
mint_cat_copy.py
Normal file
@@ -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()
|
||||||
53
recipes/identity-then-dog/recipe.yaml
Normal file
53
recipes/identity-then-dog/recipe.yaml
Normal file
@@ -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"
|
||||||
101
render_dog_cat.py
Normal file
101
render_dog_cat.py
Normal file
@@ -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()
|
||||||
Reference in New Issue
Block a user