diff --git a/packages/core/src/schema.sql b/packages/core/src/schema.sql index 10d177e..1d5ef87 100644 --- a/packages/core/src/schema.sql +++ b/packages/core/src/schema.sql @@ -74,6 +74,8 @@ CREATE INDEX IF NOT EXISTS idx_tc_message ON tool_calls(message_uuid); CREATE INDEX IF NOT EXISTS idx_tc_file ON tool_calls(file_path); CREATE INDEX IF NOT EXISTS idx_tr_session ON tool_results(session_id); CREATE INDEX IF NOT EXISTS idx_tr_message ON tool_results(message_uuid); +CREATE INDEX IF NOT EXISTS idx_tr_failure_session ON tool_results(session_id) + WHERE is_error = 1 OR content LIKE 'Exit code %'; CREATE INDEX IF NOT EXISTS idx_sa_session ON subagents(session_id); CREATE INDEX IF NOT EXISTS idx_wf_session ON workflows(session_id); CREATE INDEX IF NOT EXISTS idx_wa_run ON workflow_agents(run_id); diff --git a/tests/db-schema.test.mjs b/tests/db-schema.test.mjs index 3f98cd8..9e25748 100644 --- a/tests/db-schema.test.mjs +++ b/tests/db-schema.test.mjs @@ -127,6 +127,35 @@ test('tool results schema indexes live session patch lookups', async () => { } }); +test('tool results schema limits failure scans to failure rows', async () => { + const db = new DatabaseSync(':memory:'); + try { + db.exec(await readExecutableSchema()); + const plan = db.prepare(` + EXPLAIN QUERY PLAN + SELECT tr.* + FROM tool_results tr + LEFT JOIN messages rm ON rm.uuid=tr.message_uuid + LEFT JOIN tool_calls tc ON tc.id=tr.tool_use_id + LEFT JOIN messages cm ON cm.uuid=tc.message_uuid + LEFT JOIN sessions s ON s.id=tr.session_id + WHERE (tr.is_error = 1 OR tr.content LIKE 'Exit code %') + AND COALESCE(rm.visibility,'visible')='visible' + AND COALESCE(cm.visibility,'visible')='visible' + AND COALESCE(s.source, 'claude') = ? + ORDER BY rm.timestamp DESC + LIMIT ? + `).all('codex', 50); + + assert.ok( + plan.some(row => /USING INDEX idx_tr_failure_session/.test(String(row.detail))), + `expected partial failure index, got: ${plan.map(row => row.detail).join('; ')}`, + ); + } finally { + db.close(); + } +}); + test('session detail queries use the visible main timeline index', async () => { const db = new DatabaseSync(':memory:'); try { diff --git a/tests/provider-schema-stability.test.mjs b/tests/provider-schema-stability.test.mjs index 82514dd..5af87cd 100644 --- a/tests/provider-schema-stability.test.mjs +++ b/tests/provider-schema-stability.test.mjs @@ -12,8 +12,8 @@ test('canonical transcript persistence schema changes only by explicit decision' const schema = readFileSync(new URL('../packages/core/src/schema.sql', import.meta.url)); assert.equal( createHash('sha256').update(schema).digest('hex'), - // 2026-08-24: indexed Activity queries and the visible main session timeline. - '0417d1a044a574a9adc8f347db5f46428a984e89900504bb082ce7366ebb1d4a', + // 2026-09-03: indexed the sparse failure-result subset by session. + '1375f420b93f62ad289bc9b2d10f7ba5fb30672195938eb17f54cd8206177e24', ); });