diff --git a/spec/cache-key-format.md b/spec/cache-key-format.md index 172bc7d..19d5e90 100644 --- a/spec/cache-key-format.md +++ b/spec/cache-key-format.md @@ -15,6 +15,7 @@ ## Table of Contents - [Key Format](#key-format) +- [Server-Side Requirements](#server-side-requirements) - [Cross-SDK Key Generation Strategy](#cross-sdk-key-generation-strategy) - [Argument Hashing Algorithm](#argument-hashing-algorithm) - [Character Normalization](#character-normalization) @@ -32,6 +33,13 @@ ### Full Key Structure +> [!NOTE] +> This 7-segment structure is the **Python SDK's internal convention**, not a +> server requirement. The CachekitIO backend validates keys security-only (see +> [Server-Side Requirements](#server-side-requirements)) and otherwise treats +> them as opaque strings — TypeScript/Rust `{ns}:{hash}` keys and +> [Interop Mode](interop-mode.md) keys are equally valid on the wire. + ``` ns:{namespace}:func:{module}.{qualname}:args:{blake2b_hash}:{ic_flag}{serializer_code} ``` @@ -78,6 +86,26 @@ ns:cache:func:app.views.index:args:0000...0000:0s --- +## Server-Side Requirements + +The CachekitIO SaaS stores keys as opaque strings; the ONLY structure it +enforces is security-relevant (per `saas` issue #91 / SRP refactor): + +| Check | Rule | +| :--- | :--- | +| Transport | Key is percent-encoded into the URL path; the server decodes it once. | +| Length | Decoded key ≤ 400 characters. | +| Charset | `[a-zA-Z0-9_.:-]` only — no `/` (sub-resource routing), no `%`, no control chars. | +| Traversal | `..` is rejected anywhere in the key. | +| Namespace | Keys starting `ns:{namespace}:` or `nsapi:{namespace}:` must have a namespace of 1–64 chars of `[a-zA-Z0-9_-]`. Keys without either prefix scope to the `default` namespace. | +| Write spaces | `ns:` keys are mutable only by SDK (`ck_sdk_`) API keys; `nsapi:` keys only by direct (`ck_api_`) API keys. Reads are open to both. Legacy `ck_live_` keys are exempt. | + +Everything else in this document — segment count, `func:`/`args:` literals, +hash length, metadata flags — is SDK convention for deterministic key +generation, invisible to the server. + +--- + ## Cross-SDK Key Generation Strategy For multi-language interoperability, all SDKs MUST use **explicit namespaces** rather than auto-generated function signatures. The `func:` segment is inherently language-specific (Python modules vs PHP namespaces vs Go packages), so cross-language cache sharing requires: diff --git a/spec/interop-mode.md b/spec/interop-mode.md index 6948dc2..aca6127 100644 --- a/spec/interop-mode.md +++ b/spec/interop-mode.md @@ -11,6 +11,9 @@ > [npm](https://www.npmjs.com/package/@cachekit-io/cachekit) 0.1.3+, Rust on > [crates.io](https://crates.io/crates/cachekit-rs) 0.4.0+ — floors, not snapshots; consult > each registry or the [SDK feature matrix](../sdk-feature-matrix.md#compliance-status) for current versions. +> Server-side: the CachekitIO validator accepts interop-format keys +> (`{namespace}:{operation}:{args_hash}` scopes to the `default` namespace; +> see [cache-key-format.md → Server-Side Requirements](cache-key-format.md#server-side-requirements)). > Design discussion: [Issue #1](https://github.com/cachekit-io/protocol/issues/1) · > Test vectors: [`test-vectors/interop-mode.json`](../test-vectors/interop-mode.json) · > Reference implementation: [`tools/interop-reference.py`](../tools/interop-reference.py) @@ -375,13 +378,15 @@ bytes ([saas-api.md](saas-api.md)). Interop keys carry **no `ns:` prefix**; the `{namespace}` segment is an SDK-level convention, not a SaaS routing element (tenant isolation comes from authentication, not key parsing). -> [!WARNING] -> The deployed SaaS cache-key validator currently enforces auto-mode grammar and -> would reject interop-format keys. Shrinking that validator to security-only checks -> is tracked in [saas#91](https://github.com/cachekit-io/saas/issues/91) and MUST land -> before interop mode ships against the CachekitIO backend. The interop segment -> grammar (lowercase, no `:` beyond the two delimiters, no `/`, max 194 chars) is -> deliberately a strict subset of what a security-only validator accepts. +> [!NOTE] +> The SaaS cache-key validator was shrunk to security-only checks +> ([saas#91](https://github.com/cachekit-io/saas/issues/91), landed in +> [saas#231](https://github.com/cachekit-io/saas/pull/231)) — the deployed validator +> accepts interop-format keys; see +> [cache-key-format.md → Server-Side Requirements](cache-key-format.md#server-side-requirements). +> The interop segment grammar (lowercase, no `:` beyond the two delimiters, no `/`, +> max 194 chars) is deliberately a strict subset of what the security-only +> validator accepts. --- diff --git a/test-vectors/cache-keys.json b/test-vectors/cache-keys.json index 304fae8..003d242 100644 --- a/test-vectors/cache-keys.json +++ b/test-vectors/cache-keys.json @@ -2,8 +2,8 @@ "version": "1.0.0", "generator": "cachekit-py v0.12.0", "ci_verification": "Vendored (sha256-pinned) and byte-verified against CacheKeyGenerator in cachekit-py default CI: tests/unit/protocol/test_cache_key_vectors.py", - "note": "Keys include __main__ module because vectors were generated at top level. Cross-SDK implementations should substitute their own module path \u2014 only the args hash portion must match.", - "key_format": "ns:{namespace}:func:{module}.{qualname}:args:{blake2b_256_hex}:{ic_flag}{serializer_code}", + "note": "Keys include __main__ module because vectors were generated at top level. Cross-SDK implementations should substitute their own module path — only the args hash portion must match. The 7-segment shape is SDK-internal convention: the CachekitIO server accepts any key passing its security-only checks (length, charset, namespace prefix shape), including TS/Rust {ns}:{hash} and interop-mode keys.", + "key_format": "ns:{namespace}:func:{module}.{qualname}:args:{blake2b_256_hex}:{ic_flag}{serializer_code} (Python SDK convention — server validates security-only, see spec/cache-key-format.md#server-side-requirements)", "hash_algorithm": "blake2b-256 of msgpack([normalized_args, sorted_kwargs])", "vectors": [ {