Provenance records are now stored on IPFS before the DB transaction.
The provenance CID is included in both:
- The provenance stored in the asset record
- The ActivityPub activity object
This enables:
- Immutable, content-addressed provenance
- Bitcoin timestamping of provenance for cross-L2 dispute resolution
- Verifiable chain of custody: inputs → recipe → output
Provenance structure on IPFS:
{
"inputs": [...],
"output": {"content_hash": "...", "ipfs_cid": "..."},
"recipe": {...},
"recipe_cid": "Qm...",
"provenance_cid": "Qm...", // self-reference for verification
...
}
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Handle 404 explicitly with clear "not found" message
- Log actual response body when JSON parsing fails
- Include status code and body preview in error messages
This helps diagnose issues like empty responses or HTML error pages from L1.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added logging throughout record_run, publish_cache, and _register_asset_impl:
- Log start of each operation with key parameters
- Log L1 fetch operations and their results
- Log IPFS pin operations
- Log DB transaction start and completion
- Log errors with context
- Log successful completion
This makes it easier to diagnose timeout and other issues.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Wrap synchronous requests.get() and ipfs_client calls in
asyncio.to_thread() to prevent blocking the FastAPI event loop.
This fixes web UI slowness during publishing operations.
- record_run: L1 fetches and IPFS operations now async
- _register_asset_impl: IPFS pin now async
- publish_cache: IPFS pin now async
- get_anchor_tree: IPFS get_bytes now async
- verify_activity_anchor: IPFS get_bytes now async
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements merkle tree anchoring for ActivityPub activities with
provable Bitcoin timestamps. Activities are batched into merkle trees,
submitted to OpenTimestamps (free), and proofs stored on IPFS.
Key features:
- Merkle tree construction with membership proofs
- OpenTimestamps integration for Bitcoin anchoring
- JSONL backup file on persistent volume (survives DB wipes)
- API endpoints for creating/verifying anchors
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The db now returns datetime objects instead of strings in some cases.
Added format_date() helper function that handles both datetime and string
values, and replaced all [:10] date slicing with calls to this helper.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
All publishing operations now use three-phase atomic approach:
1. Phase 1: Preparation - validate inputs, gather IPFS CIDs
2. Phase 2: IPFS operations - pin all content before any DB changes
3. Phase 3: DB transaction - all-or-nothing database commits
Changes:
ipfs_client.py:
- Add IPFSError exception class
- Add add_bytes() to store content on IPFS
- Add add_json() to store JSON documents on IPFS
- Add pin_or_raise() for synchronous pinning with error handling
db.py:
- Add transaction() context manager for atomic DB operations
- Add create_asset_tx() for transactional asset creation
- Add create_activity_tx() for transactional activity creation
- Add get_asset_by_hash_tx() for lookup within transactions
- Add asset_exists_by_name_tx() for existence check within transactions
server.py:
- Rewrite record_run:
- Check L2 first for inputs, fall back to L1
- Store recipe JSON on IPFS with CID in provenance
- Auto-register input assets if not already on L2
- All operations atomic
- Rewrite publish_cache:
- IPFS CID now required
- Synchronous pinning before DB commit
- Transaction for asset + activity
- Rewrite _register_asset_impl:
- IPFS CID now required
- Synchronous pinning before DB commit
- Transaction for asset + activity
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
IPFS pinning can take a long time if content needs to be fetched
from the network. Changed all pin operations to run in background
threads so they don't block the HTTP response.
This fixes the 30s timeout issue when publishing assets.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fetch media_type and ipfs_cid from L1's cache endpoint instead of
hardcoding asset_type to "video". This fixes images being incorrectly
displayed as videos.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The ipfs_cid column was being stored but not retrieved in queries,
causing the UI to show "Not on IPFS" even when the data exists.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use 'or {}' pattern to handle both missing keys and explicit None
values for origin, provenance, and metadata fields.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace ipfshttpclient library with direct HTTP requests to IPFS API.
This fixes compatibility with newer Kubo versions (0.39.0+) which are
not supported by the outdated ipfshttpclient library.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add IPFS container to docker-compose
- Add ipfshttpclient dependency
- Add ipfs_client.py module for IPFS operations
- Add ipfs_cid field to Asset model and database schema
- Pin content on L2 IPFS when assets are published/registered
L2 now stores content on its own IPFS node, enabling
federation - content remains available even if L1 goes down.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add COOKIE_DOMAIN env var (e.g., ".rose-ash.com")
- Auto-derive from ARTDAG_DOMAIN if not set (strips first subdomain)
- Set domain on auth cookies for sharing across L1/L2 subdomains
- Add secure=True for cross-subdomain cookies
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
L1 needs to know which L2 server issued the token so it can verify
the token with the correct server. Now tokens include:
- l2_server: The L2 server URL (e.g., https://artdag.rose-ash.com)
- username: Also include username for compatibility (in addition to sub)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Parse JSONB fields (provenance, origin, tags, metadata, object_data, signature)
if they come back as strings instead of dicts.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
asyncpg requires datetime objects, not ISO strings. Added _parse_timestamp
helper to convert string timestamps to datetime objects.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix depends_on syntax for Docker Swarm (simple list, not extended)
- Add /registry/record-run as legacy alias for /assets/record-run
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add PostgreSQL with asyncpg for persistent storage
- Create db.py module with async database operations
- Create migrate.py script to migrate JSON data to PostgreSQL
- Update docker-compose.yml with PostgreSQL service
- Home page now shows README with styled headings
- Remove /ui prefix routes, use content negotiation on main routes
- Add /activities/{idx} as canonical route (with /activity redirect)
- Update /assets/{name} to support HTML and JSON responses
- Convert auth.py to use async database operations
- RSA keys still stored as files in $ARTDAG_DATA/keys/
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Home page shows asset/activity/user counts with links (not redirect)
- Rename /registry to /assets everywhere
- Clean auth routes: /login, /logout, /register
- Update all navigation links to clean URLs
- Remove /ui prefix from main links
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- /registry, /activities, /users → HTML (browsers) or JSON (APIs)
- /asset/{name}, /activity/{index}, /users/{username} → same
- Infinite scroll on all list pages via HTMX
- API pagination: ?page=1&limit=20 with has_more
- All internal links updated to use clean URLs
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- /users/{username}: Redirects to /ui/user/{username} for browsers (Accept: text/html)
- /objects/{hash}: Redirects to /ui/asset/{name} for browsers
- APIs still get JSON (application/activity+json) as before
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Include provenance in activity object_data when creating activities
- Add fallback: look up asset from registry if activity lacks provenance
- Existing activities now show inputs by looking up the asset
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Display actual images/videos for inputs (not just hash links)
- Video auto-plays if detected, falls back to image
- Consistent display on both activity and asset detail pages
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- record-run now stores effect_url, effects_commit, infrastructure in provenance
- Asset detail uses stored effect_url (with fallback for older records)
- Shows effects commit hash under effect button
- Shows infrastructure info (software/hardware) if available
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add L1_PUBLIC_URL and EFFECTS_REPO_URL config
- Display images/videos directly from L1 cache
- Show provenance section for rendered outputs:
- Effect name with link to source code
- Input content hashes (linked)
- L1 run ID (linked)
- Render timestamp
- Download button for content
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- /ui/asset/{name}: Shows full asset info (owner, hash, origin, tags, description, ActivityPub URL)
- /ui/user/{username}: Shows user profile with their published assets and activity stats
- Updated users list to link to user detail pages
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace custom CSS with Tailwind CSS via CDN
- Dark theme matching L1 server styling
- Responsive layouts for all pages
- Updated: home, login/register, registry, activities, users pages
- Modern form styling and table layouts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Each registered user now has their own ActivityPub actor:
- Generate RSA keys per user on registration
- Webfinger resolves any registered user (@user@domain)
- Actor endpoints work for any registered user
- Each user has their own outbox (filtered activities)
- Activities signed with the publishing user's keys
- Objects attributed to the asset owner
Removed:
- ARTDAG_USER config (no longer single-actor)
- L1_SERVER config (comes with each request)
Added:
- /ui/users page listing all registered users
- user_exists() helper function
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add l1_server field to RecordRunRequest
- L2 fetches run data from the specified L1 URL instead of hardcoded config
- Store l1_server in provenance and metadata
- Remove ARTDAG_L1 config requirement from L2
- Update docker-stack.yml comments
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Move ARTDAG_DOMAIN, ARTDAG_USER, ARTDAG_L1, JWT_SECRET to .env
- Update docker-stack.yml L1 config to use env_file
- Add .env.example with all required variables
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add POST /registry/publish-cache for publishing cache items with metadata
- Requires origin (self or external URL) for publishing
- Add PATCH /registry/{name} for updating existing assets
- Update activities now created when assets are modified
- Ownership check ensures only asset owner can update
- Origin info included in ActivityPub objects (generator/source)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add authentication to /registry endpoint
- Add authentication to /registry/record-run endpoint
- Extract register logic to _register_asset_impl helper
- Store owner username in registered assets
- Use authenticated user for ActivityPub actor ID in activities
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Home page showing README and stats
- Login/register forms with HTMX
- Registry and activities pages
- Cookie-based auth for web UI
- JWT secret from Docker secrets (/run/secrets/jwt_secret)
- Updated README with secret generation instructions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- POST /auth/register - create account
- POST /auth/login - get JWT token
- GET /auth/me - get current user
- POST /auth/verify - verify token (for L1)
- Password hashing with bcrypt
- 30-day JWT tokens
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>