/** * Circuit breaker for the codebase indexer. * * The indexer can wedge: a hung filesystem, a parser pathology, or another * wstack process holding the SQLite write lock (several surfaces — TUI, WebUI, * parallel terminals — share one per-project `index.db`). Without protection, * every queued reindex piles up behind the process-wide mutex, `isIndexing()` * stays true forever, and anything that awaits an index run (the startup scan, * `/codebase-reindex`) locks its terminal. * * Standard three-state breaker: * * closed — normal operation; consecutive failures are counted. * open — after `failureThreshold` consecutive failures, every request * is rejected fast ({@link CircuitOpenError}) for `cooldownMs`. * half-open — after the cooldown exactly one probe run is admitted; * success closes the circuit, failure re-opens it. * * Watchdog timeouts ({@link IndexTimeoutError}) count as failures; * caller-initiated aborts (session teardown) do not — the background indexer * makes that distinction before recording. * * Lock conflicts ({@link LockError}) do NOT count as failures — they are expected * transient conditions when multiple wstack surfaces share the same `index.db`. * The index store retries automatically; a LockError only reaches the circuit * breaker when all retries are exhausted. */ export type CircuitState = 'closed' | 'open' | 'half-open'; export interface CircuitSnapshot { state: CircuitState; consecutiveFailures: number; lastFailure: string | null; /** ms until an open circuit admits a half-open probe (0 unless open). */ cooldownRemainingMs: number; } /** Thrown when a run is rejected because the circuit is open. */ export declare class CircuitOpenError extends Error { readonly name = "CircuitOpenError"; } /** Thrown by the background indexer's watchdog when a run exceeds its timeout. */ export declare class IndexTimeoutError extends Error { readonly name = "IndexTimeoutError"; } /** * Thrown when an SQLite operation fails with a lock conflict (SQLITE_BUSY or * SQLITE_LOCKED) even after all retry attempts are exhausted. * * The circuit breaker does **not** count `LockError` as a failure — a lock * conflict means another writer is active, not that this indexer is broken. * The caller should treat it as a transient failure and retry later. */ export declare class LockError extends Error { readonly name = "LockError"; } interface CircuitBreakerOptions { /** Consecutive failures before the circuit opens. Default: 3. */ failureThreshold?: number | undefined; /** How long an open circuit rejects requests before allowing a probe. Default: 60s. */ cooldownMs?: number | undefined; /** Injectable clock for tests. Default: Date.now. */ now?: (() => number) | undefined; } export declare class IndexCircuitBreaker { private readonly failureThreshold; private readonly cooldownMs; private readonly now; private state; private consecutiveFailures; private openedAt; private lastFailure; private probeInFlight; constructor(opts?: CircuitBreakerOptions); /** * True when a run may proceed. An open circuit transitions to half-open once * the cooldown has elapsed, admitting exactly one probe; further requests * are rejected until that probe settles via recordSuccess/recordFailure. */ allowRequest(): boolean; recordSuccess(): void; recordFailure(err: unknown): void; /** * Release a probe that ended without a verdict (the caller aborted it). * Neither success nor failure is recorded, but a half-open circuit must be * able to admit the next probe — otherwise it refuses every request until a * manual reset. */ abandonProbe(): void; /** Force-close the circuit (manual recovery: `/codebase-reindex`). */ reset(): void; snapshot(): CircuitSnapshot; } /** * Process-wide breaker shared by every index path (startup scan, per-edit * incremental, external watcher, the `codebase-index` tool). Module-level for * the same reason the mutex is: there is one `index.db` per project and one * indexing pipeline per process. */ export declare const indexCircuitBreaker: IndexCircuitBreaker; /** Reset the shared breaker — used by `/codebase-reindex` and tests. */ export declare function resetIndexCircuitBreaker(): void; export {}; //# sourceMappingURL=circuit-breaker.d.ts.map