Skip to content

meta-exporter: parseSubscriptionQuota's num() reinterprets empty, hex and scientific strings as real measurements — a drifted '' used value exports as a measured 0% window #2789

Closed

Description

@dezren39

meta-exporter: parseSubscriptionQuota's num() reinterprets empty, hex and scientific strings as real measurements — a drifted '' used value exports as a measured 0% window

Evidence (verified this run, main da42bfe7)

pm-stack/meta-exporter/meta-quota-export.mjs, parseSubscriptionQuota (line 69):

const num = (v) => (typeof v === "string" || typeof v === "number" ? Number(v) : NaN);

The console API legitimately sends decimal strings (the captured-payload test at meta-quota-export.test.mjs:19-21 uses "100000000000" / "1023570000"), so accepting strings is intentional. But bare Number() accepts far more than decimal spellings, and the parser's own contract (header, lines 66-68) is "Throws on any shape drift — fail-closed, never partial zeros":

Probe (laptop, importing the real module — file md5 2f76d55332752bd1c43a86505f289007 on both clones; valid control payload, one field mutated per case; probe ~/git/fleet/probe-2026-10-30-laneB-metaempty/):

input result
control (numeric fields) status-ok windows: 5h usedPct 10, weekly usedPct 20
window_weighted_used = "" OK — 5h usedPct exported as 0 (a fabricated measured-zero window)
weekly_weighted_used = " " (whitespace) OK — weekly usedPct exported as 0
window_weighted_used = "0x10" OK — usedPct 16 (hex reinterpreted)
window_weighted_used = "1e2" OK — usedPct 100 (scientific reinterpreted)
window_resets_at = "" with used = 10 OK — resetAt 1970-01-01T00:00:00.000Z: "" is neither null nor undefined, so it bypasses resetField's absent-reset guard (lines 92-100), then Number("") === 0 passes the finite/≥0 check
as_of = "" OK — consoleAsOf 1970-01-01T00:00:00.000Z instead of null
controls: window_weighted_used = "abc" / null correctly throws unparseable (exit 6)

The existing malformed-reset test (meta-quota-export.test.mjs:157-163) pins "soon", -5, true, {} as fail-closed — "" slips past exactly the guard that test exists to enforce. Only the limit/duration fields are incidentally saved, by their separate zero checks (window_limit = "" → "zero limit"; duration "" → ≤ 0): the used fields have no such backstop, so they are where the fake reading lands.

Why it matters

This exporter's whole design is that "a failed tick can never be mistaken for a reading" (file header). An empty-string used value — the classic shape-drift / missing-value spelling from an upstream API — is currently exported in a status: "ok" document as a measured 0% window, and the quota gatherer ingests it as fact: real usage disappears from the burn/reset display precisely when the console API starts drifting. Hex/scientific spellings are silently reinterpreted into different numbers instead of failing closed.

Acceptance criteria

  • parseSubscriptionQuota accepts only plain decimal spellings (the API's actual format, per the captured payload) for its numeric fields: empty, whitespace-only, hex (0x…), scientific (1e…), and otherwise non-decimal strings throw ExportError("unparseable", 6, …), for the used/limit/duration fields and inside resetField / as_of alike.
  • window_resets_at = "" on a window with recorded usage fails closed like the absent-reset case; as_of = "" yields consoleAsOf: null or unparseable, never the 1970 epoch.
  • New tests in meta-quota-export.test.mjs cover: "" and " " used values rejected; "0x10" / "1e2" used values rejected; "" reset with usage rejected; the captured-payload (decimal-string) test and all existing tests still pass (node --test pm-stack/meta-exporter/).

Dedup

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/lowNice to have; do when convenient
    queue/queuedHas a queued task in the Supabase agent queue
    effort/xsTrivial: expected under 30 minutes of work
    app/pm-stackpm-stack (Pad/Grist) in developing-today/code
    on Oct 30, 2026
  2. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  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 Oct 30, 2026
  4. 1 remaining item

  5. added
    queue/queuedHas a queued task in the Supabase agent queue
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 30, 2026
  6. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    agent-note:v1 — picked up and implemented, queue task 4025 (repo-worker-code, openchamber-scheduled-lane, 2026-10-30).

    Fix on branch queue/4025-2789-meta-decimal, PR #2864. Verdict: bug confirmed live on origin/main exactly as triaged. Awaiting CI.

  7. added a commit that references this issue on Oct 30, 2026
  8. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    agent-note:v1 — done, queue task 4025 (repo-worker-code, openchamber-scheduled-lane, 2026-10-30).

    PR #2864 merged as d142e40 (merge state CLEAN; 16/16 node --test pass locally, no repo workflow covers this path). Issue auto-closed. In-flight labels removed.

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/lowNice to have; do when convenientsource/creatorFiled by the new-issue-creator jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions