Skip to content

aggregator(feed): isQuotaWindow accepts any string as resetsAt and any finite used/limit/percent — an impossible reset date is clocked as Mar 2 in guidance, a -50% window reads healthy and a 15000% window walls a pool #3427

Closed

Description

@dezren39

Problem

isQuotaWindow — the feed boundary guard behind sanitizeQuotaView / loadFeed (#2429) in pm-stack/aggregator/src/feed.ts:286-296 — checks only types, never values:

  • resetsAt passes as any string (feed.ts:293: v.resetsAt === null || typeof v.resetsAt === "string")
  • limit passes as any finite number, including negative (feed.ts:291)
  • used passes as any finite number, including negative (feed.ts:292)
  • percent passes as any finite number, including negative or ≫100 (feed.ts:294)

The sister boundary for the same fields already refuses exactly these shapes: isSnapshotRecord (src/snapshots.ts:313-316) requires isNonNegative(limit) and isIsoDate(resetsAt), with the comment that downstream code dereferences them (pct-of-limit, reset comparisons). The feed gate does not, and the shared strict primitive (isIsoDate / parseIsoDate, src/validate.ts) is already imported by feed.ts for other sections.

Evidence (verified this run, main 574c04a)

Laptop probe ~/git/fleet/probe-2026-11-01-laneA-feedquota/ (copies of the real src/ tree, feed.ts md5 e564f17aea918d5281fd37d433a01017, identical to main; run with bun 1.4.2 against the real isQuotaWindow, windowPercent, poolState, computeChoices, sanitizeQuotaView). Control: a well-formed window passes. Every malformed case below also passes (isQuotaWindow=true) and survives sanitizeQuotaView with droppedWindows=0:

case result
resetsAt: "2026-02-30T05:00:00Z" kept; Date.parse rolls it to 2026-03-02T05:00:00Z
resetsAt: "banana" / resetsAt: "" kept verbatim
percent: -50, limit: null windowPercent = -50, pool reads healthy
percent: 15000, limit: null windowPercent = 15000, pool reads walled
used: -500, limit: 1000 windowPercent = -50, pool reads healthy
limit: -5, no percent windowPercent = 100 (the limit <= 0 fallback in derive.ts:156), pool reads walled with zero usage

Guidance effect, same probe: a healthy pool whose only window carries resetsAt: "2026-02-30T05:00:00Z" is returned in computeChoices(...)'s burnBeforeReset list at 2026-03-01T12:00Z — a burn-it-before-reset recommendation clocked from a day that never existed (the Mar 2 rollover is 17h out, inside RESET_SOON_MS). quota-guidance.ts:18 (soonestReset) filters only non-finite Date.parse results, so impossible-but-parseable dates sail through, while derive.ts:839 prints resetsAt.slice(0, 10) verbatim in attention flags and render.ts fmtDate renders the rolled-over March date as the reset date.

Why it matters

The ingest contract (src/validate.ts header) is that every ingested document is validated once at its boundary — shape, element types, value ranges, and real calendar dates — before any consumer sees it. The quota section is the one section whose gate stops at types: a corrupt or buggy collector write (or a hand-edited feed) injects windows that flip pools between healthy and walled and fabricate reset timing in the guidance every session-placement decision reads, instead of being dropped-and-counted by the sanitizer like every other malformed element in the same view.

Acceptance criteria

Dedup

Distinct from #2470 (quota-extract emitting unvalidated resetsAt upstream — this is the feed-ingest gate that must refuse them regardless of source), #3365 (snapshot rollup guard re-admitting shapes), #2416 (parseQuotaCurrent window entries in the gatherer overlay), and #3396/#3393/#3379 (tier-items / actions / snapshot-records date gates). Searched open + closed for isQuotaWindow, "quota window resetsAt" before filing.

Activity

  1. developing-today commented on Nov 1, 2026

    @developing-today
    Contributor
  2. added
    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
    priority/lowNice to have; do when convenient
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    app/aggregatorPackage/app: aggregator (pm-stack in this repo)
    effort/sSmall: expected under 2 hours of work
    on Nov 1, 2026
  3. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator (new-issue-creator-code-a)
    • Session / ID: cron new-issue-creator-code-a, run 2026-11-01 02:35 CST
    • Found via: filed this run
    • Task: Triage the isQuotaWindow value-range/date gap in the feed quota gate and route the fix per the issue's acceptance criteria
    • Expectation: effort/s — done looks like the malformed windows in the evidence table dropping in sanitizeQuotaView with drop counts, plus regression tests
    • Links: laptop probe ~/git/fleet/probe-2026-11-01-laneA-feedquota/ (real feed.ts at main 574c04a)
  4. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    Cross-link: sibling feed-gate gap filed in the same run — #3428 (isBurnSegment accepts negative tokens, non-date from/to and inverted ranges).

  5. added
    app/pm-stackpm-stack (Pad/Grist) in developing-today/code
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  6. 7 remaining items

  7. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    Progress checkpoint (queue task 6079): tightened isQuotaWindow to require real ISO reset dates, non-negative finite limit/used, and percent in 0–100. Added regression coverage for Feb-30, negative usage/limit, -50 and 15000 percent, plus individual drop counts and valid-window preservation in sanitizeQuotaView. Running targeted aggregator feed tests now; no PR yet.

  8. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    Queue task 6079 done: implemented the guard fix, tests pass, PR opened.

    • Changed pm-stack/aggregator/src/feed.ts: isQuotaWindow now requires non-null resetsAt to satisfy isIsoDate, non-null limit/used to be non-negative finite, and percent (when present) within 0..100. Malformed windows drop individually in sanitizeQuotaView with droppedWindows counting them.
    • Added regression tests in pm-stack/aggregator/test/feed.test.ts: Feb-30 resetsAt dropped, negative used dropped, negative limit dropped, percent -50 dropped, percent 15000 dropped, valid windows untouched + droppedWindows count.
    • Test results: bun test test/feed.test.ts — 54 pass, 0 fail (run on laptop).
    • PR: fix(quota): guard isQuotaWindow against impossible resetsAt, negative values, out-of-range percent #4423 (not merged, per task instructions).
  9. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Conductor binding · manual dispatch wave (Drew-ordered queue unblock) 2026-11-02

  10. added
    work/assignedQueue worker lifecycle state
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 2, 2026
  11. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  12. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done — PR merged

  13. removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    work/assignedQueue worker lifecycle state
    on Nov 2, 2026
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/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/lowNice to have; do when convenientsource/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