auth/migrations.ts view source
"fuz_auth" import {AUTH_MIGRATION_NAMESPACE} from '@fuzdev/fuz_app/auth/migrations.js'; Namespace identifier for fuz_app auth migrations.
Auth schema migrations.
Ordered list of {name, up} migrations for the fuz identity system tables.
Consumed by run_migrations with namespace 'fuz_auth'.
The released chain is frozen — every schema change ships as an appended
migration. Once a consumer holds a long-lived production database, an
already-bootstrapped DB has recorded the existing migrations as applied, so
editing a released migration's body in place is a silent no-op there: the
CREATE TABLE IF NOT EXISTS doesn't re-run, the runner sees nothing new,
and the new column never lands — a silent, total auth outage. (The
deleted_at / deleted_by soft-delete columns were added to v0's base DDL
this way; an older deployed DB never got them and every login broke.) So:
never edit, rename, reorder, or re-purpose an entry in auth_migrations
below. Add every additive change as a NEW appended entry using idempotent
ALTER TABLE ... ADD COLUMN IF NOT EXISTS — a fresh bootstrap and an old
deployed DB then converge on the same shape:
// v2: add display_name to account
{
name: 'account_display_name',
up: async (db) => {
await db.query('ALTER TABLE account ADD COLUMN IF NOT EXISTS display_name TEXT');
},
},The /ready schema-drift probe (db/schema_ready.ts) is the runtime net:
it fails the deploy loud when a live DB is missing a column the running code
expects, rather than letting auth break silently. Discipline prevents the
drift; the probe catches a lapse before cutover.
Migrations are forward-only (no down). Use IF NOT EXISTS / IF EXISTS
for DDL safety. The name appears in error messages on failure. Dev/test
DBs (no long-lived data) may still drop + re-bootstrap freely on a break —
the freeze is the contract for the deployed chain, not local iteration.
Every entry is twinned on the Rust spine (fuz_auth/src/migrations.rs),
and the two names must match byte for byte: the _testing_migration_tracker
parity gate compares the (namespace, name, sequence) rows both spines
record, and a divergence there breaks the swap-freely invariant (a consumer
pointing either implementation at one database). Append on both sides in the
same change, so no entry below repeats the rule.
4 declarations
auth/migrations.ts view source
"fuz_auth" import {AUTH_MIGRATION_NAMESPACE} from '@fuzdev/fuz_app/auth/migrations.js'; Namespace identifier for fuz_app auth migrations.
auth/migrations.ts view source
MigrationNamespace import {auth_migration_ns} from '@fuzdev/fuz_app/auth/migrations.js'; Pre-composed migration namespace for auth tables.
auth/migrations.ts view source
Migration[] import {auth_migrations} from '@fuzdev/fuz_app/auth/migrations.js'; Auth schema migrations in order.
role_grant_offer table for consentful grants; adds scope_id /
scope_kind / source_offer_id / revoked_reason to role_grant and
swaps the (actor_id, role) partial unique index for a scope-aware
variant using the index-side 'GLOBAL' token + all-zeros sentinel
UUID. The (scope_kind, scope_id) pair is enforced paired-null by
role_grant_scope_kind_paired / role_grant_offer_scope_kind_paired CHECK
constraints — both null for global, both non-null for scoped. The
role_grant_offer table carries a superseded_at terminal state; its
partial unique index is scoped by
(to_account, role, scope_kind, scope, from_actor) so multiple
grantors may coexist. scope_kind is informative-only in v1
(registry-membership validation against create_scope_kind_schema);
v2 may add INSERT-time (role, scope_kind) enforcement.auth/migrations.ts view source
readonly string[] import {reserved_migration_namespaces} from '@fuzdev/fuz_app/auth/migrations.js'; Migration namespaces reserved by fuz_app. Consumers passing
migration_namespaces to create_app_backend must choose a name not in
this list — the runtime check rejects matches with a thrown error. Typed
as ReadonlyArray<string> (not a literal tuple) so .includes() accepts
any consumer-supplied namespace string without a cast.