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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ This repo is a CortexKit-maintained Anthropic auth monorepo for OpenCode and Pi.
### Dependencies

- OpenTUI core and solid move to 0.6.6 together and the OpenCode 2 plugin SDK packages to 2.0.32, so each installs once. solid-js stays at 1.9.12, the exact version OpenTUI Solid 0.6.6 requires.
- Update common-auth to 0.22.0 so vault quota sweeps skip duplicate usage polls while insula starts or its upstream fetch has failed; requests also skip polls after an upstream failure.

## 2.1.2

Expand Down
4 changes: 2 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 37 additions & 5 deletions mutations.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1111,13 +1111,13 @@ broad_id = "{name}"
id = "vault-insula-joins-vault-account-id"
guards = "A background insula lookup must join on the roster row's vault account id (list_scoped account_id), never another row field such as the credential id."
file = "packages/core/src/native-vault-runtime.ts"
old = "swept = insulaReading(row.accountIdentity)"
new = "swept = insulaReading(row.credentialId) // NON-VACUITY BREAK: join on the credential id"
old = "swept = insulaLookup(row.accountIdentity)"
new = "swept = insulaLookup(row.credentialId) // NON-VACUITY BREAK: join on the credential id"
test_file = "packages/core/src/tests/native-vault-insula-quota.test.ts"
runner = "command"
command = ["bun", "scripts/run-mutation-bun.ts", "packages/core/src/tests/native-vault-insula-quota.test.ts", "{test}"]
test_count_pattern = "CKDEV executed {count} tests"
expect_red = ["background sweep takes a fresh insula reading and sends no usage GET", "the primary account is looked up in insula by its roster row account id, not its logical id", "a stale, missing or failed insula entry falls back to one usage GET for that account only", "runtimes on one connection file and token path share one insula source until all release it", "the Fable window from extraRateWindows drives scoped routing and the scoped killswitch floor", "the source switch is logged once per change with fixed codes and no identity"]
expect_red = ["background sweep takes a fresh insula reading and sends no usage GET", "the primary account is looked up in insula by its roster row account id, not its logical id", "runtimes on one connection file and token path share one insula source until all release it", "the Fable window from extraRateWindows drives scoped routing and the scoped killswitch floor", "the source switch is logged once per change with fixed codes and no identity"]
expect_message = '/error: expect\(received\)\.(toBe|toEqual)\(expected\)/'
broad_command = ["bun", "scripts/run-mutation-bun.ts", "--broad", "packages/core", "tmp/mutations/core.xml"]
broad_report = "tmp/mutations/core.xml"
Expand All @@ -1140,7 +1140,7 @@ broad_report = "tmp/mutations/core.xml"
broad_id = "{name}"
[[control]]
id = "vault-insula-reading-skips-usage-poll"
guards = "A current insula reading for a vault account must replace that account's usage GET; only a miss polls Anthropic."
guards = "A current insula reading for a vault account must replace that account's usage GET rather than polling Anthropic."
file = "packages/core/src/native-vault-runtime.ts"
old = ''' if (reading) {
const stored'''
Expand All @@ -1150,7 +1150,7 @@ test_file = "packages/core/src/tests/native-vault-insula-quota.test.ts"
runner = "command"
command = ["bun", "scripts/run-mutation-bun.ts", "packages/core/src/tests/native-vault-insula-quota.test.ts", "{test}"]
test_count_pattern = "CKDEV executed {count} tests"
expect_red = ["background sweep takes a fresh insula reading and sends no usage GET", "the primary account is looked up in insula by its roster row account id, not its logical id", "a stale, missing or failed insula entry falls back to one usage GET for that account only", "admission reads only the insula cache: never refreshes it, polls on a miss", "runtimes on one connection file and token path share one insula source until all release it", "the Fable window from extraRateWindows drives scoped routing and the scoped killswitch floor", "the source switch is logged once per change with fixed codes and no identity"]
expect_red = ["background sweep takes a fresh insula reading and sends no usage GET", "the primary account is looked up in insula by its roster row account id, not its logical id", "admission reads only the insula cache: never refreshes it, polls on a miss", "runtimes on one connection file and token path share one insula source until all release it", "the Fable window from extraRateWindows drives scoped routing and the scoped killswitch floor", "the source switch is logged once per change with fixed codes and no identity"]
expect_message = '/error: expect\(received\)\.(toBe|toEqual)\(expected\)/'
broad_command = ["bun", "scripts/run-mutation-bun.ts", "--broad", "packages/core", "tmp/mutations/core.xml"]
broad_report = "tmp/mutations/core.xml"
Expand Down Expand Up @@ -1440,3 +1440,35 @@ expect_message = "error: expect(received).toBe(expected)"
broad_command = ["bun", "scripts/run-mutation-bun.ts", "--broad", "packages/core", "tmp/mutations/core.xml"]
broad_report = "tmp/mutations/core.xml"
broad_id = "{name}"

[[control]]
id = "vault-insula-cold-skips-usage-poll"
guards = "A cold insula cache must defer background usage polling without changing stored quota, its due time or poll bookkeeping."
file = "packages/core/src/native-vault-runtime.ts"
old = "if (swept.miss === 'cold' || swept.miss === 'upstream-failed') {"
new = "if (swept.miss === 'upstream-failed') { // NON-VACUITY BREAK: restore cold fallback polling"
test_file = "packages/core/src/tests/native-vault-insula-quota.test.ts"
runner = "command"
command = ["bun", "scripts/run-mutation-bun.ts", "packages/core/src/tests/native-vault-insula-quota.test.ts", "{test}"]
test_count_pattern = "CKDEV executed {count} tests"
expect_red = ["background cold defers without a usage GET or poll bookkeeping"]
expect_message = "error: expect(received).toEqual(expected)"
broad_command = ["bun", "scripts/run-mutation-bun.ts", "--broad", "packages/core", "tmp/mutations/core.xml"]
broad_report = "tmp/mutations/core.xml"
broad_id = "{name}"

[[control]]
id = "vault-insula-upstream-failed-skips-usage-poll"
guards = "An insula upstream failure must defer background usage polling without changing stored quota, its due time or poll bookkeeping."
file = "packages/core/src/native-vault-runtime.ts"
old = "if (swept.miss === 'cold' || swept.miss === 'upstream-failed') {"
new = "if (swept.miss === 'cold') { // NON-VACUITY BREAK: restore upstream-failed fallback polling"
test_file = "packages/core/src/tests/native-vault-insula-quota.test.ts"
runner = "command"
command = ["bun", "scripts/run-mutation-bun.ts", "packages/core/src/tests/native-vault-insula-quota.test.ts", "{test}"]
test_count_pattern = "CKDEV executed {count} tests"
expect_red = ["background upstream-failed defers without a usage GET or poll bookkeeping"]
expect_message = "error: expect(received).toEqual(expected)"
broad_command = ["bun", "scripts/run-mutation-bun.ts", "--broad", "packages/core", "tmp/mutations/core.xml"]
broad_report = "tmp/mutations/core.xml"
broad_id = "{name}"
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"prepublishOnly": "bun run build"
},
"devDependencies": {
"@cortexkit/common-auth": "0.21.1"
"@cortexkit/common-auth": "0.22.0"
},
"dependencies": {
"@cortexkit/claustrum-client": "0.12.0",
Expand Down
8 changes: 4 additions & 4 deletions packages/core/scripts/check-native-type-closure.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ test('rejects unsupported published producer versions before copying declaration
producer,
'package.json',
JSON.stringify({
version: '0.21.0',
version: '0.21.1',
exports: { './store': { types: './dist/store.d.ts' } },
}),
)
Expand All @@ -124,7 +124,7 @@ test('rejects unsupported published producer versions before copying declaration
)

await expect(closeNativeTypeDeclarations(dist, producer)).rejects.toThrow(
'Expected published common-auth 0.21.1, got 0.21.0',
'Expected published common-auth 0.22.0, got 0.21.1',
)
expect(await readdir(dist)).not.toContain('internal-types')
})
Expand All @@ -137,7 +137,7 @@ test('copies the complete published relative declaration graph including cycles
producer,
'package.json',
JSON.stringify({
version: '0.21.1',
version: '0.22.0',
exports: { './store': { types: './dist/store/index.d.ts' } },
}),
)
Expand Down Expand Up @@ -273,7 +273,7 @@ test('packed Core types and Node runtime work in a fresh consumer without common
const manifest = JSON.parse(
await readFile(join(core, 'package.json'), 'utf8'),
)
expect(manifest.devDependencies['@cortexkit/common-auth']).toBe('0.21.1')
expect(manifest.devDependencies['@cortexkit/common-auth']).toBe('0.22.0')
expect(manifest.dependencies['@cortexkit/claustrum-client']).toBe('0.12.0')
expect(manifest.dependencies['@cortexkit/subc-client']).toBe('^0.22.0')
expect(manifest.dependencies['jsonc-parser']).toBe('^3.3.1')
Expand Down
4 changes: 2 additions & 2 deletions packages/core/scripts/check-native-type-closure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,9 @@ export async function closeNativeTypeDeclarations(
version: string
exports: Record<string, { types?: string }>
}
if (manifest.version !== '0.21.1')
if (manifest.version !== '0.22.0')
throw new Error(
`Expected published common-auth 0.21.1, got ${manifest.version}`,
`Expected published common-auth 0.22.0, got ${manifest.version}`,
)
const producerDist = join(producerRoot, 'dist')
const assets = join(distRoot, TYPE_ASSETS)
Expand Down
87 changes: 59 additions & 28 deletions packages/core/src/native-vault-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
ClaustrumConsumerError,
connectClaustrumScopedClient,
declineAccount,
type InsulaQuotaMiss,
type InsulaQuotaResult,
type InsulaQuotaSource,
mutateVaultRoster,
readClaustrumEnrollmentToken,
Expand Down Expand Up @@ -94,9 +96,10 @@ import { fetchOAuthAccountProfile } from './oauth-profile.ts'
import { NativeAuthorityError } from './pool-authority.ts'
import type { NativePoolPaths } from './pool-paths.ts'
import { createNativePoolStore } from './pool-store.ts'
import type {
QuotaReadingPurpose,
QuotaRequestDispatch,
import {
type QuotaReadingPurpose,
QuotaRefreshDeferred,
type QuotaRequestDispatch,
} from './quota-manager.ts'

export interface NativeVaultRuntimeOptions {
Expand Down Expand Up @@ -131,10 +134,12 @@ export interface NativeVaultRuntimeOptions {
* background quota cache may answer instead of Anthropic's usage endpoint.
* - 'background': a periodic sweep. Refresh insula's cache (at most once a
* minute per daemon and enrollment), look the route's account up in it,
* and skip the usage GET when it holds a current reading.
* and skip the usage GET for a current reading, a cold cache or an upstream
* failure. Skipped misses leave the account due for the next sweep.
* - 'admission': a request needs a reading. Look the served credential's
* account up in insula's cache only, never refreshing it, then poll on a
* miss.
* miss except an upstream failure, which uses a dated reading if attached
* and otherwise leaves quota unknown.
* Omitted: poll Anthropic directly (explicit user refreshes and other
* callers that must see the provider's own reading).
*/
Expand Down Expand Up @@ -517,27 +522,31 @@ export function createNativeVaultRuntime(
/**
* Cache-only lookup by the vault's account id for this account (the
* list_scoped or get_scoped account_id), never an email or label.
* common-auth returns only a current entry; anything we cannot map is a miss.
* Preserve insula's miss reason, even when the lookup also contains a dated reading.
*/
function insulaReading(
function insulaLookup(
accountId: string | undefined,
): InsulaClaudeQuota | undefined {
if (!insula || !accountId || shutdown.signal.aborted) return undefined
const current = insula.source.current('claude', accountId, clock())
return current
? normalizeInsulaClaudeQuota(current.entry, current.context)
: undefined
): InsulaQuotaResult<InsulaClaudeQuota> {
if (!insula || !accountId || shutdown.signal.aborted)
return { miss: 'unreachable' }
const result = insula.source.lookup('claude', accountId, clock())
const reading =
result.reading &&
normalizeInsulaClaudeQuota(result.reading.entry, result.reading.context)
if (result.miss) return { miss: result.miss, ...(reading && { reading }) }
return reading ? { reading } : { miss: 'unreachable' }
}
// The last source each route's guided quota reading came from, so a change
// is logged once rather than on every sweep.
const quotaSources = new Map<string, 'insula' | 'fallback'>()
const quotaSources = new Map<string, string>()
function noteQuotaSource(
routeId: string,
source: 'insula' | 'fallback',
reason: 'insula-reading' | 'no-insula-reading' | 'account-changed',
source: 'insula' | 'fallback' | 'skipped',
reason: 'insula-reading' | 'account-changed' | InsulaQuotaMiss,
) {
if (quotaSources.get(routeId) === source) return
quotaSources.set(routeId, source)
const transition = `${source}:${reason}`
if (quotaSources.get(routeId) === transition) return
quotaSources.set(routeId, transition)
if (quotaSources.size > 1000) {
const oldest = quotaSources.keys().next().value
if (oldest !== undefined) quotaSources.delete(oldest)
Expand Down Expand Up @@ -1050,11 +1059,15 @@ export function createNativeVaultRuntime(
const row = await rowFor(await runtime.read(), routeId)
// A background sweep asks insula about the roster row's account (its
// list_scoped account_id) before any credential is fetched.
let swept: InsulaClaudeQuota | undefined
let swept: InsulaQuotaResult<InsulaClaudeQuota> | undefined
if (purpose === 'background' && row) {
await refreshInsula()
check(signal)
swept = insulaReading(row.accountIdentity)
swept = insulaLookup(row.accountIdentity)
if (swept.miss === 'cold' || swept.miss === 'upstream-failed') {
noteQuotaSource(row.routeId, 'skipped', swept.miss)
throw new QuotaRefreshDeferred()
}
}
const state = await readNativeRuntime(paths.runtime, paths.storageId)
const before =
Expand Down Expand Up @@ -1082,8 +1095,10 @@ export function createNativeVaultRuntime(
// A served receipt is still needed with an insula reading: the
// reading is published bound to this credential version.
let reading: InsulaClaudeQuota | undefined
let miss: 'no-insula-reading' | 'account-changed' =
'no-insula-reading'
let lookup: InsulaQuotaResult<InsulaClaudeQuota> = {
miss: 'unreachable',
}
let accountChanged = false
if (purpose === 'background') {
// The sweep joined the roster row's account; publish it only for
// a credential served for that same account.
Expand All @@ -1092,17 +1107,30 @@ export function createNativeVaultRuntime(
row?.accountIdentity &&
receipt.assertedAccountIdentity === row.accountIdentity
)
reading = swept
else if (swept) miss = 'account-changed'
lookup = swept
else if (swept) accountChanged = true
} else if (purpose === 'admission') {
// Only the account id the vault asserted for this credential.
reading = insulaReading(receipt.assertedAccountIdentity)
lookup = insulaLookup(receipt.assertedAccountIdentity)
}
// Admission polls on every miss except an upstream failure. A dated
// reading attached to that failure avoids another GET to a refusing account.
reading =
!lookup.miss ||
(purpose === 'admission' && lookup.miss === 'upstream-failed')
? lookup.reading
: undefined
if (purpose)
noteQuotaSource(
routeId,
reading ? 'insula' : 'fallback',
reading ? 'insula-reading' : miss,
row?.routeId ?? routeId,
reading
? 'insula'
: lookup.miss === 'upstream-failed'
? 'skipped'
: 'fallback',
accountChanged
? 'account-changed'
: (lookup.miss ?? 'insula-reading'),
)
if (reading) {
const stored = matchingBefore(receipt)?.quota
Expand All @@ -1111,6 +1139,8 @@ export function createNativeVaultRuntime(
stored ? fromNativeQuotaMap(stored) : undefined,
)
}
if (purpose === 'admission' && lookup.miss === 'upstream-failed')
return undefined
return fetchOAuthQuotaSnapshot({
accessToken: receipt.accessToken,
fetchImpl: transport,
Expand All @@ -1133,6 +1163,7 @@ export function createNativeVaultRuntime(
},
dispatch,
)
if (!result.value) throw new QuotaRefreshDeferred()
const quota = {
...result.value,
accountIdentity: result.receipt.assertedAccountIdentity,
Expand Down
19 changes: 19 additions & 0 deletions packages/core/src/quota-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ export type QuotaRefreshResult = {
*/
export type QuotaReadingPurpose = 'background' | 'admission'

/** Insula is still fetching or its upstream failed; preserve quota without recording a poll. */
export class QuotaRefreshDeferred extends Error {}

/** Pace a physical usage GET and verify the selected credential is still eligible before sending. */
export type QuotaRequestDispatch = (
send: () => Promise<Response>,
Expand Down Expand Up @@ -1171,6 +1174,14 @@ export class QuotaManager {
)
return { quota: completedQuota, fetched: true }
} catch (error) {
if (error instanceof QuotaRefreshDeferred) {
return {
quota: this.getMain(mainAccountId)?.quota ?? {
accountIdentity: mainAccountId,
},
fetched: false,
}
}
this._handleMainFetchError(error, generation)
throw error
} finally {
Expand Down Expand Up @@ -1292,6 +1303,14 @@ export class QuotaManager {
await fileLock.release()
}
} catch (error) {
if (error instanceof QuotaRefreshDeferred) {
return {
quota: this.getFallback(accountId, account)?.quota ?? {
accountIdentity: accountId,
},
fetched: false,
}
}
this._handleFallbackFetchError(accountId, error, generation)
throw error
}
Expand Down
Loading
Loading