/** * Atomic peer-recovery primitive. * * `recoverUser` is a SEPARATE operation from `revoke + grant`. It * exists because peer-recovery has different semantics than account * removal-then-reissue: * * 1. **Same identity preserved.** `userId`, `role`, `permissions`, * capability bits, user envelope (if any), policy override (if * any) all survive. Only the wrapping changes. * 2. **No key rotation.** The existing DEKs stay valid — every * OTHER principal in the vault keeps their access. Rotating * keys would invalidate every co-user's wrapping. * 3. **Atomic by construction.** A single `store.put` overwrites * `_keyring/` with the recovered file. No revoke step * means no partial-failure window. * 4. **Owner→owner natively allowed.** Two co-owners recovering * each other is the explicitly-intentional case (a partner * forgot the master phrase). The existing `canRevoke` rule that * blocks owner→owner is correct for `revoke` (which is account * *removal*) and intentionally NOT replicated here. The policy * gate `peer-recover-user` carries the freshness requirement. * 5. **Tier-2 slots dropped.** The slots wrap the OLD KEK under * method-derived keys; after recovery the KEK is re-derived * from the new temp passphrase. Match `rotatePassphrase`'s * precedent — the recovered user re-enrols slots after picking * their own phrase. * * Caller must be at least as privileged as the target. The hub * `db.recoverUser` method gates this with the `peer-recover-user` * policy gate (the `peer-recover-user` factor-proof requirement); the function below * enforces only the role + anti-privilege-escalation invariants. * * @module */ import type { NoydbStore, Role } from '../../kernel/types.js'; import { type PassphrasePolicy } from '../../kernel/validation.js'; import type { UnlockedKeyring } from './keyring.js'; /** Input shape for {@link recoverUser}. */ export interface RecoverUserOptions { /** Target user id whose keyring is being recovered. */ readonly userId: string; /** * Temporary passphrase under which the new keyring is wrapped. * The recipient should call `db.rotatePassphrase` immediately on * acceptance to choose their own phrase — this temp acts as a * single-use bridge in invite / peer-recovery flows. */ readonly passphrase: string; /** Override the target's role. Defaults to the existing target's role. */ readonly role?: Role; /** Override the target's display name. Defaults to existing. */ readonly displayName?: string; /** Validate phrase strength against the configured policy. */ readonly validatePassphrase?: boolean; /** * Skip phrase strength validation even when `validatePassphrase` is * set. The escape hatch matches `grant`'s shape — used when the * temp phrase is a high-entropy one-shot string that doesn't need * to satisfy the human-typeable rules. */ readonly allowWeakPassphrase?: boolean; /** * Optional explicit phrase policy override (passed through to * `assertStrongPassphrase`). Mirrors how `grant` accepts a custom * `PassphrasePolicy` for app-specific tightening. */ readonly passphrasePolicy?: PassphrasePolicy; } /** * Atomically rewrap the target user's keyring under a fresh temp * passphrase. Single store write; no revoke step; no key rotation. * * Caller's responsibilities (NOT enforced here): * - Run the `peer-recover-user` policy gate first via * `Noydb.checkGate` to enforce the freshness factor proof. * - Communicate the temp passphrase to the recipient via a secure * channel (URL fragment, in-person, etc.) — the hub does not * transport secrets. */ export declare function recoverUser(store: NoydbStore, vault: string, callerKeyring: UnlockedKeyring, options: RecoverUserOptions): Promise;