/** * What a credential mode MEANS operationally — the one module both halves read. * * Design: docs/superpowers/specs/2026-08-21-cli-credential-modes-design.md (§2 the constraints, * §6 the mixed-mode ladder, §8.4 the mutual-exclusion guard). Every bare `§n` below refers to that * document, NOT to docs/designs/clustly-cli.md which the older `hosting/*` modules cite. * * `manifest-schema.ts` owns the VOCABULARY (`byok` | `subscription` | `mixed`) because that is part * of the file's shape. This module owns what each value implies: which env names must be set before * an agent can serve, which credential a given run attempt uses, and when a subscription token is * considered dead. The dependency points this way and only this way — * `manifest-schema.test.ts` asserts that module's only relative import is `./discovery`. * * It imports nothing from `node:`, deliberately, for the same reason `limits.ts` does not: the * marketplace reads it directly (`@/sdk/hosting/credential-policy`), exactly as * `lib/hosting/bundle/collect.ts` reads `@/sdk/hosting/limits`. One implementation, every surface — * not two constants pinned by a test, which is what `HUMAN_REVIEW_MARKER` had to settle for because * its home module drags Node in. * * THE INVARIANT THIS MODULE EXISTS TO PROTECT. Claude Code resolves credentials in a fixed * precedence order, and `ANTHROPIC_API_KEY` outranks `CLAUDE_CODE_OAUTH_TOKEN`. A sandbox carrying * both therefore bills the API account on every run and never touches the subscription — silently, * with no error, discovered on an invoice. So `mixed` is NOT "set both and let it choose": it is an * ordered ladder of single-credential attempts, and `singleCredentialViolation` is the assertion * that keeps it that way. */ import { type ConcurrencyMode, type CredentialMode } from "./manifest-schema"; /** Read at precedence rank 3 — outranks the OAuth token. Metered to the builder's Console account. */ export declare const ANTHROPIC_API_KEY_ENV = "ANTHROPIC_API_KEY"; /** Read at precedence rank 5. Minted by `claude setup-token`; bills the builder's Claude plan. */ export declare const CLAUDE_CODE_OAUTH_TOKEN_ENV = "CLAUDE_CODE_OAUTH_TOKEN"; /** * The credentials a mode uses, IN THE ORDER IT TRIES THEM. * * Two readings, one list, on purpose. As a SET it is "every env name that must hold a value before * this agent can serve" — what the manifest declares and the secrets preflight collects. As a * SEQUENCE it is the failover ladder: `mixed` tries the subscription first and the key second. * Deriving both from one function is what stops the manifest from requiring a name the runner never * uses, or the runner from reaching for one nobody was asked to set. */ export declare function credentialEnvNames(mode?: CredentialMode): string[]; /** * The ONE credential attempt N gets, or null when the ladder is spent. * * Callers inject exactly this name and nothing else. Returning a single name rather than an env map * is the point: there is no shape here that can express "both", so a runner cannot accidentally * compose one. * * ponytail: no caller in this repo either — this IS the contract the hangar runner implements, and * it is kept as the published shape of §6's ladder rather than reinvented there. If hangar ends up * deriving the order itself, delete this and `hasFailover` keeps the two callers that remain. */ export declare function credentialForAttempt(mode?: CredentialMode, attempt?: number): string | null; /** True when a failed run has a second credential to retry on (mixed only). */ export declare function hasFailover(mode?: CredentialMode): boolean; /** True when the mode depends on a `setup-token` credential that can expire (§5). */ export declare function usesSubscription(mode?: CredentialMode): boolean; /** * The §8.4 guard, as a refusal message or null — the house shape for a policy that must name its * own fix (V13), matching `destroyedRefusal` and `agentHireBlock`. * * Call it with whatever env a run is ABOUT to get. A violation is a hard refusal to start, never a * warning: the failure it prevents is invisible at runtime and only shows up as a bill. */ export declare function singleCredentialViolation(env: Readonly>): string | null; /** * The invariant applied to a manifest's declared env NAMES rather than a live env. * * Both enforcement points ask the same question of the same shape, and both were spelling out the * `Object.fromEntries(...)` incantation to get there. */ export declare function declaredCredentialViolation(mode: CredentialMode | undefined, envNames: readonly string[]): string | null; /** * Read the credential settings back out of an intake manifest (the JSON `hostedManifest` emits). * * The marketplace learns an agent's mode from the release it just brokered — that JSON is the only * place the choice crosses the wire, and `hostedManifest` always emits both fields precisely so * this read never has to guess. Defensive rather than strict: an unparseable or foreign payload * yields the defaults, because a release must not fail over a settings read. */ export declare function readIntakeCredential(intakeManifestJson: string): { mode: CredentialMode; concurrency: ConcurrencyMode; }; /** * How long a `claude setup-token` credential lasts. Anthropic mints it for one year and the token * is opaque, so this is the basis of an ESTIMATE, never a reading (§2.3). */ export declare const SUBSCRIPTION_TOKEN_LIFETIME_DAYS = 365; /** * How long before the ESTIMATED expiry an agent starts warning (owner decision 2026-08-22). * * Short on purpose: a warning that stands for a third of the credential's life stops being read. * `claude setup-token` takes under a minute at a terminal, so seven days is ample lead — and an * observed authentication failure does not wait for this window at all. */ export declare const CREDENTIAL_WARN_WINDOW_DAYS = 7; /** * When the token stored at `setAtMs` is expected to stop working. * * We stamp the moment the VALUE lands in the store, not the moment it was minted — those are * minutes apart in practice (you generate it and paste it), and the token carries nothing we can * introspect. The estimate drives warnings only; `authFailed` below is what actually blocks a hire. */ export declare function estimateExpiryMs(setAtMs: number): number; export type CredentialState = "valid" | "expiring_soon" | "expired"; /** * The credential's state, observation first. * * `authFailed` is a fact — a run came back with `authentication_failed` — and it outranks the * estimate in both directions: it expires a credential the estimate still likes, and nothing about * a lapsed estimate can mark a credential dead that is still authenticating. That ordering is why * a 7-day warn window is safe despite the estimate being approximate. */ export declare function credentialState(input: { expiresAtMs: number | null; nowMs: number; authFailed?: boolean; }): CredentialState;