Module-owned versioned migrations for phoenix_kit_ai — the protocol core's
mix phoenix_kit.update discovers through PhoenixKitAI.migration_module/0
(current_version/0, migrated_version_runtime/1, up/1, down/1). Same
shape as PhoenixKitAgent.Migrations: two version readers, up/1
re-reading the version before it changes anything, the SQL kept as data
(up_statements/2, down_statements/2).
This chain holds only objects core does not create. The tables
phoenix_kit_ai_endpoints, phoenix_kit_ai_prompts and
phoenix_kit_ai_requests stay in core's chain and are never touched here.
Tables (V1)
phoenix_kit_ai_model_capabilities— the snapshot of what the catalogue says each model an endpoint uses can do (PhoenixKitAI.Capabilities), one row per resolved base URL and model; the flags areyes,noorunknown.
V2: the task guard's settings and events
phoenix_kit_ai_task_settings— what an admin set for an AI task (one row per task key): its limits, the warn percent, and the guard mode and classifier fields later parts of the guard read.phoenix_kit_ai_task_user_limits— a user's personal limits in a task (one row per task key and user).phoenix_kit_ai_guard_events— what the guard noticed (kind, checked against the known kinds; a new kind is a new version).dedupe_keyis unique where it is set, so a signal sent once per window is an insert that either happens or does not.- Two more flags on
phoenix_kit_ai_model_capabilities:file_inputandimage_output(yes,noorunknown, defaultunknown), for what a later version of the capability cache reads. - An index on the journal (core's
phoenix_kit_ai_requests):((metadata->>'task'), inserted_at), with no condition on the status, for the task limits and the models that worked in a task. Core's schema check reports extra columns of its tables, not extra indexes, so it is not drift there; the index is dropped with V2.
V3: blocks
phoenix_kit_ai_guard_blocks— a person blocked from a group of AI tasks (block_group; a task's own key unless it names a group) until the block is lifted. The row keeps where it came from (the task and the flag), whether its reason is private, who set it and when, and who lifted it, when and why. One active block per group and person: a unique index on (block_group,user_uuid) wherelifted_atis NULL; lifted blocks stay as the history. The guard event kinds of V2 already includeoff_purpose,off_purpose_signal,anomaly,blockedandunblocked.
V4: money in dollars
Core's V223 turned the journal's cost_cents (millionths of a dollar) into
cost, dollars. V4 does the same for what this module stores, so every
amount is dollars (PhoenixKitAI.Money). It is data, not shape, and it
converts by shape, so a re-run converts nothing twice:
- the spend caps
ai_daily_budget,ai_daily_budget_per_endpointandai_daily_budget_per_user(core'sphoenix_kit_settings, digits = millionths) are copied toai_daily_cap,ai_daily_cap_per_endpointandai_daily_cap_per_useras dollar strings ("5.00"), only where the new key does not exist yet; the old keys are left as they were; - a money cell of a task's limits (
phoenix_kit_ai_task_settings.limits,phoenix_kit_ai_task_user_limits.limits) that is a JSON number or a digits-only string becomes a dollar string; - an endpoint's manual price (
phoenix_kit_ai_endpoints.provider_settings -> 'pricing') whose halves are numbers becomes dollar strings per 1M.
It needs core's V223 first (the cost column) and stops otherwise. Down to 3
turns the task limits and prices back into integer millionths — losing what is
finer than a millionth ("0.0000004" becomes 0, and a money limit of 0
allows nothing) — and deletes the new cap keys, also ones set after V4 (the
old keys still hold the released values).
Version marker
COMMENT ON TABLE phoenix_kit_ai_model_capabilities IS 'ai_schema:<N>'.
No table, or one without an ai_schema: comment, reads as version 0.
Idempotency and rollback
Every statement guards itself (IF NOT EXISTS / IF EXISTS), so the chain
can be re-run. down/1 to version 0 drops the snapshot table; it is
rebuilt from the providers' catalogues on the next refresh.
Summary
Functions
The version this code expects the schema to be at.
Rolls back to opts[:version] (default 0). Migration context only. To 0 it
drops every table of this chain — see the moduledoc.
The SQL down/1 runs, as data: each later version undone, newest first,
then the marker re-stamped (to 0 there is no table left to stamp).
The installed version, read inside a migration through Ecto.Migration's
repo(). No rescue: a version that cannot be read aborts the migration.
The installed version, read outside a migration (what mix phoenix_kit.update
calls) through PhoenixKit's configured repo. An invalid prefix raises; a
database that cannot be reached reads as 0, which only costs a redundant
migration file because up/1 re-reads the version before it changes
anything.
Applies the chain up to opts[:version] (default current_version/0).
Migration context only. Re-reads the installed version first: a database
already at (or past) the target is left alone.
The SQL up/1 runs, as data. target is how far to go (default
current_version/0); 0 is nothing.
The table carrying the ai_schema:<N> marker.
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. To 0 it
drops every table of this chain — see the moduledoc.
@spec down_statements(String.t(), non_neg_integer()) :: [String.t()]
The SQL down/1 runs, as data: each later version undone, newest first,
then the marker re-stamped (to 0 there is no table left to stamp).
@spec migrated_version(keyword() | map()) :: non_neg_integer()
The installed version, read inside a migration through Ecto.Migration's
repo(). No rescue: a version that cannot be read aborts the migration.
@spec migrated_version_runtime(keyword() | map()) :: non_neg_integer()
The installed version, read outside a migration (what mix phoenix_kit.update
calls) through PhoenixKit's configured repo. An invalid prefix raises; a
database that cannot be reached reads as 0, which only costs a redundant
migration file because up/1 re-reads the version before it changes
anything.
Applies the chain up to opts[:version] (default current_version/0).
Migration context only. Re-reads the installed version first: a database
already at (or past) the target is left alone.
@spec up_statements(String.t(), non_neg_integer()) :: [String.t()]
The SQL up/1 runs, as data. target is how far to go (default
current_version/0); 0 is nothing.
@spec version_table() :: String.t()
The table carrying the ai_schema:<N> marker.