diff --git a/README.md b/README.md index f2720cb..a771728 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,7 @@ let user: Option = cache.interop_get(&key).await?; // strict read: exactly Argument hashing is byte-identical across SDKs (canonical MessagePack + Blake2b-256), verified against the shared [protocol test vectors](https://github.com/cachekit-io/protocol/blob/main/test-vectors/interop-mode.json) in this repo's test suite. `interop_get` (also on `SecureCache`) rejects trailing bytes and Python-internal CK frames instead of silently misreading them. Encryption works unchanged — interop keys are identical across SDKs, so the AAD verifies cross-SDK. > [!IMPORTANT] -> Use interop keys on a client **without** `.namespace()` / `CACHEKIT_NAMESPACE` — a client prefix would rewrite the storage key to `{prefix}:{interop_key}`, which no other SDK computes. `interop_get` fails closed with a config error rather than silently missing; interop keys already carry their own namespace segment. +> Use interop keys on a client **without** `.namespace()` — a client prefix would rewrite the storage key to `{prefix}:{interop_key}`, which no other SDK computes. `interop_get` fails closed with a config error rather than silently missing; interop keys already carry their own namespace segment. Interop mode in production: [Skyline](https://github.com/cachekit-io/bluesky-thinking) — the canonical example project — runs this SDK on `wasm32` (Cloudflare Workers) deriving interop keys and verifying payload integrity for the namespace the Python and TypeScript SDKs share (public aggregate reads stay on the TypeScript edge — see the [example page](https://docs.cachekit.io/examples/skyline/)). diff --git a/crates/cachekit-macros/src/lib.rs b/crates/cachekit-macros/src/lib.rs index 3195f5e..9ccd131 100644 --- a/crates/cachekit-macros/src/lib.rs +++ b/crates/cachekit-macros/src/lib.rs @@ -197,11 +197,15 @@ fn extract_ok_type(ret: &ReturnType) -> syn::Result { /// (`NAN`, `±INFINITY`) and `i128` outside `[-2^63, 2^64-1]`. This /// fail-loud contract matches the Python and TypeScript SDKs — silently /// running uncached would mask cross-SDK key divergence. -/// - The client must be built **without** `.namespace()` / -/// `CACHEKIT_NAMESPACE` — interop keys carry their own namespace segment, -/// and reads fail closed on a namespaced client. -/// - A stored entry that cannot be decoded as the return type is treated as -/// a miss and overwritten (self-healing), never an error loop. +/// - The client must be built **without** `.namespace()` — interop keys +/// carry their own namespace segment, and reads fail closed on a +/// namespaced client. +/// - A stored entry that cannot be decoded as the return type +/// (`CachekitError::Serialization`) is treated as a miss and overwritten +/// (self-healing). On `secure` functions this covers only post-decrypt +/// decode failures: an entry that fails AES-GCM authentication raises +/// `CachekitError::Encryption`, which propagates (fail-closed) until the +/// entry expires or is deleted. /// /// # Requirements (continued) /// diff --git a/crates/cachekit/src/client.rs b/crates/cachekit/src/client.rs index 9014511..c6e958a 100644 --- a/crates/cachekit/src/client.rs +++ b/crates/cachekit/src/client.rs @@ -378,7 +378,7 @@ impl CacheKit { /// # Errors /// /// Returns [`CachekitError::Config`] if the client was built with - /// [`CacheKitBuilder::namespace`] (or `CACHEKIT_NAMESPACE`): the prefix + /// [`CacheKitBuilder::namespace`]: the prefix /// would rewrite the storage key to `{prefix}:{interop_key}`, which no /// other SDK computes — every cross-SDK entry would silently miss. Interop /// keys carry their own namespace segment; failing loudly here beats a @@ -400,8 +400,8 @@ impl CacheKit { match self.namespace { None => Ok(()), Some(_) => Err(CachekitError::Config( - "interop reads require a client without a namespace prefix: .namespace() / \ - CACHEKIT_NAMESPACE would store interop entries under {prefix}:{interop_key}, \ + "interop reads require a client without a namespace prefix: .namespace() \ + would store interop entries under {prefix}:{interop_key}, \ which other SDKs never compute (interop keys already carry a namespace \ segment) — use a dedicated non-namespaced client for interop entries" .to_owned(),