From 2b5872e8b3346ee7deb1b5c0698d9a58bbc4076c Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Sat, 10 Oct 2026 07:34:21 +1100 Subject: [PATCH] docs(matrix): cachekit-rs 0.9.0 ships the empty-key rejection and path-encoding.json 1.1.0 --- changelog.d/20261010_lab-8738.md | 10 ++++++++++ sdk-feature-matrix.md | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 changelog.d/20261010_lab-8738.md diff --git a/changelog.d/20261010_lab-8738.md b/changelog.d/20261010_lab-8738.md new file mode 100644 index 0000000..4208642 --- /dev/null +++ b/changelog.d/20261010_lab-8738.md @@ -0,0 +1,10 @@ +### SDK feature matrix — cachekit-rs 0.9.0 ships the empty-key rejection and `path-encoding.json` 1.1.0 (LAB-8738) + +- [`sdk-feature-matrix.md`](sdk-feature-matrix.md): the Rust path-encoding cell gives the + empty-key rejection as since `cachekit-rs` 0.9.0 + ([cachekit-rs#100](https://github.com/cachekit-io/cachekit-rs/pull/100)), no longer + unreleased; `.`, `..`, `health`, `ttl` and `lock` stay since 0.8.0. +- The Rust "Test vectors in CI" cell gives + [`test-vectors/path-encoding.json`](test-vectors/path-encoding.json) 1.1.0, vendored byte + for byte (sha256-pinned) and CI-executed, as since 0.9.0. Both were checked on the + published 0.9.0 `.crate`. diff --git a/sdk-feature-matrix.md b/sdk-feature-matrix.md index 7b9b2b4..7814cc6 100644 --- a/sdk-feature-matrix.md +++ b/sdk-feature-matrix.md @@ -299,8 +299,8 @@ its spec: | Encryption (AES-256-GCM) | ✅ Compliant | ✅ Canonical (cachekit-core) | ✅ Compliant | ⚠️ Untested | | AAD v0x03 | ⚠️ Compliant (5 components — every auto serializer appends `original_type`; interop mode is the sole 4-component path) except the `cache_key` component behind a prefixing backend: through `cachekit` 0.22.0 it omits the backend's key prefix (`MemcachedBackend` `key_prefix`, the tenant-scoped Redis backend's `t:{tenant}:`), short of the [logical backend key](spec/encryption.md#additional-authenticated-data-aad); bound in [cachekit-py#518](https://github.com/cachekit-io/cachekit-py/pull/518), unreleased (see the Encryption table); and the post-decryption container rule ([ENC-2](spec/encryption.md#enc-2)/[ENC-3](spec/encryption.md#enc-3)): through `cachekit` 0.22.0 an encrypted Arrow read accepts bare Arrow IPC; fixed in [cachekit-py#517](https://github.com/cachekit-io/cachekit-py/pull/517), unreleased (see the Encryption section); and, through `cachekit` 0.22.0, a config-drift read by an `auto` handler with encryption off sniffs the decrypted plaintext of an entry stored under the `auto` serializer name ([ENC-2](spec/encryption.md#enc-2)/[ENC-3](spec/encryption.md#enc-3)); fixed in [cachekit-py#521](https://github.com/cachekit-io/cachekit-py/pull/521), unreleased (see the Encryption section); and, from `cachekit` 0.1.0 through 0.22.0, a directly built `EncryptionWrapper(serializer=AutoSerializer())` sniffs the decrypted plaintext ([ENC-2](spec/encryption.md#enc-2)/[ENC-3](spec/encryption.md#enc-3)); fixed in [cachekit-py#521](https://github.com/cachekit-io/cachekit-py/pull/521), unreleased; and, from `cachekit` 0.1.0 through 0.4.0, the default `EncryptionWrapper` constructor and every encrypted read by a cache handler built with `encryption=True` sniff it too; not from 0.6.1 (see the Encryption section) | ✅ Compliant (4 components) | ✅ Compliant (4 components) | ❌ Not implemented | | SaaS API | ⚠️ Compliant (CachekitIO backend) except, through `cachekit` 0.22.0 (wheel inspected 2026-10-05): a PUT with a TTL also sends the legacy `X-TTL` ([API-44](spec/saas-api.md#api-44)); any fractional `ttl`, the window `@cache.io` derives from it, and a fractional `refresh_ttl` value go on the wire unrounded, so the server answers `400` and the write is lost ([API-42](spec/saas-api.md#api-42), [API-54](spec/saas-api.md#api-54)); both fixed in [cachekit-py#520](https://github.com/cachekit-io/cachekit-py/pull/520), unreleased. Where the [TTL rules](spec/saas-api.md#put-v1cachekey) have the SDK reject a TTL with an error to the caller, `ttl=0` and a TTL over 2,592,000 are sent instead, so the server answers `400` and the write is lost | ⚠️ Compliant (CachekitIO backend) except, through `cachekit-rs` 0.10.0 (`.crate` inspected 2026-10-05): `refresh_ttl` on the CachekitIO and Workers backends rejects a sub-second TTL instead of sending 1 ([API-42](spec/saas-api.md#api-42); `PATCH /ttl` takes the `X-CacheKit-TTL` rules), fixed in [cachekit-rs#142](https://github.com/cachekit-io/cachekit-rs/pull/142), unreleased; and a TTL over 2,592,000 is sent, where the [TTL rules](spec/saas-api.md#put-v1cachekey) have the SDK reject it, so the server answers `400` and the write is lost (`validate_ttl` rejects only zero) | ⚠️ through npm 0.1.5 (`.tgz` inspected 2026-10-05) a PUT sends only the legacy `X-TTL` ([API-44](spec/saas-api.md#api-44)), unrounded, omits it for `ttl` 0, storing the entry with no expiry, and sends a TTL over 2,592,000 for the server to reject with `400`; on `main` since [cachekit-ts#110](https://github.com/cachekit-io/cachekit-ts/pull/110), unreleased, a PUT sends `X-CacheKit-TTL` ceiled to whole seconds and rejects 0 and an over-cap TTL. Every release and `main`: a `200` lock response whose `lock_id` is absent, empty or not a string counts as a lease, so single-flight is off and the release sends `undefined` as the lock id ([API-65](spec/saas-api.md#api-65)); fixed in [cachekit-ts#215](https://github.com/cachekit-io/cachekit-ts/pull/215), unreleased | ❌ Not implemented | -| SaaS API — cache-key path encoding ([spec](spec/saas-api.md#cache-key-path-encoding)) | ✅ Compliant on `main`, unreleased — rejects the empty key ([cachekit-py#397](https://github.com/cachekit-io/cachekit-py/pull/397)); `.`, `..`, `health`, `ttl` and `lock` since 0.20.0 ([cachekit-py#364](https://github.com/cachekit-io/cachekit-py/pull/364)); rules 1/3/4 since 0.18.0 ([cachekit-py#279](https://github.com/cachekit-io/cachekit-py/pull/279)) | ✅ Compliant on `main`, unreleased ([cachekit-rs#100](https://github.com/cachekit-io/cachekit-rs/pull/100), which adds the empty key); rejects `.`, `..`, `health`, `ttl` and `lock` since `cachekit-rs` 0.8.0 ([cachekit-rs#76](https://github.com/cachekit-io/cachekit-rs/pull/76); `.crate` inspected 2026-09-30) | ✅ Compliant on `main`, unreleased ([cachekit-ts#118](https://github.com/cachekit-io/cachekit-ts/pull/118)) | ❌ Not implemented | -| Test vectors in CI¹⁶ | ✅ interop/v1 (full set, incl. AAD + encryption through the real stack) — fixture 1.1.0 (`ns`/`nsapi` namespace reservation) since PyPI 0.20.0 ([cachekit-py#350](https://github.com/cachekit-io/cachekit-py/pull/350)); fixture 1.2.0 (no `..` in a segment) since 0.21.0 ([cachekit-py#391](https://github.com/cachekit-io/cachekit-py/pull/391)); fixture 1.3.0 vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-py#537](https://github.com/cachekit-io/cachekit-py/pull/537), unreleased, its key, value and error vectors and both reader groups through the interop key derivation and value reader; `decode-bounds.json` vendored + CI-executed since [cachekit-py#276](https://github.com/cachekit-io/cachekit-py/pull/276) (LAB-2503); `1.1.0` since 0.20.0 ([cachekit-py#363](https://github.com/cachekit-io/cachekit-py/pull/363)); `1.2.0` (the `incomplete` reason) vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-py#537](https://github.com/cachekit-io/cachekit-py/pull/537), unreleased, both `incomplete` vectors included; every version asserting the structural guard's own error for every reject vector at every payload read path (the [Decode bounds](spec/interop-mode.md#decode-bounds) MUST) — ⚠️ except the envelope entry point (`ByteStorage::retrieve`), which has no guard to assert until cachekit-py moves to a core release carrying [cachekit-core#80](https://github.com/cachekit-io/cachekit-core/pull/80) (see the Wire format row); `path-encoding.json` vendored + CI-executed since 0.20.0 ([cachekit-py#364](https://github.com/cachekit-io/cachekit-py/pull/364)); fixture 1.1.0 (empty-key row) since 0.21.0 ([cachekit-py#397](https://github.com/cachekit-io/cachekit-py/pull/397)); `path-encoding.json` 1.2.0 and `freshness-headers.json` 1.0.0 vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-py#522](https://github.com/cachekit-io/cachekit-py/pull/522), unreleased; `cache-keys.json` 1.1.0 ([cachekit-py#460](https://github.com/cachekit-io/cachekit-py/pull/460)), `file-backend.json` 1.1.0 ([cachekit-py#431](https://github.com/cachekit-io/cachekit-py/pull/431)) and `wire-format.json` 1.3.0 ([cachekit-py#448](https://github.com/cachekit-io/cachekit-py/pull/448); its reject vectors: see the Wire format row) vendored byte for byte (sha256-pinned) + CI-executed, each since 0.21.0; `wire-format.json` 1.4.0 (every new accept, constructed, reject, payload-reject and temporal vector), `file-backend.json` 1.2.0 (every `miss_preserve` vector on all five read paths, the expired flagged entries included, and every `return_payload` vector) and `python-frame.json` (first vendor: every frame vector through `SerializationWrapper.unwrap`, every error vector at the check its `rejected_by` names, the CK frame through the interop reader, and every `encrypted_read_vectors` frame failing closed under both tamper policies with the tenant resolved by the reader) vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-py#550](https://github.com/cachekit-io/cachekit-py/pull/550), unreleased; `encryption.json` `1.5.0` (first vendor: both `master_key_input` accept rows and every reject row, every `keyring.configuration` row, every `aad_reject_vectors` row failing authentication, and the `decrypted_container` rows for its checksummed Arrow and orjson readers returning no value), `cache-keys.json` `1.3.0` (every key, error and serializer-object vector) and the current `python-frame.json` revision, each vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-py#561](https://github.com/cachekit-io/cachekit-py/pull/561), unreleased — ⚠️ except two `keyring.configuration` cases, `keyring_current_key_decrypt_only` and its uppercase twin, each with the current key given as `master_key=`, which run as strict expected failures naming [ENC-9](spec/encryption.md#enc-9): cachekit-py refuses the current key repeated in `CACHEKIT_PREVIOUS_MASTER_KEYS` only on the first call, not at load | ✅ interop/v1 (full set) since [#33](https://github.com/cachekit-io/cachekit-rs/pull/33) — fixture 1.1.0 since `cachekit-rs` 0.8.0 ([cachekit-rs#89](https://github.com/cachekit-io/cachekit-rs/pull/89); `.crate` inspected 2026-09-30); fixture 1.2.0 since 0.9.0 ([cachekit-rs#98](https://github.com/cachekit-io/cachekit-rs/pull/98); `.crate` inspected 2026-10-06); fixture 1.3.0 vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-rs#152](https://github.com/cachekit-io/cachekit-rs/pull/152), unreleased, both reader groups through `interop::deserialize` and every value vector read back through it; `decode-bounds.json` vendored + CI-executed since [cachekit-rs#73](https://github.com/cachekit-io/cachekit-rs/pull/73) (LAB-2503; default CI green on `main`); `1.1.0` since 0.8.0 ([cachekit-rs#93](https://github.com/cachekit-io/cachekit-rs/pull/93)); `1.2.0` (the `incomplete` reason) in [cachekit-rs#152](https://github.com/cachekit-io/cachekit-rs/pull/152), unreleased, asserting the structural guard's own `decode bound:` error for every reject vector through both decoders and `get` / `interop_get` / `interop_get_swr` — the guard enforces depth itself since [cachekit-rs#93](https://github.com/cachekit-io/cachekit-rs/pull/93) (no envelope entry point¹⁵); `path-encoding.json` 1.1.0 vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-rs#100](https://github.com/cachekit-io/cachekit-rs/pull/100), unreleased, driving both the `encode_key` and the URL-builder tests; fixture 1.2.0 (encoder-mistake rows) and `freshness-headers.json` 1.0.0, vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-rs#146](https://github.com/cachekit-io/cachekit-rs/pull/146), unreleased, every freshness row run through the `GET` response's header reader (`freshness_from_headers`); `file-backend.json` 1.1.0 vendored byte for byte + CI-executed since 0.9.0 ([cachekit-rs#107](https://github.com/cachekit-io/cachekit-rs/pull/107); `.crate` inspected 2026-10-05), by tests in `src/backend/file.rs` behind the `file` feature, which default CI enables; `1.2.0` re-vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-rs#158](https://github.com/cachekit-io/cachekit-rs/pull/158), unreleased, every vector through both header read paths (`read_entry` behind `get`, `probe_header` behind `exists` and `ttl`), the expired flagged entries included, and the five vectors a version-1 writer can produce through `write_entry`; `python-frame.json` first vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-rs#158](https://github.com/cachekit-io/cachekit-rs/pull/158) and re-vendored in [cachekit-rs#160](https://github.com/cachekit-io/cachekit-rs/pull/160), unreleased, its 29 CK-prefixed vectors refused by both decoders and `get` / `interop_get` / `interop_get_swr`, on a client without encryption (auto mode by the trailing bytes, interop mode by naming the frame) — ⚠️ except `bare_envelope_fed_to_frame_reader`, which decodes¹⁵; `encryption.json` first vendored byte for byte (sha256-pinned) + CI-executed at `1.3.0` in [cachekit-rs#150](https://github.com/cachekit-io/cachekit-rs/pull/150) and re-vendored at `1.5.0` in [cachekit-rs#160](https://github.com/cachekit-io/cachekit-rs/pull/160), unreleased: both master key input accept rows accepted by every hex and raw-bytes entry point and decrypted through the `secure` hex and `CACHEKIT_MASTER_KEY` paths, the `keyring.configuration` rows at load, and the `aad_reject_vectors` and `decrypted_container` rows whose AAD this reader builds, each through the reads of `get` / `interop_get` / `interop_get_swr` that can present it (`get` only for the namespaced-prefix row, the interop reads only for the two interop-reader container rows) | ✅ interop/v1 (full set, incl. its key vectors) + inline Python-generated AAD-construction and encryption (decrypt-Python-ciphertext) vectors — fixture 1.1.0 in [cachekit-ts#143](https://github.com/cachekit-io/cachekit-ts/pull/143), unreleased; fixture 1.2.0 in [cachekit-ts#164](https://github.com/cachekit-io/cachekit-ts/pull/164), unreleased; fixture 1.3.0 vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-ts#224](https://github.com/cachekit-io/cachekit-ts/pull/224), unreleased: the key, value and error vectors and both reader groups, every value vector read back through `decodeInteropValue` as its value, with the reader fixes two of them needed (a `__proto__` map key reads as an ordinary key, and a leading U+FEFF is kept); decode bounds enforced ([#112](https://github.com/cachekit-io/cachekit-ts/pull/112)); `decode-bounds.json` vendored + CI-executed since [cachekit-ts#121](https://github.com/cachekit-io/cachekit-ts/pull/121) (LAB-2737), `1.1.0` in [cachekit-ts#152](https://github.com/cachekit-io/cachekit-ts/pull/152), unreleased; `1.2.0` (the `incomplete` reason) in [cachekit-ts#224](https://github.com/cachekit-io/cachekit-ts/pull/224), unreleased, both incomplete vectors through all three untrusted decode entry points (`MessagePackSerializer.decode`, `decodeInteropValue`, `deserializeEvent`), asserting the pre-scan's own error; every reject vector asserts the guard error it trips (pre-scan, or the event size cap ahead of it) — ⚠️ except the envelope entry point (`unpack` in `cachekit-core-ts` and `cachekit-core-wasm`), which has no guard to assert until both bindings move to a core release carrying [cachekit-core#80](https://github.com/cachekit-io/cachekit-core/pull/80) and the vectors run through `unpack` (see the Wire format row); for the three reject vectors over the invalidation event entry point's (`deserializeEvent`) 4096 B size cap, the test also calls that site's structural guard (`assertDecodeDepth`, at depth 3) directly and asserts its rejection, as [Decode bounds](spec/interop-mode.md#decode-bounds) requires where a size cap rejects first, in [cachekit-ts#182](https://github.com/cachekit-io/cachekit-ts/pull/182), unreleased; `path-encoding.json` vendored + CI-executed since [cachekit-ts#118](https://github.com/cachekit-io/cachekit-ts/pull/118), unreleased; fixture 1.1.0 (empty-key row) in [cachekit-ts#165](https://github.com/cachekit-io/cachekit-ts/pull/165), unreleased; fixture 1.2.0 (encoder-mistake rows) in [cachekit-ts#217](https://github.com/cachekit-io/cachekit-ts/pull/217), unreleased, every row through `encodeKey` and all eight request-path operations; `freshness-headers.json` 1.0.0 vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-ts#217](https://github.com/cachekit-io/cachekit-ts/pull/217), unreleased, each row through a real `Headers` and `freshnessFromHeaders`; `wire-format.json` 1.3.0 vendored byte for byte + CI-executed in the Node and Workers lanes in [cachekit-ts#184](https://github.com/cachekit-io/cachekit-ts/pull/184), unreleased (its reject vectors: see the Wire format row); `1.4.0` re-vendored byte for byte (sha256-pinned) in [cachekit-ts#229](https://github.com/cachekit-io/cachekit-ts/pull/229), unreleased, every group in both lanes (reject and payload reject vectors: see the Wire format row; temporal sentinels: see their row); `file-backend.json` 1.2.0 in [cachekit-ts#229](https://github.com/cachekit-io/cachekit-ts/pull/229) (footnote ⁴); `python-frame.json` vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-ts#229](https://github.com/cachekit-io/cachekit-ts/pull/229), unreleased, for its container vectors ([WIRE-21](spec/wire-format.md#wire-21)): the envelope read path and the interop reader refuse `ck_frame_fed_to_interop_reader`, the envelope read path refuses `plain_msgpack_fed_to_frame_reader`, and `bare_envelope_fed_to_frame_reader`, cachekit-ts's own default container, is read by every cachekit-ts reader; the current revision re-vendored byte for byte (sha256-pinned) in [cachekit-ts#230](https://github.com/cachekit-io/cachekit-ts/pull/230), unreleased, where both readers also refuse the error vectors `serializer_name_missing` and `serializer_name_empty`, whole CK v3 frames. Its frame-check and `encrypted_read_vectors` groups target cachekit-py's CK frame reader, which cachekit-ts does not have; `encryption.json` vendored + CI-executed in the Workers lane since 0.1.4 ([cachekit-ts#78](https://github.com/cachekit-io/cachekit-ts/pull/78)); `1.2.0`, with its `keyring` and `default_tenant` groups, re-vendored byte for byte (sha256-pinned) in [cachekit-ts#211](https://github.com/cachekit-io/cachekit-ts/pull/211), unreleased; `1.3.0` re-vendored byte for byte (sha256-pinned) in [cachekit-ts#222](https://github.com/cachekit-io/cachekit-ts/pull/222), unreleased, its accept and eleven hex reject master key input rows driven through `createCache.secure` with the key passed both as `masterKey` and through `CACHEKIT_MASTER_KEY` (cachekit-ts has no raw-key entry point, so the raw-bytes rows do not apply); `1.5.0` re-vendored byte for byte (sha256-pinned) in [cachekit-ts#230](https://github.com/cachekit-io/cachekit-ts/pull/230), unreleased: both master key accept rows, `master_key_first_byte_80` included, through `createCache.secure`; each `keyring.configuration` row accepted or refused as its `verdict` says, through `encryption.previousMasterKeys`, `createCache.secure`'s `previousMasterKeys` and `CACHEKIT_PREVIOUS_MASTER_KEYS`; the five `aad_reject_vectors` rows whose AAD inputs a cachekit-ts reader builds, each failing authentication with degradation off (the other five need an `original_type` or a format no cachekit-ts reader builds); the four `decrypted_container` rows that name a cachekit-ts reader (the Arrow and orjson rows name cachekit-py's); and the raw wasm bindings decrypt `standard_serializer_default` and `standard_serializer_integrity_off` in the Workers lane, each under its own `aad_hex` (no cachekit-ts reader builds their five-component AAD) | ⚠️ Pending | +| SaaS API — cache-key path encoding ([spec](spec/saas-api.md#cache-key-path-encoding)) | ✅ Compliant on `main`, unreleased — rejects the empty key ([cachekit-py#397](https://github.com/cachekit-io/cachekit-py/pull/397)); `.`, `..`, `health`, `ttl` and `lock` since 0.20.0 ([cachekit-py#364](https://github.com/cachekit-io/cachekit-py/pull/364)); rules 1/3/4 since 0.18.0 ([cachekit-py#279](https://github.com/cachekit-io/cachekit-py/pull/279)) | ✅ Compliant — rejects the empty key since `cachekit-rs` 0.9.0 ([cachekit-rs#100](https://github.com/cachekit-io/cachekit-rs/pull/100); `.crate` inspected 2026-10-10); rejects `.`, `..`, `health`, `ttl` and `lock` since `cachekit-rs` 0.8.0 ([cachekit-rs#76](https://github.com/cachekit-io/cachekit-rs/pull/76); `.crate` inspected 2026-09-30) | ✅ Compliant on `main`, unreleased ([cachekit-ts#118](https://github.com/cachekit-io/cachekit-ts/pull/118)) | ❌ Not implemented | +| Test vectors in CI¹⁶ | ✅ interop/v1 (full set, incl. AAD + encryption through the real stack) — fixture 1.1.0 (`ns`/`nsapi` namespace reservation) since PyPI 0.20.0 ([cachekit-py#350](https://github.com/cachekit-io/cachekit-py/pull/350)); fixture 1.2.0 (no `..` in a segment) since 0.21.0 ([cachekit-py#391](https://github.com/cachekit-io/cachekit-py/pull/391)); fixture 1.3.0 vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-py#537](https://github.com/cachekit-io/cachekit-py/pull/537), unreleased, its key, value and error vectors and both reader groups through the interop key derivation and value reader; `decode-bounds.json` vendored + CI-executed since [cachekit-py#276](https://github.com/cachekit-io/cachekit-py/pull/276) (LAB-2503); `1.1.0` since 0.20.0 ([cachekit-py#363](https://github.com/cachekit-io/cachekit-py/pull/363)); `1.2.0` (the `incomplete` reason) vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-py#537](https://github.com/cachekit-io/cachekit-py/pull/537), unreleased, both `incomplete` vectors included; every version asserting the structural guard's own error for every reject vector at every payload read path (the [Decode bounds](spec/interop-mode.md#decode-bounds) MUST) — ⚠️ except the envelope entry point (`ByteStorage::retrieve`), which has no guard to assert until cachekit-py moves to a core release carrying [cachekit-core#80](https://github.com/cachekit-io/cachekit-core/pull/80) (see the Wire format row); `path-encoding.json` vendored + CI-executed since 0.20.0 ([cachekit-py#364](https://github.com/cachekit-io/cachekit-py/pull/364)); fixture 1.1.0 (empty-key row) since 0.21.0 ([cachekit-py#397](https://github.com/cachekit-io/cachekit-py/pull/397)); `path-encoding.json` 1.2.0 and `freshness-headers.json` 1.0.0 vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-py#522](https://github.com/cachekit-io/cachekit-py/pull/522), unreleased; `cache-keys.json` 1.1.0 ([cachekit-py#460](https://github.com/cachekit-io/cachekit-py/pull/460)), `file-backend.json` 1.1.0 ([cachekit-py#431](https://github.com/cachekit-io/cachekit-py/pull/431)) and `wire-format.json` 1.3.0 ([cachekit-py#448](https://github.com/cachekit-io/cachekit-py/pull/448); its reject vectors: see the Wire format row) vendored byte for byte (sha256-pinned) + CI-executed, each since 0.21.0; `wire-format.json` 1.4.0 (every new accept, constructed, reject, payload-reject and temporal vector), `file-backend.json` 1.2.0 (every `miss_preserve` vector on all five read paths, the expired flagged entries included, and every `return_payload` vector) and `python-frame.json` (first vendor: every frame vector through `SerializationWrapper.unwrap`, every error vector at the check its `rejected_by` names, the CK frame through the interop reader, and every `encrypted_read_vectors` frame failing closed under both tamper policies with the tenant resolved by the reader) vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-py#550](https://github.com/cachekit-io/cachekit-py/pull/550), unreleased; `encryption.json` `1.5.0` (first vendor: both `master_key_input` accept rows and every reject row, every `keyring.configuration` row, every `aad_reject_vectors` row failing authentication, and the `decrypted_container` rows for its checksummed Arrow and orjson readers returning no value), `cache-keys.json` `1.3.0` (every key, error and serializer-object vector) and the current `python-frame.json` revision, each vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-py#561](https://github.com/cachekit-io/cachekit-py/pull/561), unreleased — ⚠️ except two `keyring.configuration` cases, `keyring_current_key_decrypt_only` and its uppercase twin, each with the current key given as `master_key=`, which run as strict expected failures naming [ENC-9](spec/encryption.md#enc-9): cachekit-py refuses the current key repeated in `CACHEKIT_PREVIOUS_MASTER_KEYS` only on the first call, not at load | ✅ interop/v1 (full set) since [#33](https://github.com/cachekit-io/cachekit-rs/pull/33) — fixture 1.1.0 since `cachekit-rs` 0.8.0 ([cachekit-rs#89](https://github.com/cachekit-io/cachekit-rs/pull/89); `.crate` inspected 2026-09-30); fixture 1.2.0 since 0.9.0 ([cachekit-rs#98](https://github.com/cachekit-io/cachekit-rs/pull/98); `.crate` inspected 2026-10-06); fixture 1.3.0 vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-rs#152](https://github.com/cachekit-io/cachekit-rs/pull/152), unreleased, both reader groups through `interop::deserialize` and every value vector read back through it; `decode-bounds.json` vendored + CI-executed since [cachekit-rs#73](https://github.com/cachekit-io/cachekit-rs/pull/73) (LAB-2503; default CI green on `main`); `1.1.0` since 0.8.0 ([cachekit-rs#93](https://github.com/cachekit-io/cachekit-rs/pull/93)); `1.2.0` (the `incomplete` reason) in [cachekit-rs#152](https://github.com/cachekit-io/cachekit-rs/pull/152), unreleased, asserting the structural guard's own `decode bound:` error for every reject vector through both decoders and `get` / `interop_get` / `interop_get_swr` — the guard enforces depth itself since [cachekit-rs#93](https://github.com/cachekit-io/cachekit-rs/pull/93) (no envelope entry point¹⁵); `path-encoding.json` 1.1.0 vendored byte for byte (sha256-pinned) + CI-executed since 0.9.0 ([cachekit-rs#100](https://github.com/cachekit-io/cachekit-rs/pull/100); `.crate` inspected 2026-10-10), driving both the `encode_key` and the URL-builder tests; fixture 1.2.0 (encoder-mistake rows) and `freshness-headers.json` 1.0.0, vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-rs#146](https://github.com/cachekit-io/cachekit-rs/pull/146), unreleased, every freshness row run through the `GET` response's header reader (`freshness_from_headers`); `file-backend.json` 1.1.0 vendored byte for byte + CI-executed since 0.9.0 ([cachekit-rs#107](https://github.com/cachekit-io/cachekit-rs/pull/107); `.crate` inspected 2026-10-05), by tests in `src/backend/file.rs` behind the `file` feature, which default CI enables; `1.2.0` re-vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-rs#158](https://github.com/cachekit-io/cachekit-rs/pull/158), unreleased, every vector through both header read paths (`read_entry` behind `get`, `probe_header` behind `exists` and `ttl`), the expired flagged entries included, and the five vectors a version-1 writer can produce through `write_entry`; `python-frame.json` first vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-rs#158](https://github.com/cachekit-io/cachekit-rs/pull/158) and re-vendored in [cachekit-rs#160](https://github.com/cachekit-io/cachekit-rs/pull/160), unreleased, its 29 CK-prefixed vectors refused by both decoders and `get` / `interop_get` / `interop_get_swr`, on a client without encryption (auto mode by the trailing bytes, interop mode by naming the frame) — ⚠️ except `bare_envelope_fed_to_frame_reader`, which decodes¹⁵; `encryption.json` first vendored byte for byte (sha256-pinned) + CI-executed at `1.3.0` in [cachekit-rs#150](https://github.com/cachekit-io/cachekit-rs/pull/150) and re-vendored at `1.5.0` in [cachekit-rs#160](https://github.com/cachekit-io/cachekit-rs/pull/160), unreleased: both master key input accept rows accepted by every hex and raw-bytes entry point and decrypted through the `secure` hex and `CACHEKIT_MASTER_KEY` paths, the `keyring.configuration` rows at load, and the `aad_reject_vectors` and `decrypted_container` rows whose AAD this reader builds, each through the reads of `get` / `interop_get` / `interop_get_swr` that can present it (`get` only for the namespaced-prefix row, the interop reads only for the two interop-reader container rows) | ✅ interop/v1 (full set, incl. its key vectors) + inline Python-generated AAD-construction and encryption (decrypt-Python-ciphertext) vectors — fixture 1.1.0 in [cachekit-ts#143](https://github.com/cachekit-io/cachekit-ts/pull/143), unreleased; fixture 1.2.0 in [cachekit-ts#164](https://github.com/cachekit-io/cachekit-ts/pull/164), unreleased; fixture 1.3.0 vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-ts#224](https://github.com/cachekit-io/cachekit-ts/pull/224), unreleased: the key, value and error vectors and both reader groups, every value vector read back through `decodeInteropValue` as its value, with the reader fixes two of them needed (a `__proto__` map key reads as an ordinary key, and a leading U+FEFF is kept); decode bounds enforced ([#112](https://github.com/cachekit-io/cachekit-ts/pull/112)); `decode-bounds.json` vendored + CI-executed since [cachekit-ts#121](https://github.com/cachekit-io/cachekit-ts/pull/121) (LAB-2737), `1.1.0` in [cachekit-ts#152](https://github.com/cachekit-io/cachekit-ts/pull/152), unreleased; `1.2.0` (the `incomplete` reason) in [cachekit-ts#224](https://github.com/cachekit-io/cachekit-ts/pull/224), unreleased, both incomplete vectors through all three untrusted decode entry points (`MessagePackSerializer.decode`, `decodeInteropValue`, `deserializeEvent`), asserting the pre-scan's own error; every reject vector asserts the guard error it trips (pre-scan, or the event size cap ahead of it) — ⚠️ except the envelope entry point (`unpack` in `cachekit-core-ts` and `cachekit-core-wasm`), which has no guard to assert until both bindings move to a core release carrying [cachekit-core#80](https://github.com/cachekit-io/cachekit-core/pull/80) and the vectors run through `unpack` (see the Wire format row); for the three reject vectors over the invalidation event entry point's (`deserializeEvent`) 4096 B size cap, the test also calls that site's structural guard (`assertDecodeDepth`, at depth 3) directly and asserts its rejection, as [Decode bounds](spec/interop-mode.md#decode-bounds) requires where a size cap rejects first, in [cachekit-ts#182](https://github.com/cachekit-io/cachekit-ts/pull/182), unreleased; `path-encoding.json` vendored + CI-executed since [cachekit-ts#118](https://github.com/cachekit-io/cachekit-ts/pull/118), unreleased; fixture 1.1.0 (empty-key row) in [cachekit-ts#165](https://github.com/cachekit-io/cachekit-ts/pull/165), unreleased; fixture 1.2.0 (encoder-mistake rows) in [cachekit-ts#217](https://github.com/cachekit-io/cachekit-ts/pull/217), unreleased, every row through `encodeKey` and all eight request-path operations; `freshness-headers.json` 1.0.0 vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-ts#217](https://github.com/cachekit-io/cachekit-ts/pull/217), unreleased, each row through a real `Headers` and `freshnessFromHeaders`; `wire-format.json` 1.3.0 vendored byte for byte + CI-executed in the Node and Workers lanes in [cachekit-ts#184](https://github.com/cachekit-io/cachekit-ts/pull/184), unreleased (its reject vectors: see the Wire format row); `1.4.0` re-vendored byte for byte (sha256-pinned) in [cachekit-ts#229](https://github.com/cachekit-io/cachekit-ts/pull/229), unreleased, every group in both lanes (reject and payload reject vectors: see the Wire format row; temporal sentinels: see their row); `file-backend.json` 1.2.0 in [cachekit-ts#229](https://github.com/cachekit-io/cachekit-ts/pull/229) (footnote ⁴); `python-frame.json` vendored byte for byte (sha256-pinned) + CI-executed in [cachekit-ts#229](https://github.com/cachekit-io/cachekit-ts/pull/229), unreleased, for its container vectors ([WIRE-21](spec/wire-format.md#wire-21)): the envelope read path and the interop reader refuse `ck_frame_fed_to_interop_reader`, the envelope read path refuses `plain_msgpack_fed_to_frame_reader`, and `bare_envelope_fed_to_frame_reader`, cachekit-ts's own default container, is read by every cachekit-ts reader; the current revision re-vendored byte for byte (sha256-pinned) in [cachekit-ts#230](https://github.com/cachekit-io/cachekit-ts/pull/230), unreleased, where both readers also refuse the error vectors `serializer_name_missing` and `serializer_name_empty`, whole CK v3 frames. Its frame-check and `encrypted_read_vectors` groups target cachekit-py's CK frame reader, which cachekit-ts does not have; `encryption.json` vendored + CI-executed in the Workers lane since 0.1.4 ([cachekit-ts#78](https://github.com/cachekit-io/cachekit-ts/pull/78)); `1.2.0`, with its `keyring` and `default_tenant` groups, re-vendored byte for byte (sha256-pinned) in [cachekit-ts#211](https://github.com/cachekit-io/cachekit-ts/pull/211), unreleased; `1.3.0` re-vendored byte for byte (sha256-pinned) in [cachekit-ts#222](https://github.com/cachekit-io/cachekit-ts/pull/222), unreleased, its accept and eleven hex reject master key input rows driven through `createCache.secure` with the key passed both as `masterKey` and through `CACHEKIT_MASTER_KEY` (cachekit-ts has no raw-key entry point, so the raw-bytes rows do not apply); `1.5.0` re-vendored byte for byte (sha256-pinned) in [cachekit-ts#230](https://github.com/cachekit-io/cachekit-ts/pull/230), unreleased: both master key accept rows, `master_key_first_byte_80` included, through `createCache.secure`; each `keyring.configuration` row accepted or refused as its `verdict` says, through `encryption.previousMasterKeys`, `createCache.secure`'s `previousMasterKeys` and `CACHEKIT_PREVIOUS_MASTER_KEYS`; the five `aad_reject_vectors` rows whose AAD inputs a cachekit-ts reader builds, each failing authentication with degradation off (the other five need an `original_type` or a format no cachekit-ts reader builds); the four `decrypted_container` rows that name a cachekit-ts reader (the Arrow and orjson rows name cachekit-py's); and the raw wasm bindings decrypt `standard_serializer_default` and `standard_serializer_integrity_off` in the Workers lane, each under its own `aad_hex` (no cachekit-ts reader builds their five-component AAD) | ⚠️ Pending | | Interop mode ([spec](spec/interop-mode.md), opt-in) | ✅ Released — PyPI 0.14.0+¹⁷ ([#220](https://github.com/cachekit-io/cachekit-py/pull/220)) | ✅ Released — crates.io 0.4.0+ ([#33](https://github.com/cachekit-io/cachekit-rs/pull/33)) | ✅ Released — npm 0.1.3+ ([#71](https://github.com/cachekit-io/cachekit-ts/pull/71)) | ❌ Not implemented | > [!NOTE]