Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -128,18 +128,33 @@ extension CostUsageStore {
untilDay: String?,
calendar: Calendar) -> Range<Int64>?
{
let scanCalendar = CostUsageScanner.CostUsageDayRange.localGregorianCalendar(matching: calendar)
guard let sinceDay, let untilDay,
let since = CostUsageScanner.parseDayKey(sinceDay, calendar: calendar),
let until = CostUsageScanner.parseDayKey(untilDay, calendar: calendar)
let since = Self.dayStart(sinceDay, calendar: scanCalendar),
let until = Self.dayStart(untilDay, calendar: scanCalendar)
else { return nil }
let scanCalendar = CostUsageScanner.CostUsageDayRange.localGregorianCalendar(matching: calendar)
let end = scanCalendar.date(byAdding: .day, value: 1, to: until) ?? until
let lower = Int64(since.timeIntervalSince1970 * 1000)
let upper = Int64(end.timeIntervalSince1970 * 1000)
guard lower < upper else { return nil }
return lower..<upper
}

private static func dayStart(_ key: String, calendar: Calendar) -> Date? {
let parts = key.split(separator: "-")
guard parts.count == 3,
let year = Int(parts[0]),
let month = Int(parts[1]),
let day = Int(parts[2])
else { return nil }
return calendar.date(from: DateComponents(
calendar: calendar,
timeZone: calendar.timeZone,
year: year,
month: month,
day: day))
}

private static func retentionCandidates(
_ database: OpaquePointer,
sinceDay: String,
Expand Down
102 changes: 102 additions & 0 deletions Tests/CodexBarTests/CostUsagePerformanceGateTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,100 @@ struct CostUsagePerformanceGateTests {
#expect(warm.data.first?.totalTokens == cold.data.first?.totalTokens)
}

@Test
func `over budget prune retains stale coverage file modified inside the window`() async throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let oldDay = try env.makeLocalNoon(year: 2026, month: 5, day: 10)
let windowStart = try env.makeLocalNoon(year: 2026, month: 7, day: 27)
let windowDay = try env.makeLocalNoon(year: 2026, month: 8, day: 2)
let oldISO = env.isoString(for: oldDay)

// One still-active session whose usage rows are all out of window, plus idle stale
// sessions. The active file lives in the scanned window's directory and keeps an
// in-window mtime, exactly like a session that stopped producing usage weeks ago.
let activeURL = try env.writeCodexSessionFile(
day: windowDay,
filename: "stale-active.jsonl",
contents: [
#"{"type":"session_meta","timestamp":"\#(oldISO)","payload":{"session_id":"stale-active-session"}}"#,
#"{"type":"turn_context","timestamp":"\#(oldISO)","payload":{"model":"openai/gpt-5.2-codex"}}"#,
#"{"type":"event_msg","timestamp":"\#(oldISO)","payload":{"type":"token_count","info":"#
+ #"{"total_token_usage":{"input_tokens":100,"cached_input_tokens":20,"output_tokens":10},"#
+ #""model":"openai/gpt-5.2-codex"}}}"#,
].joined(separator: "\n") + "\n")
try FileManager.default.setAttributes(
[.modificationDate: windowDay],
ofItemAtPath: activeURL.path)
for index in 0..<30 {
let idleURL = try env.writeCodexSessionFile(
day: windowDay,
filename: "idle-\(index).jsonl",
contents: [
#"{"type":"session_meta","timestamp":"\#(oldISO)","payload":{"session_id":"idle-\#(index)"}}"#,
#"{"type":"event_msg","timestamp":"\#(oldISO)","payload":{"type":"token_count","info":"#
+ #"{"total_token_usage":{"input_tokens":10,"cached_input_tokens":2,"output_tokens":1},"#
+ #""model":"openai/gpt-5.2-codex"}}}"#,
].joined(separator: "\n") + "\n")
try FileManager.default.setAttributes(
[.modificationDate: oldDay],
ofItemAtPath: idleURL.path)
}

var options = CostUsageScanner.Options(
codexSessionsRoot: env.codexSessionsRoot,
claudeProjectsRoots: nil,
cacheRoot: env.cacheRoot,
codexTraceDatabaseURL: env.root.appendingPathComponent("missing.sqlite"))
options.refreshMinIntervalSeconds = 0
_ = CostUsageScanner.loadDailyReport(
provider: .codex,
since: windowStart,
until: windowDay,
now: windowDay,
options: options)

let store = CostUsageStore(cacheRoot: env.cacheRoot)
let activeFile = { (files: [CostUsageStoreFile]) -> CostUsageStoreFile? in
files.first { $0.sessionID == "stale-active-session" }
}
let coldRow = try #require(await activeFile(store.readSnapshot().files))
#expect(coldRow.scanState.isComplete)
let coldAnchor = coldRow.anchor?.sha256
let coldParsedBytes = coldRow.parsedBytes

// Force the over-budget branch of the production save path: 31 retained files
// exceed the 1-file row budget, so the window prune must run before the row cap.
let budget = await store.enforceBudgets(
maxRows: 1,
maxFileBytes: .max,
requestedSinceDay: Self.dayKeyString(for: windowStart),
requestedUntilDay: Self.dayKeyString(for: windowDay),
calendar: .current)
#expect(budget.rowCount == 1)
let retained = try #require(await activeFile(store.readSnapshot().files))
#expect(retained.scanState.isComplete)
#expect(retained.anchor?.sha256 == coldAnchor)
print("[retention-proof] stale-coverage file retained after over-budget prune: \(retained.path)")

let warmCounter = HeadParseCounter()
_ = CostUsageScanner.withCodexSessionHeadParseObserverForTesting {
warmCounter.increment()
} operation: {
_ = CostUsageScanner.loadDailyReport(
provider: .codex,
since: windowStart,
until: windowDay,
now: windowDay,
options: options)
}
#expect(warmCounter.value == 0)
let warmRow = try #require(await activeFile(store.readSnapshot().files))
#expect(warmRow.anchor?.sha256 == coldAnchor)
#expect(warmRow.parsedBytes == coldParsedBytes)
print("[retention-proof] warm refresh reused the cached row, headParses=0")
}

@Test
func `priority turns refresh must scan only appended trace rows`() throws {
let env = try CostUsageTestEnvironment()
Expand Down Expand Up @@ -1405,6 +1499,14 @@ extension CostUsagePerformanceGateTests {
}
return Int64(CostUsageScanner.codexActiveSessionLookbackDays * existingRootCount)
}

private static func dayKeyString(for date: Date) -> String {
let components = Calendar.current.dateComponents([.year, .month, .day], from: date)
let year = components.year ?? 0
let month = components.month ?? 0
let day = components.day ?? 0
return String(format: "%04d-%02d-%02d", year, month, day)
}
}

private final class HeadParseCounter: @unchecked Sendable {
Expand Down
104 changes: 104 additions & 0 deletions Tests/CodexBarTests/CostUsageStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,110 @@ extension CostUsageStoreTests {
#expect(await store.fetchFile(path: parent.path) != nil)
}

@Test
func `retention keeps recently modified file with stale coverage`() async throws {
let fixture = try StoreFixture()
defer { fixture.remove() }
let store = CostUsageStore(cacheRoot: fixture.root)
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = try #require(TimeZone(identifier: "UTC"))
var file = Self.file(path: "/rollouts/stale-but-active.jsonl", day: "2026-07-01")
let recentMtime = try #require(calendar.date(from: DateComponents(
year: 2026,
month: 8,
day: 1,
hour: 6)))
file.mtimeUnixMs = Int64(recentMtime.timeIntervalSince1970 * 1000)
#expect(await store.upsertFile(file))

_ = await store.retainDayWindow(
sinceDay: "2026-08-01",
untilDay: "2026-08-03",
calendar: calendar)
#expect(await store.fetchFile(path: file.path) != nil)
}

@Test
func `retention prunes stale file modified before the window`() async throws {
let fixture = try StoreFixture()
defer { fixture.remove() }
let store = CostUsageStore(cacheRoot: fixture.root)
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = try #require(TimeZone(identifier: "UTC"))
var file = Self.file(path: "/rollouts/stale-and-idle.jsonl", day: "2026-07-01")
let oldMtime = try #require(calendar.date(from: DateComponents(
year: 2026,
month: 7,
day: 1,
hour: 12)))
file.mtimeUnixMs = Int64(oldMtime.timeIntervalSince1970 * 1000)
#expect(await store.upsertFile(file))

_ = await store.retainDayWindow(
sinceDay: "2026-08-01",
untilDay: "2026-08-03",
calendar: calendar)
#expect(await store.fetchFile(path: file.path) == nil)
}

@Test
func `retention prunes stale file modified after the window`() async throws {
let fixture = try StoreFixture()
defer { fixture.remove() }
let store = CostUsageStore(cacheRoot: fixture.root)
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = try #require(TimeZone(identifier: "UTC"))
var file = Self.file(path: "/rollouts/stale-after-window.jsonl", day: "2026-07-01")
let lateMtime = try #require(calendar.date(from: DateComponents(
year: 2026,
month: 8,
day: 4,
hour: 12)))
file.mtimeUnixMs = Int64(lateMtime.timeIntervalSince1970 * 1000)
#expect(await store.upsertFile(file))

_ = await store.retainDayWindow(
sinceDay: "2026-08-01",
untilDay: "2026-08-03",
calendar: calendar)
#expect(await store.fetchFile(path: file.path) == nil)
}

@Test
func `retention keeps stale file modified at the window edges`() async throws {
let fixture = try StoreFixture()
defer { fixture.remove() }
let store = CostUsageStore(cacheRoot: fixture.root)
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = try #require(TimeZone(identifier: "UTC"))
let startMtime = try #require(calendar.date(from: DateComponents(
year: 2026,
month: 8,
day: 1,
hour: 0)))
let endMtime = try #require(calendar.date(from: DateComponents(
year: 2026,
month: 8,
day: 3,
hour: 23,
minute: 59,
second: 59)))
for (index, mtime) in [startMtime, endMtime].enumerated() {
var file = Self.file(
path: "/rollouts/edge-\(index).jsonl",
day: "2026-07-01")
file.mtimeUnixMs = Int64(mtime.timeIntervalSince1970 * 1000)
#expect(await store.upsertFile(file))
}

_ = await store.retainDayWindow(
sinceDay: "2026-08-01",
untilDay: "2026-08-03",
calendar: calendar)
#expect(await store.fetchFile(path: "/rollouts/edge-0.jsonl") != nil)
#expect(await store.fetchFile(path: "/rollouts/edge-1.jsonl") != nil)
}

@Test
func `retention prunes discovery references for removed files`() async throws {
let fixture = try StoreFixture()
Expand Down
Loading