Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions sdk-feature-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

**Feature parity and compliance status across all CacheKit SDK implementations.**

*Last updated: 2026-07-28 — LAB-998: interop/v1 ship-status corrected — the row no longer reads `unreleased`; all three SDKs have published it (PyPI 0.14.0+, crates.io 0.4.0+, npm 0.1.3+), stated as floors per footnote ⁴, aligned with docs.cachekit.io (LAB-996). 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*
*Last updated: 2026-07-29 — LAB-903: SDK Overview versions refreshed against the registries after the protocol 1.1 (envelope `bin` encoding, cachekit-core 0.4.0) rollout — py 0.17.0, core 0.4.0, rs 0.5.0 + macros 0.5.0, ts 0.1.4; per-SDK rollout state (which releases carry the flip, which bumps sit merged-unreleased on main) is recorded once, in the [cachekit-core architecture note](#architecture-notes). LAB-998: interop/v1 ship-status corrected — the row no longer reads `unreleased`; all three SDKs have published it (PyPI 0.14.0+, crates.io 0.4.0+, npm 0.1.3+), stated as floors per footnote ⁴, aligned with docs.cachekit.io (LAB-996). 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*

</div>

Expand All @@ -30,10 +30,10 @@

| SDK | Package | Version | Language | Status |
| :--- | :--- | :---: | :--- | :---: |
| cachekit-py | `cachekit` (PyPI) | 0.12.0 | Python 3.10+ | ✅ Production |
| cachekit-rs | `cachekit-rs` (crates.io) | 0.3.0 | Rust 1.82+ | ✅ Production |
| cachekit-core | `cachekit-core` (crates.io) | 0.3.0 | Rust (shared core) | ✅ Production |
| cachekit-ts | `@cachekit-io/cachekit` (npm) | 0.1.2 | TypeScript | ✅ Production |
| cachekit-py | `cachekit` (PyPI) | 0.17.0 | Python 3.10+ | ✅ Production |
| cachekit-rs | `cachekit-rs` (crates.io) | 0.5.0 | Rust 1.82+ | ✅ Production |
| cachekit-core | `cachekit-core` (crates.io) | 0.4.0 | Rust (shared core) | ✅ Production |
| cachekit-ts | `@cachekit-io/cachekit` (npm) | 0.1.4 | TypeScript | ✅ Production |
| cachekit-php | — | — | PHP 8.1+ | 🔜 Development |

---
Expand Down Expand Up @@ -224,22 +224,22 @@ its spec:
<details>
<summary><strong>Rust SDK (cachekit-rs)</strong></summary>

- Published on crates.io as `cachekit-rs` v0.3.0 + `cachekit-macros` v0.3.0
- Published on crates.io as `cachekit-rs` v0.5.0 + `cachekit-macros` v0.5.0
- Feature flags: `redis`, `cachekitio`, `encryption`, `l1`, `macros`, `workers`
- Backends: `RedisBackend` (fred), `CachekitIO` (reqwest), `WorkersCachekitIO` (CF Workers fetch)
- L1 cache via moka (native only, `l1` feature)
- `#[cachekit]` proc-macro for decorator-style caching
- `SecureCache` for zero-knowledge encrypted caching
- SSRF protection, credential redaction, `Zeroizing` key material
- WASM/Workers support: `?Send` + `Rc` paths via `cfg(target_arch = "wasm32")`
- Depends on `cachekit-core` v0.2.0 for ByteStorage and encryption primitives
- Depends on `cachekit-core` for encryption primitives only (the envelope is unused for stored values — see [Compliance Status](#compliance-status) note ²); core-version rollout state is recorded in the cachekit-core note below

</details>

<details>
<summary><strong>Rust Core (cachekit-core)</strong></summary>

- Published on crates.io as `cachekit-core` v0.3.0 (`cachekit-rs` still depends on the 0.2 line — Renovate bump tracked separately)
- Published on crates.io as `cachekit-core` v0.4.0 — the protocol 1.1 writer flip: `StorageEnvelope.compressed_data` now *emits* msgpack `bin` (`serde_bytes`); readers dual-decode both `bin` and the legacy array-of-ints ([spec/wire-format.md](spec/wire-format.md), [decisions/envelope-bin-encoding.md](decisions/envelope-bin-encoding.md)). Consumers: cachekit-py ≥ 0.17.0 ships it ([cachekit-py#249](https://github.com/cachekit-io/cachekit-py/pull/249)); released cachekit-rs 0.5.0 and cachekit-ts 0.1.4 still pin the 0.3 line — their 0.4.0 bumps ([cachekit-rs#53](https://github.com/cachekit-io/cachekit-rs/pull/53), [cachekit-ts#91](https://github.com/cachekit-io/cachekit-ts/pull/91)) are merged on main, unreleased
- Provides: `ByteStorage`, `ZeroKnowledgeEncryptor`, `derive_domain_key`, `derive_tenant_keys`
- Dependencies: `lz4_flex`, `xxhash-rust`, `ring` (native) / `aes-gcm` (wasm32), `hkdf`, `sha2`, `rmp-serde`
- Formally verified security properties via Kani
Expand Down
18 changes: 11 additions & 7 deletions spec/wire-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

**LZ4 compression + xxHash3-64 integrity wrapping for cached payloads that use the envelope.**

*Protocol Version 1.1 · Verified against `cachekit-core` v0.3.0 (`src/byte_storage.rs`); legacy envelope test vectors generated at v0.2.0 and unchanged since — `bin`-encoded twins added in protocol 1.1 ([decisions/envelope-bin-encoding.md](../decisions/envelope-bin-encoding.md))*
*Protocol Version 1.1 · Verified against `cachekit-core` v0.4.0 (`src/byte_storage.rs`); legacy envelope test vectors generated at v0.2.0 and unchanged since — `bin`-encoded twins added in protocol 1.1 ([decisions/envelope-bin-encoding.md](../decisions/envelope-bin-encoding.md))*

</div>

Expand Down Expand Up @@ -85,10 +85,12 @@ accept the legacy encoding below — a stored envelope never expires on a schedu
so legacy-read support is permanent.

> [!NOTE]
> **Implementation status:** no shipped release emits the canonical `bin`
> encoding yet — `cachekit-core` v0.3.0 (current) still writes the legacy
> encoding. The writer flip is tracked in LAB-764 /
> [cachekit-core#54](https://github.com/cachekit-io/cachekit-core/issues/54).
> **Implementation status:** the canonical `bin` encoding **is** shipped —
> `cachekit-core` v0.4.0 carries the writer flip, and `cachekit` (Python)
> ≥ 0.17.0 emits it. The released `cachekit-rs` 0.5.0 and `cachekit-ts` 0.1.4
> lines still pin core 0.3 and therefore still write the legacy encoding, so
> readers encounter both on the wire today. Per-SDK rollout state is tracked in
> [sdk-feature-matrix.md](../sdk-feature-matrix.md#architecture-notes).

`checksum` (element `[1]`) is **deliberately excluded** from the `bin` encoding: it
stays an array of 8 integers. The saving would be 1–7 bytes per envelope, and the
Expand Down Expand Up @@ -481,8 +483,10 @@ interop value (consume exactly one document, reject trailing bytes, the
[`test-vectors/python-frame.json`](../test-vectors/python-frame.json) pins the CK v3
frame against the real `cachekit-py` implementation: a minimal frame, a complete
default-path write (frame → ByteStorage envelope → inner MessagePack → value, full
round-trip), an Arrow-envelope frame (structural checks), and must-reject error
vectors — including a CK frame fed to a strict interop reader.
round-trip) in both envelope encodings — the legacy array-of-ints original
(cachekit 0.11.1) and its protocol 1.1 `bin` twin (cachekit 0.17.0, the first
release emitting `bin`) — an Arrow-envelope frame (structural checks), and
must-reject error vectors — including a CK frame fed to a strict interop reader.

Verify:

Expand Down
34 changes: 33 additions & 1 deletion test-vectors/python-frame.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"description": "Python SDK (cachekit-py) auto-mode storage container: CK v3 frame. Python-SDK-internal \u2014 other SDKs identify and reject, never decode. See spec/wire-format.md 'SDK Storage Containers (auto mode)'.",
"frame_layout": "MAGIC 'CK' (0x43 0x4B) | VERSION u8 (0x03) | HDR_LEN u32 big-endian | HEADER (UTF-8 JSON: {s, m, v}) | PAYLOAD (raw bytes)",
"generator": "cachekit 0.11.1 (PyPI wheel; Rust core via PyO3), generated by tools/python-frame-reference.py generate",
"generator": "legacy vectors: cachekit 0.11.1 (PyPI wheel; Rust core via PyO3), generated by tools/python-frame-reference.py generate (unchanged since); *_bin twins carry their own per-vector 'generator' field",
"frame_vectors": [
{
"name": "raw_payload_frame",
Expand Down Expand Up @@ -39,6 +39,7 @@
"expected_payload_hex": "94dc0022ccf011cc83cca7757365725f69642acca46e616d65cca863616368656b6974cca6616374697665ccc398ccdaccf46861cc8e624bccfe20a76d73677061636b",
"payload_envelope": {
"encoding": "rmp_serde::to_vec positional fixarray(4); Vec<u8>/[u8;8] fields encode as msgpack arrays of integers",
"envelope_encoding": "int-array",
"compressed_data_hex": "f01183a7757365725f69642aa46e616d65a863616368656b6974a6616374697665c3",
"checksum_hex": "daf468618e624bfe",
"original_size": 32,
Expand Down Expand Up @@ -66,6 +67,37 @@
"ipc_magic_offset": 8,
"ipc_magic": "ARROW1"
}
},
{
"name": "default_saas_write_msgpack_bytestorage_bin",
"description": "Protocol 1.1 twin of default_saas_write_msgpack_bytestorage: same value, same default @cache write path, but the ByteStorage envelope's compressed_data is msgpack bin (serde_bytes) instead of an array of integers. Readers MUST accept both encodings; the legacy encoding stays pinned by the legacy vector's bytes.",
"value_json": {
"user_id": 42,
"name": "cachekit",
"active": true
},
"frame_hex": "434b030000007d7b2273223a202264656661756c74222c20226d223a207b22666f726d6174223a20226d73677061636b222c2022656e636f64696e67223a20227574662d38222c2022636f6d70726573736564223a20747275652c20226f726967696e616c5f74797065223a20226d73677061636b227d2c202276223a2022322e30227d94c422f01183a7757365725f69642aa46e616d65a863616368656b6974a6616374697665c398ccdaccf46861cc8e624bccfe20a76d73677061636b",
"expected_header": {
"s": "default",
"m": {
"format": "msgpack",
"encoding": "utf-8",
"compressed": true,
"original_type": "msgpack"
},
"v": "2.0"
},
"expected_payload_hex": "94c422f01183a7757365725f69642aa46e616d65a863616368656b6974a6616374697665c398ccdaccf46861cc8e624bccfe20a76d73677061636b",
"payload_envelope": {
"encoding": "rmp_serde positional fixarray(4); compressed_data encodes as msgpack bin (serde_bytes, protocol 1.1); checksum [u8;8] stays an array of integers",
"envelope_encoding": "bin",
"compressed_data_hex": "f01183a7757365725f69642aa46e616d65a863616368656b6974a6616374697665c3",
"checksum_hex": "daf468618e624bfe",
"original_size": 32,
"format": "msgpack",
"inner_msgpack_hex": "83a7757365725f69642aa46e616d65a863616368656b6974a6616374697665c3"
},
"generator": "cachekit 0.17.0 (PyPI wheel; Rust core via PyO3), generated by tools/python-frame-reference.py generate-bin-twin"
}
],
"error_vectors": [
Expand Down
Loading
Loading