Repository navigation
Description
Bug
pm-stack/aggregator — the fleet feed's gate isFleetFeed checks the quota section even more thinly than the rest: it requires only that quota.pools is an array. It never checks burn or choices (both mandatory in QuotaView, src/model.ts:274-286), never checks pool entries, and never checks a pool's windows. The live source serves feed.quota verbatim, so one malformed pool — or a feed written by an older collector that lacks burn/choices — crashes the /quota page instead of degrading.
Sibling of #2427 (same gate, attention entries); filed separately because the section, consumers, and fix differ.
Evidence (verified this run, main 5a34fa70)
- Gate:
src/feed.ts:207-219— the entire quota check istypeof f.quota === "object" && f.quota !== null && Array.isArray((f.quota as QuotaView).pools).burn,choices, pool shape, and window shape are unchecked. - Verbatim consumer:
src/sources/live.ts:754-785—quota()setsview = feed.quotaand returns it (the gatherer overlay aside); there is no pool/window filter anywhere on this path, unlike the registry/projects/snapshots filters in the same file. - Unguarded dereferences:
src/render.ts:1754view.pools.map(p => p.windows.map(...)),:1776view.burn.map(...),:1785-1787view.choices.hasHeadroom/...;src/derive.ts:813buildAttentionFlagsiteratespool.windowsandwindowPercent(w)readsw.limit(derive.ts:155-158) — called uncaught fromusage()'s feed-saas branch (live.ts:832). The /quota route (server.ts:485-488) and /api/quota.json (server.ts:651) await these with no catch, andfetchHandlerhas no top-level catch.
Laptop probe (~/git/fleet/probe-2026-10-29-laneA-feedquota/, real aggregator src/ from origin/main — feed.ts/render.ts md5-identical to the sandbox clone — Bun 1.4.2, real isFleetFeed + renderQuota + buildAttentionFlags):
== control: well-formed quota
isFleetFeed: true
renderQuota: ok, html bytes = 18339
buildAttentionFlags: ok, flags = 0
== quota missing burn+choices (gate only checks pools)
isFleetFeed: true
renderQuota THROWS: TypeError - undefined is not an object (evaluating 'view.burn.map')
== pools [null]
isFleetFeed: true
renderQuota THROWS: TypeError - null is not an object (evaluating 'p.windows')
buildAttentionFlags THROWS: TypeError - null is not an object (evaluating 'pool.windows')
== pool missing windows
isFleetFeed: true
renderQuota THROWS: TypeError - undefined is not an object (evaluating 'p.windows.map')
buildAttentionFlags THROWS: TypeError
== pool windows [null]
isFleetFeed: true
renderQuota THROWS: TypeError - null is not an object (evaluating 'w.limit')
buildAttentionFlags THROWS: TypeError - null is not an object (evaluating 'w.limit')
== pool windows non-array (string "soon")
isFleetFeed: true
renderQuota THROWS: TypeError - p.windows.map is not a function
buildAttentionFlags: ok, flags = 4 (iterates the string's characters as windows — bogus flags)
Why it matters
/quota is the page the fleet's pool discipline runs off (headroom, burn-before-reset, do-not-start guidance). The feed is written by a separate collector job and the aggregator's stated contract is that a stale or partial feed "keeps rendering with its age stated" — instead, schema drift in one pool (a renamed windows, a null placeholder, an older collector omitting burn/choices) takes the whole page down, and a non-array windows silently fabricates attention flags in the usage view. Every sibling section got a validator or an element filter for exactly this class; the quota section — the feed's original payload — never did.
Acceptance criteria
- A feed passing
isFleetFeedcan no longer crashrenderQuotaorbuildAttentionFlags: either the gate validates the fullQuotaViewshape the renderers dereference (burnandchoicespresent with their arrays, every pool an object with awindowsarray of window objects), or the live source filters/defaults malformed pools and windows the way it filters registry/projects records — triage pins the layer, and a rejected feed must fall back to fixtures like other invalid feeds rather than 500. - Malformed pools/windows are dropped or defaulted individually; the well-formed pools still render on /quota and /api/quota.json.
- No fabricated attention flags: a non-array
windowsvalue can never be iterated as a collection of windows. - Regression tests (aggregator suite) cover: quota missing
burn/choices;pools: [null]; a pool missingwindows;windows: [null];windowsas a string — each rendering the valid remainder or the fixture fallback, never throwing; a fully well-formed feed is unchanged, and the collector (which sharesisFleetFeed) still accepts its own emitted feed.
Dedup
gh searches (open + closed) for "quota pools / pool windows / poolState" return #1170 (per-pool read age display), #1119 (contract tests for the /api/quota.json saas/payg schema), #1114 (disabled providers in quota), and #2416 (quota-current.ts overlay windows — a different file and pipeline stage: the gatherer overlay, not the feed's own quota section). No open or closed issue covers isFleetFeed's quota check or feed pool/window validation. Related: #2427 (attention entries, same gate).
Activity
developing-today commentedon Oct 29, 2026
CODE-1240
dezren39 commentedon Oct 29, 2026
🤖 Agent note · filed
Merge pull request #3070 from developing-today/queue/4006-2578-phase2…