[object Object]

← back to Sample Followup Sweep

sample-followup: fix TK-12091 window regression + shared window + canary tripwire

616e3a84bb9baafae17cd4dc37d1662c5c4d3277 · 2026-09-24 09:36:47 -0700 · Steve Abrams

Part 1 (DTD panel verdict A): restore the FULL active [today-60..today-10] query
window every run. TK-12091 (a11a644) had narrowed scheduled-run.mjs to a
day-of-week band, silently reopening TK-12013/14's permanent-hole bug — a live
canary found 15 outstanding stragglers (some 56d old, incl. DWTT70793) never
drafted. Dedup (Sent-stamp + draft-ledger) makes the wide re-query a safe no-op.

Part 2 (Cody's co-required fix): single source of truth for the window —
followupWindow() in lib/sweep.js, called by BOTH scheduled-run.mjs and the
coverage canary, so production and audit can never drift again. Canary now runs
windowCoversActiveBand() as a TK-12091-regression tripwire: if production's
window is ever re-narrowed below the full active band, the canary goes FAIL
instead of silently auditing a wider window than production chases. Ships a
negative test (test 8) proving the tripwire goes red on an injected narrow window.

Verified: window now 07/26..09/14; DWTT70793/Thibaut back in candidate set;
canary leaked=0, band covers=true; 8/8 tests pass. sweep()'s orphaned DOW logic
is dead code (nothing calls it) — left untouched, noted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 616e3a84bb9baafae17cd4dc37d1662c5c4d3277
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 24 09:36:47 2026 -0700

    sample-followup: fix TK-12091 window regression + shared window + canary tripwire
    
    Part 1 (DTD panel verdict A): restore the FULL active [today-60..today-10] query
    window every run. TK-12091 (a11a644) had narrowed scheduled-run.mjs to a
    day-of-week band, silently reopening TK-12013/14's permanent-hole bug — a live
    canary found 15 outstanding stragglers (some 56d old, incl. DWTT70793) never
    drafted. Dedup (Sent-stamp + draft-ledger) makes the wide re-query a safe no-op.
    
    Part 2 (Cody's co-required fix): single source of truth for the window —
    followupWindow() in lib/sweep.js, called by BOTH scheduled-run.mjs and the
    coverage canary, so production and audit can never drift again. Canary now runs
    windowCoversActiveBand() as a TK-12091-regression tripwire: if production's
    window is ever re-narrowed below the full active band, the canary goes FAIL
    instead of silently auditing a wider window than production chases. Ships a
    negative test (test 8) proving the tripwire goes red on an injected narrow window.
    
    Verified: window now 07/26..09/14; DWTT70793/Thibaut back in candidate set;
    canary leaked=0, band covers=true; 8/8 tests pass. sweep()'s orphaned DOW logic
    is dead code (nothing calls it) — left untouched, noted.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 lib/sweep.js                     | 36 +++++++++++++++++++++++++++++++++++-
 scripts/coverage-reconcile.mjs   | 23 +++++++++++++++++------
 scripts/scheduled-run.mjs        | 29 +++++++++--------------------
 test/coverage-reconcile.test.cjs | 26 +++++++++++++++++++++++++-
 4 files changed, 86 insertions(+), 28 deletions(-)

diff --git a/lib/sweep.js b/lib/sweep.js
index 44f985c..429d83d 100644
--- a/lib/sweep.js
+++ b/lib/sweep.js
@@ -82,6 +82,40 @@ function suppressChase(enteredDate, siblings = []) {
   return { suppress: false, reason: null };
 }
 
+// TK-12091 regression fix (2026-09-24): SINGLE SOURCE OF TRUTH for the production follow-up window.
+// Both scheduled-run.mjs (what actually drafts vendor letters) AND coverage-reconcile.mjs (the canary
+// that audits coverage) call THIS one function — so the audit can never again model a different window
+// than production actually chases. TK-12091 narrowed production's window to a day-of-week band while the
+// canary kept auditing [10..60]; the two drifted and hid 15 real stragglers behind a green canary.
+// DEFAULT = the full active band [today-maxAge .. today-minAge]. WIN/CATCHUP env overrides preserved.
+function followupWindow(today = new Date(), { minAge = 10, maxAge = 60 } = {}) {
+  const pad = (n) => String(n).padStart(2, '0');
+  const fmtD = (d) => `${pad(d.getMonth() + 1)}/${pad(d.getDate())}/${d.getFullYear()}`;
+  if (process.env.WIN) return process.env.WIN;                       // explicit manual override
+  const catchup = Number(process.env.CATCHUP) || 0;
+  const hi = new Date(today); hi.setDate(hi.getDate() - minAge);     // today-minAge (10-day floor)
+  const lo = new Date(today);
+  if (catchup > 0) lo.setDate(lo.getDate() - minAge - (catchup - 1)); // manual fixed rolling band
+  else lo.setDate(lo.getDate() - maxAge);                            // DEFAULT: full active band floor
+  return `${fmtD(lo)}...${fmtD(hi)}`;
+}
+// Parse an "MM/DD/YYYY...MM/DD/YYYY" window; return { lo, hi } Dates or null.
+function parseWindow(win) {
+  const m = /^(\d{1,2})\/(\d{1,2})\/(\d{4})\.\.\.(\d{1,2})\/(\d{1,2})\/(\d{4})$/.exec(String(win || '').trim());
+  if (!m) return null;
+  return { lo: new Date(+m[3], +m[1] - 1, +m[2]), hi: new Date(+m[6], +m[4] - 1, +m[5]) };
+}
+// TK-12091 regression tripwire: does production's ACTUAL window cover the full active band? Returns
+// { covers, prodLo, expectedLo, gapDays }. covers===false means production is narrower than [10..60]
+// and outstanding in-band memos are unreachable — the exact TK-12091 failure, now a canary FAIL.
+function windowCoversActiveBand(today = new Date(), { minAge = 10, maxAge = 60 } = {}) {
+  const prod = parseWindow(followupWindow(today, { minAge, maxAge }));
+  const expectedLo = new Date(today); expectedLo.setDate(expectedLo.getDate() - maxAge);
+  if (!prod) return { covers: false, prodLo: null, expectedLo, gapDays: null, why: 'unparseable production window' };
+  const gapDays = Math.round((prod.lo.getTime() - expectedLo.getTime()) / 86400000);
+  return { covers: prod.lo.getTime() <= expectedLo.getTime() + 86400000, prodLo: prod.lo, expectedLo, gapDays };
+}
+
 // KNOWN UNRESOLVABLE SAMPLES — vendor has no vid/contact anywhere in the fleet, so sweep can't chase.
 // These are ACCOUNTED (tracked, WARN-visible) but not actioned. Add SKU::vendor pairs here.
 const KNOWN_UNRESOLVABLE = new Set(['DWLC1059::LA Walls']); // TK-12119: LA Walls (no vid anywhere)
@@ -172,4 +206,4 @@ function reconcileCoverage(records, { contacts = {}, recentSet = new Set(), runS
   return { verdict, status: verdict, population, accounted, covered, uncovered, leaked, reconciles, counts, buckets: b };
 }
 
-module.exports = { sweep, ageDays, suppressChase, reconcileCoverage };
+module.exports = { sweep, ageDays, suppressChase, reconcileCoverage, followupWindow, parseWindow, windowCoversActiveBand };
diff --git a/scripts/coverage-reconcile.mjs b/scripts/coverage-reconcile.mjs
index 8fbc183..801d404 100644
--- a/scripts/coverage-reconcile.mjs
+++ b/scripts/coverage-reconcile.mjs
@@ -16,7 +16,7 @@ import { homedir } from 'node:os'; import { join, dirname } from 'node:path'; im
 import { createRequire } from 'node:module'; import http from 'node:http';
 const require = createRequire(import.meta.url);
 const __dir = dirname(fileURLToPath(import.meta.url)); const ROOT = join(__dir, '..');
-const { reconcileCoverage } = require(join(ROOT, 'lib', 'sweep.js'));
+const { reconcileCoverage, windowCoversActiveBand } = require(join(ROOT, 'lib', 'sweep.js'));
 
 const SELF_TEST = process.argv.includes('--self-test');
 const MIN_AGE = 10, MAX_AGE = Number(process.env.MAX_AGE) || 60;
@@ -100,9 +100,19 @@ async function main() {
   const runStale = runStaleToday();
   const r = reconcileCoverage(records, { contacts, recentSet: set, runStale });
 
-  const out = { verdict: r.verdict, status: r.status, window, population: r.population, accounted: r.accounted,
-    covered: r.covered, uncovered: r.uncovered, leakedCount: r.leaked.length, reconciles: r.reconciles,
-    runStale, buckets: r.counts, leaked: r.leaked.slice(0, 20) };
+  // TK-12091 REGRESSION TRIPWIRE (Cody's co-required fix, 2026-09-24): the canary and production now
+  // share ONE window function (lib/sweep.js followupWindow). Assert production's window still covers the
+  // full active [MIN_AGE..MAX_AGE] band — if a future change re-narrows it (like TK-12091 did), rows
+  // this canary audits become unreachable in production and this goes FAIL, instead of the audit
+  // silently modeling a wider window than production chases (the exact drift that hid 15 stragglers).
+  const band = windowCoversActiveBand(new Date(), { minAge: MIN_AGE, maxAge: MAX_AGE });
+  let verdict = r.verdict;
+  if (!band.covers) verdict = 'FAIL';   // production window narrower than active band = unreachable memos
+
+  const out = { verdict, status: verdict, reconcileVerdict: r.verdict, window, population: r.population,
+    accounted: r.accounted, covered: r.covered, uncovered: r.uncovered, leakedCount: r.leaked.length,
+    reconciles: r.reconciles, runStale, windowCoversActiveBand: band.covers, windowGapDays: band.gapDays,
+    buckets: r.counts, leaked: r.leaked.slice(0, 20) };
   writeLatest(out);
 
   console.log(`Coverage reconciliation — window ${window}`);
@@ -113,7 +123,8 @@ async function main() {
   console.log(`  leaked (outstanding, NO bucket): ${r.leaked.length}`);
   if (r.leaked.length) r.leaked.slice(0, 10).forEach((x) => console.log(`     ⚠ LEAK vid=${JSON.stringify(x.vid)} sku=${x.sku} — ${x.why}`));
   console.log(`  reconciles (accounted+leaked==population): ${r.reconciles}   run-stale: ${runStale}`);
-  console.log(`  VERDICT: ${r.verdict}`);
-  process.exit(r.verdict === 'FAIL' ? 1 : 0);
+  if (!band.covers) console.log(`  ⚠ PRODUCTION WINDOW TOO NARROW: floor is ${band.gapDays}d short of the ${MAX_AGE}d active band — in-band outstanding memos are UNREACHABLE by production (TK-12091-class regression)`);
+  console.log(`  VERDICT: ${verdict}${verdict !== r.verdict ? ` (reconcile said ${r.verdict}; window-band tripwire forced ${verdict})` : ''}`);
+  process.exit(verdict === 'FAIL' ? 1 : 0);
 }
 main();
diff --git a/scripts/scheduled-run.mjs b/scripts/scheduled-run.mjs
index b736018..0942161 100644
--- a/scripts/scheduled-run.mjs
+++ b/scripts/scheduled-run.mjs
@@ -30,7 +30,7 @@ const __dir = dirname(fileURLToPath(import.meta.url));
 const ROOT = join(__dir, '..');
 const { compose } = require(join(ROOT, 'lib', 'compose.js'));
 const { georgeRequest } = require(join(ROOT, 'lib', 'george-transport.js'));
-const { suppressChase } = require(join(ROOT, 'lib', 'sweep.js'));   // TK-12105 never-false-chase guard
+const { suppressChase, followupWindow } = require(join(ROOT, 'lib', 'sweep.js'));   // TK-12105 guard + TK-12091 shared window
 
 const SEND = process.argv.includes('--send');
 // Steve 8/20: DRAFT mode — create a Gmail draft per vendor in info@ Drafts for human review,
@@ -124,26 +124,15 @@ function lastCoveredHi(today) {
   }
   return best;
 }
+// TK-12091 REGRESSION FIX (DTD panel verdict A, 2026-09-24): delegate to the SHARED followupWindow in
+// lib/sweep.js — the SINGLE source of truth the coverage canary also uses, so production and audit can
+// never drift again. Default = FULL active band [today-MAX_AGE_DAYS .. today-MIN_AGE_DAYS]; WIN/CATCHUP
+// env overrides are honored inside followupWindow. TK-12091 had narrowed this to a day-of-week band,
+// silently reopening TK-12013/14's permanent-hole bug (15 stragglers, some 56d old, incl. DWTT70793,
+// never drafted). The dedup (FileMaker Sent-stamp + recordId draft-ledger) makes re-querying an
+// already-chased memo a no-op, so the wide window catches stragglers without ever re-chasing.
 function windowRange(today = new Date()) {
-  if (process.env.WIN) return process.env.WIN;   // explicit override, e.g. "08/17/2026...08/20/2026"
-  if (CATCHUP_DAYS > 0) {
-    // Manual override — fixed rolling band [today-10-(N-1) .. today-10].
-    const hi = new Date(today); hi.setDate(hi.getDate() - MIN_AGE_DAYS);
-    const lo = new Date(today); lo.setDate(lo.getDate() - MIN_AGE_DAYS - (CATCHUP_DAYS - 1));
-    return `${fmtDate(lo)}...${fmtDate(hi)}`;
-  }
-  // TK-12091 (Steve 2026-09-23): DAY-OF-WEEK-AWARE window. A follow-up fires the day a sample's
-  // Entered date reaches its 10th CALENDAR day, mapped onto the Tue-Fri run schedule so every
-  // landing-day is covered exactly once (no gap, no double-chase):
-  //   • TUESDAY → 4-day catch-up: Entered ∈ [today-13 .. today-10] — covers the 10th-days that
-  //     landed on the preceding Sat(−13)/Sun(−12)/Mon(−11) plus Tue(−10) itself.
-  //   • WED/THU/FRI → STRICT single day: Entered == today-10.
-  //   • any other weekday (Sat/Sun/Mon, manual runs only) → STRICT single day (never re-chase).
-  // hi is always today-10; only Tuesday widens the low end. CATCHUP/WIN stay as manual recovery nets.
-  const span = (today.getDay() === 2) ? 3 : 0;                        // Tue widens 3 days back
-  const hi = new Date(today); hi.setDate(hi.getDate() - MIN_AGE_DAYS);           // today-10
-  const lo = new Date(today); lo.setDate(lo.getDate() - MIN_AGE_DAYS - span);    // today-10-span
-  return `${fmtDate(lo)}...${fmtDate(hi)}`;
+  return followupWindow(today, { minAge: MIN_AGE_DAYS, maxAge: MAX_AGE_DAYS });
 }
 const norm = (s) => String(s || '').toLowerCase().trim();
 const readJSON = (f, d) => { try { return JSON.parse(readFileSync(join(ROOT, f), 'utf8')); } catch { return d; } };
diff --git a/test/coverage-reconcile.test.cjs b/test/coverage-reconcile.test.cjs
index a55bc5b..688e63e 100644
--- a/test/coverage-reconcile.test.cjs
+++ b/test/coverage-reconcile.test.cjs
@@ -3,7 +3,7 @@
 // Proves the canary goes RED on an injected fault (an outstanding row in NO bucket = silent skip)
 // and GREEN only when the population is fully accounted + actioned.
 const assert = require('node:assert');
-const { reconcileCoverage } = require('../lib/sweep');
+const { reconcileCoverage, followupWindow, windowCoversActiveBand } = require('../lib/sweep');
 
 // A resolvable vendor (chaseable) + a no-chase vendor as the "known good" contacts.
 const contacts = {
@@ -81,4 +81,28 @@ const row = (vid, sku, entered, wp = '', letter = '') => ({ fieldData: {
   console.log(`(6) PASS — recipient-busy aged >24d -> WARN not covered (recipientBusyStale=${r.counts.recipientBusyStale}, was suppressed14d/PASS)`);
 }
 
+// 7. WINDOW-BAND TRIPWIRE — POSITIVE: the default production window covers the full [10..60] active band.
+{
+  const today = new Date(2026, 8, 24);
+  const band = windowCoversActiveBand(today, { minAge: 10, maxAge: 60 });
+  assert.equal(band.covers, true, '7 default full window must cover the active band');
+  assert.equal(band.gapDays, 0, '7 default window floor == today-60 (no gap)');
+  assert.ok(/07\/26\/2026\.\.\.09\/14\/2026/.test(followupWindow(today, { minAge: 10, maxAge: 60 })), '7 window is the full [today-60..today-10] band');
+  console.log('(7) PASS — default production window covers the full active band (gapDays=0)');
+}
+
+// 8. NEGATIVE (TK-12091 regression tripwire) — inject a NARROWED production window (a re-narrowing like
+//    TK-12091) and prove the canary tripwire goes RED: windowCoversActiveBand must return covers=false.
+{
+  const today = new Date(2026, 8, 24);
+  const prev = process.env.WIN;
+  process.env.WIN = '09/13/2026...09/14/2026';   // a 1-day band = the TK-12091-class narrowing
+  try {
+    const band = windowCoversActiveBand(today, { minAge: 10, maxAge: 60 });
+    assert.equal(band.covers, false, '8 a narrowed production window MUST fail the tripwire (covers=false)');
+    assert.ok(band.gapDays > 0, '8 gapDays must report how far short the floor is');
+    console.log(`(8) PASS — injected narrow window -> tripwire RED (covers=false, gapDays=${band.gapDays})`);
+  } finally { if (prev === undefined) delete process.env.WIN; else process.env.WIN = prev; }
+}
+
 console.log('\nALL COVERAGE-RECONCILE ASSERTIONS PASSED');

← 9f289bf auto-data-snapshot: 2026-09-24T09:12:24 (1 data files) — dat  ·  back to Sample Followup Sweep  ·  auto-data-snapshot: 2026-09-24T09:44:11 (3 data files) — dat ae3a82b →