Repository navigation
Description
Summary
freshness() in the aggregator's derive module has no upper bound: a future-dated measuredAt is reported as age 0 — "today", not stale — and stays that way until cadenceDays after its future date arrives. A read stamped +30 days in the future with a 7-day cadence is presented as fresh for 37 days; a read stamped +365 days is fresh for a year. Both branches (date-only and datetime) clamp the negative age to 0, so the stale check ageDays > cadenceDays can never fire for a future stamp.
This is the freshness stamp on the hub's SaaS-service and PAYG cards — exactly the surface whose job is to say a dated read is old (§1.1). Garbage and missing stamps are handled correctly (stale: true, ageDays: null); only the future direction is missing.
Evidence (verified this run on main db6f7d00)
pm-stack/aggregator/src/derive.ts:855-868—freshness(measuredAt, cadenceDays, now):- date-only branch (:862):
ageDays = Math.max(0, Math.round((startOfToday - t) / 86_400_000)) - datetime branch (:867):
ageDays = Math.max(0, Math.floor((now.getTime() - t) / 86_400_000)) - both then return
stale: ageDays > cadenceDays— for any futuret, age is clamped to 0 andstaleis false.
- date-only branch (:862):
- Laptop probe (Bun 1.4.2, real
derive.ts, sha256312ac0fbidentical on sandbox and laptop; probe + log in the fleet's probe records for this run), fixednow = 2026-10-27T12:00:00Z, cadence 7:- controls correct:
2026-10-26→{stale:false, ageDays:1};2026-10-19→{stale:true, ageDays:8}; garbage →{stale:true, ageDays:null};null→{stale:true, ageDays:null} 2026-10-28(+1d),2026-11-26(+30d, both date-only and datetime forms), and2027-10-27T12:00:00Z(+365d) all →{stale:false, ageDays:0}- the +30d stamp evaluated forward: still
{stale:false, ageDays:0}at +29d and +30d,ageDays:7(still not stale) at +37d, and only stale at +38d.
- controls correct:
- Render surface:
freshnessStamp(pm-stack/aggregator/src/render.ts:1651-1659) rendersageDays === 0as "· today" and adds the stale badge only whenf.stale; callers are the PAYG card (render.ts:1704viaserviceCardHtml's freshness, and the PAYG line stamps),serviceCardHtml(:1704), and the service detail page (render.ts:1889). - A future stamp is reachable, not hypothetical: the SaaS extractors set
measuredAtfrom the freshest date in a section when no**Last checked:**bullet is present (pm-stack/aggregator/src/saas-extract.ts:628,:651-652—freshestDate(section)), and sections legitimately carry future operational dates (renewals, cycle ends — the exact hazardsnapshot-records.ts'slastCheckedDatecomment warns about). One future renewal date in a section without a Last-checked bullet becomes that service'smeasuredAt, and this bug then certifies it "today" for weeks. A typo'd year in the human-maintained quota view does the same. - Existing tests pin only the past/null directions:
pm-stack/aggregator/test/usage.test.ts:266-270(1-day-old not stale, 25-day-old stale, null stale). No future-dated case exists.
Why it matters
The freshness stamp is the dashboard's honesty mechanism for dated reads: it is what stops an old console read from being presented as current. A future stamp defeats it in the worst direction — the card claims the read happened today, carries no stale badge, and keeps claiming that until well after the stamped date — so a mis-dated or renewal-contaminated read silently pins a service's card as current while the real data ages underneath.
Acceptance criteria
- A
measuredAtin the future (beyond a small, named clock-skew tolerance that triage pins — e.g. 1 day for date-only stamps) is never reported asageDays: 0/ "today" withstale: false: it is stale, or carries an explicit future/invalid age state the render surfaces honestly. - The rule applies to both branches (date-only and datetime) in
freshness(). - Controls unchanged: yesterday is 1 day old and not stale at cadence 7, a read older than its cadence is stale, garbage/null stay
{stale: true, ageDays: null}. - Tests in
usage.test.ts(or the render freshness tests) pin: +1d, +30d, and +365d future stamps in both stamp forms; the boundary at the chosen skew tolerance. -
freshnessStampdoes not render a future stamp as "· today" without a stale/future indication.
Dedup / distinctness
Searched open + closed issues for freshness, future-dated, and ageDays: no filing covers derive.ts freshness(). Adjacent but different modules, gates, and fix surfaces:
- aggregator: a future-dated gatherer read is 'fresh' forever until its date passes — quota-current freshness has no upper bound, so one skewed measuredAt pins a pool's windows for weeks #1541 —
quota-current.ts entryState: a futuremeasuredAton a gatherer entry replaces a pool's windows. Different input (gatherer JSON, 20-minute bound), different function, different consequence (window replacement vs the hub cards' stale badge/age). - aggregator: a garbage or future-dated generatedAt is 'live' forever — the orchestration diagnosis and fleet-feed gate never check that the timestamp parses #1564 —
feed.ts/health.ts: a garbage or futuregeneratedAton the fleet feed / orchestration section never goes stale. Different stamps (feed/section, not per-service dated reads), and that filing's garbage case is the opposite of this one (here garbage is already handled).
The fix for either of those does not touch derive.ts freshness() or its render callers.
Activity
developing-today commentedon Oct 28, 2026
CODE-612
dezren39 commentedon Oct 28, 2026
🤖 Agent note · filed
4 remaining items
dezren39 commentedon Oct 31, 2026
🤖 Agent note · picked up
aggregator: freshness() future-stamp bound — stale beyond 1-day toler…
Merge pull request #2939 from developing-today/queue/1839-1568-freshn…
dezren39 commentedon Oct 31, 2026
🤖 Agent note · done