Expand description
AnswerCacheKey — pure key derivation and TTL policy for the ASK
answer cache.
Issue #403 (PRD #391): an opt-in answer cache lets ASK skip the LLM
when the same question lands against the same data under the same
determinism knobs. The cache is keyed by
hash(tenant, user_scope, question, provider, model, temperature, seed, sources_fingerprint) and gated by per-query CACHE TTL '5m'
/ NOCACHE clauses on top of deployment defaults.
Deep module: no I/O, no clock, no storage. The caller hands in the
identity scope, the determinism-resolved request shape (Applied
from #400 in real wiring, plain fields here so the module stays
decoupled), and the source fingerprint that retrieval (#398) already
computes. We return a stable lowercase-hex SHA-256 key and, given
Mode + Settings, an effective TTL.
§Why the module owns these decisions
The cache key is a security boundary: cross-tenant key collisions
leak answers. Pinning the canonical form here — with tests around
the per-tenant scope, around Some(0) vs None seed, around
temperature float canonicalisation — keeps the key derivation in
one place a reviewer can audit. The wiring slice that follows can
treat the key as an opaque string.
§Key canonical form
Fields are concatenated in fixed order with the ASCII Unit Separator (0x1f) as delimiter:
tenant | 0x1f | user | 0x1f | question | 0x1f | provider | 0x1f
| model | 0x1f | temperature | 0x1f | seed | 0x1f | fingerprinttemperatureserializes as"none"when absent, otherwise as the shortest round-tripping IEEE-754 representation produced by Rust’s{}formatter (0,0.5, etc.).0andnoneare distinct.seedserializes as"none"when absent, otherwise as the decimalu64.0andnoneare distinct (guards against the same kind ofunwrap_or(0)regressionDeterminismDecideralready pins).0x1fcannot appear in any of the inputs (SQL parser rejects it in strings; the fingerprint, provider, model, decimals, and hex are all ASCII printable), so the concatenation is injective without escaping. Same trick assuper::determinism_decider::derive_seed.
Structs§
- Inputs
- All inputs that determine which answer a given call would receive.
Re-evaluating against a changed
temperature,seed,model, orsources_fingerprintmust miss the cache, so each appears verbatim in the key. - Scope
- Identity scope.
tenantis mandatory;useris empty when the cache should be tenant-wide. Anonymous / embedded callers with no auth context pass empty strings for both. - Settings
- Deployment-level cache settings, surfaced via
ask.cache.*.
Enums§
- Decision
- What the cache wrapper should do for a single ASK call.
- Mode
- Per-query
CACHE TTL '...'/NOCACHEclause, parsed from the SQL surface. Default-constructedModeisMode::Default, which means “fall back to settings”. - TtlParse
Error - Why
parse_ttlrejected a literal. Named variants so the runtime can map each to a deterministic error message without a stringly typed switch.
Functions§
- decide
- Combine the per-query
Modewith deploymentSettingsto get the effective behaviour for this call. - derive_
key - Derive the lowercase-hex SHA-256 cache key for one ASK call.
- parse_
ttl - Parse a TTL literal from
CACHE TTL '<lit>'.