/**
 * SessionDrainRunner — the OWNER-side bounded drain for an active-topic
 * transfer (WS1.2, MULTI-MACHINE-SEAMLESSNESS-SPEC).
 *
 * Executes the receiver half of the `drain` mesh verb: the router (transfer
 * planner) has ordered this machine — the topic's CURRENT owner — to hand the
 * topic to `target`. The runner:
 *
 *   1. Re-validates ownership + the sender's observed epoch, then CASes
 *      active → transferring(target). A stale/replayed drain dies HERE at the
 *      fence (reach ≠ authority — RBAC proved the sender may ask; the CAS
 *      proves the ask still matches reality).
 *   2. Suspends the topic's autonomous run for the move (the REMOTE arm of
 *      WS1.4 — the state file survives, rides the working-set carrier).
 *   3. Waits for the session's turn boundary, bounded by `drainBoundMs`,
 *      checking the emergency stop EVERY poll.
 *   4. On the boundary (or the bound): closes the local session — forced at
 *      the bound, marked `interrupted-mid-task` with ONE honest notice — and
 *      COMPLETES the transfer by landing the target's claim CAS (the FSM
 *      permits a non-target sender to land a claim NAMING the target — the
 *      established router-confirmClaim precedent), so the durable queue's
 *      barrier releases to the new owner exactly at drain completion.
 *   5. On emergency stop: CAS abort-transfer — the topic STAYS HERE, the
 *      sender marks the transfer failed-needs-retry, nothing is left split.
 *
 * The drain BARRIER needs no new machinery: while the record is
 * `transferring`, route() queues inbound (ownership-contention); the claim in
 * step 4 is the release point. The WS1.3 reconciler's transferring-to-me claim
 * is held back by `drainClaimGraceMs` so it backstops a dead-mid-drain owner
 * WITHOUT front-running a live one.
 *
 * Honest bound (named in the side-effects artifact): the spec's full "final
 * context flush durably replicated" release gate is the Track H ledger
 * machinery; today's release point is drain completion, with the target's
 * spawn-time history + working-set fetch carrying the context.
 *
 * All I/O is injected — the sequence is deterministic and unit-testable.
 * P19: one bounded poll loop (drainBoundMs / pollMs), no retries, no timers
 * that outlive run().
 */
import type { OwnershipAction, SessionOwnershipRecord } from './SessionOwnership.js';
export interface DrainRequest {
    /** The topic key being transferred. */
    sessionKey: string;
    /** The machine the topic is moving to. */
    target: string;
    /** The ownership epoch the SENDER observed when planning the transfer. */
    senderObservedEpoch: number;
}
export type DrainStatus = 'drained' | 'drained-interrupted' | 'aborted-emergency-stop' | 'refused-not-owner' | 'refused-stale-epoch' | 'refused-cas-lost';
export interface DrainOutcome {
    status: DrainStatus;
    /** True when the topic's autonomous run was suspended for the move. */
    autonomousRunSuspended: boolean;
    /** Milliseconds spent waiting for the turn boundary. */
    drainedInMs?: number;
    detail?: string;
}
export interface SessionDrainRunnerDeps {
    selfMachineId: string;
    readOwnership: (sessionKey: string) => SessionOwnershipRecord | null;
    /** The registry CAS (epoch-fenced; legality enforced by the FSM). */
    cas: (action: OwnershipAction, ctx: {
        sessionKey: string;
        sender: string;
        nonce: string;
    }) => {
        ok: boolean;
        reason?: string;
    };
    /** WS1.4 remote arm: suspend the topic's autonomous run, file survives. */
    suspendAutonomousRun: (topic: string, target: string) => {
        suspended: boolean;
    };
    /** Turn-boundary signal: is the topic's local session quiet (no turn in flight)?
     *  `true` when there is no local session at all — nothing to drain. */
    sessionQuiet: (sessionKey: string) => boolean;
    /** The emergency-stop flag (checked EVERY poll — user safety preempts the move). */
    emergencyStopActive: () => boolean;
    /** Close the topic's local session. `force` at the bound (the session did not
     *  reach a boundary in time); implementations escalate to hard-kill if wedged. */
    terminateSession: (sessionKey: string, reason: string, opts: {
        force: boolean;
    }) => Promise<{
        terminated: boolean;
        skipped?: string;
    }>;
    /** Stamp the topic's suspended run file `interrupted_mid_task: true` (no-op without a run). */
    markInterrupted: (topic: string) => void;
    /** ONE honest notice for a forced close ("moved to X mid-task — final turn may be partial"). */
    notifyInterrupted: (topic: string, target: string, detail: string) => void;
    audit?: (event: Record<string, unknown>) => void;
    now: () => number;
    sleep: (ms: number) => Promise<void>;
    nonce: () => string;
}
export interface SessionDrainRunnerConfig {
    /** Hard bound on the turn-boundary wait (spec: a long reply must not block a transfer). */
    drainBoundMs: number;
    /** Poll cadence inside the bound. */
    pollMs: number;
}
export declare const DEFAULT_DRAIN_RUNNER_CONFIG: SessionDrainRunnerConfig;
/**
 * How long the WS1.3 reconciler must hold back its transferring-to-me claim so
 * it backstops a dead-mid-drain owner without front-running a live drain:
 * the drain bound plus slack for the close + claim CAS to land.
 */
export declare const DEFAULT_DRAIN_CLAIM_GRACE_MS: number;
export declare class SessionDrainRunner {
    private readonly deps;
    private readonly cfg;
    constructor(deps: SessionDrainRunnerDeps, cfg?: Partial<SessionDrainRunnerConfig>);
    run(req: DrainRequest): Promise<DrainOutcome>;
}
//# sourceMappingURL=SessionDrainRunner.d.ts.map