Join provider refreshes and prevent scoped false zeros - #1235
Merged
iamtoruk merged 20 commits intoSep 2, 2026
Conversation
avs-io
force-pushed
the
codex/provider-reconciliation-races
branch
from
September 2, 2026 11:15
699db51 to
735b20d
Compare
avs-io
force-pushed
the
codex/provider-reconciliation-races
branch
from
September 2, 2026 13:46
735b20d to
c9c5bc0
Compare
…#1132 Two open branches claim daily-cache version 30, so a v30 file on disk carries unknown accounting. Bump this branch to 31 and move the Hermes pending-rederive contract version with it, so a v30 file written under getagentseal#1132's accounting is treated as a foreign prior version: carried forward per slice, never adopted as the finalized base. The getagentseal#946 copilot fixture seeded itself at DAILY_CACHE_VERSION - 5, a moving target that crossed the copilot contract boundary (26) at this bump and silently stopped granting the entitlement those tests exist to check. Pinned to 25, which is the fixed historical version the fixture actually means. Disclosure: the bump re-derives every day whose sources still survive, so finalized history shifts. On the reviewer's machine Claude moved -0.78 percent. That is a corrected figure replacing a stale cached one, not a loss, but it is a visible change to numbers users have already seen.
…s with shrunken parses The provider-scoped overlay blind-set a fresh slice for every date it could produce one. A settled day's sources age off disk continuously, so that fresh slice is a lower bound, not a correction, and the overlay replaced finalized cache days with whatever the surviving files still explained. It ran over the whole period and over the whole history, so the scoped view could report a fraction of the same day the all-provider view served off the same cache. The overlay now routes through mergeDayEntries with guardPartialSurvival: fresh may FILL a (date, provider) the cache lacks and still wins inside the settle window, but it can no longer SHRINK a settled day the cache already holds. Filling has to keep working, because on a cold cache the scoped parse is the only source a historical day has. Measured on this machine over 2026-08-03..2026-09-01, comparing scoped --provider claude against the claude slice of the unscoped run: before $16649.42 / 81812 calls (short by $291.70 and 2507 calls) after $16941.12 / 84319 calls (exact match, both fields)
…pend hasUsage is a new payload field, so every RELEASED CLI omits it. Both decoders treated the absent case as "derive from cost", which is the strict reading of a field that is not there: a provider with $0 spend in the period vanished from the picker even though the user has it and is actively using it on an included subscription. Absent now means visible on both surfaces. The strict signal still applies wherever the CLI actually emits the field, so an explicit false still hides an idle discovery row. Settings.tsx carried the same fallback and is fixed with it.
The loader reads only the v3 path, so a hermes-session-ledger.v1.json from an earlier install sat in the cache dir unread and undeleted. It is dead weight that can only mislead: the parse-version bump forces a cold re-parse that rebuilds every cursor from source, so nothing in it is recoverable. Unlink it when the v3 file is absent. The v2 test this replaces was vacuous. It wrote a v2 file and asserted the ledger was empty, but the loader never looks at a v2 path, so it passed whatever the code did. The replacement uses a real v1 fixture and asserts both halves: the ledger loads empty AND the v1 file is gone afterwards.
…-flight fetch The interactive refresh path bailed with false whenever the key was already being fetched. recoverFromStuckLoading() reads that return value, so a healthy fetch that was simply still running was announced as a failed recovery, and a second interactive refresh threw away a result that was moments from landing. Join the in-flight fetch and report on its outcome, which is what the quiet path already did. Adds a DataClient fetch hook under DEBUG: slot ownership and acceptance ordering are only observable when two REAL fetches overlap, which needs a fetch whose completion the test controls rather than a spawned CLI.
inFlightKeys held only a timestamp, so the slot had no notion of WHICH fetch owned it. The watchdog and the display-sleep reset both evict an occupant, and a later fetch then legitimately claims the same key. When the evicted fetch finally returned, its defer cleared the new owner's slot and resumed every task parked on it, so a waiter woke on an empty cache and reported failure while the real fetch was still running. The slot now carries a token. A fetch releases it only if it still owns it; the watchdog and the resets take an explicit force path, because they are declaring the slot dead rather than reporting a fetch that ended.
…every tab click To guarantee the all-provider slice was cached before a scoped result could be accepted, the code simply awaited the all-provider fetch BEFORE starting the scoped one. That is two full parses back to back on every tab click, and the ordering it bought is only needed at acceptance time, not at start time. Both fetches now start together. fetchPayload awaits the all-provider task after its own fetch has already returned and before the consistency check reads it, so the evidence is still present when the scoped result is judged. The resident-bypassing cold fetch was already conditional on a contradiction actually being detected; it is unchanged.
…eing served When a scoped payload disagreed with its all-provider slice, the code re-ran the fetch bypassing the resident child and, if it STILL disagreed, threw. That verified payload is what the CLI actually reports for the provider, so throwing traded a real number for an error banner and left the tab with nothing at all. The payload is now cached with a contradictsAll flag on CachedPayload (add-only) and served, and the popover carries one subdued secondary line saying the total may be incomplete. The refresh paths treat an already-verified contradiction as usable, so a poll cannot re-run the one-shot parse forever.
… under the selected tab Switching providers stashed the outgoing payload and rendered it under the new tab until that tab's own fetch landed. That is not continuity, it is a wrong number: opening the Hermes tab printed the all-provider total, and the test suite pinned that behaviour by asserting $12 under Hermes and $12.34 under Cursor. The fallback is gone. With no cached data for the selection, `payload` is empty, `hasCachedData` is false, and the popover renders the loading state it already has for a cold key, so no dollar figure is shown for a provider whose data has not arrived. AgentTabStrip and MenuBarContent take the same hasCachedData gate for the same reason.
ProviderFilter.cliArg is both the --provider argument and the id the payload-consistency guard matches against providerDetails. A case whose cliArg is not a registry name silently disables that guard for its tab: the lookup finds no detail row, so no contradiction is ever detected. Nothing in either language can see both sides, so the contract is checked from the TypeScript side, where the registry is importable and the Swift source is readable. Table-driven over all 30 enum cases. No mapping needed fixing: .kimiCode already yields `kimicode`, which is the registry name (`kimi-code` is not a provider the CLI knows), and every other case resolves too. Setting .kimiCode to `kimi-code` makes this suite fail, which is the check working.
iamtoruk
marked this pull request as ready for review
September 2, 2026 18:51
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Dependency
Stacked on #1234. Review the final three commits for this slice; once #1234 lands, this PR reduces to four macOS files.
Summary
hasUsagesignalsVerification
AppStoreRefreshRecoveryTestsServeConnectionTestsacceptance-a1ec918everified live: Hermes$0.48, 7 calls, 7 sessions; Cursor$0.28, 7 calls, 7 sessionsThis remains draft until #1234 is accepted and the stacked diff is reviewed in that state.