mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-05-16 19:44:14 +00:00
daily-cache: discard pre-v5 caches (fixes menubar providers regression) (#297)
PR #296 (Cursor per-project breakdown) bumped DAILY_CACHE_VERSION from 4 to 5 but left MIN_SUPPORTED_VERSION at 2. The migration path (isMigratableCache + migrateDays) only fills in missing default fields; it does NOT recompute the providers / categories / models rollups from session data, because raw sessions are not retained in the cache. So a v4 cache migrated to v5 carried forward its old per-day provider totals (single 'cursor' bucket) for the full retention window. Effect on users post-#296: the macOS menubar's `current.providers.cursor` would show the orphan-bucket subtotal instead of the full Cursor cost for any historical day whose daily entry was computed before #296 landed. Live-test on my machine showed cursor=$3.78 against a migrated v4 cache vs cursor=$4.08 (correct) after the daily cache was discarded — the $0.30 gap was the workspace projects whose costs were no longer aggregated under the 'cursor' label by the new code. Fix: raise MIN_SUPPORTED_VERSION to 5 so any cache with version < DAILY_CACHE_VERSION is renamed to `.bak` and the cache is recomputed from scratch on next run. The recompute is the same operation that backfills the cache for a new user, so the cost is a one-time cold-path hit (~3s on the test machine). Test for the migration case updated to assert the new discard-and-bak behavior. Full suite: 46 files / 654 tests pass.
This commit is contained in:
parent
810b214476
commit
d142bd97ef
2 changed files with 23 additions and 12 deletions
|
|
@ -10,10 +10,19 @@ import type { DateRange, ProjectSummary } from './types.js'
|
|||
// label. After the upgrade, the breakdown produces per-workspace project
|
||||
// labels for new days; without invalidation the dashboard would show
|
||||
// 'cursor' for historical days and `-Users-you-myproject` for new ones
|
||||
// in the same window, producing a confusing mixed projection. v5 forces a
|
||||
// full recompute.
|
||||
// in the same window, producing a confusing mixed projection.
|
||||
export const DAILY_CACHE_VERSION = 5
|
||||
const MIN_SUPPORTED_VERSION = 2
|
||||
// MIN_SUPPORTED_VERSION bumped to 5 too. The migration path
|
||||
// (isMigratableCache + migrateDays) only fills in missing default fields;
|
||||
// it does NOT recompute the providers / categories / models rollups from
|
||||
// session data, because those raw sessions are not stored in the cache.
|
||||
// So a migrated v2/v3/v4 cache would carry forward stale provider totals
|
||||
// (single 'cursor' bucket instead of per-workspace) for the full cache
|
||||
// retention window. Setting the floor to 5 forces those older caches to
|
||||
// be discarded and recomputed cleanly. Confirmed by live test:
|
||||
// menubar-json --period all reported cursor=$3.78 against a migrated
|
||||
// v4 cache but $4.08 (correct) after the cache was discarded.
|
||||
const MIN_SUPPORTED_VERSION = 5
|
||||
const DAILY_CACHE_FILENAME = 'daily-cache.json'
|
||||
|
||||
export type DailyEntry = {
|
||||
|
|
|
|||
|
|
@ -77,7 +77,13 @@ describe('loadDailyCache', () => {
|
|||
expect(existsSync(join(TMP_CACHE_ROOT, 'daily-cache.json.v1.bak'))).toBe(true)
|
||||
})
|
||||
|
||||
it('migrates an older supported version by filling missing fields', async () => {
|
||||
it('discards a v2 cache and starts fresh (provider rollups would be stale)', async () => {
|
||||
// MIN_SUPPORTED_VERSION was raised to DAILY_CACHE_VERSION because the
|
||||
// migration path cannot recompute the providers / categories / models
|
||||
// rollups from session data (the cache does not retain raw sessions),
|
||||
// so a migrated old cache would carry forward stale provider totals
|
||||
// for the full retention window. Older caches now get discarded and
|
||||
// recomputed from scratch on next run.
|
||||
const saved = {
|
||||
version: 2,
|
||||
lastComputedDate: '2026-04-10',
|
||||
|
|
@ -92,14 +98,10 @@ describe('loadDailyCache', () => {
|
|||
await writeFile(join(TMP_CACHE_ROOT, 'daily-cache.json'), JSON.stringify(saved), 'utf-8')
|
||||
const cache = await loadDailyCache()
|
||||
expect(cache.version).toBe(DAILY_CACHE_VERSION)
|
||||
expect(cache.days).toHaveLength(1)
|
||||
expect(cache.days[0].date).toBe('2026-04-10')
|
||||
expect(cache.days[0].cost).toBe(10)
|
||||
expect(cache.days[0].editTurns).toBe(0)
|
||||
expect(cache.days[0].oneShotTurns).toBe(0)
|
||||
expect(cache.days[0].categories).toEqual({})
|
||||
expect(cache.days[0].providers).toEqual({})
|
||||
expect(cache.days[0].models['claude-opus-4-6'].calls).toBe(5)
|
||||
expect(cache.days).toEqual([])
|
||||
expect(cache.lastComputedDate).toBeNull()
|
||||
// Old cache is renamed to .v2.bak rather than deleted.
|
||||
expect(existsSync(join(TMP_CACHE_ROOT, 'daily-cache.json.v2.bak'))).toBe(true)
|
||||
})
|
||||
|
||||
it('round-trips a valid cache through save and load', async () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue