From 2177898aa249c75739320af0281736e1d02d9e70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 17 May 2026 17:27:09 +0200 Subject: [PATCH] refactor(bp-in-target): rename private dayKey to bucketDayKey The helper used `d.toISOString().slice(0, 10)` (UTC slice) but carried the generic `dayKey` name, inviting a contributor to swap in `userDayKey()` from `@/lib/tz/resolver` and silently shift the pairing key for non-near-UTC tenants. Rename to `bucketDayKey` and add a docstring spelling out why the UTC slice is correct here (both SYS and DIA buckets share UTC midnight; pairing is against each other, not against any user-local series). --- src/lib/analytics/bp-in-target-fast-path.ts | 24 ++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/lib/analytics/bp-in-target-fast-path.ts b/src/lib/analytics/bp-in-target-fast-path.ts index cb7437db8..c5cacc29a 100644 --- a/src/lib/analytics/bp-in-target-fast-path.ts +++ b/src/lib/analytics/bp-in-target-fast-path.ts @@ -215,7 +215,7 @@ async function computeFromRollups( const sysByDay = new Map(); for (const r of sysBuckets) { - sysByDay.set(dayKey(r.bucketStart), { + sysByDay.set(bucketDayKey(r.bucketStart), { day: r.bucketStart, meanValue: r.mean, perDayCount: r.count, @@ -223,7 +223,7 @@ async function computeFromRollups( } const diaByDay = new Map(); for (const r of diaBuckets) { - diaByDay.set(dayKey(r.bucketStart), { + diaByDay.set(bucketDayKey(r.bucketStart), { day: r.bucketStart, meanValue: r.mean, perDayCount: r.count, @@ -343,7 +343,25 @@ function bucketWindow( }; } -function dayKey(d: Date): string { +/** + * v1.4.38 — UTC-slice bucket key for the rollup pairing. + * + * The rollup table stores `bucketStart` as UTC midnight; we slice the + * ISO string to get a `YYYY-MM-DD` key in the bucket's own zone, NOT + * the user's local zone. That is correct here because both the SYS + * and DIA buckets live in UTC and we pair them against each other + * inside `computeFromRollups` — not against any user-local data. + * + * Renamed from the generic `dayKey` to make the UTC nature explicit. + * Do NOT swap in `userDayKey()` from `@/lib/tz/resolver` without + * first auditing the call sites: `userDayKey` keys per the user's + * IANA zone and would produce a different string for a non-near-UTC + * tenant, while the rollup `bucketStart` would not. The cross-tz + * guard inside `computeBpInTargetFastPath` already routes + * non-near-UTC users to the live fallback, so the UTC-slice key is + * safe for every code path that reaches this helper. + */ +function bucketDayKey(d: Date): string { return d.toISOString().slice(0, 10); }