L1 servers now verify tokens by calling L2's /auth/verify endpoint,
so L2 no longer needs to share cookies across subdomains. Each L1
sets its own first-party cookie via its /auth endpoint.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add user_renderers table to track which L1 servers users are attached to
- Add L1_SERVERS config to define available renderers
- Add /renderers page showing attachment status for each L1 server
- Add attach functionality (redirects to L1 /auth with token)
- Add detach functionality with HTMX updates
- Add Renderers link to nav bar
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Login page accepts return_to URL. After login, redirects to
return_to with auth_token in URL so target site can set its
own first-party cookie (works around iOS Safari ITP).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Change SameSite from Lax to None to allow cookie to be sent
when navigating between L1 and L2 subdomains. iOS Safari's
Intelligent Tracking Prevention may block Lax cookies.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Download links now point to /cache/{hash}/raw which returns proper
Content-Type headers and filename with extension.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When publishing to L2, if the asset already exists, return it with
existing: true flag instead of raising a 400 error. This makes
re-publishing idempotent and handles race conditions gracefully.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
After L1 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>
- RecordRunRequest.output_name now optional (deprecated)
- Input assets named by their content_hash
- Output assets named by their content_hash
- Simplified registered_inputs - just content_hash and ipfs_cid
- All assets now referenced by content_hash, not friendly names
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
All public endpoints (/assets/{name}, /activities/{id}, /objects/{hash})
now default to HTML for browsers. JSON/ActivityStreams is only returned
when explicitly requested via Accept header. Fixes browser link clicks
showing JSON instead of HTML pages.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When auto-registering input assets from a run, detect recipes
and give them appropriate names (recipe-{hash}) and tags
(auto-registered, input, recipe) instead of generic input names.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Activity IDs are now VARCHAR(64) content hashes, not UUIDs.
Removed all UUID() conversions and updated SQL casts from uuid[]
to text[].
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Recipe assets now display their YAML source code in a formatted
code block instead of just a download link.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Include artdag-client.tar.gz package
- Add /download/client route to serve the package
- Add "Download Client" link in nav bar
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- compute_run_id() for deterministic run identification
- /assets/by-run-id/{run_id} endpoint for L1 recovery
- Store run_id in provenance when recording runs
- Activities now use content hash as ID instead of UUID
- URLs: /activities/{content_hash} instead of /activities/1
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add styled 404 page for HTML requests (returns JSON for API calls)
- Add Web UI section to README documenting available routes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The README on the home page uses prose classes which require the
typography plugin to render headers and other markdown elements.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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>