ADR-0067 — FileManager never deletes files it doesn't own (purge tier guard)¶
- Status: Accepted
- Date: 2026-06-18
- Builds on: ADR-0062 (custodian + catalog), ADR-0064 (indexer), ADR-0065 (Photos), ADR-0066 (RAG catalog)
Context¶
Granting IRIS Full Disk Access (needed to index iCloud Drive / the Apple Photos library) widens what FileManager can reach. That raised a safety question: can cataloguing ever delete a user's real files?
Audit of every delete path in src/iris_harness/filemanager + src/iris_harness/rag:
- Indexer (FMX2), Photos (FMX3), RAG ingest (FMX4) — strictly read-only:
open(..., "rb"),stat, and a read-only (mode=ro&immutable=1) SQLite open. Nounlink/rmtreeanywhere. - Governed delete (
FileWriteGovernor) — soft: snapshots to trash, R3 + human-approved, agent can't self-approve, reversible via undo. Its onlyunlinkreverts an undo of a create (a file IRIS itself just made). FileCustodian.enforce_retention()— filterslocation_tier="managed"and observer entries carryretention_days=0, so the daily sweep can never touch in-place files.FileCustodian.purge()— the gap. It unlinkedentry.storage_pathfor any tier. For an indexer/Photos/RAG entry that path is the user's real original, soiris files catalog purge <id>would have deleted it.
Decision¶
purge() may only ever delete bytes FileManager owns — the managed tier
(files the custodian itself wrote under data/filemanager/store/). For any
in-place tier (os_local, icloud, photos) it refuses and never calls
unlink. To remove a stale catalog row for an in-place file, a new forget()
drops the row without touching the file. Deleting a real file remains
possible only through the governed path (soft, undoable, human-approved).
The CLI mirrors this: iris files catalog purge refuses non-managed entries
with guidance; iris files catalog forget drops a row, file untouched.
Consequence¶
The guarantee is now structural, not incidental:
FileManager never deletes a file it did not itself create. Indexing, Photos cataloguing and RAG registration are read-only; retention only reaps the custodian's own managed bytes;
purgeis tier-guarded; and the only way to delete a real user file is an explicit, human-approved, reversible (trash + undo) governed operation.
Full Disk Access therefore broadens read/catalogue reach only — it grants no new ability to destroy data.
Verification¶
tests/unit/test_filemanager/test_custodian.py:test_purge_refuses_in_place_files_and_never_unlinks(real file survives, not marked deleted),test_forget_drops_row_without_deleting_file,test_retention_never_touches_in_place_files(long window set, tier filter still excludes it). 128 filemanager tests pass; ruff/black/mypy clean.