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:
@@ -118,12 +118,13 @@ class TestL2SharedChecker:
|
||||
mock_l2.get.return_value = Mock(status_code=404)
|
||||
assert checker.is_shared("abc123") is False
|
||||
|
||||
def test_error_returns_false(self, mock_l2):
|
||||
"""API errors return False (safe for deletion)."""
|
||||
def test_error_returns_true(self, mock_l2):
|
||||
"""API errors return True (safe - prevents accidental deletion)."""
|
||||
checker = L2SharedChecker("http://mock:8200")
|
||||
mock_l2.get.side_effect = Exception("Network error")
|
||||
|
||||
assert checker.is_shared("abc123") is False
|
||||
# On error, assume IS shared to prevent accidental deletion
|
||||
assert checker.is_shared("abc123") is True
|
||||
|
||||
|
||||
class TestL1CacheManagerStorage:
|
||||
@@ -276,18 +277,17 @@ class TestL1CacheManagerDeletionRules:
|
||||
assert can_delete is False
|
||||
assert "output" in reason.lower()
|
||||
|
||||
def test_cannot_delete_shared_item(self, manager, temp_dir, mock_l2):
|
||||
"""Published items cannot be deleted."""
|
||||
def test_cannot_delete_pinned_item(self, manager, temp_dir):
|
||||
"""Pinned items cannot be deleted."""
|
||||
test_file = create_test_file(temp_dir / "shared.txt", "shared")
|
||||
cached = manager.put(test_file, node_type="test")
|
||||
|
||||
# Mark as published
|
||||
mock_l2.get.return_value = Mock(status_code=200)
|
||||
manager.l2_checker.invalidate(cached.content_hash)
|
||||
# Mark as pinned (published)
|
||||
manager.pin(cached.content_hash, reason="published")
|
||||
|
||||
can_delete, reason = manager.can_delete(cached.content_hash)
|
||||
assert can_delete is False
|
||||
assert "L2" in reason
|
||||
assert "pinned" in reason
|
||||
|
||||
def test_delete_orphaned_item(self, manager, temp_dir):
|
||||
"""Can delete orphaned items."""
|
||||
@@ -338,8 +338,8 @@ class TestL1CacheManagerActivityDiscard:
|
||||
can_discard, reason = manager.can_discard_activity("run-001")
|
||||
assert can_discard is True
|
||||
|
||||
def test_cannot_discard_activity_with_shared_output(self, manager, temp_dir, mock_l2):
|
||||
"""Activities with shared outputs cannot be discarded."""
|
||||
def test_cannot_discard_activity_with_pinned_output(self, manager, temp_dir):
|
||||
"""Activities with pinned outputs cannot be discarded."""
|
||||
input_file = create_test_file(temp_dir / "input.txt", "input")
|
||||
output_file = create_test_file(temp_dir / "output.txt", "output")
|
||||
|
||||
@@ -352,12 +352,12 @@ class TestL1CacheManagerActivityDiscard:
|
||||
"run-001",
|
||||
)
|
||||
|
||||
# Mark output as shared
|
||||
manager.l2_checker.mark_shared(output_cached.content_hash)
|
||||
# Mark output as pinned (published)
|
||||
manager.pin(output_cached.content_hash, reason="published")
|
||||
|
||||
can_discard, reason = manager.can_discard_activity("run-001")
|
||||
assert can_discard is False
|
||||
assert "L2" in reason
|
||||
assert "pinned" in reason
|
||||
|
||||
def test_discard_activity_cleans_up(self, manager, temp_dir):
|
||||
"""Discarding activity cleans up orphaned items."""
|
||||
|
||||
Reference in New Issue
Block a user