mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-08-21 22:44:31 +00:00
The codex parse version and CODEX_CACHE_VERSION bumps in #1040 make codex sessions re-parse, but the daily cache has no per-provider invalidation, so every day already finalized keeps its old per-model rows - and usage-aggregator serves every day before today from that cache, with ten-year retention. Raise DAILY_CACHE_VERSION and MIN_SUPPORTED_VERSION to 20 so history re-derives once off the warm session cache. The re-derivation test now seeds v19, the last shipped version, so it models the real 19 -> 20 path, and the upgrade-path check expects daily-cache.v20.json. Measured on a real 110-day cache: no day lost value, none disappeared, 100 came back identical, and 9 grok days rose by $19.80 in total from rollups an earlier parse change had left stale. Every codex model row was unchanged - that corpus predates the provenance field the fix corrects.
107 lines
3.1 KiB
TypeScript
107 lines
3.1 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
import { mkdir, readFile, rm, writeFile } from 'fs/promises'
|
|
import { join } from 'path'
|
|
import { tmpdir } from 'os'
|
|
|
|
import {
|
|
currentTzKey,
|
|
ensureCacheHydrated,
|
|
toDateString,
|
|
type DailyEntry,
|
|
} from '../src/daily-cache.js'
|
|
|
|
// The last SHIPPED daily-cache version before the Codex session_meta model fix,
|
|
// so this models the real 19 -> 20 upgrade path users hit. Anything below
|
|
// MIN_SUPPORTED_VERSION is untrusted, which is what makes the re-derivation
|
|
// global rather than scoped to the provider the bump exists for.
|
|
const PRE_FIX_DAILY_VERSION = 19
|
|
const cacheRoot = join(tmpdir(), `codeburn-daily-rederive-${process.pid}-${Date.now()}`)
|
|
|
|
function day(date: string, cost: number): DailyEntry {
|
|
return {
|
|
date,
|
|
cost,
|
|
savingsUSD: 0,
|
|
calls: 1,
|
|
sessions: 1,
|
|
inputTokens: 100,
|
|
outputTokens: 20,
|
|
cacheReadTokens: 30,
|
|
cacheWriteTokens: 0,
|
|
editTurns: 0,
|
|
oneShotTurns: 0,
|
|
models: {
|
|
'Grok Build': {
|
|
calls: 1,
|
|
cost,
|
|
savingsUSD: 0,
|
|
inputTokens: 100,
|
|
outputTokens: 20,
|
|
cacheReadTokens: 30,
|
|
cacheWriteTokens: 0,
|
|
},
|
|
},
|
|
categories: {},
|
|
providers: {
|
|
grok: {
|
|
calls: 1,
|
|
cost,
|
|
savingsUSD: 0,
|
|
sessions: 1,
|
|
inputTokens: 100,
|
|
outputTokens: 20,
|
|
cacheReadTokens: 30,
|
|
cacheWriteTokens: 0,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
process.env['CODEBURN_CACHE_DIR'] = cacheRoot
|
|
await rm(cacheRoot, { recursive: true, force: true })
|
|
await mkdir(cacheRoot, { recursive: true })
|
|
})
|
|
|
|
afterEach(async () => {
|
|
await rm(cacheRoot, { recursive: true, force: true })
|
|
})
|
|
|
|
// Raising MIN_SUPPORTED_VERSION re-derives EVERY day from EVERY provider - the
|
|
// daily cache has no per-provider invalidation. Which provider the seeded day
|
|
// belongs to is incidental; the mechanism under test is version-wide.
|
|
describe('daily-cache re-derivation on a DAILY_CACHE_VERSION bump', () => {
|
|
it('re-derives a day from a below-minimum v17 cache while preserving the old file', async () => {
|
|
const date = toDateString(new Date(Date.now() - 7 * 24 * 60 * 60 * 1000))
|
|
const yesterday = toDateString(new Date(Date.now() - 24 * 60 * 60 * 1000))
|
|
const oldPath = join(cacheRoot, `daily-cache.v${PRE_FIX_DAILY_VERSION}.json`)
|
|
const oldCache = {
|
|
version: PRE_FIX_DAILY_VERSION,
|
|
savingsConfigHash: 'cfg',
|
|
tzKey: currentTzKey(),
|
|
lastComputedDate: yesterday,
|
|
days: [day(date, 99)],
|
|
complete: true,
|
|
watermarkTrusted: true,
|
|
}
|
|
await writeFile(oldPath, JSON.stringify(oldCache))
|
|
|
|
let parseCount = 0
|
|
const corrected = day(date, 2)
|
|
const hydrated = await ensureCacheHydrated(
|
|
async () => {
|
|
parseCount++
|
|
return []
|
|
},
|
|
() => [corrected],
|
|
'cfg',
|
|
() => true,
|
|
)
|
|
|
|
const refreshedDay = hydrated.days.find(entry => entry.date === date)
|
|
expect(parseCount).toBe(1)
|
|
expect(refreshedDay?.providers.grok?.cost).toBe(2)
|
|
expect(refreshedDay?.cost).toBe(2)
|
|
expect(JSON.parse(await readFile(oldPath, 'utf8'))).toEqual(oldCache)
|
|
})
|
|
})
|