/**
 * PrHandLease — per-branch ownership lease so two of the agent's OWN concurrent
 * sessions ("hands") cannot push competing commits to the same branch.
 *
 * Spec: docs/specs/parallel-hand-pr-lease.md (review-convergence + approved).
 * Closes the push-competition cause of the 2026-06-15 PR #1183 merge thrash.
 *
 * Design anchors (from the converged spec):
 *  - Identity = holderTopicId (stable across the constant session respawns);
 *    holderSessionId is a LIVENESS-PROBE HANDLE ONLY, never the identity (§3.2/B2).
 *  - ONE process-wide lock guards the single JSON file (matching ResumeQueue's
 *    single lockPath); per-record compare-and-swap under that lock (§3.3/B3/M-D).
 *  - Liveness probe is TTL-gated (only when a lease is past ttl) and reads the
 *    in-memory running set — never a tmux exec (§3.5/M4).
 *  - A holder on a DIFFERENT machine is NEVER judged dead from local session
 *    absence (§3.5/M6); foreign-machine holder → fail-closed → treat as live.
 *  - maxHold ceiling is liveness-discriminated: a DEAD/foreign-unverified holder
 *    past the ceiling is CAS-seized + attention; a LIVE same-machine holder past
 *    the ceiling is NOT seized — the caller escalates instead (§3.9/codex#3).
 *  - Unreadable/corrupt/absent state file → fail-OPEN (treat as no lease) (§3.4/M10).
 *  - dryRun leases carry dryRun:true; non-acquisition readers MUST ignore them (§5).
 *
 * This module is the STORE + the pure key-derivation helpers. The PreToolUse Bash
 * hook (pr-hand-lease-guard.js) and the GET /pr-leases route consume it. Enforcement
 * is the hook's (the real chokepoint where agent `git push` runs); SafeGitExecutor
 * carries the same check as defense-in-depth for instar's own internal pushes.
 */
export type LeaseIntent = 'build' | 'rework' | 'merge';
export type LeaseTombstoneStatus = 'released' | 'merged' | 'abandoned';
export interface PrHandLeaseRecord {
    /** STABLE logical owner — the conversation topic. Survives session respawn. */
    holderTopicId: number;
    /** Liveness-probe handle ONLY (tmux session name). NEVER the identity. */
    holderSessionId: string;
    /** Load-bearing for the never-falsely-dead rule (§3.5/M6). */
    holderMachineId: string;
    /** Secondary metadata once the PR exists; never the primary key. */
    prNumber?: number | null;
    intent: LeaseIntent;
    acquiredAt: number;
    renewedAt: number;
    ttlMs: number;
    maxHoldMs: number;
    /** Observe-only soak: a dryRun lease MUST be ignored by all non-acquisition readers. */
    dryRun?: boolean;
    /** Set only on a tombstoned (released) record; a tombstone is never a live holder. */
    tombstone?: {
        status: LeaseTombstoneStatus;
        at: number;
    };
}
/** Derived liveness reported by GET /pr-leases — honest, computed, never the raw record. */
export type DerivedLeaseLiveness = 'live' | 'stale-dead' | 'stale-ttl' | 'tombstoned' | 'foreign-machine';
export interface LeaseEvalResult {
    /** What the push chokepoint should do. */
    decision: 'allow' | 'deny' | 'escalate';
    reason: string;
    holder?: PrHandLeaseRecord;
}
export interface PrHandLeaseDeps {
    stateDir: string;
    machineId: string;
    /** Returns the tmux session names currently running (in-memory; no tmux exec). */
    runningSessionNames: () => string[];
    /** ms clock (injectable for tests). */
    now?: () => number;
    /** Emitted on a forced release / fail-open recurrence for the attention surface. */
    onAttention?: (item: {
        kind: string;
        detail: string;
    }) => void;
    /** Structured audit sink — one row per acquire/renew/yield/auto-heal/release. */
    onAudit?: (row: Record<string, unknown>) => void;
}
/**
 * Canonicalize a `git push` command's destination ref to `refs/heads/<name>`.
 * Two-tier (§3.1): explicit refspec / local @{push} fast path (no network);
 * `git push --dry-run --porcelain` only for the ambiguous case, timeout-bounded.
 * Returns null when no branch key is derivable (detached, tag, delete, resolver
 * error/timeout) → the caller FAILS OPEN (does not gate).
 */
export declare function canonicalPushKey(command: string, cwd: string, opts?: {
    execTimeoutMs?: number;
}): string | null;
export declare class PrHandLease {
    private readonly d;
    private readonly filePath;
    private readonly lockPath;
    private readonly now;
    private failOpenCounts;
    constructor(deps: PrHandLeaseDeps);
    /** The path the BackupManager denylist must exclude (ephemeral, never restored). */
    static stateFileRelPath(): string;
    /**
     * Evaluate a push against the lease for `key` (from canonicalPushKey).
     * Returns the chokepoint decision. NEVER throws — any internal error resolves
     * to allow (the hook's own-crash fail-open is the outer guarantee; this is the
     * inner one).
     */
    evaluate(key: string, myTopicId: number, mySessionId: string): LeaseEvalResult;
    /** Acquire or renew the lease for `key` for my (topic, session, machine). Atomic CAS under the lock. */
    acquireOrRenew(key: string, holder: {
        topicId: number;
        sessionId: string;
        intent?: LeaseIntent;
        prNumber?: number | null;
        dryRun?: boolean;
    }): PrHandLeaseRecord;
    /**
     * Atomic-CAS takeover of a stale lease (§3.3/B3). Writes my record only if the
     * on-disk record still matches the observed (holderTopicId, acquiredAt). Returns
     * the new record on success, or null if another hand healed first (loser yields).
     */
    takeOverIfStale(key: string, observed: {
        holderTopicId: number;
        acquiredAt: number;
    }, holder: {
        topicId: number;
        sessionId: string;
        intent?: LeaseIntent;
        prNumber?: number | null;
        dryRun?: boolean;
    }): PrHandLeaseRecord | null;
    /** Release my lease (terminal status), tombstoned briefly to guard the re-grab race (§3.7). */
    release(key: string, topicId: number, status: LeaseTombstoneStatus): void;
    /** Read view for GET /pr-leases — each record + its DERIVED (honest) liveness. */
    list(): Array<PrHandLeaseRecord & {
        key: string;
        liveness: DerivedLeaseLiveness;
    }>;
    private derivedLiveness;
    /**
     * Raw same-machine liveness probe: is the holder's tmux session in the
     * in-memory running set? Same key at write + lookup (M-C). On probe error,
     * fail toward LIVE (yield) — uncertainty about the probe must not seize a
     * holder; the caller's past-ceiling path handles a persistently-wedged probe.
     */
    private isSessionRunning;
    private acquireUnlocked;
    /** Read with fail-OPEN on corrupt/absent (§3.4 step 1 / M10). */
    private readAll;
    private readAllUnlocked;
    private pruneExpiredTombstones;
    private persist;
    /** One process-wide lock (matching ResumeQueue's single lockPath); 'wx' first-writer-wins. */
    private withLock;
    private recordFailOpen;
    private attention;
    private audit;
}
//# sourceMappingURL=PrHandLease.d.ts.map