Skip to content
Open
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
@@ -1,5 +1,5 @@
// Generated by Scripts/regenerate-codex-parser-hash.sh. Do not edit by hand.

enum CodexParserHash {
static let value = "48ac20dad61e9a7f"
static let value = "60c110df71a146ea"
}
13 changes: 9 additions & 4 deletions Sources/CodexBarCore/Vendored/CostUsage/CostUsageCache.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import Foundation

enum CostUsageCacheIO {
/// Producer keys from older parser hashes whose caches are still valid under the current
/// delta semantics. Cleared for #2037: interleave containment changed how cumulative
/// totals are counted, so every earlier cache must be rebuilt.
private static let compatibleCodexProducerKeys: Set<String> = []
/// Producer keys from older parser hashes whose caches remain structurally compatible.
/// #2037 invalidated every earlier producer because interleave containment changed cumulative
/// accounting. This immediate predecessor is safe to admit because the scanner selectively
/// reparses its parent-dependent forked files via `codexForkAttributionVersion`.
private static let compatibleCodexProducerKeys: Set<String> = ["codex:cu:p48ac20dad61e9a7f"]

/// Parsing and attribution changes rotate the Codex parser producer key.
/// Increment this artifact version only when the stored schema or cache layout becomes incompatible.
Expand Down Expand Up @@ -114,6 +115,8 @@ struct CostUsageCache: Codable {
var codexPricingKey: String?
var codexPriorityMetadataKey: String?
var codexProjectMetadataVersion: Int?
/// Optional migration marker; absent caches must inspect parent-dependent fork candidates.
var codexForkAttributionVersion: Int?
var codexPriorityTurnKeys: [String: String]?
var codexPriorityTurnIDsByDay: [String: [String]]?

Expand Down Expand Up @@ -144,6 +147,8 @@ struct CostUsageFileUsage: Codable {
var sessionId: String?
var forkedFromId: String?
var forkBaselineDependencyKey: String?
/// Set after this file has passed the fork-attribution parser; nil requires the dependency-key check.
var codexForkAttributionVersion: Int?
var projectPath: String?
var canonicalProjectPath: String?
var codexCostCacheComplete: Bool?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,25 @@ import Darwin
#endif

extension CostUsageScanner {
/// #2285 persisted this key for every compact parent candidate, including known-model rows.
/// Only the sentinel proves the file never depended on parent context.
static func isLegacyForkAttributionCandidate(_ usage: CostUsageFileUsage) -> Bool {
usage.forkedFromId != nil
&& usage.codexForkAttributionVersion != codexForkAttributionVersion
&& usage.forkBaselineDependencyKey != codexForkDependencyNotRequiredKey
}

/// Keep every cache-backed presentation surface on the same migration boundary. This is
/// intentionally provenance-based: a stale copied prefix may already carry a known model.
static func codexCacheForPresentation(_ cache: CostUsageCache) -> CostUsageCache {
var projected = cache
for (path, usage) in cache.files where Self.isLegacyForkAttributionCandidate(usage) {
Self.applyFileDays(cache: &projected, fileDays: usage.days, sign: -1)
projected.files.removeValue(forKey: path)
}
return projected
}

private final class CodexModelsDevCatalogResolver {
private var catalog: ModelsDevCatalog?
private let cacheRoot: URL?
Expand Down Expand Up @@ -894,11 +913,17 @@ extension CostUsageScanner {
let needsSessionId = cached.sessionId == nil
guard cached.mtimeUnixMs == input.metadata.mtimeUnixMs,
cached.size == input.metadata.size,
cached.parsedBytes.map({ $0 == cached.size }) == true,
!needsSessionId,
!context.forceFullScan
else { return false }

guard !Self.cachedCodexFileNeedsPriorityRescan(cached, context: context) else { return false }
if context.needsForkAttributionMigration,
Self.isLegacyForkAttributionCandidate(cached)
{
return false
}

let sessionAlreadyContributed = cached.sessionId.map { state.contributingSessionIds.contains($0) } ?? false
let cachedRows = cached.codexRows ?? []
Expand Down Expand Up @@ -1131,7 +1156,13 @@ extension CostUsageScanner {
if let cached = input.cached {
self.applyFileDays(cache: &cache, fileDays: cached.days, sign: -1)
}
let migratedCached = input.cached.map { Self.codexFileUsageWithCostCache($0, context: context) }
// A legacy parent-dependent fork file cannot carry unreparsed days across this migration: the
// current request may not cover its suspect day. Drop its retained projection and stamp
// the file current only from fresh source rows.
let hasLegacyForkCandidate = input.cached.map { Self.isLegacyForkAttributionCandidate($0) } ?? false
let migratedCached = hasLegacyForkCandidate
? nil
: input.cached.map { Self.codexFileUsageWithCostCache($0, context: context) }
var usageDays = context.dropDeferredCodexRows
? [:]
: Self.fileDaysOutsideScanWindow(migratedCached?.days ?? [:], range: context.range)
Expand All @@ -1140,6 +1171,7 @@ extension CostUsageScanner {
fileURL: input.fileURL,
range: context.range,
inheritedTotalsResolver: context.resources.inheritedResolver.inheritedTotals(for:atOrBefore:),
inheritedRawTotalsResolver: context.resources.inheritedResolver.rawTotals(for:atOrBefore:),
checkCancellation: context.checkCancellation)
let forkBaselineDependencyKey = Self.codexForkBaselineDependencyKey(
parentSessionId: parsed.forkedFromId,
Expand Down Expand Up @@ -1237,6 +1269,7 @@ extension CostUsageScanner {
codexRows: context.dropDeferredCodexRows
? nil
: Self.mergeCodexRows(migratedCached?.codexRows, rows: uniqueRows, sessionId: sessionId))
cache.files[input.metadata.path]?.codexForkAttributionVersion = Self.codexForkAttributionVersion
Self.applyFileDays(cache: &cache, fileDays: cache.files[input.metadata.path]?.days ?? [:], sign: 1)
Self.rememberScannedCodexFile(
input: input,
Expand Down Expand Up @@ -1366,8 +1399,10 @@ extension CostUsageScanner {
let catalogResolver = CodexModelsDevCatalogResolver(
catalog: modelsDevCatalog,
cacheRoot: modelsDevCacheRoot)
var reportCache = cache
for (path, usage) in cache.files where self.needsCodexCostCache(usage, range: range) {
// A compatible p48 cache may hydrate before migration completes. Do not present a
// parent-dependent candidate; current files and sentinel-owned forks remain visible.
var reportCache = Self.codexCacheForPresentation(cache)
for (path, usage) in reportCache.files where self.needsCodexCostCache(usage, range: range) {
reportCache.files[path] = self.codexFileUsageWithCostCache(
usage,
range: range,
Expand Down
Loading
Loading