# `PhoenixKitWarehouse.Migrations`
[🔗](https://github.com/BeamLabEU/phoenix_kit_warehouse/blob/0.5.2/lib/phoenix_kit_warehouse/migrations.ex#L1)

Module-owned versioned migrations for `phoenix_kit_warehouse` — the
decentralized-migrations protocol that core's `mix phoenix_kit.update`
discovers via `migration_module/0`. This follows the canonical shape
documented in `phoenix_kit_hello_world`'s README ("Versioned migrations",
"Adopting a table core already creates") and its
`mix phoenix_kit_hello_world.audit_migrations` task: **two readers**
(`migrated_version/1` for migration context, `migrated_version_runtime/1`
for Mix-task context), `up/1` re-reading the version before it changes
anything, and a namespaced `COMMENT ON TABLE` marker on one anchor table
for a chain that spans eight. `phoenix_kit_billing` (`v4_statements/2`,
ten adopted tables in one version) and `phoenix_kit_dashboards` (one
adopted table) are the closest sibling examples of this exact adoption
situation.

## Ownership situation — read before touching

All 8 `phoenix_kit_warehouse_*` tables are core's baseline: `V140` created
`stock`, `inventory_documents`, `internal_orders`, `supplier_orders`,
`goods_receipts`, and `goods_issues`; `V144` added `transfers` and
`min_stock`. On every existing install all 8 already have their full
current shape before this chain ever executes — this is an ADOPTION, not
a create. Varchar widths are never restated as a second number: each of
the six document schemas' own `column_widths/0` (`GoodsReceipt`,
`GoodsIssue`, `InternalOrder`, `SupplierOrder`, `InventoryDocument`,
`Transfer` — `Stock` and `MinStock` have no varchar column) is the single
shape authority this chain's DDL interpolates.

Rather than stamp all 8 tables, the chain anchors its version marker on a
single table — `phoenix_kit_warehouse_stock`, chosen because it is the
first table core's `V140` creates and has no FK dependencies of its own —
the same way `phoenix_kit_billing`'s multi-table V4 anchors on a single
pre-existing table instead of stamping all ten it adopts.

### Two reconciled discrepancies between core's migration source and its `ExpectedSchema` manifest

1. Eight columns (`item_uuid`/`location_uuid` across `stock`, `min_stock`,
   `goods_issues`, `goods_receipts`, `internal_orders`,
   `inventory_documents`, `supplier_orders`) are declared `NOT NULL` by
   both core's actual migration source (`v140.ex`/`v144.ex`) and the
   manifest's own structured `revisions.not_null` field, but the
   manifest's human-readable `create:` string for those columns omits
   `NOT NULL`. This chain's DDL follows the source and `revisions` (`NOT
   NULL` on all eight) — the `create:` string is the buggy
   representation.
2. The FK on `phoenix_kit_warehouse_inventory_documents.performed_by_uuid`
   is named `phoenix_kit_warehouse_inventory_document_performed_by_uuid_fkey`
   — **singular** "document" — unlike its five sibling `performed_by_uuid`
   FKs, which all use the plural table name. Postgres's default FK-naming
   silently truncates the `<table>_<column>_fkey` identifier once it
   exceeds the 63-byte `NAMEDATALEN` limit, which is what actually
   happened here (`v140.ex` never names this FK explicitly). This chain
   hard-codes the singular name as a literal constraint name — it is not
   derived programmatically from the table name.

### Phase 0 — this V1 adopts, and changes NOTHING

`CREATE TABLE IF NOT EXISTS` shape-identical to core's `V140`/`V144`
baseline, under core's exact object names (every pkey, index, check
constraint, and FK), then a **namespaced** marker stamp on the anchor
table (`pkw_schema:1` — an adopted table may already carry a foreign
comment, so the reader must treat prose as version 0, never crash on it,
never assume it means V1). Because the shape is unchanged, core's
`ExpectedSchema` manifest stays accurate: **no core release is required
and there is no release-ordering hazard.** This package releases alone.

### Phase 1 — the first real shape change (V2+) is when core must move too

Before shipping a version that changes any of the 8 tables' shape:

  1. add the objects that version alters to core's manifest generator's
     `@excluded_exact` (`dev_docs/squash/generate_baseline.exs`) and
     regenerate `ExpectedSchema`;
  2. raise this package's `:phoenix_kit` floor to the release that ships
     that regenerated manifest.

Skipping step 1 means `mix phoenix_kit.repair` restores the old shape
after every run, silently undoing the new version.

### Phase 2 — creation leaves core's baseline at the next squash cycle

When core cuts its next baseline, module-owned tables are simply not
included: fresh installs from then on get all 8 `phoenix_kit_warehouse_*`
tables from THIS chain's V1 — which is why V1's `up/1` ensures the
`uuid_generate_v7()` function (and its `pgcrypto` extension) exist rather
than assuming core's chain already provided them, and why every `CREATE
TABLE` must already be the full, correct definition on its own, not
merely a shape-matching no-op for an already-existing table. Existing
installs are untouched — a baseline squash only affects fresh installs
and below-floor bridging.

## What must NEVER happen

No conditional core migration of the form "module absent → drop the
tables" — that is nondeterministic (depends on which packages are
compiled in) and destroys data on a host that merely removed the
package. Removing this module's data is a human, manual step — see
README.md "Removing this module" for the operator SQL. There is
deliberately no automated uninstall path, and `down/1` NEVER drops any of
the 8 tables for ANY target version, including `0` — it only unstamps
(or re-stamps) the marker on the anchor table. The rows are live stock
balances and document history, and on most installs every table is
core-created; rolling back this module's chain must not destroy any of
them.

The migrated version is tracked as a `pkw_schema:<N>` COMMENT on
`phoenix_kit_warehouse_stock`. A marker-less table, or one carrying a
foreign (non-`pkw_schema:`) comment, reads as version 0 — the
core-baseline shape before this chain existed.

# `current_version`

```elixir
@spec current_version() :: pos_integer()
```

The version this code expects the schema to be at.

# `down`

```elixir
@spec down(keyword() | map()) :: :ok
```

Rolls back to `opts[:version]` (default `0`). Migration-context only.
Never drops a table or a row in any of the 8, for any target — see the
moduledoc.

# `down_statements`

```elixir
@spec down_statements(String.t(), non_neg_integer()) :: [String.t()]
```

The SQL `down/1` executes, as data (marker bookkeeping only, on the
anchor table). V1 changes no shape of its own — it is pure adoption — so
there is nothing to drop beyond the marker; all 8 tables and every row in
them are left untouched, for any target including `0`.

# `initial_version`

```elixir
@spec initial_version() :: pos_integer()
```

The version a bare, freshly-created set of tables is at (Phase 2 — a
future install whose core baseline no longer creates these tables).

# `migrated_version`

```elixir
@spec migrated_version(keyword() | map()) :: non_neg_integer()
```

The version currently installed, read INSIDE a migration — through
`Ecto.Migration`'s own `repo()`. No rescue: inside a migration a version
that cannot be read must abort the transaction, never be guessed at.
`up/1` and `down/1` call this — never `migrated_version_runtime/1` —
before making any change.

# `migrated_version_runtime`

```elixir
@spec migrated_version_runtime(keyword() | map()) :: non_neg_integer()
```

Runtime-safe reader — the one `mix phoenix_kit.update` calls, from a Mix
task with no migrator running, through PhoenixKit's configured repo
instead of `Ecto.Migration`'s.

An invalid prefix is re-raised, matching core's own reader: `0` means
"not installed here", so reporting it for a bad prefix would tell the
operator something false and send the updater off to install a schema
over live data. Genuine unreachability still yields `0`, which is safe
only because `up/1` re-reads the version in migration context before
touching anything — a wrong `0` costs a redundant migration file, never
wrong DDL.

# `up`

```elixir
@spec up(keyword() | map()) :: :ok
```

Applies every chain version up to `opts[:version]` (default
`current_version/0`). Migration-context only — re-reads the installed
version via `migrated_version/1` before making any change, so a database
already at (or ahead of) the target does nothing.

# `up_statements`

```elixir
@spec up_statements(String.t(), non_neg_integer()) :: [String.t()]
```

The SQL `up/1` executes, as data — the testable single source. The
ownership test suite parses these statements to prove that the object
names are core's `V140`/`V144` names, that every `CREATE TABLE` stays
shape-identical to core's `ExpectedSchema` manifest, that every varchar
width is its owning schema's `column_widths/0`, and that nothing here can
drop a table.

`target` selects how much of the chain to emit (default
`current_version/0`): `0` applies nothing (not an operation — clearing
the marker is `down/1`'s job); `1` is the pure `V140`/`V144`-adoption step
across all 8 tables.

# `version_table`

```elixir
@spec version_table() :: String.t()
```

The table carrying the `pkw_schema:<N>` marker for the whole 8-table chain.

Not part of the protocol `mix phoenix_kit.update` calls. Exported so an
auditor (`mix phoenix_kit_hello_world.audit_migrations`) can verify the
marker is really a number without hard-coding this table's name.

---

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