V125: Project workflow statuses (entities-backed, cement-at-start).
Adds a user-defined "workflow status" capability to phoenix_kit_projects,
orthogonal to the computed Project.derived_status/2 lifecycle and the
archived_at soft-hide. The status vocabulary is configured through the
optional phoenix_kit_entities module (the "catalog"), and snapshotted into
local projects-owned storage when a project starts (the "cement").
Two layers:
1. Catalog reference on phoenix_kit_projects
Two nullable columns let a project (or template) point at the catalog list it draws statuses from, and remember the currently-selected status:
status_entity_uuid UUID→ FKphoenix_kit_entities(uuid) ON DELETE SET NULL— which entity (status vocabulary) this project/template uses. NULL = the shared default list.ON DELETE SET NULLso deleting a catalog entity degrades the project to the shared default, never cascades.current_status_slug VARCHAR(255)— the selected status, addressed by its stable slug. The slug is the cross-boundary identity: pre-start it resolves against the live catalog rows; post-start against the cemented local rows below. Storing a slug (not a row UUID) avoids a foreign key whose target table changes at the cement boundary.
A partial index on (status_entity_uuid) WHERE status_entity_uuid IS NOT NULL
backs the "Used by N projects" reverse-reference count and grouped lookups.
2. Local cemented copy: phoenix_kit_project_statuses
When a project starts, its chosen catalog statuses are copied into this table and the running project uses its own frozen, independently-editable copy — later edits to the catalog entity do NOT retroactively rewrite live projects. Mirrors the module's existing template→instance philosophy (an Assignment copies its Task template's fields at creation, then edits independently).
project_uuid→ FKphoenix_kit_projects(uuid) ON DELETE CASCADE— the cemented statuses die with the project.label/slug/position— the snapshotted status (primary-language label + stable slug + order).data JSONB— per-status attributes (e.g.{"color": "#34d399"}). JSONB so colour and any future fields ride along without a migration.translations JSONB— secondary-language label overrides, workspace shape%{"es-ES" => %{"label" => "…"}}(mirrors Project/Task/Assignment). Empty today; ready for status-label i18n.source_entity_data_uuid UUID— provenance pointer back to the catalogphoenix_kit_entity_datarow it was copied from. Intentionally NOT a foreign key:phoenix_kit_entitiesis an optional module, the cemented row must survive the catalog row being deleted, and the value is informational.
Unique (project_uuid, slug) so a project's cemented statuses are
slug-addressable (matching current_status_slug); index on (project_uuid)
for list reads.
3. External identifier on phoenix_kit_projects
A single nullable column lets a project be tied to a record in some external system, with no UI of its own (set programmatically):
external_id VARCHAR(255)— an arbitrary external reference. Deliberately a free-form string so it can hold a numeric id, a UUID, or a slug from whatever the project is being linked to. Not unique (several projects may reference the same external thing) and not a foreign key (the target lives outside this database). A partial index on(external_id) WHERE external_id IS NOT NULLbacks lookup-by-external-id without indexing the common NULL case.
Idempotent: re-running is a no-op once the table + columns are in the post-V125 shape.