Merge pull request #910 from ozymandiashh/fix/swarm-cache-correctness

fix(optimize): strengthen the result-cache fingerprint against collisions
This commit is contained in:
Resham Joshi 2026-08-04 03:24:38 -07:00 committed by GitHub
commit 22e122f137
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 45 additions and 2 deletions

View file

@ -2965,9 +2965,23 @@ export function computeInputCostRate(projects: ProjectSummary[]): number {
type CacheEntry = { data: OptimizeResult; ts: number }
const resultCache = new Map<string, CacheEntry>()
function cacheKey(projects: ProjectSummary[], dateRange: DateRange | undefined): string {
export function cacheKey(projects: ProjectSummary[], dateRange: DateRange | undefined): string {
const dr = dateRange ? `${dateRange.start.getTime()}-${dateRange.end.getTime()}` : 'all'
const fingerprint = projects.length + ':' + projects.reduce((s, p) => s + p.totalApiCalls, 0)
// Fingerprint enough of the dataset that two materially different inputs
// cannot collide onto one cached OptimizeResult. Project count + api-call
// sum alone collided any two datasets sharing those two numbers, and served
// stale findings when cost/tokens moved (e.g. a re-price) while call count
// held - reachable in the long-lived menubar process within the 60s TTL.
// Cost is scaled to whole micro-dollars so float jitter cannot thrash the key.
let calls = 0, cost = 0, savings = 0, proxied = 0
for (const p of projects) {
calls += p.totalApiCalls
cost += p.totalCostUSD
savings += p.totalSavingsUSD
proxied += p.totalProxiedCostUSD
}
// Costs scaled to whole micro-dollars so float jitter cannot thrash the key.
const fingerprint = `${projects.length}:${calls}:${Math.round(cost * 1e6)}:${Math.round(savings * 1e6)}:${Math.round(proxied * 1e6)}`
return `${dr}:${fingerprint}`
}

View file

@ -22,6 +22,7 @@ import {
detectLowWorthSessions,
detectSessionOutliers,
scanAndDetect,
cacheKey,
computeHealth,
computeTrend,
buildOptimizeJsonReport,
@ -1041,6 +1042,34 @@ describe('detectSessionOutliers', () => {
})
})
describe('optimize cacheKey collision resistance', () => {
it('does not collide two datasets that share project count and api-call sum', () => {
// The old fingerprint was projectCount + sum(api calls) only, so any two
// datasets agreeing on those two numbers shared one cached OptimizeResult -
// the second scan got the first's findings. Same shape, different spend must
// now key differently.
const a = projectWithSessions([100, 1, 1, 1]) // 4 calls, cost 103
const b = projectWithSessions([1, 1, 1, 1]) // 4 calls, cost 4
const range = optimizeDateRange(4)
expect(a.totalApiCalls).toBe(b.totalApiCalls)
expect(cacheKey([a], range)).not.toBe(cacheKey([b], range))
})
it('is stable for the identical dataset (still caches a genuine repeat)', () => {
const a = projectWithSessions([5, 3, 2])
const range = optimizeDateRange(3)
expect(cacheKey([a], range)).toBe(cacheKey([projectWithSessions([5, 3, 2])], range))
})
it('separates a re-price that leaves call count unchanged', () => {
// A dataset re-priced (cost moves, calls do not) must not serve stale findings.
const before = projectWithSessions([10, 10])
const after = projectWithSessions([25, 10]) // same 2 calls, higher cost
const range = optimizeDateRange(2)
expect(cacheKey([before], range)).not.toBe(cacheKey([after], range))
})
})
describe('computeHealth', () => {
it('returns A with 100 for no findings', () => {
const { score, grade } = computeHealth([])