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
- Eight columns (
item_uuid/location_uuidacrossstock,min_stock,goods_issues,goods_receipts,internal_orders,inventory_documents,supplier_orders) are declaredNOT NULLby both core's actual migration source (v140.ex/v144.ex) and the manifest's own structuredrevisions.not_nullfield, but the manifest's human-readablecreate:string for those columns omitsNOT NULL. This chain's DDL follows the source andrevisions(NOT NULLon all eight) — thecreate:string is the buggy representation. - The FK on
phoenix_kit_warehouse_inventory_documents.performed_by_uuidis namedphoenix_kit_warehouse_inventory_document_performed_by_uuid_fkey— singular "document" — unlike its five siblingperformed_by_uuidFKs, which all use the plural table name. Postgres's default FK-naming silently truncates the<table>_<column>_fkeyidentifier once it exceeds the 63-byteNAMEDATALENlimit, which is what actually happened here (v140.exnever 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:
- add the objects that version alters to core's manifest generator's
@excluded_exact(dev_docs/squash/generate_baseline.exs) and regenerateExpectedSchema; - raise this package's
:phoenix_kitfloor 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.
Summary
Functions
The version this code expects the schema to be at.
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.
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.
The version a bare, freshly-created set of tables is at (Phase 2 — a future install whose core baseline no longer creates these tables).
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.
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.
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.
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.
The table carrying the pkw_schema:<N> marker for the whole 8-table chain.
Functions
@spec current_version() :: pos_integer()
The version this code expects the schema to be at.
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.
@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.
@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).
@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.
@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.
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.
@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.
@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.