Repository navigation
Description
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()returnsnullfor 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 ") returnnull, 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 returnnull. The pin is recorded in thenum()header comment. - New unit tests in
aggregator/test/aa.test.tscovernum()directly for the cases above (today it has none). - Existing aggregator tests pass (
bun testinpm-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
dezren39 commentedon Oct 31, 2026
🤖 Agent note · filed
pm-stack/aggregator/src/aa.ts:30-37num()— the single coercion gate for every AA ranking/cost/speed figure — uses bareNumber(), so radix-prefixed strings are reinterpreted as measurements instead of the header contract'snull("not measured"). Verified on the laptop importing the real module (md5969672a1…identical on both clones, maincffd524f, bun 1.4.2):"0x10"→ 16,"0b101"→ 5,"0o17"→ 15,"1e2"→ 100," 12 "→ 12; controls"12"/"12.5"parse and""/"abc"/"12abc"/"Infinity"/"1,200"correctly return null. No test inaggregator/test/aa.test.tsexercisesnum()directly.num()returns null for0x/0b/0ospellings at all call sites, the whitespace-padded and scientific spellings are pinned by triage (recorded in the header comment), and directnum()unit tests exist. NOT self-solved this run: the scientific-notation pin depends on which AA source columns are float-typed, which is triage's call.dezren39 commentedon Oct 31, 2026
🤖 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 (returnnull— 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.77 remaining items
dezren39 commentedon Nov 2, 2026
🤖 Agent note · parked (awaiting CI)
num()+ header pins + 6 direct unit tests; branchqueue/5498-2985-aa-num-strict-decimalpushed; 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 installedweb-push, load-flake hook timeouts —quota-contractpasses isolated).~/git/fleet/5498-2985-aa-num.dezren39 commentedon Nov 2, 2026
🤖 Agent note · picked up
Merge pull request #4431 from developing-today/queue/5498-2985-aa-num…
dezren39 commentedon Nov 2, 2026
🤖 Agent note · done