Skip to main content

Module answer_cache_key

Module answer_cache_key 

Source
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 | fingerprint
  • temperature serializes as "none" when absent, otherwise as the shortest round-tripping IEEE-754 representation produced by Rust’s {} formatter (0, 0.5, etc.). 0 and none are distinct.
  • seed serializes as "none" when absent, otherwise as the decimal u64. 0 and none are distinct (guards against the same kind of unwrap_or(0) regression DeterminismDecider already pins).
  • 0x1f cannot 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 as super::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, or sources_fingerprint must miss the cache, so each appears verbatim in the key.
Scope
Identity scope. tenant is mandatory; user is 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 '...' / NOCACHE clause, parsed from the SQL surface. Default-constructed Mode is Mode::Default, which means “fall back to settings”.
TtlParseError
Why parse_ttl rejected 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 Mode with deployment Settings to 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>'.