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