Skip to content

fix(quota): one reading decides every exhausted question, and a spent window is not a hidden one - #330

Merged
jaywedgeworth22 merged 4 commits into
mainfrom
minimax/quota-cap-exhaustion
Oct 27, 2026
Merged

jaywedgeworth22 merged 4 commits into
mainfrom
minimax/quota-cap-exhaustion

Conversation

@jaywedgeworth22

@jaywedgeworth22 jaywedgeworth22 commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

Fixes CC-184

Summary

Unifies all quota-exhausted decisions on the shared QuotaCapRule and decouples "allowance is spent" from "reading must be hidden".

Previously the card / at-cap line treated near-zero readings (e.g.  0.3-0.4%) as spent while the Mac alert required an exact zero (remaining > 0) and the export used a local zero test.  The same window was therefore shown as "At cap" in one surface and quiet / live in another.  skip also followed the exhausted state, so a spent window disappeared from picture-in-picture with its cap line while the menu bar still announced the cap.

What changed

  • Exhaustion alert (MonitorModel): fires on QuotaCapRule.isAtCap(remainingPercent:) instead of remaining > 0, so it fires at the same threshold as the "At cap" line and companion surfaces.  Alert body reworded from "has no quota remaining." to "has reached its limit."
  • Wire flag (QuotaPublisher): exported isExhausted now uses QuotaCapRule.isExhausted(remainingPercent:providerSaysExhausted:), where reading at/under threshold is spent, provider flag is fallback for windows with no figure and does not overrule a healthy reading.
  • Visibility (QuotaWindow.normalizedForExport): skip no longer equals isExhausted.  skip now means "unbelievable reading": pre-existing skip, exact 0, or provider isExhausted when the reading disagrees (bounded > spentThreshold) or when there is no reading.  A near-zero spent window stays visible; an exact zero stays hidden with reason "quota exhausted".  Logic is stable when derivation is applied twice on the display / PiP path.
  • Settings copy (SettingsNotificationsPage): updated from "fires at zero remaining" to "alerts when 0.5% or less remains", and the threshold is formatted so a whole-number rule prints 1% rather than 1.0%.
  • Skip reason (QuotaWindow.normalizedForExport): the derived reason is attached only to a window that is hidden and spent, so a provider that hides a healthy window without saying why no longer puts "quota exhausted" on the wire.  The provider's own reason is passed through untouched.

Coverage

Adds ExhaustionCapRuleTests for the shared rule and source pinning, and extends LocalQuotaSnapshotTests, QuotaPublisherTests, and PipInteractionTests for near-zero visibility, skip-reason preservation, second-pass stability, and wire-flag agreement.

Review follow-ups

A copy pass on this branch raised two nits, both taken here rather than in a follow-up.  The threshold is a Double, so a plain interpolation would print 1.0% once the rule reaches a whole number, and it is now formatted with a fraction length of zero to one.  The footer also named an internal rule rather than what the reader sees, so it now reads "Quota Exhausted alerts when 0.5% or less remains."  A test asserts the formatting, so the whole-number form cannot come back.

Kody's round on this branch found that the derived skip reason could claim an exhaustion a window did not have: a provider could hide a window that still had room, and the export attached "quota exhausted" to it, so a consumer filtering on the reason would act on a false exhaustion.  The reason now attaches only to a window that is hidden and spent.  The gate is copy.isExhausted and not copy.skip alone, on purpose, because a near zero window is deliberately visible in this branch, and giving it a reason would invite a consumer to hide exactly the window this pull request keeps on screen.  The test asserts that the hidden and healthy window carries no reason, the hidden and spent window keeps one, and the visible near zero window carries none.  The non-vacuity proof has a fourth variant that restores the old gate, and that variant fails on the test.

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
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
@linear-code

linear-code Bot commented Oct 26, 2026

Copy link
Copy Markdown

CC-184

@kody-ai

kody-ai Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug ✅
Performance ✅
Security ✅
Business Logic ✅

Access your configuration settings here.

​

@jaywedgeworth22
jaywedgeworth22 enabled auto-merge (squash) October 26, 2026 22:23
@kody-ai

kody-ai Bot commented Oct 26, 2026

Copy link
Copy Markdown

kody code-review Business Logic medium

Task description is insufficient for business logic validation.

🤔 Insufficient Task Context

I found a task linked to this PR, but it only contains minimal information (title only, no description or acceptance criteria). To perform a meaningful business rules validation, I need more details.

🔍 What I need to validate:

  • Business requirements and acceptance criteria
  • Expected behavior and business rules
  • Edge cases and constraints to consider

💡 How to improve the task context:

  • Add a description to the linked ticket
  • Include acceptance criteria or business rules
  • Describe the expected behavior after the change

⚠️ Important:

A task title alone is not sufficient to determine whether the implementation is correct or complete.


💡 This validation runs automatically only on the first review of a pull request. To run it again, comment @kody -v business-logic.

Prompt for LLM
Task description is insufficient for business logic validation.

<!-- task_context_status:weak -->
## 🤔 Insufficient Task Context

I found a task linked to this PR, but it only contains minimal information (title only, no description or acceptance criteria). To perform a meaningful business rules validation, I need more details.

### 🔍 What I need to validate:
- Business requirements and acceptance criteria
- Expected behavior and business rules
- Edge cases and constraints to consider

### 💡 How to improve the task context:
- Add a description to the linked ticket
- Include acceptance criteria or business rules
- Describe the expected behavior after the change

### ⚠️ Important:
A task title alone is not sufficient to determine whether the implementation is correct or complete.

---
> 💡 This validation runs automatically only on the first review of a pull request. To run it again, comment `@kody -v business-logic`.

​

Comment thread Sources/QuotaCore/QuotaModels.swift Outdated
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
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.
@jaywedgeworth22
jaywedgeworth22 merged commit 4bfb412 into main Oct 27, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant