Use local pinned metadata for deletion checks instead of L2 API
- 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>
This commit is contained in:
40
server.py
40
server.py
@@ -444,6 +444,17 @@ async def discard_run(run_id: str, username: str = Depends(get_required_user)):
|
||||
|
||||
# Failed runs can always be deleted (no output to protect)
|
||||
if run.status != "failed":
|
||||
# Check if any items are pinned (published or input to published)
|
||||
items_to_check = list(run.inputs or [])
|
||||
if run.output_hash:
|
||||
items_to_check.append(run.output_hash)
|
||||
|
||||
for content_hash in items_to_check:
|
||||
meta = load_cache_meta(content_hash)
|
||||
if meta.get("pinned"):
|
||||
pin_reason = meta.get("pin_reason", "published")
|
||||
raise HTTPException(400, f"Cannot discard run: item {content_hash[:16]}... is pinned ({pin_reason})")
|
||||
|
||||
# Check if activity exists for this run
|
||||
activity = cache_manager.get_activity(run_id)
|
||||
|
||||
@@ -457,15 +468,6 @@ async def discard_run(run_id: str, username: str = Depends(get_required_user)):
|
||||
success, msg = cache_manager.discard_activity(run_id)
|
||||
if not success:
|
||||
raise HTTPException(500, f"Failed to discard: {msg}")
|
||||
else:
|
||||
# Legacy run without activity record - check L2 shared status manually
|
||||
items_to_check = list(run.inputs or [])
|
||||
if run.output_hash:
|
||||
items_to_check.append(run.output_hash)
|
||||
|
||||
for content_hash in items_to_check:
|
||||
if cache_manager.l2_checker.is_shared(content_hash):
|
||||
raise HTTPException(400, f"Cannot discard run: item {content_hash[:16]}... is published to L2")
|
||||
|
||||
# Remove from Redis
|
||||
redis_client.delete(f"{RUNS_KEY_PREFIX}{run_id}")
|
||||
@@ -491,6 +493,17 @@ async def ui_discard_run(run_id: str, request: Request):
|
||||
|
||||
# Failed runs can always be deleted
|
||||
if run.status != "failed":
|
||||
# Check if any items are pinned (published or input to published)
|
||||
items_to_check = list(run.inputs or [])
|
||||
if run.output_hash:
|
||||
items_to_check.append(run.output_hash)
|
||||
|
||||
for content_hash in items_to_check:
|
||||
meta = load_cache_meta(content_hash)
|
||||
if meta.get("pinned"):
|
||||
pin_reason = meta.get("pin_reason", "published")
|
||||
return f'<div class="bg-red-900/50 border border-red-700 text-red-300 px-4 py-3 rounded-lg mb-4">Cannot discard: item {content_hash[:16]}... is pinned ({pin_reason})</div>'
|
||||
|
||||
# Check if activity exists for this run
|
||||
activity = cache_manager.get_activity(run_id)
|
||||
|
||||
@@ -502,15 +515,6 @@ async def ui_discard_run(run_id: str, request: Request):
|
||||
success, msg = cache_manager.discard_activity(run_id)
|
||||
if not success:
|
||||
return f'<div class="bg-red-900/50 border border-red-700 text-red-300 px-4 py-3 rounded-lg mb-4">Failed to discard: {msg}</div>'
|
||||
else:
|
||||
# Legacy run - check L2 shared status
|
||||
items_to_check = list(run.inputs or [])
|
||||
if run.output_hash:
|
||||
items_to_check.append(run.output_hash)
|
||||
|
||||
for content_hash in items_to_check:
|
||||
if cache_manager.l2_checker.is_shared(content_hash):
|
||||
return f'<div class="bg-red-900/50 border border-red-700 text-red-300 px-4 py-3 rounded-lg mb-4">Cannot discard: item {content_hash[:16]}... is published to L2</div>'
|
||||
|
||||
# Remove from Redis
|
||||
redis_client.delete(f"{RUNS_KEY_PREFIX}{run_id}")
|
||||
|
||||
Reference in New Issue
Block a user