From 45bc9198c5ee88d5aac3eb930d107f84d73a111e Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Fri, 24 Jul 2026 00:28:46 +1000 Subject: [PATCH 1/2] docs(matrix): rs Memcached + File backends shipped; refresh stale capability cells (LAB-429) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cache Backends: rs Memcached/File flip to shipped (cargo features memcached/file, cachekit-rs PR #44), with a footnote covering the py-format byte compatibility, the deliberate rs v1 gaps (no eviction/ mmap; single-server memcached), and the CI container evidence. Also corrects three cells the current code disproves: - py File implements TTLInspectableBackend (get_ttl/refresh_ttl off the header) — the ❌ predated that landing; py Memcached ships refresh_ttl as a bare touch wrapper outside the protocol. - rs Redis implements LockableBackend (SET NX PX + Lua compare-and- delete, landed via LAB-426 with a live-redis CI job) — the ❌ and the 'TTL inspection only' note were stale. - Reliability rows (TTL management / distributed locking) updated to match. Co-authored-by: multica-agent --- sdk-feature-matrix.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/sdk-feature-matrix.md b/sdk-feature-matrix.md index 320208c..4ce39d2 100644 --- a/sdk-feature-matrix.md +++ b/sdk-feature-matrix.md @@ -72,8 +72,8 @@ | Backend | Python | Rust | TypeScript | PHP | | :--- | :---: | :---: | :---: | :---: | | Redis (direct) | ✅ `backends/redis` (redis-py, required dep) | ✅ `redis` feature (fred) | ✅ `backends/redis.ts` (ioredis) | ❌ | -| Memcached | ✅ `backends/memcached` (`memcached` extra) | ❌ | ❌ | ❌ | -| File (local) | ✅ `backends/file` (stdlib + mmap) | ❌ | ❌ | ❌ | +| Memcached | ✅ `backends/memcached` (`memcached` extra) | ✅ `memcached` feature (async-memcached)³ | ❌ | ❌ | +| File (local) | ✅ `backends/file` (stdlib + mmap) | ✅ `file` feature (byte-compatible with py)³ | ❌ | ❌ | | CacheKit SaaS (HTTP) | ✅ `backends/cachekitio` (httpx) | ✅ `cachekitio` feature (reqwest, default) | ✅ `backends/cachekitio.ts` (fetch) | 🔜 Planned | | Cloudflare Workers | N/A | ✅ `workers` feature (`worker::Fetch`) | ❌ Node 20+ only¹ | N/A | | DynamoDB | ❌² | ❌ | ❌ | ❌ | @@ -81,8 +81,10 @@ > ¹ The TS SDK cannot run on the Workers runtime today: crypto is a native NAPI module and the Redis backend is ioredis (TCP). Recorded as ❌ (a plausible target, unsupported) rather than N/A. Feasibility spike LAB-431 (2026-07-22, revised 2026-07-23) returned **GO** via a **wasm32 build of cachekit-core** — measured at ~64 KB gzipped (153 KB raw + JS glue, wasm-bindgen + wasm-opt) for the full crypto + ByteStorage surface, so counter nonces and the envelope carry over unchanged and the crypto stays single-touchpoint in the Rust core. The CachekitIO backend is already pure `fetch`, and a planned `workerd`-conditional entrypoint keeps ioredis/NAPI out of the edge bundle. WebCrypto (AES-256-GCM + HKDF-SHA256, random-nonce fallback per [encryption.md → Nonce Generation](spec/encryption.md#nonce-generation)) was evaluated as viable and stands as the documented fallback. Implementation tracked as LAB-595. > > ² DynamoDB has never shipped in any SDK. The previous Python ✅ traced to the [custom-backend tutorial](https://github.com/cachekit-io/cachekit-py/blob/main/docs/backends/custom.md), which shows how a *user* can implement the backend protocol against DynamoDB — that is an extension point, not shipped support (LAB-273). +> +> ³ Added 2026-07-24 (LAB-429). Both are cargo features on cachekit-rs (`--features memcached` / `--features file`), native targets only (compile-error guarded against `workers`). The rs File backend shares py's on-disk format — Blake2b-128 hashed filenames, the 14-byte `CK` header, atomic write-then-rename, lazy expiry — verified by running the actual py `FileBackend` against an rs-written directory and vice versa. Not yet ported from py: LRU eviction/size caps and the mmap buffer read. rs Memcached is single-server (py's `HashClient` shards across servers); CI exercises it against a live memcached 1.6 container. -**Backend selection / env auto-detection:** Python is the only SDK that auto-detects *which* backend to use from the environment — a single unambiguous selector (`CACHEKIT_API_KEY` → SaaS, `CACHEKIT_REDIS_URL` → Redis, `CACHEKIT_MEMCACHED_SERVERS` → Memcached, `CACHEKIT_FILE_CACHE_DIR` → File; fallback `REDIS_URL` / localhost Redis; setting more than one selector raises `ConfigurationError`). Rust `CacheKit::from_env()` reads SaaS credentials only (`CACHEKIT_API_KEY` / `CACHEKIT_API_URL` / `CACHEKIT_MASTER_KEY` / `CACHEKIT_DEFAULT_TTL`) and always builds the CachekitIO backend — Redis is wired explicitly via `.backend()`. TypeScript has no backend auto-detection: each preset fixes the backend type and env vars (`CACHEKIT_API_KEY`, `CACHEKIT_MASTER_KEY`) serve only as credential fallbacks. Whether rs/ts should gain selector-style auto-detection is an open product question, not a recorded decision (LAB-273 finding). +**Backend selection / env auto-detection:** Python is the only SDK that auto-detects *which* backend to use from the environment — a single unambiguous selector (`CACHEKIT_API_KEY` → SaaS, `CACHEKIT_REDIS_URL` → Redis, `CACHEKIT_MEMCACHED_SERVERS` → Memcached, `CACHEKIT_FILE_CACHE_DIR` → File; fallback `REDIS_URL` / localhost Redis; setting more than one selector raises `ConfigurationError`). Rust `CacheKit::from_env()` reads SaaS credentials only (`CACHEKIT_API_KEY` / `CACHEKIT_API_URL` / `CACHEKIT_MASTER_KEY` / `CACHEKIT_DEFAULT_TTL`) and always builds the CachekitIO backend — Redis/Memcached/File are wired explicitly via `.backend()`. TypeScript has no backend auto-detection: each preset fixes the backend type and env vars (`CACHEKIT_API_KEY`, `CACHEKIT_MASTER_KEY`) serve only as credential fallbacks. Whether rs/ts should gain selector-style auto-detection is an open product question, not a recorded decision (LAB-273 finding). --- @@ -104,15 +106,15 @@ The contract a storage backend must satisfy per SDK (bytes in / bytes out; seria | Capability | Python | Rust | TypeScript | | :--- | :--- | :--- | :--- | -| TTL inspect / refresh | `TTLInspectableBackend` — Redis ✅, SaaS ✅, Memcached/File ❌ | `TtlInspectable` — Redis ✅, SaaS ✅, Workers ❌ | `TTLBackend` — SaaS ✅ (`TTLCachekitIO`), Redis ❌ | -| Distributed locking | `LockableBackend` — Redis ✅ (`redis.lock.Lock`), SaaS ✅ | `LockableBackend` — SaaS ✅, Redis ❌, Workers ❌ | `LockableBackend` — SaaS ✅ (`LockableCachekitIO`), Redis ❌ | +| TTL inspect / refresh | `TTLInspectableBackend` — Redis ✅, SaaS ✅, File ✅ (header read/rewrite), Memcached ❌ (deliberate: protocol can't read TTLs; `refresh_ttl` ships as a bare `touch` wrapper outside the protocol) | `TtlInspectable` — Redis ✅, SaaS ✅, File ✅, Memcached ❌ (deliberate, mirrors py), Workers ❌ | `TTLBackend` — SaaS ✅ (`TTLCachekitIO`), Redis ❌ | +| Distributed locking | `LockableBackend` — Redis ✅ (`redis.lock.Lock`), SaaS ✅ | `LockableBackend` — SaaS ✅, Redis ✅ (`SET NX PX` + Lua compare-and-delete, `:lock` namespace shared with py; LAB-426), Workers ❌ | `LockableBackend` — SaaS ✅ (`LockableCachekitIO`), Redis ❌ | | Per-operation timeout | `TimeoutConfigurableBackend` — Redis ✅ (SaaS ships a non-protocol `with_timeout` variant) | — no equivalent | — no equivalent | | Zero-copy buffer read | `BufferReadableBackend` / `BufferHandle` — File ✅ (mmap; #171) | — no equivalent | — no equivalent | > [!NOTE] > **Lock API shape divergence:** Python's `acquire_lock` is an async context manager yielding `bool` — the lock token stays internal and release is automatic. Rust and TypeScript return the raw `lock_id` capability token from `acquire_lock`/`acquireLock` and require an explicit `release_lock(key, lock_id)` — a direct mirror of the SaaS lock endpoint. All three pass the **bare cache key** (backends own any `:lock` namespace derivation). Porting a lockable backend across SDKs must bridge this shape difference. > -> **Coverage, not shape, is the parity gap:** all three SDKs use the same required-base + optional-capability pattern, but Redis optional-capability coverage varies by SDK: Python has the broadest coverage (both locking and TTL inspection), Rust's Redis backend implements TTL inspection only (no locking), and TypeScript's Redis backend implements neither. Rust's Workers backend also lacks both despite speaking the same SaaS API (gap tickets under LAB-102). +> **Coverage, not shape, is the parity gap:** all three SDKs use the same required-base + optional-capability pattern, but optional-capability coverage varies by SDK: Python and Rust both cover Redis locking + TTL inspection (rs Redis locking landed via LAB-426), while TypeScript's Redis backend implements neither. Rust's Workers backend also lacks both despite speaking the same SaaS API (gap tickets under LAB-102). --- @@ -122,10 +124,10 @@ The contract a storage backend must satisfy per SDK (bytes in / bytes out; seria | :--- | :---: | :---: | :---: | :---: | | Circuit breaker | ✅ | ❌ | ✅ | ❌ | | Backpressure | ✅ | ❌ | ⚠️ Concurrent refresh limits | ❌ | -| Distributed locking | ✅ Redis + SaaS backends | ✅ SaaS backend only (`LockableBackend`; Redis/Workers lack impls) | ✅ SaaS backend only | ❌ | +| Distributed locking | ✅ Redis + SaaS backends | ✅ Redis + SaaS (`LockableBackend`; LAB-426. Workers lacks an impl) | ✅ SaaS backend only | ❌ | | L1/L2 dual-layer cache | ✅ | ✅ moka (native) / `l1` feature | ✅ | ❌ | | Cache stampede prevention | ✅ | ❌ | ✅ Version tokens + background L1 refresh | ❌ | -| TTL management | ✅ Redis + SaaS | ✅ Redis + SaaS (`TtlInspectable`; Workers ❌) | ✅ SaaS only (`TTLBackend`) | ❌ | +| TTL management | ✅ Redis + SaaS + File | ✅ Redis + SaaS + File (`TtlInspectable`; Memcached deliberately ❌, Workers ❌) | ✅ SaaS only (`TTLBackend`) | ❌ | | Stale-while-revalidate (server stale-grace) | 🚧 LAB-381 | ❌ | ❌ | ❌ | > **Lock id transport (CWE-532):** the unlock call carries the lock capability token in the `X-CacheKit-Lock-Id` request header, never the `?lock_id=` query string (which leaks via access/proxy logs and OTel `http.url` spans). **Migration complete in all three SDKs** (verified 2026-07-20, LAB-273): Python (#131, closed), Rust (#24, closed), TypeScript ships the header (ts#63 remains open only for an unrelated NAPI-rebuild item). SaaS dual-reads both during the rollout window. See [spec/saas-api.md](spec/saas-api.md#delete-v1cachekeylock). From 36bbc706fe5c1f875f8a84986fce3b7a35c05259 Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Fri, 24 Jul 2026 12:47:01 +1000 Subject: [PATCH 2/2] docs(matrix): memcached crate + TTL-capability phrasing after panel remediation (LAB-429) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rs Memcached cell: rust-memcache (async-memcached was dropped — it ships toxiproxy_rust/openssl as runtime deps). - TTL inspect/refresh cells now state the true shape for BOTH py and rs: the protocol can't read TTLs, and each SDK ships a bare refresh_ttl/touch wrapper outside the capability trait. Co-authored-by: multica-agent --- sdk-feature-matrix.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk-feature-matrix.md b/sdk-feature-matrix.md index 4ce39d2..95354c6 100644 --- a/sdk-feature-matrix.md +++ b/sdk-feature-matrix.md @@ -72,7 +72,7 @@ | Backend | Python | Rust | TypeScript | PHP | | :--- | :---: | :---: | :---: | :---: | | Redis (direct) | ✅ `backends/redis` (redis-py, required dep) | ✅ `redis` feature (fred) | ✅ `backends/redis.ts` (ioredis) | ❌ | -| Memcached | ✅ `backends/memcached` (`memcached` extra) | ✅ `memcached` feature (async-memcached)³ | ❌ | ❌ | +| Memcached | ✅ `backends/memcached` (`memcached` extra) | ✅ `memcached` feature (rust-memcache)³ | ❌ | ❌ | | File (local) | ✅ `backends/file` (stdlib + mmap) | ✅ `file` feature (byte-compatible with py)³ | ❌ | ❌ | | CacheKit SaaS (HTTP) | ✅ `backends/cachekitio` (httpx) | ✅ `cachekitio` feature (reqwest, default) | ✅ `backends/cachekitio.ts` (fetch) | 🔜 Planned | | Cloudflare Workers | N/A | ✅ `workers` feature (`worker::Fetch`) | ❌ Node 20+ only¹ | N/A | @@ -106,7 +106,7 @@ The contract a storage backend must satisfy per SDK (bytes in / bytes out; seria | Capability | Python | Rust | TypeScript | | :--- | :--- | :--- | :--- | -| TTL inspect / refresh | `TTLInspectableBackend` — Redis ✅, SaaS ✅, File ✅ (header read/rewrite), Memcached ❌ (deliberate: protocol can't read TTLs; `refresh_ttl` ships as a bare `touch` wrapper outside the protocol) | `TtlInspectable` — Redis ✅, SaaS ✅, File ✅, Memcached ❌ (deliberate, mirrors py), Workers ❌ | `TTLBackend` — SaaS ✅ (`TTLCachekitIO`), Redis ❌ | +| TTL inspect / refresh | `TTLInspectableBackend` — Redis ✅, SaaS ✅, File ✅ (header read/rewrite), Memcached ❌ (protocol can't read TTLs; a bare `refresh_ttl`/`touch` wrapper ships outside the protocol) | `TtlInspectable` — Redis ✅, SaaS ✅, File ✅, Memcached ❌ (protocol can't read TTLs; a bare `refresh_ttl`/`touch` wrapper ships outside the trait — same shape as py) | `TTLBackend` — SaaS ✅ (`TTLCachekitIO`), Redis ❌ | | Distributed locking | `LockableBackend` — Redis ✅ (`redis.lock.Lock`), SaaS ✅ | `LockableBackend` — SaaS ✅, Redis ✅ (`SET NX PX` + Lua compare-and-delete, `:lock` namespace shared with py; LAB-426), Workers ❌ | `LockableBackend` — SaaS ✅ (`LockableCachekitIO`), Redis ❌ | | Per-operation timeout | `TimeoutConfigurableBackend` — Redis ✅ (SaaS ships a non-protocol `with_timeout` variant) | — no equivalent | — no equivalent | | Zero-copy buffer read | `BufferReadableBackend` / `BufferHandle` — File ✅ (mmap; #171) | — no equivalent | — no equivalent |