0010. Give the schema one owner and every runtime an explicit lifecycle
Questi contenuti non sono ancora disponibili nella tua lingua.
- Status: accepted; the parts on Graphile Worker’s migrations are superseded by 0019
- Date: 2026-10-26
Context
Section titled “Context”Several packages need tables: the domain (db), Better Auth (auth), the scheduler (jobs), Graphile Worker itself. Deployments need one migration step before web and worker start, under a privileged role, with ordinary runtimes that never hold migration credentials (analysis, chapters 8 and 12). The same composition runs as a Vercel function, a Next.js build, a resident worker, a migrator and an OpenAPI exporter, each with different prerequisites (structure review, findings R04 and R06).
Decision
Section titled “Decision”Schema ownership
dbis the single physical owner of GuestCraft’s SQL: domain tables, RLS policies, the table inventory, the Better Auth table definitions and the scheduler tables, in separate subpath exports (db/auth,db/scheduler,db/migrations,db/bootstrap). There is one migration journal.- Only
authand approved migration or test tooling importdb/auth; onlyjobsand the composition importdb/scheduler. - Graphile Worker keeps its own schema and migrations; they run through its pinned migration API, invoked by the same migration command.
- Role bootstrap (creating
gc_<instance>_appandgc_<instance>_worker) is a separate step from migrations.
One migration command. server/migrate runs, under an advisory lock and with the migration role, GuestCraft’s migrations and then Graphile’s. Vercel production builds and Docker’s one-shot migrate service call the same command. Runtimes check at startup that the schema revision is the expected one and stop with a clear error otherwise: they never migrate, and never fall back to an owner connection.
Runtime lifecycle
serverhas one entry point per runtime:server/http,server/worker,server/migrateandserver/config, built from shared composition factories. There is no barrel that imports them all.- Importing a module has no side effects: no configuration parsing, connections, timers, signal handlers or migrations. Each runtime initializes explicitly; Next.js does it lazily at the first request, so that
next buildneeds no runtime secret. - Next.js-specific code (
next/server,after(),server-only) stays inapps/web; background execution is injected, so the worker never imports Next.js. - Each runtime owns its disposal: the worker finishes the current step and closes its pool on
SIGTERM.
Consequences
Section titled “Consequences”- A test imports every shared entry point with an empty environment and asserts that no connection or timer was created.
- Adding a table is always a change in
db, reviewed by the owners of migrations and RLS. - The migrator is a separate artifact with its own credentials; the web and worker artifacts cannot run migrations even by mistake.
Alternatives considered
Section titled “Alternatives considered”- Each adapter owns its tables. Several migration journals, ordering problems at deploy time, and a likely
db → auth → dbcycle. - Migrations at application startup. Rejected in the analysis: concurrent instances, owner credentials at runtime, and no single point that can stop a bad deploy.