Skip to content

aggregator(aa): num() reinterprets hex/binary/octal strings as measurements — a drifted '0x10' raw_score renders as intelligence 16 instead of 'not measured' #2985

Closed

Description

@dezren39

Evidence (verified this run, main cffd524f)

pm-stack/aggregator/src/aa.ts, num() (lines 26–37):

// The Supabase Management API serializes numeric columns as JSON
// strings, so every number crosses a coercion gate here; anything
// unparseable becomes null ("not measured"), never 0.

export function num(v: unknown): number | null {
  if (typeof v === "number") return Number.isFinite(v) ? v : null;
  if (typeof v === "string" && v.trim() !== "") {
    const n = Number(v);
    return Number.isFinite(n) ? n : null;
  }
  return null;
}

num() is the single coercion gate for every AA figure the aggregator renders: Test_Runs sheet fields (raw_score → intelligence, cost_per_task_usd, output_speed_tps, ttft_seconds, … at aa.ts:96–102), workbook rankings/thresholds (aa.ts:917–956), and catalog context/output limits (aa.ts:878–879). Its header contract promises that anything unparseable becomes null ("not measured"). But bare Number() accepts radix-prefixed and whitespace-padded spellings that neither Postgres numeric serialization (the Supabase Management API's format) nor a workbook cell produces — so a drifted value is silently reinterpreted into a different measured number instead of reading as not-measured.

Probe (laptop, importing the real module — aa.ts md5 969672a1a6e49152544ceca205a2fedc identical on both clones at main cffd524f; probe ~/git/fleet/probe-2026-10-31-laneA-aanum/, bun 1.4.2):

input num() result
"0x10" 16 (hex reinterpreted as a measurement)
"0b101" 5 (binary reinterpreted)
"0o17" 15 (octal reinterpreted)
"1e2" 100 (scientific reinterpreted)
" 12 " 12 (whitespace-padded accepted)
controls: "12", "12.5" 12, 12.5 (correct)
controls: "", " ", "abc", "12abc", "Infinity", "1,200", 42, NaN, null null / null / null / null / null / null / 42 / null / null (correct)

No test in aggregator/test/aa.test.ts exercises num() directly.

Why it matters

This is the same reinterpretation class already filed and fixed for sibling gates — meta-exporter's num() (#2789) and cloudflare-export's num() (#2791) — but the aggregator's AA gate was never covered. The AA section feeds model rankings, cost-per-task and speed figures that the dashboard presents as measured data. A shape-drifted upstream string (the exact failure this gate exists to absorb, per its own header) currently publishes as a plausible wrong measurement — e.g. a drifted raw_score of "0x10" renders as intelligence 16 — instead of the section's honest "not measured" null.

Acceptance criteria

  • num() returns null for radix-prefixed string spellings (0x…, 0b…, 0o…, any case) at every call site — sheet fields, rankings/thresholds, and catalog limits.
  • Whitespace-padded numeric strings (" 12 ") return null, or triage explicitly pins padded acceptance with a test; plain decimal spellings ("12", "12.5", "-3") and finite numeric inputs keep working exactly as today.
  • Scientific spellings ("1e2"): triage pins whether any AA source legitimately serializes a float column in e-notation as a string; if none does, they also return null. The pin is recorded in the num() header comment.
  • New unit tests in aggregator/test/aa.test.ts cover num() directly for the cases above (today it has none).
  • Existing aggregator tests pass (bun test in pm-stack/aggregator).

Dedup: searched open + closed for aa/ainventory num() coercion — no hit. #2789 (meta-exporter) and #2791 (cloudflare-export) are different modules with their own gates; #2835 is the env-config number contract, not data-ingest coercion.

Activity

  1. added
    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
    priority/lowNice to have; do when convenient
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    app/aggregatorPackage/app: aggregator (pm-stack in this repo)
    effort/xsTrivial: expected under 30 minutes of work
    on Oct 31, 2026
  2. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  3. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    🤖 Design-architect pass 25 (2026-10-31) — agent note: this issue is ADOPTED as Phase 1 of design #3005 (a measurement is a plain decimal — one number grammar for every measurement string), linked as its sub-issue and labelled source/design-architect. Triage: do NOT re-file, re-scope, or split it; record the phase structure instead. The design pins this issue's open questions: scientific spellings are REFUSED (return null — no named source serializes a measurement in e-notation as a string, and the landed meta-exporter gate via #2789 refuses them), and whitespace-padded spellings are REFUSED (null — padding is drift, per the same precedent). Signed plain decimals (-3) and finite typed numbers keep working exactly as the acceptance requires. Flag genuine blockers on #3005.

  4. 77 remaining items

  5. added
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 2, 2026
  6. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked (awaiting CI)

    • Agent: repo-worker-code (OpenChamber scheduled lane)
    • Task: 5498 · repo-health-code-pilot (parked 20 min)
    • Done this run: Strict-decimal num() + header pins + 6 direct unit tests; branch queue/5498-2985-aa-num-strict-decimal pushed; PR fix(aggregator): strict decimal contract in aa num() (#2985) #4431 opened. Targeted: 82/82 pass (aa, aa-gate-2415, quota). Full suite: only pre-existing/env failures (missing installed web-push, load-flake hook timeouts — quota-contract passes isolated).
    • Resume: Read fix(aggregator): strict decimal contract in aa num() (#2985) #4431 checks fresh; merge under standing criteria (green, diff matches intent) or address failures. Worktree: ~/git/fleet/5498-2985-aa-num.
  7. 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
  8. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  9. added a commit that references this issue on Nov 2, 2026
  10. dezren39 commented on Nov 2, 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/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/lowNice to have; do when convenientsource/creatorFiled by the new-issue-creator jobsource/design-architectFiled by the repo design architect 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