From a579c3686e0a7592eab0164c66de77a9719d2cc7 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Wed, 22 Jul 2026 10:57:10 -0700 Subject: [PATCH] fix(cycle): record propose_takes deadline break as a halt in the extract rollup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/core/cycle/propose-takes.ts | 7 +++++-- test/propose-takes.test.ts | 9 ++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/core/cycle/propose-takes.ts b/src/core/cycle/propose-takes.ts index a1d39aad9..91cafead3 100644 --- a/src/core/cycle/propose-takes.ts +++ b/src/core/cycle/propose-takes.ts @@ -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 { diff --git a/test/propose-takes.test.ts b/test/propose-takes.test.ts index e310a2565..af5674a0d 100644 --- a/test/propose-takes.test.ts +++ b/test/propose-takes.test.ts @@ -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 () => {