import type { ClickHouseClient } from "@clickhouse/client"; import type { MigrationLogger } from "./logger.js"; export interface AwaitControlPlaneIdleOpts { client: ClickHouseClient; /** The OTHER side of the mutex — never the caller's own identity. */ users: readonly string[]; /** Total wait budget. Exceeding it returns `timeout`; it never throws. */ timeoutMs: number; /** Interval between probes. Defaults to 5000. */ pollMs?: number; /** Overrides `CONTROL_PLANE_ACTIVITY_RECENCY_SECONDS`. */ recencySeconds?: number; signal?: AbortSignal; logger?: MigrationLogger; } export type ControlPlaneIdleResult = { outcome: "idle"; } | { outcome: "timeout"; waitedMs: number; } | { outcome: "aborted"; }; /** * Waits for the other control-plane identities to go idle — the migration * runner calls this as the schema admin with `users: [FJALL_MAINTENANCE_USER]` * before its ClickHouse phase (the maintenance sidecars run the same SQL the * other way round, in sh). Polls `buildControlPlaneActivityQuery` until it * reports `active = 0`. * * Returns rather than throws on `timeout` and `aborted`: the runner owns the * verdict, and it fails closed on `timeout` (a still-busy maintenance * identity means an OPTIMIZE or BACKUP is holding the server). A probe that * cannot be read is NOT proof of idleness — `busy` / `unreachable` / * `indeterminate` probe errors are logged and polling continues inside the * budget — including the `Code: 60` a brand-new server returns until its * first log flush creates `system.query_log` (verified on 26.3; the probe's * own queries create it inside one flush interval). A `denied` probe throws * immediately: no amount of waiting grants `SELECT ON system.processes` / * `system.query_log`. */ export declare function awaitControlPlaneIdle(opts: AwaitControlPlaneIdleOpts): Promise;