fix(engines): pre-add v0.27 subagent_messages.provider_id in bootstrap

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) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-05-06 12:01:33 -07:00
co-authored by Claude Opus 4.7
parent 68adcb4fa6
commit 1a1dcf6e92
3 changed files with 59 additions and 4 deletions
+25 -2
View File
@@ -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<T>(fn: (conn: ReservedConnection) => Promise<T>): Promise<T> {
+25 -2
View File
@@ -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<T>(fn: (engine: BrainEngine) => Promise<T>): Promise<T> {
+9
View File
@@ -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.