V206: user-owned storage, the schema half (dev_docs/plans/2026-09-22-storage-libraries.md,
"Next: V206").
A user may keep a library's bytes in their own S3-compatible bucket. Two nullable columns say whose:
phoenix_kit_buckets.owner_uuidandphoenix_kit_storage_profiles.owner_uuid— NULL is the site's (every bucket and profile that exists today). A non-NULL one is a user's, andStorage.list_buckets/0,Storage.list_enabled_buckets/0andStorage.Profiles.list_profiles/0leave it out, so nothing that enumerates the site's storage ever sees it.
owner_uuid has no foreign key, on purpose. ON DELETE SET NULL would
turn a deleted user's bucket, credentials and all, into a site bucket in the
shared pool; RESTRICT would block deleting a user. A dangling owner is
inert: the owner's libraries are purged and their buckets and profiles
removed with them.
One check keeps an owned bucket safe whatever the application does:
phoenix_kit_buckets_owned_check—owner_uuid IS NULL OR (provider <> 'local' AND integration_uuid IS NOT NULL AND access_type <> 'public'). A user bucket is never a filesystem path (that would be arbitrary write access on the server), never carries keys of its own (they live in the user's personal Integrations connection), and never hands out a plain object URL (a private library's files are served through short-lived links only).
Nothing is copied, moved or rewritten, and every existing row has a NULL owner, so the check holds for all of them.
Locks
Two nullable columns without defaults are metadata-only; the partial indexes
cover no existing row; the check is added NOT VALID and validated over a
handful of bucket rows. Re-runnable.
Summary
Functions
Rolls V206 back. A user-owned bucket or profile cannot exist without the column, so they are removed first, after the library rows that point at the profiles: those libraries go back to the Default profile.