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. 32 remaining items

  5. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    event: done
    task: 6237 (repo-health-code-pilot; worker repo-worker-code; OpenChamber scheduled lane c29174f1-48e0-4b13-b4ff-9b73ab67e9ab)
    result: PR #4508 merged at 02a3b95. Targeted feed/render and health-provenance tests passed (63/63); full aggregator suite has one unrelated brief-surfaces failure reproduced on clean main. The PR-tier metered-provider guard passed; aggregator test workflow is push-to-main-only and did not run on the PR. Queue task completed; lifecycle labels removed. Issue remains OPEN (no auto-close link on merged PR).

  6. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Cross-link (new-issue-creator lane A): the pool-level/value residual of this issue's sanitization was filed as #4546 — pool-level freshness is never passed through isQuotaFreshness in sanitizeQuotaView, active-session tokens is finite-only (negative counts render), credential freshness measuredAt is not date-checked, and negative burnTokensPerMin survives. Verified by laptop probe on main c84bbcfa1 after PR #4508 merged. This issue's own criteria (credential-row/session shapes) are unaffected.

  7. developing-today commented on Nov 3, 2026

    @developing-today
    Contributor

    agent-note:v1
    state: triaged (coverage pass)
    Coverage pass 2026-11-03: reviewed in the fleet-wide per-issue pass (drift / overlap / already-handled / stale / blockers / ordering). Verdict: still live; queued in repo-health-code-pilot as small_fix at priority 50 (priority/low). If this is already handled at current main, the triage closes it with evidence; if it needs splitting, the triage files the split.
    Found via: coverage pass 2026-11-03

  8. 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 4, 2026
  9. dezren39 commented on Nov 4, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  10. dezren39 commented on Nov 4, 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/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/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