Skip to content

aggregator(feed): isQuotaPool never checks credential-row label/freshness or activeSessions — a windows-only credential row or a null session crashes renderQuota #3921

Open

Description

@dezren39

Finding

isQuotaPool (pm-stack/aggregator/src/feed.ts:321-334) — the gate whose doc comment promises "One quota pool whose own shape is safe to hand to the renderers (#2429)" — validates a pool's credential rows only as records holding a windows array, and never checks activeSessions at all. sanitizeQuotaView (feed.ts:392-408) filters credential-row windows and pool windows, but passes credential label/freshness and every activeSessions entry through untouched. renderQuota then dereferences both unguarded, so one malformed row or session entry that no producer emits crashes the whole /quota render instead of dropping at the boundary like its sibling fields do (#2429's drop-and-count pattern).

The QuotaCredentialRow contract (model.ts:269-282) requires label: string, providerId: string, and a required freshness: PoolFreshness (measuredAt/source/state). The only producer, credentialRows (quota-current.ts:195-214), always emits all three. activeSessions (model.ts:256) is { session: string; model: string; tokens: number }[]; the collector (scripts/collect-fleet-feed.ts:580-587) always emits that shape.

Evidence (main 33aefa7, verified this run)

  • feed.ts:326-331 — credentials check is only isRecord(row) && Array.isArray(row.windows); no label, providerId, or freshness check. activeSessions is never mentioned in the gate.
  • feed.ts:399-406 — sanitizeQuotaView maps credential rows replacing only windows; a row's missing/null freshness survives sanitization.
  • render.ts:2045-2048 — c.freshness.state === "ok", esc(c.freshness.state), c.freshness.error dereferenced unguarded on every credential row.
  • render.ts:2035-2040 — (p.activeSessions ?? []).map((s) => … s.tokens.toLocaleString()): a non-array value, a null element, or an element without tokens all throw.
  • Laptop Bun probe (~/git/fleet/probe-2026-11-01-laneB-credrows/, real feed.ts/render.ts/model.ts copied from the laptop primary at 33aefa7; sha256 feed.ts d62b3096…, render.ts 061fc8ad…, model.ts f987be93… — identical to sandbox main; control pool with a well-formed credential row + active session renders OK, 25,787 bytes):
    • credentials: [{ windows: [] }] (no label/providerId/freshness) → gate true, droppedPools=0, renderQuota throws TypeError: undefined is not an object (evaluating 'c.freshness.state')
    • credentials: [{ …, freshness: null }] → gate true, throws TypeError: null is not an object (evaluating 'c.freshness.state')
    • credentials: [{ …, freshness: "ok" }] (string) → gate true, renders — the row's state silently renders blank (falsified, no crash)
    • activeSessions: [null] → gate true, throws TypeError: null is not an object (evaluating 's.session')
    • activeSessions: "s1" → gate true, throws TypeError: (p.activeSessions ?? []).map is not a function
    • activeSessions: [{ session: "s1", model: "gpt" }] (no tokens) → gate true, throws TypeError: undefined is not an object (evaluating 's.tokens.toLocaleString')
    • activeSessions: [{ …, tokens: "5000" }] → gate true, renders the string verbatim (no crash; type falsified)

Why it matters

/quota is the fleet's quota page; its feed is a hand-/script-produced JSON document, exactly the input the #2429 gate-and-sanitize boundary exists to fail soft against. Every sibling surface in the same pool — windows, burn segments, charges, subscriptions — drops malformed elements individually and counts the drops. Credential rows and active sessions are the two remaining unguarded dereferences in the same renderer: one bad row takes the whole page down (and the unsanitized shape is also served verbatim by /api/quota.json) instead of losing one row.

Acceptance criteria

  • A pool whose credential rows lack a string label/providerId or a record freshness with a valid state, or whose activeSessions is not an array of { session: string, model: string, tokens: finite number }, no longer crashes renderQuota; the owning layer is pinned (extend isQuotaPool vs drop-and-count in sanitizeQuotaView, following the aggregator(feed): isFleetFeed checks only that quota.pools is an array — burn/choices, pool entries and windows unvalidated, one malformed pool crashes /quota #2429 pattern) and applied consistently to both surfaces, with drops counted/surfaced where the sanitizer owns them.
  • A well-formed pool with credential rows and active sessions (existing fixtures/tests) renders unchanged.
  • Regression tests cover at least: credential row without freshness, freshness: null, activeSessions: [null], a non-array activeSessions, and a session entry without tokens.

Dedup: #2429 (closed) built the pool/window gate and sanitizer — its credential check stopped at record+windows and it never touched activeSessions. #3427/#3428 cover quota window/burn fields, #3513/#3458 subscriptions/charges, #3897/#2417 the orchestration section — different fields in different gates. Searches for "isQuotaPool" and "activeSessions" across open and closed issues return no covering issue.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/triageQueue packet kind: verify, reproduce, and route this item
    source/creatorFiled by the new-issue-creator job
    priority/lowNice to have; do when convenient
    queue/queuedHas a queued task in the Supabase agent queue
    effort/sSmall: expected under 2 hours of work
    app/aggregatorPackage/app: aggregator (pm-stack in this repo)
    on Nov 1, 2026
  2. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  3. 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 2, 2026
  4. 38 remaining items

  5. added
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 4, 2026
  6. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  7. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

  8. added
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    on Nov 4, 2026
  9. developing-today commented on Nov 8, 2026

    @developing-today
    Contributor

    state: handed_off
    queue: repo-health-code-pilot
    task: 12132 (triage)
    by: lane code-k (worker repo-worker-code)

    Picked up and revalidated fresh: issue #3921 is OPEN, no hard-stop labels (held, user/owned, do-not-merge, do-not-auto-edit absent). Execution-lane assignment returned opencode — handed off to the laptop opencode claimant, which launches the bound session that drives task 12132 to its terminal state. This lane does not work it in parallel.

  10. dezren39 commented on Nov 8, 2026

    @dezren39
    MemberAuthor

    Partial verification — staying open (2026-11-07 done-task-only audit, tracking: #7016).

    Queue task(s) [5876, 6237, 8295] were marked done, and some work did land:

    • Direct commits referencing this issue: 02a3b95

    No merged change closes this issue's full scope, so the done status is not treated as completion. Remainder re-queued as triage task 11683.

  11. dezren39 commented on Nov 8, 2026

    @dezren39
    MemberAuthor

    Handed off to opencode claimant: task 12132 claimed by lane code-e, execution lane assigned opencode. The claimant launches the bound opencode session for issue #3921; this lane does not work it directly.

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 convenientqueue/claimedQueue worker lifecycle statesource/creatorFiled by the new-issue-creator jobstatus/in-progressQueue worker lifecycle statetask/small-fixQueue packet kind: bounded fix with reproduction and targeted testswork/assignedQueue worker lifecycle state

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions