/** * Storage layer for the framework secrets registry. * * Values are encrypted at rest with AES-256-GCM. The workspace-shared * encryption key prefers `WORKSPACE_SECRETS_ENCRYPTION_KEY`, then the legacy * combined `SECRETS_ENCRYPTION_KEY`, then the workspace-wide `A2A_SECRET`. * A configured previous workspace key, `BETTER_AUTH_SECRET`, and app-scoped * keys remain read fallbacks for rows written before sibling apps converged on * the shared key. Successful fallback reads race-safely refresh the shared * ciphertext. * * A development process connected to a remote database shares its rows with * the deployments reading that database, but not necessarily their key * material. It never refreshes shared ciphertext and refuses to write a secret * unless the scope's stored secrets already use its preferred key, so it * cannot strand values those deployments read. * * Secret values are NEVER logged and NEVER returned from any route handler. */ import type { SecretScope } from "./register.js"; export declare const appSecretsStore: import("../db/store-registry.js").Store; export declare function ensureTable(): Promise; export declare const VAULT_SYNC_DESCRIPTION_PREFIX = "Synced from Dispatch vault:"; export declare function last4(value: string): string; export interface SecretRef { key: string; scope: SecretScope; scopeId: string; } export interface WriteSecretArgs extends SecretRef { value: string; description?: string; urlAllowlist?: string; } export declare class AppSecretKeyMismatchError extends Error { constructor(ref: SecretRef); } /** * Write (insert or update) a secret. The value is encrypted before being * stored — the caller's plaintext is never persisted. Returns the new * record's id. */ export declare function writeAppSecret(args: WriteSecretArgs): Promise; export interface ReadSecretResult { value: string; last4: string; updatedAt: number; } /** * Keys whose stored rows this request couldn't decrypt. Reads treat those rows * as missing; callers that must not fall back to other settings check here. */ export declare function undecryptableSecretKeysForRequest(): ReadonlySet; /** * True only when the database says the app_secrets table itself is missing. * Other query failures must propagate unchanged: attempting schema bootstrap * for connectivity, permission, or syntax errors both hides the real failure * and can introduce DDL onto a latency-sensitive read path. */ export declare function isMissingAppSecretsTableError(error: unknown): boolean; /** * Read a secret's plaintext value. Returns null when not found. The caller * is responsible for never logging the returned value. */ export declare function readAppSecret(ref: SecretRef): Promise; export declare function readAppSecrets(args: { keys: readonly string[]; scope: SecretScope; scopeId: string; }): Promise>; export declare function getAppSecretMeta(ref: SecretRef): Promise<{ last4: string; updatedAt: number; } | null>; export interface SecretMeta { key: string; scope: SecretScope; scopeId: string; last4: string; description: string | null; urlAllowlist: string[] | null; createdAt: number; updatedAt: number; } export declare function hasAppSecret(ref: SecretRef): Promise; /** * Read an opaque version marker for a secret without decrypting its value. * The marker changes whenever either saved ciphertext is replaced. */ export declare function readAppSecretVersion(ref: SecretRef): Promise; export declare function readAppSecretMeta(ref: SecretRef): Promise; /** * List all secrets for a given scope. Returns metadata only — values are * never decrypted or returned. Used by the ad-hoc list route to surface * user-created keys. */ export declare function listAppSecretsForScope(scope: SecretScope, scopeId: string): Promise; export declare function deleteAppSecret(ref: SecretRef): Promise; /** * Delete only if the stored ciphertext is still the version the caller read. * The ciphertext comparison in the DELETE closes the race between checking * the opaque version and removing the row. */ export declare function deleteAppSecretIfVersion(ref: SecretRef, expectedVersion: string): Promise; /** * Delete a confirmed set only while every stored ciphertext still matches. * Locking and deleting the rows in one transaction prevents a rotation from * leaving a partially removed credential set. */ export declare function deleteAppSecretsIfVersions(expectedSecrets: readonly { ref: SecretRef; expectedVersion: string; }[]): Promise;