Ga naar inhoud

Runbook: deleting a document

Today the only retirement is superseded (one-document-per-object), which keeps the row, the text and the index entry — "a retired copy is not deleted" (docs/reference/cli.md). This is the actual delete: during development a wrong or trial ingest has no way out except a hand edit to the database, and a hand edit leaves no record.

There is no undo. Deleting is a last resort, not a correction — if the document is merely wrong for a dossier, remove the membership instead (dossiers.remove); if it is a copy of one already here, retire it (wordsworth-dedupe).

What goes, what stays

Deleting a document removes:

  • the stored object (unless another, not-yet-deleted document row still claims the same object key — a superseded copy keeps its former key)
  • the stored (de-identified) text
  • its pseudonyms, and the shared encrypted mapping underneath each one unless another document's pseudonyms still reference it (the mapping store is global — see grants)
  • its search-index entry
  • its grants scoped to this document

What stays: the documents row and every earlier audit record. The audit table's document_id is a non-null foreign key, and the trail never deletes a record — removing the row would either break the trail or require deleting audit records, the one thing this system never does (same reason a superseded copy keeps its row). The document's state becomes deleted, terminal and reachable from every state including superseded; nothing transitions out of it.

One deleted audit record is written, holding the document id, the actor and how many of each kind were removed — never a filename, a token or a value.

Who may

Only a caller with role beheerder, or one carrying a label in WORDSWORTH_GRANT_ISSUER_LABELS, may delete. Anyone else is refused before anything is read or removed, and a refusal writes no deleted record.

A caller holds beheerder by being the recipient of an active, unexpired grant that names the role — the same fact grants already uses to resolve a role-granted reveal, read here as yes/no instead of a type set. There is no separate identity-to-role table (see roles): "who someone is" comes from sign-on, "what they may do" is decided here, at the one place every delete passes through.

Delete a document

curl -XDELETE $API/documents/<document-id>

404 for an unknown id, 403 for a caller who may not. The API has no dry run — the CLI is where a human looks first.

The CLI

wordsworth-delete <document-id> [<document-id> ...] --actor <who> --dry-run
wordsworth-delete <document-id> [<document-id> ...] --actor <who>

--dry-run reports the counts it would remove, using the exact same code path as a real run, and stops before the first removal. --actor is required and checked the same way as above; there is no implicit "whoever is running this".

Each document commits on its own, like wordsworth-dedupe — a failure partway through a multi-document run leaves only the untouched ones for a rerun, and removing what a previous run already removed is a no-op (the state is already deleted, the content already gone).

A failed removal is a hard error

If any part fails — the index unreachable, a database error — the call fails, no deleted record is written, and the document stays in its previous state. A later call (the same curl, the same wordsworth-delete) finishes the work; each content kind is removed only once, so repeating is safe.

Delete a dossier

curl -XDELETE $API/dossiers/<dossier-id>
wordsworth-delete --dossier <dossier-id> --actor <who> [--dry-run]

Removes the dossier's memberships, the dossier's topics and the dossier row. A member document is deleted, as above, only if it belongs to no other dossier; one that belongs to another dossier too keeps its content and loses only its membership of this one — deleting one case must not silently empty another. The topics (onderwerpen) are removed in the same transaction, so a dossier that has topics is deleted instead of failing on topics_dossier_id_fkey.

One dossier-deleted audit record is written per document the deletion touched, whether it was deleted or only released: the document id, the dossier id, the actor and which of the two happened — never a name or a value. A document that is also deleted gets its usual deleted record too (above).

Who may: the same guard as deleting a document — only role beheerder or an issuer label, refused before anything is read or removed, no record on a refusal.

--dry-run walks the same code path and reports exactly what the real run would do: which documents would be deleted, which would only lose their membership, and how many topics would be removed (a count, not a list of names — topic names are derived from document terms and the report keeps names out). Nothing changes and no record is written.

What is not here

  • Un-deleting. There is no restore path, by design.