/**
 * ReplicationBudget — Component 7 bounds (WS2 replicated-store foundation, §8 +
 * §8.1 Phase C).
 *
 * Spec: docs/specs/multi-machine-replicated-store-foundation.md §8 (per-store /
 * per-record-class bounds, replication rate cap with COALESCING, the AGGREGATE
 * journal budget that caps TOTAL bytes/sec across ALL kinds with proportional
 * fair-share throttle, the sustained-failure breaker, the dark-peer accumulation
 * bound + tombstone horizon forced full-snapshot re-join), §8.1 (Phase C: budgets
 * = per-peer allowance × live online-peer count, hard pool-wide ceiling,
 * hysteresis on the multiplier — NOT a 2-machine constant).
 *
 * PURE-ish: the clock is injected so the token buckets + hysteresis are
 * unit-testable across simulated windows. No fs, no Date directly, no network.
 *
 * The ANTI-STARVATION invariant (§8): a flood on kind A must NOT consume kind B's
 * replication share. The aggregate budget caps total bytes/sec; when pressured,
 * per-kind caps throttle PROPORTIONALLY (fair-share) and the throttle is SURFACED
 * in degradation (never a silent stall). A test enforces the budget ACROSS kinds.
 */
import { type HlcTimestamp } from './HybridLogicalClock.js';
import type { KindRetention, RateCapConfig } from './CoherenceJournal.js';
/**
 * The per-replicated-kind bounds a concrete store (WS2.1+) ships when it
 * registers its kind (§8): a `retention` window (maxFileBytes + rotateKeep) AND a
 * token-bucket rate cap. The foundation provides the SHAPE + conservative
 * defaults; a store overrides per kind. These extend the journal's existing
 * DEFAULT_RETENTION / DEFAULT_RATE_CAP machinery — same types, applied to the
 * replicated kind's own stream.
 */
export interface ReplicatedKindBounds {
    retention: KindRetention;
    rateCap: RateCapConfig;
}
/** Conservative default bounds for a replicated kind that does not override them.
 *  A high-write store (preferences) should ship a tighter window; this is the
 *  bounded fallback so a kind is NEVER unbounded. */
export declare const DEFAULT_REPLICATED_KIND_BOUNDS: ReplicatedKindBounds;
/** One coalesced pending replication: the LATEST record for a recordKey within
 *  the current interval (a burst on one key collapses to one record). */
export interface CoalescedRecord {
    recordKey: string;
    /** The opaque serialized record line (the journal entry to replicate). */
    line: string;
    /** Bytes the line would consume (for the budget accounting). */
    bytes: number;
    /** The HLC of the latest record (for last-wins coalescing within the interval). */
    hlc: HlcTimestamp;
}
/**
 * CoalescingReplicator — collapses a burst of edits to one recordKey into ONE
 * replicated record per interval (§8 coalescing). `stage(recordKey, line, bytes,
 * hlc)` keeps only the HLC-LATEST per key; `drain()` emits the coalesced set and
 * clears the buffer for the next interval. Per replicated kind (one instance per
 * kind), so a chatty kind cannot starve another — the aggregate budget below is
 * the cross-kind guard.
 */
export declare class CoalescingReplicator {
    private pending;
    /** Stage a write; keeps only the HLC-latest per recordKey within the interval. */
    stage(recordKey: string, line: string, bytes: number, hlc: HlcTimestamp): void;
    /** The number of distinct keys currently coalesced (for observability). */
    get pendingCount(): number;
    /** The total bytes the coalesced set would replicate this interval. */
    pendingBytes(): number;
    /** Emit + clear the coalesced set (the LATEST per key). */
    drain(): CoalescedRecord[];
}
/** Phase-C budget config (§8.1). */
export interface PhaseCBudgetConfig {
    /** Per-peer byte allowance (the unit scaled by online-peer count). */
    perPeerBytes: number;
    /** Hard pool-wide ceiling — the scaled budget is clamped here (protects a single
     *  machine's disk/CPU regardless of pool size). */
    hardCeilingBytes: number;
    /** Hysteresis: the effective online-peer MULTIPLIER only RISES after the higher
     *  peer count has been observed for `hysteresisRiseMs`; it falls immediately
     *  (shrinking the budget on a real peer loss is the safe direction). A transient
     *  peer-count SPIKE does not instantly widen the budget. */
    hysteresisRiseMs: number;
}
/**
 * Compute the Phase-C aggregate budget (§8.1): `min(perPeerBytes ×
 * effectivePeerCount, hardCeilingBytes)`. NOT a 2-machine constant — a 5-machine
 * pool gets a proportionally larger budget, bounded by the hard ceiling.
 *
 * The `effectivePeerCount` is the HYSTERESIS-damped online count (computed by
 * PhaseCBudgetController below); this pure helper just applies the formula so it
 * is independently testable.
 */
export declare function phaseCBudget(perPeerBytes: number, effectivePeerCount: number, hardCeilingBytes: number): number;
/**
 * PhaseCBudgetController — tracks the live online-peer count with hysteresis on
 * the multiplier (§8.1). A RISE in peer count only takes effect after it has held
 * for `hysteresisRiseMs` (a transient spike does not widen the budget); a FALL
 * takes effect immediately (shrink-on-loss is safe). `currentBudget()` returns the
 * live aggregate byte budget.
 */
export declare class PhaseCBudgetController {
    private readonly cfg;
    private readonly now;
    private effectivePeerCount;
    /** Seeded on the first observation — there is no prior narrower budget to
     *  protect at bootstrap, so the first count takes effect immediately; only a
     *  SUBSEQUENT rise is hysteresis-gated. */
    private seeded;
    /** A pending higher count + when it was first observed (for the rise hysteresis). */
    private pendingRise;
    constructor(cfg: PhaseCBudgetConfig, now: () => number);
    /** Feed the live online-peer count (from the capacity heartbeat). Applies the
     *  rise-hysteresis / immediate-fall policy and returns the effective count. */
    observePeerCount(online: number): number;
    /** The current effective (hysteresis-damped) online-peer count. */
    getEffectivePeerCount(): number;
    /** The current aggregate byte budget (§8.1). */
    currentBudget(): number;
}
/** A per-kind throttle verdict (§8). When the aggregate budget is pressured, each
 *  kind's allowance is its FAIR SHARE of the budget; an over-share request is
 *  throttled (deferred to the next interval) and the throttle is surfaced. */
export interface AggregateThrottleResult {
    /** The bytes admitted this interval for the kind. */
    admittedBytes: number;
    /** The bytes deferred (throttled) — coalesced to the next interval, never dropped. */
    throttledBytes: number;
    /** True iff this kind was throttled (surfaced in degradation; never silent). */
    throttled: boolean;
}
/** Degradation surface for the aggregate budget (§8 — never a silent stall). */
export interface AggregateBudgetDegradation {
    /** Intervals in which at least one kind was throttled. */
    throttledIntervals: number;
    /** Total bytes deferred across all kinds (cumulative). */
    totalThrottledBytes: number;
    /** Per-kind cumulative throttled bytes (so a starving kind is visible). */
    perKindThrottledBytes: Record<string, number>;
}
/**
 * AggregateJournalBudget — the cross-kind anti-starvation guard (§8). Each
 * interval, the controller admits each kind's pending bytes up to its FAIR SHARE
 * of the live aggregate budget; an over-share request is throttled (the surplus
 * defers to the next interval, never dropped — coalescing already collapsed it to
 * the latest state). The fair share is `budget / activeKindCount`, with UNUSED
 * share from quiet kinds redistributed to the others (so a single chatty kind on
 * an otherwise-idle pool still gets the whole budget — fair, not wasteful), but
 * NEVER beyond a kind's own pending demand and never letting one kind exceed the
 * aggregate while another has unserved demand.
 *
 * The §12 #9/#15 invariant: a flood on kind A is capped at its fair share when
 * kind B also has demand — B keeps its share; A's surplus is deferred + surfaced.
 */
export declare class AggregateJournalBudget {
    private readonly degradation;
    /**
     * Allocate the live aggregate `budgetBytes` across the kinds' pending demands
     * for ONE interval. Returns per-kind throttle verdicts. Fair-share with
     * redistribution of unused share, bounded by each kind's demand and the
     * aggregate.
     */
    allocate(budgetBytes: number, demand: Record<string, number>): Record<string, AggregateThrottleResult>;
    /** The cumulative degradation surface (§8 — surfaced, never a silent stall). */
    getDegradation(): AggregateBudgetDegradation;
}
/** The re-join verdict for a recovering dark peer (§8). */
export type RejoinVerdict = {
    mode: 'tail';
    fromSeq: number;
} | {
    mode: 'full-snapshot';
    reason: 'beyond-tombstone-horizon' | 'below-oldest-retained';
};
/**
 * Decide how a recovering peer re-joins a kind's stream (§8 dark-peer accumulation
 * bound + tombstone horizon). If the peer's last-held seq is below the holder's
 * `oldestRetainedSeq` (the tombstone for a delete may have rotated out), a stale
 * tail would risk delete-resurrection — so the peer is FORCED to a full
 * single-origin snapshot re-join (§6.5 delete-resurrection guard). Otherwise a
 * normal seq tail from `lastHeldSeq` is safe.
 *
 * PURE: a function of the seqs — independently testable (§12 #13).
 */
export declare function rejoinVerdict(lastHeldSeq: number, oldestRetainedSeq: number): RejoinVerdict;
//# sourceMappingURL=ReplicationBudget.d.ts.map