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