/**
 * Pure rotation-policy gate for the Claude bridge watcher.
 *
 * Lives outside `worker.ts` so tests can import without dragging worker-level
 * fs / IPC side-effects.
 */
export type PidFollowResult = 'unavailable' | 'same' | 'switched';
/**
 * Decide whether `bridgeIngest` should fall through to the directory-mtime
 * `maybeFollowQuietRotation` heuristic this tick.
 *
 * Inputs:
 *   - `pidFollow`: result of the authoritative pid-state probe
 *     (`maybeFollowSessionRotationViaPid`). `'switched'` means it already
 *     moved us; `'same'` means the pid file's `sessionId` matches our
 *     current path; `'unavailable'` means the pid file was unreadable
 *     (non-Linux, no `~/.claude/sessions/<pid>.json`, validation failure).
 *   - `switched`: whether ANY earlier rotation step (pid resolver OR
 *     fingerprint fallback) already moved the watcher this tick.
 *
 * Returns true only when there was no earlier switch AND the pid resolver
 * gave no opinion.
 *
 * Trade-off: pid resolver `'same'` is NOT proof that no rotation happened
 * — Claude Code 2.1.123 writes `sessionId` ONCE at process start and the
 * in-pane `/clear` path does not refresh it. We still skip the mtime
 * heuristic on `'same'` because the alternative is sibling-pane hijack:
 * any other Claude pane in the same cwd gets a busier jsonl and the
 * heuristic picks it. The cost is that a pure-local `/clear` with no
 * pending Lark turn won't auto-follow until the user sends a Lark
 * message (which arms fingerprint fallback). The Lark-message path is
 * the dominant /clear recovery flow in practice; sibling-pane
 * corruption would silently break every multi-pane adopt setup.
 *
 * `--resume` is a fresh spawn and rewrites the pid file's sessionId, so
 * it surfaces here as `'switched'`, not `'same'` — it's not affected by
 * this gate.
 */
export declare function shouldRunQuietRotation(pidFollow: PidFollowResult, switched: boolean): boolean;
export interface PidResolverPullbackInput {
    /** sessionId reported by the pid file this tick. */
    resolvedCliSessionId: string;
    /** The pid file's full jsonl path (derived from sessionId + cwd). */
    resolvedPath: string;
    /** Current bridge jsonl path. */
    currentBridgeJsonlPath: string | undefined;
    /** Sid recorded as "stale" by the fingerprint fallback the last time it
     *  accepted a candidate the pid file disagreed about. Undefined when no
     *  fingerprint accept has overridden the pid file. */
    stalePidStateSessionId: string | undefined;
}
export interface PidResolverPullbackDecision {
    /** True ⇒ pid resolver should report 'same' rather than rotate the
     *  watcher back to `resolvedPath`. */
    suppress: boolean;
    /** True ⇒ caller should clear `stalePidStateSessionId`: a fresh sid
     *  (different from the stale one) appeared in the pid file, meaning a
     *  real rotation has happened (`--resume` / fresh spawn) and the prior
     *  fingerprint accept is no longer relevant. */
    clearStale: boolean;
}
/**
 * Decide whether the pid resolver should pull the watcher back to a path
 * that disagrees with the current bridgeJsonlPath, given the worker's
 * staleness bookkeeping.
 *
 * Rules:
 * - No `stalePidStateSessionId` recorded ⇒ honour pid resolver as before.
 * - Recorded sid matches pid file's current sid ⇒ suppress: this is the
 *   spawn-time sid that fingerprint fallback already overrode for an
 *   in-pane /clear that pid file can't see.
 * - Recorded sid differs from pid file's current sid ⇒ a NEW rotation has
 *   happened (`--resume` / fresh spawn / Claude restart with new pid file
 *   contents). Clear the stale flag and let pid resolver switch normally.
 */
export declare function evaluatePidResolverPullback(input: PidResolverPullbackInput): PidResolverPullbackDecision;
/** UUID-shaped Claude jsonl filename (sessionId.jsonl). Duplicated from
 *  `src/adapters/cli/claude-code.ts:SESSION_UUID_RE` to keep this module
 *  free of adapter imports. Keep the two patterns in sync; both gate
 *  trust-set membership and fingerprint-fallback candidate eligibility. */
export declare const SESSION_ID_FILENAME_RE: RegExp;
/** Extract the sessionId portion of a `<sid>.jsonl` path (basename minus
 *  the `.jsonl` extension). Returns the empty string when the path lacks
 *  the expected suffix; callers should treat that as "untrusted". */
export declare function sessionIdFromJsonlPath(path: string): string;
export interface FingerprintSwitchInput {
    /** Substring fingerprint to feed Phase 1's scanner. Required. */
    contentFingerprint: string;
    /** Full normalised content of the Lark turn. Phase 2 (unknown-sid
     *  exact-content recovery) is skipped when this is missing or empty —
     *  short content can't anchor a recovery without ambiguity. */
    contentNormalized?: string;
    /** Trust set populated from initial attach, pid resolver hits, fd probes. */
    knownSessionIds: ReadonlySet<string>;
    /** Phase 1 scanner: substring fingerprint search. Caller wires it to
     *  `findJsonlContainingFingerprint`; this helper only sees the result. */
    findSubstring: (acceptCandidate: (path: string) => boolean) => string | null;
    /** Phase 2 scanner: exact-content search returning ALL matches in
     *  mtime-descending order. Caller wires it to
     *  `findJsonlsContainingExactContent`. */
    findExact: (acceptCandidate: (path: string) => boolean) => string[];
}
export type FingerprintSwitchDecision = {
    action: 'switch';
    path: string;
    reason: 'known-sid-substring' | 'unknown-sid-exact';
} | {
    action: 'abstain';
    reason: 'multiple-unknown-exact';
    candidates: string[];
} | {
    action: 'no-match';
};
/**
 * Two-phase candidate selection for the bridge fingerprint fallback.
 *
 * Phase 1 (known-sid substring): the cheap path. Run the substring
 * fingerprint scanner with an acceptCandidate predicate that requires
 * `(UUID-shaped sid) AND (sid in knownSessionIds)`. Sibling panes are
 * UUID-shaped but not in our trust set — their fingerprint hits are
 * rejected. UUID gate also blocks accidental non-Claude jsonls.
 *
 * Phase 2 (unknown-sid exact-content recovery): only runs when
 * `contentNormalized` is non-empty. Fires when Phase 1 found no match,
 * which is the worst-case in-pane `/clear` scenario where the new sid
 * never reaches our trust set (pid file lags, fd probe missed the open
 * window). Run the exact-content scanner with predicate
 * `(UUID-shaped sid) AND (sid NOT in knownSessionIds)`. Decision:
 *   - 0 matches → no-match
 *   - 1 match  → switch (the post-/clear file we couldn't otherwise see)
 *   - ≥2 matches → abstain; multiple untrusted files normalise to the
 *     same Lark content, so we cannot pick one without further evidence.
 *     Caller is expected to log and surface a diagnostic.
 *
 * Pure: never touches fs / IPC / module state. Tests inject fakes for
 * `findSubstring` and `findExact` to exercise every branch.
 */
export declare function decideFingerprintSwitch(input: FingerprintSwitchInput): FingerprintSwitchDecision;
export interface AbsentBaselineHealInput {
    /** Whether the bridge has already completed baseline (gate already open). */
    baselineDone: boolean;
    /** Whether a `bridgeJsonlPath` is currently pinned. */
    hasJsonlPath: boolean;
    /** Whether that pinned path currently exists on disk. */
    jsonlFileExists: boolean;
}
/**
 * Decide whether a pending Lark turn arriving against a not-yet-baselined
 * bridge should SELF-HEAL by arming fresh-empty readiness, instead of being
 * dropped with "baseline not ready".
 *
 * Background: in `baseline-existing` (resume / restart-restore) mode the
 * worker pins `bridgeJsonlPath` to `<sessionId>.jsonl` and waits for that
 * file to appear before baselining. If Claude wrote its transcript under a
 * DIFFERENT sessionId than the one we guessed — `--session-id` not honoured,
 * a stale resume id, or an /adopt sid persisted as the botmux sid — the
 * guessed file never appears, `baselineDone` stays false forever, and every
 * turn is dropped ("baseline not ready"). The bridge is then permanently
 * stuck on a path that does not exist (the user-reported "message went into
 * the PTY but final_output was never attributed" symptom). The pid-state
 * resolver that would normally correct this needs a live CLI pid, which is
 * often missing on restart-restore — so the only recovery path that still
 * works is the exact-content fingerprint scan, and IT is starved because no
 * turn is ever marked.
 *
 * An ABSENT file has no prior history to absorb, so it is safe to declare
 * fresh-empty readiness (offset 0, baseline done) here: the pending turn
 * then gets marked, which arms the per-tick exact-content fingerprint
 * recovery (`decideFingerprintSwitch`) to discover the jsonl Claude actually
 * appended this message to and switch the bridge onto it — no dependence on
 * Claude's internal pid-state / tasks files, so it is Claude-version-robust.
 *
 * Returns true ONLY when baseline isn't done, a path is pinned, and that
 * path is absent. When the file EXISTS (the genuine slow-resume-with-history
 * case), this returns false so normal lazy-baseline runs instead and prior
 * turns are absorbed rather than re-emitted.
 */
export declare function shouldHealAbsentBaseline(input: AbsentBaselineHealInput): boolean;
//# sourceMappingURL=bridge-rotation-policy.d.ts.map