# Art Celery L1 rendering server for the Art DAG system. Manages distributed rendering jobs via Celery workers. ## Dependencies - **artdag** (GitHub): Core DAG execution engine - **artdag-effects** (rose-ash): Effect implementations - **Redis**: Message broker, result backend, and run persistence ## Setup ```bash # Install Redis sudo apt install redis-server # Install Python dependencies pip install -r requirements.txt # Start a worker celery -A celery_app worker --loglevel=info # Start the L1 server python server.py ``` ## Web UI The server provides a web interface at the root URL: | Path | Description | |------|-------------| | `/` | Home page with server info | | `/runs` | View and manage rendering runs | | `/run/{id}` | Run detail page | | `/recipes` | Browse and run available recipes | | `/recipe/{id}` | Recipe detail page | | `/media` | Browse cached media files | | `/auth` | Receive auth token from L2 | | `/auth/revoke` | Revoke a specific token | | `/auth/revoke-user` | Revoke all tokens for a user (called by L2 on logout) | | `/logout` | Log out | | `/download/client` | Download CLI client | ## Authentication L1 servers authenticate users via L2 (the ActivityPub registry). No shared secrets are required. ### Configuration ```bash export L1_PUBLIC_URL=https://celery-artdag.rose-ash.com ``` ### How it works 1. User clicks "Attach" on L2's Renderers page 2. L2 creates a **scoped token** bound to this specific L1 3. User is redirected to L1's `/auth?auth_token=...` 4. L1 calls L2's `/auth/verify` to validate the token 5. L2 checks: token valid, not revoked, scope matches this L1 6. L1 sets a local cookie and records the token ### Token revocation When a user logs out of L2, L2 calls `/auth/revoke-user` on all attached L1s. L1 maintains a Redis-based token tracking and revocation system: - Tokens registered per-user when authenticating (`artdag:user_tokens:{username}`) - `/auth/revoke-user` revokes all tokens for a username - Revoked token hashes stored in Redis with 30-day expiry - Every authenticated request checks the revocation list - Revoked tokens are immediately rejected ### Security - **Scoped tokens**: Tokens are bound to a specific L1. A stolen token can't be used on other L1 servers. - **L2 verification**: L1 verifies every token with L2, which checks its revocation table. - **No shared secrets**: L1 doesn't need L2's JWT secret. ## API Interactive docs: http://localhost:8100/docs ### Endpoints | Method | Path | Description | |--------|------|-------------| | GET | `/` | Server info | | POST | `/runs` | Start a rendering run | | GET | `/runs` | List all runs | | GET | `/runs/{run_id}` | Get run status | | DELETE | `/runs/{run_id}` | Delete a run | | GET | `/cache` | List cached content hashes | | GET | `/cache/{hash}` | Download cached content | | DELETE | `/cache/{hash}` | Delete cached content | | POST | `/cache/import?path=` | Import local file to cache | | POST | `/cache/upload` | Upload file to cache | | GET | `/assets` | List known assets | | POST | `/configs/upload` | Upload a config YAML | | GET | `/configs` | List configs | | GET | `/configs/{id}` | Get config details | | DELETE | `/configs/{id}` | Delete a config | | POST | `/configs/{id}/run` | Run a config | ### Configs Configs are YAML files that define reusable DAG pipelines. They can have: - **Fixed inputs**: Assets with pre-defined content hashes - **Variable inputs**: Placeholders filled at run time Example config: ```yaml name: my-effect version: "1.0" description: "Apply effect to user image" registry: effects: dog: hash: "abc123..." dag: nodes: - id: user_image type: SOURCE config: input: true # Variable input name: "input_image" - id: apply_dog type: EFFECT config: effect: dog inputs: - user_image output: apply_dog ``` ### Start a run ```bash curl -X POST http://localhost:8100/runs \ -H "Content-Type: application/json" \ -d '{"recipe": "dog", "inputs": ["33268b6e..."], "output_name": "my-output"}' ``` ### Check run status ```bash curl http://localhost:8100/runs/{run_id} ``` ### Delete a run ```bash curl -X DELETE http://localhost:8100/runs/{run_id} \ -H "Authorization: Bearer " ``` Note: Failed runs can always be deleted. Completed runs can only be deleted if their outputs haven't been published to L2. ### Delete cached content ```bash curl -X DELETE http://localhost:8100/cache/{hash} \ -H "Authorization: Bearer " ``` Note: Items that are inputs/outputs of runs, or published to L2, cannot be deleted. ## Storage - **Cache**: `~/.artdag/cache/` (content-addressed files) - **Runs**: Redis db 5, keys `artdag:run:*` (persists across restarts) ## CLI Usage ```bash # Render cat through dog effect python render.py dog cat --sync # Render cat through identity effect python render.py identity cat --sync # Submit async (don't wait) python render.py dog cat ``` ## Architecture ``` server.py (L1 Server - FastAPI) │ ├── POST /runs → Submit job │ │ │ ▼ │ celery_app.py (Celery broker) │ │ │ ▼ │ tasks.py (render_effect task) │ │ │ ├── artdag (GitHub) - DAG execution │ └── artdag-effects (rose-ash) - Effects │ │ │ ▼ │ Output + Provenance │ └── GET /cache/{hash} → Retrieve output ``` ## Provenance Every render produces a provenance record: ```json { "task_id": "celery-task-uuid", "rendered_at": "2026-01-07T...", "rendered_by": "@giles@artdag.rose-ash.com", "output": {"name": "...", "content_hash": "..."}, "inputs": [...], "effects": [...], "infrastructure": { "software": {"name": "infra:artdag", "content_hash": "..."}, "hardware": {"name": "infra:giles-hp", "content_hash": "..."} } } ```