import type { Logger } from "./observability/logger.js"; /** Algorithm tag — MUST equal registry-core's `SEALED_BOX_ALG` (v1 sole value; a different alg on * the wire is a loud per-model rejection in applyEffective, never a guess). */ export declare const SEALED_KEY_ALG = "libsodium-sealedbox-x25519"; /** The subset applyEffective needs (keeps sema-registry decoupled from fs/boot concerns). */ export interface SealedKeyOpener { has(publicKeyId: string): boolean; /** All private-key ids this host holds (surfaces in the no-private-key warn for operator triage). */ ids(): string[]; /** Unseal a base64 `crypto_box_seal` ciphertext with the EXACT keypair registered under * `publicKeyId`. Returns the plaintext (utf-8). THROWS on unknown id / malformed base64 / * MAC failure — fail-loud is the contract; the caller decides what "loud" looks like. * 🔴 The returned plaintext is a live secret: memory-only, never log it, never persist it. */ open(ciphertextB64: string, publicKeyId: string): string; } export interface SealedKeyStore extends SealedKeyOpener { /** The keypair NEW ciphertexts should be sealed against (max `createdAt`, ties broken by id) — * this is the key we report/print for center registration. `publicKey` is base64 (32 raw bytes, * 44 chars): public by definition, safe to log. */ latest: { publicKeyId: string; publicKey: string; createdAt: string; }; } /** Why a model's sealed key could not be unsealed (the poison-pill diagnosis, see SealedKeyPoison). */ export type SealedKeyPoisonReason = "sealed_decrypt_failed" | "stale_ciphertext" | "unsupported_alg" | "no_key_store"; /** * Poison-pill entry for the per-model key map (`config.modelApiKeys`): planted by applyEffective when * a model CARRIES `sealedApiKey` but the ciphertext cannot be unsealed. We deliberately do NOT drop * the model from the map — dropping made "sealed key broken" indistinguishable from "no per-model key * configured", and when the deployment also has a gateway MODEL_API_KEY the model's tasks silently * fell back onto the shared account (the exact hole the no-apiKeyEnv-fallback contract exists to * close). createKeyResolver turns this marker into a task-level `SealedKeyPoisonedError` — never * `undefined`, because undefined IS the "use the gateway key" semantic. Carries DIAGNOSIS only: * never the ciphertext, never any plaintext. */ export interface SealedKeyPoison { readonly sealedKeyPoison: true; readonly publicKeyId: string; readonly reason: SealedKeyPoisonReason; /** Error CLASS of the unseal failure (e.g. "Error") — never the message (message text is logged * at plant time; keeping the object minimal keeps every serialization face trivially secret-free). */ readonly errKind?: string; /** ISO timestamp of the apply that detected the failure. */ readonly at: string; } export declare function sealedKeyPoison(publicKeyId: string, reason: SealedKeyPoisonReason, errKind?: string): SealedKeyPoison; export declare function isSealedKeyPoison(v: unknown): v is SealedKeyPoison; /** * The task-level fail-loud face of a poison pill: thrown by the key resolver on EVERY brain call that * would otherwise authenticate this model, so a broken sealed key fails the task visibly (same family * as a rotation-leg auth failure) instead of silently burning the gateway account. Message carries the * sealed-failure marker (reason + publicKeyId + detection time) plus the operator fix — and nothing * secret (no ciphertext, no plaintext, by construction of SealedKeyPoison). */ export declare class SealedKeyPoisonedError extends Error { readonly publicKeyId: string; readonly reason: SealedKeyPoisonReason; readonly at: string; constructor(modelName: string, poison: SealedKeyPoison); } /** Default id = fingerprint of the RAW public-key bytes: `exec-`. * Deterministic → regenerating the id from the same key file is stable across boots. */ export declare function deriveKeyId(publicKeyRaw: Uint8Array): string; /** * Load every keypair under `dir` (creating dir + ONE fresh keypair when none exist) and return the * custody store. Invalid/corrupt files are skipped with a warn (they may be an OLD format or a * half-write — deleting operator key material on a parse error would destroy decryptability, so we * never do; we just don't serve them). Throws only when the store cannot exist at all (fs errors, * zero loadable keys AND generation failed). */ export declare function ensureSealedKeyStore(dir: string, logger?: Logger): Promise; /** * Best-effort public-key registration with the sema-registry (`POST /api/config/execution-keys`). * * HONESTY over fake automation: that endpoint is ADMIN-gated, and a fleet worker only holds * SERVICE_PULL_TOKEN — so automatic registration works ONLY when the operator deliberately provides * an admin token (SEMA_REGISTRY_ADMIN_TOKEN in this service's env; the REGISTRY_ADMIN_TOKEN alias was tombstoned in 5.0.0). Without * one we do the next-best REAL thing: print the FULL public key + id at boot (a public key is not a * secret) with the exact registration instructions, and warn that the web "paste key" face stays * dark for this host until an admin registers it. Never throws (fire-and-forget from boot). */ export declare function reportExecutionPublicKey(opts: { /** sema-registry base URL (config.configCenter.baseUrl). Absent = no center → manual print only. */ baseUrl?: string; /** Admin bearer for the center's config write face. Absent = manual print (see above). */ adminToken?: string; key: { publicKeyId: string; publicKey: string; createdAt: string; }; notes?: string; logger?: Logger; /** Test seam. */ fetchImpl?: typeof fetch; }): Promise; //# sourceMappingURL=sealed-key.d.ts.map