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.
4.1 KiB
schema-namespacing
Purpose
Defines PostgreSQL schema separation per design Decisions 85/86/113: domain tables in per-module schemas, integration modules in schema-per-provider, shared infrastructure in public.
Requirements
Requirement: Integration modules get schema-per-provider
Each provider integration SHALL own its own PostgreSQL schema (e.g.,
fedwiki, stripe) containing only provider-specific state: projections
and mappings of core entities, and provider-mechanism tables (e.g.,
fedwiki.site_swap_policy). Integration tables MAY hold foreign keys into
core; core tables SHALL NOT hold foreign keys into integration schemas.
Scenario: FedWiki integration schema
- WHEN the FedWiki integration is installed
- THEN its tables SHALL exist in the
fedwikischema - AND its foreign keys SHALL reference
coretables (e.g.,core.workspaces)
Scenario: Integration decommissioning
- WHEN an integration is decommissioned
- THEN dropping its schema SHALL remove all provider-specific state
- AND no
coretable SHALL be invalidated by the drop
Requirement: Domain tables live in the core schema
All domain tables SHALL reside in a single core schema. The database SHALL contain
the following application schemas: core (domain), core-module schemas owned by
core modules with their own migration stream (domains — the domains registry), and
one schema per active integration (stripe, fedwiki). The public schema SHALL
hold only shared extensions and functions (e.g., update_updated_at_column()).
The provider registry tables (providers, provider_operations,
provider_states, outbox, webhook_events) are core infrastructure — the
contract integrations register against — and SHALL live in core, not in
any provider's schema.
Schema layout (as of the 2026-07-04 attribution sweep, amended by this change):
core— 38 end-state domain tables + theproduct_kindsview (formerly billing 14, entitlements 13, organization 6, identity 2, integration 5, as enumerated by the attribution sweep;product_entitlement_rulesis created and later dropped within the old chain itself, per Decision 107)domains— 3 tables (claims, placements, name_rules)stripe— 9 tables (Stripe projections/mappings + provider_configs)fedwiki— 2 tables (sites, site_swap_policy)
Scenario: Fresh install creates tables in core
- WHEN migrations run against an empty database
- THEN all domain tables SHALL exist in the
coreschema - AND no
identity,organization,entitlements,billing,integration,cooperative, orauditschema SHALL exist - AND the
publicschema SHALL contain only theupdate_updated_at_column()function and the per-streamgoose_db_version_<stream>ledgers (no sharedgoose_db_versiontable — seemodule-migrations)
Scenario: Registry tables live in the domains schema
- WHEN the domains stream has run
- THEN
domains.claims,domains.placements, anddomains.name_rulesSHALL exist in thedomainsschema - AND no registry table SHALL exist in
coreor any integration schema
Requirement: search_path resolves the core schema without qualification
The database connection SHALL set search_path to core,public. Domain
queries MAY use unqualified table names; core-module-schema and integration-schema
queries SHALL use schema-qualified names (e.g., domains.claims, fedwiki.sites),
and neither core-module schemas nor integration schemas SHALL appear in
search_path.
Scenario: Unqualified query resolves to core
- WHEN a query references
productswithout schema qualification - THEN PostgreSQL SHALL resolve it to
core.products
Scenario: Integration queries use explicit schema
- WHEN a FedWiki query references
fedwiki.sites - THEN PostgreSQL SHALL resolve it to the
fedwikischema regardless ofsearch_path
Scenario: Registry queries use explicit schema
- WHEN a registry query references
domains.placements - THEN PostgreSQL SHALL resolve it to the
domainsschema regardless ofsearch_path