import { type SecretRef } from "../secrets/storage.js"; export interface CredentialContext { userEmail: string; orgId?: string | null; /** Restricts lookup to shared credentials in the explicit org. */ credentialScope?: "org"; } export type CredentialStorageScope = "user" | "org"; export interface ResolvedCredential { value: string; scope: SecretRef["scope"]; scopeId: string; } export interface CredentialProvenance { scope: SecretRef["scope"] | "deployment"; scopeId?: string; source?: string; connectionId?: string; } export interface CredentialEndpointOwner { scope: string; scopeId?: string; source?: string; connectionId?: string; } export declare class CredentialEndpointMismatchError extends Error { constructor(message: string); } export declare function assertCredentialCanReachEndpoint(endpoint: CredentialEndpointOwner, credential: Pick | { scope?: string; scopeId?: string; source?: string; connectionId?: string; } | undefined, key?: string): void; /** * Resolve a credential from one explicit legacy SQL credential scope. * * Prefer `resolveCredential()` for normal app-local credential lookup. This * helper exists for workspace connection refs, where a ref can explicitly say * "use the org-scoped key" and must not accidentally read a user override. */ export declare function resolveCredentialForScope(key: string, ctx: CredentialContext & { scope: CredentialStorageScope; }): Promise; /** * Resolve a credential across the encrypted app_secrets store and the legacy * settings-backed credential store. A member's own value wins, followed by the * active org/workspace shared value. * * SECURITY: NEVER reads from process.env. Env vars are global to the * deployment and would leak across users in a multi-tenant app. * * Read order: * 1. user-scoped app_secrets * 2. user-scoped legacy settings credential * 3. org-scoped app_secrets * 4. legacy workspace-scoped app_secrets for the org * 5. org-scoped legacy settings credential * 6. solo workspace-scoped app_secrets (`solo:`) * * An owner or admin of the org reads 3-5 before 1-2 (`readsOrgCredentialFirst`). * * Steps 3-5 use `ctx.orgId` when given, else the org resolved from * `ctx.userEmail` (see `resolveEffectiveOrgId`), and are skipped only when the * caller truly has no org. A membership lookup that could not be read throws * `CredentialStoreUnavailableError` instead of silently reporting the * credential as unset — "the store didn't answer" and "nothing is saved" are * different outcomes callers must not conflate. */ export declare function resolveCredentialDetailed(key: string, ctx: CredentialContext): Promise; export declare function resolveCredential(key: string, ctx: CredentialContext): Promise; /** * Explain an empty credential lookup when a key of that name is saved in a * scope the caller cannot read. * * Two distinct causes produce the same "the key is right there and it still * says it's missing" report: * * - Non-interactive runs — integration/webhook deliveries, scheduled jobs, * automations, and inbound A2A calls — resolve credentials as an owner * identity rather than as the person who triggered them, so a teammate's * Personal key is invisible to them even though the vault visibly holds it. * - The key is saved in a DIFFERENT organization the caller also belongs to. * Credentials are per-organization, so gaining a second organization (or * having `active-org-id` repointed at one) orphans every key synced under * the first. Without this, the only symptom is a missing-env-var error that * names the key rather than the org mismatch that actually caused it. * * SECURITY: both probes are bounded to organizations the caller is a member * of, report only the scope kind (and, for the cross-org case, the name of an * org the caller already belongs to), and never return the owning account, how * many rows matched, or any part of the value. Without an active org there is * no boundary to bound the probe to, so it declines to answer rather than * revealing that some other tenant holds a key of the same name — this * includes callers whose `ctx.orgId` is unset AND whose org could not be * resolved from `ctx.userEmail` (see `resolveEffectiveOrgId`). * * Returns null when nothing safe and useful can be said. */ export declare function describeCredentialScopeGap(keys: readonly string[], ctx: CredentialContext): Promise; export declare function hasCredential(key: string, ctx: CredentialContext): Promise; export declare function saveCredential(key: string, value: string, ctx: CredentialContext & { scope?: "user" | "org"; }): Promise; /** * Remove every row of `key` the named owner holds: the caller's own (user * secret, legacy user setting, `solo:` workspace secret) or, with * `scope: "org"`, the organization's (org secret, legacy workspace secret, * legacy org setting). Callers check that the caller may change the * organization's credentials; `deleteResolvedCredential` does it for them. */ export declare function deleteCredential(key: string, ctx: CredentialContext & { scope?: "user" | "org"; }): Promise; export declare class CredentialDeleteForbiddenError extends Error { statusCode: number; constructor(); } /** * Remove the credential `resolveCredentialDetailed` answers with, so a * disconnect takes effect: every row of the owner that answers (see * `deleteCredential`), since deleting just the answering row would let the * next row of that owner answer instead. Only an owner or admin may remove * the organization's. A save that clears a value knows its scope and calls * `deleteCredential` with it instead, which never follows the read order. */ export declare function deleteResolvedCredential(key: string, ctx: CredentialContext): Promise;