Skip to content

fix: restore JSON-cache retention semantics lost in the SQLite cutover (refs #2760) - #2767

Merged
steipete merged 3 commits into
mainfrom
review/semantics-parity
Aug 8, 2026
Merged

fix: restore JSON-cache retention semantics lost in the SQLite cutover (refs #2760)#2767
steipete merged 3 commits into
mainfrom
review/semantics-parity

Conversation

@steipete

@steipete steipete commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Adversarial semantics-parity audit of the SQLite cutover (#2761/#2762, refs #2760): every behavior pinned by the deleted CostUsageCacheTests.swift (39 tests, 1,677 lines) was mapped against the current suite and production code. Most behaviors survived the migration or became legitimately obsolete with the JSON artifact, but four real regressions did not — this PR restores them, with regression tests for each.

Gaps found and fixed

  1. Discovery pruning never reached the scanner. CostUsageStore retention pruned only the typed columns of discovery_state, while the scanner round-trips discovery exclusively through the opaque JSON payload (CostUsageStore+CodexCache.swift, discovery(from:)). After retention deleted files, the reloaded cache still listed them (stale filePaths, fileStamps, filePathBySessionId, missingSessionIds) with isComplete == true — the entire pruning path was dead code end-to-end. Prune now updates both representations in lockstep and restores the old cache's cursor resets and incomplete marking so the next discovery pass revalidates instead of trusting stale coverage.

  2. The row budget deleted in-window files. The old entry budget never sacrificed in-window data — only the byte budget could (old pin: "save never drops in-window files even when over the entry budget"). The cutover's row-budget loop deleted the oldest files regardless of window, which for a >25k-session in-window corpus means delete → rediscover → full rescan → delete again, a permanent catch-up churn loop the old design explicitly avoided. The row loop now protects files touching the requested window (and recently active ones); the byte budget (256 MiB) remains the sole authority that may drop in-window data, with the same catch-up marking and previous-report preservation.

  3. Fork-parent protection diverged from the old rules. Retention protected parents referenced by lineage-only children (dependency key "not required"), which the old cache deliberately did not, and a stale parent whose only referencing child was pruned in the same pass survived as an unreferenced row (old pin: "save drops stale parents referenced only by stale children"). Candidates now honor the dependency key and iterate to a fixpoint.

  4. Recently active out-of-window files were deletable. The old isRecentlyActive protected any file whose mtime fell inside the scan window — its scanned coverage may be out-of-window solely because new rows have not been scanned yet, and deleting it forces rediscovery plus a full reparse every refresh. Retention and the row budget now honor the mtime window again.

Restored test pins

saveCodexCache budgets are injectable for tests, which made the old cache-level pins portable: previous-report preservation across repeated trims, the non-Gregorian catch-up report calendar fix (#2703), detail stripping of protected/incomplete files instead of stalling, and fork-parent compaction (strip, not delete) when a surviving child still needs the baseline.

The full coverage map (behavior → ported / obsolete / gap) is in the review session; obsolete classifications are all JSON-encoding mechanics (producer keys and predecessor acceptance lists → schema/parser-hash user_version, load caps and estimate-underestimation loops → SQLite byte budget with incremental vacuum).

Proof

  • swift test --filter CostUsage: 398 tests, 25 suites, all pass
  • make check: clean (0 violations in 1,804 files)
  • Codex autoreview (branch vs origin/main): clean, no accepted findings

Known pre-existing flakes (present on the base commit, unrelated to this change): "app refresh bypasses scanner debounce" and "cached codex token hydration populates startup token snapshot" fail under specific parallel suite combinations, pass in isolation and on rerun.

…r (refs #2760)

Semantics-parity audit of the CostUsageCache -> CostUsageStore migration
found four behaviors the cutover dropped:

- Retention pruned only the typed discovery columns while the scanner
  round-trips discovery through the opaque payload, so deleted files
  resurfaced on the next load; prune now updates both in lockstep and
  resets cursors/isComplete like the old cache did.
- The row budget deleted the oldest files regardless of window; the old
  entry budget never sacrificed in-window or recently active files (the
  byte budget remains the only authority that may).
- Fork-parent protection ignored the lineage-only dependency key and a
  stale parent survived when its only referencing child was pruned in
  the same pass; candidates now honor the dependency key and iterate to
  a fixpoint.
- Out-of-window files with an in-window mtime (active sessions with
  unscanned rows) were deletable; they are protected again.

saveCodexCache budgets are injectable for tests, restoring pins for
previous-report preservation across trims and the non-Gregorian
catch-up report (#2703), plus strip/compaction coverage.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: afd374639a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +137 to +138
let lower = Int64(since.timeIntervalSince1970 * 1000)
let upper = Int64(end.timeIntervalSince1970 * 1000)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use local day starts for active retention bounds

CostUsageScanner.parseDayKey creates the date at noon for a day key, but these timestamps are now used as the active mtime window boundaries. With a window like 2026-08-01...2026-08-03, an out-of-window session modified at 08:00 on Aug 1 is treated as inactive and can be pruned even though it falls inside the requested local day, while one modified before noon on Aug 4 is incorrectly protected. That defeats the new protection for files modified inside the scan window; derive these bounds from local start-of-day values instead.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 session-state 🚨 Merging this PR could lose, corrupt, stale, or mis-associate session or agent state. labels Aug 8, 2026
@clawsweeper

clawsweeper Bot commented Aug 8, 2026

Copy link
Copy Markdown

Codex review: needs changes before merge. Reviewed August 8, 2026, 11:51 AM ET / 15:51 UTC.

ClawSweeper review

What this changes

The PR restores SQLite cost-cache retention semantics for discovery metadata, row budgets, fork-parent cleanup, and recently modified files.

Regression provenance

Possible regression — probable (reviewed change; known regression link). No predecessor PR is attributed.

Merge readiness

Blocked by patch quality or review findings - 5 items remain

Keep this owner-authored PR open: it addresses real SQLite retention regressions, but the active-file protection still uses noon-based day parsing and can prune files modified during the first morning of the requested window.

Priority: P2
Reviewed head: fe767a59fdb68f36e359ac1a73d4a711ef762f46

Review scores

Measure Result What it means
Overall readiness 🦪 silver shellfish (2/6) The patch has substantial focused coverage but retains a concrete day-boundary correctness defect.
Proof confidence 🌊 off-meta tidepool Not applicable: This owner-authored PR is exempt from the external-contributor real-behavior-proof gate; its reported focused tests remain useful supplemental validation.
Patch quality 🦪 silver shellfish (2/6) 1 actionable review finding remain.

Verification

Check Result Evidence
Real behavior Not applicable Not applicable: This owner-authored PR is exempt from the external-contributor real-behavior-proof gate; its reported focused tests remain useful supplemental validation.
Evidence reviewed 3 items Active-bound defect: The PR computes active mtime bounds directly from parseDayKey, while that parser intentionally produces local noon; the resulting range excludes the first morning and includes the following morning.
Existing review confirmation: The supplied PR review independently identifies the same first-day and post-window noon boundary problem.
Migration provenance: Current main records the SQLite cutover as commit 26fd0bb, and this PR is a focused follow-up for retention behavior introduced by that cutover.
Findings 1 actionable finding [P2] Use local day starts for active retention bounds
Security None None.

How this fits together

CodexBar scans local Codex session files and persists derived usage data in a SQLite cache. Retention and budget enforcement decide which cached file and discovery records survive before the scanner uses them to resume or revalidate coverage.

flowchart LR
A[Session files] --> B[Usage scanner]
B --> C[SQLite usage cache]
C --> D[Retention and budgets]
D --> E[Discovery state]
E --> B
C --> F[Usage reports]
Loading

Before merge

  • Use local day starts for active retention bounds (P2) - parseDayKey returns local noon, so this range excludes files modified during the first morning of the requested window and includes files modified before noon after the final day. Use local start-of-day values for both bounds and cover those two boundaries.
  • Resolve merge risk (P1) - Merging as-is can delete an out-of-window cached file modified before noon on the first requested day, causing unnecessary rediscovery and reparse; it also protects files modified before noon after the final day.
  • Complete next step (P2) - A narrow source-level repair and regression test can resolve the remaining merge blocker.
  • Improve patch quality - Use local day-start bounds for the active range.
  • Improve patch quality - Add regressions for a first-day morning timestamp and a morning timestamp after the final day.

Findings

  • [P2] Use local day starts for active retention bounds — Sources/CodexBarCore/Vendored/CostUsage/CostUsageStore+Retention.swift:132-138
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Patch scope production +126/-30; tests +325/-5 The patch materially changes derived-cache retention behavior and adds focused coverage for the intended parity rules.

Merge-risk options

Maintainer options:

  1. Correct local-day bounds before merge (recommended)
    Derive the active mtime range from local day starts and add first-morning and post-window-morning regression coverage.

Technical review

Best possible solution:

Represent the active mtime interval as local start-of-day for the first requested day through local start-of-day after the final requested day, with boundary regressions for both sides.

Do we have a high-confidence way to reproduce the issue?

Yes, from source: a file modified at 08:00 on the first requested local day falls before parseDayKey's noon lower bound and is treated as stale. The existing test covers an interior-day timestamp, not this boundary.

Is this the best way to solve the issue?

No. The proposed active-file protection needs local day-start bounds; preserving the noon parser for date-key normalization is fine, but it is not a day-range boundary primitive.

Full review comments:

  • [P2] Use local day starts for active retention bounds — Sources/CodexBarCore/Vendored/CostUsage/CostUsageStore+Retention.swift:132-138
    parseDayKey returns local noon, so this range excludes files modified during the first morning of the requested window and includes files modified before noon after the final day. Use local start-of-day values for both bounds and cover those two boundaries.
    Confidence: 0.99

Overall correctness: patch is incorrect
Overall confidence: 0.99

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 5205dd1e003c.

Labels

Label changes:

  • add P2: This is a bounded cached-usage correctness issue without evidence of an emergency outage.
  • add merge-risk: 🚨 compatibility: Retention changes alter which existing SQLite cache entries are preserved during normal use and upgrades.
  • add merge-risk: 🚨 session-state: The affected cache records include scanner discovery and session-lineage state used to resume scanning.
  • add rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🌊 off-meta tidepool and patch quality is 🦪 silver shellfish.
  • add status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Not applicable: This owner-authored PR is exempt from the external-contributor real-behavior-proof gate; its reported focused tests remain useful supplemental validation.

Label justifications:

  • P2: This is a bounded cached-usage correctness issue without evidence of an emergency outage.
  • merge-risk: 🚨 compatibility: Retention changes alter which existing SQLite cache entries are preserved during normal use and upgrades.
  • merge-risk: 🚨 session-state: The affected cache records include scanner discovery and session-lineage state used to resume scanning.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🌊 off-meta tidepool and patch quality is 🦪 silver shellfish.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Not applicable: This owner-authored PR is exempt from the external-contributor real-behavior-proof gate; its reported focused tests remain useful supplemental validation.

Evidence

Acceptance criteria:

  • [P1] swift test --filter CostUsageStoreTests.
  • [P1] make test.
  • [P1] make check.

What I checked:

Likely related people:

  • steipete: Current-main history attributes the SQLite cutover and this PR's retention follow-up to steipete. (role: SQLite persistence and retention contributor; confidence: high; commits: 26fd0bbd7eed, a5989a3778a8; files: Sources/CodexBarCore/Vendored/CostUsage/CostUsageStore+Retention.swift, Sources/CodexBarCore/Vendored/CostUsage/CostUsageStore+CodexCache.swift)

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 session-state 🚨 Merging this PR could lose, corrupt, stale, or mis-associate session or agent state. P2 Normal priority bug or improvement with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant