diff --git a/changelog.d/20261010_lab-8577.md b/changelog.d/20261010_lab-8577.md new file mode 100644 index 0000000..1972261 --- /dev/null +++ b/changelog.d/20261010_lab-8577.md @@ -0,0 +1,11 @@ +### SDK feature matrix — cachekit-ts passes `reject_envelope_slots_overclaim` (LAB-8577) + +- [`sdk-feature-matrix.md`](sdk-feature-matrix.md): the TypeScript Wire format cell no longer + records `reject_envelope_slots_overclaim` as an expected failure. Since + [cachekit-ts#232](https://github.com/cachekit-io/cachekit-ts/pull/232) (unreleased), the + SDK's header walk keeps a running total of declared slots and refuses the vector with the + envelope pre-scan's own error ([WIRE-9](spec/wire-format.md#wire-9)) in both test lanes, + before core sees the bytes. The cell's "flips when" condition now names only the core + release. +- Note ¹⁶ gives cachekit-ts one gap held as an expected failure, the temporal sentinels, + where it gave two. diff --git a/sdk-feature-matrix.md b/sdk-feature-matrix.md index 7814cc6..92c23a7 100644 --- a/sdk-feature-matrix.md +++ b/sdk-feature-matrix.md @@ -293,7 +293,7 @@ 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¹⁵ except the envelope pre-scan: payload decodes are pre-scanned (`unpackb_bounded`), but the envelope goes through `cachekit-core`'s `ByteStorage::retrieve`, which has no [Retrieve Flow](spec/wire-format.md#retrieve-flow) step-2 pre-scan in any published core; it is on core `main` ([cachekit-core#80](https://github.com/cachekit-io/cachekit-core/pull/80)), unreleased, and this cell flips when cachekit-py bumps to a core release that carries it; ✅ 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 in [cachekit-py#550](https://github.com/cachekit-io/cachekit-py/pull/550), unreleased, 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), with `reject_envelope_slots_overclaim` a strict expected failure (WIRE-9) until cachekit-py pins a core release with the pre-scan, and both `payload_reject_vectors` asserting the structural guard's own error after the envelope read; ⚠️ the allocation bound for the size-cap and ratio vectors needs a probe on `cachekit-core`'s allocator, which no core release has | 🚧 unreleased — Canonical (`cachekit-core`), unused for stored values¹⁵; step-2 pre-scan on core `main` ([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`; absent from crates.io 0.6.0 | ⚠️ 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 any published core; it is on core `main` ([cachekit-core#80](https://github.com/cachekit-io/cachekit-core/pull/80)), unreleased, and this cell flips when both bindings move to a core release that carries it and the `reject_envelope_slots_overclaim` expected failure below closes; 🚧 unreleased — on `main`, 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; ⚠️ the read refuses `reject_envelope_slots_overclaim` before unpack, but as a type error at element 1 rather than with the pre-scan's own error, because the SDK's own header walk checks each header against the bytes after it, not the slot sum ([WIRE-9](spec/wire-format.md#wire-9)): an expected failure in both lanes. A core release carrying [cachekit-core#80](https://github.com/cachekit-io/cachekit-core/pull/80) does not change it, because the read path refuses these bytes before core sees them. The walk allocates nothing and admits only complete envelopes; ⚠️ the allocation bound for the size-cap and ratio vectors needs a probe on `cachekit-core`'s allocator, which no core release has | ⚠️ Untested | +| Wire format (ByteStorage) | ⚠️ Compliant¹⁵ except the envelope pre-scan: payload decodes are pre-scanned (`unpackb_bounded`), but the envelope goes through `cachekit-core`'s `ByteStorage::retrieve`, which has no [Retrieve Flow](spec/wire-format.md#retrieve-flow) step-2 pre-scan in any published core; it is on core `main` ([cachekit-core#80](https://github.com/cachekit-io/cachekit-core/pull/80)), unreleased, and this cell flips when cachekit-py bumps to a core release that carries it; ✅ 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 in [cachekit-py#550](https://github.com/cachekit-io/cachekit-py/pull/550), unreleased, 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), with `reject_envelope_slots_overclaim` a strict expected failure (WIRE-9) until cachekit-py pins a core release with the pre-scan, and both `payload_reject_vectors` asserting the structural guard's own error after the envelope read; ⚠️ the allocation bound for the size-cap and ratio vectors needs a probe on `cachekit-core`'s allocator, which no core release has | 🚧 unreleased — Canonical (`cachekit-core`), unused for stored values¹⁵; step-2 pre-scan on core `main` ([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`; absent from crates.io 0.6.0 | ⚠️ 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 any published core; it is on core `main` ([cachekit-core#80](https://github.com/cachekit-io/cachekit-core/pull/80)), unreleased, and this cell flips when both bindings move to a core release that carries it; 🚧 unreleased — on `main`, 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 [cachekit-ts#232](https://github.com/cachekit-io/cachekit-ts/pull/232) (unreleased): 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 needs a probe on `cachekit-core`'s allocator, which no core release has | ⚠️ 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.1.5 (`.tgz` inspected 2026-10-07): the default path revives none, and writes a `Date` as its ISO string; interop mode revives only `__datetime__`, leaving `__date__` and `__time__` as maps, since JavaScript has no date-only or time-only type; unchanged on `main`, where 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), unreleased) | ⚠️ Untested | | Encryption (AES-256-GCM) | ✅ Compliant | ✅ Canonical (cachekit-core) | ✅ Compliant | ⚠️ Untested | @@ -308,7 +308,7 @@ its spec: > > ¹⁵ Auto-mode **stored bytes** are SDK-internal and differ per SDK — see [wire-format.md → SDK Storage Containers](spec/wire-format.md#sdk-storage-containers-auto-mode). Python stores the ByteStorage envelope *inside* its CK v3 frame; `cachekit-rs` does not use the envelope for values at all (it uses `cachekit-core` only for encryption). Because `cachekit-rs` reads plain MessagePack, it decodes another SDK's container that is one well-formed MessagePack document: cachekit-ts's bare ByteStorage envelope (`python-frame.json` `bare_envelope_fed_to_frame_reader`) decodes on every read path of a client without encryption, and a read fails only when the requested type's shape does not fit. That is a WIRE-21 violation for that container, and it holds in every release and on `main`; [`conformance/coverage.md`](conformance/coverage.md) counts cachekit-rs's WIRE-21 vectors as held because they are vendored, not because cachekit-rs refuses them all. crates.io 0.10.0 also decodes a CK frame in auto mode, as the integer 67, a second WIRE-21 violation (its interop mode names the frame); on `main`, auto mode rejects the trailing bytes since [cachekit-rs#140](https://github.com/cachekit-io/cachekit-rs/pull/140), unreleased. Cross-SDK value compatibility is exclusively an [interop-mode](spec/interop-mode.md) property (protocol#11). > -> ¹⁶ "Test vectors in CI" = vectors the SDK's own default CI executes. Beyond the SDKs, this repo's `verify.yml` CI-verifies `interop-mode.json`, `encryption.json`, `python-frame.json`, `file-backend.json` ([`tools/file-backend-reference.py`](tools/file-backend-reference.py)), and — since LAB-423 — `wire-format.json` ([`tools/wire-format-reference.py`](tools/wire-format-reference.py)), and — since LAB-2503 — `decode-bounds.json` ([`tools/decode-bounds-reference.py`](tools/decode-bounds-reference.py), `verify` in both the stdlib and the optional-deps legs), and — since LAB-2879 — `path-encoding.json` ([`tools/path-encoding-verify.py`](tools/path-encoding-verify.py)), and `freshness-headers.json` ([`tools/freshness-headers-verify.py`](tools/freshness-headers-verify.py)) against reference implementations. This repo's `interop-mode.json` is `1.3.0` (44 key, 6 value, 34 error, 6 reader accept and 1 reader reject vectors), which adds key and error vectors for the segment grammar on both segments, out-of-model values nested in a list, a map value or a set, map keys a collator or a length-first sort orders wrongly, a `__proto__` key, strings that start with U+FEFF, integers a float64 cannot hold and small integers passed as `{"$int": …}`, and the two reader groups: documents a value reader must accept though no canonical writer emits them, and a trailing byte it must reject. All three SDKs vendor `1.3.0` byte for byte, in [cachekit-py#537](https://github.com/cachekit-io/cachekit-py/pull/537), [cachekit-rs#152](https://github.com/cachekit-io/cachekit-rs/pull/152) and [cachekit-ts#224](https://github.com/cachekit-io/cachekit-ts/pull/224) (unreleased). This repo's `wire-format.json` is `1.4.0` (LAB-8147): both sides of the `bin8` → `bin16` edge, three constructed envelopes on the `bin16` → `bin32` edge (one a legacy `array32`), eight more `reject_vectors` (step 2's typed decode and pre-scan, an `original_size` with the sign bit set, and a ratio a float32 rounds to exactly 1000), and two new groups: `payload_reject_vectors` and `temporal_sentinel_vectors`. cachekit-py vendors `1.4.0` byte for byte and runs every vector in [cachekit-py#550](https://github.com/cachekit-io/cachekit-py/pull/550) (unreleased), `reject_envelope_slots_overclaim` as a strict expected failure until it pins a `cachekit-core` release with the envelope pre-scan; cachekit-ts vendors it byte for byte in [cachekit-ts#229](https://github.com/cachekit-io/cachekit-ts/pull/229) (unreleased), with two gaps held as expected failures (see the Wire format and Temporal sentinels rows). `1.3.0` added six ByteStorage `reject_vectors` (LAB-6508). cachekit-py runs all six since 0.21.0 ([cachekit-py#448](https://github.com/cachekit-io/cachekit-py/pull/448)) and cachekit-ts on `main` ([cachekit-ts#198](https://github.com/cachekit-io/cachekit-ts/pull/198), unreleased), each through its envelope read path; `cachekit-rs` stores no envelope¹⁵. No `cachekit-core` release runs them (0.6.0 vendors `wire-format.json` 1.1.0), and no core release has the allocation probe their size-cap and ratio vectors require, so neither SDK asserts that bound yet. This repo's `file-backend.json` is `1.2.0` (LAB-8147), which adds an empty and a binary payload, flag bit 15, reserved values 1 and 255, and two expired entries with an unknown flag or reserved value, which a reader keeps; cachekit-py vendors `1.2.0` byte for byte in [cachekit-py#550](https://github.com/cachekit-io/cachekit-py/pull/550) and cachekit-rs in [cachekit-rs#158](https://github.com/cachekit-io/cachekit-rs/pull/158) (both unreleased), and cachekit-ts vendors `1.2.0` in [cachekit-ts#229](https://github.com/cachekit-io/cachekit-ts/pull/229) (unreleased). `python-frame.json` (LAB-8147) adds frame-check boundary vectors, other SDKs' containers and an `encrypted_read_vectors` group; cachekit-py vendors it byte for byte and runs every frame, error and encrypted read vector in [cachekit-py#550](https://github.com/cachekit-io/cachekit-py/pull/550), and its current revision in [cachekit-py#561](https://github.com/cachekit-io/cachekit-py/pull/561) (both unreleased); cachekit-rs vendors it byte for byte in [cachekit-rs#158](https://github.com/cachekit-io/cachekit-rs/pull/158), and the current revision in [cachekit-rs#160](https://github.com/cachekit-io/cachekit-rs/pull/160) (both unreleased); cachekit-ts vendors it byte for byte for its container vectors in [cachekit-ts#229](https://github.com/cachekit-io/cachekit-ts/pull/229), and its current revision in [cachekit-ts#230](https://github.com/cachekit-io/cachekit-ts/pull/230) (both unreleased). This repo's `path-encoding.json` is `1.2.0` (31 rows, 6 reject); all three SDKs vendor `1.2.0` byte for byte, in [cachekit-py#522](https://github.com/cachekit-io/cachekit-py/pull/522), [cachekit-ts#217](https://github.com/cachekit-io/cachekit-ts/pull/217) and [cachekit-rs#146](https://github.com/cachekit-io/cachekit-rs/pull/146) (unreleased). This repo's `freshness-headers.json` is `1.0.0` (31 rows), and all three SDKs vendor it byte for byte in the same PRs (unreleased). This repo's `encryption.json` is `1.5.0` (`1.4.0` adds a second master key input accept row and two StandardSerializer AAD shapes; `1.5.0` adds the `aad_reject_vectors`, `decrypted_container` and `keyring.configuration` tables). `1.3.0` added master key input rows for [intent-presets.md → Master Key Input](spec/intent-presets.md#master-key-input): a sealed 32-byte key with a leading zero byte, bytes above 7f and every lowercase hex digit, eleven malformed or short hex keys, and five raw keys of the wrong length. cachekit-ts runs the `1.3.0` accept and hex reject rows since [cachekit-ts#222](https://github.com/cachekit-io/cachekit-ts/pull/222) and vendors `1.5.0` byte for byte in [cachekit-ts#230](https://github.com/cachekit-io/cachekit-ts/pull/230) (both unreleased). cachekit-rs vendors `1.5.0` byte for byte in [cachekit-rs#160](https://github.com/cachekit-io/cachekit-rs/pull/160) (unreleased). cachekit-py vendors `1.5.0` byte for byte in [cachekit-py#561](https://github.com/cachekit-io/cachekit-py/pull/561) (unreleased) and runs the rows the Test vectors in CI row lists, with two `keyring.configuration` cases as strict expected failures naming [ENC-9](spec/encryption.md#enc-9). This repo's `decode-bounds.json` is `1.2.0` (19 reject, 3 accept), which adds `incomplete_within_slot_budget` and its map twin, documents cut short inside the slot budget, under a new `incomplete` reject reason. All three SDKs vendor `1.2.0` byte for byte, in [cachekit-py#537](https://github.com/cachekit-io/cachekit-py/pull/537), [cachekit-rs#152](https://github.com/cachekit-io/cachekit-rs/pull/152) and [cachekit-ts#224](https://github.com/cachekit-io/cachekit-ts/pull/224) (unreleased). Their previous copies are `1.1.0` (17 reject, 3 accept): cachekit-py's released since 0.20.0 ([cachekit-py#363](https://github.com/cachekit-io/cachekit-py/pull/363)), cachekit-rs's released since 0.8.0 ([cachekit-rs#93](https://github.com/cachekit-io/cachekit-rs/pull/93)), and cachekit-ts's in [cachekit-ts#152](https://github.com/cachekit-io/cachekit-ts/pull/152) (unreleased); the SDKs' earlier copies were `1.0.0` revisions (13 reject / 2 accept in py and ts, 10 / 2 in rs). `cachekit-core` vendors `1.1.0` too and drives it through `ByteStorage::retrieve`, on core `main` ([cachekit-core#80](https://github.com/cachekit-io/cachekit-core/pull/80)), unreleased. `cache-keys.json` is vendored and CI-verified in cachekit-py since [cachekit-py#229](https://github.com/cachekit-io/cachekit-py/pull/229) (LAB-425). This repo's copy is `1.3.0` (21 key vectors, the first 10 byte-identical to the v0.5.0 originals): `1.2.0` adds four derived serializer codes, `1.3.0` 4 `error_vectors` and 5 `serializer_object_vectors`. cachekit-py vendors `1.1.0` byte for byte since 0.21.0 ([cachekit-py#460](https://github.com/cachekit-io/cachekit-py/pull/460)), and `1.3.0` byte for byte in [cachekit-py#561](https://github.com/cachekit-io/cachekit-py/pull/561) (unreleased), running every vector. +> ¹⁶ "Test vectors in CI" = vectors the SDK's own default CI executes. Beyond the SDKs, this repo's `verify.yml` CI-verifies `interop-mode.json`, `encryption.json`, `python-frame.json`, `file-backend.json` ([`tools/file-backend-reference.py`](tools/file-backend-reference.py)), and — since LAB-423 — `wire-format.json` ([`tools/wire-format-reference.py`](tools/wire-format-reference.py)), and — since LAB-2503 — `decode-bounds.json` ([`tools/decode-bounds-reference.py`](tools/decode-bounds-reference.py), `verify` in both the stdlib and the optional-deps legs), and — since LAB-2879 — `path-encoding.json` ([`tools/path-encoding-verify.py`](tools/path-encoding-verify.py)), and `freshness-headers.json` ([`tools/freshness-headers-verify.py`](tools/freshness-headers-verify.py)) against reference implementations. This repo's `interop-mode.json` is `1.3.0` (44 key, 6 value, 34 error, 6 reader accept and 1 reader reject vectors), which adds key and error vectors for the segment grammar on both segments, out-of-model values nested in a list, a map value or a set, map keys a collator or a length-first sort orders wrongly, a `__proto__` key, strings that start with U+FEFF, integers a float64 cannot hold and small integers passed as `{"$int": …}`, and the two reader groups: documents a value reader must accept though no canonical writer emits them, and a trailing byte it must reject. All three SDKs vendor `1.3.0` byte for byte, in [cachekit-py#537](https://github.com/cachekit-io/cachekit-py/pull/537), [cachekit-rs#152](https://github.com/cachekit-io/cachekit-rs/pull/152) and [cachekit-ts#224](https://github.com/cachekit-io/cachekit-ts/pull/224) (unreleased). This repo's `wire-format.json` is `1.4.0` (LAB-8147): both sides of the `bin8` → `bin16` edge, three constructed envelopes on the `bin16` → `bin32` edge (one a legacy `array32`), eight more `reject_vectors` (step 2's typed decode and pre-scan, an `original_size` with the sign bit set, and a ratio a float32 rounds to exactly 1000), and two new groups: `payload_reject_vectors` and `temporal_sentinel_vectors`. cachekit-py vendors `1.4.0` byte for byte and runs every vector in [cachekit-py#550](https://github.com/cachekit-io/cachekit-py/pull/550) (unreleased), `reject_envelope_slots_overclaim` as a strict expected failure until it pins a `cachekit-core` release with the envelope pre-scan; cachekit-ts vendors it byte for byte in [cachekit-ts#229](https://github.com/cachekit-io/cachekit-ts/pull/229) (unreleased), with one gap held as an expected failure (see the Temporal sentinels row); `reject_envelope_slots_overclaim`, the other, passes since [cachekit-ts#232](https://github.com/cachekit-io/cachekit-ts/pull/232) (unreleased). `1.3.0` added six ByteStorage `reject_vectors` (LAB-6508). cachekit-py runs all six since 0.21.0 ([cachekit-py#448](https://github.com/cachekit-io/cachekit-py/pull/448)) and cachekit-ts on `main` ([cachekit-ts#198](https://github.com/cachekit-io/cachekit-ts/pull/198), unreleased), each through its envelope read path; `cachekit-rs` stores no envelope¹⁵. No `cachekit-core` release runs them (0.6.0 vendors `wire-format.json` 1.1.0), and no core release has the allocation probe their size-cap and ratio vectors require, so neither SDK asserts that bound yet. This repo's `file-backend.json` is `1.2.0` (LAB-8147), which adds an empty and a binary payload, flag bit 15, reserved values 1 and 255, and two expired entries with an unknown flag or reserved value, which a reader keeps; cachekit-py vendors `1.2.0` byte for byte in [cachekit-py#550](https://github.com/cachekit-io/cachekit-py/pull/550) and cachekit-rs in [cachekit-rs#158](https://github.com/cachekit-io/cachekit-rs/pull/158) (both unreleased), and cachekit-ts vendors `1.2.0` in [cachekit-ts#229](https://github.com/cachekit-io/cachekit-ts/pull/229) (unreleased). `python-frame.json` (LAB-8147) adds frame-check boundary vectors, other SDKs' containers and an `encrypted_read_vectors` group; cachekit-py vendors it byte for byte and runs every frame, error and encrypted read vector in [cachekit-py#550](https://github.com/cachekit-io/cachekit-py/pull/550), and its current revision in [cachekit-py#561](https://github.com/cachekit-io/cachekit-py/pull/561) (both unreleased); cachekit-rs vendors it byte for byte in [cachekit-rs#158](https://github.com/cachekit-io/cachekit-rs/pull/158), and the current revision in [cachekit-rs#160](https://github.com/cachekit-io/cachekit-rs/pull/160) (both unreleased); cachekit-ts vendors it byte for byte for its container vectors in [cachekit-ts#229](https://github.com/cachekit-io/cachekit-ts/pull/229), and its current revision in [cachekit-ts#230](https://github.com/cachekit-io/cachekit-ts/pull/230) (both unreleased). This repo's `path-encoding.json` is `1.2.0` (31 rows, 6 reject); all three SDKs vendor `1.2.0` byte for byte, in [cachekit-py#522](https://github.com/cachekit-io/cachekit-py/pull/522), [cachekit-ts#217](https://github.com/cachekit-io/cachekit-ts/pull/217) and [cachekit-rs#146](https://github.com/cachekit-io/cachekit-rs/pull/146) (unreleased). This repo's `freshness-headers.json` is `1.0.0` (31 rows), and all three SDKs vendor it byte for byte in the same PRs (unreleased). This repo's `encryption.json` is `1.5.0` (`1.4.0` adds a second master key input accept row and two StandardSerializer AAD shapes; `1.5.0` adds the `aad_reject_vectors`, `decrypted_container` and `keyring.configuration` tables). `1.3.0` added master key input rows for [intent-presets.md → Master Key Input](spec/intent-presets.md#master-key-input): a sealed 32-byte key with a leading zero byte, bytes above 7f and every lowercase hex digit, eleven malformed or short hex keys, and five raw keys of the wrong length. cachekit-ts runs the `1.3.0` accept and hex reject rows since [cachekit-ts#222](https://github.com/cachekit-io/cachekit-ts/pull/222) and vendors `1.5.0` byte for byte in [cachekit-ts#230](https://github.com/cachekit-io/cachekit-ts/pull/230) (both unreleased). cachekit-rs vendors `1.5.0` byte for byte in [cachekit-rs#160](https://github.com/cachekit-io/cachekit-rs/pull/160) (unreleased). cachekit-py vendors `1.5.0` byte for byte in [cachekit-py#561](https://github.com/cachekit-io/cachekit-py/pull/561) (unreleased) and runs the rows the Test vectors in CI row lists, with two `keyring.configuration` cases as strict expected failures naming [ENC-9](spec/encryption.md#enc-9). This repo's `decode-bounds.json` is `1.2.0` (19 reject, 3 accept), which adds `incomplete_within_slot_budget` and its map twin, documents cut short inside the slot budget, under a new `incomplete` reject reason. All three SDKs vendor `1.2.0` byte for byte, in [cachekit-py#537](https://github.com/cachekit-io/cachekit-py/pull/537), [cachekit-rs#152](https://github.com/cachekit-io/cachekit-rs/pull/152) and [cachekit-ts#224](https://github.com/cachekit-io/cachekit-ts/pull/224) (unreleased). Their previous copies are `1.1.0` (17 reject, 3 accept): cachekit-py's released since 0.20.0 ([cachekit-py#363](https://github.com/cachekit-io/cachekit-py/pull/363)), cachekit-rs's released since 0.8.0 ([cachekit-rs#93](https://github.com/cachekit-io/cachekit-rs/pull/93)), and cachekit-ts's in [cachekit-ts#152](https://github.com/cachekit-io/cachekit-ts/pull/152) (unreleased); the SDKs' earlier copies were `1.0.0` revisions (13 reject / 2 accept in py and ts, 10 / 2 in rs). `cachekit-core` vendors `1.1.0` too and drives it through `ByteStorage::retrieve`, on core `main` ([cachekit-core#80](https://github.com/cachekit-io/cachekit-core/pull/80)), unreleased. `cache-keys.json` is vendored and CI-verified in cachekit-py since [cachekit-py#229](https://github.com/cachekit-io/cachekit-py/pull/229) (LAB-425). This repo's copy is `1.3.0` (21 key vectors, the first 10 byte-identical to the v0.5.0 originals): `1.2.0` adds four derived serializer codes, `1.3.0` 4 `error_vectors` and 5 `serializer_object_vectors`. cachekit-py vendors `1.1.0` byte for byte since 0.21.0 ([cachekit-py#460](https://github.com/cachekit-io/cachekit-py/pull/460)), and `1.3.0` byte for byte in [cachekit-py#561](https://github.com/cachekit-io/cachekit-py/pull/561) (unreleased), running every vector. > > ¹⁷ Version cells are **floors** (`X+`), not snapshots — they stay true as new versions publish; check the registry for the current release. Python's floor is the first *installable* one: interop merged under the `v0.13.0` tag, but neither `0.12.0` nor `0.13.0` was ever published to PyPI, so `0.14.0` is the earliest PyPI release containing interop mode. Do not "correct" this to 0.13.0 from the cachekit-py changelog alone.