diff --git a/sdk-feature-matrix.md b/sdk-feature-matrix.md index 3152a3c..0cd3ff0 100644 --- a/sdk-feature-matrix.md +++ b/sdk-feature-matrix.md @@ -6,7 +6,7 @@ **Feature parity and compliance status across all CacheKit SDK implementations.** -*Last updated: 2026-07-25 — LAB-430 shipped TypeScript Node-only Memcached and File backends; the protocol-owned File format and vectors now define fail-closed flag negotiation. LAB-446: Python File backend gains full TTL inspection/refresh; Memcached gains `refresh_ttl` (touch) only (see [TTL management note](#reliability-features)). LAB-595 shipped: ts Cloudflare Workers flipped ❌ → ✅ via the `@cachekit-io/cachekit/workers` entrypoint on a wasm32 cachekit-core build (~55 KB gz measured); footnote ¹ records the phase-1 surface and semantics deltas. LAB-519: ts cold-miss single-flight (in-process, always on) + LockableBackend wired into `wrap()`'s miss path (opt-in); ts backpressure decision recorded; ts Redis lock/TTL capability cells refreshed for LAB-427. LAB-272 code-verified protocol-adherence audit (2026-07-22): interop/v1 merged in Python ([cachekit-py#220](https://github.com/cachekit-io/cachekit-py/pull/220)), TypeScript ([cachekit-ts#71](https://github.com/cachekit-io/cachekit-ts/pull/71)), and Rust ([cachekit-rs#33](https://github.com/cachekit-io/cachekit-rs/pull/33)); test-vector CI coverage corrected* +*Last updated: 2026-07-28 — LAB-729: rs backpressure flipped ❌ → ✅ (semaphore + bounded queue in the rs reliability stack; decision footnote records why the LAB-519 ts rationale doesn't transfer to tokio). LAB-430 shipped TypeScript Node-only Memcached and File backends; the protocol-owned File format and vectors now define fail-closed flag negotiation. LAB-446: Python File backend gains full TTL inspection/refresh; Memcached gains `refresh_ttl` (touch) only (see [TTL management note](#reliability-features)). LAB-595 shipped: ts Cloudflare Workers flipped ❌ → ✅ via the `@cachekit-io/cachekit/workers` entrypoint on a wasm32 cachekit-core build (~55 KB gz measured); footnote ¹ records the phase-1 surface and semantics deltas. LAB-519: ts cold-miss single-flight (in-process, always on) + LockableBackend wired into `wrap()`'s miss path (opt-in); ts backpressure decision recorded; ts Redis lock/TTL capability cells refreshed for LAB-427. LAB-272 code-verified protocol-adherence audit (2026-07-22): interop/v1 merged in Python ([cachekit-py#220](https://github.com/cachekit-io/cachekit-py/pull/220)), TypeScript ([cachekit-ts#71](https://github.com/cachekit-io/cachekit-ts/pull/71)), and Rust ([cachekit-rs#33](https://github.com/cachekit-io/cachekit-rs/pull/33)); test-vector CI coverage corrected* @@ -126,7 +126,7 @@ The contract a storage backend must satisfy per SDK (bytes in / bytes out; seria | Feature | Python | Rust | TypeScript | PHP | | :--- | :---: | :---: | :---: | :---: | | Circuit breaker | ✅ | ❌ | ✅ | ❌ | -| Backpressure | ✅ | ❌ | ⚠️ Refresh cap only — deliberate, decision recorded (LAB-519) | ❌ | +| Backpressure | ✅ | ✅ Semaphore + bounded queue, decision recorded (LAB-729) | ⚠️ Refresh cap only — deliberate, decision recorded (LAB-519) | ❌ | | Distributed locking | ✅ Redis + SaaS backends | ✅ Redis + SaaS (`LockableBackend`; LAB-426. Workers lacks an impl) | ✅ Redis + SaaS backends; wired into `wrap()` cold miss (opt-in `stampede.distributedLock`, LAB-519) | ❌ | | L1/L2 dual-layer cache | ✅ | ✅ moka (native) / `l1` feature | ✅ | ❌ | | Cache stampede prevention | ✅ | ❌ | ✅ Cold-miss single-flight + SWR version tokens (LAB-519) | ❌ | @@ -136,6 +136,8 @@ The contract a storage backend must satisfy per SDK (bytes in / bytes out; seria > **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). > > **TypeScript backpressure decision (LAB-519):** general admission control beyond L1's `maxConcurrentRefreshes` was evaluated and declined. On Node's single-threaded event loop concurrent misses don't compete for threads, cold-miss single-flight collapses the per-key herd (the amplification vector metered-misses punishes), and distinct-key miss floods are bounded by backend timeouts plus the circuit breaker — a global miss semaphore would add queueing latency and a tuning knob without a failure mode it prevents. Revisit only with evidence of backend connection exhaustion. Full rationale on `StampedeConfig` in cachekit-ts. +> +> **Rust backpressure decision (LAB-729):** built — parity with Python's `reliability/load_control.py`, not net-new. The LAB-519 event-loop rationale does not transfer: Node's single event loop bounds nothing here either, but ts declined on herd-shape grounds specific to its stack, while in cachekit-rs a tokio runtime (multi-threaded or single-threaded `unsync` alike) will happily hold unbounded concurrent backend ops in flight — `fred`'s single multiplexed Redis connection buffers unbounded in-flight commands (caller memory), and the `reqwest` SaaS client grows per-host connections without cap (socket/FD exhaustion) — exactly the failure modes backpressure sheds. Shape: `tokio::sync::Semaphore` as the outermost layer of the `ReliableBackend` stack (`backpressure(breaker(retry(op)))`) — one permit per logical operation held across the whole retry sequence, so retry amplification is bounded and shed calls never skew breaker state. Over-limit calls wait in a bounded queue (max 1 000) up to 100 ms, then fail with the typed, non-retryable `BackendErrorKind::Backpressure` — never an unbounded silent queue. Defaults mirror Python (100 concurrent / 1 000 queued / 100 ms); on in `production`/`encrypted`/`io` presets, off in `minimal`. Divergence from Python: rejections are one typed non-retryable kind rather than Transient/Timeout — immediately retrying a shed call would re-amplify the overload being shed. > [!NOTE] > **TTL management is per-backend.** "TTL inspection/refresh" means the `TTLInspectableBackend`