Files

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 the users and persons tables
  • AND the module SHALL export an embedded filesystem containing those migrations
  • AND the init migration SHALL create tables as identity.users and identity.persons

Scenario: Organization module migration directory

  • WHEN the organization module is compiled
  • THEN internal/organization/migrations/ SHALL contain SQL migration files for the organizations, org_members, workspaces, roles, and role_assignments tables
  • 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_version table

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.FS from the new module
  • AND the developer SHALL add the module's MigrationSource to 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.sql is 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, 04003 for 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 identity schema 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 the workspaces FK
  • 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 CASCADE before creating the new users table
  • AND the sites and payments tables SHALL retain their user_id columns but without FK constraints

Scenario: Schema separation migration on existing database

  • WHEN migration 00003_schema_separation.sql runs against a database with tables in public
  • THEN schemas core and fedwiki SHALL be created
  • AND all domain tables SHALL be moved to core
  • AND the sites table SHALL be moved to fedwiki

Scenario: Schema separation migration on fresh database

  • WHEN migration 00003_schema_separation.sql runs against an empty database
  • THEN schemas core and fedwiki SHALL be created
  • AND the ALTER TABLE IF EXISTS statements 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, and integration SHALL 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 core schema
  • THEN tables SHALL be moved from core to their respective module schemas
  • AND the core schema SHALL be dropped
  • AND the fedwiki schema 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 organizations and persons
  • THEN the FKs SHALL use REFERENCES organization.organizations(org_id) and REFERENCES identity.persons(person_id)