/**
 * ReapGuard — the shared, stateless "is it safe to reap this session?" guard.
 *
 * Spec: docs/specs/unified-session-lifecycle-robustness.md §P2.
 *
 * Extracted from `SessionReaper.evaluate()` so that EVERY autonomous killer —
 * not just the careful new reaper — consults the same positive-evidence KEEP
 * checks before a session is ended. The single ReapAuthority
 * (`SessionManager.terminateSession`) calls this guard; a non-null result means
 * the session must be KEPT and the terminate is a no-op.
 *
 * Scope is the STATELESS guards only (deps-driven, no per-tick observation
 * state): protected, spawn-grace, recovery-in-flight, pending-injection,
 * relay-lease, recent-user-message, open-commitment, active-subagent,
 * structural-long-work, active-process, main-process-uninspectable/active.
 *
 * The STATEFUL checks — transcript-growth (compares against the previous tick)
 * and positive-idle proof (needs a captured pane frame) — stay inside
 * `SessionReaper.evaluate()`, which calls this guard FIRST (preserving the exact
 * keptBy ordering) and then layers its own per-instance checks.
 *
 * Ordering is identical to the original `evaluate()` so a refactor preserves
 * every `keptBy` reason for the same input (asserted by the reaper's tests).
 */
import type { Session } from './types.js';
import type { WorkEvidenceName } from './WorkEvidence.js';
export type ReapConfidence = 'high' | 'low';
/** A KEEP verdict: the reason a session must not be reaped, + a confidence. */
export interface ReapKeepReason {
    reason: string;
    confidence: ReapConfidence;
}
/**
 * Signal sources for the stateless guards. A subset of `SessionReaperDeps` so a
 * single set of closures (built once in server.ts) can back both the reaper and
 * the authority. Every "cannot inspect" path resolves to KEEP, never to reap.
 */
export interface ReapGuardDeps {
    protectedSessions: () => string[];
    isRecoveryActive: (session: Session) => boolean;
    hasPendingInjection: (tmuxSession: string) => boolean;
    isRelayLeaseActive: (sessionId: string) => boolean;
    topicBinding: (tmuxSession: string) => number | null;
    recentUserMessage: (topicId: number, withinMs: number) => boolean;
    activeCommitmentForTopic: (topicId: number) => boolean;
    activeSubagentCount: (claudeSessionId: string | undefined) => number;
    buildOrAutonomousActive: (topicId: number | null) => boolean;
    hasActiveProcesses: (tmuxSession: string) => boolean;
    /** Optional main-process CPU/IO liveness. `undefined` ⇒ cannot inspect ⇒ KEEP. */
    mainProcessActive?: (tmuxSession: string) => boolean | undefined;
    now?: () => number;
}
export interface ReapGuardOptions {
    /** Minimum session age before reap-eligibility (spawn grace). 0 disables. */
    minAgeMs: number;
    /** Window for "recent user message" on a bound topic. */
    recentUserWindowMs: number;
    /** Whether an open commitment on the bound topic blocks reaping. */
    protectOpenCommitments: boolean;
    /**
     * Staleness horizon for the open-commitment veto. An open commitment protects a
     * session ONLY while there has been a user message within this window; past it
     * the commitment is treated as abandoned and no longer blocks reaping. A
     * commitment left open for days on a session the user hasn't touched is itself
     * stale — without this bound it pins the dead session forever (the dominant
     * keep-reason behind un-reaped idle sessions). Set to `Infinity` to restore the
     * always-protect behavior. Must be ≥ `recentUserWindowMs`.
     */
    staleCommitmentWindowMs: number;
}
export declare const DEFAULT_REAP_GUARD_OPTIONS: ReapGuardOptions;
export declare class ReapGuard {
    private readonly deps;
    private readonly opts;
    private readonly now;
    constructor(deps: ReapGuardDeps, opts?: Partial<ReapGuardOptions>);
    /**
     * Returns the reason this session must be KEPT (not reaped), or `null` if all
     * stateless guards clear. Cheap-first ordering: in-memory checks before any
     * subprocess fork (process tree / main-process probe) so a guarded session is
     * rejected without paying for a capture.
     *
     * Never throws — a thrown signal source is caught and resolved to KEEP
     * ('guard-error'), because we can never reason about a session we cannot
     * inspect, and the safe answer is always "keep".
     */
    blockedReason(session: Session): ReapKeepReason | null;
    private evaluate;
    /**
     * Observe-only work-evidence collection (reap-notify spec R2.1) — the
     * CHOKEPOINT FALLBACK when the killer supplied no evidence of its own.
     *
     * Documented expected-empty for guard-cleared kills: an autonomous kill
     * reaches the chokepoint's body only when these same closures returned
     * nothing (or a named bypass fired), so a re-run here usually proves
     * nothing — real evidence comes from the killer at its decision point.
     *
     * Differences from `blockedReason` (deliberate, per spec):
     *  - Collects ALL work-POSITIVE signals, not first-hit; protected /
     *    spawn-grace / recovery-in-flight are not work evidence and are skipped.
     *  - A closure that throws contributes NOTHING (no keep-true fail-safe:
     *    "cannot inspect ⇒ keep" is correct for blocking a kill, wrong for
     *    asserting work happened).
     *  - With `skipForkChecks` (pressure tier critical), the fork-based
     *    closures (process tree, main-process probe) are not run and the
     *    `unverified-under-pressure` marker is stamped instead — a record
     *    that verification was skipped, never resume-eligible.
     */
    workEvidence(session: Session, opts?: {
        skipForkChecks?: boolean;
    }): WorkEvidenceName[];
}
//# sourceMappingURL=ReapGuard.d.ts.map