Repository navigation
14.0.0 - 2026-09-14
- Redis refills across runtime-override expiry using the rate applicable to
each interval. A retained:override_expirykey supplies history to later
readers, including new processes. Diagnostics use the same arithmetic
without refreshing storage. - Redis and SQLite retain unpaid capacity debt after lowering a maximum,
extending bucket-state lifetime when necessary. Inactivity can no longer
forgive debt that needs longer than the base TTL to refill. This does not
extend reservation lifetimes or the local state-loss confirmation window. - Breaking: persistent backends reject limit changes and rebuilds with
ValueErrorwhen required debt retention exceeds2**31 - 1seconds.
Retention conservatively uses the slower of the configured and active
override rates, even for short-lived overrides. Predictable Redis rebuild
rejection validates all buckets before changing accounting or overrides.
Arbitrary transport failures during separate rebuild writes still lack a
cross-bucket rollback guarantee. - Interrupted acquisitions whose commit is known now preserve an authentic
reservation inAcquireRefundFailedErrorif ordinary cleanup fails. This
covers Memory, Redis and SQLite, async and sync. Retrying the refund after
the storage problem clears cannot double-credit a racing background refund.
Successful cleanup still propagates the original interruption.
Upgrade steps:
- Breaking deployment requirement: stop every older process sharing a
Redis namespace or SQLite database/namespace, upgrade all participants,
then restart them. Mixed-version operation is unsupported: older clients
can shorten debt retention, and ordinary older Redis reads can refresh an
override without updating its expiry history, causing over- or under-credit. - Already-expired Redis overrides without history, stale history from mixed
versions, and previously lost accounting cannot be reconstructed. Upgrading
or reapplying an override does not repair historical balances. Keep static
configuration consistent across shared-storage participants. - Handle the new finite-retention rejection when reducing limits against deep
debt; allow debt to refill or choose a less restrictive limit. Redis ordinary
reads now require retention Lua withGET,PTTLandPEXPIREpermissions.
See operational guidance
for retention and Redis upgrade details. - No SQLite schema migration is introduced. Retain the migration and API
upgrade requirements from earlier major versions when upgrading past them.
13.0.0 - 2026-09-14
- Breaking: Redis and SQLite now drain a bucket to zero when both state
fields disappear after the backend recently confirmed them present. Previously
total absence always started a full bucket. Confirmation must be younger than
90% of the bucket TTL; partial loss continues to drain regardless of prior
confirmation. Normal refill resumes after the durable repair. - State-loss evidence survives callable reconfiguration for surviving bucket
identities without extending its original confirmation window. SQLite promotes
confirmation only after a successful commit;
rollback and failed commits do not create evidence. Redis confirms validated
successful observations and writes using server time. - Diagnostics predict zero after detectable loss without repairing storage or
refreshing confirmation. Detectable total absence uses the new bucket status
state_loss; partial absence usespartial_missingand now reports the zero
capacity that the decision path will use. - Breaking: missing-state callbacks use
state_loss_drainedfor detected
loss, replacing Redis'spartial_state_drained. Acquisition, consumption, and
refund callbacks on Memory, Redis, and SQLite usefresh_startfor
initialization and include missing/present state-field tuples. A blocked
acquire can emit a committed state-loss repair event. Runtime-capacity and
rebuild snapshots may initialize or repair state without emitting callbacks. - This protection is local to the observing backend. New observers after a
wipe, or observers without recent evidence, still start absent buckets full.
A cold observer that recreates state first can hide the loss from a warm one.
Durable storage and protection against eviction/deletion remain necessary.
Upgrade steps:
- Expect some requests after detected storage loss to wait or time out while
the drained quota refills. Do not rely on deletion to reset an active quota. - Update callback reason matching from
partial_state_drainedto
state_loss_drained, and accept the new diagnostic statusstate_loss. - Keep the v11 database migration and TTL rules and the v12 reconfiguration
and timeout rules when upgrading from older versions. No new SQLite schema
migration is introduced in this release.
12.0.0 - 2026-09-14
This release makes callable reconfiguration safe for pending requests and fixes
SQLite deadline, override, and cleanup behavior across async and sync use.
- Breaking: changing a callable configuration's bucket set now raises
ValueErrorwhile another acquisition for the same model family is pending.
This includes adding or removing a metric and changing a quota window.
Refunds can still release the existing waiters through the cached backend.
Changes to limits alone retain their existing behavior. - SQLite refills at each runtime override's rate until its expiry, then at the
configured rate. Expiry preserves debt and uncapped overflow, and diagnostic
reads predict the same transition without modifying the database. Overrides
remain bound to the configuration under which they were set. - SQLite bucket inactivity preserves an override until its own expiry.
Callable bucket-set rebuilds preserve static configuration and consult the
database for active overrides; expired or externally changed overrides no
longer reappear from a limiter's local cache. - Breaking: SQLite
timeout=0now rejects a busy executor or engine lock
immediately, even when capacity remains. It previously queued behind other
callers in the same process. Use a positive timeout to wait for contention. - Async SQLite initialization runs off the event loop. Finite acquisition
deadlines cover cold initialization, executor queues, and engine locks;
timeout=0rejects a busy SQLite executor or lock without queueing.
Custom builders' publicbuild()overrides remain honored. Async construction
requires explicit__token_throttle_async_build__opt-in. - Closing a limiter after its drain times out can be retried to release its
resources. The limiter continues to reject new operations once closing starts.
Cancelling an async SQLite builder close retains unfinished backends for a
later cleanup attempt. - Scoped refunds on Memory and Redis preserve unrelated buckets' stored
overflow while retaining complete callback snapshots. - Cancelling or timing out an identical SQLite acquisition replay leaves the
original reservation and its consumed capacity intact. - Regression coverage includes shared accounting sequences, override expiry
boundaries, rollback after database errors, and recovery after a writer
process is killed during a transaction.
Upgrade steps:
- Drain pending acquisitions before changing a callable configuration's metrics
or quota windows, or retry the change after those acquisitions finish. - Use a positive SQLite acquisition timeout if requests should wait behind
other local callers when capacity is available. - Keep the SQLite database migration and persistent-backend TTL requirements
from 11.0.0 when upgrading from an older release. This release adds no new
SQLite schema version.