9.6 KiB
module-migrations
Purpose
Defines per-module migration infrastructure: each module owns its schema via embedded migration files, assembled into a global sequence by the db package.
Requirements
Requirement: Per-module migration directories
Each module SHALL own its database schema via a migrations/ directory under its package (e.g., internal/identity/migrations/, internal/organization/migrations/). Each module SHALL export an embed.FS containing its migration files. Module init migrations SHALL create tables within their module's schema using schema-qualified CREATE TABLE statements (e.g., CREATE TABLE identity.users (...)).
Scenario: Identity module migration directory
- WHEN the identity module is compiled
- THEN
internal/identity/migrations/SHALL contain SQL migration files for theusersandpersonstables - AND the module SHALL export an embedded filesystem containing those migrations
- AND the init migration SHALL create tables as
identity.usersandidentity.persons
Scenario: Organization module migration directory
- WHEN the organization module is compiled
- THEN
internal/organization/migrations/SHALL contain SQL migration files for theorganizations,org_members,workspaces,roles, androle_assignmentstables - AND the module SHALL export an embedded filesystem containing those migrations
- AND the init migration SHALL create tables as
organization.organizations,organization.org_members, etc.
Requirement: Dependency-ordered migration execution
The db package SHALL collect migrations from all registered modules and execute them in dependency order through a single goose instance. The dependency order SHALL be: db → identity → organization → billing → entitlements → fedwiki. Modules later in the order MAY reference tables from modules earlier in the order but SHALL NOT reference tables from modules later in the order.
Scenario: Migrations run in dependency order
- WHEN the application starts and calls the migration function
- THEN identity module migrations SHALL execute before organization module migrations
- AND all migrations SHALL be tracked in a single
goose_db_versiontable
Scenario: Migration against empty database
- WHEN migrations run against a database with no existing tables
- THEN all module migrations SHALL complete successfully in order
- AND all tables from all modules SHALL exist with correct schemas and constraints
Scenario: Idempotent migration execution
- WHEN migrations run against a database that is already up to date
- THEN no migrations SHALL be re-applied
- AND the application SHALL start normally
Requirement: Module migration registration
Each module SHALL provide a MigrationSource (or equivalent) that pairs its module name with its embedded migration filesystem. The db package SHALL accept an ordered slice of these sources to define the execution order.
Scenario: Adding a new module
- WHEN a new module (e.g., billing) needs to be added to the migration sequence
- THEN the developer SHALL create a
migrations/directory in the new module - AND the developer SHALL export an
embed.FSfrom the new module - AND the developer SHALL add the module's
MigrationSourceto the ordered slice in the correct dependency position
Requirement: Stable migration versioning via module namespaces
The migration orchestrator SHALL assemble per-module migrations into a temporary directory with stable version numbers derived from each module's position in the dependency-ordered sources slice. Each module at index i SHALL receive version numbers in the range (i+1)*1000 + intra-module sequence (e.g., module at index 0 gets versions 1001-1999, module at index 3 gets versions 4001-4999). Goose SHALL run against this assembled directory.
Adding a new migration file to any module SHALL NOT change the version numbers of any other module's migrations. Each module SHALL have capacity for up to 999 migrations within its namespace.
Scenario: Assembly produces correct ordering with stable versions
- WHEN the migration orchestrator assembles migrations from db (index 0, 2 files), identity (index 1, 1 file), and organization (index 2, 2 files)
- THEN the assembled directory SHALL contain
01001_db_init.sql,01002_db_drop_legacy_tables.sql,02001_identity_init.sql,03001_organization_init.sql,03002_organization_seed_system_roles.sql
Scenario: Adding a migration to one module does not affect others
- WHEN a new migration
00003_add_index.sqlis added to the billing module (index 3) - THEN the new file SHALL be assembled as
04003_billing_add_index.sql - AND all entitlements module migrations (index 4) SHALL retain their existing version numbers (e.g.,
05001,05002) - AND goose SHALL only run the new migration on an existing database without re-running or skipping any other migrations
Scenario: Module with multiple migrations preserves intra-module order
- WHEN a module has multiple migration files (e.g.,
00001_init.sql,00002_seed_data.sql,00003_add_column.sql) - THEN the assembler SHALL assign versions in ascending order within the module's namespace (e.g.,
04001,04002,04003for billing)
Requirement: Per-module sqlc configuration
Each module SHALL have its own sqlc.yaml configuration that generates Go code from the module's migration files. Cross-module FK references in sqlc queries SHALL use schema-qualified names.
Scenario: Identity module sqlc config
- WHEN sqlc generates code for the identity module
- THEN it SHALL read schema from
internal/identity/migrations/ - AND queries SHALL reference
identityschema tables
Scenario: Organization module sqlc config with cross-module schemas
- WHEN sqlc generates code for the organization module
- THEN it SHALL include
internal/identity/migrations/as an additional schema for FK inference - AND cross-module references SHALL use schema-qualified notation (e.g.,
identity.persons)
Scenario: FedWiki sqlc config with schema awareness
- WHEN sqlc generates code for the fedwiki module
- THEN it SHALL reference
internal/organization/migrations/for theworkspacesFK - AND table references SHALL be schema-qualified where cross-module
Requirement: Old migration coexistence
The existing internal/db/migrations/00001_init.sql SHALL remain in place for legacy table creation and cleanup. A new internal/db/migrations/00003_schema_separation.sql SHALL create the core and fedwiki schemas and move any existing domain tables from public to their correct schemas using ALTER TABLE IF EXISTS. The update_updated_at_column() function SHALL remain in the public schema as shared infrastructure.
Scenario: Old users table dropped
- WHEN the identity module's migration runs
- THEN it SHALL execute
DROP TABLE IF EXISTS users CASCADEbefore creating the newuserstable - AND the
sitesandpaymentstables SHALL retain theiruser_idcolumns but without FK constraints
Scenario: Schema separation migration on existing database
- WHEN migration
00003_schema_separation.sqlruns against a database with tables inpublic - THEN schemas
coreandfedwikiSHALL be created - AND all domain tables SHALL be moved to
core - AND the
sitestable SHALL be moved tofedwiki
Scenario: Schema separation migration on fresh database
- WHEN migration
00003_schema_separation.sqlruns against an empty database - THEN schemas
coreandfedwikiSHALL be created - AND the
ALTER TABLE IF EXISTSstatements SHALL be no-ops (no tables to move) - AND subsequent module migrations SHALL create tables in the correct schema via
search_path
Requirement: Root migration creates module schemas and roles
The root migration (internal/db/migrations/) SHALL create all module schemas and per-schema roles before any module migrations execute. This ensures module init migrations can reference their schema and grant privileges to roles.
Scenario: Root migration creates schemas on fresh install
- WHEN the root migration runs on an empty database
- THEN schemas
identity,organization,entitlements,billing,cooperative,audit, andintegrationSHALL exist - AND per-schema roles (
{schema}_owner,{schema}_writer,{schema}_reader) SHALL exist for each schema
Scenario: Root migration moves tables from core on existing install
- WHEN the root migration runs on a database with tables in the
coreschema - THEN tables SHALL be moved from
coreto their respective module schemas - AND the
coreschema SHALL be dropped - AND the
fedwikischema SHALL remain unchanged
Requirement: Cross-module FK references in migrations use schema-qualified notation
When a module migration references a table in another module, the FK constraint SHALL use schema-qualified notation (e.g., REFERENCES identity.persons(person_id)) (Decision 114). Intra-module FK references SHALL omit the schema prefix.
Scenario: Organization migration references identity schema
- WHEN the organization module's init migration creates a FK to
persons - THEN the FK SHALL use
REFERENCES identity.persons(person_id) - AND intra-module FKs like
REFERENCES organizations(org_id)SHALL omit the schema prefix
Scenario: Billing migration references organization and identity schemas
- WHEN the billing module's migration creates FKs to
organizationsandpersons - THEN the FKs SHALL use
REFERENCES organization.organizations(org_id)andREFERENCES identity.persons(person_id)