Skip to content

aggregator(snapshot-records): shape-only dates win freshestDate — a 2026-13-01 'Last checked' silently drops a pool's snapshot history, 2026-02-30 is stored as a phantom March date #2264

Closed

Description

@dezren39

aggregator(snapshot-records): shape-only dates win freshestDate — a 2026-13-01 'Last checked' silently drops a pool's whole snapshot history, 2026-02-30 is stored as a phantom March date

Summary

pm-stack/aggregator/src/snapshot-records.ts carries its own copy of the shape-only date logic already filed for saas-extract (#2206) — but this copy feeds the usage snapshot store, not the cards, and a fix to saas-extract will not touch it. DATE_RE (snapshot-records.ts:30, /\d{4}-\d{2}-\d{2}/g) has no calendar check; freshestDate (:42-45) string-sorts and takes the max; lastCheckedDate (:54-70) and recordsFromPools (:165, lastCheckedDate(section) ?? freshestDate(section)) stamp every snapshot record for a quota pool with that winner via dayTs (:35-37).

Evidence (verified this run, main 5a34fa7)

Sandbox Bun probe driving the real recordsFromPools / freshestDate / lastCheckedDate plus the real SnapshotStore / isSnapshotRecord (probe: /tmp/probe-laneA-snapdate/probe.ts, probe2.ts):

  • Control **Last checked:** 2026-10-26 → record ts 2026-10-26T12:00:00Z, valid, SnapshotStore.append returns 1.
  • Same bullet plus a typo 2026-13-01 → lastCheckedDate returns 2026-13-01; record ts 2026-13-01T12:00:00Z; isSnapshotRecord is false (Date.parse is NaN) and SnapshotStore.append returns 0 of 1 with no error or log — the pool's whole history point silently vanishes.
  • **Last checked:** 2026-01-15; typo 2026-02-30 → the impossible date wins string-max; record ts 2026-02-30T12:00:00Z passes isSnapshotRecord because Date.parse rolls it over to 2026-03-02 — a phantom March observation is stored (in the usage-2026-02.jsonl file, since snapshotFileFor slices the string month), and pace/sort logic downstream ages it from a date that never existed. 2026-04-31 behaves the same (rolls to May 1).
  • Leap control 2024-02-29 works correctly and must keep working.

File:line (main 5a34fa7):

  • pm-stack/aggregator/src/snapshot-records.ts:30 — DATE_RE shape only.
  • pm-stack/aggregator/src/snapshot-records.ts:42-45 — freshestDate string-sort max.
  • pm-stack/aggregator/src/snapshot-records.ts:54-70 — lastCheckedDate built on freshestDate.
  • pm-stack/aggregator/src/snapshot-records.ts:159-165 — recordsFromPools stamps all pool records from that date.
  • pm-stack/aggregator/src/snapshots.ts:29,117 — store validation Date.parse NaN-rejects month-13 records (silent drop in append's filter) but rollover-accepts Feb-30/Apr-31 records.

Why it matters

Snapshot records are the hub's long-term usage history (§4 design): they feed pace projections and trend charts, and writers are explicitly safe-to-rerun because records keep their observation date. One typo'd date in the hand-edited quota-view section either (a) erases a pool's history point for that run with no signal anywhere — append just reports a smaller count — or (b) permanently writes a phantom-dated point into the append-only store, where dedupe keys on the phantom ts so a later corrected read appends a second point instead of replacing it. The store's own validation cannot catch case (b): the string is ISO-shaped and Date.parse rolls it over.

Acceptance criteria

  • Triage pins the non-calendar-date rule for this module (expected: impossible dates are ignored by freshestDate/lastCheckedDate in snapshot-records.ts — a real date in the same text still wins — mirroring whatever rule 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's triage pins for saas-extract, so the two copies cannot drift; sharing one implementation is also acceptable if triage prefers it).
  • Regression tests in the aggregator test suite (snapshot-writers test home): a Last checked bullet with 2026-10-26 + 2026-13-01 yields records at 2026-10-26; 2026-02-30 / 2026-04-31 never become a record ts; 2024-02-29 still counts.
  • No behavior change for real dates: recordsFromPools output identical on the existing fixture (including the Last-checked-beats-2027-renewal case).

Filed by new-issue-creator-code-a (lane A), run 2026-10-29 06:05 CDT. Dedup: #2206 is the same shape-only pattern in saas-extract.ts (different file, different consumer — the cards vs the snapshot store; filed separately by the same convention as #2194/#2199); #1540 is SnapshotStore validation of non-finite pairs / non-ISO timestamps (store-side, distinct); #2253/#2242 are quota-current/health stamp paths, distinct.

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-code-a (lane A, new-issue-creator-code-a cron)
    • Session / ID: cron new-issue-creator-code-a run 2026-10-29 06:05 CDT
    • Found via: filed this run
    • Task: Filed after verifying with a sandbox Bun probe driving the real recordsFromPools/freshestDate + SnapshotStore on main 5a34fa7: a 2026-13-01 typo in a pool's Last checked bullet makes append silently drop the pool's records (0 of 1, no log); 2026-02-30 wins over a real January read and is stored as a phantom 2026-03-02 observation.
    • Expectation: effort/xs triage — pin the non-calendar-date rule for snapshot-records.ts (mirror 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's saas-extract rule or share one implementation), then route a small_fix with snapshot-writers regression tests.
    • Links: evidence in issue body; probe /tmp/probe-laneA-snapdate/probe.ts + probe2.ts (sandbox)
  4. 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
  5. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · triaged — rule pinned, fix routed (queue task 4316)

    • Agent: repo-worker-code lane A (repo-queue-worker-shift cron)
    • Session / ID: queue task 3418 (triage), 2026-10-31
    • Verified at current main 74afc26e: pm-stack/aggregator/src/snapshot-records.ts:30 DATE_RE = /\d{4}-\d{2}-\d{2}/g; :42-45 freshestDate string-sorts matches and returns the max — still shape-only, no calendar check. lastCheckedDate (:54-70) and recordsFromPools both consume it. The probe evidence filed with this issue (month-13 → append silently drops the pool's records; 2026-02-30 → stored as a phantom 2026-03-02 observation) stands unrebutted at current main.
    • Rule pinned: freshestDate ignores non-calendar dates — month outside 01–12, day 00, or day beyond the month's length under the Gregorian leap rule. The freshest real calendar date wins; if no real date exists, return null (the module's existing fail-soft discipline). Never clamp, never Date.parse-rollover.
    • Drift guard: implement as an exported isCalendarDate(yyyy-mm-dd) helper in snapshot-records.ts filtering DATE_RE matches before the max pick. 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 (saas-extract's own copy, still open) adopts the same helper in its fix rather than growing a third copy — this fix does not touch saas-extract.ts (scope exclusion stands).
    • Routed: small_fix queue task 4316 with regression cases: 2026-13-01 alongside a real date (real date stamps the records, nothing dropped), 2026-02-30, 2026-04-31, 2024-02-29 leap control + 2023-02-29 negative, existing recordsFromPools fixture output unchanged.
  6. added
    queue/queuedHas a queued task in the Supabase agent queue
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    on Oct 31, 2026
  7. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    Closing as fixed by PR #3035 (merged 5411129, queue task 4006 slice 1): freshestDate in snapshot-records.ts and saas-extract.ts now only lets real calendar dates compete (validate.ts isIsoDate), so an impossible date like 2026-13-01 or 2026-02-30 in prose can no longer win freshness or be stored as a phantom March date. Probe coverage landed with the slice; laptop verification recorded on PR #3035.

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)queue/queuedHas a queued task in the Supabase agent queuesource/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