{
"version": "1.1.0",
"generator": "urllib.parse.quote(key, safe=\"\")",
"spec": "spec/saas-api.md § Cache-Key Path Encoding",
"ci_verification": "tools/path-encoding-verify.py (stdlib only; mutation self-test first)",
"contract": "`encoded` is the single `{key}` path segment in the reference form (spec rule 1); `decoded` is the key the server sees after its single percent-decode and equals `key` in every transmittable row (spec rules 3-4). Rows with `reject: true` are the keys spec rule 2 reserves (the empty key, `.`, `..`, `health`, `ttl`, `lock`): a conformant client raises before building the URL, so `encoded` and `decoded` are null. `encoded_alternates` lists the `encodeURIComponent` form (`! * ' ( )` raw) where it differs from `encoded`, and is absent otherwise; reject rows carry none. An SDK conforms when its encoding of `key` is in `[encoded] + encoded_alternates` (a missing `encoded_alternates` is `[]`).",
"vectors": [
{
"key": "ns:test:func:__main__.get_user:args:3870b2ea5735ae639ded9450ef117768db676f037bec636503796c5b81095153:1s",
"encoded": "ns%3Atest%3Afunc%3A__main__.get_user%3Aargs%3A3870b2ea5735ae639ded9450ef117768db676f037bec636503796c5b81095153%3A1s",
"decoded": "ns:test:func:__main__.get_user:args:3870b2ea5735ae639ded9450ef117768db676f037bec636503796c5b81095153:1s",
"note": "Canonical 7-segment auto-mode key (test-vectors/cache-keys.json `single_integer`). Only `:` is encoded; identical bytes from all three reference encoders."
},
{
"key": "default:../../admin",
"encoded": "default%3A..%2F..%2Fadmin",
"decoded": "default:../../admin",
"note": "Embedded traversal: every `/` is `%2F`, so no `../` boundary exists for a URL parser to collapse. Server decodes once, then rejects (`/` outside charset; `..` substring)."
},
{
"key": "x/../../health",
"encoded": "x%2F..%2F..%2Fhealth",
"decoded": "x/../../health",
"note": "Traversal aimed at the `/v1/cache/health` route token; inert once `/` is `%2F`. Server rejects the decoded key (charset)."
},
{
"key": "k?x=1#f",
"encoded": "k%3Fx%3D1%23f",
"decoded": "k?x=1#f",
"note": "Query/fragment injection: `?` and `#` MUST be encoded or the client's URL parser truncates the key and emits a query string. Server rejects the decoded key (charset)."
},
{
"key": "a b",
"encoded": "a%20b",
"decoded": "a b",
"note": "Space is `%20` in a path segment, never `+` (form-encoding, which the server does not decode). Server rejects the decoded key (charset)."
},
{
"key": "100%",
"encoded": "100%25",
"decoded": "100%",
"note": "A literal `%` is encoded exactly once; the server decodes once and sees `100%`, then rejects it (charset). `%2525` would decode to `100%25`, a different key."
},
{
"key": "",
"encoded": null,
"decoded": null,
"reject": true,
"note": "Empty key: it encodes to an empty segment, so `/v1/cache/{key}` becomes `/v1/cache/` and `/v1/cache/{key}/ttl` becomes `/v1/cache//ttl`, neither of which addresses a stored entry."
},
{
"key": ".",
"encoded": null,
"decoded": null,
"reject": true,
"note": "Dot segment: the plain encoding `.` is removed by every URL parser, and `%2E` is collapsed by the server's WHATWG parse (`/v1/cache/%2E` → `/v1/cache/`). No wire form reaches the validator."
},
{
"key": "..",
"encoded": null,
"decoded": null,
"reject": true,
"note": "Dot segment: unencoded, `/v1/cache/..` collapses to `/v1/` and `/v1/cache/../ttl` to `/v1/ttl`; `%2E%2E` is collapsed the same way by the server's WHATWG parse. No wire form reaches the validator."
},
{
"key": "health",
"encoded": null,
"decoded": null,
"reject": true,
"note": "Route token: `/v1/cache/health` is the health endpoint, so a GET for this key would return the health payload as a cache hit."
},
{
"key": "ttl",
"encoded": null,
"decoded": null,
"reject": true,
"note": "Route token: a final `ttl` segment selects the TTL sub-resource, so `/v1/cache/ttl` is read as an empty key plus `/ttl`."
},
{
"key": "lock",
"encoded": null,
"decoded": null,
"reject": true,
"note": "Route token: a final `lock` segment selects the lock sub-resource, so `/v1/cache/lock` is read as an empty key plus `/lock`."
},
{
"key": "a:..",
"encoded": "a%3A..",
"decoded": "a:..",
"note": "Trailing dots but NOT an all-dot segment: not a dot segment under either parsing model, so the dots stay raw. Server rejects the decoded `..` substring."
},
{
"key": "..a",
"encoded": "..a",
"decoded": "..a",
"note": "Leading dots, not an all-dot segment: sent verbatim (all characters unreserved). Server rejects the decoded `..` substring."
},
{
"key": "ns:key",
"encoded": "ns%3Akey",
"decoded": "ns:key",
"note": "`:` → `%3A`, decoded once server-side. The server then rejects it: a key starting `ns:` must be `ns:{namespace}:{rest}` with a non-empty `{rest}` (spec/cache-key-format.md § Server-Side Requirements, Namespace), and this one has no `{rest}`."
},
{
"key": "f(x)!*'",
"encoded": "f%28x%29%21%2A%27",
"decoded": "f(x)!*'",
"encoded_alternates": ["f(x)!*'"],
"note": "Encoder-variance row (spec rule 4): `quote(safe=\"\")` and `urlencoding::encode` produce `encoded`; `encodeURIComponent` produces the alternate. Both decode to `key`. Server rejects the decoded key (charset)."
}
]
}