/**
 * FencedOutbox — fencing-token-gated outbound reply path (spec §8 G3a).
 *
 * "No duplicate replies" is enforced here STRUCTURALLY, not by assuming a single
 * consumer. A reply is sent only while this machine holds the lease at the epoch
 * the reply was stamped under; a fenced (stale-epoch) machine's in-flight reply
 * is SUPPRESSED at the send path. The reply is committed to the message ledger
 * with a deterministic idempotency key (hash(dedupeKey + replyIndex)) so any
 * machine re-running the same event reproduces it identically — and the ledger
 * recognizes an already-committed reply and refuses to re-send.
 *
 * On a channel with native outbound dedup the idempotency key makes a re-send a
 * no-op; on a channel without it (Telegram), the dual-medium reply_committed
 * marker (propagated by the sync layer) is what prevents a failover re-send.
 */
import { MessageProcessingLedger } from './MessageProcessingLedger.js';
export interface FencedOutboxDeps {
    ledger: MessageProcessingLedger;
    /** The current effective fencing epoch. */
    currentEpoch: () => number;
    /** Whether this machine structurally holds the lease right now. */
    holdsLease: () => boolean;
    logger?: (msg: string) => void;
}
export interface SendOutcome {
    sent: boolean;
    suppressed: boolean;
    reason: 'sent' | 'already-replied' | 'fenced-no-lease' | 'fenced-stale-epoch' | 'send-failed';
    idempotencyKey: string;
}
export declare class FencedOutbox {
    private readonly d;
    constructor(deps: FencedOutboxDeps);
    private log;
    /**
     * Send a reply for `dedupeKey`, stamped under `stampedEpoch` (the epoch the
     * turn began processing under). Returns the outcome. The actual platform send
     * is `sendFn`; it is invoked ONLY after the fencing checks pass.
     */
    send(dedupeKey: string, replyIndex: number, stampedEpoch: number, sendFn: () => Promise<void>): Promise<SendOutcome>;
}
//# sourceMappingURL=FencedOutbox.d.ts.map