Repository navigation
Slice 2 (Module 02): Delete document with atomic cascade #10
Copy link
Copy link
Closed
Labels
afkEligible for the main agent loopEligible for the main agent looppriority:2Priority 2Priority 2qa-readyPR opened, awaiting human QAPR opened, awaiting human QAsliceVertical tracer-bullet sliceVertical tracer-bullet slice
Description
What to build
Add the ability for a patient to permanently delete one of their documents. The deletion must be atomic: document_shares rows are removed and the documents row is deleted within a single transaction, then the file is removed from disk. If the disk delete fails after the transaction commits, return a 500 with a descriptive body (EH-002 partial-failure path) — the committed DB state is the source of truth. Add a delete button to the document list on /patient/documents.
Acceptance criteria
-
DELETE /documents/:idby the owning patient returns 204; thedocumentsrow, all associateddocument_sharesrows, and the UUID file on disk are all gone. - The DB removal (documents row + shares rows) occurs inside a single transaction — no observable intermediate state where one is absent and the other is not.
- Deleting a document that is currently shared with one or more doctors removes the sharing records atomically along with the document row.
- If the file cannot be deleted from disk after the transaction commits, the endpoint returns 500 with
{ error: "DELETE_FAILED", message: "Document metadata removed but file could not be deleted from storage" }. -
DELETE /documents/:idfor a document owned by a different patient returns 403. -
DELETE /documents/:idfor a non-existent id returns 404{ error: "DOCUMENT_NOT_FOUND", ... }. - The delete button on the
/patient/documentsUI removes the entry from the list on success. -
pnpm verifyexits 0.
User stories covered
- US Slice 3: Role-based access control + cross-role redirects #3: As a patient, I want to delete a document so that it is permanently removed from storage.
- US Slice 3 (Module 02): Per-doctor sharing management (grant / revoke / state) #11: As a patient, I want deleting a shared document to atomically remove all sharing records.
- US Slice 3 (Module 03): Slot deletion with booked guard #22: As an operator, I want a partial-delete failure to return a 500 with a descriptive body.
Blocked by
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
afkEligible for the main agent loopEligible for the main agent looppriority:2Priority 2Priority 2qa-readyPR opened, awaiting human QAPR opened, awaiting human QAsliceVertical tracer-bullet sliceVertical tracer-bullet slice
Activity
mGasiorek998 commentedon May 7, 2026
Plan: Add
deleteDocumentuse case inapps/api/src/use-cases/documents/that runs a single Drizzle transaction deleting fromdocument_sharesthendocuments(cascade ensures atomicity at the SQL level too — but explicit txn pattern documents intent). After commit, attemptunlinkvia the storage service; on failure return 500DELETE_FAILEDwith body matching AC. WireDELETE /documents/:idroute behindrequireAuth()+requireRole(['patient']), with 404 for unknown id, 403 when caller doesn't own the document. Fillpackages/contracts/src/documents.tswith the new contract endpoint. On the web side: adduseDeleteDocumentmutation hook inapps/web/src/features/documents/and wire a delete button on the patient documents list. Tests planned: HTTP integration tests (204 happy path + cascade verification, 403 ownership, 404 unknown, 500 disk-failure path), use-case integration test against real Postgres for transaction atomicity, web component test for delete button. Rough order: contract → DB transaction use case → HTTP route → UI button. Seven red→green cycles.mGasiorek998 commentedon May 7, 2026
Implementation PR opened: #16. One red commit (7 failing integration tests covering all backend ACs), one green commit (repo + use case + route + contract + UI). All 75 tests pass;
pnpm verifyclean. Note: stacked on #15 — needs to merge after Slice 1 lands.