V205: storage profiles and variant sets, the schema half (phase 4 of
dev_docs/plans/2026-09-22-storage-libraries.md).
A library says where its bytes live by pointing at a storage profile, and which derived files it gets by pointing at a variant set. Both default to NULL, which means the Default one, so nothing changes for an existing install:
phoenix_kit_storage_profiles—copies_originals,copies_variants,min_copies_on_writeand arevisionbumped on every change. One row is seeded: Default, under the fixed uuid00000000-0000-7000-8000-000000000002, with both copy counts taken fromstorage_redundancy_copies.phoenix_kit_storage_profile_buckets— the profile's buckets, each with arole(primary|replica|backup), what itstores(all|originals|derived), a fixedwrite_priority(NULL = the shuffled pool), aserve_orderand astatus(active|read_only|draining).storage_classandencryptionare reserved. Every bucket joins Default:write_priorityis itspriority(0 becomes the pool) andserve_orderis today's read order, local buckets first and then by priority. A bucket a profile uses cannot be deleted.phoenix_kit_variant_sets—generate_variantsandgenerate_tiles(seeded fromstorage_auto_generate_variantsandstorage_tile_generation_enabled),selectable(user libraries may pick it) and arevision. One row is seeded: Default, under00000000-0000-7000-8000-000000000003.variant_set_uuidonphoenix_kit_storage_dimensions: NOT NULL,ON DELETE CASCADE, with the Default set's uuid as its column DEFAULT, so every existing size is in Default and a writer that names no set keeps working. Size names are unique per set instead of site-wide (phoenix_kit_storage_dimensions_name_indexgains a leadingvariant_set_uuid).storage_profile_uuid/variant_set_uuidon libraries (NULL = the Default,ON DELETE RESTRICT).- What a file was placed by:
placed_profile_uuid/placed_revisionandplaced_variant_set_uuid/placed_variant_revisionon files. NULL means the Default at revision 1, which is what every existing file is, so no file row is rewritten. A file is stale, and the reconciler moves or regenerates it, when what it was placed by differs from its library's.reconcile_attempted_atis when the reconciler last tried a file and could not finish it: it waits a while before the next try, so a file that keeps failing is not retried on every pass. The files that are under-replicated today are marked stale here (placed_revision = 0): the ones whose completed instances do not each have as many active locations on enabled buckets as the Default wants (or as there are enabled buckets, if fewer). spec_hashon file instances: which spec of a size produced the instance. Stamped here on every instance whose name is a size of the Default set (or one of its alternative formats), with that size's current spec (PhoenixKit.Modules.Storage.VariantSets.spec_hash/2computes the same value). Instances not made from a size (the original, an annotated thumbnail, a burned render) have none.
Nothing is copied, moved or regenerated here.
Locks
New tables and nullable columns without defaults are metadata-only. The
dimensions column has a constant default (metadata-only) and becomes NOT
NULL through a validated check (the V164 pattern). The spec_hash stamp is
one UPDATE over the instances named like a size. The name index is a
plain rebuild on a table of a few rows.
Re-runnable.
Summary
Functions
Rolls V205 back. Sizes of any set other than Default are deleted (size names become unique site-wide again), then the tables and columns go.