# `PhoenixKit.Migrations.Postgres.V125`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.165/lib/phoenix_kit/migrations/postgres/v125.ex#L1)

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` → FK `phoenix_kit_entities(uuid) ON DELETE SET NULL`
    — which entity (status vocabulary) this project/template uses. NULL = the
    shared default list. `ON DELETE SET NULL` so 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` → FK `phoenix_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 catalog
    `phoenix_kit_entity_data` row it was copied from. Intentionally NOT a
    foreign key: `phoenix_kit_entities` is 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 NULL` backs 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.

# `down`

# `up`

---

*Consult [api-reference.md](api-reference.md) for complete listing*
