/**
 * CredentialWriteFunnel — Step 4 (primitive) of live credential re-pointing (spec §2.2).
 *
 * The SOLE in-process serialization point for credential writes to a config-home slot. Every
 * in-process keychain credential write (the swap executor, the QuotaPoller 401-refresh closure,
 * OAuthRefresher / EnrollmentWizard writes, KeychainCredentialProvider.writeCredentials) is meant
 * to go through `withSlotLock(slot, fn)` so a write can never interleave with another write — or
 * with a swap — on the SAME slot. A companion lint (Step 4b) forbids those write primitives
 * outside this funnel, mirroring the SafeGitExecutor / SafeFsExecutor single-funnel precedent.
 *
 * This file is the PRIMITIVE only: the lock machinery + its tests. Routing the existing writers
 * through it and adding the forbidding lint is the next commit (the lint can't land until every
 * existing writer is routed, or it would break the build).
 *
 * Concurrency model (spec §2.2 "Concurrency model"):
 *   - The server process is the only writer; these locks serialize WITHIN that process.
 *   - One per-slot lock serializes every write to a given slot.
 *   - One machine-local single-mover mutex serializes whole SWAPS against each other (a swap
 *     touches two slots; two concurrent swaps could deadlock on cross-ordered slot locks, so a
 *     swap takes the single-mover first, then the slot locks in a canonical order).
 *   - Lock order: single-mover (swaps only) → per-slot locks ordered by slot path → ledger write.
 *   - Crash-stale lock state is cleared by process restart (all state is in-memory).
 *
 * Bounded, never wedged (spec §2.2 "Bounded under the lock"): lock acquisition is a
 * try-lock-WITH-TIMEOUT. On timeout the caller is told it was SKIPPED with a named reason rather
 * than blocking forever — a slow holder degrades to a skipped action, never a wedged slot. (The
 * caller is responsible for bounding any `await` it does INSIDE `fn` — e.g. a refresh fetch must
 * carry its own `AbortSignal.timeout`; the funnel bounds acquisition, not the work.)
 */
/** Outcome of a funnel-guarded operation. `ran:false` = the lock could not be acquired in time. */
export interface FunnelResult<T> {
    ran: boolean;
    /** Present when `ran` is true. */
    value?: T;
    /** Present when `ran` is false — a human-readable, credential-free reason. */
    skippedReason?: string;
}
export interface CredentialWriteFunnelOptions {
    /** Default try-lock timeout for a single slot acquisition (ms). */
    slotLockTimeoutMs?: number;
}
export declare class CredentialWriteFunnel {
    /** Per-slot serialization: slot → the tail promise of that slot's lock queue. */
    private readonly slotTails;
    /** Machine-local single-mover mutex (swaps). In-memory ⇒ a restart clears any crash-stale hold. */
    private singleMoverHeld;
    private readonly slotLockTimeoutMs;
    constructor(opts?: CredentialWriteFunnelOptions);
    /**
     * Run `fn` while holding the per-slot lock for `slot`. Serializes against every other
     * `withSlotLock` on the same slot. Acquisition is bounded: if the lock is not free within
     * `timeoutMs`, `fn` does NOT run and the result is `{ ran: false, skippedReason }`.
     */
    withSlotLock<T>(slot: string, fn: () => Promise<T> | T, opts?: {
        timeoutMs?: number;
    }): Promise<FunnelResult<T>>;
    /**
     * Run `fn` while holding the per-slot locks for ALL `slots`, acquired in a CANONICAL order
     * (sorted by path) so two concurrent multi-slot operations can't deadlock on opposite orders.
     * If ANY slot lock cannot be acquired in time, NO `fn` runs and the result is skipped (the
     * already-held inner locks are released by unwinding). Used by the swap executor (Step 5).
     */
    withSlotLocks<T>(slots: string[], fn: () => Promise<T> | T, opts?: {
        timeoutMs?: number;
    }): Promise<FunnelResult<T>>;
    /**
     * Run `fn` holding the machine-local single-mover mutex. A swap takes this BEFORE its slot
     * locks so two swaps never run at once. Try-acquire: if a move is already in flight, `fn`
     * does NOT run and the result is skipped (never blocks).
     */
    withSingleMover<T>(fn: () => Promise<T> | T): Promise<FunnelResult<T>>;
    /** Test/observability: is the single-mover mutex currently held? */
    isSingleMoverHeld(): boolean;
    /** Test/observability: how many slots currently have a live lock queue tail. */
    get trackedSlotCount(): number;
}
/**
 * Process-wide shared funnel singleton (Step 4b). Every in-process credential writer — the
 * QuotaPoller refresh path (via `refreshClaudeToken`), the AccountSwitcher write (via
 * `writeCredentialsSerialized`), and the swap executor (Step 5) — routes through THIS instance so
 * a refresh write and a swap on the same slot share one per-slot lock and can never interleave.
 * In-memory ⇒ a process restart clears any crash-stale hold. Tests inject their own instance.
 */
export declare const credentialWriteFunnel: CredentialWriteFunnel;
/**
 * Thrown by a void-returning serialized writer when the per-slot lock could not be acquired in
 * time (the funnel SKIPPED the write rather than wedging). It is a transient "busy, retry" signal
 * — NEVER a credential failure: callers must not treat it as a dead login / needs-reauth.
 */
export declare class CredentialWriteSkippedError extends Error {
    constructor(reason: string);
}
//# sourceMappingURL=CredentialWriteFunnel.d.ts.map