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:
gilesb
2026-01-06 23:19:04 +00:00
commit 10c6af3736
5 changed files with 217 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
__pycache__/
*.py[cod]
.env
.venv

30
README.md Normal file
View 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`

View 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`

View 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
View 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()