/** * Quota-error detection for the assistant's raw-fetch transport. * * The assistant talks to the wspace gateway over `fetch` (not Apollo), so it * can't rely on the Apollo error link. `isQuotaExhaustedError` inspects the * thrown error for the gateway's 429 / QUOTA_EXHAUSTED signal across the shapes * `executeSandboxGraphQL` surfaces (Error.message, `.statusCode`, `.result`). * * Only the assistant's two quota walls are modelled: `sandbox` (the * sandboxes-created @usage limit hit during provisioning) and `ai-credits` * (per-token budget). */ /** * Whether this error is an EXHAUSTED quota specifically. * * ★ BOFF-6302 — this used to match any HTTP 429, and a workspace with no assignment row is refused * `429 QUOTA_NOT_PROVISIONED`. That is not exhaustion: the farmer has not used anything up, their * plan was never granted, and "you have reached your limit" sends them to buy an upgrade that would * not unblock them. {@link classifyQuotaError} now owns the distinction and this delegates to it. */ export declare function isQuotaExhaustedError(error: unknown): boolean; /** * The two ways the gateway refuses a metered mutation. They are NOT the same thing and must not be * shown to a farmer with the same words (BOFF-6302). * * - `exhausted` — the workspace has the quota and has used it up. Deleting something, or upgrading, * fixes it. Actionable by the user. * - `not-provisioned` — the workspace has **no assignment row for that quota at all**, so the * gateway fails closed. Nothing the user does fixes it: they have not hit a limit, their plan was * never granted. Measured in prod 2026-08-13, this is why `createHealthyBowlFarm` returns * `429 QUOTA_NOT_PROVISIONED` on several workspaces — the core create path is simply dead there. * * ★ Keyed on `extensions.code`, never on the HTTP status or the message text. Both refusals are * `429`, so status alone cannot tell them apart — which is exactly how "no active subscription * covers this" would otherwise be reported to a farmer as "you have reached your limit". */ export type QuotaRefusal = "exhausted" | "not-provisioned"; /** * Which refusal this is, or null when the error is not a quota wall at all. * * `not-provisioned` wins when both appear: it is the more specific and the more serious of the two, * and telling someone to upgrade when their plan was never granted sends them to buy something that * would not help. */ export declare function classifyQuotaError(error: unknown): QuotaRefusal | null; /** * A quota refusal, plus the facts needed to act on it. * * ★★★ Why this exists (prod, 2026-09-07). The assistant was dead for hours and * the only thing anyone could see was "You've reached your sandbox limit on the * current plan." The gateway had named the exact ceiling — * `platform.sandbox.runtime_hours` — in `extensions.quotaName`, and we threw it * away. The workspace had TWO sandbox quotas; the one everybody stared at * (`platform.sandbox.environments`, 0 of 60 used) was fine, and the one that was * actually blocking went unexamined because nothing on screen named it. * * A ceiling you cannot name is a ceiling you cannot raise. */ export interface QuotaRefusalDetail { refusal: QuotaRefusal; /** WHICH ceiling — e.g. `platform.sandbox.runtime_hours`. */ quotaName: string | null; /** The refusing service's trace id, so support can find the request. */ traceId: string | null; } /** * Which refusal this is AND what it was about, or null when not a quota wall. * * ★ Deliberately falls back to {@link isQuotaExhaustedError} rather than using * {@link classifyQuotaError} alone: the classifier keys on `extensions.code`, * but the assistant's raw-fetch transport can surface a bare quota string with * no code at all. Dropping that path here would have quietly narrowed the wall * the chat area already catches. */ export declare function describeQuotaError(error: unknown): QuotaRefusalDetail | null; export type QuotaKind = "sandbox" | "ai-credits" | "generic"; /** * Event the assistant dispatches on a quota wall, namespaced by product. * * ★ A function rather than a constant: the shared code owns the mechanism, the * product owns its namespace — the same rule as translation keys. Passing the * product keeps HealthyBowl's existing `healthybowl:quota-exhausted` byte for * byte, and stops two products in one page colliding. * * ★ Nothing listens for it yet, in any product — verified across the repo. The * chat area surfaces the wall inline instead. Kept because the dispatch is the * cheap half of a future upgrade dialog, but do not mistake it for a live path. */ export declare function quotaExhaustedEventName(product: string): string; export interface QuotaExhaustedEventDetail { kind: QuotaKind; message?: string; operationName?: string | null; } //# sourceMappingURL=quota.d.ts.map