- video_src_for_request now returns /raw endpoint for video source
- /raw endpoint sets proper Content-Type based on file format
- /raw endpoint sets filename with extension for downloads
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
After content negotiation fix, /cache/{hash} returns HTML for
browsers. Embedded <img> tags need /raw to get actual image data.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove output_name from publish form and endpoint
- Assets on L2 are now named by their content_hash
- All inputs, recipe, and output referenced by content_hash
- Simplified publish flow - no user input needed
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The /cache/{hash} endpoint now defaults to HTML for browsers. Raw
data was being returned for requests without explicit text/html in
Accept header (e.g., link clicks). JSON is only returned when
explicitly requested. Raw data is served only from /cache/{hash}/raw.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When cache metadata has type "recipe", return that instead of
auto-detecting (which returns "unknown" for YAML files). This
ensures L2 can properly register recipes as inputs.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Recipe detail page now shows the full YAML source
- Shows "View on L2" link if recipe is shared to L2
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The run_recipe endpoint was still using uuid.uuid4() instead of
compute_run_id(). Now it:
- Computes deterministic run_id from inputs + recipe
- Checks L1 cache before running
- Checks L2 and pulls from IPFS if needed
- Only runs Celery if output not found
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add run_cache table for fast run_id -> output lookup
- compute_run_id() computes deterministic run_id from inputs + recipe
- create_run checks L1 cache then L2 before running Celery
- If output exists on L2 but not L1, pulls from IPFS
- Saves run results to cache on completion
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove obsolete /ui, /ui/login, /ui/register, /ui/logout redirects
- Fix /ui/login links to use /login directly
- Add styled 404 page for HTML requests
- Add Web UI section to README documenting routes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Database: Use DISTINCT ON to deduplicate items by content_hash
- Database: Count unique content_hashes in count_user_items
- Server: Fix media card link from /ui/cache to /cache
- Server: Use /raw endpoint for image thumbnails
- Server: Add seen_hashes dedup in media list iteration
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Run detail page now shows "Published to L2" with link when already published
- Publish success message now includes "View on L2" link
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The cache_manager now uses Redis hashes for the content_index and
ipfs_cids mappings. This allows multiple uvicorn workers to share
state, so files added by one worker are immediately visible to all
others.
- Added redis_client parameter to L1CacheManager
- Index lookups check Redis first, then fall back to in-memory
- Index updates go to both Redis and JSON file (backup)
- Migrates existing JSON indexes to Redis on first load
- Re-enabled workers=4 in uvicorn
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
With multiple workers, each process has its own in-memory cache index.
Files added by one worker aren't visible to others, causing intermittent
404 errors.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Recipes have their own section under /recipes, so exclude them
from the media list by checking node_type == "recipe".
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When ui_publish_run publishes a run to L2, now properly records the
share in the local database so the UI can display the L2 badge and
published status.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Check Sec-Fetch-Mode: navigate header for direct browser access.
This ensures /cache/{hash} shows HTML detail page when navigating
directly, but still serves raw files for embedded <img>/<video> tags.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Run uvicorn with 4 workers to handle concurrent requests
- Add socket_timeout and socket_connect_timeout to Redis client (5s)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Make get_user_context_from_cookie and get_user_from_cookie async
since they call async get_verified_user_context. Update all callers.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add "Sync with L2" button on media page to fetch user's outbox
- Link asset names to L2 asset pages in publish status
- Add green "L2" badge to media list for published items
- Create /user/sync-l2 and /ui/sync-l2 endpoints
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add add_json() to ipfs_client for storing JSON data
- Update render_effect task to store provenance on IPFS
- Update execute_dag task to store DAG provenance on IPFS
- Add provenance_cid field to RunStatus model
- Extract provenance_cid from task results
Provenance is now immutable and content-addressed, enabling:
- Cross-L2 verification
- Bitcoin timestamping for dispute resolution
- Complete audit trail on IPFS
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Log timestamps for each operation in get_run and get_cached to
diagnose slowness:
- load_run from Redis
- Celery AsyncResult and task.ready()
- cache_file operations
- database queries for cache metadata
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Browsers get HTML detail page with video/image preview
- API clients with Accept: application/json get metadata JSON
- Other requests get raw file
- Add /cache/{content_hash}/raw for explicit file downloads
- Remove old /cache/{content_hash}/detail endpoint
- Update all /detail links to use clean /cache/{hash} URL
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use keyword arguments for Node constructor
- Pass inputs list to Node instead of calling non-existent add_edge
- Two-pass approach: create SOURCE nodes first, then resolve input
names to content-addressed IDs for dependent nodes
- Properly set output node using resolved ID
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When republishing/updating an asset on L2, now sends the IPFS CID
so L2 can update its record and pin the content.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Make cache_file() async and save ipfs_cid to cache_items table
- Update all call sites to use await
- Add create_cache_item call in upload endpoint
Fixes IPFS info not showing for uploaded files.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Escape {run_id} and {hash} as {{run_id}} and {{hash}} to
prevent NameError in f-string interpolation.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
L1 now includes IPFS CID in publish requests so L2 can
pin content on its own IPFS node for federation.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- IPFS_GATEWAY_URL env var for local gateway (e.g., https://ipfs.celery-artdag.rose-ash.com)
- Local gateway shown first with green button when configured
- Removed Pinata from public gateways list
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Cache detail page shows IPFS section with CID and links to
ipfs.io, dweb.link, Cloudflare, and Pinata gateways
- Media list cards show purple "IPFS" badge for items with CID
- JSON API response includes ipfs_cid field
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Convert static HOME_HTML to render_home_html() function that
dynamically shows user info with link to L2 profile when
authenticated, or login button when not logged in.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace all load_cache_meta/save_cache_meta calls with database functions
- Update get_user_cache_hashes to async, query both DB and legacy JSON
- Replace get_user_from_cookie with get_user_context_from_cookie throughout
- Update all endpoints to use UserContext (ctx) instead of plain username
- Update render_page calls to use ctx.actor_id
- Add high-level database helper functions:
- save_item_metadata, load_item_metadata, update_item_metadata
- save_l2_share, get_user_items, count_user_items
- Keep legacy JSON functions for backwards compatibility during migration
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- L1 never handles credentials - redirects to L2 for login/register
- L2 sets shared cookie (domain=.rose-ash.com) for cross-subdomain auth
- Display logged-in user as @user@server (ActivityPub format)
- Remove login/register form handling from L1
- Add L1_PUBLIC_URL env var for redirect callbacks
- Rename /ui/cache-list to /ui/media-list
- Update nav links to use clean URLs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change navigation tab from "Cache" to "Media"
- Change /cache list route to /media
- Add Recipes link to home page navigation
- Add Login button to home page
- Update all active_tab="cache" to active_tab="media"
- Update "Back to cache" links to "Back to media"
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Removed duplicate /recipes route that was causing infinite redirect.
Now single route handles both HTML and JSON responses directly.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add PostgreSQL database for cache metadata storage with schema for
cache_items, item_types, pin_reasons, and l2_shares tables
- Add IPFS integration as durable backing store (local cache as hot storage)
- Add postgres and ipfs services to docker-compose.yml
- Update cache_manager to upload to IPFS and track CIDs
- Rename all config references to recipe throughout server.py
- Update API endpoints: /configs/* -> /recipes/*
- Update models: ConfigStatus -> RecipeStatus, ConfigRunRequest -> RecipeRunRequest
- Update UI tabs and pages to show Recipes instead of Configs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add PyYAML dependency for parsing config files
- Add Pydantic models: VariableInput, FixedInput, ConfigStatus, ConfigRunRequest
- Add Redis storage functions for configs
- Add config YAML parsing with variable and fixed input detection
- Add config API endpoints: upload, list, get, delete, run
- Add config UI: Configs tab, list page, detail page with run form
- Add HTMX endpoints for config operations
- Add pinning on publish: configs and their fixed inputs are pinned
when runs from configs are published to L2
- Clean up debug logging in cache_manager
Config YAML format supports:
- Fixed inputs: resolve asset hashes from registry
- Variable inputs: marked with `input: true`, filled at run time
- DAG definition with nodes and edges
- Registry of assets and effects
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add is_pinned(), pin(), _load_meta(), _save_meta() to L1CacheManager
- Update can_delete() and can_discard_activity() to check local pinned status
- Update run deletion endpoints (API and UI) to check pinned metadata
- Remove L2 shared check fallback from run deletion
- Fix L2SharedChecker to return True on error (safer - prevents accidental deletion)
- Update tests for new pinned behavior
When items are published to L2, the publish flow marks them as pinned
locally. This ensures items remain non-deletable even if L2 is unreachable,
and both outputs AND inputs of published runs are protected.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add delete_html section to run detail page with delete button
- Add /ui/runs/{run_id}/discard HTMX endpoint
- Failed runs can always be deleted without restrictions
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Failed runs have no output to protect, so they can be deleted
without checking L2 shared status or activity records.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
tasks.py:
- Import artdag DAG, Node, Engine, Executor
- Register executors for effect:dog, effect:identity, SOURCE
- Add execute_dag task for running full DAG workflows
- Add build_effect_dag helper for simple effect-to-DAG conversion
server.py:
- Add use_dag and dag_json fields to RunRequest
- Update create_run to support DAG mode
- Handle both legacy render_effect and new execute_dag result formats
- Import new tasks (execute_dag, build_effect_dag)
The DAG engine executes nodes in topological order with automatic
caching. This enables multi-step pipelines like: source -> effect1 ->
effect2 -> output.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update list_all() to scan cache_dir for legacy files directly
(old files stored as CACHE_DIR/{hash}, not CACHE_DIR/legacy/)
- Update cache listing endpoints to use cache_manager.list_all()
instead of iterating CACHE_DIR.iterdir() directly
- This ensures uploaded files appear in the cache UI regardless
of whether they're in the old or new cache structure
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Run deletion: Handle legacy runs without activity records by
checking L2 shared status directly (instead of failing)
- Cache deletion: Check Redis runs in addition to activity store
to prevent deleting inputs/outputs that belong to runs
- Add find_runs_using_content() helper to check if content_hash
is used as input or output of any run
This fixes the inverted deletion logic where runs couldn't be
deleted but their cache items could.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove symlink hack from cache_file() - no longer needed
- Add get_cache_path() helper for content_hash lookups
- Update all CACHE_DIR / content_hash patterns to use cache_manager
- Fix cache_manager.get_by_content_hash() to check path.exists()
- Fix legacy path lookup (cache_dir not legacy_dir)
- Update upload endpoint to use cache_manager.put()
This ensures cache lookups work correctly for both legacy files
(stored directly in CACHE_DIR) and new files (stored in nodes/).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update get_cached endpoint to check cache_manager first, then legacy
- Update get_cached_mp4 endpoint similarly
- Make cache_file() create symlink in legacy location for backward compat
Fixes issue where newly cached outputs weren't being served because
they were stored in new location but served from legacy location.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add cache_manager.py with L1CacheManager wrapping artdag Cache
- Add L2SharedChecker for checking published status via L2 API
- Update server.py to use cache_manager for storage
- Update DELETE /cache/{content_hash} to enforce deletion rules
- Add DELETE /runs/{run_id} endpoint for discarding runs
- Record activities when runs complete for deletion tracking
- Add comprehensive tests for cache manager
Deletion rules enforced:
- Cannot delete items published to L2
- Cannot delete inputs/outputs of runs
- Can delete orphaned items
- Runs can only be discarded if no items are shared
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>