Repository navigation
Open
Labels
app/pm-stackpm-stack (Pad/Grist) in developing-today/codepm-stack (Pad/Grist) in developing-today/codekind/bugSomething isn't working (prefixed form of the classic bug label)Something isn't working (prefixed form of the classic bug label)priority/lowNice to have; do when convenientNice to have; do when convenientqueue/parkedQueue task is parked (waiting on CI, time, or an event)Queue task is parked (waiting on CI, time, or an event)source/creatorFiled by the new-issue-creator jobFiled by the new-issue-creator jobstatus/parkedDeliberately paused with a recorded resume conditionDeliberately paused with a recorded resume conditiontask/small-fixQueue packet kind: bounded fix with reproduction and targeted testsQueue packet kind: bounded fix with reproduction and targeted testswork/assignedQueue worker lifecycle stateQueue worker lifecycle state
Description
Problem
isSaasService in pm-stack/aggregator/src/feed.ts:697 (the element gate behind sanitizeSaasServices, applied at the loadFeed boundary) accepts any string as a SaaS service's measuredAt:
(typeof v.measuredAt === "string" || v.measuredAt === null) &&That is weaker than every sibling measuredAt gate in the same file:
isQuotaFreshness—feed.ts:548:isIsoDate(v.measuredAt)isPaygLine—feed.ts:969:(v.measuredAt === null || isIsoDate(v.measuredAt))
isIsoDate (validate.ts:116) requires a real ISO calendar date. The SaaS gate does not, so impossible and non-ISO stamps survive sanitization verbatim.
Verified (laptop bun probe at origin/main 12fcad9, probe in ~/git/fleet/probe-2026-11-03-laneA4-scan, removed after the run)
A minimal otherwise-valid service was passed through sanitizeSaasServices, then recordsFromSaasServices + isSnapshotRecord:
| measuredAt | kept by sanitizeSaasServices | derived record ts | isSnapshotRecord |
|---|---|---|---|
2026-02-30 |
yes (dropped=0) | 2026-02-30T12:00:00Z |
false |
2026-04-31 |
yes (dropped=0) | 2026-04-31T12:00:00Z |
false |
March 2, 2026 |
yes (dropped=0) | March 2, 2026 |
false |
not-a-date |
yes (dropped=0) | not-a-dateT12:00:00Z |
false |
2026-03-01 (control) |
yes | 2026-03-01T12:00:00Z |
true |
Why it matters
recordsFromSaasServices(snapshot-records.ts:85, viatsForat:32) derives one snapshot record per measured metric at the service'smeasuredAt. With a bad stamp every derived record failsisSnapshotRecord, so the service's snapshot history is silently dropped at append — the same failure shape as pm-stack(aggregator): recordsFromProviderUsage accepts impossible/non-ISO generated_at — Date.parse-only gate yields records isSnapshotRecord refuses, so the append CLI silently drops the whole provider-usage snapshot #5140, through a different gate.conflicts.ts:133-134feedssvc.measuredAttoageDaysFrom, which uses rawDate.parse:2026-02-30rolls to Mar 2 and reports a concrete conflict age instead ofnull(unknown).render.tsfreshnessStampprintsmeasuredAt.slice(0, 10)verbatim, so2026-02-30/March 2, 2render as the measured date on the service card (freshness itself is strict since pm-stack(aggregator): freshness() ages impossible and non-ISO measuredAt stamps as fresh — Date.parse rolls 2026-02-30 to Mar 2 and accepts 'March 2, 2026' #5102, so the stamp also reads stale with a nonsense date attached).
Distinct from: #5188 (same sanitizer — PAYG/metric range gates, now fixed; it left measuredAt type-only), #5018 (the saas-extract export-overlay documents, a different input path), and #5102 (freshness() consumer).
Acceptance criteria
isSaasServicerequiresmeasuredAtto benullorisIsoDate(...), matchingisPaygLinein the same file; a service with an impossible or non-ISOmeasuredAtis dropped bysanitizeSaasServicesand counted indropped.- Regression tests cover at least
2026-02-30,2026-04-31, and a non-ISO string (March 2, 2026) being dropped, with a valid ISO date and anullstamp kept as controls. - Aggregator test suite and typecheck stay green.
Metadata
Metadata
Assignees
Labels
app/pm-stackpm-stack (Pad/Grist) in developing-today/codepm-stack (Pad/Grist) in developing-today/codekind/bugSomething isn't working (prefixed form of the classic bug label)Something isn't working (prefixed form of the classic bug label)priority/lowNice to have; do when convenientNice to have; do when convenientqueue/parkedQueue task is parked (waiting on CI, time, or an event)Queue task is parked (waiting on CI, time, or an event)source/creatorFiled by the new-issue-creator jobFiled by the new-issue-creator jobstatus/parkedDeliberately paused with a recorded resume conditionDeliberately paused with a recorded resume conditiontask/small-fixQueue packet kind: bounded fix with reproduction and targeted testsQueue packet kind: bounded fix with reproduction and targeted testswork/assignedQueue worker lifecycle stateQueue worker lifecycle state
Activity
dezren39 commentedon Nov 4, 2026
🤖 Agent note · filed
dezren39 commentedon Nov 4, 2026
🤖 Agent note · picked up
dezren39 commentedon Nov 4, 2026
🤖 Agent note · parked
dezren39 commentedon Nov 4, 2026
🤖 Agent note · parked
dezren39 commentedon Nov 4, 2026
🤖 Agent note · parked