import { type OnApplicationBootstrap, type OnApplicationShutdown } from '@nestjs/common';
import type { ResolvedCoreConfig } from '../config/options.js';
import type { StorageProvider } from '../storage/storage-provider.js';
/** What kicked off a prune cycle: the interval timer, or an on-demand request. */
export type PruneTrigger = 'scheduled' | 'manual';
/**
 * One recorded prune cycle, kept in an in-memory ring buffer on the pruner so
 * the dashboard's Prunes screen can show retention activity. Like the
 * server-stats history ring this is PER-POD (each replica records its own
 * cycles); prune runs are deliberately NOT stored as telescope entries — they
 * would be pruned themselves and add write load to the very store retention is
 * meant to shrink.
 */
export interface PruneRun {
    /** ISO timestamp when the cycle started. */
    at: string;
    trigger: PruneTrigger;
    /** Wall-clock duration of the whole cycle. */
    durationMs: number;
    /** Total entries deleted across the bulk delete and every per-type scope. */
    deletedTotal: number;
    /**
     * Real per-type delete counts for the individually-handled scopes (entry
     * types with a `perType` override or an archived type, each pruned in its own
     * scope at its own cutoff). The global bulk delete spans every other type and
     * returns a single aggregate count from the storage SPI, so it is folded into
     * `deletedTotal` only — it is never attributed to a fabricated type key here.
     */
    deletedByType: Record<string, number>;
    /** Entries handed to the archive sink before deletion this cycle, if any. */
    archivedTotal?: number;
    /** First step error message captured this cycle (steps still swallow + log). */
    error?: string;
}
export declare class TelescopePruner implements OnApplicationBootstrap, OnApplicationShutdown {
    private readonly config;
    private readonly storage;
    private readonly logger;
    private timer;
    /**
     * Latches once after the FIRST time we fall back from a missing
     * `pruneScoped` to the global `prune`, so a third-party provider without
     * per-type support logs the capability warning a single time, not every tick.
     */
    private warnedNoScopedPrune;
    /** Recent prune cycles, newest-first, capped at {@link MAX_PRUNE_RUNS}. */
    private readonly runs;
    /** Start time (epoch ms) of the most recent SCHEDULED cycle, for nextRunAt. */
    private lastScheduledRunAtMs;
    constructor(config: ResolvedCoreConfig, storage: StorageProvider);
    onApplicationBootstrap(): void;
    /**
     * Run ONE prune cycle on demand (the dashboard's "Prune now" button → the
     * controller's `retention/prune` route), recording it as a `manual` run.
     * Returns the total number of entries deleted. Throws only if `prune` is
     * unconfigured — the caller (controller) gates that and the mutation guard.
     */
    pruneNow(): Promise<number>;
    /** Recent prune runs (newest-first), copied so callers can't mutate the ring. */
    getRuns(): PruneRun[];
    /**
     * Predicted next SCHEDULED prune time (epoch ms), or null when no `prune`
     * window is configured. Derived from the last scheduled run's start + the
     * interval, falling back to now + interval before the first cycle has run.
     */
    getNextRunAtMs(): number | null;
    onApplicationShutdown(): void;
    /**
     * One prune tick. The retention model is:
     *  - Each type that needs INDIVIDUAL handling — one with a `perType` override
     *    OR an archived type (which must be exported before its own delete) — is
     *    pruned in its OWN scope, at its own cutoff (its `perType` value, else the
     *    global `after`), with archiving (when configured) first.
     *  - Every OTHER type is pruned in a single bulk delete at the global cutoff,
     *    with the individually-handled types carved out.
     *
     * Archived types are ALWAYS carved out of the bulk delete even with no `perType`
     * override, so a failed sink can spare them (the bulk delete would otherwise
     * wipe entries the sink never saw). With no overrides and no archive (the common
     * case) the individual set is empty and this collapses to exactly one global
     * `prune(cutoff, keepLast)` — identical to the historical behaviour.
     */
    private runCycle;
    /**
     * Append a run to the newest-first ring buffer, evicting the oldest past the
     * cap. Recording must NEVER throw into the prune path (a bad ISO/serialization
     * would otherwise turn observability into an outage), so it is fully guarded.
     */
    private recordRun;
    /**
     * Archives (if configured) the entries this `scope` is about to delete, then
     * deletes them. When the scope targets a single archived `type` whose sink
     * fails, the delete is SKIPPED (entries survive to retry next cycle) but the
     * caller's other scopes are unaffected. Errors never propagate out of here.
     *
     * `fallbackOlderThan`/`fallbackKeepLast` are used only by the legacy global
     * fallback path when the provider lacks `pruneScoped`.
     */
    private pruneArchivedThenDelete;
    /**
     * Exports the doomed entries for an archived single-type scope to the sink in
     * bounded batches. `proceed` is `true` when it is safe to delete (nothing to
     * archive, archiving succeeded, or this type/scope is not archived) and
     * `false` when the sink failed (skip the delete this cycle); `archived` is the
     * number of entries actually handed to the sink.
     */
    private archiveScope;
    /**
     * Deletes the scope via the provider's `pruneScoped` when available, else
     * falls back ONCE (with a logged warning) to the legacy global `prune` — which
     * uses the global cutoff for ALL types, the best a provider without per-type
     * support can do. The fallback runs only for the global scope to avoid
     * deleting more than intended on a per-type scope.
     */
    private deleteScope;
}
//# sourceMappingURL=telescope-pruner.service.d.ts.map