From 9245047c85395634fedf301d51e645b09d1e4382 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Mon, 13 Jul 2026 11:43:01 -0400 Subject: [PATCH 01/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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 1aa2182a613db177b43fc3b46fff52d46cf09775 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Mon, 13 Jul 2026 13:59:06 -0400 Subject: [PATCH 10/28] Classify Codex lineage residuals --- .../CodexLineageResidualClassifier.swift | 189 ++++++++++++++++++ .../CodexLineageResidualClassifierTests.swift | 147 ++++++++++++++ 2 files changed, 336 insertions(+) create mode 100644 Sources/CodexBarCore/Providers/Codex/CodexLineageResidualClassifier.swift create mode 100644 Tests/CodexBarTests/CodexLineageResidualClassifierTests.swift diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageResidualClassifier.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageResidualClassifier.swift new file mode 100644 index 0000000000..5311942c77 --- /dev/null +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageResidualClassifier.swift @@ -0,0 +1,189 @@ +import Foundation + +/// Classifies differences between local Codex accounting paths and a finalized UTC usage source. +/// +/// This is an analysis seam, not an oracle-tuning mechanism. Callers supply independently observed +/// totals and corpus diagnostics; the classifier only applies a documented, bounded policy. +enum CodexLineageResidualClassifier { + enum Classification: String, Equatable, Sendable { + case invalidInput + case provisional + case withinTolerance + case unavailableHistory + case unsupportedEventShape + case utcLocalAttribution + case accountingSemantics + case containment + case ledgerDefect + } + + struct Evidence: Equatable, Sendable { + var localCorpusWasExhaustive = false + var rejectedObservationCount = 0 + var unresolvedParentCount = 0 + var duplicateObservationCount = 0 + } + + struct Sample: Equatable, Sendable { + let day: String + let referenceTokens: Int + let isReferenceFinalized: Bool + let isOrdinaryDay: Bool + let legacyTokens: Int + let ledgerUTCTokens: Int + let ledgerLocalTokens: Int + let evidence: Evidence + } + + struct DayResult: Equatable, Sendable { + let day: String + let classification: Classification + let legacyAbsoluteError: Int? + let ledgerAbsoluteError: Int? + } + + struct Report: Equatable, Sendable { + let days: [DayResult] + let finalizedReferenceTokens: Int + let finalizedLegacyTokens: Int + let finalizedLedgerTokens: Int + let legacyAbsoluteError: Int + let ledgerAbsoluteError: Int + let ordinaryDayRegressionCount: Int + let invalidSampleCount: Int + + var improvesAggregateError: Bool { + self.ledgerAbsoluteError < self.legacyAbsoluteError + } + } + + struct Policy: Equatable, Sendable { + /// A residual no larger than this fraction of the reference is not assigned a speculative cause. + let largeResidualFraction: Double + /// Ordinary days may move by this fraction before the ledger is considered regressive. + let ordinaryDayRegressionFraction: Double + + static let validation = Self( + largeResidualFraction: 0.05, + ordinaryDayRegressionFraction: 0.01) + } + + static func classify(samples: [Sample], policy: Policy = .validation) -> Report { + let ordered = samples.sorted { $0.day < $1.day } + let days = ordered.map { sample -> DayResult in + guard Self.isValid(sample: sample, policy: policy) else { + return DayResult( + day: sample.day, + classification: .invalidInput, + legacyAbsoluteError: nil, + ledgerAbsoluteError: nil) + } + guard sample.isReferenceFinalized else { + return DayResult( + day: sample.day, + classification: .provisional, + legacyAbsoluteError: nil, + ledgerAbsoluteError: nil) + } + let legacyError = Self.absoluteDifference(sample.legacyTokens, sample.referenceTokens) + let ledgerError = Self.absoluteDifference(sample.ledgerUTCTokens, sample.referenceTokens) + return DayResult( + day: sample.day, + classification: Self.classification( + sample: sample, + legacyError: legacyError, + ledgerError: ledgerError, + policy: policy), + legacyAbsoluteError: legacyError, + ledgerAbsoluteError: ledgerError) + } + + let finalized = ordered.filter { $0.isReferenceFinalized && Self.isValid(sample: $0, policy: policy) } + let referenceTokens = finalized.reduce(0) { Self.saturatingSum($0, $1.referenceTokens) } + let legacyTokens = finalized.reduce(0) { Self.saturatingSum($0, $1.legacyTokens) } + let ledgerTokens = finalized.reduce(0) { Self.saturatingSum($0, $1.ledgerUTCTokens) } + let ordinaryDayRegressionCount = finalized.count { sample in + guard sample.isOrdinaryDay else { return false } + let allowed = Self.threshold( + reference: sample.referenceTokens, + fraction: policy.ordinaryDayRegressionFraction) + return Self.absoluteDifference(sample.ledgerUTCTokens, sample.referenceTokens) + > Self.saturatingSum(Self.absoluteDifference(sample.legacyTokens, sample.referenceTokens), allowed) + } + + return Report( + days: days, + finalizedReferenceTokens: referenceTokens, + finalizedLegacyTokens: legacyTokens, + finalizedLedgerTokens: ledgerTokens, + legacyAbsoluteError: Self.absoluteDifference(legacyTokens, referenceTokens), + ledgerAbsoluteError: Self.absoluteDifference(ledgerTokens, referenceTokens), + ordinaryDayRegressionCount: ordinaryDayRegressionCount, + invalidSampleCount: days.count { $0.classification == .invalidInput }) + } + + private static func classification( + sample: Sample, + legacyError: Int, + ledgerError: Int, + policy: Policy) -> Classification + { + let largeResidual = Self.threshold( + reference: sample.referenceTokens, + fraction: policy.largeResidualFraction) + guard ledgerError > largeResidual else { return .withinTolerance } + if sample.evidence.rejectedObservationCount > 0 { + return .unsupportedEventShape + } + + let ordinaryAllowance = Self.threshold( + reference: sample.referenceTokens, + fraction: policy.ordinaryDayRegressionFraction) + if sample.isOrdinaryDay, ledgerError > Self.saturatingSum(legacyError, ordinaryAllowance) { + return .ledgerDefect + } + + let localError = Self.absoluteDifference(sample.ledgerLocalTokens, sample.referenceTokens) + if Self.saturatingSum(ledgerError, ordinaryAllowance) < localError { + return .utcLocalAttribution + } + if sample.evidence.unresolvedParentCount > 0 { + return .containment + } + if sample.evidence.localCorpusWasExhaustive, sample.ledgerUTCTokens < sample.referenceTokens { + return .unavailableHistory + } + if sample.evidence.duplicateObservationCount > 0, ledgerError < legacyError { + return .containment + } + return .accountingSemantics + } + + private static func threshold(reference: Int, fraction: Double) -> Int { + let value = (Double(reference) * fraction).rounded(.up) + return value >= Double(Int.max) ? Int.max : Int(value) + } + + private static func absoluteDifference(_ lhs: Int, _ rhs: Int) -> Int { + lhs >= rhs ? lhs - rhs : rhs - lhs + } + + private static func isValid(sample: Sample, policy: Policy) -> Bool { + sample.referenceTokens >= 0 && + sample.legacyTokens >= 0 && + sample.ledgerUTCTokens >= 0 && + sample.ledgerLocalTokens >= 0 && + sample.evidence.rejectedObservationCount >= 0 && + sample.evidence.unresolvedParentCount >= 0 && + sample.evidence.duplicateObservationCount >= 0 && + policy.largeResidualFraction.isFinite && + policy.largeResidualFraction >= 0 && + policy.ordinaryDayRegressionFraction.isFinite && + policy.ordinaryDayRegressionFraction >= 0 + } + + private static func saturatingSum(_ lhs: Int, _ rhs: Int) -> Int { + let (sum, overflow) = lhs.addingReportingOverflow(rhs) + return overflow ? Int.max : sum + } +} diff --git a/Tests/CodexBarTests/CodexLineageResidualClassifierTests.swift b/Tests/CodexBarTests/CodexLineageResidualClassifierTests.swift new file mode 100644 index 0000000000..b583256e00 --- /dev/null +++ b/Tests/CodexBarTests/CodexLineageResidualClassifierTests.swift @@ -0,0 +1,147 @@ +import Testing +@testable import CodexBarCore + +struct CodexLineageResidualClassifierTests { + @Test + func `sanitized finalized UTC replay materially closes the aggregate gap`() { + let report = CodexLineageResidualClassifier.classify(samples: Self.forkHeavySamples) + + #expect(report.finalizedReferenceTokens == 3_692_873_480) + #expect(report.finalizedLegacyTokens == 2_882_545_128) + #expect(report.finalizedLedgerTokens == 3_589_444_942) + #expect(report.legacyAbsoluteError == 810_328_352) + #expect(report.ledgerAbsoluteError == 103_428_538) + #expect(report.improvesAggregateError) + #expect(report.days.allSatisfy { day in + guard let legacy = day.legacyAbsoluteError, let ledger = day.ledgerAbsoluteError else { return true } + return ledger < legacy + }) + } + + @Test + func `large undercount is unavailable history only after exhaustive local checks`() { + let verified = CodexLineageResidualClassifier.classify(samples: [Self.forkHeavySamples[0]]) + #expect(verified.days.first?.classification == .unavailableHistory) + + var incomplete = Self.forkHeavySamples[0] + incomplete = .init( + day: incomplete.day, + referenceTokens: incomplete.referenceTokens, + isReferenceFinalized: true, + isOrdinaryDay: false, + legacyTokens: incomplete.legacyTokens, + ledgerUTCTokens: incomplete.ledgerUTCTokens, + ledgerLocalTokens: incomplete.ledgerLocalTokens, + evidence: .init(localCorpusWasExhaustive: false)) + let unverified = CodexLineageResidualClassifier.classify(samples: [incomplete]) + #expect(unverified.days.first?.classification == .accountingSemantics) + } + + @Test + func `ordinary non-fork days do not materially regress`() { + let samples = [ + Self.ordinary(day: "2026-06-29", legacy: 120_000_000, ledger: 120_000_000), + Self.ordinary(day: "2026-06-30", legacy: 180_000_000, ledger: 180_000_000), + Self.ordinary(day: "2026-07-05", legacy: 290_018_710, ledger: 290_777_623), + Self.ordinary(day: "2026-07-06", legacy: 435_123_419, ledger: 435_123_419), + Self.ordinary(day: "2026-07-07", legacy: 324_480_000, ledger: 324_480_000), + Self.ordinary(day: "2026-07-08", legacy: 240_000_000, ledger: 240_000_000), + ] + + let report = CodexLineageResidualClassifier.classify(samples: samples) + #expect(report.ordinaryDayRegressionCount == 0) + #expect(report.days.allSatisfy { $0.classification == .withinTolerance }) + } + + @Test + func `reference day remains provisional until finalized`() { + let sample = CodexLineageResidualClassifier.Sample( + day: "2026-07-12", + referenceTokens: 618_121_840, + isReferenceFinalized: false, + isOrdinaryDay: false, + legacyTokens: 600_000_000, + ledgerUTCTokens: 615_000_000, + ledgerLocalTokens: 610_000_000, + evidence: .init()) + + let report = CodexLineageResidualClassifier.classify(samples: [sample]) + #expect(report.days.first?.classification == .provisional) + #expect(report.finalizedReferenceTokens == 0) + #expect(report.days.first?.ledgerAbsoluteError == nil) + } + + @Test + func `invalid token evidence is excluded without overflowing aggregate totals`() { + let invalid = CodexLineageResidualClassifier.Sample( + day: "2026-07-08", + referenceTokens: -1, + isReferenceFinalized: true, + isOrdinaryDay: true, + legacyTokens: 10, + ledgerUTCTokens: 10, + ledgerLocalTokens: 10, + evidence: .init()) + let large = CodexLineageResidualClassifier.Sample( + day: "2026-07-09", + referenceTokens: Int.max, + isReferenceFinalized: true, + isOrdinaryDay: false, + legacyTokens: Int.max, + ledgerUTCTokens: Int.max, + ledgerLocalTokens: Int.max, + evidence: .init()) + + let report = CodexLineageResidualClassifier.classify(samples: [invalid, large, large]) + + #expect(report.days.first?.classification == .invalidInput) + #expect(report.invalidSampleCount == 1) + #expect(report.finalizedReferenceTokens == Int.max) + #expect(report.finalizedLegacyTokens == Int.max) + #expect(report.finalizedLedgerTokens == Int.max) + #expect(report.legacyAbsoluteError == 0) + #expect(report.ledgerAbsoluteError == 0) + } + + private static let forkHeavySamples = [ + CodexLineageResidualClassifier.Sample( + day: "2026-07-09", + referenceTokens: 852_682_935, + isReferenceFinalized: true, + isOrdinaryDay: false, + legacyTokens: 946_818_053, + ledgerUTCTokens: 764_026_920, + ledgerLocalTokens: 764_026_920, + evidence: .init(localCorpusWasExhaustive: true, duplicateObservationCount: 100)), + CodexLineageResidualClassifier.Sample( + day: "2026-07-10", + referenceTokens: 1_580_199_588, + isReferenceFinalized: true, + isOrdinaryDay: false, + legacyTokens: 1_414_122_342, + ledgerUTCTokens: 1_510_013_760, + ledgerLocalTokens: 1_430_000_000, + evidence: .init(localCorpusWasExhaustive: true, duplicateObservationCount: 100)), + CodexLineageResidualClassifier.Sample( + day: "2026-07-11", + referenceTokens: 1_259_990_957, + isReferenceFinalized: true, + isOrdinaryDay: false, + legacyTokens: 521_604_733, + ledgerUTCTokens: 1_315_404_262, + ledgerLocalTokens: 1_200_000_000, + evidence: .init(localCorpusWasExhaustive: true, duplicateObservationCount: 100)), + ] + + private static func ordinary(day: String, legacy: Int, ledger: Int) -> CodexLineageResidualClassifier.Sample { + .init( + day: day, + referenceTokens: legacy, + isReferenceFinalized: true, + isOrdinaryDay: true, + legacyTokens: legacy, + ledgerUTCTokens: ledger, + ledgerLocalTokens: ledger, + evidence: .init(localCorpusWasExhaustive: true)) + } +} From aca6abcf13ab9c88a1c370fc7893ec62594d6ee7 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Mon, 13 Jul 2026 14:24:42 -0400 Subject: [PATCH 11/28] Handle ambiguous Codex lineage evidence --- .../Generated/CodexParserHash.generated.swift | 2 +- .../Codex/CodexLineageDiscovery.swift | 107 +++++--- .../Providers/Codex/CodexLineageLedger.swift | 246 +++++++++++++++++- .../Providers/Codex/CodexLineageShadow.swift | 44 +++- .../Vendored/CostUsage/CostUsageScanner.swift | 29 ++- .../CodexLineageDiscoveryTests.swift | 101 ++++++- .../CodexLineageLedgerTests.swift | 164 ++++++++++++ .../CodexLineageShadowTests.swift | 13 +- 8 files changed, 652 insertions(+), 54 deletions(-) diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index b37be1f6f8..90a5d3ff38 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 = "4c426503aec9cb49" } diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageDiscovery.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageDiscovery.swift index cccee7bec4..8d61444a76 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageDiscovery.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageDiscovery.swift @@ -5,7 +5,7 @@ enum CodexLineageDiscovery { struct Report: Equatable, Sendable { let documents: [CodexLineageLedger.Document] let referencedParentDocumentCount: Int - let unresolvedParentIDs: Set + let unresolvedParents: Set } static func discover( @@ -15,17 +15,24 @@ enum CodexLineageDiscovery { { var locator = ParentFileLocator(roots: roots, checkCancellation: checkCancellation) var documents: [CodexLineageLedger.Document] = [] - var knownIDs: Set = [] + var knownIDs: Set = [] var seenPaths: Set = [] - var pendingParentIDs: [String] = [] - var unresolvedParentIDs: Set = [] + var pendingParentIDs: [ScopedIdentity] = [] + var unresolvedParents: Set = [] var referencedParentDocumentCount = 0 func remember(_ document: CodexLineageLedger.Document) { documents.append(document) - knownIDs.insert(Self.canonicalSessionID(document.ownerID)) + knownIDs.insert(.init(scopeID: document.scopeID, sessionID: Self.canonicalSessionID(document.ownerID))) + if let metadataSessionID = Self.nonEmpty(document.metadataSessionID) { + knownIDs.insert(.init( + scopeID: document.scopeID, + sessionID: Self.canonicalSessionID(metadataSessionID))) + } if let parentSessionID = Self.nonEmpty(document.parentSessionID) { - pendingParentIDs.append(Self.canonicalSessionID(parentSessionID)) + pendingParentIDs.append(.init( + scopeID: document.scopeID, + sessionID: Self.canonicalSessionID(parentSessionID))) } } @@ -41,34 +48,46 @@ enum CodexLineageDiscovery { var nextParent = 0 while nextParent < pendingParentIDs.count { try checkCancellation?() - let parentID = pendingParentIDs[nextParent] + let parentIdentity = 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) + let unresolvedIdentity = CodexLineageLedger.ParentIdentity( + scopeID: parentIdentity.scopeID, + sessionID: parentIdentity.sessionID) + guard !knownIDs.contains(parentIdentity), !unresolvedParents.contains(unresolvedIdentity) else { continue } - let path = parentURL.standardizedFileURL.path - guard seenPaths.insert(path).inserted else { - unresolvedParentIDs.insert(parentID) + guard let parentURLs = try locator.fileURLs( + for: parentIdentity.sessionID, + scopeID: parentIdentity.scopeID) + else { + unresolvedParents.insert(unresolvedIdentity) continue } - let parent = try CostUsageScanner.parseCodexLineageDocument( - fileURL: parentURL, - checkCancellation: checkCancellation) - let ownerID = Self.canonicalSessionID(parent.ownerID) - guard ownerID == parentID else { - unresolvedParentIDs.insert(parentID) - continue + var foundParent = false + for parentURL in parentURLs { + let path = parentURL.standardizedFileURL.path + guard seenPaths.insert(path).inserted else { 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 == parentIdentity.sessionID || metadataSessionID == parentIdentity.sessionID else { + continue + } + referencedParentDocumentCount += 1 + foundParent = true + remember(parent) + } + if !foundParent { + unresolvedParents.insert(unresolvedIdentity) } - referencedParentDocumentCount += 1 - remember(parent) } return Report( documents: documents, referencedParentDocumentCount: referencedParentDocumentCount, - unresolvedParentIDs: unresolvedParentIDs) + unresolvedParents: unresolvedParents) } private static func nonEmpty(_ value: String?) -> String? { @@ -80,21 +99,38 @@ enum CodexLineageDiscovery { UUID(uuidString: value)?.uuidString.lowercased() ?? value } + private struct ScopedIdentity: Equatable, Hashable { + let scopeID: String + let sessionID: String + } + private struct ParentFileLocator { let roots: [URL] let checkCancellation: CostUsageScanner.CancellationCheck? var didIndexRoots = false - var filesByID: [String: URL] = [:] + var filesByID: [ScopedIdentity: Set] = [:] - mutating func fileURL(for sessionID: String) throws -> URL? { + mutating func fileURLs(for sessionID: String, scopeID: String) throws -> [URL]? { let canonicalID = CodexLineageDiscovery.canonicalSessionID(sessionID) - if let known = self.filesByID[canonicalID] { - return known + let key = ScopedIdentity(scopeID: scopeID, sessionID: canonicalID) + if let known = self.filesByID[key] { + return Self.unambiguousMatches(known) } if !self.didIndexRoots { try self.indexRoots() } - return self.filesByID[canonicalID] + guard let matches = self.filesByID[key] else { return nil } + return Self.unambiguousMatches(matches) + } + + private static func unambiguousMatches(_ matches: Set) -> [URL]? { + guard !matches.isEmpty else { return nil } + if matches.count == 1 { + return Array(matches) + } + let ownerIDs = Set(matches.compactMap(CostUsageScanner.codexRolloutOwnerID(fileURL:))) + guard ownerIDs.count == 1 else { return nil } + return matches.sorted { $0.path < $1.path } } private mutating func indexRoots() throws { @@ -118,7 +154,20 @@ enum CodexLineageDiscovery { try self.checkCancellation?() if let ownerID = CostUsageScanner.codexRolloutOwnerID(fileURL: fileURL) { let canonicalID = CodexLineageDiscovery.canonicalSessionID(ownerID) - self.filesByID[canonicalID] = self.filesByID[canonicalID] ?? fileURL + let key = ScopedIdentity( + scopeID: CostUsageScanner.codexLineageScopeID(fileURL: fileURL), + sessionID: canonicalID) + self.filesByID[key, default: []].insert(fileURL) + } + if let metadataID = try CostUsageScanner.parseCodexSessionIdentifier( + fileURL: fileURL, + checkCancellation: self.checkCancellation) + { + let canonicalID = CodexLineageDiscovery.canonicalSessionID(metadataID) + let key = ScopedIdentity( + scopeID: CostUsageScanner.codexLineageScopeID(fileURL: fileURL), + sessionID: canonicalID) + self.filesByID[key, default: []].insert(fileURL) } } } diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift index c9d8e9b773..4c365c6884 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift @@ -49,6 +49,60 @@ enum CodexLineageLedger { let metadataSessionID: String? let parentSessionID: String? let observations: [Observation] + let scopeID: String + let incompleteObservationCount: Int + + init( + ownerID: String, + metadataSessionID: String?, + parentSessionID: String?, + observations: [Observation], + scopeID: String = "", + incompleteObservationCount: Int = 0) + { + self.ownerID = ownerID + self.metadataSessionID = metadataSessionID + self.parentSessionID = parentSessionID + self.observations = observations + self.scopeID = scopeID + self.incompleteObservationCount = incompleteObservationCount + } + } + + enum ContainmentReason: String, CaseIterable, Equatable, Hashable, Sendable { + case malformedTimestamp + case incompleteObservation + case conflictingOwnerIdentity + case identityCollision + case ancestryCycle + } + + enum FamilyQuality: Equatable, Sendable { + case primary + case incompleteProvenance + case contained(Set) + } + + struct FamilyDisposition: Equatable, Sendable { + let scopeID: String + let ownerIDs: Set + let quality: FamilyQuality + } + + struct ParentIdentity: Equatable, Hashable, Sendable { + let scopeID: String + let sessionID: String + + init(scopeID: String = "", sessionID: String) { + self.scopeID = scopeID + self.sessionID = sessionID + } + } + + struct ConservativeReport: Equatable, Sendable { + let primary: Report + let families: [FamilyDisposition] + let containedDocuments: [Document] } struct Report: Equatable, Sendable { @@ -86,12 +140,13 @@ enum CodexLineageLedger { for document in documents { try checkCancellation?() guard !document.ownerID.isEmpty else { throw LedgerError.emptyOwnerID } - graph.insert(document.ownerID) + let ownerID = Self.scoped(document.ownerID, document: document) + graph.insert(ownerID) if let metadataSessionID = Self.nonEmpty(document.metadataSessionID) { - graph.union(document.ownerID, metadataSessionID) + graph.union(ownerID, Self.scoped(metadataSessionID, document: document)) } if let parentSessionID = Self.nonEmpty(document.parentSessionID) { - graph.union(document.ownerID, parentSessionID) + graph.union(ownerID, Self.scoped(parentSessionID, document: document)) } } @@ -99,7 +154,7 @@ enum CodexLineageLedger { var physicalObservationCount = 0 for document in documents { try checkCancellation?() - let componentID = graph.find(document.ownerID) + let componentID = graph.find(Self.scoped(document.ownerID, document: document)) var accepted = acceptedByComponent[componentID] ?? [:] for observation in document.observations { try checkCancellation?() @@ -148,11 +203,81 @@ enum CodexLineageLedger { localDays: localDays, utcRows: Self.dailyRows(from: utcRows), localRows: Self.dailyRows(from: localRows), - componentCount: Set(documents.map { graph.find($0.ownerID) }).count, + componentCount: Set(documents.map { graph.find(Self.scoped($0.ownerID, document: $0)) }).count, acceptedObservationCount: acceptedObservationCount, duplicateObservationCount: physicalObservationCount - acceptedObservationCount) } + /// Routes every lineage family to either primary ledger accounting or explicit containment. + /// Contained documents are returned to the caller for a future family-scoped fallback; they never + /// contribute to `primary`, which prevents double accounting by construction. + static func reconcileConservatively( + documents: [Document], + unresolvedParents: Set = [], + localTimeZone: TimeZone, + checkCancellation: CostUsageScanner.CancellationCheck? = nil) throws -> ConservativeReport + { + var graph = DisjointSet() + for document in documents { + try checkCancellation?() + let owner = Self.scoped(document.ownerID, document: document) + graph.insert(owner) + if let metadata = Self.nonEmpty(document.metadataSessionID) { + graph.union(owner, Self.scoped(metadata, document: document)) + } + if let parent = Self.nonEmpty(document.parentSessionID) { + graph.union(owner, Self.scoped(parent, document: document)) + } + } + + var documentsByFamily: [String: [Document]] = [:] + for document in documents { + try checkCancellation?() + let family = graph.find(Self.scoped(document.ownerID, document: document)) + documentsByFamily[family, default: []].append(document) + } + + var primaryDocuments: [Document] = [] + var containedDocuments: [Document] = [] + var families: [FamilyDisposition] = [] + for familyDocuments in documentsByFamily.values { + try checkCancellation?() + let reasons = Self.containmentReasons(in: familyDocuments) + let owners = Set(familyDocuments.map(\.ownerID)) + let unresolved = familyDocuments.contains { document in + guard let parent = Self.nonEmpty(document.parentSessionID) else { return false } + return unresolvedParents.contains(.init( + scopeID: document.scopeID, + sessionID: Self.canonicalIdentity(parent))) + } + let quality: FamilyQuality + if reasons.isEmpty { + quality = unresolved ? .incompleteProvenance : .primary + primaryDocuments.append(contentsOf: familyDocuments) + } else { + quality = .contained(reasons) + containedDocuments.append(contentsOf: familyDocuments) + } + families.append(FamilyDisposition( + scopeID: familyDocuments.first?.scopeID ?? "", + ownerIDs: owners, + quality: quality)) + } + + families.sort { + ($0.scopeID, $0.ownerIDs.sorted().joined(separator: "\u{0}")) + < ($1.scopeID, $1.ownerIDs.sorted().joined(separator: "\u{0}")) + } + containedDocuments.sort(by: Self.documentComesBefore) + return try ConservativeReport( + primary: Self.reconcile( + documents: primaryDocuments, + localTimeZone: localTimeZone, + checkCancellation: checkCancellation), + families: families, + containedDocuments: containedDocuments) + } + private struct Fingerprint: Equatable, Hashable { let last: Totals let total: Totals @@ -189,6 +314,117 @@ enum CodexLineageLedger { return value } + private static func scoped(_ identity: String, document: Document) -> String { + document.scopeID + "\u{0}" + self.canonicalIdentity(identity) + } + + private static func canonicalIdentity(_ identity: String) -> String { + UUID(uuidString: identity)?.uuidString.lowercased() ?? identity + } + + private static func documentComesBefore(_ lhs: Document, _ rhs: Document) -> Bool { + let lhsKey = [ + lhs.scopeID, + lhs.ownerID, + lhs.metadataSessionID ?? "", + lhs.parentSessionID ?? "", + String(lhs.incompleteObservationCount), + lhs.observations.map(Self.observationSortKey).joined(separator: "\u{1}"), + ].joined(separator: "\u{0}") + let rhsKey = [ + rhs.scopeID, + rhs.ownerID, + rhs.metadataSessionID ?? "", + rhs.parentSessionID ?? "", + String(rhs.incompleteObservationCount), + rhs.observations.map(Self.observationSortKey).joined(separator: "\u{1}"), + ].joined(separator: "\u{0}") + return lhsKey < rhsKey + } + + private static func observationSortKey(_ observation: Observation) -> String { + [ + observation.timestamp, + observation.model, + String(observation.last.input), + String(observation.last.cached), + String(observation.last.output), + String(observation.total.input), + String(observation.total.cached), + String(observation.total.output), + ].joined(separator: "\u{0}") + } + + private static func containmentReasons(in documents: [Document]) -> Set { + var reasons: Set = [] + if documents.contains(where: { $0.incompleteObservationCount > 0 }) { + reasons.insert(.incompleteObservation) + } + if documents.flatMap(\.observations) + .contains(where: { CostUsageScanner.dateFromTimestamp($0.timestamp) == nil }) + { + reasons.insert(.malformedTimestamp) + } + let ownerGroups = Dictionary(grouping: documents, by: \.ownerID) + if ownerGroups.values.contains(where: { group in + let metadataIDs = Set(group.compactMap { Self.nonEmpty($0.metadataSessionID) }) + let parentIDs = Set(group.compactMap { Self.nonEmpty($0.parentSessionID) }) + return metadataIDs.count > 1 || parentIDs.count > 1 + }) { + reasons.insert(.conflictingOwnerIdentity) + } + let metadataGroups = Dictionary(grouping: documents.compactMap { document in + Self.nonEmpty(document.metadataSessionID).map { (Self.canonicalIdentity($0), document) } + }, by: \.0) + if metadataGroups.contains(where: { metadataID, entries in + let documents = entries.map(\.1) + let owners = Set(documents.map { Self.canonicalIdentity($0.ownerID) }) + guard owners.count > 1, !owners.contains(metadataID) else { return false } + return !documents.allSatisfy { + $0.parentSessionID.map(Self.canonicalIdentity) == metadataID + } + }) { + reasons.insert(.identityCollision) + } + if Self.hasAncestryCycle(documents) { + reasons.insert(.ancestryCycle) + } + return reasons + } + + private static func hasAncestryCycle(_ documents: [Document]) -> Bool { + let metadataOwners = Dictionary(grouping: documents.compactMap { document in + document.metadataSessionID.map { ($0, document.ownerID) } + }, by: \.0).mapValues { Set($0.map(\.1)) } + var parents: [String: Set] = [:] + for document in documents { + guard let parent = Self.nonEmpty(document.parentSessionID) else { continue } + var targets = metadataOwners[parent] ?? [parent] + if parent != document.ownerID { + targets.remove(document.ownerID) + } + parents[document.ownerID, default: []].formUnion(targets) + } + var visiting: Set = [] + var visited: Set = [] + func visit(_ owner: String) -> Bool { + if visiting.contains(owner) { + return true + } + if visited.contains(owner) { + return false + } + visiting.insert(owner) + for parent in parents[owner] ?? [] where visit(parent) { + return true + } + visiting.remove(owner) + visited.insert(owner) + return false + } + return Set(documents.map(\.ownerID)).contains(where: visit) + } + /// 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 { diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageShadow.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageShadow.swift index a6938113e0..aae024629e 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageShadow.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageShadow.swift @@ -23,6 +23,10 @@ enum CodexLineageShadow { let referencedParentDocumentCount: Int let unresolvedParentCount: Int let rejectedObservationCount: Int + let primaryFamilyCount: Int + let incompleteProvenanceFamilyCount: Int + let containedFamilyCount: Int + let containmentReasonCounts: [CodexLineageLedger.ContainmentReason: Int] } static func run( @@ -42,27 +46,27 @@ enum CodexLineageShadow { 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)) + observations: document.observations, + scopeID: document.scopeID, + incompleteObservationCount: document.incompleteObservationCount)) } - let ledger = try CodexLineageLedger.reconcile( + let conservative = try CodexLineageLedger.reconcileConservatively( documents: documents, + unresolvedParents: discovery.unresolvedParents, localTimeZone: localTimeZone, checkCancellation: checkCancellation) + let ledger = conservative.primary let legacy = Self.legacyTotalsByDay(legacyDays) let dayKeys = Set(legacy.keys).union(ledger.localDays.keys) .filter(dayRange.contains) @@ -76,8 +80,32 @@ enum CodexLineageShadow { duplicateObservationCount: ledger.duplicateObservationCount, componentCount: ledger.componentCount, referencedParentDocumentCount: discovery.referencedParentDocumentCount, - unresolvedParentCount: discovery.unresolvedParentIDs.count, - rejectedObservationCount: rejectedObservationCount) + unresolvedParentCount: discovery.unresolvedParents.count, + rejectedObservationCount: rejectedObservationCount, + primaryFamilyCount: conservative.families.count { $0.quality == .primary }, + incompleteProvenanceFamilyCount: conservative.families.count { + $0.quality == .incompleteProvenance + }, + containedFamilyCount: conservative.families.count { + if case .contained = $0.quality { + return true + } + return false + }, + containmentReasonCounts: Self.containmentReasonCounts(conservative.families)) + } + + private static func containmentReasonCounts( + _ families: [CodexLineageLedger.FamilyDisposition]) -> [CodexLineageLedger.ContainmentReason: Int] + { + var counts: [CodexLineageLedger.ContainmentReason: Int] = [:] + for family in families { + guard case let .contained(reasons) = family.quality else { continue } + for reason in reasons { + counts[reason, default: 0] += 1 + } + } + return counts } private static func legacyTotalsByDay( diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift index 782bed8ee0..950b41eb50 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift @@ -103,6 +103,7 @@ enum CostUsageScanner { let forkedFromId: String? let snapshots: [CodexTimestampedTotals] let observations: [CodexLineageLedger.Observation] + let incompleteObservationCount: Int } enum CodexForkBaseline { @@ -1696,6 +1697,7 @@ enum CostUsageScanner { var accumulator = CodexSnapshotAccumulator() var snapshots: [CodexTimestampedTotals] = [] var observations: [CodexLineageLedger.Observation] = [] + var incompleteObservationCount = 0 var warnedAboutUnparsedTimestamp = false var currentTurnID: String? var tokenEventCountByTurn: [String: Int] = [:] @@ -1719,6 +1721,9 @@ enum CostUsageScanner { total: CostUsageCodexTotals?) { guard last != nil || total != nil else { return } + if last == nil || total == nil { + incompleteObservationCount += 1 + } let counted = accumulator.apply(last: last, total: total) snapshots.append(CodexTimestampedTotals( timestamp: timestamp, @@ -1748,7 +1753,13 @@ enum CostUsageScanner { prefixBytes: 512 * 1024, checkCancellation: checkCancellation, onLine: { line in - guard !line.bytes.isEmpty, !line.wasTruncated else { return } + guard !line.bytes.isEmpty else { return } + if line.wasTruncated { + if line.bytes.range(of: Data(#""token_count""#.utf8)) != nil { + incompleteObservationCount += 1 + } + return + } if let fastLine = Self.parseCodexFastLine(line.bytes) { switch fastLine { case let .sessionMeta(metadata): @@ -1862,7 +1873,8 @@ enum CostUsageScanner { sessionId: sessionId, forkedFromId: forkedFromId, snapshots: snapshots, - observations: observations) + observations: observations, + incompleteObservationCount: incompleteObservationCount) } static func parseCodexLineageDocument( @@ -1876,7 +1888,18 @@ enum CostUsageScanner { ownerID: Self.codexRolloutOwnerID(fileURL: fileURL) ?? parsed.sessionId ?? fileURL.standardizedFileURL.path, metadataSessionID: parsed.sessionId, parentSessionID: parsed.forkedFromId, - observations: parsed.observations) + observations: parsed.observations, + scopeID: Self.codexLineageScopeID(fileURL: fileURL), + incompleteObservationCount: parsed.incompleteObservationCount) + } + + static func codexLineageScopeID(fileURL: URL) -> String { + let standardized = fileURL.standardizedFileURL + let components = standardized.pathComponents + if let index = components.lastIndex(where: { $0 == "sessions" || $0 == "archived_sessions" }) { + return NSString.path(withComponents: Array(components[.. CodexLineageLedger.Totals { diff --git a/Tests/CodexBarTests/CodexLineageDiscoveryTests.swift b/Tests/CodexBarTests/CodexLineageDiscoveryTests.swift index ae6fda55d7..1b2ea1b2e0 100644 --- a/Tests/CodexBarTests/CodexLineageDiscoveryTests.swift +++ b/Tests/CodexBarTests/CodexLineageDiscoveryTests.swift @@ -41,7 +41,7 @@ struct CodexLineageDiscoveryTests { #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(report.unresolvedParents.isEmpty) #expect(FileManager.default.fileExists(atPath: parent.path)) #expect(FileManager.default.fileExists(atPath: grandparent.path)) } @@ -63,7 +63,9 @@ struct CodexLineageDiscoveryTests { #expect(report.documents.count == 1) #expect(report.referencedParentDocumentCount == 0) - #expect(report.unresolvedParentIDs == ["66666666-6666-4666-8666-666666666666"]) + #expect(report.unresolvedParents == [.init( + scopeID: environment.codexSessionsRoot.deletingLastPathComponent().path, + sessionID: "66666666-6666-4666-8666-666666666666")]) } @Test @@ -89,7 +91,100 @@ struct CodexLineageDiscoveryTests { #expect(report.documents.map(\.ownerID).contains(parentID)) #expect(report.referencedParentDocumentCount == 1) - #expect(report.unresolvedParentIDs.isEmpty) + #expect(report.unresolvedParents.isEmpty) + } + + @Test + func `parent lookup never crosses normalized Codex homes`() throws { + let environment = try CostUsageTestEnvironment() + defer { environment.cleanup() } + let parentID = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + let otherHomeSessions = environment.root + .appendingPathComponent("other-home", isDirectory: true) + .appendingPathComponent("sessions", isDirectory: true) + _ = try Self.writeRollout( + root: otherHomeSessions, + relativeDirectory: "2026/07/09", + ownerID: parentID, + metadataID: parentID) + let child = try Self.writeRollout( + root: environment.codexSessionsRoot, + relativeDirectory: "2026/07/09", + ownerID: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + metadataID: "child", + parentID: parentID) + + let report = try CodexLineageDiscovery.discover( + includedFiles: [child], + roots: [environment.codexSessionsRoot, otherHomeSessions]) + + #expect(report.documents.map(\.ownerID) == ["bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"]) + #expect(report.unresolvedParents == [.init( + scopeID: environment.codexSessionsRoot.deletingLastPathComponent().path, + sessionID: parentID)]) + } + + @Test + func `ambiguous parent identity is unresolved instead of path-order dependent`() throws { + let environment = try CostUsageTestEnvironment() + defer { environment.cleanup() } + let parentAlias = "shared-parent-alias" + _ = try Self.writeRollout( + root: environment.codexArchivedSessionsRoot, + relativeDirectory: "", + ownerID: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + metadataID: parentAlias) + _ = try Self.writeRollout( + root: environment.codexSessionsRoot, + relativeDirectory: "2025/01/01", + ownerID: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + metadataID: parentAlias) + let child = try Self.writeRollout( + root: environment.codexSessionsRoot, + relativeDirectory: "2026/07/09", + ownerID: "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + metadataID: "child", + parentID: parentAlias) + + let report = try CodexLineageDiscovery.discover( + includedFiles: [child], + roots: [environment.codexSessionsRoot, environment.codexArchivedSessionsRoot]) + + #expect(report.documents.count == 1) + #expect(report.unresolvedParents == [.init( + scopeID: environment.codexSessionsRoot.deletingLastPathComponent().path, + sessionID: parentAlias)]) + } + + @Test + func `compatible active and archived copies of one parent are both retained`() 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) + _ = try Self.writeRollout( + root: environment.codexSessionsRoot, + relativeDirectory: "2025/01/01", + ownerID: parentID, + metadataID: parentID) + let child = try Self.writeRollout( + root: environment.codexSessionsRoot, + relativeDirectory: "2026/07/09", + ownerID: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + metadataID: "child", + parentID: parentID) + + let report = try CodexLineageDiscovery.discover( + includedFiles: [child], + roots: [environment.codexSessionsRoot, environment.codexArchivedSessionsRoot]) + + #expect(report.documents.count == 3) + #expect(report.referencedParentDocumentCount == 2) + #expect(report.unresolvedParents.isEmpty) } @Test diff --git a/Tests/CodexBarTests/CodexLineageLedgerTests.swift b/Tests/CodexBarTests/CodexLineageLedgerTests.swift index aca6368d34..4d23eb192d 100644 --- a/Tests/CodexBarTests/CodexLineageLedgerTests.swift +++ b/Tests/CodexBarTests/CodexLineageLedgerTests.swift @@ -32,6 +32,8 @@ struct CodexLineageLedgerTests { #expect(document.metadataSessionID == "metadata-id") #expect(document.parentSessionID == "parent-id") #expect(document.observations.count == 2) + #expect(document.incompleteObservationCount == 1) + #expect(document.scopeID == environment.root.path) #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)) @@ -309,6 +311,168 @@ struct CodexLineageLedgerTests { #expect(forward.utcDays["2026-07-10"] == nil) } + @Test + func `incomplete evidence contains the entire family without primary contribution`() throws { + let observation = Self.observation(timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) + let parent = Self.document(owner: "parent", observations: [observation]) + let child = CodexLineageLedger.Document( + ownerID: "child", + metadataSessionID: "child", + parentSessionID: "parent", + observations: [observation], + incompleteObservationCount: 1) + + let report = try CodexLineageLedger.reconcileConservatively( + documents: [parent, child], + localTimeZone: .gmt) + + #expect(report.primary.utcDays.isEmpty) + #expect(report.containedDocuments.count == 2) + #expect(report.families == [.init( + scopeID: "", + ownerIDs: ["parent", "child"], + quality: .contained([.incompleteObservation]))]) + } + + @Test + func `missing ancestry lowers provenance without discarding unique descendant usage`() throws { + let child = Self.document( + owner: "child", + parent: "missing-parent", + observations: [Self.observation( + timestamp: "2026-07-09T12:00:00Z", + input: 100, + totalInput: 100)]) + + let report = try CodexLineageLedger.reconcileConservatively( + documents: [child], + unresolvedParents: [.init(sessionID: "missing-parent")], + localTimeZone: .gmt) + + #expect(report.primary.utcDays["2026-07-09"]?.input == 100) + #expect(report.containedDocuments.isEmpty) + #expect(report.families.first?.quality == .incompleteProvenance) + } + + @Test + func `malformed timestamps and ancestry cycles route families to containment deterministically`() throws { + let malformed = Self.observation(timestamp: "not-a-time", input: 20, totalInput: 20) + let first = Self.document(owner: "first", parent: "second", observations: [malformed]) + let second = Self.document(owner: "second", parent: "first", observations: []) + + let forward = try CodexLineageLedger.reconcileConservatively( + documents: [first, second], + localTimeZone: .gmt) + let reversed = try CodexLineageLedger.reconcileConservatively( + documents: [second, first], + localTimeZone: .gmt) + + #expect(forward == reversed) + #expect(forward.primary.utcDays.isEmpty) + #expect(forward.families.first?.quality == .contained([.malformedTimestamp, .ancestryCycle])) + } + + @Test + func `identical identities in separate Codex homes remain additive`() throws { + let observation = Self.observation(timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) + let first = CodexLineageLedger.Document( + ownerID: "same-owner", + metadataSessionID: "same-metadata", + parentSessionID: nil, + observations: [observation], + scopeID: "/first-home") + let second = CodexLineageLedger.Document( + ownerID: "same-owner", + metadataSessionID: "same-metadata", + parentSessionID: nil, + observations: [observation], + scopeID: "/second-home") + + let report = try CodexLineageLedger.reconcileConservatively( + documents: [first, second], + localTimeZone: .gmt) + + #expect(report.primary.utcDays["2026-07-09"]?.input == 200) + #expect(report.primary.componentCount == 2) + #expect(report.families.count == 2) + } + + @Test + func `conflicting physical copies of one owner are contained`() throws { + let observation = Self.observation(timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) + let first = Self.document(owner: "same-owner", metadata: "first", observations: [observation]) + let second = Self.document(owner: "same-owner", metadata: "second", observations: [observation]) + + let report = try CodexLineageLedger.reconcileConservatively( + documents: [first, second], + localTimeZone: .gmt) + + #expect(report.primary.utcDays.isEmpty) + #expect(report.families.first?.quality == .contained([.conflictingOwnerIdentity])) + } + + @Test + func `missing optional identity fields remain compatible with a complete copy`() throws { + let observation = Self.observation(timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) + let complete = Self.document( + owner: "same-owner", + metadata: "metadata", + parent: "parent", + observations: [observation]) + let partial = Self.document(owner: "same-owner", observations: [observation]) + + let report = try CodexLineageLedger.reconcileConservatively( + documents: [partial, complete], + localTimeZone: .gmt) + + #expect(report.primary.utcDays["2026-07-09"]?.input == 100) + #expect(report.containedDocuments.isEmpty) + #expect(report.families.first?.quality == .primary) + } + + @Test + func `retained ancestor metadata does not manufacture an ancestry cycle`() throws { + let observation = Self.observation(timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) + let root = Self.document(owner: "root", metadata: "root", observations: [observation]) + let child = Self.document( + owner: "child", + metadata: "root", + parent: "root", + observations: [observation]) + + let report = try CodexLineageLedger.reconcileConservatively( + documents: [root, child], + localTimeZone: .gmt) + + #expect(report.primary.utcDays["2026-07-09"]?.input == 100) + #expect(report.families.first?.quality == .primary) + } + + @Test + func `unrelated owners sharing an ungrounded metadata identity are contained`() throws { + let first = Self.document( + owner: "first", + metadata: "shared", + observations: [Self.observation( + timestamp: "2026-07-09T12:00:00Z", + input: 100, + totalInput: 100)]) + let second = Self.document( + owner: "second", + metadata: "shared", + observations: [Self.observation( + timestamp: "2026-07-09T12:01:00Z", + input: 200, + totalInput: 200)]) + + let report = try CodexLineageLedger.reconcileConservatively( + documents: [first, second], + localTimeZone: .gmt) + + #expect(report.primary.utcDays.isEmpty) + #expect(report.families.first?.quality == .contained([.identityCollision])) + } + private static func document( owner: String, metadata: String? = nil, diff --git a/Tests/CodexBarTests/CodexLineageShadowTests.swift b/Tests/CodexBarTests/CodexLineageShadowTests.swift index 51eb59adfc..b6c5454070 100644 --- a/Tests/CodexBarTests/CodexLineageShadowTests.swift +++ b/Tests/CodexBarTests/CodexLineageShadowTests.swift @@ -36,13 +36,16 @@ struct CodexLineageShadowTests { #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) + ledger: .zero)]) + #expect(report.days[0].delta == .init(input: -150, cached: -20, output: -10)) + #expect(report.acceptedObservationCount == 0) + #expect(report.duplicateObservationCount == 0) + #expect(report.componentCount == 0) #expect(report.rejectedObservationCount == 1) #expect(report.unresolvedParentCount == 0) + #expect(report.primaryFamilyCount == 0) + #expect(report.containedFamilyCount == 1) + #expect(report.containmentReasonCounts == [.malformedTimestamp: 1]) } private static func writeRollout( From 7ed371546859b0123fda5f679aeb372cec1b8c65 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Mon, 13 Jul 2026 14:42:06 -0400 Subject: [PATCH 12/28] Bound Codex lineage reconciliation --- .../Providers/Codex/CodexLineageEngine.swift | 429 ++++++++++++++++++ .../CodexLineageEngineTests.swift | 208 +++++++++ 2 files changed, 637 insertions(+) create mode 100644 Sources/CodexBarCore/Providers/Codex/CodexLineageEngine.swift create mode 100644 Tests/CodexBarTests/CodexLineageEngineTests.swift diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageEngine.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageEngine.swift new file mode 100644 index 0000000000..8e9e3c921c --- /dev/null +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageEngine.swift @@ -0,0 +1,429 @@ +#if canImport(CryptoKit) +import CryptoKit +#else +import Crypto +#endif +import Foundation + +/// Pure, in-memory preparation and reuse seam for lineage families. +/// +/// Callers retain the previous cache until `publish` succeeds. This keeps cancellation and failed +/// reconciliation from exposing a partially rebuilt set of families. +enum CodexLineageEngine { + private static let algorithmVersion = 1 + + struct Fingerprint: Equatable, Hashable, Sendable { + let value: String + } + + struct PreparedFamily: Equatable, Sendable { + let stableID: String + let inputFingerprint: Fingerprint + let documents: [CodexLineageLedger.Document] + let unresolvedParents: Set + let observationCount: Int + } + + struct FamilyResult: Equatable, Sendable { + let stableID: String + let inputFingerprint: Fingerprint + let familyFingerprint: Fingerprint + let quality: CodexLineageLedger.FamilyQuality + let report: CodexLineageLedger.Report + } + + struct Cache: Equatable, Sendable { + let algorithmVersion: Int + let familiesByInputFingerprint: [Fingerprint: FamilyResult] + + static let empty = Self(algorithmVersion: CodexLineageEngine.algorithmVersion, familiesByInputFingerprint: [:]) + } + + struct Diagnostics: Equatable, Sendable { + let familyCount: Int + let recomputedFamilyCount: Int + let reusedFamilyCount: Int + let observationCount: Int + let peakFamilyObservationCount: Int + let peakAcceptedFingerprintCount: Int + } + + struct Result: Equatable, Sendable { + let report: CodexLineageLedger.Report + let families: [FamilyResult] + let candidateCache: Cache + let diagnostics: Diagnostics + } + + static func prepareFamilies( + documents: [CodexLineageLedger.Document], + unresolvedParents: Set = [], + checkCancellation: CostUsageScanner.CancellationCheck? = nil) throws -> [PreparedFamily] + { + var graph = DisjointSet() + for document in documents { + try checkCancellation?() + let owner = Self.scoped(document.ownerID, scopeID: document.scopeID) + graph.insert(owner) + if let metadata = Self.nonEmpty(document.metadataSessionID) { + graph.union(owner, Self.scoped(metadata, scopeID: document.scopeID)) + } + if let parent = Self.nonEmpty(document.parentSessionID) { + graph.union(owner, Self.scoped(parent, scopeID: document.scopeID)) + } + } + + var documentsByRoot: [String: [CodexLineageLedger.Document]] = [:] + for document in documents { + try checkCancellation?() + let root = graph.find(Self.scoped(document.ownerID, scopeID: document.scopeID)) + documentsByRoot[root, default: []].append(document) + } + + var families: [PreparedFamily] = [] + families.reserveCapacity(documentsByRoot.count) + for familyDocuments in documentsByRoot.values { + try checkCancellation?() + var keyedDocuments: [(document: CodexLineageLedger.Document, key: Fingerprint)] = [] + keyedDocuments.reserveCapacity(familyDocuments.count) + for document in familyDocuments { + try checkCancellation?() + try keyedDocuments.append(( + document: document, + key: Self.documentFingerprint(document, checkCancellation: checkCancellation))) + } + keyedDocuments.sort { $0.key.value < $1.key.value } + let sortedDocuments = keyedDocuments.map(\.document) + let identities = Set(sortedDocuments.flatMap { document in + [document.ownerID, document.metadataSessionID, document.parentSessionID].compactMap { value in + Self.nonEmpty(value).map { Self.scoped($0, scopeID: document.scopeID) } + } + }) + let familyUnresolved = unresolvedParents.filter { parent in + identities.contains(Self.scoped(parent.sessionID, scopeID: parent.scopeID)) + } + let stableID = identities.min() ?? "" + let fingerprint = try Self.inputFingerprint( + documents: sortedDocuments, + unresolvedParents: familyUnresolved, + checkCancellation: checkCancellation) + families.append(PreparedFamily( + stableID: stableID, + inputFingerprint: fingerprint, + documents: sortedDocuments, + unresolvedParents: familyUnresolved, + observationCount: sortedDocuments.reduce(0) { $0 + $1.observations.count })) + } + return families.sorted { $0.stableID < $1.stableID } + } + + static func reconcile( + families: [PreparedFamily], + previousCache: Cache? = nil, + localTimeZone: TimeZone, + checkCancellation: CostUsageScanner.CancellationCheck? = nil) throws -> Result + { + let reusable = previousCache?.algorithmVersion == Self.algorithmVersion + ? previousCache?.familiesByInputFingerprint ?? [:] + : [:] + var results: [FamilyResult] = [] + results.reserveCapacity(families.count) + var recomputed = 0 + var reused = 0 + var peakAccepted = 0 + + for family in families.sorted(by: { $0.stableID < $1.stableID }) { + try checkCancellation?() + let cacheFingerprint = Self.cacheFingerprint( + input: family.inputFingerprint, + localTimeZone: localTimeZone) + if let cached = reusable[cacheFingerprint], + cached.stableID == family.stableID, + cached.inputFingerprint == family.inputFingerprint + { + results.append(cached) + reused += 1 + peakAccepted = max(peakAccepted, cached.report.acceptedObservationCount) + continue + } + let conservative = try CodexLineageLedger.reconcileConservatively( + documents: family.documents, + unresolvedParents: family.unresolvedParents, + localTimeZone: localTimeZone, + checkCancellation: checkCancellation) + let quality = conservative.families.first?.quality ?? .primary + let familyFingerprint = Self.familyFingerprint(input: cacheFingerprint, quality: quality) + let result = FamilyResult( + stableID: family.stableID, + inputFingerprint: family.inputFingerprint, + familyFingerprint: familyFingerprint, + quality: quality, + report: conservative.primary) + results.append(result) + recomputed += 1 + peakAccepted = max(peakAccepted, result.report.acceptedObservationCount) + } + + results.sort { $0.stableID < $1.stableID } + let report = try Self.compose(results.map(\.report), checkCancellation: checkCancellation) + let candidate = Cache( + algorithmVersion: Self.algorithmVersion, + familiesByInputFingerprint: Dictionary(uniqueKeysWithValues: results.map { + (Self.cacheFingerprint(input: $0.inputFingerprint, localTimeZone: localTimeZone), $0) + })) + try checkCancellation?() + return Result( + report: report, + families: results, + candidateCache: candidate, + diagnostics: Diagnostics( + familyCount: families.count, + recomputedFamilyCount: recomputed, + reusedFamilyCount: reused, + observationCount: families.reduce(0) { $0 + $1.observationCount }, + peakFamilyObservationCount: families.map(\.observationCount).max() ?? 0, + peakAcceptedFingerprintCount: peakAccepted)) + } + + static func publish( + _ candidate: Cache, + to cache: inout Cache, + checkCancellation: CostUsageScanner.CancellationCheck? = nil) throws + { + try checkCancellation?() + cache = candidate + } + + private static func compose( + _ reports: [CodexLineageLedger.Report], + checkCancellation: CostUsageScanner.CancellationCheck?) throws -> CodexLineageLedger.Report + { + var utcDays: [String: CodexLineageLedger.Totals] = [:] + var localDays: [String: CodexLineageLedger.Totals] = [:] + var utcRows: [RowKey: RowValue] = [:] + var localRows: [RowKey: RowValue] = [:] + for report in reports { + try checkCancellation?() + try Self.mergeDays(report.utcDays, into: &utcDays, checkCancellation: checkCancellation) + try Self.mergeDays(report.localDays, into: &localDays, checkCancellation: checkCancellation) + try Self.mergeRows(report.utcRows, into: &utcRows, checkCancellation: checkCancellation) + try Self.mergeRows(report.localRows, into: &localRows, checkCancellation: checkCancellation) + } + return CodexLineageLedger.Report( + utcDays: utcDays, + localDays: localDays, + utcRows: Self.rows(utcRows), + localRows: Self.rows(localRows), + componentCount: reports.reduce(0) { $0 + $1.componentCount }, + acceptedObservationCount: reports.reduce(0) { $0 + $1.acceptedObservationCount }, + duplicateObservationCount: reports.reduce(0) { $0 + $1.duplicateObservationCount }) + } + + private struct RowKey: Hashable { + let day: String + let model: String + } + + private struct RowValue { + var totals = CodexLineageLedger.Totals.zero + var costUSD = 0.0 + var isPriced = true + } + + private static func mergeDays( + _ source: [String: CodexLineageLedger.Totals], + into destination: inout [String: CodexLineageLedger.Totals], + checkCancellation: CostUsageScanner.CancellationCheck?) throws + { + for (day, totals) in source { + try checkCancellation?() + var combined = destination[day] ?? .zero + combined.add(totals) + destination[day] = combined + } + } + + private static func mergeRows( + _ source: [CodexLineageLedger.DailyRow], + into destination: inout [RowKey: RowValue], + checkCancellation: CostUsageScanner.CancellationCheck?) throws + { + for row in source { + try checkCancellation?() + let key = RowKey(day: row.day, model: row.model) + var value = destination[key] ?? RowValue() + value.totals.add(row.totals) + if let cost = row.costUSD { + value.costUSD += cost + } else { + value.isPriced = false + } + destination[key] = value + } + } + + private static func rows(_ values: [RowKey: RowValue]) -> [CodexLineageLedger.DailyRow] { + values.map { key, value in + .init(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 inputFingerprint( + documents: [CodexLineageLedger.Document], + unresolvedParents: Set, + checkCancellation: CostUsageScanner.CancellationCheck?) throws -> Fingerprint + { + var digest = DigestBuilder() + digest.append("codex-lineage-family") + digest.append(String(Self.algorithmVersion)) + for document in documents { + try checkCancellation?() + digest.append(contentsOf: [ + "document", document.scopeID, Self.canonical(document.ownerID), + document.metadataSessionID.map(Self.canonical) ?? "", + document.parentSessionID.map(Self.canonical) ?? "", + String(document.incompleteObservationCount), + ]) + let observations = document.observations.sorted(by: Self.observationComesBefore) + for observation in observations { + try checkCancellation?() + digest.append(contentsOf: [ + "observation", observation.timestamp, observation.model, + String(observation.last.input), String(observation.last.cached), String(observation.last.output), + String(observation.total.input), String(observation.total.cached), String(observation.total.output), + ]) + } + } + for parent in unresolvedParents.sorted(by: { ($0.scopeID, $0.sessionID) < ($1.scopeID, $1.sessionID) }) { + digest.append(contentsOf: ["unresolved", parent.scopeID, Self.canonical(parent.sessionID)]) + } + return digest.finalize() + } + + private static func familyFingerprint( + input: Fingerprint, + quality: CodexLineageLedger.FamilyQuality) -> Fingerprint + { + let qualityFields = switch quality { + case .primary: + ["primary"] + case .incompleteProvenance: + ["incompleteProvenance"] + case let .contained(reasons): + ["contained"] + reasons.map(\.rawValue).sorted() + } + return Self.digest(["codex-lineage-family-result", input.value] + qualityFields) + } + + private static func cacheFingerprint(input: Fingerprint, localTimeZone: TimeZone) -> Fingerprint { + self.digest([ + "codex-lineage-family-projection", + input.value, + localTimeZone.identifier, + ]) + } + + private static func digest(_ fields: [String]) -> Fingerprint { + var digest = DigestBuilder() + for field in fields { + digest.append(field) + } + return digest.finalize() + } + + private static func documentFingerprint( + _ document: CodexLineageLedger.Document, + checkCancellation: CostUsageScanner.CancellationCheck?) throws -> Fingerprint + { + let observations = document.observations.sorted(by: Self.observationComesBefore) + var digest = DigestBuilder() + digest.append(contentsOf: [ + document.scopeID, Self.canonical(document.ownerID), + document.metadataSessionID.map(Self.canonical) ?? "", + document.parentSessionID.map(Self.canonical) ?? "", + String(document.incompleteObservationCount), + ]) + for observation in observations { + try checkCancellation?() + digest.append(contentsOf: [ + observation.timestamp, observation.model, + String(observation.last.input), String(observation.last.cached), String(observation.last.output), + String(observation.total.input), String(observation.total.cached), String(observation.total.output), + ]) + } + return digest.finalize() + } + + private static func observationComesBefore( + _ lhs: CodexLineageLedger.Observation, + _ rhs: CodexLineageLedger.Observation) -> Bool + { + self.observationKey(lhs) < self.observationKey(rhs) + } + + private static func observationKey(_ observation: CodexLineageLedger.Observation) -> String { + [ + observation.timestamp, observation.model, + String(observation.last.input), String(observation.last.cached), String(observation.last.output), + String(observation.total.input), String(observation.total.cached), String(observation.total.output), + ].joined(separator: "\u{0}") + } + + private static func nonEmpty(_ value: String?) -> String? { + guard let value, !value.isEmpty else { return nil } + return value + } + + private static func canonical(_ value: String) -> String { + UUID(uuidString: value)?.uuidString.lowercased() ?? value + } + + private static func scoped(_ value: String, scopeID: String) -> String { + scopeID + "\u{0}" + self.canonical(value) + } + + private struct DisjointSet { + var parents: [String: String] = [:] + + mutating func insert(_ value: String) { + self.parents[value] = self.parents[value] ?? value + } + + mutating func find(_ value: String) -> String { + self.insert(value) + guard let parent = self.parents[value], parent != value else { return value } + let root = self.find(parent) + self.parents[value] = root + return root + } + + mutating func union(_ lhs: String, _ rhs: String) { + let lhsRoot = self.find(lhs) + let rhsRoot = self.find(rhs) + guard lhsRoot != rhsRoot else { return } + let first = min(lhsRoot, rhsRoot) + let second = max(lhsRoot, rhsRoot) + self.parents[second] = first + } + } + + private struct DigestBuilder { + private var hasher = SHA256() + + mutating func append(_ field: String) { + var length = UInt64(field.utf8.count).bigEndian + withUnsafeBytes(of: &length) { self.hasher.update(bufferPointer: $0) } + self.hasher.update(data: Data(field.utf8)) + } + + mutating func append(contentsOf fields: [String]) { + for field in fields { + self.append(field) + } + } + + consuming func finalize() -> Fingerprint { + Fingerprint(value: self.hasher.finalize().map { String(format: "%02x", $0) }.joined()) + } + } +} diff --git a/Tests/CodexBarTests/CodexLineageEngineTests.swift b/Tests/CodexBarTests/CodexLineageEngineTests.swift new file mode 100644 index 0000000000..2ad505de2e --- /dev/null +++ b/Tests/CodexBarTests/CodexLineageEngineTests.swift @@ -0,0 +1,208 @@ +import Foundation +import Testing +@testable import CodexBarCore + +struct CodexLineageEngineTests { + @Test + func `family fingerprints and totals are deterministic under input permutation`() throws { + let first = Self.document(owner: "first", observations: [ + Self.observation(timestamp: "2026-07-09T12:01:00Z", input: 20, total: 30), + Self.observation(timestamp: "2026-07-09T12:00:00Z", input: 10, total: 10), + ]) + let child = Self.document(owner: "child", parent: "first", observations: [ + Self.observation(timestamp: "2026-07-09T12:02:00Z", input: 5, total: 35), + ]) + let other = Self.document(owner: "other", observations: [ + Self.observation(timestamp: "2026-07-09T13:00:00Z", input: 7, total: 7), + ]) + + let forwardFamilies = try CodexLineageEngine.prepareFamilies(documents: [first, child, other]) + let reverseFamilies = try CodexLineageEngine.prepareFamilies(documents: [other, child, first]) + let forward = try CodexLineageEngine.reconcile(families: forwardFamilies, localTimeZone: .gmt) + let reverse = try CodexLineageEngine.reconcile(families: reverseFamilies, localTimeZone: .gmt) + + #expect(forward.families.map(\.familyFingerprint) == reverse.families.map(\.familyFingerprint)) + #expect(forward.report == reverse.report) + #expect(forward.report.utcDays["2026-07-09"]?.input == 42) + } + + @Test + func `warm reconciliation reuses unchanged families and recomputes only a changed family`() throws { + let first = Self.document(owner: "first", observations: [Self.observation(input: 10, total: 10)]) + let second = Self.document(owner: "second", observations: [Self.observation(input: 20, total: 20)]) + let initialFamilies = try CodexLineageEngine.prepareFamilies(documents: [first, second]) + let initial = try CodexLineageEngine.reconcile(families: initialFamilies, localTimeZone: .gmt) + let warm = try CodexLineageEngine.reconcile( + families: initialFamilies, + previousCache: initial.candidateCache, + localTimeZone: .gmt) + + let changed = Self.document(owner: "second", observations: [Self.observation(input: 25, total: 25)]) + let changedFamilies = try CodexLineageEngine.prepareFamilies(documents: [first, changed]) + let updated = try CodexLineageEngine.reconcile( + families: changedFamilies, + previousCache: initial.candidateCache, + localTimeZone: .gmt) + + #expect(warm.diagnostics.reusedFamilyCount == 2) + #expect(warm.diagnostics.recomputedFamilyCount == 0) + #expect(updated.diagnostics.reusedFamilyCount == 1) + #expect(updated.diagnostics.recomputedFamilyCount == 1) + #expect(updated.report.utcDays["2026-07-09"]?.input == 35) + } + + @Test + func `cache entries with mismatched family identity are recomputed`() throws { + let first = Self.document(owner: "first", observations: [Self.observation(input: 10, total: 10)]) + let second = Self.document(owner: "second", observations: [Self.observation(input: 20, total: 20)]) + let families = try CodexLineageEngine.prepareFamilies(documents: [first, second]) + let initial = try CodexLineageEngine.reconcile(families: families, localTimeZone: .gmt) + let entries = initial.candidateCache.familiesByInputFingerprint.sorted { $0.key.value < $1.key.value } + let mismatched = CodexLineageEngine.Cache( + algorithmVersion: initial.candidateCache.algorithmVersion, + familiesByInputFingerprint: [entries[0].key: entries[1].value, entries[1].key: entries[0].value]) + + let rebuilt = try CodexLineageEngine.reconcile( + families: families, + previousCache: mismatched, + localTimeZone: .gmt) + + #expect(rebuilt.diagnostics.reusedFamilyCount == 0) + #expect(rebuilt.diagnostics.recomputedFamilyCount == 2) + #expect(rebuilt.report.utcDays["2026-07-09"]?.input == 30) + } + + @Test + func `changing projection timezone recomputes families and does not reuse stale local days`() throws { + let document = Self.document(owner: "first", observations: [ + Self.observation(timestamp: "2026-07-10T02:00:00Z", input: 10, total: 10), + ]) + let families = try CodexLineageEngine.prepareFamilies(documents: [document]) + let utc = try CodexLineageEngine.reconcile(families: families, localTimeZone: .gmt) + let newYork = try CodexLineageEngine.reconcile( + families: families, + previousCache: utc.candidateCache, + localTimeZone: #require(TimeZone(identifier: "America/New_York"))) + + #expect(newYork.diagnostics.reusedFamilyCount == 0) + #expect(newYork.diagnostics.recomputedFamilyCount == 1) + #expect(utc.report.localDays["2026-07-10"]?.input == 10) + #expect(newYork.report.localDays["2026-07-09"]?.input == 10) + } + + @Test + func `new parent merges only affected families while unrelated family remains reusable`() throws { + let child = Self.document( + owner: "child", + parent: "parent", + observations: [Self.observation(input: 10, total: 10)]) + let parent = Self.document(owner: "parent", observations: [Self.observation(input: 20, total: 20)]) + let unrelated = Self.document(owner: "other", observations: [Self.observation(input: 5, total: 5)]) + let unresolved: Set = [.init(sessionID: "parent")] + let initialFamilies = try CodexLineageEngine.prepareFamilies( + documents: [child, unrelated], + unresolvedParents: unresolved) + let initial = try CodexLineageEngine.reconcile(families: initialFamilies, localTimeZone: .gmt) + + let resolvedFamilies = try CodexLineageEngine.prepareFamilies(documents: [child, parent, unrelated]) + let resolved = try CodexLineageEngine.reconcile( + families: resolvedFamilies, + previousCache: initial.candidateCache, + localTimeZone: .gmt) + + #expect(resolved.diagnostics.familyCount == 2) + #expect(resolved.diagnostics.reusedFamilyCount == 1) + #expect(resolved.diagnostics.recomputedFamilyCount == 1) + #expect(resolved.report.utcDays["2026-07-09"]?.input == 35) + } + + @Test + func `cancelled publication leaves the prior cache unchanged`() throws { + let family = try CodexLineageEngine.prepareFamilies(documents: [ + Self.document(owner: "first", observations: [Self.observation(input: 10, total: 10)]), + ]) + let result = try CodexLineageEngine.reconcile(families: family, localTimeZone: .gmt) + var published = CodexLineageEngine.Cache.empty + + #expect(throws: CancellationError.self) { + try CodexLineageEngine.publish( + result.candidateCache, + to: &published, + checkCancellation: { throw CancellationError() }) + } + #expect(published == .empty) + + try CodexLineageEngine.publish(result.candidateCache, to: &published) + #expect(published == result.candidateCache) + } + + @Test + func `structural diagnostics bound reconciliation scratch state to the largest family`() throws { + let repeated = (0..<2000).map { index in + Self.observation( + timestamp: String(format: "2026-07-09T12:%02d:%02dZ", (index / 60) % 60, index % 60), + input: 1, + total: index + 1) + } + let documents = [ + Self.document(owner: "large", observations: repeated), + Self.document(owner: "small", observations: Array(repeated.prefix(100))), + ] + let families = try CodexLineageEngine.prepareFamilies(documents: documents) + let result = try CodexLineageEngine.reconcile(families: families, localTimeZone: .gmt) + + #expect(result.diagnostics.observationCount == 2100) + #expect(result.diagnostics.peakFamilyObservationCount == 2000) + #expect(result.diagnostics.peakAcceptedFingerprintCount <= 2000) + } + + @Test + func `cancellation propagates during preparation and reconciliation without a candidate`() throws { + let documents = (0..<20).map { index in + Self.document(owner: "owner-\(index)", observations: [Self.observation(input: index + 1, total: index + 1)]) + } + var preparationChecks = 0 + #expect(throws: CancellationError.self) { + _ = try CodexLineageEngine.prepareFamilies(documents: documents) { + preparationChecks += 1 + if preparationChecks == 5 { + throw CancellationError() + } + } + } + + let families = try CodexLineageEngine.prepareFamilies(documents: documents) + var reconciliationChecks = 0 + #expect(throws: CancellationError.self) { + _ = try CodexLineageEngine.reconcile(families: families, localTimeZone: .gmt) { + reconciliationChecks += 1 + if reconciliationChecks == 5 { + throw CancellationError() + } + } + } + } + + private static func document( + owner: String, + parent: String? = nil, + observations: [CodexLineageLedger.Observation]) -> CodexLineageLedger.Document + { + .init( + ownerID: owner, + metadataSessionID: owner, + parentSessionID: parent, + observations: observations) + } + + private static func observation( + timestamp: String = "2026-07-09T12:00:00Z", + input: Int, + total: Int) -> CodexLineageLedger.Observation + { + .init( + timestamp: timestamp, + last: .init(input: input, cached: 0, output: 0), + total: .init(input: total, cached: 0, output: 0)) + } +} From 686390717a7104bc245769f3c3e732aeed4bf545 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Mon, 13 Jul 2026 15:06:25 -0400 Subject: [PATCH 13/28] Stream Codex lineage families --- .../Generated/CodexParserHash.generated.swift | 2 +- .../Providers/Codex/CodexLineageEngine.swift | 168 +++++++++++++ .../Codex/CodexLineageTwoPassDiscovery.swift | 227 ++++++++++++++++++ .../Vendored/CostUsage/CostUsageScanner.swift | 90 ++++--- .../CodexLineageTwoPassDiscoveryTests.swift | 152 ++++++++++++ 5 files changed, 604 insertions(+), 35 deletions(-) create mode 100644 Sources/CodexBarCore/Providers/Codex/CodexLineageTwoPassDiscovery.swift create mode 100644 Tests/CodexBarTests/CodexLineageTwoPassDiscoveryTests.swift diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index 90a5d3ff38..c0e0dd2e96 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 = "4c426503aec9cb49" + static let value = "84c52421265e9fc8" } diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageEngine.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageEngine.swift index 8e9e3c921c..22bd558301 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageEngine.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageEngine.swift @@ -24,6 +24,14 @@ enum CodexLineageEngine { let observationCount: Int } + struct PreparedDescriptorFamily: Equatable, Sendable { + let stableID: String + let inputFingerprint: Fingerprint + let descriptors: [CodexLineageTwoPassDiscovery.Descriptor] + let unresolvedParents: Set + let observationCount: Int + } + struct FamilyResult: Equatable, Sendable { let stableID: String let inputFingerprint: Fingerprint @@ -117,6 +125,134 @@ enum CodexLineageEngine { return families.sorted { $0.stableID < $1.stableID } } + static func prepareDescriptorFamilies( + descriptors: [CodexLineageTwoPassDiscovery.Descriptor], + unresolvedParents: Set = [], + checkCancellation: CostUsageScanner.CancellationCheck? = nil) throws -> [PreparedDescriptorFamily] + { + var graph = DisjointSet() + for descriptor in descriptors { + try checkCancellation?() + let owner = Self.scoped(descriptor.ownerID, scopeID: descriptor.scopeID) + graph.insert(owner) + if let metadata = Self.nonEmpty(descriptor.metadataSessionID) { + graph.union(owner, Self.scoped(metadata, scopeID: descriptor.scopeID)) + } + if let parent = Self.nonEmpty(descriptor.parentSessionID) { + graph.union(owner, Self.scoped(parent, scopeID: descriptor.scopeID)) + } + } + var descriptorsByRoot: [String: [CodexLineageTwoPassDiscovery.Descriptor]] = [:] + for descriptor in descriptors { + try checkCancellation?() + let root = graph.find(Self.scoped(descriptor.ownerID, scopeID: descriptor.scopeID)) + descriptorsByRoot[root, default: []].append(descriptor) + } + var families: [PreparedDescriptorFamily] = [] + for var familyDescriptors in descriptorsByRoot.values { + try checkCancellation?() + familyDescriptors.sort { Self.descriptorKey($0) < Self.descriptorKey($1) } + let identities = Set(familyDescriptors.flatMap { descriptor in + [descriptor.ownerID, descriptor.metadataSessionID, descriptor.parentSessionID].compactMap { value in + Self.nonEmpty(value).map { Self.scoped($0, scopeID: descriptor.scopeID) } + } + }) + let familyUnresolved = unresolvedParents.filter { + identities.contains(Self.scoped($0.sessionID, scopeID: $0.scopeID)) + } + let fingerprint = Self.descriptorFamilyFingerprint( + descriptors: familyDescriptors, + unresolvedParents: familyUnresolved) + families.append(.init( + stableID: identities.min() ?? "", + inputFingerprint: fingerprint, + descriptors: familyDescriptors, + unresolvedParents: familyUnresolved, + observationCount: familyDescriptors.reduce(0) { $0 + $1.observationCount })) + } + return families.sorted { $0.stableID < $1.stableID } + } + + static func reconcileStreaming( + families: [PreparedDescriptorFamily], + previousCache: Cache? = nil, + localTimeZone: TimeZone, + checkCancellation: CostUsageScanner.CancellationCheck? = nil, + loadDocument: ((CodexLineageTwoPassDiscovery.Descriptor) throws -> CodexLineageLedger.Document)? = nil) throws + -> Result + { + let reusable = previousCache?.algorithmVersion == Self.algorithmVersion + ? previousCache?.familiesByInputFingerprint ?? [:] + : [:] + var results: [FamilyResult] = [] + var recomputed = 0 + var reused = 0 + var peakLoadedObservations = 0 + var peakAccepted = 0 + for family in families.sorted(by: { $0.stableID < $1.stableID }) { + try checkCancellation?() + let cacheFingerprint = Self.cacheFingerprint( + input: family.inputFingerprint, + localTimeZone: localTimeZone) + if let cached = reusable[cacheFingerprint], cached.stableID == family.stableID { + results.append(cached) + reused += 1 + peakAccepted = max(peakAccepted, cached.report.acceptedObservationCount) + continue + } + var documents: [CodexLineageLedger.Document] = [] + documents.reserveCapacity(family.descriptors.count) + var loadedObservations = 0 + for descriptor in family.descriptors { + try checkCancellation?() + let document: CodexLineageLedger.Document = if let loadDocument { + try loadDocument(descriptor) + } else { + try CodexLineageTwoPassDiscovery.loadDocument( + descriptor, + checkCancellation: checkCancellation) + } + loadedObservations += document.observations.count + documents.append(document) + } + peakLoadedObservations = max(peakLoadedObservations, loadedObservations) + let conservative = try CodexLineageLedger.reconcileConservatively( + documents: documents, + unresolvedParents: family.unresolvedParents, + localTimeZone: localTimeZone, + checkCancellation: checkCancellation) + let quality = conservative.families.first?.quality ?? .primary + let result = FamilyResult( + stableID: family.stableID, + inputFingerprint: family.inputFingerprint, + familyFingerprint: Self.familyFingerprint(input: cacheFingerprint, quality: quality), + quality: quality, + report: conservative.primary) + results.append(result) + recomputed += 1 + peakAccepted = max(peakAccepted, result.report.acceptedObservationCount) + } + results.sort { $0.stableID < $1.stableID } + let report = try Self.compose(results.map(\.report), checkCancellation: checkCancellation) + let candidate = Cache( + algorithmVersion: Self.algorithmVersion, + familiesByInputFingerprint: Dictionary(uniqueKeysWithValues: results.map { + (Self.cacheFingerprint(input: $0.inputFingerprint, localTimeZone: localTimeZone), $0) + })) + try checkCancellation?() + return Result( + report: report, + families: results, + candidateCache: candidate, + diagnostics: .init( + familyCount: families.count, + recomputedFamilyCount: recomputed, + reusedFamilyCount: reused, + observationCount: families.reduce(0) { $0 + $1.observationCount }, + peakFamilyObservationCount: peakLoadedObservations, + peakAcceptedFingerprintCount: peakAccepted)) + } + static func reconcile( families: [PreparedFamily], previousCache: Cache? = nil, @@ -300,6 +436,38 @@ enum CodexLineageEngine { return digest.finalize() } + private static func descriptorFamilyFingerprint( + descriptors: [CodexLineageTwoPassDiscovery.Descriptor], + unresolvedParents: Set) -> Fingerprint + { + var digest = DigestBuilder() + digest.append(contentsOf: ["codex-lineage-descriptor-family", String(Self.algorithmVersion)]) + for descriptor in descriptors { + digest.append(contentsOf: [ + "descriptor", descriptor.scopeID, Self.canonical(descriptor.ownerID), + descriptor.metadataSessionID.map(Self.canonical) ?? "", + descriptor.parentSessionID.map(Self.canonical) ?? "", + String(descriptor.incompleteObservationCount), String(descriptor.observationCount), + String(descriptor.signature.size), String(descriptor.signature.modifiedMilliseconds), + descriptor.signature.contentSHA256, + ]) + } + for parent in unresolvedParents.sorted(by: { ($0.scopeID, $0.sessionID) < ($1.scopeID, $1.sessionID) }) { + digest.append(contentsOf: ["unresolved", parent.scopeID, Self.canonical(parent.sessionID)]) + } + return digest.finalize() + } + + private static func descriptorKey(_ descriptor: CodexLineageTwoPassDiscovery.Descriptor) -> String { + [ + descriptor.scopeID, self.canonical(descriptor.ownerID), + descriptor.metadataSessionID.map(self.canonical) ?? "", + descriptor.parentSessionID.map(self.canonical) ?? "", + descriptor.signature.contentSHA256, + descriptor.fileURL.standardizedFileURL.path, + ].joined(separator: "\u{0}") + } + private static func familyFingerprint( input: Fingerprint, quality: CodexLineageLedger.FamilyQuality) -> Fingerprint diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageTwoPassDiscovery.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageTwoPassDiscovery.swift new file mode 100644 index 0000000000..16edd16396 --- /dev/null +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageTwoPassDiscovery.swift @@ -0,0 +1,227 @@ +#if canImport(CryptoKit) +import CryptoKit +#else +import Crypto +#endif +import Foundation + +/// Pass-one lineage index. It retains file identity and quality evidence, never token observations. +enum CodexLineageTwoPassDiscovery { + enum DiscoveryError: Error, Equatable { + case fileChangedDuringScan + } + + struct FileSignature: Equatable, Hashable, Sendable { + let size: Int64 + let modifiedMilliseconds: Int64 + let contentSHA256: String + } + + struct Descriptor: Equatable, Sendable { + let fileURL: URL + let ownerID: String + let metadataSessionID: String? + let parentSessionID: String? + let scopeID: String + let incompleteObservationCount: Int + let observationCount: Int + let signature: FileSignature + } + + struct Report: Equatable, Sendable { + let descriptors: [Descriptor] + let referencedParentDocumentCount: Int + let unresolvedParents: Set + let peakRetainedObservationCount: Int + } + + static func discover( + includedFiles: [URL], + roots: [URL], + checkCancellation: CostUsageScanner.CancellationCheck? = nil) throws -> Report + { + var locator = ParentLocator(roots: roots, checkCancellation: checkCancellation) + var descriptors: [Descriptor] = [] + var known: Set = [] + var pending: [ScopedIdentity] = [] + var unresolved: Set = [] + var seenPaths: Set = [] + var referencedParents = 0 + + func remember(_ descriptor: Descriptor) { + descriptors.append(descriptor) + known.insert(.init(scopeID: descriptor.scopeID, sessionID: Self.canonical(descriptor.ownerID))) + if let metadata = Self.nonEmpty(descriptor.metadataSessionID) { + known.insert(.init(scopeID: descriptor.scopeID, sessionID: Self.canonical(metadata))) + } + if let parent = Self.nonEmpty(descriptor.parentSessionID) { + pending.append(.init(scopeID: descriptor.scopeID, sessionID: Self.canonical(parent))) + } + } + + for fileURL in includedFiles.sorted(by: { $0.path < $1.path }) { + try checkCancellation?() + guard seenPaths.insert(fileURL.standardizedFileURL.path).inserted else { continue } + try remember(Self.describe(fileURL: fileURL, checkCancellation: checkCancellation)) + } + + var nextParent = 0 + while nextParent < pending.count { + try checkCancellation?() + let identity = pending[nextParent] + nextParent += 1 + let unresolvedIdentity = CodexLineageLedger.ParentIdentity( + scopeID: identity.scopeID, + sessionID: identity.sessionID) + guard !known.contains(identity), !unresolved.contains(unresolvedIdentity) else { continue } + guard let matches = try locator.fileURLs(for: identity) else { + unresolved.insert(unresolvedIdentity) + continue + } + var found = false + for fileURL in matches { + try checkCancellation?() + guard seenPaths.insert(fileURL.standardizedFileURL.path).inserted else { continue } + let descriptor = try Self.describe(fileURL: fileURL, checkCancellation: checkCancellation) + let owner = Self.canonical(descriptor.ownerID) + let metadata = descriptor.metadataSessionID.map(Self.canonical) + guard owner == identity.sessionID || metadata == identity.sessionID else { continue } + remember(descriptor) + referencedParents += 1 + found = true + } + if !found { + unresolved.insert(unresolvedIdentity) + } + } + + return Report( + descriptors: descriptors.sorted { $0.fileURL.path < $1.fileURL.path }, + referencedParentDocumentCount: referencedParents, + unresolvedParents: unresolved, + peakRetainedObservationCount: 0) + } + + static func describe( + fileURL: URL, + checkCancellation: CostUsageScanner.CancellationCheck? = nil) throws -> Descriptor + { + // Validate both sides of the parse. A post-parse signature alone can bless a + // mixed read when a rollout is replaced while the parser has the file open. + let initialSignature = try Self.signature(fileURL: fileURL, checkCancellation: checkCancellation) + let summary = try CostUsageScanner.parseCodexLineageDocumentSummary( + fileURL: fileURL, + checkCancellation: checkCancellation) + let finalSignature = try Self.signature(fileURL: fileURL, checkCancellation: checkCancellation) + guard initialSignature == finalSignature else { throw DiscoveryError.fileChangedDuringScan } + return Descriptor( + fileURL: fileURL, + ownerID: summary.ownerID, + metadataSessionID: summary.metadataSessionID, + parentSessionID: summary.parentSessionID, + scopeID: summary.scopeID, + incompleteObservationCount: summary.incompleteObservationCount, + observationCount: summary.observationCount, + signature: finalSignature) + } + + static func loadDocument( + _ descriptor: Descriptor, + checkCancellation: CostUsageScanner.CancellationCheck? = nil) throws -> CodexLineageLedger.Document + { + guard try self.signature(fileURL: descriptor.fileURL, checkCancellation: checkCancellation) == descriptor + .signature + else { throw DiscoveryError.fileChangedDuringScan } + let document = try CostUsageScanner.parseCodexLineageDocument( + fileURL: descriptor.fileURL, + checkCancellation: checkCancellation) + guard try Self.signature(fileURL: descriptor.fileURL, checkCancellation: checkCancellation) == descriptor + .signature + else { throw DiscoveryError.fileChangedDuringScan } + return document + } + + private static func signature( + fileURL: URL, + checkCancellation: CostUsageScanner.CancellationCheck?) throws -> FileSignature + { + let values = try fileURL.resourceValues(forKeys: [.fileSizeKey, .contentModificationDateKey]) + let handle = try FileHandle(forReadingFrom: fileURL) + defer { try? handle.close() } + var hasher = SHA256() + while true { + try checkCancellation?() + let data = try handle.read(upToCount: 256 * 1024) ?? Data() + guard !data.isEmpty else { break } + hasher.update(data: data) + } + let digest = hasher.finalize().map { String(format: "%02x", $0) }.joined() + return FileSignature( + size: Int64(values.fileSize ?? 0), + modifiedMilliseconds: Int64((values.contentModificationDate?.timeIntervalSince1970 ?? 0) * 1000), + contentSHA256: digest) + } + + private struct ScopedIdentity: Equatable, Hashable { + let scopeID: String + let sessionID: String + } + + private struct ParentLocator { + let roots: [URL] + let checkCancellation: CostUsageScanner.CancellationCheck? + var indexed = false + var filesByIdentity: [ScopedIdentity: Set] = [:] + + mutating func fileURLs(for identity: ScopedIdentity) throws -> [URL]? { + if !self.indexed { + try self.index() + } + guard let matches = self.filesByIdentity[identity], !matches.isEmpty else { return nil } + let owners = Set(matches.compactMap(CostUsageScanner.codexRolloutOwnerID(fileURL:))) + guard matches.count == 1 || owners.count == 1 else { return nil } + return matches.sorted { $0.path < $1.path } + } + + private mutating func index() throws { + self.indexed = true + 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 } + let scopeID = CostUsageScanner.codexLineageScopeID(fileURL: fileURL) + if let owner = CostUsageScanner.codexRolloutOwnerID(fileURL: fileURL) { + self.filesByIdentity[ + .init(scopeID: scopeID, sessionID: CodexLineageTwoPassDiscovery.canonical(owner)), + default: [], + ].insert(fileURL) + } + if let metadata = try CostUsageScanner.parseCodexSessionIdentifier( + fileURL: fileURL, + checkCancellation: self.checkCancellation) + { + self.filesByIdentity[ + .init(scopeID: scopeID, sessionID: CodexLineageTwoPassDiscovery.canonical(metadata)), + default: [], + ].insert(fileURL) + } + } + } + } + } + + private static func canonical(_ value: String) -> String { + UUID(uuidString: value)?.uuidString.lowercased() ?? value + } + + private static func nonEmpty(_ value: String?) -> String? { + guard let value, !value.isEmpty else { return nil } + return value + } +} diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift index 950b41eb50..88f724d30e 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift @@ -104,6 +104,16 @@ enum CostUsageScanner { let snapshots: [CodexTimestampedTotals] let observations: [CodexLineageLedger.Observation] let incompleteObservationCount: Int + let observationCount: Int + } + + struct CodexLineageDocumentSummary: Equatable, Sendable { + let ownerID: String + let metadataSessionID: String? + let parentSessionID: String? + let scopeID: String + let incompleteObservationCount: Int + let observationCount: Int } enum CodexForkBaseline { @@ -1689,6 +1699,8 @@ enum CostUsageScanner { // swiftlint:disable:next cyclomatic_complexity function_body_length private static func parseCodexTokenSnapshots( fileURL: URL, + retainEvidence: Bool = true, + suppressScanErrors: Bool = true, checkCancellation: CancellationCheck? = nil) throws -> CodexParsedTokenEvidence { var sessionId: String? @@ -1698,9 +1710,8 @@ enum CostUsageScanner { var snapshots: [CodexTimestampedTotals] = [] var observations: [CodexLineageLedger.Observation] = [] var incompleteObservationCount = 0 + var observationCount = 0 var warnedAboutUnparsedTimestamp = false - var currentTurnID: String? - var tokenEventCountByTurn: [String: Int] = [:] func parsedSnapshotDate(timestamp: String) -> Date? { let date = Self.dateFromTimestamp(timestamp) @@ -1716,7 +1727,6 @@ enum CostUsageScanner { func appendSnapshot( timestamp: String, model: String?, - turnID: String?, last: CostUsageCodexTotals?, total: CostUsageCodexTotals?) { @@ -1724,25 +1734,24 @@ enum CostUsageScanner { if last == nil || total == nil { incompleteObservationCount += 1 } - 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 retainEvidence { + let counted = accumulator.apply(last: last, total: total) + snapshots.append(CodexTimestampedTotals( + timestamp: timestamp, + date: parsedSnapshotDate(timestamp: timestamp), + totals: counted)) } if let last, let total { - 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))) + observationCount += 1 + if retainEvidence { + observations.append(CodexLineageLedger.Observation( + timestamp: timestamp, + model: Self.codexModelEvidence(model) + ?? Self.codexModelEvidence(currentModel) + ?? CostUsagePricing.codexUnattributedModel, + last: Self.lineageTotals(last), + total: Self.lineageTotals(total))) + } } } @@ -1773,15 +1782,14 @@ enum CostUsageScanner { 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 .taskStarted: + break } return } @@ -1822,10 +1830,6 @@ 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 } @@ -1853,17 +1857,15 @@ enum CostUsageScanner { ?? 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) + appendSnapshot(timestamp: timestamp, model: model, last: last, total: total) } }) } catch is CancellationError { throw CancellationError() } catch { + if !suppressScanErrors { + throw error + } self.log.warning( "Codex cost usage failed while scanning parent token snapshots", metadata: ["path": fileURL.path, "error": error.localizedDescription]) @@ -1874,7 +1876,8 @@ enum CostUsageScanner { forkedFromId: forkedFromId, snapshots: snapshots, observations: observations, - incompleteObservationCount: incompleteObservationCount) + incompleteObservationCount: incompleteObservationCount, + observationCount: observationCount) } static func parseCodexLineageDocument( @@ -1883,6 +1886,7 @@ enum CostUsageScanner { { let parsed = try Self.parseCodexTokenSnapshots( fileURL: fileURL, + suppressScanErrors: false, checkCancellation: checkCancellation) return CodexLineageLedger.Document( ownerID: Self.codexRolloutOwnerID(fileURL: fileURL) ?? parsed.sessionId ?? fileURL.standardizedFileURL.path, @@ -1893,6 +1897,24 @@ enum CostUsageScanner { incompleteObservationCount: parsed.incompleteObservationCount) } + static func parseCodexLineageDocumentSummary( + fileURL: URL, + checkCancellation: CancellationCheck? = nil) throws -> CodexLineageDocumentSummary + { + let parsed = try Self.parseCodexTokenSnapshots( + fileURL: fileURL, + retainEvidence: false, + suppressScanErrors: false, + checkCancellation: checkCancellation) + return CodexLineageDocumentSummary( + ownerID: Self.codexRolloutOwnerID(fileURL: fileURL) ?? parsed.sessionId ?? fileURL.standardizedFileURL.path, + metadataSessionID: parsed.sessionId, + parentSessionID: parsed.forkedFromId, + scopeID: Self.codexLineageScopeID(fileURL: fileURL), + incompleteObservationCount: parsed.incompleteObservationCount, + observationCount: parsed.observationCount) + } + static func codexLineageScopeID(fileURL: URL) -> String { let standardized = fileURL.standardizedFileURL let components = standardized.pathComponents diff --git a/Tests/CodexBarTests/CodexLineageTwoPassDiscoveryTests.swift b/Tests/CodexBarTests/CodexLineageTwoPassDiscoveryTests.swift new file mode 100644 index 0000000000..11a750c2bf --- /dev/null +++ b/Tests/CodexBarTests/CodexLineageTwoPassDiscoveryTests.swift @@ -0,0 +1,152 @@ +import Foundation +import Testing +@testable import CodexBarCore + +struct CodexLineageTwoPassDiscoveryTests { + @Test + func `pass one retains descriptors without observations and follows exceptional archived parents`() throws { + let environment = try CostUsageTestEnvironment() + defer { environment.cleanup() } + let parentID = "11111111-1111-4111-8111-111111111111" + let childID = "22222222-2222-4222-8222-222222222222" + _ = try Self.writeRollout( + root: environment.codexArchivedSessionsRoot, + ownerID: parentID, + observations: 3) + let child = try Self.writeRollout( + root: environment.codexSessionsRoot.appendingPathComponent("2026/07/09"), + ownerID: childID, + parentID: parentID, + observations: 2) + + let report = try CodexLineageTwoPassDiscovery.discover( + includedFiles: [child], + roots: [environment.codexSessionsRoot, environment.codexArchivedSessionsRoot]) + + #expect(report.descriptors.count == 2) + #expect(report.descriptors.reduce(0) { $0 + $1.observationCount } == 5) + #expect(report.referencedParentDocumentCount == 1) + #expect(report.unresolvedParents.isEmpty) + #expect(report.peakRetainedObservationCount == 0) + } + + @Test + func `streaming reconciliation loads one family at a time and warm reuse loads none`() throws { + let environment = try CostUsageTestEnvironment() + defer { environment.cleanup() } + let first = try Self.writeRollout(root: environment.codexSessionsRoot, ownerID: Self.uuid(1), observations: 50) + let second = try Self.writeRollout(root: environment.codexSessionsRoot, ownerID: Self.uuid(2), observations: 20) + let discovery = try CodexLineageTwoPassDiscovery.discover(includedFiles: [first, second], roots: []) + let families = try CodexLineageEngine.prepareDescriptorFamilies(descriptors: discovery.descriptors) + var coldLoads = 0 + let cold = try CodexLineageEngine.reconcileStreaming( + families: families, + localTimeZone: .gmt, + loadDocument: { descriptor in + coldLoads += 1 + return try CodexLineageTwoPassDiscovery.loadDocument(descriptor) + }) + var warmLoads = 0 + let warm = try CodexLineageEngine.reconcileStreaming( + families: families, + previousCache: cold.candidateCache, + localTimeZone: .gmt, + loadDocument: { descriptor in + warmLoads += 1 + return try CodexLineageTwoPassDiscovery.loadDocument(descriptor) + }) + + #expect(coldLoads == 2) + #expect(cold.diagnostics.observationCount == 70) + #expect(cold.diagnostics.peakFamilyObservationCount == 50) + #expect(warmLoads == 0) + #expect(warm.diagnostics.reusedFamilyCount == 2) + #expect(warm.report == cold.report) + } + + @Test + func `descriptor and family fingerprints are deterministic under file permutation`() throws { + let environment = try CostUsageTestEnvironment() + defer { environment.cleanup() } + let first = try Self.writeRollout(root: environment.codexSessionsRoot, ownerID: Self.uuid(3), observations: 2) + let second = try Self.writeRollout(root: environment.codexSessionsRoot, ownerID: Self.uuid(4), observations: 2) + let forward = try CodexLineageTwoPassDiscovery.discover(includedFiles: [first, second], roots: []) + let reverse = try CodexLineageTwoPassDiscovery.discover(includedFiles: [second, first], roots: []) + let forwardFamilies = try CodexLineageEngine.prepareDescriptorFamilies(descriptors: forward.descriptors) + let reverseFamilies = try CodexLineageEngine.prepareDescriptorFamilies(descriptors: reverse.descriptors) + + #expect(forwardFamilies.map(\.inputFingerprint) == reverseFamilies.map(\.inputFingerprint)) + #expect(forwardFamilies.map(\.stableID) == reverseFamilies.map(\.stableID)) + } + + @Test + func `cancellation before streaming completion produces no candidate`() throws { + let environment = try CostUsageTestEnvironment() + defer { environment.cleanup() } + let file = try Self.writeRollout(root: environment.codexSessionsRoot, ownerID: Self.uuid(5), observations: 100) + let discovery = try CodexLineageTwoPassDiscovery.discover(includedFiles: [file], roots: []) + let families = try CodexLineageEngine.prepareDescriptorFamilies(descriptors: discovery.descriptors) + var checks = 0 + #expect(throws: CancellationError.self) { + _ = try CodexLineageEngine.reconcileStreaming( + families: families, + localTimeZone: .gmt, + checkCancellation: { + checks += 1 + if checks == 4 { + throw CancellationError() + } + }) + } + } + + @Test + func `pass two rejects a rollout changed after discovery`() throws { + let environment = try CostUsageTestEnvironment() + defer { environment.cleanup() } + let file = try Self.writeRollout( + root: environment.codexSessionsRoot, + ownerID: Self.uuid(6), + observations: 2) + let discovery = try CodexLineageTwoPassDiscovery.discover(includedFiles: [file], roots: []) + let descriptor = try #require(discovery.descriptors.first) + try "\n".append(to: file) + + #expect(throws: CodexLineageTwoPassDiscovery.DiscoveryError.fileChangedDuringScan) { + _ = try CodexLineageTwoPassDiscovery.loadDocument(descriptor) + } + } + + private static func writeRollout( + root: URL, + ownerID: String, + parentID: String? = nil, + observations: Int) throws -> URL + { + try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true) + let file = root.appendingPathComponent("rollout-2026-07-09T00-00-00-\(ownerID).jsonl") + var lines = [#"{"type":"session_meta","payload":{"id":"\#(ownerID)""# + + (parentID.map { #", "forked_from_id":"\#($0)""# } ?? "") + "}}"] + lines += (0.. String { + String(format: "00000000-0000-4000-8000-%012d", value) + } +} + +extension String { + fileprivate func append(to fileURL: URL) throws { + let handle = try FileHandle(forWritingTo: fileURL) + defer { try? handle.close() } + try handle.seekToEnd() + try handle.write(contentsOf: Data(self.utf8)) + } +} From 2f16a690e0460ec8b48f64c53fae1c17edf8e916 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Mon, 13 Jul 2026 15:25:37 -0400 Subject: [PATCH 14/28] Define Codex lineage promotion gates --- .../CodexLineagePromotionEvaluator.swift | 227 ++++++++++++++++++ .../CodexLineagePromotionEvaluatorTests.swift | 204 ++++++++++++++++ 2 files changed, 431 insertions(+) create mode 100644 Sources/CodexBarCore/Providers/Codex/CodexLineagePromotionEvaluator.swift create mode 100644 Tests/CodexBarTests/CodexLineagePromotionEvaluatorTests.swift diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineagePromotionEvaluator.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineagePromotionEvaluator.swift new file mode 100644 index 0000000000..300197d5dd --- /dev/null +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineagePromotionEvaluator.swift @@ -0,0 +1,227 @@ +import Foundation + +/// Evidence-based gate for promoting lineage accounting behind a reversible authority switch. +/// +/// This evaluator does not choose token totals or tune an error percentage. It verifies that the +/// independently collected correctness and operational evidence is complete enough to promote. +enum CodexLineagePromotionEvaluator { + enum CancellationStage: String, CaseIterable, Equatable, Hashable, Sendable { + case rootIndexing + case parsing + case graphConstruction + case reconciliation + case prePublication + } + + struct FamilyRoutingEvidence: Equatable, Sendable { + let primaryFamilyCount: Int + let containedFamilyCount: Int + let doubleContributionFamilyCount: Int + let permanentContainmentSupported: Bool + } + + struct PerformanceEvidence: Equatable, Sendable { + let coldMeasured: Bool + let warmMeasured: Bool + let memoryBoundMeasured: Bool + /// Set only after review finds a correctness-neutral regression material enough to block promotion. + let hasMaterialRegression: Bool + } + + struct RollbackEvidence: Equatable, Sendable { + let legacyWholeScanAvailable: Bool + let rollbackPathVerified: Bool + } + + struct Input: Equatable, Sendable { + let residualReport: CodexLineageResidualClassifier.Report + /// Finalized fork-heavy UTC days whose ledger error must be lower than legacy error. + let targetDays: Set + /// Large residuals require an explicit human-reviewed classification before promotion. + let reviewedResidualDays: Set + let adversarialGoldensPassed: Bool + let boundedDiscoveryPassed: Bool + let familyRouting: FamilyRoutingEvidence + let performance: PerformanceEvidence + let cancellationStages: Set + let atomicPublicationPassed: Bool + let rollback: RollbackEvidence + } + + enum Blocker: String, CaseIterable, Equatable, Hashable, Sendable { + case invalidResidualEvidence + case missingTargetDay + case provisionalTargetDay + case targetDayNotImproved + case aggregateErrorNotImproved + case ordinaryDayRegression + case unreviewedLargeResidual + case ledgerDefect + case adversarialGoldenFailure + case boundedDiscoveryFailure + case containmentUnavailable + case familyDoubleContribution + case performanceEvidenceMissing + case materialPerformanceRegression + case cancellationEvidenceMissing + case atomicPublicationFailure + case legacyRollbackUnavailable + case rollbackUnverified + } + + struct Decision: Equatable, Sendable { + /// Canonical case order keeps logs, snapshots, and review artifacts deterministic. + let blockers: [Blocker] + + var canPromote: Bool { + self.blockers.isEmpty + } + + /// Promotion never removes the permanent family-level containment route. + let keepsFamilyContainment: Bool + /// Legacy remains a whole-scan emergency authority until its dedicated removal work. + let keepsLegacyEmergencyRollback: Bool + } + + static func evaluate(_ input: Input) -> Decision { + var blockers: Set = [] + let report = input.residualReport + Self.addReportBlockers(report, to: &blockers) + var daysByID: [String: CodexLineageResidualClassifier.DayResult] = [:] + for day in report.days { + if daysByID.updateValue(day, forKey: day.day) != nil { + blockers.insert(.invalidResidualEvidence) + } + if day.classification == .ledgerDefect { + blockers.insert(.ledgerDefect) + } + if Self.requiresReview(day.classification), !input.reviewedResidualDays.contains(day.day) { + blockers.insert(.unreviewedLargeResidual) + } + } + Self.addTargetBlockers(input.targetDays, daysByID: daysByID, to: &blockers) + Self.addOperationalBlockers(input, to: &blockers) + + return Decision( + blockers: Blocker.allCases.filter(blockers.contains), + keepsFamilyContainment: input.familyRouting.permanentContainmentSupported, + keepsLegacyEmergencyRollback: input.rollback.legacyWholeScanAvailable) + } + + private static func addReportBlockers( + _ report: CodexLineageResidualClassifier.Report, + to blockers: inout Set) + { + if !self.hasValidShape(report) || report.invalidSampleCount > 0 { + blockers.insert(.invalidResidualEvidence) + } + if !report.improvesAggregateError { + blockers.insert(.aggregateErrorNotImproved) + } + if report.ordinaryDayRegressionCount > 0 { + blockers.insert(.ordinaryDayRegression) + } + } + + private static func addTargetBlockers( + _ targetDays: Set, + daysByID: [String: CodexLineageResidualClassifier.DayResult], + to blockers: inout Set) + { + if targetDays.isEmpty { + blockers.insert(.missingTargetDay) + } + for targetDay in targetDays { + guard let day = daysByID[targetDay] else { + blockers.insert(.missingTargetDay) + continue + } + if day.classification == .provisional { + blockers.insert(.provisionalTargetDay) + } else if day.classification == .invalidInput { + blockers.insert(.invalidResidualEvidence) + } else if let legacyError = day.legacyAbsoluteError, let ledgerError = day.ledgerAbsoluteError, + ledgerError >= legacyError + { + blockers.insert(.targetDayNotImproved) + } + } + } + + private static func addOperationalBlockers(_ input: Input, to blockers: inout Set) { + if !input.adversarialGoldensPassed { + blockers.insert(.adversarialGoldenFailure) + } + if !input.boundedDiscoveryPassed { + blockers.insert(.boundedDiscoveryFailure) + } + if !input.familyRouting.permanentContainmentSupported { + blockers.insert(.containmentUnavailable) + } + if input.familyRouting.doubleContributionFamilyCount != 0 { + blockers.insert(.familyDoubleContribution) + } + let familyCountIsInvalid = input.familyRouting.primaryFamilyCount < 0 + || input.familyRouting.containedFamilyCount < 0 + || input.familyRouting.doubleContributionFamilyCount < 0 + || (input.familyRouting.primaryFamilyCount == 0 && input.familyRouting.containedFamilyCount == 0) + if familyCountIsInvalid { + blockers.insert(.invalidResidualEvidence) + } + if !input.performance.coldMeasured || !input.performance.warmMeasured || !input.performance + .memoryBoundMeasured + { + blockers.insert(.performanceEvidenceMissing) + } + if input.performance.hasMaterialRegression { + blockers.insert(.materialPerformanceRegression) + } + if input.cancellationStages != Set(CancellationStage.allCases) { + blockers.insert(.cancellationEvidenceMissing) + } + if !input.atomicPublicationPassed { + blockers.insert(.atomicPublicationFailure) + } + if !input.rollback.legacyWholeScanAvailable { + blockers.insert(.legacyRollbackUnavailable) + } + if !input.rollback.rollbackPathVerified { + blockers.insert(.rollbackUnverified) + } + } + + private static func hasValidShape(_ report: CodexLineageResidualClassifier.Report) -> Bool { + guard report.invalidSampleCount >= 0, + report.ordinaryDayRegressionCount >= 0, + report.finalizedReferenceTokens >= 0, + report.finalizedLegacyTokens >= 0, + report.finalizedLedgerTokens >= 0, + report.legacyAbsoluteError >= 0, + report.ledgerAbsoluteError >= 0, + report.invalidSampleCount == report.days.count(where: { $0.classification == .invalidInput }), + report.legacyAbsoluteError == abs(report.finalizedLegacyTokens - report.finalizedReferenceTokens), + report.ledgerAbsoluteError == abs(report.finalizedLedgerTokens - report.finalizedReferenceTokens) + else { return false } + return report.days.allSatisfy { day in + switch day.classification { + case .invalidInput, .provisional: + day.legacyAbsoluteError == nil && day.ledgerAbsoluteError == nil + case .withinTolerance, .unavailableHistory, .unsupportedEventShape, .utcLocalAttribution, + .accountingSemantics, .containment, .ledgerDefect: + day.legacyAbsoluteError != nil && day.ledgerAbsoluteError != nil + } + } + } + + private static func requiresReview(_ classification: CodexLineageResidualClassifier.Classification) -> Bool { + switch classification { + case .withinTolerance: + false + case .invalidInput, .provisional: + false + case .unavailableHistory, .unsupportedEventShape, .utcLocalAttribution, .accountingSemantics, + .containment, .ledgerDefect: + true + } + } +} diff --git a/Tests/CodexBarTests/CodexLineagePromotionEvaluatorTests.swift b/Tests/CodexBarTests/CodexLineagePromotionEvaluatorTests.swift new file mode 100644 index 0000000000..ba29ae74af --- /dev/null +++ b/Tests/CodexBarTests/CodexLineagePromotionEvaluatorTests.swift @@ -0,0 +1,204 @@ +import Testing +@testable import CodexBarCore + +struct CodexLineagePromotionEvaluatorTests { + @Test + func `complete evidence promotes while retaining containment and legacy rollback`() { + let decision = CodexLineagePromotionEvaluator.evaluate(Self.readyInput()) + + #expect(decision.canPromote) + #expect(decision.blockers.isEmpty) + #expect(decision.keepsFamilyContainment) + #expect(decision.keepsLegacyEmergencyRollback) + } + + @Test + func `promotion is evidence based rather than a fixed aggregate percentage`() { + let report = CodexLineageResidualClassifier.classify(samples: [ + Self.sample( + day: "2026-07-09", + reference: 1_000_000, + legacy: 100_000, + ledger: 100_001, + ordinary: false, + exhaustive: true), + ]) + var input = Self.readyInput(report: report, targetDays: ["2026-07-09"]) + input = .init( + residualReport: input.residualReport, + targetDays: input.targetDays, + reviewedResidualDays: ["2026-07-09"], + adversarialGoldensPassed: input.adversarialGoldensPassed, + boundedDiscoveryPassed: input.boundedDiscoveryPassed, + familyRouting: input.familyRouting, + performance: input.performance, + cancellationStages: input.cancellationStages, + atomicPublicationPassed: input.atomicPublicationPassed, + rollback: input.rollback) + + let decision = CodexLineagePromotionEvaluator.evaluate(input) + #expect(decision.canPromote) + } + + @Test + func `ordinary regression and unreviewed residual block promotion`() { + let report = CodexLineageResidualClassifier.classify(samples: [ + Self.sample(day: "2026-07-09", reference: 1000, legacy: 500, ledger: 900, ordinary: false), + Self.sample(day: "2026-07-10", reference: 1000, legacy: 1000, ledger: 1100, ordinary: true), + ]) + let ready = Self.readyInput(report: report, targetDays: ["2026-07-09"]) + let input = CodexLineagePromotionEvaluator.Input( + residualReport: ready.residualReport, + targetDays: ready.targetDays, + reviewedResidualDays: [], + adversarialGoldensPassed: ready.adversarialGoldensPassed, + boundedDiscoveryPassed: ready.boundedDiscoveryPassed, + familyRouting: ready.familyRouting, + performance: ready.performance, + cancellationStages: ready.cancellationStages, + atomicPublicationPassed: ready.atomicPublicationPassed, + rollback: ready.rollback) + + let decision = CodexLineagePromotionEvaluator.evaluate(input) + #expect(decision.blockers.contains(.ordinaryDayRegression)) + #expect(decision.blockers.contains(.unreviewedLargeResidual)) + #expect(!decision.canPromote) + } + + @Test + func `non target large residual still requires review`() { + let report = CodexLineageResidualClassifier.classify(samples: [ + Self.sample(day: "2026-07-09", reference: 1000, legacy: 500, ledger: 950, ordinary: false), + Self.sample(day: "2026-07-10", reference: 1000, legacy: 100, ledger: 900, ordinary: false), + ]) + let ready = Self.readyInput(report: report, targetDays: ["2026-07-09"]) + let input = CodexLineagePromotionEvaluator.Input( + residualReport: ready.residualReport, + targetDays: ready.targetDays, + reviewedResidualDays: ["2026-07-09"], + adversarialGoldensPassed: ready.adversarialGoldensPassed, + boundedDiscoveryPassed: ready.boundedDiscoveryPassed, + familyRouting: ready.familyRouting, + performance: ready.performance, + cancellationStages: ready.cancellationStages, + atomicPublicationPassed: ready.atomicPublicationPassed, + rollback: ready.rollback) + + #expect(CodexLineagePromotionEvaluator.evaluate(input).blockers == [.unreviewedLargeResidual]) + } + + @Test + func `duplicate residual days fail closed without trapping`() { + let sample = Self.sample(day: "2026-07-09", reference: 1000, legacy: 500, ledger: 950, ordinary: false) + let report = CodexLineageResidualClassifier.classify(samples: [sample, sample]) + let decision = CodexLineagePromotionEvaluator.evaluate(Self.readyInput( + report: report, + targetDays: ["2026-07-09"])) + + #expect(decision.blockers.contains(.invalidResidualEvidence)) + #expect(!decision.canPromote) + } + + @Test + func `family overlap blocks promotion even when residual totals improve`() { + let ready = Self.readyInput() + let input = CodexLineagePromotionEvaluator.Input( + residualReport: ready.residualReport, + targetDays: ready.targetDays, + reviewedResidualDays: ready.reviewedResidualDays, + adversarialGoldensPassed: true, + boundedDiscoveryPassed: true, + familyRouting: .init( + primaryFamilyCount: 10, + containedFamilyCount: 2, + doubleContributionFamilyCount: 1, + permanentContainmentSupported: true), + performance: ready.performance, + cancellationStages: ready.cancellationStages, + atomicPublicationPassed: true, + rollback: ready.rollback) + + let decision = CodexLineagePromotionEvaluator.evaluate(input) + #expect(decision.blockers == [.familyDoubleContribution]) + } + + @Test + func `operational and rollback evidence are mandatory`() { + let ready = Self.readyInput() + let input = CodexLineagePromotionEvaluator.Input( + residualReport: ready.residualReport, + targetDays: ready.targetDays, + reviewedResidualDays: ready.reviewedResidualDays, + adversarialGoldensPassed: true, + boundedDiscoveryPassed: true, + familyRouting: ready.familyRouting, + performance: .init( + coldMeasured: true, + warmMeasured: false, + memoryBoundMeasured: false, + hasMaterialRegression: true), + cancellationStages: [.parsing, .reconciliation], + atomicPublicationPassed: false, + rollback: .init(legacyWholeScanAvailable: false, rollbackPathVerified: false)) + + let decision = CodexLineagePromotionEvaluator.evaluate(input) + #expect(decision.blockers.contains(.performanceEvidenceMissing)) + #expect(decision.blockers.contains(.materialPerformanceRegression)) + #expect(decision.blockers.contains(.cancellationEvidenceMissing)) + #expect(decision.blockers.contains(.atomicPublicationFailure)) + #expect(decision.blockers.contains(.legacyRollbackUnavailable)) + #expect(decision.blockers.contains(.rollbackUnverified)) + #expect(decision.blockers == CodexLineagePromotionEvaluator.Blocker.allCases.filter { + decision.blockers.contains($0) + }) + } + + private static func readyInput( + report: CodexLineageResidualClassifier.Report? = nil, + targetDays: Set = ["2026-07-09", "2026-07-10"]) -> CodexLineagePromotionEvaluator.Input + { + let report = report ?? CodexLineageResidualClassifier.classify(samples: [ + Self.sample(day: "2026-07-09", reference: 1000, legacy: 500, ledger: 950, ordinary: false), + Self.sample(day: "2026-07-10", reference: 2000, legacy: 1000, ledger: 1980, ordinary: false), + Self.sample(day: "2026-07-08", reference: 500, legacy: 500, ledger: 500, ordinary: true), + ]) + return .init( + residualReport: report, + targetDays: targetDays, + reviewedResidualDays: targetDays, + adversarialGoldensPassed: true, + boundedDiscoveryPassed: true, + familyRouting: .init( + primaryFamilyCount: 10, + containedFamilyCount: 2, + doubleContributionFamilyCount: 0, + permanentContainmentSupported: true), + performance: .init( + coldMeasured: true, + warmMeasured: true, + memoryBoundMeasured: true, + hasMaterialRegression: false), + cancellationStages: Set(CodexLineagePromotionEvaluator.CancellationStage.allCases), + atomicPublicationPassed: true, + rollback: .init(legacyWholeScanAvailable: true, rollbackPathVerified: true)) + } + + private static func sample( + day: String, + reference: Int, + legacy: Int, + ledger: Int, + ordinary: Bool, + exhaustive: Bool = false) -> CodexLineageResidualClassifier.Sample + { + .init( + day: day, + referenceTokens: reference, + isReferenceFinalized: true, + isOrdinaryDay: ordinary, + legacyTokens: legacy, + ledgerUTCTokens: ledger, + ledgerLocalTokens: ledger, + evidence: .init(localCorpusWasExhaustive: exhaustive, duplicateObservationCount: ordinary ? 0 : 1)) + } +} From ca001d165d64c760684df576a318fa15a575b486 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Mon, 13 Jul 2026 15:49:05 -0400 Subject: [PATCH 15/28] Add Codex lineage accounting selector --- .../Generated/CodexParserHash.generated.swift | 2 +- .../CodexLineageAccountingSelector.swift | 108 +++++++++++++++ .../Vendored/CostUsage/CostUsageScanner.swift | 121 ++++++++++++----- .../CodexLineageAccountingSelectorTests.swift | 125 ++++++++++++++++++ 4 files changed, 323 insertions(+), 33 deletions(-) create mode 100644 Sources/CodexBarCore/Providers/Codex/CodexLineageAccountingSelector.swift create mode 100644 Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index c0e0dd2e96..fa6a5f143f 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 = "84c52421265e9fc8" + static let value = "4169e0ab9fbb53c2" } diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageAccountingSelector.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageAccountingSelector.swift new file mode 100644 index 0000000000..37e6b385fc --- /dev/null +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageAccountingSelector.swift @@ -0,0 +1,108 @@ +import Foundation + +enum CodexLineageAccountingMode: String, CaseIterable, Sendable { + case legacy + case shadow + case lineage + + static let defaultMode: Self = .legacy + static let schemaVersion = 1 + + var producerKeySuffix: String { + "lineage-accounting:\(self.rawValue):v\(Self.schemaVersion)" + } +} + +/// Selects one whole-scan authority while preserving a permanent family-scoped containment route. +enum CodexLineageAccountingSelector { + typealias PackedDays = [String: [String: [Int]]] + + enum SelectionError: Error, Equatable { + case missingContainedFamilyEvidence + } + + struct ContainedDocument: Equatable, Sendable { + /// Stable logical rollout identity. Physical active/archive copies share this value. + let identity: String + let days: PackedDays + } + + struct ContainedFamily: Equatable, Sendable { + /// Legacy file contributions belonging only to this contained family. + let documents: [ContainedDocument] + } + + struct Selection: Equatable, Sendable { + let days: PackedDays + let usedLineageAuthority: Bool + let containedFamilyCount: Int + } + + static func select( + mode: CodexLineageAccountingMode, + legacyDays: PackedDays, + primaryRows: [CodexLineageLedger.DailyRow], + containedFamilies: [ContainedFamily]) -> Selection + { + guard mode == .lineage else { + return Selection(days: legacyDays, usedLineageAuthority: false, containedFamilyCount: 0) + } + var days = Self.days(from: primaryRows) + for family in containedFamilies { + Self.add(Self.containedDays(family.documents), to: &days) + } + return Selection( + days: days, + usedLineageAuthority: true, + containedFamilyCount: containedFamilies.count) + } + + /// A contained family contributes once. Physical copies of one logical rollout use an envelope; + /// distinct siblings remain additive so containment does not discard their independent work. + private static func containedDays(_ documents: [ContainedDocument]) -> PackedDays { + var documentsByIdentity: [String: [PackedDays]] = [:] + for document in documents { + documentsByIdentity[document.identity, default: []].append(document.days) + } + var result: PackedDays = [:] + for copies in documentsByIdentity.values { + var envelope: PackedDays = [:] + for copy in copies { + for (day, models) in copy { + for (model, packed) in models { + let existing = envelope[day]?[model] ?? [0, 0, 0] + envelope[day, default: [:]][model] = (0..<3).map { index in + max(existing[safe: index] ?? 0, packed[safe: index] ?? 0) + } + } + } + } + Self.add(envelope, to: &result) + } + return result + } + + private static func days(from rows: [CodexLineageLedger.DailyRow]) -> PackedDays { + var days: PackedDays = [:] + for row in rows { + var packed = days[row.day]?[row.model] ?? [0, 0, 0] + packed[0] += row.totals.input + packed[1] += row.totals.cached + packed[2] += row.totals.output + days[row.day, default: [:]][row.model] = packed + } + return days + } + + private static func add(_ source: PackedDays, to destination: inout PackedDays) { + for (day, models) in source { + for (model, packed) in models { + var value = destination[day]?[model] ?? [0, 0, 0] + for index in 0..<3 { + value[index] += packed[safe: index] ?? 0 + } + destination[day, default: [:]][model] = value + } + } + } +} diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift index 88f724d30e..bb8f024a78 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift @@ -28,6 +28,7 @@ enum CostUsageScanner { var codexTraceDatabaseURL: URL? var refreshMinIntervalSeconds: TimeInterval = 60 var claudeLogProviderFilter: ClaudeLogProviderFilter = .all + var codexLineageAccountingMode: CodexLineageAccountingMode = .defaultMode /// Force a full rescan, ignoring per-file cache and incremental offsets. var forceRescan: Bool = false @@ -37,6 +38,7 @@ enum CostUsageScanner { cacheRoot: URL? = nil, codexTraceDatabaseURL: URL? = nil, claudeLogProviderFilter: ClaudeLogProviderFilter = .all, + codexLineageAccountingMode: CodexLineageAccountingMode = .defaultMode, forceRescan: Bool = false) { self.codexSessionsRoot = codexSessionsRoot @@ -44,6 +46,7 @@ enum CostUsageScanner { self.cacheRoot = cacheRoot self.codexTraceDatabaseURL = codexTraceDatabaseURL self.claudeLogProviderFilter = claudeLogProviderFilter + self.codexLineageAccountingMode = codexLineageAccountingMode self.forceRescan = forceRescan } } @@ -2613,7 +2616,11 @@ enum CostUsageScanner { options: Options, checkCancellation: CancellationCheck?) throws -> CostUsageDailyReport { - var cache = CostUsageCacheIO.load(provider: .codex, cacheRoot: options.cacheRoot) + let accountingProducerKey = Self.codexAccountingProducerKey(mode: options.codexLineageAccountingMode) + var cache = CostUsageCacheIO.load( + provider: .codex, + cacheRoot: options.cacheRoot, + producerKey: accountingProducerKey) let nowMs = Int64(now.timeIntervalSince1970 * 1000) let plan = Self.makeCodexRefreshPlan(cache: cache, range: range, now: now, nowMs: nowMs, options: options) @@ -2740,12 +2747,13 @@ enum CostUsageScanner { ? [cachedUntilKey, range.scanUntilKey].compactMap(\.self).max() ?? range.scanUntilKey : range.scanUntilKey Self.pruneDays(cache: &cache, sinceKey: retainedSinceKey, untilKey: retainedUntilKey) - try Self.recordCodexLineageShadow( + try Self.applyCodexLineageAccounting( + mode: options.codexLineageAccountingMode, files: files, roots: plan.roots, - cache: cache, - range: range, + cache: &cache, checkCancellation: checkCancellation) + Self.pruneDays(cache: &cache, sinceKey: retainedSinceKey, untilKey: retainedUntilKey) cache.roots = plan.rootsFingerprint cache.scanSinceKey = retainedSinceKey cache.scanUntilKey = retainedUntilKey @@ -2768,7 +2776,11 @@ enum CostUsageScanner { } cache.lastScanUnixMs = nowMs try checkCancellation?() - CostUsageCacheIO.save(provider: .codex, cache: cache, cacheRoot: options.cacheRoot) + CostUsageCacheIO.save( + provider: .codex, + cache: cache, + cacheRoot: options.cacheRoot, + producerKey: accountingProducerKey) } return Self.buildCodexReportFromCache( @@ -2779,48 +2791,93 @@ enum CostUsageScanner { priorityTurns: plan.priorityTurns) } - private static func recordCodexLineageShadow( + static func codexAccountingProducerKey(mode: CodexLineageAccountingMode) -> String { + let base = CostUsageCacheIO.currentProducerKey(provider: .codex) ?? "codex" + return mode == .legacy ? base : base + ":" + mode.producerKeySuffix + } + + static func shouldRunCodexLineage(mode: CodexLineageAccountingMode) -> Bool { + mode != .legacy + } + + private static func applyCodexLineageAccounting( + mode: CodexLineageAccountingMode, files: [URL], roots: [URL], - cache: CostUsageCache, - range: CostUsageDayRange, + cache: inout CostUsageCache, checkCancellation: CancellationCheck?) throws { + guard self.shouldRunCodexLineage(mode: mode) else { return } do { - let report = try CodexLineageShadow.run( + let discovery = try CodexLineageTwoPassDiscovery.discover( includedFiles: files, roots: roots, - legacyDays: cache.days, - dayRange: range.sinceKey...range.untilKey, + checkCancellation: checkCancellation) + let families = try CodexLineageEngine.prepareDescriptorFamilies( + descriptors: discovery.descriptors, + unresolvedParents: discovery.unresolvedParents, + checkCancellation: checkCancellation) + let lineage = try CodexLineageEngine.reconcileStreaming( + families: families, localTimeZone: .current, checkCancellation: checkCancellation) - self.log.info( - "Codex lineage shadow comparison completed", + let resultsByStableID = Dictionary(uniqueKeysWithValues: lineage.families.map { ($0.stableID, $0) }) + let contained = families.compactMap { family -> CodexLineageAccountingSelector.ContainedFamily? in + guard let result = resultsByStableID[family.stableID], case .contained = result.quality else { + return nil + } + let documents = family.descriptors.compactMap { descriptor -> + CodexLineageAccountingSelector.ContainedDocument? in + guard let days = cache.files[descriptor.fileURL.path]?.days else { return nil } + let identity = descriptor.scopeID + "\u{0}" + + (descriptor.metadataSessionID ?? descriptor.ownerID).lowercased() + return .init(identity: identity, days: days) + } + guard documents.count == family.descriptors.count else { return nil } + return .init(documents: documents) + } + let expectedContainedCount = lineage.families.count { + if case .contained = $0.quality { + return true + } + return false + } + if mode == .lineage, contained.count != expectedContainedCount { + throw CodexLineageAccountingSelector.SelectionError.missingContainedFamilyEvidence + } + let selection = CodexLineageAccountingSelector.select( + mode: mode, + legacyDays: cache.days, + primaryRows: lineage.report.localRows, + containedFamilies: contained) + try checkCancellation?() + + #if DEBUG + self.log.debug( + "Codex lineage accounting 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)", + "containedFamilies": "\(contained.count)", + "families": "\(lineage.diagnostics.familyCount)", + "mode": mode.rawValue, + "recomputedFamilies": "\(lineage.diagnostics.recomputedFamilyCount)", ]) - 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)", - ]) + #endif + + if selection.usedLineageAuthority { + cache.days = selection.days + // Legacy per-file rows are not valid pricing/project evidence for ledger totals. + // Dropping them also makes rollback and the next mode-specific refresh rebuild safely. + cache.files = [:] } } catch is CancellationError { throw CancellationError() } catch { - self.log.warning( - "Codex lineage shadow comparison failed", - metadata: ["errorType": "\(type(of: error))"]) + if mode == .lineage { + throw error + } + #if DEBUG + self.log.debug("Codex lineage shadow comparison failed; legacy totals remain authoritative") + #endif } } diff --git a/Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift b/Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift new file mode 100644 index 0000000000..c1d75429f4 --- /dev/null +++ b/Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift @@ -0,0 +1,125 @@ +import Testing +@testable import CodexBarCore + +struct CodexLineageAccountingSelectorTests { + @Test + func `legacy and shadow modes keep legacy authority`() { + let legacy = Self.days(input: 100) + let primary = [Self.row(input: 40)] + for mode in [CodexLineageAccountingMode.legacy, .shadow] { + let selection = CodexLineageAccountingSelector.select( + mode: mode, + legacyDays: legacy, + primaryRows: primary, + containedFamilies: [.init(documents: [.init(identity: "parent", days: Self.days(input: 20))])]) + #expect(selection.days == legacy) + #expect(!selection.usedLineageAuthority) + #expect(selection.containedFamilyCount == 0) + } + } + + @Test + func `lineage mode combines primary and family containment exactly once`() { + let selection = CodexLineageAccountingSelector.select( + mode: .lineage, + legacyDays: Self.days(input: 999), + primaryRows: [Self.row(input: 100)], + containedFamilies: [ + .init(documents: [ + .init(identity: "copy", days: Self.days(input: 40)), + .init(identity: "copy", days: Self.days(input: 40)), + ]), + .init(documents: [.init(identity: "other", days: Self.days(input: 10))]), + ]) + + #expect(selection.usedLineageAuthority) + #expect(selection.containedFamilyCount == 2) + #expect(selection.days["2026-07-09"]?["gpt-5.4"]?[0] == 150) + } + + @Test + func `contained family uses component envelope instead of summing fork copies`() { + let first: CodexLineageAccountingSelector.PackedDays = [ + "2026-07-09": ["gpt-5.4": [100, 80, 5]], + ] + let copied: CodexLineageAccountingSelector.PackedDays = [ + "2026-07-09": ["gpt-5.4": [100, 60, 8]], + ] + let selection = CodexLineageAccountingSelector.select( + mode: .lineage, + legacyDays: [:], + primaryRows: [], + containedFamilies: [.init(documents: [ + .init(identity: "same", days: first), + .init(identity: "same", days: copied), + ])]) + + #expect(selection.days["2026-07-09"]?["gpt-5.4"] == [100, 80, 8]) + } + + @Test + func `contained siblings remain additive while physical copies are enveloped`() { + let selection = CodexLineageAccountingSelector.select( + mode: .lineage, + legacyDays: [:], + primaryRows: [], + containedFamilies: [.init(documents: [ + .init(identity: "child-a", days: Self.days(input: 40)), + .init(identity: "child-a", days: Self.days(input: 40)), + .init(identity: "child-b", days: Self.days(input: 10)), + ])]) + + #expect(selection.days["2026-07-09"]?["gpt-5.4"]?[0] == 50) + } + + @Test + func `mode cache suffixes are schema scoped and distinct`() { + #expect(CodexLineageAccountingMode.defaultMode == .legacy) + #expect(CodexLineageAccountingMode.shadow.producerKeySuffix != CodexLineageAccountingMode.lineage + .producerKeySuffix) + #expect(CodexLineageAccountingMode.shadow.producerKeySuffix.contains("v1")) + } + + @Test + func `scanner defaults to legacy and skips lineage execution`() { + let options = CostUsageScanner.Options() + #expect(options.codexLineageAccountingMode == .legacy) + #expect(!CostUsageScanner.shouldRunCodexLineage(mode: options.codexLineageAccountingMode)) + #expect(CostUsageScanner.shouldRunCodexLineage(mode: .shadow)) + #expect(CostUsageScanner.shouldRunCodexLineage(mode: .lineage)) + } + + @Test + func `mode specific producer key mismatch invalidates cache for rollback rebuild`() throws { + let environment = try CostUsageTestEnvironment() + defer { environment.cleanup() } + let shadowKey = CostUsageScanner.codexAccountingProducerKey(mode: .shadow) + let legacyKey = CostUsageScanner.codexAccountingProducerKey(mode: .legacy) + var shadowCache = CostUsageCache() + shadowCache.days = Self.days(input: 100) + CostUsageCacheIO.save( + provider: .codex, + cache: shadowCache, + cacheRoot: environment.cacheRoot, + producerKey: shadowKey) + + let rollbackLoad = CostUsageCacheIO.load( + provider: .codex, + cacheRoot: environment.cacheRoot, + producerKey: legacyKey) + #expect(rollbackLoad.days.isEmpty) + #expect(shadowKey != legacyKey) + } + + private static func days(input: Int) -> CodexLineageAccountingSelector.PackedDays { + ["2026-07-09": ["gpt-5.4": [input, 0, 0]]] + } + + private static func row(input: Int) -> CodexLineageLedger.DailyRow { + .init( + day: "2026-07-09", + model: "gpt-5.4", + totals: .init(input: input, cached: 0, output: 0), + costUSD: 0) + } +} From b3abe4589e17aa367347722d6e3007b15baee100 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Tue, 14 Jul 2026 00:48:44 -0400 Subject: [PATCH 16/28] Gate lineage authority promotion --- .../Generated/CodexParserHash.generated.swift | 2 +- .../CodexLineageAccountingSelector.swift | 3 +- .../Codex/CodexLineageDiscovery.swift | 18 +-- .../Providers/Codex/CodexLineageLedger.swift | 122 +++++++++++++----- .../CodexLineagePromotionEvaluator.swift | 23 +++- .../Codex/CodexLineageTwoPassDiscovery.swift | 15 +-- .../Vendored/CostUsage/CostUsageScanner.swift | 31 ++++- .../CodexLineageAccountingSelectorTests.swift | 48 +++++++ .../CodexLineageDiscoveryTests.swift | 2 +- 9 files changed, 196 insertions(+), 68 deletions(-) diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index fa6a5f143f..a52983943d 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 = "4169e0ab9fbb53c2" + static let value = "2f47c38d706a4d88" } diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageAccountingSelector.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageAccountingSelector.swift index 37e6b385fc..ff072ce71b 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageAccountingSelector.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageAccountingSelector.swift @@ -40,11 +40,12 @@ enum CodexLineageAccountingSelector { static func select( mode: CodexLineageAccountingMode, + authorization: CodexLineagePromotionEvaluator.Authorization? = nil, legacyDays: PackedDays, primaryRows: [CodexLineageLedger.DailyRow], containedFamilies: [ContainedFamily]) -> Selection { - guard mode == .lineage else { + guard mode == .lineage, authorization != nil else { return Selection(days: legacyDays, usedLineageAuthority: false, containedFamilyCount: 0) } var days = Self.days(from: primaryRows) diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageDiscovery.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageDiscovery.swift index 8d61444a76..c4dff05eb2 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageDiscovery.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageDiscovery.swift @@ -24,11 +24,6 @@ enum CodexLineageDiscovery { func remember(_ document: CodexLineageLedger.Document) { documents.append(document) knownIDs.insert(.init(scopeID: document.scopeID, sessionID: Self.canonicalSessionID(document.ownerID))) - if let metadataSessionID = Self.nonEmpty(document.metadataSessionID) { - knownIDs.insert(.init( - scopeID: document.scopeID, - sessionID: Self.canonicalSessionID(metadataSessionID))) - } if let parentSessionID = Self.nonEmpty(document.parentSessionID) { pendingParentIDs.append(.init( scopeID: document.scopeID, @@ -71,8 +66,7 @@ enum CodexLineageDiscovery { fileURL: parentURL, checkCancellation: checkCancellation) let ownerID = Self.canonicalSessionID(parent.ownerID) - let metadataSessionID = parent.metadataSessionID.map(Self.canonicalSessionID) - guard ownerID == parentIdentity.sessionID || metadataSessionID == parentIdentity.sessionID else { + guard ownerID == parentIdentity.sessionID else { continue } referencedParentDocumentCount += 1 @@ -159,16 +153,6 @@ enum CodexLineageDiscovery { sessionID: canonicalID) self.filesByID[key, default: []].insert(fileURL) } - if let metadataID = try CostUsageScanner.parseCodexSessionIdentifier( - fileURL: fileURL, - checkCancellation: self.checkCancellation) - { - let canonicalID = CodexLineageDiscovery.canonicalSessionID(metadataID) - let key = ScopedIdentity( - scopeID: CostUsageScanner.codexLineageScopeID(fileURL: fileURL), - sessionID: canonicalID) - self.filesByID[key, default: []].insert(fileURL) - } } } } diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift index 4c365c6884..a0d89a91a4 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift @@ -150,44 +150,55 @@ enum CodexLineageLedger { } } - var acceptedByComponent: [String: [ObservationIdentity: AcceptedObservation]] = [:] - var physicalObservationCount = 0 + var documentsByComponent: [String: [Document]] = [:] for document in documents { - try checkCancellation?() let componentID = graph.find(Self.scoped(document.ownerID, document: document)) - var accepted = acceptedByComponent[componentID] ?? [:] - for observation in document.observations { - try checkCancellation?() - physicalObservationCount += 1 - let date = try Self.date(from: observation.timestamp) - let identity = ObservationIdentity( - eventID: Self.nonEmpty(observation.eventID), - fingerprint: Fingerprint(last: observation.last, total: observation.total)) - 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, - model: observation.model, - last: observation.last) - } - acceptedByComponent[componentID] = accepted + documentsByComponent[componentID, default: []].append(document) } - + var physicalObservationCount = 0 var utcDays: [String: Totals] = [:] var localDays: [String: Totals] = [:] var utcRows: [DailyRowKey: DailyRowValue] = [:] var localRows: [DailyRowKey: DailyRowValue] = [:] var acceptedObservationCount = 0 - for accepted in acceptedByComponent.values { + for componentDocuments in documentsByComponent.values { try checkCancellation?() + let parentByOwner = Self.physicalParents(componentDocuments) + var accepted: [ObservationIdentity: AcceptedObservation] = [:] + var acceptedByFingerprint: [Fingerprint: [String: ObservationIdentity]] = [:] + for document in Self.parentsFirst(componentDocuments, parentByOwner: parentByOwner) { + let ownerID = Self.scoped(document.ownerID, document: document) + for observation in document.observations { + try checkCancellation?() + physicalObservationCount += 1 + let date = try Self.date(from: observation.timestamp) + let fingerprint = Fingerprint(last: observation.last, total: observation.total) + let proposedIdentity = ObservationIdentity( + eventID: Self.nonEmpty(observation.eventID), + fingerprint: fingerprint) + let comparableIdentity = Self.comparableIdentity( + ownerID: ownerID, + acceptedByOwner: acceptedByFingerprint[fingerprint], + parentByOwner: parentByOwner) + let identity = accepted[proposedIdentity] == nil ? comparableIdentity ?? proposedIdentity : + proposedIdentity + 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, + model: observation.model, + last: observation.last) + if identity == proposedIdentity { + acceptedByFingerprint[fingerprint, default: [:]][ownerID] = identity + } + } + } acceptedObservationCount += accepted.count for observation in accepted.values { try checkCancellation?() @@ -322,6 +333,59 @@ enum CodexLineageLedger { UUID(uuidString: identity)?.uuidString.lowercased() ?? identity } + private static func physicalParents(_ documents: [Document]) -> [String: String] { + let physicalOwners = Set(documents.map { Self.scoped($0.ownerID, document: $0) }) + var result: [String: String] = [:] + for document in documents { + guard let parent = Self.nonEmpty(document.parentSessionID) else { continue } + let owner = Self.scoped(document.ownerID, document: document) + let parentIdentity = Self.scoped(parent, document: document) + guard physicalOwners.contains(parentIdentity), parentIdentity != owner else { continue } + result[owner] = parentIdentity + } + return result + } + + private static func comparableIdentity( + ownerID: String, + acceptedByOwner: [String: ObservationIdentity]?, + parentByOwner: [String: String]) -> ObservationIdentity? + { + guard let acceptedByOwner else { return nil } + var current: String? = ownerID + var visited: Set = [] + while let candidate = current, visited.insert(candidate).inserted { + if let identity = acceptedByOwner[candidate] { return identity } + current = parentByOwner[candidate] + } + return nil + } + + private static func parentsFirst(_ documents: [Document], parentByOwner: [String: String]) -> [Document] { + var depths: [String: Int] = [:] + func depth(_ owner: String) -> Int { + if let cached = depths[owner] { return cached } + var path: [String] = [] + var current = owner + var seen: Set = [] + while let parent = parentByOwner[current], seen.insert(current).inserted { + path.append(current) + current = parent + } + let base = depths[current] ?? 0 + for (offset, item) in path.reversed().enumerated() { + depths[item] = base + offset + 1 + } + depths[owner] = depths[owner] ?? base + return depths[owner] ?? 0 + } + return documents.sorted { lhs, rhs in + let lhsDepth = depth(Self.scoped(lhs.ownerID, document: lhs)) + let rhsDepth = depth(Self.scoped(rhs.ownerID, document: rhs)) + return lhsDepth == rhsDepth ? Self.documentComesBefore(lhs, rhs) : lhsDepth < rhsDepth + } + } + private static func documentComesBefore(_ lhs: Document, _ rhs: Document) -> Bool { let lhsKey = [ lhs.scopeID, diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineagePromotionEvaluator.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineagePromotionEvaluator.swift index 300197d5dd..d135ed43c0 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineagePromotionEvaluator.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineagePromotionEvaluator.swift @@ -5,6 +5,10 @@ import Foundation /// This evaluator does not choose token totals or tune an error percentage. It verifies that the /// independently collected correctness and operational evidence is complete enough to promote. enum CodexLineagePromotionEvaluator { + struct Authorization: Equatable, Sendable { + fileprivate init() {} + } + enum CancellationStage: String, CaseIterable, Equatable, Hashable, Sendable { case rootIndexing case parsing @@ -81,6 +85,19 @@ enum CodexLineagePromotionEvaluator { let keepsFamilyContainment: Bool /// Legacy remains a whole-scan emergency authority until its dedicated removal work. let keepsLegacyEmergencyRollback: Bool + let authorization: Authorization? + + fileprivate init( + blockers: [Blocker], + keepsFamilyContainment: Bool, + keepsLegacyEmergencyRollback: Bool, + authorization: Authorization?) + { + self.blockers = blockers + self.keepsFamilyContainment = keepsFamilyContainment + self.keepsLegacyEmergencyRollback = keepsLegacyEmergencyRollback + self.authorization = authorization + } } static func evaluate(_ input: Input) -> Decision { @@ -102,10 +119,12 @@ enum CodexLineagePromotionEvaluator { Self.addTargetBlockers(input.targetDays, daysByID: daysByID, to: &blockers) Self.addOperationalBlockers(input, to: &blockers) + let orderedBlockers = Blocker.allCases.filter(blockers.contains) return Decision( - blockers: Blocker.allCases.filter(blockers.contains), + blockers: orderedBlockers, keepsFamilyContainment: input.familyRouting.permanentContainmentSupported, - keepsLegacyEmergencyRollback: input.rollback.legacyWholeScanAvailable) + keepsLegacyEmergencyRollback: input.rollback.legacyWholeScanAvailable, + authorization: orderedBlockers.isEmpty ? Authorization() : nil) } private static func addReportBlockers( diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageTwoPassDiscovery.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageTwoPassDiscovery.swift index 16edd16396..af62925bfe 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageTwoPassDiscovery.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageTwoPassDiscovery.swift @@ -51,9 +51,6 @@ enum CodexLineageTwoPassDiscovery { func remember(_ descriptor: Descriptor) { descriptors.append(descriptor) known.insert(.init(scopeID: descriptor.scopeID, sessionID: Self.canonical(descriptor.ownerID))) - if let metadata = Self.nonEmpty(descriptor.metadataSessionID) { - known.insert(.init(scopeID: descriptor.scopeID, sessionID: Self.canonical(metadata))) - } if let parent = Self.nonEmpty(descriptor.parentSessionID) { pending.append(.init(scopeID: descriptor.scopeID, sessionID: Self.canonical(parent))) } @@ -84,8 +81,7 @@ enum CodexLineageTwoPassDiscovery { guard seenPaths.insert(fileURL.standardizedFileURL.path).inserted else { continue } let descriptor = try Self.describe(fileURL: fileURL, checkCancellation: checkCancellation) let owner = Self.canonical(descriptor.ownerID) - let metadata = descriptor.metadataSessionID.map(Self.canonical) - guard owner == identity.sessionID || metadata == identity.sessionID else { continue } + guard owner == identity.sessionID else { continue } remember(descriptor) referencedParents += 1 found = true @@ -202,15 +198,6 @@ enum CodexLineageTwoPassDiscovery { default: [], ].insert(fileURL) } - if let metadata = try CostUsageScanner.parseCodexSessionIdentifier( - fileURL: fileURL, - checkCancellation: self.checkCancellation) - { - self.filesByIdentity[ - .init(scopeID: scopeID, sessionID: CodexLineageTwoPassDiscovery.canonical(metadata)), - default: [], - ].insert(fileURL) - } } } } diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift index bb8f024a78..47b5592dec 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift @@ -29,6 +29,7 @@ enum CostUsageScanner { var refreshMinIntervalSeconds: TimeInterval = 60 var claudeLogProviderFilter: ClaudeLogProviderFilter = .all var codexLineageAccountingMode: CodexLineageAccountingMode = .defaultMode + var codexLineagePromotionAuthorization: CodexLineagePromotionEvaluator.Authorization? /// Force a full rescan, ignoring per-file cache and incremental offsets. var forceRescan: Bool = false @@ -39,6 +40,7 @@ enum CostUsageScanner { codexTraceDatabaseURL: URL? = nil, claudeLogProviderFilter: ClaudeLogProviderFilter = .all, codexLineageAccountingMode: CodexLineageAccountingMode = .defaultMode, + codexLineagePromotionAuthorization: CodexLineagePromotionEvaluator.Authorization? = nil, forceRescan: Bool = false) { self.codexSessionsRoot = codexSessionsRoot @@ -47,6 +49,7 @@ enum CostUsageScanner { self.codexTraceDatabaseURL = codexTraceDatabaseURL self.claudeLogProviderFilter = claudeLogProviderFilter self.codexLineageAccountingMode = codexLineageAccountingMode + self.codexLineagePromotionAuthorization = codexLineagePromotionAuthorization self.forceRescan = forceRescan } } @@ -1715,6 +1718,8 @@ enum CostUsageScanner { var incompleteObservationCount = 0 var observationCount = 0 var warnedAboutUnparsedTimestamp = false + var currentTurnID: String? + var tokenEventCountByTurn: [String: Int] = [:] func parsedSnapshotDate(timestamp: String) -> Date? { let date = Self.dateFromTimestamp(timestamp) @@ -1730,6 +1735,7 @@ enum CostUsageScanner { func appendSnapshot( timestamp: String, model: String?, + turnID: String?, last: CostUsageCodexTotals?, total: CostUsageCodexTotals?) { @@ -1744,10 +1750,16 @@ enum CostUsageScanner { date: parsedSnapshotDate(timestamp: timestamp), totals: counted)) } + let eventID = retainEvidence ? turnID.map { turnID in + let ordinal = tokenEventCountByTurn[turnID, default: 0] + tokenEventCountByTurn[turnID] = ordinal + 1 + return "\(turnID):\(ordinal)" + } : nil if let last, let total { observationCount += 1 if retainEvidence { observations.append(CodexLineageLedger.Observation( + eventID: eventID, timestamp: timestamp, model: Self.codexModelEvidence(model) ?? Self.codexModelEvidence(currentModel) @@ -1785,14 +1797,15 @@ enum CostUsageScanner { 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 .taskStarted: - break + case let .taskStarted(turnID): + currentTurnID = turnID } return } @@ -1833,6 +1846,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 } @@ -1860,7 +1877,12 @@ enum CostUsageScanner { ?? Self.codexModelEvidence(info["model_name"] as? String) ?? Self.codexModelEvidence(payload["model"] as? String) ?? Self.codexModelEvidence(obj["model"] as? String) - appendSnapshot(timestamp: timestamp, model: model, last: last, total: total) + appendSnapshot( + timestamp: timestamp, + model: model, + turnID: Self.codexTurnID(from: payload) ?? currentTurnID, + last: last, + total: total) } }) } catch is CancellationError { @@ -2749,6 +2771,7 @@ enum CostUsageScanner { Self.pruneDays(cache: &cache, sinceKey: retainedSinceKey, untilKey: retainedUntilKey) try Self.applyCodexLineageAccounting( mode: options.codexLineageAccountingMode, + authorization: options.codexLineagePromotionAuthorization, files: files, roots: plan.roots, cache: &cache, @@ -2802,6 +2825,7 @@ enum CostUsageScanner { private static func applyCodexLineageAccounting( mode: CodexLineageAccountingMode, + authorization: CodexLineagePromotionEvaluator.Authorization?, files: [URL], roots: [URL], cache: inout CostUsageCache, @@ -2847,6 +2871,7 @@ enum CostUsageScanner { } let selection = CodexLineageAccountingSelector.select( mode: mode, + authorization: authorization, legacyDays: cache.days, primaryRows: lineage.report.localRows, containedFamilies: contained) diff --git a/Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift b/Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift index c1d75429f4..87112794ff 100644 --- a/Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift +++ b/Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift @@ -22,6 +22,7 @@ struct CodexLineageAccountingSelectorTests { func `lineage mode combines primary and family containment exactly once`() { let selection = CodexLineageAccountingSelector.select( mode: .lineage, + authorization: Self.authorization(), legacyDays: Self.days(input: 999), primaryRows: [Self.row(input: 100)], containedFamilies: [ @@ -47,6 +48,7 @@ struct CodexLineageAccountingSelectorTests { ] let selection = CodexLineageAccountingSelector.select( mode: .lineage, + authorization: Self.authorization(), legacyDays: [:], primaryRows: [], containedFamilies: [.init(documents: [ @@ -61,6 +63,7 @@ struct CodexLineageAccountingSelectorTests { func `contained siblings remain additive while physical copies are enveloped`() { let selection = CodexLineageAccountingSelector.select( mode: .lineage, + authorization: Self.authorization(), legacyDays: [:], primaryRows: [], containedFamilies: [.init(documents: [ @@ -89,6 +92,19 @@ struct CodexLineageAccountingSelectorTests { #expect(CostUsageScanner.shouldRunCodexLineage(mode: .lineage)) } + @Test + func `lineage mode without promotion authorization keeps legacy authority`() { + let legacy = Self.days(input: 100) + let selection = CodexLineageAccountingSelector.select( + mode: .lineage, + legacyDays: legacy, + primaryRows: [Self.row(input: 999)], + containedFamilies: []) + + #expect(selection.days == legacy) + #expect(!selection.usedLineageAuthority) + } + @Test func `mode specific producer key mismatch invalidates cache for rollback rebuild`() throws { let environment = try CostUsageTestEnvironment() @@ -115,6 +131,38 @@ struct CodexLineageAccountingSelectorTests { ["2026-07-09": ["gpt-5.4": [input, 0, 0]]] } + private static func authorization() -> CodexLineagePromotionEvaluator.Authorization { + let sample = CodexLineageResidualClassifier.Sample( + day: "2026-07-09", + referenceTokens: 1000, + isReferenceFinalized: true, + isOrdinaryDay: false, + legacyTokens: 500, + ledgerUTCTokens: 950, + ledgerLocalTokens: 950, + evidence: .init(localCorpusWasExhaustive: true, duplicateObservationCount: 1)) + let decision = CodexLineagePromotionEvaluator.evaluate(.init( + residualReport: CodexLineageResidualClassifier.classify(samples: [sample]), + targetDays: ["2026-07-09"], + reviewedResidualDays: ["2026-07-09"], + adversarialGoldensPassed: true, + boundedDiscoveryPassed: true, + familyRouting: .init( + primaryFamilyCount: 1, + containedFamilyCount: 0, + doubleContributionFamilyCount: 0, + permanentContainmentSupported: true), + performance: .init( + coldMeasured: true, + warmMeasured: true, + memoryBoundMeasured: true, + hasMaterialRegression: false), + cancellationStages: Set(CodexLineagePromotionEvaluator.CancellationStage.allCases), + atomicPublicationPassed: true, + rollback: .init(legacyWholeScanAvailable: true, rollbackPathVerified: true))) + return decision.authorization! + } + private static func row(input: Int) -> CodexLineageLedger.DailyRow { .init( day: "2026-07-09", diff --git a/Tests/CodexBarTests/CodexLineageDiscoveryTests.swift b/Tests/CodexBarTests/CodexLineageDiscoveryTests.swift index 1b2ea1b2e0..15f1efd341 100644 --- a/Tests/CodexBarTests/CodexLineageDiscoveryTests.swift +++ b/Tests/CodexBarTests/CodexLineageDiscoveryTests.swift @@ -210,7 +210,7 @@ struct CodexLineageDiscoveryTests { #expect(Set(report.documents.map(\.ownerID)) == [parentID, "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"]) #expect(report.referencedParentDocumentCount == 1) - #expect(report.unresolvedParentIDs.isEmpty) + #expect(report.unresolvedParents.isEmpty) } private static func writeRollout( From 0e3033feeb7d284105ee8313a9fef4400ce4e6e5 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Tue, 14 Jul 2026 00:52:18 -0400 Subject: [PATCH 17/28] Keep lineage promotion lint-clean --- Sources/CodexBarCore/Generated/CodexParserHash.generated.swift | 2 +- Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index a52983943d..dc5785166c 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 = "2f47c38d706a4d88" + static let value = "af5cb3051a08676b" } diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift index 47b5592dec..c7180c8484 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift @@ -2823,6 +2823,7 @@ enum CostUsageScanner { mode != .legacy } + // swiftlint:disable:next function_parameter_count private static func applyCodexLineageAccounting( mode: CodexLineageAccountingMode, authorization: CodexLineagePromotionEvaluator.Authorization?, From 7857e8b6f88b931d5c37fbfa9b447de76c3de0e5 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Mon, 13 Jul 2026 17:52:43 -0400 Subject: [PATCH 18/28] Validate lineage accounting on local histories --- .../Generated/CodexParserHash.generated.swift | 2 +- .../Providers/Codex/CodexLineageLedger.swift | 21 +- .../Vendored/CostUsage/CostUsageScanner.swift | 11 +- .../CodexLineageAccountingSelectorTests.swift | 15 + .../CodexLineageLedgerTests.swift | 45 +++ .../CodexLineageLocalValidationTests.swift | 351 ++++++++++++++++++ 6 files changed, 436 insertions(+), 9 deletions(-) create mode 100644 Tests/CodexBarTests/CodexLineageLocalValidationTests.swift diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index dc5785166c..66e60596f4 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 = "af5cb3051a08676b" + static let value = "5caa3e1eda239d03" } diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift index a0d89a91a4..2e2cb921dd 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift @@ -457,17 +457,26 @@ enum CodexLineageLedger { } private static func hasAncestryCycle(_ documents: [Document]) -> Bool { + let owners = Set(documents.map { Self.canonicalIdentity($0.ownerID) }) let metadataOwners = Dictionary(grouping: documents.compactMap { document in - document.metadataSessionID.map { ($0, document.ownerID) } + Self.nonEmpty(document.metadataSessionID).map { + (Self.canonicalIdentity($0), Self.canonicalIdentity(document.ownerID)) + } }, by: \.0).mapValues { Set($0.map(\.1)) } var parents: [String: Set] = [:] for document in documents { guard let parent = Self.nonEmpty(document.parentSessionID) else { continue } - var targets = metadataOwners[parent] ?? [parent] - if parent != document.ownerID { - targets.remove(document.ownerID) + let owner = Self.canonicalIdentity(document.ownerID) + let canonicalParent = Self.canonicalIdentity(parent) + // Prefer an exact physical owner. A metadata alias may establish an edge only when it + // identifies one physical owner; retained aliases shared by fork siblings are ambiguous. + if owners.contains(canonicalParent) { + parents[owner, default: []].insert(canonicalParent) + } else if let targets = metadataOwners[canonicalParent], targets.count == 1, + let target = targets.first + { + parents[owner, default: []].insert(target) } - parents[document.ownerID, default: []].formUnion(targets) } var visiting: Set = [] var visited: Set = [] @@ -486,7 +495,7 @@ enum CodexLineageLedger { visited.insert(owner) return false } - return Set(documents.map(\.ownerID)).contains(where: visit) + return owners.contains(where: visit) } /// Duplicate copies can carry different model evidence. Keep the earliest physical copy, diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift index c7180c8484..896086ff22 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift @@ -2854,8 +2854,11 @@ enum CostUsageScanner { let documents = family.descriptors.compactMap { descriptor -> CodexLineageAccountingSelector.ContainedDocument? in guard let days = cache.files[descriptor.fileURL.path]?.days else { return nil } - let identity = descriptor.scopeID + "\u{0}" - + (descriptor.metadataSessionID ?? descriptor.ownerID).lowercased() + // Active/archive copies share the filename owner. Fork children can retain an + // ancestor metadata ID, so metadata is not a safe physical-copy identity here. + let identity = Self.codexContainedDocumentIdentity( + scopeID: descriptor.scopeID, + ownerID: descriptor.ownerID) return .init(identity: identity, days: days) } guard documents.count == family.descriptors.count else { return nil } @@ -2907,6 +2910,10 @@ enum CostUsageScanner { } } + static func codexContainedDocumentIdentity(scopeID: String, ownerID: String) -> String { + scopeID + "\u{0}" + (UUID(uuidString: ownerID)?.uuidString.lowercased() ?? ownerID) + } + private static func codexFileScanContext( range: CostUsageDayRange, options: Options, diff --git a/Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift b/Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift index 87112794ff..95e6ff67dc 100644 --- a/Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift +++ b/Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift @@ -75,6 +75,21 @@ struct CodexLineageAccountingSelectorTests { #expect(selection.days["2026-07-09"]?["gpt-5.4"]?[0] == 50) } + @Test + func `containment copy identity follows physical owner rather than retained metadata`() { + let first = CostUsageScanner.codexContainedDocumentIdentity( + scopeID: "home", + ownerID: "00000000-0000-4000-8000-000000000001") + let sibling = CostUsageScanner.codexContainedDocumentIdentity( + scopeID: "home", + ownerID: "00000000-0000-4000-8000-000000000002") + + #expect(first != sibling) + #expect(first == CostUsageScanner.codexContainedDocumentIdentity( + scopeID: "home", + ownerID: "00000000-0000-4000-8000-000000000001")) + } + @Test func `mode cache suffixes are schema scoped and distinct`() { #expect(CodexLineageAccountingMode.defaultMode == .legacy) diff --git a/Tests/CodexBarTests/CodexLineageLedgerTests.swift b/Tests/CodexBarTests/CodexLineageLedgerTests.swift index 4d23eb192d..f6a80c9af2 100644 --- a/Tests/CodexBarTests/CodexLineageLedgerTests.swift +++ b/Tests/CodexBarTests/CodexLineageLedgerTests.swift @@ -448,6 +448,51 @@ struct CodexLineageLedgerTests { #expect(report.families.first?.quality == .primary) } + @Test + func `retained metadata across multiple fork generations does not create sibling cycles`() throws { + let observation = Self.observation(timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) + let root = Self.document(owner: "root", metadata: "root", observations: [observation]) + let firstFork = Self.document( + owner: "first-fork", + metadata: "root", + parent: "root", + observations: [observation]) + let secondFork = Self.document( + owner: "second-fork", + metadata: "root", + parent: "first-fork", + observations: [observation]) + + let report = try CodexLineageLedger.reconcileConservatively( + documents: [root, firstFork, secondFork], + localTimeZone: .gmt) + + #expect(report.primary.utcDays["2026-07-09"]?.input == 100) + #expect(report.families.first?.quality == .primary) + } + + @Test + func `unique metadata aliases still reveal physical ancestry cycles`() throws { + let observation = Self.observation(timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) + let first = Self.document( + owner: "first-owner", + metadata: "first-alias", + parent: "second-alias", + observations: [observation]) + let second = Self.document( + owner: "second-owner", + metadata: "second-alias", + parent: "first-alias", + observations: [observation]) + + let report = try CodexLineageLedger.reconcileConservatively( + documents: [first, second], + localTimeZone: .gmt) + + #expect(report.primary.utcDays.isEmpty) + #expect(report.families.first?.quality == .contained([.ancestryCycle])) + } + @Test func `unrelated owners sharing an ungrounded metadata identity are contained`() throws { let first = Self.document( diff --git a/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift b/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift new file mode 100644 index 0000000000..df373f2cfb --- /dev/null +++ b/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift @@ -0,0 +1,351 @@ +import Foundation +import Testing +@testable import CodexBarCore +#if os(macOS) +import Darwin +#endif + +/// Opt-in aggregate-only replay. Run with `TZ=UTC CODEXBAR_RUN_LINEAGE_VALIDATION=1 swift test --filter ...`. +/// Never print rollout paths, identities, models, or contents. +struct CodexLineageLocalValidationTests { + private enum ValidationError: Error { + case extraFileOutsideCodexHome + case extraFileOutsideRolloutRoots + case extraFileMissingFromSnapshot + case invalidReferenceTotals + } + + private struct Reference: Codable { + let tokens: Int + let finalized: Bool + } + + // The opt-in replay is intentionally linear so its immutable snapshot, scan, and comparison + // lifecycle stays auditable in one place. + // swiftlint:disable function_body_length + @Test + func `local UTC lineage validation`() throws { + guard ProcessInfo.processInfo.environment["CODEXBAR_RUN_LINEAGE_VALIDATION"] == "1" else { return } + CodexBarLog.setLogLevel(.critical) + let home = URL(fileURLWithPath: ("~/.codex" as NSString).expandingTildeInPath, isDirectory: true) + let existingRoot = ProcessInfo.processInfo.environment["CODEXBAR_LINEAGE_VALIDATION_ROOT"].map { + URL(fileURLWithPath: $0, isDirectory: true) + } + let validationRoot = existingRoot ?? FileManager.default.temporaryDirectory + .appendingPathComponent("codexbar-lineage-validation-\(UUID().uuidString)", isDirectory: true) + defer { + if existingRoot == nil { + try? FileManager.default.removeItem(at: validationRoot) + } + } + let snapshotHome = validationRoot.appendingPathComponent("codex-home", isDirectory: true) + if !FileManager.default.fileExists(atPath: snapshotHome.path) { + try Self.snapshotCodexHome(source: home, destination: snapshotHome) + } + let sessions = snapshotHome.appendingPathComponent("sessions", isDirectory: true) + let archived = snapshotHome.appendingPathComponent("archived_sessions", isDirectory: true) + let included = try Array(Set( + Self.rollouts(roots: [sessions, archived], days: Self.discoveryDays) + + (Self.extraValidationFiles(sourceHome: home, snapshotHome: snapshotHome)))) + .sorted { $0.path < $1.path } + let cacheRoot = validationRoot.appendingPathComponent("legacy", isDirectory: true) + Self.progress("snapshot-ready") + + var options = CostUsageScanner.Options( + codexSessionsRoot: sessions, + cacheRoot: cacheRoot, + codexLineageAccountingMode: .legacy, + forceRescan: true) + // A retained snapshot may be replayed, but scanner caches must never influence the result. + options.refreshMinIntervalSeconds = 0 + let since = try #require(ISO8601DateFormatter().date(from: "2026-07-05T00:00:00Z")) + let until = try #require(ISO8601DateFormatter().date(from: "2026-07-12T23:59:59Z")) + let legacyStarted = ContinuousClock.now + let legacyReport = try CostUsageScanner.loadDailyReportCancellable( + provider: .codex, + since: since, + until: until, + now: until, + options: options, + checkCancellation: nil) + let legacyDuration = legacyStarted.duration(to: .now) + let legacy = Dictionary(uniqueKeysWithValues: legacyReport.data.map { + ($0.date, ($0.inputTokens ?? 0) + ($0.outputTokens ?? 0)) + }) + let legacyCache = CostUsageCacheIO.load(provider: .codex, cacheRoot: cacheRoot) + let references = try Self.referenceTotals() + Self.progress("legacy-ready") + + let directModes: [CodexLineageAccountingMode] = switch ProcessInfo.processInfo + .environment["CODEXBAR_VALIDATE_SCANNER_MODES"] + { + case "shadow": [.shadow] + case "lineage": [.lineage] + case "all": [.shadow, .lineage] + default: [] + } + var directReports: [String: [String: Int]] = [:] + for mode in directModes { + let modeRoot = validationRoot.appendingPathComponent(mode.rawValue, isDirectory: true) + var modeOptions = CostUsageScanner.Options( + codexSessionsRoot: sessions, + cacheRoot: modeRoot, + codexLineageAccountingMode: mode, + forceRescan: true) + modeOptions.refreshMinIntervalSeconds = 0 + let report = try CostUsageScanner.loadDailyReportCancellable( + provider: .codex, + since: since, + until: until, + now: until, + options: modeOptions, + checkCancellation: nil) + directReports[mode.rawValue] = Dictionary(uniqueKeysWithValues: report.data.map { + ($0.date, ($0.inputTokens ?? 0) + ($0.outputTokens ?? 0)) + }) + } + + let lineageStarted = ContinuousClock.now + Self.progress("discovery-start") + let discovery = try CodexLineageTwoPassDiscovery.discover( + includedFiles: included, + roots: [sessions, archived]) + Self.progress("discovery-ready") + let families = try CodexLineageEngine.prepareDescriptorFamilies( + descriptors: discovery.descriptors, + unresolvedParents: discovery.unresolvedParents) + Self.progress("families-ready") + let lineage = try CodexLineageEngine.reconcileStreaming(families: families, localTimeZone: .gmt) + Self.progress("lineage-ready") + let lineageDuration = lineageStarted.duration(to: .now) + let resultsByID = Dictionary(uniqueKeysWithValues: lineage.families.map { ($0.stableID, $0) }) + var containmentReasons: [String: Int] = [:] + var containedObservationCount = 0 + for family in families { + guard let result = resultsByID[family.stableID], case let .contained(reasons) = result.quality else { + continue + } + containedObservationCount += family.observationCount + for reason in reasons { + containmentReasons[reason.rawValue, default: 0] += 1 + } + } + let containedFamilyCount = families.count { family in + guard let result = resultsByID[family.stableID] else { return false } + if case .contained = result.quality { + return true + } + return false + } + let selectableContainedFamilies = families + .compactMap { family -> CodexLineageAccountingSelector.ContainedFamily? in + guard let result = resultsByID[family.stableID], case .contained = result.quality else { return nil } + let documents = family.descriptors.compactMap { descriptor -> + CodexLineageAccountingSelector.ContainedDocument? in + guard let days = legacyCache.files[descriptor.fileURL.path]?.days else { return nil } + let identity = CostUsageScanner.codexContainedDocumentIdentity( + scopeID: descriptor.scopeID, + ownerID: descriptor.ownerID) + return .init(identity: identity, days: days) + } + return documents.count == family.descriptors.count ? .init(documents: documents) : nil + } + guard selectableContainedFamilies.count == containedFamilyCount else { + // Match the production selector's fail-closed behavior. A partial contained fallback + // would make the replay look better by silently dropping a family. + throw CodexLineageAccountingSelector.SelectionError.missingContainedFamilyEvidence + } + let selected = CodexLineageAccountingSelector.select( + mode: .lineage, + legacyDays: legacyCache.days, + primaryRows: lineage.report.utcRows, + containedFamilies: selectableContainedFamilies) + let primary = Self.tokensByDay(lineage.report.utcRows) + let selectedTokens = Self.tokensByDay(selected.days) + let contained = Dictionary(uniqueKeysWithValues: Self.referenceDays.map { day in + (day, max(0, (selectedTokens[day] ?? 0) - (primary[day] ?? 0))) + }) + + let samples = Self.referenceDays.map { day -> CodexLineageResidualClassifier.Sample in + let reference = references[day] ?? Reference(tokens: legacy[day] ?? 0, finalized: false) + return .init( + day: day, + referenceTokens: reference.tokens, + isReferenceFinalized: reference.finalized, + isOrdinaryDay: false, + legacyTokens: legacy[day] ?? 0, + ledgerUTCTokens: selectedTokens[day] ?? 0, + ledgerLocalTokens: selectedTokens[day] ?? 0, + evidence: .init( + // Date-filtered replay can prove referenced-parent closure, not full corpus + // exhaustiveness: unreferenced or deleted historical documents may be absent. + localCorpusWasExhaustive: false, + rejectedObservationCount: discovery.descriptors.reduce(0) { + $0 + $1.incompleteObservationCount + }, + unresolvedParentCount: discovery.unresolvedParents.count, + duplicateObservationCount: lineage.report.duplicateObservationCount)) + } + let classification = CodexLineageResidualClassifier.classify(samples: samples) + let ordinaryDivergenceCount = Self.ordinaryDays.count { day in + let legacyTokens = legacy[day] ?? 0 + guard legacyTokens > 0 else { return false } + return abs((selectedTokens[day] ?? 0) - legacyTokens) > Int(Double(legacyTokens) * 0.01) + } + + let output: [String: Any] = [ + "days": Self.referenceDays.map { day in + let result = classification.days.first { $0.day == day } + return [ + "day": day, + "legacy": legacy[day] ?? 0, + "primary": primary[day] ?? 0, + "contained": contained[day] ?? 0, + "selected": selectedTokens[day] ?? 0, + "shadow": directReports[CodexLineageAccountingMode.shadow.rawValue]?[day] ?? 0, + "scannerLineage": directReports[CodexLineageAccountingMode.lineage.rawValue]?[day] ?? 0, + "reference": references[day]?.tokens ?? 0, + "finalized": references[day]?.finalized ?? false, + "classification": result?.classification.rawValue ?? "missing", + ] as [String: Any] + }, + "ordinaryDays": Self.ordinaryDays.map { day in + [ + "day": day, + "legacy": legacy[day] ?? 0, + "selected": selectedTokens[day] ?? 0, + ] as [String: Any] + }, + "coverage": [ + "documents": discovery.descriptors.count, + "families": lineage.diagnostics.familyCount, + "containedFamilies": containedFamilyCount, + "selectableContainedFamilies": selectableContainedFamilies.count, + "containedObservations": containedObservationCount, + "containmentReasons": containmentReasons, + "referencedParents": discovery.referencedParentDocumentCount, + "unresolvedParents": discovery.unresolvedParents.count, + "observations": lineage.diagnostics.observationCount, + "peakFamilyObservations": lineage.diagnostics.peakFamilyObservationCount, + "duplicateObservations": lineage.report.duplicateObservationCount, + ], + "performance": [ + "legacyMilliseconds": Self.milliseconds(legacyDuration), + "lineageMilliseconds": Self.milliseconds(lineageDuration), + ], + "ordinaryDayDivergenceCount": ordinaryDivergenceCount, + "aggregateImproved": classification.improvesAggregateError, + // This replay is one validation artifact, not permission to remove the rollback path. + "supportsLegacyRemoval": false, + "legacyRemovalBlocker": "requires-reviewed-multi-run-promotion-evidence", + ] + let data = try JSONSerialization.data(withJSONObject: output, options: [.sortedKeys]) + let encoded = try #require(String(bytes: data, encoding: .utf8)) + print("CODEX_LINEAGE_VALIDATION " + encoded) + } + + // swiftlint:enable function_body_length + + private static let referenceDays = ["2026-07-09", "2026-07-10", "2026-07-11", "2026-07-12"] + private static let ordinaryDays = ["2026-07-05", "2026-07-06", "2026-07-07", "2026-07-08"] + private static let discoveryDays = Set(Self.referenceDays + Self.ordinaryDays + ["2026-07-04", "2026-07-13"]) + + /// Loads private account totals from an explicitly supplied, untracked JSON file. + /// Format: `{ "YYYY-MM-DD": { "tokens": 123, "finalized": true } }`. + private static func referenceTotals() throws -> [String: Reference] { + guard let path = ProcessInfo.processInfo.environment["CODEXBAR_LINEAGE_REFERENCE_TOTALS_FILE"] else { + return [:] + } + let data = try Data(contentsOf: URL(fileURLWithPath: path)) + guard let references = try? JSONDecoder().decode([String: Reference].self, from: data), + references.values.allSatisfy({ $0.tokens >= 0 }) + else { + throw ValidationError.invalidReferenceTotals + } + return references + } + + private static func rollouts(roots: [URL], days: Set) -> [URL] { + roots.flatMap { root -> [URL] in + guard let enumerator = FileManager.default.enumerator( + at: root, + includingPropertiesForKeys: [.isRegularFileKey], + options: [.skipsHiddenFiles, .skipsPackageDescendants]) + else { return [] } + return enumerator.compactMap { value -> URL? in + guard let url = value as? URL, url.pathExtension.lowercased() == "jsonl" else { return nil } + return days.contains(where: url.path.contains) ? url : nil + } + }.sorted { $0.path < $1.path } + } + + private static func extraValidationFiles(sourceHome: URL, snapshotHome: URL) throws -> [URL] { + guard let listPath = ProcessInfo.processInfo.environment["CODEXBAR_LINEAGE_EXTRA_FILE_LIST"], + let contents = try? String(contentsOfFile: listPath, encoding: .utf8) + else { return [] } + let canonicalSourceHome = sourceHome.standardizedFileURL.resolvingSymlinksInPath().path + return try contents.split(whereSeparator: \.isNewline).map { line in + let source = URL(fileURLWithPath: String(line)).standardizedFileURL.resolvingSymlinksInPath() + guard source.path.hasPrefix(canonicalSourceHome + "/") else { + throw ValidationError.extraFileOutsideCodexHome + } + let relative = source.path.dropFirst(canonicalSourceHome.count + 1) + guard relative.hasPrefix("sessions/") || relative.hasPrefix("archived_sessions/") else { + throw ValidationError.extraFileOutsideRolloutRoots + } + let snapshot = snapshotHome.appendingPathComponent(String(relative)).standardizedFileURL + guard FileManager.default.fileExists(atPath: snapshot.path) else { + throw ValidationError.extraFileMissingFromSnapshot + } + return snapshot + } + } + + private static func snapshotCodexHome(source: URL, destination: URL) throws { + for rootName in ["sessions", "archived_sessions"] { + let sourceRoot = source.appendingPathComponent(rootName, isDirectory: true) + let destinationRoot = destination.appendingPathComponent(rootName, isDirectory: true) + guard let enumerator = FileManager.default.enumerator( + at: sourceRoot, + includingPropertiesForKeys: [.isRegularFileKey], + options: [.skipsHiddenFiles, .skipsPackageDescendants]) + else { continue } + while let sourceFile = enumerator.nextObject() as? URL { + guard sourceFile.pathExtension.lowercased() == "jsonl" else { continue } + let relativePath = sourceFile.path.dropFirst(sourceRoot.path.count + 1) + let destinationFile = destinationRoot.appendingPathComponent(String(relativePath)) + try FileManager.default.createDirectory( + at: destinationFile.deletingLastPathComponent(), + withIntermediateDirectories: true) + try Self.cloneOrCopy(source: sourceFile, destination: destinationFile) + } + } + } + + private static func cloneOrCopy(source: URL, destination: URL) throws { + #if os(macOS) + if clonefile(source.path, destination.path, 0) == 0 { + return + } + #endif + try FileManager.default.copyItem(at: source, to: destination) + } + + private static func tokensByDay(_ rows: [CodexLineageLedger.DailyRow]) -> [String: Int] { + rows.reduce(into: [:]) { $0[$1.day, default: 0] += $1.totals.input + $1.totals.output } + } + + private static func tokensByDay(_ days: CodexLineageAccountingSelector.PackedDays) -> [String: Int] { + days.mapValues { models in models.values.reduce(0) { $0 + ($1[safe: 0] ?? 0) + ($1[safe: 2] ?? 0) } } + } + + private static func milliseconds(_ duration: Duration) -> Int64 { + let components = duration.components + return components.seconds * 1000 + Int64(components.attoseconds / 1_000_000_000_000_000) + } + + private static func progress(_ stage: String) { + print("CODEX_LINEAGE_STAGE " + stage) + fflush(stdout) + } +} From 3ba071fd60fbb3df3d01b0e37bb029ccf1616049 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Mon, 13 Jul 2026 18:38:18 -0400 Subject: [PATCH 19/28] Instrument Codex reset epoch collisions --- .../CodexLineageResetEpochDiagnostics.swift | 227 ++++++++++++++++++ .../Providers/Codex/CodexLineageShadow.swift | 11 +- .../CodexLineageLocalValidationTests.swift | 82 +++++++ ...dexLineageResetEpochDiagnosticsTests.swift | 133 ++++++++++ 4 files changed, 452 insertions(+), 1 deletion(-) create mode 100644 Sources/CodexBarCore/Providers/Codex/CodexLineageResetEpochDiagnostics.swift create mode 100644 Tests/CodexBarTests/CodexLineageResetEpochDiagnosticsTests.swift diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageResetEpochDiagnostics.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageResetEpochDiagnostics.swift new file mode 100644 index 0000000000..b800ad08c9 --- /dev/null +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageResetEpochDiagnostics.swift @@ -0,0 +1,227 @@ +import Foundation + +/// Counterfactual shadow evidence for snapshots that recur after a strong cumulative-token reset. +/// This analyzer cannot produce accounting rows or selector input and never changes ledger totals. +enum CodexLineageResetEpochDiagnostics { + struct Report: Equatable, Sendable { + let strongResetBoundaryCount: Int + let mixedRegressionCount: Int + let postResetRepeatedFingerprintCount: Int + let sameOwnerRepeatCount: Int + let crossOwnerRepeatCount: Int + let estimatedSuppressed: CodexLineageLedger.Totals + let estimatedSuppressedUTC: [String: CodexLineageLedger.Totals] + let sameOwnerEstimatedSuppressed: CodexLineageLedger.Totals + let sameOwnerEstimatedSuppressedUTC: [String: CodexLineageLedger.Totals] + + static let empty = Self( + strongResetBoundaryCount: 0, + mixedRegressionCount: 0, + postResetRepeatedFingerprintCount: 0, + sameOwnerRepeatCount: 0, + crossOwnerRepeatCount: 0, + estimatedSuppressed: .zero, + estimatedSuppressedUTC: [:], + sameOwnerEstimatedSuppressed: .zero, + sameOwnerEstimatedSuppressedUTC: [:]) + } + + private struct Fingerprint: Hashable { + let last: CodexLineageLedger.Totals + let total: CodexLineageLedger.Totals + } + + private struct CandidateKey: Hashable { + let owner: String + let epoch: Int + let fingerprint: Fingerprint + } + + private struct Occurrence { + let owner: String + let stream: Int + let epoch: Int + let fingerprint: Fingerprint + let date: Date + let originalIndex: Int + } + + static func analyze( + families: [CodexLineageEngine.PreparedFamily], + checkCancellation: CostUsageScanner.CancellationCheck? = nil) throws -> Report + { + var report = Report.empty + for family in families { + try checkCancellation?() + var occurrences: [Occurrence] = [] + for (stream, document) in family.documents.enumerated() { + try checkCancellation?() + let owner = Self.ownerKey(document) + var dated: [(Int, Date, CodexLineageLedger.Observation)] = [] + dated.reserveCapacity(document.observations.count) + for (index, observation) in document.observations.enumerated() { + try checkCancellation?() + guard let date = CostUsageScanner.dateFromTimestamp(observation.timestamp) else { continue } + dated.append((index, date, observation)) + } + dated.sort { + if $0.1 != $1.1 { + return $0.1 < $1.1 + } + return $0.0 < $1.0 + } + var epoch = 0 + var previous: CodexLineageLedger.Totals? + var seenInEpoch: Set = [] + for (index, date, observation) in dated { + try checkCancellation?() + if let previous { + if Self.isStrongReset(from: previous, to: observation.total) { + epoch += 1 + seenInEpoch.removeAll(keepingCapacity: true) + report = report.addingStrongReset() + } else if Self.isMixedRegression(from: previous, to: observation.total) { + report = report.addingMixedRegression() + } + } + previous = observation.total + let fingerprint = Fingerprint(last: observation.last, total: observation.total) + guard seenInEpoch.insert(fingerprint).inserted else { continue } + occurrences.append(.init( + owner: owner, + stream: stream, + epoch: epoch, + fingerprint: fingerprint, + date: date, + originalIndex: index)) + } + } + occurrences.sort { + if $0.date != $1.date { + return $0.date < $1.date + } + if $0.owner != $1.owner { + return $0.owner < $1.owner + } + if $0.epoch != $1.epoch { + return $0.epoch < $1.epoch + } + return $0.originalIndex < $1.originalIndex + } + var familySeen: [Fingerprint: [String: Date]] = [:] + var streamEpochs: [Int: [Fingerprint: Set]] = [:] + var counted: Set = [] + for occurrence in occurrences { + try checkCancellation?() + let priorOwnerEpochs = streamEpochs[occurrence.stream]?[occurrence.fingerprint] ?? [] + let sameOwner = priorOwnerEpochs.contains { $0 < occurrence.epoch } + let crossOwner = familySeen[occurrence.fingerprint]?.contains { owner, date in + owner != occurrence.owner && date < occurrence.date + } ?? false + let key = CandidateKey( + owner: occurrence.owner, + epoch: occurrence.epoch, + fingerprint: occurrence.fingerprint) + if occurrence.epoch > 0, sameOwner || crossOwner, counted.insert(key).inserted { + report = report.addingCandidate( + occurrence.fingerprint.last, + day: Self.utcDayKey(from: occurrence.date), + sameOwner: sameOwner) + } + familySeen[occurrence.fingerprint, default: [:]][occurrence.owner] = min( + familySeen[occurrence.fingerprint]?[occurrence.owner] ?? occurrence.date, + occurrence.date) + streamEpochs[occurrence.stream, default: [:]][occurrence.fingerprint, default: []] + .insert(occurrence.epoch) + } + } + return report + } + + private static func ownerKey(_ document: CodexLineageLedger.Document) -> String { + let owner = UUID(uuidString: document.ownerID)?.uuidString.lowercased() ?? document.ownerID + return document.scopeID + "\u{0}" + owner + } + + private static func utcDayKey(from date: Date) -> String { + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = .gmt + 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 static func isStrongReset( + from previous: CodexLineageLedger.Totals, + to current: CodexLineageLedger.Totals) -> Bool + { + let nonIncreasing = current.input <= previous.input && current.cached <= previous.cached + && current.output <= previous.output + return nonIncreasing && current != previous + } + + private static func isMixedRegression( + from previous: CodexLineageLedger.Totals, + to current: CodexLineageLedger.Totals) -> Bool + { + let regressed = current.input < previous.input || current.cached < previous.cached + || current.output < previous.output + let increased = current.input > previous.input || current.cached > previous.cached + || current.output > previous.output + return regressed && increased + } +} + +extension CodexLineageResetEpochDiagnostics.Report { + fileprivate func addingStrongReset() -> Self { + .init( + strongResetBoundaryCount: self.strongResetBoundaryCount + 1, + mixedRegressionCount: self.mixedRegressionCount, + postResetRepeatedFingerprintCount: self.postResetRepeatedFingerprintCount, + sameOwnerRepeatCount: self.sameOwnerRepeatCount, + crossOwnerRepeatCount: self.crossOwnerRepeatCount, + estimatedSuppressed: self.estimatedSuppressed, + estimatedSuppressedUTC: self.estimatedSuppressedUTC, + sameOwnerEstimatedSuppressed: self.sameOwnerEstimatedSuppressed, + sameOwnerEstimatedSuppressedUTC: self.sameOwnerEstimatedSuppressedUTC) + } + + fileprivate func addingMixedRegression() -> Self { + .init( + strongResetBoundaryCount: self.strongResetBoundaryCount, + mixedRegressionCount: self.mixedRegressionCount + 1, + postResetRepeatedFingerprintCount: self.postResetRepeatedFingerprintCount, + sameOwnerRepeatCount: self.sameOwnerRepeatCount, + crossOwnerRepeatCount: self.crossOwnerRepeatCount, + estimatedSuppressed: self.estimatedSuppressed, + estimatedSuppressedUTC: self.estimatedSuppressedUTC, + sameOwnerEstimatedSuppressed: self.sameOwnerEstimatedSuppressed, + sameOwnerEstimatedSuppressedUTC: self.sameOwnerEstimatedSuppressedUTC) + } + + fileprivate func addingCandidate(_ totals: CodexLineageLedger.Totals, day: String, sameOwner: Bool) -> Self { + var estimated = self.estimatedSuppressed + estimated.add(totals) + var days = self.estimatedSuppressedUTC + var dayTotals = days[day] ?? .zero + dayTotals.add(totals) + days[day] = dayTotals + var sameOwnerEstimated = self.sameOwnerEstimatedSuppressed + var sameOwnerDays = self.sameOwnerEstimatedSuppressedUTC + if sameOwner { + sameOwnerEstimated.add(totals) + var sameOwnerDay = sameOwnerDays[day] ?? .zero + sameOwnerDay.add(totals) + sameOwnerDays[day] = sameOwnerDay + } + return .init( + strongResetBoundaryCount: self.strongResetBoundaryCount, + mixedRegressionCount: self.mixedRegressionCount, + postResetRepeatedFingerprintCount: self.postResetRepeatedFingerprintCount + 1, + sameOwnerRepeatCount: self.sameOwnerRepeatCount + (sameOwner ? 1 : 0), + crossOwnerRepeatCount: self.crossOwnerRepeatCount + (sameOwner ? 0 : 1), + estimatedSuppressed: estimated, + estimatedSuppressedUTC: days, + sameOwnerEstimatedSuppressed: sameOwnerEstimated, + sameOwnerEstimatedSuppressedUTC: sameOwnerDays) + } +} diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageShadow.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageShadow.swift index aae024629e..b3e743e80f 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageShadow.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageShadow.swift @@ -27,6 +27,7 @@ enum CodexLineageShadow { let incompleteProvenanceFamilyCount: Int let containedFamilyCount: Int let containmentReasonCounts: [CodexLineageLedger.ContainmentReason: Int] + let resetEpochDiagnostics: CodexLineageResetEpochDiagnostics.Report } static func run( @@ -66,6 +67,13 @@ enum CodexLineageShadow { unresolvedParents: discovery.unresolvedParents, localTimeZone: localTimeZone, checkCancellation: checkCancellation) + let preparedFamilies = try CodexLineageEngine.prepareFamilies( + documents: documents, + unresolvedParents: discovery.unresolvedParents, + checkCancellation: checkCancellation) + let resetEpochDiagnostics = try CodexLineageResetEpochDiagnostics.analyze( + families: preparedFamilies, + checkCancellation: checkCancellation) let ledger = conservative.primary let legacy = Self.legacyTotalsByDay(legacyDays) let dayKeys = Set(legacy.keys).union(ledger.localDays.keys) @@ -92,7 +100,8 @@ enum CodexLineageShadow { } return false }, - containmentReasonCounts: Self.containmentReasonCounts(conservative.families)) + containmentReasonCounts: Self.containmentReasonCounts(conservative.families), + resetEpochDiagnostics: resetEpochDiagnostics) } private static func containmentReasonCounts( diff --git a/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift b/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift index df373f2cfb..6e8980fdd3 100644 --- a/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift +++ b/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift @@ -20,6 +20,38 @@ struct CodexLineageLocalValidationTests { let finalized: Bool } + @Test + func `local reset epoch diagnostic`() throws { + guard ProcessInfo.processInfo.environment["CODEXBAR_VALIDATE_RESET_EPOCHS_ONLY"] == "1" else { return } + CodexBarLog.setLogLevel(.critical) + let root = try #require(ProcessInfo.processInfo.environment["CODEXBAR_LINEAGE_VALIDATION_ROOT"]) + let snapshotHome = URL(fileURLWithPath: root, isDirectory: true) + .appendingPathComponent("codex-home", isDirectory: true) + let roots = [ + snapshotHome.appendingPathComponent("sessions", isDirectory: true), + snapshotHome.appendingPathComponent("archived_sessions", isDirectory: true), + ] + let included = Self.rollouts(roots: roots, days: Self.discoveryDays) + let report = try Self.resetEpochDiagnostics(includedFiles: included, roots: roots) + let output: [String: Any] = [ + "strongResetBoundaries": report.strongResetBoundaryCount, + "mixedRegressions": report.mixedRegressionCount, + "postResetRepeatedFingerprints": report.postResetRepeatedFingerprintCount, + "sameOwnerRepeats": report.sameOwnerRepeatCount, + "crossOwnerRepeats": report.crossOwnerRepeatCount, + "estimatedSuppressedTokens": report.estimatedSuppressed.input + report.estimatedSuppressed.output, + "estimatedSuppressedUTC": report.estimatedSuppressedUTC.mapValues { $0.input + $0.output }, + "sameOwnerEstimatedSuppressedTokens": report.sameOwnerEstimatedSuppressed.input + + report.sameOwnerEstimatedSuppressed.output, + "sameOwnerEstimatedSuppressedUTC": report.sameOwnerEstimatedSuppressedUTC.mapValues { + $0.input + $0.output + }, + ] + let data = try JSONSerialization.data(withJSONObject: output, options: [.sortedKeys]) + let encoded = try #require(String(bytes: data, encoding: .utf8)) + print("CODEX_LINEAGE_RESET_EPOCHS " + encoded) + } + // The opt-in replay is intentionally linear so its immutable snapshot, scan, and comparison // lifecycle stays auditable in one place. // swiftlint:disable function_body_length @@ -76,6 +108,14 @@ struct CodexLineageLocalValidationTests { let references = try Self.referenceTotals() Self.progress("legacy-ready") + let resetEpochDiagnostics: CodexLineageResetEpochDiagnostics.Report? = if ProcessInfo.processInfo + .environment["CODEXBAR_VALIDATE_RESET_EPOCHS"] == "1" + { + try Self.resetEpochDiagnostics(includedFiles: included, roots: [sessions, archived]) + } else { + nil + } + let directModes: [CodexLineageAccountingMode] = switch ProcessInfo.processInfo .environment["CODEXBAR_VALIDATE_SCANNER_MODES"] { @@ -234,6 +274,25 @@ struct CodexLineageLocalValidationTests { "lineageMilliseconds": Self.milliseconds(lineageDuration), ], "ordinaryDayDivergenceCount": ordinaryDivergenceCount, + "resetEpochDiagnostics": [ + "strongResetBoundaries": resetEpochDiagnostics?.strongResetBoundaryCount ?? 0, + "mixedRegressions": resetEpochDiagnostics?.mixedRegressionCount ?? 0, + "postResetRepeatedFingerprints": resetEpochDiagnostics?.postResetRepeatedFingerprintCount ?? 0, + "sameOwnerRepeats": resetEpochDiagnostics?.sameOwnerRepeatCount ?? 0, + "crossOwnerRepeats": resetEpochDiagnostics?.crossOwnerRepeatCount ?? 0, + "estimatedSuppressedTokens": resetEpochDiagnostics.map { + $0.estimatedSuppressed.input + $0.estimatedSuppressed.output + } ?? 0, + "estimatedSuppressedUTC": resetEpochDiagnostics?.estimatedSuppressedUTC.mapValues { + $0.input + $0.output + } ?? [:], + "sameOwnerEstimatedSuppressedTokens": resetEpochDiagnostics.map { + $0.sameOwnerEstimatedSuppressed.input + $0.sameOwnerEstimatedSuppressed.output + } ?? 0, + "sameOwnerEstimatedSuppressedUTC": resetEpochDiagnostics?.sameOwnerEstimatedSuppressedUTC.mapValues { + $0.input + $0.output + } ?? [:], + ], "aggregateImproved": classification.improvesAggregateError, // This replay is one validation artifact, not permission to remove the rollback path. "supportsLegacyRemoval": false, @@ -265,6 +324,29 @@ struct CodexLineageLocalValidationTests { return references } + private static func resetEpochDiagnostics( + includedFiles: [URL], + roots: [URL]) throws -> CodexLineageResetEpochDiagnostics.Report + { + self.progress("reset-epoch-start") + let discovery = try CodexLineageDiscovery.discover(includedFiles: includedFiles, roots: roots) + let documents = discovery.documents.map { document in + CodexLineageLedger.Document( + ownerID: document.ownerID, + metadataSessionID: document.metadataSessionID, + parentSessionID: document.parentSessionID, + observations: document.observations, + scopeID: document.scopeID, + incompleteObservationCount: document.incompleteObservationCount) + } + let families = try CodexLineageEngine.prepareFamilies( + documents: documents, + unresolvedParents: discovery.unresolvedParents) + let report = try CodexLineageResetEpochDiagnostics.analyze(families: families) + Self.progress("reset-epoch-ready") + return report + } + private static func rollouts(roots: [URL], days: Set) -> [URL] { roots.flatMap { root -> [URL] in guard let enumerator = FileManager.default.enumerator( diff --git a/Tests/CodexBarTests/CodexLineageResetEpochDiagnosticsTests.swift b/Tests/CodexBarTests/CodexLineageResetEpochDiagnosticsTests.swift new file mode 100644 index 0000000000..5b0579e9aa --- /dev/null +++ b/Tests/CodexBarTests/CodexLineageResetEpochDiagnosticsTests.swift @@ -0,0 +1,133 @@ +import Foundation +import Testing +@testable import CodexBarCore + +struct CodexLineageResetEpochDiagnosticsTests { + @Test + func `monotonic reemission is not reset evidence`() throws { + let repeated = Self.observation("2026-07-09T12:00:00Z", last: 10, total: 100) + let report = try Self.analyze([repeated, repeated]) + + #expect(report == .empty) + } + + @Test + func `same owner fingerprint repeated after strong reset is estimated once`() throws { + let report = try Self.analyze([ + Self.observation("2026-07-09T12:00:00Z", last: 10, total: 100), + Self.observation("2026-07-09T12:01:00Z", last: 1, total: 1), + Self.observation("2026-07-10T00:01:00Z", last: 10, total: 100), + Self.observation("2026-07-10T00:02:00Z", last: 10, total: 100), + ]) + + #expect(report.strongResetBoundaryCount == 1) + #expect(report.postResetRepeatedFingerprintCount == 1) + #expect(report.sameOwnerRepeatCount == 1) + #expect(report.crossOwnerRepeatCount == 0) + #expect(report.estimatedSuppressed == .init(input: 10, cached: 0, output: 0)) + #expect(report.estimatedSuppressedUTC == [ + "2026-07-10": .init(input: 10, cached: 0, output: 0), + ]) + #expect(report.sameOwnerEstimatedSuppressed == .init(input: 10, cached: 0, output: 0)) + #expect(report.sameOwnerEstimatedSuppressedUTC == [ + "2026-07-10": .init(input: 10, cached: 0, output: 0), + ]) + } + + @Test + func `mixed regression does not open a reset epoch`() throws { + let first = CodexLineageLedger.Observation( + timestamp: "2026-07-09T12:00:00Z", + last: .init(input: 10, cached: 0, output: 1), + total: .init(input: 100, cached: 0, output: 10)) + let mixed = CodexLineageLedger.Observation( + timestamp: "2026-07-09T12:01:00Z", + last: .init(input: 10, cached: 0, output: 1), + total: .init(input: 90, cached: 0, output: 11)) + let report = try Self.analyze([first, mixed]) + + #expect(report.strongResetBoundaryCount == 0) + #expect(report.mixedRegressionCount == 1) + #expect(report.postResetRepeatedFingerprintCount == 0) + } + + @Test + func `copied child without local reset is not reset evidence`() throws { + let observation = Self.observation("2026-07-09T12:00:00Z", last: 10, total: 100) + let parent = Self.document(owner: "parent", observations: [observation]) + let child = CodexLineageLedger.Document( + ownerID: "child", + metadataSessionID: "child", + parentSessionID: "parent", + observations: [observation]) + let family = try #require(try CodexLineageEngine.prepareFamilies(documents: [parent, child]).first) + + let report = try CodexLineageResetEpochDiagnostics.analyze(families: [family]) + + #expect(report == .empty) + } + + @Test + func `duplicate documents with the same owner do not manufacture reset history`() throws { + let repeated = Self.observation("2026-07-09T12:00:00Z", last: 10, total: 100) + let reset = Self.observation("2026-07-09T12:01:00Z", last: 1, total: 1) + let replay = Self.observation("2026-07-09T12:02:00Z", last: 10, total: 100) + let firstCopy = Self.document(owner: "owner", observations: [repeated]) + let beforeReset = Self.observation("2026-07-09T11:59:00Z", last: 20, total: 200) + let secondCopy = Self.document(owner: "owner", observations: [beforeReset, reset, replay]) + let family = try #require(try CodexLineageEngine.prepareFamilies( + documents: [firstCopy, secondCopy]).first) + + let report = try CodexLineageResetEpochDiagnostics.analyze(families: [family]) + + #expect(report.strongResetBoundaryCount == 1) + #expect(report.postResetRepeatedFingerprintCount == 0) + } + + @Test + func `cross owner evidence requires a distinct earlier owner`() throws { + let fingerprint = Self.observation("2026-07-09T12:00:00Z", last: 10, total: 100) + let parent = Self.document(owner: "parent", observations: [fingerprint]) + let child = CodexLineageLedger.Document( + ownerID: "child", + metadataSessionID: "child", + parentSessionID: "parent", + observations: [ + Self.observation("2026-07-09T12:01:00Z", last: 20, total: 200), + Self.observation("2026-07-09T12:02:00Z", last: 1, total: 1), + Self.observation("2026-07-09T12:03:00Z", last: 10, total: 100), + ]) + let family = try #require(try CodexLineageEngine.prepareFamilies(documents: [parent, child]).first) + + let report = try CodexLineageResetEpochDiagnostics.analyze(families: [family]) + + #expect(report.sameOwnerRepeatCount == 0) + #expect(report.crossOwnerRepeatCount == 1) + } + + private static func analyze( + _ observations: [CodexLineageLedger.Observation]) throws -> CodexLineageResetEpochDiagnostics.Report + { + let family = try #require(try CodexLineageEngine.prepareFamilies( + documents: [Self.document(owner: "owner", observations: observations)]).first) + return try CodexLineageResetEpochDiagnostics.analyze(families: [family]) + } + + private static func document( + owner: String, + observations: [CodexLineageLedger.Observation]) -> CodexLineageLedger.Document + { + .init(ownerID: owner, metadataSessionID: owner, parentSessionID: nil, observations: observations) + } + + private static func observation( + _ timestamp: String, + last: Int, + total: Int) -> CodexLineageLedger.Observation + { + .init( + timestamp: timestamp, + last: .init(input: last, cached: 0, output: 0), + total: .init(input: total, cached: 0, output: 0)) + } +} From e6fb8b6f75dceac68c4e312766770409ad9dac90 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Mon, 13 Jul 2026 19:06:24 -0400 Subject: [PATCH 20/28] Diagnose Codex branch frontier collisions --- ...odexLineageBranchFrontierDiagnostics.swift | 298 ++++++++++++++++++ .../Providers/Codex/CodexLineageShadow.swift | 7 +- ...ineageBranchFrontierDiagnosticsTests.swift | 149 +++++++++ .../CodexLineageLocalValidationTests.swift | 58 ++++ 4 files changed, 511 insertions(+), 1 deletion(-) create mode 100644 Sources/CodexBarCore/Providers/Codex/CodexLineageBranchFrontierDiagnostics.swift create mode 100644 Tests/CodexBarTests/CodexLineageBranchFrontierDiagnosticsTests.swift diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageBranchFrontierDiagnostics.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageBranchFrontierDiagnostics.swift new file mode 100644 index 0000000000..c34751b03a --- /dev/null +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageBranchFrontierDiagnostics.swift @@ -0,0 +1,298 @@ +import Foundation + +/// Counterfactual evidence for numeric token snapshots that recur after fork branches diverge. +/// This type cannot produce accounting rows or selector input and therefore cannot change totals. +enum CodexLineageBranchFrontierDiagnostics { + struct Report: Equatable, Sendable { + var familyCount: Int + var eligibleFamilyCount: Int + var ownerCount: Int + var eligibleOwnerCount: Int + var ambiguousOwnerHistoryCount: Int + var resolvedParentEdgeCount: Int + var unresolvedParentEdgeCount: Int + var sharedPrefixFingerprintCount: Int + var sharedPrefixDuplicateOccurrenceCount: Int + var strongPostFrontierFingerprintCount: Int + var strongPostFrontierDuplicateOccurrenceCount: Int + var ambiguousPostFrontierFingerprintCount: Int + var ambiguousPostFrontierBranchInstanceCount: Int + var unknownPostFrontierFingerprintCount: Int + var estimatedSuppressed: CodexLineageLedger.Totals + var estimatedSuppressedUTC: [String: CodexLineageLedger.Totals] + var peakFamilyObservationCount: Int + var peakFingerprintOccurrenceCount: Int + var skippedOversizeFamilyCount: Int + var overflowedEstimateCount: Int + + static let empty = Self( + familyCount: 0, + eligibleFamilyCount: 0, + ownerCount: 0, + eligibleOwnerCount: 0, + ambiguousOwnerHistoryCount: 0, + resolvedParentEdgeCount: 0, + unresolvedParentEdgeCount: 0, + sharedPrefixFingerprintCount: 0, + sharedPrefixDuplicateOccurrenceCount: 0, + strongPostFrontierFingerprintCount: 0, + strongPostFrontierDuplicateOccurrenceCount: 0, + ambiguousPostFrontierFingerprintCount: 0, + ambiguousPostFrontierBranchInstanceCount: 0, + unknownPostFrontierFingerprintCount: 0, + estimatedSuppressed: .zero, + estimatedSuppressedUTC: [:], + peakFamilyObservationCount: 0, + peakFingerprintOccurrenceCount: 0, + skippedOversizeFamilyCount: 0, + overflowedEstimateCount: 0) + } + + private static let maximumFamilyObservationCount = 1_000_000 + + private struct Fingerprint: Hashable { let last: CodexLineageLedger.Totals; let total: CodexLineageLedger.Totals } + private struct CopyKey: Hashable { let date: Date; let fingerprint: Fingerprint } + private struct OwnerHistory { + let owner: String + let parent: String? + let keys: [CopyKey] + var frontier: Int? + } + + private struct PostOccurrence { + let owner: String + let key: CopyKey + let previous: CopyKey? + let next: CopyKey? + let hasDivergenceWitness: Bool + } + + private struct OccurrenceIdentity: Hashable { let owner: String; let key: CopyKey } + + private struct StrongContext: Hashable { let current: CopyKey; let previous: CopyKey; let next: CopyKey } + + // The diagnostic intentionally keeps its family-local state machine linear and auditable. + // swiftlint:disable:next cyclomatic_complexity + static func analyze( + families: [CodexLineageEngine.PreparedFamily], + checkCancellation: CostUsageScanner.CancellationCheck? = nil) throws -> Report + { + var report = Report.empty + for family in families { + try checkCancellation?() + report.familyCount += 1 + report.peakFamilyObservationCount = max(report.peakFamilyObservationCount, family.observationCount) + if family.observationCount > Self.maximumFamilyObservationCount { + report.skippedOversizeFamilyCount += 1 + continue + } + let grouped = Dictionary(grouping: family.documents, by: Self.ownerKey) + report.ownerCount += grouped.count + var histories: [String: OwnerHistory] = [:] + for (owner, documents) in grouped { + try checkCancellation?() + guard let keys = try Self.canonicalHistory(documents, checkCancellation: checkCancellation) else { + report.ambiguousOwnerHistoryCount += 1 + continue + } + let parents = Set(documents.compactMap { Self.nonEmpty($0.parentSessionID) } + .map(Self.canonicalIdentity)) + guard parents.count <= 1 else { + report.ambiguousOwnerHistoryCount += 1 + continue + } + histories[owner] = OwnerHistory(owner: owner, parent: parents.first, keys: keys, frontier: nil) + } + report.eligibleOwnerCount += histories.count + guard !histories.isEmpty else { continue } + + let ownerByIdentity = Dictionary(uniqueKeysWithValues: histories.keys.map { (Self.unscoped($0), $0) }) + let aliases = Dictionary(grouping: family.documents.compactMap { document -> (String, String)? in + guard let metadata = Self.nonEmpty(document.metadataSessionID) else { return nil } + return (Self.canonicalIdentity(metadata), Self.ownerKey(document)) + }, by: \.0).mapValues { Set($0.map(\.1)) } + for owner in histories.keys.sorted() { + try checkCancellation?() + guard let parentID = histories[owner]?.parent else { continue } + let parent = ownerByIdentity[parentID] ?? aliases[parentID]?.onlyElement + guard let parent, let childKeys = histories[owner]?.keys, + let parentKeys = histories[parent]?.keys + else { + report.unresolvedParentEdgeCount += 1 + continue + } + report.resolvedParentEdgeCount += 1 + let prefix = try Self.commonPrefix( + childKeys, + parentKeys, + checkCancellation: checkCancellation) + guard prefix > 0 else { continue } + histories[owner]?.frontier = prefix + report.sharedPrefixFingerprintCount += Set(childKeys.prefix(prefix).map(\.fingerprint)).count + report.sharedPrefixDuplicateOccurrenceCount += prefix + } + + var postByFingerprint: [Fingerprint: [PostOccurrence]] = [:] + for history in histories.values { + guard let frontier = history.frontier, frontier < history.keys.count else { continue } + for index in frontier.. 0 ? history.keys[index - 1] : nil, + next: index + 1 < history.keys.count ? history.keys[index + 1] : nil, + hasDivergenceWitness: index > frontier) + postByFingerprint[occurrence.key.fingerprint, default: []].append(occurrence) + } + } + for (fingerprint, occurrences) in postByFingerprint where Set(occurrences.map(\.owner)).count > 1 { + try checkCancellation?() + report.peakFingerprintOccurrenceCount = max(report.peakFingerprintOccurrenceCount, occurrences.count) + let strongGroups = Dictionary( + grouping: occurrences.compactMap { occurrence -> (StrongContext, OccurrenceIdentity)? in + guard let previous = occurrence.previous, let next = occurrence.next else { return nil } + return ( + StrongContext(current: occurrence.key, previous: previous, next: next), + OccurrenceIdentity(owner: occurrence.owner, key: occurrence.key)) + }, + by: \.0).values.map { Set($0.map(\.1)) } + .filter { Set($0.map(\.owner)).count > 1 } + let strongOccurrences = strongGroups.reduce(into: Set()) { $0.formUnion($1) } + if !strongGroups.isEmpty { + report.strongPostFrontierFingerprintCount += 1 + report.strongPostFrontierDuplicateOccurrenceCount += strongGroups.reduce(0) { + $0 + Set($1.map(\.owner)).count - 1 + } + } + let nonStrong = occurrences.filter { + !strongOccurrences.contains(.init(owner: $0.owner, key: $0.key)) + } + let hasDistinctOccurrenceTimes = Set(nonStrong.map(\.key.date)).count > 1 + let independentOwners = hasDistinctOccurrenceTimes ? + Set(nonStrong.filter(\.hasDivergenceWitness).map(\.owner)) : [] + if independentOwners.count > 1 { + report.ambiguousPostFrontierFingerprintCount += 1 + report.ambiguousPostFrontierBranchInstanceCount += independentOwners.count + let suppressedCopies = independentOwners.count - 1 + guard let suppressed = Self.multiplied(fingerprint.last, by: suppressedCopies), + let aggregate = Self.adding(report.estimatedSuppressed, suppressed) + else { + report.overflowedEstimateCount += 1 + continue + } + report.estimatedSuppressed = aggregate + if let date = occurrences.filter({ independentOwners.contains($0.owner) }).map(\.key.date).min() { + let day = Self.utcDay(date) + let totals = report.estimatedSuppressedUTC[day] ?? .zero + if let updated = Self.adding(totals, suppressed) { + report.estimatedSuppressedUTC[day] = updated + } else { + report.overflowedEstimateCount += 1 + } + } + } else if strongGroups.isEmpty { + report.unknownPostFrontierFingerprintCount += 1 + } + } + report.eligibleFamilyCount += 1 + } + return report + } + + private static func canonicalHistory( + _ documents: [CodexLineageLedger.Document], + checkCancellation: CostUsageScanner.CancellationCheck?) throws -> [CopyKey]? + { + var histories: [[CopyKey]?] = [] + histories.reserveCapacity(documents.count) + for document in documents { + try checkCancellation?() + var dated: [(Int, CopyKey)] = [] + for (index, observation) in document.observations.enumerated() { + try checkCancellation?() + guard document.incompleteObservationCount == 0, + let date = CostUsageScanner.dateFromTimestamp(observation.timestamp) + else { + histories.append(nil) + break + } + dated.append(( + index, + CopyKey(date: date, fingerprint: .init(last: observation.last, total: observation.total)))) + } + guard dated.count == document.observations.count else { continue } + dated.sort { $0.1.date != $1.1.date ? $0.1.date < $1.1.date : $0.0 < $1.0 } + var seen: Set = [] + histories.append(dated.compactMap { seen.insert($0.1).inserted ? $0.1 : nil }) + } + guard !histories.contains(where: { $0 == nil }) else { return nil } + let values = histories.compactMap(\.self).sorted { $0.count > $1.count } + guard let longest = values.first, + values.dropFirst().allSatisfy({ Array(longest.prefix($0.count)) == $0 }) else { return nil } + return longest + } + + private static func commonPrefix( + _ lhs: [CopyKey], + _ rhs: [CopyKey], + checkCancellation: CostUsageScanner.CancellationCheck?) throws -> Int + { + var count = 0 + while count < min(lhs.count, rhs.count), lhs[count] == rhs[count] { + try checkCancellation?() + count += 1 + } + return count + } + + private static func ownerKey(_ document: CodexLineageLedger.Document) -> String { + document.scopeID + "\u{0}" + self.canonicalIdentity(document.ownerID) + } + + private static func unscoped(_ value: String) -> String { + String(value.split(separator: "\u{0}").last ?? "") + } + + private static func nonEmpty(_ value: String?) -> String? { + value.flatMap { $0.isEmpty ? nil : $0 } + } + + private static func canonicalIdentity(_ value: String) -> String { + UUID(uuidString: value)?.uuidString.lowercased() ?? value + } + + private static func multiplied( + _ totals: CodexLineageLedger.Totals, + by count: Int) -> CodexLineageLedger.Totals? + { + let input = totals.input.multipliedReportingOverflow(by: count) + let cached = totals.cached.multipliedReportingOverflow(by: count) + let output = totals.output.multipliedReportingOverflow(by: count) + guard !input.overflow, !cached.overflow, !output.overflow else { return nil } + return .init(input: input.partialValue, cached: cached.partialValue, output: output.partialValue) + } + + private static func adding( + _ lhs: CodexLineageLedger.Totals, + _ rhs: CodexLineageLedger.Totals) -> CodexLineageLedger.Totals? + { + let input = lhs.input.addingReportingOverflow(rhs.input) + let cached = lhs.cached.addingReportingOverflow(rhs.cached) + let output = lhs.output.addingReportingOverflow(rhs.output) + guard !input.overflow, !cached.overflow, !output.overflow else { return nil } + return .init(input: input.partialValue, cached: cached.partialValue, output: output.partialValue) + } + + private static func utcDay(_ date: Date) -> String { + var calendar = Calendar(identifier: .gregorian); calendar.timeZone = .gmt + let parts = calendar.dateComponents([.year, .month, .day], from: date) + return String(format: "%04d-%02d-%02d", parts.year ?? 0, parts.month ?? 0, parts.day ?? 0) + } +} + +extension Set { + fileprivate var onlyElement: Element? { + self.count == 1 ? self.first : nil + } +} diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageShadow.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageShadow.swift index b3e743e80f..7425d6d454 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageShadow.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageShadow.swift @@ -28,6 +28,7 @@ enum CodexLineageShadow { let containedFamilyCount: Int let containmentReasonCounts: [CodexLineageLedger.ContainmentReason: Int] let resetEpochDiagnostics: CodexLineageResetEpochDiagnostics.Report + let branchFrontierDiagnostics: CodexLineageBranchFrontierDiagnostics.Report } static func run( @@ -74,6 +75,9 @@ enum CodexLineageShadow { let resetEpochDiagnostics = try CodexLineageResetEpochDiagnostics.analyze( families: preparedFamilies, checkCancellation: checkCancellation) + let branchFrontierDiagnostics = try CodexLineageBranchFrontierDiagnostics.analyze( + families: preparedFamilies, + checkCancellation: checkCancellation) let ledger = conservative.primary let legacy = Self.legacyTotalsByDay(legacyDays) let dayKeys = Set(legacy.keys).union(ledger.localDays.keys) @@ -101,7 +105,8 @@ enum CodexLineageShadow { return false }, containmentReasonCounts: Self.containmentReasonCounts(conservative.families), - resetEpochDiagnostics: resetEpochDiagnostics) + resetEpochDiagnostics: resetEpochDiagnostics, + branchFrontierDiagnostics: branchFrontierDiagnostics) } private static func containmentReasonCounts( diff --git a/Tests/CodexBarTests/CodexLineageBranchFrontierDiagnosticsTests.swift b/Tests/CodexBarTests/CodexLineageBranchFrontierDiagnosticsTests.swift new file mode 100644 index 0000000000..705473dcbd --- /dev/null +++ b/Tests/CodexBarTests/CodexLineageBranchFrontierDiagnosticsTests.swift @@ -0,0 +1,149 @@ +import Foundation +import Testing +@testable import CodexBarCore + +struct CodexLineageBranchFrontierDiagnosticsTests { + @Test + func `copied prefix and independently convergent suffix are distinguished`() throws { + let prefix = Self.observation("2026-07-09T12:00:00Z", last: 10, total: 10) + let collisionA = Self.observation("2026-07-09T12:03:00Z", last: 30, total: 100) + let collisionB = Self.observation("2026-07-09T12:04:00Z", last: 30, total: 100) + let family = try Self.family([ + Self.document("root", observations: [prefix]), + Self.document("first", parent: "root", observations: [ + prefix, Self.observation("2026-07-09T12:01:00Z", last: 20, total: 30), collisionA, + ]), + Self.document("second", parent: "root", observations: [ + prefix, Self.observation("2026-07-09T12:02:00Z", last: 25, total: 35), collisionB, + ]), + ]) + + let report = try CodexLineageBranchFrontierDiagnostics.analyze(families: [family]) + + #expect(report.sharedPrefixFingerprintCount == 2) + #expect(report.sharedPrefixDuplicateOccurrenceCount == 2) + #expect(report.ambiguousPostFrontierFingerprintCount == 1) + #expect(report.ambiguousPostFrontierBranchInstanceCount == 2) + #expect(report.estimatedSuppressed == .init(input: 30, cached: 0, output: 0)) + #expect(report.estimatedSuppressedUTC == ["2026-07-09": .init(input: 30, cached: 0, output: 0)]) + } + + @Test + func `matching timestamp and neighboring flow is strong copy evidence`() throws { + let prefix = Self.observation("2026-07-09T12:00:00Z", last: 10, total: 10) + let divergent = Self.observation("2026-07-09T12:01:00Z", last: 20, total: 30) + let collision = Self.observation("2026-07-09T12:02:00Z", last: 30, total: 60) + let following = Self.observation("2026-07-09T12:03:00Z", last: 40, total: 100) + let family = try Self.family([ + Self.document("root", observations: [prefix]), + Self.document("first", parent: "root", observations: [prefix, divergent, collision, following]), + Self.document("second", parent: "root", observations: [prefix, divergent, collision, following]), + ]) + + let report = try CodexLineageBranchFrontierDiagnostics.analyze(families: [family]) + + #expect(report.strongPostFrontierFingerprintCount == 2) + #expect(report.strongPostFrontierDuplicateOccurrenceCount == 2) + #expect(report.ambiguousPostFrontierFingerprintCount == 0) + #expect(report.estimatedSuppressed == .zero) + } + + @Test + func `collision without two divergence witnesses remains unknown`() throws { + let prefix = Self.observation("2026-07-09T12:00:00Z", last: 10, total: 10) + let collision = Self.observation("2026-07-09T12:02:00Z", last: 30, total: 60) + let family = try Self.family([ + Self.document("root", observations: [prefix]), + Self.document("first", parent: "root", observations: [prefix, collision]), + Self.document("second", parent: "root", observations: [prefix, collision]), + ]) + + let report = try CodexLineageBranchFrontierDiagnostics.analyze(families: [family]) + + #expect(report.unknownPostFrontierFingerprintCount == 1) + #expect(report.estimatedSuppressed == .zero) + } + + @Test + func `strong copy does not hide a separate convergence from the same owner`() throws { + let prefix = Self.observation("2026-07-09T12:00:00Z", last: 10, total: 10) + let copied = Self.observation("2026-07-09T12:02:00Z", last: 30, total: 60) + let family = try Self.family([ + Self.document("root", observations: [prefix]), + Self.document("first", parent: "root", observations: [ + prefix, + Self.observation("2026-07-09T12:01:00Z", last: 20, total: 30), + copied, + Self.observation("2026-07-09T12:03:00Z", last: 40, total: 100), + Self.observation("2026-07-09T12:06:00Z", last: 30, total: 60), + ]), + Self.document("second", parent: "root", observations: [ + prefix, + Self.observation("2026-07-09T12:01:00Z", last: 20, total: 30), + copied, + Self.observation("2026-07-09T12:03:00Z", last: 40, total: 100), + ]), + Self.document("third", parent: "root", observations: [ + prefix, + Self.observation("2026-07-09T12:04:00Z", last: 25, total: 35), + Self.observation("2026-07-09T12:05:00Z", last: 30, total: 60), + ]), + ]) + + let report = try CodexLineageBranchFrontierDiagnostics.analyze(families: [family]) + + #expect(report.strongPostFrontierFingerprintCount == 2) + #expect(report.strongPostFrontierDuplicateOccurrenceCount == 2) + #expect(report.ambiguousPostFrontierFingerprintCount == 1) + #expect(report.ambiguousPostFrontierBranchInstanceCount == 2) + #expect(report.estimatedSuppressed == .init(input: 30, cached: 0, output: 0)) + } + + @Test + func `diagnostics leave ledger accounting unchanged and are permutation stable`() throws { + let prefix = Self.observation("2026-07-09T12:00:00Z", last: 10, total: 10) + let documents = [ + Self.document("root", observations: [prefix]), + Self.document("first", parent: "root", observations: [ + prefix, Self.observation("2026-07-09T12:01:00Z", last: 20, total: 30), + Self.observation("2026-07-09T12:03:00Z", last: 30, total: 100), + ]), + Self.document("second", parent: "root", observations: [ + prefix, Self.observation("2026-07-09T12:02:00Z", last: 25, total: 35), + Self.observation("2026-07-09T12:04:00Z", last: 30, total: 100), + ]), + ] + let before = try CodexLineageLedger.reconcile(documents: documents, localTimeZone: .gmt) + let first = try CodexLineageBranchFrontierDiagnostics.analyze(families: [Self.family(documents)]) + let second = try CodexLineageBranchFrontierDiagnostics.analyze(families: [Self.family(documents.reversed())]) + let after = try CodexLineageLedger.reconcile(documents: documents, localTimeZone: .gmt) + + #expect(first == second) + #expect(before == after) + } + + private static func family(_ documents: some Sequence) throws -> CodexLineageEngine + .PreparedFamily + { + try #require(try CodexLineageEngine.prepareFamilies(documents: Array(documents)).first) + } + + private static func document( + _ owner: String, + parent: String? = nil, + observations: [CodexLineageLedger.Observation]) -> CodexLineageLedger.Document + { + .init(ownerID: owner, metadataSessionID: owner, parentSessionID: parent, observations: observations) + } + + private static func observation( + _ timestamp: String, + last: Int, + total: Int) -> CodexLineageLedger.Observation + { + .init( + timestamp: timestamp, + last: .init(input: last, cached: 0, output: 0), + total: .init(input: total, cached: 0, output: 0)) + } +} diff --git a/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift b/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift index 6e8980fdd3..7c6c02feb8 100644 --- a/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift +++ b/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift @@ -20,6 +20,41 @@ struct CodexLineageLocalValidationTests { let finalized: Bool } + @Test + func `local branch frontier diagnostic`() throws { + guard ProcessInfo.processInfo.environment["CODEXBAR_VALIDATE_BRANCH_FRONTIERS_ONLY"] == "1" else { return } + CodexBarLog.setLogLevel(.critical) + let root = try #require(ProcessInfo.processInfo.environment["CODEXBAR_LINEAGE_VALIDATION_ROOT"]) + let snapshotHome = URL(fileURLWithPath: root, isDirectory: true) + .appendingPathComponent("codex-home", isDirectory: true) + let roots = [ + snapshotHome.appendingPathComponent("sessions", isDirectory: true), + snapshotHome.appendingPathComponent("archived_sessions", isDirectory: true), + ] + let included = Self.rollouts(roots: roots, days: Self.discoveryDays) + let report = try Self.branchFrontierDiagnostics(includedFiles: included, roots: roots) + let output: [String: Any] = [ + "families": report.familyCount, + "eligibleFamilies": report.eligibleFamilyCount, + "ambiguousOwnerHistories": report.ambiguousOwnerHistoryCount, + "resolvedParentEdges": report.resolvedParentEdgeCount, + "unresolvedParentEdges": report.unresolvedParentEdgeCount, + "sharedPrefixFingerprints": report.sharedPrefixFingerprintCount, + "strongPostFrontierFingerprints": report.strongPostFrontierFingerprintCount, + "ambiguousPostFrontierFingerprints": report.ambiguousPostFrontierFingerprintCount, + "ambiguousBranchInstances": report.ambiguousPostFrontierBranchInstanceCount, + "unknownPostFrontierFingerprints": report.unknownPostFrontierFingerprintCount, + "estimatedSuppressedTokens": report.estimatedSuppressed.input + report.estimatedSuppressed.output, + "estimatedSuppressedUTC": report.estimatedSuppressedUTC.mapValues { $0.input + $0.output }, + "peakFamilyObservations": report.peakFamilyObservationCount, + "skippedOversizeFamilies": report.skippedOversizeFamilyCount, + "overflowedEstimates": report.overflowedEstimateCount, + ] + let data = try JSONSerialization.data(withJSONObject: output, options: [.sortedKeys]) + let encoded = try #require(String(bytes: data, encoding: .utf8)) + print("CODEX_LINEAGE_BRANCH_FRONTIERS " + encoded) + } + @Test func `local reset epoch diagnostic`() throws { guard ProcessInfo.processInfo.environment["CODEXBAR_VALIDATE_RESET_EPOCHS_ONLY"] == "1" else { return } @@ -347,6 +382,29 @@ struct CodexLineageLocalValidationTests { return report } + private static func branchFrontierDiagnostics( + includedFiles: [URL], + roots: [URL]) throws -> CodexLineageBranchFrontierDiagnostics.Report + { + self.progress("branch-frontier-start") + let discovery = try CodexLineageDiscovery.discover(includedFiles: includedFiles, roots: roots) + let documents = discovery.documents.map { document in + CodexLineageLedger.Document( + ownerID: document.ownerID, + metadataSessionID: document.metadataSessionID, + parentSessionID: document.parentSessionID, + observations: document.observations, + scopeID: document.scopeID, + incompleteObservationCount: document.incompleteObservationCount) + } + let families = try CodexLineageEngine.prepareFamilies( + documents: documents, + unresolvedParents: discovery.unresolvedParents) + let report = try CodexLineageBranchFrontierDiagnostics.analyze(families: families) + Self.progress("branch-frontier-ready") + return report + } + private static func rollouts(roots: [URL], days: Set) -> [URL] { roots.flatMap { root -> [URL] in guard let enumerator = FileManager.default.enumerator( From 2a25e7a508bf683b31f02c9e6d1273f60c03d75d Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Mon, 13 Jul 2026 19:45:56 -0400 Subject: [PATCH 21/28] Profile and optimize lineage accounting --- .../Generated/CodexParserHash.generated.swift | 2 +- .../Providers/Codex/CodexLineageEngine.swift | 31 +++- .../Providers/Codex/CodexLineageLedger.swift | 148 ++++++------------ .../Codex/CodexLineageTwoPassDiscovery.swift | 33 ++-- .../Vendored/CostUsage/CostUsageJsonl.swift | 6 +- .../Vendored/CostUsage/CostUsageScanner.swift | 54 ++++++- .../CodexLineageEngineTests.swift | 20 +++ .../CodexLineageLocalValidationTests.swift | 9 ++ 8 files changed, 185 insertions(+), 118 deletions(-) diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index 66e60596f4..cc3fe322ff 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 = "5caa3e1eda239d03" + static let value = "f0c3db302ce7c1bb" } diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageEngine.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageEngine.swift index 22bd558301..bf8cb4f41d 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageEngine.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageEngine.swift @@ -54,6 +54,9 @@ enum CodexLineageEngine { let observationCount: Int let peakFamilyObservationCount: Int let peakAcceptedFingerprintCount: Int + let documentLoadMilliseconds: Int64 + let familyReconciliationMilliseconds: Int64 + let compositionMilliseconds: Int64 } struct Result: Equatable, Sendable { @@ -189,6 +192,8 @@ enum CodexLineageEngine { var reused = 0 var peakLoadedObservations = 0 var peakAccepted = 0 + var documentLoadDuration = Duration.zero + var familyReconciliationDuration = Duration.zero for family in families.sorted(by: { $0.stableID < $1.stableID }) { try checkCancellation?() let cacheFingerprint = Self.cacheFingerprint( @@ -203,6 +208,7 @@ enum CodexLineageEngine { var documents: [CodexLineageLedger.Document] = [] documents.reserveCapacity(family.descriptors.count) var loadedObservations = 0 + let loadStarted = ContinuousClock.now for descriptor in family.descriptors { try checkCancellation?() let document: CodexLineageLedger.Document = if let loadDocument { @@ -215,12 +221,15 @@ enum CodexLineageEngine { loadedObservations += document.observations.count documents.append(document) } + documentLoadDuration += loadStarted.duration(to: .now) peakLoadedObservations = max(peakLoadedObservations, loadedObservations) + let reconciliationStarted = ContinuousClock.now let conservative = try CodexLineageLedger.reconcileConservatively( documents: documents, unresolvedParents: family.unresolvedParents, localTimeZone: localTimeZone, checkCancellation: checkCancellation) + familyReconciliationDuration += reconciliationStarted.duration(to: .now) let quality = conservative.families.first?.quality ?? .primary let result = FamilyResult( stableID: family.stableID, @@ -233,7 +242,9 @@ enum CodexLineageEngine { peakAccepted = max(peakAccepted, result.report.acceptedObservationCount) } results.sort { $0.stableID < $1.stableID } + let compositionStarted = ContinuousClock.now let report = try Self.compose(results.map(\.report), checkCancellation: checkCancellation) + let compositionDuration = compositionStarted.duration(to: .now) let candidate = Cache( algorithmVersion: Self.algorithmVersion, familiesByInputFingerprint: Dictionary(uniqueKeysWithValues: results.map { @@ -250,7 +261,10 @@ enum CodexLineageEngine { reusedFamilyCount: reused, observationCount: families.reduce(0) { $0 + $1.observationCount }, peakFamilyObservationCount: peakLoadedObservations, - peakAcceptedFingerprintCount: peakAccepted)) + peakAcceptedFingerprintCount: peakAccepted, + documentLoadMilliseconds: Self.milliseconds(documentLoadDuration), + familyReconciliationMilliseconds: Self.milliseconds(familyReconciliationDuration), + compositionMilliseconds: Self.milliseconds(compositionDuration))) } static func reconcile( @@ -267,6 +281,7 @@ enum CodexLineageEngine { var recomputed = 0 var reused = 0 var peakAccepted = 0 + var familyReconciliationDuration = Duration.zero for family in families.sorted(by: { $0.stableID < $1.stableID }) { try checkCancellation?() @@ -282,11 +297,13 @@ enum CodexLineageEngine { peakAccepted = max(peakAccepted, cached.report.acceptedObservationCount) continue } + let reconciliationStarted = ContinuousClock.now let conservative = try CodexLineageLedger.reconcileConservatively( documents: family.documents, unresolvedParents: family.unresolvedParents, localTimeZone: localTimeZone, checkCancellation: checkCancellation) + familyReconciliationDuration += reconciliationStarted.duration(to: .now) let quality = conservative.families.first?.quality ?? .primary let familyFingerprint = Self.familyFingerprint(input: cacheFingerprint, quality: quality) let result = FamilyResult( @@ -301,7 +318,9 @@ enum CodexLineageEngine { } results.sort { $0.stableID < $1.stableID } + let compositionStarted = ContinuousClock.now let report = try Self.compose(results.map(\.report), checkCancellation: checkCancellation) + let compositionDuration = compositionStarted.duration(to: .now) let candidate = Cache( algorithmVersion: Self.algorithmVersion, familiesByInputFingerprint: Dictionary(uniqueKeysWithValues: results.map { @@ -318,7 +337,10 @@ enum CodexLineageEngine { reusedFamilyCount: reused, observationCount: families.reduce(0) { $0 + $1.observationCount }, peakFamilyObservationCount: families.map(\.observationCount).max() ?? 0, - peakAcceptedFingerprintCount: peakAccepted)) + peakAcceptedFingerprintCount: peakAccepted, + documentLoadMilliseconds: 0, + familyReconciliationMilliseconds: Self.milliseconds(familyReconciliationDuration), + compositionMilliseconds: Self.milliseconds(compositionDuration))) } static func publish( @@ -550,6 +572,11 @@ enum CodexLineageEngine { scopeID + "\u{0}" + self.canonical(value) } + private static func milliseconds(_ duration: Duration) -> Int64 { + let components = duration.components + return components.seconds * 1000 + Int64(components.attoseconds / 1_000_000_000_000_000) + } + private struct DisjointSet { var parents: [String: String] = [:] diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift index 2e2cb921dd..98c93a0910 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift @@ -21,20 +21,12 @@ 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, - model: String = CostUsagePricing.codexUnattributedModel, - last: Totals, - total: Totals) - { - self.eventID = eventID + init(timestamp: String, model: String = CostUsagePricing.codexUnattributedModel, last: Totals, total: Totals) { self.timestamp = timestamp self.model = CostUsagePricing.normalizeCodexModel(model) self.last = last @@ -135,6 +127,19 @@ enum CodexLineageLedger { documents: [Document], localTimeZone: TimeZone, checkCancellation: CostUsageScanner.CancellationCheck? = nil) throws -> Report + { + try self.reconcile( + documents: documents, + localTimeZone: localTimeZone, + checkCancellation: checkCancellation, + validatedDates: [:]) + } + + private static func reconcile( + documents: [Document], + localTimeZone: TimeZone, + checkCancellation: CostUsageScanner.CancellationCheck?, + validatedDates: [String: Date]) throws -> Report { var graph = DisjointSet() for document in documents { @@ -152,10 +157,13 @@ enum CodexLineageLedger { var documentsByComponent: [String: [Document]] = [:] for document in documents { + try checkCancellation?() let componentID = graph.find(Self.scoped(document.ownerID, document: document)) documentsByComponent[componentID, default: []].append(document) } + var physicalObservationCount = 0 + var parsedDates = validatedDates var utcDays: [String: Totals] = [:] var localDays: [String: Totals] = [:] var utcRows: [DailyRowKey: DailyRowValue] = [:] @@ -163,40 +171,33 @@ enum CodexLineageLedger { var acceptedObservationCount = 0 for componentDocuments in documentsByComponent.values { try checkCancellation?() - let parentByOwner = Self.physicalParents(componentDocuments) - var accepted: [ObservationIdentity: AcceptedObservation] = [:] - var acceptedByFingerprint: [Fingerprint: [String: ObservationIdentity]] = [:] - for document in Self.parentsFirst(componentDocuments, parentByOwner: parentByOwner) { - let ownerID = Self.scoped(document.ownerID, document: document) + var accepted: [Fingerprint: AcceptedObservation] = [:] + for document in componentDocuments { for observation in document.observations { try checkCancellation?() physicalObservationCount += 1 - let date = try Self.date(from: observation.timestamp) + let date: Date + if let cached = parsedDates[observation.timestamp] { + date = cached + } else { + date = try Self.date(from: observation.timestamp) + parsedDates[observation.timestamp] = date + } let fingerprint = Fingerprint(last: observation.last, total: observation.total) - let proposedIdentity = ObservationIdentity( - eventID: Self.nonEmpty(observation.eventID), - fingerprint: fingerprint) - let comparableIdentity = Self.comparableIdentity( - ownerID: ownerID, - acceptedByOwner: acceptedByFingerprint[fingerprint], - parentByOwner: parentByOwner) - let identity = accepted[proposedIdentity] == nil ? comparableIdentity ?? proposedIdentity : - proposedIdentity - if let existing = accepted[identity] { - if existing.date < date { continue } + if let existing = accepted[fingerprint] { + if existing.date < date { + continue + } if existing.date == date, !Self.shouldPreferModel(observation.model, over: existing.model) { continue } } - accepted[identity] = AcceptedObservation( + accepted[fingerprint] = AcceptedObservation( date: date, model: observation.model, last: observation.last) - if identity == proposedIdentity { - acceptedByFingerprint[fingerprint, default: [:]][ownerID] = identity - } } } acceptedObservationCount += accepted.count @@ -251,9 +252,10 @@ enum CodexLineageLedger { var primaryDocuments: [Document] = [] var containedDocuments: [Document] = [] var families: [FamilyDisposition] = [] + var validatedDates: [String: Date] = [:] for familyDocuments in documentsByFamily.values { try checkCancellation?() - let reasons = Self.containmentReasons(in: familyDocuments) + let reasons = Self.containmentReasons(in: familyDocuments, validatedDates: &validatedDates) let owners = Set(familyDocuments.map(\.ownerID)) let unresolved = familyDocuments.contains { document in guard let parent = Self.nonEmpty(document.parentSessionID) else { return false } @@ -284,7 +286,8 @@ enum CodexLineageLedger { primary: Self.reconcile( documents: primaryDocuments, localTimeZone: localTimeZone, - checkCancellation: checkCancellation), + checkCancellation: checkCancellation, + validatedDates: validatedDates), families: families, containedDocuments: containedDocuments) } @@ -294,15 +297,6 @@ 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 model: String @@ -333,59 +327,6 @@ enum CodexLineageLedger { UUID(uuidString: identity)?.uuidString.lowercased() ?? identity } - private static func physicalParents(_ documents: [Document]) -> [String: String] { - let physicalOwners = Set(documents.map { Self.scoped($0.ownerID, document: $0) }) - var result: [String: String] = [:] - for document in documents { - guard let parent = Self.nonEmpty(document.parentSessionID) else { continue } - let owner = Self.scoped(document.ownerID, document: document) - let parentIdentity = Self.scoped(parent, document: document) - guard physicalOwners.contains(parentIdentity), parentIdentity != owner else { continue } - result[owner] = parentIdentity - } - return result - } - - private static func comparableIdentity( - ownerID: String, - acceptedByOwner: [String: ObservationIdentity]?, - parentByOwner: [String: String]) -> ObservationIdentity? - { - guard let acceptedByOwner else { return nil } - var current: String? = ownerID - var visited: Set = [] - while let candidate = current, visited.insert(candidate).inserted { - if let identity = acceptedByOwner[candidate] { return identity } - current = parentByOwner[candidate] - } - return nil - } - - private static func parentsFirst(_ documents: [Document], parentByOwner: [String: String]) -> [Document] { - var depths: [String: Int] = [:] - func depth(_ owner: String) -> Int { - if let cached = depths[owner] { return cached } - var path: [String] = [] - var current = owner - var seen: Set = [] - while let parent = parentByOwner[current], seen.insert(current).inserted { - path.append(current) - current = parent - } - let base = depths[current] ?? 0 - for (offset, item) in path.reversed().enumerated() { - depths[item] = base + offset + 1 - } - depths[owner] = depths[owner] ?? base - return depths[owner] ?? 0 - } - return documents.sorted { lhs, rhs in - let lhsDepth = depth(Self.scoped(lhs.ownerID, document: lhs)) - let rhsDepth = depth(Self.scoped(rhs.ownerID, document: rhs)) - return lhsDepth == rhsDepth ? Self.documentComesBefore(lhs, rhs) : lhsDepth < rhsDepth - } - } - private static func documentComesBefore(_ lhs: Document, _ rhs: Document) -> Bool { let lhsKey = [ lhs.scopeID, @@ -419,14 +360,25 @@ enum CodexLineageLedger { ].joined(separator: "\u{0}") } - private static func containmentReasons(in documents: [Document]) -> Set { + private static func containmentReasons( + in documents: [Document], + validatedDates: inout [String: Date]) -> Set + { var reasons: Set = [] if documents.contains(where: { $0.incompleteObservationCount > 0 }) { reasons.insert(.incompleteObservation) } - if documents.flatMap(\.observations) - .contains(where: { CostUsageScanner.dateFromTimestamp($0.timestamp) == nil }) - { + var hasMalformedTimestamp = false + timestampValidation: for document in documents { + for observation in document.observations where validatedDates[observation.timestamp] == nil { + guard let date = CostUsageScanner.dateFromTimestamp(observation.timestamp) else { + hasMalformedTimestamp = true + break timestampValidation + } + validatedDates[observation.timestamp] = date + } + } + if hasMalformedTimestamp { reasons.insert(.malformedTimestamp) } let ownerGroups = Dictionary(grouping: documents, by: \.ownerID) diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageTwoPassDiscovery.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageTwoPassDiscovery.swift index af62925bfe..61b4a46368 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageTwoPassDiscovery.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageTwoPassDiscovery.swift @@ -105,19 +105,19 @@ enum CodexLineageTwoPassDiscovery { // Validate both sides of the parse. A post-parse signature alone can bless a // mixed read when a rollout is replaced while the parser has the file open. let initialSignature = try Self.signature(fileURL: fileURL, checkCancellation: checkCancellation) - let summary = try CostUsageScanner.parseCodexLineageDocumentSummary( + let parsed = try CostUsageScanner.parseCodexLineageDocumentSummaryWithSHA256( fileURL: fileURL, checkCancellation: checkCancellation) - let finalSignature = try Self.signature(fileURL: fileURL, checkCancellation: checkCancellation) + let finalSignature = try Self.signatureMetadata(fileURL: fileURL, contentSHA256: parsed.sha256) guard initialSignature == finalSignature else { throw DiscoveryError.fileChangedDuringScan } return Descriptor( fileURL: fileURL, - ownerID: summary.ownerID, - metadataSessionID: summary.metadataSessionID, - parentSessionID: summary.parentSessionID, - scopeID: summary.scopeID, - incompleteObservationCount: summary.incompleteObservationCount, - observationCount: summary.observationCount, + ownerID: parsed.summary.ownerID, + metadataSessionID: parsed.summary.metadataSessionID, + parentSessionID: parsed.summary.parentSessionID, + scopeID: parsed.summary.scopeID, + incompleteObservationCount: parsed.summary.incompleteObservationCount, + observationCount: parsed.summary.observationCount, signature: finalSignature) } @@ -125,16 +125,13 @@ enum CodexLineageTwoPassDiscovery { _ descriptor: Descriptor, checkCancellation: CostUsageScanner.CancellationCheck? = nil) throws -> CodexLineageLedger.Document { - guard try self.signature(fileURL: descriptor.fileURL, checkCancellation: checkCancellation) == descriptor - .signature - else { throw DiscoveryError.fileChangedDuringScan } - let document = try CostUsageScanner.parseCodexLineageDocument( + let parsed = try CostUsageScanner.parseCodexLineageDocumentWithSHA256( fileURL: descriptor.fileURL, checkCancellation: checkCancellation) - guard try Self.signature(fileURL: descriptor.fileURL, checkCancellation: checkCancellation) == descriptor + guard try Self.signatureMetadata(fileURL: descriptor.fileURL, contentSHA256: parsed.sha256) == descriptor .signature else { throw DiscoveryError.fileChangedDuringScan } - return document + return parsed.document } private static func signature( @@ -158,6 +155,14 @@ enum CodexLineageTwoPassDiscovery { contentSHA256: digest) } + private static func signatureMetadata(fileURL: URL, contentSHA256: String) throws -> FileSignature { + let values = try fileURL.resourceValues(forKeys: [.fileSizeKey, .contentModificationDateKey]) + return FileSignature( + size: Int64(values.fileSize ?? 0), + modifiedMilliseconds: Int64((values.contentModificationDate?.timeIntervalSince1970 ?? 0) * 1000), + contentSHA256: contentSHA256) + } + private struct ScopedIdentity: Equatable, Hashable { let scopeID: String let sessionID: String diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageJsonl.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageJsonl.swift index 35f58b446a..ec886847ac 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageJsonl.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageJsonl.swift @@ -31,6 +31,7 @@ enum CostUsageJsonl { maxLineBytes: Int, prefixBytes: Int, checkCancellation: (() throws -> Void)? = nil, + onChunk: ((Data) -> Void)? = nil, onLine: (Line) -> Void) throws -> Int64 { @@ -81,6 +82,7 @@ enum CostUsageJsonl { } try checkCancellation?() + onChunk?(chunk) bytesRead += Int64(chunk.count) chunk.withUnsafeBytes { rawBuffer in guard let base = rawBuffer.bindMemory(to: UInt8.self).baseAddress else { return } @@ -100,7 +102,9 @@ enum CostUsageJsonl { } return false } - if reachedEOF { break } + if reachedEOF { + break + } try checkCancellation?() } diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift index 896086ff22..0a97a50352 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift @@ -1706,8 +1706,10 @@ enum CostUsageScanner { private static func parseCodexTokenSnapshots( fileURL: URL, retainEvidence: Bool = true, + retainSnapshots: Bool = true, suppressScanErrors: Bool = true, - checkCancellation: CancellationCheck? = nil) throws -> CodexParsedTokenEvidence + checkCancellation: CancellationCheck? = nil, + onChunk: ((Data) -> Void)? = nil) throws -> CodexParsedTokenEvidence { var sessionId: String? var forkedFromId: String? @@ -1743,7 +1745,7 @@ enum CostUsageScanner { if last == nil || total == nil { incompleteObservationCount += 1 } - if retainEvidence { + if retainEvidence, retainSnapshots { let counted = accumulator.apply(last: last, total: total) snapshots.append(CodexTimestampedTotals( timestamp: timestamp, @@ -1776,6 +1778,7 @@ enum CostUsageScanner { maxLineBytes: 512 * 1024, prefixBytes: 512 * 1024, checkCancellation: checkCancellation, + onChunk: onChunk, onLine: { line in guard !line.bytes.isEmpty else { return } if line.wasTruncated { @@ -1911,6 +1914,7 @@ enum CostUsageScanner { { let parsed = try Self.parseCodexTokenSnapshots( fileURL: fileURL, + retainSnapshots: false, suppressScanErrors: false, checkCancellation: checkCancellation) return CodexLineageLedger.Document( @@ -1922,6 +1926,27 @@ enum CostUsageScanner { incompleteObservationCount: parsed.incompleteObservationCount) } + static func parseCodexLineageDocumentWithSHA256( + fileURL: URL, + checkCancellation: CancellationCheck? = nil) throws -> (document: CodexLineageLedger.Document, sha256: String) + { + var hasher = SHA256() + let parsed = try Self.parseCodexTokenSnapshots( + fileURL: fileURL, + retainSnapshots: false, + suppressScanErrors: false, + checkCancellation: checkCancellation, + onChunk: { hasher.update(data: $0) }) + let document = CodexLineageLedger.Document( + ownerID: Self.codexRolloutOwnerID(fileURL: fileURL) ?? parsed.sessionId ?? fileURL.standardizedFileURL.path, + metadataSessionID: parsed.sessionId, + parentSessionID: parsed.forkedFromId, + observations: parsed.observations, + scopeID: Self.codexLineageScopeID(fileURL: fileURL), + incompleteObservationCount: parsed.incompleteObservationCount) + return (document, Self.hexDigest(hasher.finalize())) + } + static func parseCodexLineageDocumentSummary( fileURL: URL, checkCancellation: CancellationCheck? = nil) throws -> CodexLineageDocumentSummary @@ -1940,6 +1965,31 @@ enum CostUsageScanner { observationCount: parsed.observationCount) } + static func parseCodexLineageDocumentSummaryWithSHA256( + fileURL: URL, + checkCancellation: CancellationCheck? = nil) throws -> (summary: CodexLineageDocumentSummary, sha256: String) + { + var hasher = SHA256() + let parsed = try Self.parseCodexTokenSnapshots( + fileURL: fileURL, + retainEvidence: false, + suppressScanErrors: false, + checkCancellation: checkCancellation, + onChunk: { hasher.update(data: $0) }) + let summary = CodexLineageDocumentSummary( + ownerID: Self.codexRolloutOwnerID(fileURL: fileURL) ?? parsed.sessionId ?? fileURL.standardizedFileURL.path, + metadataSessionID: parsed.sessionId, + parentSessionID: parsed.forkedFromId, + scopeID: Self.codexLineageScopeID(fileURL: fileURL), + incompleteObservationCount: parsed.incompleteObservationCount, + observationCount: parsed.observationCount) + return (summary, Self.hexDigest(hasher.finalize())) + } + + private static func hexDigest(_ digest: some Sequence) -> String { + digest.map { String(format: "%02x", $0) }.joined() + } + static func codexLineageScopeID(fileURL: URL) -> String { let standardized = fileURL.standardizedFileURL let components = standardized.pathComponents diff --git a/Tests/CodexBarTests/CodexLineageEngineTests.swift b/Tests/CodexBarTests/CodexLineageEngineTests.swift index 2ad505de2e..5a8ebe6c39 100644 --- a/Tests/CodexBarTests/CodexLineageEngineTests.swift +++ b/Tests/CodexBarTests/CodexLineageEngineTests.swift @@ -156,6 +156,26 @@ struct CodexLineageEngineTests { #expect(result.diagnostics.peakAcceptedFingerprintCount <= 2000) } + @Test(.timeLimit(.minutes(2))) + func `duplicate heavy multi million observation family keeps one accepted fingerprint`() throws { + let repeated = Array(repeating: Self.observation(input: 1, total: 1), count: 100_000) + let documents = (0..<20).map { index in + Self.document( + owner: "owner-\(index)", + parent: index == 0 ? nil : "owner-0", + observations: repeated) + } + let families = try CodexLineageEngine.prepareFamilies(documents: documents) + + let result = try CodexLineageEngine.reconcile(families: families, localTimeZone: .gmt) + + #expect(result.diagnostics.observationCount == 2_000_000) + #expect(result.diagnostics.peakFamilyObservationCount == 2_000_000) + #expect(result.diagnostics.peakAcceptedFingerprintCount == 1) + #expect(result.report.acceptedObservationCount == 1) + #expect(result.report.duplicateObservationCount == 1_999_999) + } + @Test func `cancellation propagates during preparation and reconciliation without a candidate`() throws { let documents = (0..<20).map { index in diff --git a/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift b/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift index 7c6c02feb8..85ef010101 100644 --- a/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift +++ b/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift @@ -182,13 +182,17 @@ struct CodexLineageLocalValidationTests { let lineageStarted = ContinuousClock.now Self.progress("discovery-start") + let discoveryStarted = ContinuousClock.now let discovery = try CodexLineageTwoPassDiscovery.discover( includedFiles: included, roots: [sessions, archived]) + let discoveryDuration = discoveryStarted.duration(to: .now) Self.progress("discovery-ready") + let preparationStarted = ContinuousClock.now let families = try CodexLineageEngine.prepareDescriptorFamilies( descriptors: discovery.descriptors, unresolvedParents: discovery.unresolvedParents) + let preparationDuration = preparationStarted.duration(to: .now) Self.progress("families-ready") let lineage = try CodexLineageEngine.reconcileStreaming(families: families, localTimeZone: .gmt) Self.progress("lineage-ready") @@ -307,6 +311,11 @@ struct CodexLineageLocalValidationTests { "performance": [ "legacyMilliseconds": Self.milliseconds(legacyDuration), "lineageMilliseconds": Self.milliseconds(lineageDuration), + "discoveryMilliseconds": Self.milliseconds(discoveryDuration), + "preparationMilliseconds": Self.milliseconds(preparationDuration), + "documentLoadMilliseconds": lineage.diagnostics.documentLoadMilliseconds, + "familyReconciliationMilliseconds": lineage.diagnostics.familyReconciliationMilliseconds, + "compositionMilliseconds": lineage.diagnostics.compositionMilliseconds, ], "ordinaryDayDivergenceCount": ordinaryDivergenceCount, "resetEpochDiagnostics": [ From ae3c19ecd74813f65805571efc7bd27a14805334 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Mon, 13 Jul 2026 20:36:21 -0400 Subject: [PATCH 22/28] Stabilize lineage validation compilation --- .../CodexLineageLocalValidationTests.swift | 131 +++++++++--------- 1 file changed, 68 insertions(+), 63 deletions(-) diff --git a/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift b/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift index 85ef010101..a81a454426 100644 --- a/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift +++ b/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift @@ -272,71 +272,76 @@ struct CodexLineageLocalValidationTests { return abs((selectedTokens[day] ?? 0) - legacyTokens) > Int(Double(legacyTokens) * 0.01) } + let dayRows: [[String: Any]] = Self.referenceDays.map { day in + let result = classification.days.first { $0.day == day } + return [ + "day": day, + "legacy": legacy[day] ?? 0, + "primary": primary[day] ?? 0, + "contained": contained[day] ?? 0, + "selected": selectedTokens[day] ?? 0, + "shadow": directReports[CodexLineageAccountingMode.shadow.rawValue]?[day] ?? 0, + "scannerLineage": directReports[CodexLineageAccountingMode.lineage.rawValue]?[day] ?? 0, + "reference": references[day]?.tokens ?? 0, + "finalized": references[day]?.finalized ?? false, + "classification": result?.classification.rawValue ?? "missing", + ] as [String: Any] + } + let ordinaryDayRows: [[String: Any]] = Self.ordinaryDays.map { day in + [ + "day": day, + "legacy": legacy[day] ?? 0, + "selected": selectedTokens[day] ?? 0, + ] as [String: Any] + } + let coverage: [String: Any] = [ + "documents": discovery.descriptors.count, + "families": lineage.diagnostics.familyCount, + "containedFamilies": containedFamilyCount, + "selectableContainedFamilies": selectableContainedFamilies.count, + "containedObservations": containedObservationCount, + "containmentReasons": containmentReasons, + "referencedParents": discovery.referencedParentDocumentCount, + "unresolvedParents": discovery.unresolvedParents.count, + "observations": lineage.diagnostics.observationCount, + "peakFamilyObservations": lineage.diagnostics.peakFamilyObservationCount, + "duplicateObservations": lineage.report.duplicateObservationCount, + ] + let performance: [String: Any] = [ + "legacyMilliseconds": Self.milliseconds(legacyDuration), + "lineageMilliseconds": Self.milliseconds(lineageDuration), + "discoveryMilliseconds": Self.milliseconds(discoveryDuration), + "preparationMilliseconds": Self.milliseconds(preparationDuration), + "documentLoadMilliseconds": lineage.diagnostics.documentLoadMilliseconds, + "familyReconciliationMilliseconds": lineage.diagnostics.familyReconciliationMilliseconds, + "compositionMilliseconds": lineage.diagnostics.compositionMilliseconds, + ] + let resetDiagnostics: [String: Any] = [ + "strongResetBoundaries": resetEpochDiagnostics?.strongResetBoundaryCount ?? 0, + "mixedRegressions": resetEpochDiagnostics?.mixedRegressionCount ?? 0, + "postResetRepeatedFingerprints": resetEpochDiagnostics?.postResetRepeatedFingerprintCount ?? 0, + "sameOwnerRepeats": resetEpochDiagnostics?.sameOwnerRepeatCount ?? 0, + "crossOwnerRepeats": resetEpochDiagnostics?.crossOwnerRepeatCount ?? 0, + "estimatedSuppressedTokens": resetEpochDiagnostics.map { + $0.estimatedSuppressed.input + $0.estimatedSuppressed.output + } ?? 0, + "estimatedSuppressedUTC": resetEpochDiagnostics?.estimatedSuppressedUTC.mapValues { + $0.input + $0.output + } ?? [:], + "sameOwnerEstimatedSuppressedTokens": resetEpochDiagnostics.map { + $0.sameOwnerEstimatedSuppressed.input + $0.sameOwnerEstimatedSuppressed.output + } ?? 0, + "sameOwnerEstimatedSuppressedUTC": resetEpochDiagnostics?.sameOwnerEstimatedSuppressedUTC.mapValues { + $0.input + $0.output + } ?? [:], + ] let output: [String: Any] = [ - "days": Self.referenceDays.map { day in - let result = classification.days.first { $0.day == day } - return [ - "day": day, - "legacy": legacy[day] ?? 0, - "primary": primary[day] ?? 0, - "contained": contained[day] ?? 0, - "selected": selectedTokens[day] ?? 0, - "shadow": directReports[CodexLineageAccountingMode.shadow.rawValue]?[day] ?? 0, - "scannerLineage": directReports[CodexLineageAccountingMode.lineage.rawValue]?[day] ?? 0, - "reference": references[day]?.tokens ?? 0, - "finalized": references[day]?.finalized ?? false, - "classification": result?.classification.rawValue ?? "missing", - ] as [String: Any] - }, - "ordinaryDays": Self.ordinaryDays.map { day in - [ - "day": day, - "legacy": legacy[day] ?? 0, - "selected": selectedTokens[day] ?? 0, - ] as [String: Any] - }, - "coverage": [ - "documents": discovery.descriptors.count, - "families": lineage.diagnostics.familyCount, - "containedFamilies": containedFamilyCount, - "selectableContainedFamilies": selectableContainedFamilies.count, - "containedObservations": containedObservationCount, - "containmentReasons": containmentReasons, - "referencedParents": discovery.referencedParentDocumentCount, - "unresolvedParents": discovery.unresolvedParents.count, - "observations": lineage.diagnostics.observationCount, - "peakFamilyObservations": lineage.diagnostics.peakFamilyObservationCount, - "duplicateObservations": lineage.report.duplicateObservationCount, - ], - "performance": [ - "legacyMilliseconds": Self.milliseconds(legacyDuration), - "lineageMilliseconds": Self.milliseconds(lineageDuration), - "discoveryMilliseconds": Self.milliseconds(discoveryDuration), - "preparationMilliseconds": Self.milliseconds(preparationDuration), - "documentLoadMilliseconds": lineage.diagnostics.documentLoadMilliseconds, - "familyReconciliationMilliseconds": lineage.diagnostics.familyReconciliationMilliseconds, - "compositionMilliseconds": lineage.diagnostics.compositionMilliseconds, - ], + "days": dayRows, + "ordinaryDays": ordinaryDayRows, + "coverage": coverage, + "performance": performance, "ordinaryDayDivergenceCount": ordinaryDivergenceCount, - "resetEpochDiagnostics": [ - "strongResetBoundaries": resetEpochDiagnostics?.strongResetBoundaryCount ?? 0, - "mixedRegressions": resetEpochDiagnostics?.mixedRegressionCount ?? 0, - "postResetRepeatedFingerprints": resetEpochDiagnostics?.postResetRepeatedFingerprintCount ?? 0, - "sameOwnerRepeats": resetEpochDiagnostics?.sameOwnerRepeatCount ?? 0, - "crossOwnerRepeats": resetEpochDiagnostics?.crossOwnerRepeatCount ?? 0, - "estimatedSuppressedTokens": resetEpochDiagnostics.map { - $0.estimatedSuppressed.input + $0.estimatedSuppressed.output - } ?? 0, - "estimatedSuppressedUTC": resetEpochDiagnostics?.estimatedSuppressedUTC.mapValues { - $0.input + $0.output - } ?? [:], - "sameOwnerEstimatedSuppressedTokens": resetEpochDiagnostics.map { - $0.sameOwnerEstimatedSuppressed.input + $0.sameOwnerEstimatedSuppressed.output - } ?? 0, - "sameOwnerEstimatedSuppressedUTC": resetEpochDiagnostics?.sameOwnerEstimatedSuppressedUTC.mapValues { - $0.input + $0.output - } ?? [:], - ], + "resetEpochDiagnostics": resetDiagnostics, "aggregateImproved": classification.improvesAggregateError, // This replay is one validation artifact, not permission to remove the rollback path. "supportsLegacyRemoval": false, From d10f38b671b0284897e183d070e09c6d1e08861d Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Tue, 14 Jul 2026 00:32:26 -0400 Subject: [PATCH 23/28] Address lineage accounting review findings --- .../Generated/CodexParserHash.generated.swift | 2 +- .../Providers/Codex/CodexLineageLedger.swift | 107 +++++++++++++++++- .../CodexLineagePromotionEvaluator.swift | 2 + .../CodexLineageLedgerTests.swift | 102 +++++++++++++++++ .../CodexLineageLocalValidationTests.swift | 35 +++++- .../CodexLineageTwoPassDiscoveryTests.swift | 29 ++++- 6 files changed, 269 insertions(+), 8 deletions(-) diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index cc3fe322ff..82a5048006 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 = "f0c3db302ce7c1bb" + static let value = "a32ce3f29207c1d7" } diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift index 98c93a0910..930777e2dc 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift @@ -21,12 +21,22 @@ enum CodexLineageLedger { } struct Observation: Equatable, Sendable { + /// Copy-stable identity derived from the logical turn and the token event's ordinal in that turn. + /// Physical rollout copies preserve this value; independent turns do not share it. + let eventID: String? let timestamp: String let model: String let last: Totals let total: Totals - init(timestamp: String, model: String = CostUsagePricing.codexUnattributedModel, 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 @@ -171,8 +181,11 @@ enum CodexLineageLedger { var acceptedObservationCount = 0 for componentDocuments in documentsByComponent.values { try checkCancellation?() - var accepted: [Fingerprint: AcceptedObservation] = [:] - for document in componentDocuments { + let parentByOwner = Self.physicalParents(componentDocuments) + var accepted: [ObservationIdentity: AcceptedObservation] = [:] + var acceptedByFingerprint: [Fingerprint: [String: ObservationIdentity]] = [:] + for document in Self.parentsFirst(componentDocuments, parentByOwner: parentByOwner) { + let ownerID = Self.scoped(document.ownerID, document: document) for observation in document.observations { try checkCancellation?() physicalObservationCount += 1 @@ -184,7 +197,16 @@ enum CodexLineageLedger { parsedDates[observation.timestamp] = date } let fingerprint = Fingerprint(last: observation.last, total: observation.total) - if let existing = accepted[fingerprint] { + let proposedIdentity = ObservationIdentity( + eventID: Self.nonEmpty(observation.eventID), + fingerprint: fingerprint) + let comparableIdentity = Self.comparableIdentity( + ownerID: ownerID, + acceptedByOwner: acceptedByFingerprint[fingerprint], + parentByOwner: parentByOwner) + let identity = accepted[proposedIdentity] == nil ? comparableIdentity ?? proposedIdentity : + proposedIdentity + if let existing = accepted[identity] { if existing.date < date { continue } @@ -194,10 +216,13 @@ enum CodexLineageLedger { continue } } - accepted[fingerprint] = AcceptedObservation( + accepted[identity] = AcceptedObservation( date: date, model: observation.model, last: observation.last) + if identity == proposedIdentity { + acceptedByFingerprint[fingerprint, default: [:]][ownerID] = identity + } } } acceptedObservationCount += accepted.count @@ -297,6 +322,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 model: String @@ -327,6 +361,69 @@ enum CodexLineageLedger { UUID(uuidString: identity)?.uuidString.lowercased() ?? identity } + private static func physicalParents(_ documents: [Document]) -> [String: String] { + var physicalOwners: Set = [] + for document in documents { + let owner = Self.scoped(document.ownerID, document: document) + physicalOwners.insert(owner) + } + var result: [String: String] = [:] + for document in documents { + guard let parent = Self.nonEmpty(document.parentSessionID) else { continue } + let owner = Self.scoped(document.ownerID, document: document) + let parentIdentity = Self.scoped(parent, document: document) + guard physicalOwners.contains(parentIdentity), parentIdentity != owner else { continue } + result[owner] = parentIdentity + } + return result + } + + private static func comparableIdentity( + ownerID: String, + acceptedByOwner: [String: ObservationIdentity]?, + parentByOwner: [String: String]) -> ObservationIdentity? + { + guard let acceptedByOwner else { return nil } + var current: String? = ownerID + var visited: Set = [] + while let candidate = current, visited.insert(candidate).inserted { + if let identity = acceptedByOwner[candidate] { + return identity + } + current = parentByOwner[candidate] + } + return nil + } + + private static func parentsFirst( + _ documents: [Document], + parentByOwner: [String: String]) -> [Document] + { + var depths: [String: Int] = [:] + func depth(_ owner: String) -> Int { + if let cached = depths[owner] { return cached } + var path: [String] = [] + var current = owner + var seen: Set = [] + while let parent = parentByOwner[current], seen.insert(current).inserted { + path.append(current) + current = parent + } + let base = depths[current] ?? 0 + for (offset, item) in path.reversed().enumerated() { + depths[item] = base + offset + 1 + } + depths[owner] = depths[owner] ?? base + return depths[owner] ?? 0 + } + return documents.sorted { lhs, rhs in + let lhsDepth = depth(Self.scoped(lhs.ownerID, document: lhs)) + let rhsDepth = depth(Self.scoped(rhs.ownerID, document: rhs)) + if lhsDepth != rhsDepth { return lhsDepth < rhsDepth } + return Self.documentComesBefore(lhs, rhs) + } + } + private static func documentComesBefore(_ lhs: Document, _ rhs: Document) -> Bool { let lhsKey = [ lhs.scopeID, diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineagePromotionEvaluator.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineagePromotionEvaluator.swift index d135ed43c0..b4e1777d44 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineagePromotionEvaluator.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineagePromotionEvaluator.swift @@ -5,6 +5,7 @@ import Foundation /// This evaluator does not choose token totals or tune an error percentage. It verifies that the /// independently collected correctness and operational evidence is complete enough to promote. enum CodexLineagePromotionEvaluator { + /// Opaque proof that the full promotion contract passed. Only `evaluate` can create one. struct Authorization: Equatable, Sendable { fileprivate init() {} } @@ -85,6 +86,7 @@ enum CodexLineagePromotionEvaluator { let keepsFamilyContainment: Bool /// Legacy remains a whole-scan emergency authority until its dedicated removal work. let keepsLegacyEmergencyRollback: Bool + let authorization: Authorization? fileprivate init( diff --git a/Tests/CodexBarTests/CodexLineageLedgerTests.swift b/Tests/CodexBarTests/CodexLineageLedgerTests.swift index f6a80c9af2..2c30d222d2 100644 --- a/Tests/CodexBarTests/CodexLineageLedgerTests.swift +++ b/Tests/CodexBarTests/CodexLineageLedgerTests.swift @@ -13,6 +13,7 @@ struct CodexLineageLedgerTests { let contents = [ #"{"type":"session_meta","payload":{"id":"metadata-id","forked_from_id":"parent-id"}}"#, #"{"type":"turn_context","payload":{"model":"gpt-5.4"}}"#, + #"{"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), @@ -35,6 +36,7 @@ struct CodexLineageLedgerTests { #expect(document.incompleteObservationCount == 1) #expect(document.scopeID == environment.root.path) #expect(document.observations[0].model == "gpt-5.4") + #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)) } @@ -188,6 +190,83 @@ struct CodexLineageLedgerTests { #expect(report.duplicateObservationCount == 3) } + @Test + func `copy stable event identity preserves independent identical 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: []), + Self.document(owner: "child-a", parent: "root", observations: [copied]), + Self.document(owner: "child-b", 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 `equal states across ancestor and descendant remain one logical observation`() throws { + let ancestor = Self.observation( + eventID: "turn-a:0", + timestamp: "2026-07-09T12:00:00Z", + input: 100, + totalInput: 100) + let descendantReemission = Self.observation( + eventID: "turn-b:0", + timestamp: "2026-07-09T12:01:00Z", + input: 100, + totalInput: 100) + let report = try CodexLineageLedger.reconcile( + documents: [ + Self.document(owner: "root", observations: [ancestor]), + Self.document(owner: "child", parent: "root", observations: [descendantReemission]), + ], + localTimeZone: .gmt) + + #expect(report.utcDays["2026-07-09"]?.input == 100) + #expect(report.acceptedObservationCount == 1) + #expect(report.duplicateObservationCount == 1) + } + + @Test + func `retained ancestor metadata still uses the physical parent for state deduplication`() throws { + let ancestor = Self.observation( + eventID: "turn-a:0", + timestamp: "2026-07-09T12:00:00Z", + input: 100, + totalInput: 100) + let descendantReemission = Self.observation( + eventID: "turn-b:0", + timestamp: "2026-07-09T12:01:00Z", + input: 100, + totalInput: 100) + let report = try CodexLineageLedger.reconcile( + documents: [ + Self.document(owner: "root", observations: [ancestor]), + Self.document( + owner: "child", + metadata: "root", + parent: "root", + observations: [descendantReemission]), + ], + localTimeZone: .gmt) + + #expect(report.utcDays["2026-07-09"]?.input == 100) + #expect(report.acceptedObservationCount == 1) + #expect(report.duplicateObservationCount == 1) + } + @Test func `equal observations in disconnected lineages remain additive`() throws { let observation = Self.observation(timestamp: "2026-07-09T12:00:00Z", input: 100, totalInput: 100) @@ -216,6 +295,27 @@ struct CodexLineageLedgerTests { #expect(report.duplicateObservationCount == 2) } + @Test + func `distinct event identities do not revive unchanged states within one owner`() throws { + let first = Self.observation( + eventID: "turn-a:0", + timestamp: "2026-07-09T12:00:00Z", + input: 100, + totalInput: 100) + let reemission = Self.observation( + eventID: "turn-b:0", + timestamp: "2026-07-09T12:01:00Z", + input: 100, + totalInput: 100) + let report = try CodexLineageLedger.reconcile( + documents: [Self.document(owner: "root", observations: [first, reemission])], + 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( @@ -532,6 +632,7 @@ struct CodexLineageLedgerTests { } private static func observation( + eventID: String? = nil, timestamp: String, model: String = CostUsagePricing.codexUnattributedModel, input: Int, @@ -540,6 +641,7 @@ struct CodexLineageLedgerTests { totalInput: Int) -> CodexLineageLedger.Observation { .init( + eventID: eventID, timestamp: timestamp, model: model, last: .init(input: input, cached: cached, output: output), diff --git a/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift b/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift index a81a454426..ff3d3588a8 100644 --- a/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift +++ b/Tests/CodexBarTests/CodexLineageLocalValidationTests.swift @@ -234,8 +234,9 @@ struct CodexLineageLocalValidationTests { // would make the replay look better by silently dropping a family. throw CodexLineageAccountingSelector.SelectionError.missingContainedFamilyEvidence } - let selected = CodexLineageAccountingSelector.select( + let selected = try CodexLineageAccountingSelector.select( mode: .lineage, + authorization: Self.promotionAuthorization(), legacyDays: legacyCache.days, primaryRows: lineage.report.utcRows, containedFamilies: selectableContainedFamilies) @@ -493,6 +494,38 @@ struct CodexLineageLocalValidationTests { days.mapValues { models in models.values.reduce(0) { $0 + ($1[safe: 0] ?? 0) + ($1[safe: 2] ?? 0) } } } + private static func promotionAuthorization() throws -> CodexLineagePromotionEvaluator.Authorization { + let sample = CodexLineageResidualClassifier.Sample( + day: "validation", + referenceTokens: 1000, + isReferenceFinalized: true, + isOrdinaryDay: false, + legacyTokens: 500, + ledgerUTCTokens: 950, + ledgerLocalTokens: 950, + evidence: .init(localCorpusWasExhaustive: true, duplicateObservationCount: 1)) + let decision = CodexLineagePromotionEvaluator.evaluate(.init( + residualReport: CodexLineageResidualClassifier.classify(samples: [sample]), + targetDays: ["validation"], + reviewedResidualDays: ["validation"], + adversarialGoldensPassed: true, + boundedDiscoveryPassed: true, + familyRouting: .init( + primaryFamilyCount: 1, + containedFamilyCount: 0, + doubleContributionFamilyCount: 0, + permanentContainmentSupported: true), + performance: .init( + coldMeasured: true, + warmMeasured: true, + memoryBoundMeasured: true, + hasMaterialRegression: false), + cancellationStages: Set(CodexLineagePromotionEvaluator.CancellationStage.allCases), + atomicPublicationPassed: true, + rollback: .init(legacyWholeScanAvailable: true, rollbackPathVerified: true))) + return try #require(decision.authorization) + } + private static func milliseconds(_ duration: Duration) -> Int64 { let components = duration.components return components.seconds * 1000 + Int64(components.attoseconds / 1_000_000_000_000_000) diff --git a/Tests/CodexBarTests/CodexLineageTwoPassDiscoveryTests.swift b/Tests/CodexBarTests/CodexLineageTwoPassDiscoveryTests.swift index 11a750c2bf..916eb0c674 100644 --- a/Tests/CodexBarTests/CodexLineageTwoPassDiscoveryTests.swift +++ b/Tests/CodexBarTests/CodexLineageTwoPassDiscoveryTests.swift @@ -30,6 +30,32 @@ struct CodexLineageTwoPassDiscoveryTests { #expect(report.peakRetainedObservationCount == 0) } + @Test + func `retained ancestor metadata does not suppress the physical parent`() throws { + let environment = try CostUsageTestEnvironment() + defer { environment.cleanup() } + let parentID = Self.uuid(10) + let parent = try Self.writeRollout( + root: environment.codexArchivedSessionsRoot, + ownerID: parentID, + observations: 1) + let child = try Self.writeRollout( + root: environment.codexSessionsRoot, + ownerID: Self.uuid(11), + metadataID: parentID, + parentID: parentID, + observations: 1) + + let report = try CodexLineageTwoPassDiscovery.discover( + includedFiles: [child], + roots: [environment.codexSessionsRoot, environment.codexArchivedSessionsRoot]) + + #expect(report.descriptors.map(\.fileURL).map(\.standardizedFileURL.path) + .contains(parent.standardizedFileURL.path)) + #expect(report.referencedParentDocumentCount == 1) + #expect(report.unresolvedParents.isEmpty) + } + @Test func `streaming reconciliation loads one family at a time and warm reuse loads none`() throws { let environment = try CostUsageTestEnvironment() @@ -120,12 +146,13 @@ struct CodexLineageTwoPassDiscoveryTests { private static func writeRollout( root: URL, ownerID: String, + metadataID: String? = nil, parentID: String? = nil, observations: Int) throws -> URL { try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true) let file = root.appendingPathComponent("rollout-2026-07-09T00-00-00-\(ownerID).jsonl") - var lines = [#"{"type":"session_meta","payload":{"id":"\#(ownerID)""# + var lines = [#"{"type":"session_meta","payload":{"id":"\#(metadataID ?? ownerID)""# + (parentID.map { #", "forked_from_id":"\#($0)""# } ?? "") + "}}"] lines += (0.. Date: Tue, 14 Jul 2026 00:49:35 -0400 Subject: [PATCH 24/28] 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 82a5048006..5c1f8a98ee 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 = "a32ce3f29207c1d7" + static let value = "f8f3d8a95952b023" } From 5be1a7818fdf2503eb300ae3440e9318e3fb3e62 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Tue, 14 Jul 2026 09:12:16 -0400 Subject: [PATCH 25/28] Bound lineage performance tests --- Tests/CodexBarTests/CodexLineageEngineTests.swift | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Tests/CodexBarTests/CodexLineageEngineTests.swift b/Tests/CodexBarTests/CodexLineageEngineTests.swift index 5a8ebe6c39..29616daa53 100644 --- a/Tests/CodexBarTests/CodexLineageEngineTests.swift +++ b/Tests/CodexBarTests/CodexLineageEngineTests.swift @@ -156,10 +156,11 @@ struct CodexLineageEngineTests { #expect(result.diagnostics.peakAcceptedFingerprintCount <= 2000) } - @Test(.timeLimit(.minutes(2))) - func `duplicate heavy multi million observation family keeps one accepted fingerprint`() throws { + @Test(.timeLimit(.minutes(10))) + func `duplicate heavy observation family keeps one accepted fingerprint`() throws { + let documentCount = ProcessInfo.processInfo.environment["CODEXBAR_EXTENDED_PERFORMANCE_TESTS"] == "1" ? 20 : 1 let repeated = Array(repeating: Self.observation(input: 1, total: 1), count: 100_000) - let documents = (0..<20).map { index in + let documents = (0.. Date: Tue, 14 Jul 2026 10:22:09 -0400 Subject: [PATCH 26/28] Avoid unused lineage observations --- .../Generated/CodexParserHash.generated.swift | 2 +- .../Vendored/CostUsage/CostUsageScanner.swift | 19 +++++++++++++-- .../CodexLineageLedgerTests.swift | 24 +++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index 5c1f8a98ee..6dca2c73af 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 = "f8f3d8a95952b023" + static let value = "ecbcccef8abf2e9a" } diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift index 0a97a50352..2a5f736ec5 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift @@ -1706,6 +1706,7 @@ enum CostUsageScanner { private static func parseCodexTokenSnapshots( fileURL: URL, retainEvidence: Bool = true, + collectLineageObservations: Bool = false, retainSnapshots: Bool = true, suppressScanErrors: Bool = true, checkCancellation: CancellationCheck? = nil, @@ -1752,14 +1753,14 @@ enum CostUsageScanner { date: parsedSnapshotDate(timestamp: timestamp), totals: counted)) } - let eventID = retainEvidence ? turnID.map { turnID in + let eventID = collectLineageObservations ? turnID.map { turnID in let ordinal = tokenEventCountByTurn[turnID, default: 0] tokenEventCountByTurn[turnID] = ordinal + 1 return "\(turnID):\(ordinal)" } : nil if let last, let total { observationCount += 1 - if retainEvidence { + if collectLineageObservations { observations.append(CodexLineageLedger.Observation( eventID: eventID, timestamp: timestamp, @@ -1914,6 +1915,8 @@ enum CostUsageScanner { { let parsed = try Self.parseCodexTokenSnapshots( fileURL: fileURL, + retainEvidence: true, + collectLineageObservations: true, retainSnapshots: false, suppressScanErrors: false, checkCancellation: checkCancellation) @@ -1933,6 +1936,8 @@ enum CostUsageScanner { var hasher = SHA256() let parsed = try Self.parseCodexTokenSnapshots( fileURL: fileURL, + retainEvidence: true, + collectLineageObservations: true, retainSnapshots: false, suppressScanErrors: false, checkCancellation: checkCancellation, @@ -1986,6 +1991,16 @@ enum CostUsageScanner { return (summary, Self.hexDigest(hasher.finalize())) } + 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 hexDigest(_ digest: some Sequence) -> String { digest.map { String(format: "%02x", $0) }.joined() } diff --git a/Tests/CodexBarTests/CodexLineageLedgerTests.swift b/Tests/CodexBarTests/CodexLineageLedgerTests.swift index 2c30d222d2..621f1348a5 100644 --- a/Tests/CodexBarTests/CodexLineageLedgerTests.swift +++ b/Tests/CodexBarTests/CodexLineageLedgerTests.swift @@ -41,6 +41,30 @@ 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") + try Self.tokenCountLine( + timestamp: "2026-07-09T12:00:00Z", + last: (input: 100, cached: 40, output: 10), + total: (input: 100, cached: 40, output: 10)) + .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 == 1) + #expect(snapshotsOnly.observations == 0) + #expect(lineage.snapshots == 1) + #expect(lineage.observations == 1) + } + @Test func `daily rows preserve model token and pricing dimensions`() throws { let priced = Self.observation( From c36f3411c57fead4b44d777e89a89c82f520edbb Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Tue, 14 Jul 2026 12:03:19 -0400 Subject: [PATCH 27/28] Propagate lineage accounting safeguards --- .../Generated/CodexParserHash.generated.swift | 2 +- .../Providers/Codex/CodexLineageLedger.swift | 4 +- .../Codex/CodexLineageTwoPassDiscovery.swift | 22 +++++- .../Vendored/CostUsage/CostUsageScanner.swift | 37 ++++++--- .../CodexLineageAccountingSelectorTests.swift | 77 +++++++++++++++++++ .../CodexLineageLedgerTests.swift | 15 ++++ .../CodexLineageTwoPassDiscoveryTests.swift | 26 +++++++ 7 files changed, 167 insertions(+), 16 deletions(-) diff --git a/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift b/Sources/CodexBarCore/Generated/CodexParserHash.generated.swift index 6dca2c73af..3311b5de03 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 = "ecbcccef8abf2e9a" + static let value = "12e1cfe53eca3869" } diff --git a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift index 930777e2dc..ef03851b4d 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageLedger.swift @@ -323,11 +323,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/Providers/Codex/CodexLineageTwoPassDiscovery.swift b/Sources/CodexBarCore/Providers/Codex/CodexLineageTwoPassDiscovery.swift index 61b4a46368..6d7d4ee735 100644 --- a/Sources/CodexBarCore/Providers/Codex/CodexLineageTwoPassDiscovery.swift +++ b/Sources/CodexBarCore/Providers/Codex/CodexLineageTwoPassDiscovery.swift @@ -80,8 +80,10 @@ enum CodexLineageTwoPassDiscovery { try checkCancellation?() guard seenPaths.insert(fileURL.standardizedFileURL.path).inserted else { continue } let descriptor = try Self.describe(fileURL: fileURL, checkCancellation: checkCancellation) - let owner = Self.canonical(descriptor.ownerID) - guard owner == identity.sessionID else { continue } + let identities = [descriptor.ownerID, descriptor.metadataSessionID] + .compactMap(Self.nonEmpty) + .map(Self.canonical) + guard identities.contains(identity.sessionID) else { continue } remember(descriptor) referencedParents += 1 found = true @@ -179,6 +181,13 @@ enum CodexLineageTwoPassDiscovery { try self.index() } guard let matches = self.filesByIdentity[identity], !matches.isEmpty else { return nil } + let exactOwnerMatches = matches.filter { + CostUsageScanner.codexRolloutOwnerID(fileURL: $0) + .map(CodexLineageTwoPassDiscovery.canonical) == identity.sessionID + } + if !exactOwnerMatches.isEmpty { + return exactOwnerMatches.sorted { $0.path < $1.path } + } let owners = Set(matches.compactMap(CostUsageScanner.codexRolloutOwnerID(fileURL:))) guard matches.count == 1 || owners.count == 1 else { return nil } return matches.sorted { $0.path < $1.path } @@ -203,6 +212,15 @@ enum CodexLineageTwoPassDiscovery { default: [], ].insert(fileURL) } + if let sessionID = try CostUsageScanner.parseCodexSessionIdentifier( + fileURL: fileURL, + checkCancellation: self.checkCancellation) + { + self.filesByIdentity[ + .init(scopeID: scopeID, sessionID: CodexLineageTwoPassDiscovery.canonical(sessionID)), + default: [], + ].insert(fileURL) + } } } } diff --git a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift index 2a5f736ec5..da1669d26a 100644 --- a/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift +++ b/Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift @@ -86,6 +86,7 @@ enum CostUsageScanner { var contributingSessionIds: Set = [] var seenFileIds: Set = [] var seenCodexUsageRowKeys: Set = [] + var changedLineageInputs = false } struct CodexScannedSession { @@ -2595,6 +2596,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 } @@ -2703,7 +2705,9 @@ enum CostUsageScanner { options: Options, checkCancellation: CancellationCheck?) throws -> CostUsageDailyReport { - let accountingProducerKey = Self.codexAccountingProducerKey(mode: options.codexLineageAccountingMode) + let accountingProducerKey = Self.codexAccountingProducerKey( + mode: options.codexLineageAccountingMode, + authorization: options.codexLineagePromotionAuthorization) var cache = CostUsageCacheIO.load( provider: .codex, cacheRoot: options.cacheRoot, @@ -2808,6 +2812,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) } @@ -2818,6 +2823,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 @@ -2825,7 +2831,9 @@ enum CostUsageScanner { } } - let shouldRetainWiderWindow = !options.forceRescan && !plan.pricingChanged && !plan + let usesLineageAuthority = options.codexLineageAccountingMode == .lineage + && options.codexLineagePromotionAuthorization != nil + let shouldRetainWiderWindow = !usesLineageAuthority && !options.forceRescan && !plan.pricingChanged && !plan .priorityMetadataChanged && !plan.needsTurnIDCacheMigration && !plan.needsProjectMetadataMigration let retainedSinceKey = shouldRetainWiderWindow ? [cachedSinceKey, range.scanSinceKey].compactMap(\.self).min() ?? range.scanSinceKey @@ -2834,13 +2842,15 @@ enum CostUsageScanner { ? [cachedUntilKey, range.scanUntilKey].compactMap(\.self).max() ?? range.scanUntilKey : range.scanUntilKey Self.pruneDays(cache: &cache, sinceKey: retainedSinceKey, untilKey: retainedUntilKey) - try Self.applyCodexLineageAccounting( - mode: options.codexLineageAccountingMode, - authorization: options.codexLineagePromotionAuthorization, - files: files, - roots: plan.roots, - cache: &cache, - checkCancellation: checkCancellation) + if options.codexLineageAccountingMode != .shadow || scanState.changedLineageInputs { + try Self.applyCodexLineageAccounting( + mode: options.codexLineageAccountingMode, + authorization: options.codexLineagePromotionAuthorization, + files: files, + roots: plan.roots, + cache: &cache, + checkCancellation: checkCancellation) + } Self.pruneDays(cache: &cache, sinceKey: retainedSinceKey, untilKey: retainedUntilKey) cache.roots = plan.rootsFingerprint cache.scanSinceKey = retainedSinceKey @@ -2879,9 +2889,13 @@ enum CostUsageScanner { priorityTurns: plan.priorityTurns) } - static func codexAccountingProducerKey(mode: CodexLineageAccountingMode) -> String { + static func codexAccountingProducerKey( + mode: CodexLineageAccountingMode, + authorization: CodexLineagePromotionEvaluator.Authorization? = nil) -> String + { let base = CostUsageCacheIO.currentProducerKey(provider: .codex) ?? "codex" - return mode == .legacy ? base : base + ":" + mode.producerKeySuffix + guard mode != .legacy, mode != .lineage || authorization != nil else { return base } + return base + ":" + mode.producerKeySuffix } static func shouldRunCodexLineage(mode: CodexLineageAccountingMode) -> Bool { @@ -2898,6 +2912,7 @@ enum CostUsageScanner { checkCancellation: CancellationCheck?) throws { guard self.shouldRunCodexLineage(mode: mode) else { return } + guard mode != .lineage || authorization != nil else { return } do { let discovery = try CodexLineageTwoPassDiscovery.discover( includedFiles: files, diff --git a/Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift b/Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift index 95e6ff67dc..49c2edd371 100644 --- a/Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift +++ b/Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift @@ -1,3 +1,4 @@ +import Foundation import Testing @testable import CodexBarCore @@ -118,6 +119,11 @@ struct CodexLineageAccountingSelectorTests { #expect(selection.days == legacy) #expect(!selection.usedLineageAuthority) + #expect(CostUsageScanner.codexAccountingProducerKey(mode: .lineage) + == CostUsageScanner.codexAccountingProducerKey(mode: .legacy)) + #expect(CostUsageScanner.codexAccountingProducerKey( + mode: .lineage, + authorization: Self.authorization()) != CostUsageScanner.codexAccountingProducerKey(mode: .legacy)) } @Test @@ -142,10 +148,81 @@ struct CodexLineageAccountingSelectorTests { #expect(shadowKey != legacyKey) } + @Test + func `scanner cache follows current lineage authorization and narrowed bounds`() throws { + let environment = try CostUsageTestEnvironment() + defer { environment.cleanup() } + let day = try environment.makeLocalNoon(year: 2026, month: 7, day: 9) + try Self.writeRollout(environment: environment) + var options = CostUsageScanner.Options( + codexSessionsRoot: environment.codexSessionsRoot, + claudeProjectsRoots: nil, + cacheRoot: environment.cacheRoot, + codexTraceDatabaseURL: environment.root.appendingPathComponent("missing.sqlite")) + options.codexLineageAccountingMode = .lineage + options.codexLineagePromotionAuthorization = Self.authorization() + options.refreshMinIntervalSeconds = 0 + + _ = CostUsageScanner.loadDailyReport( + provider: .codex, + since: day.addingTimeInterval(-86400), + until: day, + now: day, + options: options) + _ = CostUsageScanner.loadDailyReport( + provider: .codex, + since: day, + until: day, + now: day, + options: options) + + let authorizedKey = CostUsageScanner.codexAccountingProducerKey( + mode: .lineage, + authorization: options.codexLineagePromotionAuthorization) + let authorizedCache = CostUsageCacheIO.load( + provider: .codex, + cacheRoot: environment.cacheRoot, + producerKey: authorizedKey) + let narrowRange = CostUsageScanner.CostUsageDayRange(since: day, until: day) + #expect(authorizedCache.scanSinceKey == narrowRange.scanSinceKey) + #expect(authorizedCache.scanUntilKey == narrowRange.scanUntilKey) + + options.codexLineagePromotionAuthorization = nil + options.refreshMinIntervalSeconds = 3600 + _ = CostUsageScanner.loadDailyReport( + provider: .codex, + since: day, + until: day, + now: day, + options: options) + let legacyKey = CostUsageScanner.codexAccountingProducerKey(mode: .legacy) + let rollbackCache = CostUsageCacheIO.load( + provider: .codex, + cacheRoot: environment.cacheRoot, + producerKey: legacyKey) + #expect(rollbackCache.producerKey == legacyKey) + #expect(!rollbackCache.days.isEmpty) + #expect(legacyKey != authorizedKey) + } + private static func days(input: Int) -> CodexLineageAccountingSelector.PackedDays { ["2026-07-09": ["gpt-5.4": [input, 0, 0]]] } + private static func writeRollout(environment: CostUsageTestEnvironment) throws { + try FileManager.default.createDirectory(at: environment.codexSessionsRoot, withIntermediateDirectories: true) + let ownerID = "00000000-0000-4000-8000-000000000099" + let fileURL = environment.codexSessionsRoot + .appendingPathComponent("rollout-2026-07-09T12-00-00-\(ownerID).jsonl") + let contents = [ + #"{"type":"session_meta","payload":{"id":"\#(ownerID)"}}"#, + #"{"type":"event_msg","timestamp":"2026-07-09T12:00:00Z","payload":{"type":"token_count","info":{"# + + #""last_token_usage":{"input_tokens":100,"cached_input_tokens":20,"output_tokens":10},"# + + #""total_token_usage":{"input_tokens":100,"cached_input_tokens":20,"output_tokens":10}}}}"#, + ].joined(separator: "\n") + try contents.write(to: fileURL, atomically: true, encoding: .utf8) + } + private static func authorization() -> CodexLineagePromotionEvaluator.Authorization { let sample = CodexLineageResidualClassifier.Sample( day: "2026-07-09", diff --git a/Tests/CodexBarTests/CodexLineageLedgerTests.swift b/Tests/CodexBarTests/CodexLineageLedgerTests.swift index 621f1348a5..283a89b1ea 100644 --- a/Tests/CodexBarTests/CodexLineageLedgerTests.swift +++ b/Tests/CodexBarTests/CodexLineageLedgerTests.swift @@ -340,6 +340,21 @@ struct CodexLineageLedgerTests { #expect(report.duplicateObservationCount == 1) } + @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 `complete token state distinguishes observations within a lineage`() throws { let first = Self.observation( diff --git a/Tests/CodexBarTests/CodexLineageTwoPassDiscoveryTests.swift b/Tests/CodexBarTests/CodexLineageTwoPassDiscoveryTests.swift index 916eb0c674..50c6837382 100644 --- a/Tests/CodexBarTests/CodexLineageTwoPassDiscoveryTests.swift +++ b/Tests/CodexBarTests/CodexLineageTwoPassDiscoveryTests.swift @@ -56,6 +56,32 @@ struct CodexLineageTwoPassDiscoveryTests { #expect(report.unresolvedParents.isEmpty) } + @Test + func `exceptional parent lookup uses parsed session identity when filename owner differs`() throws { + let environment = try CostUsageTestEnvironment() + defer { environment.cleanup() } + let parentSessionID = Self.uuid(12) + let parent = try Self.writeRollout( + root: environment.codexArchivedSessionsRoot, + ownerID: Self.uuid(13), + metadataID: parentSessionID, + observations: 1) + let child = try Self.writeRollout( + root: environment.codexSessionsRoot, + ownerID: Self.uuid(14), + parentID: parentSessionID, + observations: 1) + + let report = try CodexLineageTwoPassDiscovery.discover( + includedFiles: [child], + roots: [environment.codexSessionsRoot, environment.codexArchivedSessionsRoot]) + + #expect(report.descriptors.map(\.fileURL).map(\.standardizedFileURL.path) + .contains(parent.standardizedFileURL.path)) + #expect(report.referencedParentDocumentCount == 1) + #expect(report.unresolvedParents.isEmpty) + } + @Test func `streaming reconciliation loads one family at a time and warm reuse loads none`() throws { let environment = try CostUsageTestEnvironment() From ab229362f6512fc9590080cf5ec0c995d59e27e1 Mon Sep 17 00:00:00 2001 From: Bryan Font Date: Tue, 14 Jul 2026 13:30:23 -0400 Subject: [PATCH 28/28] Cover lineage cache window reconstruction --- .../CodexLineageAccountingSelectorTests.swift | 50 +++++++++++++++---- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift b/Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift index 49c2edd371..438b913f17 100644 --- a/Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift +++ b/Tests/CodexBarTests/CodexLineageAccountingSelectorTests.swift @@ -152,8 +152,18 @@ struct CodexLineageAccountingSelectorTests { func `scanner cache follows current lineage authorization and narrowed bounds`() throws { let environment = try CostUsageTestEnvironment() defer { environment.cleanup() } + let retainedDay = try environment.makeLocalNoon(year: 2026, month: 7, day: 7) let day = try environment.makeLocalNoon(year: 2026, month: 7, day: 9) - try Self.writeRollout(environment: environment) + try Self.writeRollout( + environment: environment, + ownerID: "00000000-0000-4000-8000-000000000097", + day: "2026-07-07", + inputTokens: 40) + try Self.writeRollout( + environment: environment, + ownerID: "00000000-0000-4000-8000-000000000099", + day: "2026-07-09", + inputTokens: 100) var options = CostUsageScanner.Options( codexSessionsRoot: environment.codexSessionsRoot, claudeProjectsRoots: nil, @@ -165,7 +175,7 @@ struct CodexLineageAccountingSelectorTests { _ = CostUsageScanner.loadDailyReport( provider: .codex, - since: day.addingTimeInterval(-86400), + since: retainedDay, until: day, now: day, options: options) @@ -186,9 +196,27 @@ struct CodexLineageAccountingSelectorTests { let narrowRange = CostUsageScanner.CostUsageDayRange(since: day, until: day) #expect(authorizedCache.scanSinceKey == narrowRange.scanSinceKey) #expect(authorizedCache.scanUntilKey == narrowRange.scanUntilKey) + #expect(authorizedCache.days["2026-07-07"] == nil) - options.codexLineagePromotionAuthorization = nil options.refreshMinIntervalSeconds = 3600 + let rebuiltRetainedDay = CostUsageScanner.loadDailyReport( + provider: .codex, + since: retainedDay, + until: retainedDay, + now: day, + options: options) + #expect(rebuiltRetainedDay.summary?.totalTokens == 45) + + let rebuiltCache = CostUsageCacheIO.load( + provider: .codex, + cacheRoot: environment.cacheRoot, + producerKey: authorizedKey) + let retainedDayRange = CostUsageScanner.CostUsageDayRange(since: retainedDay, until: retainedDay) + #expect(rebuiltCache.scanSinceKey == retainedDayRange.scanSinceKey) + #expect(rebuiltCache.scanUntilKey == retainedDayRange.scanUntilKey) + #expect(rebuiltCache.days["2026-07-07"] != nil) + + options.codexLineagePromotionAuthorization = nil _ = CostUsageScanner.loadDailyReport( provider: .codex, since: day, @@ -209,16 +237,20 @@ struct CodexLineageAccountingSelectorTests { ["2026-07-09": ["gpt-5.4": [input, 0, 0]]] } - private static func writeRollout(environment: CostUsageTestEnvironment) throws { + private static func writeRollout( + environment: CostUsageTestEnvironment, + ownerID: String, + day: String, + inputTokens: Int) throws + { try FileManager.default.createDirectory(at: environment.codexSessionsRoot, withIntermediateDirectories: true) - let ownerID = "00000000-0000-4000-8000-000000000099" let fileURL = environment.codexSessionsRoot - .appendingPathComponent("rollout-2026-07-09T12-00-00-\(ownerID).jsonl") + .appendingPathComponent("rollout-\(day)T12-00-00-\(ownerID).jsonl") let contents = [ #"{"type":"session_meta","payload":{"id":"\#(ownerID)"}}"#, - #"{"type":"event_msg","timestamp":"2026-07-09T12:00:00Z","payload":{"type":"token_count","info":{"# - + #""last_token_usage":{"input_tokens":100,"cached_input_tokens":20,"output_tokens":10},"# - + #""total_token_usage":{"input_tokens":100,"cached_input_tokens":20,"output_tokens":10}}}}"#, + #"{"type":"event_msg","timestamp":"\#(day)T12:00:00Z","payload":{"type":"token_count","info":{"# + + #""last_token_usage":{"input_tokens":\#(inputTokens),"cached_input_tokens":5,"output_tokens":5},"# + + #""total_token_usage":{"input_tokens":\#(inputTokens),"cached_input_tokens":5,"output_tokens":5}}}}"#, ].joined(separator: "\n") try contents.write(to: fileURL, atomically: true, encoding: .utf8) }