From 3f4b151f45df59047984ab83aa6efa7aea4faa35 Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Fri, 19 Jun 2026 00:54:24 +0200 Subject: [PATCH] feat(drive): plan: ensure no root folder without drive --- docs/plan/drive.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/docs/plan/drive.md b/docs/plan/drive.md index 1be7a3d4..80406832 100644 --- a/docs/plan/drive.md +++ b/docs/plan/drive.md @@ -394,6 +394,47 @@ visibility, not the CTE shared snapshot). FK timing works without `DEFERRABLE`: each FK is satisfied at the moment its row is written because the referenced rows already exist. +#### DB-level invariant: no orphan root folder + +`storage.drives.root_folder_id` is NULLable at the column level +(required by the four-write transaction above — the drive INSERT +can't reference a folder that doesn't exist yet). The "every drive +has a root folder" invariant is application-enforced by the atomic +creation transaction being the only mint path. But the symmetric +invariant — "every root folder belongs to a drive" — is *not* free +either: a `storage.folders` row with `parent_id IS NULL` whose +`drive_id` doesn't point at a drive whose `root_folder_id` equals +its `id` would be an orphan that the resolver can't reach (the +chroot can't land on it) but that still occupies the +`(parent_id IS NULL, name, drive_id)` unique slot. + +D0 lands a DB-level guard for this case, as a defence-in-depth +layer behind the application invariant. Implementation: an +`AFTER INSERT OR UPDATE` constraint trigger on +`storage.folders` that fires when `NEW.parent_id IS NULL` and +refuses unless: + +```sql +EXISTS ( + SELECT 1 FROM storage.drives + WHERE id = NEW.drive_id + AND root_folder_id = NEW.id +) +``` + +Declared `DEFERRABLE INITIALLY DEFERRED` so the four-write +transaction's order (folder INSERTed at step 2, drive's +`root_folder_id` UPDATEd at step 3) doesn't trip the trigger +mid-transaction — the check fires at COMMIT, by which point both +sides of the cycle are wired. + +Test coverage: a Hurl/integration test that hand-rolls +`INSERT INTO storage.folders (… parent_id=NULL, drive_id=)` +*without* the matching drives.root_folder_id update — asserts the +trigger raises and the row is rejected. The atomic transaction +remains the only legitimate creation path; arbitrary SQL writes +that try to bypass it now hit a DB-level wall. + #### Capabilities matrix | Capability | `kind='personal'`, default (`default_for_user` set) | `kind='personal'`, secondary (`default_for_user IS NULL`) | `kind='shared'` | @@ -1242,6 +1283,13 @@ every storage query. We phase it for safety: CTE being the only creation path, not by a constraint. Verification step 6 checks the invariant on the populated dataset; ongoing enforcement is application-layer. +8. Land the **no-orphan-root-folder constraint trigger** (§3, + "DB-level invariant"): `AFTER INSERT OR UPDATE` on + `storage.folders`, fires when `NEW.parent_id IS NULL`, refuses + unless the matching drive has its `root_folder_id` pointing at + the row. `DEFERRABLE INITIALLY DEFERRED` so the atomic + four-write transaction commits cleanly. Hurl test asserts the + trigger rejects hand-rolled orphan inserts. **Keep `user_id`** on resources alongside `drive_id` for the entire Phase A release cycle. Code is updated to read `drive_id` everywhere;