feat(cleanup): delete-run / archive+purge-project endpoints + file cleanup - #43
Merged
Merged
Conversation
Chunk 1 of the deletion/cleanup work (docs/DELETION_AND_CLEANUP.md §3–§4), implementing the Materia delete-run brief with the five corrections from review. - storage: DataStore.delete(relpath) on the seam (Local unlinks the leaf — symlink-safe, never the target; Azure deletes the blob). - models: Project.source_managed (+ migration 0019); set True by the zip importer (we own the copied tree), left False for in-place ingest_path. - cleanup.py: delete_run() with the runner_handle ownership predicate + terminal-status guard, the corrected orphan check (dataset__project, skips embedded ligand CIFs), the shared-out_dir guard, and an audit summary. DB cascade commits first; the out_dir rm is best-effort after. Findings/Crystals are kept (durable human layer). - views: DELETE /runs/<id>?delete_outdir=false|true|force. - runservice: submit-side zombie guard — refuse a populated out_dir that no Run row owns (the desync the delete default would otherwise create). - tests: 23 cases across the seam delete, ownership, orphan/shared/force paths, endpoint modes, and the zombie guard. Full suite green (142). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Chunk 2 of the deletion/cleanup work (docs/DELETION_AND_CLEANUP.md §2). - models: Project.archived tombstone (+ migration 0020). - cleanup.py: project_loss_summary (the confirm summary — decisions/built/ refined that a purge would destroy), archive_project (reversible soft-delete), purge_project (whole-project hard delete: no per-run orphan check, no current_model guard, ownership-gated out_dir rm + source_managed source-tree rm + BUILT/REFINED byte sweep via the seam; force nukes regardless). - views: DELETE /projects/<id> archives; POST /projects/<id>/unarchive restores; POST /projects/<id>/purge?delete_outdirs=false|true|force does the irreversible work and requires the project be archived first. List hides archived (showable via ?include_archived); serializer exposes `archived`. - tests: +10 (archive/unarchive, list filtering, purge requires-archived, db-only vs owned-tree vs in-place-safety vs force vs source_managed, and the BUILT byte sweep under an unremoved source_root). Full suite green (152). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the deletion & cleanup design (docs/DELETION_AND_CLEANUP.md) and the Materia delete-endpoint brief, with the five corrections found in review. Two reviewable chunks (commits).
Why
Materia needs to drive teardown of Reinspect Projects and Runs (re-run a campaign and clear the prior run; throwaway dev projects; decommissioning). Until now the ViewSets were read-only — teardown meant hand-typed ORM in a shell.
The core idea
The DB cascades were already wired; the hard part is which bytes are ours to delete. The discriminators:
Run.runner_handle— non-empty iff we dispatched the run (so we wrote itsout_dir). The synthetic in-place-ingest Run leaves it empty and setsout_dir == source_root(the user's own tree), so we neverrmdata we don't own.Artifact.origin— BUILT/REFINED bytes are ours to sweep; IMPORTED in-place trees are the user's.Project.source_managed— gates the zip-import copy underPANDDA_DATA_ROOT/<name>.Endpoints
DELETE /runs/<id>?delete_outdir=false|true|forceDELETE /projects/<id>POST /projects/<id>/unarchive/POST /projects/<id>/purge/?delete_outdirs=false|true|force<mode>:false(DB-only, returns path),true(safe — ownership/orphan/shared guarded),force(rm regardless).Chunk 1 — run-delete core + paired zombie guard (
f5b75da)DataStore.delete(relpath)on the seam (Local unlinks the leaf, symlink-safe; Azure deletes the blob).Project.source_managed(migration 0019); set True by the zip importer, False for in-place ingest.cleanup.delete_run: ownership predicate, corrected orphan check (dataset__project, skips embedded ligand CIFs), shared-out_dirguard, audit summary. DB cascade commits first; treermbest-effort after. Findings/Crystals kept.runservice.submit_run— refuse a populatedout_dirnoRunowns (the desync thefalsedefault would otherwise create). Ships with the delete endpoint.Chunk 2 — project archive + purge (
0a74e86)Project.archivedtombstone (migration 0020); list hides archived (showable via?include_archived).cleanup.archive_project/purge_project: whole-project purge skips the per-run orphan check and current_model guard (everything's going), still ownership-gatesout_dirrm +source_managedsource-tree rm + BUILT/REFINED byte sweep;forcenukes regardless.project_loss_summary: the confirm summary (decisions / built / refined that purge would destroy).The five Materia-brief corrections (all encoded + tested)
dataset__project(dataset-scoped artifacts carryproject=NULL).contents, not files).out_dirguard on run-delete; orphan check skipped on project-purge.source_managedgate for the zip-import copy.Tests
inspect_api/tests/test_delete_cleanup.py— 33 cases (seam delete incl. symlink-safety, ownership, orphan/shared/force paths, endpoint modes, zombie guard, archive/unarchive/list-filtering, purge modes incl. in-place safety + BUILT sweep). Full suite green: 152.Heads-up for Materia
The project DELETE→archive / purge split means
delete_outdirsmoved off the projectDELETEonto/purge/— their CLI needs a separate purge command. (Noted in §4.)Not in scope (follow-ups)
out_dirteardown deferred (per-blobdelete()works; tree-rm is local-fs only).🤖 Generated with Claude Code