import { LimiterSpec } from './config.js'; import { d as ReplayFingerprint, h as ReplayTrace, a as Recording, e as ReplayRefusal } from './recorder-85_KFgH5.js'; import { T as ThrottleKitError } from './quota-C4WEn9R6.js'; import './types-DKirIBQt.js'; import './clock-CnB6yaAt.js'; /** * On-disk format for a serialized {@link PolicySet}. A serialized set from any other version is * refused on parse (fail-loud) — like {@link TRACE_FORMAT_VERSION}, a stored artifact must be * re-exported on a version bump rather than silently mis-read. */ declare const POLICY_SET_FORMAT_VERSION: 1; /** * One named admission policy — a declarative leaf {@link LimiterSpec} plus the {@link ReplayFingerprint} * needed to rebuild and validate the exact limiter it describes. Immutable + content-addressed (via the * enclosing {@link PolicySet}'s `contentHash`): "which policy is running" is a hash, "did it change" is a * hash compare. Built only from a buildable leaf-rate spec — non-replayable axes (concurrency / escrow / * joint-LP) are carried on the set as {@link UnreplayablePolicy}, never as a `Policy`. */ interface Policy { readonly name: string; readonly spec: LimiterSpec; readonly fingerprint: ReplayFingerprint; } /** * A policy that exists operationally but cannot be diffed by replay (a concurrency axis — releases are * not decisions; an escrow / leased / joint-LP path — warm or post-hoc state a cold replay can't * reconstruct). Listed on a {@link PolicySet} so a {@link Plan} can surface it honestly ("observe live") * rather than silently omit an axis. */ interface UnreplayablePolicy { readonly name: string; readonly reason: string; } /** * A versioned, content-addressed set of admission policies — the unit a {@link plan} diffs. `contentHash` * is a SHA-256 over the canonical (name-sorted, key-sorted) policies + unreplayable list, so two sets * compare by hash and a serialized set's integrity is checkable on parse. */ interface PolicySet { readonly label?: string; readonly policies: readonly Policy[]; readonly unreplayable?: readonly UnreplayablePolicy[]; readonly contentHash: string; } /** * Build one {@link Policy} from a declarative leaf {@link LimiterSpec}. Validates the spec eagerly via * {@link buildStrategy} (an incomplete or non-leaf spec throws here, not later at plan time) and captures * the rebuild fingerprint. * * @experimental Part of the opt-in Policy Plans surface (`throttlekit/policy`); see STABILITY.md. */ declare function policy(name: string, spec: LimiterSpec): Policy; interface PolicySetOptions { readonly label?: string; readonly unreplayable?: readonly UnreplayablePolicy[]; } /** Assemble a content-addressed {@link PolicySet}. Refuses duplicate policy names (an ambiguous set). */ declare function policySet(policies: readonly Policy[], options?: PolicySetOptions): PolicySet; interface PolicySetFromConfigOptions { readonly label?: string; /** Force a format. Default: auto-detect (text starting with `{`/`[` is JSON, else YAML). */ readonly format?: "yaml" | "json"; } /** * Build a {@link PolicySet} from `throttlekit/config` text (`.throttlekit.yaml` / `.json`). Reads the * `limiters` map as declarative specs — it never instantiates a live limiter (no `Store` needed), so it * is safe to run anywhere (CI, a CLI). Only leaf-rate limiters are read; a server config's non-replayable * axes are added separately as {@link UnreplayablePolicy}. * * @experimental Part of the opt-in Policy Plans surface; see STABILITY.md. */ declare function policySetFromConfig(text: string, options?: PolicySetFromConfigOptions): PolicySet; /** Serialize a {@link PolicySet} to JSON (the fingerprint is re-derived on parse, so it is not stored). */ declare function serializePolicySet(set: PolicySet): string; /** * Parse a serialized {@link PolicySet}, refusing an incompatible {@link POLICY_SET_FORMAT_VERSION} and a * `contentHash` that does not match the rebuilt set (tampered or version-skewed). Each policy's spec is * re-validated through {@link policy} (so a malformed spec is refused, not trusted). */ declare function parsePolicySet(text: string): PolicySet; /** * One recorded arrival — the `(key, cost, instant)` inputs a policy saw, *without* the recorded decision. * A {@link plan} re-derives the baseline by cold-replaying the *current* policy over these arrivals, so * the diff is candidate-vs-current-from-a-clean-start over your real arrival timing — never a comparison * against a warm production node's exact decisions (which a cold replay cannot reproduce; see DESIGN §4). */ interface Arrival { readonly key: string; readonly cost: number; readonly at: number; } /** One policy's slice of the corpus: its arrival stream, whether the source was capped, and trace count. */ interface PolicyCorpus { readonly arrivals: readonly Arrival[]; /** True if any source trace hit its recording cap — the arrivals are a prefix, so a diff understates. */ readonly truncated: boolean; readonly traces: number; } /** Recorded traffic to plan against, grouped by policy name. */ type TraceCorpus = Readonly>; /** Extract the arrival stream `(key, cost, at)` from a recorded {@link ReplayTrace}. */ declare function arrivalsFromTrace(trace: ReplayTrace): Arrival[]; /** Fold one or more traces (for the same policy) into a {@link PolicyCorpus}. */ declare function policyCorpus(traces: readonly ReplayTrace[]): PolicyCorpus; /** * Build a corpus from recorded {@link ReplayTrace}s, keyed by policy name (each value is one trace or an * array of traces). Manual-clock traces (from `recordLimiter` or the server shadow) are replayable as-is. * * @experimental Part of the opt-in Policy Plans surface; see STABILITY.md. */ declare function corpusFromTraces(traces: Readonly>): TraceCorpus; /** Build a corpus directly from `recordLimiter` {@link Recording}s, keyed by policy name. */ declare function corpusFromRecordings(recordings: Readonly>): TraceCorpus; /** An empty corpus — every policy then plans to the honest `empty` state (nothing to diff). */ declare function emptyCorpus(): TraceCorpus; /** Default number of top flipped keys/tenants reported per policy. */ declare const DEFAULT_TOP_FLIPPED_KEYS = 10; /** * The honest outcome for one policy's diff. Never a fabricated zero: * - `ok` — replayed cleanly; the flip ledger is exact. * - `empty` — no recorded traffic for this policy (nothing to diff). * - `truncated` — the corpus was a prefix (a source trace hit its cap); the ledger covers the prefix and * *understates* the full effect. * - `not-replayable` — a known non-rate axis (concurrency / escrow / joint-LP): observe live via attribution. * - `refused` — a replay precondition was violated (carries the {@link ReplayRefusal} reason). */ type PolicyDiffState = "ok" | "empty" | "truncated" | "not-replayable" | "refused"; /** A key/tenant whose admit/deny decision flipped, and in which direction(s). */ interface KeyFlip { readonly key: string; readonly allowToDeny: number; readonly denyToAllow: number; readonly total: number; } interface PolicyDiffRefusal { readonly reason: ReplayRefusal | "not-replayable"; readonly message: string; } /** One policy's decision diff: the candidate vs the current-cold baseline over the recorded arrivals. */ interface PolicyDiff { readonly policy: string; readonly state: PolicyDiffState; /** Requests the current policy admitted that the candidate would deny (a *tightening* — the blast radius). */ readonly allowToDeny: number; /** Requests the current policy denied that the candidate would admit (a *loosening*). */ readonly denyToAllow: number; /** `allowToDeny + denyToAllow` (== the divergence report's `flipped`). */ readonly flippedTotal: number; /** Steps differing on any decision field (context — raising a limit shifts `remaining` everywhere). */ readonly divergent: number; /** Arrivals replayed. */ readonly steps: number; /** Distinct keys/tenants with at least one flip. */ readonly affectedKeys: number; readonly topFlippedKeys: readonly KeyFlip[]; readonly refusal?: PolicyDiffRefusal; } interface PlanSummary { readonly policies: number; /** Policies in state `ok` or `truncated` (the diffs that produced a ledger). */ readonly replayable: number; readonly allowToDeny: number; readonly denyToAllow: number; readonly flippedTotal: number; readonly affectedKeys: number; /** Policy names present in the candidate but not the current set. */ readonly added: readonly string[]; /** Policy names present in the current set but not the candidate. */ readonly removed: readonly string[]; } /** The whole plan: a serializable, diffable, CI-gateable artifact. */ interface Plan { readonly current: { readonly contentHash: string; readonly label?: string; }; readonly candidate: { readonly contentHash: string; readonly label?: string; }; readonly corpus: { readonly policies: number; readonly steps: number; readonly truncated: boolean; }; readonly diffs: readonly PolicyDiff[]; readonly summary: PlanSummary; } interface PlanOptions { /** How many top flipped keys to report per policy (default {@link DEFAULT_TOP_FLIPPED_KEYS}). */ readonly topFlippedKeys?: number; } /** * Diff a candidate policy set against the current one over recorded traffic — the hero of Policy Plans. * * For each policy present in **both** sets, it cold-records the *current* spec over that policy's recorded * arrivals to derive the baseline, then replays the *candidate* spec over the same arrivals and folds the * divergence into a directional flip ledger + top movers. The baseline is therefore always the current * policy from a clean start (never stale, never a warm-production comparison). Policies only in the * candidate / current set are reported as added / removed; declared non-replayable axes are surfaced as * `not-replayable` rows. **Pure and never-throws** — every unreplayable policy maps to a typed state, so a * caller can always read a result. * * @experimental Part of the opt-in Policy Plans surface (`throttlekit/policy`); see STABILITY.md. */ declare function plan(current: PolicySet, candidate: PolicySet, corpus: TraceCorpus, options?: PlanOptions): Plan; /** * A blast-radius budget for {@link assertPlanAcceptable} — the "plan in CI" gate. A policy change is * allowed to merge only if its predicted effect stays within these bounds. Every field is optional; * an absent bound is not checked. */ interface PlanBudget { /** Max requests that may newly flip allow→deny (the tightening blast radius). */ readonly maxAllowToDeny?: number; /** Max requests that may newly flip deny→allow (the loosening). */ readonly maxDenyToAllow?: number; /** Max total flips (allow→deny + deny→allow). */ readonly maxFlippedTotal?: number; /** Max distinct keys/tenants affected by any flip. */ readonly maxAffectedKeys?: number; /** Fail if any policy could not be replayed (state `refused` or `not-replayable`). */ readonly requireAllReplayable?: boolean; } /** A {@link Plan} exceeded its {@link PlanBudget}. Carries the machine-readable list of violations. */ declare class PlanRejectedError extends ThrottleKitError { readonly violations: readonly string[]; constructor(violations: readonly string[]); } /** * Fail-loud if a {@link Plan} breaches its {@link PlanBudget} — the adaptive promote-or-hold lever. Throws * {@link PlanRejectedError} (with every violation) so a CI step exits non-zero on a too-large change; * returns silently when the plan is within budget. * * @experimental Part of the opt-in Policy Plans surface; see STABILITY.md. */ declare function assertPlanAcceptable(plan: Plan, budget: PlanBudget): void; /** Serialize a {@link Plan} to pretty JSON — the machine-readable artifact (PR comment, CI evidence). */ declare function planToJSON(plan: Plan): string; interface RenderPlanOptions { /** Top flipped keys to show per policy line (default 3). */ readonly topKeys?: number; } /** * Render a {@link Plan} as a human-readable summary — the default CLI / TUI output. Pure (no I/O, no * color); a caller can print it, log it, or diff it. Carries the honest non-claims (truncation, the * non-replayable axes) inline so the reader is never misled. * * @experimental Part of the opt-in Policy Plans surface; see STABILITY.md. */ declare function renderPlan(plan: Plan, options?: RenderPlanOptions): string; export { type Arrival, DEFAULT_TOP_FLIPPED_KEYS, type KeyFlip, POLICY_SET_FORMAT_VERSION, type Plan, type PlanBudget, type PlanOptions, PlanRejectedError, type PlanSummary, type Policy, type PolicyCorpus, type PolicyDiff, type PolicyDiffRefusal, type PolicyDiffState, type PolicySet, type PolicySetFromConfigOptions, type PolicySetOptions, type RenderPlanOptions, type TraceCorpus, type UnreplayablePolicy, arrivalsFromTrace, assertPlanAcceptable, corpusFromRecordings, corpusFromTraces, emptyCorpus, parsePolicySet, plan, planToJSON, policy, policyCorpus, policySet, policySetFromConfig, renderPlan, serializePolicySet };