@opengeni/subscriptions 4.3.3-canary.39786771332001 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -854,7 +854,7 @@ function buildSubscriptionUsagePool(input) {
854
854
  windows2.set(reading.window, readings);
855
855
  readingsByAccount.set(reading.connectionId, windows2);
856
856
  }
857
- const windows = WINDOW_IDS.map((id) => {
857
+ const readWindows = WINDOW_IDS.map((id) => {
858
858
  const duration = WINDOW_DURATION[id];
859
859
  const data = input.accounts.map((account) => {
860
860
  const snapshot = [...account.windows.filter((window) => window.id === id)].sort(
@@ -863,10 +863,10 @@ function buildSubscriptionUsagePool(input) {
863
863
  const observedAt = finite(snapshot?.observedAt) && snapshot.observedAt <= input.now ? snapshot.observedAt : null;
864
864
  const observedReset = finite(snapshot?.resetsAt) ? snapshot.resetsAt : null;
865
865
  const blockedUntil = account.blocked?.[id];
866
- const blocked = finite(blockedUntil) && blockedUntil > input.now;
867
- const resetsAt = blocked ? blockedUntil : observedReset;
866
+ const blocked = blockedUntil === null || finite(blockedUntil) && blockedUntil > input.now;
867
+ const resetsAt = blocked ? blockedUntil ?? null : observedReset;
868
868
  const current = observedAt !== null && (observedReset === null ? input.now - observedAt <= FRESHNESS[id] : observedReset > input.now && observedReset <= observedAt + duration);
869
- const remainingPercent2 = blocked ? 0 : current && snapshot && percent(snapshot.usedPercent) ? 100 - snapshot.usedPercent : null;
869
+ const remainingPercent = blocked ? 0 : current && snapshot && percent(snapshot.usedPercent) ? 100 - snapshot.usedPercent : null;
870
870
  const readings = [...readingsByAccount.get(account.id)?.get(id) ?? []];
871
871
  if (snapshot && percent(snapshot.usedPercent) && observedAt !== null && observedAt >= since) {
872
872
  readings.push({ usedPercent: snapshot.usedPercent, observedAt, resetsAt: observedReset });
@@ -877,7 +877,7 @@ function buildSubscriptionUsagePool(input) {
877
877
  account,
878
878
  observedAt,
879
879
  resetsAt,
880
- remainingPercent: remainingPercent2,
880
+ remainingPercent,
881
881
  segments,
882
882
  planWeight,
883
883
  calibrated: calibratedCapacity(segments),
@@ -902,14 +902,31 @@ function buildSubscriptionUsagePool(input) {
902
902
  weightBasis: row.calibrated === null ? "plan_estimate" : "observed",
903
903
  observedAt: row.observedAt
904
904
  }));
905
+ const limitedUntil = accounts.map(
906
+ (account) => account.remainingPercent === 0 ? account.resetsAt ?? Infinity : null
907
+ );
908
+ return { id, duration, data, accounts, limitedUntil };
909
+ });
910
+ const windows = readWindows.map(({ id, duration, data, accounts }) => {
911
+ const heldUntil = accounts.map(
912
+ (_, index) => readWindows.reduce((until, other) => {
913
+ const limit = other.id === id ? null : other.limitedUntil[index];
914
+ return limit === null ? until : Math.max(until ?? limit, limit);
915
+ }, null)
916
+ );
905
917
  const knownIndexes = accounts.flatMap(
906
- (account, index) => account.remainingPercent === null ? [] : [index]
918
+ (account, index) => account.remainingPercent === null && heldUntil[index] === null ? [] : [index]
907
919
  );
908
920
  const known = knownIndexes.map((index) => accounts[index]);
921
+ const held = knownIndexes.map((index) => heldUntil[index]);
909
922
  const totalWeight = sum(known.map((account) => account.weight));
910
923
  const complete = known.length > 0;
911
924
  const remainingPercent = complete ? clamp(
912
- sum(known.map((account) => account.weight * account.remainingPercent)) / totalWeight,
925
+ sum(
926
+ known.map(
927
+ (account, index) => held[index] === null ? account.weight * account.remainingPercent : 0
928
+ )
929
+ ) / totalWeight,
913
930
  0,
914
931
  100
915
932
  ) : null;
@@ -934,17 +951,24 @@ function buildSubscriptionUsagePool(input) {
934
951
  if (!complete) {
935
952
  forecast = insufficient("No account has a current observation for this window.");
936
953
  } else if (remainingPercent === 0) {
954
+ const returns = known.map(
955
+ (account, index) => Math.max(
956
+ account.remainingPercent === null ? Infinity : account.remainingPercent === 0 ? account.resetsAt ?? Infinity : -Infinity,
957
+ held[index] ?? -Infinity
958
+ )
959
+ ).filter((at) => Number.isFinite(at) && at > input.now);
937
960
  forecast = {
938
961
  kind: "runs_out",
939
962
  basis: "Observed pool exhaustion.",
940
963
  runsOutAt: input.now,
941
- nextResetAt,
964
+ nextResetAt: returns.length ? Math.min(...returns) : null,
942
965
  unusedPercent: null,
943
- utilizationPercent: 100,
966
+ // A short-window hold does not mean the weekly allowance was consumed.
967
+ utilizationPercent: known.every((account) => account.remainingPercent === 0) ? 100 : null,
944
968
  lowestPercent: 0,
945
969
  historyDays
946
970
  };
947
- } else if (resets.length !== known.length) {
971
+ } else if (known.some((account) => account.remainingPercent === null) || resets.length !== known.length) {
948
972
  forecast = insufficient(
949
973
  "A future reset is unknown; remaining limits are observed, not forecast."
950
974
  );
@@ -973,6 +997,7 @@ function buildSubscriptionUsagePool(input) {
973
997
  ].join("; ");
974
998
  forecast = simulate(
975
999
  known,
1000
+ held,
976
1001
  profile.map((rate) => rate * scale),
977
1002
  input.now,
978
1003
  duration,
@@ -1133,27 +1158,41 @@ function lastHourAdjustment(paces, now) {
1133
1158
  const average = sum(paces.map((pace) => sum(pace.profile) / 168));
1134
1159
  return actual !== null && average > EPSILON ? clamp(Math.sqrt(actual / average), 0.5, 2) : 1;
1135
1160
  }
1136
- function simulate(accounts, profile, now, duration, historyDays, basis) {
1161
+ function simulate(accounts, heldUntil, profile, now, duration, historyDays, basis) {
1137
1162
  const total = sum(accounts.map((account) => account.weight));
1138
1163
  const remaining = accounts.map((account) => account.weight * account.remainingPercent / 100);
1139
1164
  const resets = accounts.map((account) => account.resetsAt);
1165
+ const releases = heldUntil.filter(
1166
+ (at2) => at2 !== null && Number.isFinite(at2) && at2 > now
1167
+ );
1168
+ const returns = heldUntil.filter(
1169
+ (at2, index) => at2 !== null && Number.isFinite(at2) && at2 > now && remaining[index] > 0
1170
+ );
1171
+ const reliefs = resets.map((reset, index) => Math.max(reset, heldUntil[index] ?? reset));
1172
+ const open = (at2) => heldUntil.map((until) => until === null || until <= at2);
1173
+ const usable = (serving) => sum(remaining.filter((_, index) => serving[index]));
1140
1174
  const horizon = Math.min(now + duration, Math.max(...resets));
1141
1175
  let at = now;
1142
- let lowest = sum(remaining);
1176
+ let lowest = usable(open(now));
1143
1177
  let unused = 0;
1144
1178
  let runsOutAt = null;
1145
1179
  while (at < horizon) {
1146
- const nextReset = Math.min(...resets.filter((reset) => reset > at));
1147
- const end = Math.min(horizon, (Math.floor(at / HOUR) + 1) * HOUR, nextReset);
1180
+ const nextRelief = Math.min(...[...reliefs, ...returns].filter((event) => event > at));
1181
+ const end = Math.min(
1182
+ horizon,
1183
+ (Math.floor(at / HOUR) + 1) * HOUR,
1184
+ ...[...resets, ...releases].filter((event) => event > at)
1185
+ );
1148
1186
  const rate = profile[hourOfWeek(at)];
1149
1187
  const demand = rate * (end - at) / HOUR;
1150
- const available = sum(remaining);
1188
+ const serving = open(at);
1189
+ const available = usable(serving);
1151
1190
  if (rate > 0 && demand >= available && runsOutAt === null) {
1152
1191
  const exhaustedAt = at + available / rate * HOUR;
1153
- if (exhaustedAt < nextReset - 1e-3) runsOutAt = exhaustedAt;
1192
+ if (exhaustedAt < nextRelief - 1e-3) runsOutAt = exhaustedAt;
1154
1193
  }
1155
- consume(remaining, accounts, Math.min(demand, available));
1156
- lowest = Math.min(lowest, sum(remaining));
1194
+ consume(remaining, accounts, serving, Math.min(demand, available));
1195
+ lowest = Math.min(lowest, usable(serving));
1157
1196
  for (let index = 0; index < accounts.length; index++) {
1158
1197
  if (resets[index] === end) {
1159
1198
  unused += remaining[index];
@@ -1162,7 +1201,9 @@ function simulate(accounts, profile, now, duration, historyDays, basis) {
1162
1201
  }
1163
1202
  at = end;
1164
1203
  }
1165
- const nextResetAt = Math.min(...resets.filter((reset) => reset >= (runsOutAt ?? now)));
1204
+ const nextResetAt = Math.min(
1205
+ ...[...reliefs, ...returns].filter((event) => event >= (runsOutAt ?? now))
1206
+ );
1166
1207
  return {
1167
1208
  kind: runsOutAt === null ? "unused" : "runs_out",
1168
1209
  basis,
@@ -1174,8 +1215,8 @@ function simulate(accounts, profile, now, duration, historyDays, basis) {
1174
1215
  historyDays
1175
1216
  };
1176
1217
  }
1177
- function consume(remaining, accounts, demand) {
1178
- let active = remaining.map((amount, index) => ({ amount, index })).filter((row) => row.amount > 0);
1218
+ function consume(remaining, accounts, serving, demand) {
1219
+ let active = remaining.map((amount, index) => ({ amount, index })).filter((row) => row.amount > 0 && serving[row.index]);
1179
1220
  while (demand > EPSILON && active.length) {
1180
1221
  const totalWeight = sum(active.map((row) => accounts[row.index].weight));
1181
1222
  let consumed = 0;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/settings.ts","../src/quota.ts","../src/cache.ts","../src/eligibility.ts","../src/reasoning.ts","../src/spread.ts","../src/placement.ts","../src/adapter.ts","../src/settlement.ts","../src/wait-presentation.ts","../src/pool-usage.ts"],"sourcesContent":["import type {\n EffectiveSubscriptionSettings,\n EffectiveSettingSource,\n KeyedSettingKey,\n ProviderId,\n ProviderSwitches,\n RotationSetting,\n ScalarSettingKey,\n SettingSource,\n SubscriptionSettingOverrides,\n SubscriptionSettingKey,\n SubscriptionSettingsPolicy,\n SubscriptionSettingsView,\n SubscriptionSettingValues,\n} from \"./types\";\n\nexport const SCALAR_SETTING_KEYS: readonly ScalarSettingKey[] = Object.freeze([\n \"crossProviderFailover\",\n \"personalConnectionsAllowed\",\n \"personalFallbackAllowed\",\n]);\n\nexport const KEYED_SETTING_KEYS: readonly KeyedSettingKey[] = Object.freeze([\n \"rotation\",\n \"providers\",\n \"fallbackOrder\",\n]);\n\nexport const SUBSCRIPTION_SETTING_KEYS: readonly SubscriptionSettingKey[] = Object.freeze([\n ...SCALAR_SETTING_KEYS,\n ...KEYED_SETTING_KEYS,\n]);\n\n/** Default when a provider has no rotation entry: spread work (design 3.3). */\nexport const DEFAULT_ROTATION: RotationSetting = Object.freeze({ mode: \"spread\" });\n\nexport const DEFAULT_PROVIDER_SWITCHES: ProviderSwitches = Object.freeze({ enabled: true });\n\n/**\n * Resolve one workspace's effective settings (SUB-SET-01..03).\n *\n * Every setting resolves to exactly one value with its source: the\n * organization default, or the workspace override when the setting is not\n * locked. For map-valued settings (`rotation`, `providers`, `fallbackOrder`) a\n * workspace row overrides individual entries, so overriding one provider's\n * rotation does not reset another provider's, and a provider's switches\n * override field by field (D-25). Locking a map-valued setting locks every\n * entry.\n */\nexport function effectiveSettings(\n policy: SubscriptionSettingsPolicy,\n workspaceId: string,\n): EffectiveSubscriptionSettings {\n const override = policy.workspaces[workspaceId] ?? {};\n const locked = new Set<SubscriptionSettingKey>(policy.locked);\n const organization = policy.organization;\n\n const scalar = <K extends ScalarSettingKey>(key: K) => {\n const value = override[key];\n const overridden = value !== undefined && !locked.has(key);\n return {\n value: (overridden ? value : organization[key]) as SubscriptionSettingValues[K],\n source: (overridden ? \"workspace\" : \"organization\") as SettingSource,\n };\n };\n const keyed = <K extends KeyedSettingKey>(key: K) => {\n const base = organization[key] as Readonly<Record<string, unknown>>;\n const entries = locked.has(key)\n ? undefined\n : (override[key] as Readonly<Record<string, unknown>> | undefined);\n const value: Record<string, unknown> = { ...base };\n const sources: Record<string, SettingSource> = {};\n for (const [entryKey, entry] of Object.entries(base)) {\n if (key === \"providers\") value[entryKey] = normalizeProviderSwitches(entry);\n sources[entryKey] = \"organization\";\n }\n for (const [entryKey, entry] of Object.entries(entries ?? {})) {\n if (entry === undefined) continue;\n // A provider's switches override field by field (D-25).\n value[entryKey] =\n key === \"providers\"\n ? mergeProviderSwitches(\n base[entryKey] as ProviderSwitches | undefined,\n entry as Partial<ProviderSwitches>,\n )\n : entry;\n sources[entryKey] = \"workspace\";\n }\n return { value: value as SubscriptionSettingValues[K], sources };\n };\n\n const crossProviderFailover = scalar(\"crossProviderFailover\");\n const personalConnectionsAllowed = scalar(\"personalConnectionsAllowed\");\n const personalFallbackAllowed = scalar(\"personalFallbackAllowed\");\n const rotation = keyed(\"rotation\");\n const providers = keyed(\"providers\");\n const fallbackOrder = keyed(\"fallbackOrder\");\n return {\n values: {\n rotation: rotation.value,\n providers: providers.value,\n crossProviderFailover: crossProviderFailover.value,\n fallbackOrder: fallbackOrder.value,\n personalConnectionsAllowed: personalConnectionsAllowed.value,\n personalFallbackAllowed: personalFallbackAllowed.value,\n },\n sources: {\n crossProviderFailover: crossProviderFailover.source,\n personalConnectionsAllowed: personalConnectionsAllowed.source,\n personalFallbackAllowed: personalFallbackAllowed.source,\n rotation: rotation.sources,\n providers: providers.sources,\n fallbackOrder: fallbackOrder.sources,\n },\n };\n}\n\nexport function rotationFor(\n settings: SubscriptionSettingValues,\n provider: ProviderId,\n): RotationSetting {\n return settings.rotation[provider] ?? DEFAULT_ROTATION;\n}\n\nexport function providerSwitchesFor(\n settings: SubscriptionSettingValues,\n provider: ProviderId,\n): ProviderSwitches {\n const entry = settings.providers[provider];\n return entry ? normalizeProviderSwitches(entry) : DEFAULT_PROVIDER_SWITCHES;\n}\n\n/** A stored provider entry as its one switch; keys from older releases are ignored. */\nfunction normalizeProviderSwitches(entry: unknown): ProviderSwitches {\n return { enabled: (entry as Partial<ProviderSwitches> | undefined)?.enabled !== false };\n}\n\nexport function isSubscriptionSettingKey(value: unknown): value is SubscriptionSettingKey {\n return (\n typeof value === \"string\" && (SUBSCRIPTION_SETTING_KEYS as readonly string[]).includes(value)\n );\n}\n\n/**\n * One workspace's effective settings with one source per setting (SUB-SET-03,\n * SUB-SET-05): `locked` when the organization locked it (the organization\n * value applies and the workspace cannot override it), `workspace` when the\n * workspace overrides it (for a map-valued setting: at least one entry), and\n * `organization` otherwise. `entrySources` keeps the per-entry sources of the\n * map-valued settings.\n */\nexport function settingsView(\n policy: SubscriptionSettingsPolicy,\n workspaceId: string,\n): SubscriptionSettingsView {\n return settingsViewFromEffective(effectiveSettings(policy, workspaceId), policy.locked);\n}\n\n/** The same view from an already resolved effective settings value (for example the SQL one). */\nexport function settingsViewFromEffective(\n effective: EffectiveSubscriptionSettings,\n locked: readonly SubscriptionSettingKey[],\n): SubscriptionSettingsView {\n const lockedSet = new Set(locked);\n const sources = {} as Record<SubscriptionSettingKey, EffectiveSettingSource>;\n for (const key of SCALAR_SETTING_KEYS) {\n sources[key] = lockedSet.has(key) ? \"locked\" : effective.sources[key];\n }\n for (const key of KEYED_SETTING_KEYS) {\n sources[key] = lockedSet.has(key)\n ? \"locked\"\n : Object.values(effective.sources[key]).includes(\"workspace\")\n ? \"workspace\"\n : \"organization\";\n }\n return {\n values: effective.values,\n sources,\n entrySources: {\n rotation: effective.sources.rotation,\n providers: effective.sources.providers,\n fallbackOrder: effective.sources.fallbackOrder,\n },\n };\n}\n\nconst OVERRIDE_COLUMN_VALUE = (\n overrides: SubscriptionSettingOverrides | null | undefined,\n key: SubscriptionSettingKey,\n): unknown => (overrides ? overrides[key] : undefined);\n\n/**\n * The locked settings a workspace-row write would widen: the TypeScript\n * mirror of the database guard (migration 0724). A write widens a setting\n * when it sets a value that is not already stored unchanged; for a\n * map-valued setting, when it adds or changes an entry. Clearing an override,\n * or leaving it unchanged, never widens. A rotation lock also covers the\n * workspace's primary connections (`primaryConnectionIds`).\n */\nexport function lockedSettingsWidened(input: {\n locked: readonly SubscriptionSettingKey[];\n before: SubscriptionSettingOverrides | null;\n after: SubscriptionSettingOverrides | null;\n primaryConnectionIds?: {\n before: Readonly<Record<string, string | null>>;\n after: Readonly<Record<string, string | null>>;\n };\n}): SubscriptionSettingKey[] {\n const widened: SubscriptionSettingKey[] = [];\n for (const key of SUBSCRIPTION_SETTING_KEYS) {\n if (!input.locked.includes(key)) continue;\n const next = OVERRIDE_COLUMN_VALUE(input.after, key);\n const previous = OVERRIDE_COLUMN_VALUE(input.before, key);\n if (overrideWidened(next, previous, (KEYED_SETTING_KEYS as readonly string[]).includes(key))) {\n widened.push(key);\n continue;\n }\n if (key === \"rotation\" && input.primaryConnectionIds) {\n const { before, after } = input.primaryConnectionIds;\n if (\n Object.entries(after).some(\n ([column, value]) => value !== null && value !== (before[column] ?? null),\n )\n ) {\n widened.push(key);\n }\n }\n }\n return widened;\n}\n\nfunction overrideWidened(next: unknown, previous: unknown, keyed: boolean): boolean {\n if (next === undefined || next === null) return false;\n if (!keyed) return !jsonEqual(next, previous ?? null);\n if (typeof next !== \"object\" || Array.isArray(next)) return true;\n const stored =\n previous && typeof previous === \"object\" && !Array.isArray(previous)\n ? (previous as Record<string, unknown>)\n : null;\n return Object.entries(next as Record<string, unknown>).some(\n ([entry, value]) =>\n // JSON drops undefined members, as the stored jsonb does.\n value !== undefined &&\n (stored === null || !(entry in stored) || !jsonEqual(stored[entry], value)),\n );\n}\n\n/** Structural equality of JSON values (jsonb equality: object key order does not matter). */\nexport function jsonEqual(left: unknown, right: unknown): boolean {\n if (left === right) return true;\n if (left === null || right === null || typeof left !== \"object\" || typeof right !== \"object\") {\n return false;\n }\n if (Array.isArray(left) !== Array.isArray(right)) return false;\n if (Array.isArray(left)) {\n const other = right as unknown[];\n return (\n left.length === other.length && left.every((value, index) => jsonEqual(value, other[index]))\n );\n }\n const leftEntries = Object.entries(left).filter(([, value]) => value !== undefined);\n const rightRecord = right as Record<string, unknown>;\n const rightKeys = Object.keys(rightRecord).filter((key) => rightRecord[key] !== undefined);\n return (\n leftEntries.length === rightKeys.length &&\n leftEntries.every(([key, value]) => key in rightRecord && jsonEqual(value, rightRecord[key]))\n );\n}\n\n/** Bounds on a stored fallback order (also enforced by the API contract). */\nexport const FALLBACK_ORDER_LIMITS = Object.freeze({\n preferredModels: 64,\n fallbacksPerModel: 8,\n modelIdLength: 200,\n});\n\n/**\n * Why a fallback order is invalid, or null when it is valid: each preferred\n * model maps to distinct model ids, never itself, within the bounds above.\n * Model ids are not required to be in a catalog: placement skips a fallback\n * model it does not know (`failoverCandidateModels`).\n */\nexport function fallbackOrderProblem(order: unknown): string | null {\n if (order === null || typeof order !== \"object\" || Array.isArray(order)) {\n return \"fallback order must be an object\";\n }\n const entries = Object.entries(order as Record<string, unknown>);\n if (entries.length > FALLBACK_ORDER_LIMITS.preferredModels) {\n return `fallback order has more than ${FALLBACK_ORDER_LIMITS.preferredModels} models`;\n }\n const validId = (id: unknown): id is string =>\n typeof id === \"string\" &&\n id.trim() === id &&\n id.length > 0 &&\n id.length <= FALLBACK_ORDER_LIMITS.modelIdLength;\n for (const [preferred, fallbacks] of entries) {\n if (!validId(preferred)) return \"fallback order has an invalid model id\";\n if (!Array.isArray(fallbacks)) return `fallback order for ${preferred} must be a list`;\n if (fallbacks.length > FALLBACK_ORDER_LIMITS.fallbacksPerModel) {\n return `fallback order for ${preferred} has more than ${FALLBACK_ORDER_LIMITS.fallbacksPerModel} models`;\n }\n if (!fallbacks.every(validId)) return `fallback order for ${preferred} has an invalid model id`;\n if (new Set(fallbacks).size !== fallbacks.length) {\n return `fallback order for ${preferred} repeats a model`;\n }\n if (fallbacks.includes(preferred)) {\n return `fallback order for ${preferred} names the model itself`;\n }\n }\n return null;\n}\n\n/**\n * The fallback models the effective settings name for a preferred model, in\n * order, before the placement-time filters (catalog, cross-provider switch,\n * restrictions; see `failoverCandidateModels`). The read the core uses for\n * the organization's fallback order. Empty both for a missing entry and for\n * an explicit empty one; `hasFallbackOrderEntry` tells them apart.\n */\nexport function fallbackOrderFor(\n settings: SubscriptionSettingValues,\n preferredModelId: string,\n): readonly string[] {\n if (!hasFallbackOrderEntry(settings, preferredModelId)) return [];\n return settings.fallbackOrder[preferredModelId] ?? [];\n}\n\n/**\n * Whether the effective settings carry a fallback entry for a preferred\n * model, including an explicit empty list, which means \"no fallback model\"\n * (D-35). Only a missing entry lets placement derive a default order. Only\n * the map's own keys are entries, never an inherited object property.\n */\nexport function hasFallbackOrderEntry(\n settings: SubscriptionSettingValues,\n preferredModelId: string,\n): boolean {\n return Object.hasOwn(settings.fallbackOrder, preferredModelId);\n}\n\nfunction mergeProviderSwitches(\n base: ProviderSwitches | undefined,\n override: Partial<ProviderSwitches>,\n): ProviderSwitches {\n return { enabled: override.enabled ?? base?.enabled ?? true };\n}\n","import type { ModelId, QuotaBlockClaim, SubscriptionConnection, SubscriptionQuota } from \"./types\";\n\n/**\n * What the shared quota model says about a connection's capacity now\n * (design 2.2). `unknown` is never treated as available or exhausted\n * (SUB-ELIG-06): it participates in ordinary rotation without a ranking penalty (D-14).\n */\nexport type QuotaCapacity =\n | { kind: \"available\" }\n | { kind: \"unknown\" }\n /** `resetsAt` is null when the provider did not say when it resets. */\n | { kind: \"exhausted\"; resetsAt: number | null };\n\nconst UNKNOWN: QuotaCapacity = Object.freeze({ kind: \"unknown\" });\nconst AVAILABLE: QuotaCapacity = Object.freeze({ kind: \"available\" });\n\n/**\n * Capacity of a quota observation at `now`.\n *\n * - An exhaustion deadline (`exhaustedUntil`) in the future is authoritative.\n * - A quota exhaustion whose deadline has passed is an observed reset, so it\n * counts as known capacity; a passed rate-limit deadline says nothing about\n * quota.\n * - A window reported exhausted stays exhausted until its reset; an exhausted\n * window whose reset has passed counts as reset (known capacity).\n * - With nothing exhausted, any window with a known status is known capacity.\n * - No observation, only windows of unknown status, or an observation older\n * than `staleAfterMs` (or undated, when a bound is given), is unknown.\n */\nexport function quotaCapacity(\n quota: SubscriptionQuota | null,\n now: number,\n staleAfterMs?: number,\n): QuotaCapacity {\n if (!quota) return UNKNOWN;\n // A stale or undated observation no longer says the account has capacity\n // (SUB-ELIG-06); observed exhaustion still stands until its reset.\n const stale =\n staleAfterMs !== undefined &&\n (quota.observedAt === null || now - quota.observedAt > staleAfterMs);\n let exhausted = false;\n let resetsAt: number | null = null;\n let resetUnknown = false;\n const extend = (at: number | null) => {\n exhausted = true;\n if (at === null) resetUnknown = true;\n else resetsAt = resetsAt === null ? at : Math.max(resetsAt, at);\n };\n let known = false;\n if (quota.exhaustedUntil !== null) {\n if (quota.exhaustedUntil > now) extend(quota.exhaustedUntil);\n else if (quota.exhaustedKind === \"quota\" && !stale) known = true;\n }\n for (const window of quota.windows) {\n if (window.status === \"exhausted\") {\n if (window.resetsAt === null || window.resetsAt > now) extend(window.resetsAt);\n else if (!stale) known = true;\n } else if (window.status !== \"unknown\" && !stale) {\n known = true;\n }\n }\n if (exhausted) return { kind: \"exhausted\", resetsAt: resetUnknown ? null : resetsAt };\n return known ? AVAILABLE : UNKNOWN;\n}\n\n/** End of a model-specific cooldown that is still running, else null. */\nexport function modelCooldownUntil(\n quota: SubscriptionQuota | null,\n modelId: ModelId,\n now: number,\n): number | null {\n const until = quota?.modelCooldowns[modelId];\n return until !== undefined && until > now ? until : null;\n}\n\n/** One stored block a usage reading may end: a model cooldown, or (model null) the exhaustion deadline. */\nexport type SubscriptionQuotaBlock = {\n modelId: ModelId | null;\n until: number;\n /** Its claim, only while it still explains this exact deadline. */\n claim: QuotaBlockClaim | null;\n};\n\n/**\n * The claim of a stored block (a model's cooldown, or with `modelId` null\n * the exhaustion deadline), only while it is tied to the block's current\n * deadline; null otherwise (fail closed).\n */\nexport function quotaBlockClaim(\n quota: Pick<\n SubscriptionQuota,\n \"modelCooldowns\" | \"modelCooldownClaims\" | \"exhaustedUntil\" | \"exhaustedClaim\"\n >,\n modelId: ModelId | null,\n): QuotaBlockClaim | null {\n const until = modelId === null ? quota.exhaustedUntil : quota.modelCooldowns[modelId];\n const claim = modelId === null ? quota.exhaustedClaim : quota.modelCooldownClaims?.[modelId];\n return until !== null &&\n until !== undefined &&\n claim !== null &&\n claim !== undefined &&\n claim.until === until &&\n claim.windowIds.length > 0\n ? claim\n : null;\n}\n\n/** The quota document a store writes: claims only while they explain a block. */\nexport type StoredQuotaDocument = Pick<\n SubscriptionQuota,\n | \"windows\"\n | \"modelCooldowns\"\n | \"modelCooldownClaims\"\n | \"exhaustedUntil\"\n | \"exhaustedKind\"\n | \"exhaustedClaim\"\n | \"source\"\n>;\n\n/** The stored document of one quota (a claim that no longer explains its block is dropped). */\nexport function storedQuotaDocument(quota: SubscriptionQuota): StoredQuotaDocument {\n const modelCooldownClaims: Record<string, QuotaBlockClaim> = {};\n for (const modelId of Object.keys(quota.modelCooldowns)) {\n const claim = quotaBlockClaim(quota, modelId);\n if (claim) modelCooldownClaims[modelId] = claim;\n }\n const exhaustedClaim = quotaBlockClaim(quota, null);\n return {\n windows: quota.windows,\n modelCooldowns: quota.modelCooldowns,\n ...(Object.keys(modelCooldownClaims).length > 0 ? { modelCooldownClaims } : {}),\n exhaustedUntil: quota.exhaustedUntil,\n exhaustedKind: quota.exhaustedKind,\n ...(exhaustedClaim ? { exhaustedClaim } : {}),\n source: quota.source,\n };\n}\n\n/**\n * The claim of a block merged from several observations at `until`: every\n * source whose own block has that exact deadline must carry a claim tied to\n * it (their window ids are joined); otherwise none (an unexplained limit\n * with the same deadline keeps the block).\n */\nfunction mergedClaim(\n until: number,\n sources: readonly { until: number | null | undefined; claim: QuotaBlockClaim | null }[],\n): QuotaBlockClaim | null {\n const deciding = sources.filter((source) => source.until === until);\n if (deciding.length === 0 || deciding.some((source) => source.claim === null)) return null;\n const claims = deciding.map((source) => source.claim!);\n return {\n until,\n observedAt: Math.max(...claims.map((claim) => claim.observedAt)),\n windowIds: [...new Set(claims.flatMap((claim) => claim.windowIds))],\n };\n}\n\n/**\n * A quota observation applies only to the credential generation it was made\n * with, so a refusal seen before a refresh cannot quarantine the renewed\n * credential (design 2.2).\n */\nexport function quotaObservationApplies(\n connection: Pick<SubscriptionConnection, \"refreshGeneration\">,\n observation: Pick<SubscriptionQuota, \"observedRefreshGeneration\">,\n): boolean {\n return observation.observedRefreshGeneration === connection.refreshGeneration;\n}\n\n/**\n * The stored quota once the provider has confirmed, at `resetAt`, that it\n * reset the connection's rate limits (a redeemed reset credit). Every window\n * reading and the stored exhaustion deadline were observed before the reset,\n * so none of them describes the account any more: each window keeps its id\n * with unknown usage and reset until a later reading reports it, and the\n * deadline and its claim are dropped. A stale window that still said\n * \"exhausted until next week\" would otherwise keep every waiter parked until\n * that date. Model cooldowns are not rate-limit windows and stand. The\n * observation time moves to `resetAt`, so a usage reading fetched before the\n * reset can no longer replace this state with pre-reset windows. Returns the\n * same object when nothing it records is invalidated.\n */\nexport function quotaAfterRateLimitReset(\n quota: SubscriptionQuota,\n resetAt: number,\n): SubscriptionQuota {\n const invalidates =\n quota.exhaustedUntil !== null ||\n quota.exhaustedKind !== null ||\n quota.windows.some(\n (window) =>\n window.status !== \"unknown\" || window.usedPercent !== null || window.resetsAt !== null,\n );\n if (!invalidates) return quota;\n const next: SubscriptionQuota = {\n ...quota,\n windows: quota.windows.map((window) => ({\n id: window.id,\n usedPercent: null,\n resetsAt: null,\n status: \"unknown\" as const,\n })),\n exhaustedUntil: null,\n exhaustedKind: null,\n observedAt: Math.max(quota.observedAt ?? resetAt, resetAt),\n };\n delete next.exhaustedClaim;\n return next;\n}\n\n/**\n * Apply an observation to the stored quota (design 2.2). It applies only to\n * the current credential generation, never replaces a newer or dated reading\n * with an older or undated one, and never shortens a running exhaustion\n * deadline or model cooldown: a later usage reading without them does not\n * clear them. A stored exhausted window with a known reset that an\n * observation does not report at all stays until that reset (one that\n * reports the window again replaces it), unless the observation is a\n * usage-endpoint reading of an adapter that declares its usage readings\n * complete (`options.usageReadingsComplete`, from the adapter's\n * `usageReadingsComplete`): such a reading replaces the stored windows, so\n * it can show an early reset (also of a refusal's fallback window).\n * Response headers and refusals, and the usage readings of every other\n * adapter, report only some windows (fail closed: absent means partial).\n * So a window that only refusals report (a policy hold the caller records\n * itself, for example) ends at its reset or with a complete usage reading,\n * never with a header reading. A caller's own refusal must be tagged\n * `refusal`, not `usage_endpoint`, or a complete adapter's reading would\n * erase the stored windows.\n * Clearing a cooldown early is an explicit, revision-fenced store operation,\n * not an observation. Revision numbers are assigned by the store.\n */\nexport function applyQuotaObservation(\n connection: Pick<SubscriptionConnection, \"refreshGeneration\" | \"quota\">,\n observation: SubscriptionQuota,\n options: {\n /** The observing adapter declares its usage-endpoint readings complete. */\n usageReadingsComplete?: boolean;\n } = {},\n): SubscriptionQuota | null {\n if (!quotaObservationApplies(connection, observation)) return connection.quota;\n const current = connection.quota;\n if (!current || current.observedRefreshGeneration !== connection.refreshGeneration) {\n return observation;\n }\n if (\n current.observedAt !== null &&\n (observation.observedAt === null || observation.observedAt < current.observedAt)\n ) {\n return current;\n }\n // Keep only what still runs after this observation was made: later values\n // win, and deadlines and cooldowns that ended before it are dropped.\n const observedAt = observation.observedAt ?? -Infinity;\n const modelCooldowns: Record<string, number> = {};\n for (const source of [current.modelCooldowns, observation.modelCooldowns]) {\n for (const [modelId, until] of Object.entries(source)) {\n if (until <= observedAt) continue;\n modelCooldowns[modelId] = Math.max(modelCooldowns[modelId] ?? until, until);\n }\n }\n const keepCurrentDeadline =\n current.exhaustedUntil !== null &&\n current.exhaustedUntil > observedAt &&\n (observation.exhaustedUntil === null || observation.exhaustedUntil < current.exhaustedUntil);\n const exhaustedUntil = keepCurrentDeadline ? current.exhaustedUntil : observation.exhaustedUntil;\n // A block keeps its evidence only where every observation that set its\n // merged deadline explained it (`QuotaBlockClaim`).\n const sources = [current, observation];\n const modelCooldownClaims: Record<string, QuotaBlockClaim> = {};\n for (const [modelId, until] of Object.entries(modelCooldowns)) {\n const claim = mergedClaim(\n until,\n sources.map((source) => ({\n until: source.modelCooldowns[modelId],\n claim: quotaBlockClaim(source, modelId),\n })),\n );\n if (claim) modelCooldownClaims[modelId] = claim;\n }\n const exhaustedClaim =\n exhaustedUntil === null\n ? null\n : mergedClaim(\n exhaustedUntil,\n sources.map((source) => ({\n until: source.exhaustedUntil,\n claim: quotaBlockClaim(source, null),\n })),\n );\n const reported = new Set(observation.windows.map((window) => window.id));\n const omittedExhausted =\n observation.source === \"usage_endpoint\" && options.usageReadingsComplete === true\n ? []\n : current.windows.filter(\n (window) =>\n !reported.has(window.id) &&\n window.status === \"exhausted\" &&\n window.resetsAt !== null &&\n window.resetsAt > observedAt,\n );\n const merged: SubscriptionQuota = {\n ...observation,\n windows: omittedExhausted.length\n ? [...observation.windows, ...omittedExhausted]\n : observation.windows,\n modelCooldowns,\n exhaustedUntil,\n exhaustedKind: keepCurrentDeadline ? current.exhaustedKind : observation.exhaustedKind,\n };\n delete merged.modelCooldownClaims;\n delete merged.exhaustedClaim;\n if (Object.keys(modelCooldownClaims).length > 0) merged.modelCooldownClaims = modelCooldownClaims;\n if (exhaustedClaim) merged.exhaustedClaim = exhaustedClaim;\n return merged;\n}\n","import type { CacheFacts, SessionBinding } from \"./types\";\n\n/**\n * Idle cut-off used for providers whose cut-off has not been measured yet\n * (design 6.1). Erring towards \"warm\" avoids paying a cache miss for a switch\n * that was not needed.\n */\nexport const DEFAULT_IDLE_CUTOFF_MS = 60 * 60 * 1000;\n\n/** How long the provider prompt cache stays warm after a model call. */\nexport function cacheLifetimeMs(facts: CacheFacts | undefined): number {\n if (!facts) return DEFAULT_IDLE_CUTOFF_MS;\n if (facts.kind === \"exact_ttl\") return Math.max(0, facts.ttlMs);\n return facts.cutoffMs === null ? DEFAULT_IDLE_CUTOFF_MS : Math.max(0, facts.cutoffMs);\n}\n\n/**\n * The cache is warm while the session has been idle no longer than the\n * provider's cache lifetime (SUB-STICK-04). Idle time is measured from the\n * latest completed model call, not the turn start, so a long turn is not\n * mistaken for a cold cache. An exact lifetime sent with the latest request\n * (an exact cache TTL) takes precedence over the provider's facts.\n */\nexport function isCacheWarm(\n binding: Pick<SessionBinding, \"lastModelCallAt\" | \"cacheTtlMs\">,\n facts: CacheFacts | undefined,\n now: number,\n): boolean {\n const lifetime =\n binding.cacheTtlMs !== undefined && binding.cacheTtlMs !== null\n ? Math.max(0, binding.cacheTtlMs)\n : cacheLifetimeMs(facts);\n return now - binding.lastModelCallAt <= lifetime;\n}\n","import { modelCooldownUntil, quotaCapacity, type QuotaCapacity } from \"./quota\";\nimport { providerSwitchesFor } from \"./settings\";\nimport type {\n ModelDescriptor,\n ModelId,\n PlacementInput,\n PlacementPerson,\n PlacementWorkspace,\n SubscriptionConnection,\n} from \"./types\";\n\n/**\n * Reasons a connection may not serve a session at all. These are authority:\n * the database enforces the same rules, so a connection with any of them must\n * never be selected (SUB-ELIG-01, SUB-ELIG-05, SUB-SET-06, SUB-FAIL-04).\n */\nexport type AuthorizationIneligibility =\n | \"provider_disabled\"\n | \"out_of_scope\"\n | \"personal_connections_disabled\"\n | \"personal_owner_inactive\"\n | \"personal_not_owners_work\"\n | \"personal_authority_missing\";\n\n/** Reasons an authorized connection cannot serve a model now (SUB-ELIG-02..04). */\nexport type ServiceIneligibility =\n | \"unhealthy\"\n | \"allocator_disabled\"\n | \"wrong_provider\"\n | \"model_unknown\"\n | \"model_not_allowed_by_workspace\"\n | \"model_not_entitled\"\n | \"model_not_allowed_by_connection\"\n | \"model_cooling_down\"\n | \"exhausted\";\n\nexport type Ineligibility = AuthorizationIneligibility | ServiceIneligibility;\n\nexport const AUTHORIZATION_INELIGIBILITIES: readonly AuthorizationIneligibility[] = Object.freeze([\n \"provider_disabled\",\n \"out_of_scope\",\n \"personal_connections_disabled\",\n \"personal_owner_inactive\",\n \"personal_not_owners_work\",\n \"personal_authority_missing\",\n]);\n\nconst AUTHORIZATION_SET = new Set<Ineligibility>(AUTHORIZATION_INELIGIBILITIES);\n\nexport function isAuthorizationIneligibility(\n reason: Ineligibility,\n): reason is AuthorizationIneligibility {\n return AUTHORIZATION_SET.has(reason);\n}\n\nconst PERMANENT_SERVICE = new Set<Ineligibility>([\n \"wrong_provider\",\n \"model_unknown\",\n \"model_not_allowed_by_workspace\",\n \"model_not_entitled\",\n \"model_not_allowed_by_connection\",\n]);\n\n/**\n * Reasons that neither time nor a quota reset clears: the work is not\n * authorized on the account, or the account cannot serve the model at all.\n * Health and allocator eligibility are not permanent: reconnecting or\n * re-enabling allocation restores them.\n */\nexport function isPermanentIneligibility(reason: Ineligibility): boolean {\n return AUTHORIZATION_SET.has(reason) || PERMANENT_SERVICE.has(reason);\n}\n\n/** Capacity of a connection now, with the input's staleness bound for its provider. */\nexport function connectionCapacity(\n input: PlacementInput,\n connection: SubscriptionConnection,\n): QuotaCapacity {\n const capacity = quotaCapacity(\n connection.quota,\n input.now,\n input.quotaStaleAfterMs?.[connection.provider],\n );\n if (connectionUsesExtraCredits(input, connection)) return { kind: \"available\" };\n return capacity;\n}\n\n/** Only included-window exhaustion is eligible for opted-in credit fallback. */\nexport function connectionUsesExtraCredits(\n input: PlacementInput,\n connection: SubscriptionConnection,\n): boolean {\n return (\n connection.extraCreditsEnabled === true &&\n !(connection.quota?.exhaustedUntil != null && connection.quota.exhaustedUntil > input.now) &&\n quotaCapacity(connection.quota, input.now, input.quotaStaleAfterMs?.[connection.provider])\n .kind === \"exhausted\"\n );\n}\n\nfunction findModel(input: PlacementInput, modelId: ModelId): ModelDescriptor | undefined {\n return input.models.find((model) => model.id === modelId);\n}\n\nfunction findPerson(input: PlacementInput, membershipId: string): PlacementPerson | undefined {\n return input.people.find((person) => person.membershipId === membershipId);\n}\n\n/** Workspace model restrictions are a ceiling on every selection (SUB-ELIG-02). */\nexport function modelAllowedInWorkspace(workspace: PlacementWorkspace, modelId: ModelId): boolean {\n return workspace.allowedModelIds === null || workspace.allowedModelIds.includes(modelId);\n}\n\n/** Is the provider switched on for this workspace (SUB-SET-06)? */\nexport function providerEnabled(input: PlacementInput, provider: string): boolean {\n return providerSwitchesFor(input.settings, provider).enabled;\n}\n\n/**\n * Authorization of one connection for the session's work, independent of\n * model and capacity. Personal connections serve only their owner's own work,\n * in the owner's private sessions or Personal workspace, with frozen personal\n * authority for that provider (SUB-ELIG-05, design 3.7, 3.8). Shared\n * connections serve the workspaces and people in their scope, where people are\n * evaluated against the session owner.\n */\nexport function authorizationIneligibility(\n input: PlacementInput,\n connection: SubscriptionConnection,\n): AuthorizationIneligibility[] {\n const reasons: AuthorizationIneligibility[] = [];\n const { session, workspace } = input;\n const switches = providerSwitchesFor(input.settings, connection.provider);\n if (!switches.enabled) reasons.push(\"provider_disabled\");\n const ownership = connection.ownership;\n if (ownership.kind === \"personal\") {\n const owner = ownership.ownerMembershipId;\n if (!input.settings.personalConnectionsAllowed) reasons.push(\"personal_connections_disabled\");\n if (!findPerson(input, owner)?.active) reasons.push(\"personal_owner_inactive\");\n const authority = session.personalAuthority.find(\n (entry) => entry.provider === connection.provider && entry.ownerMembershipId === owner,\n );\n // A new shared-chat entry is a restriction, never a pool-wide grant and\n // never an alias for the chat owner's identity. Historical entries keep\n // their private-work eligibility unchanged.\n const ownersOwnWork = authority?.sharedSessionChoice\n ? session.ownerMembershipId !== null &&\n authority.sharedSessionChoice.sessionId === session.id &&\n authority.sharedSessionChoice.connectionId === connection.id\n : session.ownerMembershipId === owner &&\n (session.visibility === \"private\" ||\n (workspace.kind === \"personal\" && workspace.ownerMembershipId === owner));\n if (!ownersOwnWork) reasons.push(\"personal_not_owners_work\");\n if (!authority) reasons.push(\"personal_authority_missing\");\n return reasons;\n }\n const scope = ownership.scope;\n const inScope =\n scope.kind === \"organization\" ||\n (scope.kind === \"workspaces\" &&\n (scope.workspaceIds.includes(workspace.id) ||\n (workspace.kind === \"personal\" && scope.allowPersonalWorkspaces))) ||\n (scope.kind === \"people\" &&\n session.ownerMembershipId !== null &&\n scope.membershipIds.includes(session.ownerMembershipId));\n if (!inScope) reasons.push(\"out_of_scope\");\n return reasons;\n}\n\n/** Reasons the connection cannot serve this model, ignoring capacity and cooldowns. */\nfunction staticServiceIneligibility(\n input: PlacementInput,\n connection: SubscriptionConnection,\n modelId: ModelId,\n): ServiceIneligibility[] {\n const reasons: ServiceIneligibility[] = [];\n if (connection.health !== \"healthy\") reasons.push(\"unhealthy\");\n if (!connection.allocatorEnabled) reasons.push(\"allocator_disabled\");\n const model = findModel(input, modelId);\n if (!model) reasons.push(\"model_unknown\");\n else if (model.provider !== connection.provider) reasons.push(\"wrong_provider\");\n if (!modelAllowedInWorkspace(input.workspace, modelId)) {\n reasons.push(\"model_not_allowed_by_workspace\");\n }\n const entitled =\n (connection.entitledModelIds === null || connection.entitledModelIds.includes(modelId)) &&\n !connection.excludedModelIds.includes(modelId);\n if (!entitled) reasons.push(\"model_not_entitled\");\n if (connection.allowedModelIds !== null && !connection.allowedModelIds.includes(modelId)) {\n reasons.push(\"model_not_allowed_by_connection\");\n }\n return reasons;\n}\n\n/** Every reason this connection cannot serve this session and model now; empty when it can. */\nexport function connectionIneligibility(\n input: PlacementInput,\n connection: SubscriptionConnection,\n modelId: ModelId,\n): Ineligibility[] {\n const reasons: Ineligibility[] = [\n ...authorizationIneligibility(input, connection),\n ...staticServiceIneligibility(input, connection, modelId),\n ];\n if (modelCooldownUntil(connection.quota, modelId, input.now) !== null) {\n reasons.push(\"model_cooling_down\");\n }\n if (connectionCapacity(input, connection).kind === \"exhausted\") reasons.push(\"exhausted\");\n return reasons;\n}\n\n/** SUB-ELIG-01..06: may this connection serve this session's model now? */\nexport function canServe(\n input: PlacementInput,\n connection: SubscriptionConnection,\n modelId: ModelId,\n): boolean {\n return connectionIneligibility(input, connection, modelId).length === 0;\n}\n\n/**\n * When a connection that is blocked only by capacity or a model cooldown can\n * serve this model again. Returns `undefined` when something other than time\n * blocks it, or when it is not blocked at all, and `null` when the reset time\n * is unknown.\n */\nexport function servableAgainAt(\n input: PlacementInput,\n connection: SubscriptionConnection,\n modelId: ModelId,\n): number | null | undefined {\n if (authorizationIneligibility(input, connection).length > 0) return undefined;\n if (staticServiceIneligibility(input, connection, modelId).length > 0) return undefined;\n const capacity = connectionCapacity(input, connection);\n const cooldown = modelCooldownUntil(connection.quota, modelId, input.now);\n if (capacity.kind !== \"exhausted\" && cooldown === null) return undefined;\n // A refusal can expire before an exhausted allowance resets. At that\n // point credit consent may make the account usable, even without a known\n // allowance reset. Evaluate every observed transition using the same policy.\n const transitions = [\n cooldown,\n connection.quota?.exhaustedUntil,\n ...(connection.quota?.windows.map((window) => window.resetsAt) ?? []),\n ]\n .filter((at): at is number => at != null && at > input.now)\n .sort((a, b) => a - b);\n return transitions.find((now) => canServe({ ...input, now }, connection, modelId)) ?? null;\n}\n\n/** Own Personal workspaces use connected accounts directly; elsewhere fallback requires consent. */\nexport function personalFallbackActive(input: PlacementInput): boolean {\n if (input.session.ownerMembershipId === null) return false;\n if (input.workspace.kind === \"shared\" && input.session.visibility === \"shared\") return false;\n return (\n (input.workspace.kind === \"personal\" &&\n input.workspace.ownerMembershipId === input.session.ownerMembershipId) ||\n (input.settings.personalFallbackAllowed &&\n !!findPerson(input, input.session.ownerMembershipId)?.personalFallbackOptIn)\n );\n}\n","import type { ModelDescriptor } from \"./types\";\n\n/**\n * Map a requested reasoning level onto a target model (SUB-FAIL-03, D-16).\n *\n * A level the target supports by name is kept. Otherwise the level's relative\n * position on the source model's ladder (lowest 0, highest 1) is mapped to the\n * nearest relative position on the target's ladder, an exact tie going to the\n * lower level. A level the source ladder does not list has no position and\n * maps to the middle of the target's ladder (the lower middle of two).\n */\nexport function mapReasoningLevel(\n requested: string,\n source: Pick<ModelDescriptor, \"reasoningLevels\"> | undefined,\n target: Pick<ModelDescriptor, \"reasoningLevels\">,\n): string {\n const levels = target.reasoningLevels;\n if (levels.includes(requested)) return requested;\n if (levels.length <= 1) return levels[0] ?? \"\";\n const sourceLevels = source?.reasoningLevels ?? [];\n const position = sourceLevels.indexOf(requested);\n // The requested position as an exact fraction numerator / denominator.\n const [numerator, denominator] =\n position < 0 || sourceLevels.length <= 1 ? [1, 2] : [position, sourceLevels.length - 1];\n // round(numerator / denominator * (L - 1)) with ties down, in integers.\n const scaled = numerator * (levels.length - 1);\n const index = Math.floor((2 * scaled + denominator - 1) / (2 * denominator));\n return levels[Math.min(index, levels.length - 1)]!;\n}\n","/**\n * Spread hash (D-21, D-23): 32-bit FNV-1a over the UTF-16 code units of\n * `<session id>|<connection id>`, finished with the murmur3 32-bit finalizer.\n * Among otherwise equal connections a session takes the one with the lowest\n * value, so each session has a stable home that does not move when an\n * unrelated connection joins or leaves the pool, and placement needs no lock\n * across sessions (SUB-SEL-05). Without the finalizer, FNV-1a values of keys\n * that differ only in their last characters are not uniformly ordered, which\n * skews the spread (SUB-SEL-03, D-23).\n */\nexport function spreadHash(sessionId: string, connectionId: string): number {\n const key = `${sessionId}|${connectionId}`;\n let hash = 0x811c9dc5;\n for (let index = 0; index < key.length; index += 1) {\n hash ^= key.charCodeAt(index);\n hash = Math.imul(hash, 0x01000193) >>> 0;\n }\n hash ^= hash >>> 16;\n hash = Math.imul(hash, 0x85ebca6b) >>> 0;\n hash ^= hash >>> 13;\n hash = Math.imul(hash, 0xc2b2ae35) >>> 0;\n hash ^= hash >>> 16;\n return hash >>> 0;\n}\n","import { isCacheWarm } from \"./cache\";\nimport {\n canServe,\n connectionUsesExtraCredits,\n connectionIneligibility,\n isPermanentIneligibility,\n modelAllowedInWorkspace,\n personalFallbackActive,\n providerEnabled,\n servableAgainAt,\n type Ineligibility,\n} from \"./eligibility\";\nimport { mapReasoningLevel } from \"./reasoning\";\nimport {\n FALLBACK_ORDER_LIMITS,\n fallbackOrderFor,\n hasFallbackOrderEntry,\n rotationFor,\n} from \"./settings\";\nimport { spreadHash } from \"./spread\";\nimport type {\n ModelDescriptor,\n ModelId,\n ModelTier,\n PlacementDecision,\n PlacementInput,\n PlacementSwitch,\n ReselectionPoint,\n SubscriptionConnection,\n WaitReason,\n} from \"./types\";\n\nfunction findModel(input: PlacementInput, modelId: ModelId): ModelDescriptor | undefined {\n return input.models.find((model) => model.id === modelId);\n}\n\n/** Every model tier, as declared once in the pure package (D-35). */\nexport const MODEL_TIERS: readonly ModelTier[] = Object.freeze([\"frontier\", \"standard\", \"fast\"]);\n\n/** Whether a value is one of the declared model tiers. */\nexport function isModelTier(value: unknown): value is ModelTier {\n return typeof value === \"string\" && (MODEL_TIERS as readonly string[]).includes(value);\n}\n\n/**\n * The default fallback order of a preferred model that has no explicit\n * entry (D-35): the models of other providers that declare the preferred\n * model's tier, never a lower or a higher one, providers in id order and\n * each provider's models in their input (declaration) order, at most\n * `FALLBACK_ORDER_LIMITS.fallbacksPerModel`. Empty while cross-provider\n * failover is off, and for a preferred model that is unknown or declares no\n * tier; a model without a tier is never derived. Same-provider model\n * fallback stays explicit only. Stepping down a tier is not allowed until\n * the owner decides it (Q-04). Provider ids are opaque keys here: which\n * providers' models the input carries is the caller's choice.\n */\nexport function derivedFallbackOrder(input: PlacementInput): ModelId[] {\n if (!input.settings.crossProviderFailover) return [];\n const preferred = findModel(input, input.session.preferredModelId);\n if (!preferred || !isModelTier(preferred.tier)) return [];\n const tier = preferred.tier;\n const derived = input.models\n .map((model, index) => ({ model, index }))\n .filter(\n ({ model }) =>\n model.provider !== preferred.provider && model.tier === tier && model.id !== preferred.id,\n )\n .sort(\n (left, right) =>\n (left.model.provider < right.model.provider\n ? -1\n : left.model.provider > right.model.provider\n ? 1\n : 0) || left.index - right.index,\n )\n .map(({ model }) => model.id);\n return [...new Set(derived)].slice(0, FALLBACK_ORDER_LIMITS.fallbacksPerModel);\n}\n\n/**\n * Candidate models in failover order (design 4 step 3): the preferred model,\n * then its fallbacks: the explicit effective entry when there is one, even\n * an empty one, else the same-tier order `derivedFallbackOrder` derives\n * (D-35). Same-provider fallbacks are always candidates; other providers\n * need cross-provider failover (SUB-FAIL-02, SUB-FAIL-03). A session limited\n * to \"only this model\" or bound by an explicit choice has only the preferred\n * model (SUB-FAIL-05). Restrictions do not remove candidates, so a restricted\n * preferred model keeps its place in the order (D-17); see `modelPermitted`.\n */\nexport function failoverCandidateModels(input: PlacementInput): ModelId[] {\n const { session, settings } = input;\n const preferredId = session.preferredModelId;\n if (session.onlyThisModel || session.binding?.choice === \"explicit\") return [preferredId];\n const preferred = findModel(input, preferredId);\n const candidates: ModelId[] = [preferredId];\n const order = hasFallbackOrderEntry(settings, preferredId)\n ? fallbackOrderFor(settings, preferredId)\n : derivedFallbackOrder(input);\n for (const modelId of order) {\n if (candidates.includes(modelId)) continue;\n const model = findModel(input, modelId);\n if (!model) continue;\n if (model.provider !== preferred?.provider && !settings.crossProviderFailover) continue;\n candidates.push(modelId);\n }\n return candidates;\n}\n\n/**\n * May this session's work use this model at all? The workspace model ceiling\n * (SUB-ELIG-02), the provider switch (SUB-SET-06) and a compaction provider\n * lock (SUB-FAIL-09) restrict models alike: a restricted preferred model is\n * treated like one without capacity and work moves to the next permitted\n * candidate (D-17, D-26).\n */\nexport function modelPermitted(\n input: PlacementInput,\n modelId: ModelId,\n honourCompactionLock = true,\n): boolean {\n const model = findModel(input, modelId);\n if (!model) return false;\n if (!modelAllowedInWorkspace(input.workspace, modelId)) return false;\n if (!providerEnabled(input, model.provider)) return false;\n const lock = input.session.compactionProviderLock;\n return !honourCompactionLock || lock === null || model.provider === lock;\n}\n\n/**\n * Order servable connections for one model (design 4 step 5): the primary\n * first, whether or not its quota is known (D-13, D-15), then the session's\n * spread hash (D-14, D-21, D-23), then id. Missing telemetry is not a penalty.\n */\nexport function rankConnections(\n input: PlacementInput,\n connections: readonly SubscriptionConnection[],\n): SubscriptionConnection[] {\n const keyed = connections.map((connection) => {\n const rotation = rotationFor(input.settings, connection.provider);\n return {\n connection,\n credits: connectionUsesExtraCredits(input, connection),\n primary: rotation.mode === \"primary_first\" && rotation.primaryConnectionId === connection.id,\n hash: spreadHash(input.session.id, connection.id),\n };\n });\n keyed.sort(\n (left, right) =>\n Number(left.credits) - Number(right.credits) ||\n Number(right.primary) - Number(left.primary) ||\n left.hash - right.hash ||\n (left.connection.id < right.connection.id\n ? -1\n : left.connection.id > right.connection.id\n ? 1\n : 0),\n );\n return keyed.map((entry) => entry.connection);\n}\n\n/**\n * Why an automatic binding does not keep its account for this placement\n * (SUB-STICK-03). Every reason except `cache_cold` is forced: the account or\n * its model cannot (or should not) serve this work now, whatever the cache.\n */\nexport type ReselectionReason =\n /** The session has no account yet. */\n | \"unbound\"\n /**\n * The bound account is not in this session's world: removed, or outside\n * the session's scope (a disconnected account stays in the world and is\n * `account_ineligible`).\n */\n | \"account_unavailable\"\n /** The bound model may no longer be used here (workspace, provider switch, compaction lock). */\n | \"model_not_permitted\"\n /**\n * The bound account cannot serve now; `ineligibility` says why (exhausted\n * or rate-limited, unhealthy or quarantined, disconnected, switched off,\n * personal authority or owner gone).\n */\n | \"account_ineligible\"\n /** The bound account would spend paid credits while another has included allowance (SUB-CREDITS-01). */\n | \"extra_credits\"\n /** A compaction or model change replaced the cached prefix (SUB-STICK-05). */\n | \"reselection_point\"\n /** Idle longer than the provider's declared cache lifetime (SUB-STICK-04). */\n | \"cache_cold\";\n\nexport type BindingStickiness =\n | { kind: \"keep\"; connectionId: string; modelId: ModelId }\n | {\n kind: \"reselect\";\n reason: ReselectionReason;\n /** The bound account's ineligibility for its model (empty unless `account_ineligible`). */\n ineligibility: readonly Ineligibility[];\n /** The re-selection points that applied (empty unless `reselection_point`). */\n points: readonly ReselectionPoint[];\n };\n\n/**\n * Does the session's automatic binding keep its account (SUB-STICK-02,\n * SUB-STICK-03)? It does while the account can still serve the bound model,\n * no re-selection point applies and the prompt cache is warm; otherwise\n * placement re-runs normal selection, which may land on the same account.\n * Forced reasons are checked before warmth, so a warm cache never keeps an\n * account the session may not use (SUB-STICK-07). An explicit choice is not\n * evaluated here: placement keeps it or waits for it (SUB-STICK-06).\n */\nexport function bindingStickiness(input: PlacementInput): BindingStickiness {\n const { session, connections } = input;\n const binding = session.binding;\n const reselect = (\n reason: ReselectionReason,\n ineligibility: readonly Ineligibility[] = [],\n points: readonly ReselectionPoint[] = [],\n ): BindingStickiness => ({ kind: \"reselect\", reason, ineligibility, points });\n if (!binding) return reselect(\"unbound\");\n const bound = connections.find((connection) => connection.id === binding.connectionId);\n if (!bound) return reselect(\"account_unavailable\");\n const permitted = failoverCandidateModels(input).filter((modelId) =>\n modelPermitted(input, modelId),\n );\n if (!permitted.includes(binding.modelId))\n // The session moved to another model (no longer a candidate at all) is\n // a re-selection point; a candidate the session may not use is forced.\n return session.reselectionPoints.length > 0 &&\n !failoverCandidateModels(input).includes(binding.modelId)\n ? reselect(\"reselection_point\", [], session.reselectionPoints)\n : reselect(\"model_not_permitted\");\n const ineligibility = connectionIneligibility(input, bound, binding.modelId);\n if (ineligibility.length > 0) return reselect(\"account_ineligible\", ineligibility);\n const fallback = personalFallbackActive(input);\n if (\n connectionUsesExtraCredits(input, bound) &&\n connections.some(\n (candidate) =>\n (candidate.ownership.kind === \"shared\" || fallback) &&\n !connectionUsesExtraCredits(input, candidate) &&\n canServe(input, candidate, binding.modelId),\n )\n ) {\n return reselect(\"extra_credits\");\n }\n if (session.reselectionPoints.length > 0)\n return reselect(\"reselection_point\", [], session.reselectionPoints);\n if (!isCacheWarm(binding, input.cacheFacts[bound.provider], input.now))\n return reselect(\"cache_cold\");\n return { kind: \"keep\", connectionId: bound.id, modelId: binding.modelId };\n}\n\n/**\n * Place one session's turn (design 4): run on an account and model, or wait\n * with an explained reason. The sender is deliberately not an input: the\n * account belongs to the session (SUB-STICK-01).\n */\nexport function decidePlacement(input: PlacementInput): PlacementDecision {\n const { session, connections } = input;\n const preferred = findModel(input, session.preferredModelId);\n const models = failoverCandidateModels(input);\n const permitted = models.filter((modelId) => modelPermitted(input, modelId));\n const fallback = personalFallbackActive(input);\n const wait = (reason: WaitReason, earliestResetAt: number | null): PlacementDecision => ({\n kind: \"wait\",\n reason,\n earliestResetAt,\n });\n const run = (\n connection: SubscriptionConnection,\n modelId: ModelId,\n kind: PlacementSwitch,\n ): PlacementDecision => ({\n kind: \"run\",\n connectionId: connection.id,\n provider: connection.provider,\n modelId,\n reasoningLevel: mapReasoningLevel(\n session.reasoningLevel,\n preferred,\n findModel(input, modelId)!,\n ),\n switch: kind,\n personal: connection.ownership.kind === \"personal\",\n });\n const servableAutomatically = (modelId: ModelId) =>\n connections.some(\n (connection) =>\n (connection.ownership.kind === \"shared\" || fallback) &&\n canServe(input, connection, modelId),\n );\n // Candidates the compaction lock alone keeps from this session (SUB-FAIL-09).\n const lockedOut = models.filter(\n (modelId) => !modelPermitted(input, modelId) && modelPermitted(input, modelId, false),\n );\n\n // No candidate model may be used here: wait on the restriction (D-17).\n if (permitted.length === 0) {\n return wait(lockedOut.length > 0 ? \"compaction_provider_locked\" : \"model_not_allowed\", null);\n }\n\n const binding = session.binding;\n const bound = binding\n ? connections.find((connection) => connection.id === binding.connectionId)\n : undefined;\n\n // An explicit choice is binding: run there or wait for it (SUB-SEL-04,\n // SUB-STICK-06). One that can never serve this work says so instead of\n // waiting silently (D-24, SUB-ACCESS-06).\n if (binding?.choice === \"explicit\") {\n if (bound && canServe(input, bound, session.preferredModelId)) {\n return run(bound, session.preferredModelId, \"pinned\");\n }\n if (\n !bound ||\n connectionIneligibility(input, bound, session.preferredModelId).some(isPermanentIneligibility)\n ) {\n return wait(\"pinned_account_ineligible\", null);\n }\n if (\n connectionIneligibility(input, bound, session.preferredModelId).includes(\"allocator_disabled\")\n ) {\n return wait(\"pinned_account_disabled\", null);\n }\n const at = servableAgainAt(input, bound, session.preferredModelId);\n return wait(\"pinned_account_unavailable\", typeof at === \"number\" ? at : null);\n }\n\n const bindingServable =\n !!binding &&\n !!bound &&\n permitted.includes(binding.modelId) &&\n canServe(input, bound, binding.modelId);\n\n // Keep a warm session where it is unless a re-selection point applies\n // or the account is forced away (SUB-STICK-02, SUB-STICK-03, SUB-STICK-05).\n const stickiness = bindingStickiness(input);\n if (stickiness.kind === \"keep\" && bound) return run(bound, stickiness.modelId, \"sticky\");\n\n // Each model on shared connections, then (opt-in) on the owner's personal\n // connections, before the next model (SUB-SEL-01, SUB-SEL-02, D-12).\n for (const [index, modelId] of models.entries()) {\n if (!modelPermitted(input, modelId)) continue;\n const eligible = connections.filter(\n (connection) =>\n canServe(input, connection, modelId) &&\n (connection.ownership.kind === \"shared\" || fallback),\n );\n const included = eligible.filter(\n (connection) => !connectionUsesExtraCredits(input, connection),\n );\n const servable = included.length ? included : eligible;\n const chosen =\n rankConnections(\n input,\n servable.filter((connection) => connection.ownership.kind === \"shared\"),\n )[0] ??\n (fallback\n ? rankConnections(\n input,\n servable.filter((connection) => connection.ownership.kind === \"personal\"),\n )[0]\n : undefined);\n if (!chosen) continue;\n let kind: PlacementSwitch;\n if (!binding) kind = \"initial\";\n else if (binding.connectionId === chosen.id && binding.modelId === modelId) kind = \"sticky\";\n else if (index === 0) {\n if (binding.modelId !== modelId) kind = \"return_to_preferred\";\n else kind = bindingServable ? \"reselected_cold\" : \"failover_same_provider\";\n } else {\n kind =\n findModel(input, modelId)?.provider === preferred?.provider\n ? \"failover_same_provider\"\n : \"failover_cross_provider\";\n }\n return run(chosen, modelId, kind);\n }\n\n // Nothing servable: wait for the earliest known time a connection this\n // session may use automatically can serve a permitted model (SUB-WAIT-01,\n // SUB-WAIT-02, D-22), and name the compaction lock when it is what keeps a\n // servable model away (SUB-FAIL-09).\n let earliest: number | null = null;\n for (const modelId of permitted) {\n for (const connection of connections) {\n if (connection.ownership.kind === \"personal\" && !fallback) continue;\n const at = servableAgainAt(input, connection, modelId);\n if (typeof at === \"number\") earliest = earliest === null ? at : Math.min(earliest, at);\n }\n }\n return wait(\n lockedOut.some(servableAutomatically) ? \"compaction_provider_locked\" : \"no_eligible_capacity\",\n earliest,\n );\n}\n","import type { SubscriptionQuotaBlock } from \"./quota\";\nimport type { CacheFacts, ModelId, ModelTier, ProviderId, SubscriptionQuota } from \"./types\";\n\n/**\n * The prompt-cache lifetime a provider adapter declares (SUB-PROV-01,\n * SUB-STICK-04): either the exact lifetime Opengeni sends with each request,\n * or an idle cut-off taken from the provider's public documentation. Every\n * adapter states a positive number; the shared core never falls back to a\n * default for a provider and never names one. Choose it conservatively:\n * a session switches account only once idle longer than this, so a value\n * that is too short pays for a cache miss the provider would have avoided.\n */\nexport type DeclaredCacheFacts =\n | { kind: \"exact_ttl\"; ttlMs: number }\n | { kind: \"measured_idle_cutoff\"; cutoffMs: number };\n\n/** The declared lifetime in milliseconds, or a thrown error for an unusable declaration. */\nexport function declaredCacheLifetimeMs(facts: DeclaredCacheFacts): number {\n const lifetime = facts.kind === \"exact_ttl\" ? facts.ttlMs : facts.cutoffMs;\n if (!Number.isSafeInteger(lifetime) || lifetime <= 0)\n throw new Error(\"A provider adapter must declare a positive whole-millisecond cache lifetime\");\n return lifetime;\n}\n\n/**\n * Provider differences are capability flags, never provider conditionals in\n * shared code (SUB-PROV-02).\n */\nexport type ProviderCapabilities = {\n /**\n * The credential renews itself through refresh. A provider whose credential\n * formats differ (OAuth renews, a setup token or an API key does not)\n * declares it per format through `capabilitiesFor`.\n */\n autoRenews: boolean;\n resetCredits: boolean;\n /** Paid usage beyond the plan can be enabled per connection (consent-gated). */\n extraCredits: boolean;\n modelEntitlements: boolean;\n realtime: boolean;\n fundsMedia: boolean;\n apps: boolean;\n remoteCompaction: boolean;\n quotaWindows: boolean;\n};\n\n/**\n * Shared error outcomes every adapter classifies into (SUB-PROV-01). A\n * refusal that limits one model only carries its `modelId`, so the core can\n * record it as that model's cooldown on the connection\n * (`modelCooldownFromOutcome`). The shared settlement step\n * (`planSubscriptionCoreRefusal`) records it; the live worker does once it\n * adopts that step.\n */\nexport type ProviderErrorOutcome =\n | { kind: \"exhausted\"; resetAt: number | null; modelId?: ModelId }\n | { kind: \"rate_limited\"; retryAfterMs: number | null; modelId?: ModelId }\n | { kind: \"unauthorized\" }\n | { kind: \"forbidden\" }\n | { kind: \"entitlement_missing\"; modelId: ModelId }\n | { kind: \"overloaded\" }\n | { kind: \"transient\" }\n | { kind: \"fatal\" };\n\n/** An encrypted credential as stored; the core never decrypts it. */\nexport type EncryptedCredential = {\n credentialEncrypted: string;\n credentialFormat: string;\n expiresAt: number | null;\n};\n\n/** Identity a sign-in reports for the connection row. */\nexport type ConnectionIdentity = {\n providerAccountId: string;\n accountEmail: string | null;\n planType: string | null;\n};\n\nexport type SignInResult = { credential: EncryptedCredential; identity: ConnectionIdentity };\n\n/** Items in conversation history that only their own provider accepts. */\nexport type ProviderHistoryItemKind =\n | \"encrypted_reasoning\"\n | \"thinking_signature\"\n | \"provider_tool\"\n | \"remote_compaction\";\n\n/**\n * What a provider can accept from history written while another provider\n * served the session (design 4, SUB-FAIL-08).\n */\nexport type HistoryCompatibility = {\n /** Provider-specific item kinds to drop from a request copy for this provider. */\n dropFromOtherProviders: readonly ProviderHistoryItemKind[];\n};\n\n/**\n * The adapter surface every model connection implements, including API-key\n * connectors (SUB-PROV-04). `Transport` is the provider's request-local\n * authorization handle; `ProviderError` is whatever the transport throws.\n */\nexport interface ModelConnectionAdapter<Transport = unknown, ProviderError = unknown> {\n readonly provider: ProviderId;\n readonly capabilities: ProviderCapabilities;\n /** Request-local authorization and wire normalization for one selected connection. */\n transport(input: { connectionId: string; credential: EncryptedCredential }): Promise<Transport>;\n /** Models the connection's plan can serve, including provider-observed exclusions. */\n entitledModels(input: {\n planType: string | null;\n excludedModelIds: readonly ModelId[];\n }): readonly ModelId[] | null;\n /** Null when the error is not a provider outcome (for example a programming error). */\n classifyError(error: ProviderError): ProviderErrorOutcome | null;\n cacheFacts(input: { modelId: ModelId }): CacheFacts;\n historyCompatibility(): HistoryCompatibility;\n /**\n * Optional out-of-turn quota probe (a provider usage endpoint), decoded by\n * the subscription adapter's `decodeQuota`.\n */\n fetchUsage?(transport: Transport): Promise<unknown>;\n /** Optional live model catalog of the connection, cached per refresh generation. */\n liveModels?(transport: Transport): Promise<readonly ModelId[]>;\n}\n\n/**\n * The full adapter of a signed-in subscription (SUB-PROV-01): sign-in,\n * refresh, quota decoding, plus the shared connection surface.\n */\nexport interface SubscriptionProviderAdapter<\n Transport = unknown,\n ProviderError = unknown,\n SignInInput = unknown,\n UsageResponse = unknown,\n> extends ModelConnectionAdapter<Transport, ProviderError> {\n /** OAuth, device code or setup token; returns an encrypted secret and identity. */\n signIn(input: SignInInput): Promise<SignInResult>;\n /**\n * Token refresh, called only under the core's single per-connection lock.\n * The core increments `refresh_generation` for every successful refresh.\n */\n refresh(input: {\n connectionId: string;\n credential: EncryptedCredential;\n }): Promise<EncryptedCredential>;\n /** Usage responses or headers decoded into the shared quota model. */\n decodeQuota(input: {\n response: UsageResponse;\n observedAt: number;\n refreshGeneration: number;\n }): Omit<SubscriptionQuota, \"revision\">;\n}\n\n/**\n * How a provider's credential is obtained and kept valid. `oauth` renews\n * through refresh; `setup_token` is pasted once and may not renew; `api_key`\n * is a static key with no refresh (API-key connectors).\n */\nexport type CredentialKind = \"oauth\" | \"setup_token\" | \"api_key\";\n\n/** How a provider reports usage limits to the shared quota model. */\nexport type QuotaKind =\n /** Rolling usage windows with reset times (subscriptions). */\n | \"usage_windows\"\n /** A spend budget in currency units (API-key connectors). */\n | \"spend_budget\"\n /** Request or token rate limits only, observed from refusals or headers. */\n | \"rate_limits\";\n\n/**\n * The provider facts the shared subscription runtime needs, beyond the\n * connection surface: everything the core does is identical for every\n * provider, and these members are the only per-provider inputs. Shared core\n * modules receive an adapter as data and never branch on its `provider`.\n *\n * `Credential` is the decoded secret (OAuth tokens, a setup token, an API\n * key). The core stores only its encrypted encoding and never inspects it.\n */\nexport interface SubscriptionCoreAdapter<Credential = unknown> {\n readonly provider: ProviderId;\n /** Product name used in operator-facing error texts (never in routing). */\n readonly displayName: string;\n /** Capabilities of the provider's renewable credential format. */\n readonly capabilities: ProviderCapabilities;\n /**\n * Capabilities of one credential format, as `credential.format` derives it\n * from a decoded secret. Pass that derived value, never the stored\n * `credential_format` column: the column is informational and may hold the\n * shared writers' envelope version (`v1`). The core never calls `refresh`\n * for a format whose `autoRenews` is false.\n */\n capabilitiesFor(credentialFormat: string): ProviderCapabilities;\n readonly credentialKind: CredentialKind;\n readonly quotaKind: QuotaKind;\n /** Provider identity used by workspace model policy for this provider's models. */\n readonly modelPolicyProviderId: string;\n /**\n * Every workspace model policy provider identity one model must be allowed\n * under, for a provider whose models carry more than one (absent, null or\n * empty: `modelPolicyProviderId` alone). A model is allowed only when the\n * policy allows each identity, so a second identity can only narrow access.\n * Read through `subscriptionCoreModelPolicyProviderIds`.\n */\n readonly modelPolicyProviderIdsFor?: (modelId: ModelId) => readonly string[] | null;\n /**\n * The prompt-cache lifetime placement keeps a session's account for\n * (SUB-STICK-04): an exact TTL Opengeni sends, or a documented idle cut-off.\n */\n readonly cacheFacts: DeclaredCacheFacts;\n /** Health policy applied to this provider's connections. */\n readonly health: {\n /** How long a connection the provider refused (forbidden) stays quarantined. */\n readonly forbiddenQuarantineMs: number;\n /** How long a model the plan is not entitled to stays cooled down. */\n readonly entitlementCooldownMs: number;\n /** How long a rate-limited connection rests when the provider gave no retry time. */\n readonly rateLimitFallbackMs: number;\n /**\n * How long an exhausted connection (usage limit or spend budget) rests\n * when the provider gave no reset. Placement retries it then, so a\n * connector without a usage reading still recovers on its own.\n */\n readonly exhaustedFallbackMs: number;\n };\n /** Plaintext codec for the encrypted credential column. */\n readonly credential: {\n /** Parse decrypted plaintext; throw fixed text (never echo the plaintext). */\n decode(plaintext: string): Credential;\n encode(credential: Credential): string;\n /** The expiry embedded in the credential (a JWT exp), used when the store has none. */\n expiry(credential: Credential): Date | null;\n /** The stored `credential_format` of a decoded credential. */\n format(credential: Credential): string;\n };\n /**\n * Credential renewal under the core's single per-connection lock. Null for\n * credentials that never renew (API keys, setup tokens without OAuth).\n */\n readonly refresh: CredentialRefresher<Credential> | null;\n /** Stored needs-relogin text for a provider message (fills an empty one). */\n reloginText(message: string): string;\n /**\n * Optional tier of one of the provider's models (D-35). With no explicit\n * fallback entry, placement derives a cross-provider order from equal\n * tiers. Absent, or null for a model: no tier, so nothing is derived for\n * or to that model and only explicit fallback entries apply.\n */\n modelTier?(modelId: ModelId): ModelTier | null;\n /**\n * Optional out-of-turn quota probe: read the provider's usage endpoint\n * with one connection's bearer. Every physical request goes through\n * `input.fetch` (the core's request custody). The response is opaque to\n * the core and decoded by `decodeQuota`.\n */\n fetchUsage?(input: SubscriptionCoreConnectionRead<Credential>): Promise<unknown>;\n /**\n * Decode a `fetchUsage` response into the shared quota model, fenced on\n * the refresh generation of the bearer that read it. Null when the\n * response carries no quota (an error payload or no windows).\n *\n * `modelIds`, when the caller passes it, lists the product models the\n * connection can serve when the response is decoded (the caller's\n * catalog), for providers whose usage reports limits of model groups\n * that become per-model cooldowns. Absent: no catalog is known, and only\n * models the response itself names can be cooled down.\n */\n decodeQuota?(input: {\n response: unknown;\n observedAt: number;\n refreshGeneration: number;\n modelIds?: readonly ModelId[];\n }): SubscriptionQuota | null;\n /**\n * The provider's usage endpoint is authoritative for its quota: a\n * `decodeQuota` reading below the limit ends a stored quota exhaustion\n * (`exhaustedKind: \"quota\"`) before its deadline, as an explicit,\n * revision-fenced store write. Absent: a running deadline is kept until\n * it passes (design 2.2).\n */\n usageReadEndsQuotaExhaustion?: boolean;\n /**\n * With `usageReadEndsQuotaExhaustion`: whether one `fetchUsage` response\n * confirms the connection may serve again. A response can report every\n * window below the limit and still say the account is refused (or that a\n * feature-specific limit is spent); such a reading never ends a stored\n * quota exhaustion. Absent: every reading below the limit does.\n */\n usageReadConfirmsAvailability?(response: unknown): boolean;\n /**\n * With `usageReadEndsQuotaExhaustion`: which stored blocks one `fetchUsage`\n * response that confirms availability shows have ended, judged from each\n * block's own claim (the limits the observation that set it named, tied\n * to its exact deadline: `QuotaBlockClaim`). `modelId` is the cooled\n * product model, or null for the quota exhaustion deadline. The predicate\n * must be false for a block without a claim, for a claim observed no\n * earlier than the response, and unless the response reports every\n * claimed limit numerically below it (an explicit \"no such limit\" does\n * not clear a limit that refused). A cooldown the reading itself reports\n * stands, and so does every block the predicate does not end.\n * When defined, a stored quota exhaustion deadline ends only where it\n * returns true. Absent: a reading ends no cooldown (each runs until its\n * own end), and `usageReadEndsQuotaExhaustion` alone decides the deadline.\n */\n usageReadEndsClaimedBlock?(response: unknown): (block: SubscriptionQuotaBlock) => boolean;\n /**\n * The provider's usage-endpoint readings are complete: each reports every\n * window that limits the connection, so a stored exhausted window that a\n * newer reading omits has ended and is dropped. Absent or false: a\n * reading may omit a window that still runs, and the shared quota merge\n * keeps an omitted exhausted window until its own reset (fail closed,\n * design 2.2). Response headers and refusals are always partial.\n */\n usageReadingsComplete?: boolean;\n /**\n * Optional live model catalog of one connection (provider model ids),\n * read with its bearer through `input.fetch`. Throws when the provider\n * refuses the credential.\n */\n liveModels?(input: SubscriptionCoreConnectionRead<Credential>): Promise<readonly ModelId[]>;\n}\n\n/** One connection's bearer as a connection read sees it. */\nexport type SubscriptionCoreConnectionBearer<Credential> = {\n /** The decoded, current credential. */\n credential: Credential;\n providerAccountId: string | null;\n /** Provider-owned connection facts; only the provider's adapter interprets them. */\n providerState: Record<string, unknown>;\n};\n\n/** What a connection read (usage, live catalog) receives from the core. */\nexport type SubscriptionCoreConnectionRead<Credential> = {\n /** The current bearer (refreshed under the core lock when stale). */\n getToken(): Promise<SubscriptionCoreConnectionBearer<Credential>>;\n /** A forced refresh under the core lock, after the provider refused the bearer. */\n refresh(): Promise<SubscriptionCoreConnectionBearer<Credential>>;\n /** A provider HTTP call under the core's request custody. */\n fetch: (input: string | URL, init?: RequestInit) => Promise<Response>;\n};\n\n/** Renewal of one credential; the core persists the result before anything else. */\nexport type CredentialRefresher<Credential> = {\n /** Refresh this long before the credential expires. */\n readonly windowMs: number;\n /** Refresh after this long when the expiry is unknown. */\n readonly fallbackMs: number;\n /** Call the provider and return the rotated credential. */\n rotate(credential: Credential): Promise<RotatedCredential<Credential>>;\n /**\n * The needs-relogin message when `error` is a permanent refusal of the\n * credential (revoked or expired refresh token), else null.\n */\n reloginMessage(error: unknown): string | null;\n};\n\nexport type RotatedCredential<Credential> = {\n credential: Credential;\n expiresAt: Date | null;\n /** The plan the rotated credential reports, or null when it reports none. */\n planType: string | null;\n};\n\n/**\n * The refresher the core may call for one decoded credential: the adapter's\n * refresher when the credential's format renews, else null (the credential\n * is used until it expires or is refused, then needs a new sign-in).\n */\nexport function subscriptionCoreCredentialRefresher<Credential>(\n adapter: Pick<SubscriptionCoreAdapter<Credential>, \"capabilitiesFor\" | \"credential\" | \"refresh\">,\n credential: Credential,\n): CredentialRefresher<Credential> | null {\n if (adapter.refresh === null) return null;\n return adapter.capabilitiesFor(adapter.credential.format(credential)).autoRenews\n ? adapter.refresh\n : null;\n}\n\n/**\n * The workspace model policy provider identities one of the adapter's models\n * must all be allowed under (never empty).\n */\nexport function subscriptionCoreModelPolicyProviderIds(\n adapter: Pick<SubscriptionCoreAdapter, \"modelPolicyProviderId\" | \"modelPolicyProviderIdsFor\">,\n modelId: ModelId,\n): readonly string[] {\n const ids = adapter.modelPolicyProviderIdsFor?.(modelId);\n return ids && ids.length > 0 ? ids : [adapter.modelPolicyProviderId];\n}\n\n/**\n * The model cooldown a refusal implies, or null when it limits the whole\n * connection (no `modelId`) or carries no time. `rate_limited` without a\n * known delay is not a cooldown: the connection's own rate-limit handling\n * applies.\n */\nexport function modelCooldownFromOutcome(\n outcome: ProviderErrorOutcome,\n now: number,\n): { modelId: ModelId; until: number } | null {\n if (outcome.kind === \"rate_limited\") {\n if (outcome.modelId === undefined || outcome.retryAfterMs === null) return null;\n return { modelId: outcome.modelId, until: now + Math.max(0, outcome.retryAfterMs) };\n }\n if (outcome.kind === \"exhausted\") {\n if (outcome.modelId === undefined || outcome.resetAt === null) return null;\n return outcome.resetAt > now ? { modelId: outcome.modelId, until: outcome.resetAt } : null;\n }\n return null;\n}\n","import {\n modelCooldownFromOutcome,\n type ProviderErrorOutcome,\n type SubscriptionCoreAdapter,\n} from \"./adapter\";\nimport type { ModelId, SubscriptionQuota } from \"./types\";\n\n/**\n * How a reserved model request settles (the request ledger's outcome):\n * `refused` when the provider definitively declined it, `response_received`\n * when a response arrived (an error response included), `unknown` when the\n * outcome cannot be known (the connection dropped mid-call), which blocks\n * every later request of the turn instead of a replay.\n */\nexport type SubscriptionCoreRequestOutcome = \"response_received\" | \"refused\" | \"unknown\";\n\n/** The connection state a definitive refusal leaves, so placement avoids it. */\nexport type SubscriptionCoreRefusalHealth =\n /** A quota observation: exhausted (usage limit, spend budget or rate limit) until `exhaustedUntil`. */\n | { kind: \"quota\"; observation: SubscriptionQuota }\n /** One model rests on the connection until `until`. */\n | { kind: \"model_cooldown\"; modelId: ModelId; until: number }\n /** `sign_in`: needs a new sign-in or key; `forbidden`: a time-bound quarantine. */\n | { kind: \"quarantine\"; reason: \"sign_in\" | \"forbidden\" };\n\n/**\n * The provider-neutral settlement of one classified upstream outcome on the\n * leased connection (design 5.3, \"API-key connectors on the shared core\").\n *\n * - `retry_after_refresh`: a refused renewable credential is refreshed once\n * under the core's lock and the same connection is retried; only a refusal\n * that survives the refresh is settled.\n * - `settle`: the (turn, connection) failure receipt is written first (it\n * counts toward the per-turn failover bound and carries the refresh\n * generation the refused credential held), then `health`; placement then\n * fails over.\n * - `no_failover`: an overloaded, failed or fatal reply is not a refusal of\n * the connection: no receipt, no health change, no rotation (it may have\n * consumed work; the turn's own error handling decides).\n */\nexport type SubscriptionCoreRefusalPlan =\n | { kind: \"retry_after_refresh\"; requestOutcome: SubscriptionCoreRequestOutcome }\n | {\n kind: \"settle\";\n requestOutcome: SubscriptionCoreRequestOutcome;\n receipt: { kind: ProviderErrorOutcome[\"kind\"]; evidence: { refreshGeneration: number } };\n health: SubscriptionCoreRefusalHealth;\n }\n | { kind: \"no_failover\"; requestOutcome: SubscriptionCoreRequestOutcome };\n\n/**\n * Plan the settlement of one classified outcome. A null outcome (the\n * connector could not classify what happened, such as a dropped connection)\n * is an unknown request outcome and never a refusal.\n *\n * - a `rate_limited` or `exhausted` refusal naming a model (`modelId`): that\n * model cools down on the connection until the provider's time, or the\n * matching fallback below; the connection keeps serving other models;\n * - `rate_limited`: exhausted (`rate_limit`) until the retry time, or the\n * adapter's `rateLimitFallbackMs` when the provider gave none;\n * - `exhausted`: exhausted (`quota`) until the provider's reset, which is\n * authoritative. Without a known future reset, an exhausted window that\n * resets after the adapter's `exhaustedFallbackMs` instead: placement\n * retries the connection then even when no usage reading exists, and a\n * later usage reading of an adapter that declares its readings complete\n * (which replaces windows but never shortens an exhaustion deadline) can\n * show recovered capacity sooner; a partial reading keeps the window;\n * - `entitlement_missing`: the model cools down for `entitlementCooldownMs`;\n * - `unauthorized` and `forbidden`: refresh and retry once when the credential\n * renews and has not been refreshed for this refusal; otherwise\n * `unauthorized` needs a new sign-in (a static key: a new key) and\n * `forbidden` is quarantined for `forbiddenQuarantineMs`.\n */\nexport function planSubscriptionCoreRefusal(\n adapter: Pick<SubscriptionCoreAdapter, \"health\">,\n outcome: ProviderErrorOutcome | null,\n input: {\n now: number;\n /** The refresh generation of the credential the refused request carried. */\n refreshGeneration: number;\n /** The credential renews (the core may refresh it). */\n renewable: boolean;\n /** This refusal already follows a refresh of the same connection. */\n refreshed: boolean;\n },\n): SubscriptionCoreRefusalPlan {\n if (outcome === null) return { kind: \"no_failover\", requestOutcome: \"unknown\" };\n const { now, refreshGeneration } = input;\n const quota = (\n fields: Pick<SubscriptionQuota, \"windows\" | \"exhaustedUntil\" | \"exhaustedKind\">,\n ): SubscriptionCoreRefusalHealth => ({\n kind: \"quota\" as const,\n observation: {\n modelCooldowns: {},\n revision: 0,\n observedAt: now,\n observedRefreshGeneration: refreshGeneration,\n source: \"refusal\" as const,\n ...fields,\n },\n });\n const settle = (health: SubscriptionCoreRefusalHealth): SubscriptionCoreRefusalPlan => ({\n kind: \"settle\",\n requestOutcome: \"refused\",\n receipt: { kind: outcome.kind, evidence: { refreshGeneration } },\n health,\n });\n // A limit on one model (a gateway's per-model rate limit or budget) cools\n // that model only, until the provider's time or the adapter's fallback;\n // the connection keeps serving its other models.\n if (\n (outcome.kind === \"rate_limited\" || outcome.kind === \"exhausted\") &&\n outcome.modelId !== undefined\n ) {\n const known = modelCooldownFromOutcome(outcome, now);\n return settle({\n kind: \"model_cooldown\",\n modelId: outcome.modelId,\n until:\n known !== null && known.until > now\n ? known.until\n : now +\n (outcome.kind === \"rate_limited\"\n ? adapter.health.rateLimitFallbackMs\n : adapter.health.exhaustedFallbackMs),\n });\n }\n switch (outcome.kind) {\n case \"rate_limited\":\n return settle(\n quota({\n windows: [],\n exhaustedUntil:\n now +\n (outcome.retryAfterMs !== null && outcome.retryAfterMs > 0\n ? outcome.retryAfterMs\n : adapter.health.rateLimitFallbackMs),\n exhaustedKind: \"rate_limit\",\n }),\n );\n case \"exhausted\":\n return settle(\n outcome.resetAt !== null && outcome.resetAt > now\n ? quota({ windows: [], exhaustedUntil: outcome.resetAt, exhaustedKind: \"quota\" })\n : quota({\n windows: [\n {\n id: \"exhausted_without_reset\",\n usedPercent: 100,\n resetsAt: now + adapter.health.exhaustedFallbackMs,\n status: \"exhausted\",\n },\n ],\n exhaustedUntil: null,\n exhaustedKind: null,\n }),\n );\n case \"entitlement_missing\":\n return settle({\n kind: \"model_cooldown\",\n modelId: outcome.modelId,\n until: now + adapter.health.entitlementCooldownMs,\n });\n case \"unauthorized\":\n case \"forbidden\":\n if (input.renewable && !input.refreshed)\n return { kind: \"retry_after_refresh\", requestOutcome: \"refused\" };\n return settle({\n kind: \"quarantine\",\n reason: outcome.kind === \"unauthorized\" ? \"sign_in\" : \"forbidden\",\n });\n case \"overloaded\":\n return { kind: \"no_failover\", requestOutcome: \"refused\" };\n case \"transient\":\n case \"fatal\":\n return { kind: \"no_failover\", requestOutcome: \"response_received\" };\n }\n}\n","import type { WaitReason } from \"./types\";\n\n/** Current wait guidance shared by initial parking and later reclassification.\n * These are new audit events only; never rewrite an earlier model request. */\nexport function subscriptionCapacityWaitReasonText(reason: string, name: string): string | null {\n const messages: Record<WaitReason, string> = {\n pinned_account_unavailable: `The ${name} account chosen for this session is unavailable right now. This turn continues automatically once it is available again.`,\n pinned_account_disabled: `The ${name} account pinned to this chat is turned off. Turn it back on, choose another account, or switch the chat to automatic. Waiting alone will not fix this.`,\n pinned_account_ineligible: `The ${name} account chosen for this session can no longer serve this work. This turn continues once another account is chosen or the session is switched back to automatic.`,\n no_eligible_capacity: `No ${name} subscription has capacity for this turn right now. It continues automatically when an account is available.`,\n model_not_allowed: `This model is not allowed for the ${name} subscriptions available here. This turn continues if that changes.`,\n compaction_provider_locked:\n \"This session is tied to a provider that has no capacity right now. It continues automatically when an account is available.\",\n accepted_authority_unavailable:\n \"This work was accepted before the account move and its account access could not be carried. Send it again to continue.\",\n };\n return Object.hasOwn(messages, reason) ? messages[reason as WaitReason] : null;\n}\n\nexport function subscriptionCapacityWaitNeedsAction(reason: string): boolean {\n return [\n \"pinned_account_disabled\",\n \"pinned_account_ineligible\",\n \"model_not_allowed\",\n \"accepted_authority_unavailable\",\n ].includes(reason);\n}\n","/** Pure limit projections. All timestamps are milliseconds; no currency leaves this module. */\nexport type SubscriptionUsagePoolWindowId = \"five_hour\" | \"weekly\";\n\nexport type SubscriptionUsagePoolInput = {\n provider: string;\n now: number;\n accounts: Array<{\n id: string;\n label: string;\n plan: string | null;\n /**\n * Adapter-owned relative tier estimate, not call units. Unknown tiers default to 1.\n * Keep provider/tier tables outside the provider-neutral policy package.\n * The same tier scale must be used for every account in this pool.\n */\n planWeight?: number;\n /**\n * The provider refuses this window until the given time (a usage-limit\n * response), whatever its last percentage said: none of it is left.\n */\n blocked?: Partial<Record<SubscriptionUsagePoolWindowId, number>>;\n windows: Array<{\n id: SubscriptionUsagePoolWindowId;\n usedPercent: number | null;\n resetsAt: number | null;\n observedAt: number | null;\n }>;\n }>;\n readings: Array<{\n connectionId: string;\n window: SubscriptionUsagePoolWindowId;\n usedPercent: number;\n resetsAt: number | null;\n observedAt: number;\n }>;\n /** Attributed, estimated API-equivalent usage; used only for calibration and shape. */\n calls: Array<{ connectionId: string; at: number; units: number }>;\n historyStart: number | null;\n};\n\nexport type SubscriptionUsagePoolAccount = {\n id: string;\n label: string;\n remainingPercent: number | null;\n resetsAt: number | null;\n weight: number;\n weightBasis: \"observed\" | \"plan_estimate\";\n observedAt: number | null;\n};\n\nexport type SubscriptionUsagePoolForecast = {\n kind: \"runs_out\" | \"unused\" | \"insufficient_history\";\n basis: string;\n runsOutAt: number | null;\n nextResetAt: number | null;\n /** Fraction of original full capacity discarded at each account's FIRST upcoming reset. */\n unusedPercent: number | null;\n /** Included capacity used by each account's first upcoming reset, including use so far. */\n utilizationPercent: number | null;\n /** Lowest aggregate remaining percentage, sampled BEFORE reset refills. */\n lowestPercent: number | null;\n historyDays: number;\n};\n\nexport type SubscriptionUsagePool = {\n provider: string;\n observedAt: number | null;\n windows: Array<{\n id: SubscriptionUsagePoolWindowId;\n remainingPercent: number | null;\n accounts: SubscriptionUsagePoolAccount[];\n forecast: SubscriptionUsagePoolForecast;\n }>;\n};\n\nconst HOUR = 3_600_000;\nconst DAY = 24 * HOUR;\nconst WEEK = 7 * DAY;\nconst EPSILON = 1e-10;\nconst WINDOW_DURATION = { five_hour: 5 * HOUR, weekly: WEEK };\n// A reading is not refreshed by the passage of a reset. These TTLs are presentation policy.\nconst FRESHNESS = { five_hour: HOUR, weekly: DAY };\nconst WINDOW_IDS: SubscriptionUsagePoolWindowId[] = [\"five_hour\", \"weekly\"];\n\ntype Reading = { usedPercent: number; resetsAt: number | null; observedAt: number };\ntype Call = SubscriptionUsagePoolInput[\"calls\"][number];\ntype Segment = { start: number; end: number; reset: number; fraction: number; calls: Call[] };\ntype Bucket = { amount: number; hours: number };\ntype Pace = {\n profile: number[];\n buckets: Map<number, Bucket>;\n fallback: boolean;\n callShape: boolean;\n};\n\nconst finite = (value: number | null | undefined): value is number =>\n typeof value === \"number\" && Number.isFinite(value);\nconst percent = (value: number | null): value is number =>\n finite(value) && value >= 0 && value <= 100;\nconst clamp = (value: number, low: number, high: number) => Math.min(high, Math.max(low, value));\nconst sum = (values: number[]) => values.reduce((total, value) => total + value, 0);\nfunction median(values: number[]): number {\n const sorted = [...values].sort((a, b) => a - b);\n const middle = Math.floor(sorted.length / 2);\n return sorted.length % 2 ? sorted[middle]! : (sorted[middle - 1]! + sorted[middle]!) / 2;\n}\nfunction hourOfWeek(at: number): number {\n const date = new Date(at);\n return ((date.getUTCDay() + 6) % 7) * 24 + date.getUTCHours();\n}\n\n/**\n * Both windows and every supplied account stay visible, including missing windows.\n * Snapshots are authoritative for CURRENT remaining; historical readings never fill a\n * missing snapshot. A reading stays current until its window resets: usage only grows\n * within a window, so an older reading is still what the account list shows. The total\n * and forecast cover the accounts with a current reading; accounts without one are\n * listed with an unknown remaining so the caller can say they are left out.\n * Call units never set demand magnitude: stable percentage movements include usage\n * outside this application. Calibration is only a relative proxy for usable capacity.\n */\nexport function buildSubscriptionUsagePool(\n input: SubscriptionUsagePoolInput,\n): SubscriptionUsagePool {\n if (!finite(input.now) || Math.abs(input.now) > 8.64e15 - 2 * WEEK) {\n throw new RangeError(\"now must be a valid millisecond timestamp\");\n }\n const ids = new Set(input.accounts.map((account) => account.id));\n if (ids.size !== input.accounts.length) throw new RangeError(\"account ids must be unique\");\n const since = Math.max(\n input.now - 2 * WEEK,\n finite(input.historyStart) ? Math.min(input.now, input.historyStart) : -Infinity,\n );\n const callsByAccount = new Map<string, Call[]>();\n for (const call of input.calls) {\n if (\n !ids.has(call.connectionId) ||\n !finite(call.at) ||\n call.at < since ||\n call.at > input.now ||\n !finite(call.units) ||\n call.units <= 0\n )\n continue;\n const calls = callsByAccount.get(call.connectionId) ?? [];\n calls.push(call);\n callsByAccount.set(call.connectionId, calls);\n }\n for (const calls of callsByAccount.values()) calls.sort((a, b) => a.at - b.at);\n const readingsByAccount = new Map<string, Map<SubscriptionUsagePoolWindowId, Reading[]>>();\n for (const reading of input.readings) {\n if (\n !ids.has(reading.connectionId) ||\n !percent(reading.usedPercent) ||\n !finite(reading.observedAt) ||\n reading.observedAt < since ||\n reading.observedAt > input.now\n )\n continue;\n const windows = readingsByAccount.get(reading.connectionId) ?? new Map();\n const readings = windows.get(reading.window) ?? [];\n readings.push(reading);\n windows.set(reading.window, readings);\n readingsByAccount.set(reading.connectionId, windows);\n }\n\n const windows = WINDOW_IDS.map((id) => {\n const duration = WINDOW_DURATION[id];\n const data = input.accounts.map((account) => {\n // Newest supplied snapshot wins; an explicit null must not resurrect older quota.\n const snapshot = [...account.windows.filter((window) => window.id === id)].sort(\n (a, b) => (b.observedAt ?? -Infinity) - (a.observedAt ?? -Infinity),\n )[0];\n const observedAt =\n finite(snapshot?.observedAt) && snapshot.observedAt <= input.now\n ? snapshot.observedAt\n : null;\n const observedReset = finite(snapshot?.resetsAt) ? snapshot.resetsAt : null;\n const blockedUntil = account.blocked?.[id];\n const blocked = finite(blockedUntil) && blockedUntil > input.now;\n const resetsAt = blocked ? blockedUntil : observedReset;\n const current =\n observedAt !== null &&\n (observedReset === null\n ? input.now - observedAt <= FRESHNESS[id]\n : observedReset > input.now && observedReset <= observedAt + duration);\n const remainingPercent = blocked\n ? 0\n : current && snapshot && percent(snapshot.usedPercent)\n ? 100 - snapshot.usedPercent\n : null;\n const readings = [...(readingsByAccount.get(account.id)?.get(id) ?? [])];\n if (snapshot && percent(snapshot.usedPercent) && observedAt !== null && observedAt >= since) {\n readings.push({ usedPercent: snapshot.usedPercent, observedAt, resetsAt: observedReset });\n }\n const segments = stableSegments(readings, callsByAccount.get(account.id) ?? [], duration);\n const planWeight =\n finite(account.planWeight) && account.planWeight > 0 ? account.planWeight : 1;\n return {\n account,\n observedAt,\n resetsAt,\n remainingPercent,\n segments,\n planWeight,\n calibrated: calibratedCapacity(segments),\n readings,\n };\n });\n\n // Convert measured capacities to the SAME dimensionless scale as plan estimates.\n // Known-tier anchors take priority; otherwise the median measured account is one tier.\n const knownAnchors = data.filter(\n (row) =>\n row.calibrated !== null && finite(row.account.planWeight) && row.account.planWeight > 0,\n );\n const anchors = knownAnchors.length\n ? knownAnchors\n : data.filter((row) => row.calibrated !== null);\n const anchor = anchors.length\n ? median(anchors.map((row) => row.calibrated! / row.planWeight))\n : 1;\n const accounts: SubscriptionUsagePoolAccount[] = data.map((row) => ({\n id: row.account.id,\n label: row.account.label,\n remainingPercent: row.remainingPercent,\n resetsAt: row.resetsAt,\n weight: clamp(\n row.calibrated === null ? row.planWeight : row.calibrated / anchor,\n 0.01,\n 10_000,\n ),\n weightBasis: row.calibrated === null ? \"plan_estimate\" : \"observed\",\n observedAt: row.observedAt,\n }));\n // Accounts without a current reading are left out of the total and forecast\n // (and stay listed as unknown), rather than hiding what the others report.\n const knownIndexes = accounts.flatMap((account, index) =>\n account.remainingPercent === null ? [] : [index],\n );\n const known = knownIndexes.map((index) => accounts[index]!);\n const totalWeight = sum(known.map((account) => account.weight));\n const complete = known.length > 0;\n const remainingPercent = complete\n ? clamp(\n sum(known.map((account) => account.weight * account.remainingPercent!)) / totalWeight,\n 0,\n 100,\n )\n : null;\n const earliest = data.reduce(\n (at, row) =>\n row.readings.reduce((minimum, reading) => Math.min(minimum, reading.observedAt), at),\n input.now,\n );\n const historyDays = (input.now - Math.max(since, earliest)) / DAY;\n const resets = known\n .map((account) => account.resetsAt)\n .filter((at): at is number => at !== null && at > input.now && at <= input.now + duration);\n const nextResetAt = resets.length ? Math.min(...resets) : null;\n const insufficient = (basis: string): SubscriptionUsagePoolForecast => ({\n kind: \"insufficient_history\",\n basis,\n runsOutAt: null,\n nextResetAt,\n unusedPercent: null,\n utilizationPercent: null,\n lowestPercent: null,\n historyDays,\n });\n let forecast: SubscriptionUsagePoolForecast;\n if (!complete) {\n forecast = insufficient(\"No account has a current observation for this window.\");\n } else if (remainingPercent === 0) {\n forecast = {\n kind: \"runs_out\",\n basis: \"Observed pool exhaustion.\",\n runsOutAt: input.now,\n nextResetAt,\n unusedPercent: null,\n utilizationPercent: 100,\n lowestPercent: 0,\n historyDays,\n };\n } else if (resets.length !== known.length) {\n forecast = insufficient(\n \"A future reset is unknown; remaining limits are observed, not forecast.\",\n );\n } else {\n const paces = knownIndexes.map((index) =>\n buildPace(data[index]!.segments, accounts[index]!, duration, input.now),\n );\n if (paces.some((pace) => pace === null)) {\n forecast = insufficient(\n \"Insufficient observed activity coverage; absence of calls is not zero demand.\",\n );\n } else {\n const availablePaces = paces as Pace[];\n const profile = Array.from({ length: 168 }, (_, hour) =>\n sum(availablePaces.map((pace) => pace.profile[hour]!)),\n );\n const trend = weeklyTrend(availablePaces, input.now);\n const recent = id === \"five_hour\" ? lastHourAdjustment(availablePaces, input.now) : 1;\n const scale = clamp(trend * recent, 0.5, 2);\n const basis = [\n availablePaces.some((pace) => pace.fallback)\n ? \"Last-reset average fallback\"\n : \"Observed percentage movements\",\n historyDays >= 7\n ? \"last 7 days\"\n : `${Math.max(1, Math.ceil(historyDays))} days of history`,\n `same-hours weekly trend ${trend.toFixed(2)}x`,\n ...(id === \"five_hour\" ? [`last-hour adjustment ${recent.toFixed(2)}x`] : []),\n ].join(\"; \");\n forecast = simulate(\n known,\n profile.map((rate) => rate * scale),\n input.now,\n duration,\n historyDays,\n basis,\n );\n }\n }\n return { id, remainingPercent, accounts, forecast };\n });\n const observations = windows\n .flatMap((window) => window.accounts.map((account) => account.observedAt))\n .filter((at): at is number => at !== null);\n return {\n provider: input.provider,\n observedAt: observations.length ? Math.max(...observations) : null,\n windows,\n };\n}\n\nfunction stableSegments(readings: Reading[], calls: Call[], duration: number): Segment[] {\n // Identical duplicates are harmless. Conflicting same-time readings break continuity.\n const grouped = new Map<number, Reading[]>();\n for (const reading of readings) {\n const group = grouped.get(reading.observedAt) ?? [];\n group.push(reading);\n grouped.set(reading.observedAt, group);\n }\n const ordered = [...grouped]\n .sort(([a], [b]) => a - b)\n .map(([, group]) =>\n group.every(\n (row) => row.usedPercent === group[0]!.usedPercent && row.resetsAt === group[0]!.resetsAt,\n )\n ? group[0]!\n : null,\n );\n const result: Segment[] = [];\n let callIndex = 0;\n let before: Reading | null = null;\n for (const after of ordered) {\n if (\n !before ||\n !after ||\n !finite(before.resetsAt) ||\n before.resetsAt !== after.resetsAt ||\n before.resetsAt < after.observedAt ||\n before.resetsAt > before.observedAt + duration ||\n after.observedAt - before.observedAt > Math.min(duration, DAY) ||\n after.usedPercent < before.usedPercent ||\n before.usedPercent === 100\n ) {\n before = after;\n continue;\n }\n // Polling more often than five minutes accumulates into a sample, rather than\n // dropping every adjacent pair. A correction/reset still breaks the chain above.\n if (after.observedAt - before.observedAt < HOUR / 12) continue;\n while (callIndex < calls.length && calls[callIndex]!.at <= before.observedAt) callIndex++;\n const start = callIndex;\n while (callIndex < calls.length && calls[callIndex]!.at <= after.observedAt) callIndex++;\n result.push({\n start: before.observedAt,\n end: after.observedAt,\n reset: before.resetsAt,\n fraction: (after.usedPercent - before.usedPercent) / 100,\n calls: calls.slice(start, callIndex),\n });\n before = after;\n }\n return result;\n}\n\nfunction calibratedCapacity(segments: Segment[]): number | null {\n // Accumulate quantized/small movements without ever spanning a gap or reset.\n const positive: Segment[] = [];\n let pending: Segment | null = null;\n for (const segment of segments) {\n if (pending && pending.end === segment.start && pending.reset === segment.reset) {\n pending.end = segment.end;\n pending.fraction += segment.fraction;\n for (const call of segment.calls) pending.calls.push(call);\n } else {\n pending = { ...segment, calls: [...segment.calls] };\n }\n if (pending.fraction >= 0.01 - EPSILON) {\n positive.push(pending);\n pending = null;\n }\n }\n const paired = positive\n .map((segment) => ({\n segment,\n capacity: sum(segment.calls.map((call) => call.units)) / segment.fraction,\n }))\n .filter((sample) => finite(sample.capacity) && sample.capacity > 0);\n if (paired.length < 3) return null;\n const center = median(paired.map((sample) => sample.capacity));\n const stable = paired.filter(\n (sample) => sample.capacity >= center / 2 && sample.capacity <= center * 2,\n );\n // Three samples, six calls, ten percentage points, an hour, 60% movement coverage,\n // and median absolute dispersion <= 35%. Reset crossings/corrections never calibrate.\n if (\n stable.length < 3 ||\n stable.length < paired.length * 0.6 ||\n sum(stable.map(({ segment }) => segment.calls.length)) < 6 ||\n sum(stable.map(({ segment }) => segment.fraction)) < 0.1 ||\n sum(stable.map(({ segment }) => segment.end - segment.start)) < HOUR ||\n sum(stable.map(({ segment }) => segment.fraction)) <\n sum(segments.map((segment) => segment.fraction)) * 0.6 ||\n median(stable.map((sample) => Math.abs(sample.capacity - center))) > center * 0.35\n )\n return null;\n return median(stable.map((sample) => sample.capacity));\n}\n\nfunction addBucket(buckets: Map<number, Bucket>, at: number, amount: number, hours: number): void {\n const previous = buckets.get(at) ?? { amount: 0, hours: 0 };\n buckets.set(at, { amount: previous.amount + amount, hours: previous.hours + hours });\n}\n\nfunction buildPace(\n segments: Segment[],\n account: SubscriptionUsagePoolAccount,\n duration: number,\n now: number,\n): Pace | null {\n const buckets = new Map<number, Bucket>();\n let callShape = false;\n for (const segment of segments) {\n const hours = (segment.end - segment.start) / HOUR;\n const shape = new Map<number, number>();\n const reliableCalls = segment.calls.length >= 3 && segment.fraction > 0;\n if (reliableCalls) {\n const cap = median(segment.calls.map((call) => call.units)) * 3;\n for (const call of segment.calls) {\n // An endpoint call belongs to the interval ending there, not a future hour.\n const hour = Math.floor(Math.min(call.at, segment.end - 1) / HOUR) * HOUR;\n shape.set(hour, (shape.get(hour) ?? 0) + Math.min(call.units, cap));\n }\n }\n const shapeTotal = sum([...shape.values()]);\n callShape ||= shapeTotal > 0;\n for (let at = Math.floor(segment.start / HOUR) * HOUR; at < segment.end; at += HOUR) {\n const overlap = (Math.min(at + HOUR, segment.end) - Math.max(at, segment.start)) / HOUR;\n // Preserve total observed movement, with a uniform floor for unattributed activity.\n const fraction =\n shapeTotal > 0\n ? (0.25 * overlap) / hours + (0.75 * (shape.get(at) ?? 0)) / shapeTotal\n : overlap / hours;\n addBucket(buckets, at, segment.fraction * account.weight * fraction, overlap);\n }\n }\n const recentSegments = segments.filter((segment) => segment.end > now - WEEK);\n const recent = [...buckets].filter(([at]) => at + HOUR > now - WEEK);\n const coverage = sum(recent.map(([, bucket]) => bucket.hours));\n // Explicit flat percentage observations ARE evidence of zero use; an empty call log is not.\n if (\n recentSegments.length < 2 ||\n coverage < 1 ||\n !recentSegments.some(\n (segment) => segment.end >= now - FRESHNESS[duration === WEEK ? \"weekly\" : \"five_hour\"],\n )\n ) {\n const elapsed =\n account.resetsAt !== null && account.observedAt !== null\n ? (account.observedAt - (account.resetsAt - duration)) / HOUR\n : 0;\n if (\n account.remainingPercent === null ||\n account.remainingPercent >= 100 ||\n elapsed < 0.25 ||\n elapsed > duration / HOUR\n )\n return null;\n const rate = (account.weight * (1 - account.remainingPercent / 100)) / elapsed;\n return { buckets, profile: Array(168).fill(rate), fallback: true, callShape: false };\n }\n const positiveRates = [...buckets.values()]\n .filter((bucket) => bucket.amount > 0)\n .map((bucket) => bucket.amount / bucket.hours);\n const cap = positiveRates.length >= 3 ? median(positiveRates) * 3 : Infinity;\n // Winsorization damps isolated activity spikes without turning genuine idle observations into gaps.\n for (const bucket of buckets.values())\n bucket.amount = Math.min(bucket.amount, cap * bucket.hours);\n const average = sum(recent.map(([, bucket]) => bucket.amount)) / coverage;\n const profile = Array<number>(168).fill(average);\n const slots = new Map<number, Bucket>();\n for (const [at, bucket] of recent) addBucket(slots, hourOfWeek(at), bucket.amount, bucket.hours);\n for (const [hour, bucket] of slots) {\n const confidence = Math.min(1, bucket.hours);\n profile[hour] = (confidence * bucket.amount) / bucket.hours + (1 - confidence) * average;\n }\n return { buckets, profile, fallback: false, callShape };\n}\n\nfunction coveredRate(paces: Pace[], at: number): number | null {\n const buckets = paces.map((pace) => pace.buckets.get(at));\n if (buckets.some((bucket) => !bucket || bucket.hours < 0.75)) return null;\n return sum(buckets.map((bucket) => bucket!.amount / bucket!.hours));\n}\n\nfunction weeklyTrend(paces: Pace[], now: number): number {\n const completed = Math.floor(now / HOUR) * HOUR;\n const weekStart = completed - hourOfWeek(completed) * HOUR;\n let current = 0;\n let previous = 0;\n let pairs = 0;\n for (let at = weekStart; at < completed; at += HOUR) {\n const here = coveredRate(paces, at);\n const there = coveredRate(paces, at - WEEK);\n if (here === null || there === null) continue;\n current += here;\n previous += there;\n pairs++;\n }\n return pairs >= 3 && previous > EPSILON ? clamp(Math.sqrt(current / previous), 0.5, 2) : 1;\n}\n\nfunction lastHourAdjustment(paces: Pace[], now: number): number {\n // Use the last FULL hour: partial current-hour observations must not inflate the pace.\n const at = Math.floor(now / HOUR) * HOUR - HOUR;\n const actual = coveredRate(paces, at);\n const average = sum(paces.map((pace) => sum(pace.profile) / 168));\n return actual !== null && average > EPSILON ? clamp(Math.sqrt(actual / average), 0.5, 2) : 1;\n}\n\nfunction simulate(\n accounts: SubscriptionUsagePoolAccount[],\n profile: number[],\n now: number,\n duration: number,\n historyDays: number,\n basis: string,\n): SubscriptionUsagePoolForecast {\n const total = sum(accounts.map((account) => account.weight));\n const remaining = accounts.map((account) => (account.weight * account.remainingPercent!) / 100);\n const resets = accounts.map((account) => account.resetsAt!);\n const horizon = Math.min(now + duration, Math.max(...resets));\n let at = now;\n let lowest = sum(remaining);\n let unused = 0;\n let runsOutAt: number | null = null;\n while (at < horizon) {\n const nextReset = Math.min(...resets.filter((reset) => reset > at));\n const end = Math.min(horizon, (Math.floor(at / HOUR) + 1) * HOUR, nextReset);\n const rate = profile[hourOfWeek(at)]!;\n const demand = (rate * (end - at)) / HOUR;\n const available = sum(remaining);\n if (rate > 0 && demand >= available && runsOutAt === null) {\n const exhaustedAt = at + (available / rate) * HOUR;\n // Empty exactly as a reset refills the pool is not a period without capacity.\n if (exhaustedAt < nextReset - 0.001) runsOutAt = exhaustedAt;\n }\n consume(remaining, accounts, Math.min(demand, available));\n lowest = Math.min(lowest, sum(remaining));\n // Do not carry unserved demand as debt into the next account's renewed window.\n for (let index = 0; index < accounts.length; index++) {\n if (resets[index] === end) {\n unused += remaining[index]!;\n remaining[index] = accounts[index]!.weight;\n }\n }\n at = end;\n }\n const nextResetAt = Math.min(...resets.filter((reset) => reset >= (runsOutAt ?? now)));\n return {\n kind: runsOutAt === null ? \"unused\" : \"runs_out\",\n basis,\n runsOutAt,\n nextResetAt: finite(nextResetAt) ? nextResetAt : null,\n unusedPercent: runsOutAt === null ? clamp((unused / total) * 100, 0, 100) : null,\n utilizationPercent: clamp(100 - (unused / total) * 100, 0, 100),\n lowestPercent: clamp((lowest / total) * 100, 0, 100),\n historyDays,\n };\n}\n\n/** Water-fill by relative capacity, reallocating only what a depleted account cannot serve. */\nfunction consume(\n remaining: number[],\n accounts: SubscriptionUsagePoolAccount[],\n demand: number,\n): void {\n let active = remaining\n .map((amount, index) => ({ amount, index }))\n .filter((row) => row.amount > 0);\n while (demand > EPSILON && active.length) {\n const totalWeight = sum(active.map((row) => accounts[row.index]!.weight));\n let consumed = 0;\n for (const { index } of active) {\n const amount = Math.min(remaining[index]!, (demand * accounts[index]!.weight) / totalWeight);\n remaining[index] = Math.max(0, remaining[index]! - amount);\n consumed += amount;\n }\n demand = Math.max(0, demand - consumed);\n active = active.filter((row) => remaining[row.index]! > EPSILON);\n }\n}\n"],"mappings":"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","names":["findModel","ineligibility","windows","remainingPercent","cap"]}
1
+ {"version":3,"sources":["../src/settings.ts","../src/quota.ts","../src/cache.ts","../src/eligibility.ts","../src/reasoning.ts","../src/spread.ts","../src/placement.ts","../src/adapter.ts","../src/settlement.ts","../src/wait-presentation.ts","../src/pool-usage.ts"],"sourcesContent":["import type {\n EffectiveSubscriptionSettings,\n EffectiveSettingSource,\n KeyedSettingKey,\n ProviderId,\n ProviderSwitches,\n RotationSetting,\n ScalarSettingKey,\n SettingSource,\n SubscriptionSettingOverrides,\n SubscriptionSettingKey,\n SubscriptionSettingsPolicy,\n SubscriptionSettingsView,\n SubscriptionSettingValues,\n} from \"./types\";\n\nexport const SCALAR_SETTING_KEYS: readonly ScalarSettingKey[] = Object.freeze([\n \"crossProviderFailover\",\n \"personalConnectionsAllowed\",\n \"personalFallbackAllowed\",\n]);\n\nexport const KEYED_SETTING_KEYS: readonly KeyedSettingKey[] = Object.freeze([\n \"rotation\",\n \"providers\",\n \"fallbackOrder\",\n]);\n\nexport const SUBSCRIPTION_SETTING_KEYS: readonly SubscriptionSettingKey[] = Object.freeze([\n ...SCALAR_SETTING_KEYS,\n ...KEYED_SETTING_KEYS,\n]);\n\n/** Default when a provider has no rotation entry: spread work (design 3.3). */\nexport const DEFAULT_ROTATION: RotationSetting = Object.freeze({ mode: \"spread\" });\n\nexport const DEFAULT_PROVIDER_SWITCHES: ProviderSwitches = Object.freeze({ enabled: true });\n\n/**\n * Resolve one workspace's effective settings (SUB-SET-01..03).\n *\n * Every setting resolves to exactly one value with its source: the\n * organization default, or the workspace override when the setting is not\n * locked. For map-valued settings (`rotation`, `providers`, `fallbackOrder`) a\n * workspace row overrides individual entries, so overriding one provider's\n * rotation does not reset another provider's, and a provider's switches\n * override field by field (D-25). Locking a map-valued setting locks every\n * entry.\n */\nexport function effectiveSettings(\n policy: SubscriptionSettingsPolicy,\n workspaceId: string,\n): EffectiveSubscriptionSettings {\n const override = policy.workspaces[workspaceId] ?? {};\n const locked = new Set<SubscriptionSettingKey>(policy.locked);\n const organization = policy.organization;\n\n const scalar = <K extends ScalarSettingKey>(key: K) => {\n const value = override[key];\n const overridden = value !== undefined && !locked.has(key);\n return {\n value: (overridden ? value : organization[key]) as SubscriptionSettingValues[K],\n source: (overridden ? \"workspace\" : \"organization\") as SettingSource,\n };\n };\n const keyed = <K extends KeyedSettingKey>(key: K) => {\n const base = organization[key] as Readonly<Record<string, unknown>>;\n const entries = locked.has(key)\n ? undefined\n : (override[key] as Readonly<Record<string, unknown>> | undefined);\n const value: Record<string, unknown> = { ...base };\n const sources: Record<string, SettingSource> = {};\n for (const [entryKey, entry] of Object.entries(base)) {\n if (key === \"providers\") value[entryKey] = normalizeProviderSwitches(entry);\n sources[entryKey] = \"organization\";\n }\n for (const [entryKey, entry] of Object.entries(entries ?? {})) {\n if (entry === undefined) continue;\n // A provider's switches override field by field (D-25).\n value[entryKey] =\n key === \"providers\"\n ? mergeProviderSwitches(\n base[entryKey] as ProviderSwitches | undefined,\n entry as Partial<ProviderSwitches>,\n )\n : entry;\n sources[entryKey] = \"workspace\";\n }\n return { value: value as SubscriptionSettingValues[K], sources };\n };\n\n const crossProviderFailover = scalar(\"crossProviderFailover\");\n const personalConnectionsAllowed = scalar(\"personalConnectionsAllowed\");\n const personalFallbackAllowed = scalar(\"personalFallbackAllowed\");\n const rotation = keyed(\"rotation\");\n const providers = keyed(\"providers\");\n const fallbackOrder = keyed(\"fallbackOrder\");\n return {\n values: {\n rotation: rotation.value,\n providers: providers.value,\n crossProviderFailover: crossProviderFailover.value,\n fallbackOrder: fallbackOrder.value,\n personalConnectionsAllowed: personalConnectionsAllowed.value,\n personalFallbackAllowed: personalFallbackAllowed.value,\n },\n sources: {\n crossProviderFailover: crossProviderFailover.source,\n personalConnectionsAllowed: personalConnectionsAllowed.source,\n personalFallbackAllowed: personalFallbackAllowed.source,\n rotation: rotation.sources,\n providers: providers.sources,\n fallbackOrder: fallbackOrder.sources,\n },\n };\n}\n\nexport function rotationFor(\n settings: SubscriptionSettingValues,\n provider: ProviderId,\n): RotationSetting {\n return settings.rotation[provider] ?? DEFAULT_ROTATION;\n}\n\nexport function providerSwitchesFor(\n settings: SubscriptionSettingValues,\n provider: ProviderId,\n): ProviderSwitches {\n const entry = settings.providers[provider];\n return entry ? normalizeProviderSwitches(entry) : DEFAULT_PROVIDER_SWITCHES;\n}\n\n/** A stored provider entry as its one switch; keys from older releases are ignored. */\nfunction normalizeProviderSwitches(entry: unknown): ProviderSwitches {\n return { enabled: (entry as Partial<ProviderSwitches> | undefined)?.enabled !== false };\n}\n\nexport function isSubscriptionSettingKey(value: unknown): value is SubscriptionSettingKey {\n return (\n typeof value === \"string\" && (SUBSCRIPTION_SETTING_KEYS as readonly string[]).includes(value)\n );\n}\n\n/**\n * One workspace's effective settings with one source per setting (SUB-SET-03,\n * SUB-SET-05): `locked` when the organization locked it (the organization\n * value applies and the workspace cannot override it), `workspace` when the\n * workspace overrides it (for a map-valued setting: at least one entry), and\n * `organization` otherwise. `entrySources` keeps the per-entry sources of the\n * map-valued settings.\n */\nexport function settingsView(\n policy: SubscriptionSettingsPolicy,\n workspaceId: string,\n): SubscriptionSettingsView {\n return settingsViewFromEffective(effectiveSettings(policy, workspaceId), policy.locked);\n}\n\n/** The same view from an already resolved effective settings value (for example the SQL one). */\nexport function settingsViewFromEffective(\n effective: EffectiveSubscriptionSettings,\n locked: readonly SubscriptionSettingKey[],\n): SubscriptionSettingsView {\n const lockedSet = new Set(locked);\n const sources = {} as Record<SubscriptionSettingKey, EffectiveSettingSource>;\n for (const key of SCALAR_SETTING_KEYS) {\n sources[key] = lockedSet.has(key) ? \"locked\" : effective.sources[key];\n }\n for (const key of KEYED_SETTING_KEYS) {\n sources[key] = lockedSet.has(key)\n ? \"locked\"\n : Object.values(effective.sources[key]).includes(\"workspace\")\n ? \"workspace\"\n : \"organization\";\n }\n return {\n values: effective.values,\n sources,\n entrySources: {\n rotation: effective.sources.rotation,\n providers: effective.sources.providers,\n fallbackOrder: effective.sources.fallbackOrder,\n },\n };\n}\n\nconst OVERRIDE_COLUMN_VALUE = (\n overrides: SubscriptionSettingOverrides | null | undefined,\n key: SubscriptionSettingKey,\n): unknown => (overrides ? overrides[key] : undefined);\n\n/**\n * The locked settings a workspace-row write would widen: the TypeScript\n * mirror of the database guard (migration 0724). A write widens a setting\n * when it sets a value that is not already stored unchanged; for a\n * map-valued setting, when it adds or changes an entry. Clearing an override,\n * or leaving it unchanged, never widens. A rotation lock also covers the\n * workspace's primary connections (`primaryConnectionIds`).\n */\nexport function lockedSettingsWidened(input: {\n locked: readonly SubscriptionSettingKey[];\n before: SubscriptionSettingOverrides | null;\n after: SubscriptionSettingOverrides | null;\n primaryConnectionIds?: {\n before: Readonly<Record<string, string | null>>;\n after: Readonly<Record<string, string | null>>;\n };\n}): SubscriptionSettingKey[] {\n const widened: SubscriptionSettingKey[] = [];\n for (const key of SUBSCRIPTION_SETTING_KEYS) {\n if (!input.locked.includes(key)) continue;\n const next = OVERRIDE_COLUMN_VALUE(input.after, key);\n const previous = OVERRIDE_COLUMN_VALUE(input.before, key);\n if (overrideWidened(next, previous, (KEYED_SETTING_KEYS as readonly string[]).includes(key))) {\n widened.push(key);\n continue;\n }\n if (key === \"rotation\" && input.primaryConnectionIds) {\n const { before, after } = input.primaryConnectionIds;\n if (\n Object.entries(after).some(\n ([column, value]) => value !== null && value !== (before[column] ?? null),\n )\n ) {\n widened.push(key);\n }\n }\n }\n return widened;\n}\n\nfunction overrideWidened(next: unknown, previous: unknown, keyed: boolean): boolean {\n if (next === undefined || next === null) return false;\n if (!keyed) return !jsonEqual(next, previous ?? null);\n if (typeof next !== \"object\" || Array.isArray(next)) return true;\n const stored =\n previous && typeof previous === \"object\" && !Array.isArray(previous)\n ? (previous as Record<string, unknown>)\n : null;\n return Object.entries(next as Record<string, unknown>).some(\n ([entry, value]) =>\n // JSON drops undefined members, as the stored jsonb does.\n value !== undefined &&\n (stored === null || !(entry in stored) || !jsonEqual(stored[entry], value)),\n );\n}\n\n/** Structural equality of JSON values (jsonb equality: object key order does not matter). */\nexport function jsonEqual(left: unknown, right: unknown): boolean {\n if (left === right) return true;\n if (left === null || right === null || typeof left !== \"object\" || typeof right !== \"object\") {\n return false;\n }\n if (Array.isArray(left) !== Array.isArray(right)) return false;\n if (Array.isArray(left)) {\n const other = right as unknown[];\n return (\n left.length === other.length && left.every((value, index) => jsonEqual(value, other[index]))\n );\n }\n const leftEntries = Object.entries(left).filter(([, value]) => value !== undefined);\n const rightRecord = right as Record<string, unknown>;\n const rightKeys = Object.keys(rightRecord).filter((key) => rightRecord[key] !== undefined);\n return (\n leftEntries.length === rightKeys.length &&\n leftEntries.every(([key, value]) => key in rightRecord && jsonEqual(value, rightRecord[key]))\n );\n}\n\n/** Bounds on a stored fallback order (also enforced by the API contract). */\nexport const FALLBACK_ORDER_LIMITS = Object.freeze({\n preferredModels: 64,\n fallbacksPerModel: 8,\n modelIdLength: 200,\n});\n\n/**\n * Why a fallback order is invalid, or null when it is valid: each preferred\n * model maps to distinct model ids, never itself, within the bounds above.\n * Model ids are not required to be in a catalog: placement skips a fallback\n * model it does not know (`failoverCandidateModels`).\n */\nexport function fallbackOrderProblem(order: unknown): string | null {\n if (order === null || typeof order !== \"object\" || Array.isArray(order)) {\n return \"fallback order must be an object\";\n }\n const entries = Object.entries(order as Record<string, unknown>);\n if (entries.length > FALLBACK_ORDER_LIMITS.preferredModels) {\n return `fallback order has more than ${FALLBACK_ORDER_LIMITS.preferredModels} models`;\n }\n const validId = (id: unknown): id is string =>\n typeof id === \"string\" &&\n id.trim() === id &&\n id.length > 0 &&\n id.length <= FALLBACK_ORDER_LIMITS.modelIdLength;\n for (const [preferred, fallbacks] of entries) {\n if (!validId(preferred)) return \"fallback order has an invalid model id\";\n if (!Array.isArray(fallbacks)) return `fallback order for ${preferred} must be a list`;\n if (fallbacks.length > FALLBACK_ORDER_LIMITS.fallbacksPerModel) {\n return `fallback order for ${preferred} has more than ${FALLBACK_ORDER_LIMITS.fallbacksPerModel} models`;\n }\n if (!fallbacks.every(validId)) return `fallback order for ${preferred} has an invalid model id`;\n if (new Set(fallbacks).size !== fallbacks.length) {\n return `fallback order for ${preferred} repeats a model`;\n }\n if (fallbacks.includes(preferred)) {\n return `fallback order for ${preferred} names the model itself`;\n }\n }\n return null;\n}\n\n/**\n * The fallback models the effective settings name for a preferred model, in\n * order, before the placement-time filters (catalog, cross-provider switch,\n * restrictions; see `failoverCandidateModels`). The read the core uses for\n * the organization's fallback order. Empty both for a missing entry and for\n * an explicit empty one; `hasFallbackOrderEntry` tells them apart.\n */\nexport function fallbackOrderFor(\n settings: SubscriptionSettingValues,\n preferredModelId: string,\n): readonly string[] {\n if (!hasFallbackOrderEntry(settings, preferredModelId)) return [];\n return settings.fallbackOrder[preferredModelId] ?? [];\n}\n\n/**\n * Whether the effective settings carry a fallback entry for a preferred\n * model, including an explicit empty list, which means \"no fallback model\"\n * (D-35). Only a missing entry lets placement derive a default order. Only\n * the map's own keys are entries, never an inherited object property.\n */\nexport function hasFallbackOrderEntry(\n settings: SubscriptionSettingValues,\n preferredModelId: string,\n): boolean {\n return Object.hasOwn(settings.fallbackOrder, preferredModelId);\n}\n\nfunction mergeProviderSwitches(\n base: ProviderSwitches | undefined,\n override: Partial<ProviderSwitches>,\n): ProviderSwitches {\n return { enabled: override.enabled ?? base?.enabled ?? true };\n}\n","import type { ModelId, QuotaBlockClaim, SubscriptionConnection, SubscriptionQuota } from \"./types\";\n\n/**\n * What the shared quota model says about a connection's capacity now\n * (design 2.2). `unknown` is never treated as available or exhausted\n * (SUB-ELIG-06): it participates in ordinary rotation without a ranking penalty (D-14).\n */\nexport type QuotaCapacity =\n | { kind: \"available\" }\n | { kind: \"unknown\" }\n /** `resetsAt` is null when the provider did not say when it resets. */\n | { kind: \"exhausted\"; resetsAt: number | null };\n\nconst UNKNOWN: QuotaCapacity = Object.freeze({ kind: \"unknown\" });\nconst AVAILABLE: QuotaCapacity = Object.freeze({ kind: \"available\" });\n\n/**\n * Capacity of a quota observation at `now`.\n *\n * - An exhaustion deadline (`exhaustedUntil`) in the future is authoritative.\n * - A quota exhaustion whose deadline has passed is an observed reset, so it\n * counts as known capacity; a passed rate-limit deadline says nothing about\n * quota.\n * - A window reported exhausted stays exhausted until its reset; an exhausted\n * window whose reset has passed counts as reset (known capacity).\n * - With nothing exhausted, any window with a known status is known capacity.\n * - No observation, only windows of unknown status, or an observation older\n * than `staleAfterMs` (or undated, when a bound is given), is unknown.\n */\nexport function quotaCapacity(\n quota: SubscriptionQuota | null,\n now: number,\n staleAfterMs?: number,\n): QuotaCapacity {\n if (!quota) return UNKNOWN;\n // A stale or undated observation no longer says the account has capacity\n // (SUB-ELIG-06); observed exhaustion still stands until its reset.\n const stale =\n staleAfterMs !== undefined &&\n (quota.observedAt === null || now - quota.observedAt > staleAfterMs);\n let exhausted = false;\n let resetsAt: number | null = null;\n let resetUnknown = false;\n const extend = (at: number | null) => {\n exhausted = true;\n if (at === null) resetUnknown = true;\n else resetsAt = resetsAt === null ? at : Math.max(resetsAt, at);\n };\n let known = false;\n if (quota.exhaustedUntil !== null) {\n if (quota.exhaustedUntil > now) extend(quota.exhaustedUntil);\n else if (quota.exhaustedKind === \"quota\" && !stale) known = true;\n }\n for (const window of quota.windows) {\n if (window.status === \"exhausted\") {\n if (window.resetsAt === null || window.resetsAt > now) extend(window.resetsAt);\n else if (!stale) known = true;\n } else if (window.status !== \"unknown\" && !stale) {\n known = true;\n }\n }\n if (exhausted) return { kind: \"exhausted\", resetsAt: resetUnknown ? null : resetsAt };\n return known ? AVAILABLE : UNKNOWN;\n}\n\n/** End of a model-specific cooldown that is still running, else null. */\nexport function modelCooldownUntil(\n quota: SubscriptionQuota | null,\n modelId: ModelId,\n now: number,\n): number | null {\n const until = quota?.modelCooldowns[modelId];\n return until !== undefined && until > now ? until : null;\n}\n\n/** One stored block a usage reading may end: a model cooldown, or (model null) the exhaustion deadline. */\nexport type SubscriptionQuotaBlock = {\n modelId: ModelId | null;\n until: number;\n /** Its claim, only while it still explains this exact deadline. */\n claim: QuotaBlockClaim | null;\n};\n\n/**\n * The claim of a stored block (a model's cooldown, or with `modelId` null\n * the exhaustion deadline), only while it is tied to the block's current\n * deadline; null otherwise (fail closed).\n */\nexport function quotaBlockClaim(\n quota: Pick<\n SubscriptionQuota,\n \"modelCooldowns\" | \"modelCooldownClaims\" | \"exhaustedUntil\" | \"exhaustedClaim\"\n >,\n modelId: ModelId | null,\n): QuotaBlockClaim | null {\n const until = modelId === null ? quota.exhaustedUntil : quota.modelCooldowns[modelId];\n const claim = modelId === null ? quota.exhaustedClaim : quota.modelCooldownClaims?.[modelId];\n return until !== null &&\n until !== undefined &&\n claim !== null &&\n claim !== undefined &&\n claim.until === until &&\n claim.windowIds.length > 0\n ? claim\n : null;\n}\n\n/** The quota document a store writes: claims only while they explain a block. */\nexport type StoredQuotaDocument = Pick<\n SubscriptionQuota,\n | \"windows\"\n | \"modelCooldowns\"\n | \"modelCooldownClaims\"\n | \"exhaustedUntil\"\n | \"exhaustedKind\"\n | \"exhaustedClaim\"\n | \"source\"\n>;\n\n/** The stored document of one quota (a claim that no longer explains its block is dropped). */\nexport function storedQuotaDocument(quota: SubscriptionQuota): StoredQuotaDocument {\n const modelCooldownClaims: Record<string, QuotaBlockClaim> = {};\n for (const modelId of Object.keys(quota.modelCooldowns)) {\n const claim = quotaBlockClaim(quota, modelId);\n if (claim) modelCooldownClaims[modelId] = claim;\n }\n const exhaustedClaim = quotaBlockClaim(quota, null);\n return {\n windows: quota.windows,\n modelCooldowns: quota.modelCooldowns,\n ...(Object.keys(modelCooldownClaims).length > 0 ? { modelCooldownClaims } : {}),\n exhaustedUntil: quota.exhaustedUntil,\n exhaustedKind: quota.exhaustedKind,\n ...(exhaustedClaim ? { exhaustedClaim } : {}),\n source: quota.source,\n };\n}\n\n/**\n * The claim of a block merged from several observations at `until`: every\n * source whose own block has that exact deadline must carry a claim tied to\n * it (their window ids are joined); otherwise none (an unexplained limit\n * with the same deadline keeps the block).\n */\nfunction mergedClaim(\n until: number,\n sources: readonly { until: number | null | undefined; claim: QuotaBlockClaim | null }[],\n): QuotaBlockClaim | null {\n const deciding = sources.filter((source) => source.until === until);\n if (deciding.length === 0 || deciding.some((source) => source.claim === null)) return null;\n const claims = deciding.map((source) => source.claim!);\n return {\n until,\n observedAt: Math.max(...claims.map((claim) => claim.observedAt)),\n windowIds: [...new Set(claims.flatMap((claim) => claim.windowIds))],\n };\n}\n\n/**\n * A quota observation applies only to the credential generation it was made\n * with, so a refusal seen before a refresh cannot quarantine the renewed\n * credential (design 2.2).\n */\nexport function quotaObservationApplies(\n connection: Pick<SubscriptionConnection, \"refreshGeneration\">,\n observation: Pick<SubscriptionQuota, \"observedRefreshGeneration\">,\n): boolean {\n return observation.observedRefreshGeneration === connection.refreshGeneration;\n}\n\n/**\n * The stored quota once the provider has confirmed, at `resetAt`, that it\n * reset the connection's rate limits (a redeemed reset credit). Every window\n * reading and the stored exhaustion deadline were observed before the reset,\n * so none of them describes the account any more: each window keeps its id\n * with unknown usage and reset until a later reading reports it, and the\n * deadline and its claim are dropped. A stale window that still said\n * \"exhausted until next week\" would otherwise keep every waiter parked until\n * that date. Model cooldowns are not rate-limit windows and stand. The\n * observation time moves to `resetAt`, so a usage reading fetched before the\n * reset can no longer replace this state with pre-reset windows. Returns the\n * same object when nothing it records is invalidated.\n */\nexport function quotaAfterRateLimitReset(\n quota: SubscriptionQuota,\n resetAt: number,\n): SubscriptionQuota {\n const invalidates =\n quota.exhaustedUntil !== null ||\n quota.exhaustedKind !== null ||\n quota.windows.some(\n (window) =>\n window.status !== \"unknown\" || window.usedPercent !== null || window.resetsAt !== null,\n );\n if (!invalidates) return quota;\n const next: SubscriptionQuota = {\n ...quota,\n windows: quota.windows.map((window) => ({\n id: window.id,\n usedPercent: null,\n resetsAt: null,\n status: \"unknown\" as const,\n })),\n exhaustedUntil: null,\n exhaustedKind: null,\n observedAt: Math.max(quota.observedAt ?? resetAt, resetAt),\n };\n delete next.exhaustedClaim;\n return next;\n}\n\n/**\n * Apply an observation to the stored quota (design 2.2). It applies only to\n * the current credential generation, never replaces a newer or dated reading\n * with an older or undated one, and never shortens a running exhaustion\n * deadline or model cooldown: a later usage reading without them does not\n * clear them. A stored exhausted window with a known reset that an\n * observation does not report at all stays until that reset (one that\n * reports the window again replaces it), unless the observation is a\n * usage-endpoint reading of an adapter that declares its usage readings\n * complete (`options.usageReadingsComplete`, from the adapter's\n * `usageReadingsComplete`): such a reading replaces the stored windows, so\n * it can show an early reset (also of a refusal's fallback window).\n * Response headers and refusals, and the usage readings of every other\n * adapter, report only some windows (fail closed: absent means partial).\n * So a window that only refusals report (a policy hold the caller records\n * itself, for example) ends at its reset or with a complete usage reading,\n * never with a header reading. A caller's own refusal must be tagged\n * `refusal`, not `usage_endpoint`, or a complete adapter's reading would\n * erase the stored windows.\n * Clearing a cooldown early is an explicit, revision-fenced store operation,\n * not an observation. Revision numbers are assigned by the store.\n */\nexport function applyQuotaObservation(\n connection: Pick<SubscriptionConnection, \"refreshGeneration\" | \"quota\">,\n observation: SubscriptionQuota,\n options: {\n /** The observing adapter declares its usage-endpoint readings complete. */\n usageReadingsComplete?: boolean;\n } = {},\n): SubscriptionQuota | null {\n if (!quotaObservationApplies(connection, observation)) return connection.quota;\n const current = connection.quota;\n if (!current || current.observedRefreshGeneration !== connection.refreshGeneration) {\n return observation;\n }\n if (\n current.observedAt !== null &&\n (observation.observedAt === null || observation.observedAt < current.observedAt)\n ) {\n return current;\n }\n // Keep only what still runs after this observation was made: later values\n // win, and deadlines and cooldowns that ended before it are dropped.\n const observedAt = observation.observedAt ?? -Infinity;\n const modelCooldowns: Record<string, number> = {};\n for (const source of [current.modelCooldowns, observation.modelCooldowns]) {\n for (const [modelId, until] of Object.entries(source)) {\n if (until <= observedAt) continue;\n modelCooldowns[modelId] = Math.max(modelCooldowns[modelId] ?? until, until);\n }\n }\n const keepCurrentDeadline =\n current.exhaustedUntil !== null &&\n current.exhaustedUntil > observedAt &&\n (observation.exhaustedUntil === null || observation.exhaustedUntil < current.exhaustedUntil);\n const exhaustedUntil = keepCurrentDeadline ? current.exhaustedUntil : observation.exhaustedUntil;\n // A block keeps its evidence only where every observation that set its\n // merged deadline explained it (`QuotaBlockClaim`).\n const sources = [current, observation];\n const modelCooldownClaims: Record<string, QuotaBlockClaim> = {};\n for (const [modelId, until] of Object.entries(modelCooldowns)) {\n const claim = mergedClaim(\n until,\n sources.map((source) => ({\n until: source.modelCooldowns[modelId],\n claim: quotaBlockClaim(source, modelId),\n })),\n );\n if (claim) modelCooldownClaims[modelId] = claim;\n }\n const exhaustedClaim =\n exhaustedUntil === null\n ? null\n : mergedClaim(\n exhaustedUntil,\n sources.map((source) => ({\n until: source.exhaustedUntil,\n claim: quotaBlockClaim(source, null),\n })),\n );\n const reported = new Set(observation.windows.map((window) => window.id));\n const omittedExhausted =\n observation.source === \"usage_endpoint\" && options.usageReadingsComplete === true\n ? []\n : current.windows.filter(\n (window) =>\n !reported.has(window.id) &&\n window.status === \"exhausted\" &&\n window.resetsAt !== null &&\n window.resetsAt > observedAt,\n );\n const merged: SubscriptionQuota = {\n ...observation,\n windows: omittedExhausted.length\n ? [...observation.windows, ...omittedExhausted]\n : observation.windows,\n modelCooldowns,\n exhaustedUntil,\n exhaustedKind: keepCurrentDeadline ? current.exhaustedKind : observation.exhaustedKind,\n };\n delete merged.modelCooldownClaims;\n delete merged.exhaustedClaim;\n if (Object.keys(modelCooldownClaims).length > 0) merged.modelCooldownClaims = modelCooldownClaims;\n if (exhaustedClaim) merged.exhaustedClaim = exhaustedClaim;\n return merged;\n}\n","import type { CacheFacts, SessionBinding } from \"./types\";\n\n/**\n * Idle cut-off used for providers whose cut-off has not been measured yet\n * (design 6.1). Erring towards \"warm\" avoids paying a cache miss for a switch\n * that was not needed.\n */\nexport const DEFAULT_IDLE_CUTOFF_MS = 60 * 60 * 1000;\n\n/** How long the provider prompt cache stays warm after a model call. */\nexport function cacheLifetimeMs(facts: CacheFacts | undefined): number {\n if (!facts) return DEFAULT_IDLE_CUTOFF_MS;\n if (facts.kind === \"exact_ttl\") return Math.max(0, facts.ttlMs);\n return facts.cutoffMs === null ? DEFAULT_IDLE_CUTOFF_MS : Math.max(0, facts.cutoffMs);\n}\n\n/**\n * The cache is warm while the session has been idle no longer than the\n * provider's cache lifetime (SUB-STICK-04). Idle time is measured from the\n * latest completed model call, not the turn start, so a long turn is not\n * mistaken for a cold cache. An exact lifetime sent with the latest request\n * (an exact cache TTL) takes precedence over the provider's facts.\n */\nexport function isCacheWarm(\n binding: Pick<SessionBinding, \"lastModelCallAt\" | \"cacheTtlMs\">,\n facts: CacheFacts | undefined,\n now: number,\n): boolean {\n const lifetime =\n binding.cacheTtlMs !== undefined && binding.cacheTtlMs !== null\n ? Math.max(0, binding.cacheTtlMs)\n : cacheLifetimeMs(facts);\n return now - binding.lastModelCallAt <= lifetime;\n}\n","import { modelCooldownUntil, quotaCapacity, type QuotaCapacity } from \"./quota\";\nimport { providerSwitchesFor } from \"./settings\";\nimport type {\n ModelDescriptor,\n ModelId,\n PlacementInput,\n PlacementPerson,\n PlacementWorkspace,\n SubscriptionConnection,\n} from \"./types\";\n\n/**\n * Reasons a connection may not serve a session at all. These are authority:\n * the database enforces the same rules, so a connection with any of them must\n * never be selected (SUB-ELIG-01, SUB-ELIG-05, SUB-SET-06, SUB-FAIL-04).\n */\nexport type AuthorizationIneligibility =\n | \"provider_disabled\"\n | \"out_of_scope\"\n | \"personal_connections_disabled\"\n | \"personal_owner_inactive\"\n | \"personal_not_owners_work\"\n | \"personal_authority_missing\";\n\n/** Reasons an authorized connection cannot serve a model now (SUB-ELIG-02..04). */\nexport type ServiceIneligibility =\n | \"unhealthy\"\n | \"allocator_disabled\"\n | \"wrong_provider\"\n | \"model_unknown\"\n | \"model_not_allowed_by_workspace\"\n | \"model_not_entitled\"\n | \"model_not_allowed_by_connection\"\n | \"model_cooling_down\"\n | \"exhausted\";\n\nexport type Ineligibility = AuthorizationIneligibility | ServiceIneligibility;\n\nexport const AUTHORIZATION_INELIGIBILITIES: readonly AuthorizationIneligibility[] = Object.freeze([\n \"provider_disabled\",\n \"out_of_scope\",\n \"personal_connections_disabled\",\n \"personal_owner_inactive\",\n \"personal_not_owners_work\",\n \"personal_authority_missing\",\n]);\n\nconst AUTHORIZATION_SET = new Set<Ineligibility>(AUTHORIZATION_INELIGIBILITIES);\n\nexport function isAuthorizationIneligibility(\n reason: Ineligibility,\n): reason is AuthorizationIneligibility {\n return AUTHORIZATION_SET.has(reason);\n}\n\nconst PERMANENT_SERVICE = new Set<Ineligibility>([\n \"wrong_provider\",\n \"model_unknown\",\n \"model_not_allowed_by_workspace\",\n \"model_not_entitled\",\n \"model_not_allowed_by_connection\",\n]);\n\n/**\n * Reasons that neither time nor a quota reset clears: the work is not\n * authorized on the account, or the account cannot serve the model at all.\n * Health and allocator eligibility are not permanent: reconnecting or\n * re-enabling allocation restores them.\n */\nexport function isPermanentIneligibility(reason: Ineligibility): boolean {\n return AUTHORIZATION_SET.has(reason) || PERMANENT_SERVICE.has(reason);\n}\n\n/** Capacity of a connection now, with the input's staleness bound for its provider. */\nexport function connectionCapacity(\n input: PlacementInput,\n connection: SubscriptionConnection,\n): QuotaCapacity {\n const capacity = quotaCapacity(\n connection.quota,\n input.now,\n input.quotaStaleAfterMs?.[connection.provider],\n );\n if (connectionUsesExtraCredits(input, connection)) return { kind: \"available\" };\n return capacity;\n}\n\n/** Only included-window exhaustion is eligible for opted-in credit fallback. */\nexport function connectionUsesExtraCredits(\n input: PlacementInput,\n connection: SubscriptionConnection,\n): boolean {\n return (\n connection.extraCreditsEnabled === true &&\n !(connection.quota?.exhaustedUntil != null && connection.quota.exhaustedUntil > input.now) &&\n quotaCapacity(connection.quota, input.now, input.quotaStaleAfterMs?.[connection.provider])\n .kind === \"exhausted\"\n );\n}\n\nfunction findModel(input: PlacementInput, modelId: ModelId): ModelDescriptor | undefined {\n return input.models.find((model) => model.id === modelId);\n}\n\nfunction findPerson(input: PlacementInput, membershipId: string): PlacementPerson | undefined {\n return input.people.find((person) => person.membershipId === membershipId);\n}\n\n/** Workspace model restrictions are a ceiling on every selection (SUB-ELIG-02). */\nexport function modelAllowedInWorkspace(workspace: PlacementWorkspace, modelId: ModelId): boolean {\n return workspace.allowedModelIds === null || workspace.allowedModelIds.includes(modelId);\n}\n\n/** Is the provider switched on for this workspace (SUB-SET-06)? */\nexport function providerEnabled(input: PlacementInput, provider: string): boolean {\n return providerSwitchesFor(input.settings, provider).enabled;\n}\n\n/**\n * Authorization of one connection for the session's work, independent of\n * model and capacity. Personal connections serve only their owner's own work,\n * in the owner's private sessions or Personal workspace, with frozen personal\n * authority for that provider (SUB-ELIG-05, design 3.7, 3.8). Shared\n * connections serve the workspaces and people in their scope, where people are\n * evaluated against the session owner.\n */\nexport function authorizationIneligibility(\n input: PlacementInput,\n connection: SubscriptionConnection,\n): AuthorizationIneligibility[] {\n const reasons: AuthorizationIneligibility[] = [];\n const { session, workspace } = input;\n const switches = providerSwitchesFor(input.settings, connection.provider);\n if (!switches.enabled) reasons.push(\"provider_disabled\");\n const ownership = connection.ownership;\n if (ownership.kind === \"personal\") {\n const owner = ownership.ownerMembershipId;\n if (!input.settings.personalConnectionsAllowed) reasons.push(\"personal_connections_disabled\");\n if (!findPerson(input, owner)?.active) reasons.push(\"personal_owner_inactive\");\n const authority = session.personalAuthority.find(\n (entry) => entry.provider === connection.provider && entry.ownerMembershipId === owner,\n );\n // A new shared-chat entry is a restriction, never a pool-wide grant and\n // never an alias for the chat owner's identity. Historical entries keep\n // their private-work eligibility unchanged.\n const ownersOwnWork = authority?.sharedSessionChoice\n ? session.ownerMembershipId !== null &&\n authority.sharedSessionChoice.sessionId === session.id &&\n authority.sharedSessionChoice.connectionId === connection.id\n : session.ownerMembershipId === owner &&\n (session.visibility === \"private\" ||\n (workspace.kind === \"personal\" && workspace.ownerMembershipId === owner));\n if (!ownersOwnWork) reasons.push(\"personal_not_owners_work\");\n if (!authority) reasons.push(\"personal_authority_missing\");\n return reasons;\n }\n const scope = ownership.scope;\n const inScope =\n scope.kind === \"organization\" ||\n (scope.kind === \"workspaces\" &&\n (scope.workspaceIds.includes(workspace.id) ||\n (workspace.kind === \"personal\" && scope.allowPersonalWorkspaces))) ||\n (scope.kind === \"people\" &&\n session.ownerMembershipId !== null &&\n scope.membershipIds.includes(session.ownerMembershipId));\n if (!inScope) reasons.push(\"out_of_scope\");\n return reasons;\n}\n\n/** Reasons the connection cannot serve this model, ignoring capacity and cooldowns. */\nfunction staticServiceIneligibility(\n input: PlacementInput,\n connection: SubscriptionConnection,\n modelId: ModelId,\n): ServiceIneligibility[] {\n const reasons: ServiceIneligibility[] = [];\n if (connection.health !== \"healthy\") reasons.push(\"unhealthy\");\n if (!connection.allocatorEnabled) reasons.push(\"allocator_disabled\");\n const model = findModel(input, modelId);\n if (!model) reasons.push(\"model_unknown\");\n else if (model.provider !== connection.provider) reasons.push(\"wrong_provider\");\n if (!modelAllowedInWorkspace(input.workspace, modelId)) {\n reasons.push(\"model_not_allowed_by_workspace\");\n }\n const entitled =\n (connection.entitledModelIds === null || connection.entitledModelIds.includes(modelId)) &&\n !connection.excludedModelIds.includes(modelId);\n if (!entitled) reasons.push(\"model_not_entitled\");\n if (connection.allowedModelIds !== null && !connection.allowedModelIds.includes(modelId)) {\n reasons.push(\"model_not_allowed_by_connection\");\n }\n return reasons;\n}\n\n/** Every reason this connection cannot serve this session and model now; empty when it can. */\nexport function connectionIneligibility(\n input: PlacementInput,\n connection: SubscriptionConnection,\n modelId: ModelId,\n): Ineligibility[] {\n const reasons: Ineligibility[] = [\n ...authorizationIneligibility(input, connection),\n ...staticServiceIneligibility(input, connection, modelId),\n ];\n if (modelCooldownUntil(connection.quota, modelId, input.now) !== null) {\n reasons.push(\"model_cooling_down\");\n }\n if (connectionCapacity(input, connection).kind === \"exhausted\") reasons.push(\"exhausted\");\n return reasons;\n}\n\n/** SUB-ELIG-01..06: may this connection serve this session's model now? */\nexport function canServe(\n input: PlacementInput,\n connection: SubscriptionConnection,\n modelId: ModelId,\n): boolean {\n return connectionIneligibility(input, connection, modelId).length === 0;\n}\n\n/**\n * When a connection that is blocked only by capacity or a model cooldown can\n * serve this model again. Returns `undefined` when something other than time\n * blocks it, or when it is not blocked at all, and `null` when the reset time\n * is unknown.\n */\nexport function servableAgainAt(\n input: PlacementInput,\n connection: SubscriptionConnection,\n modelId: ModelId,\n): number | null | undefined {\n if (authorizationIneligibility(input, connection).length > 0) return undefined;\n if (staticServiceIneligibility(input, connection, modelId).length > 0) return undefined;\n const capacity = connectionCapacity(input, connection);\n const cooldown = modelCooldownUntil(connection.quota, modelId, input.now);\n if (capacity.kind !== \"exhausted\" && cooldown === null) return undefined;\n // A refusal can expire before an exhausted allowance resets. At that\n // point credit consent may make the account usable, even without a known\n // allowance reset. Evaluate every observed transition using the same policy.\n const transitions = [\n cooldown,\n connection.quota?.exhaustedUntil,\n ...(connection.quota?.windows.map((window) => window.resetsAt) ?? []),\n ]\n .filter((at): at is number => at != null && at > input.now)\n .sort((a, b) => a - b);\n return transitions.find((now) => canServe({ ...input, now }, connection, modelId)) ?? null;\n}\n\n/** Own Personal workspaces use connected accounts directly; elsewhere fallback requires consent. */\nexport function personalFallbackActive(input: PlacementInput): boolean {\n if (input.session.ownerMembershipId === null) return false;\n if (input.workspace.kind === \"shared\" && input.session.visibility === \"shared\") return false;\n return (\n (input.workspace.kind === \"personal\" &&\n input.workspace.ownerMembershipId === input.session.ownerMembershipId) ||\n (input.settings.personalFallbackAllowed &&\n !!findPerson(input, input.session.ownerMembershipId)?.personalFallbackOptIn)\n );\n}\n","import type { ModelDescriptor } from \"./types\";\n\n/**\n * Map a requested reasoning level onto a target model (SUB-FAIL-03, D-16).\n *\n * A level the target supports by name is kept. Otherwise the level's relative\n * position on the source model's ladder (lowest 0, highest 1) is mapped to the\n * nearest relative position on the target's ladder, an exact tie going to the\n * lower level. A level the source ladder does not list has no position and\n * maps to the middle of the target's ladder (the lower middle of two).\n */\nexport function mapReasoningLevel(\n requested: string,\n source: Pick<ModelDescriptor, \"reasoningLevels\"> | undefined,\n target: Pick<ModelDescriptor, \"reasoningLevels\">,\n): string {\n const levels = target.reasoningLevels;\n if (levels.includes(requested)) return requested;\n if (levels.length <= 1) return levels[0] ?? \"\";\n const sourceLevels = source?.reasoningLevels ?? [];\n const position = sourceLevels.indexOf(requested);\n // The requested position as an exact fraction numerator / denominator.\n const [numerator, denominator] =\n position < 0 || sourceLevels.length <= 1 ? [1, 2] : [position, sourceLevels.length - 1];\n // round(numerator / denominator * (L - 1)) with ties down, in integers.\n const scaled = numerator * (levels.length - 1);\n const index = Math.floor((2 * scaled + denominator - 1) / (2 * denominator));\n return levels[Math.min(index, levels.length - 1)]!;\n}\n","/**\n * Spread hash (D-21, D-23): 32-bit FNV-1a over the UTF-16 code units of\n * `<session id>|<connection id>`, finished with the murmur3 32-bit finalizer.\n * Among otherwise equal connections a session takes the one with the lowest\n * value, so each session has a stable home that does not move when an\n * unrelated connection joins or leaves the pool, and placement needs no lock\n * across sessions (SUB-SEL-05). Without the finalizer, FNV-1a values of keys\n * that differ only in their last characters are not uniformly ordered, which\n * skews the spread (SUB-SEL-03, D-23).\n */\nexport function spreadHash(sessionId: string, connectionId: string): number {\n const key = `${sessionId}|${connectionId}`;\n let hash = 0x811c9dc5;\n for (let index = 0; index < key.length; index += 1) {\n hash ^= key.charCodeAt(index);\n hash = Math.imul(hash, 0x01000193) >>> 0;\n }\n hash ^= hash >>> 16;\n hash = Math.imul(hash, 0x85ebca6b) >>> 0;\n hash ^= hash >>> 13;\n hash = Math.imul(hash, 0xc2b2ae35) >>> 0;\n hash ^= hash >>> 16;\n return hash >>> 0;\n}\n","import { isCacheWarm } from \"./cache\";\nimport {\n canServe,\n connectionUsesExtraCredits,\n connectionIneligibility,\n isPermanentIneligibility,\n modelAllowedInWorkspace,\n personalFallbackActive,\n providerEnabled,\n servableAgainAt,\n type Ineligibility,\n} from \"./eligibility\";\nimport { mapReasoningLevel } from \"./reasoning\";\nimport {\n FALLBACK_ORDER_LIMITS,\n fallbackOrderFor,\n hasFallbackOrderEntry,\n rotationFor,\n} from \"./settings\";\nimport { spreadHash } from \"./spread\";\nimport type {\n ModelDescriptor,\n ModelId,\n ModelTier,\n PlacementDecision,\n PlacementInput,\n PlacementSwitch,\n ReselectionPoint,\n SubscriptionConnection,\n WaitReason,\n} from \"./types\";\n\nfunction findModel(input: PlacementInput, modelId: ModelId): ModelDescriptor | undefined {\n return input.models.find((model) => model.id === modelId);\n}\n\n/** Every model tier, as declared once in the pure package (D-35). */\nexport const MODEL_TIERS: readonly ModelTier[] = Object.freeze([\"frontier\", \"standard\", \"fast\"]);\n\n/** Whether a value is one of the declared model tiers. */\nexport function isModelTier(value: unknown): value is ModelTier {\n return typeof value === \"string\" && (MODEL_TIERS as readonly string[]).includes(value);\n}\n\n/**\n * The default fallback order of a preferred model that has no explicit\n * entry (D-35): the models of other providers that declare the preferred\n * model's tier, never a lower or a higher one, providers in id order and\n * each provider's models in their input (declaration) order, at most\n * `FALLBACK_ORDER_LIMITS.fallbacksPerModel`. Empty while cross-provider\n * failover is off, and for a preferred model that is unknown or declares no\n * tier; a model without a tier is never derived. Same-provider model\n * fallback stays explicit only. Stepping down a tier is not allowed until\n * the owner decides it (Q-04). Provider ids are opaque keys here: which\n * providers' models the input carries is the caller's choice.\n */\nexport function derivedFallbackOrder(input: PlacementInput): ModelId[] {\n if (!input.settings.crossProviderFailover) return [];\n const preferred = findModel(input, input.session.preferredModelId);\n if (!preferred || !isModelTier(preferred.tier)) return [];\n const tier = preferred.tier;\n const derived = input.models\n .map((model, index) => ({ model, index }))\n .filter(\n ({ model }) =>\n model.provider !== preferred.provider && model.tier === tier && model.id !== preferred.id,\n )\n .sort(\n (left, right) =>\n (left.model.provider < right.model.provider\n ? -1\n : left.model.provider > right.model.provider\n ? 1\n : 0) || left.index - right.index,\n )\n .map(({ model }) => model.id);\n return [...new Set(derived)].slice(0, FALLBACK_ORDER_LIMITS.fallbacksPerModel);\n}\n\n/**\n * Candidate models in failover order (design 4 step 3): the preferred model,\n * then its fallbacks: the explicit effective entry when there is one, even\n * an empty one, else the same-tier order `derivedFallbackOrder` derives\n * (D-35). Same-provider fallbacks are always candidates; other providers\n * need cross-provider failover (SUB-FAIL-02, SUB-FAIL-03). A session limited\n * to \"only this model\" or bound by an explicit choice has only the preferred\n * model (SUB-FAIL-05). Restrictions do not remove candidates, so a restricted\n * preferred model keeps its place in the order (D-17); see `modelPermitted`.\n */\nexport function failoverCandidateModels(input: PlacementInput): ModelId[] {\n const { session, settings } = input;\n const preferredId = session.preferredModelId;\n if (session.onlyThisModel || session.binding?.choice === \"explicit\") return [preferredId];\n const preferred = findModel(input, preferredId);\n const candidates: ModelId[] = [preferredId];\n const order = hasFallbackOrderEntry(settings, preferredId)\n ? fallbackOrderFor(settings, preferredId)\n : derivedFallbackOrder(input);\n for (const modelId of order) {\n if (candidates.includes(modelId)) continue;\n const model = findModel(input, modelId);\n if (!model) continue;\n if (model.provider !== preferred?.provider && !settings.crossProviderFailover) continue;\n candidates.push(modelId);\n }\n return candidates;\n}\n\n/**\n * May this session's work use this model at all? The workspace model ceiling\n * (SUB-ELIG-02), the provider switch (SUB-SET-06) and a compaction provider\n * lock (SUB-FAIL-09) restrict models alike: a restricted preferred model is\n * treated like one without capacity and work moves to the next permitted\n * candidate (D-17, D-26).\n */\nexport function modelPermitted(\n input: PlacementInput,\n modelId: ModelId,\n honourCompactionLock = true,\n): boolean {\n const model = findModel(input, modelId);\n if (!model) return false;\n if (!modelAllowedInWorkspace(input.workspace, modelId)) return false;\n if (!providerEnabled(input, model.provider)) return false;\n const lock = input.session.compactionProviderLock;\n return !honourCompactionLock || lock === null || model.provider === lock;\n}\n\n/**\n * Order servable connections for one model (design 4 step 5): the primary\n * first, whether or not its quota is known (D-13, D-15), then the session's\n * spread hash (D-14, D-21, D-23), then id. Missing telemetry is not a penalty.\n */\nexport function rankConnections(\n input: PlacementInput,\n connections: readonly SubscriptionConnection[],\n): SubscriptionConnection[] {\n const keyed = connections.map((connection) => {\n const rotation = rotationFor(input.settings, connection.provider);\n return {\n connection,\n credits: connectionUsesExtraCredits(input, connection),\n primary: rotation.mode === \"primary_first\" && rotation.primaryConnectionId === connection.id,\n hash: spreadHash(input.session.id, connection.id),\n };\n });\n keyed.sort(\n (left, right) =>\n Number(left.credits) - Number(right.credits) ||\n Number(right.primary) - Number(left.primary) ||\n left.hash - right.hash ||\n (left.connection.id < right.connection.id\n ? -1\n : left.connection.id > right.connection.id\n ? 1\n : 0),\n );\n return keyed.map((entry) => entry.connection);\n}\n\n/**\n * Why an automatic binding does not keep its account for this placement\n * (SUB-STICK-03). Every reason except `cache_cold` is forced: the account or\n * its model cannot (or should not) serve this work now, whatever the cache.\n */\nexport type ReselectionReason =\n /** The session has no account yet. */\n | \"unbound\"\n /**\n * The bound account is not in this session's world: removed, or outside\n * the session's scope (a disconnected account stays in the world and is\n * `account_ineligible`).\n */\n | \"account_unavailable\"\n /** The bound model may no longer be used here (workspace, provider switch, compaction lock). */\n | \"model_not_permitted\"\n /**\n * The bound account cannot serve now; `ineligibility` says why (exhausted\n * or rate-limited, unhealthy or quarantined, disconnected, switched off,\n * personal authority or owner gone).\n */\n | \"account_ineligible\"\n /** The bound account would spend paid credits while another has included allowance (SUB-CREDITS-01). */\n | \"extra_credits\"\n /** A compaction or model change replaced the cached prefix (SUB-STICK-05). */\n | \"reselection_point\"\n /** Idle longer than the provider's declared cache lifetime (SUB-STICK-04). */\n | \"cache_cold\";\n\nexport type BindingStickiness =\n | { kind: \"keep\"; connectionId: string; modelId: ModelId }\n | {\n kind: \"reselect\";\n reason: ReselectionReason;\n /** The bound account's ineligibility for its model (empty unless `account_ineligible`). */\n ineligibility: readonly Ineligibility[];\n /** The re-selection points that applied (empty unless `reselection_point`). */\n points: readonly ReselectionPoint[];\n };\n\n/**\n * Does the session's automatic binding keep its account (SUB-STICK-02,\n * SUB-STICK-03)? It does while the account can still serve the bound model,\n * no re-selection point applies and the prompt cache is warm; otherwise\n * placement re-runs normal selection, which may land on the same account.\n * Forced reasons are checked before warmth, so a warm cache never keeps an\n * account the session may not use (SUB-STICK-07). An explicit choice is not\n * evaluated here: placement keeps it or waits for it (SUB-STICK-06).\n */\nexport function bindingStickiness(input: PlacementInput): BindingStickiness {\n const { session, connections } = input;\n const binding = session.binding;\n const reselect = (\n reason: ReselectionReason,\n ineligibility: readonly Ineligibility[] = [],\n points: readonly ReselectionPoint[] = [],\n ): BindingStickiness => ({ kind: \"reselect\", reason, ineligibility, points });\n if (!binding) return reselect(\"unbound\");\n const bound = connections.find((connection) => connection.id === binding.connectionId);\n if (!bound) return reselect(\"account_unavailable\");\n const permitted = failoverCandidateModels(input).filter((modelId) =>\n modelPermitted(input, modelId),\n );\n if (!permitted.includes(binding.modelId))\n // The session moved to another model (no longer a candidate at all) is\n // a re-selection point; a candidate the session may not use is forced.\n return session.reselectionPoints.length > 0 &&\n !failoverCandidateModels(input).includes(binding.modelId)\n ? reselect(\"reselection_point\", [], session.reselectionPoints)\n : reselect(\"model_not_permitted\");\n const ineligibility = connectionIneligibility(input, bound, binding.modelId);\n if (ineligibility.length > 0) return reselect(\"account_ineligible\", ineligibility);\n const fallback = personalFallbackActive(input);\n if (\n connectionUsesExtraCredits(input, bound) &&\n connections.some(\n (candidate) =>\n (candidate.ownership.kind === \"shared\" || fallback) &&\n !connectionUsesExtraCredits(input, candidate) &&\n canServe(input, candidate, binding.modelId),\n )\n ) {\n return reselect(\"extra_credits\");\n }\n if (session.reselectionPoints.length > 0)\n return reselect(\"reselection_point\", [], session.reselectionPoints);\n if (!isCacheWarm(binding, input.cacheFacts[bound.provider], input.now))\n return reselect(\"cache_cold\");\n return { kind: \"keep\", connectionId: bound.id, modelId: binding.modelId };\n}\n\n/**\n * Place one session's turn (design 4): run on an account and model, or wait\n * with an explained reason. The sender is deliberately not an input: the\n * account belongs to the session (SUB-STICK-01).\n */\nexport function decidePlacement(input: PlacementInput): PlacementDecision {\n const { session, connections } = input;\n const preferred = findModel(input, session.preferredModelId);\n const models = failoverCandidateModels(input);\n const permitted = models.filter((modelId) => modelPermitted(input, modelId));\n const fallback = personalFallbackActive(input);\n const wait = (reason: WaitReason, earliestResetAt: number | null): PlacementDecision => ({\n kind: \"wait\",\n reason,\n earliestResetAt,\n });\n const run = (\n connection: SubscriptionConnection,\n modelId: ModelId,\n kind: PlacementSwitch,\n ): PlacementDecision => ({\n kind: \"run\",\n connectionId: connection.id,\n provider: connection.provider,\n modelId,\n reasoningLevel: mapReasoningLevel(\n session.reasoningLevel,\n preferred,\n findModel(input, modelId)!,\n ),\n switch: kind,\n personal: connection.ownership.kind === \"personal\",\n });\n const servableAutomatically = (modelId: ModelId) =>\n connections.some(\n (connection) =>\n (connection.ownership.kind === \"shared\" || fallback) &&\n canServe(input, connection, modelId),\n );\n // Candidates the compaction lock alone keeps from this session (SUB-FAIL-09).\n const lockedOut = models.filter(\n (modelId) => !modelPermitted(input, modelId) && modelPermitted(input, modelId, false),\n );\n\n // No candidate model may be used here: wait on the restriction (D-17).\n if (permitted.length === 0) {\n return wait(lockedOut.length > 0 ? \"compaction_provider_locked\" : \"model_not_allowed\", null);\n }\n\n const binding = session.binding;\n const bound = binding\n ? connections.find((connection) => connection.id === binding.connectionId)\n : undefined;\n\n // An explicit choice is binding: run there or wait for it (SUB-SEL-04,\n // SUB-STICK-06). One that can never serve this work says so instead of\n // waiting silently (D-24, SUB-ACCESS-06).\n if (binding?.choice === \"explicit\") {\n if (bound && canServe(input, bound, session.preferredModelId)) {\n return run(bound, session.preferredModelId, \"pinned\");\n }\n if (\n !bound ||\n connectionIneligibility(input, bound, session.preferredModelId).some(isPermanentIneligibility)\n ) {\n return wait(\"pinned_account_ineligible\", null);\n }\n if (\n connectionIneligibility(input, bound, session.preferredModelId).includes(\"allocator_disabled\")\n ) {\n return wait(\"pinned_account_disabled\", null);\n }\n const at = servableAgainAt(input, bound, session.preferredModelId);\n return wait(\"pinned_account_unavailable\", typeof at === \"number\" ? at : null);\n }\n\n const bindingServable =\n !!binding &&\n !!bound &&\n permitted.includes(binding.modelId) &&\n canServe(input, bound, binding.modelId);\n\n // Keep a warm session where it is unless a re-selection point applies\n // or the account is forced away (SUB-STICK-02, SUB-STICK-03, SUB-STICK-05).\n const stickiness = bindingStickiness(input);\n if (stickiness.kind === \"keep\" && bound) return run(bound, stickiness.modelId, \"sticky\");\n\n // Each model on shared connections, then (opt-in) on the owner's personal\n // connections, before the next model (SUB-SEL-01, SUB-SEL-02, D-12).\n for (const [index, modelId] of models.entries()) {\n if (!modelPermitted(input, modelId)) continue;\n const eligible = connections.filter(\n (connection) =>\n canServe(input, connection, modelId) &&\n (connection.ownership.kind === \"shared\" || fallback),\n );\n const included = eligible.filter(\n (connection) => !connectionUsesExtraCredits(input, connection),\n );\n const servable = included.length ? included : eligible;\n const chosen =\n rankConnections(\n input,\n servable.filter((connection) => connection.ownership.kind === \"shared\"),\n )[0] ??\n (fallback\n ? rankConnections(\n input,\n servable.filter((connection) => connection.ownership.kind === \"personal\"),\n )[0]\n : undefined);\n if (!chosen) continue;\n let kind: PlacementSwitch;\n if (!binding) kind = \"initial\";\n else if (binding.connectionId === chosen.id && binding.modelId === modelId) kind = \"sticky\";\n else if (index === 0) {\n if (binding.modelId !== modelId) kind = \"return_to_preferred\";\n else kind = bindingServable ? \"reselected_cold\" : \"failover_same_provider\";\n } else {\n kind =\n findModel(input, modelId)?.provider === preferred?.provider\n ? \"failover_same_provider\"\n : \"failover_cross_provider\";\n }\n return run(chosen, modelId, kind);\n }\n\n // Nothing servable: wait for the earliest known time a connection this\n // session may use automatically can serve a permitted model (SUB-WAIT-01,\n // SUB-WAIT-02, D-22), and name the compaction lock when it is what keeps a\n // servable model away (SUB-FAIL-09).\n let earliest: number | null = null;\n for (const modelId of permitted) {\n for (const connection of connections) {\n if (connection.ownership.kind === \"personal\" && !fallback) continue;\n const at = servableAgainAt(input, connection, modelId);\n if (typeof at === \"number\") earliest = earliest === null ? at : Math.min(earliest, at);\n }\n }\n return wait(\n lockedOut.some(servableAutomatically) ? \"compaction_provider_locked\" : \"no_eligible_capacity\",\n earliest,\n );\n}\n","import type { SubscriptionQuotaBlock } from \"./quota\";\nimport type { CacheFacts, ModelId, ModelTier, ProviderId, SubscriptionQuota } from \"./types\";\n\n/**\n * The prompt-cache lifetime a provider adapter declares (SUB-PROV-01,\n * SUB-STICK-04): either the exact lifetime Opengeni sends with each request,\n * or an idle cut-off taken from the provider's public documentation. Every\n * adapter states a positive number; the shared core never falls back to a\n * default for a provider and never names one. Choose it conservatively:\n * a session switches account only once idle longer than this, so a value\n * that is too short pays for a cache miss the provider would have avoided.\n */\nexport type DeclaredCacheFacts =\n | { kind: \"exact_ttl\"; ttlMs: number }\n | { kind: \"measured_idle_cutoff\"; cutoffMs: number };\n\n/** The declared lifetime in milliseconds, or a thrown error for an unusable declaration. */\nexport function declaredCacheLifetimeMs(facts: DeclaredCacheFacts): number {\n const lifetime = facts.kind === \"exact_ttl\" ? facts.ttlMs : facts.cutoffMs;\n if (!Number.isSafeInteger(lifetime) || lifetime <= 0)\n throw new Error(\"A provider adapter must declare a positive whole-millisecond cache lifetime\");\n return lifetime;\n}\n\n/**\n * Provider differences are capability flags, never provider conditionals in\n * shared code (SUB-PROV-02).\n */\nexport type ProviderCapabilities = {\n /**\n * The credential renews itself through refresh. A provider whose credential\n * formats differ (OAuth renews, a setup token or an API key does not)\n * declares it per format through `capabilitiesFor`.\n */\n autoRenews: boolean;\n resetCredits: boolean;\n /** Paid usage beyond the plan can be enabled per connection (consent-gated). */\n extraCredits: boolean;\n modelEntitlements: boolean;\n realtime: boolean;\n fundsMedia: boolean;\n apps: boolean;\n remoteCompaction: boolean;\n quotaWindows: boolean;\n};\n\n/**\n * Shared error outcomes every adapter classifies into (SUB-PROV-01). A\n * refusal that limits one model only carries its `modelId`, so the core can\n * record it as that model's cooldown on the connection\n * (`modelCooldownFromOutcome`). The shared settlement step\n * (`planSubscriptionCoreRefusal`) records it; the live worker does once it\n * adopts that step.\n */\nexport type ProviderErrorOutcome =\n | { kind: \"exhausted\"; resetAt: number | null; modelId?: ModelId }\n | { kind: \"rate_limited\"; retryAfterMs: number | null; modelId?: ModelId }\n | { kind: \"unauthorized\" }\n | { kind: \"forbidden\" }\n | { kind: \"entitlement_missing\"; modelId: ModelId }\n | { kind: \"overloaded\" }\n | { kind: \"transient\" }\n | { kind: \"fatal\" };\n\n/** An encrypted credential as stored; the core never decrypts it. */\nexport type EncryptedCredential = {\n credentialEncrypted: string;\n credentialFormat: string;\n expiresAt: number | null;\n};\n\n/** Identity a sign-in reports for the connection row. */\nexport type ConnectionIdentity = {\n providerAccountId: string;\n accountEmail: string | null;\n planType: string | null;\n};\n\nexport type SignInResult = { credential: EncryptedCredential; identity: ConnectionIdentity };\n\n/** Items in conversation history that only their own provider accepts. */\nexport type ProviderHistoryItemKind =\n | \"encrypted_reasoning\"\n | \"thinking_signature\"\n | \"provider_tool\"\n | \"remote_compaction\";\n\n/**\n * What a provider can accept from history written while another provider\n * served the session (design 4, SUB-FAIL-08).\n */\nexport type HistoryCompatibility = {\n /** Provider-specific item kinds to drop from a request copy for this provider. */\n dropFromOtherProviders: readonly ProviderHistoryItemKind[];\n};\n\n/**\n * The adapter surface every model connection implements, including API-key\n * connectors (SUB-PROV-04). `Transport` is the provider's request-local\n * authorization handle; `ProviderError` is whatever the transport throws.\n */\nexport interface ModelConnectionAdapter<Transport = unknown, ProviderError = unknown> {\n readonly provider: ProviderId;\n readonly capabilities: ProviderCapabilities;\n /** Request-local authorization and wire normalization for one selected connection. */\n transport(input: { connectionId: string; credential: EncryptedCredential }): Promise<Transport>;\n /** Models the connection's plan can serve, including provider-observed exclusions. */\n entitledModels(input: {\n planType: string | null;\n excludedModelIds: readonly ModelId[];\n }): readonly ModelId[] | null;\n /** Null when the error is not a provider outcome (for example a programming error). */\n classifyError(error: ProviderError): ProviderErrorOutcome | null;\n cacheFacts(input: { modelId: ModelId }): CacheFacts;\n historyCompatibility(): HistoryCompatibility;\n /**\n * Optional out-of-turn quota probe (a provider usage endpoint), decoded by\n * the subscription adapter's `decodeQuota`.\n */\n fetchUsage?(transport: Transport): Promise<unknown>;\n /** Optional live model catalog of the connection, cached per refresh generation. */\n liveModels?(transport: Transport): Promise<readonly ModelId[]>;\n}\n\n/**\n * The full adapter of a signed-in subscription (SUB-PROV-01): sign-in,\n * refresh, quota decoding, plus the shared connection surface.\n */\nexport interface SubscriptionProviderAdapter<\n Transport = unknown,\n ProviderError = unknown,\n SignInInput = unknown,\n UsageResponse = unknown,\n> extends ModelConnectionAdapter<Transport, ProviderError> {\n /** OAuth, device code or setup token; returns an encrypted secret and identity. */\n signIn(input: SignInInput): Promise<SignInResult>;\n /**\n * Token refresh, called only under the core's single per-connection lock.\n * The core increments `refresh_generation` for every successful refresh.\n */\n refresh(input: {\n connectionId: string;\n credential: EncryptedCredential;\n }): Promise<EncryptedCredential>;\n /** Usage responses or headers decoded into the shared quota model. */\n decodeQuota(input: {\n response: UsageResponse;\n observedAt: number;\n refreshGeneration: number;\n }): Omit<SubscriptionQuota, \"revision\">;\n}\n\n/**\n * How a provider's credential is obtained and kept valid. `oauth` renews\n * through refresh; `setup_token` is pasted once and may not renew; `api_key`\n * is a static key with no refresh (API-key connectors).\n */\nexport type CredentialKind = \"oauth\" | \"setup_token\" | \"api_key\";\n\n/** How a provider reports usage limits to the shared quota model. */\nexport type QuotaKind =\n /** Rolling usage windows with reset times (subscriptions). */\n | \"usage_windows\"\n /** A spend budget in currency units (API-key connectors). */\n | \"spend_budget\"\n /** Request or token rate limits only, observed from refusals or headers. */\n | \"rate_limits\";\n\n/**\n * The provider facts the shared subscription runtime needs, beyond the\n * connection surface: everything the core does is identical for every\n * provider, and these members are the only per-provider inputs. Shared core\n * modules receive an adapter as data and never branch on its `provider`.\n *\n * `Credential` is the decoded secret (OAuth tokens, a setup token, an API\n * key). The core stores only its encrypted encoding and never inspects it.\n */\nexport interface SubscriptionCoreAdapter<Credential = unknown> {\n readonly provider: ProviderId;\n /** Product name used in operator-facing error texts (never in routing). */\n readonly displayName: string;\n /** Capabilities of the provider's renewable credential format. */\n readonly capabilities: ProviderCapabilities;\n /**\n * Capabilities of one credential format, as `credential.format` derives it\n * from a decoded secret. Pass that derived value, never the stored\n * `credential_format` column: the column is informational and may hold the\n * shared writers' envelope version (`v1`). The core never calls `refresh`\n * for a format whose `autoRenews` is false.\n */\n capabilitiesFor(credentialFormat: string): ProviderCapabilities;\n readonly credentialKind: CredentialKind;\n readonly quotaKind: QuotaKind;\n /** Provider identity used by workspace model policy for this provider's models. */\n readonly modelPolicyProviderId: string;\n /**\n * Every workspace model policy provider identity one model must be allowed\n * under, for a provider whose models carry more than one (absent, null or\n * empty: `modelPolicyProviderId` alone). A model is allowed only when the\n * policy allows each identity, so a second identity can only narrow access.\n * Read through `subscriptionCoreModelPolicyProviderIds`.\n */\n readonly modelPolicyProviderIdsFor?: (modelId: ModelId) => readonly string[] | null;\n /**\n * The prompt-cache lifetime placement keeps a session's account for\n * (SUB-STICK-04): an exact TTL Opengeni sends, or a documented idle cut-off.\n */\n readonly cacheFacts: DeclaredCacheFacts;\n /** Health policy applied to this provider's connections. */\n readonly health: {\n /** How long a connection the provider refused (forbidden) stays quarantined. */\n readonly forbiddenQuarantineMs: number;\n /** How long a model the plan is not entitled to stays cooled down. */\n readonly entitlementCooldownMs: number;\n /** How long a rate-limited connection rests when the provider gave no retry time. */\n readonly rateLimitFallbackMs: number;\n /**\n * How long an exhausted connection (usage limit or spend budget) rests\n * when the provider gave no reset. Placement retries it then, so a\n * connector without a usage reading still recovers on its own.\n */\n readonly exhaustedFallbackMs: number;\n };\n /** Plaintext codec for the encrypted credential column. */\n readonly credential: {\n /** Parse decrypted plaintext; throw fixed text (never echo the plaintext). */\n decode(plaintext: string): Credential;\n encode(credential: Credential): string;\n /** The expiry embedded in the credential (a JWT exp), used when the store has none. */\n expiry(credential: Credential): Date | null;\n /** The stored `credential_format` of a decoded credential. */\n format(credential: Credential): string;\n };\n /**\n * Credential renewal under the core's single per-connection lock. Null for\n * credentials that never renew (API keys, setup tokens without OAuth).\n */\n readonly refresh: CredentialRefresher<Credential> | null;\n /** Stored needs-relogin text for a provider message (fills an empty one). */\n reloginText(message: string): string;\n /**\n * Optional tier of one of the provider's models (D-35). With no explicit\n * fallback entry, placement derives a cross-provider order from equal\n * tiers. Absent, or null for a model: no tier, so nothing is derived for\n * or to that model and only explicit fallback entries apply.\n */\n modelTier?(modelId: ModelId): ModelTier | null;\n /**\n * Optional out-of-turn quota probe: read the provider's usage endpoint\n * with one connection's bearer. Every physical request goes through\n * `input.fetch` (the core's request custody). The response is opaque to\n * the core and decoded by `decodeQuota`.\n */\n fetchUsage?(input: SubscriptionCoreConnectionRead<Credential>): Promise<unknown>;\n /**\n * Decode a `fetchUsage` response into the shared quota model, fenced on\n * the refresh generation of the bearer that read it. Null when the\n * response carries no quota (an error payload or no windows).\n *\n * `modelIds`, when the caller passes it, lists the product models the\n * connection can serve when the response is decoded (the caller's\n * catalog), for providers whose usage reports limits of model groups\n * that become per-model cooldowns. Absent: no catalog is known, and only\n * models the response itself names can be cooled down.\n */\n decodeQuota?(input: {\n response: unknown;\n observedAt: number;\n refreshGeneration: number;\n modelIds?: readonly ModelId[];\n }): SubscriptionQuota | null;\n /**\n * The provider's usage endpoint is authoritative for its quota: a\n * `decodeQuota` reading below the limit ends a stored quota exhaustion\n * (`exhaustedKind: \"quota\"`) before its deadline, as an explicit,\n * revision-fenced store write. Absent: a running deadline is kept until\n * it passes (design 2.2).\n */\n usageReadEndsQuotaExhaustion?: boolean;\n /**\n * With `usageReadEndsQuotaExhaustion`: whether one `fetchUsage` response\n * confirms the connection may serve again. A response can report every\n * window below the limit and still say the account is refused (or that a\n * feature-specific limit is spent); such a reading never ends a stored\n * quota exhaustion. Absent: every reading below the limit does.\n */\n usageReadConfirmsAvailability?(response: unknown): boolean;\n /**\n * With `usageReadEndsQuotaExhaustion`: which stored blocks one `fetchUsage`\n * response that confirms availability shows have ended, judged from each\n * block's own claim (the limits the observation that set it named, tied\n * to its exact deadline: `QuotaBlockClaim`). `modelId` is the cooled\n * product model, or null for the quota exhaustion deadline. The predicate\n * must be false for a block without a claim, for a claim observed no\n * earlier than the response, and unless the response reports every\n * claimed limit numerically below it (an explicit \"no such limit\" does\n * not clear a limit that refused). A cooldown the reading itself reports\n * stands, and so does every block the predicate does not end.\n * When defined, a stored quota exhaustion deadline ends only where it\n * returns true. Absent: a reading ends no cooldown (each runs until its\n * own end), and `usageReadEndsQuotaExhaustion` alone decides the deadline.\n */\n usageReadEndsClaimedBlock?(response: unknown): (block: SubscriptionQuotaBlock) => boolean;\n /**\n * The provider's usage-endpoint readings are complete: each reports every\n * window that limits the connection, so a stored exhausted window that a\n * newer reading omits has ended and is dropped. Absent or false: a\n * reading may omit a window that still runs, and the shared quota merge\n * keeps an omitted exhausted window until its own reset (fail closed,\n * design 2.2). Response headers and refusals are always partial.\n */\n usageReadingsComplete?: boolean;\n /**\n * Optional live model catalog of one connection (provider model ids),\n * read with its bearer through `input.fetch`. Throws when the provider\n * refuses the credential.\n */\n liveModels?(input: SubscriptionCoreConnectionRead<Credential>): Promise<readonly ModelId[]>;\n}\n\n/** One connection's bearer as a connection read sees it. */\nexport type SubscriptionCoreConnectionBearer<Credential> = {\n /** The decoded, current credential. */\n credential: Credential;\n providerAccountId: string | null;\n /** Provider-owned connection facts; only the provider's adapter interprets them. */\n providerState: Record<string, unknown>;\n};\n\n/** What a connection read (usage, live catalog) receives from the core. */\nexport type SubscriptionCoreConnectionRead<Credential> = {\n /** The current bearer (refreshed under the core lock when stale). */\n getToken(): Promise<SubscriptionCoreConnectionBearer<Credential>>;\n /** A forced refresh under the core lock, after the provider refused the bearer. */\n refresh(): Promise<SubscriptionCoreConnectionBearer<Credential>>;\n /** A provider HTTP call under the core's request custody. */\n fetch: (input: string | URL, init?: RequestInit) => Promise<Response>;\n};\n\n/** Renewal of one credential; the core persists the result before anything else. */\nexport type CredentialRefresher<Credential> = {\n /** Refresh this long before the credential expires. */\n readonly windowMs: number;\n /** Refresh after this long when the expiry is unknown. */\n readonly fallbackMs: number;\n /** Call the provider and return the rotated credential. */\n rotate(credential: Credential): Promise<RotatedCredential<Credential>>;\n /**\n * The needs-relogin message when `error` is a permanent refusal of the\n * credential (revoked or expired refresh token), else null.\n */\n reloginMessage(error: unknown): string | null;\n};\n\nexport type RotatedCredential<Credential> = {\n credential: Credential;\n expiresAt: Date | null;\n /** The plan the rotated credential reports, or null when it reports none. */\n planType: string | null;\n};\n\n/**\n * The refresher the core may call for one decoded credential: the adapter's\n * refresher when the credential's format renews, else null (the credential\n * is used until it expires or is refused, then needs a new sign-in).\n */\nexport function subscriptionCoreCredentialRefresher<Credential>(\n adapter: Pick<SubscriptionCoreAdapter<Credential>, \"capabilitiesFor\" | \"credential\" | \"refresh\">,\n credential: Credential,\n): CredentialRefresher<Credential> | null {\n if (adapter.refresh === null) return null;\n return adapter.capabilitiesFor(adapter.credential.format(credential)).autoRenews\n ? adapter.refresh\n : null;\n}\n\n/**\n * The workspace model policy provider identities one of the adapter's models\n * must all be allowed under (never empty).\n */\nexport function subscriptionCoreModelPolicyProviderIds(\n adapter: Pick<SubscriptionCoreAdapter, \"modelPolicyProviderId\" | \"modelPolicyProviderIdsFor\">,\n modelId: ModelId,\n): readonly string[] {\n const ids = adapter.modelPolicyProviderIdsFor?.(modelId);\n return ids && ids.length > 0 ? ids : [adapter.modelPolicyProviderId];\n}\n\n/**\n * The model cooldown a refusal implies, or null when it limits the whole\n * connection (no `modelId`) or carries no time. `rate_limited` without a\n * known delay is not a cooldown: the connection's own rate-limit handling\n * applies.\n */\nexport function modelCooldownFromOutcome(\n outcome: ProviderErrorOutcome,\n now: number,\n): { modelId: ModelId; until: number } | null {\n if (outcome.kind === \"rate_limited\") {\n if (outcome.modelId === undefined || outcome.retryAfterMs === null) return null;\n return { modelId: outcome.modelId, until: now + Math.max(0, outcome.retryAfterMs) };\n }\n if (outcome.kind === \"exhausted\") {\n if (outcome.modelId === undefined || outcome.resetAt === null) return null;\n return outcome.resetAt > now ? { modelId: outcome.modelId, until: outcome.resetAt } : null;\n }\n return null;\n}\n","import {\n modelCooldownFromOutcome,\n type ProviderErrorOutcome,\n type SubscriptionCoreAdapter,\n} from \"./adapter\";\nimport type { ModelId, SubscriptionQuota } from \"./types\";\n\n/**\n * How a reserved model request settles (the request ledger's outcome):\n * `refused` when the provider definitively declined it, `response_received`\n * when a response arrived (an error response included), `unknown` when the\n * outcome cannot be known (the connection dropped mid-call), which blocks\n * every later request of the turn instead of a replay.\n */\nexport type SubscriptionCoreRequestOutcome = \"response_received\" | \"refused\" | \"unknown\";\n\n/** The connection state a definitive refusal leaves, so placement avoids it. */\nexport type SubscriptionCoreRefusalHealth =\n /** A quota observation: exhausted (usage limit, spend budget or rate limit) until `exhaustedUntil`. */\n | { kind: \"quota\"; observation: SubscriptionQuota }\n /** One model rests on the connection until `until`. */\n | { kind: \"model_cooldown\"; modelId: ModelId; until: number }\n /** `sign_in`: needs a new sign-in or key; `forbidden`: a time-bound quarantine. */\n | { kind: \"quarantine\"; reason: \"sign_in\" | \"forbidden\" };\n\n/**\n * The provider-neutral settlement of one classified upstream outcome on the\n * leased connection (design 5.3, \"API-key connectors on the shared core\").\n *\n * - `retry_after_refresh`: a refused renewable credential is refreshed once\n * under the core's lock and the same connection is retried; only a refusal\n * that survives the refresh is settled.\n * - `settle`: the (turn, connection) failure receipt is written first (it\n * counts toward the per-turn failover bound and carries the refresh\n * generation the refused credential held), then `health`; placement then\n * fails over.\n * - `no_failover`: an overloaded, failed or fatal reply is not a refusal of\n * the connection: no receipt, no health change, no rotation (it may have\n * consumed work; the turn's own error handling decides).\n */\nexport type SubscriptionCoreRefusalPlan =\n | { kind: \"retry_after_refresh\"; requestOutcome: SubscriptionCoreRequestOutcome }\n | {\n kind: \"settle\";\n requestOutcome: SubscriptionCoreRequestOutcome;\n receipt: { kind: ProviderErrorOutcome[\"kind\"]; evidence: { refreshGeneration: number } };\n health: SubscriptionCoreRefusalHealth;\n }\n | { kind: \"no_failover\"; requestOutcome: SubscriptionCoreRequestOutcome };\n\n/**\n * Plan the settlement of one classified outcome. A null outcome (the\n * connector could not classify what happened, such as a dropped connection)\n * is an unknown request outcome and never a refusal.\n *\n * - a `rate_limited` or `exhausted` refusal naming a model (`modelId`): that\n * model cools down on the connection until the provider's time, or the\n * matching fallback below; the connection keeps serving other models;\n * - `rate_limited`: exhausted (`rate_limit`) until the retry time, or the\n * adapter's `rateLimitFallbackMs` when the provider gave none;\n * - `exhausted`: exhausted (`quota`) until the provider's reset, which is\n * authoritative. Without a known future reset, an exhausted window that\n * resets after the adapter's `exhaustedFallbackMs` instead: placement\n * retries the connection then even when no usage reading exists, and a\n * later usage reading of an adapter that declares its readings complete\n * (which replaces windows but never shortens an exhaustion deadline) can\n * show recovered capacity sooner; a partial reading keeps the window;\n * - `entitlement_missing`: the model cools down for `entitlementCooldownMs`;\n * - `unauthorized` and `forbidden`: refresh and retry once when the credential\n * renews and has not been refreshed for this refusal; otherwise\n * `unauthorized` needs a new sign-in (a static key: a new key) and\n * `forbidden` is quarantined for `forbiddenQuarantineMs`.\n */\nexport function planSubscriptionCoreRefusal(\n adapter: Pick<SubscriptionCoreAdapter, \"health\">,\n outcome: ProviderErrorOutcome | null,\n input: {\n now: number;\n /** The refresh generation of the credential the refused request carried. */\n refreshGeneration: number;\n /** The credential renews (the core may refresh it). */\n renewable: boolean;\n /** This refusal already follows a refresh of the same connection. */\n refreshed: boolean;\n },\n): SubscriptionCoreRefusalPlan {\n if (outcome === null) return { kind: \"no_failover\", requestOutcome: \"unknown\" };\n const { now, refreshGeneration } = input;\n const quota = (\n fields: Pick<SubscriptionQuota, \"windows\" | \"exhaustedUntil\" | \"exhaustedKind\">,\n ): SubscriptionCoreRefusalHealth => ({\n kind: \"quota\" as const,\n observation: {\n modelCooldowns: {},\n revision: 0,\n observedAt: now,\n observedRefreshGeneration: refreshGeneration,\n source: \"refusal\" as const,\n ...fields,\n },\n });\n const settle = (health: SubscriptionCoreRefusalHealth): SubscriptionCoreRefusalPlan => ({\n kind: \"settle\",\n requestOutcome: \"refused\",\n receipt: { kind: outcome.kind, evidence: { refreshGeneration } },\n health,\n });\n // A limit on one model (a gateway's per-model rate limit or budget) cools\n // that model only, until the provider's time or the adapter's fallback;\n // the connection keeps serving its other models.\n if (\n (outcome.kind === \"rate_limited\" || outcome.kind === \"exhausted\") &&\n outcome.modelId !== undefined\n ) {\n const known = modelCooldownFromOutcome(outcome, now);\n return settle({\n kind: \"model_cooldown\",\n modelId: outcome.modelId,\n until:\n known !== null && known.until > now\n ? known.until\n : now +\n (outcome.kind === \"rate_limited\"\n ? adapter.health.rateLimitFallbackMs\n : adapter.health.exhaustedFallbackMs),\n });\n }\n switch (outcome.kind) {\n case \"rate_limited\":\n return settle(\n quota({\n windows: [],\n exhaustedUntil:\n now +\n (outcome.retryAfterMs !== null && outcome.retryAfterMs > 0\n ? outcome.retryAfterMs\n : adapter.health.rateLimitFallbackMs),\n exhaustedKind: \"rate_limit\",\n }),\n );\n case \"exhausted\":\n return settle(\n outcome.resetAt !== null && outcome.resetAt > now\n ? quota({ windows: [], exhaustedUntil: outcome.resetAt, exhaustedKind: \"quota\" })\n : quota({\n windows: [\n {\n id: \"exhausted_without_reset\",\n usedPercent: 100,\n resetsAt: now + adapter.health.exhaustedFallbackMs,\n status: \"exhausted\",\n },\n ],\n exhaustedUntil: null,\n exhaustedKind: null,\n }),\n );\n case \"entitlement_missing\":\n return settle({\n kind: \"model_cooldown\",\n modelId: outcome.modelId,\n until: now + adapter.health.entitlementCooldownMs,\n });\n case \"unauthorized\":\n case \"forbidden\":\n if (input.renewable && !input.refreshed)\n return { kind: \"retry_after_refresh\", requestOutcome: \"refused\" };\n return settle({\n kind: \"quarantine\",\n reason: outcome.kind === \"unauthorized\" ? \"sign_in\" : \"forbidden\",\n });\n case \"overloaded\":\n return { kind: \"no_failover\", requestOutcome: \"refused\" };\n case \"transient\":\n case \"fatal\":\n return { kind: \"no_failover\", requestOutcome: \"response_received\" };\n }\n}\n","import type { WaitReason } from \"./types\";\n\n/** Current wait guidance shared by initial parking and later reclassification.\n * These are new audit events only; never rewrite an earlier model request. */\nexport function subscriptionCapacityWaitReasonText(reason: string, name: string): string | null {\n const messages: Record<WaitReason, string> = {\n pinned_account_unavailable: `The ${name} account chosen for this session is unavailable right now. This turn continues automatically once it is available again.`,\n pinned_account_disabled: `The ${name} account pinned to this chat is turned off. Turn it back on, choose another account, or switch the chat to automatic. Waiting alone will not fix this.`,\n pinned_account_ineligible: `The ${name} account chosen for this session can no longer serve this work. This turn continues once another account is chosen or the session is switched back to automatic.`,\n no_eligible_capacity: `No ${name} subscription has capacity for this turn right now. It continues automatically when an account is available.`,\n model_not_allowed: `This model is not allowed for the ${name} subscriptions available here. This turn continues if that changes.`,\n compaction_provider_locked:\n \"This session is tied to a provider that has no capacity right now. It continues automatically when an account is available.\",\n accepted_authority_unavailable:\n \"This work was accepted before the account move and its account access could not be carried. Send it again to continue.\",\n };\n return Object.hasOwn(messages, reason) ? messages[reason as WaitReason] : null;\n}\n\nexport function subscriptionCapacityWaitNeedsAction(reason: string): boolean {\n return [\n \"pinned_account_disabled\",\n \"pinned_account_ineligible\",\n \"model_not_allowed\",\n \"accepted_authority_unavailable\",\n ].includes(reason);\n}\n","/** Pure limit projections. All timestamps are milliseconds; no currency leaves this module. */\nexport type SubscriptionUsagePoolWindowId = \"five_hour\" | \"weekly\";\n\nexport type SubscriptionUsagePoolInput = {\n provider: string;\n now: number;\n accounts: Array<{\n id: string;\n label: string;\n plan: string | null;\n /**\n * Adapter-owned relative tier estimate, not call units. Unknown tiers default to 1.\n * Keep provider/tier tables outside the provider-neutral policy package.\n * The same tier scale must be used for every account in this pool.\n */\n planWeight?: number;\n /**\n * The provider refuses this window until the given time (a usage-limit\n * response), whatever its last percentage said: none of it is left.\n * Null means the provider refused it without reporting when it resets.\n */\n blocked?: Partial<Record<SubscriptionUsagePoolWindowId, number | null>>;\n windows: Array<{\n id: SubscriptionUsagePoolWindowId;\n usedPercent: number | null;\n resetsAt: number | null;\n observedAt: number | null;\n }>;\n }>;\n readings: Array<{\n connectionId: string;\n window: SubscriptionUsagePoolWindowId;\n usedPercent: number;\n resetsAt: number | null;\n observedAt: number;\n }>;\n /** Attributed, estimated API-equivalent usage; used only for calibration and shape. */\n calls: Array<{ connectionId: string; at: number; units: number }>;\n historyStart: number | null;\n};\n\nexport type SubscriptionUsagePoolAccount = {\n id: string;\n label: string;\n remainingPercent: number | null;\n resetsAt: number | null;\n weight: number;\n weightBasis: \"observed\" | \"plan_estimate\";\n observedAt: number | null;\n};\n\nexport type SubscriptionUsagePoolForecast = {\n kind: \"runs_out\" | \"unused\" | \"insufficient_history\";\n basis: string;\n runsOutAt: number | null;\n nextResetAt: number | null;\n /** Fraction of original full capacity discarded at each account's FIRST upcoming reset. */\n unusedPercent: number | null;\n /** Included capacity used by each account's first upcoming reset, including use so far. */\n utilizationPercent: number | null;\n /** Lowest aggregate remaining percentage, sampled BEFORE reset refills. */\n lowestPercent: number | null;\n historyDays: number;\n};\n\nexport type SubscriptionUsagePool = {\n provider: string;\n observedAt: number | null;\n windows: Array<{\n id: SubscriptionUsagePoolWindowId;\n remainingPercent: number | null;\n accounts: SubscriptionUsagePoolAccount[];\n forecast: SubscriptionUsagePoolForecast;\n }>;\n};\n\nconst HOUR = 3_600_000;\nconst DAY = 24 * HOUR;\nconst WEEK = 7 * DAY;\nconst EPSILON = 1e-10;\nconst WINDOW_DURATION = { five_hour: 5 * HOUR, weekly: WEEK };\n// A reading is not refreshed by the passage of a reset. These TTLs are presentation policy.\nconst FRESHNESS = { five_hour: HOUR, weekly: DAY };\nconst WINDOW_IDS: SubscriptionUsagePoolWindowId[] = [\"five_hour\", \"weekly\"];\n\ntype Reading = { usedPercent: number; resetsAt: number | null; observedAt: number };\ntype Call = SubscriptionUsagePoolInput[\"calls\"][number];\ntype Segment = { start: number; end: number; reset: number; fraction: number; calls: Call[] };\ntype Bucket = { amount: number; hours: number };\ntype Pace = {\n profile: number[];\n buckets: Map<number, Bucket>;\n fallback: boolean;\n callShape: boolean;\n};\n\nconst finite = (value: number | null | undefined): value is number =>\n typeof value === \"number\" && Number.isFinite(value);\nconst percent = (value: number | null): value is number =>\n finite(value) && value >= 0 && value <= 100;\nconst clamp = (value: number, low: number, high: number) => Math.min(high, Math.max(low, value));\nconst sum = (values: number[]) => values.reduce((total, value) => total + value, 0);\nfunction median(values: number[]): number {\n const sorted = [...values].sort((a, b) => a - b);\n const middle = Math.floor(sorted.length / 2);\n return sorted.length % 2 ? sorted[middle]! : (sorted[middle - 1]! + sorted[middle]!) / 2;\n}\nfunction hourOfWeek(at: number): number {\n const date = new Date(at);\n return ((date.getUTCDay() + 6) % 7) * 24 + date.getUTCHours();\n}\n\n/**\n * Both windows and every supplied account stay visible, including missing windows.\n * Snapshots are authoritative for CURRENT remaining; historical readings never fill a\n * missing snapshot. A reading stays current until its window resets: usage only grows\n * within a window, so an older reading is still what the account list shows. The total\n * and forecast cover the accounts with a current reading; accounts without one are\n * listed with an unknown remaining so the caller can say they are left out.\n * An account at its other window's limit (a weekly account at its five-hour limit, or\n * the reverse) serves nothing until that limit resets: each account keeps its own\n * reading, but the window's total counts none of it as usable now, and the forecast\n * makes it usable again only when that limit ends. A pool with nothing usable now\n * reports when capacity next returns instead of a runout.\n * Call units never set demand magnitude: stable percentage movements include usage\n * outside this application. Calibration is only a relative proxy for usable capacity.\n */\nexport function buildSubscriptionUsagePool(\n input: SubscriptionUsagePoolInput,\n): SubscriptionUsagePool {\n if (!finite(input.now) || Math.abs(input.now) > 8.64e15 - 2 * WEEK) {\n throw new RangeError(\"now must be a valid millisecond timestamp\");\n }\n const ids = new Set(input.accounts.map((account) => account.id));\n if (ids.size !== input.accounts.length) throw new RangeError(\"account ids must be unique\");\n const since = Math.max(\n input.now - 2 * WEEK,\n finite(input.historyStart) ? Math.min(input.now, input.historyStart) : -Infinity,\n );\n const callsByAccount = new Map<string, Call[]>();\n for (const call of input.calls) {\n if (\n !ids.has(call.connectionId) ||\n !finite(call.at) ||\n call.at < since ||\n call.at > input.now ||\n !finite(call.units) ||\n call.units <= 0\n )\n continue;\n const calls = callsByAccount.get(call.connectionId) ?? [];\n calls.push(call);\n callsByAccount.set(call.connectionId, calls);\n }\n for (const calls of callsByAccount.values()) calls.sort((a, b) => a.at - b.at);\n const readingsByAccount = new Map<string, Map<SubscriptionUsagePoolWindowId, Reading[]>>();\n for (const reading of input.readings) {\n if (\n !ids.has(reading.connectionId) ||\n !percent(reading.usedPercent) ||\n !finite(reading.observedAt) ||\n reading.observedAt < since ||\n reading.observedAt > input.now\n )\n continue;\n const windows = readingsByAccount.get(reading.connectionId) ?? new Map();\n const readings = windows.get(reading.window) ?? [];\n readings.push(reading);\n windows.set(reading.window, readings);\n readingsByAccount.set(reading.connectionId, windows);\n }\n\n const readWindows = WINDOW_IDS.map((id) => {\n const duration = WINDOW_DURATION[id];\n const data = input.accounts.map((account) => {\n // Newest supplied snapshot wins; an explicit null must not resurrect older quota.\n const snapshot = [...account.windows.filter((window) => window.id === id)].sort(\n (a, b) => (b.observedAt ?? -Infinity) - (a.observedAt ?? -Infinity),\n )[0];\n const observedAt =\n finite(snapshot?.observedAt) && snapshot.observedAt <= input.now\n ? snapshot.observedAt\n : null;\n const observedReset = finite(snapshot?.resetsAt) ? snapshot.resetsAt : null;\n const blockedUntil = account.blocked?.[id];\n const blocked = blockedUntil === null || (finite(blockedUntil) && blockedUntil > input.now);\n const resetsAt = blocked ? (blockedUntil ?? null) : observedReset;\n const current =\n observedAt !== null &&\n (observedReset === null\n ? input.now - observedAt <= FRESHNESS[id]\n : observedReset > input.now && observedReset <= observedAt + duration);\n const remainingPercent = blocked\n ? 0\n : current && snapshot && percent(snapshot.usedPercent)\n ? 100 - snapshot.usedPercent\n : null;\n const readings = [...(readingsByAccount.get(account.id)?.get(id) ?? [])];\n if (snapshot && percent(snapshot.usedPercent) && observedAt !== null && observedAt >= since) {\n readings.push({ usedPercent: snapshot.usedPercent, observedAt, resetsAt: observedReset });\n }\n const segments = stableSegments(readings, callsByAccount.get(account.id) ?? [], duration);\n const planWeight =\n finite(account.planWeight) && account.planWeight > 0 ? account.planWeight : 1;\n return {\n account,\n observedAt,\n resetsAt,\n remainingPercent,\n segments,\n planWeight,\n calibrated: calibratedCapacity(segments),\n readings,\n };\n });\n\n // Convert measured capacities to the SAME dimensionless scale as plan estimates.\n // Known-tier anchors take priority; otherwise the median measured account is one tier.\n const knownAnchors = data.filter(\n (row) =>\n row.calibrated !== null && finite(row.account.planWeight) && row.account.planWeight > 0,\n );\n const anchors = knownAnchors.length\n ? knownAnchors\n : data.filter((row) => row.calibrated !== null);\n const anchor = anchors.length\n ? median(anchors.map((row) => row.calibrated! / row.planWeight))\n : 1;\n const accounts: SubscriptionUsagePoolAccount[] = data.map((row) => ({\n id: row.account.id,\n label: row.account.label,\n remainingPercent: row.remainingPercent,\n resetsAt: row.resetsAt,\n weight: clamp(\n row.calibrated === null ? row.planWeight : row.calibrated / anchor,\n 0.01,\n 10_000,\n ),\n weightBasis: row.calibrated === null ? \"plan_estimate\" : \"observed\",\n observedAt: row.observedAt,\n }));\n // Until when each account is at this window's own limit (unknown end: never).\n const limitedUntil = accounts.map((account) =>\n account.remainingPercent === 0 ? (account.resetsAt ?? Infinity) : null,\n );\n return { id, duration, data, accounts, limitedUntil };\n });\n\n const windows = readWindows.map(({ id, duration, data, accounts }) => {\n // The latest end of any OTHER window's limit holds the account in this one.\n const heldUntil = accounts.map((_, index) =>\n readWindows.reduce<number | null>((until, other) => {\n const limit = other.id === id ? null : other.limitedUntil[index]!;\n return limit === null ? until : Math.max(until ?? limit, limit);\n }, null),\n );\n // Accounts without a current reading are left out of the total and forecast\n // (and stay listed as unknown), rather than hiding what the others report.\n const knownIndexes = accounts.flatMap((account, index) =>\n account.remainingPercent === null && heldUntil[index] === null ? [] : [index],\n );\n const known = knownIndexes.map((index) => accounts[index]!);\n const held = knownIndexes.map((index) => heldUntil[index]!);\n const totalWeight = sum(known.map((account) => account.weight));\n const complete = known.length > 0;\n // Capacity usable now: an account held by its other limit serves nothing yet.\n const remainingPercent = complete\n ? clamp(\n sum(\n known.map((account, index) =>\n held[index] === null ? account.weight * account.remainingPercent! : 0,\n ),\n ) / totalWeight,\n 0,\n 100,\n )\n : null;\n const earliest = data.reduce(\n (at, row) =>\n row.readings.reduce((minimum, reading) => Math.min(minimum, reading.observedAt), at),\n input.now,\n );\n const historyDays = (input.now - Math.max(since, earliest)) / DAY;\n const resets = known\n .map((account) => account.resetsAt)\n .filter((at): at is number => at !== null && at > input.now && at <= input.now + duration);\n const nextResetAt = resets.length ? Math.min(...resets) : null;\n const insufficient = (basis: string): SubscriptionUsagePoolForecast => ({\n kind: \"insufficient_history\",\n basis,\n runsOutAt: null,\n nextResetAt,\n unusedPercent: null,\n utilizationPercent: null,\n lowestPercent: null,\n historyDays,\n });\n let forecast: SubscriptionUsagePoolForecast;\n if (!complete) {\n forecast = insufficient(\"No account has a current observation for this window.\");\n } else if (remainingPercent === 0) {\n // Nothing is usable now: report when the first account can serve again, which\n // is when the later of its own limit and any other window's limit ends.\n const returns = known\n .map((account, index) =>\n Math.max(\n account.remainingPercent === null\n ? Infinity\n : account.remainingPercent === 0\n ? (account.resetsAt ?? Infinity)\n : -Infinity,\n held[index] ?? -Infinity,\n ),\n )\n .filter((at) => Number.isFinite(at) && at > input.now);\n forecast = {\n kind: \"runs_out\",\n basis: \"Observed pool exhaustion.\",\n runsOutAt: input.now,\n nextResetAt: returns.length ? Math.min(...returns) : null,\n unusedPercent: null,\n // A short-window hold does not mean the weekly allowance was consumed.\n utilizationPercent: known.every((account) => account.remainingPercent === 0) ? 100 : null,\n lowestPercent: 0,\n historyDays,\n };\n } else if (\n known.some((account) => account.remainingPercent === null) ||\n resets.length !== known.length\n ) {\n forecast = insufficient(\n \"A future reset is unknown; remaining limits are observed, not forecast.\",\n );\n } else {\n const paces = knownIndexes.map((index) =>\n buildPace(data[index]!.segments, accounts[index]!, duration, input.now),\n );\n if (paces.some((pace) => pace === null)) {\n forecast = insufficient(\n \"Insufficient observed activity coverage; absence of calls is not zero demand.\",\n );\n } else {\n const availablePaces = paces as Pace[];\n const profile = Array.from({ length: 168 }, (_, hour) =>\n sum(availablePaces.map((pace) => pace.profile[hour]!)),\n );\n const trend = weeklyTrend(availablePaces, input.now);\n const recent = id === \"five_hour\" ? lastHourAdjustment(availablePaces, input.now) : 1;\n const scale = clamp(trend * recent, 0.5, 2);\n const basis = [\n availablePaces.some((pace) => pace.fallback)\n ? \"Last-reset average fallback\"\n : \"Observed percentage movements\",\n historyDays >= 7\n ? \"last 7 days\"\n : `${Math.max(1, Math.ceil(historyDays))} days of history`,\n `same-hours weekly trend ${trend.toFixed(2)}x`,\n ...(id === \"five_hour\" ? [`last-hour adjustment ${recent.toFixed(2)}x`] : []),\n ].join(\"; \");\n forecast = simulate(\n known,\n held,\n profile.map((rate) => rate * scale),\n input.now,\n duration,\n historyDays,\n basis,\n );\n }\n }\n return { id, remainingPercent, accounts, forecast };\n });\n const observations = windows\n .flatMap((window) => window.accounts.map((account) => account.observedAt))\n .filter((at): at is number => at !== null);\n return {\n provider: input.provider,\n observedAt: observations.length ? Math.max(...observations) : null,\n windows,\n };\n}\n\nfunction stableSegments(readings: Reading[], calls: Call[], duration: number): Segment[] {\n // Identical duplicates are harmless. Conflicting same-time readings break continuity.\n const grouped = new Map<number, Reading[]>();\n for (const reading of readings) {\n const group = grouped.get(reading.observedAt) ?? [];\n group.push(reading);\n grouped.set(reading.observedAt, group);\n }\n const ordered = [...grouped]\n .sort(([a], [b]) => a - b)\n .map(([, group]) =>\n group.every(\n (row) => row.usedPercent === group[0]!.usedPercent && row.resetsAt === group[0]!.resetsAt,\n )\n ? group[0]!\n : null,\n );\n const result: Segment[] = [];\n let callIndex = 0;\n let before: Reading | null = null;\n for (const after of ordered) {\n if (\n !before ||\n !after ||\n !finite(before.resetsAt) ||\n before.resetsAt !== after.resetsAt ||\n before.resetsAt < after.observedAt ||\n before.resetsAt > before.observedAt + duration ||\n after.observedAt - before.observedAt > Math.min(duration, DAY) ||\n after.usedPercent < before.usedPercent ||\n before.usedPercent === 100\n ) {\n before = after;\n continue;\n }\n // Polling more often than five minutes accumulates into a sample, rather than\n // dropping every adjacent pair. A correction/reset still breaks the chain above.\n if (after.observedAt - before.observedAt < HOUR / 12) continue;\n while (callIndex < calls.length && calls[callIndex]!.at <= before.observedAt) callIndex++;\n const start = callIndex;\n while (callIndex < calls.length && calls[callIndex]!.at <= after.observedAt) callIndex++;\n result.push({\n start: before.observedAt,\n end: after.observedAt,\n reset: before.resetsAt,\n fraction: (after.usedPercent - before.usedPercent) / 100,\n calls: calls.slice(start, callIndex),\n });\n before = after;\n }\n return result;\n}\n\nfunction calibratedCapacity(segments: Segment[]): number | null {\n // Accumulate quantized/small movements without ever spanning a gap or reset.\n const positive: Segment[] = [];\n let pending: Segment | null = null;\n for (const segment of segments) {\n if (pending && pending.end === segment.start && pending.reset === segment.reset) {\n pending.end = segment.end;\n pending.fraction += segment.fraction;\n for (const call of segment.calls) pending.calls.push(call);\n } else {\n pending = { ...segment, calls: [...segment.calls] };\n }\n if (pending.fraction >= 0.01 - EPSILON) {\n positive.push(pending);\n pending = null;\n }\n }\n const paired = positive\n .map((segment) => ({\n segment,\n capacity: sum(segment.calls.map((call) => call.units)) / segment.fraction,\n }))\n .filter((sample) => finite(sample.capacity) && sample.capacity > 0);\n if (paired.length < 3) return null;\n const center = median(paired.map((sample) => sample.capacity));\n const stable = paired.filter(\n (sample) => sample.capacity >= center / 2 && sample.capacity <= center * 2,\n );\n // Three samples, six calls, ten percentage points, an hour, 60% movement coverage,\n // and median absolute dispersion <= 35%. Reset crossings/corrections never calibrate.\n if (\n stable.length < 3 ||\n stable.length < paired.length * 0.6 ||\n sum(stable.map(({ segment }) => segment.calls.length)) < 6 ||\n sum(stable.map(({ segment }) => segment.fraction)) < 0.1 ||\n sum(stable.map(({ segment }) => segment.end - segment.start)) < HOUR ||\n sum(stable.map(({ segment }) => segment.fraction)) <\n sum(segments.map((segment) => segment.fraction)) * 0.6 ||\n median(stable.map((sample) => Math.abs(sample.capacity - center))) > center * 0.35\n )\n return null;\n return median(stable.map((sample) => sample.capacity));\n}\n\nfunction addBucket(buckets: Map<number, Bucket>, at: number, amount: number, hours: number): void {\n const previous = buckets.get(at) ?? { amount: 0, hours: 0 };\n buckets.set(at, { amount: previous.amount + amount, hours: previous.hours + hours });\n}\n\nfunction buildPace(\n segments: Segment[],\n account: SubscriptionUsagePoolAccount,\n duration: number,\n now: number,\n): Pace | null {\n const buckets = new Map<number, Bucket>();\n let callShape = false;\n for (const segment of segments) {\n const hours = (segment.end - segment.start) / HOUR;\n const shape = new Map<number, number>();\n const reliableCalls = segment.calls.length >= 3 && segment.fraction > 0;\n if (reliableCalls) {\n const cap = median(segment.calls.map((call) => call.units)) * 3;\n for (const call of segment.calls) {\n // An endpoint call belongs to the interval ending there, not a future hour.\n const hour = Math.floor(Math.min(call.at, segment.end - 1) / HOUR) * HOUR;\n shape.set(hour, (shape.get(hour) ?? 0) + Math.min(call.units, cap));\n }\n }\n const shapeTotal = sum([...shape.values()]);\n callShape ||= shapeTotal > 0;\n for (let at = Math.floor(segment.start / HOUR) * HOUR; at < segment.end; at += HOUR) {\n const overlap = (Math.min(at + HOUR, segment.end) - Math.max(at, segment.start)) / HOUR;\n // Preserve total observed movement, with a uniform floor for unattributed activity.\n const fraction =\n shapeTotal > 0\n ? (0.25 * overlap) / hours + (0.75 * (shape.get(at) ?? 0)) / shapeTotal\n : overlap / hours;\n addBucket(buckets, at, segment.fraction * account.weight * fraction, overlap);\n }\n }\n const recentSegments = segments.filter((segment) => segment.end > now - WEEK);\n const recent = [...buckets].filter(([at]) => at + HOUR > now - WEEK);\n const coverage = sum(recent.map(([, bucket]) => bucket.hours));\n // Explicit flat percentage observations ARE evidence of zero use; an empty call log is not.\n if (\n recentSegments.length < 2 ||\n coverage < 1 ||\n !recentSegments.some(\n (segment) => segment.end >= now - FRESHNESS[duration === WEEK ? \"weekly\" : \"five_hour\"],\n )\n ) {\n const elapsed =\n account.resetsAt !== null && account.observedAt !== null\n ? (account.observedAt - (account.resetsAt - duration)) / HOUR\n : 0;\n if (\n account.remainingPercent === null ||\n account.remainingPercent >= 100 ||\n elapsed < 0.25 ||\n elapsed > duration / HOUR\n )\n return null;\n const rate = (account.weight * (1 - account.remainingPercent / 100)) / elapsed;\n return { buckets, profile: Array(168).fill(rate), fallback: true, callShape: false };\n }\n const positiveRates = [...buckets.values()]\n .filter((bucket) => bucket.amount > 0)\n .map((bucket) => bucket.amount / bucket.hours);\n const cap = positiveRates.length >= 3 ? median(positiveRates) * 3 : Infinity;\n // Winsorization damps isolated activity spikes without turning genuine idle observations into gaps.\n for (const bucket of buckets.values())\n bucket.amount = Math.min(bucket.amount, cap * bucket.hours);\n const average = sum(recent.map(([, bucket]) => bucket.amount)) / coverage;\n const profile = Array<number>(168).fill(average);\n const slots = new Map<number, Bucket>();\n for (const [at, bucket] of recent) addBucket(slots, hourOfWeek(at), bucket.amount, bucket.hours);\n for (const [hour, bucket] of slots) {\n const confidence = Math.min(1, bucket.hours);\n profile[hour] = (confidence * bucket.amount) / bucket.hours + (1 - confidence) * average;\n }\n return { buckets, profile, fallback: false, callShape };\n}\n\nfunction coveredRate(paces: Pace[], at: number): number | null {\n const buckets = paces.map((pace) => pace.buckets.get(at));\n if (buckets.some((bucket) => !bucket || bucket.hours < 0.75)) return null;\n return sum(buckets.map((bucket) => bucket!.amount / bucket!.hours));\n}\n\nfunction weeklyTrend(paces: Pace[], now: number): number {\n const completed = Math.floor(now / HOUR) * HOUR;\n const weekStart = completed - hourOfWeek(completed) * HOUR;\n let current = 0;\n let previous = 0;\n let pairs = 0;\n for (let at = weekStart; at < completed; at += HOUR) {\n const here = coveredRate(paces, at);\n const there = coveredRate(paces, at - WEEK);\n if (here === null || there === null) continue;\n current += here;\n previous += there;\n pairs++;\n }\n return pairs >= 3 && previous > EPSILON ? clamp(Math.sqrt(current / previous), 0.5, 2) : 1;\n}\n\nfunction lastHourAdjustment(paces: Pace[], now: number): number {\n // Use the last FULL hour: partial current-hour observations must not inflate the pace.\n const at = Math.floor(now / HOUR) * HOUR - HOUR;\n const actual = coveredRate(paces, at);\n const average = sum(paces.map((pace) => sum(pace.profile) / 168));\n return actual !== null && average > EPSILON ? clamp(Math.sqrt(actual / average), 0.5, 2) : 1;\n}\n\nfunction simulate(\n accounts: SubscriptionUsagePoolAccount[],\n /** When an account held by its other window's limit can serve again; null: now. */\n heldUntil: Array<number | null>,\n profile: number[],\n now: number,\n duration: number,\n historyDays: number,\n basis: string,\n): SubscriptionUsagePoolForecast {\n const total = sum(accounts.map((account) => account.weight));\n const remaining = accounts.map((account) => (account.weight * account.remainingPercent!) / 100);\n const resets = accounts.map((account) => account.resetsAt!);\n const releases = heldUntil.filter(\n (at): at is number => at !== null && Number.isFinite(at) && at > now,\n );\n // A held account with something left returns capacity when its other limit ends.\n const returns = heldUntil.filter(\n (at, index): at is number =>\n at !== null && Number.isFinite(at) && at > now && remaining[index]! > 0,\n );\n const reliefs = resets.map((reset, index) => Math.max(reset, heldUntil[index] ?? reset));\n const open = (at: number) => heldUntil.map((until) => until === null || until <= at);\n const usable = (serving: boolean[]) => sum(remaining.filter((_, index) => serving[index]));\n const horizon = Math.min(now + duration, Math.max(...resets));\n let at = now;\n let lowest = usable(open(now));\n let unused = 0;\n let runsOutAt: number | null = null;\n while (at < horizon) {\n const nextRelief = Math.min(...[...reliefs, ...returns].filter((event) => event > at));\n const end = Math.min(\n horizon,\n (Math.floor(at / HOUR) + 1) * HOUR,\n ...[...resets, ...releases].filter((event) => event > at),\n );\n const rate = profile[hourOfWeek(at)]!;\n const demand = (rate * (end - at)) / HOUR;\n const serving = open(at);\n const available = usable(serving);\n if (rate > 0 && demand >= available && runsOutAt === null) {\n const exhaustedAt = at + (available / rate) * HOUR;\n // Empty exactly as a reset or return refills the pool is not a period without capacity.\n if (exhaustedAt < nextRelief - 0.001) runsOutAt = exhaustedAt;\n }\n consume(remaining, accounts, serving, Math.min(demand, available));\n lowest = Math.min(lowest, usable(serving));\n // Do not carry unserved demand as debt into the next account's renewed window.\n for (let index = 0; index < accounts.length; index++) {\n if (resets[index] === end) {\n unused += remaining[index]!;\n remaining[index] = accounts[index]!.weight;\n }\n }\n at = end;\n }\n const nextResetAt = Math.min(\n ...[...reliefs, ...returns].filter((event) => event >= (runsOutAt ?? now)),\n );\n return {\n kind: runsOutAt === null ? \"unused\" : \"runs_out\",\n basis,\n runsOutAt,\n nextResetAt: finite(nextResetAt) ? nextResetAt : null,\n unusedPercent: runsOutAt === null ? clamp((unused / total) * 100, 0, 100) : null,\n utilizationPercent: clamp(100 - (unused / total) * 100, 0, 100),\n lowestPercent: clamp((lowest / total) * 100, 0, 100),\n historyDays,\n };\n}\n\n/** Water-fill by relative capacity, reallocating only what a depleted account cannot serve. */\nfunction consume(\n remaining: number[],\n accounts: SubscriptionUsagePoolAccount[],\n serving: boolean[],\n demand: number,\n): void {\n let active = remaining\n .map((amount, index) => ({ amount, index }))\n .filter((row) => row.amount > 0 && serving[row.index]);\n while (demand > EPSILON && active.length) {\n const totalWeight = sum(active.map((row) => accounts[row.index]!.weight));\n let consumed = 0;\n for (const { index } of active) {\n const amount = Math.min(remaining[index]!, (demand * accounts[index]!.weight) / totalWeight);\n remaining[index] = Math.max(0, remaining[index]! - amount);\n consumed += amount;\n }\n demand = Math.max(0, demand - consumed);\n active = active.filter((row) => remaining[row.index]! > EPSILON);\n }\n}\n"],"mappings":"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","names":["findModel","ineligibility","windows","cap","at"]}
@@ -16,8 +16,9 @@ export type SubscriptionUsagePoolInput = {
16
16
  /**
17
17
  * The provider refuses this window until the given time (a usage-limit
18
18
  * response), whatever its last percentage said: none of it is left.
19
+ * Null means the provider refused it without reporting when it resets.
19
20
  */
20
- blocked?: Partial<Record<SubscriptionUsagePoolWindowId, number>>;
21
+ blocked?: Partial<Record<SubscriptionUsagePoolWindowId, number | null>>;
21
22
  windows: Array<{
22
23
  id: SubscriptionUsagePoolWindowId;
23
24
  usedPercent: number | null;
@@ -79,6 +80,11 @@ export type SubscriptionUsagePool = {
79
80
  * within a window, so an older reading is still what the account list shows. The total
80
81
  * and forecast cover the accounts with a current reading; accounts without one are
81
82
  * listed with an unknown remaining so the caller can say they are left out.
83
+ * An account at its other window's limit (a weekly account at its five-hour limit, or
84
+ * the reverse) serves nothing until that limit resets: each account keeps its own
85
+ * reading, but the window's total counts none of it as usable now, and the forecast
86
+ * makes it usable again only when that limit ends. A pool with nothing usable now
87
+ * reports when capacity next returns instead of a runout.
82
88
  * Call units never set demand magnitude: stable percentage movements include usage
83
89
  * outside this application. Calibration is only a relative proxy for usable capacity.
84
90
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/subscriptions",
3
- "version": "4.3.3-canary.39786771332001",
3
+ "version": "4.5.0",
4
4
  "description": "Pure, provider-neutral subscription account policy for Opengeni: contract types, effective settings, eligibility, placement, failover order, cache coldness, the shared quota model and adapter interfaces. No database, provider SDK or network dependency.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/src/pool-usage.ts CHANGED
@@ -17,8 +17,9 @@ export type SubscriptionUsagePoolInput = {
17
17
  /**
18
18
  * The provider refuses this window until the given time (a usage-limit
19
19
  * response), whatever its last percentage said: none of it is left.
20
+ * Null means the provider refused it without reporting when it resets.
20
21
  */
21
- blocked?: Partial<Record<SubscriptionUsagePoolWindowId, number>>;
22
+ blocked?: Partial<Record<SubscriptionUsagePoolWindowId, number | null>>;
22
23
  windows: Array<{
23
24
  id: SubscriptionUsagePoolWindowId;
24
25
  usedPercent: number | null;
@@ -116,6 +117,11 @@ function hourOfWeek(at: number): number {
116
117
  * within a window, so an older reading is still what the account list shows. The total
117
118
  * and forecast cover the accounts with a current reading; accounts without one are
118
119
  * listed with an unknown remaining so the caller can say they are left out.
120
+ * An account at its other window's limit (a weekly account at its five-hour limit, or
121
+ * the reverse) serves nothing until that limit resets: each account keeps its own
122
+ * reading, but the window's total counts none of it as usable now, and the forecast
123
+ * makes it usable again only when that limit ends. A pool with nothing usable now
124
+ * reports when capacity next returns instead of a runout.
119
125
  * Call units never set demand magnitude: stable percentage movements include usage
120
126
  * outside this application. Calibration is only a relative proxy for usable capacity.
121
127
  */
@@ -164,7 +170,7 @@ export function buildSubscriptionUsagePool(
164
170
  readingsByAccount.set(reading.connectionId, windows);
165
171
  }
166
172
 
167
- const windows = WINDOW_IDS.map((id) => {
173
+ const readWindows = WINDOW_IDS.map((id) => {
168
174
  const duration = WINDOW_DURATION[id];
169
175
  const data = input.accounts.map((account) => {
170
176
  // Newest supplied snapshot wins; an explicit null must not resurrect older quota.
@@ -177,8 +183,8 @@ export function buildSubscriptionUsagePool(
177
183
  : null;
178
184
  const observedReset = finite(snapshot?.resetsAt) ? snapshot.resetsAt : null;
179
185
  const blockedUntil = account.blocked?.[id];
180
- const blocked = finite(blockedUntil) && blockedUntil > input.now;
181
- const resetsAt = blocked ? blockedUntil : observedReset;
186
+ const blocked = blockedUntil === null || (finite(blockedUntil) && blockedUntil > input.now);
187
+ const resetsAt = blocked ? (blockedUntil ?? null) : observedReset;
182
188
  const current =
183
189
  observedAt !== null &&
184
190
  (observedReset === null
@@ -233,17 +239,38 @@ export function buildSubscriptionUsagePool(
233
239
  weightBasis: row.calibrated === null ? "plan_estimate" : "observed",
234
240
  observedAt: row.observedAt,
235
241
  }));
242
+ // Until when each account is at this window's own limit (unknown end: never).
243
+ const limitedUntil = accounts.map((account) =>
244
+ account.remainingPercent === 0 ? (account.resetsAt ?? Infinity) : null,
245
+ );
246
+ return { id, duration, data, accounts, limitedUntil };
247
+ });
248
+
249
+ const windows = readWindows.map(({ id, duration, data, accounts }) => {
250
+ // The latest end of any OTHER window's limit holds the account in this one.
251
+ const heldUntil = accounts.map((_, index) =>
252
+ readWindows.reduce<number | null>((until, other) => {
253
+ const limit = other.id === id ? null : other.limitedUntil[index]!;
254
+ return limit === null ? until : Math.max(until ?? limit, limit);
255
+ }, null),
256
+ );
236
257
  // Accounts without a current reading are left out of the total and forecast
237
258
  // (and stay listed as unknown), rather than hiding what the others report.
238
259
  const knownIndexes = accounts.flatMap((account, index) =>
239
- account.remainingPercent === null ? [] : [index],
260
+ account.remainingPercent === null && heldUntil[index] === null ? [] : [index],
240
261
  );
241
262
  const known = knownIndexes.map((index) => accounts[index]!);
263
+ const held = knownIndexes.map((index) => heldUntil[index]!);
242
264
  const totalWeight = sum(known.map((account) => account.weight));
243
265
  const complete = known.length > 0;
266
+ // Capacity usable now: an account held by its other limit serves nothing yet.
244
267
  const remainingPercent = complete
245
268
  ? clamp(
246
- sum(known.map((account) => account.weight * account.remainingPercent!)) / totalWeight,
269
+ sum(
270
+ known.map((account, index) =>
271
+ held[index] === null ? account.weight * account.remainingPercent! : 0,
272
+ ),
273
+ ) / totalWeight,
247
274
  0,
248
275
  100,
249
276
  )
@@ -272,17 +299,35 @@ export function buildSubscriptionUsagePool(
272
299
  if (!complete) {
273
300
  forecast = insufficient("No account has a current observation for this window.");
274
301
  } else if (remainingPercent === 0) {
302
+ // Nothing is usable now: report when the first account can serve again, which
303
+ // is when the later of its own limit and any other window's limit ends.
304
+ const returns = known
305
+ .map((account, index) =>
306
+ Math.max(
307
+ account.remainingPercent === null
308
+ ? Infinity
309
+ : account.remainingPercent === 0
310
+ ? (account.resetsAt ?? Infinity)
311
+ : -Infinity,
312
+ held[index] ?? -Infinity,
313
+ ),
314
+ )
315
+ .filter((at) => Number.isFinite(at) && at > input.now);
275
316
  forecast = {
276
317
  kind: "runs_out",
277
318
  basis: "Observed pool exhaustion.",
278
319
  runsOutAt: input.now,
279
- nextResetAt,
320
+ nextResetAt: returns.length ? Math.min(...returns) : null,
280
321
  unusedPercent: null,
281
- utilizationPercent: 100,
322
+ // A short-window hold does not mean the weekly allowance was consumed.
323
+ utilizationPercent: known.every((account) => account.remainingPercent === 0) ? 100 : null,
282
324
  lowestPercent: 0,
283
325
  historyDays,
284
326
  };
285
- } else if (resets.length !== known.length) {
327
+ } else if (
328
+ known.some((account) => account.remainingPercent === null) ||
329
+ resets.length !== known.length
330
+ ) {
286
331
  forecast = insufficient(
287
332
  "A future reset is unknown; remaining limits are observed, not forecast.",
288
333
  );
@@ -314,6 +359,7 @@ export function buildSubscriptionUsagePool(
314
359
  ].join("; ");
315
360
  forecast = simulate(
316
361
  known,
362
+ held,
317
363
  profile.map((rate) => rate * scale),
318
364
  input.now,
319
365
  duration,
@@ -544,6 +590,8 @@ function lastHourAdjustment(paces: Pace[], now: number): number {
544
590
 
545
591
  function simulate(
546
592
  accounts: SubscriptionUsagePoolAccount[],
593
+ /** When an account held by its other window's limit can serve again; null: now. */
594
+ heldUntil: Array<number | null>,
547
595
  profile: number[],
548
596
  now: number,
549
597
  duration: number,
@@ -553,24 +601,40 @@ function simulate(
553
601
  const total = sum(accounts.map((account) => account.weight));
554
602
  const remaining = accounts.map((account) => (account.weight * account.remainingPercent!) / 100);
555
603
  const resets = accounts.map((account) => account.resetsAt!);
604
+ const releases = heldUntil.filter(
605
+ (at): at is number => at !== null && Number.isFinite(at) && at > now,
606
+ );
607
+ // A held account with something left returns capacity when its other limit ends.
608
+ const returns = heldUntil.filter(
609
+ (at, index): at is number =>
610
+ at !== null && Number.isFinite(at) && at > now && remaining[index]! > 0,
611
+ );
612
+ const reliefs = resets.map((reset, index) => Math.max(reset, heldUntil[index] ?? reset));
613
+ const open = (at: number) => heldUntil.map((until) => until === null || until <= at);
614
+ const usable = (serving: boolean[]) => sum(remaining.filter((_, index) => serving[index]));
556
615
  const horizon = Math.min(now + duration, Math.max(...resets));
557
616
  let at = now;
558
- let lowest = sum(remaining);
617
+ let lowest = usable(open(now));
559
618
  let unused = 0;
560
619
  let runsOutAt: number | null = null;
561
620
  while (at < horizon) {
562
- const nextReset = Math.min(...resets.filter((reset) => reset > at));
563
- const end = Math.min(horizon, (Math.floor(at / HOUR) + 1) * HOUR, nextReset);
621
+ const nextRelief = Math.min(...[...reliefs, ...returns].filter((event) => event > at));
622
+ const end = Math.min(
623
+ horizon,
624
+ (Math.floor(at / HOUR) + 1) * HOUR,
625
+ ...[...resets, ...releases].filter((event) => event > at),
626
+ );
564
627
  const rate = profile[hourOfWeek(at)]!;
565
628
  const demand = (rate * (end - at)) / HOUR;
566
- const available = sum(remaining);
629
+ const serving = open(at);
630
+ const available = usable(serving);
567
631
  if (rate > 0 && demand >= available && runsOutAt === null) {
568
632
  const exhaustedAt = at + (available / rate) * HOUR;
569
- // Empty exactly as a reset refills the pool is not a period without capacity.
570
- if (exhaustedAt < nextReset - 0.001) runsOutAt = exhaustedAt;
633
+ // Empty exactly as a reset or return refills the pool is not a period without capacity.
634
+ if (exhaustedAt < nextRelief - 0.001) runsOutAt = exhaustedAt;
571
635
  }
572
- consume(remaining, accounts, Math.min(demand, available));
573
- lowest = Math.min(lowest, sum(remaining));
636
+ consume(remaining, accounts, serving, Math.min(demand, available));
637
+ lowest = Math.min(lowest, usable(serving));
574
638
  // Do not carry unserved demand as debt into the next account's renewed window.
575
639
  for (let index = 0; index < accounts.length; index++) {
576
640
  if (resets[index] === end) {
@@ -580,7 +644,9 @@ function simulate(
580
644
  }
581
645
  at = end;
582
646
  }
583
- const nextResetAt = Math.min(...resets.filter((reset) => reset >= (runsOutAt ?? now)));
647
+ const nextResetAt = Math.min(
648
+ ...[...reliefs, ...returns].filter((event) => event >= (runsOutAt ?? now)),
649
+ );
584
650
  return {
585
651
  kind: runsOutAt === null ? "unused" : "runs_out",
586
652
  basis,
@@ -597,11 +663,12 @@ function simulate(
597
663
  function consume(
598
664
  remaining: number[],
599
665
  accounts: SubscriptionUsagePoolAccount[],
666
+ serving: boolean[],
600
667
  demand: number,
601
668
  ): void {
602
669
  let active = remaining
603
670
  .map((amount, index) => ({ amount, index }))
604
- .filter((row) => row.amount > 0);
671
+ .filter((row) => row.amount > 0 && serving[row.index]);
605
672
  while (demand > EPSILON && active.length) {
606
673
  const totalWeight = sum(active.map((row) => accounts[row.index]!.weight));
607
674
  let consumed = 0;