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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions changelog.d/20261027_lab-11371.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
### Intent presets — TypeScript default tenant reads ✅ (LAB-11371)

- `spec/intent-presets.md` § SDK Conformance: the TypeScript cell of "Default `tenant_id` is `"default"`, identical
for HKDF and AAD" flips from ❌ to ✅ from `@cachekit-io/cachekit` 0.2.1. It had said key derivation used
`'default'` while the AAD used `''`; npm 0.1.5 still does, 0.2.1 does not. `createCache.secure()` with no
`tenantId` decrypts the `default_tenant_interop` vector through an interop-mode `wrap` read on the published
0.2.1 package. The section preface gains the TypeScript 0.2.1 floor sentence.
4 changes: 2 additions & 2 deletions conformance/coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ Marked not-a-requirement in `spec/intent-presets.md`:
- line 53: The key words **MUST**, MUST NOT, SHOULD, SHOULD NOT and MAY are used as in RFC 2119.
- line 53: The key words MUST, **MUST NOT**, SHOULD, SHOULD NOT and MAY are used as in RFC 2119.
- line 143: … storage container, which is SDK-internal (protocol#11). This specification therefore fixes no **MUST** on integrity:
- line 502: No **MUST** on integrity. The checksum lives in the storage container, which protocol#11 makes SDK-internal; a MUST here would …
- line 503: … on integrity. The checksum lives in the storage container, which protocol#11 makes SDK-internal; a **MUST** here would legislate the container by the back door.
- line 505: No **MUST** on integrity. The checksum lives in the storage container, which protocol#11 makes SDK-internal; a MUST here would …
- line 506: … on integrity. The checksum lives in the storage container, which protocol#11 makes SDK-internal; a **MUST** here would legislate the container by the back door.

## spec/interop-mode.md

Expand Down
5 changes: 4 additions & 1 deletion spec/intent-presets.md
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,9 @@ their 0.11.0 claims against the published 0.11.0 `.crate` on 2026-10-23, and
their 0.12.0 claims against the published 0.12.0 `.crate` on 2026-10-26, and the "still in
0.13.0" and "from 0.13.0" claims against the published 0.13.0 `.crate` on 2026-10-26; they
carry no line cite, because this document's line refs are pinned to `cachekit-rs@6587ce9`.
TypeScript claims carrying the floor `@cachekit-io/cachekit` 0.2.1+ were verified against the
published 0.2.1 `.tgz` on 2026-10-27; they carry no line cite, because this document's line refs
are pinned to `cachekit-ts@379847c`.

| Requirement | Python | Rust | TypeScript |
| :--- | :--- | :--- | :--- |
Expand All @@ -459,7 +462,7 @@ carry no line cite, because this document's line refs are pinned to `cachekit-rs
| `secure` takes a hex key and falls back to `CACHEKIT_MASTER_KEY` | ✅ ≥ 32 B (`validation.py:95`) | ✅ hex `CacheKit::secure(url, master_key_hex)`, ≥ 32 B, and `CacheKit::secure_from_env(url)` reads `CACHEKIT_MASTER_KEY` — `cachekit-rs` 0.8.0+ (0.7.0 and earlier: `encrypted(url, &[u8])`, raw bytes, no env fallback); [Master Key Input](#master-key-input) rule 4: the raw-bytes entry points `encryption_from_bytes`, `encryption_from_bytes_with_previous`, `EncryptionLayer::new` and `EncryptionLayer::with_previous_keys` require exactly 32 B, current and previous keys alike — `cachekit-rs` 0.9.0+ (0.8.0 and earlier check `len() >= 32` and so accept more than exactly 32 B) | ✅ exactly 32 B (`constants.ts:138`) |
| Missing master key fails at construction | ✅ `intent.py:212` | ✅ required argument on `::secure`; `::secure_from_env` with `CACHEKIT_MASTER_KEY` unset or empty → `Err` at construction, before any Redis I/O (`cachekit-rs` 0.8.0+); short or non-hex key → `Err` | ✅ `intents-core.ts:257` |
| `secure` companion env constructor honours `CACHEKIT_PREVIOUS_MASTER_KEYS` ([Explicit Configuration](#explicit-configuration) rule 4) | N/A — no companion constructor; the preset's optional key argument falls back itself | ✅ `::secure_from_env` reads `CACHEKIT_MASTER_KEY` and `CACHEKIT_PREVIOUS_MASTER_KEYS`, validating the previous keys as `CachekitConfig::from_env()` does, before any Redis I/O — `cachekit-rs` 0.9.0+ (0.8.0 reads `CACHEKIT_MASTER_KEY` only and ignores `CACHEKIT_PREVIOUS_MASTER_KEYS` without an error) | N/A — no companion constructor; the preset's optional `masterKey` falls back itself |
| Default `tenant_id` is `"default"`, identical for HKDF and AAD | ✅ `cachekit` 0.20.0+ — `DEFAULT_TENANT_ID` (LAB-4666); explicit `deployment_uuid` / `CACHEKIT_DEPLOYMENT_UUID` override only, no machine-local fallback | ✅ `cachekit-rs` 0.8.0+ — every path that encrypts with no caller-supplied tenant derives the key and builds the AAD under `"default"`: `CacheKit::secure` and `CacheKit::secure_from_env`, each read against `default_tenant_interop` by `hex_path_decrypts_default_tenant_vector` and `env_path_decrypts_default_tenant_vector`, and, through 0.11.x, `CacheKit::from_env()` with `CACHEKIT_MASTER_KEY` set, whose tenant is `config.namespace`, which `CachekitConfig::from_env()` never sets, so it falls back to `"default"`. Through 0.11.x, every other encryption entry point takes the tenant as an argument. Verified 2026-10-23 against each published `.crate` from 0.8.0 to 0.11.1. From 0.12.0, `from_env()` no longer encrypts, and the builder call `.encryption_from_env()` that replaces it also derives under `"default"`, read against `default_tenant_interop` by `from_env_encryption_from_env_decrypts_default_tenant_vector` from 0.13.0 (0.12.0 and 0.13.0 `.crate`s inspected 2026-10-26) | ❌ HKDF `'default'` (`manager-core.ts:206`) but AAD `''` (`manager-core.ts:375`) — mismatched within one SDK — LAB-4668 |
| Default `tenant_id` is `"default"`, identical for HKDF and AAD | ✅ `cachekit` 0.20.0+ — `DEFAULT_TENANT_ID` (LAB-4666); explicit `deployment_uuid` / `CACHEKIT_DEPLOYMENT_UUID` override only, no machine-local fallback | ✅ `cachekit-rs` 0.8.0+ — every path that encrypts with no caller-supplied tenant derives the key and builds the AAD under `"default"`: `CacheKit::secure` and `CacheKit::secure_from_env`, each read against `default_tenant_interop` by `hex_path_decrypts_default_tenant_vector` and `env_path_decrypts_default_tenant_vector`, and, through 0.11.x, `CacheKit::from_env()` with `CACHEKIT_MASTER_KEY` set, whose tenant is `config.namespace`, which `CachekitConfig::from_env()` never sets, so it falls back to `"default"`. Through 0.11.x, every other encryption entry point takes the tenant as an argument. Verified 2026-10-23 against each published `.crate` from 0.8.0 to 0.11.1. From 0.12.0, `from_env()` no longer encrypts, and the builder call `.encryption_from_env()` that replaces it also derives under `"default"`, read against `default_tenant_interop` by `from_env_encryption_from_env_decrypts_default_tenant_vector` from 0.13.0 (0.12.0 and 0.13.0 `.crate`s inspected 2026-10-26) | ✅ `@cachekit-io/cachekit` 0.2.1+ — with no `tenantId`, one `effectiveTenantId` of `'default'` feeds both key derivation and the AAD, and an empty `tenantId` is rejected at construction. Executed 2026-10-27 against the published 0.2.1 `.tgz`: `createCache.secure()` with the vector's master key, no `tenantId` and a backend holding only `default_tenant_interop`'s ciphertext at its `cache_key`, read through `wrap` in interop mode (`users` / `get_user`, arity 1, called with `42`), returns `{age: 30, name: "alice"}` without calling the wrapped function. npm 0.1.5 derives the key under `'default'` but builds the AAD with `''`, and the same read misses (`.tgz` executed 2026-10-27) |
| `CACHEKIT_MASTER_KEY` does not activate encryption on `minimal` / `production` / `io` | ✅ `cachekit` 0.21.0+ — the key activates no preset but `secure` (its key fallback), and `local` never encrypts. In `cachekit` 0.21.0, migration step 3's one-release gate also makes `minimal`, `production`, `io` and every other preset except `secure` and `local` fail at construction with `ConfigurationError`, naming the explicit spellings, when a key is present and no intent is stated (no `encryption=`, or an `EncryptionConfig` without `enabled=`), `backend=None` included | ✅ `cachekit-rs` 0.12.0+ ([cachekit-rs#173](https://github.com/cachekit-io/cachekit-rs/pull/173)) — `from_env()` no longer activates: it reads and validates `CACHEKIT_MASTER_KEY` and `CACHEKIT_PREVIOUS_MASTER_KEYS`, `.encryption_from_env()` encrypts with them for tenant `"default"` (an unset or empty key is `CachekitError::Config`, never plaintext), and `.no_encryption()` is the explicit opt-out (rule 3). From 0.12.0 (still in 0.13.0), a transitional check makes a `from_env()` builder that saw the key and states no encryption intent (any encryption call, or `.no_encryption()`) fail at `build()` with `CachekitError::Config`, like `cachekit` 0.21.0's one-release gate (migration step 3). 0.12.0 and 0.13.0 `.crate`s inspected 2026-10-26. 0.11.x and earlier: `from_env()` activated from key presence | ✅ `secure()` only (`intents-core.ts:255`) |
| Explicit encryption option encrypts every operation (activation rule 1) | ✅ `cachekit` 0.20.0+ — `encryption=True` wraps the serializer on the backend path, and `backend=None` with encryption raises `ConfigurationError` at decoration | ✅ `cachekit-rs` 0.10.0+ ([cachekit-rs#133](https://github.com/cachekit-io/cachekit-rs/pull/133)) — every builder encryption method (`.encryption()`, `.encryption_from_bytes()`, `.encryption_from_bytes_with_previous()`, `.encryption_with_previous()` from 0.11.0 and `.encryption_from_env()` from 0.12.0, `.crate` inspected 2026-10-26) and the `secure` / `secure_from_env` presets encrypt every value read and write: `get`, `set`, `set_with_ttl`, `interop_get`, `interop_get_swr` and `#[cachekit]` with or without `secure`. With the `encryption` feature off, the builder methods return `CachekitError::Config`. Through 0.9.0 only the `SecureCache` handle encrypted, and with the feature off the builder methods returned `Ok(self)` | ✅ `if (this.encryption)` on both paths (`cache-core.ts:558`, `:823`) |
| Encrypted preset is spelled `secure` | ✅ `@cache.secure` | ✅ `CacheKit::secure(url, key)` — `cachekit-rs` 0.8.0+ (`CacheKit::encrypted` through 0.7.0) | ✅ `createCache.secure()` |
Expand Down
Loading