Skip to content

aggregator(quota-extract): Claude and Meta reset dates are never validated — an impossible date is emitted verbatim as resetsAt, and a Feb-30 Meta reset silently discards the pool's whole read #2470

Closed

Description

@dezren39

aggregator(quota-extract): Claude and Meta reset dates are never validated — an impossible date is emitted verbatim as resetsAt, and a Feb-30 Meta reset silently discards the pool's whole read

Summary

extractClaude and extractMeta in pm-stack/aggregator/src/quota-extract.ts capture a reset date/time from the human-written quota view with shape-only regexes ((\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})) and pass the captures through dateTimeCDT, which is a pure string interpolator — it builds ${date}T${hhmm}:00-05:00 with no calendar or clock validation at all. The result:

  • Claude: a reset read of 2026-13-28 14:00, 2026-10-28 99:99, or 2026-10-39 14:00 is emitted verbatim as the window's resetsAt ("2026-13-28T14:00:00-05:00" etc.). Date.parse of those strings is NaN, so every downstream consumer that parses the value (quota-guidance's reset-time filter, derive's pace/reset logic) silently drops or mis-handles it, and render's fmtDate falls back to printing the raw impossible string as the reset date. A 2026-02-30 reset is worse: Date.parse rolls it over to 2026-03-02, so downstream treats a phantom date as the real reset.
  • Meta: the same invalid string feeds the reset-passed check now >= Date.parse(resetIso). For month-13 / 99:99 the parse is NaN, the comparison is false, and the invalid string is emitted verbatim (and, on the burned-to-empty path, also becomes wallLiftsAt). For a 2026-02-30 reset the parse rolls over to 2026-03-02 — which is before now in the probe — so the extractor takes the "window already reset, no post-reset read yet" branch and discards the pool's entire matched 46% read, windows and all.

This is the module's own fail-soft contract failing in the opposite direction from the usual drop: the percentages were successfully read, so an invalid reset should degrade to resetsAt: null, not be published as a fact or take the whole read down with it.

Evidence (verified this run, main 5a34fa7)

Laptop probe driving the real extractQuotaPools (Bun 1.4.2; laptop quota-extract.ts/derive.ts sha256-identical to main 5a34fa7; probe: ~/git/fleet/probe-2026-10-29-laneB-claudereset/probe.ts on the laptop), minimal Claude + Meta sections, now = 2026-10-25T15:00:00Z:

reset read Claude window Meta result
control 2026-10-28 14:00 / 2026-10-25 19:00 68%, resetsAt 2026-10-28T14:00:00-05:00 ✅ 46%, resetsAt 2026-10-25T19:00:00-05:00 ✅
month-13 (2026-13-…) 68%, resetsAt 2026-13-28T14:00:00-05:00 verbatim, Date.parse → NaN ❌ 46%, resetsAt 2026-13-25T19:00:00-05:00 verbatim, Date.parse → NaN ❌
Feb-30 (2026-02-30 …) 68%, resetsAt 2026-02-30T14:00:00-05:00; Date.parse → 2026-03-02 (phantom rollover) ❌ windows [] — whole 46% read discarded as "reset already passed" (rolled-over Mar 2 < now) ❌
time 99:99 68%, resetsAt 2026-10-28T99:99:00-05:00 verbatim, Date.parse → NaN ❌ 46%, resetsAt verbatim, Date.parse → NaN ❌
day-39 (2026-10-39 …) 68%, resetsAt 2026-10-39T14:00:00-05:00 verbatim, Date.parse → NaN ❌ 46%, resetsAt verbatim, Date.parse → NaN ❌

File:line (main 5a34fa7):

  • pm-stack/aggregator/src/quota-extract.ts:43-45 — dateTimeCDT: pure interpolation, no validation of the date or time components.
  • pm-stack/aggregator/src/quota-extract.ts:98-100 — extractClaude: shape-only reset capture, result becomes the window's resetsAt unchecked.
  • pm-stack/aggregator/src/quota-extract.ts:132-149 — extractMeta: same capture → resetIso; the now >= Date.parse(resetIso) reset-passed branch (:135), the burned path's wallLiftsAt: resetIso, and the normal path's resetsAt all consume the unvalidated string.
  • Downstream: pm-stack/aggregator/src/render.ts:92-95 — fmtDate prints an unparseable resetsAt verbatim; pm-stack/aggregator/src/quota-guidance.ts:18 — Date.parse + finite filter silently drops NaN resets; pm-stack/aggregator/src/derive.ts:820 — attention-flag detail interpolates resetsAt.slice(0, 10).

Why it matters

The quota view is a human-written document, and these two pools' resets are typed by hand — a typo'd month, day, or time is exactly the input the fail-soft design exists to absorb. Today one bad digit either publishes an impossible reset date as a measured fact (which the stewardship rotation then reads), or — the Feb-30 Meta case — makes a healthy, current read vanish with a plausible-looking "no post-reset read yet" note, so nothing signals that anything went wrong.

Acceptance criteria

  • Triage pins the rule for the dateTimeCDT call sites in extractClaude/extractMeta (expected: validate the captured date as a real calendar date and the time as a real clock time — e.g. the isCalendarDate discipline already used in derive.ts — and treat an invalid reset as resetsAt: null for that window, keeping the matched percentage windows; Meta's reset-passed and burned/wallLiftsAt branches must not consume an invalid reset string).
  • Regression tests in pm-stack/aggregator/test/quota.test.ts: month-13, Feb-30, day-39, and 99:99 resets keep the Claude 68% and Meta 46% windows with resetsAt: null; the Feb-30 Meta read is NOT discarded as already-reset; the existing control cases (Claude 2026-10-28T14:00:00-05:00, Meta walled-until-reset) are unchanged.
  • No change to extractQuotaPools' per-pool try/catch semantics for genuinely unmatched sections (shell note still renders).

Filed by new-issue-creator-code-b (lane B), run 2026-10-29 18:50 CDT. Dedup: #2197 is dateTimeCDT's fixed UTC-5 offset for valid dates (an hour-early error, not validation); #2383 is extractClinePass's Measured anchor through the same helper (its fix validates the ClinePass base/rel path only, and its failure mode is throw-and-drop); #2196 is monthDayTime (Codex / OpenCode Go / Command Code). No open or closed issue covers validation at the Claude/Meta reset call sites. NOT self-solved: where validation lives (helper vs call sites) and its interplay with #2197's pending offset fix are triage's pin.

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 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  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 Nov 1, 2026
  5. 38 remaining items

  6. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 2, 2026
  7. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked — shared-file coordination blocker

  8. added
    queue/claimedQueue worker lifecycle state
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Nov 2, 2026
  9. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  10. dezren39 commented on Nov 2, 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)app/pm-stackpm-stack (Pad/Grist) in developing-today/codeeffort/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/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions