/**
 * LeaseCoordinator — drives the FencedLease over the durable (git) and fast
 * (tunnel) media, and owns the lifecycle of acquisition / renewal / fencing /
 * escalation. Spec §6 + §8 G1.
 *
 * Correctness substrate is GIT: acquisition is a compare-and-swap implemented
 * as write-then-push-or-reject-reread plus epoch monotonicity (git has no
 * native CAS). The TUNNEL is an optional low-latency accelerator: it can raise
 * the observed epoch (speeding acquisition) but can NEVER lower it below the
 * git-committed floor, and a replayed/below-floor tunnel message is dropped by
 * FencedLease.acceptTunnelLease. If the tunnel is unavailable the system
 * degrades to git-only — correct, just bounded by git cadence — which is why
 * Phase-0 pairing worked over git alone.
 *
 * Renewal requires the tunnel medium when one is configured: a holder that
 * cannot renew for > leaseTtlMs MUST self-suspend ingress regardless of its
 * local clock (closes the tunnel-down / git-up split-authority window).
 *
 * The store/transport are injected so the dangerous CAS-contention and
 * self-suspend logic are unit-testable with in-memory fakes.
 */
import { FencedLease } from './FencedLease.js';
import type { LeaseRecord } from './types.js';
/** Durable (git-backed) view + CAS write of the lease. */
export interface LeaseStore {
    /** Read the current committed lease + its epoch (0 if none). */
    read(): {
        lease: LeaseRecord | null;
        epoch: number;
    };
    /**
     * Attempt to commit `candidate` as the new lease. Implements the CAS:
     * returns ok:true if the candidate landed (fast-forward push accepted), or
     * ok:false + the freshly-observed lease/epoch after a reject+reread so the
     * caller can re-evaluate. MUST NOT force-push.
     */
    casWrite(candidate: LeaseRecord): {
        ok: boolean;
        observed: {
            lease: LeaseRecord | null;
            epoch: number;
        };
    };
    /**
     * Refresh the SAME-epoch lease's expiry durably (renewal, not acquisition).
     * Returns true if the refresh was confirmed over the durable medium (push
     * succeeded). A holder that cannot refresh (partitioned) must self-suspend —
     * this is the git-medium equivalent of the tunnel-renewal requirement, and
     * it is what prevents a partitioned old-awake from extending its lease
     * locally forever (the split-brain). Optional: a tunnel-backed deployment
     * confirms over the tunnel instead and may no-op this.
     */
    refresh(lease: LeaseRecord): boolean;
    /**
     * Force the local self-lease to read as expired (git-less relinquish, spec
     * §Problem A). Used by LeaseCoordinator.relinquish() to break a same-epoch
     * contested tie WITHOUT lowering the epoch floor. Optional: only the git-less
     * LocalLeaseStore implements it; a git-substrate store resolves contention via
     * CAS instead, so this is a no-op there.
     */
    forceLocalExpiry?(): void;
}
/** Optional low-latency tunnel transport for the lease. */
export interface LeaseTransport {
    /** Broadcast our lease to peers. Resolves false if unreachable. */
    broadcast(lease: LeaseRecord): Promise<boolean>;
    /** The most-recent lease observed over the tunnel (and its source nonce map). */
    observed(): {
        lease: LeaseRecord | null;
        lastNonceByHolder: Record<string, number>;
    };
    /** Whether the tunnel medium is currently reachable. */
    isReachable(): boolean;
    /**
     * Cross-Machine Coherence — active PULL of a single peer's current lease over
     * the authenticated channel, folding the result into observed() via the same
     * receive path. Optional: a git-only mesh has no pull-capable transport.
     */
    pullPeer?(peer: {
        machineId: string;
        url: string;
    }): Promise<LeaseRecord | null>;
    /** Best-effort fan-out pull of every peer's lease. Optional (see pullPeer). */
    pullAllPeers?(): Promise<void>;
}
export interface LeaseCoordinatorDeps {
    lease: FencedLease;
    store: LeaseStore;
    tunnel?: LeaseTransport;
    /** Machines presumed dead (lastSeen older than failoverThresholdMs). */
    presumedDeadHolders: () => ReadonlySet<string>;
    /**
     * Wall clock (injectable for tests). Used ONLY to stamp human-readable
     * `acquiredAt`/`expiresAt` ISO fields on lease records (display + the
     * liveness heuristic). It is NEVER the authority for whether THIS machine
     * still holds the lease — see `monotonicNow` (spec §L−1: a holder's own
     * expiry is judged on its monotonic-local clock, never wall-clock).
     */
    now?: () => number;
    /**
     * Monotonic clock (injectable for tests) — the AUTHORITY for the holder's
     * self-expiry / self-fence. Returns a strictly non-decreasing millisecond
     * reading (default `performance.now()`) that an NTP step, a VM pause/resume,
     * a sleep/wake, or a CPU-starvation clock jump CANNOT move backward. The
     * router-lease self-fence (spec §L−1 "TTL self-fence" + §L1) measures
     * "elapsed since my last confirmed renewal" on THIS clock, so a partitioned
     * or clock-chaotic holder goes quiet on its own reading before the TTL
     * elapses — independent of wall-clock. This is the LEASE-SUBSTRATE-ROBUSTNESS
     * fold-in and directly answers the SleepWakeDetector CPU-starvation lesson.
     */
    monotonicNow?: () => number;
    /** Escalate an unresolvable split-brain (deduped per partitionEpisodeId by the caller's sink). */
    onEscalate?: (info: {
        partitionEpisodeId: string;
        holder: string;
        reason: string;
    }) => void;
    /** Fired when the holder must self-suspend ingress (tunnel-renewal lapse). */
    onSelfSuspend?: (reason: string) => void;
    /** Fired whenever our effective epoch advances (drives leaseEpochChange → registry push). */
    onEpochAdvance?: (epoch: number) => void;
    logger?: (msg: string) => void;
}
export declare class LeaseCoordinator {
    private readonly d;
    private readonly fl;
    private nonceCounter;
    /**
     * Monotonic-clock reading at the last CONFIRMED renewal/acquisition — the
     * authority for the holder's self-fence (spec §L−1): an NTP step / VM pause /
     * sleep / CPU-starvation jump cannot move it backward, so a partitioned or
     * clock-chaotic holder still goes quiet on its own monotonic reading before
     * the TTL elapses. (Wall-clock is used only for the display `expiresAt`.)
     */
    private lastRenewOkMonoMs;
    private lastObservedEpoch;
    private suspended;
    /**
     * The freshest lease THIS machine has signed (acquisition or renewal). It is
     * the authoritative low-latency copy of our own holding — we broadcast it
     * over the tunnel, but git is only updated coarsely (on epoch change), so a
     * renewal's new expiry lives here, not in git. Folded into effectiveView only
     * while it is not superseded by a higher epoch.
     */
    private selfIssued;
    constructor(deps: LeaseCoordinatorDeps);
    private now;
    /**
     * Monotonic clock — strictly non-decreasing, immune to wall-clock jumps (NTP
     * step, VM pause/resume, sleep/wake, CPU-starvation timer slip). Default uses
     * `process.hrtime`. The authority for the holder's self-expiry/self-fence
     * (spec §L−1); injectable for tests.
     */
    private monotonicNow;
    /**
     * Record a CONFIRMED renewal/acquisition on the monotonic self-fence clock.
     * Called wherever a renewal/acquisition/broadcast is confirmed over a medium.
     */
    private markRenewOk;
    private log;
    private nextNonce;
    get selfMachineId(): string;
    get isSuspended(): boolean;
    /**
     * Compute the current effective epoch = max(tunnel-observed accepted, git).
     * A tunnel lease is folded in only if acceptTunnelLease passes (valid sig,
     * ≥ git floor, fresh nonce).
     */
    private effectiveView;
    /** Does THIS machine currently hold a valid lease at the effective epoch? */
    holdsLease(): boolean;
    /** The current effective epoch (for stamping writes/sends). */
    currentEpoch(): number;
    currentHolder(): string | null;
    /**
     * The current effective-view signed lease (max of tunnel-observed, git-committed,
     * and this machine's self-issued renewal), or null. Used to SERVE an active PULL
     * (POST /api/lease/pull, Cross-Machine Coherence): a peer asks for our lease and
     * we return this. Includes the holder's self-issued lease (which the transport's
     * observed() — receive-only — does not), so a holder serves its own current lease.
     */
    currentLease(): LeaseRecord | null;
    /**
     * Relinquish this machine's claim to break a same-epoch contested tie (spec
     * §Problem A — the deterministic loser, lower-`machineId` LOSES is FALSE: the
     * lower machineId WINS, the higher relinquishes). Two effects, both required
     * for convergence:
     *  (a) clears `selfIssued` AND forces the local store's persisted self-lease to
     *      read as expired — so we stop being a "live holder at epoch N": the
     *      winner's `canAcquire()` no longer returns `held-by-live-peer` (it can
     *      now advance to N+1) and our `holdsLease()` returns false (we reconcile
     *      to standby);
     *  (b) we then ADOPT the winner's N+1 lease via the strict-`>` tunnel fold in
     *      effectiveView() (N+1 > N), so currentHolder() names the winner.
     * Idempotent. The caller (MultiMachineCoordinator's contested branch) latches
     * this ONE-SHOT per contested episode so we do not re-clear+re-acquire every
     * tick (which would re-introduce the leapfrog). The epoch FLOOR is preserved
     * (forceLocalExpiry keeps the committed epoch) so a replayed stale lease can't
     * win after we relinquish.
     */
    relinquish(): void;
    /**
     * Force a ONE-TIME epoch advance to resolve a same-epoch contested tie (spec
     * §Problem A — the WINNER side, lower `machineId`). Unlike acquireIfEligible(),
     * which RENEWS the same epoch when we already hold it, this builds the NEXT
     * epoch (N+1) and CAS-writes it, establishing a strictly-higher signed lease
     * that the contested peer (the loser, having relinquished) adopts via the
     * strict-`>` tunnel fold. The caller latches this ONE-SHOT per contested
     * episode (NOT per tick), so it is a tie-resolution, never a per-tick bump →
     * no leapfrog. Routes through the SAME casWrite/broadcast/sign path as a normal
     * acquisition. Returns true if the advance landed (or we already hold a
     * strictly-higher epoch). Idempotent against a peer that advanced first.
     */
    advanceEpochForContestedWin(): Promise<boolean>;
    /**
     * Whether the attached transport can actively PULL peer leases (Cross-Machine
     * Coherence). False on a git-only mesh — the standby pull loop is then a no-op.
     */
    canPullPeers(): boolean;
    /**
     * Active-pull every peer's current lease over the tunnel and fold the freshest
     * into our observed view (the transport's recordObserved path). Safe no-op when
     * the transport has no pull capability. Awaiting this then reading holdsLease()/
     * observedPeerLease() reflects whatever a peer just disclosed.
     */
    pullFromPeers(): Promise<void>;
    /**
     * The RAW lease most-recently observed from a peer (push or pull), independent
     * of our own self-issued/git view. effectiveView()'s max() masks a *same-epoch*
     * peer (our self-issued wins the tie), so the standby pull loop reads this to
     * detect a same-epoch contested split-brain that currentHolder() would hide.
     */
    observedPeerLease(): LeaseRecord | null;
    /**
     * Whether the most-recently OBSERVED peer lease genuinely SUPERSEDES ours and
     * may therefore drive a pull-loop demotion. True ONLY when that peer lease is
     * LIVE (not expired) AND at a STRICTLY HIGHER epoch than our own self-issued
     * lease.
     *
     * Why this stricter gate exists (live incident 2026-06-02, the real laptop+mini
     * pair): the pull loop demoted on ANY non-null `observedPeerLease()`. A standby
     * peer kept disclosing a 2-DAY-EXPIRED, epoch-150 lease (vs our live epoch-1400+),
     * which satisfied that loose gate — so every time our own ~60s lease lapsed
     * transiently between renewals, the legitimate holder flipped to read-only
     * standby (~50% of the time), blocking real writes (#673 caught the crash but the
     * writes still failed). A stale/expired or lower-or-equal-epoch peer carries no
     * fencing authority and must NEVER demote a legitimate holder; that holder's
     * transient self-lapse is re-acquired by `tickLease`. Same-epoch contention is a
     * separate concern handled by the contested-split-brain resolver, not here.
     *
     * NOTE: compare against `selfIssued.epoch` (our OWN lease), NOT `currentEpoch()`
     * — the latter is `effectiveView().epoch = max(self, observed-peer)`, so it would
     * already fold the peer in and make `peer.epoch > currentEpoch()` impossible.
     */
    peerLeaseSupersedes(): boolean;
    /**
     * Attempt to acquire (or self-renew) the lease if eligible. Returns true if
     * THIS machine holds the lease afterward. Implements the bounded-retry CAS
     * with livelock backoff.
     */
    acquireIfEligible(): Promise<boolean>;
    /**
     * Consented planned-handoff acquisition (spec §8 G3e). The incoming machine
     * takes the lease while the OUTGOING is still alive — which the liveness-gated
     * acquireIfEligible() correctly refuses. This path is reachable ONLY from the
     * onYield handler, which fires only on an authenticated POST /api/handoff/yield
     * after the outgoing verified the ack + passed validation. The existing
     * split-brain-critical canAcquire() gate is deliberately left UNTOUCHED — this
     * is an additive consent path, not a weakening of the liveness rule.
     *
     * Security guard: the yielding machine MUST be the holder we currently observe.
     * A yield from any non-holder is refused, so a forged/misdirected yield cannot
     * trigger a takeover. On success: CAS to epoch+1, broadcast, emit the new epoch.
     */
    acquireOnConsent(yieldFromMachineId: string): Promise<boolean>;
    /**
     * Renew the held lease: re-sign with a fresh expiry, broadcast over the
     * tunnel, and (coarsely, via the store on epoch change) keep git current.
     * Returns false (and self-suspends) if the tunnel medium is configured but
     * has been unreachable for longer than the lease TTL.
     */
    renew(): Promise<boolean>;
    private broadcast;
    private emitEpoch;
    /**
     * Detection (signal only): does the synced state show contention the lease
     * cannot resolve (e.g. a presumed-dead holder we cannot demote because no
     * shared medium can advance the epoch)? Escalates ONCE per partition episode.
     */
    checkForUnresolvableSplit(partitionEpisodeId: string): void;
}
//# sourceMappingURL=LeaseCoordinator.d.ts.map