/**
 * `olam upgrade` CLI lock — atomic create-or-fail with stale-lock recovery.
 *
 * Lock file: ~/.olam/.upgrade.lock
 * Contents:  {"pid": <int>, "startTs": <epoch-ms>}
 *
 * Stale-lock semantics:
 *   - File unreadable / parse error / empty  → stale
 *   - PID not alive (process.kill(pid, 0) throws) → stale
 *   - PID alive but command name is not node/olam → stale (avoids macOS PID-recycling false-positives)
 *   - start_ts > 30 min ago → stale
 *   - Otherwise → live; second invocation refuses with exit 1.
 *
 * Atomicity: fs.openSync(path, 'wx') is atomic create-or-fail per POSIX. No TOCTOU window.
 */
export declare const LOCK_FILE_PATH: string;
export declare const STALE_LOCK_TIMEOUT_MS: number;
export interface LockContent {
    readonly pid: number;
    readonly startTs: number;
}
export type AcquireResult = {
    acquired: true;
    lockPath: string;
} | {
    acquired: false;
    reason: 'live' | 'race';
    existingPid?: number;
    existingStartTs?: number;
};
/**
 * Read lock file contents. Returns null on:
 *   - file missing
 *   - empty file
 *   - JSON parse error
 *   - shape mismatch (missing pid or startTs as numbers)
 *
 * Caller treats null as "stale lock; can recover."
 */
export declare function readLockFile(lockPath: string): LockContent | null;
/** Returns true if process exists, false on ESRCH or any other error. */
export declare function isPidAlive(pid: number): boolean;
/**
 * Sentinel returned when `ps` itself is unavailable (binary missing, fork
 * pressure, container without procfs). Distinct from "ps ran but the PID
 * doesn't exist" which returns the empty string equivalent (null).
 *
 * Callers MUST treat this sentinel as "command unknown — assume live, refuse
 * to recover" so a missing `ps` binary cannot stealth-break the lock.
 *
 * Per audit A1-003: previously returned null on both branches and isStaleLock
 * treated null as stale, which let `ps`-unavailable invocations silently
 * unlink a live olam upgrade's lock and corrupt the docker layer cache.
 */
export declare const PS_UNAVAILABLE = "__ps_unavailable__";
/**
 * Match command names that legitimately hold the upgrade lock.
 *
 * Accepts:
 *   - `node`, `olam`, `olam-cli` (canonical)
 *   - path-prefixed forms (`/usr/local/bin/node` — strip via basename)
 *   - worker-pool suffixed forms (`node (vitest 1)` — strip parenthesized suffix)
 *
 * Rejects unrelated commands (bash, zsh, python3, etc.) so PID recycling to a
 * non-node process correctly classifies the lock as stale.
 */
export declare function isOlamUpgradeCommand(comm: string | null | typeof PS_UNAVAILABLE): boolean;
/**
 * Stale-lock test. Returns true if the lock should be recovered (deleted + retried).
 *
 * Inputs:
 *   - content: parsed lock contents (null → empty/parse-error/missing → stale)
 *   - nowMs: current epoch ms (defaults to Date.now(); injected for test determinism)
 */
export declare function isStaleLock(content: LockContent | null, nowMs?: number): boolean;
/**
 * Atomically acquire the upgrade lock.
 *
 * Returns { acquired: true } on success, or { acquired: false, reason } when a live lock exists.
 * Stale locks are auto-recovered (delete + retry once).
 *
 * Side effects:
 *   - mkdir -p path.dirname(lockPath) (ensures ~/.olam exists)
 *   - writes JSON {pid, startTs} to lockPath
 */
export declare function acquireLock(lockPath?: string, nowMs?: number): AcquireResult;
/** Release the lock. Idempotent — no error if file already removed. */
export declare function releaseLock(lockPath?: string): void;
/** Format a human-readable refusal message for the operator. */
export declare function formatRefusalMessage(result: Extract<AcquireResult, {
    acquired: false;
}>, lockPath?: string): string;
//# sourceMappingURL=upgrade-lock.d.ts.map