feat: initial recipes repo with identity-cat
- Recipe schema using artdag primitives (SOURCE, EFFECT) - identity-cat: applies identity effect to cat image - Test verifying identity property: output_hash == input_hash - Demonstrates content-addressed DAG execution Owner: @giles@artdag.rose-ash.com 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
30
README.md
Normal file
30
README.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Art DAG Recipes
|
||||||
|
|
||||||
|
Recipes that transform assets using effects from [art-dag](https://github.com/gilesbradshaw/art-dag).
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
recipes/
|
||||||
|
└── identity-cat/
|
||||||
|
├── recipe.yaml # DAG definition
|
||||||
|
└── README.md # Description
|
||||||
|
```
|
||||||
|
|
||||||
|
## Registry References
|
||||||
|
|
||||||
|
Recipes reference assets and effects by content hash from:
|
||||||
|
- **Assets**: https://github.com/gilesbradshaw/art-dag/blob/main/registry/registry.json
|
||||||
|
- **Effects**: https://github.com/gilesbradshaw/art-dag/tree/main/effects
|
||||||
|
|
||||||
|
## Recipe Schema
|
||||||
|
|
||||||
|
A recipe is a DAG with:
|
||||||
|
- `name`: Unique recipe identifier
|
||||||
|
- `inputs`: Assets referenced by content_hash
|
||||||
|
- `nodes`: DAG nodes using primitives (SOURCE, TRANSFORM, etc.) and effects
|
||||||
|
- `output`: Expected output hash (for verification)
|
||||||
|
|
||||||
|
## Owner
|
||||||
|
|
||||||
|
Recipes owned by `@giles@artdag.rose-ash.com`
|
||||||
29
recipes/identity-cat/README.md
Normal file
29
recipes/identity-cat/README.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# identity-cat
|
||||||
|
|
||||||
|
The simplest possible recipe: apply the identity effect to the foundational cat image.
|
||||||
|
|
||||||
|
## DAG
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┐ ┌────────────────┐ ┌────────────┐
|
||||||
|
│ SOURCE(cat) │ ──▶ │ EFFECT(identity)│ ──▶ │ output │
|
||||||
|
└─────────────┘ └────────────────┘ └────────────┘
|
||||||
|
33268b... passthrough 33268b...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
Since `identity(x) = x`:
|
||||||
|
- Input hash: `33268b6e167deaf018cc538de12dbe562612b33e89a749391cef855b320a269b`
|
||||||
|
- Output hash: `33268b6e167deaf018cc538de12dbe562612b33e89a749391cef855b320a269b`
|
||||||
|
|
||||||
|
The hashes are identical, proving the identity property.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- **Asset**: [cat](https://rose-ash.com/content/images/2026/01/cat.jpg)
|
||||||
|
- **Effect**: [identity](https://github.com/gilesbradshaw/art-dag/tree/main/effects/identity)
|
||||||
|
|
||||||
|
## Owner
|
||||||
|
|
||||||
|
`@giles@artdag.rose-ash.com`
|
||||||
43
recipes/identity-cat/recipe.yaml
Normal file
43
recipes/identity-cat/recipe.yaml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# identity-cat recipe
|
||||||
|
# Applies the identity effect to the foundational cat image
|
||||||
|
# Demonstrates: SOURCE → EFFECT → output
|
||||||
|
|
||||||
|
name: identity-cat
|
||||||
|
version: "1.0"
|
||||||
|
description: "Apply identity effect to cat - output equals input"
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
# DAG definition
|
||||||
|
dag:
|
||||||
|
nodes:
|
||||||
|
- id: source_cat
|
||||||
|
type: SOURCE
|
||||||
|
config:
|
||||||
|
asset: cat
|
||||||
|
|
||||||
|
- id: apply_identity
|
||||||
|
type: EFFECT
|
||||||
|
config:
|
||||||
|
effect: identity
|
||||||
|
inputs:
|
||||||
|
- source_cat
|
||||||
|
|
||||||
|
output: apply_identity
|
||||||
|
|
||||||
|
# Verification
|
||||||
|
output:
|
||||||
|
# Identity property: output hash equals input hash
|
||||||
|
expected_hash: "33268b6e167deaf018cc538de12dbe562612b33e89a749391cef855b320a269b"
|
||||||
|
|
||||||
|
# Ownership
|
||||||
|
owner: "@giles@artdag.rose-ash.com"
|
||||||
111
test_identity_cat.py
Normal file
111
test_identity_cat.py
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test the identity-cat recipe.
|
||||||
|
|
||||||
|
Verifies that identity(cat) produces the same content hash as cat.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
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()
|
||||||
|
# Resolve symlinks to get actual content
|
||||||
|
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():
|
||||||
|
# Cat image location (from registry)
|
||||||
|
cat_url = "https://rose-ash.com/content/images/2026/01/cat.jpg"
|
||||||
|
cat_hash = "33268b6e167deaf018cc538de12dbe562612b33e89a749391cef855b320a269b"
|
||||||
|
|
||||||
|
# Try to find cat locally
|
||||||
|
cat_paths = [
|
||||||
|
Path("/home/giles/Pictures/cat.jpg"),
|
||||||
|
Path.home() / "Pictures" / "cat.jpg",
|
||||||
|
]
|
||||||
|
|
||||||
|
cat_path = None
|
||||||
|
for p in cat_paths:
|
||||||
|
if p.exists():
|
||||||
|
cat_path = p
|
||||||
|
break
|
||||||
|
|
||||||
|
if not cat_path:
|
||||||
|
print(f"Cat image not found locally. Download from: {cat_url}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Verify input hash
|
||||||
|
input_hash = file_hash(cat_path)
|
||||||
|
print(f"Input: {cat_path}")
|
||||||
|
print(f"Input hash: {input_hash}")
|
||||||
|
|
||||||
|
if input_hash != cat_hash:
|
||||||
|
print(f"WARNING: Input hash doesn't match registry!")
|
||||||
|
print(f"Expected: {cat_hash}")
|
||||||
|
|
||||||
|
# Build the DAG: SOURCE -> EFFECT(identity) -> output
|
||||||
|
dag = DAG()
|
||||||
|
|
||||||
|
# Node 1: SOURCE(cat)
|
||||||
|
source_node = Node(
|
||||||
|
node_type=NodeType.SOURCE,
|
||||||
|
config={"path": str(cat_path)},
|
||||||
|
inputs=[],
|
||||||
|
name="source_cat",
|
||||||
|
)
|
||||||
|
source_id = dag.add_node(source_node)
|
||||||
|
|
||||||
|
# Node 2: EFFECT(identity)
|
||||||
|
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
|
||||||
|
with tempfile.TemporaryDirectory() as cache_dir:
|
||||||
|
engine = Engine(cache_dir)
|
||||||
|
result = engine.execute(dag)
|
||||||
|
|
||||||
|
if not result.success:
|
||||||
|
print(f"FAILED: {result.error}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Verify output hash
|
||||||
|
output_hash = file_hash(result.output_path)
|
||||||
|
print(f"\nOutput: {result.output_path}")
|
||||||
|
print(f"Output hash: {output_hash}")
|
||||||
|
|
||||||
|
# The identity property: output hash == input hash
|
||||||
|
if output_hash == input_hash:
|
||||||
|
print("\n✓ PASS: identity(cat) == cat")
|
||||||
|
print(" Output hash equals input hash - identity property verified!")
|
||||||
|
else:
|
||||||
|
print("\n✗ FAIL: identity(cat) != cat")
|
||||||
|
print(f" Expected: {input_hash}")
|
||||||
|
print(f" Got: {output_hash}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user