Repository navigation
Description
Problem
isFleetFeed (pm-stack/aggregator/src/feed.ts:207-219) never validates the
optional charges section at all — it checks only version, generatedAt,
quota.pools being an array, and attention being an array. But the live
source serves feed.charges verbatim as the usage view's charge rows, and
the /quota renderer dereferences every row unguarded, so one malformed
row in a feed that passes the gate crashes the whole Usage & billing page.
Evidence (verified this run, main 5a34fa70)
feed.ts:47-48:FleetFeed.charges?: ChargeRow[], whereChargeRow
(model.ts:1169-1178) requiresat: string,label: string,
amountUsd: number,method: string,source: string.feed.ts:207-219:isFleetFeedcontains nochargescheck 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:
charges: feed.charges ?? []— no filter or per-entry guard, unlike the
registry / projects / snapshots consumers in the same file, which
validate or filter first.render.ts:1734-1746(chargesStripHtml, called fromrenderQuota):
rows are mapped withc.amountUsd.toFixed(2)and totalled with
usage.charges.reduce((sum, c) => sum + c.amountUsd, 0)— a null row,
a missingamountUsd, or a stringamountUsdall throw; a non-array
stringchargesthrows on.map.server.ts:486-487:/quotaawaitssource.usage()and calls
renderQuota(...)with no catch, so the throw becomes a 500 for the
entire usage hub page — not just a missing charges strip.
/api/quota.json(server.ts:656-665) does not crash, but serializes
the malformed rows verbatim to API consumers. A non-array object
chargesis silently swallowed instead (lengthis undefined, so the
strip renders "No charge evidence") — real charge rows vanish with no
signal.
Laptop Bun probe (~/git/fleet/probe-2026-10-29-laneA-feedcharges/, real
feed.ts + render.ts from main 5a34fa70, md5-identical, Bun 1.4.2;
usage view assembled exactly as live.ts:811-815 assembles it):
charges |
isFleetFeed |
renderQuota (the real /quota render) |
|---|---|---|
| valid entry (control) | true |
OK — renders, $12.50 row present |
[null] |
true |
throws TypeError: null is not an object (evaluating 'c.at') |
entry missing amountUsd |
true |
throws TypeError: undefined is not an object (evaluating 'c.amountUsd.toFixed') |
amountUsd: "12.50" (string) |
true |
throws TypeError: c.amountUsd.toFixed is not a function |
"oops" (non-array string) |
true |
throws TypeError: usage.charges.map is not a function |
{amountUsd: 1} (non-array object) |
true |
no throw — charges strip silently renders "No charge evidence" |
[valid, null] mixed |
true |
throws — the valid row never renders |
Why it matters
charges is the hub's charge-evidence strip (§2.4): the collector emits
these rows from Plaid/Gmail receipt records, so a single drifted or
hand-edited feed row takes down /quota entirely instead of dropping
one row, and the JSON twin silently serves the garbage onward. This is
the same failure class as #2427 (attention entries), #2429 (quota pools)
and #2439 (chargesAttention) at the same gate — the fix should stay
consistent with whatever layer those pin. Note this issue is the
charges field specifically: #2439 covers the separate
chargesAttention field and its usage() flag map.
Acceptance criteria
- A feed passing
isFleetFeedcan no longer crash/quota: either the
gate validatescharges(array of entries with string
at/label/method/sourceand finite numberamountUsd), or the
live source filters/defaults malformed entries 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
rulings. - Malformed entries are dropped individually; well-formed entries in the
same array still render, and the strip total sums only valid rows. - A non-array
chargesdegrades the same way (rows dropped, page
renders), never throws and never silently masquerades as "no charge
evidence" when valid rows were present. - Regression tests cover:
[null]entry, entry missingamountUsd,
stringamountUsd, non-arraycharges(string and object) — 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 charges isFleetFeed and feed charges validator returned no covering issue. Siblings at the same
gate: #2427 (attention), #2429 (quota pools), #2439 (chargesAttention —
distinct field, distinct consumer in usage()). #2130/#2194 cover
charges-match/charges-collect internals, not the feed gate, the live
usage() pass-through, or the /quota render.
Activity
developing-today commentedon Oct 29, 2026
CODE-1248
dezren39 commentedon Oct 29, 2026
🤖 Agent note · filed
fix(aggregator): surface chargesDropped in usage provenance on all re…
fix(aggregator): sanitize feed charges at loadFeed — isChargeRow drop…
Merge pull request #3437 from developing-today/queue/5329-2445-charge…
17 remaining items
dezren39 commentedon Nov 1, 2026
🤖 Agent note · picked up
fix(aggregator): restore closing brace of isUnmatchedCharge dropped i…
feat(aggregator): alert throat — sentinel intake, Supabase ledger, We…
Merge pull request #3679 from developing-today/fix/feed-ts-missing-brace