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 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-07-22 10:53:55 -07:00
co-authored by Claude Fable 5
parent 0dff84b16a
commit 921048827a
2 changed files with 24 additions and 4 deletions
+9 -4
View File
@@ -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<number> {
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;
@@ -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);
});
});