From cd2883b9caab34567f9f6e82a5306f2c09aaae36 Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Mon, 2 Nov 2026 09:21:57 +0000 Subject: [PATCH 1/2] docs(matrix): TypeScript keys hash through the Rust bindings from npm 0.2.2 The Blake2b-256 key generation cell said @noble/hashes. The published @cachekit-io/cachekit 0.2.2 pins cachekit-core-ts 0.2.1 and cachekit-core-wasm 0.2.1, which export blake2b256Hex, and hashes keys through them; @noble/hashes stays as the fallback with the same digest. Note 1 quoted the first wasm build's sizes and wasm-opt -Oz. The published core-wasm 0.2.1 is 186,188 B (66,940 B gzipped) plus 27,654 B of glue, built with -O3 and simd128. --- changelog.d/20261102_lab-11096.md | 10 ++++++++++ sdk-feature-matrix.md | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 changelog.d/20261102_lab-11096.md diff --git a/changelog.d/20261102_lab-11096.md b/changelog.d/20261102_lab-11096.md new file mode 100644 index 0000000..dbd38e2 --- /dev/null +++ b/changelog.d/20261102_lab-11096.md @@ -0,0 +1,10 @@ +### Feature matrix — TypeScript cache keys hash through the Rust bindings from npm 0.2.2 (LAB-11096) + +- The **Blake2b-256 key generation** TypeScript cell said "via @noble/hashes". From + `@cachekit-io/cachekit` 0.2.2, keys hash with Rust `blake2` through the NAPI binding on + Node and the wasm binding on Workers, once a cache there first instantiates the module. + @noble/hashes stays as the fallback and gives the same digest. Checked by running the + published 0.2.2 and its pinned `cachekit-core-ts` 0.2.1 and `cachekit-core-wasm` 0.2.1. +- Note ¹'s Workers binding sizes were those of the first wasm build. The published + `cachekit-core-wasm` 0.2.1 is 186,188 B (66,940 B gzipped) plus 27,654 B of JS glue, + built with wasm-opt `-O3` and simd128, not `-Oz`. diff --git a/sdk-feature-matrix.md b/sdk-feature-matrix.md index d0f9995..63e3716 100644 --- a/sdk-feature-matrix.md +++ b/sdk-feature-matrix.md @@ -52,7 +52,7 @@ | OrjsonSerializer (fast JSON) | ✅ `[json]` extra | N/A | N/A | N/A | | ArrowSerializer (columnar) | ✅ `[data]` extra | N/A | ❌ declined⁰ | ❌ | | ByteStorage (LZ4 + xxHash3-64) | ✅ via Rust FFI | ✅ canonical (cachekit-core) | ✅ via NAPI (Rust) | 🔜 Planned | -| Blake2b-256 key generation | ✅ | ✅ interop mode only — N/A auto mode (see [Compliance Status](#compliance-status) note ¹⁴) | ✅ via @noble/hashes | 🔜 Planned | +| Blake2b-256 key generation | ✅ | ✅ interop mode only — N/A auto mode (see [Compliance Status](#compliance-status) note ¹⁴) | ✅ `@cachekit-io/cachekit` 0.2.2+ — Rust `blake2` through the bindings ([cachekit-ts#259](https://github.com/cachekit-io/cachekit-ts/pull/259)): NAPI on Node, wasm on Workers once a cache first instantiates the module (compression or encryption on). @noble/hashes, same digest, before that on Workers, for Workers hash input over 64 KiB, and for a binding that lacks `blake2b256Hex`, fails its self-test or throws (`setKeyHashBinding` in `serialization/blake2b.ts`, `initWasm` in `workers/runtime.ts`; `test/workers/key-hash.workers.test.ts`, `test/node-entry/older-core.test.ts`). 0.2.2 pins `cachekit-core-ts` 0.2.1 and `cachekit-core-wasm` 0.2.1, both run 2026-11-02. 0.2.1 and earlier: @noble/hashes only | 🔜 Planned | > ⁰ TypeScript Arrow was listed 🔜 Planned from 2026-06-06 with no code, stub, or tracking issue behind it; corrected to ❌, then **declined** on 2026-09-23. No Node columnar use case has been reported or found, and cross-SDK values are interop/v1 MessagePack only, so a TypeScript Arrow serializer would serve single-SDK Node workloads alone. Have one? Open an issue on [cachekit-ts](https://github.com/cachekit-io/cachekit-ts/issues). Orjson and Arrow live behind cachekit-py's `[json]` / `[data]` extras (`pyproject.toml`, `[project.optional-dependencies]`). @@ -121,7 +121,7 @@ | Cache API (Cloudflare) | N/A | ❌ | ✅ `workersCacheAPI` on the `/workers` entry (LAB-750)¹ | N/A | | DynamoDB | ❌² | ❌ | ❌ | ❌ | -> ¹ Shipped in LAB-595 (2026-07-24), following spike LAB-431's GO verdict: the `@cachekit-io/cachekit/workers` subpath (also the `workerd` condition on the root export) runs crypto and the ByteStorage envelope on a **wasm32 build of cachekit-core** (`@cachekit-io/cachekit-core-wasm`, 137 KB raw / ~55 KB gzipped + ~10 KB JS glue, wasm-bindgen `--target web` + wasm-opt `-Oz`) — counter nonces and the envelope carry over unchanged and the crypto stays single-touchpoint in the Rust core; byte-verified against the Python-ground-truth `test-vectors/encryption.json` and `wire-format.json` suites inside real workerd (`wire-format.json` 1.4.0 `vectors`, `constructed_vectors` including the `bin16` → `bin32` edge, and, since npm 0.2.1, all fourteen `reject_vectors` and both `payload_reject_vectors` on the envelope read path — [cachekit-ts#198](https://github.com/cachekit-io/cachekit-ts/pull/198), 1.4.0 in [cachekit-ts#229](https://github.com/cachekit-io/cachekit-ts/pull/229); see the Wire format row). Backend surface (phase 2, LAB-750): CachekitIO (pure `fetch`), **Workers KV** (`workersKV({ kv })` over a `KVNamespace` binding; native `expirationTtl` — KV rejects values under its 60s minimum, so the SDK clamps shorter TTLs up to 60s before the `put`, and by an SDK rule (not KV's) `ttl <= 0` stores without expiry; eventually consistent ~60s), the **Cache API** (`workersCacheAPI()` over `caches.default`/named caches; per-data-center only, `Cache-Control: max-age` to the second, best-effort eviction, keys mapped to synthetic never-fetched URLs), or a custom `Backend` instance — all storage transports over the unchanged opaque ByteStorage payload (encryption above the backend; secure caches store ciphertext only). Backend instances are accepted by the `minimal`/`production`/`secure` intents (`{ backend }` in place of `{ url }`, both entrypoints); Redis-URL intents, Redis Pub/Sub invalidation, and Prometheus metrics stay Node-only and are excluded from the edge bundle (CI-guarded: no `node:*`, no NAPI, no ioredis/prom-client — no `nodejs_compat` flag needed); SWR background refresh requires binding the request's `ExecutionContext` — `cache.withExecutionContext(ctx)` returns a cheap per-request view whose refreshes ride `ctx.waitUntil`, because workerd cancels fire-and-forget work at response return; a read on a cache with no bound context fails safe to a plain (no-SWR) L1 get (LAB-751, the module doc comment of `packages/cachekit/src/workers/index.ts`; `workersRuntime.swrRequiresWaitUntil` and `WorkersCache.withExecutionContext` in `workers/runtime.ts`). Semantics delta: keys live in wasm linear memory (a host-readable ArrayBuffer), weaker isolation than NAPI's Rust heap but ~JS-heap-equivalent on Workers where the host is your own isolate; zeroized deterministically on `dispose()`. WebCrypto (AES-256-GCM + HKDF-SHA256, random-nonce fallback per [encryption.md → Nonce Generation](spec/encryption.md#nonce-generation)) remains the documented fallback if the wasm path ever hits a wall. +> ¹ Shipped in LAB-595 (2026-07-24), following spike LAB-431's GO verdict: the `@cachekit-io/cachekit/workers` subpath (also the `workerd` condition on the root export) runs crypto and the ByteStorage envelope on a **wasm32 build of cachekit-core** (`@cachekit-io/cachekit-core-wasm`; the published 0.2.1 is a 186,188 B `.wasm`, 66,940 B at `gzip -9`, plus 27,654 B of JS glue, 6,084 B gzipped, measured 2026-11-02; wasm-bindgen `--target web` + wasm-opt `-O3` with simd128) — counter nonces and the envelope carry over unchanged and the crypto stays single-touchpoint in the Rust core; byte-verified against the Python-ground-truth `test-vectors/encryption.json` and `wire-format.json` suites inside real workerd (`wire-format.json` 1.4.0 `vectors`, `constructed_vectors` including the `bin16` → `bin32` edge, and, since npm 0.2.1, all fourteen `reject_vectors` and both `payload_reject_vectors` on the envelope read path — [cachekit-ts#198](https://github.com/cachekit-io/cachekit-ts/pull/198), 1.4.0 in [cachekit-ts#229](https://github.com/cachekit-io/cachekit-ts/pull/229); see the Wire format row). Backend surface (phase 2, LAB-750): CachekitIO (pure `fetch`), **Workers KV** (`workersKV({ kv })` over a `KVNamespace` binding; native `expirationTtl` — KV rejects values under its 60s minimum, so the SDK clamps shorter TTLs up to 60s before the `put`, and by an SDK rule (not KV's) `ttl <= 0` stores without expiry; eventually consistent ~60s), the **Cache API** (`workersCacheAPI()` over `caches.default`/named caches; per-data-center only, `Cache-Control: max-age` to the second, best-effort eviction, keys mapped to synthetic never-fetched URLs), or a custom `Backend` instance — all storage transports over the unchanged opaque ByteStorage payload (encryption above the backend; secure caches store ciphertext only). Backend instances are accepted by the `minimal`/`production`/`secure` intents (`{ backend }` in place of `{ url }`, both entrypoints); Redis-URL intents, Redis Pub/Sub invalidation, and Prometheus metrics stay Node-only and are excluded from the edge bundle (CI-guarded: no `node:*`, no NAPI, no ioredis/prom-client — no `nodejs_compat` flag needed); SWR background refresh requires binding the request's `ExecutionContext` — `cache.withExecutionContext(ctx)` returns a cheap per-request view whose refreshes ride `ctx.waitUntil`, because workerd cancels fire-and-forget work at response return; a read on a cache with no bound context fails safe to a plain (no-SWR) L1 get (LAB-751, the module doc comment of `packages/cachekit/src/workers/index.ts`; `workersRuntime.swrRequiresWaitUntil` and `WorkersCache.withExecutionContext` in `workers/runtime.ts`). Semantics delta: keys live in wasm linear memory (a host-readable ArrayBuffer), weaker isolation than NAPI's Rust heap but ~JS-heap-equivalent on Workers where the host is your own isolate; zeroized deterministically on `dispose()`. WebCrypto (AES-256-GCM + HKDF-SHA256, random-nonce fallback per [encryption.md → Nonce Generation](spec/encryption.md#nonce-generation)) remains the documented fallback if the wasm path ever hits a wall. > > ² 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). > From daf2cbdfa0e25aab6e85a92d34367c74445e075c Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Mon, 2 Nov 2026 09:55:52 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20address=20coderabbit=20review=20?= =?UTF-8?q?=E2=80=94=20say=20what=20was=20run=20on=20the=20check=20date?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit-Resolved: sdk-feature-matrix.md:55:both run 2026-11-02 is incomp --- sdk-feature-matrix.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-feature-matrix.md b/sdk-feature-matrix.md index 63e3716..b1cd891 100644 --- a/sdk-feature-matrix.md +++ b/sdk-feature-matrix.md @@ -52,7 +52,7 @@ | OrjsonSerializer (fast JSON) | ✅ `[json]` extra | N/A | N/A | N/A | | ArrowSerializer (columnar) | ✅ `[data]` extra | N/A | ❌ declined⁰ | ❌ | | ByteStorage (LZ4 + xxHash3-64) | ✅ via Rust FFI | ✅ canonical (cachekit-core) | ✅ via NAPI (Rust) | 🔜 Planned | -| Blake2b-256 key generation | ✅ | ✅ interop mode only — N/A auto mode (see [Compliance Status](#compliance-status) note ¹⁴) | ✅ `@cachekit-io/cachekit` 0.2.2+ — Rust `blake2` through the bindings ([cachekit-ts#259](https://github.com/cachekit-io/cachekit-ts/pull/259)): NAPI on Node, wasm on Workers once a cache first instantiates the module (compression or encryption on). @noble/hashes, same digest, before that on Workers, for Workers hash input over 64 KiB, and for a binding that lacks `blake2b256Hex`, fails its self-test or throws (`setKeyHashBinding` in `serialization/blake2b.ts`, `initWasm` in `workers/runtime.ts`; `test/workers/key-hash.workers.test.ts`, `test/node-entry/older-core.test.ts`). 0.2.2 pins `cachekit-core-ts` 0.2.1 and `cachekit-core-wasm` 0.2.1, both run 2026-11-02. 0.2.1 and earlier: @noble/hashes only | 🔜 Planned | +| Blake2b-256 key generation | ✅ | ✅ interop mode only — N/A auto mode (see [Compliance Status](#compliance-status) note ¹⁴) | ✅ `@cachekit-io/cachekit` 0.2.2+ — Rust `blake2` through the bindings ([cachekit-ts#259](https://github.com/cachekit-io/cachekit-ts/pull/259)): NAPI on Node, wasm on Workers once a cache first instantiates the module (compression or encryption on). @noble/hashes, same digest, before that on Workers, for Workers hash input over 64 KiB, and for a binding that lacks `blake2b256Hex`, fails its self-test or throws (`setKeyHashBinding` in `serialization/blake2b.ts`, `initWasm` in `workers/runtime.ts`; `test/workers/key-hash.workers.test.ts`, `test/node-entry/older-core.test.ts`). 0.2.2 pins `cachekit-core-ts` 0.2.1 and `cachekit-core-wasm` 0.2.1; the three published artifacts were run on 2026-11-02. 0.2.1 and earlier: @noble/hashes only | 🔜 Planned | > ⁰ TypeScript Arrow was listed 🔜 Planned from 2026-06-06 with no code, stub, or tracking issue behind it; corrected to ❌, then **declined** on 2026-09-23. No Node columnar use case has been reported or found, and cross-SDK values are interop/v1 MessagePack only, so a TypeScript Arrow serializer would serve single-SDK Node workloads alone. Have one? Open an issue on [cachekit-ts](https://github.com/cachekit-io/cachekit-ts/issues). Orjson and Arrow live behind cachekit-py's `[json]` / `[data]` extras (`pyproject.toml`, `[project.optional-dependencies]`).