From d4c0d0a6bcab075037d34bd5c08d46519274985d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 16 Apr 2026 23:02:43 -0400 Subject: [PATCH] refactor(db): move run-deliverable detection out of raw db.queryAll P2-37: automation-run-completion.checkRunProducedDeliverable issued 5 raw SQLs via db.queryAll. Consolidate into db.runProducedDeliverable so schema changes to knowledge_base / documents / task_comments surface in the knowledge domain module rather than silently breaking at the caller. --- .../automation/automation-run-completion.ts | 36 +--------------- src/main/database/knowledge.ts | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/src/main/automation/automation-run-completion.ts b/src/main/automation/automation-run-completion.ts index 1d66f453..8198fda0 100644 --- a/src/main/automation/automation-run-completion.ts +++ b/src/main/automation/automation-run-completion.ts @@ -114,41 +114,7 @@ export function checkBudgetThreshold( // ─── Internal DB-backed deliverable check ─────────────────────────────────── function checkRunProducedDeliverable(db: AppDatabase, runId: string, companyId: string, taskId: string): boolean { - // Prefer causal binding via source_run_id (set when document/knowledge - // is created during a run). Falls back to time-window for legacy data - // that predates the source_run_id column. - const causalKnowledge = db.queryAll<{ id: string }>( - "select id from knowledge_base where company_id = ? and source_run_id = ? limit 1", - companyId, runId, - ); - if (causalKnowledge.length > 0) return true; - - const causalDocs = db.queryAll<{ id: string }>( - "select id from documents where company_id = ? and source_run_id = ? limit 1", - companyId, runId, - ); - if (causalDocs.length > 0) return true; - - // Time-window fallback: check for legacy records without source_run_id - const legacyKnowledge = db.queryAll<{ id: string }>( - "select id from knowledge_base where company_id = ? and source_run_id is null and created_at >= (select started_at from runs where id = ?) limit 1", - companyId, runId, - ); - if (legacyKnowledge.length > 0) return true; - - const legacyDocs = db.queryAll<{ id: string }>( - "select id from documents where company_id = ? and source_run_id is null and created_at >= (select started_at from runs where id = ?) limit 1", - companyId, runId, - ); - if (legacyDocs.length > 0) return true; - - const recentComments = db.queryAll<{ body: string }>( - "select body from task_comments where task_id = ? and created_at >= (select started_at from runs where id = ?) and length(body) > 100", - taskId, runId, - ); - if (recentComments.length > 0) return true; - - return false; + return db.runProducedDeliverable(runId, companyId, taskId); } /** Returns important documents created during this run (matching IMPORTANT_DOCUMENT_TYPES). */ diff --git a/src/main/database/knowledge.ts b/src/main/database/knowledge.ts index a3aa556c..c67a6b83 100644 --- a/src/main/database/knowledge.ts +++ b/src/main/database/knowledge.ts @@ -172,6 +172,48 @@ export function buildKnowledgeMethods( getDb().prepare("delete from sprints where id = ? and company_id = ?").run(id, companyId); }, + /** + * Returns true when the given run caused a durable deliverable — a + * knowledge entry, a document, or a substantial task comment (>100 + * chars). Causal binding via source_run_id is preferred; time-window + * fallback handles legacy rows written before that column existed. + * + * Encapsulates the five raw SELECTs that automation-run-completion + * previously issued via db.queryAll, so schema changes to knowledge_base + * / documents / task_comments surface here rather than silently. + */ + runProducedDeliverable(runId: string, companyId: string, taskId: string): boolean { + const causalKnowledge = readAll<{ id: string }>( + "select id from knowledge_base where company_id = ? and source_run_id = ? limit 1", + companyId, runId, + ); + if (causalKnowledge.length > 0) return true; + + const causalDocs = readAll<{ id: string }>( + "select id from documents where company_id = ? and source_run_id = ? limit 1", + companyId, runId, + ); + if (causalDocs.length > 0) return true; + + const legacyKnowledge = readAll<{ id: string }>( + "select id from knowledge_base where company_id = ? and source_run_id is null and created_at >= (select started_at from runs where id = ?) limit 1", + companyId, runId, + ); + if (legacyKnowledge.length > 0) return true; + + const legacyDocs = readAll<{ id: string }>( + "select id from documents where company_id = ? and source_run_id is null and created_at >= (select started_at from runs where id = ?) limit 1", + companyId, runId, + ); + if (legacyDocs.length > 0) return true; + + const recentComments = readAll<{ body: string }>( + "select body from task_comments where task_id = ? and created_at >= (select started_at from runs where id = ?) and length(body) > 100", + taskId, runId, + ); + return recentComments.length > 0; + }, + /** * Returns all sprints for a company, ordered by start_date desc. * Used by GET /api/companies/:companyId/sprints.