// Regression: every cloud drizzle instance must be constructed with the // merge of cloud + executor schemas. If ANY call site builds its own // `{ schema }` without spreading both, `db._.fullSchema` comes back // missing tables and the drizzle adapter throws // `unknown model "integration"` at the first request that touches scoped data. // // We hit this in prod (MCP endpoint) when `mcp-session.ts` built // `combinedSchema = { ...cloudSchema }` and forgot to spread the executor // schema — web-app requests worked because they went through // `DbService.Live`, but the MCP Durable Object constructed its own. // // This test asserts the only sanctioned constant (`combinedSchema` from // `./db`) actually contains every executor-schema export AND that drizzle // surfaces them all under `db._.fullSchema`. import { describe, expect, it } from "@effect/vitest"; import { drizzle } from "drizzle-orm/postgres-js"; import { Effect } from "effect"; import postgres from "postgres"; import { collectTables } from "@executor-js/sdk"; import * as cloudSchema from "./schema"; import * as executorSchema from "./executor-schema"; import { combinedSchema } from "./db"; import { createDrizzleFumaDb } from "./fuma"; interface GeneratedFumaTable { readonly names: { readonly drizzle: string; }; readonly columns: Record< string, { readonly ormName: string; readonly names: { readonly drizzle: string; }; } >; } const fumaDbInternals = ( value: unknown, ): { internal: { tables: Record } } => value as { internal: { tables: Record } }; const drizzleTableColumns = (value: unknown): Record => value as Record; describe("combinedSchema", () => { it("spreads every cloud + executor schema export", () => { const expected = new Set([...Object.keys(cloudSchema), ...Object.keys(executorSchema)]); for (const key of expected) { expect(combinedSchema, `combinedSchema missing "${key}"`).toHaveProperty(key); } }); // Executor-schema drives the scope-sharded tables (integration/tool/etc). If // any of these go missing the drizzle adapter's `getTable` lookup throws. it("includes scope-sharded executor tables", () => { for (const key of Object.keys(executorSchema)) { expect(combinedSchema, `combinedSchema missing "${key}"`).toHaveProperty(key); } }); // The prod bug was actually at the drizzle layer: spread + __exportAll // getters could theoretically drop tables if evaluated before their // declarations. Construct a drizzle instance and walk its fullSchema // to catch that class of bug too. it.effect("drizzle(combinedSchema) exposes every table under _.fullSchema", () => // postgres() lazily connects — safe to build with a dummy url, we // never .query() so no socket is opened. Effect.acquireRelease( Effect.sync(() => postgres("postgres://u:p@127.0.0.1:1/x", { max: 1 })), (sql) => Effect.promise(() => sql.end({ timeout: 0 })), ).pipe( Effect.flatMap((sql) => Effect.sync(() => { const db = drizzle(sql, { schema: combinedSchema }); const drizzleInternals = ( value: unknown, ): { _: { fullSchema: Record } } => value as { _: { fullSchema: Record } }; const fullSchema = drizzleInternals(db)._.fullSchema; for (const key of Object.keys(executorSchema)) { expect(fullSchema, `fullSchema missing "${key}"`).toHaveProperty(key); } }), ), ), ); it.effect("generated drizzle tables expose every executor Fuma column", () => Effect.acquireRelease( Effect.sync(() => postgres("postgres://u:p@127.0.0.1:1/x", { max: 1 })), (sql) => Effect.promise(() => sql.end({ timeout: 0 })), ).pipe( Effect.flatMap((sql) => Effect.sync(() => { const db = drizzle(sql, { schema: combinedSchema }); const fuma = createDrizzleFumaDb({ db, tables: collectTables(), namespace: "executor_cloud", provider: "postgresql", }); const schemaTables = drizzleTableColumns(combinedSchema); const missingColumns: string[] = []; for (const table of Object.values(fumaDbInternals(fuma.db).internal.tables)) { const drizzleTable = drizzleTableColumns(schemaTables[table.names.drizzle]); for (const column of Object.values(table.columns)) { if (drizzleTable[column.names.drizzle] === undefined) { missingColumns.push( `${table.names.drizzle}.${column.ormName} -> ${column.names.drizzle}`, ); } } } expect(missingColumns).toEqual([]); }), ), ), ); });