import type { CoverJob, CoverJobProgress, CoverSchedulerStatus } from "./coverScheduler"; import type { CoverRuntime } from "./coverRuntime"; import type { IRTCPeerConnection } from "../api/webrtc/interfaces"; /** Authenticated per-chunk tokens, batched only within the same transfer root. */ export declare const queueScheduledChunkReceipt: (epc: IRTCPeerConnection, merkleRoot: Uint8Array, token: Uint8Array) => boolean; /** * Queue one legacy scheduled terminal receipt (token equals transfer root) for * the next available reverse control slots. Returns false when the edge has no * cover runtime (immediate mode) so the caller falls back to immediate * receipts, and drops oldest-first beyond the flood bound. Completion replaces * this root's unsent chunk ACKs so they cannot consume an extra reverse cycle. */ export declare const queueScheduledReceipt: (epc: IRTCPeerConnection, merkleRoot: Uint8Array, token: Uint8Array) => boolean; /** Wipe and drop every queued scheduled receipt (edge teardown). */ export declare const releaseScheduledReceipts: (epc: IRTCPeerConnection) => void; /** * A synthetic constant-shape label for receipt routing: handleReadReceipt only * consumes the Merkle-root element, so the name element is canonical zeros. */ export declare const syntheticScheduledLabel: (merkleRootHex: string) => string; /** * The transfer root carried by a compiled channel-message label (which is also * the scheduled-send job id). Null for anything that is not one — the * synchronous counterpart of decompileChannelMessageLabel, used where a cancel * cannot await. */ export declare const scheduledTransferRootFromLabel: (channelMessageLabel: string) => Uint8Array | null; /** * Queue this transfer's wire CANCEL as a standalone reverse control cell. * * A send job carries its own `cancelCell`, but only while the scheduler still * holds it. By the time a delivery bound expires the job has usually emitted * every declared cell and settled "completed", so `CoverScheduler.cancel` * answers "not-found" and nothing would ever reach the receiver — which is the * state that strands it: an unfillable N/M partial that a re-send cannot fill, * because decoy chunks are fresh randomness and the Merkle root differs (C3). * This is the same substitution a receipt uses, so it costs no extra cells and * changes nothing observable about the schedule. */ export declare const queueScheduledCancel: (epc: IRTCPeerConnection, channelMessageLabel: string) => boolean; export interface ScheduledDeliveryScheduleShape { readonly coverCadenceMs: number; readonly coverLanes: number; readonly coverFramesPerCell: number; readonly coverDurationEpochs: number; } /** * The earliest a terminal scheduled receipt can possibly exist, plus head-room. * * A job is admitted only at a cycle open, so a send enqueued at t first emits * at the next boundary; the receiver's terminal receipt is itself a job * admitted at the boundary after that. Two cycles is therefore the arithmetic * floor. The shipped "5 min cycles" preset needs 301.2 s of it — against the * fixed 120 s bound this replaces, every send in such a room failed (C3). * * The third term is the wait inside the receipt's own cycle: one slot spacing, * except that a room stripes its lanes across the cycle, so a control job * dequeued onto the LAST lane emits (lanes - 0.5) spacings in, not one. That * term is what the margin sits on top of, so the margin stays head-room for * every lane count instead of being silently spent covering the stripe — a * bound under the floor is C3 again with a different number. */ export declare const scheduledDeliveryTimeoutMs: (schedule: ScheduledDeliveryScheduleShape) => number; /** The scheduler surface a delivery wait polls; `CoverRuntime` satisfies it. */ export interface ScheduledJobProgressSource { getJobProgress(jobId: string): CoverJobProgress | undefined; } /** What one scheduled send has achieved; `CoverJobProgress` verbatim. */ export type ScheduledSendProgress = CoverJobProgress; /** * What this send has achieved so far, or undefined once the scheduler no * longer holds the job. * * Batched chunk ACKs support selective resume, while older peers send only a * terminal receipt. Local progress also covers time before reverse receipts * can be admitted: the job moving up the queue and cells reaching the wire. */ export declare const scheduledSendProgress: (runtime: ScheduledJobProgressSource | undefined, jobId: string) => ScheduledSendProgress | undefined; /** * Whether `next` is genuinely AHEAD of the best this send has managed so far. * * The progress signal is NOT monotone, so "it changed" is not "it advanced". * `CoverScheduler.sortQueue` ranks a cancel-pending job first and a `control` * job ahead of every `real` one (jobTier), so a receipt-drain job admitted * while this send waits pushes its queue position BACKWARDS; and every requeue * — backpressure, a lane that failed to open, a missed deadline, a suspension — * sends an admitted job back to "queued". Counting either as progress lets an * oscillating signal reset the budget forever: the send is never abandoned, the * receiver's partial is never cancelled, and because `sendScheduled` fans out * serially, every later peer in the room waits behind it unboundedly. * * So exactly three things count, in this order: * * - the admission stage rising (queued → admitted → terminal); * - within one stage, the queue position falling, which is a job climbing * towards admission (-1 means "not queued at all", which the stage already * carries, so it is never compared as a position); * - cells accepted onto the wire rising — the one component the scheduler * itself only ever increments. * * `sentCells` survives a requeue, so a job knocked back to the queue and * re-admitted has to emit past its own high-water mark to count again. That is * deliberate: a job that is requeued every cycle and never emits anything IS * stuck, and must still be caught. */ export declare const isForwardScheduledProgress: (previous: ScheduledSendProgress | undefined, next: ScheduledSendProgress) => boolean; /** * True while this edge's cover scheduler is SUSPENDED: a browser-imposed gap * (a hidden tab, pagehide, freeze, offline) in which the schedule is not * running at all. * * Deliberately narrow. An absent runtime (the edge was torn down) and a stopped * one are NOT this: they are conditions the idle budget must still expire on, * so the send can reach its resume — or, failing that, tell the receiver. * Only the state that both keeps the job alive and makes progress impossible * holds the clock. */ export declare const scheduledCoverIsSuspended: (runtime: { readonly status: CoverSchedulerStatus; } | undefined) => boolean; export interface ScheduledDeliveryWaitOptions { /** True once the receiver's terminal scheduled receipt has settled the send. */ readonly isComplete: () => boolean; /** See scheduledSendProgress. */ readonly progress: () => ScheduledSendProgress | undefined; /** * True while progress is IMPOSSIBLE rather than merely absent. See * scheduledCoverIsSuspended; omitted, the budget always accrues. */ readonly isSuspended?: () => boolean; /** Explicit transport/PQ generation change retires this attempt immediately. */ readonly isInvalidated?: () => boolean; /** * How long this send may make NO observable progress before it is given up * on. An IDLE budget, never a total deadline. */ readonly idleTimeoutMs: number; readonly pollMs?: number; readonly now?: () => number; readonly sleep?: (ms: number) => Promise; readonly signal?: AbortSignal; } /** * Wait for one scheduled send to be confirmed, giving up only when it stops * making progress. * * `scheduledDeliveryTimeoutMs` derives the arithmetic FLOOR of a confirmation: * two cycles plus the receipt's wait inside its own cycle. That floor assumes * this send is admitted at the very next cycle open — but only `coverLanes` * jobs are admitted per cycle (CoverScheduler.openCycle dequeues exactly that * many), so a third concurrent message on a two-lane five-minute edge is not * admitted for cycles and legitimately needs many times the floor. Treating * the floor as a total deadline therefore failed healthy sends, and — once an * inbound CANCEL sweeps the receiver's partial — that spurious failure DELETES * a transfer that was still arriving. * * Widening the arithmetic cannot fix this: the queue depth is not knowable * when the bound is computed and keeps changing while the send waits. So the * same derived number is used as an idle budget instead. A send that is still * climbing the queue or still emitting cells is never abandoned however long * it takes in total; a send that has genuinely stopped is abandoned exactly * one floor after its last sign of life. * * The budget measures time in which progress was POSSIBLE and did not happen. * `CoverRuntime` suspends the scheduler on visibilitychange→hidden, pagehide, * freeze and offline, and `CoverScheduler.suspend` requeues but KEEPS the job: * the signal freezes while the send is perfectly healthy. Spending the budget * through that gap abandons a live send, and the CANCEL that follows deletes a * partial the peer is still holding — so the clock is held while the scheduler * is suspended AND still holds this job. It is deliberately not held for a * runtime that is merely gone: only the state that makes progress impossible * without ending the job counts, so a genuinely stalled send is still caught. * * The boundary of holding the clock is a page that is hidden and never comes * back: this wait then does not return until it does, or until `signal` aborts. * That is the direction the peer can survive — the job stays queued and still * has every cell to give — where giving up would destroy its partial for good. */ export declare const waitForScheduledDelivery: (options: ScheduledDeliveryWaitOptions) => Promise; /** Seal exactly one already-staged chunk into its uniform 65,490-byte cell. */ export type SealTransferSlotCell = (chunkIndex: number) => Promise | Uint8Array | null; export interface ScheduledSendJobOptions { readonly runtime: CoverRuntime; /** The compiled per-message channel label; it is the job/lane id. */ readonly channelMessageLabel: string; readonly totalChunks: number; /** This transfer's 64-byte Merkle root; the wire CANCEL is scoped to it. */ readonly merkleRoot: Uint8Array; /** Seal the sender's cell for one chunk index (real bytes). */ readonly sealSlotCell: SealTransferSlotCell; /** Set of already-acked real chunk indices for reconcile skipping. */ readonly getAckedChunks?: () => ReadonlySet; /** Live constant-time membership; preferred over repeated defensive copies. */ readonly isChunkAcked?: (chunkIndex: number) => boolean; /** Real chunk indices only (decoys never resend); default is all indices. */ readonly realChunkIndices?: readonly number[]; } /** * Build the lazy scheduled-send job for one message on one edge. It seals at * most ONE chunk per scheduled slot (never a burst), returns null (dummy * substitution) once every not-yet-acked real chunk has been offered this * pass, and refuses admission if the declared cell count cannot fit the * authenticated `F × D` capacity class. Reconcile is scheduled, not burst: an * un-acked chunk is re-offered on the next pass through the job's cells. */ export declare const buildScheduledSendJob: (options: ScheduledSendJobOptions) => CoverJob; /** * The scheduled-mode wire CANCEL for one transfer on one edge: scheduler * cancellation emits the encrypted CANCEL cell in the job's next slot and * leaves a dummy tail through the fixed boundary. Never a channel close. */ export declare const cancelScheduledTransfer: (epc: IRTCPeerConnection, channelLabel: string, merkleRootHex: string) => Promise;