Skip to content

v14.0.0

Latest

Choose a tag to compare

@Elijas Elijas released this 14 Sep 16:08
· 33 commits to main since this release

14.0.0 - 2026-09-14

  • Redis refills across runtime-override expiry using the rate applicable to
    each interval. A retained :override_expiry key 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
    ValueError when required debt retention exceeds 2**31 - 1 seconds.
    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 in AcquireRefundFailedError if 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 with GET, PTTL and PEXPIRE permissions.
    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 uses partial_missing and now reports the zero
    capacity that the decision path will use.
  • Breaking: missing-state callbacks use state_loss_drained for detected
    loss, replacing Redis's partial_state_drained. Acquisition, consumption, and
    refund callbacks on Memory, Redis, and SQLite use fresh_start for
    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_drained to
    state_loss_drained, and accept the new diagnostic status state_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
    ValueError while 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=0 now 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=0 rejects a busy SQLite executor or lock without queueing.
    Custom builders' public build() 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.