import type { LeaseCapableStorage } from '../storage/storage-provider.js';
/**
 * The lock name the pruner asks for. Deliberately a single fleet-wide constant:
 * every replica pruning the SAME store is exactly the set that should contend,
 * and two apps sharing one Telescope store share the entries table too, so they
 * should share the lock as well.
 */
export declare const PRUNE_LOCK_KEY = "telescope:prune";
/**
 * A lease that was successfully acquired. Handed back inside a
 * {@link TelescopePruneLockResult} so it is unreachable without first checking
 * that the acquire actually succeeded.
 */
export interface TelescopePruneLease {
    /** The lock name this lease covers. */
    readonly key: string;
    /** The holder identity that was granted the lease. */
    readonly owner: string;
    /**
     * Epoch-ms after which the lease is considered abandoned and MAY be granted to
     * somebody else, even if `release` was never called. This is what makes a pod
     * that dies mid-prune cost one TTL of fleet-wide silence rather than forever.
     */
    readonly expiresAtMs: number;
    /**
     * Gives the lease up early. MUST be idempotent, MUST NOT throw (swallow and,
     * at most, log — the TTL is the backstop), and MUST NOT release the lease if
     * this holder's lease already expired and was re-granted to somebody else.
     */
    release(): Promise<void>;
}
/**
 * The result of an acquire attempt, as a discriminated union so the `lease` is
 * unreachable until `acquired` has been narrowed — a caller physically cannot
 * forget to handle "somebody else has it".
 *
 * `reason` distinguishes the two failures because the pruner treats them
 * OPPOSITELY:
 *  - `'held'` — a healthy, expected outcome. Another replica is pruning; this
 *    one stands down silently and tries again next tick.
 *  - `'unavailable'` — the lock MECHANISM failed (backend down, table missing,
 *    permissions). The pruner then FAILS OPEN and prunes anyway, because a
 *    broken lock must not become "retention silently stops and the table grows
 *    without bound". Failing open is at worst today's behaviour.
 *
 * Getting this distinction wrong in a host implementation is the one thing that
 * really hurts, so: if you cannot tell the two apart, return `'unavailable'`.
 */
export type TelescopePruneLockResult = {
    readonly acquired: true;
    readonly lease: TelescopePruneLease;
} | {
    readonly acquired: false;
    readonly reason: 'held' | 'unavailable';
    /** Optional human-readable detail, surfaced in the pruner's log line. */
    readonly detail?: string;
};
/** The request handed to {@link TelescopePruneLock.acquire}. */
export interface TelescopePruneLockRequest {
    /** Lock name. Always {@link PRUNE_LOCK_KEY} today; an object so it can grow. */
    readonly key: string;
    /**
     * Identity of the process asking. Stable for the life of the process and
     * distinct between replicas (the pruner uses `<instanceId>#<pid>`).
     */
    readonly owner: string;
    /**
     * How long the lease should survive without a release. The pruner derives it
     * from `prune.lockTtlMs`, defaulted to three prune intervals, so a crashed
     * holder costs at most that much fleet-wide silence.
     */
    readonly ttlMs: number;
}
/**
 * The seam. Implement this in the host to back Telescope's prune lock with a
 * primitive you already run — e.g. a durable-workflow singleton mutex, a Redis
 * SET NX PX, a Postgres advisory lock.
 *
 * ## Contract (all of it)
 *
 * 1. `acquire` MUST NOT throw. Return `{ acquired: false, reason: 'unavailable' }`
 *    instead. (The pruner catches a throw and treats it as `'unavailable'`
 *    anyway, but a host that relies on that is relying on a backstop.)
 * 2. `acquire` MUST be atomic across processes for a given `key`: at most one
 *    caller gets `acquired: true` while a lease is live.
 * 3. A lease MUST expire on its own after roughly `ttlMs` even if `release` is
 *    never called. A holder that is SIGKILLed must not wedge the fleet.
 * 4. Re-acquiring with the SAME `owner` while that owner still holds the lease
 *    SHOULD succeed (refresh), so a restart with a stable identity is not locked
 *    out by its own previous lease.
 * 5. `lease.release()` MUST be idempotent, MUST NOT throw, and MUST NOT release a
 *    lease that has since been granted to a different owner.
 * 6. Precision is NOT required. Clock skew, an expiry that fires while the
 *    holder is still working, two simultaneous winners — all acceptable. The
 *    lock is advisory; the only cost of getting it wrong is a duplicated delete.
 * 7. `acquire` SHOULD return promptly and MUST NOT block waiting for the lock.
 *    The pruner treats "held" as "skip this tick", not "queue up" — waiting
 *    would rebuild the pile-up this whole change exists to remove.
 *
 * ## What the pruner does with it
 *
 * Once per cycle, before any delete: `acquire({ key, owner, ttlMs })`. On
 * `'held'` the cycle is skipped entirely (no `PruneRun` is recorded, `pruneNow()`
 * resolves `0`). On `'unavailable'` the cycle runs unlocked and one warning is
 * logged per streak. On success the cycle runs and the lease is released in a
 * `finally`, whether the cycle succeeded, failed, or threw.
 *
 * @example a host implementation over a singleton-mutex job engine
 * ```ts
 * class DurablePruneLock implements TelescopePruneLock {
 *   constructor(private readonly durable: DurableClient) {}
 *   async acquire({ key, owner, ttlMs }: TelescopePruneLockRequest) {
 *     try {
 *       const started = await this.durable.tryStartSingleton(key, { ttlMs, owner });
 *       if (!started) return pruneLockHeld('another pod holds the singleton');
 *       return pruneLockAcquired({
 *         key,
 *         owner,
 *         expiresAtMs: Date.now() + ttlMs,
 *         release: () => this.durable.finishSingleton(key, owner).catch(() => undefined),
 *       });
 *     } catch (error) {
 *       return pruneLockUnavailable(String(error));
 *     }
 *   }
 * }
 * ```
 */
export interface TelescopePruneLock {
    acquire(request: TelescopePruneLockRequest): Promise<TelescopePruneLockResult>;
}
/** Builds the success arm of a {@link TelescopePruneLockResult}. */
export declare function pruneLockAcquired(lease: TelescopePruneLease): TelescopePruneLockResult;
/** Builds the "somebody else has it" arm — the healthy, expected refusal. */
export declare function pruneLockHeld(detail?: string): TelescopePruneLockResult;
/** Builds the "the lock mechanism itself failed" arm — the pruner fails OPEN on this. */
export declare function pruneLockUnavailable(detail?: string): TelescopePruneLockResult;
/**
 * The DEFAULT prune lock: a lease row in the store Telescope is already using.
 *
 * Telescope always has a database — that is where the entries live — so a lease
 * with an owner and an expiry costs no new dependency, no new deployment
 * concern, and works on every provider that implements the lease SPI. A holder
 * that dies never releases; the expiry is what reclaims it, which is why the SPI
 * takes a TTL rather than a plain "locked" flag.
 *
 * Used automatically when the configured provider {@link isLeaseCapableStorage}
 * and the host supplied no `prune.lock` of its own.
 */
export declare class StorageLeasePruneLock implements TelescopePruneLock {
    private readonly storage;
    constructor(storage: LeaseCapableStorage);
    acquire(request: TelescopePruneLockRequest): Promise<TelescopePruneLockResult>;
}
//# sourceMappingURL=prune-lock.d.ts.map