/** SDK-owned platform module. This implementation is maintained in goodvibes-sdk. */ import type { SecretsManager } from '../config/secrets.js'; /** * The credential an adapter should check against, or the reason it has none. * * Deliberately not `string | null`. See the bypass note above: an adapter that * cannot tell "nothing configured" from "configured but broken" will read the * second as the first and stop authenticating. */ export type SurfaceCredential = /** A usable credential. This is the only shape carrying a value. */ { readonly state: 'resolved'; readonly value: string; } /** No credential is configured anywhere. The surface is unconfigured. */ | { readonly state: 'absent'; } /** * A credential IS configured and could not be produced, a reference whose * secret is missing from the store, in a scope this process cannot read, or * malformed. Never authorise on this. */ | { readonly state: 'unresolvable'; readonly configKey: string; }; /** Where an adapter looks for its credential, in the order it looks. */ export type SurfaceCredentialSource = /** A config key. The only kind that can hold a `goodvibes://` reference. */ { readonly kind: 'config'; readonly key: string; } /** A registered service secret. Already resolved by the registry. */ | { readonly kind: 'registry'; readonly service: string; readonly field: ServiceSecretFieldName; } /** An environment variable. Always a literal. */ | { readonly kind: 'env'; readonly name: string; }; /** The registry secret slots the adapters ask for. */ type ServiceSecretFieldName = 'primary' | 'password' | 'authToken' | 'signingSecret' | 'appToken'; /** * The slice of an adapter context this needs. * * Structural, so both `SurfaceAdapterContext` and * `GenericWebhookAdapterContext` satisfy it without either importing the other, * and so a test can supply three fields instead of thirty. */ export interface SurfaceCredentialContext { readonly configManager: { get(key: string): unknown; }; readonly serviceRegistry?: { resolveSecret(service: string, field: ServiceSecretFieldName): Promise; } | undefined; readonly secretsManager?: Pick | undefined; } /** * Read the first source that has a credential, resolving config references. * * ── Ordering, and why a broken config value does not fall through ─────────── * * Sources are tried in the order given, which is the order each adapter already * used. A source that holds nothing is skipped. But a CONFIG source that holds * something and fails to resolve stops the walk and returns `unresolvable` * rather than continuing to the next source. * * That is deliberate. Falling through would mean an operator whose swept * reference broke gets quietly authenticated by a stale environment variable * instead, the surface keeps working, against a credential nobody chose, and * the broken reference is never noticed. A refusal that names the key in the * log is worse for one request and better for the operator, which is the same * trade `acceptRefShapedLiteral` already makes one layer down. * * Only config values are resolved. Registry values arrive already resolved, and * an environment variable is a literal by construction. */ export declare function resolveSurfaceCredential(context: SurfaceCredentialContext, ...sources: readonly SurfaceCredentialSource[]): Promise; /** * The response for a credential that is configured and cannot be produced. * * 503, not 401: nothing is wrong with the caller, and answering 401 sends * whoever debugs it hunting for a wrong token, which is precisely the wild * goose chase this whole defect caused. * * The body names no config key. The caller is a third party (Telegram, Twilio, * an operator's own script) and has no business learning which of this * daemon's settings is broken; the operator learns that from the log line, * which names the key and never the value. */ export declare function surfaceCredentialUnavailable(surface: string, credential: Extract): Response; export {}; //# sourceMappingURL=surface-credential.d.ts.map