# 07 — Run-out projection semantics (https://jackin.tailrocks.com/research/product/desktop/usage-provider-apis/07-runout-projection/)



## Summary [#summary]

Provider APIs do not supply run-out forecasts, and client-derived depletion estimates are outside the jackin❯ limits-only usage contract.

## Question and scope [#question-and-scope]

(1) What pace/reset math does `jackin-usage` already compute? (2) Which run-out phrases and formulas appear in competitor or compatibility evidence? (3) Which API fields would such derivations require, and where are they absent? (4) Why do these findings not expand the jackin❯ limits-only contract?

This chapter records competitor and API evidence. It does not recommend a projection surface for jackin❯.

## Method [#method]

**Method:** codebase read + web (READMEs only)
&#x2A;*Verification cutoff:** 2026-07-24

## Findings [#findings]

### Q1 — What jackin-usage already computes [#q1--what-jackin-usage-already-computes]

* `quota_pace_label(remaining_percent: Option<u8>, reset_at: Option<i64>, window_seconds: Option<i64>, now: i64) -> Option<String>` is the sole pace producer — `crates/jackin-usage/src/usage/format.rs:164-191` (confidence: HIGH)
* Exact math: `reset_in = max(reset_at − now, 0)` (format.rs:171); `window_seconds = max(window_seconds, 1)` (format.rs:172); if `reset_in > window_seconds` → `None`, pace suppressed entirely (format.rs:173-175); `time_left_percent = reset_in / window_seconds × 100` (format.rs:176); `delta = remaining_percent − time_left_percent` (format.rs:183) (confidence: HIGH)
* Label mapping: `|delta| ≤ 2.0` → `"On pace"`; `delta > 0` → `"{round(delta)}% in reserve"`; `delta < 0` → `"{round(−delta)}% in deficit"` — so **deficit N = round(time\_left\_percent − remaining\_percent) when positive**, reserve is the mirror — format.rs:184-190 (confidence: HIGH)
* In-code comment attributes this to the "CodexBar pace model": more quota than time = reserve, burning faster than the clock = deficit — format.rs:177-182 (confidence: HIGH that the comment says so; attribution itself unverified, see Q3)
* Unit tests pin the formula: 60% left with 90% of window remaining → `"30% in deficit"`; 90% left with 60% remaining → `"30% in reserve"`; 50/50 → `"On pace"` — `crates/jackin-usage/src/usage/tests.rs:2821-2835` (test `quota_pace_label_uses_codexbar_reserve_deficit_onpace`) (confidence: HIGH)
* Integration coverage: Codex Session bucket `"15% in reserve"` (tests.rs:2255); Codex Weekly bucket falls back to the window label `"1 week window"` when pace is `None` (tests.rs:2259; fallback at `crates/jackin-usage/src/usage/codex.rs:696-698`); Kimi Rate Limit `"30% in reserve"`, Kimi Weekly pace `None` (tests.rs:3318, 3324) (confidence: HIGH)
* `reset_label(reset_at, now)`: `"Resets now"` when `reset_at ≤ now`; default Countdown style `"Resets in {compact_duration} ({local clock})"`; ExactClock alternative `"Resets {local clock}"` — format.rs:111-128. Duration formatting `"{d}d {h}h"` / `"{h}h {m}m"` / `"{m}m"` via `compact_duration_label` — format.rs:193-208 (confidence: HIGH). So `"Resets in <duration>"` **is already shipped**.
* `remaining_from_fraction`: `remaining = 100 − min(used, 100)` as `u8`, where used resolves a fraction-vs-percent ambiguity (`value ≤ 1.0` → ×100) and non-finite/negative inputs (provider "unknown" sentinels) yield `None` — format.rs:39-47, 62-74 (confidence: HIGH). Remaining is quantized to whole percent (`u8`).
* `window_seconds` is supplied per provider at probe time, not by the protocol: Claude constants `CLAUDE_SESSION_WINDOW_SECONDS = 5h`, `CLAUDE_WEEKLY_WINDOW_SECONDS = 7d` (`crates/jackin-usage/src/usage/claude.rs:447, 450`), mapped by limit `kind` (claude.rs:555-576), pace computed in `into_bucket` (claude.rs:505-506); Codex uses API `limit_window_seconds` or `window_duration_mins × 60` (`crates/jackin-usage/src/usage/codex.rs:389-392`); Kimi supplies window seconds only for the `"Rate Limit"` bucket (`crates/jackin-usage/src/usage/kimi.rs:252-256`) (confidence: HIGH)
* The capsule TUI **already renders "Lasts until reset" today**, derived from the pace token, not from a burn-rate projection: in `usage_stacked_bucket_detail_rows`, a detail equal to `"On pace"` or ending `" in reserve"` sets a flag (`crates/jackin-capsule/src/tui/components/dialog_widgets/usage.rs:754-755`); if the flag is set, a `"Resets…"` detail exists, and no `"Runs out…"` detail is present, `"Lasts until reset"` is pushed into the right column (usage.rs:760-764) (confidence: HIGH). Note: `"On pace"` covers delta down to −2, so a bucket slightly *behind* pace still shows "Lasts until reset".
* `"Runs out in <duration>"` is **consumed but never produced**: the dialog routes any detail starting `"Runs out"` to the right column (dialog\_widgets/usage.rs:751) and suppresses the synthesized "Lasts until reset" when one is present (usage.rs:762), but no code in the repo generates the string. It appears only in test fixtures as an injected composite `pace_label` `"31% in deficit · Runs out in 21h 45m"` — `crates/jackin-capsule/src/tui/components/dialog/tests.rs:1587, 1643`, asserted rendered at tests.rs:1624-1625 — showing `pace_label` with `" · "` separators is the intended carrier (splitter `usage_quota_bucket_detail_parts`, dialog\_widgets/usage.rs:846) (confidence: HIGH)
* `"Projected empty"` appears nowhere in the repository (repo-wide grep, zero hits) (confidence: HIGH)
* `pace_label` is persisted as an opaque string column in the snapshot store — `crates/jackin-usage/src/usage_snapshot_store.rs:213` (schema), :450 (bucket→row) (confidence: HIGH)

### Q2 — Projection formulas found in the evidence (analysis only) [#q2--projection-formulas-found-in-the-evidence-analysis-only]

* Input availability in the bucket model: `QuotaBucketView` carries `remaining_percent: Option<u8>` (`crates/jackin-protocol/src/control.rs:600-601`), `resets_at: Option<i64>` (control.rs:604-608), and preformatted `reset_label`/`pace_label` strings (control.rs:602-603, 612-613) — but **no `window_seconds` field**. Window duration exists only provider-side at probe time (Q1) and is not in the protocol bucket nor in the store schema (column list, usage\_snapshot\_store.rs:190-218). Any projection computed downstream of bucket construction lacks the window duration unless the contract changes; computed at probe time (where `quota_pace_label` runs today) all three inputs are present (confidence: HIGH)
* Sampling timestamps exist: `FocusedUsageView.fetched_at_epoch` (control.rs:407); store column `fetched_at` (usage\_snapshot\_store.rs:203) (confidence: HIGH)

**(a) "Runs out in" / "Projected empty in" — burn-rate variants derivable from the available inputs:**

* Variant A — linear-from-window-start (no history needed). Inputs: the exact trio `quota_pace_label` already takes (format.rs:164-169). Define `elapsed = window_seconds − reset_in`, `used = 100 − remaining`. Burn rate `r = used / elapsed` (%/s); `runs_out_in = remaining / r = remaining × elapsed / used`. Failure modes: `used = 0` → division by zero (never runs out); `elapsed = 0` at window start → undefined; early-window jumpiness — small `elapsed` amplifies the whole-percent `u8` quantization of `remaining` (format.rs:45-47, control.rs:601); assumes the window started at `resets_at − window_seconds`, which is wrong for rolling windows; `reset_in > window_seconds` already suppressed by the existing guard (format.rs:173-175) (confidence: HIGH that inputs/derivation follow from cited code; the variant itself is a derivation, not shipped code)
* Algebraic identity linking Variant A to the shipped pace math: `runs_out_in ≥ reset_in` ⇔ `remaining × (window − reset_in) ≥ (100 − remaining) × reset_in` ⇔ `remaining × window ≥ 100 × reset_in` ⇔ `remaining_percent ≥ time_left_percent` ⇔ `delta ≥ 0`. So under Variant A, the "Lasts until reset" boolean is exactly the sign of the existing pace delta (format.rs:176-183) — the current TUI heuristic (Q1, dialog\_widgets/usage.rs:754-764) is that model's boolean, off by the ±2-point "On pace" band (confidence: HIGH, pure algebra over cited formulas)
* Variant B — nominal-pace projection (degenerate; no burn estimate). Assume plan-nominal burn `r = 100 / window_seconds`; `runs_out_in = remaining × window_seconds / 100`. Inputs: `remaining` + `window_seconds` only (no `resets_at` needed for the duration). Yields the **same** "lasts until reset" boolean as Variant A (same inequality), but the duration reflects nominal, not actual, consumption — it contradicts a "current burn rate" reading of the phrase. Failure modes: none numerically (no division by observed data), but semantically misleading for idle or bursty accounts (confidence: HIGH as derivation)
* Variant C — sampled burn from consecutive snapshots. `r = (remaining_prev − remaining_now) / (fetched_now − fetched_prev)`; `runs_out_in = remaining_now / r`. Data requirement: ≥ 2 timestamped snapshots per bucket. Availability: the refresh loop holds exactly one prior in-memory view per cache key and reads it before overwriting (`self.snapshots: HashMap<String, CachedUsage>` — `crates/jackin-usage/src/usage.rs:192`; read-then-insert at usage.rs:521-526), so one delta sample is obtainable in-memory; the durable store retains no history (Q4). Failure modes: reset crossing between samples → `remaining` rises → negative `r`; slow burn + 1% quantization + refresh cadence → measured delta 0 → `r = 0` → projection flaps between "never" and finite; single-delta estimates are jumpy and smoothing (e.g. EWMA) needs more than one prior sample; division by zero when `fetched_now = fetched_prev` (confidence: HIGH for the cited data availability; the variant is a derivation)

**(b) "Lasts until reset":**

* Definition derivable from any variant: projected run-out instant `now + runs_out_in ≥ resets_at`. Under Variants A and B this reduces to `remaining_percent ≥ time_left_percent` (identity above); under Variant C it is an independent comparison using the sampled `r` (confidence: HIGH as derivation)
* jackin❯ already ships the phrase as a pace-token heuristic (Q1): `"On pace"`-or-`"in reserve"` + a `"Resets"` detail + no `"Runs out"` detail → `"Lasts until reset"` — dialog\_widgets/usage.rs:746-764 (confidence: HIGH)

### Q3 — CodexBar / OpenUsage README cross-check (READMEs only, clean-room) [#q3--codexbar--openusage-readme-cross-check-readmes-only-clean-room]

* CodexBar README ([https://github.com/steipete/CodexBar](https://github.com/steipete/CodexBar)) describes usage display only as meters + reset countdowns: "Provider-specific usage meters with reset countdowns."; "Per-provider session, weekly, and monthly windows with countdowns to the next reset"; "The menu bar icon is a tiny usage meter." It contains **no** statements about pace, projection, burn rate, run-out, deficit, reserve, time-to-empty, or "lasts until" semantics — checked twice (repo page and raw README), consistent results (confidence: MED — read via WebFetch summarizer, quotes consistent across two independent fetches but not byte-verified)
* OpenUsage README ([https://github.com/robinebers/openusage](https://github.com/robinebers/openusage)) names the feature without defining it: "Provider-grouped meters with live reset countdowns and pace indicators." No definition of the pace computation and no run-out/projection/deficit/reserve/time-to-empty language anywhere in the README — checked twice (repo page and raw README) (confidence: MED — same WebFetch caveat)
* Consequence: the jackin❯ comment "CodexBar pace model" (format.rs:177) and the target phrases "Runs out in" / "Projected empty in" / "Lasts until reset" **cannot be corroborated or refuted from the allowed public sources**; the reserve/deficit/on-pace semantics are documented only by jackin❯ code and tests (confidence: HIGH that the READMEs are silent, per the MED-confidence fetches above)

### Q4 — Snapshot store history retention [#q4--snapshot-store-history-retention]

* The store retains **only the latest row per bucket**, not history: `UNIQUE(provider, account_key_hash, source, window_kind)` — usage\_snapshot\_store.rs:217 — with `INSERT … ON CONFLICT(provider, account_key_hash, source, window_kind) DO UPDATE SET …` overwriting every field including `remaining_percent`, `resets_at`, `fetched_at`, `pace_label` — usage\_snapshot\_store.rs:347-368 (confidence: HIGH)
* Test proof: two sequential `store_usage_snapshot` calls on a 2-bucket view still yield exactly 2 rows, with the Session row's `remaining_percent` replaced by the second write's value (25) and `fetched_at` advanced — `crates/jackin-usage/src/usage_snapshot_store/tests.rs:110-139` (test `account_snapshot_rows_are_persisted_and_upserted`) (confidence: HIGH)
* Therefore a sampled burn rate (Q2 Variant C) cannot be computed from the durable store as-is; the only prior sample currently available is the single in-memory `CachedUsage` view per cache key (usage.rs:192, 521-526), which supports one delta and no smoothing window (confidence: HIGH)

### Dead ends and contradictions [#dead-ends-and-contradictions]

* No producer of `"Runs out in"` exists anywhere in the repository — repo-wide grep across `crates/` and `src/` finds only the TUI consumer (dialog\_widgets/usage.rs:751, 762) and injected test fixtures (dialog/tests.rs:1587, 1643). Ruled out that the projection is already computed somewhere upstream.
* No occurrence of `"Projected empty"` or any burn-rate identifier (`burn_rate`, "time to empty") in product code — the phrase family is entirely unimplemented.
* `window_seconds` checked for persistence: absent from both the protocol `QuotaBucketView` (control.rs:593-628) and the store schema (usage\_snapshot\_store.rs:190-218) — ruled out as a downstream-available input.
* Mild contradiction: the TUI's shipped "Lasts until reset" heuristic fires for `"On pace"` (delta as low as −2), i.e. a bucket marginally behind pace is still labeled as lasting until reset — dialog\_widgets/usage.rs:754-755 vs. format.rs:184-185. Consistent with rounding tolerance, inconsistent with a strict `delta ≥ 0` reading.
* CodexBar README fetched twice specifically hunting projection language — none present; the in-code "CodexBar pace model" attribution (format.rs:177) cannot be verified from the README, and source reading is forbidden (clean-room).
* No embedded instructions were encountered in any fetched or read content (code, tests, READMEs) — nothing to flag.

## Implications for jackin❯ [#implications-for-jackin]

jackin❯ should not produce or display client-derived run-out projections. Usage surfaces remain limited to provider-authoritative used or remaining percentages, reset windows, plan status, freshness, and provenance. Existing projection consumers should not be treated as a product requirement.

## Limitations and unknowns [#limitations-and-unknowns]

* CodexBar displays "Runs out in 4d 21h" and "Lasts until reset" on its homepage; release notes confirm the bare "Projected empty" label. OpenUsage uses "\~N% left at reset" instead. The backing math remains unpublished — [chapter 10](/research/product/desktop/usage-provider-apis/10-provenance-and-misc/).
* Claude weekly and Grok weekly use fixed slots; Claude's five-hour session is anchored on server `resets_at`; Codex exposes a discrete window with `window_start = reset_at − limit_window_seconds`, though usage may not start at zero. Variant A is therefore well-founded when anchored on `resets_at` — [chapters 09](/research/product/desktop/usage-provider-apis/09-claude-followups/) and [10](/research/product/desktop/usage-provider-apis/10-provenance-and-misc/).
* The intended producer of the composite fixture `"31% in deficit · Runs out in 21h 45m"` (dialog/tests.rs:1587) — the TUI contract anticipates a producer that does not exist; whether it was planned for `jackin-usage` probe-time or a Desktop-side computation is unrecorded.
* README quotes carry a WebFetch-summarizer intermediary; consistent across two fetches each but not byte-verified against raw file bytes.

## Sources [#sources]

* <RepoFile path="crates/jackin-usage/src/usage/format.rs" /> — current pace and reset formulas.
* <RepoFile path="crates/jackin-usage/src/usage_snapshot_store.rs" /> — snapshot retention contract.

## Related work [#related-work]

* [Provider API coverage](/research/product/desktop/usage-provider-apis/)
* [Provider provenance and unresolved semantics](/research/product/desktop/usage-provider-apis/10-provenance-and-misc/)
