diff --git a/src/core/pglite-engine.ts b/src/core/pglite-engine.ts index faa090d09..79133967c 100644 --- a/src/core/pglite-engine.ts +++ b/src/core/pglite-engine.ts @@ -223,6 +223,8 @@ export class PGLiteEngine implements BrainEngine { * - `pages.deleted_at` column (indexed by `pages_deleted_at_purge_idx`) — v0.26.5 * - `mcp_request_log.agent_name` + `params` + `error_message` columns * (indexed by `idx_mcp_log_agent_time`) — v0.26.3 + * - `subagent_messages.provider_id` column (indexed by + * `idx_subagent_messages_provider`) — v0.27 * * **Maintenance contract:** when a future migration adds a column-with-index * or new-table-with-FK referenced by PGLITE_SCHEMA_SQL, extend this method @@ -256,7 +258,11 @@ export class PGLiteEngine implements BrainEngine { EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema='public' AND table_name='mcp_request_log') AS mcp_log_exists, EXISTS (SELECT 1 FROM information_schema.columns - WHERE table_schema='public' AND table_name='mcp_request_log' AND column_name='agent_name') AS agent_name_exists + WHERE table_schema='public' AND table_name='mcp_request_log' AND column_name='agent_name') AS agent_name_exists, + EXISTS (SELECT 1 FROM information_schema.tables + WHERE table_schema='public' AND table_name='subagent_messages') AS subagent_messages_exists, + EXISTS (SELECT 1 FROM information_schema.columns + WHERE table_schema='public' AND table_name='subagent_messages' AND column_name='provider_id') AS subagent_provider_id_exists `); const probe = rows[0] as { pages_exists: boolean; @@ -271,6 +277,8 @@ export class PGLiteEngine implements BrainEngine { search_vector_exists: boolean; mcp_log_exists: boolean; agent_name_exists: boolean; + subagent_messages_exists: boolean; + subagent_provider_id_exists: boolean; }; const needsPagesBootstrap = probe.pages_exists && !probe.source_id_exists; @@ -281,9 +289,12 @@ export class PGLiteEngine implements BrainEngine { const needsPagesDeletedAt = probe.pages_exists && !probe.deleted_at_exists; // v0.26.3 (v33): idx_mcp_log_agent_time in PGLITE_SCHEMA_SQL needs agent_name col. const needsMcpLogBootstrap = probe.mcp_log_exists && !probe.agent_name_exists; + // v0.27 (v36): idx_subagent_messages_provider in PGLITE_SCHEMA_SQL needs + // provider_id (the SECOND column in the composite index `(job_id, provider_id)`). + const needsSubagentProviderId = probe.subagent_messages_exists && !probe.subagent_provider_id_exists; // Fresh installs (no tables yet) and modern brains both no-op. - if (!needsPagesBootstrap && !needsLinksBootstrap && !needsChunksBootstrap && !needsPagesDeletedAt && !needsMcpLogBootstrap) return; + if (!needsPagesBootstrap && !needsLinksBootstrap && !needsChunksBootstrap && !needsPagesDeletedAt && !needsMcpLogBootstrap && !needsSubagentProviderId) return; console.log(' Pre-v0.21 brain detected, applying forward-reference bootstrap'); @@ -359,6 +370,18 @@ export class PGLiteEngine implements BrainEngine { ALTER TABLE mcp_request_log ADD COLUMN IF NOT EXISTS error_message TEXT; `); } + + if (needsSubagentProviderId) { + // v36 (subagent_provider_neutral_persistence_v0_27) adds provider_id + + // schema_version on subagent_messages and subagent_tool_executions. + // PGLITE_SCHEMA_SQL's `CREATE INDEX idx_subagent_messages_provider ON + // subagent_messages (job_id, provider_id)` crashes without provider_id + // (composite-index second column). v36 runs later via runMigrations and + // is idempotent. + await this.db.exec(` + ALTER TABLE subagent_messages ADD COLUMN IF NOT EXISTS provider_id TEXT; + `); + } } async withReservedConnection(fn: (conn: ReservedConnection) => Promise): Promise { diff --git a/src/core/postgres-engine.ts b/src/core/postgres-engine.ts index 58e25805a..4a25a3f84 100644 --- a/src/core/postgres-engine.ts +++ b/src/core/postgres-engine.ts @@ -167,6 +167,8 @@ export class PostgresEngine implements BrainEngine { * - `pages.deleted_at` column (indexed by `pages_deleted_at_purge_idx`) — v0.26.5 * - `mcp_request_log.agent_name` + `params` + `error_message` columns * (indexed by `idx_mcp_log_agent_time`) — v0.26.3 + * - `subagent_messages.provider_id` column (indexed by + * `idx_subagent_messages_provider`) — v0.27 * * Keep this in sync with the PGLite version; covered by * `test/schema-bootstrap-coverage.test.ts` (PGLite side) and @@ -191,6 +193,8 @@ export class PostgresEngine implements BrainEngine { search_vector_exists: boolean; mcp_log_exists: boolean; agent_name_exists: boolean; + subagent_messages_exists: boolean; + subagent_provider_id_exists: boolean; }[]>` SELECT EXISTS (SELECT 1 FROM information_schema.tables @@ -216,7 +220,11 @@ export class PostgresEngine implements BrainEngine { EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = 'mcp_request_log') AS mcp_log_exists, EXISTS (SELECT 1 FROM information_schema.columns - WHERE table_schema = current_schema() AND table_name = 'mcp_request_log' AND column_name = 'agent_name') AS agent_name_exists + WHERE table_schema = current_schema() AND table_name = 'mcp_request_log' AND column_name = 'agent_name') AS agent_name_exists, + EXISTS (SELECT 1 FROM information_schema.tables + WHERE table_schema = current_schema() AND table_name = 'subagent_messages') AS subagent_messages_exists, + EXISTS (SELECT 1 FROM information_schema.columns + WHERE table_schema = current_schema() AND table_name = 'subagent_messages' AND column_name = 'provider_id') AS subagent_provider_id_exists `; const probe = probeRows[0]!; @@ -230,8 +238,11 @@ export class PostgresEngine implements BrainEngine { const needsPagesDeletedAt = probe.pages_exists && !probe.deleted_at_exists; // v0.26.3 (v33): idx_mcp_log_agent_time in SCHEMA_SQL needs agent_name col. const needsMcpLogBootstrap = probe.mcp_log_exists && !probe.agent_name_exists; + // v0.27 (v36): idx_subagent_messages_provider in SCHEMA_SQL needs provider_id + // (the SECOND column in the composite index `(job_id, provider_id)`). + const needsSubagentProviderId = probe.subagent_messages_exists && !probe.subagent_provider_id_exists; - if (!needsPagesBootstrap && !needsLinksBootstrap && !needsChunksBootstrap && !needsPagesDeletedAt && !needsMcpLogBootstrap) return; + if (!needsPagesBootstrap && !needsLinksBootstrap && !needsChunksBootstrap && !needsPagesDeletedAt && !needsMcpLogBootstrap && !needsSubagentProviderId) return; console.log(' Pre-v0.21 brain detected, applying forward-reference bootstrap'); @@ -307,6 +318,18 @@ export class PostgresEngine implements BrainEngine { ALTER TABLE mcp_request_log ADD COLUMN IF NOT EXISTS error_message TEXT; `); } + + if (needsSubagentProviderId) { + // v36 (subagent_provider_neutral_persistence_v0_27) adds provider_id + + // schema_version on subagent_messages and subagent_tool_executions. + // SCHEMA_SQL's `CREATE INDEX idx_subagent_messages_provider ON + // subagent_messages (job_id, provider_id)` crashes without provider_id + // (composite-index second column). v36 runs later via runMigrations and + // is idempotent. + await conn.unsafe(` + ALTER TABLE subagent_messages ADD COLUMN IF NOT EXISTS provider_id TEXT; + `); + } } async transaction(fn: (engine: BrainEngine) => Promise): Promise { diff --git a/test/schema-bootstrap-coverage.test.ts b/test/schema-bootstrap-coverage.test.ts index 99dd51251..a31250295 100644 --- a/test/schema-bootstrap-coverage.test.ts +++ b/test/schema-bootstrap-coverage.test.ts @@ -76,6 +76,12 @@ const REQUIRED_BOOTSTRAP_COVERAGE: ForwardReference[] = [ // v0.26.3 (v33) — forward-referenced by `CREATE INDEX idx_mcp_log_agent_time // ON mcp_request_log(agent_name, created_at DESC)`. { kind: 'column', table: 'mcp_request_log', column: 'agent_name' }, + // v0.27 (v36) — forward-referenced by `CREATE INDEX + // idx_subagent_messages_provider ON subagent_messages (job_id, provider_id)`. + // Composite-index second column; the array-based test pattern misses these + // by default, which is why this fix wave's Step 3 replaces this with a + // SQL parser that extracts every column referenced by any DDL. + { kind: 'column', table: 'subagent_messages', column: 'provider_id' }, ]; test('applyForwardReferenceBootstrap covers every forward reference declared in REQUIRED_BOOTSTRAP_COVERAGE', async () => { @@ -122,6 +128,9 @@ test('applyForwardReferenceBootstrap covers every forward reference declared in ALTER TABLE mcp_request_log DROP COLUMN IF EXISTS agent_name; ALTER TABLE mcp_request_log DROP COLUMN IF EXISTS params; ALTER TABLE mcp_request_log DROP COLUMN IF EXISTS error_message; + + DROP INDEX IF EXISTS idx_subagent_messages_provider; + ALTER TABLE subagent_messages DROP COLUMN IF EXISTS provider_id; `); // Run bootstrap in isolation (NOT initSchema). This is what we're testing.