a missing plugin can no longer stop the platform booting
A server died on `Cannot find module '../../../../plugins/music/db/schema'` before a line of its own code ran. The pull that broke it was the one untracking `plugins/` — git deleted those directories from the working tree, and the gitignored `plugin-schemas.gen.ts` still named them. The barrel being wrong is NORMAL. It is written by `db:push` and by installing a plugin, and nothing rewrites it when a directory goes away by other means: a pull, `git clean -xfd`, `rm`. It describes a machine, not the project, which is why it is gitignored in the first place. The defect was not the stale file. It was that a file allowed to be wrong sat in the import graph of the process that must always start — via `db.ts` → `schema.ts` → the barrel. An import graph is resolved before `main`, so this was not catchable, loggable or survivable. Everywhere else the plugin system already holds the line: `refreshPluginMounts` isolates a bad plugin, `discoverPlugins` reports a broken manifest instead of throwing. This was the one remaining path that could take the whole server down, and it took it down. So the two consumers get their own entry points. `schema.ts` keeps the core tables and is what the runtime imports. `drizzle-schema.ts` re-exports it, adds the generated barrel, and is read only by drizzle-kit — which regenerates the barrel before every run and can afford to fail loudly on a genuinely broken plugin. `drizzle.config.ts` points at the new one. Verified by reproducing it: with the barrel naming a plugin directory that does not exist, importing the server's db module now succeeds while importing `drizzle-schema.ts` still fails with that exact error — so the runtime is immune and drizzle-kit's strictness is intact. First attempt at that test was worthless, since I "poisoned" the barrel with a path that existed here; redone with an absent one. `bun db:gen` then loaded the new entry and emitted both core and plugin tables (music_*, offscale_servers), which is what proves `db:push` still works. Pulling this is enough to boot a server in that state — the stale barrel stops being loaded rather than needing to be repaired. tsgo clean, frontend builds, 865 pass / 7 fail unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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';
|
||||
@@ -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.
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
Reference in New Issue
Block a user