Repository navigation
Description
Bug
SnapshotStore.readAll has two doors into the same reading path, and only one of them is hardened. Raw lines must pass isSnapshotRecord (pm-stack/aggregator/src/snapshots.ts:296), which was deliberately tightened (#2264/#2483): timestamps must be real ISO calendar dates via the strict isIsoDate primitive (because Date.parse rolls 2026-02-30 into March), limit must be a non-negative finite number when present, resetsAt must be a real ISO date when present, and pair values must have finite endpoints with low <= high.
Rollup lines take the other door: readAll gates them only on isSnapshotRollup (snapshots.ts:82, used at snapshots.ts:552) and then expandRollup (snapshots.ts:226) emits records straight into the reading path — the expanded records are never passed through isSnapshotRecord. But isSnapshotRollup does not apply the record-side rules:
- Timestamps (
firstTs/lastTs/minTs/maxTs) are checked withNumber.isNaN(Date.parse(f))(snapshots.ts:94-95) — exactly the loose check the record validator was fixed away from.2026-02-30T00:00:00.000Zpasses. limitandresetsAtare not checked at all, even thoughexpandRollupcopies them onto the expandedlastrecord and the record-side comment says these fields "are dereferenced downstream (pct-of-limit, reset comparisons), so a non-finite/negative limit or an impossible resetsAt date must refuse the record here, at the boundary."- Pair values (
valueMin/valueMax/valueLast) accept any{low, high}withtypeof === "number"endpoints (snapshots.ts:97-103): an inverted pair{low: 10, high: 5}passes, while the record side explicitly refuses inverted pairs.
The same loose gate also seeds compaction: existing rollups are re-aggregated through isSnapshotRollup at snapshots.ts:707, so a malformed rollup line is not just read, it is carried forward into the next compaction.
Evidence (verified this run, main 1a68d6a)
Laptop probe ~/git/fleet/probe-2026-11-01-laneA-rollupgate/probe.ts driving the real snapshots.ts (sha256 c6685035… matched on both clones; output in probe.txt in the same directory):
- Control: a valid rollup passes
isSnapshotRollup, expands to 3 records, and all 3 passisSnapshotRecord. lastTs = "2026-02-30T00:00:00.000Z"→isSnapshotRollup=true; one expanded record failsisSnapshotRecord.limit = -5→ passes; the expandedlastrecord (the one carryinglimit) failsisSnapshotRecord.limit = "100"(string) → passes; expandedlastrecord failsisSnapshotRecord.resetsAt = "not-a-date"→ passes; expandedlastrecord failsisSnapshotRecord.valueMin = {low: 10, high: 5}(unitusd-est) → passes; expanded min record failsisSnapshotRecord.
Rollup files are kept indefinitely (13-month raw retention, rollups forever), so a single malformed or hand-edited rollup line poisons the reading path permanently, where a malformed raw line would only be dropped.
Minor, same function: the month check (/^\d{4}-\d{2}$/, snapshots.ts:86) accepts 2026-13 / 2026-00, months no writer produces.
Why it matters
The snapshot store is the usage hub's history: latest-value readers, pct-of-limit computations, and reset markers all consume readAll output trusting that the boundary validators refused impossible data. The record side of that boundary was hardened after real incidents (#2264/#2483); the rollup side silently re-admits the same shapes, and compaction re-seeds them forward, so the corruption is self-perpetuating rather than one-off.
Acceptance criteria
isSnapshotRolluprefuses a rollup whosefirstTs/lastTs/minTs/maxTs(andresetsAtTs/limitTswhen present) are not strict ISO calendar dates (sameisIsoDateprimitive asisSnapshotRecord).isSnapshotRolluprefuses a present non-nulllimitthat is not a non-negative finite number, and a present non-nullresetsAtthat is not a strict ISO date — mirroring the record-side rules.isSnapshotRolluprefuses pair values with non-finite endpoints orlow > high, mirroring the record side.monthrefuses impossible months (00,13+).- Tests pin each refused shape above plus a valid rollup (including a valid pair-valued rollup and one carrying
limit/resetsAt) still passing and expanding to records that all passisSnapshotRecord. - No change to the compaction writer's output format or to
expandRollup's expansion semantics for valid rollups.
Distinct from #2568 (recordsFromProviderUsage used_percent range, writer side) and #3295/#3285 (snapshot script argument handling). Found by comparing the two store-read validators in snapshots.ts.
Activity
dezren39 commentedon Nov 1, 2026
🤖 Agent note · filed
isSnapshotRollup(pm-stack/aggregator/src/snapshots.ts:82) to the record-side boundary rules so malformed rollup lines cannot expand into the reading path unchecked.isSnapshotRecord.dezren39 commentedon Nov 1, 2026
agent-note:v1
event: picked-up
task: 5086 (repo-health-code-pilot, OpenChamber scheduled lane)
state: claimed
verified: #3365 is open; no matching open PR found. Nearby open #3443 concerns invalid rollup counts, a distinct field from #3365 timestamp/limit/resetsAt/pair validation. Packet includes a prior real-code probe; I will recheck the current main state before routing.
next: Verify the reported rollup/record gate mismatch against current main and post triage evidence plus a focused follow-up.
67 remaining items
dezren39 commentedon Nov 2, 2026
🤖 Agent note · parked
pendingwith no test checks yet; CodeRabbit is neutral/skipped.dezren39 commentedon Nov 2, 2026
🤖 Agent note · picked up
dezren39 commentedon Nov 2, 2026
🤖 Agent note · done