fix(cycle): record propose_takes deadline break as a halt in the extract rollup

A deadline-hit run breaks the page loop mid-list — same posture as budget
exhaustion — but the rollup still counted it as a completed round with no
halt, hiding chronic never-finishing nightly runs from extract-status/
doctor. Treat deadline_hit like budget_exhausted in the rollup deltas;
deadline test now pins halt=1 / completed=0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-07-22 10:57:10 -07:00
co-authored by Claude Fable 5
parent 4b76e6f13a
commit a579c3686e
2 changed files with 13 additions and 3 deletions
+5 -2
View File
@@ -473,11 +473,14 @@ class ProposeTakesPhase extends BaseCyclePhase {
console.error(`[propose_takes] receipt write failed: ${(err as Error).message}`);
}
}
// A deadline-hit run halted mid-list the same way a budget-exhausted one
// does — record it as a halt, not a completed round.
const halted = result.budget_exhausted || result.deadline_hit === true;
await upsertExtractRollup(engine, {
kind: 'takes.proposed',
source_id: sourceIdForReceipt,
round_completed_delta: result.budget_exhausted ? 0 : 1,
halt_delta: result.budget_exhausted ? 1 : 0,
round_completed_delta: halted ? 0 : 1,
halt_delta: halted ? 1 : 0,
});
return {
+8 -1
View File
@@ -373,7 +373,7 @@ New prose appended here.`;
buildPage({ slug: 'wiki/slow-a', body: 'page a' }),
buildPage({ slug: 'wiki/slow-b', body: 'page b' }),
];
const { engine } = buildMockEngine({ pages });
const { engine, captured } = buildMockEngine({ pages });
let extractorCalls = 0;
const extractor: ProposeTakesExtractor = async () => {
extractorCalls++;
@@ -390,6 +390,13 @@ New prose appended here.`;
expect(details.pages_scanned).toBe(1);
expect(extractorCalls).toBe(1);
expect((details.warnings as string[]).some(w => w.includes('phase deadline hit'))).toBe(true);
// Rollup records the deadline break as a halt, not a completed round
// (same posture as budget exhaustion). Params: $5 = halt, $8 = completed.
const rollup = captured.find((c) => c.sql.includes('extract_rollup_7d'));
expect(rollup).toBeDefined();
expect(rollup!.params[4]).toBe(1); // halt_count delta
expect(rollup!.params[7]).toBe(0); // round_completed delta
});
test('default deadline does not fire on a fast run', async () => {