diff --git a/scripts/gen-plugin-schemas.ts b/scripts/gen-plugin-schemas.ts index dc9c2eb5..39350a18 100644 --- a/scripts/gen-plugin-schemas.ts +++ b/scripts/gen-plugin-schemas.ts @@ -5,9 +5,12 @@ import { generatePluginSchemas } from '../src/servers/plugins/schema'; // // ── The bug this closes ── // -// `officer_db/src/schema.ts` ends with `export * from './plugin-schemas.gen'` — UNCONDITIONAL, because -// drizzle-kit needs one file listing every table. That generated file is gitignored (it describes this -// machine, not the project), and until 2026-08-15 its only writer was `installPlugin`. +// `officer_db/src/drizzle-schema.ts` ends with `export * from './plugin-schemas.gen'` — UNCONDITIONAL, +// because drizzle-kit needs one file listing every table. That generated file is gitignored (it describes +// this machine, not the project), and until 2026-08-15 its only writer was `installPlugin`. +// +// (It was `schema.ts` until 2026-08-16, which put the barrel in the SERVER's import graph too. A missing +// plugin directory then stopped the platform booting, so the two entry points were split.) // // So on a fresh clone the file did not exist, and `bun db:push` died with MODULE_NOT_FOUND before // creating a single table. That is exactly what `scripts/setup/officer-setup.sh` section 8 runs, so diff --git a/src/databases/officer_db/drizzle.config.ts b/src/databases/officer_db/drizzle.config.ts index 572c9286..e9c8e431 100644 --- a/src/databases/officer_db/drizzle.config.ts +++ b/src/databases/officer_db/drizzle.config.ts @@ -15,7 +15,8 @@ try { } catch {} export default defineConfig({ - schema: './src/schema.ts', + // Not `schema.ts` — that one is the runtime's and deliberately excludes the generated plugin barrel. + schema: './src/drizzle-schema.ts', out: './migrations', dialect: 'postgresql', dbCredentials: { diff --git a/src/databases/officer_db/src/drizzle-schema.ts b/src/databases/officer_db/src/drizzle-schema.ts new file mode 100644 index 00000000..ed3940fa --- /dev/null +++ b/src/databases/officer_db/src/drizzle-schema.ts @@ -0,0 +1,31 @@ +/** + * drizzle-kit's entry point. The RUNTIME must never import this file. + * + * ── What this separation is for ── + * + * `plugin-schemas.gen.ts` is generated from the plugin directories on this machine, and it is gitignored + * because it describes a machine rather than the project. It used to be re-exported from `schema.ts`, + * which `db.ts` imports to build the drizzle client — so it sat in the server's own module graph. + * + * That made a plugin's absence fatal to the platform. A `git pull` across the commit that untracked + * `plugins/` deleted those directories from the working tree; the generated file still named them; and + * the server died on `Cannot find module '../../../../plugins/music/db/schema'` before a line of its own + * code ran. Not a crash it could catch, log or survive — the import graph is resolved before `main`. + * + * A stale generated file is normal and expected. It is written by `db:push` and by installing a plugin, + * and nothing rewrites it when a directory is deleted by other means: `git pull`, `git clean -xfd`, `rm`. + * So the failure was not "someone forgot a step"; it was a file that is allowed to be wrong being loaded + * by the one process that must always start. + * + * Everywhere else the plugin system already follows the rule that a plugin which will not load must not + * stop the platform from starting — `refreshPluginMounts` isolates a bad plugin, `discoverPlugins` + * reports a broken manifest instead of throwing. This was the one path that could still take the whole + * server down, and it did. + * + * So: `schema.ts` holds the core tables and is safe for the runtime to import. This file adds the plugin + * tables on top and is read only by drizzle-kit, which regenerates the barrel before it runs + * (`bun db:push` → `scripts/gen-plugin-schemas.ts`) and can afford to fail loudly when a plugin is + * genuinely broken. `drizzle.config.ts` points here. + */ +export * from './schema'; +export * from './plugin-schemas.gen'; diff --git a/src/databases/officer_db/src/schema.ts b/src/databases/officer_db/src/schema.ts index e37d36f6..75c31563 100644 --- a/src/databases/officer_db/src/schema.ts +++ b/src/databases/officer_db/src/schema.ts @@ -53,10 +53,14 @@ export * from './service-connections/schema'; // service_connections // export * from './vault/schema'; // vault_tokens, vault_unlock_keys officer-vault // export * from './wallet/schema'; // wallet_wallets, _labels, _frozen_utxos, _chain_cache -// ── Plugin tables ──────────────────────────────────────────────────────────────────────────────── +// ── Plugin tables are NOT here ─────────────────────────────────────────────────────────────────── // -// Generated from the plugin DIRECTORIES on disk (servers/plugins/schema.ts), not from what is installed. -// push drops what it cannot see, so following the install table would delete a plugin's tables the moment -// it was uninstalled. Following the directory means uninstall keeps every row and only deleting a -// plugin's source can lose data. -export * from './plugin-schemas.gen'; +// They live in `drizzle-schema.ts`, which re-exports this file and adds the generated barrel. That file +// is drizzle-kit's entry; this one is the runtime's, and the split exists because the barrel names plugin +// directories that may not be on this machine. Importing it here put a file that is ALLOWED to be wrong +// into the module graph of the process that must always start — and on 2026-08-16 it stopped a server +// booting after a pull deleted `plugins/`. See `drizzle-schema.ts`. +// +// The barrel still follows DIRECTORIES rather than installs: push drops what it cannot see, so tracking +// installs would delete a plugin's tables on uninstall. Following the directory means uninstall keeps +// every row and only deleting a plugin's source can lose data. diff --git a/src/servers/plugins/schema.ts b/src/servers/plugins/schema.ts index facfac6d..accd6f58 100644 --- a/src/servers/plugins/schema.ts +++ b/src/servers/plugins/schema.ts @@ -38,7 +38,7 @@ const specifier = (target: string): string => { return (rel.startsWith('.') ? rel : `./${rel}`).replace(/\.ts$/, ''); }; -/** Write the barrel. Always written, even empty — `schema.ts` imports it unconditionally. */ +/** Write the barrel. Always written, even empty — `drizzle-schema.ts` imports it unconditionally. */ export function generatePluginSchemas(plugins: DiscoveredPlugin[]): void { const withSchema = plugins.filter((p) => p.schema); const lines = withSchema.map((p) => `export * from '${specifier(p.schema!)}'; // ${p.appName}`);