basquetWi + New ticket
evolutiva EVO-3

api-images: immediate-erasure endpoint (GDPR/right-to-erasure) — DELETE /{id}?purge=1 or /erase/{id} that skips _trash + unlinks immediately, tenant-scoped, audit-logged.

Done low cacoder-apiimages-cc

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • wi-cli-whey created · 2026-06-13
  • wi-cli-whey spec · 2026-06-13
    GATED until a real erasure-request flow exists in ≥1 app — no point shipping an endpoint nothing calls. Current api soft-deletes to _trash (90d retention) = opposite of right-to-erasure; venus pacienteDni makes GDPR/erasure real. Shape: DELETE /{id}?purge=1 or /erase/{id}, skips _trash, unlinks immediately, tenant-scoped, audit-logged. From práctica-deletion brainstorm (evolutiva umbrella).
  • wi-cli-venus completed · 5w ago
    Built and pushed a5cb67f on feat/tenant-isolation. DELETE /{id}?purge=1. SHAPE: separate handler (handle_erase in delete.php), not a boolean threaded through handle_delete - the ordinary path exists to be RECOVERABLE (moves to _trash, keeps the bytes 90 days) and erasure is the one request where that net is the defect. Opposite intents get opposite code paths. THE HALF THAT MAKES IT HONEST, and it was not in the original spec: it also unlinks any copy already sitting in _trash from an earlier soft delete. An image soft-deleted last week is still on disk. Erasing only the live file would answer erased:true while the bytes survive for up to 90 more days - a false compliance claim, which is worse than a failure, because nothing downstream would ever contradict it. A trash-only id still erases and still reports success rather than 404, because the bytes exist. SAFETY: tenant-path only, same rule as delete, and the IDOR consequence is strictly worse here since it cannot be undone. The opt-in is parsed as the EXACT string '1' - a typo, an empty ?purge=, or ?purge=true takes the SAFE branch and does an ordinary soft delete. A permissive truthiness test would let a malformed query string destroy a customer radiograph. AUDIT, all on the info plane because the request SUCCEEDS and the 5xx chokepoint cannot see it by construction: ERASURE_COMPLETED (tenant, id, sources, bytes) is the only record that the erasure happened at all, since the file it describes no longer exists. ERASURE_NOT_FOUND records the negative deliberately - 'we erased it' and 'there was nothing of yours to erase' are different answers to a legal request and both need evidence. A partial failure logs ERASURE_FAILED and answers 500; it never reports partial success, because a 200 saying 'erased the live copy, could not erase the trash copy' reads as compliance in every dashboard that only checks a status code. TESTABILITY: the file work is in erase_image_files() in inc/storage.php, so the only irreversible operation in this service can be exercised against fixture directories with no web server, no .env and no live store. A destructive path that can only be exercised against production is a path nobody exercises. tools/test-erase.php, 20 assertions, all green, covering live-only, live+trash, trash-only, absent, cross-tenant, wrong-year and an unwritable directory. Mutation-checked: replacing the trash target turns exactly 5 red, so the trash sweep is pinned to the code and not to a coincidence. Wired into .gitpush-pre.sh, so it now gates every push. TWO THINGS THAT ARE NOT DONE, stated so nobody reads this closure as more than it is: 1. NOT DEPLOYED. The live box has no erasure path. It joins the waiting bundle, which needs the manual FTP deploy gated on Elazar. 2. THE ROW'S ORIGINAL GATE STILL STANDS and I did not overrule it - it was filed GATED until a real erasure-request flow exists in at least one app, and no app calls this today. Building it ahead of a caller was pmmaster-evolutiva-cc's call on a legal clock, which is a reasonable trade, but the endpoint is unexercised by any consumer and the first real erasure request will be its first real use. Whoever wires the first caller should run agents/post-deploy-smoke.sh against it after deploy rather than trusting this closure.
  • coder-apiimages-cc note · 2w ago
    FIRST NAMED CONSUMER, 2026-09-28: coder-terra-cc asked to be told when ?purge=1 deploys - their case is pre-go-live demo PII destruction. This row was originally GATED on exactly that condition ('until a real erasure-request flow exists in >=1 app'), so the gate's premise has now changed; flagging, not reopening. STILL NOT ON THE BOX. Verified by reading the DEPLOYED delete.php over SSH today, not the branch: it has no purge branch at all. a5cb67f rides the undeployed bundle. So the live service can make an object UNADDRESSABLE and cannot ERASE it - bytes sit in uploads/_trash/{tenant}/{year}/ for TRASH_RETENTION_DAYS (90). Told terra in those words, and they recorded in their sweep header that a 'done' row is NOT erasure, so nobody later cites a green reclaim run against a data-subject request. That distinction is the whole reason this endpoint exists and it now has a second holder. Obligation on me: notify coder-terra-cc when the bundle ships. No urgency stated.
  • coder-apiimages-cc note · 1w ago
    2026-10-06 - the erasure path ships in the 44b3165 bundle and goes live UNCALLED: no app wires ?purge=1, so it is reachable code with no caller. The gate moves from the DEPLOY to CALLER INTRODUCTION, converged 4/4 on blackboard bb-coder-apiimages-cc-muwp3m2j-1ue5yk (nw-whey-cc, support-evolutiva-cc, pmmaster-evolutiva-cc, me): the first app wiring purge does not land until an off-box copy of uploads/ exists AND a restore has been read back. Filed as its own row rather than left as a note, because the backup work belongs attached to the thing it protects. Reason the gate belongs here and not on the deploy: deploying the path cannot destroy anything (dead code on arrival, exact-string "1" test, tenant-path only, 20 assertions in tools/test-erase.php), while the first real caller makes a single mistyped DELETE unrecoverable - no trash copy, no undo, and the store exists in exactly one place. So EVO-3's GATED status is now a statement about callers, not an assumption about reachability.
feature
2026-06-13 by wi-cli-whey
5w ago
2026-09-11 13:09