From 9ed251de192df66a511ee9f946041862f1a6d0b2 Mon Sep 17 00:00:00 2001 From: Jay Wedgeworth <12656028+jaywedgeworth22@users.noreply.github.com> Date: Thu, 22 Oct 2026 04:29:52 -0500 Subject: [PATCH 1/4] fix(quota): one reading decides every exhausted question The exhaustion path still had its own answer on three surfaces, so the Mac, the wire and the phone could each call the same account spent or live while the other two disagreed. The Mac's exhausted alert now reads the shared cap rule instead of testing for zero, so it fires at the same threshold as the "At cap" line it sits next to. The wire's exhausted flag does the same, and a reading decides it whenever there is one: a stale provider word, which the Mac's own export never clears, must not overrule a reading that says there is room left. The phone's card, the phone's pool and the phone's alert all read the rule too. The settings copy named the old rule, so it now names the threshold the app uses. Refs CC-184 --- Sources/CodeCaps/MonitorModel.swift | 8 +- Sources/CodeCaps/SettingsViews.swift | 2 +- Sources/QuotaCore/QuotaPublisher.swift | 9 +- .../ExhaustionCapRuleTests.swift | 82 +++++++++++++++++++ .../QuotaCoreTests/QuotaPublisherTests.swift | 70 ++++++++++++++++ 5 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 Tests/CodeCapsTests/ExhaustionCapRuleTests.swift diff --git a/Sources/CodeCaps/MonitorModel.swift b/Sources/CodeCaps/MonitorModel.swift index d778a25b..af622fd4 100644 --- a/Sources/CodeCaps/MonitorModel.swift +++ b/Sources/CodeCaps/MonitorModel.swift @@ -2668,7 +2668,11 @@ final class MonitorModel: ObservableObject { let previous = exhaustionObservationState[key] guard previous == nil || observedAt > previous!.observedAt else { continue } changed = true - if remaining > 0 { + // The shared cap rule decides, so the Mac's exhausted alert fires + // at the same threshold as the "At cap" line and as the phone's: + // a reading at or below the threshold is a spent allowance, and a + // reading above it is headroom (Kody review 2026-10-21). + if !QuotaCapRule.isAtCap(remainingPercent: remaining) { exhaustionObservationState[key] = ExhaustionObservationState(observedAt: observedAt, alert: nil) activeExhaustionAlerts.removeValue(forKey: key) continue @@ -2678,7 +2682,7 @@ final class MonitorModel: ObservableObject { continue } let title = "Quota Exhausted: " + window.provider - let body = window.label + " has no quota remaining." + let body = window.label + " has reached its limit." let payload = LocalQuotaSnapshot.AlertPayload( id: "quotaExhaustion|" + key + "|" + String(observedAt.timeIntervalSince1970), kind: "quotaExhaustion", providerKey: window.canonicalProviderKey, diff --git a/Sources/CodeCaps/SettingsViews.swift b/Sources/CodeCaps/SettingsViews.swift index 56cd71f2..9c6a3923 100644 --- a/Sources/CodeCaps/SettingsViews.swift +++ b/Sources/CodeCaps/SettingsViews.swift @@ -1398,7 +1398,7 @@ struct SettingsNotificationsPage: View { Eyebrow("ALERT DELIVERY") } footer: { Text("Anomalies compare usage with your average; runaway usage compares it with your peak." + sentenceGap - + "Quota Exhausted fires at zero remaining." + sentenceGap + + "Quota Exhausted fires at the cap threshold, \(QuotaCapRule.spentThreshold)% remaining or less." + sentenceGap + "Provider bells choose which scheduled and vendor resets alert on this Mac.") } if let row = model.displaySections.first(where: { diff --git a/Sources/QuotaCore/QuotaPublisher.swift b/Sources/QuotaCore/QuotaPublisher.swift index 2a3d91ed..fb592e1f 100644 --- a/Sources/QuotaCore/QuotaPublisher.swift +++ b/Sources/QuotaCore/QuotaPublisher.swift @@ -255,7 +255,14 @@ public actor QuotaPublisher { var meta: [String: Any] = [ "bucketId": bucketId, - "isExhausted": window.isExhausted || QuotaCapRule.isAtCap(remainingPercent: clampedRemaining), + // The shared cap rule decides, as it does on every surface: a + // reading at or below the threshold is a spent allowance, and + // the provider's own exhausted word is the fallback for a + // window that reports no figure at all. A local zero test + // here left the wire disagreeing with the at-cap line the + // owner reads (Kody review 2026-10-21). + "isExhausted": QuotaCapRule.isExhausted(remainingPercent: clampedRemaining, + providerSaysExhausted: window.isExhausted), "remainingUnknown": clampedRemaining == nil, "scale": "percent_0_100", "source": Self.producerId, diff --git a/Tests/CodeCapsTests/ExhaustionCapRuleTests.swift b/Tests/CodeCapsTests/ExhaustionCapRuleTests.swift new file mode 100644 index 00000000..71d722d6 --- /dev/null +++ b/Tests/CodeCapsTests/ExhaustionCapRuleTests.swift @@ -0,0 +1,82 @@ +import XCTest +@testable import CodeCaps +import QuotaCore + +/// The exhausted alert and the at-cap line have to agree on one threshold. +/// +/// The shared rule spends a window at or below half a point remaining, and the +/// interface rounds that to zero percent, so a window reading 0.4% draws +/// "At cap" on the card. The alert path used its own zero test, so the owner +/// saw "At cap" and never received the "Quota Exhausted" notification +/// (Kody review 2026-10-21). +/// +/// The Mac's alert path lives inside a large `MonitorModel`, so the rule it +/// reads is pinned by reading the source, the way the phone's own companion +/// model is pinned in `CompanionModelTests`. +final class ExhaustionCapRuleTests: XCTestCase { + private static var monitorModelURL: URL? { + URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() // Tests/CodeCapsTests + .deletingLastPathComponent() // Tests + .deletingLastPathComponent() // repo root + .appendingPathComponent("Sources/CodeCaps/MonitorModel.swift") + } + + private static var settingsViewsURL: URL? { + URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() + .deletingLastPathComponent() + .deletingLastPathComponent() + .appendingPathComponent("Sources/CodeCaps/SettingsViews.swift") + } + + private func read(_ url: URL?) throws -> String { + guard let url, FileManager.default.fileExists(atPath: url.path) else { + throw XCTSkip("The source file is not present at \(url?.path ?? "")") + } + return try String(contentsOf: url, encoding: .utf8) + } + + /// The Mac's exhausted alert fires on the shared rule. + func testTheMacAlertFiresOnTheSharedRuleRatherThanOnZero() throws { + let source = try read(Self.monitorModelURL) + XCTAssertTrue( + source.contains("if !QuotaCapRule.isAtCap(remainingPercent: remaining)"), + "the Mac's exhausted alert must read the shared cap rule, so it fires at the same threshold as the at-cap line" + ) + XCTAssertFalse( + source.contains("if remaining > 0 {"), + "a local zero test here left the Mac quiet while the card said 'At cap'" + ) + } + + /// The settings copy names the threshold the app actually uses, so it + /// cannot drift back to "zero remaining" and describe a rule the app no + /// longer follows. + func testTheSettingsCopyNamesTheSharedThreshold() throws { + let source = try read(Self.settingsViewsURL) + XCTAssertFalse( + source.contains("Quota Exhausted fires at zero remaining"), + "the copy has to describe the threshold the app uses, not the one it used to use" + ) + XCTAssertTrue( + source.contains("QuotaCapRule.spentThreshold"), + "the copy must quote the shared threshold itself, so a change to the rule cannot leave the copy behind" + ) + } + + /// The rule itself spends a window at or below the threshold and calls a + /// larger reading headroom, whichever surface asks. + func testTheSharedRuleIsTheOneBothSurfacesAsk() { + XCTAssertTrue(QuotaCapRule.isAtCap(remainingPercent: 0.4)) + XCTAssertTrue(QuotaCapRule.isAtCap(remainingPercent: 0)) + XCTAssertFalse(QuotaCapRule.isAtCap(remainingPercent: 0.6)) + XCTAssertFalse(QuotaCapRule.isAtCap(remainingPercent: nil)) + // The reading decides, and the provider's own word is the fallback + // for a window that reports no figure at all. + XCTAssertTrue(QuotaCapRule.isExhausted(remainingPercent: 0.4, providerSaysExhausted: false)) + XCTAssertFalse(QuotaCapRule.isExhausted(remainingPercent: 50, providerSaysExhausted: true)) + XCTAssertTrue(QuotaCapRule.isExhausted(remainingPercent: nil, providerSaysExhausted: true)) + XCTAssertFalse(QuotaCapRule.isExhausted(remainingPercent: nil, providerSaysExhausted: false)) + } +} diff --git a/Tests/QuotaCoreTests/QuotaPublisherTests.swift b/Tests/QuotaCoreTests/QuotaPublisherTests.swift index c4be725c..6194f3c9 100644 --- a/Tests/QuotaCoreTests/QuotaPublisherTests.swift +++ b/Tests/QuotaCoreTests/QuotaPublisherTests.swift @@ -489,4 +489,74 @@ final class QuotaPublisherTests: XCTestCase { XCTFail("Unexpected error: \(error)") } } + + // MARK: - The wire's exhausted flag follows the shared cap rule + + private func makeCapWindow( + id: String, + window token: String, + remaining: Double, + providerWord: Bool + ) -> QuotaWindow { + QuotaWindow( + id: id, + provider: "anthropic", + providerKey: "anthropic", + providerLabel: "Anthropic", + via: nil, + sourceApp: nil, + modelId: nil, + modelType: nil, + label: "Claude \(token)", + remainingPercent: remaining, + absoluteRemaining: nil, + absoluteLimit: nil, + quotaUnit: nil, + planName: nil, + remainingUnknown: false, + isExhausted: providerWord, + resetAt: "2026-09-14T22:00:00Z", + window: token, + status: .available, + skip: false, + skipReason: nil, + occurredAt: "2026-09-14T19:00:00Z", + source: "local" + ) + } + + /// The wire's exhausted flag has to read the same rule the interface + /// reads. + /// + /// A window reading 0.4% is spent by the shared rule, and the interface + /// rounds that to zero percent, so the wire has to say the window is + /// exhausted too: otherwise the phone's cap line says "At cap" while the + /// exhausted alert stays quiet. A window reading 40% is headroom, and the + /// provider's own exhausted word, which the Mac's export never clears, + /// must not overrule a reading that says there is room left + /// (Kody review 2026-10-21). + func testTheExportedExhaustedFlagFollowsTheSharedCapRule() throws { + let publisher = QuotaPublisher() + let windows = [ + makeCapWindow(id: "spent", window: "5h", remaining: 0.4, providerWord: false), + makeCapWindow(id: "headroom", window: "weekly", remaining: 40, providerWord: false), + makeCapWindow(id: "stale-word", window: "monthly", remaining: 40, providerWord: true) + ] + let data = try publisher.buildUsageMonitorV2Payload( + windows: windows, occurredAtIso: "2026-09-14T19:00:00Z", + machineName: "Test-Mac", producerInstanceId: "test-instance") + let json = try XCTUnwrap(JSONSerialization.jsonObject(with: data) as? [String: Any]) + let events = try XCTUnwrap(json["events"] as? [[String: Any]]) + XCTAssertEqual(events.count, 3) + + let flags = events.map { event -> Bool in + let meta = event["metadata"] as? [String: Any] + return meta?["isExhausted"] as? Bool ?? false + } + XCTAssertEqual( + flags, + [true, false, false], + "a 0.4% reading is spent by the shared rule, and a 40% reading is headroom whatever the provider's word says" + ) + } } From 1a7c981cfb1584fb96d4d9837915917f673a0a1e Mon Sep 17 00:00:00 2001 From: Jay Wedgeworth <12656028+jaywedgeworth22@users.noreply.github.com> Date: Thu, 22 Oct 2026 07:29:38 -0500 Subject: [PATCH 2/4] fix(quota): a window at its cap is not a window to hide The shared cap rule that #298 put in place calls any reading at or under 0.5% spent, because the interface rounds a reading to the nearest whole percent. When `skip` followed that rule, a window at 0.3% was dropped from the picture in picture together with its cap line, while the menu bar still announced the cap, so the same window was live in one place and gone in the other. `skip` means "a reading that must not be believed", which is a different question from "is the allowance spent". The provider's own exhausted word is believed only when the reading disagrees with it, because only the provider knows why (a rejected session, a suspended plan) and the reading cannot say. It is also the answer when there is no reading at all. An exact zero is still the one reading that is never shown as a meter, and the provider's own `skip` flag is never erased. The reading is what makes that rule safe to apply twice, which the display path does: the picture in picture rebuilds a snapshot from an already derived window, and a rule that read the derived `isExhausted` back as the provider's word would hide the window again on the second pass. That is why the rule is written around the reading and not around the flag, and why each of the three broken variants of it is covered by a test that fails without it. Refs CC-184 --- Sources/QuotaCore/QuotaModels.swift | 30 +++++- Tests/CodeCapsTests/PipInteractionTests.swift | 30 ++++++ .../LocalQuotaSnapshotTests.swift | 94 +++++++++++++++++++ 3 files changed, 152 insertions(+), 2 deletions(-) diff --git a/Sources/QuotaCore/QuotaModels.swift b/Sources/QuotaCore/QuotaModels.swift index a3c8e412..bf30afdd 100644 --- a/Sources/QuotaCore/QuotaModels.swift +++ b/Sources/QuotaCore/QuotaModels.swift @@ -285,8 +285,34 @@ public struct QuotaWindow: Codable, Equatable, Sendable { } else if status == .unknown, bounded != nil { copy.status = QuotaWindowStatus.derived(remainingPercent: bounded) } - copy.skip = skip || copy.isExhausted - copy.skipReason = skipReason ?? (copy.isExhausted ? "quota exhausted" : nil) + // `skip` means "a reading that must not be believed", which is not the + // same question as "is the allowance spent". The shared cap rule + // answers the second one for any reading at or under the threshold, + // and a spent window is still a window with a reading in it: when + // `skip` followed the rule, a window at 0.3% was dropped from the + // picture in picture along with its cap line, while the menu bar still + // announced the cap, so the same window was live in one place and + // gone in the other (Kody review 2026-10-22). + // + // The provider's own exhausted word is believed only when the reading + // disagrees with it, because only the provider knows why (a rejected + // session, a suspended plan) and the reading cannot say. It is also + // the answer when there is no reading at all. An exact zero is the + // one reading that is never shown as a meter. + // + // The reading is what makes this rule safe to apply twice, which the + // display path does: the picture in picture rebuilds a snapshot from + // an already derived window, and a rule that read the derived + // `isExhausted` back as the provider's word would hide the window + // again on the second pass. + let providerWordCounts: Bool + if let bounded { + providerWordCounts = isExhausted && bounded > QuotaCapRule.spentThreshold + } else { + providerWordCounts = isExhausted + } + copy.skip = skip || bounded == 0 || providerWordCounts + copy.skipReason = skipReason ?? (copy.skip ? "quota exhausted" : nil) let monthlyHint = copy.canonicalProviderKey == "cursor" && copy.label.lowercased().contains("plan") if let rewritten = QuotaWindowVocabulary.sanitizeLabelIfNeeded( diff --git a/Tests/CodeCapsTests/PipInteractionTests.swift b/Tests/CodeCapsTests/PipInteractionTests.swift index 8721d645..ff4d3ba7 100644 --- a/Tests/CodeCapsTests/PipInteractionTests.swift +++ b/Tests/CodeCapsTests/PipInteractionTests.swift @@ -23,6 +23,36 @@ final class PipInteractionTests: XCTestCase { } } + /// A window that is at its cap is still a window the panel shows. + /// + /// The shared cap rule calls any reading at or under 0.5% spent, because + /// the interface rounds a reading to the nearest whole percent. When the + /// export also hid such a window, the picture in picture dropped it and + /// its cap line while the menu bar still announced the cap, so the same + /// window was live in one place and gone in the other. + func testANearZeroReadingStaysVisibleInThePictureInPicture() throws { + let (model, defaults, suite) = GlanceFixtures.makeModel(view: .fromMac, alarmsAll: false, fleet: false) + defer { defaults.removePersistentDomain(forName: suite) } + let window = GlanceFixtures.window("anthropic:5h", provider: "anthropic", + label: "5h", token: "5h", remaining: 0.3, resetIn: 18_000) + model.injectForTests(sections: QuotaResponse(generatedAt: "", windows: [window]) + .platformSections(now: GlanceFixtures.now), now: GlanceFixtures.now) + let row = try XCTUnwrap(model.displaySections.first { $0.id == "anthropic" }) + + let current = PipWidgetController.currentWindows(for: row, now: model.now) + XCTAssertEqual(current.count, 1, "a window at 0.3% is at its cap, not a reading to hide") + XCTAssertEqual(current.first?.remainingPercent, 0.3) + XCTAssertNil(PipWidgetController.readingStatus(for: row, now: model.now)) + + // The cap line is built from the same filtered list, so it has to be + // there too: a spent window that is visible still says it is spent. + let section = QuotaPlatformSection(providerKey: row.providerKey, + providerLabel: row.section.providerLabel, + expected: row.section.expected, + windows: current) + XCTAssertNotNil(section.atCapWindow(), "the cap line still reads the shared cap rule") + } + func testBodyDragIncludesLogosBarsAndTextButNotResizeEdgesOrControls() { let size = NSSize(width: 400, height: 180) for point in [NSPoint(x: 18, y: 70), NSPoint(x: 160, y: 110), NSPoint(x: 310, y: 40)] { diff --git a/Tests/QuotaCoreTests/LocalQuotaSnapshotTests.swift b/Tests/QuotaCoreTests/LocalQuotaSnapshotTests.swift index 816b24be..24e4920a 100644 --- a/Tests/QuotaCoreTests/LocalQuotaSnapshotTests.swift +++ b/Tests/QuotaCoreTests/LocalQuotaSnapshotTests.swift @@ -228,6 +228,100 @@ final class LocalQuotaSnapshotTests: XCTestCase { XCTAssertEqual(QuotaWindowStatus.derived(remainingPercent: 20), .available) } + /// A window that is at its cap is not a window that must be hidden. + /// + /// The shared cap rule calls any reading at or under 0.5% spent, because + /// the interface rounds a reading to the nearest whole percent. When + /// `skip` followed that rule, a window at 0.3% was dropped from the + /// picture in picture along with its cap line, while the menu bar still + /// announced the cap, so the same window was live in one place and gone + /// in the other. `skip` keeps its own meaning: a reading that must not + /// be believed. + func testSkipDoesNotFollowTheSharedCapThreshold() { + // At the cap by the shared rule, but a real reading that is not zero. + let nearZero = QuotaWindow(id: "near-zero", provider: "openai", label: "5h", + remainingPercent: 0.3, occurredAt: "2026-10-03T23:00:00Z") + .normalizedForExport() + XCTAssertTrue(nearZero.isExhausted, "0.3% is at the cap, so the window is spent") + XCTAssertEqual(nearZero.status, .exhausted) + XCTAssertFalse(nearZero.skip, "a window at 0.3% is at its cap, not a reading to hide") + XCTAssertNil(nearZero.skipReason) + + // Exactly zero is still hidden, and still says why. + let zero = QuotaWindow(id: "zero", provider: "openai", label: "5h", + remainingPercent: 0, occurredAt: "2026-10-03T23:00:00Z") + .normalizedForExport() + XCTAssertTrue(zero.isExhausted) + XCTAssertTrue(zero.skip, "an exact zero is the one reading that is not believed") + XCTAssertEqual(zero.skipReason, "quota exhausted") + + // The provider's own flag still hides the window, and its own reason + // is never talked over. + let reported = QuotaWindow(id: "reported", provider: "openai", label: "5h", + remainingPercent: 15, isExhausted: true, status: .exhausted, + skip: true, skipReason: "session rejected", + occurredAt: "2026-10-03T23:00:00Z") + .normalizedForExport() + XCTAssertTrue(reported.skip) + XCTAssertEqual(reported.skipReason, "session rejected") + + // A provider that calls a window exhausted while the reading says + // there is room left is believed: only the provider knows why, and + // the reading cannot say. + let contradicted = QuotaWindow(id: "contradicted", provider: "openai", label: "5h", + remainingPercent: 15, isExhausted: true, + occurredAt: "2026-10-03T23:00:00Z") + .normalizedForExport() + XCTAssertTrue(contradicted.isExhausted) + XCTAssertTrue(contradicted.skip, "a provider's word against a reading is believed") + + // The same word beside a reading that already agrees with it settles + // nothing: the window is spent either way, and it stays visible. + let agreed = QuotaWindow(id: "agreed", provider: "openai", label: "5h", + remainingPercent: 0.3, isExhausted: true, + occurredAt: "2026-10-03T23:00:00Z") + .normalizedForExport() + XCTAssertTrue(agreed.isExhausted) + XCTAssertFalse(agreed.skip) + + // A provider that hides a window without saying why still leaves a + // reason on the wire, so a skip is never unexplained. + let silent = QuotaWindow(id: "silent", provider: "openai", label: "5h", + remainingPercent: 40, skip: true, + occurredAt: "2026-10-03T23:00:00Z") + .normalizedForExport() + XCTAssertTrue(silent.skip) + XCTAssertEqual(silent.skipReason, "quota exhausted") + + // An unreadable reading is not a spent one and raises nothing. + let unreadable = QuotaWindow(id: "unreadable", provider: "cursor", label: "Included plan", + remainingUnknown: true, occurredAt: "2026-10-03T23:00:00Z") + .normalizedForExport() + XCTAssertFalse(unreadable.isExhausted) + XCTAssertFalse(unreadable.skip) + XCTAssertNil(unreadable.skipReason) + + // The derivation is applied twice on the display path, the picture in + // picture rebuilds a snapshot from an already derived window, so the + // second pass has to reach the same answer as the first. + for source in [ + QuotaWindow(id: "a", provider: "openai", label: "5h", remainingPercent: 0.3, + occurredAt: "2026-10-03T23:00:00Z"), + QuotaWindow(id: "b", provider: "openai", label: "5h", remainingPercent: 0, + occurredAt: "2026-10-03T23:00:00Z"), + QuotaWindow(id: "c", provider: "openai", label: "5h", remainingPercent: 15, + isExhausted: true, occurredAt: "2026-10-03T23:00:00Z"), + QuotaWindow(id: "d", provider: "openai", label: "5h", remainingPercent: 15, + isExhausted: true, skip: true, skipReason: "session rejected", + occurredAt: "2026-10-03T23:00:00Z"), + QuotaWindow(id: "e", provider: "cursor", label: "Included plan", remainingUnknown: true, + occurredAt: "2026-10-03T23:00:00Z"), + ] { + let once = source.normalizedForExport() + XCTAssertEqual(once.normalizedForExport(), once, "\(source.id) is not stable under a second pass") + } + } + func testExhaustedFlagHasTheSameStatusInExportAndDisplayAtNonzeroPercent() throws { let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) defer { try? FileManager.default.removeItem(at: directory) } From 853ac1176dd646091947b74c88a0bc8440e987db Mon Sep 17 00:00:00 2001 From: Jay Wedgeworth <12656028+jaywedgeworth22@users.noreply.github.com> Date: Mon, 26 Oct 2026 17:59:05 -0500 Subject: [PATCH 3/4] fix(settings): name the exhausted threshold in plain words The notifications footer said "Quota Exhausted fires at the cap threshold", which names an internal rule rather than what the person reading it sees. It now reads "Quota Exhausted alerts when 0.5% or less remains." The threshold is a Double, so a plain interpolation would print 1.0% the day the rule reaches a whole number. It is now formatted with a fraction length of zero to one, which keeps 0.5% and gives 1%. A test asserts that formatting, so the whole-number form cannot come back. Refs CC-184 --- Sources/CodeCaps/SettingsViews.swift | 2 +- Tests/CodeCapsTests/ExhaustionCapRuleTests.swift | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/CodeCaps/SettingsViews.swift b/Sources/CodeCaps/SettingsViews.swift index 9c6a3923..04be9b73 100644 --- a/Sources/CodeCaps/SettingsViews.swift +++ b/Sources/CodeCaps/SettingsViews.swift @@ -1398,7 +1398,7 @@ struct SettingsNotificationsPage: View { Eyebrow("ALERT DELIVERY") } footer: { Text("Anomalies compare usage with your average; runaway usage compares it with your peak." + sentenceGap - + "Quota Exhausted fires at the cap threshold, \(QuotaCapRule.spentThreshold)% remaining or less." + sentenceGap + + "Quota Exhausted alerts when \(QuotaCapRule.spentThreshold.formatted(.number.precision(.fractionLength(0...1))))% or less remains." + sentenceGap + "Provider bells choose which scheduled and vendor resets alert on this Mac.") } if let row = model.displaySections.first(where: { diff --git a/Tests/CodeCapsTests/ExhaustionCapRuleTests.swift b/Tests/CodeCapsTests/ExhaustionCapRuleTests.swift index 71d722d6..037bfa57 100644 --- a/Tests/CodeCapsTests/ExhaustionCapRuleTests.swift +++ b/Tests/CodeCapsTests/ExhaustionCapRuleTests.swift @@ -63,6 +63,10 @@ final class ExhaustionCapRuleTests: XCTestCase { source.contains("QuotaCapRule.spentThreshold"), "the copy must quote the shared threshold itself, so a change to the rule cannot leave the copy behind" ) + XCTAssertTrue( + source.contains(".formatted(.number.precision(.fractionLength(0...1)))"), + "the threshold is a Double, so a plain interpolation would print 1.0% once the rule reaches a whole number" + ) } /// The rule itself spends a window at or below the threshold and calls a From a5f3734420f3322e8b91bf133961d08a93c71d66 Mon Sep 17 00:00:00 2001 From: Jay Wedgeworth <12656028+jaywedgeworth22@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:29:44 -0500 Subject: [PATCH 4/4] fix(quota): a skip reason never claims an exhaustion the window lacks A provider can hide a window without saying why. The export path invented the words "quota exhausted" for any hidden window, so a consumer that filters on the reason saw a healthy window reported as spent. The reason now attaches only to a window that is hidden AND spent, so it explains a skip without contradicting the reading. The provider's own reason is still passed through untouched. The gate is isExhausted rather than skip alone on purpose. A window that stays visible at a near zero reading is a spent window, and giving it a reason would invite a consumer to hide exactly the window this work keeps on screen. Tested by LocalQuotaSnapshotTests.testSkipDoesNotFollowTheSharedCapThreshold: the hidden-but-healthy window carries no reason, the hidden-and-spent window carries one, and the visible near zero window carries none. --- Sources/QuotaCore/QuotaModels.swift | 8 +++++++- Tests/QuotaCoreTests/LocalQuotaSnapshotTests.swift | 9 ++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Sources/QuotaCore/QuotaModels.swift b/Sources/QuotaCore/QuotaModels.swift index bf30afdd..8db34836 100644 --- a/Sources/QuotaCore/QuotaModels.swift +++ b/Sources/QuotaCore/QuotaModels.swift @@ -312,7 +312,13 @@ public struct QuotaWindow: Codable, Equatable, Sendable { providerWordCounts = isExhausted } copy.skip = skip || bounded == 0 || providerWordCounts - copy.skipReason = skipReason ?? (copy.skip ? "quota exhausted" : nil) + // The reason explains a skip, so it is attached to a skip and never + // to a window that stays visible, and it never claims an exhaustion + // the window does not have: a provider that hides a healthy window + // without saying why gets no reason at all, because "quota exhausted" + // on a window with room left is a false claim, and a consumer that + // filters on the reason would act on it (Kody review 2026-10-26). + copy.skipReason = skipReason ?? (copy.skip && copy.isExhausted ? "quota exhausted" : nil) let monthlyHint = copy.canonicalProviderKey == "cursor" && copy.label.lowercased().contains("plan") if let rewritten = QuotaWindowVocabulary.sanitizeLabelIfNeeded( diff --git a/Tests/QuotaCoreTests/LocalQuotaSnapshotTests.swift b/Tests/QuotaCoreTests/LocalQuotaSnapshotTests.swift index 24e4920a..24a1e1ed 100644 --- a/Tests/QuotaCoreTests/LocalQuotaSnapshotTests.swift +++ b/Tests/QuotaCoreTests/LocalQuotaSnapshotTests.swift @@ -274,6 +274,7 @@ final class LocalQuotaSnapshotTests: XCTestCase { .normalizedForExport() XCTAssertTrue(contradicted.isExhausted) XCTAssertTrue(contradicted.skip, "a provider's word against a reading is believed") + XCTAssertEqual(contradicted.skipReason, "quota exhausted", "a hidden window says why it is hidden") // The same word beside a reading that already agrees with it settles // nothing: the window is spent either way, and it stays visible. @@ -284,14 +285,16 @@ final class LocalQuotaSnapshotTests: XCTestCase { XCTAssertTrue(agreed.isExhausted) XCTAssertFalse(agreed.skip) - // A provider that hides a window without saying why still leaves a - // reason on the wire, so a skip is never unexplained. + // A provider that hides a window without saying why gets no reason + // invented for it: the window has room left, so "quota exhausted" + // would be a false claim for a consumer that filters on the reason. let silent = QuotaWindow(id: "silent", provider: "openai", label: "5h", remainingPercent: 40, skip: true, occurredAt: "2026-10-03T23:00:00Z") .normalizedForExport() XCTAssertTrue(silent.skip) - XCTAssertEqual(silent.skipReason, "quota exhausted") + XCTAssertFalse(silent.isExhausted, "40% is not a spent window") + XCTAssertNil(silent.skipReason, "a reason must never claim an exhaustion the window does not have") // An unreadable reading is not a spent one and raises nothing. let unreadable = QuotaWindow(id: "unreadable", provider: "cursor", label: "Included plan",