Repository navigation
Description
Problem
isFleetFeed (pm-stack/aggregator/src/feed.ts:207-219) never validates
the optional payg section at all — it checks only version,
generatedAt, quota.pools being an array, and attention being an
array. But the live source serves feed.payg verbatim as the usage
view's PAYG lines, and both the attention-flag builder and the /quota
renderer dereference every line unguarded, so one malformed line in a
feed that passes the gate crashes the whole Usage & billing page —
inside usage() itself, before rendering even starts.
Evidence (verified this run, main 5a34fa70)
feed.ts:44:FleetFeed.payg?: PaygLine[](shape atmodel.ts:1154-1167:
stringid/label/note, numeric-or-nullaccruedUsd/unbilledUsd,
optionalestimate { low, high, period }).feed.ts:207-219:isFleetFeedcontains nopaygcheck of any
kind — not array-ness, not entry shape.sources/live.ts:808-815: inusage()'s feed branch (taken whenever
feed.saasis non-empty), the section is passed through verbatim:
payg: feed.payg ?? []— no filter or per-entry guard.sources/live.ts:832:usage()then calls
buildAttentionFlags(feed.quota, partial)while building its return
value;derive.ts:852-862dereferencesline.unbilledUsd/
line.estimate.low|highper line — a null line throws here, inside
usage(), so every route that awaitsusage()fails.render.ts:1684-1700:paygCardHtmlmaps lines with
l.accruedUsd.toFixed(2)/l.estimate.low.toFixed(2)etc. — a
stringaccruedUsdor a null line throws in the render too.server.ts:486-487(/quota),:492(/quota/service/:id),
:656-665(/api/quota.json): all awaitsource.usage()with no
catch, so the throw becomes a 500 for the whole hub page and both
JSON twins — not just a missing PAYG card.
Laptop Bun probe (~/git/fleet/probe-2026-10-29-laneA-feedpayg/, real
feed.ts + derive.ts + render.ts from main 5a34fa70,
md5-identical, Bun 1.4.2; usage view assembled exactly as
live.ts:811-815 assembles it, flags built exactly as live.ts:832):
payg |
isFleetFeed |
buildAttentionFlags (in usage()) |
renderQuota |
|---|---|---|---|
| valid line (control) | true |
OK | OK — line renders |
[null] |
true |
throws TypeError: null is not an object (evaluating 'line.unbilledUsd') |
throws on l.accruedUsd |
"oops" (non-array string) |
true |
OK (iterates chars, no flags) | throws usage.payg.map is not a function |
{...} (non-array object) |
true |
throws TypeError: {} is not iterable |
OK (empty card — but usage() already threw) |
entry with string accruedUsd |
true |
OK | throws l.accruedUsd.toFixed is not a function |
[valid, null] mixed |
true |
throws on line.unbilledUsd |
throws — the valid line never renders |
Why it matters
payg is the hub's PAYG dollars card (§1.2) — the metered-spill lines
the fleet's spend discipline reads. The collector emits these lines
from the quota records, so a single drifted or hand-edited feed line
takes down /quota, /quota/service/:id and /api/quota.json
entirely instead of dropping one line. This is the same failure class
as #2427 (attention entries), #2429 (quota pools), #2439
(chargesAttention), #2445 (charges) and #2446 (subscriptions) at the
same gate — the fix should stay consistent with whatever layer those
pin. This issue is the payg field specifically, with its own
consumers (buildAttentionFlags + paygCardHtml).
Acceptance criteria
- A feed passing
isFleetFeedcan no longer crashusage()or
/quota: either the gate validatespayg(array of lines with
stringid/label/note, numeric-or-nullaccruedUsd/
unbilledUsd, and a well-formedestimatewhen present), or the
live source filters/defaults malformed lines before the view is
built — the layer pinned consistently with the
aggregator(feed): isFleetFeed never checks attention entries — one null entry crashes /attention + /today, entries without a string sessionId silently collapse into one #2427/aggregator(feed): isFleetFeed checks only that quota.pools is an array — burn/choices, pool entries and windows unvalidated, one malformed pool crashes /quota #2429/aggregator(feed): isFleetFeed never checks chargesAttention — one malformed entry crashes usage() and takes down /quota + the quota JSON twins #2439/aggregator(feed): isFleetFeed never checks charges — one malformed row crashes /quota, a non-array object silently empties the charges strip #2445/aggregator(feed): isFleetFeed never checks subscriptions — one null row crashes /quota #2446 rulings. - Malformed lines are dropped individually; well-formed lines in the
same array still render and still contribute attention flags. - Regression tests cover:
[null]line, non-arraypayg(string and
object), stringaccruedUsd,[valid, null]mixed — each rendering
the valid remainder, never throwing; a well-formed feed unchanged,
and the collector still accepts its own emitted feed.
Dedup
gh searches (open + closed) for payg, isFleetFeed and saas feed
returned no covering issue for the feed payg gate. #1119 is a
contract-test request for the /api/quota.json saas/payg schema —
related, but it does not track this verified crash at the feed gate.
Siblings at the same gate: #2427 (attention), #2429 (quota pools),
#2439 (chargesAttention), #2445 (charges), #2446 (subscriptions) —
each a distinct field and consumer.
Activity
developing-today commentedon Oct 29, 2026
CODE-1251
dezren39 commentedon Oct 29, 2026
🤖 Agent note · filed
30 remaining items
dezren39 commentedon Nov 2, 2026
Lane E resumed task 5338: PR #4195 head 78ffd9a revalidated — MERGEABLE, Codex review on this head completed with no findings, targeted bun tests passed on laptop (feed+charges-feed-gate 49, quota-contract 9). The pm-aggregator test CI run 41392609663 has been stuck QUEUED since 05:12Z (~90 min, infra queue stall, not a failure). Merging per standing merge authority (PRs do not wait for CI when the change itself is verified).
Merge pull request #4195 from developing-today/queue/5338-2450-saniti…
dezren39 commentedon Nov 2, 2026
Done: PR #4195 merged (merge commit 58ea183); issue #2450 closed by the merge. Codex clean on head 78ffd9a; targeted bun tests passed (49 + 9). Note: CI run 41392609663 was stuck QUEUED from 05:12Z and never started — infra queue stall, recorded in the fleet backlog.