Skip to content

Latest commit

 

History

History
699 lines (434 loc) · 91.4 KB

CHANGELOG.md

File metadata and controls

699 lines (434 loc) · 91.4 KB

Changelog

0.24.1 (2026-10-21)

Bug Fixes

  • deps: floor certifi at 2026.10.19 for PYSEC-2026-4320 (LAB-10251) (#598) (00d8af5)

0.24.0 (2026-10-18)

⚠ BREAKING CHANGES

  • decorators: a custom key= key carries the decorated function's identity (LAB-10003) (#583)

Bug Fixes

  • arrow: return writable pandas frames from single-batch Arrow reads (LAB-10013) (#582) (73e57c2)
  • decorators: a custom key= key carries the decorated function's identity (LAB-10003) (#583) (087d4af)

Performance Improvements

  • arrow: zero-copy File-backend reads for ArrowSerializer instances and Arrow/polars results (LAB-9999) (#581) (5d4e106)

0.23.0 (2026-10-18)

⚠ BREAKING CHANGES

  • decorators: with nothing configured, @cache caches in L1 instead of dialling localhost Redis (LAB-10002) (#580)
  • monitoring: removes cachekit.monitoring.pool_monitor (PoolMonitor, PoolStats, CachedPoolMetrics), FeatureOrchestrator.set_pool_manager() and its pool_monitor property, and StructuredLogger.connection_pool_utilization() with HIGH_UTILIZATION_THRESHOLD. No deprecation aliases are provided. Nothing in the package ever called set_pool_manager() or the acquire/release hooks; pool statistics remain available through cachekit.health, which is unchanged.
  • logging: CachekitConfig.log_buffer_size, CachekitConfig.log_batch_size and CachekitConfig.log_flush_interval are removed, along with their env vars CACHEKIT_LOG_BUFFER_SIZE, CACHEKIT_LOG_BATCH_SIZE and CACHEKIT_LOG_FLUSH_INTERVAL. They configured the background log writer thread, which is deleted: structured log entries are now written synchronously. Passing one of these fields to the constructor now raises ValidationError. A still-exported env var is ignored, so startup is unaffected.
  • config: refuse a malformed deployment UUID at load without quoting it (LAB-8772) (#570)
  • config: refuse an invalid assignment to the loaded settings and write nothing (LAB-8739) (#567)
  • decorators: share one call between concurrent misses on a key, run in its own task (LAB-7441) (#547)
  • redis: code that reads redis-py's exception off the error stops seeing it. BackendError.original_exception and __cause__ for a redis-py failure are now a RedisClientError, not redis-py's exception. Code that checked isinstance(err.original_exception, redis.exceptions.AuthenticationError) should check issubclass(err.original_exception.exc_type, ...). A RedisBackend error carried no original_exception before; it now carries the same RedisClientError. repr(RedisBackendConfig(...)) and str(...) no longer show redis_url; the attribute is unchanged.
  • observability: the tracing surface, which never traced, is removed: MonitoringConfig.enable_tracing (passing it raises TypeError), the enable_tracing key of DecoratorConfig.to_dict(), create_cache_wrapper(enable_tracing=), FeatureOrchestrator.create_span, FeatureOrchestrator.set_span_attributes, FeatureOrchestrator.record_exception, handle_cache_error(span=), and StructuredLogger.set_trace_id / clear_trace_id. A failed sync client creation or L2 GET no longer records a second cache_operations_total sample with operation="exception"; it records one failure sample, as the async paths do.
  • config: master_key= on @cache, @cache.secure, DecoratorConfig.secure(), EncryptionConfig, CacheSerializationHandler and validate_encryption_config raises TypeError for a bytes, bytearray or memoryview key: pass key.hex(). Only EncryptionWrapper takes raw key bytes. With encryption on, such a key already failed, with AttributeError. With encryption unset, the decorator raised ConfigurationError for it, and now raises TypeError. With encryption off (encryption=False, EncryptionConfig(enabled=False)) it was accepted, and failed only when an encrypted entry was read; it now raises where it is passed. On Python 3.14, CacheSerializationHandler read a bytes key holding ASCII hex digits as that hex; it now raises too.
  • decorators: @cache(config=..., encryption=...) raises ConfigurationError when the config is encrypted (DecoratorConfig.secure(...), or a config built with encryption=EncryptionConfig(enabled=True, ...)), and @cache(config=DecoratorConfig.io(...), backend=...) raises ConfigurationError. A keyword that names no DecoratorConfig field raises ConfigurationError, not TypeError, from @cache, from every preset but @cache.local, from the DecoratorConfig preset classmethods and beside config=; so does encryption= on @cache.secure and DecoratorConfig.secure(). EncryptionWrapper(master_key=...) raises EncryptionError, and previous_master_keys= raises KeyringConfigurationError, for a raw key that is not exactly 32 bytes: pass bytes.fromhex(hex_key), never hex_key.encode().
  • serializers: EncryptionWrapper now raises ConfigurationError at construction when its serializer's class does not declare cross_sdk_compatible = True. AutoSerializer and unmarked custom serializers are refused. This is a breaking change for code that builds the wrapper directly around AutoSerializer.
  • cachekitio: a subdomain of the two API hosts now needs CACHEKIT_ALLOW_CUSTOM_HOST=true. A custom host with non-ASCII characters must be given in its ASCII (xn--) form.
  • decorators: DecoratorConfig().backend is UNSET, not None. UNSET is falsy, so if config.backend: reads as before, and to_dict() still reports an unset backend as None. Code that tests config.backend is None on an unset backend must test is UNSET instead. Type checkers now see UNSET in the field's type, so narrowing with is not None and then using the value as a backend is flagged; narrow with if config.backend: or is not UNSET. DecoratorConfig(..., backend=None) and every preset with backend=None now mean L1-only instead of "resolve the default backend". To keep the old behaviour, omit backend from the config. DecoratorConfig.secure(..., backend=None) and an interop config with backend=None, which decorated before, now raise ConfigurationError at decoration.
  • encryption: encrypted entries written by earlier releases through a prefixing backend (MemcachedBackend with a key_prefix, the tenant-scoped Redis backend from env auto-detection or RedisBackendProvider, or a custom backend with a non-empty key_prefix) no longer authenticate. By default each one is read once as a miss, counted as auth_tamper, recomputed and overwritten. With fail_closed=True, every read of one raises DecryptionAuthenticationError until it expires or is deleted. Delete those entries, or move the cache to a fresh key space (a new namespace or Memcached key_prefix), rather than wait out the TTL. During a rolling upgrade, old and new processes read each other's writes as failed authentication, so give the new release a fresh key space or stop the old processes first. There is no fallback to the earlier AAD. Unaffected: a RedisBackend you construct yourself, FileBackend, CachekitIOBackend, and interop mode.

Features

  • invalidation: report the L2 delete outcome of an exact-key invalidation (LAB-9554) (#576) (27454b2)

Bug Fixes

  • backends: declare with_timeout's decorated shape and restore the pool timeout after overlapping windows (LAB-4489) (#558) (93d4d93)
  • backends: drop with_timeout windows a forked child inherits (LAB-4489) (#559) (70ffd59)
  • cachekitio: hold a bytes api_key only wrapped when a later check raises (LAB-8288) (#529) (707aad2)
  • cachekitio: keep the API key off a failed request's exception chain (LAB-8304) (#539) (6919e13)
  • cachekitio: keep the API key off an interrupt raised mid-request (LAB-8339) (#541) (966f25f)
  • cachekitio: re-lease a client that close_http_clients() closed (LAB-8032) (#533) (f2bf102)
  • cachekitio: send X-CacheKit-TTL alone and ceil sub-second TTLs (LAB-8206) (#520) (ead1a09)
  • cachekitio: validate the host the HTTP client connects to (LAB-8207) (#519) (f38fd55)
  • circuit-breaker: count a probe's outcome only toward the cycle that admitted it (LAB-5374) (#565) (a2c5f2c)
  • config: refuse a bytes master_key where a hex string is taken (LAB-8281) (#526) (22d330b)
  • config: refuse a malformed deployment UUID at load without quoting it (LAB-8772) (#570) (277aac3)
  • config: refuse an invalid assignment to the loaded settings and write nothing (LAB-8739) (#567) (29c6a59)
  • decorators: count the async backed miss in cache_info() (LAB-8298) (#538) (904e4bb)
  • decorators: keep backend=None L1-only on reused decorators and in DecoratorConfig (LAB-8059) (#515) (da70f3a)
  • decorators: refuse an out-of-model interop return on uncached calls (LAB-5375) (#530) (2e1fba5)
  • decorators: reject keywords a preset does not take, and raw keys that are not 32 bytes (LAB-8223) (#524) (9b3eb30)
  • decorators: restore the stats context on every sync exit, interrupts included (LAB-8741) (#568) (d2dc83a)
  • decorators: start no SWR refresh thread in a child forked without at-fork hooks (LAB-9129) (#573) (dc69e28)
  • decorators: time each latency sample over the operation it names (LAB-7148) (#554) (e1a4f8f)
  • decorators: with nothing configured, @cache caches in L1 instead of dialling localhost Redis (LAB-10002) (#580) (e0cbde5)
  • deps: raise the data extra's pandas floor to 2.2.2 (LAB-10026) (#585) (c65e842)
  • encryption: bind the backend key prefix into the AES-GCM AAD (LAB-8115) (#518) (30201ee)
  • encryption: refuse bare Arrow after decrypt and a non-bool compressed AAD token (LAB-8182) (#517) (256f01d)
  • encryption: refuse master_key= repeated in previous keys at decoration (LAB-8701) (#563) (97bc0b6)
  • file: expire a File-backend entry at its expiry second (LAB-8073) (#514) (9312ba7)
  • fork: start no thread where cachekit is first imported after a fork from C of a threaded parent (LAB-9128) (#579) (f79463d)
  • interop: decode non-string map keys; run interop-mode 1.3.0 and decode-bounds 1.2.0 vectors (LAB-8307) (#537) (819460b)
  • logging: honour sub-percent sampling rates and gate the remaining sinks in a hookless child (LAB-9155) (#574) (a1e5d97)
  • logging: write structured entries synchronously so a forked child keeps them (LAB-9130) (#572) (e06cc4c)
  • observability: remove the no-op tracing surface (LAB-6369) (#546) (ba6368b)
  • redis: clear an interrupt's frames on its way out of a Redis operation (LAB-8396) (#564) (09c2b8a)
  • redis: free a failed op's frames on LOADING/WRONGPASS, and log a release failure after the lock block raised (LAB-8383) (#551) (67006b5)
  • redis: keep the Redis password off a failed operation's traceback (LAB-8338) (#542) (84ec76c)
  • serializers: EncryptionWrapper refuses a serializer that is not cross-SDK (LAB-8195) (#521) (fc45785)
  • serializers: raise SerializationError when numpy or pandas cannot load on decode (LAB-8131) (#534) (7aeb26c)
  • serializers: skip the envelope probe on decrypted StandardSerializer reads (LAB-8371) (#544) (1e779a0)
  • tests: draw redis_proc ports below the Kubernetes NodePort range (LAB-8072) (#516) (78d1fa6)

Performance Improvements

  • cachekitio: free a failed request's frames without the cyclic GC (LAB-8378) (#552) (c0b5cab)
  • decorators: build the L2 cache handler once per backend; class-based backpressure permit (LAB-7112) (#553) (25bbabe)
  • decorators: share one call between concurrent misses on a key, run in its own task (LAB-7441) (#547) (b8967d3)
  • file: keep entry count and bytes in counters instead of two directory scans per set (LAB-7107) (#512) (b1cfc89)
  • file: narrow the FileBackend lock to the rename commit and resolve cache_dir once (LAB-7076) (#535) (6ad08ae)
  • ir: ratchet four stale perf-ir budgets to main's cost (LAB-8629) (#556) (1c049a1)
  • serializers: load numpy, pandas and pyarrow on first use, not at import cachekit (LAB-7093) (#509) (9533b55)
  • serializers: skip the standard object_hook's .get() calls on non-marker maps (LAB-7113) (#508) (3db2941)
  • telemetry: skip disabled cache-op logging and bind each Prometheus series once (LAB-7067) (#555) (fef0bb1)

Code Refactoring

  • monitoring: remove the unwired PoolMonitor and StructuredLogger.connection_pool_utilization (LAB-9448) (7dcba73)

0.22.0 (2026-10-04)

⚠ BREAKING CHANGES

  • cachekitio: replace httpx with urllib3 for CachekitIO requests (LAB-7931) (#502)
  • decorators: refuse an encrypting serializer at decoration (LAB-5320) (#467)

Features

  • cachekitio: replace httpx with urllib3 for CachekitIO requests (LAB-7931) (#502) (3fe6772)
  • cachekitio: send a cachekit-py/<version> User-Agent (LAB-7052) (#479) (9dc474a)
  • invalidation: announce each invalidation on a Redis pub/sub channel (LAB-652) (#470) (5733626)
  • invalidation: detect a silent Redis peer on the listener with TCP_USER_TIMEOUT (LAB-7586) (#499) (986e4ff)
  • invalidation: opt-in listener evicts L1 on other processes' invalidations (LAB-652) (#472) (1ae8f12)
  • l1: a forked child starts with an empty L1 (LAB-652) (#471) (5097c12)

Bug Fixes

  • arrow: reject dict values that pyarrow stores as the wrong column (LAB-7490) (#459) (5cec332)
  • cachekitio: build the sync client on HTTP/1.1 with a 32-connection pool (LAB-7062) (#489) (80dbf5c)
  • cachekitio: never reuse a parent's pooled connection after fork (LAB-7146) (#468) (15c1351)
  • cachekitio: parse X-CacheKit-Fresh-For with the spec grammar (LAB-7838) (#504) (6cb5f4d)
  • cachekitio: retry a write once on 503 with a short Retry-After (LAB-7686) (#477) (77a16a5)
  • circuit-breaker: stop the decorated function's exceptions tripping the breaker (LAB-5319) (#485) (af48926)
  • decorators: degrade on async lock errors instead of raising or polling (LAB-5346) (#473) (6f901f6)
  • decorators: refuse an encrypting serializer at decoration (LAB-5320) (#467) (ef4a7f2)
  • invalidation: delete the pre-0.20.0 twin on no-args invalidation (LAB-5308) (#464) (f8365d3)
  • invalidation: recommend --py-call-uwsgi-fork-hooks, never --py-call-osafterfork, for uWSGI (LAB-7855) (#491) (3e192f7)
  • invalidation: throttle the listener's reconnect WARNING to one a minute (LAB-7638) (#482) (b3a6c8a)
  • l1: bound one entry to an eighth of the L1 budget (LAB-7823) (#487) (35b6791)
  • metrics: size the duration and size histogram buckets for ms and bytes (LAB-7587) (#476) (f1a52bd)
  • object-cache: repair L1-only caches in a forked child (LAB-7856) (#494) (0a78131)
  • redaction: digest keys holding an unpaired surrogate instead of raising (LAB-7624) (#497) (000b110)
  • redis: decide on hiredis before redis imports so 3.14t keeps the GIL off (LAB-7077) (#465) (c19ab6b)
  • swr: release refresh slots stranded on stopped event loops (LAB-7295) (#488) (b41c626)

Performance Improvements

  • cachekitio: fan out whole-function invalidation deletes, 16 at a time (LAB-7070) (#478) (72026b1)
  • cachekitio: keep idle connections 390 s with TCP keepalive 60/10/3 (LAB-7061) (#483) (c695bf2)
  • cachekitio: skip the uncontended double-check read and release the miss lock in the background (LAB-7064) (#480) (f4b8dfe)
  • decorators: decide refresh_ttl_on_get from Fresh-For, refresh in the background (LAB-7074) (#435) (cc9bf65)
  • metrics: recover the perf-ir drift on the L2 and miss paths (LAB-7801) (#481) (d200c3a)
  • serializers: memoize CK frame headers and pack AutoSerializer values without rebuilding the tree (LAB-7069) (#441) (c2e907a)

0.21.0 (2026-10-03)

⚠ BREAKING CHANGES

  • reliability: removed the cachekit.reliability.profiles module, with ReliabilityProfile, ProfileConfig, PROFILE_CONFIGS, get_profile_config, get_profile_description, recommend_profile, HIGH_THROUGHPUT_THRESHOLD_RPS and LOW_THROUGHPUT_THRESHOLD_RPS; cachekit.reliability no longer re-exports ReliabilityProfile, ProfileConfig, get_profile_config or recommend_profile. No cachekit API read these values, so no runtime behaviour changes.
  • encryption: a master key with no stated encryption intent fails at construction (LAB-4739) (#411)
  • config: CachekitConfig.l1_enabled, CachekitConfig.enable_prometheus_metrics, CachekitConfig.backend_provider_class and CachekitIOBackendConfig.max_retries are removed, along with their env vars CACHEKIT_L1_ENABLED, CACHEKIT_ENABLE_PROMETHEUS_METRICS, CACHEKIT_BACKEND_PROVIDER_CLASS and CACHEKIT_MAX_RETRIES. Passing one of these fields to the constructor now raises ValidationError. A still-exported env var is ignored, so startup is unaffected.
  • interop: an interop namespace or operation containing .. now raises ConfigurationError at decoration time, on every backend; the manual generate_interop_key helper raises InteropError. Rename the segment; its keys become a full cache miss.

Features

  • encryption: a master key with no stated encryption intent fails at construction (LAB-4739) (#411) (2baad1b)
  • l1-only: back off a failed background refresh before retrying it (LAB-7442) (#462) (b6f3d46)

Bug Fixes

  • build: remove build-pgo target and unused PGO profiles (LAB-7151) (#426) (a8c8f16)
  • cachekitio: DELETE never reports existence, so a 404 raises (LAB-5580) (#447) (e56a139)
  • cachekitio: key the async client per running event loop (LAB-7102) (#433) (c6a04b9)
  • cachekitio: reject the empty cache key client-side (LAB-6550) (#397) (9c2c2be)
  • config: drop raw-input locals from RedactingSettings frames before raising (LAB-4766) (#386) (0722b08)
  • config: honour an explicit redis_url kwarg when REDIS_URL is set (LAB-6831) (#418) (74d12c1)
  • config: intent presets accept overrides of the fields they set (LAB-5361) (#390) (3d02435)
  • config: redact config validation errors raised through the core schema (LAB-5213) (#404) (25baba1)
  • config: remove four config fields nothing reads (LAB-4765) (#394) (4307cd5)
  • decorators: async return value the cache write cannot serialize no longer counts toward the circuit breaker (LAB-7461) (#442) (e410aa7)
  • decorators: key and name the registry set by a str namespace's exact value (LAB-6197) (#388) (74937d5)
  • decorators: key-generation failures no longer count toward the circuit breaker (LAB-5376) (#381) (67732fb)
  • decorators: L1-only encryption refusal names the full backend resolution order (LAB-6738) (#413) (704db62)
  • decorators: log failed L2 deletes of a no-args invalidation once, with the count (LAB-6074) (#383) (ff952ef)
  • decorators: re-raise KeyringConfigurationError from @cache L2 reads (LAB-4841) (#393) (eb6a6e4)
  • decorators: re-raise KeyringConfigurationError from @cache writes (LAB-6519) (#396) (08179ba)
  • decorators: re-track a key on any exception from its L2 delete (LAB-6198) (#382) (5bef998)
  • decorators: run a decorated function at most once when it raises BackendError (LAB-5360) (#429) (7be20d7)
  • decorators: serve L1 hits whatever the circuit-breaker state (LAB-5351) (#385) (6407b58)
  • deps: raise the redis-py floor to 4.6.0 and test it in CI (LAB-7560) (#474) (fe317c9)
  • deps: raise urllib3 to 2.8.0 for three 2026-09-30 advisories (LAB-6743) (#412) (90d705c)
  • encryption: resolve the caller's tenant on encrypted reads (LAB-4260) (#410) (0f436f1)
  • file: miss on nonzero header flags; correct File backend docs (LAB-7152) (#431) (6f649c9)
  • interop: hash str/int/float/bytes subclass arguments as their exact base-type value (LAB-6201) (#384) (4044866)
  • interop: reject double-dot interop segments (LAB-5906) (#391) (5ade81d)
  • l1: at-fork hook repairs every cache even when its drop warning raises (LAB-6370) (#392) (b75bcbb)
  • l1: never clear a cache under a live lock holder on fork reset (LAB-6375) (#416) (468c7c8)
  • l1: remove the old entry before eviction on update (LAB-6897) (#419) (eb7af5f)
  • l1: start no cleanup thread in a child forked without at-fork hooks (LAB-7271) (#436) (c3fabda)
  • memcached: pool connections per server so threads never share a socket (LAB-6744) (#414) (7d5da3a)
  • memcached: raise when HashClient skips a command in its retry window (LAB-6740) (#432) (20fce0e)
  • metrics: export circuit_breaker_state to Prometheus as a per-state count (LAB-6403) (#428) (1f05bd4)
  • metrics: give a forked child its own batching state and record synchronously after shutdown (LAB-6779) (#434) (7b985b0)
  • metrics: make the circuit breaker the only writer of circuit_breaker_state (LAB-7269) (#463) (0b27d40)
  • metrics: record label-less generic counters and histograms (LAB-6389) (#403) (a57569c)
  • metrics: restart the batching worker when auto-detect returns to batched mode (LAB-6376) (#407) (dab6b08)
  • redis: size every pool from config (default 50) and wait on exhaustion (LAB-7457) (#444) (e2a4c55)
  • redis: wire socket_keepalive into both connection pools (LAB-7150) (#427) (87ed6b8)
  • reliability: count CLOSED breaker failures in a 60 s rolling window (LAB-5352) (#405) (22a4178)
  • security: keep secrets wrapped in cachekit frames on raised errors (LAB-6515) (#430) (02fb730)
  • serializers: classify a non-encodable compressed header as corruption (LAB-6354) (#380) (1971dd6)
  • serializers: integrity-off AutoSerializer refuses a header format no writer emits (LAB-6359) (#387) (b475b6d)
  • swr: log failed and skipped background refreshes at WARNING (LAB-7456) (#446) (1ba3312)

Performance Improvements

  • backends: batch L2 deletes in whole-function invalidation (LAB-6075) (#408) (db0345e)
  • cachekitio: return the 404 miss before raise_for_status (LAB-7066) (#423) (d2934eb)
  • keys: fast-path exact primitives in key normalisation (LAB-7068) (#422) (84db725)
  • keys: skip func-name regex passes that cannot change the name (LAB-7405) (#469) (21c87b9)
  • redis: cache the redis.Redis client per PooledClientProvider (LAB-7075) (#421) (b3312de)
  • serializers: hash checksummed Arrow/NumPy bodies once per read (LAB-6371) (#406) (df121d8)

Code Refactoring

  • reliability: delete unconsumed reliability profiles module (LAB-6352) (#445) (0b71441)

0.20.0 (2026-09-30)

⚠ BREAKING CHANGES

  • reliability: removed cachekit.reliability.create_decorator_config and, from cachekit.reliability.profiles, get_decorator_kwargs, minimal_reliability_decorator, balanced_reliability_decorator and full_reliability_decorator. Their output raised TypeError when passed to @cache, so no working call is lost. cachekit.decorators.wrapper.create_cache_wrapper no longer accepts undeclared keyword arguments; it raises TypeError instead of ignoring them.
  • decorators: FeatureOrchestrator (exported from cachekit.decorators.__all__) no longer defines generate_correlation_id, create_correlation_id, set_correlation_id, clear_correlation_id, start_request, end_request, or the correlation_tracker property. FeatureOrchestrator.handle_cache_error no longer accepts a correlation_id keyword argument. An explicit caller passing correlation_id= now flows it through **extra_context, and its log record is unchanged. The decorator's own *_failed error records no longer carry a correlation_id key; that value was a per-call UUID, or None, and nothing read it. src/cachekit/monitoring/correlation_tracking.py (CorrelationTracker, LoggerIntegratedTracker, module-level generate_correlation_id/set_correlation_id/get_correlation_id/clear_correlation_id/correlation_context) is removed entirely.
  • metrics: AsyncMetricsCollector.record_cache_operation() and FeatureOrchestrator.record_cache_operation() no longer accept hit. Callers that pass it now raise TypeError. FeatureOrchestrator.record_success() no longer emits a metrics record.
  • config: presets apply the canonical default TTLs; drop CACHEKIT_DEFAULT_TTL (LAB-4641) (#318)
  • intent: .secure rejects integrity_checking=False on both paths (LAB-3970) (#359)
  • interop: Existing deployments that use ns or nsapi as an interop namespace on Redis, Memcached or File backends worked before this change and must now rename the namespace. Renaming means a full cache miss for that namespace.
  • backends: if your deployment used cachekit under more than one tenant (a call with no tenant set counts as default), purge the Redis entries that earlier releases wrote. In earlier releases a decorated function that resolved Redis from the environment (CACHEKIT_REDIS_URL, REDIS_URL or the localhost default), or a backend taken from RedisBackendProvider.get_backend(), stayed bound to the tenant current when that backend was first obtained, so every tenant's L2 writes through it landed under that one tenant's t:<tenant>: prefix. That tenant keeps reading those entries as its own after the upgrade, and some hold another tenant's value. Entries written with ttl=None, or kept alive by refresh_ttl_on_get=True, never expire, and a no-argument invalidate_cache() reaches only those the calling tenant has read in that process since it started, because no key registry recorded them. After the last process running an earlier release has stopped, delete every t:* key in each database cachekit uses. Run this with the Python that cachekit is installed in, since it uses cachekit's redis-py dependency; it prints 0 keys left when it is done: python -c 'import itertools, sys, redis; r = redis.Redis.from_url(sys.argv[1]); keys = r.scan_iter(match=sys.argv[2], count=1000); [r.unlink(*b) for b in iter(lambda: list(itertools.islice(keys, 1000)), [])]; print(sum(1 for _ in r.scan_iter(match=sys.argv[2], count=1000)), "keys left")' '<redis-url>' 't:*'. Do not use a redis-cli --scan pipeline: a key set through key= can contain a newline or a NUL byte, which the pipeline turns into names that match nothing, so the key survives and the pipeline still exits 0. Run FLUSHDB instead only if that database is dedicated to cachekit. Then restart every process, because L1 keeps any entry a process read before the purge for up to the function's ttl (300 s with ttl=None), and expect a cold cache. On a shared database t:* also matches any other application's keys that start with t:, so run the same command with 't:<tenant>:*' in place of 't:*' for default and for each tenant you have set, the tenant percent-encoded with urllib.parse.quote(tenant, safe=''), an int or UUID tenant as its str() first (tenant org:123 is 't:org%3A123:*'). A deployment that only ever used one tenant is unaffected. RedisBackendProvider.get_backend() no longer binds its backend to one tenant: the backend follows tenant_context on every operation and falls back to the tenant current at the get_backend() call only when the calling context has none; for a backend bound to one tenant, construct cachekit.backends.redis.provider.PerRequestRedisBackend(client, tenant) directly, with client a redis.Redis. A no-argument invalidate_cache() on the Redis backend now deletes only the calling tenant's L2 entries. Set tenant_context only to a str, bytes, int or uuid.UUID: the Redis backend checks each operation's tenant and raises TypeError for any other type, including a bool, an IntEnum member and a bytearray, so convert an IntEnum member with int() and a bytearray with bytes() first. int and UUID tenants now work, keyed by their str() form (42 and "42" share t:42:), which no earlier release did.
  • backends: the decorator namespace "ck" and any "ck:*" namespace are now reserved for cachekit's internal keys and raise ConfigurationError at decoration time. Rename any function cached under namespace="ck" or a "ck:" prefix before upgrading.
  • decorators: configure the live circuit breaker from @cache(circuit_breaker=...) (LAB-5340) (#340)
  • decorators: refuse encryption with backend=None — L1-only held plaintext (LAB-4665) (#322)
  • logging: cachekit.logging.JsonFormatter is removed; importing it now raises ImportError. StructuredLogger.circuit_breaker_state_change and StructuredLogger.set_correlation_id are removed and now raise AttributeError. No deprecation aliases. Use set_trace_id / clear_trace_id (or cachekit.monitoring.correlation_tracking) for request correlation, and your own logging.Formatter subclass for JSON output.
  • reliability: cachekit.reliability.create_optimized_decorator_config is renamed to cachekit.reliability.create_decorator_config. Update imports; no alias is provided. cachekit.hash_utils.fast_hash is renamed to blake3_hash (module-internal, never exported from the package).
  • keys: cache keys for any serializer other than the default change identity on upgrade — no configuration change is required to be affected. A deployment using serializer="auto", "orjson", "arrow", or any serializer passed as an instance was writing :1s keys and will now write :1a, :1o, :1w or an x-prefixed code. That function's entire working set recomputes once at deploy, so plan a cold cache or roll out behind existing warm-up / stampede controls. Deployments on the default serializer are unaffected: their keys were already :1s and stay :1s. Orphaned entries are also a retention question, not only a hit-rate one: once the key changes, invalidate_cache() computes the new key and can no longer reach the old copy, so a deletion for erasure, consent withdrawal or permission revocation reports success while the pre-upgrade entry survives to its TTL — or indefinitely where ttl=None. Flush the affected namespaces on upgrade if you cache personal data rather than relying on expiry.
  • config: CachekitConfig drops retry_on_timeout, max_retries, retry_delay_ms, early_refresh_ratio, enable_corruption_detection and max_key_size, and no longer reads CACHEKIT_RETRY_ON_TIMEOUT, CACHEKIT_MAX_RETRIES, CACHEKIT_RETRY_DELAY_MS, CACHEKIT_EARLY_REFRESH_RATIO, CACHEKIT_ENABLE_CORRUPTION_DETECTION or CACHEKIT_MAX_KEY_SIZE. None of them changed behaviour. Passing one to CachekitConfig(...) now raises ValidationError; CachekitConfig ignores a still-exported env var, so startup is unaffected. CachekitIOBackendConfig declares its own max_retries under the same prefix and still parses CACHEKIT_MAX_RETRIES; this release does not change it. Integrity checking is set per decorator (integrity_checking=), refresh-ahead timing by L1CacheConfig.swr_threshold_ratio, and Memcached retries by MemcachedBackendConfig.retry_attempts.
  • decorators: CachekitIOBackend(...) configuration errors (missing, empty or whitespace-containing API key; invalid API URL) now raise cachekit.config.ConfigurationError at construction instead of ValueError or pydantic ValidationError, so code catching ValueError there must catch ConfigurationError. @cache.io(backend=...) and @cache.io(config=...) now raise ConfigurationError instead of silently ignoring the argument. Under set_default_backend(), @cache(config=...) now uses the backend inside config= instead of the module default. cachekit.backends.cachekitio.client.get_sync_http_client() is replaced by lease_sync_http_client(), which returns a SyncClientLease: hold the lease for as long as its .client is used, because the client is closed when the lease is dropped.
  • encryption: single-tenant tenant_id defaults to "default", not a deployment UUID (LAB-4666) (#321)

Features

  • backends: server-side key registry for cross-process whole-function invalidation (LAB-651) (#343) (d918a19)
  • build: publish CPython 3.14 wheels (LAB-6297) (#361) (63fbe14)
  • encryption: warn once when CACHEKIT_MASTER_KEY auto-activates encryption (LAB-4642) (#319) (6c8b1da)

Bug Fixes

  • backends: scope each Redis operation to the calling tenant, not the first caller (LAB-4773) (#329) (0386d44)
  • cache_handler: evict on corrupt CK frame header, not just payload (LAB-4075) (#307) (ed799c1)
  • cachekitio: drain lock POST/DELETE so a cancel cannot orphan a granted lock (LAB-3648) (#345) (288cb0e)
  • cachekitio: pin the hpack logger at INFO so DEBUG logs cannot expose the API key (LAB-5914) (#351) (3738339)
  • cachekitio: reject API keys outside the RFC 6750 bearer-token charset (LAB-5943) (#352) (c9c9a0e)
  • cachekitio: reject reserved key segments client-side (LAB-2880) (#364) (44b36f1)
  • config: presets apply the canonical default TTLs; drop CACHEKIT_DEFAULT_TTL (LAB-4641) (#318) (f838c60)
  • config: redact inputs from backend config ValidationErrors (LAB-5038) (#334) (c949454)
  • decorators: @cache.io accepts api_key= and rejects backend= (LAB-4643) (#320) (4d36167)
  • decorators: accept l1_enabled on intent presets and keep config= L1 tuning (LAB-4828) (#358) (3f5c1e0)
  • decorators: async miss-store records carry serializer/hit like sync (LAB-3755) (#295) (b733196)
  • decorators: configure the live circuit breaker from @cache(circuit_breaker=...) (LAB-5340) (#340) (a20cc69)
  • decorators: invalidate_cache resolves the same key the write path wrote (LAB-4387) (#312) (bebfc1d)
  • decorators: keep a key re-recorded during whole-function invalidation (LAB-5774) (#348) (c1b125b)
  • decorators: keep L2 decrypt and integrity failures out of circuit-breaker accounting (LAB-5865) (1b30f43)
  • decorators: raise TypeError for an unsupported tenant id on sync as on async (LAB-5713) (6612f39)
  • decorators: refuse encryption with backend=None — L1-only held plaintext (LAB-4665) (#322) (f5340f6)
  • encryption: classify a non-string original_type header as corruption, not tamper (LAB-4350) (#310) (2403e7e)
  • encryption: single-tenant tenant_id defaults to "default", not a deployment UUID (LAB-4666) (#321) (c5da48a)
  • encryption: surface keyring config faults as KeyringConfigurationError (LAB-4818) (93cafd3)
  • intent: .secure rejects integrity_checking=False on both paths (LAB-3970) (#359) (4f25185)
  • interop: key str-subclass segments by their exact str value (LAB-6196) (#360) (d067787)
  • interop: reject reserved namespaces ns and nsapi (LAB-5876) (#350) (6ba98a6)
  • keys: put the real serializer identity in the cache key (LAB-4351) (#311) (ee65250)
  • l1: restart the background cleanup thread in forked children (LAB-4772) (#328) (593c194)
  • metrics: drain queued records before batched worker exits (LAB-6351) (#365) (385229b)
  • metrics: record each cache operation once in cache_operations_total (LAB-3761) (#346) (d317494)
  • metrics: share Prometheus metric objects across collectors (LAB-6355) (#366) (e41c868)
  • redis: classify ClusterDownError as TRANSIENT (LAB-5327) (#339) (e93b693)
  • redis: classify TryAgainError transient, InvalidResponse/LockError permanent (LAB-5353) (#344) (fb9baa6)
  • redis: release a lock won after acquire_lock cancellation (LAB-3606) (#293) (4cfdc63)
  • reliability: circuit breaker recovers from OPEN on the decorator path (LAB-5326) (#341) (9182092)
  • serializers: integrity-off AutoSerializer refuses envelope-shaped values at write (LAB-6401) (#377) (ea1a039)
  • serializers: refuse verified envelopes on integrity-off StandardSerializer reads (LAB-4329) (#379) (9502662)
  • serializers: settle the envelope format by agreement, not by a winner (LAB-2736) (#309) (165c9b5)
  • serializers: treat an entry with no serializer name as a mismatch (LAB-4432) (#373) (b3869a7)

Code Refactoring

  • config: remove six CachekitConfig knobs nothing reads (LAB-4740) (#324) (27e1f95)
  • decorators: remove dead correlation-ID surface (LAB-4733) (#323) (841dba6)
  • logging: remove circuit_breaker_state_change, set_correlation_id and JsonFormatter (LAB-4638) (#317) (0a71361)
  • reliability: remove create_decorator_config family (LAB-4637) (#338) (4214448)
  • reliability: rename create_optimized_decorator_config and fast_hash (LAB-4619) (#315) (8d49e38)

0.19.0 (2026-09-22)

⚠ BREAKING CHANGES

  • logging: remove dead compat surface and Redis branding from StructuredLogger (LAB-4621) (#316)
  • logging: cachekit.logging.UltraOptimizedStructuredLogger is now StructuredLogger; the cachekit.logging.StructuredRedisLogger alias is removed; cachekit.monitoring.pool_monitor.OptimizedPoolMonitor is now PoolMonitor. No deprecation aliases are provided. get_structured_logger() is unchanged and remains the supported entry point.
  • logging: UltraOptimizedStructuredLogger.init no longer accepts mask_sensitive; get_structured_logger() no longer accepts mask_sensitive and now keys _logger_instances on name alone; mask_sensitive_patterns is removed; ProfileConfig.mask_sensitive_data and ProfileConfig.lazy_pii_masking are removed. All were read by nothing and toggled no behavior. Constructors/callers passing them now raise TypeError instead of silently no-op'ing. Same removal shape as L1CacheConfig.namespace_index in v0.18.0 and L1CacheConfig.invalidation_enabled in v0.16.0 (LAB-520).

Features

  • backend: bound L1 backfill by the server's remaining freshness (LAB-557) (#268) (7bd5abf)
  • concurrency: free-threaded CPython support — memory-ordering fixes, gil_used=false, CI lane (LAB-511) (#265) (bda770b)

Bug Fixes

  • ci: fail loudly on attestation lookup failure; decide the codecov pair (LAB-2528) (#270) (2a8b941)
  • ci: make the Atheris fuzz job capable of failing + repair its dead targets (LAB-1140) (#269) (6ab0c28)
  • decorators: async get hits record serializer/size/hit like the sync path (LAB-3765) (#297) (9b96fd2)
  • decorators: async lock double-check L2 hits record get telemetry (LAB-3769) (#303) (683b1c7)
  • decorators: honour set_default_backend() when called after decoration (LAB-4457) (#313) (d20204c)
  • file: guard eviction unlink against a concurrent rename (LAB-2685) (#285) (e917a57)
  • file: write every byte or fail; evict a payload that shrank under read (LAB-2682) (#272) (068adb2)
  • logging: redact raw cache keys on all log paths (LAB-304) (#264) (81f97fb)
  • logging: sanitise error kwarg at the structured cache-operation sinks (LAB-3666) (#301) (d27ec29)
  • redis: stop lock waiters pinning executor threads (LAB-3596) (#290) (ddbeb91)
  • serializers: bound forged-entry error echoes; retire columnar dead code (LAB-3131) (#289) (10a1049)
  • serializers: bound untrusted msgpack decode depth and header allocation (LAB-2503) (#276) (f7c087d)
  • serializers: fail closed on unverified DataFrame/Series envelopes (LAB-2736) (#304) (60da54f)
  • serializers: take the format from the envelope, not the header; drop the header-gated fall-through (LAB-2736) (#308) (69db1c5)
  • serializers: type ByteStorage.retrieve failures and collapse duplicated columnar decode (LAB-2736) (#287) (0aa78ca)

Performance Improvements

  • decorators: backfill L1 on sync L2 hits; size stats by envelope length (LAB-348) (#294) (c019b26)

Code Refactoring

  • logging: remove dead compat surface and Redis branding from StructuredLogger (LAB-4621) (#316) (98d616e)
  • logging: remove dead PII-masking knobs (LAB-3797) (#300) (705640b)
  • logging: rename UltraOptimizedStructuredLogger to StructuredLogger, OptimizedPoolMonitor to PoolMonitor (LAB-4617) (#314) (2f7c979)

0.18.0 (2026-09-03)

⚠ BREAKING CHANGES

  • L1CacheConfig.namespace_index is removed. The flag was read by nothing and toggled no behavior, but it shipped in v0.17.1 and docs/configuration.md documented a copy-pasteable L1CacheConfig(..., namespace_index=True) example — L1CacheConfig is a frozen dataclass, so constructors still passing it now raise TypeError instead of silently lying. L1Cache.invalidate_by_key(), .invalidate_by_namespace() and .invalidate_all() are removed with it; per-key L1Cache.invalidate() is unaffected. Same removal shape as L1CacheConfig.invalidation_enabled in v0.16.0 (LAB-520).

Features

  • encryption: keyring rotation — previous_master_keys + fingerprint selection (LAB-684) (#261) (e1b05ce)

Bug Fixes

  • cachekitio: percent-encode cache key in request path (LAB-2846) (#279) (f000ba3)
  • fuzz: commit per-target corpus seeds cargo-fuzz actually loads (LAB-1149) (#263) (1b85f56)
  • l1: delete dead backed-mode SWR machinery; docs stop claiming backed SWR (LAB-388) (#256) (878ad08)

Performance Improvements

  • file: eliminate two full-payload copies on the non-mmap read path (LAB-770) (#267) (f7e236b)

Code Refactoring

  • delete dead L1 namespace-index/bulk-invalidation machinery (LAB-1433) (#258) (2607faf)

0.17.1 (2026-07-29)

Bug Fixes

  • fuzz: fuzz the codec that ships — core 0.4.0, all 14 targets, fail loudly (LAB-1136) (#251) (511b1e5)

0.17.0 (2026-07-29)

Features

  • envelope: pick up cachekit-core 0.4.0 bin envelopes (LAB-900) (#249) (fa9ea36)

Bug Fixes

  • serializers: byte-aware Arrow batch sizing holds the memory bound on skewed frames (LAB-110) (#244) (e67db58)

Performance Improvements

  • arrow: stream serialize-to-backend writes via BufferWritableBackend (LAB-766) (#247) (539fde9)

0.16.0 (2026-07-24)

⚠ BREAKING CHANGES

  • L1CacheConfig.invalidation_enabled is removed. The flag was read by nothing and toggled no behavior; constructors passing it now raise TypeError instead of silently lying.

Bug Fixes

  • security: EncryptionWrapper.deserialize fails closed on plaintext-claiming input (LAB-271) (#242) (6fcb115)

Code Refactoring

  • remove unwired cross-instance invalidation package (LAB-520) (#237) (c56ac0a)

0.15.0 (2026-07-23)

⚠ BREAKING CHANGES

  • the no-op adaptive_timeout decorator kwarg, the TimeoutConfig nested config, DecoratorConfig.timeout, and ProfileConfig.adaptive_timeout are removed; CircuitBreaker.call() and .call_async() are removed (use should_allow_request() + record_success() / record_failure()). All were non-functional or dead in production.

Code Refactoring

  • remove non-functional adaptive timeout, dead CircuitBreaker.call, unused tenacity (LAB-522) (#239) (5ade101)

0.14.0 (2026-07-22)

Features

  • cache: stale-while-revalidate for cache.io — client transport + decorator surface (LAB-381) (#228) (7841ff0)

Bug Fixes

  • decorators: bind session counters to function identity, not wrapper instance (#233) (83b7c92)
  • swr: LAB-381 panel fast-follow — contextvar propagation, CWE-532 redaction, orphan cut (#235) (9e276b6)

0.13.0 (2026-07-21)

Features

  • interop: opt-in interop/v1 mode — cross-SDK keys + plain-MessagePack values (LAB-245) (#220) (e77fa6d)
  • security: tamper telemetry + fail-closed decrypt policy (LAB-108) (#218) (52d6a46)

Bug Fixes

  • address coderabbit review — run async interop guard before L1 lookup (#227) (1ae680f)
  • security: route async decorator L2 reads through get_cached_value_async (LAB-111) (#216) (7df5860), closes #159

0.12.0 (2026-07-20)

⚠ BREAKING CHANGES

  • config: CachekitConfig fields enable_compression, compression_level and max_chunk_size_mb were removed; constructing CachekitConfig with them now raises a ValidationError (their CACHEKIT_* env vars were already no-ops and remain ignored). max_value_size is now enforced: serialized envelopes larger than it (default 100MB, CACHEKIT_MAX_VALUE_SIZE) are no longer cached.

Features

  • checksum FFI binding + benchmark (cachekit-core#13 Phase 2) (#212) (9245c30)

Bug Fixes

  • config: enforce l1_max_size_mb + max_value_size, remove dead zlib/chunk knobs (LAB-109) (#217) (a5a347c)
  • decorators: L1-only mode (backend=None) honors L1CacheConfig SWR + max_size_mb (LAB-106) (#219) (bef4c05)
  • redis: honour redis_url, fix zero-config DI crash, add finite socket timeouts (LAB-352) (#224) (e45106e)
  • security: fail closed on plaintext frames when encryption is enabled (LAB-241) (#215) (7e5be4a)
  • serializers: AutoSerializer sets compressed metadata from the actual ByteStorage codec (#211) (47f8520)

Performance Improvements

  • release the GIL during ByteStorage compress/hash (LAB-347) (#223) (269aecf)

0.11.1 (2026-06-26)

Bug Fixes

  • re-read settings when CACHEKIT_MASTER_KEY appears + correct missing-key env var name (#200) (12c9fff)

0.11.0 (2026-06-20)

⚠ BREAKING CHANGES

  • orjson is no longer installed by pip install cachekit. To use the orjson serializer (serializer="orjson" or OrjsonSerializer), install cachekit[json]. Without it, get_serializer("orjson") raises an ImportError with an actionable install hint.

Features

  • make orjson an optional dependency (cachekit[json]) (#196) (4512a2a)

Security

  • bump msgpack floor to 1.2.1 (GHSA-6v7p-g79w-8964) (#197) (fc1bb80)

0.10.1 (2026-06-19)

Bug Fixes

  • canonicalize the pythonic serializer alias to auto (#192) (666d09c)
  • checksum-protect numpy serialization without compression overhead (#189) (3b0565f)
  • return writable numpy/Series reads and surface DataFrame/Series corruption clearly (#191) (46a3c3c)

0.10.0 (2026-06-18)

Features

  • zero-copy mmap read path for large plaintext Arrow on the File backend (#171) (#187) (1105454)

Performance Improvements

  • unwrap returns a zero-copy memoryview instead of copying the payload (#162) (#184) (0901732)

0.9.1 (2026-06-15)

Bug Fixes

  • cachekitio: surface HTTP 413 as a clear permanent "value too large" error (#182) (d76d526)
  • deps: bump pyo3 to 0.29 to clear RUSTSEC-2026-0176 and -0177 (#183) (beffbfc)

Security

  • send lock_id via X-CacheKit-Lock-Id header, not query string (#131) (#179) (4cb00df)

0.9.0 (2026-06-11)

Features

  • drop Python 3.9 support, require >=3.10 (#148) (1bb9953)
  • honor user serializer under encryption via cross_sdk_compatible marker (#153) (2ad219d), closes #134
  • support explicit per-function encryption opt-out (tri-state) (#151) (bf86c43)

Bug Fixes

  • bound memory for large DataFrame/Arrow caching (was OOMing at real sizes) (#152) (ccd32c5)
  • evict poisoned L2 entry on corruption at the read API (#177) (3a538fa), closes #159
  • handle pandas nullable/extension dtypes in no-pyarrow DataFrame fallback (#176) (4de3608)
  • harden cache-envelope framing and compression config resolution (#172) (d079f79)
  • reject non-finite (NaN/inf) TTL to prevent immortal cache entries (#174) (d90958c)
  • stop legacy RedisBackend corrupting binary payloads (#173) (82d0417)

0.8.0 (2026-05-31)

Features

  • auto-detect memcached and file backends from environment (#139) (3f69e92)

Bug Fixes

  • pass bare cache key to LockableBackend.acquire_lock (#135) (4d880b7)

0.7.0 (2026-05-28)

Features

  • add tuple preservation to AutoSerializer and 'pythonic' alias (#121) (4ab03d8)

Bug Fixes

  • 5 issues from encrypted-payload E2E testing (#127) (b1aab22)
  • conform CachekitIOBackend.acquire_lock to LockableBackend protocol (#130) (835d20b)
  • preserve Python types in L1-only mode, allow cache_clear() on async (#117) (1fc506b)

0.6.1 (2026-05-16)

Bug Fixes

  • invalidate_cache() with no args now clears all entries (#108) (f0dca32)

0.6.0 (2026-04-28)

Features

Bug Fixes

  • use local> preset ref for private renovate-config repo (#99) (bc30e7e)

0.5.1 (2026-03-28)

Bug Fixes

  • fall back to CACHEKIT_MASTER_KEY env var in cache.secure decorator (#79) (7275e70)

0.5.0 (2026-03-27)

Features

  • add Memcached backend with pymemcache HashClient (#67) (a06e88a)

0.4.1 (2026-03-23)

Bug Fixes

  • default L1-Status header for standalone CachekitIO usage (#64) (1eed9e4)

0.4.0 (2026-03-17)

Features

  • add CachekitIO SaaS backend with SSRF protection (#60) (472623e)

0.3.1 (2026-02-22)

Bug Fixes

  • async cache_clear() TypeError + nested numpy serialization (#54) (5a9c8ca), closes #50
  • lazy-load ArrowSerializer to avoid ImportError without pyarrow (#44) (065eb23)

0.3.0 (2025-12-18)

Features

  • extend key generator with Path, UUID, Decimal, Enum, datetime, and constrained numpy support (#38) (78e025a)

0.2.3 (2025-12-18)

Bug Fixes

  • downgrade to edition 2021 and MSRV 1.80 for stable Rust compatibility (#32) (9b13722)
  • use AliasChoices for REDIS_URL env var fallback (#35) (274a925)

0.2.2 (2025-12-18)

Bug Fixes

  • fail-fast import guard for ArrowSerializer when pyarrow missing (#29) (e6ab19e)

0.2.1 (2025-12-17)

Bug Fixes

  • resolve async implementation bugs and race conditions (#26) (f75477a)

0.2.0 (2025-12-16)

Features

  • add FileBackend for filesystem-based caching (#18) (e73b865)

0.1.0 (2025-12-11)

Features

  • initial cachekit v0.1.0-alpha oss release (a0800c3)

Bug Fixes

  • backend=None uses L1 (in-memory) cache which works everywhere. (7ce81e2)
  • L1-only mode (backend=None) should not attempt Redis connection (0898986)

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.