-
Notifications
You must be signed in to change notification settings - Fork 0
Policy Plans
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.
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 requestplan() 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 |
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-replayableThe 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.
- The baseline is the current policy cold-replayed over your arrival timing — not 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.
- 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.
ThrottleKit · MIT · 1.0 — API frozen under SemVer (Stability)
- Getting Started
- Choosing a strategy
- Frameworks & the edge
- Distributed & provable
- Federation
- Scaling & the Fleet
- Unified admission
- Pillar 4 — Weighted Fair Escrow
- Middleware integration
- Distributed adaptive concurrency
- Advanced limiting
- Overload, fairness & DDoS
- Operations
- Monitoring — ThrottleKit Lens
- Policy Plans
- Replay
- Performance
- Migrating
- Polyglot & Python
- GALE & TALE