Repository navigation
Description
aggregator: isItemHistoryCutoff accepts impossible/non-ISO cutoffAt (and any string as coverage.fetchedAt)
Evidence
pm-stack/aggregator/src/item-history.ts:144 — the structural gate for stored item-history cutoff lines:
if (typeof c.cutoffAt !== "string" || Number.isNaN(Date.parse(c.cutoffAt))) return false;Date.parse is lenient: impossible calendar dates roll over (2026-02-30 → Mar 2) and non-ISO strings parse in a timezone-dependent way. The sibling gates in this codebase use the real-calendar rule instead — isIsoDate (pm-stack/aggregator/src/validate.ts:116, via parseIsoDate), e.g. snapshots.ts:328 ("Real calendar dates only (#2264/#2483): Date.parse rolls…") and the gates fixed in #5091/#5140/#5154.
coverage.fetchedAt is weaker still — item-history.ts:147 checks only typeof coverage.fetchedAt === "string", with no parse at all.
Verified with a laptop bun probe at main 298f35f75b4a62e71666810f9a972138a088fb55 (probe /tmp/probe-ih.ts on the laptop, importing isItemHistoryCutoff / blockerFirstObserved from the primary tree):
isItemHistoryCutoffreturnstrueforcutoffAt=2026-02-30T00:00:00Z,2026-04-31T00:00:00Z,"March 2, 2026", and"03/02/2026"; control"not-a-date"→false, valid ISO →true.coverage.fetchedAt="not-a-date"and"2026-02-30T00:00:00Z"both pass the gate.- Downstream effect:
blockerFirstObservedover cutoffs2026-02-30→2026-03-10reportsfirstObservedAt: "2026-02-30T00:00:00Z"verbatim withlowerBoundAgeDays: 8— the age is computed from the rolled-over Mar 2 instant, for a date that does not exist.
Why it matters
ItemHistoryStore.readAll() (item-history.ts:198-215) trusts this gate for every line of item-history.jsonl, then sorts by cutoffAt.localeCompare (a non-ISO stamp like "March 2, 2026" sorts lexicographically, not chronologically) and feeds weeklyTransitions, weeklyCompletions, and blockerFirstObserved, whose ages are Date.parse differences (line 321). One corrupt or hand-edited cutoff line with an impossible/non-ISO stamp is therefore silently accepted and shifts blocker-age and weekly-flow numbers instead of being quarantined like other invalid stored records in this codebase.
Acceptance criteria
isItemHistoryCutoffrejects acutoffAtthat is not a real ISO calendar date/time (gate withisIsoDatefromvalidate.ts, consistent with the sibling gates), including2026-02-30,2026-04-31,"March 2, 2026", and"03/02/2026".coverage.fetchedAtis validated to the same standard (not typeof-only).- Valid ISO cutoffs captured by
captureItemHistorystill pass the gate (existing round-trip test attest/item-history.test.ts:88stays green). - New tests in
pm-stack/aggregator/test/item-history.test.tscover the refused stamps above for both fields plus the valid control. - Aggregator test suite and typecheck pass.
Activity
dezren39 commentedon Nov 3, 2026
🤖 Agent note · filed
dezren39 commentedon Nov 3, 2026
🤖 Agent note · picked up
isItemHistoryCutoff'scutoffAtandcoverage.fetchedAtwithisIsoDateinpm-stack/aggregator/src/item-history.ts, plus the refused-stamp tests intest/item-history.test.tsdezren39 commentedon Nov 3, 2026
🤖 Agent note · parked
queue/8855-5172-item-history-isodateMerge pull request #5176 from developing-today/queue/8855-5172-item-h…
dezren39 commentedon Nov 3, 2026
🤖 Agent note · done — fixed and merged
00d7ef5d1f05b5fb73164384585ddc04e01d81ad.isItemHistoryCutoffnow gates bothcutoffAtandcoverage.fetchedAtwithisIsoDate— verified fresh in origin/mainitem-history.ts(comment cites aggregator: isItemHistoryCutoff accepts impossible/non-ISO cutoffAt (and any string as coverage.fetchedAt) #5172). Codex review completed with no findings; CodeRabbit check terminal (NEUTRAL, review rate-limited). Tests (prior attempt, laptop): item-history 13/13 pass incl. 3 new aggregator: isItemHistoryCutoff accepts impossible/non-ISO cutoffAt (and any string as coverage.fetchedAt) #5172 gate tests; full aggregator suite 1398/1399 with the known path-segment flake passing in isolation; tsc showed only the 2 pre-existing baseline errors.