Skip to content

Policy Plans

Ameya Borkar edited this page Jun 10, 2026 · 1 revision

Policy Plans — a terraform plan for your limits

Changing a limit in production is usually a blind bet: ship it, watch the logs, hope. Policy Plans (throttlekit/policy, @experimental) makes it a terraform plan for rate / cost limits — replay your own recorded traffic against a candidate policy and read the exact, per-policy, per-key allow↔deny decision diff before you deploy.

It is pure and never-throws, built entirely on throttlekit/testkit — no change to the frozen core.

The library — plan(current, candidate, corpus)

import { recordLimiter } from "throttlekit/testkit";
import { policy, policySet, corpusFromRecordings, plan, renderPlan, assertPlanAcceptable } from "throttlekit/policy";

// record real traffic against today's limiter…
const rec = recordLimiter({ strategy: "fixedWindow", limit: 3, windowMs: 1000 });
for (let i = 0; i < 6; i++) rec.limiter.checkSync("tenant-a");

// …then ask what tightening to limit 2 would have done — before shipping it
const current   = policySet([policy("api", { strategy: "fixedWindow", limit: 3, windowMs: 1000 })]);
const candidate = policySet([policy("api", { strategy: "fixedWindow", limit: 2, windowMs: 1000 })]);
const result = plan(current, candidate, corpusFromRecordings({ api: rec }));

console.log(renderPlan(result));                   // "api: 1 allow→deny, 0 deny→allow over 6 arrival(s)…"
assertPlanAcceptable(result, { maxAllowToDeny: 0 }); // throws in CI — the change would 429 a live request

plan() cold-records the current policy over the recorded arrivals to derive the baseline, replays the candidate over the same arrivals, and returns a directional flip ledger + the top movers per policy.

Piece What it is
policy / policySet / policySetFromConfig Content-addressed Policy / PolicySet artifacts (fingerprinted, serializable via serializePolicySet / parsePolicySet)
corpusFromRecordings / corpusFromTraces Build a corpus from recordLimiter recordings or from exported ReplayTrace JSON
plan(current, candidate, corpus) The engine — a PolicyDiff of allow↔deny flips + top movers, pure and never-throws
renderPlan / planToJSON Human and machine renderers
assertPlanAcceptable(result, …) The CI gate — throws when the predicted blast radius exceeds your thresholds

On the server — policy plan + the Lens Plan tab

The same engine ships in throttlekit-server as a fail-closed, audited subcommand that diffs a candidate config against the current one over recorded traffic:

# diff a candidate config over recorded traffic (a trace file, or the durable capture store)
throttlekit-server policy plan --config .throttlekit.yaml --candidate candidate.yaml --corpus traffic.json

# gate it in CI — non-zero exit if the change is too big
throttlekit-server policy plan -c current.yaml --candidate candidate.yaml --from-capture \
  --credential "$TK_CAP" --max-allow-deny 0 --require-replayable

The corpus is either a trace JSON file or the server's durable capture store (--from-capture, read through the same fail-closed + audited path as decision capture). The --max-allow-deny / --max-deny-allow / --max-flips / --max-keys / --require-replayable gate exits non-zero past the predicted blast radius; --json emits the machine-readable Plan artifact.

You can also run a whole-config plan live in ThrottleKit Lens: start with --plan-candidate <config> (plus an enabled replay: block for the corpus), open the Plan tab, and press P to diff the candidate against the running config over the recorded shadow traffic.

Honest by construction

  • The baseline is the current policy cold-replayed over your arrival timingnot a warm-production comparison (a cold replay can't reproduce a warm node's exact decisions).
  • Leaf rate + cost limiters diff exactly. Every other axis — concurrency, two-tier, escrow, federated, joint-LP — is reported not-replayable ("observe it live via binding-axis attribution"), never scored as a fabricated zero.

See also

  • Replay — the deterministic record/replay testkit Policy Plans is built on.
  • Monitoring — ThrottleKit Lens — the live Plan tab and binding-axis attribution for the non-replayable axes.
  • Operations — decision capture, the durable corpus source for --from-capture.

Clone this wiki locally