Skip to content

aggregator(quota-gather-map): entryFromTurso never range-checks usage — negative counts publish as live ok data and append negative snapshot records #2553

Closed

Description

@dezren39

Problem

entryFromTurso (pm-stack/aggregator/src/quota-gather-map.ts:405) never range-checks the Turso usage counts it maps. Its only numeric gate is the shared helper at quota-gather-map.ts:38:

const num = (v: unknown): number | null => (typeof v === "number" && Number.isFinite(v) ? v : null);

Finite is the whole check — a negative count passes. The five usage fields are read through it (databases :453, storage_bytes :455, rows_read :463, rows_written, bytes_synced :479), converted with gb() (:390) / pct3() (:391), and pushed as windows on an entry whose state becomes "ok" (no error) as long as at least one window exists. The gatherer caller passes the raw API JSON straight in with no intervening validation (pm-stack/aggregator/scripts/gather-quota.ts:301, entryFromTurso(usage, subscription, { measuredAt: tickIso })).

Verified this run (laptop probe, real module, main 02c774a)

Probe: laptop ~/git/fleet/probe-2026-10-29-laneA-tursoneg/probe.ts (Bun, imports the real quota-gather-map.ts; the laptop copy is md5-identical to sandbox main).

Feeding usage = { databases: -3, storage_bytes: -2_000_000_000, rows_read: -1000, rows_written: -5, bytes_synced: -1_000_000_000 } with a normal Starter subscription doc:

  • Entry state is ok, error null.
  • Windows publish impossible values: databases value -3, pct -3, label "-3 / 100"; storage value -2, pct -40, label "-2 GB / 5 GB"; sync value -1, pct -33.333; rows windows carry negative raw values (their pct rounds to -0).
  • recordsFromEntries([entry], …) then appends negative snapshot records for the native-unit series: {metricId: "databases", value: -3, unit: "count"}, {metricId: "storage", value: -2, unit: "gb"}, {metricId: "sync", value: -1, unit: "gb"} — so one bad read does not just render badly, it writes impossible points into the pool's snapshot history.
  • A mixed read (databases: 3, rows_read: -42) is also ok, with the negative metric published alongside the good one — there is no per-metric rejection either.

Control: a normal read (databases: 3) maps to ok with only that window, as the existing tests expect.

Why it matters

Usage counts (databases, bytes stored/synced, rows read/written) cannot be negative. A Turso API shape drift, a partial/incorrect response, or a future endpoint change that returns a signed delta instead of a total would currently be stamped as fresh live measured data, shown on the Turso SaaS card as negative usage / negative percent-of-limit, and persisted as negative snapshot points that later pace/chart/history code has to interpret. Every sibling mapper in this stack that was audited for the same class treats negative amounts as invalid (cf. #2508 saas-extract, #2493 github-billing, #2492 supabase-export) — the Turso mapper is the one that was missed. Whether one negative metric should poison the whole entry (fail-closed error entry, the file-export adapter discipline) or just that metric (omit, like missing metrics) is the triage decision this issue asks for — silently publishing it is the one outcome that is wrong either way.

Acceptance criteria

  • A Turso usage doc carrying a negative value for any of databases, storage_bytes, rows_read, rows_written, or bytes_synced no longer produces an ok entry with a negative window: either the affected metric is omitted or the entry is an error entry (triage pins which, consistent with this module's fail-soft conventions), and the chosen behaviour is documented at the mapper.
  • recordsFromEntries on such an entry appends no negative-value snapshot record.
  • Regression tests in pm-stack/aggregator/test/quota-gather-map.test.ts cover at least: an all-negative usage doc, and a mixed doc with one negative metric among valid ones.
  • Existing behaviour is unchanged for valid, missing (omitted, never zeroed), and non-numeric metrics, and the existing entryFromTurso tests still pass.

Dedup notes

Searched open + closed for entryFromTurso, "turso negative", and quota-gather-map: #2332 is this file's entryFromResult fetchedAt RangeError (a different function and failure), #2416 is parseQuotaCurrent window entries, #1049 is the original Turso collector feature. The negative-amount issues #2492/#2493/#2508 cover the supabase-export, github-billing, and saas-extract mappers respectively — none touches entryFromTurso.

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/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    effort/xsTrivial: expected under 30 minutes of work
    app/aggregatorPackage/app: aggregator (pm-stack in this repo)
    on Oct 30, 2026
  2. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator (new-issue-creator-code-a)
    • Session / ID: new-issue-creator-code-a run 2026-10-30T04:14Z
    • Found via: filed this run
    • Task: Triage the entryFromTurso negative-usage finding: pin omit-metric vs error-entry, then route the fix.
    • Expectation: effort xs; done when a negative Turso usage value can no longer publish as an ok entry / negative snapshot record, with regression tests.
    • Links: probe laptop ~/git/fleet/probe-2026-10-29-laneA-tursoneg/ (real quota-gather-map.ts, main 02c774a)
  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 1, 2026
  4. 51 remaining items

  5. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  6. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  7. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up — OpenChamber scheduled lane

  8. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked — OpenChamber scheduled lane

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

    @dezren39
    MemberAuthor

    Task 5364 resumed by lane A. Prior park premise corrected: PR #4207 is fleet-owned (branch queue/5364-2553-turso-negative-guard, PR body names task 5364) — author metadata alone is not Drew-ownership. Fresh verification at head 7d0abb7: Codex review completed, no findings; laptop bun test test/quota-gather-map.test.ts 35/35 pass. Merging PR #4207 under standing merge authority (CI test check still QUEUED in the known runner backlog).

  11. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    Task 5364 done: PR #4207 squash-merged (082f5ba), #2553 closed. Fresh laptop verification at head 7d0abb7: bun test test/quota-gather-map.test.ts 35/35 pass; Codex review completed with no findings. CI test check was still QUEUED at merge time (known runner backlog); merged under standing merge authority on laptop-verified tests.

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/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 jobtask/fixhas a fix task queued

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions