Skip to content

supabase-export: billedFromAddons silently zeroes unpriced selected addons (and accepts negative amounts) — a paid addon reports as measured $0 #2492

Closed

Description

@dezren39

Problem

billedFromAddons in pm-stack/supabase-export/supabase-quota-export.mjs:101-113 silently treats a selected addon whose price is missing, null, or a string as $0, and accepts a negative amount verbatim. run() then stamps the result as a measured billing read (supabase-quota-export.mjs:280-285: billedUsd: usd, measured: true), so the aggregator trusts it: pm-stack/aggregator/src/saas-extract.ts:742-743 copies any finite billedUsd onto the Supabase card whenever measured !== false.

That breaks the exporter's own contract in both directions:

  • The module header (lines 115-117 of the sibling pattern / function doc at :99-100) says billed is "Measured $0 (or more) from billing/addons" — an addon the API selected but whose price shape drifted is unknown, not $0. The fleet already fixed the mirror-image bug on the consumer side ("billed amount unknown when supabase billing probe unavailable", feat(usage-hub): v2 Supabase usage collector — Management API scope + muse-dev metrics #1050 follow-up): unknown must not be reported as a measured zero.
  • A negative amount yields a negative billedUsd on a measured: true document, which the consumer accepts (finite check only).

The function is also inconsistent with its own file: it throws ExportError("unparseable", 6, …) when selected_addons is missing (:104-106), and parseDbSize (:91-98) throws on any invalid row — but per-addon price drift is silently zeroed instead of throwing.

Evidence (verified this run, main 7d1b081c)

Laptop probe (~/git/fleet/probe-2026-10-29-laneB-billing/probe.mjs, real module imported from a fresh clone at main):

control: priced addon 25            => billedFromAddons = 25
selected addon, price missing       => billedFromAddons = 0
selected addon, amount is string    => billedFromAddons = 0
selected addon, amount null         => billedFromAddons = 0
selected addon, amount negative -25 => billedFromAddons = -25
one priced + one unpriced           => billedFromAddons = 25  (unpriced silently dropped)
contrast parseDbSize bad bytes      => THROWS unparseable
contrast billedFromAddons({})       => THROWS unparseable (selected_addons missing)

Why it matters

The Supabase card's billed figure is the fleet's paid-trigger signal for the muse-dev project. A paid addon whose price shape changes (or a new addon type with usage pricing and no fixed price.amount) would be reported as a measured $0, hiding real spend behind the strongest trust marker the format has (measured: true). A negative amount would corrupt the figure outright.

Acceptance criteria

  • A selected addon with a missing, null, non-numeric, non-finite, or negative price.amount makes the billing read fail closed in the file's own style (throw ExportError("unparseable", 6, …)), OR the document's billing is emitted as unmeasured/null — triage pins which; it must never be measured: true with a silently-zeroed or negative total.
  • Well-formed payloads are unchanged: [{price:{amount:25}}] → 25, [] → measured 0.
  • Regression tests in supabase-quota-export.test.mjs cover: price missing, amount as string, amount null, amount negative, and a priced+unpriced mix.
  • The consumer behaviour is considered: with the fix, saas-extract.ts must not display a $0/negative measured billed figure for these payloads.

Dedup: #1050 (closed) built this exporter; its follow-up fixed the consumer treating an unavailable probe as $0 — this is the producer-side mirror. #2460/#2461 are the meta-exporter (different producer, different functions). No open or closed issue covers billedFromAddons per-addon price validation.

Activity

  1. developing-today commented on Oct 30, 2026

    @developing-today
    Contributor
  2. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    app/pm-stackpm-stack (Pad/Grist) in developing-today/code
    effort/xsTrivial: expected under 30 minutes of work
    on Oct 30, 2026
  3. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job, lane B (new-issue-creator-code-b)
    • Session / ID: cron new-issue-creator-code-b, run 2026-10-30T01:20Z
    • Found via: filed this run
    • Task: Triage the billedFromAddons per-addon price validation gap (unpriced/string/null amounts silently zeroed, negative accepted) and route a fix packet.
    • Expectation: effort/xs — done when the acceptance criteria on the issue are met (fail-closed or unmeasured billing for drifted prices, regression tests, well-formed payloads unchanged).
    • Links: probe on laptop ~/git/fleet/probe-2026-10-29-laneB-billing/probe.mjs (real module, main 7d1b081)
  4. 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 1, 2026
  5. 18 remaining items

  6. added
    queue/claimedQueue worker lifecycle state
    and removed
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    on Nov 1, 2026
  7. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  8. added a commit that references this issue on Nov 1, 2026
  9. dezren39 commented on Nov 1, 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/pm-stackpm-stack (Pad/Grist) in developing-today/codeeffort/xsTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobstatus:donetask/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