From 921048827ad45b5d5cee9221a5ff10bf8e5cfe40 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Wed, 22 Jul 2026 10:53:55 -0700 Subject: [PATCH] fix(remediation): pass LINK_EXTRACTOR_VERSION_TS to the extraction-lag gate countExtractionLag omitted versionTs, so its predicate diverged from the counter it claims to share with doctor's links_extraction_lag check and the extract --stale walk: pages stamped before an extractor version bump (links_extracted_at < LINK_EXTRACTOR_VERSION_TS) lagged for doctor and extract but never tripped the sync.repo/extract.all gate. Pass the stamp; pin the version-bump arm with a backdated-page test (fails without it). Co-Authored-By: Claude Fable 5 --- src/core/remediation/context.ts | 13 +++++++++---- test/remediation-context-extraction-lag.test.ts | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/core/remediation/context.ts b/src/core/remediation/context.ts index 8599df699..e34bddcfe 100644 --- a/src/core/remediation/context.ts +++ b/src/core/remediation/context.ts @@ -8,6 +8,7 @@ import type { BrainEngine } from '../engine.ts'; import type { RecommendationContext } from '../brain-score-recommendations.ts'; +import { LINK_EXTRACTOR_VERSION_TS } from '../link-extraction.ts'; // Re-export so consumers can `import { RecommendationContext } from '../remediation'` // — the canonical RecommendationContext type still lives in @@ -74,9 +75,13 @@ export async function loadRecommendationContext( /** * Real extraction-lag count — the SAME staleness `gbrain extract --stale` - * processes (engine.countStalePagesForExtraction). Drives the sync→extract - * recommendation pipeline; replaces the legacy `health.stale_pages` proxy - * that no longer reflected real extraction work after the v10 trigger drop. + * processes (engine.countStalePagesForExtraction with + * versionTs=LINK_EXTRACTOR_VERSION_TS, matching doctor's links_extraction_lag + * check — without versionTs, pages stamped before an extractor version bump + * would lag for doctor/extract but never trip this gate). Drives the + * sync→extract recommendation pipeline; replaces the legacy + * `health.stale_pages` proxy that no longer reflected real extraction work + * after the v10 trigger drop. * * Shared by loadRecommendationContext AND the D7 per-step recheck in * runRemediation — the recheck MUST refresh this gate alongside getHealth, @@ -84,7 +89,7 @@ export async function loadRecommendationContext( */ export async function countExtractionLag(engine: BrainEngine): Promise { try { - return await engine.countStalePagesForExtraction(); + return await engine.countStalePagesForExtraction({ versionTs: LINK_EXTRACTOR_VERSION_TS }); } catch { /* counter unavailable (very old brain / mid-migration) — treat as 0 */ return 0; diff --git a/test/remediation-context-extraction-lag.test.ts b/test/remediation-context-extraction-lag.test.ts index 94a0ad362..7ac7ab1af 100644 --- a/test/remediation-context-extraction-lag.test.ts +++ b/test/remediation-context-extraction-lag.test.ts @@ -44,4 +44,19 @@ describe('loadRecommendationContext — extractionLagPages wiring', () => { const ctx = await loadRecommendationContext(engine); expect(ctx.extractionLagPages).toBeGreaterThan(0); }); + + it('counts pages stamped before LINK_EXTRACTOR_VERSION_TS (version-bump arm)', async () => { + // Backdate p0 so BOTH the NULL arm and the updated_at arm are quiet: + // updated_at < links_extracted_at, but links_extracted_at predates the + // extractor version stamp. doctor's links_extraction_lag and + // `extract --stale` both count this page; the remediation gate must too. + await engine.executeRaw( + `UPDATE pages SET updated_at = '2020-01-01T00:00:00Z'::timestamptz, + links_extracted_at = '2020-01-02T00:00:00Z'::timestamptz + WHERE slug = 'p0'`, + [], + ); + const ctx = await loadRecommendationContext(engine); + expect(ctx.extractionLagPages).toBeGreaterThan(0); + }); });