diff --git a/sdk-feature-matrix.md b/sdk-feature-matrix.md index 4de8625..a3f1ed1 100644 --- a/sdk-feature-matrix.md +++ b/sdk-feature-matrix.md @@ -293,14 +293,14 @@ its spec: | Requirement | Python | Rust | TypeScript | PHP | | :--- | :---: | :---: | :---: | :---: | | Key generation (Blake2b) | ✅ Compliant | N/A auto mode¹⁴ — interop/v1 keygen ✅ merged ([#33](https://github.com/cachekit-io/cachekit-rs/pull/33)); `#[cachekit]` mints interop keys ([#35](https://github.com/cachekit-io/cachekit-rs/pull/35)) | ✅ Compliant | ⚠️ Untested | -| Wire format (ByteStorage) | ✅ Compliant¹⁵ on `main`, unreleased — the envelope goes through `cachekit-core`'s `ByteStorage::retrieve`, which runs the [Retrieve Flow](spec/wire-format.md#retrieve-flow) step-2 pre-scan since cachekit-core 0.7.0 ([cachekit-core#80](https://github.com/cachekit-io/cachekit-core/pull/80)); cachekit-py pins 0.7.0 since [cachekit-py#618](https://github.com/cachekit-io/cachekit-py/pull/618) (unreleased; 0.24.1 and earlier pin 0.6.0 or older, none of which has the envelope pre-scan), and pre-scans payload decodes too (`unpackb_bounded`); runs all six `wire-format.json` 1.3.0 [Reject vectors](spec/wire-format.md#reject-vectors) through `ByteStorage.retrieve` since 0.21.0 ([cachekit-py#448](https://github.com/cachekit-io/cachekit-py/pull/448); `TestRejectVectors` in `tests/unit/protocol/test_envelope_wire_vectors.py`), asserting the error each row names, except that core shares one error between its zero-length and ratio checks, so the zero-length vector asserts the ratio message; all fourteen `wire-format.json` 1.4.0 reject vectors since 0.23.0 ([cachekit-py#550](https://github.com/cachekit-io/cachekit-py/pull/550)), each asserting the error its row names (the five step-2 typed-decode vectors the typed-decode error, `reject_original_size_sign_bit` the range-checked `u32` decode), and both `payload_reject_vectors` asserting the structural guard's own error after the envelope read; `reject_envelope_slots_overclaim` asserts the pre-scan's own error ([WIRE-9](spec/wire-format.md#wire-9)) since [cachekit-py#618](https://github.com/cachekit-io/cachekit-py/pull/618) (unreleased), a strict expected failure before; the allocation bound for the size-cap and ratio vectors ([WIRE-17](spec/wire-format.md#wire-17)) is relied on through `cachekit-core`'s allocation probe ([cachekit-core#100](https://github.com/cachekit-io/cachekit-core/pull/100)), which core 0.7.0's publish job ran, under the passthrough rule in [Reject vectors](spec/wire-format.md#reject-vectors): `ByteStorage.retrieve` passes the envelope bytes to core with no allocation of its own sized from an envelope field, and a test fails unless the lockfile pins that release ([cachekit-py#627](https://github.com/cachekit-io/cachekit-py/pull/627), unreleased) | ✅ Canonical (`cachekit-core`), unused for stored values¹⁵; step-2 pre-scan since crates.io 0.7.0 ([cachekit-core#80](https://github.com/cachekit-io/cachekit-core/pull/80)): `ByteStorage::retrieve` and `validate` pre-scan the envelope bytes before the typed `rmp_serde::from_slice` into `StorageEnvelope`; 0.6.0 and earlier lack it | ⚠️ Compliant except the envelope pre-scan: payload decodes are pre-scanned, but the envelope goes through `cachekit-core`'s `ByteStorage::retrieve` (`unpack` in both `cachekit-core-ts` and `cachekit-core-wasm`), which has no step-2 pre-scan in `cachekit-core` 0.6.0, the core both npm 0.2.1 bindings embed (`strings` on every `cachekit-core-ts` 0.2.0 `.node` and the `cachekit-core-wasm` 0.2.0 `.wasm`, 2026-10-24); the pre-scan ships in `cachekit-core` 0.7.0 ([cachekit-core#80](https://github.com/cachekit-io/cachekit-core/pull/80)), and this cell flips when both bindings move to 0.7.0 or later; ✅ since npm 0.2.1 (PRs in the `cachekit-v0.2.1` tag's history, checked 2026-10-24; the Node lane passes at that tag), runs all fourteen `wire-format.json` 1.4.0 [Reject vectors](spec/wire-format.md#reject-vectors) on the envelope read path, asserting the error each row names, in the Node lane (`packages/cachekit/test/protocol/wire-format.protocol.test.ts`) and the Workers lane (`packages/cachekit/test/workers/wire-format.workers.test.ts`) ([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)), and both [`payload_reject_vectors`](spec/wire-format.md#payload-decode-bounds), refused by the payload pre-scan; `reject_envelope_slots_overclaim` passes in both lanes since 0.2.1 ([cachekit-ts#232](https://github.com/cachekit-io/cachekit-ts/pull/232); `dist/serialization/envelope.js`): the SDK's own header walk keeps a running total of the declared slots and refuses the vector with the envelope pre-scan's own error ([WIRE-9](spec/wire-format.md#wire-9)), before element 1's type check and before core sees the bytes. The walk allocates nothing and admits only complete envelopes; ⚠️ the allocation bound for the size-cap and ratio vectors is unasserted: `cachekit-core` 0.7.0 asserts it in its own unit tests (`src/read_allocation_probe.rs`), but both 0.2.1 bindings embed 0.6.0 | ⚠️ Untested | +| Wire format (ByteStorage) | ✅ Compliant¹⁵ on `main`, unreleased — the envelope goes through `cachekit-core`'s `ByteStorage::retrieve`, which runs the [Retrieve Flow](spec/wire-format.md#retrieve-flow) step-2 pre-scan since cachekit-core 0.7.0 ([cachekit-core#80](https://github.com/cachekit-io/cachekit-core/pull/80)); cachekit-py pins 0.7.0 since [cachekit-py#618](https://github.com/cachekit-io/cachekit-py/pull/618) (unreleased; 0.24.1 and earlier pin 0.6.0 or older, none of which has the envelope pre-scan), and pre-scans payload decodes too (`unpackb_bounded`); runs all six `wire-format.json` 1.3.0 [Reject vectors](spec/wire-format.md#reject-vectors) through `ByteStorage.retrieve` since 0.21.0 ([cachekit-py#448](https://github.com/cachekit-io/cachekit-py/pull/448); `TestRejectVectors` in `tests/unit/protocol/test_envelope_wire_vectors.py`), asserting the error each row names, except that core shares one error between its zero-length and ratio checks, so the zero-length vector asserts the ratio message; all fourteen `wire-format.json` 1.4.0 reject vectors since 0.23.0 ([cachekit-py#550](https://github.com/cachekit-io/cachekit-py/pull/550)), each asserting the error its row names (the five step-2 typed-decode vectors the typed-decode error, `reject_original_size_sign_bit` the range-checked `u32` decode), and both `payload_reject_vectors` asserting the structural guard's own error after the envelope read; `reject_envelope_slots_overclaim` asserts the pre-scan's own error ([WIRE-9](spec/wire-format.md#wire-9)) since [cachekit-py#618](https://github.com/cachekit-io/cachekit-py/pull/618) (unreleased), a strict expected failure before; the allocation bound for the size-cap and ratio vectors ([WIRE-17](spec/wire-format.md#wire-17)) is relied on through `cachekit-core`'s allocation probe ([cachekit-core#100](https://github.com/cachekit-io/cachekit-core/pull/100)), which core 0.7.0's publish job ran, under the passthrough rule in [Reject vectors](spec/wire-format.md#reject-vectors): `ByteStorage.retrieve` passes the envelope bytes to core with no allocation of its own sized from an envelope field, and a test fails unless the lockfile pins that release ([cachekit-py#627](https://github.com/cachekit-io/cachekit-py/pull/627), unreleased) | ✅ Canonical (`cachekit-core`), unused for stored values¹⁵; step-2 pre-scan since crates.io 0.7.0 ([cachekit-core#80](https://github.com/cachekit-io/cachekit-core/pull/80)): `ByteStorage::retrieve` and `validate` pre-scan the envelope bytes before the typed `rmp_serde::from_slice` into `StorageEnvelope`; 0.6.0 and earlier lack it | ⚠️ Compliant except the envelope pre-scan: payload decodes are pre-scanned, but the envelope goes through `cachekit-core`'s `ByteStorage::retrieve` (`unpack` in both `cachekit-core-ts` and `cachekit-core-wasm`), which has no step-2 pre-scan in `cachekit-core` 0.6.0, the core both npm 0.2.1 bindings embed (`strings` on every `cachekit-core-ts` 0.2.0 `.node` and the `cachekit-core-wasm` 0.2.0 `.wasm`, 2026-10-24); the pre-scan ships in `cachekit-core` 0.7.0 ([cachekit-core#80](https://github.com/cachekit-io/cachekit-core/pull/80)); 🚧 unreleased — compliant on `main`: both bindings pin core 0.7.0 since [cachekit-ts#240](https://github.com/cachekit-io/cachekit-ts/pull/240), and every `decode-bounds.json` vector runs through both `unpack` bindings in [cachekit-ts#245](https://github.com/cachekit-io/cachekit-ts/pull/245) (see the Test vectors row); it stays unreleased until a published `@cachekit-io/cachekit` depends on `cachekit-core-ts` and `cachekit-core-wasm` releases that embed core 0.7.0, because npm 0.2.1 pins both at exactly 0.2.0; ✅ since npm 0.2.1 (PRs in the `cachekit-v0.2.1` tag's history, checked 2026-10-24; the Node lane passes at that tag), runs all fourteen `wire-format.json` 1.4.0 [Reject vectors](spec/wire-format.md#reject-vectors) on the envelope read path, asserting the error each row names, in the Node lane (`packages/cachekit/test/protocol/wire-format.protocol.test.ts`) and the Workers lane (`packages/cachekit/test/workers/wire-format.workers.test.ts`) ([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)), and both [`payload_reject_vectors`](spec/wire-format.md#payload-decode-bounds), refused by the payload pre-scan; `reject_envelope_slots_overclaim` passes in both lanes since 0.2.1 ([cachekit-ts#232](https://github.com/cachekit-io/cachekit-ts/pull/232); `dist/serialization/envelope.js`): the SDK's own header walk keeps a running total of the declared slots and refuses the vector with the envelope pre-scan's own error ([WIRE-9](spec/wire-format.md#wire-9)), before element 1's type check and before core sees the bytes. The walk allocates nothing and admits only complete envelopes; ⚠️ the allocation bound for the size-cap and ratio vectors is unasserted: `cachekit-core` 0.7.0 asserts it in its own unit tests (`src/read_allocation_probe.rs`), but both 0.2.1 bindings embed 0.6.0 | ⚠️ Untested | | Storage container (auto mode)¹⁵ | CK v3 frame (Python-internal) | Plain MessagePack (`rmp` named) — no envelope; ⚠️ decodes cachekit-ts's bare ByteStorage envelope ([WIRE-21](spec/wire-format.md#wire-21)), in every release and on `main`¹⁵ | Bare ByteStorage envelope (default) | — | | Temporal sentinels ([spec](spec/wire-format.md#datetime-extension-format)) | ✅ PyPI 0.22.0 (wheel run 2026-10-07): the default path revives `__datetime__`, `__date__` and `__time__` (each `temporal_sentinel_vectors` payload under `StandardSerializer`), as do `AutoSerializer` and interop mode, and all three write them; the three `wire-format.json` 1.4.0 `temporal_sentinel_vectors` run in CI since [cachekit-py#550](https://github.com/cachekit-io/cachekit-py/pull/550), unreleased | ❌ crates.io 0.10.0 (`.crate` inspected 2026-10-07): revives none and writes none, in either mode; interop's value profile refuses a datetime; `main` at `b5fb2a6` likewise (no sentinel reviver in `src/`). cachekit-rs vendors no `wire-format.json` | ❌ npm 0.2.1 (`.tgz` inspected 2026-10-24; 0.1.5, inspected 2026-10-07, behaves the same): the default path revives none, and writes a `Date` as its ISO string (`dist/serialization/serializer.js`); interop mode revives only `__datetime__`, leaving `__date__` and `__time__` as maps, since JavaScript has no date-only or time-only type. Since 0.2.1 the `wire-format.json` 1.4.0 `temporal_sentinel_vectors` run in both lanes as expected failures naming [WIRE-20](spec/wire-format.md#wire-20), each pinning the map the read returns ([cachekit-ts#229](https://github.com/cachekit-io/cachekit-ts/pull/229)) | ⚠️ Untested | | Encryption (AES-256-GCM) | ✅ Compliant | ✅ Canonical (cachekit-core) | ✅ Compliant | ⚠️ Untested | | AAD v0x03 | ✅ Compliant 0.23.0+ (5 components — every auto serializer appends `original_type`; interop mode is the sole 4-component path; wheels tested 2026-10-19). Earlier releases fall short: through 0.22.0 the `cache_key` component omits a prefixing backend's key prefix, short of the [logical backend key](spec/encryption.md#additional-authenticated-data-aad) ([cachekit-py#518](https://github.com/cachekit-io/cachekit-py/pull/518); see the Encryption table); and, against [ENC-2](spec/encryption.md#enc-2)/[ENC-3](spec/encryption.md#enc-3), 0.9.0 through 0.22.0 accept bare Arrow IPC on an encrypted Arrow read ([cachekit-py#517](https://github.com/cachekit-io/cachekit-py/pull/517)), 0.9.0 through 0.22.0 sniff the decrypted plaintext on a config-drift read by an `auto` handler with encryption off of an entry stored under the `auto` serializer name ([cachekit-py#521](https://github.com/cachekit-io/cachekit-py/pull/521)), 0.1.0 through 0.22.0 sniff it in a directly built `EncryptionWrapper(serializer=AutoSerializer())` ([cachekit-py#521](https://github.com/cachekit-io/cachekit-py/pull/521)), and 0.1.0 through 0.4.0 also sniff it with the default `EncryptionWrapper` constructor and on every encrypted read by a cache handler built with `encryption=True`, 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) | ✅ Compliant (CachekitIO backend) from npm 0.2.1 on the [TTL rules](spec/saas-api.md#put-v1cachekey), [API-44](spec/saas-api.md#api-44) and [API-65](spec/saas-api.md#api-65) (`.tgz` inspected 2026-10-24): a PUT sends only `X-CacheKit-TTL`, ceiled to whole seconds, and refuses a zero, negative, non-finite or over-2,592,000 TTL with `ConfigurationError` before sending ([cachekit-ts#110](https://github.com/cachekit-io/cachekit-ts/pull/110); `validateTtl` in `dist/backends/cachekitio.js`); a lock response is a lease only when its `lock_id` is a non-empty string ([API-65](spec/saas-api.md#api-65); [cachekit-ts#215](https://github.com/cachekit-io/cachekit-ts/pull/215); `dist/backends/cachekitio-lockable.js`); `src/backends/cachekitio-ttl.test.ts` and `src/backends/cachekitio-lockable.test.ts` pass at tag `cachekit-v0.2.1`. ⚠️ Through 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`; and 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 | ❌ 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 — 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 — `@cachekit-io/cachekit` 0.2.1+ ([cachekit-ts#118](https://github.com/cachekit-io/cachekit-ts/pull/118); `encodeKey` in `dist/backends/cachekitio.js`, `.tgz` inspected 2026-10-24; `test/protocol/path-encoding.protocol.test.ts` passes at tag `cachekit-v0.2.1`); absent from 0.1.5 | ❌ 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 since 0.23.0 ([cachekit-py#537](https://github.com/cachekit-io/cachekit-py/pull/537)), 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 since 0.23.0 ([cachekit-py#537](https://github.com/cachekit-io/cachekit-py/pull/537)), 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); at the envelope entry point (`ByteStorage.retrieve`), every `1.2.0` reject vector asserts cachekit-core 0.7.0's pre-scan error, with the reason for each depth-only and overclaim-only vector, and every accept vector gets past the pre-scan, in [cachekit-py#627](https://github.com/cachekit-io/cachekit-py/pull/627) (unreleased; 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 since 0.23.0 ([cachekit-py#522](https://github.com/cachekit-io/cachekit-py/pull/522)); `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 since 0.23.0 ([cachekit-py#550](https://github.com/cachekit-io/cachekit-py/pull/550)); `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 `python-frame.json` revision of [protocol#180](https://github.com/cachekit-io/protocol/pull/180) (11 frame vectors: not yet the [protocol#193](https://github.com/cachekit-io/protocol/pull/193) revision, which adds `padded_header_frame`), each vendored byte for byte (sha256-pinned) + CI-executed since 0.23.0 ([cachekit-py#561](https://github.com/cachekit-io/cachekit-py/pull/561)) — ⚠️ 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 — every "since 0.2.1" below is a PR in the `cachekit-v0.2.1` tag's history (checked 2026-10-24; the Node lane's `test/protocol` suite passes at that tag against the published `cachekit-core-ts` 0.2.0 binding); fixture 1.1.0 since npm 0.2.1 ([cachekit-ts#143](https://github.com/cachekit-io/cachekit-ts/pull/143); `'nsapi'` in `dist/serialization/interop.js` of the 0.2.1 `.tgz`, absent from 0.1.5's `validateInteropSegment`); fixture 1.2.0 since 0.2.1 ([cachekit-ts#164](https://github.com/cachekit-io/cachekit-ts/pull/164); `must not contain '..'` in `dist/serialization/interop.js`, absent from 0.1.5); 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), since 0.2.1: 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), since 0.2.1; `1.2.0` (the `incomplete` reason) in [cachekit-ts#224](https://github.com/cachekit-io/cachekit-ts/pull/224), since 0.2.1, 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) (`cachekit-core` 0.7.0+; both npm 0.2.1 bindings embed 0.6.0) 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), since 0.2.1; `path-encoding.json` vendored + CI-executed since [cachekit-ts#118](https://github.com/cachekit-io/cachekit-ts/pull/118), since 0.2.1; fixture 1.1.0 (empty-key row) in [cachekit-ts#165](https://github.com/cachekit-io/cachekit-ts/pull/165), since 0.2.1; fixture 1.2.0 (encoder-mistake rows) in [cachekit-ts#217](https://github.com/cachekit-io/cachekit-ts/pull/217), since 0.2.1, 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), since 0.2.1, 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), since 0.2.1 (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), since 0.2.1, 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), since 0.2.1, 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), since 0.2.1, 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), since 0.2.1; `1.3.0` re-vendored byte for byte (sha256-pinned) in [cachekit-ts#222](https://github.com/cachekit-io/cachekit-ts/pull/222), since 0.2.1, 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), since 0.2.1: 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 | +| 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 since 0.23.0 ([cachekit-py#537](https://github.com/cachekit-io/cachekit-py/pull/537)), 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 since 0.23.0 ([cachekit-py#537](https://github.com/cachekit-io/cachekit-py/pull/537)), 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); at the envelope entry point (`ByteStorage.retrieve`), every `1.2.0` reject vector asserts cachekit-core 0.7.0's pre-scan error, with the reason for each depth-only and overclaim-only vector, and every accept vector gets past the pre-scan, in [cachekit-py#627](https://github.com/cachekit-io/cachekit-py/pull/627) (unreleased; 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 since 0.23.0 ([cachekit-py#522](https://github.com/cachekit-io/cachekit-py/pull/522)); `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 since 0.23.0 ([cachekit-py#550](https://github.com/cachekit-io/cachekit-py/pull/550)); `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 `python-frame.json` revision of [protocol#180](https://github.com/cachekit-io/protocol/pull/180) (11 frame vectors: not yet the [protocol#193](https://github.com/cachekit-io/protocol/pull/193) revision, which adds `padded_header_frame`), each vendored byte for byte (sha256-pinned) + CI-executed since 0.23.0 ([cachekit-py#561](https://github.com/cachekit-io/cachekit-py/pull/561)) — ⚠️ 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 — every "since 0.2.1" below is a PR in the `cachekit-v0.2.1` tag's history (checked 2026-10-24; the Node lane's `test/protocol` suite passes at that tag against the published `cachekit-core-ts` 0.2.0 binding); fixture 1.1.0 since npm 0.2.1 ([cachekit-ts#143](https://github.com/cachekit-io/cachekit-ts/pull/143); `'nsapi'` in `dist/serialization/interop.js` of the 0.2.1 `.tgz`, absent from 0.1.5's `validateInteropSegment`); fixture 1.2.0 since 0.2.1 ([cachekit-ts#164](https://github.com/cachekit-io/cachekit-ts/pull/164); `must not contain '..'` in `dist/serialization/interop.js`, absent from 0.1.5); 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), since 0.2.1: 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), since 0.2.1; `1.2.0` (the `incomplete` reason) in [cachekit-ts#224](https://github.com/cachekit-io/cachekit-ts/pull/224), since 0.2.1, 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, through npm 0.2.1, the envelope entry point (`unpack` in `cachekit-core-ts` and `cachekit-core-wasm`), whose bindings embed `cachekit-core` 0.6.0 and so have no guard to assert; 🚧 unreleased — on `main`, `unpack` runs every vector too, in the Node lane and inside workerd, in [cachekit-ts#245](https://github.com/cachekit-io/cachekit-ts/pull/245): each reject vector fails with `cachekit-core` 0.7.0's envelope pre-scan error (both bindings on core 0.7.0 since [cachekit-ts#240](https://github.com/cachekit-io/cachekit-ts/pull/240)), pinning the depth bound on depth-only vectors and the byte or slot budget on overclaim-only ones, and each accept vector gets past the pre-scan to core's typed decode; it stays unreleased until a published `@cachekit-io/cachekit` depends on `cachekit-core-ts` and `cachekit-core-wasm` releases that embed core 0.7.0, because npm 0.2.1 pins both at exactly 0.2.0 (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), since 0.2.1; `path-encoding.json` vendored + CI-executed since [cachekit-ts#118](https://github.com/cachekit-io/cachekit-ts/pull/118), since 0.2.1; fixture 1.1.0 (empty-key row) in [cachekit-ts#165](https://github.com/cachekit-io/cachekit-ts/pull/165), since 0.2.1; fixture 1.2.0 (encoder-mistake rows) in [cachekit-ts#217](https://github.com/cachekit-io/cachekit-ts/pull/217), since 0.2.1, 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), since 0.2.1, 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), since 0.2.1 (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), since 0.2.1, 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), since 0.2.1, 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), since 0.2.1, 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), since 0.2.1; `1.3.0` re-vendored byte for byte (sha256-pinned) in [cachekit-ts#222](https://github.com/cachekit-io/cachekit-ts/pull/222), since 0.2.1, 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), since 0.2.1: 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]