Repository navigation
Description
aggregator(saas-extract): shape-only dates win freshestDate — a 2026-13-01 'Last checked' masks a real read (measuredAt goes stale/null), 2026-02-30 becomes a renewal date
Summary
pm-stack/aggregator/src/saas-extract.ts accepts any \d{4}-\d{2}-\d{2} string as a date, with no calendar check. datesIn (saas-extract.ts:19-20, DATE_RE at :16) collects matches and string-sorts them, and freshestDate (:24) takes the string-max — so an impossible date always beats every real date in its text. That value becomes a service's measuredAt (saas-extract.ts:628, and the Cloudflare zone merge at :651-652), is shown as the freshest entry in readsOf (:63), and extractSubscriptions passes a shape-matched renews 2026-02-30 through verbatim as renewsAt (:821-825). quota-view.md is a hand-edited local-record snapshot, so a single typo'd date in a "Last checked" bullet silently corrupts the card's freshness state.
Evidence (verified this run, main 319e6c2)
Probe: real extractSaasServices / extractSubscriptions (Bun, sandbox) on a Turso section whose Last checked bullet carries a real read plus a typo, plus a Wispr section:
- Input Last checked:
2026-10-28 real read; typo line 2026-13-01 turso.measuredAt="2026-13-01"— the impossible date wins string-max over the real 2026-10-28.freshness("2026-13-01", 30, 2026-10-29)(derive.ts:876-895) ={ stale: true, ageDays: null }(Date.parse is NaN), while the real read gives{ stale: false, ageDays: 1 }— the card renders stale/never-measured despite a read yesterday.turso.readsorder:["2026-13-01", "2026-10-27", "2026-02-30"]— impossible dates are presented as the freshest reads.freshness("2026-02-30", …)ages from a phantom date instead:Date.parse("2026-02-30T00:00:00Z")rolls over to 2026-03-02 in Bun/V8, givingageDays: 241at 2026-10-29 — the two impossible shapes take two different wrong paths (NaN vs rollover).extractSubscriptionsreturns Wispr FlowrenewsAt: "2026-02-30"— a renewal date that does not exist, shown in the subscriptions strip.
File:line (main 319e6c2):
pm-stack/aggregator/src/saas-extract.ts:16—DATE_RE = /\d{4}-\d{2}-\d{2}/g, shape only.pm-stack/aggregator/src/saas-extract.ts:19-27—datesInstring-sort;freshestDatetakes the last.pm-stack/aggregator/src/saas-extract.ts:63-72—readsOfsorts dated reads by the same strings.pm-stack/aggregator/src/saas-extract.ts:628—measuredAt = freshestDate(Last checked bullet) ?? freshestDate(section).pm-stack/aggregator/src/saas-extract.ts:651-652— Cloudflare zone date replacesmeasuredAton string>.pm-stack/aggregator/src/saas-extract.ts:821-825— Wisprrenews (\d{4}-\d{2}-\d{2})captured verbatim intorenewsAt.pm-stack/aggregator/src/derive.ts:880-887—freshness()Date.parse: NaN → stale/null; Feb-30 → rolled-over phantom date.
Why it matters
The SaaS cards' one trust signal is "when was this last actually read". An impossible date in a hand-edited snapshot currently either (a) masks a fresh real read and pins the card stale with no age, or (b) ages the card from a date that never existed — and a non-existent renewal date is displayed as fact in the subscriptions strip. There is no log line in any of these paths; the fail-soft shell discipline elsewhere in this file (a service whose patterns stop matching renders as a shell) has no equivalent guard for dates that match the shape but not the calendar.
Acceptance criteria
- Triage pins the non-calendar-date rule for this extractor (expected: impossible dates are ignored by
datesIn/freshestDate— a real date in the same text still wins — and a non-calendarrenewsdate yieldsrenewsAt: null; or they are surfaced as a conflict line — triage decides, silence is the one ruled-out option). - Regression tests in the aggregator test suite (saas-extract's test home): a Last checked bullet with
2026-10-28+2026-13-01yieldsmeasuredAt "2026-10-28";2026-02-30/2026-04-31never appear as a read'sator arenewsAt;2024-02-29(leap year) still counts. - No behavior change for real dates:
measuredAt, reads order, zone merge, and subscriptions output identical on existing fixtures.
Filed by new-issue-creator-code-b (lane B), run 2026-10-29 04:20 CDT. Dedup: #2194 is the same shape-only pattern in charges-collect and #2199 in dns.ts (different parsers, inputs, and consumers — filed separately by the same convention); #1568 is future-dated valid dates in derive.freshness (an upper-bound question, not calendar validity); #1684 is summarizeDns UTC-day aging.
Activity
developing-today commentedon Oct 29, 2026
CODE-1088
dezren39 commentedon Oct 29, 2026
🤖 Agent note · filed
dezren39 commentedon Oct 31, 2026
🤖 Agent note · triaged (verdict)
extractSaasServicesonLast checked: 2026-10-28 + 2026-13-01→measuredAt: 2026-13-01(impossible date wins, masks the real read)extractSubscriptionsonrenews 2026-02-30→renewsAt: 2026-02-30(verbatim impossible renewal)DATE_RE = /\d{4}-\d{2}-\d{2}/g(saas-extract.ts:17) is shape-only;datesInplain-.sort()s matches so any impossible date string-sorts past real dates intofreshestDate→measuredAt(:629), zone merge (:651-652),readsOf(:63); therenewsextractor regex has no calendar check either.renewsresolves tonull. Regression set: 2026-13-01 / 2026-02-30 / 2026-04-31 excluded, 2024-02-29 (leap day) kept.datesIn/freshestDate+ renews extractor, with the regression tests above).dezren39 commentedon Oct 31, 2026
🤖 Agent note · picked up
fix(aggregator): non-calendar renews date resolves to null (#2206) (#…
dezren39 commentedon Oct 31, 2026
🤖 Agent note · done