Files
cgalo5758 8d05934e93 Add domains registry with claims and placements
Domain names become an allocatable resource with one authority. A new
core module (schema `domains`, own migration stream between core and the
integrations) owns claims — a DNS node plus its whole subtree, mutually
disjoint: operator shared-domain roots, member claims carved from them,
and bring-your-own names proven by TXT verification — and placements,
which bind a name inside a claim to a provider slug and resource ref.

Verification moves to the claim and decouples from creation. A member
proves control of a domain once; afterwards every name inside it places
instantly, wildcard-CNAME friendly, with no further DNS work. The claim
workflow activates the claim and stops — it no longer creates a site —
so the sites list offers a one-click create once a domain verifies.

/domains/ask answers from placements and is registered by core rather
than the FedWiki adapter; its HTTP contract is unchanged. A configured
`domains-ask-fallback-url` forwards names the registry does not know to
a legacy answerer, the strangler seam wiki.cafe's migration needs; a
name the registry knows but has archived is refused locally.

FedWiki's create saga reserves the name before the farm call, carrying a
workflow-minted site id so retries are idempotent, and compensates on
failure. Sync places only names it owns, never stealing a member's;
lifecycle transitions and the retention purge maintain servability. An
unconditional boot pass seeds operator roots, releases orphaned
placements, and adopts pre-existing sites — grandfathering member-owned
external domains shortest-name-first, and skipping name policy, so a
live single-letter site cannot lose its certificate.

Members manage domains at /domains: claims with verification status, DNS
records including an optional wildcard row, check-now, cancel, release.
Name policy (reserved, blocked, premium, plus a single-letter guard) is
operator data; refusals collapse to a plain "unavailable" so the console
never becomes an oracle for who holds what.

BREAKING (pre-release): `fedwiki.custom_domain_verifications` and
`sites.is_custom_domain` are dropped, the flag now derived from the
placement's claim kind; resource key `fedwiki_custom_domains` migrates
to the platform-owned `external_domain_claims`; running
verify-custom-domain workflows must be terminated before deploy.
2026-07-24 21:25:40 -05:00

7.8 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: Dependency-ordered migration execution

RunMigrations SHALL apply the core stream first, then core-module streams (domains), then integration streams. Integration streams SHALL be mutually order-independent, but MAY depend on core and core-module objects (their FKs and grants reference only core and domains). Each stream SHALL be applied by its own goose run against its own ledger. Execution SHALL remain idempotent. Rollback SHALL undo one step of the most-recently-populated stream in reverse source order (integrations before core-module streams before core); status SHALL report each stream's ledger separately.

Scenario: Migrations run in dependency order

  • WHEN RunMigrations executes on an empty database
  • THEN the core stream SHALL complete before the domains stream begins
  • AND the domains stream SHALL complete before any integration stream begins

Scenario: Idempotent migration execution

  • WHEN RunMigrations executes twice against the same database
  • THEN the second run SHALL apply nothing and SHALL NOT error

Scenario: Rollback steps integrations back before core

  • WHEN RollbackMigration is invoked on a fully migrated database
  • THEN it SHALL roll back one step of the last integration stream with applied migrations, not the core or domains stream

Requirement: Per-module sqlc configuration

Each Go package with queries SHALL keep its own sqlc.yaml, with schema: paths pointing at the new baseline migration files (plus internal/db/sqlc_schemas.sql declaring exactly core, domains, stripe, fedwiki). A package whose queries join another module's tables SHALL include that module's migrations in its schema: list (the fedwiki store includes the domains migrations). Rename maps SHALL be pinned so that generated Go type names are unchanged by the schema consolidation.

Scenario: Billing package sqlc config after consolidation

  • WHEN sqlc generates for internal/billing
  • THEN queries reference core.* tables
  • AND generated type names are identical to their pre-consolidation names

Scenario: FedWiki sqlc config with schema awareness

  • WHEN sqlc generates for the fedwiki store
  • THEN provider tables resolve in the fedwiki schema, core references resolve in core, and registry joins resolve in domains

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)

Requirement: Three ordered migration streams

Migrations SHALL be organized as the core stream (internal/db/migrations/), plus core-module streams registered explicitly in internal/migrate (domains from internal/domains/migrations/), plus one stream per registered integration (internal/integrations/<slug>/migrations/). migrate.Sources() SHALL enumerate integration streams from the integration registry, after core and core-module streams. Adding a new integration SHALL add its stream via registration alone; the core stream SHALL always run first, core-module streams next, and integration streams SHALL be mutually order-independent.

Scenario: Fresh database migrates core first

  • WHEN migrations run against an empty database
  • THEN the core stream SHALL apply before the domains stream and any integration stream
  • AND integration-stream foreign keys and grants into core and domains SHALL resolve because their targets already exist

Scenario: Adding a new integration

  • WHEN a new provider integration is registered
  • THEN its migration stream SHALL be picked up from the registry without edits to internal/migrate
  • AND existing streams' ledgers and version numbers SHALL be unaffected

Requirement: Cross-schema FK direction is integration to core only

Cross-schema foreign keys SHALL follow the dependency direction only: integration-stream migrations MAY declare foreign keys referencing core tables (and MAY reference domains objects in grants or data migrations); the domains stream MAY reference core only; core-stream migrations SHALL NOT reference any other application schema.

Scenario: Stripe mapping table references core

  • WHEN a stripe-stream migration creates a mapping table
  • THEN it MAY declare REFERENCES core.products(product_id)

Scenario: Core migration attempts integration reference

  • WHEN a core-stream migration is reviewed
  • THEN any reference to stripe.*, fedwiki.*, or domains.* SHALL be rejected

Scenario: Domains migration attempts integration reference

  • WHEN a domains-stream migration is reviewed
  • THEN any reference to an integration schema SHALL be rejected

Requirement: Baseline migration creates core schema and roles

The first core-stream migration SHALL create the core schema, the core role triple, the core table grants, the member_console login role's membership in core_writer, all 38 end-state domain tables (plus the product_kinds view) in FK-dependency order, and the shared public functions. Integration schemas, role triples, grants, and member_console memberships SHALL NOT be created by the core stream — each integration's own baseline creates them. The combined baselines SHALL remain structurally complete against the superseded 10-source chain — the same object set (tables, columns, constraints, indexes, triggers, functions, net grant effects, seed rows) modulo the rename map and the documented drop list.

Scenario: Baseline on fresh install

  • WHEN all baseline migrations run on an empty database
  • THEN the normalized object set SHALL equal that of a database migrated through the superseded per-module chain (reachable via git history), after applying the five-schemas→core rename and the documented drop list

Scenario: Core baseline creates no integration objects

  • WHEN only the core stream has run
  • THEN no stripe_* or fedwiki_* role and no stripe or fedwiki schema SHALL exist

Requirement: Per-stream version ledgers

Each migration stream SHALL track its applied versions in its own goose ledger table, named goose_db_version_<stream> (_core, _domains, _fedwiki, _stripe). Streams SHALL keep their native 00001… file numbering; no global version namespace SHALL exist, and a stream's version numbers SHALL be independent of its position in the source list.

Scenario: Fresh install creates one ledger per stream

  • WHEN migrations run against an empty database
  • THEN goose_db_version_core, goose_db_version_domains, goose_db_version_fedwiki, and goose_db_version_stripe SHALL exist
  • AND no shared goose_db_version table SHALL be created

Scenario: Adding an integration renumbers nothing

  • WHEN a new integration stream is registered in migrate.Sources()
  • THEN existing streams' ledgers and version numbers SHALL be unchanged
  • AND the new stream SHALL start at its own version 1