/** * 43 号文档 Phase 3:coder-validator 判自己 success 之前,子任务必须全部 completed * 才认;以及 settleSubtaskResult 这个落库口本身的条件更新/幂等/校验。 */ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import fs from 'fs'; import os from 'os'; import path from 'path'; const TEST_DB_PATH = '/tmp/test-task-decomp-validator-gate.sqlite'; const DB_FILES = [TEST_DB_PATH, `${TEST_DB_PATH}-shm`, `${TEST_DB_PATH}-wal`]; const MODULES = [ '../../services/graph-db', '../../services/graph-service', '../../services/org-graph-service', '../../services/work-graph-db', '../../services/graph-run-sse', '../../services/graph-engine', ]; describe('43 号文档 Phase 3', () => { let wdb; let engine; let runsDir; beforeAll(() => { for (const f of DB_FILES) { try { fs.unlinkSync(f); } catch {} } process.env.GRAPH_DB_PATH = TEST_DB_PATH; runsDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-decomp-gate-runs-')); process.env.GRAPH_RUNS_DIR = runsDir; for (const m of MODULES) delete require.cache[require.resolve(m)]; wdb = require('../../services/work-graph-db'); engine = require('../../services/graph-engine'); }); afterAll(() => { for (const f of DB_FILES) { try { fs.unlinkSync(f); } catch {} } delete process.env.GRAPH_DB_PATH; try { fs.rmSync(runsDir, { recursive: true, force: true }); } catch {} delete process.env.GRAPH_RUNS_DIR; for (const m of MODULES) delete require.cache[require.resolve(m)]; }); function ensureAgent(id) { const db = wdb.getDb(); const now = new Date().toISOString(); db.prepare(`INSERT OR IGNORE INTO system_agents (id, name, scope, owner_company_id, legacy_path, status, created_at, updated_at) VALUES (?, ?, 'public', NULL, ?, 'active', ?, ?)`) .run(id, id, `system/agents/${id}`, now, now); } // ── settleSubtaskResult 本身 ───────────────────────────────────── it('settleSubtaskResult:非法 status 直接拒', () => { expect(() => wdb.settleSubtaskResult('T-whatever', 'running')).toThrow(/status 只能是/); }); it('settleSubtaskResult:不是子任务(parent_task_id 为空)不生效,changes=0', () => { const goal = wdb.createGoal({ companyId: 'co-gate-1', title: 'g', description: 'd' }); const top = wdb.createTask({ goalId: goal.id, title: 't', description: 'd' }); const changed = wdb.settleSubtaskResult(top.id, 'completed'); expect(changed).toBe(0); expect(wdb.getTask(top.id).status).toBe('draft'); // 没被动过 }); it('settleSubtaskResult:正常上报 + 终态幂等(第二次不同结果上报不生效)', () => { const goal = wdb.createGoal({ companyId: 'co-gate-2', title: 'g', description: 'd' }); const parent = wdb.createTask({ goalId: goal.id, title: 'p', description: 'd', acceptanceMode: 'itemized' }); const child = wdb.createTask({ goalId: goal.id, title: 'c', description: 'd', parentTaskId: parent.id }); expect(wdb.settleSubtaskResult(child.id, 'completed', '通过')).toBe(1); const after1 = wdb.getTask(child.id); expect(after1.status).toBe('completed'); expect(after1.summary).toBe('通过'); expect(after1.finished_at).toBeTruthy(); // 已经是终态,再上报 failed 不该覆盖——防的是 coder-validator 因 retry 重复上报 expect(wdb.settleSubtaskResult(child.id, 'failed', '改判')).toBe(0); expect(wdb.getTask(child.id).status).toBe('completed'); }); // ── 引擎侧的闸门:coder-validator 判 success 前核子任务 ───────────── async function runValidator(companyId, taskId) { ensureAgent('coder-validator'); const g = wdb.createWorkGraph({ companyId, taskId, nodes: [{ nodeName: 'v', systemAgentId: 'coder-validator' }], edges: [], }); expect(g.errors || []).toEqual([]); const run = wdb.createRun({ graphKind: 'work', graphId: g.id, companyId, taskId }); engine.markRunReady(run.id); engine.startRun(run.id, { mode: 'manual' }); engine.setAgentExecutor(async () => ({ status: 'success' })); let out; try { out = await engine.advanceOne(run.id); } finally { engine.setAgentExecutor(null); } return out; } it('itemized 任务 + 子任务还没全部 completed → coder-validator 判自己 success 但被引擎打回 fail', async () => { const companyId = 'co-gate-3'; const goal = wdb.createGoal({ companyId, title: 'g', description: 'd' }); const parent = wdb.createTask({ goalId: goal.id, title: 'p', description: 'd', acceptanceMode: 'itemized' }); const c1 = wdb.createTask({ goalId: goal.id, title: 'c1', description: 'd', parentTaskId: parent.id }); wdb.createTask({ goalId: goal.id, title: 'c2', description: 'd', parentTaskId: parent.id }); wdb.settleSubtaskResult(c1.id, 'completed'); // 只完成一半 const out = await runValidator(companyId, parent.id); // 单节点图没有兜底边可退,fail 之后引擎会继续把它收敛成 blocked(等人)—— // 这里要验的是「没被判 success」+「归因是 subtasks_incomplete」,不是具体停在哪个终态 expect(out.node.status).not.toBe('success'); expect(out.node.failReason).toBe('subtasks_incomplete'); }); it('itemized 任务 + 子任务全部 completed → coder-validator 正常判 success', async () => { const companyId = 'co-gate-4'; const goal = wdb.createGoal({ companyId, title: 'g', description: 'd' }); const parent = wdb.createTask({ goalId: goal.id, title: 'p', description: 'd', acceptanceMode: 'itemized' }); const c1 = wdb.createTask({ goalId: goal.id, title: 'c1', description: 'd', parentTaskId: parent.id }); const c2 = wdb.createTask({ goalId: goal.id, title: 'c2', description: 'd', parentTaskId: parent.id }); wdb.settleSubtaskResult(c1.id, 'completed'); wdb.settleSubtaskResult(c2.id, 'completed'); const out = await runValidator(companyId, parent.id); expect(out.node.status).toBe('success'); }); it('holistic 任务(没有子任务):闸门不生效,success 照常通过', async () => { const companyId = 'co-gate-5'; const goal = wdb.createGoal({ companyId, title: 'g', description: 'd' }); const task = wdb.createTask({ goalId: goal.id, title: 't', description: 'd' }); // 缺省 holistic const out = await runValidator(companyId, task.id); expect(out.node.status).toBe('success'); }); });