fix: update DB task status in writeBlockerPlaceholder for execute-task (#2657)
writeBlockerPlaceholder writes a placeholder SUMMARY file when idle recovery exhausts all retries, but never updated the DB task status. verifyExpectedArtifact checks the DB as the authoritative source for execute-task units — with status still "pending", verification failed, deriveState re-derived the same task, and the dispatch loop repeated indefinitely (observed as 8-9 "Advancing pipeline" messages). After writing the file, call updateTaskStatus to mark the task as "complete" in the DB. This lets verifyExpectedArtifact pass and breaks the infinite re-dispatch loop. Closes #2531
This commit is contained in:
parent
c2aaf6ace8
commit
f0e727d369
2 changed files with 78 additions and 1 deletions
|
|
@ -12,7 +12,7 @@ import { parseUnitId } from "./unit-id.js";
|
|||
import { atomicWriteSync } from "./atomic-write.js";
|
||||
import { clearParseCache } from "./files.js";
|
||||
import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
|
||||
import { isDbAvailable, getTask, getSlice, getSliceTasks } from "./gsd-db.js";
|
||||
import { isDbAvailable, getTask, getSlice, getSliceTasks, updateTaskStatus } from "./gsd-db.js";
|
||||
import { isValidationTerminal } from "./state.js";
|
||||
import {
|
||||
nativeConflictFiles,
|
||||
|
|
@ -425,6 +425,20 @@ export function writeBlockerPlaceholder(
|
|||
`Review and replace this file before relying on downstream artifacts.`,
|
||||
].join("\n");
|
||||
writeFileSync(absPath, content, "utf-8");
|
||||
|
||||
// Mark the task as complete in the DB so verifyExpectedArtifact passes.
|
||||
// Without this, the DB status stays "pending" and the dispatch loop
|
||||
// re-derives the same task indefinitely (#2531).
|
||||
if (unitType === "execute-task" && isDbAvailable()) {
|
||||
const parts = unitId.split("/");
|
||||
const mid = parts[0];
|
||||
const sid = parts[1];
|
||||
const tid = parts[2];
|
||||
if (mid && sid && tid) {
|
||||
try { updateTaskStatus(mid, sid, tid, "complete", new Date().toISOString()); } catch { /* non-fatal */ }
|
||||
}
|
||||
}
|
||||
|
||||
return diagnoseExpectedArtifact(unitType, unitId, base);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -294,3 +294,66 @@ test('verifyExpectedArtifact: hook types always return true', () => {
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
test('writeBlockerPlaceholder: updates DB task status for execute-task (#2531)', async () => {
|
||||
const base = createFixtureBase();
|
||||
try {
|
||||
const { openDatabase, closeDatabase, insertMilestone, insertSlice, insertTask, getTask, isDbAvailable } =
|
||||
await import("../gsd-db.ts");
|
||||
|
||||
const dbPath = join(base, ".gsd", "gsd.db");
|
||||
// Create the tasks directory (required for artifact path resolution)
|
||||
mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks"), { recursive: true });
|
||||
|
||||
openDatabase(dbPath);
|
||||
try {
|
||||
insertMilestone({ id: "M001", title: "Test", status: "active" });
|
||||
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "active" });
|
||||
insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "Task", status: "pending" });
|
||||
|
||||
// Before fix: writeBlockerPlaceholder wrote the file but left DB as "pending"
|
||||
writeBlockerPlaceholder("execute-task", "M001/S01/T01", base, "idle recovery exhausted");
|
||||
|
||||
const task = getTask("M001", "S01", "T01");
|
||||
assert.equal(task?.status, "complete",
|
||||
"writeBlockerPlaceholder must update DB task status to 'complete' so verifyExpectedArtifact passes");
|
||||
|
||||
// Verify the full chain works: verifyExpectedArtifact should return true
|
||||
const verified = verifyExpectedArtifact("execute-task", "M001/S01/T01", base);
|
||||
assert.equal(verified, true,
|
||||
"verifyExpectedArtifact should pass after writeBlockerPlaceholder updates DB status");
|
||||
} finally {
|
||||
if (isDbAvailable()) closeDatabase();
|
||||
}
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
||||
test('writeBlockerPlaceholder: does NOT update DB for non-execute-task types', async () => {
|
||||
const base = createFixtureBase();
|
||||
try {
|
||||
const { openDatabase, closeDatabase, insertMilestone, insertSlice, getSlice, isDbAvailable } =
|
||||
await import("../gsd-db.ts");
|
||||
|
||||
const dbPath = join(base, ".gsd", "gsd.db");
|
||||
mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01"), { recursive: true });
|
||||
|
||||
openDatabase(dbPath);
|
||||
try {
|
||||
insertMilestone({ id: "M001", title: "Test", status: "active" });
|
||||
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "active" });
|
||||
|
||||
// research-slice is NOT execute-task — DB should NOT be updated
|
||||
writeBlockerPlaceholder("research-slice", "M001/S01", base, "idle recovery exhausted");
|
||||
|
||||
const slice = getSlice("M001", "S01");
|
||||
assert.equal(slice?.status, "active",
|
||||
"writeBlockerPlaceholder should not change DB status for non-execute-task types");
|
||||
} finally {
|
||||
if (isDbAvailable()) closeDatabase();
|
||||
}
|
||||
} finally {
|
||||
cleanup(base);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue