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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 17 additions & 21 deletions Sources/QuotaCore/AntigravityQuotaGroups.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,29 +98,24 @@ public enum AntigravityQuotaGroups {
/// The pool keys, in display order.
public static let poolKeys = ["gemini", "third-party"]

/// The level a weekly cap has to reach before the pool counts as spent.
///
/// The provider reports a fractional remaining, 0.4% is ordinary, and the
/// interface rounds that to 0%, so a cap that reads "at cap" on screen can
/// carry a number that is not exactly zero. An exact-zero test let those
/// through, and the five-hour window under them was then believed when its
/// percentage must not be believed: owner 2026-10-19, "Antigravity is
/// saying vendor reset 5h quota early while the 7d is still at cap and
/// unchanged". Half a point is where the interface rounds, so the mask
/// and the interface agree on what "at cap" means.
///
/// The number itself lives in `QuotaCapRule.spentThreshold`, so the pool's
/// own weekly test and the shared cap rule cannot drift apart.
public static let weeklySpentThreshold: Double = QuotaCapRule.spentThreshold

/// Whether a pool's weekly cap is spent. A weekly that reports a number
/// is spent at or below the rounding threshold; one that reports no number
/// falls back to the provider's own word for it.
///
/// The threshold is `QuotaCapRule.spentThreshold` rather than a second
/// constant kept here: the pool used to carry its own copy, and once the
/// weekly decision moved to the shared rule nothing read that copy, so it
/// survived only as dead code kept alive by a test -- which is how a live
/// copy of the same question could drift elsewhere without failing
/// anything. The number itself, and the reason it is half a point, live
/// in `QuotaCapRule`: the provider reports a fractional remaining and the
/// interface rounds 0.4% to 0%, so an exact-zero test let a cap through
/// and the five-hour window under it was then believed when its percentage
/// must not be (owner 2026-10-19, "Antigravity is saying vendor reset 5h
/// quota early while the 7d is still at cap and unchanged").
private static func weeklyIsSpent(_ window: QuotaWindow) -> Bool {
if let remaining = window.boundedRemainingPercent {
return remaining <= weeklySpentThreshold
}
return window.isExhausted
QuotaCapRule.isExhausted(remainingPercent: window.boundedRemainingPercent,
providerSaysExhausted: window.isExhausted)
}

/// Splits Antigravity windows into their two pools. Windows that belong to
Expand Down Expand Up @@ -171,8 +166,9 @@ public enum AntigravityQuotaGroups {
let headline = driving ?? spentWeekly
result.append(Pool(key: key,
windows: ordered,
remainingPercent: headline?.boundedRemainingPercent
?? (weeklyExhausted ? 0 : nil),
remainingPercent: QuotaCapRule.publishedReading(
remainingPercent: headline?.boundedRemainingPercent,
allowanceSpent: weeklyExhausted),
weeklyExhausted: weeklyExhausted,
drivingWindowId: headline?.id,
resetAt: headline?.resetDate,
Expand Down
51 changes: 50 additions & 1 deletion Sources/QuotaCore/QuotaWindowVocabulary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,13 @@ public enum QuotaWindowVocabulary {
public static func atCapCadence(cadence: String, resetAt: Date? = nil) -> String {
let known = [fiveHour, oneDay, sevenDay, oneMonth]
if known.contains(cadence) { return cadence }
return standardCadence(windowToken: cadence, label: "", resetAt: resetAt, monthlyHint: false)
// The cadence is a token, so the trailing " window" a caller may still
// carry is stripped before the token is resolved: "7-day window" names
// the same period as "7-day".
return standardCadence(windowToken: stripTrailingWindowSuffix(cadence),
label: "",
resetAt: resetAt,
monthlyHint: false)
}

/// The countdown an at-cap line quotes: the two largest non-zero units of
Expand Down Expand Up @@ -678,6 +684,49 @@ public enum QuotaCapRule {
return bounded <= spentThreshold
}

/// Whether an allowance is spent, when the provider's own word and its
/// reading can disagree.
///
/// The reading decides whenever there is one: the Mac's export never
/// clears `isExhausted`, so a provider that still carries the flag long
/// after the allowance came back would otherwise keep reporting a spent
/// allowance, and the Mac and the phone would tell the owner different
/// things about one account. With no reading at all the provider's word is
/// the only evidence there is, so it counts.
///
/// This is the one place that decides, so no surface can carry its own
/// version of the question.
///
/// A reading exists when `bounded` can make one. A value that is not a
/// finite number is no reading at all, so it falls back to the provider's
/// word rather than passing the existence check and then being judged as
/// headroom, which is what the phone's previous `(pct ?? 100) <= 0 || flag`
/// never did.
public static func isExhausted(remainingPercent: Double?, providerSaysExhausted: Bool) -> Bool {
guard let reading = bounded(remainingPercent) else { return providerSaysExhausted }
return reading <= spentThreshold
}
Comment thread
jaywedgeworth22 marked this conversation as resolved.

/// The reading a row publishes for an allowance.
///
/// An allowance the provider reports as gone without a figure still has to
/// publish a reading, because the cap rule only binds a window that
/// carries one: a row with no percentage reports no cap at all, so the
/// cap line never appears beside it and the countdown it should quote is
/// lost. A spent allowance publishes zero.
///
/// The Mac's Antigravity pool grouper and the phone's own row synthesis
/// both do this, and both do it through here, so the two cannot drift.
///
/// A published reading is bounded here, so every caller publishes the same
/// valid percentage: the phone passes the raw wire value, and an
/// out-of-range one would otherwise render as "150%" while the cap rule
/// judged the same window at 100.
public static func publishedReading(remainingPercent: Double?, allowanceSpent: Bool) -> Double? {
if let reading = bounded(remainingPercent) { return reading }
return allowanceSpent ? 0 : nil
}
Comment thread
jaywedgeworth22 marked this conversation as resolved.

/// Whether this window is a supplementary video allowance beside
/// MiniMax's coding subscription rather than inside it.
///
Expand Down
97 changes: 90 additions & 7 deletions Tests/CodeCapsTests/CompanionModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -360,19 +360,102 @@ final class CompanionModelTests: XCTestCase {
/// interface rounds that to 0%. The phone tested for exactly zero, so the
/// same pool was capped on one surface and not on the other.
///
/// The phone now reads the shared rule's threshold instead of carrying its
/// own number, so the two surfaces cannot drift again.
/// The phone now asks the shared rule for the answer rather than carrying a
/// threshold of its own. The constant it used to keep is deleted: once
/// the weekly decision moved into the rule, nothing read it, and a test
/// that pinned a dead symbol still passed while a live copy of the same
/// question sat in the widget -- so the guarantee it claimed was false
/// (Kody review 2026-10-22).
func testThePhoneAndTheMacAgreeOnWhenAWeeklyCapCountsAsSpent() throws {
let model = try readCompanionModelSource()
XCTAssertFalse(
model.contains("weeklySpentThreshold"),
"the phone must not keep a weekly spent threshold of its own; the shared rule owns the number"
)
XCTAssertTrue(
model.contains("public static let weeklySpentThreshold: Double = QuotaCapRule.spentThreshold"),
"the phone must read the shared threshold, not carry a number of its own"
model.contains("let weeklyExhausted = QuotaCapRule.isExhausted(remainingPercent: weeklyPct,"),
"the phone must decide the weekly through the shared rule, so a reading outranks the provider's exhausted word"
)
}

/// A pool is spent when any of its windows is.
///
/// The pool decision evaluated the provider's word against `controllingPct`,
/// the lowest reading across the pool -- a reading that belongs to a
/// different window than the one the provider reports as gone. A weekly
/// the provider reported as spent without a figure, beside a healthy
/// five-hour reading, therefore reported the plan live and the owner saw a
/// usable plan whose weekly allowance was gone (Kody review 2026-10-22).
/// Each window's own decision already went through the shared rule, so the
/// pool ORs those.
func testAPoolIsSpentWhenAnyOfItsWindowsIs() throws {
let model = try readCompanionModelSource()
XCTAssertTrue(
model.contains("let isExhausted = primaryWindows.contains(where: \\.isExhausted)"),
"the pool must be spent when any of its windows is"
)
XCTAssertFalse(
model.contains("public static let weeklySpentThreshold: Double = 0.5"),
"the phone must not keep its own copy of the threshold"
model.contains("remainingPercent: controllingPct,\n providerSaysExhausted: primaryWindows"),
"the provider's word must not be weighed against another window's reading"
)
}

/// The weekly row and its own provider word have to agree.
///
/// `weeklyExhausted` is keyed off `weeklyWin`, picked by scanning ids and
/// labels, while the row is built from `driving`, picked from the period
/// buckets. When the two pick different windows, a weekly the provider
/// reports as gone published no reading, so the cap rule could not bind it
/// and the cap line never appeared (Kody review 2026-10-22).
func testTheWeeklyRowAlsoPublishesZeroForItsOwnProviderWord() throws {
let model = try readCompanionModelSource()
XCTAssertTrue(
model.contains("let providerSaysSpent = driving.isExhausted ?? false"),
"the row must consider the exhausted word of the window it is actually built from"
)
XCTAssertTrue(
model.contains("allowanceSpent: period == \"weekly\" && (weeklyExhausted || providerSaysSpent)"),
"a weekly the provider reports as gone must publish zero even when the pool's other weekly window differs"
)
}

/// The phone must let a reading outrank the provider's exhausted word.
///
/// The wire never clears `isExhausted`, so a stale flag kept the phone
/// calling a live Antigravity pool exhausted while the Mac kept it live,
/// and the two surfaces told the owner different things about one account
/// (Kody review 2026-10-21). Every place the phone decides "is this
/// spent" now asks the shared rule.
func testThePhoneLetsAReadingOutrankTheProvidersExhaustedWord() throws {
let model = try readCompanionModelSource()
XCTAssertTrue(
model.contains("QuotaCapRule.isExhausted(remainingPercent: pct,"),
"a window row must decide through the shared rule"
)
XCTAssertFalse(
model.contains("|| (weeklyWin?.isExhausted ?? false)"),
"the wire's exhausted word must not outvote a weekly reading that reports room left"
)
XCTAssertFalse(
model.contains("(?? 100) <= 0"),
"the phone must not carry its own zero test beside the shared rule"
)
}

/// An exhausted weekly that reports no figure still has to publish a
/// reading, or the shared cap rule cannot bind it and the phone's cap line
/// never appears (Kody review 2026-10-21). The Mac does the same in its
/// Antigravity pool grouper.
func testAnExhaustedWeeklyWithNoFigureStillPublishesAReading() throws {
let model = try readCompanionModelSource()
XCTAssertTrue(
model.contains("QuotaCapRule.publishedReading("),
"the phone must publish its reading through the shared rule"
)
XCTAssertTrue(
model.contains("allowanceSpent: period == \"weekly\" && (weeklyExhausted || providerSaysSpent)"),
"an exhausted weekly with no figure must publish zero"
)
XCTAssertTrue(model.contains("(weeklyPct ?? 100) <= Self.weeklySpentThreshold"))
}

// MARK: - Local Snapshot Caching and Pull-to-Refresh Parity
Expand Down
54 changes: 47 additions & 7 deletions Tests/CodeCapsTests/WidgetTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,21 +79,61 @@ final class WidgetTests: XCTestCase {
)
}

/// The widget spells the cap rule out rather than importing the module, so
/// its threshold is the one place a copy of the number could creep back in.
/// The widget target compiles `QuotaWindowVocabulary.swift` by path, so
/// `QuotaCapRule` is in scope and the number is read from the rule that
/// owns it.
/// The widget target cannot import QuotaCore as a module, but it compiles
/// `QuotaWindowVocabulary.swift` by path, so the rule that owns the number
/// is in scope. The widget asks that rule now instead of restating the
/// comparison, which is what stops a copy of the question creeping back in
/// here where no test would see it.
func testWidgetReadsTheSharedSpentThreshold() throws {
let source = try readSource(at: Self.widgetPresentationURL)
XCTAssertTrue(
source.contains("percent <= QuotaCapRule.spentThreshold"),
"the widget must read the shared threshold, not carry a number of its own"
source.contains("QuotaCapRule.isExhausted(remainingPercent: percent, providerSaysExhausted: isExhausted)"),
"the widget must ask the shared rule, not carry a number of its own"
)
XCTAssertFalse(
source.contains("if let percent { return percent <= 0 }"),
"the widget must not keep the old exact-zero test"
)
XCTAssertFalse(
source.contains("percent <= QuotaCapRule.spentThreshold"),
"the widget must not restate the threshold comparison beside the rule"
)
}

/// The widget and the phone have to answer the same question the same way
/// about one payload.
///
/// The phone moved to the shared rule while the widget kept its own copy of
/// `(pct ?? 100) <= 0 || flag`, so the widget still believed the export's
/// stale exhausted word over a healthy reading and masked the five-hour row
/// as "n/a" while the app showed live figures (Sentry 18329264, Kody review
/// 2026-10-22). Every copy of that expression has to be gone.
func testWidgetDecidesExhaustionThroughTheSharedRule() throws {
let source = try readSource(at: Self.widgetPresentationURL)
XCTAssertFalse(
source.contains("<= 0 || (w.isExhausted ?? false)"),
"the widget must not keep the old per-window exhaustion test beside the shared rule"
)
XCTAssertFalse(
source.contains("<= 0 || (weeklyWin?.isExhausted ?? false)"),
"the widget's weekly must be decided through the shared rule too"
)
XCTAssertFalse(
source.contains("isExhausted: (f.remainingPercent ?? 100) <= 0"),
"the widget's five-hour row must not keep its own zero test"
)
XCTAssertTrue(
source.contains("let weeklyExhausted = QuotaCapRule.isExhausted(remainingPercent: weeklyPct,"),
"the widget's weekly must be decided through the shared rule"
)
XCTAssertTrue(
source.contains("let isExhausted = weeklyExhausted || QuotaCapRule.isAtCap(remainingPercent: controllingPct)"),
"a spent weekly must spend the pool even when the five-hour window still reports headroom"
)
XCTAssertTrue(
source.contains("QuotaCapRule.publishedReading(remainingPercent: w.remainingPercent,"),
"the widget must publish the same bounded reading the phone publishes"
)
}

func testProjectYmlDeclaresWidgetTargets() throws {
Expand Down
Loading
Loading