From 9245047c85395634fedf301d51e645b09d1e4382 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Mon, 13 Jul 2026 11:43:01 -0400 Subject: [PATCH 01/11] Add shadow Codex lineage ledger --- .../Providers/Codex/CodexLineageLedger.swift | 163 +++++++++++++++ .../CodexLineageLedgerTests.swift | 195 ++++++++++++++++++ 2 files changed, 358 insertions(+) create mode 100644 Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift create mode 100644 Tests/CodexBarTests/CodexLineageLedgerTests.swift diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift new file mode 100644 index 0000000000..ad798a76a3 --- /dev/null +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift @@ -0,0 +1,163 @@ +import Foundation + +/// Experimental accounting model for Codex rollout families. +/// +/// Rollout files are overlapping physical views of a logical lineage. The ledger builds the +/// transitive lineage first, then admits each complete token observation once per lineage. +/// It intentionally does not participate in production cost totals yet. +enum CodexLineageLedger { + struct Totals: Equatable, Hashable, Sendable { + var input: Int + var cached: Int + var output: Int + + static let zero = Self(input: 0, cached: 0, output: 0) + + mutating func add(_ other: Self) { + self.input += other.input + self.cached += other.cached + self.output += other.output + } + } + + struct Observation: Equatable, Sendable { + let timestamp: String + let last: Totals + let total: Totals + } + + struct Document: Equatable, Sendable { + /// Canonical owner from the rollout filename when available. + let ownerID: String + /// Session identity persisted in metadata. Fork copies may retain an ancestor identity. + let metadataSessionID: String? + let parentSessionID: String? + let observations: [Observation] + } + + struct Report: Equatable, Sendable { + let utcDays: [String: Totals] + let localDays: [String: Totals] + let componentCount: Int + let acceptedObservationCount: Int + let duplicateObservationCount: Int + } + + enum LedgerError: Error, Equatable { + case emptyOwnerID + case invalidTimestamp(String) + } + + static func reconcile(documents: [Document], localTimeZone: TimeZone) throws -> Report { + var graph = DisjointSet() + for document in documents { + guard !document.ownerID.isEmpty else { throw LedgerError.emptyOwnerID } + graph.insert(document.ownerID) + if let metadataSessionID = Self.nonEmpty(document.metadataSessionID) { + graph.union(document.ownerID, metadataSessionID) + } + if let parentSessionID = Self.nonEmpty(document.parentSessionID) { + graph.union(document.ownerID, parentSessionID) + } + } + + var acceptedByComponent: [String: [Fingerprint: AcceptedObservation]] = [:] + var physicalObservationCount = 0 + for document in documents { + let componentID = graph.find(document.ownerID) + var accepted = acceptedByComponent[componentID] ?? [:] + for observation in document.observations { + physicalObservationCount += 1 + let date = try Self.date(from: observation.timestamp) + let fingerprint = Fingerprint(last: observation.last, total: observation.total) + if let existing = accepted[fingerprint], existing.date <= date { + continue + } + accepted[fingerprint] = AcceptedObservation(date: date, last: observation.last) + } + acceptedByComponent[componentID] = accepted + } + + var utcDays: [String: Totals] = [:] + var localDays: [String: Totals] = [:] + var acceptedObservationCount = 0 + for accepted in acceptedByComponent.values { + acceptedObservationCount += accepted.count + for observation in accepted.values { + Self.add(observation.last, on: observation.date, timeZone: .gmt, to: &utcDays) + Self.add(observation.last, on: observation.date, timeZone: localTimeZone, to: &localDays) + } + } + + return Report( + utcDays: utcDays, + localDays: localDays, + componentCount: Set(documents.map { graph.find($0.ownerID) }).count, + acceptedObservationCount: acceptedObservationCount, + duplicateObservationCount: physicalObservationCount - acceptedObservationCount) + } + + private struct Fingerprint: Equatable, Hashable { + let last: Totals + let total: Totals + } + + private struct AcceptedObservation { + let date: Date + let last: Totals + } + + private static func nonEmpty(_ value: String?) -> String? { + guard let value, !value.isEmpty else { return nil } + return value + } + + private static func date(from timestamp: String) throws -> Date { + guard let date = CostUsageScanner.dateFromTimestamp(timestamp) else { + throw LedgerError.invalidTimestamp(timestamp) + } + return date + } + + private static func add( + _ totals: Totals, + on date: Date, + timeZone: TimeZone, + to days: inout [String: Totals]) + { + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = timeZone + let components = calendar.dateComponents([.year, .month, .day], from: date) + guard let year = components.year, let month = components.month, let day = components.day else { return } + let key = String(format: "%04d-%02d-%02d", year, month, day) + var dayTotals = days[key] ?? .zero + dayTotals.add(totals) + days[key] = dayTotals + } + + private struct DisjointSet { + private var parents: [String: String] = [:] + + mutating func insert(_ item: String) { + if self.parents[item] == nil { + self.parents[item] = item + } + } + + mutating func find(_ item: String) -> String { + self.insert(item) + guard let parent = self.parents[item], parent != item else { return item } + let root = self.find(parent) + self.parents[item] = root + return root + } + + mutating func union(_ first: String, _ second: String) { + let firstRoot = self.find(first) + let secondRoot = self.find(second) + if firstRoot != secondRoot { + self.parents[secondRoot] = firstRoot + } + } + } +} diff --git a/Tests/CodexBarTests/CodexLineageLedgerTests.swift b/Tests/CodexBarTests/CodexLineageLedgerTests.swift new file mode 100644 index 0000000000..434e1e7572 --- /dev/null +++ b/Tests/CodexBarTests/CodexLineageLedgerTests.swift @@ -0,0 +1,195 @@ +import Foundation +import Testing +@testable import CodexBarCore + +struct CodexLineageLedgerTests { + @Test + func `transitive lineage counts copied observations once`() throws { + let first = Self.observation(timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) + let second = Self.observation(timestamp: "2026-07-09T12:01:00Z", input: 50, totalInput: 150) + let third = Self.observation(timestamp: "2026-07-09T12:02:00Z", input: 25, totalInput: 175) + let documents = [ + Self.document(owner: "root", observations: [first]), + Self.document(owner: "child", metadata: "root", parent: "root", observations: [first, second]), + Self.document(owner: "grandchild", metadata: "child", parent: "child", observations: [ + first, + second, + third, + ]), + ] + + let report = try CodexLineageLedger.reconcile( + documents: documents, + localTimeZone: #require(TimeZone(identifier: "America/New_York"))) + + #expect(report.utcDays["2026-07-09"]?.input == 175) + #expect(report.localDays["2026-07-09"]?.input == 175) + #expect(report.componentCount == 1) + #expect(report.acceptedObservationCount == 3) + #expect(report.duplicateObservationCount == 3) + } + + @Test + func `equal observations in disconnected lineages remain additive`() throws { + let observation = Self.observation(timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) + let report = try CodexLineageLedger.reconcile( + documents: [ + Self.document(owner: "first", observations: [observation]), + Self.document(owner: "second", observations: [observation]), + ], + localTimeZone: #require(TimeZone(identifier: "America/New_York"))) + + #expect(report.utcDays["2026-07-09"]?.input == 200) + #expect(report.componentCount == 2) + #expect(report.acceptedObservationCount == 2) + #expect(report.duplicateObservationCount == 0) + } + + @Test + func `unchanged state reemissions within a lineage count once`() throws { + let observation = Self.observation(timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) + let report = try CodexLineageLedger.reconcile( + documents: [Self.document(owner: "root", observations: [observation, observation, observation])], + localTimeZone: #require(TimeZone(identifier: "America/New_York"))) + + #expect(report.utcDays["2026-07-09"]?.input == 100) + #expect(report.acceptedObservationCount == 1) + #expect(report.duplicateObservationCount == 2) + } + + @Test + func `complete token state distinguishes observations within a lineage`() throws { + let first = Self.observation( + timestamp: "2026-07-09T12:00:00Z", + input: 100, + cached: 40, + output: 10, + totalInput: 100) + let changedTotal = Self.observation( + timestamp: "2026-07-09T12:01:00Z", + input: 100, + cached: 40, + output: 10, + totalInput: 200) + let changedLast = Self.observation( + timestamp: "2026-07-09T12:02:00Z", + input: 125, + cached: 50, + output: 15, + totalInput: 200) + let report = try CodexLineageLedger.reconcile( + documents: [ + Self.document(owner: "root", observations: [first, changedTotal, changedLast]), + ], + localTimeZone: #require(TimeZone(identifier: "America/New_York"))) + + #expect(report.utcDays["2026-07-09"] == .init(input: 325, cached: 130, output: 35)) + #expect(report.acceptedObservationCount == 3) + #expect(report.duplicateObservationCount == 0) + } + + @Test + func `UTC and local projections preserve their distinct day boundaries`() throws { + let observation = Self.observation(timestamp: "2026-07-10T02:00:00Z", input: 100, totalInput: 100) + let report = try CodexLineageLedger.reconcile( + documents: [Self.document(owner: "root", observations: [observation])], + localTimeZone: #require(TimeZone(identifier: "America/New_York"))) + + #expect(report.utcDays["2026-07-10"]?.input == 100) + #expect(report.localDays["2026-07-09"]?.input == 100) + } + + @Test(arguments: [ + ("archived-fork-33ce-3869", 15_309_178), + ("live-fork-4d90-52bf", 26_801_911), + ]) + func `sanitized fork fixtures collapse copied prefixes and unchanged reemissions`( + fixtureName: String, + expectedTokens: Int) throws + { + let fixture = try SanitizedForkFamilyFixture.load(named: fixtureName) + let parentMetadata = try fixture.sessionMetadata(named: "parent") + let childMetadata = try fixture.sessionMetadata(named: "child") + let parent = try fixture.events(named: "parent") + let child = try fixture.events(named: "child") + let documents = [ + CodexLineageLedger.Document( + ownerID: "parent-owner", + metadataSessionID: parentMetadata.id, + parentSessionID: parentMetadata.forkedFromID, + observations: parent.map(Self.observation)), + CodexLineageLedger.Document( + ownerID: "child-owner", + metadataSessionID: childMetadata.id, + parentSessionID: childMetadata.forkedFromID, + observations: child.map(Self.observation)), + ] + + let report = try CodexLineageLedger.reconcile( + documents: documents, + localTimeZone: #require(TimeZone(identifier: "America/New_York"))) + let total = report.utcDays.values.reduce(0) { partial, totals in + partial + totals.input + totals.output + } + + #expect(total == expectedTokens) + #expect(total < fixture.manifest.oracle.dedupedLastTokens) + } + + @Test + func `document order does not change lineage totals or attribution`() throws { + let copiedLater = Self.observation(timestamp: "2026-07-10T00:05:00Z", input: 100, totalInput: 100) + let original = Self.observation(timestamp: "2026-07-09T23:55:00Z", input: 100, totalInput: 100) + let root = Self.document(owner: "root", observations: [original]) + let child = Self.document(owner: "child", parent: "root", observations: [copiedLater]) + let timeZone = try #require(TimeZone(identifier: "America/New_York")) + + let forward = try CodexLineageLedger.reconcile(documents: [root, child], localTimeZone: timeZone) + let reversed = try CodexLineageLedger.reconcile(documents: [child, root], localTimeZone: timeZone) + + #expect(forward == reversed) + #expect(forward.utcDays["2026-07-09"]?.input == 100) + #expect(forward.utcDays["2026-07-10"] == nil) + } + + private static func document( + owner: String, + metadata: String? = nil, + parent: String? = nil, + observations: [CodexLineageLedger.Observation]) -> CodexLineageLedger.Document + { + .init( + ownerID: owner, + metadataSessionID: metadata, + parentSessionID: parent, + observations: observations) + } + + private static func observation( + timestamp: String, + input: Int, + cached: Int = 0, + output: Int = 0, + totalInput: Int) -> CodexLineageLedger.Observation + { + .init( + timestamp: timestamp, + last: .init(input: input, cached: cached, output: output), + total: .init(input: totalInput, cached: cached, output: output)) + } + + private static func observation( + _ event: SanitizedForkFamilyFixture.TokenEvent) -> CodexLineageLedger.Observation + { + .init( + timestamp: event.timestamp, + last: .init( + input: event.last.inputTokens, + cached: event.last.cachedInputTokens, + output: event.last.outputTokens), + total: .init( + input: event.total.inputTokens, + cached: event.total.cachedInputTokens, + output: event.total.outputTokens)) + } +} From c731bd1c83bcc048548c6f02279222628bb0e7b8 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Mon, 13 Jul 2026 12:52:24 -0400 Subject: [PATCH 02/11] Adapt rollout snapshots for lineage accounting --- .../Generated/CodexParserHash.generated.swift | 2 +- .../Vendored/CostUsage/CostUsageScanner.swift | 56 +++++++++++++++-- .../CodexLineageLedgerTests.swift | 61 +++++++++++++++++++ 3 files changed, 114 insertions(+), 5 deletions(-) diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index 085fda188b..2a1047a65a 100644 --- a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift +++ b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift @@ -1,5 +1,5 @@ // Generated by Scripts/regenerate-codex-parser-hash.sh. Do not edit by hand. enum CodexParserHash { - static let value = "cdef6eb9658a43e2" + static let value = "865e101428b49ab7" } diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift index fef88de04f..9bda419158 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift @@ -98,6 +98,13 @@ enum CostUsageScanner { let totals: CostUsageCodexTotals } + private struct CodexParsedTokenEvidence { + let sessionId: String? + let forkedFromId: String? + let snapshots: [CodexTimestampedTotals] + let observations: [CodexLineageLedger.Observation] + } + enum CodexForkBaseline { case resolved(CostUsageCodexTotals?) case unresolved @@ -1680,13 +1687,13 @@ enum CostUsageScanner { private static func parseCodexTokenSnapshots( fileURL: URL, - checkCancellation: CancellationCheck? = nil) throws -> ( - sessionId: String?, - snapshots: [CodexTimestampedTotals]) + checkCancellation: CancellationCheck? = nil) throws -> CodexParsedTokenEvidence { var sessionId: String? + var forkedFromId: String? var accumulator = CodexSnapshotAccumulator() var snapshots: [CodexTimestampedTotals] = [] + var observations: [CodexLineageLedger.Observation] = [] var warnedAboutUnparsedTimestamp = false func parsedSnapshotDate(timestamp: String) -> Date? { @@ -1708,6 +1715,12 @@ enum CostUsageScanner { timestamp: timestamp, date: parsedSnapshotDate(timestamp: timestamp), totals: counted)) + if let last, let total { + observations.append(CodexLineageLedger.Observation( + timestamp: timestamp, + last: Self.lineageTotals(last), + total: Self.lineageTotals(total))) + } } do { @@ -1724,6 +1737,9 @@ enum CostUsageScanner { if sessionId == nil { sessionId = metadata.sessionId } + if forkedFromId == nil { + forkedFromId = metadata.forkedFromId + } case let .tokenCount(record): appendSnapshot(timestamp: record.timestamp, last: record.last, total: record.total) case .turnContext, .taskStarted: @@ -1746,6 +1762,9 @@ enum CostUsageScanner { ?? obj["sessionId"] as? String ?? obj["id"] as? String } + if forkedFromId == nil { + forkedFromId = Self.codexForkParentId(from: payload) + } return } @@ -1785,7 +1804,36 @@ enum CostUsageScanner { metadata: ["path": fileURL.path, "error": error.localizedDescription]) } - return (sessionId, snapshots) + return CodexParsedTokenEvidence( + sessionId: sessionId, + forkedFromId: forkedFromId, + snapshots: snapshots, + observations: observations) + } + + static func parseCodexLineageDocument( + fileURL: URL, + checkCancellation: CancellationCheck? = nil) throws -> CodexLineageLedger.Document + { + let parsed = try Self.parseCodexTokenSnapshots( + fileURL: fileURL, + checkCancellation: checkCancellation) + return CodexLineageLedger.Document( + ownerID: Self.codexRolloutOwnerID(fileURL: fileURL) ?? parsed.sessionId ?? fileURL.standardizedFileURL.path, + metadataSessionID: parsed.sessionId, + parentSessionID: parsed.forkedFromId, + observations: parsed.observations) + } + + private static func lineageTotals(_ totals: CostUsageCodexTotals) -> CodexLineageLedger.Totals { + CodexLineageLedger.Totals(input: totals.input, cached: totals.cached, output: totals.output) + } + + private static func codexRolloutOwnerID(fileURL: URL) -> String? { + let stem = fileURL.deletingPathExtension().lastPathComponent + guard stem.count >= 36 else { return nil } + let candidate = String(stem.suffix(36)) + return UUID(uuidString: candidate) == nil ? nil : candidate.lowercased() } static func parseCodexFile( diff --git a/Tests/CodexBarTests/CodexLineageLedgerTests.swift b/Tests/CodexBarTests/CodexLineageLedgerTests.swift index 434e1e7572..9a6c46c808 100644 --- a/Tests/CodexBarTests/CodexLineageLedgerTests.swift +++ b/Tests/CodexBarTests/CodexLineageLedgerTests.swift @@ -3,6 +3,56 @@ import Testing @testable import CodexBarCore struct CodexLineageLedgerTests { + @Test + func `fast rollout parser adapts complete token states into a ledger document`() throws { + let environment = try CostUsageTestEnvironment() + defer { environment.cleanup() } + let ownerID = "019f55a1-7f6e-70c0-8e4f-f5bbefa9b7ac" + let fileURL = environment.root.appendingPathComponent( + "rollout-2026-07-09T12-00-00-\(ownerID).jsonl") + let contents = [ + #"{"type":"session_meta","payload":{"id":"metadata-id","forked_from_id":"parent-id"}}"#, + Self.tokenCountLine( + timestamp: "2026-07-09T12:00:00Z", + last: (input: 100, cached: 40, output: 10), + total: (input: 100, cached: 40, output: 10)), + Self.tokenCountLine( + timestamp: "2026-07-09T12:01:00Z", + last: (input: 50, cached: 20, output: 5), + total: (input: 150, cached: 60, output: 15)), + #"{"type":"event_msg","timestamp":"2026-07-09T12:02:00Z","payload":{"type":"token_count","info":{"# + + #""total_token_usage":{"input_tokens":200,"cached_input_tokens":80,"output_tokens":20}}}}"#, + ].joined(separator: "\n") + try contents.write(to: fileURL, atomically: true, encoding: .utf8) + + let document = try CostUsageScanner.parseCodexLineageDocument(fileURL: fileURL) + + #expect(document.ownerID == ownerID) + #expect(document.metadataSessionID == "metadata-id") + #expect(document.parentSessionID == "parent-id") + #expect(document.observations.count == 2) + #expect(document.observations[0].last == .init(input: 100, cached: 40, output: 10)) + #expect(document.observations[1].total == .init(input: 150, cached: 60, output: 15)) + } + + @Test + func `ledger document parsing propagates cancellation`() throws { + let environment = try CostUsageTestEnvironment() + defer { environment.cleanup() } + let fileURL = environment.root.appendingPathComponent("rollout-without-owner.jsonl") + try Self.tokenCountLine( + timestamp: "2026-07-09T12:00:00Z", + last: (input: 100, cached: 0, output: 10), + total: (input: 100, cached: 0, output: 10)) + .write(to: fileURL, atomically: true, encoding: .utf8) + + #expect(throws: CancellationError.self) { + _ = try CostUsageScanner.parseCodexLineageDocument( + fileURL: fileURL, + checkCancellation: { throw CancellationError() }) + } + } + @Test func `transitive lineage counts copied observations once`() throws { let first = Self.observation(timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) @@ -192,4 +242,15 @@ struct CodexLineageLedgerTests { cached: event.total.cachedInputTokens, output: event.total.outputTokens)) } + + private static func tokenCountLine( + timestamp: String, + last: (input: Int, cached: Int, output: Int), + total: (input: Int, cached: Int, output: Int)) -> String + { + #"{"type":"event_msg","timestamp":"\#(timestamp)","payload":{"type":"token_count","info":{"# + + #""last_token_usage":{"input_tokens":\#(last.input),"cached_input_tokens":\#(last.cached),"# + + #""output_tokens":\#(last.output)},"total_token_usage":{"input_tokens":\#(total.input),"# + + #""cached_input_tokens":\#(total.cached),"output_tokens":\#(total.output)}}}}"# + } } From 41dc8aaf98f2fea3c583e2bd2106377d710b6988 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Tue, 14 Jul 2026 00:37:44 -0400 Subject: [PATCH 03/11] Preserve rollout event identity --- .../Providers/Codex/CodexLineageLedger.swift | 27 +++++++++++--- .../Vendored/CostUsage/CostUsageScanner.swift | 35 ++++++++++++++++--- .../CodexLineageLedgerTests.swift | 20 +++++++++++ 3 files changed, 74 insertions(+), 8 deletions(-) diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift index ad798a76a3..b256b11bdc 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift @@ -21,9 +21,17 @@ enum CodexLineageLedger { } struct Observation: Equatable, Sendable { + let eventID: String? let timestamp: String let last: Totals let total: Totals + + init(eventID: String? = nil, timestamp: String, last: Totals, total: Totals) { + self.eventID = eventID + self.timestamp = timestamp + self.last = last + self.total = total + } } struct Document: Equatable, Sendable { @@ -61,7 +69,7 @@ enum CodexLineageLedger { } } - var acceptedByComponent: [String: [Fingerprint: AcceptedObservation]] = [:] + var acceptedByComponent: [String: [ObservationIdentity: AcceptedObservation]] = [:] var physicalObservationCount = 0 for document in documents { let componentID = graph.find(document.ownerID) @@ -69,11 +77,13 @@ enum CodexLineageLedger { for observation in document.observations { physicalObservationCount += 1 let date = try Self.date(from: observation.timestamp) - let fingerprint = Fingerprint(last: observation.last, total: observation.total) - if let existing = accepted[fingerprint], existing.date <= date { + let identity = ObservationIdentity( + eventID: Self.nonEmpty(observation.eventID), + fingerprint: Fingerprint(last: observation.last, total: observation.total)) + if let existing = accepted[identity], existing.date <= date { continue } - accepted[fingerprint] = AcceptedObservation(date: date, last: observation.last) + accepted[identity] = AcceptedObservation(date: date, last: observation.last) } acceptedByComponent[componentID] = accepted } @@ -102,6 +112,15 @@ enum CodexLineageLedger { let total: Totals } + private enum ObservationIdentity: Equatable, Hashable { + case event(String) + case fingerprint(Fingerprint) + + init(eventID: String?, fingerprint: Fingerprint) { + self = eventID.map(Self.event) ?? .fingerprint(fingerprint) + } + } + private struct AcceptedObservation { let date: Date let last: Totals diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift index 9bda419158..d954979289 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift @@ -1695,6 +1695,8 @@ enum CostUsageScanner { var snapshots: [CodexTimestampedTotals] = [] var observations: [CodexLineageLedger.Observation] = [] var warnedAboutUnparsedTimestamp = false + var currentTurnID: String? + var tokenEventCountByTurn: [String: Int] = [:] func parsedSnapshotDate(timestamp: String) -> Date? { let date = Self.dateFromTimestamp(timestamp) @@ -1708,15 +1710,26 @@ enum CostUsageScanner { return date } - func appendSnapshot(timestamp: String, last: CostUsageCodexTotals?, total: CostUsageCodexTotals?) { + func appendSnapshot( + timestamp: String, + turnID: String?, + last: CostUsageCodexTotals?, + total: CostUsageCodexTotals?) + { guard last != nil || total != nil else { return } let counted = accumulator.apply(last: last, total: total) snapshots.append(CodexTimestampedTotals( timestamp: timestamp, date: parsedSnapshotDate(timestamp: timestamp), totals: counted)) + let eventID = turnID.map { turnID in + let ordinal = tokenEventCountByTurn[turnID, default: 0] + tokenEventCountByTurn[turnID] = ordinal + 1 + return "\(turnID):\(ordinal)" + } if let last, let total { observations.append(CodexLineageLedger.Observation( + eventID: eventID, timestamp: timestamp, last: Self.lineageTotals(last), total: Self.lineageTotals(total))) @@ -1741,8 +1754,14 @@ enum CostUsageScanner { forkedFromId = metadata.forkedFromId } case let .tokenCount(record): - appendSnapshot(timestamp: record.timestamp, last: record.last, total: record.total) - case .turnContext, .taskStarted: + appendSnapshot( + timestamp: record.timestamp, + turnID: record.turnID ?? currentTurnID, + last: record.last, + total: record.total) + case let .taskStarted(turnID): + currentTurnID = turnID + case .turnContext: break } return @@ -1770,6 +1789,10 @@ enum CostUsageScanner { guard obj["type"] as? String == "event_msg" else { return } guard let payload = obj["payload"] as? [String: Any] else { return } + if payload["type"] as? String == "task_started" { + currentTurnID = Self.codexTurnID(from: payload) + return + } guard payload["type"] as? String == "token_count" else { return } guard let info = payload["info"] as? [String: Any] else { return } guard let timestamp = obj["timestamp"] as? String else { return } @@ -1793,7 +1816,11 @@ enum CostUsageScanner { cached: max(0, toInt($0["cached_input_tokens"] ?? $0["cache_read_input_tokens"])), output: max(0, toInt($0["output_tokens"]))) } - appendSnapshot(timestamp: timestamp, last: last, total: total) + appendSnapshot( + timestamp: timestamp, + turnID: Self.codexTurnID(from: payload) ?? currentTurnID, + last: last, + total: total) } }) } catch is CancellationError { diff --git a/Tests/CodexBarTests/CodexLineageLedgerTests.swift b/Tests/CodexBarTests/CodexLineageLedgerTests.swift index 9a6c46c808..6614cc2620 100644 --- a/Tests/CodexBarTests/CodexLineageLedgerTests.swift +++ b/Tests/CodexBarTests/CodexLineageLedgerTests.swift @@ -12,6 +12,7 @@ struct CodexLineageLedgerTests { "rollout-2026-07-09T12-00-00-\(ownerID).jsonl") let contents = [ #"{"type":"session_meta","payload":{"id":"metadata-id","forked_from_id":"parent-id"}}"#, + #"{"type":"event_msg","payload":{"type":"task_started","turn_id":"turn-a"}}"#, Self.tokenCountLine( timestamp: "2026-07-09T12:00:00Z", last: (input: 100, cached: 40, output: 10), @@ -31,6 +32,7 @@ struct CodexLineageLedgerTests { #expect(document.metadataSessionID == "metadata-id") #expect(document.parentSessionID == "parent-id") #expect(document.observations.count == 2) + #expect(document.observations.map(\.eventID) == ["turn-a:0", "turn-a:1"]) #expect(document.observations[0].last == .init(input: 100, cached: 40, output: 10)) #expect(document.observations[1].total == .init(input: 150, cached: 60, output: 15)) } @@ -95,6 +97,22 @@ struct CodexLineageLedgerTests { #expect(report.duplicateObservationCount == 0) } + @Test + func `copy stable identities preserve independent equal observations`() throws { + let copied = Self.observation(eventID: "turn-a:0", timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) + let independent = Self.observation(eventID: "turn-b:0", timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) + let report = try CodexLineageLedger.reconcile( + documents: [ + Self.document(owner: "root", observations: [copied]), + Self.document(owner: "child", parent: "root", observations: [copied, independent]), + ], + localTimeZone: .gmt) + + #expect(report.utcDays["2026-07-09"]?.input == 200) + #expect(report.acceptedObservationCount == 2) + #expect(report.duplicateObservationCount == 1) + } + @Test func `unchanged state reemissions within a lineage count once`() throws { let observation = Self.observation(timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) @@ -216,6 +234,7 @@ struct CodexLineageLedgerTests { } private static func observation( + eventID: String? = nil, timestamp: String, input: Int, cached: Int = 0, @@ -223,6 +242,7 @@ struct CodexLineageLedgerTests { totalInput: Int) -> CodexLineageLedger.Observation { .init( + eventID: eventID, timestamp: timestamp, last: .init(input: input, cached: cached, output: output), total: .init(input: totalInput, cached: cached, output: output)) From 5cce73b32414b402a983b5c2a7e9d7e067568dbe Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Tue, 14 Jul 2026 00:51:31 -0400 Subject: [PATCH 04/11] Refresh lineage parser fingerprint --- Sources/CodexBarCore/Generated/CodexParserHash.generated.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index 2a1047a65a..3c8176daa7 100644 --- a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift +++ b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift @@ -1,5 +1,5 @@ // Generated by Scripts/regenerate-codex-parser-hash.sh. Do not edit by hand. enum CodexParserHash { - static let value = "865e101428b49ab7" + static let value = "986639c31dca15d9" } From 9280c367731e0e557a298a411c24819a993f47d1 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Mon, 13 Jul 2026 13:07:49 -0400 Subject: [PATCH 05/11] Discover referenced Codex lineage parents --- .../Generated/CodexParserHash.generated.swift | 2 +- .../Codex/CodexLineageDiscovery.swift | 137 ++++++++++++++++++ .../Vendored/CostUsage/CostUsageScanner.swift | 2 +- .../CodexLineageDiscoveryTests.swift | 120 +++++++++++++++ 4 files changed, 259 insertions(+), 2 deletions(-) create mode 100644 Sources/CodexBarCore/Providers/Codex/CodexLineageDiscovery.swift create mode 100644 Tests/CodexBarTests/CodexLineageDiscoveryTests.swift diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index 3c8176daa7..a27b750991 100644 --- a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift +++ b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift @@ -1,5 +1,5 @@ // Generated by Scripts/regenerate-codex-parser-hash.sh. Do not edit by hand. enum CodexParserHash { - static let value = "986639c31dca15d9" + static let value = "f1fe17eb233c9f4d" } diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageDiscovery.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageDiscovery.swift new file mode 100644 index 0000000000..0b75a82e87 --- /dev/null +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageDiscovery.swift @@ -0,0 +1,137 @@ +import Foundation + +/// Expands an already bounded rollout-file selection with only the ancestors its documents reference. +enum CodexLineageDiscovery { + struct Report: Equatable, Sendable { + let documents: [CodexLineageLedger.Document] + let referencedParentDocumentCount: Int + let unresolvedParentIDs: Set + } + + static func discover( + includedFiles: [URL], + roots: [URL], + checkCancellation: CostUsageScanner.CancellationCheck? = nil) throws -> Report + { + var locator = ParentFileLocator(roots: roots, checkCancellation: checkCancellation) + var documents: [CodexLineageLedger.Document] = [] + var knownIDs: Set = [] + var seenPaths: Set = [] + var pendingParentIDs: [String] = [] + var unresolvedParentIDs: Set = [] + var referencedParentDocumentCount = 0 + + func remember(_ document: CodexLineageLedger.Document) { + documents.append(document) + knownIDs.insert(Self.canonicalSessionID(document.ownerID)) + if let metadataSessionID = Self.nonEmpty(document.metadataSessionID) { + knownIDs.insert(Self.canonicalSessionID(metadataSessionID)) + } + if let parentSessionID = Self.nonEmpty(document.parentSessionID) { + pendingParentIDs.append(Self.canonicalSessionID(parentSessionID)) + } + } + + for fileURL in includedFiles.sorted(by: { $0.path < $1.path }) { + try checkCancellation?() + let path = fileURL.standardizedFileURL.path + guard seenPaths.insert(path).inserted else { continue } + try remember(CostUsageScanner.parseCodexLineageDocument( + fileURL: fileURL, + checkCancellation: checkCancellation)) + } + + var nextParent = 0 + while nextParent < pendingParentIDs.count { + try checkCancellation?() + let parentID = pendingParentIDs[nextParent] + nextParent += 1 + guard !knownIDs.contains(parentID), !unresolvedParentIDs.contains(parentID) else { continue } + guard let parentURL = try locator.fileURL(for: parentID) else { + unresolvedParentIDs.insert(parentID) + continue + } + let path = parentURL.standardizedFileURL.path + guard seenPaths.insert(path).inserted else { + unresolvedParentIDs.insert(parentID) + continue + } + let parent = try CostUsageScanner.parseCodexLineageDocument( + fileURL: parentURL, + checkCancellation: checkCancellation) + let ownerID = Self.canonicalSessionID(parent.ownerID) + let metadataSessionID = parent.metadataSessionID.map(Self.canonicalSessionID) + guard ownerID == parentID || metadataSessionID == parentID else { + unresolvedParentIDs.insert(parentID) + continue + } + referencedParentDocumentCount += 1 + remember(parent) + } + + return Report( + documents: documents, + referencedParentDocumentCount: referencedParentDocumentCount, + unresolvedParentIDs: unresolvedParentIDs) + } + + private static func nonEmpty(_ value: String?) -> String? { + guard let value, !value.isEmpty else { return nil } + return value + } + + private static func canonicalSessionID(_ value: String) -> String { + UUID(uuidString: value)?.uuidString.lowercased() ?? value + } + + private struct ParentFileLocator { + let roots: [URL] + let checkCancellation: CostUsageScanner.CancellationCheck? + var didIndexRoots = false + var filesByID: [String: URL] = [:] + + mutating func fileURL(for sessionID: String) throws -> URL? { + let canonicalID = CodexLineageDiscovery.canonicalSessionID(sessionID) + if let known = self.filesByID[canonicalID] { + return known + } + if !self.didIndexRoots { + try self.indexRoots() + } + return self.filesByID[canonicalID] + } + + private mutating func indexRoots() throws { + self.didIndexRoots = true + var files: [URL] = [] + for root in self.roots { + try self.checkCancellation?() + guard let enumerator = FileManager.default.enumerator( + at: root, + includingPropertiesForKeys: [.isRegularFileKey], + options: [.skipsHiddenFiles, .skipsPackageDescendants]) + else { continue } + while let fileURL = enumerator.nextObject() as? URL { + try self.checkCancellation?() + guard fileURL.pathExtension.lowercased() == "jsonl" else { continue } + files.append(fileURL) + } + } + + for fileURL in files.sorted(by: { $0.path < $1.path }) { + try self.checkCancellation?() + if let ownerID = CostUsageScanner.codexRolloutOwnerID(fileURL: fileURL) { + let canonicalID = CodexLineageDiscovery.canonicalSessionID(ownerID) + self.filesByID[canonicalID] = self.filesByID[canonicalID] ?? fileURL + } + if let metadataID = try CostUsageScanner.parseCodexSessionIdentifier( + fileURL: fileURL, + checkCancellation: self.checkCancellation) + { + let canonicalID = CodexLineageDiscovery.canonicalSessionID(metadataID) + self.filesByID[canonicalID] = self.filesByID[canonicalID] ?? fileURL + } + } + } + } +} diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift index d954979289..a3fc9d93bd 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift @@ -1856,7 +1856,7 @@ enum CostUsageScanner { CodexLineageLedger.Totals(input: totals.input, cached: totals.cached, output: totals.output) } - private static func codexRolloutOwnerID(fileURL: URL) -> String? { + static func codexRolloutOwnerID(fileURL: URL) -> String? { let stem = fileURL.deletingPathExtension().lastPathComponent guard stem.count >= 36 else { return nil } let candidate = String(stem.suffix(36)) diff --git a/Tests/CodexBarTests/CodexLineageDiscoveryTests.swift b/Tests/CodexBarTests/CodexLineageDiscoveryTests.swift new file mode 100644 index 0000000000..9ab3126144 --- /dev/null +++ b/Tests/CodexBarTests/CodexLineageDiscoveryTests.swift @@ -0,0 +1,120 @@ +import Foundation +import Testing +@testable import CodexBarCore + +struct CodexLineageDiscoveryTests { + @Test + func `referenced parents cross the normal window and active archive roots transitively`() throws { + let environment = try CostUsageTestEnvironment() + defer { environment.cleanup() } + let grandparentID = "11111111-1111-4111-8111-111111111111" + let parentID = "22222222-2222-4222-8222-222222222222" + let childID = "33333333-3333-4333-8333-333333333333" + let unrelatedID = "44444444-4444-4444-8444-444444444444" + let grandparent = try Self.writeRollout( + root: environment.codexArchivedSessionsRoot, + relativeDirectory: "", + ownerID: grandparentID, + metadataID: grandparentID) + let parent = try Self.writeRollout( + root: environment.codexSessionsRoot, + relativeDirectory: "2025/01/01", + ownerID: parentID, + metadataID: parentID, + parentID: grandparentID) + let child = try Self.writeRollout( + root: environment.codexSessionsRoot, + relativeDirectory: "2026/07/09", + ownerID: childID, + metadataID: childID, + parentID: parentID) + _ = try Self.writeRollout( + root: environment.codexArchivedSessionsRoot, + relativeDirectory: "", + ownerID: unrelatedID, + metadataID: unrelatedID) + + let report = try CodexLineageDiscovery.discover( + includedFiles: [child], + roots: [environment.codexSessionsRoot, environment.codexArchivedSessionsRoot]) + + #expect(Set(report.documents.map(\.ownerID)) == [childID, parentID, grandparentID]) + #expect(report.documents.map(\.ownerID).contains(unrelatedID) == false) + #expect(report.referencedParentDocumentCount == 2) + #expect(report.unresolvedParentIDs.isEmpty) + #expect(FileManager.default.fileExists(atPath: parent.path)) + #expect(FileManager.default.fileExists(atPath: grandparent.path)) + } + + @Test + func `missing referenced parents are diagnosed without widening included documents`() throws { + let environment = try CostUsageTestEnvironment() + defer { environment.cleanup() } + let child = try Self.writeRollout( + root: environment.codexSessionsRoot, + relativeDirectory: "2026/07/09", + ownerID: "55555555-5555-4555-8555-555555555555", + metadataID: "55555555-5555-4555-8555-555555555555", + parentID: "66666666-6666-4666-8666-666666666666") + + let report = try CodexLineageDiscovery.discover( + includedFiles: [child], + roots: [environment.codexSessionsRoot, environment.codexArchivedSessionsRoot]) + + #expect(report.documents.count == 1) + #expect(report.referencedParentDocumentCount == 0) + #expect(report.unresolvedParentIDs == ["66666666-6666-4666-8666-666666666666"]) + } + + @Test + func `uuid parent references are matched case insensitively against filename owners`() throws { + let environment = try CostUsageTestEnvironment() + defer { environment.cleanup() } + let parentID = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + _ = try Self.writeRollout( + root: environment.codexArchivedSessionsRoot, + relativeDirectory: "", + ownerID: parentID, + metadataID: "parent-metadata-alias") + let child = try Self.writeRollout( + root: environment.codexSessionsRoot, + relativeDirectory: "2026/07/09", + ownerID: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + metadataID: "child-metadata", + parentID: parentID.uppercased()) + + let report = try CodexLineageDiscovery.discover( + includedFiles: [child], + roots: [environment.codexSessionsRoot, environment.codexArchivedSessionsRoot]) + + #expect(report.documents.map(\.ownerID).contains(parentID)) + #expect(report.referencedParentDocumentCount == 1) + #expect(report.unresolvedParentIDs.isEmpty) + } + + private static func writeRollout( + root: URL, + relativeDirectory: String, + ownerID: String, + metadataID: String, + parentID: String? = nil) throws -> URL + { + let directory = relativeDirectory.isEmpty + ? root + : root.appendingPathComponent(relativeDirectory, isDirectory: true) + try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) + let fileURL = directory.appendingPathComponent( + "rollout-2025-01-01T00-00-00-\(ownerID).jsonl") + var metadata = #"{"type":"session_meta","payload":{"id":"\#(metadataID)""# + if let parentID { + metadata += #", "forked_from_id":"\#(parentID)""# + } + metadata += "}}\n" + metadata += #"{"type":"event_msg","timestamp":"2026-07-09T12:00:00Z","payload":{"# + metadata += #""type":"token_count","info":{"last_token_usage":{"input_tokens":10,"# + metadata += #""cached_input_tokens":0,"output_tokens":1},"total_token_usage":{"input_tokens":10,"# + metadata += #""cached_input_tokens":0,"output_tokens":1}}}}"# + try metadata.write(to: fileURL, atomically: true, encoding: .utf8) + return fileURL + } +} From c751e2f2edc66c415be592811bca0c6770bc3488 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Mon, 13 Jul 2026 13:22:05 -0400 Subject: [PATCH 06/11] Preserve lineage accounting dimensions --- .../Generated/CodexParserHash.generated.swift | 2 +- .../Providers/Codex/CodexLineageLedger.swift | 106 +++++++++++++- .../Vendored/CostUsage/CostUsageScanner.swift | 32 ++++- .../CodexLineageLedgerTests.swift | 135 +++++++++++++++--- 4 files changed, 246 insertions(+), 29 deletions(-) diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index a27b750991..b4eeb35463 100644 --- a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift +++ b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift @@ -1,5 +1,5 @@ // Generated by Scripts/regenerate-codex-parser-hash.sh. Do not edit by hand. enum CodexParserHash { - static let value = "f1fe17eb233c9f4d" + static let value = "a231538286ac81c9" } diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift index b256b11bdc..7768273dfd 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift @@ -23,12 +23,20 @@ enum CodexLineageLedger { struct Observation: Equatable, Sendable { let eventID: String? let timestamp: String + let model: String let last: Totals let total: Totals - init(eventID: String? = nil, timestamp: String, last: Totals, total: Totals) { + init( + eventID: String? = nil, + timestamp: String, + model: String = CostUsagePricing.codexUnattributedModel, + last: Totals, + total: Totals) + { self.eventID = eventID self.timestamp = timestamp + self.model = CostUsagePricing.normalizeCodexModel(model) self.last = last self.total = total } @@ -46,11 +54,24 @@ enum CodexLineageLedger { struct Report: Equatable, Sendable { let utcDays: [String: Totals] let localDays: [String: Totals] + let utcRows: [DailyRow] + let localRows: [DailyRow] let componentCount: Int let acceptedObservationCount: Int let duplicateObservationCount: Int } + struct DailyRow: Equatable, Sendable { + let day: String + let model: String + let totals: Totals + let costUSD: Double? + + var isPriced: Bool { + self.costUSD != nil + } + } + enum LedgerError: Error, Equatable { case emptyOwnerID case invalidTimestamp(String) @@ -80,28 +101,44 @@ enum CodexLineageLedger { let identity = ObservationIdentity( eventID: Self.nonEmpty(observation.eventID), fingerprint: Fingerprint(last: observation.last, total: observation.total)) - if let existing = accepted[identity], existing.date <= date { - continue + if let existing = accepted[identity] { + if existing.date < date { + continue + } + if existing.date == date, + !Self.shouldPreferModel(observation.model, over: existing.model) + { + continue + } } - accepted[identity] = AcceptedObservation(date: date, last: observation.last) + accepted[identity] = AcceptedObservation( + date: date, + model: observation.model, + last: observation.last) } acceptedByComponent[componentID] = accepted } var utcDays: [String: Totals] = [:] var localDays: [String: Totals] = [:] + var utcRows: [DailyRowKey: DailyRowValue] = [:] + var localRows: [DailyRowKey: DailyRowValue] = [:] var acceptedObservationCount = 0 for accepted in acceptedByComponent.values { acceptedObservationCount += accepted.count for observation in accepted.values { Self.add(observation.last, on: observation.date, timeZone: .gmt, to: &utcDays) Self.add(observation.last, on: observation.date, timeZone: localTimeZone, to: &localDays) + Self.addRow(observation, timeZone: .gmt, to: &utcRows) + Self.addRow(observation, timeZone: localTimeZone, to: &localRows) } } return Report( utcDays: utcDays, localDays: localDays, + utcRows: Self.dailyRows(from: utcRows), + localRows: Self.dailyRows(from: localRows), componentCount: Set(documents.map { graph.find($0.ownerID) }).count, acceptedObservationCount: acceptedObservationCount, duplicateObservationCount: physicalObservationCount - acceptedObservationCount) @@ -123,14 +160,37 @@ enum CodexLineageLedger { private struct AcceptedObservation { let date: Date + let model: String let last: Totals } + private struct DailyRowKey: Hashable { + let day: String + let model: String + } + + private struct DailyRowValue { + var totals: Totals = .zero + var costUSD = 0.0 + var isPriced = true + } + private static func nonEmpty(_ value: String?) -> String? { guard let value, !value.isEmpty else { return nil } return value } + /// Duplicate copies can carry different model evidence. Keep the earliest physical copy, + /// then resolve equal-time ties deterministically while preferring attributable evidence. + private static func shouldPreferModel(_ candidate: String, over existing: String) -> Bool { + let candidateIsUnknown = CostUsagePricing.isCodexUnattributedModel(candidate) + let existingIsUnknown = CostUsagePricing.isCodexUnattributedModel(existing) + if candidateIsUnknown != existingIsUnknown { + return !candidateIsUnknown + } + return candidate < existing + } + private static func date(from timestamp: String) throws -> Date { guard let date = CostUsageScanner.dateFromTimestamp(timestamp) else { throw LedgerError.invalidTimestamp(timestamp) @@ -154,6 +214,44 @@ enum CodexLineageLedger { days[key] = dayTotals } + private static func addRow( + _ observation: AcceptedObservation, + timeZone: TimeZone, + to rows: inout [DailyRowKey: DailyRowValue]) + { + let key = DailyRowKey(day: self.dayKey(for: observation.date, timeZone: timeZone), model: observation.model) + var value = rows[key] ?? DailyRowValue() + value.totals.add(observation.last) + if let cost = CostUsagePricing.codexCostUSD( + model: observation.model, + inputTokens: observation.last.input, + cachedInputTokens: observation.last.cached, + outputTokens: observation.last.output) + { + value.costUSD += cost + } else { + value.isPriced = false + } + rows[key] = value + } + + private static func dailyRows(from rows: [DailyRowKey: DailyRowValue]) -> [DailyRow] { + rows.map { key, value in + DailyRow( + day: key.day, + model: key.model, + totals: value.totals, + costUSD: value.isPriced ? value.costUSD : nil) + }.sorted { ($0.day, $0.model) < ($1.day, $1.model) } + } + + private static func dayKey(for date: Date, timeZone: TimeZone) -> String { + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = timeZone + let components = calendar.dateComponents([.year, .month, .day], from: date) + return String(format: "%04d-%02d-%02d", components.year ?? 0, components.month ?? 0, components.day ?? 0) + } + private struct DisjointSet { private var parents: [String: String] = [:] diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift index a3fc9d93bd..934f23626b 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift @@ -1685,12 +1685,14 @@ enum CostUsageScanner { return nil } + // swiftlint:disable:next cyclomatic_complexity private static func parseCodexTokenSnapshots( fileURL: URL, checkCancellation: CancellationCheck? = nil) throws -> CodexParsedTokenEvidence { var sessionId: String? var forkedFromId: String? + var currentModel: String? var accumulator = CodexSnapshotAccumulator() var snapshots: [CodexTimestampedTotals] = [] var observations: [CodexLineageLedger.Observation] = [] @@ -1712,6 +1714,7 @@ enum CostUsageScanner { func appendSnapshot( timestamp: String, + model: String?, turnID: String?, last: CostUsageCodexTotals?, total: CostUsageCodexTotals?) @@ -1731,6 +1734,9 @@ enum CostUsageScanner { observations.append(CodexLineageLedger.Observation( eventID: eventID, timestamp: timestamp, + model: Self.codexModelEvidence(model) + ?? Self.codexModelEvidence(currentModel) + ?? CostUsagePricing.codexUnattributedModel, last: Self.lineageTotals(last), total: Self.lineageTotals(total))) } @@ -1756,13 +1762,16 @@ enum CostUsageScanner { case let .tokenCount(record): appendSnapshot( timestamp: record.timestamp, + model: record.model, turnID: record.turnID ?? currentTurnID, last: record.last, total: record.total) + case let .turnContext(model): + if let model { + currentModel = model + } case let .taskStarted(turnID): currentTurnID = turnID - case .turnContext: - break } return } @@ -1787,6 +1796,20 @@ enum CostUsageScanner { return } + if obj["type"] as? String == "turn_context" { + let payload = obj["payload"] as? [String: Any] + let info = payload?["info"] as? [String: Any] + if let model = Self.codexTurnContextModel( + payloadModel: payload?["model"] as? String, + payloadModelName: payload?["model_name"] as? String, + infoModel: info?["model"] as? String, + infoModelName: info?["model_name"] as? String) + { + currentModel = model + } + return + } + guard obj["type"] as? String == "event_msg" else { return } guard let payload = obj["payload"] as? [String: Any] else { return } if payload["type"] as? String == "task_started" { @@ -1816,8 +1839,13 @@ enum CostUsageScanner { cached: max(0, toInt($0["cached_input_tokens"] ?? $0["cache_read_input_tokens"])), output: max(0, toInt($0["output_tokens"]))) } + let model = Self.codexModelEvidence(info["model"] as? String) + ?? Self.codexModelEvidence(info["model_name"] as? String) + ?? Self.codexModelEvidence(payload["model"] as? String) + ?? Self.codexModelEvidence(obj["model"] as? String) appendSnapshot( timestamp: timestamp, + model: model, turnID: Self.codexTurnID(from: payload) ?? currentTurnID, last: last, total: total) diff --git a/Tests/CodexBarTests/CodexLineageLedgerTests.swift b/Tests/CodexBarTests/CodexLineageLedgerTests.swift index 6614cc2620..aca6368d34 100644 --- a/Tests/CodexBarTests/CodexLineageLedgerTests.swift +++ b/Tests/CodexBarTests/CodexLineageLedgerTests.swift @@ -12,7 +12,7 @@ struct CodexLineageLedgerTests { "rollout-2026-07-09T12-00-00-\(ownerID).jsonl") let contents = [ #"{"type":"session_meta","payload":{"id":"metadata-id","forked_from_id":"parent-id"}}"#, - #"{"type":"event_msg","payload":{"type":"task_started","turn_id":"turn-a"}}"#, + #"{"type":"turn_context","payload":{"model":"gpt-5.4"}}"#, Self.tokenCountLine( timestamp: "2026-07-09T12:00:00Z", last: (input: 100, cached: 40, output: 10), @@ -32,11 +32,116 @@ struct CodexLineageLedgerTests { #expect(document.metadataSessionID == "metadata-id") #expect(document.parentSessionID == "parent-id") #expect(document.observations.count == 2) - #expect(document.observations.map(\.eventID) == ["turn-a:0", "turn-a:1"]) + #expect(document.observations[0].model == "gpt-5.4") #expect(document.observations[0].last == .init(input: 100, cached: 40, output: 10)) #expect(document.observations[1].total == .init(input: 150, cached: 60, output: 15)) } + @Test + func `daily rows preserve model token and pricing dimensions`() throws { + let priced = Self.observation( + timestamp: "2026-07-10T02:00:00Z", + model: "gpt-5.4", + input: 100, + cached: 40, + output: 10, + totalInput: 100) + let unpriced = Self.observation( + timestamp: "2026-07-10T03:00:00Z", + model: "future-model", + input: 50, + output: 5, + totalInput: 150) + + let report = try CodexLineageLedger.reconcile( + documents: [Self.document(owner: "root", observations: [priced, unpriced])], + localTimeZone: #require(TimeZone(identifier: "America/New_York"))) + + #expect(report.utcRows.map(\.day) == ["2026-07-10", "2026-07-10"]) + #expect(report.localRows.map(\.day) == ["2026-07-09", "2026-07-09"]) + #expect(report.utcRows[0].model == "future-model") + #expect(report.utcRows[0].totals == .init(input: 50, cached: 0, output: 5)) + #expect(report.utcRows[0].isPriced == false) + #expect(report.utcRows[1].model == "gpt-5.4") + #expect(report.utcRows[1].isPriced) + #expect(report.utcRows[1].costUSD != nil) + } + + @Test + func `token event model overrides older turn context`() throws { + let environment = try CostUsageTestEnvironment() + let ownerID = "11111111-1111-4111-8111-111111111111" + let file = environment.codexSessionsRoot.appendingPathComponent( + "rollout-2026-07-09T12-00-00-\(ownerID).jsonl") + let contents = [ + #"{"type":"turn_context","payload":{"model":"gpt-5.4-mini"}}"#, + Self.tokenCountLine( + timestamp: "2026-07-09T12:00:00Z", + model: "gpt-5.4", + last: (input: 100, cached: 40, output: 10), + total: (input: 100, cached: 40, output: 10)), + ].joined(separator: "\n") + try FileManager.default.createDirectory(at: environment.codexSessionsRoot, withIntermediateDirectories: true) + try contents.write(to: file, atomically: true, encoding: .utf8) + + let document = try CostUsageScanner.parseCodexLineageDocument(fileURL: file) + + #expect(document.observations.map(\.model) == ["gpt-5.4"]) + } + + @Test + func `equal-time duplicate prefers attributed model deterministically`() throws { + let unknown = Self.observation(timestamp: "2026-07-10T03:00:00Z", input: 50, totalInput: 50) + let attributed = Self.observation( + timestamp: "2026-07-10T03:00:00Z", + model: "gpt-5.4", + input: 50, + totalInput: 50) + + let report = try CodexLineageLedger.reconcile( + documents: [ + Self.document(owner: "child", parent: "root", observations: [unknown]), + Self.document(owner: "root", observations: [attributed]), + ], + localTimeZone: .gmt) + + #expect(report.acceptedObservationCount == 1) + #expect(report.utcRows.map(\.model) == ["gpt-5.4"]) + #expect(report.utcRows[0].isPriced) + } + + @Test + func `daily rows price long context observations independently`() throws { + let first = Self.observation( + timestamp: "2026-07-10T03:00:00Z", + model: "gpt-5.4", + input: 272_001, + cached: 100_000, + output: 5, + totalInput: 272_001) + let second = Self.observation( + timestamp: "2026-07-10T04:00:00Z", + model: "gpt-5.4", + input: 100, + output: 5, + totalInput: 272_101) + let expected = try #require(CostUsagePricing.codexCostUSD( + model: "gpt-5.4", + inputTokens: 272_001, + cachedInputTokens: 100_000, + outputTokens: 5)) + #require(CostUsagePricing.codexCostUSD( + model: "gpt-5.4", + inputTokens: 100, + cachedInputTokens: 0, + outputTokens: 5)) + + let report = try CodexLineageLedger.reconcile( + documents: [Self.document(owner: "root", observations: [first, second])], + localTimeZone: .gmt) + + #expect(try abs(#require(report.utcRows.first?.costUSD) - expected) < 0.000001) + } + @Test func `ledger document parsing propagates cancellation`() throws { let environment = try CostUsageTestEnvironment() @@ -97,22 +202,6 @@ struct CodexLineageLedgerTests { #expect(report.duplicateObservationCount == 0) } - @Test - func `copy stable identities preserve independent equal observations`() throws { - let copied = Self.observation(eventID: "turn-a:0", timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) - let independent = Self.observation(eventID: "turn-b:0", timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) - let report = try CodexLineageLedger.reconcile( - documents: [ - Self.document(owner: "root", observations: [copied]), - Self.document(owner: "child", parent: "root", observations: [copied, independent]), - ], - localTimeZone: .gmt) - - #expect(report.utcDays["2026-07-09"]?.input == 200) - #expect(report.acceptedObservationCount == 2) - #expect(report.duplicateObservationCount == 1) - } - @Test func `unchanged state reemissions within a lineage count once`() throws { let observation = Self.observation(timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) @@ -234,16 +323,16 @@ struct CodexLineageLedgerTests { } private static func observation( - eventID: String? = nil, timestamp: String, + model: String = CostUsagePricing.codexUnattributedModel, input: Int, cached: Int = 0, output: Int = 0, totalInput: Int) -> CodexLineageLedger.Observation { .init( - eventID: eventID, timestamp: timestamp, + model: model, last: .init(input: input, cached: cached, output: output), total: .init(input: totalInput, cached: cached, output: output)) } @@ -265,12 +354,14 @@ struct CodexLineageLedgerTests { private static func tokenCountLine( timestamp: String, + model: String? = nil, last: (input: Int, cached: Int, output: Int), total: (input: Int, cached: Int, output: Int)) -> String { - #"{"type":"event_msg","timestamp":"\#(timestamp)","payload":{"type":"token_count","info":{"# + let modelJSON = model.map { #", "model":"\#($0)""# } ?? "" + return #"{"type":"event_msg","timestamp":"\#(timestamp)","payload":{"type":"token_count","info":{"# + #""last_token_usage":{"input_tokens":\#(last.input),"cached_input_tokens":\#(last.cached),"# + #""output_tokens":\#(last.output)},"total_token_usage":{"input_tokens":\#(total.input),"# - + #""cached_input_tokens":\#(total.cached),"output_tokens":\#(total.output)}}}}"# + + #""cached_input_tokens":\#(total.cached),"output_tokens":\#(total.output)}\#(modelJSON)}}}"# } } From 71427b147fa9edcd5df412f7528e470be9e265ff Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Mon, 13 Jul 2026 13:39:06 -0400 Subject: [PATCH 07/11] Run Codex lineage accounting in shadow --- .../Generated/CodexParserHash.generated.swift | 2 +- .../Providers/Codex/CodexLineageLedger.swift | 11 ++- .../Providers/Codex/CodexLineageShadow.swift | 94 +++++++++++++++++++ .../Vendored/CostUsage/CostUsageScanner.swift | 55 ++++++++++- .../CodexLineageShadowTests.swift | 68 ++++++++++++++ 5 files changed, 226 insertions(+), 4 deletions(-) create mode 100644 Sources/CodexBarCore/Providers/Codex/CodexLineageShadow.swift create mode 100644 Tests/CodexBarTests/CodexLineageShadowTests.swift diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index b4eeb35463..d57f41412c 100644 --- a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift +++ b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift @@ -1,5 +1,5 @@ // Generated by Scripts/regenerate-codex-parser-hash.sh. Do not edit by hand. enum CodexParserHash { - static let value = "a231538286ac81c9" + static let value = "07457314442f300f" } diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift index 7768273dfd..c9d8e9b773 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift @@ -77,9 +77,14 @@ enum CodexLineageLedger { case invalidTimestamp(String) } - static func reconcile(documents: [Document], localTimeZone: TimeZone) throws -> Report { + static func reconcile( + documents: [Document], + localTimeZone: TimeZone, + checkCancellation: CostUsageScanner.CancellationCheck? = nil) throws -> Report + { var graph = DisjointSet() for document in documents { + try checkCancellation?() guard !document.ownerID.isEmpty else { throw LedgerError.emptyOwnerID } graph.insert(document.ownerID) if let metadataSessionID = Self.nonEmpty(document.metadataSessionID) { @@ -93,9 +98,11 @@ enum CodexLineageLedger { var acceptedByComponent: [String: [ObservationIdentity: AcceptedObservation]] = [:] var physicalObservationCount = 0 for document in documents { + try checkCancellation?() let componentID = graph.find(document.ownerID) var accepted = acceptedByComponent[componentID] ?? [:] for observation in document.observations { + try checkCancellation?() physicalObservationCount += 1 let date = try Self.date(from: observation.timestamp) let identity = ObservationIdentity( @@ -125,8 +132,10 @@ enum CodexLineageLedger { var localRows: [DailyRowKey: DailyRowValue] = [:] var acceptedObservationCount = 0 for accepted in acceptedByComponent.values { + try checkCancellation?() acceptedObservationCount += accepted.count for observation in accepted.values { + try checkCancellation?() Self.add(observation.last, on: observation.date, timeZone: .gmt, to: &utcDays) Self.add(observation.last, on: observation.date, timeZone: localTimeZone, to: &localDays) Self.addRow(observation, timeZone: .gmt, to: &utcRows) diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageShadow.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageShadow.swift new file mode 100644 index 0000000000..a6938113e0 --- /dev/null +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageShadow.swift @@ -0,0 +1,94 @@ +import Foundation + +/// Privacy-safe comparison between the production file scanner and the experimental lineage ledger. +enum CodexLineageShadow { + struct DayDifference: Equatable, Sendable { + let day: String + let legacy: CodexLineageLedger.Totals + let ledger: CodexLineageLedger.Totals + + var delta: CodexLineageLedger.Totals { + .init( + input: self.ledger.input - self.legacy.input, + cached: self.ledger.cached - self.legacy.cached, + output: self.ledger.output - self.legacy.output) + } + } + + struct Report: Equatable, Sendable { + let days: [DayDifference] + let acceptedObservationCount: Int + let duplicateObservationCount: Int + let componentCount: Int + let referencedParentDocumentCount: Int + let unresolvedParentCount: Int + let rejectedObservationCount: Int + } + + static func run( + includedFiles: [URL], + roots: [URL], + legacyDays: [String: [String: [Int]]], + dayRange: ClosedRange, + localTimeZone: TimeZone, + checkCancellation: CostUsageScanner.CancellationCheck? = nil) throws -> Report + { + let discovery = try CodexLineageDiscovery.discover( + includedFiles: includedFiles, + roots: roots, + checkCancellation: checkCancellation) + var rejectedObservationCount = 0 + var documents: [CodexLineageLedger.Document] = [] + documents.reserveCapacity(discovery.documents.count) + for document in discovery.documents { + try checkCancellation?() + var observations: [CodexLineageLedger.Observation] = [] + observations.reserveCapacity(document.observations.count) + for observation in document.observations { + try checkCancellation?() + let accepted = CostUsageScanner.dateFromTimestamp(observation.timestamp) != nil + if !accepted { + rejectedObservationCount += 1 + } else { + observations.append(observation) + } + } + documents.append(CodexLineageLedger.Document( + ownerID: document.ownerID, + metadataSessionID: document.metadataSessionID, + parentSessionID: document.parentSessionID, + observations: observations)) + } + let ledger = try CodexLineageLedger.reconcile( + documents: documents, + localTimeZone: localTimeZone, + checkCancellation: checkCancellation) + let legacy = Self.legacyTotalsByDay(legacyDays) + let dayKeys = Set(legacy.keys).union(ledger.localDays.keys) + .filter(dayRange.contains) + .sorted() + let days = dayKeys.map { day in + DayDifference(day: day, legacy: legacy[day] ?? .zero, ledger: ledger.localDays[day] ?? .zero) + } + return Report( + days: days, + acceptedObservationCount: ledger.acceptedObservationCount, + duplicateObservationCount: ledger.duplicateObservationCount, + componentCount: ledger.componentCount, + referencedParentDocumentCount: discovery.referencedParentDocumentCount, + unresolvedParentCount: discovery.unresolvedParentIDs.count, + rejectedObservationCount: rejectedObservationCount) + } + + private static func legacyTotalsByDay( + _ days: [String: [String: [Int]]]) -> [String: CodexLineageLedger.Totals] + { + days.mapValues { models in + models.values.reduce(into: CodexLineageLedger.Totals.zero) { totals, packed in + totals.input += packed[safe: 0] ?? 0 + totals.cached += packed[safe: 1] ?? 0 + totals.output += packed[safe: 2] ?? 0 + } + } + } +} diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift index 934f23626b..2ed611f5f8 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift @@ -1706,8 +1706,7 @@ enum CostUsageScanner { warnedAboutUnparsedTimestamp = true self.log.warning( "Codex cost usage could not parse parent token snapshot timestamp; " - + "falling back to lexical comparison", - metadata: ["path": fileURL.path, "timestamp": timestamp]) + + "falling back to lexical comparison") } return date } @@ -2562,6 +2561,7 @@ enum CostUsageScanner { shouldRefresh: shouldRefresh) } + // swiftlint:disable:next function_body_length private static func loadCodexDaily( range: CostUsageDayRange, now: Date, @@ -2695,6 +2695,12 @@ enum CostUsageScanner { ? [cachedUntilKey, range.scanUntilKey].compactMap(\.self).max() ?? range.scanUntilKey : range.scanUntilKey Self.pruneDays(cache: &cache, sinceKey: retainedSinceKey, untilKey: retainedUntilKey) + try Self.recordCodexLineageShadow( + files: files, + roots: plan.roots, + cache: cache, + range: range, + checkCancellation: checkCancellation) cache.roots = plan.rootsFingerprint cache.scanSinceKey = retainedSinceKey cache.scanUntilKey = retainedUntilKey @@ -2728,6 +2734,51 @@ enum CostUsageScanner { priorityTurns: plan.priorityTurns) } + private static func recordCodexLineageShadow( + files: [URL], + roots: [URL], + cache: CostUsageCache, + range: CostUsageDayRange, + checkCancellation: CancellationCheck?) throws + { + do { + let report = try CodexLineageShadow.run( + includedFiles: files, + roots: roots, + legacyDays: cache.days, + dayRange: range.sinceKey...range.untilKey, + localTimeZone: .current, + checkCancellation: checkCancellation) + self.log.info( + "Codex lineage shadow comparison completed", + metadata: [ + "accepted": "\(report.acceptedObservationCount)", + "components": "\(report.componentCount)", + "days": "\(report.days.count)", + "duplicates": "\(report.duplicateObservationCount)", + "referencedParents": "\(report.referencedParentDocumentCount)", + "rejected": "\(report.rejectedObservationCount)", + "unresolvedParents": "\(report.unresolvedParentCount)", + ]) + for day in report.days where day.delta != .zero { + self.log.info( + "Codex lineage shadow daily difference", + metadata: [ + "cachedDelta": "\(day.delta.cached)", + "day": "\(day.day)", + "inputDelta": "\(day.delta.input)", + "outputDelta": "\(day.delta.output)", + ]) + } + } catch is CancellationError { + throw CancellationError() + } catch { + self.log.warning( + "Codex lineage shadow comparison failed", + metadata: ["errorType": "\(type(of: error))"]) + } + } + private static func codexFileScanContext( range: CostUsageDayRange, options: Options, diff --git a/Tests/CodexBarTests/CodexLineageShadowTests.swift b/Tests/CodexBarTests/CodexLineageShadowTests.swift new file mode 100644 index 0000000000..51eb59adfc --- /dev/null +++ b/Tests/CodexBarTests/CodexLineageShadowTests.swift @@ -0,0 +1,68 @@ +import Foundation +import Testing +@testable import CodexBarCore + +struct CodexLineageShadowTests { + @Test + func `shadow comparison records deltas and diagnostics without changing legacy totals`() throws { + let environment = try CostUsageTestEnvironment() + defer { environment.cleanup() } + let parentID = "11111111-1111-4111-8111-111111111111" + let childID = "22222222-2222-4222-8222-222222222222" + let parent = try Self.writeRollout( + root: environment.codexSessionsRoot, + ownerID: parentID, + metadataID: parentID, + events: [Self.event(timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100)]) + let child = try Self.writeRollout( + root: environment.codexSessionsRoot, + ownerID: childID, + metadataID: childID, + parentID: parentID, + events: [ + Self.event(timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100), + Self.event(timestamp: "not-a-time", input: 50, totalInput: 150), + ]) + let legacyDays = ["2026-07-09": ["gpt-5.4": [150, 20, 10]]] + + let report = try CodexLineageShadow.run( + includedFiles: [parent, child], + roots: [environment.codexSessionsRoot], + legacyDays: legacyDays, + dayRange: "2026-07-09"..."2026-07-09", + localTimeZone: .gmt) + + #expect(legacyDays["2026-07-09"]?["gpt-5.4"] == [150, 20, 10]) + #expect(report.days == [.init( + day: "2026-07-09", + legacy: .init(input: 150, cached: 20, output: 10), + ledger: .init(input: 100, cached: 0, output: 0))]) + #expect(report.days[0].delta == .init(input: -50, cached: -20, output: -10)) + #expect(report.acceptedObservationCount == 1) + #expect(report.duplicateObservationCount == 1) + #expect(report.componentCount == 1) + #expect(report.rejectedObservationCount == 1) + #expect(report.unresolvedParentCount == 0) + } + + private static func writeRollout( + root: URL, + ownerID: String, + metadataID: String, + parentID: String? = nil, + events: [String]) throws -> URL + { + try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true) + let file = root.appendingPathComponent("rollout-2026-07-09T12-00-00-\(ownerID).jsonl") + let parent = parentID.map { #", "forked_from_id":"\#($0)""# } ?? "" + let metadata = #"{"type":"session_meta","payload":{"id":"\#(metadataID)"\#(parent)}}"# + try ([metadata] + events).joined(separator: "\n").write(to: file, atomically: true, encoding: .utf8) + return file + } + + private static func event(timestamp: String, input: Int, totalInput: Int) -> String { + #"{"type":"event_msg","timestamp":"\#(timestamp)","payload":{"type":"token_count","info":{"# + + #""last_token_usage":{"input_tokens":\#(input),"cached_input_tokens":0,"output_tokens":0},"# + + #""total_token_usage":{"input_tokens":\#(totalInput),"cached_input_tokens":0,"output_tokens":0}}}}"# + } +} From 36dca74e07e9ded71601713b514dd9538576f88f Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Tue, 14 Jul 2026 00:41:06 -0400 Subject: [PATCH 08/11] Resolve physical lineage parents --- .../Generated/CodexParserHash.generated.swift | 2 +- .../Codex/CodexLineageDiscovery.swift | 13 +--------- .../CodexLineageDiscoveryTests.swift | 26 +++++++++++++++++++ 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index d57f41412c..7f0f8afe9c 100644 --- a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift +++ b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift @@ -1,5 +1,5 @@ // Generated by Scripts/regenerate-codex-parser-hash.sh. Do not edit by hand. enum CodexParserHash { - static let value = "07457314442f300f" + static let value = "4f91bd06fc9d8749" } diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageDiscovery.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageDiscovery.swift index 0b75a82e87..cccee7bec4 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageDiscovery.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageDiscovery.swift @@ -24,9 +24,6 @@ enum CodexLineageDiscovery { func remember(_ document: CodexLineageLedger.Document) { documents.append(document) knownIDs.insert(Self.canonicalSessionID(document.ownerID)) - if let metadataSessionID = Self.nonEmpty(document.metadataSessionID) { - knownIDs.insert(Self.canonicalSessionID(metadataSessionID)) - } if let parentSessionID = Self.nonEmpty(document.parentSessionID) { pendingParentIDs.append(Self.canonicalSessionID(parentSessionID)) } @@ -60,8 +57,7 @@ enum CodexLineageDiscovery { fileURL: parentURL, checkCancellation: checkCancellation) let ownerID = Self.canonicalSessionID(parent.ownerID) - let metadataSessionID = parent.metadataSessionID.map(Self.canonicalSessionID) - guard ownerID == parentID || metadataSessionID == parentID else { + guard ownerID == parentID else { unresolvedParentIDs.insert(parentID) continue } @@ -124,13 +120,6 @@ enum CodexLineageDiscovery { let canonicalID = CodexLineageDiscovery.canonicalSessionID(ownerID) self.filesByID[canonicalID] = self.filesByID[canonicalID] ?? fileURL } - if let metadataID = try CostUsageScanner.parseCodexSessionIdentifier( - fileURL: fileURL, - checkCancellation: self.checkCancellation) - { - let canonicalID = CodexLineageDiscovery.canonicalSessionID(metadataID) - self.filesByID[canonicalID] = self.filesByID[canonicalID] ?? fileURL - } } } } diff --git a/Tests/CodexBarTests/CodexLineageDiscoveryTests.swift b/Tests/CodexBarTests/CodexLineageDiscoveryTests.swift index 9ab3126144..ae6fda55d7 100644 --- a/Tests/CodexBarTests/CodexLineageDiscoveryTests.swift +++ b/Tests/CodexBarTests/CodexLineageDiscoveryTests.swift @@ -92,6 +92,32 @@ struct CodexLineageDiscoveryTests { #expect(report.unresolvedParentIDs.isEmpty) } + @Test + func `retained parent metadata does not hide the physical parent rollout`() throws { + let environment = try CostUsageTestEnvironment() + defer { environment.cleanup() } + let parentID = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + _ = try Self.writeRollout( + root: environment.codexArchivedSessionsRoot, + relativeDirectory: "", + ownerID: parentID, + metadataID: parentID) + let child = try Self.writeRollout( + root: environment.codexSessionsRoot, + relativeDirectory: "2026/07/09", + ownerID: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + metadataID: parentID, + parentID: parentID) + + let report = try CodexLineageDiscovery.discover( + includedFiles: [child], + roots: [environment.codexSessionsRoot, environment.codexArchivedSessionsRoot]) + + #expect(Set(report.documents.map(\.ownerID)) == [parentID, "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"]) + #expect(report.referencedParentDocumentCount == 1) + #expect(report.unresolvedParentIDs.isEmpty) + } + private static func writeRollout( root: URL, relativeDirectory: String, From b499c2815922e561a27f0f78d6f378a3aff451ba Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Tue, 14 Jul 2026 00:51:52 -0400 Subject: [PATCH 09/11] Keep lineage parser lint-clean --- Sources/CodexBarCore/Generated/CodexParserHash.generated.swift | 2 +- Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index 7f0f8afe9c..b37be1f6f8 100644 --- a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift +++ b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift @@ -1,5 +1,5 @@ // Generated by Scripts/regenerate-codex-parser-hash.sh. Do not edit by hand. enum CodexParserHash { - static let value = "4f91bd06fc9d8749" + static let value = "a13dfbcdf1b8b859" } diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift index 2ed611f5f8..782bed8ee0 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift @@ -1685,7 +1685,7 @@ enum CostUsageScanner { return nil } - // swiftlint:disable:next cyclomatic_complexity + // swiftlint:disable:next cyclomatic_complexity function_body_length private static func parseCodexTokenSnapshots( fileURL: URL, checkCancellation: CancellationCheck? = nil) throws -> CodexParsedTokenEvidence From d364597b2c10be3f63cc07c9f08e569d2324521b Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Tue, 14 Jul 2026 09:12:12 -0400 Subject: [PATCH 10/11] Avoid unused lineage observations --- .../Generated/CodexParserHash.generated.swift | 2 +- .../Vendored/CostUsage/CostUsageScanner.swift | 13 ++++++++ .../CodexLineageLedgerTests.swift | 30 +++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index b37be1f6f8..34f866f6a9 100644 --- a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift +++ b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift @@ -1,5 +1,5 @@ // Generated by Scripts/regenerate-codex-parser-hash.sh. Do not edit by hand. enum CodexParserHash { - static let value = "a13dfbcdf1b8b859" + static let value = "adea6d0a163c51e2" } diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift index 782bed8ee0..f06e733a2e 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift @@ -1688,6 +1688,7 @@ enum CostUsageScanner { // swiftlint:disable:next cyclomatic_complexity function_body_length private static func parseCodexTokenSnapshots( fileURL: URL, + collectLineageObservations: Bool = false, checkCancellation: CancellationCheck? = nil) throws -> CodexParsedTokenEvidence { var sessionId: String? @@ -1724,6 +1725,7 @@ enum CostUsageScanner { timestamp: timestamp, date: parsedSnapshotDate(timestamp: timestamp), totals: counted)) + guard collectLineageObservations else { return } let eventID = turnID.map { turnID in let ordinal = tokenEventCountByTurn[turnID, default: 0] tokenEventCountByTurn[turnID] = ordinal + 1 @@ -1871,6 +1873,7 @@ enum CostUsageScanner { { let parsed = try Self.parseCodexTokenSnapshots( fileURL: fileURL, + collectLineageObservations: true, checkCancellation: checkCancellation) return CodexLineageLedger.Document( ownerID: Self.codexRolloutOwnerID(fileURL: fileURL) ?? parsed.sessionId ?? fileURL.standardizedFileURL.path, @@ -1879,6 +1882,16 @@ enum CostUsageScanner { observations: parsed.observations) } + static func parseCodexTokenEvidenceCountsForTesting( + fileURL: URL, + collectLineageObservations: Bool) throws -> (snapshots: Int, observations: Int) + { + let parsed = try Self.parseCodexTokenSnapshots( + fileURL: fileURL, + collectLineageObservations: collectLineageObservations) + return (parsed.snapshots.count, parsed.observations.count) + } + private static func lineageTotals(_ totals: CostUsageCodexTotals) -> CodexLineageLedger.Totals { CodexLineageLedger.Totals(input: totals.input, cached: totals.cached, output: totals.output) } diff --git a/Tests/CodexBarTests/CodexLineageLedgerTests.swift b/Tests/CodexBarTests/CodexLineageLedgerTests.swift index aca6368d34..5a9393ad63 100644 --- a/Tests/CodexBarTests/CodexLineageLedgerTests.swift +++ b/Tests/CodexBarTests/CodexLineageLedgerTests.swift @@ -37,6 +37,36 @@ struct CodexLineageLedgerTests { #expect(document.observations[1].total == .init(input: 150, cached: 60, output: 15)) } + @Test + func `snapshot only parsing skips lineage observation collection`() throws { + let environment = try CostUsageTestEnvironment() + defer { environment.cleanup() } + let fileURL = environment.root.appendingPathComponent("rollout-with-token-states.jsonl") + let contents = [ + Self.tokenCountLine( + timestamp: "2026-07-09T12:00:00Z", + last: (input: 100, cached: 40, output: 10), + total: (input: 100, cached: 40, output: 10)), + Self.tokenCountLine( + timestamp: "2026-07-09T12:01:00Z", + last: (input: 50, cached: 20, output: 5), + total: (input: 150, cached: 60, output: 15)), + ].joined(separator: "\n") + try contents.write(to: fileURL, atomically: true, encoding: .utf8) + + let snapshotsOnly = try CostUsageScanner.parseCodexTokenEvidenceCountsForTesting( + fileURL: fileURL, + collectLineageObservations: false) + let lineage = try CostUsageScanner.parseCodexTokenEvidenceCountsForTesting( + fileURL: fileURL, + collectLineageObservations: true) + + #expect(snapshotsOnly.snapshots == 2) + #expect(snapshotsOnly.observations == 0) + #expect(lineage.snapshots == 2) + #expect(lineage.observations == 2) + } + @Test func `daily rows preserve model token and pricing dimensions`() throws { let priced = Self.observation( From eb019ba4db3a884feac379a7e4fd08e06e1d9c88 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Tue, 14 Jul 2026 12:03:16 -0400 Subject: [PATCH 11/11] Avoid redundant lineage shadow scans --- .../Generated/CodexParserHash.generated.swift | 2 +- .../Providers/Codex/CodexLineageLedger.swift | 18 ++++++++--- .../Vendored/CostUsage/CostUsageScanner.swift | 18 +++++++---- .../CodexLineageLedgerTests.swift | 32 +++++++++++++++++++ 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index 34f866f6a9..6c1ce7e08f 100644 --- a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift +++ b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift @@ -1,5 +1,5 @@ // Generated by Scripts/regenerate-codex-parser-hash.sh. Do not edit by hand. enum CodexParserHash { - static let value = "adea6d0a163c51e2" + static let value = "a22a4756d8f471d4" } diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift index c9d8e9b773..d4a245d93f 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift @@ -96,18 +96,24 @@ enum CodexLineageLedger { } var acceptedByComponent: [String: [ObservationIdentity: AcceptedObservation]] = [:] + var acceptedFingerprintByComponentOwner: [String: [String: [Fingerprint: ObservationIdentity]]] = [:] var physicalObservationCount = 0 for document in documents { try checkCancellation?() let componentID = graph.find(document.ownerID) var accepted = acceptedByComponent[componentID] ?? [:] + var acceptedFingerprintByOwner = acceptedFingerprintByComponentOwner[componentID] ?? [:] for observation in document.observations { try checkCancellation?() physicalObservationCount += 1 let date = try Self.date(from: observation.timestamp) - let identity = ObservationIdentity( + let fingerprint = Fingerprint(last: observation.last, total: observation.total) + let proposedIdentity = ObservationIdentity( eventID: Self.nonEmpty(observation.eventID), - fingerprint: Fingerprint(last: observation.last, total: observation.total)) + fingerprint: fingerprint) + let identity = accepted[proposedIdentity] == nil + ? acceptedFingerprintByOwner[document.ownerID]?[fingerprint] ?? proposedIdentity + : proposedIdentity if let existing = accepted[identity] { if existing.date < date { continue @@ -122,8 +128,12 @@ enum CodexLineageLedger { date: date, model: observation.model, last: observation.last) + if identity == proposedIdentity { + acceptedFingerprintByOwner[document.ownerID, default: [:]][fingerprint] = identity + } } acceptedByComponent[componentID] = accepted + acceptedFingerprintByComponentOwner[componentID] = acceptedFingerprintByOwner } var utcDays: [String: Totals] = [:] @@ -159,11 +169,11 @@ enum CodexLineageLedger { } private enum ObservationIdentity: Equatable, Hashable { - case event(String) + case event(String, Fingerprint) case fingerprint(Fingerprint) init(eventID: String?, fingerprint: Fingerprint) { - self = eventID.map(Self.event) ?? .fingerprint(fingerprint) + self = eventID.map { .event($0, fingerprint) } ?? .fingerprint(fingerprint) } } diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift index f06e733a2e..f636dbef33 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift @@ -80,6 +80,7 @@ enum CostUsageScanner { var contributingSessionIds: Set = [] var seenFileIds: Set = [] var seenCodexUsageRowKeys: Set = [] + var changedLineageInputs = false } struct CodexScannedSession { @@ -2473,6 +2474,7 @@ enum CostUsageScanner { if Self.keepCachedCodexFileIfFresh(input: input, context: context, cache: &cache, state: &state) { return } + state.changedLineageInputs = true if try Self.appendCodexFileIncrementIfPossible(input: input, context: context, cache: &cache, state: &state) { return } @@ -2682,6 +2684,7 @@ enum CostUsageScanner { let shouldDrop = shouldDropAllUnscannedFiles || old.touchesCodexScanWindow(sinceKey: range.scanSinceKey, untilKey: range.scanUntilKey) guard shouldDrop else { continue } + scanState.changedLineageInputs = true Self.applyFileDays(cache: &cache, fileDays: old.days, sign: -1) cache.files.removeValue(forKey: key) } @@ -2692,6 +2695,7 @@ enum CostUsageScanner { guard old.touchesCodexScanWindow(sinceKey: range.scanSinceKey, untilKey: range.scanUntilKey) else { continue } guard FileManager.default.fileExists(atPath: key) else { + scanState.changedLineageInputs = true Self.applyFileDays(cache: &cache, fileDays: old.days, sign: -1) cache.files.removeValue(forKey: key) continue @@ -2708,12 +2712,14 @@ enum CostUsageScanner { ? [cachedUntilKey, range.scanUntilKey].compactMap(\.self).max() ?? range.scanUntilKey : range.scanUntilKey Self.pruneDays(cache: &cache, sinceKey: retainedSinceKey, untilKey: retainedUntilKey) - try Self.recordCodexLineageShadow( - files: files, - roots: plan.roots, - cache: cache, - range: range, - checkCancellation: checkCancellation) + if scanState.changedLineageInputs { + try Self.recordCodexLineageShadow( + files: files, + roots: plan.roots, + cache: cache, + range: range, + checkCancellation: checkCancellation) + } cache.roots = plan.rootsFingerprint cache.scanSinceKey = retainedSinceKey cache.scanUntilKey = retainedUntilKey diff --git a/Tests/CodexBarTests/CodexLineageLedgerTests.swift b/Tests/CodexBarTests/CodexLineageLedgerTests.swift index 5a9393ad63..e10946837f 100644 --- a/Tests/CodexBarTests/CodexLineageLedgerTests.swift +++ b/Tests/CodexBarTests/CodexLineageLedgerTests.swift @@ -244,6 +244,36 @@ struct CodexLineageLedgerTests { #expect(report.duplicateObservationCount == 2) } + @Test + func `matching event hints with different token states remain distinct`() throws { + let first = Self.observation( + eventID: "turn-a:0", timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) + let forked = Self.observation( + eventID: "turn-a:0", timestamp: "2026-07-09T12:01:00Z", input: 25, totalInput: 125) + let report = try CodexLineageLedger.reconcile( + documents: [Self.document(owner: "root", observations: [first, forked])], + localTimeZone: .gmt) + + #expect(report.utcDays["2026-07-09"]?.input == 125) + #expect(report.acceptedObservationCount == 2) + #expect(report.duplicateObservationCount == 0) + } + + @Test + func `different event ordinals do not revive an unchanged owner state`() throws { + let first = Self.observation( + eventID: "turn-a:0", timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) + let repeated = Self.observation( + eventID: "turn-a:1", timestamp: "2026-07-09T12:01:00Z", input: 100, totalInput: 100) + let report = try CodexLineageLedger.reconcile( + documents: [Self.document(owner: "root", observations: [first, repeated])], + localTimeZone: .gmt) + + #expect(report.utcDays["2026-07-09"]?.input == 100) + #expect(report.acceptedObservationCount == 1) + #expect(report.duplicateObservationCount == 1) + } + @Test func `complete token state distinguishes observations within a lineage`() throws { let first = Self.observation( @@ -353,6 +383,7 @@ struct CodexLineageLedgerTests { } private static func observation( + eventID: String? = nil, timestamp: String, model: String = CostUsagePricing.codexUnattributedModel, input: Int, @@ -361,6 +392,7 @@ struct CodexLineageLedgerTests { totalInput: Int) -> CodexLineageLedger.Observation { .init( + eventID: eventID, timestamp: timestamp, model: model, last: .init(input: input, cached: cached, output: output),