import { type Infer } from '@forgezero/access/schema'; /** * @forgezero/providers — service registry with priority, health and fallback. * * Shared runtime schemas define stored configuration; hosts inject * its sources. It never imports `@forgezero/vault`. That constraint is what makes it * usable without ForgeZero at all — if it reached for the vault directly it * would be a ForgeZero package with a public name. * * priority providers are tried in the order the DATABASE says, not the code * health three strikes marks a provider offline; any success clears them * fallback the next provider is tried, unless the error says not to * * That last part is what most registries get wrong. See `FailureKind`. */ export declare class ProviderError extends Error { readonly code: string; /** * Structured detail for `classify`, optional and additive. * * Without it a provider has to decide `terminal` vs `backoff` by matching * substrings of its own error message, which breaks the first time a * vendor rewords one. An HTTP status or a JSON-RPC code is the fact the * decision actually rests on, so it travels with the error. */ readonly details?: Readonly> | undefined; constructor(code: string, message: string, /** * Structured detail for `classify`, optional and additive. * * Without it a provider has to decide `terminal` vs `backoff` by matching * substrings of its own error message, which breaks the first time a * vendor rewords one. An HTTP status or a JSON-RPC code is the fact the * decision actually rests on, so it travels with the error. */ details?: Readonly> | undefined); } /** Where credentials come from. The interface IS the contract. */ export interface CredentialSource { readonly name: string; get(reference: string, field: string): Promise; } /** Credentials already scoped to one provider identity. */ export interface ScopedCredentialSource { readonly name: string; get(field: string): Promise; } /** Environment variables. `smtp` + `password` → `SMTP_PASSWORD`. */ export declare function envCredentials(env: Record): CredentialSource; /** * Try each source in order, swallowing failures until one answers. * * This is what makes bootstrap work. A locked vault THROWS, and if that * propagated it would take down the environment fallback that exists to recover * it. The swallow is load-bearing, not defensive coding — and it is why there is * a test that locks the vault and asserts mail still sends. */ export declare function chainCredentials(...sources: readonly CredentialSource[]): CredentialSource; /** Bind a reference once so a provider receives only its own credential fields. */ export declare function scopeCredentials(source: CredentialSource, reference: string): ScopedCredentialSource; /** Try provider-scoped sources in order, for example Vault then systemd. */ export declare function chainScopedCredentials(...sources: readonly ScopedCredentialSource[]): ScopedCredentialSource; export declare const ProviderHealthSchema: import("@forgezero/access/schema").ContractSchema<{ status: "ok" | "degraded" | "offline"; strikes: number; } & { lastFailureAtTs?: number | undefined; }>; export type ProviderHealth = Infer; /** One provider identity, independent of service attachments. Credentials remain references. */ export declare const ProviderInstanceConfigSchema: import("@forgezero/access/schema").ContractSchema<{ config: Record; instanceKey: string; providerId: string; enabled: boolean; secretRef: string; } & {}>; export type ProviderInstanceConfig = Infer; export declare const ServiceMethodAttachmentSchema: import("@forgezero/access/schema").ContractSchema<{ instanceKey: string; enabled: boolean; providerMethod: string; priority: number; } & { providerMethodVersion?: string | undefined; health?: ({ status: "ok" | "degraded" | "offline"; strikes: number; } & { lastFailureAtTs?: number | undefined; }) | undefined; }>; export type ServiceMethodAttachment = Infer; /** Provider identities and service routing policy may live in any store. */ export interface ConfigSource { readonly name: string; provider(instanceKey: string): Promise; list(serviceKey: string, methodKey: string): Promise; /** Record health in this source’s declared store; read-only Vault adapters retain process-local health. */ recordHealth(serviceKey: string, methodKey: string, instanceKey: string, providerMethod: string, providerMethodVersion: string, health: ProviderHealth): Promise; } export interface StaticRegistryConfig { providers: readonly ProviderInstanceConfig[]; services: Record>; } export declare function staticConfig(config: StaticRegistryConfig): ConfigSource; /** * How a failure should be treated. * * terminal OUR payload is malformed. Every provider will reject it, so * trying the next buries the real error under identical ones. * retryable THIS provider or key is bad. The next may be fine. * backoff rate limited. Skip without a strike — it is working, we are * asking too fast, and taking it offline punishes it for that. * * Collapsing these into "it failed" gives a system that either gives up too * early or hammers every provider with a request none can accept. */ export type FailureKind = 'terminal' | 'retryable' | 'backoff'; export interface InvokeContext { config: Record; secret(field: string): Promise; signal?: AbortSignal; } export interface ProviderMethodSpec { /** Provider-owned contract/API branch, such as `v1` or `2026-08-01`. */ version: string; lifecycle: 'current' | 'legacy' | 'deprecated' | 'retired'; invoke(context: InvokeContext, args: Args): Promise; classify(error: unknown): FailureKind; } export declare function defineProviderMethod(method: ProviderMethodSpec): ProviderMethodSpec; export type AnyProviderMethod = ProviderMethodSpec; export interface ProviderMethodBranches { currentVersion: string; versions: Readonly>>; } export declare function defineProviderMethodBranches(branches: ProviderMethodBranches): ProviderMethodBranches; export type ProviderMethodEntry = AnyProviderMethod | ProviderMethodBranches; export type ProviderMethods = Record; /** A vendor/provider identity with named capabilities, independent of services. */ export interface ProviderDefinition { id: string; label: string; multiInstance?: boolean; /** JSON Schema. `writeOnly` fields route to secret storage. */ credentials?: Record; config?: Record; methods: Methods; } export declare function defineProvider(spec: ProviderDefinition): ProviderDefinition; /** Convenience for a provider that exposes exactly one named method. */ export declare function defineSingleMethodProvider(spec: Omit>>, 'methods'> & { method: Method; version?: string; lifecycle?: ProviderMethodSpec['lifecycle']; invoke(context: InvokeContext, args: Args): Promise; classify(error: unknown): FailureKind; }): ProviderDefinition>>; export declare const STRIKES_TO_OFFLINE = 3; export interface Attempt { providerId: string; instanceKey: string; providerMethod: string; providerMethodVersion: string; outcome: 'sent' | 'skipped' | 'failed'; kind?: FailureKind; durationMs?: number; failure?: { kind: FailureKind; code?: string; message: string; }; /** @deprecated Read `failure.message`; retained for 0.1 compatibility. */ error?: string; } export interface CallResult { ok: boolean; result?: Result; provider?: string; instance?: string; method?: string; selected?: { providerId: string; instanceKey: string; providerMethod: string; providerMethodVersion: string; }; fallbackUsed: boolean; attempts: readonly Attempt[]; error?: ProviderError; } export interface CallOptions { /** Reaches provider fetch/socket code; cancellation never falls through to another vendor. */ signal?: AbortSignal; } /** Type-only input/output contract for one service method. */ export interface ServiceMethodContract { readonly __args?: (args: Args) => void; readonly __result?: Result; } export declare function serviceMethod(): ServiceMethodContract; export type ServiceMethods = Record>; export interface ServiceDefinition { key: Key; methods: Methods; } export declare function defineService(definition: ServiceDefinition): ServiceDefinition; type ServiceArgs = T extends ServiceMethodContract ? Args : never; type ServiceResult = T extends ServiceMethodContract ? Result : never; /** * One direct service attachment. This is the normal application API: pass the * provider itself, its capability, scoped credentials, config and priority. * Stable instance keys exist only inside the dynamic control-plane adapter. */ export interface DirectServiceAttachment { provider: ProviderDefinition; method: string; /** Select an exact legacy/current branch. Omit to use the provider's declared currentVersion. */ version?: string; credentials: ScopedCredentialSource; config?: Record; priority: number; enabled?: boolean; } export type DirectServiceConfig = { [Method in keyof Methods]: readonly DirectServiceAttachment[]; }; export interface DirectServiceOptions { before?: RegistryOptions['before']; after?: RegistryOptions['after']; } export type DirectAttempt = Omit; export type DirectCallResult = Omit, 'instance' | 'selected' | 'attempts'> & { selected?: { providerId: string; providerMethod: string; providerMethodVersion: string; }; attempts: readonly DirectAttempt[]; }; export interface RegistryOptions { credentials: CredentialSource; config: ConfigSource; /** * Deliberately `any` in both parameters. * * A registry is heterogeneous by definition — an email provider and a storage * provider have nothing in common but the interface — so this list cannot be * given one useful pair of type arguments. It was `ProviderSpec`, * which is unsatisfiable in the `Result` position: every real provider had to * be cast with `as never` at the call site. The type parameters earn their * keep at the DEFINITION site, where `defineProvider` still checks that * `invoke` matches what the provider claims to take and return. */ providers: readonly ProviderDefinition[]; before?: (context: { service: string; method: string; provider: string; instance: string; }) => void | Promise; after?: (result: CallResult) => void | Promise; } export declare function nextHealth(current: ProviderHealth | undefined, kind: FailureKind | 'success'): ProviderHealth; export declare function createRegistry(options: RegistryOptions): { call: (serviceKey: string, methodKey: string, args: unknown, callOptions?: CallOptions) => Promise>; service: (definition: ServiceDefinition) => { call>(method: Method, args: ServiceArgs, callOptions?: CallOptions): Promise>>; }; }; /** * Create a typed service directly from ordered provider attachments. * * This deliberately hides provider-instance keys and the ConfigSource * ceremony. Applications normally know their providers in code; ForgeZero's * dynamic admin control plane can continue to use createRegistry separately. */ export declare function createService(definition: ServiceDefinition, methods: DirectServiceConfig, options?: DirectServiceOptions): { call>(method: Method, args: ServiceArgs, callOptions?: CallOptions): Promise>>; }; /** Passed to `registry.call('email', message)`. */ export interface EmailMessage { to: string | readonly string[]; subject: string; html?: string; text?: string; from?: string; replyTo?: string; /** Stable caller-owned key for providers that support safe replay. */ idempotencyKey?: string; } export interface EmailBatch { /** Provider-neutral hard ceiling matches JetEmail's native batch contract. */ emails: readonly EmailMessage[]; } export interface EmailBatchItemResult { status: 'success' | 'failed'; id?: string; response?: string; error?: string; } export interface EmailBatchResult { summary: { total: number; successful: number; failed: number; }; results: readonly EmailBatchItemResult[]; } export declare const VERSION = "0.1.46"; export {};