From 1a1dcf6e9245dc3349b3e232cd1d22fe94c615aa Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Wed, 6 May 2026 12:01:33 -0700 Subject: [PATCH] fix(engines): pre-add v0.27 subagent_messages.provider_id in bootstrap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #682 covered v0.20 (chunks) + v0.26.3 (mcp_request_log) but missed v0.27's subagent_messages.provider_id. The composite index `idx_subagent_messages_provider ON subagent_messages (job_id, provider_id)` in PGLITE_SCHEMA_SQL crashes on brains pinned at v0.18-v0.26 because provider_id is the SECOND column in the composite — array-extraction patterns that scan only first-column references miss it entirely. This is the wedge surfaced by issue #670 (v0.22.0 → v0.27.0 init --migrate-only crashes with "column 'provider_id' does not exist") and contributing to #661/#657. Both engines now probe for subagent_messages.provider_id and pre-add the column via ALTER TABLE ADD COLUMN IF NOT EXISTS before SCHEMA_SQL runs. Migration v36 (subagent_provider_neutral_persistence_v0_27) still runs later via runMigrations and remains idempotent. Note on the test side: REQUIRED_BOOTSTRAP_COVERAGE is hand-maintained and just gained a v0.27 entry. v0.28.5's Step 3 replaces this array with a SQL parser that auto-derives coverage from PGLITE_SCHEMA_SQL, including composite-index columns. This commit is the targeted follow-up to PR #682's cherry-pick; A2's parser closes the class permanently. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/core/pglite-engine.ts | 27 ++++++++++++++++++++++++-- src/core/postgres-engine.ts | 27 ++++++++++++++++++++++++-- test/schema-bootstrap-coverage.test.ts | 9 +++++++++ 3 files changed, 59 insertions(+), 4 deletions(-) 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.