p2.6: partition dcache by caller class; pin restricted workspace

Digest, chunk-embed, and question-embed keys include fleet|restricted so
a fleet caller cannot observe a restricted cache hit. Digest.Run takes
the class from permit_restricted.

A permit_restricted caller whose keys.toml workspace is not
guardian-star is refused at Search (no Honcho request) and flagged at
serve start and keys reload.

docs: P3 restricted callers search like the fleet; cache/logs isolate.
This commit is contained in:
2026-09-23 11:51:39 -07:00
parent 7be4ed1d43
commit 3687636ed7
12 changed files with 202 additions and 67 deletions
+14 -8
View File
@@ -78,13 +78,15 @@ Out: `search` (P3), URL→content fetch cache (P4), knowledge graph. (Digest + c
Content-addressed, SQLite in `/var/lib/sift/cache.db` (`modernc.org/sqlite`), so there is no
staleness problem: a changed page has a different hash.
1. **Digest cache** — key `sha256(content_hash | normalised_question | model | prompt_version)`
→ digest text + `chunks_used`. Normalise the question: trim, collapse whitespace,
lowercase. Hit → no Dixie call at all (`digest.cached: true`). Bump `prompt_version` whenever
the prompt or chunking changes.
2. **Chunk-embedding cache** — key `(content_hash, chunk_index, embed_model)` → vector
1. **Digest cache** — key `sha256(content_hash | normalised_question | model | prompt_version | caller_class)`
→ digest text + `chunks_used`. `caller_class` is `fleet` or `restricted` (P2.6): a fleet
caller must never hit a restricted entry (`cached: true` would leak that the query was
asked). Normalise the question: trim, collapse whitespace, lowercase. Hit → no Dixie
call at all (`digest.cached: true`). Bump `prompt_version` whenever the prompt or
chunking changes.
2. **Chunk-embedding cache** — key `(content_hash, chunk_index, embed_model, caller_class)` → vector
(float32 blob). A second question on the same long page skips re-embedding. Also cache
the question embedding by normalised question (short TTL not needed; it's content-addressed).
the question embedding by normalised question **and caller class**.
Rules: **Honcho context is never cached** (per-workspace, changes constantly) — fetch it
fresh and attach to a cached digest; nothing in the cache is workspace-specific. Size-bound
@@ -93,7 +95,8 @@ failures are warnings, never errors. `/metrics`: hit/miss per cache, evictions,
The URL → cleaned-content **fetch cache stays in P4** (it needs TTL/staleness rules).
Tests: hit/miss on identical vs changed content; normalisation; prompt_version bump
invalidates; context not stored; eviction under size cap; corrupt DB → warn and bypass.
invalidates; caller_class partitions fleet vs restricted; context not stored; eviction
under size cap; corrupt DB → warn and bypass.
## 3. Context (`internal/context`, Honcho retrieval only)
@@ -104,7 +107,10 @@ invalidates; context not stored; eviction under size cap; corrupt DB → warn an
(`context.workspaces`); anything else → no context + WARN. **Restricted workspace
`guardian-star`**: allowed only when the authenticated caller is in
`context.permit_restricted` *and* that caller's mapped workspace is `guardian-star`.
A fleet caller remapped to it is still refused (tested). Workspace is never taken
A fleet caller remapped to it is still refused (tested). **P2.6 reverse:** a
`permit_restricted` caller whose mapped workspace is not `guardian-star` is
refused (ERROR log, no Honcho request, never fall back to `hermes-fleet`).
Checked at Search, at serve start, and on keys reload. Workspace is never taken
from the request.
- Query: the question if given, else title + top 3 headings.
- Return trimmed items: `{peer, content (≤300 chars), created_at, session}`; label clearly