From f5531eba0c39d42e14c1bceaf097061eaba09a3c Mon Sep 17 00:00:00 2001 From: Sawyer Date: Thu, 17 Sep 2026 19:15:57 -0700 Subject: [PATCH] fix(hub): apply the platform schema only once per database (CL-8408) --- apps/hub/src/migrate.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/hub/src/migrate.ts b/apps/hub/src/migrate.ts index e4fc63004a..197caa160a 100644 --- a/apps/hub/src/migrate.ts +++ b/apps/hub/src/migrate.ts @@ -6,6 +6,7 @@ // Interchange applies its schema — no separate migration-runner // service, no ledger table, just the hub doing the work itself. import { runMigrations } from "@intx/db"; +import { sql } from "drizzle-orm"; import { applyCronMigrations } from "@corbits/cron"; import { createMailboxDb, runMailboxMigrations } from "@corbits/mailbox"; import { runArtifactMigrations } from "@corbits/artifacts"; @@ -20,6 +21,16 @@ export interface HubMigrateConfig { schema?: string; } +async function platformSchemaPresent( + db: Parameters[0], + schema: string, +): Promise { + const rows = await db.execute<{ present: string | null }>( + sql`select to_regclass(${`"${schema}"."user"`}) as present`, + ); + return rows[0]?.present != null; +} + function databaseUrlFrom(config: HubMigrateConfig): string { return ( `postgres://${encodeURIComponent(config.user)}:${encodeURIComponent(config.password)}` + @@ -43,7 +54,11 @@ export async function migrateHub( const databaseUrl = databaseUrlFrom(config); const schema = config.schema ?? "public"; - await runMigrations(config, { schema }); + // Upstream's platform SQL is one-shot (no ledger, no IF NOT EXISTS), + // so it runs only on a database that has no platform tables yet. + if (!(await platformSchemaPresent(db, schema))) { + await runMigrations(config, { schema }); + } await applyCronMigrations(databaseUrl, { tenantSchema: schema }); const mailboxDb = createMailboxDb(databaseUrl);