Skip to content

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 #2206

Closed

Description

@dezren39

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.reads order: ["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, giving ageDays: 241 at 2026-10-29 — the two impossible shapes take two different wrong paths (NaN vs rollover).
  • extractSubscriptions returns Wispr Flow renewsAt: "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 — datesIn string-sort; freshestDate takes the last.
  • pm-stack/aggregator/src/saas-extract.ts:63-72 — readsOf sorts 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 replaces measuredAt on string >.
  • pm-stack/aggregator/src/saas-extract.ts:821-825 — Wispr renews (\d{4}-\d{2}-\d{2}) captured verbatim into renewsAt.
  • 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-calendar renews date yields renewsAt: 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-01 yields measuredAt "2026-10-28"; 2026-02-30 / 2026-04-31 never appear as a read's at or a renewsAt; 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

  1. developing-today commented on Oct 29, 2026

    @developing-today
    Contributor
  2. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    app/aggregatorPackage/app: aggregator (pm-stack in this repo)
    effort/xsTrivial: expected under 30 minutes of work
    on Oct 29, 2026
  3. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator (new-issue-creator-code-b)
    • Session / ID: cron new-issue-creator-code-b, run 2026-10-29 04:20 CDT
    • Found via: filed this run
    • Task: Triage the saas-extract shape-only date flaw: pin the non-calendar-date rule for datesIn/freshestDate/extractSubscriptions, then route a small_fix packet.
    • Expectation: effort/xs; done = rule pinned on the issue and a fix packet enqueued with regression tests (2026-13-01 / 2026-02-30 / 2026-04-31 excluded, 2024-02-29 kept).
    • Links: evidence in the issue body; probe /tmp/probe-laneB-saas/probe2.ts (sandbox Bun, real saas-extract.ts + derive.ts, main 319e6c2)
  4. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · triaged (verdict)

    • Agent: openchamber-scheduled-lane (repo-worker-code) · queue task 3309
    • Verified on: main a69324fb3, real functions under Bun (not a re-implementation):
      • extractSaasServices on Last checked: 2026-10-28 + 2026-13-01 → measuredAt: 2026-13-01 (impossible date wins, masks the real read)
      • extractSubscriptions on renews 2026-02-30 → renewsAt: 2026-02-30 (verbatim impossible renewal)
    • Root cause: DATE_RE = /\d{4}-\d{2}-\d{2}/g (saas-extract.ts:17) is shape-only; datesIn plain-.sort()s matches so any impossible date string-sorts past real dates into freshestDate → measuredAt (:629), zone merge (:651-652), readsOf (:63); the renews extractor regex has no calendar check either.
    • Rule pinned (non-calendar-date rule): impossible dates are ignored — the real date in the same text wins; a non-calendar renews resolves to null. Regression set: 2026-13-01 / 2026-02-30 / 2026-04-31 excluded, 2024-02-29 (leap day) kept.
    • Routed: small_fix packet enqueued for 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 #2206 (real-date guard at datesIn/freshestDate + renews extractor, with the regression tests above).
  5. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 31, 2026
  6. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

    • Agent: queue worker (repo-worker-code), lane D
    • Session / ID: queue task 4281
    • Found via: queue claim
    • Task: saas-extract real-date rule: impossible calendar dates must never win freshestDate; non-calendar renews -> null (saas-extract.ts + regression tests)
    • Expectation: effort xs; done = branch + PR with tests (2026-13-01, 2026-02-30, 2026-04-31 excluded; 2024-02-29 kept), CI/Codex clean, merged, issue closed
    • Links: pm-stack/aggregator/src/saas-extract.ts
  7. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/aggregatorPackage/app: aggregator (pm-stack in this repo)effort/xsTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions