/**
 * SpeakerElection — the WS3 "one voice" gate (MULTI-MACHINE-SEAMLESSNESS-SPEC).
 *
 * Decides whether THIS machine speaks for a topic's sentinel-class emissions
 * (PresenceProxy standby notices, PromiseBeacon heartbeats). The invariant is
 * exactly-one-speaks in BOTH directions:
 *
 *   ≤1 — only the topic's owner machine speaks; a non-owner stays silent
 *        (kills the F18/F23 double-voice family).
 *   ≥1 — "unknown owner" NEVER maps to pool-wide silence: the gate fails
 *        toward speech-with-dedup via the lease-holder, then a deterministic
 *        lowest-machineId tiebreak (kills the silent-agent dual the round-1
 *        adversarial review proved was worse than double-voice).
 *
 * Lease-stability dwell: while the lease epoch is advancing/contested, the
 * verdict is DEFER (the caller re-arms) rather than a speak decision on a
 * transient lease read — and a chosen speaker identity is HELD for a dwell
 * window so a mid-flap flip cannot hand the microphone back and forth.
 * Deferral is bounded: once instability persists past the dwell window, the
 * deterministic tiebreak speaks (fail toward speech, never unbounded silence).
 *
 * Signal-vs-authority: this gate constrains only WHO SPEAKS a duplicate-prone
 * notice — never recovery actions, never message content, never user-initiated
 * flows. It is deterministic over replicated state, fails open to "speak"
 * whenever it cannot decide, and is dark behind multiMachine.seamlessness
 * (ws3OneVoice). Flag off, no machine id, or single-machine pool → verdict is
 * always "speak" with zero behavior delta (spec invariant 6).
 */
export interface SpeakerElectionDeps {
    /** Dark flag: multiMachine.seamlessness.ws3OneVoice (read live per decision). */
    enabled: () => boolean;
    /** This machine's pool id; absent → legacy behavior (always speak). */
    currentMachineId?: string;
    /** Online pool machine ids INCLUDING self. Length < 2 → single-machine no-op. */
    poolMachineIds: () => string[];
    /**
     * The topic's current owner from local replicated placement (never a mesh
     * call — hot path). null = unknown/unowned.
     */
    resolveTopicOwner: (topicId: number) => string | null;
    /** The current lease-holder's machine id (null = no/unknown holder). */
    leaseHolderId: () => string | null;
    /** Is the lease epoch stable (not mid-flap/contested)? */
    leaseStable: () => boolean;
    /** Dwell window ms for both defer-bounding and speaker-identity hold. */
    dwellMs?: number;
    /** Observability hook (P7): every verdict is reported, never silent. */
    onVerdict?: (topicId: number, verdict: SpeakerVerdict) => void;
    now?: () => number;
}
export interface SpeakerVerdict {
    speak: boolean;
    /** When true the caller should re-arm and re-ask after a short backoff —
     *  the election could not safely decide yet (lease mid-flap). */
    defer: boolean;
    reason: 'legacy-disabled' | 'legacy-no-machine-id' | 'single-machine' | 'owner-self' | 'owner-other' | 'owner-stamp-self' | 'owner-stamp-other' | 'lease-holder-fallback' | 'tiebreak-lowest-id' | 'tiebreak-lost' | 'lease-unstable-defer' | 'dwell-hold';
}
export declare class SpeakerElection {
    private readonly deps;
    /** Per-topic held verdicts (speaker-identity dwell). */
    private readonly held;
    /** Per-topic first-seen instability timestamps (defer bounding). */
    private readonly unstableSince;
    constructor(deps: SpeakerElectionDeps);
    /**
     * Decide whether this machine speaks for `topicId`.
     * `stampedOwner` is the durable fallback (e.g. a commitment's ownerMachineId)
     * used when live placement has no owner for the topic.
     */
    decide(topicId: number, stampedOwner?: string | null): SpeakerVerdict;
    private decideInner;
    private tiebreak;
    private hold;
}
//# sourceMappingURL=SpeakerElection.d.ts.map