Skip to content

fix(daily-cache): never-lose guards — degraded gap parses, residue-only days, doctor cache health - #1129

Merged
iamtoruk merged 1 commit into
mainfrom
fix/1127-daily-cache-guards
Aug 24, 2026
Merged

iamtoruk merged 1 commit into
mainfrom
fix/1127-daily-cache-guards

Conversation

@iamtoruk

Copy link
Copy Markdown
Member

Diagnosed from #1127 (full analysis posted there). The reporter's corpus-specific under-read is still being narrowed with them, but the investigation exposed a structural never-lose hole on main that gets fixed regardless:

The hole

ensureCacheHydrated's gap-fill path wrote days into durable history via a blind overwrite with no completeness gate — so a parse degraded by the refresh lock (e.g. the menubar refreshing concurrently) could permanently replace a good cached day with its undercount. The re-derive path has completeness + partial-survival guards; the gap path had neither.

The guards

  1. Gap merges are completeness-gated: complete parse → fresh wins per (date, provider) through the same guarded merge as re-derive; partial parse → baseline wins, fresh fills gaps only. (Also stops silently dropping pendingRederive.)
  2. Residue-only days self-heal: a day holding only a midnight-straddling turn's category counts (cost/calls/tokens all zero — the exact broken shape from Totals/Daily report $0 for a single date even after a full cache rebuild, while live per-tool/per-model breakdown for the same date is correct #1127, reproduced synthetically) pulls the watermark back so the next launch re-derives it. Bounded to the settle window, never the oldest cached day (treadmill caveat documented in-code). No cache version bump — every broken cache in the wild heals on next launch.
  3. codeburn doctor shows cache health (read-only): residue-only dates + failed/empty session-cache entries — the next Totals/Daily report $0 for a single date even after a full cache rebuild, while live per-tool/per-model breakdown for the same date is correct #1127-style report becomes one command instead of archaeology.

Implementation by Kimi Code from the diagnosis spec; reviewed and re-verified by maintainer.

Tests

Residue-shape pin in day-aggregator (anchor day zeroed with turns:1, neighbor carries the cost — the contract the predicate keys on); partial-gap cannot shrink a populated baseline day (fails on the old code); complete-gap parity with baseline-only provider slices carried; sealed-cache watermark pull-back re-derive. Full suite 240 files / 3,254 green (lock suite included), tsc clean — re-run by reviewer.

…nly days

Diagnosed via #1127. Three guards:

- The gap-fill path no longer overwrites cached days wholesale: a
  complete parse wins per (date, provider) through the same guarded
  merge the re-derive path uses; a partial parse only fills days and
  slices the baseline lacks. Previously a parse degraded by the
  refresh lock (menubar running) could replace a good day with its
  undercount permanently. The gap path also stops dropping
  pendingRederive.
- A day whose only content is turn-anchored residue (a midnight-
  straddling turn's category counts with zero cost/calls/tokens) is
  never sealed: the watermark pulls back so the next launch re-derives
  it — self-healing for every broken cache in the wild, no version
  bump. Bounded to the settle window and never for the oldest cached
  day.
- codeburn doctor gains a read-only Cache health section listing
  residue-only dates and failed/empty session-cache entries, so the
  next report like #1127 is diagnosable in one command.

Tests: residue-shape pin (day-aggregator), partial-gap cannot shrink a
populated day (fails on the old code), complete-gap parity, watermark
pull-back re-derive.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant