diff --git a/src/core/facts/meta-hook.ts b/src/core/facts/meta-hook.ts index 07ba339d2..4fb065285 100644 --- a/src/core/facts/meta-hook.ts +++ b/src/core/facts/meta-hook.ts @@ -51,7 +51,13 @@ export async function getBrainHotMemoryMeta( const sessionId = (ctx as { source_session?: string }).source_session ?? null; const allowListHash = hashAllowList(ctx.takesHoldersAllowList); - const cacheKey = `${sourceId}::${sessionId ?? '_'}::${allowListHash}`; + // Visibility tier: untrusted remote → world-only; trusted local + + // owner-trusted reads → all rows. Folded into the cache key (the header's + // "cache entries don't bleed across tiers" invariant): trustedFactReads is + // re-read from config per call, so a mid-session opt-out must not keep + // serving a private-inclusive cached payload for the TTL window. + const visibility = readableFactVisibilities(ctx); + const cacheKey = `${sourceId}::${sessionId ?? '_'}::${allowListHash}::${visibility ? 'world' : 'all'}`; const ttl = Math.max(1000, opts.ttlMs ?? DEFAULT_TTL_MS); const topK = Math.max(1, Math.min(opts.topK ?? DEFAULT_TOP_K, 25)); @@ -62,10 +68,6 @@ export async function getBrainHotMemoryMeta( return cached.payload; } - // Build a fresh payload. Visibility tier: untrusted remote → world-only; - // trusted local + owner-trusted reads → all rows. - const visibility = readableFactVisibilities(ctx); - let rows: FactRow[] = []; if (sessionId) { rows = await ctx.engine.listFactsBySession(sourceId, sessionId, {