/**
 * SessionOwnership — per-session ownership state machine for the Multi-Machine
 * Session Pool (spec §L3). Exactly one machine runs a session at any instant;
 * ownership is movable and fenced by a (status, epoch) pair, advanced by a
 * per-session CAS at `ownershipEpoch+1`.
 *
 * This module is PURE LOGIC — the legal transitions, the run-fence (who may run
 * the agent), the output-exclusion contract (who may write to the user channel
 * during a transfer), and the per-session nonce scoping. The durable CAS (the
 * per-session git single-ref fast-forward push of §L−1) and the distributed
 * registry sit on top and call these to validate every ownership change.
 *
 * The handoff is **claim-before-release, gated by the state machine** — NOT
 * release-before-claim. Authoritative sequence for a transfer of session K from
 * source S to target T:
 *   active(owner=S, e) → transferring(from=S, to=T, e+1) → active(owner=T, e+2) → S tears down (local)
 * Because the fence is (status, epoch) — not the physical release — there is no
 * double-run (only one machine ever sees itself active at the top epoch) and no
 * no-owner gap (transferring still names S as the draining owner until T is active).
 */
export type SessionOwnershipStatus = 'placing' | 'active' | 'transferring' | 'released';
export interface SessionOwnershipRecord {
    sessionKey: string;
    ownerMachineId: string;
    ownershipEpoch: number;
    status: SessionOwnershipStatus;
    /** During `transferring`: the target machine the session is moving to. */
    transferTo?: string;
    /** WS1.2: this transferring record was set by the DRAIN flow — the owner's
     *  SessionDrainRunner will land the target's claim itself at drain
     *  completion. Consumers (the WS1.3 reconciler) hold their backstop claim
     *  for the drain grace instead of front-running the live drain. Absent on
     *  reconciler-cooperative transfers (claim promptly) and on records from
     *  pre-WS1.2 peers. */
    drainInFlight?: boolean;
    nonce: string;
    timestamp: number;
    updatedAt: string;
    signature?: string;
}
export type OwnershipAction = {
    type: 'place';
    machineId: string;
} | {
    type: 'claim';
    machineId: string;
} | {
    type: 'transfer';
    to: string;
    drain?: boolean;
} | {
    type: 'release';
    machineId: string;
}
/**
 * WS1.3 (MULTI-MACHINE-SEAMLESSNESS-SPEC): claim ownership of a record whose
 * owner is PROVABLY DEAD — the convergence-deadline path for a pin/owner
 * divergence that cannot reconcile cooperatively because the owner machine is
 * gone. The FSM only enforces sequencing + the fenced epoch increment (the
 * stale owner's later CAS attempts fail at the old epoch — clock-proof);
 * DEATH EVIDENCE (offline past the bound, lease lapse, quorum membership) is
 * validated by the caller (OwnershipReconciler) BEFORE issuing the action,
 * never by wall-clock timers alone. A reachable-but-slow owner must get the
 * cooperative transfer path, never a force-claim.
 */
 | {
    type: 'force-claim';
    machineId: string;
}
/**
 * WS1.2 (MULTI-MACHINE-SEAMLESSNESS-SPEC): the emergency-stop-during-drain
 * path — the CURRENT owner aborts its own in-flight transfer and returns to
 * `active` at epoch+1, so the topic STAYS on the old machine and nothing is
 * left split. The fenced epoch increment makes the stale target's later
 * claim fail (claim-out-of-sequence on an active record at a newer epoch).
 * Owner-only by construction: only the machine the transferring record
 * names may abort.
 */
 | {
    type: 'abort-transfer';
    machineId: string;
};
export type OwnershipReason = 'ok' | 'already-placed' | 'claim-out-of-sequence' | 'claim-wrong-machine' | 'transfer-not-active' | 'release-not-owner' | 'release-requires-active' | 'force-claim-self' | 'abort-not-transferring' | 'abort-not-owner' | 'no-record';
/**
 * Pure transition. Given the CURRENT record (or null for a never-seen session)
 * and an action, returns the next record (the CAS candidate, at epoch+1) or a
 * typed rejection. Epoch arithmetic + legal sequencing are enforced here; the
 * CAS layer then attempts to land `next` via a fast-forward push.
 */
export declare function applyOwnershipAction(current: SessionOwnershipRecord | null, action: OwnershipAction, ctx: {
    sessionKey: string;
    nonce: string;
    now: number;
    epochFloor?: number;
}): {
    ok: true;
    next: SessionOwnershipRecord;
} | {
    ok: false;
    reason: OwnershipReason;
};
/**
 * Run-fence (spec §L3): a worker may RUN the agent for a session ONLY while it
 * observes itself as the `active` owner at the current epoch. During
 * `transferring` NO machine runs new turns (the draining owner finishes via the
 * output-exclusion contract; the target is not yet active).
 */
export declare function mayRun(record: SessionOwnershipRecord | null, machineId: string, effectiveEpoch: number): boolean;
export interface EmitDecision {
    /** May this machine write to the user channel at all right now? */
    mayEmit: boolean;
    /** If mayEmit, may it emit NEW output (vs only drain an in-flight reply)? */
    newOutputAllowed: boolean;
}
/**
 * Output-exclusion contract (spec §L3) — distinct from the run-fence. During a
 * transfer the draining source and the freshly-active target overlap in time, so
 * channel emission is gated separately so the user never sees interleaved/double
 * output:
 *  - The draining source (record `transferring`, from === machine) MAY emit the
 *    TAIL of an in-flight reply within [transferringStartedAt, +cutoffMs], but no
 *    NEW output, and nothing past the cutoff.
 *  - The target (record `active`, owner === machine, after its CAS) emits only
 *    once the cutoff has elapsed since `transferringStartedAt` (so the two
 *    windows are disjoint by construction).
 *  - The steady-state active owner (no transfer in flight) emits freely.
 */
export declare function mayEmit(record: SessionOwnershipRecord | null, machineId: string, opts: {
    now: number;
    transferringStartedAt?: number;
    cutoffMs?: number;
}): EmitDecision;
/**
 * Per-session nonce scoping (spec §L3): the replay-guard key is the tuple
 * {sessionKey, sender, ownershipEpoch} — NOT a global nonce space. The same
 * nonce value used in two different sessions does not collide; a replay WITHIN a
 * session's CAS sequence is caught.
 */
export declare function ownershipNonceKey(sessionKey: string, sender: string, ownershipEpoch: number, nonce: string): string;
//# sourceMappingURL=SessionOwnership.d.ts.map