Return existing asset instead of error when asset already exists

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>
This commit is contained in:
gilesb
2026-01-09 12:35:01 +00:00
parent b810a65f5b
commit f9b7f784e5

View File

@@ -1784,9 +1784,11 @@ async def _register_asset_impl(req: RegisterRequest, owner: str):
if not req.ipfs_cid: if not req.ipfs_cid:
raise HTTPException(400, "IPFS CID is required for registration") raise HTTPException(400, "IPFS CID is required for registration")
# Check if name exists # Check if name exists - return existing asset if so
if await db.asset_exists(req.name): existing = await db.get_asset_by_name(req.name)
raise HTTPException(400, f"Asset already exists: {req.name}") if existing:
logger.info(f"register_asset: Asset {req.name} already exists, returning existing")
return {"asset": existing, "activity": None, "existing": True}
# ===== PHASE 2: IPFS OPERATIONS (non-blocking) ===== # ===== PHASE 2: IPFS OPERATIONS (non-blocking) =====
import asyncio import asyncio
@@ -1805,7 +1807,10 @@ async def _register_asset_impl(req: RegisterRequest, owner: str):
async with db.transaction() as conn: async with db.transaction() as conn:
# Check name again inside transaction (race condition protection) # Check name again inside transaction (race condition protection)
if await db.asset_exists_by_name_tx(conn, req.name): if await db.asset_exists_by_name_tx(conn, req.name):
raise HTTPException(400, f"Asset already exists: {req.name}") # Race condition - another request created it first, return existing
existing = await db.get_asset_by_name(req.name)
logger.info(f"register_asset: Asset {req.name} created by concurrent request")
return {"asset": existing, "activity": None, "existing": True}
# Create asset # Create asset
asset = { asset = {
@@ -2084,9 +2089,11 @@ async def record_run(req: RecordRunRequest, user: User = Depends(get_required_us
} }
await db.create_asset_tx(conn, input_asset) await db.create_asset_tx(conn, input_asset)
# Check output doesn't already exist (by content_hash) # Check if output already exists (by content_hash) - return existing if so
if await db.asset_exists_by_name_tx(conn, output_hash): if await db.asset_exists_by_name_tx(conn, output_hash):
raise HTTPException(400, f"Asset already exists: {output_hash[:16]}...") existing = await db.get_asset_by_name(output_hash)
logger.info(f"record_run: Output {output_hash[:16]}... already exists")
return {"asset": existing, "activity": None, "existing": True}
# Create output asset with provenance - named by content_hash # Create output asset with provenance - named by content_hash
output_asset = { output_asset = {