mirror of
https://github.com/garrytan/gbrain.git
synced 2026-08-14 00:48:18 +00:00
Wave-assembled from PR #3544 by @time-attack. Co-Authored-By: Time Attakc <89218912+time-attack@users.noreply.github.com>
This commit is contained in:
committed by
Sina Matian
co-authored by
Time Attakc
parent
189bf856ee
commit
5bd2c51053
@@ -1841,6 +1841,13 @@ export async function hybridSearchCached(
|
||||
// resolves) into the cache key so a row written under one exclude
|
||||
// policy can't be served to a lookup under another.
|
||||
hardExcludes: resolveHardExcludes(opts?.exclude_slug_prefixes, opts?.include_slug_prefixes),
|
||||
// #3515 — fold the EFFECTIVE detail level into the cache key. detail
|
||||
// gates dedup, chunk-source filtering, and the compiled_truth boost, so
|
||||
// a `--detail low` write (compiled-truth-only result set) must never be
|
||||
// served to a default `medium` lookup. Resolve auto-detect the same way
|
||||
// bare hybridSearch does (opts.detail ?? autoDetectDetail(query)) so an
|
||||
// auto-detected `high` query keys like an explicit `high` one.
|
||||
detail: opts?.detail ?? autoDetectDetail(query),
|
||||
});
|
||||
|
||||
// Cache decision: opts.useCache (explicit) wins over global config; global
|
||||
|
||||
+28
-1
@@ -779,7 +779,18 @@ export function attributeKnob<K extends keyof ModeBundle>(
|
||||
// to cache.ttl_seconds, with no warning and no way for an operator to tell.
|
||||
// Same one-time global cold-miss pattern as the bumps above; refills within
|
||||
// cache.ttl_seconds (3600s default).
|
||||
export const KNOBS_HASH_VERSION = 15;
|
||||
//
|
||||
// bump 15→16 (#3515): `detail` folds into the key via ctx.detail (det=).
|
||||
// detail is result-affecting by design — it gates dedup, chunk-source
|
||||
// filtering, and the compiled_truth boost — but was absent from the key, so
|
||||
// a `--detail low` write (compiled-truth-only result set) was served to a
|
||||
// default `medium` lookup for the whole TTL. Same contamination class as
|
||||
// [CDX-4], floor_ratio (v=3), and relationalRetrieval (v=10). v=14 was
|
||||
// claimed by #3514 (compiled_truth boost scope, #3430) and v=15 by the
|
||||
// `fts=` fold (#3677), so this lands as v=16 per the D8 sequencing
|
||||
// convention (see the v=4/v=5 note above). Same one-time global cold-miss
|
||||
// pattern as the bumps above.
|
||||
export const KNOBS_HASH_VERSION = 16;
|
||||
|
||||
/**
|
||||
* v0.36 (D8 / CDX-2) — second-arg context for the cache key. The
|
||||
@@ -818,6 +829,17 @@ export interface KnobsHashContext {
|
||||
* 'none' for legacy callers that don't thread excludes.
|
||||
*/
|
||||
hardExcludes?: string[];
|
||||
/**
|
||||
* v=16 (#3515): the EFFECTIVE detail level for this call — per-call
|
||||
* SearchOpts.detail, or the auto-detected level when the caller didn't
|
||||
* specify (hybridSearchCached threads `opts.detail ?? autoDetectDetail(query)`,
|
||||
* matching what bare hybridSearch resolves). detail gates dedup,
|
||||
* chunk-source filtering, and the compiled_truth boost, so a detail=low
|
||||
* write must never be served to a detail=medium lookup. Lives in ctx (not
|
||||
* ResolvedSearchKnobs) because it's per-call, not a mode knob — same path
|
||||
* as col=/prov=. Undefined falls back to 'medium' (the documented default).
|
||||
*/
|
||||
detail?: 'low' | 'medium' | 'high';
|
||||
}
|
||||
|
||||
export function knobsHash(
|
||||
@@ -921,6 +943,11 @@ export function knobsHash(
|
||||
// memoizes and validates against /^[a-z][a-z0-9_]*$/, so this stays a
|
||||
// cheap, bounded string.
|
||||
`fts=${getFtsLanguage()}`,
|
||||
// v=16 addition (#3515, append-only): effective detail level. detail
|
||||
// gates dedup, chunk-source filtering, and the compiled_truth boost, so
|
||||
// a low write (compiled-truth-only set) must never be served to a
|
||||
// medium/high lookup. Undefined falls back to 'medium' (the default).
|
||||
`det=${ctx?.detail ?? 'medium'}`,
|
||||
];
|
||||
const h = createHash('sha256');
|
||||
h.update(parts.join('|'));
|
||||
|
||||
@@ -136,7 +136,7 @@ describe('D2 — knobsHash differs across cross-modal knob values', () => {
|
||||
return resolveSearchMode({ mode: 'balanced' });
|
||||
}
|
||||
|
||||
test('KNOBS_HASH_VERSION is 15 (cross-modal still appended; 14→15 FTS language fold)', () => {
|
||||
test('KNOBS_HASH_VERSION is 16 (cross-modal still appended; 15→16 detail fold #3515)', () => {
|
||||
// v0.35 ladder: 1→2 reranker, 2→3 floor_ratio. v0.36 piggybacks on v=3
|
||||
// with 7 cross-modal knobs + column/provider context. v0.40.4 (salem) +
|
||||
// v0.39 T21 (master) bump to v=4 for graph_signals + schema-pack fields.
|
||||
@@ -149,7 +149,8 @@ describe('D2 — knobsHash differs across cross-modal knob values', () => {
|
||||
// #3430: 13→14 compiled_truth boost no longer applies at detail=medium.
|
||||
// 14→15: the resolved FTS configuration name (fts=) — a language switch
|
||||
// plus `reindex-search-vector` must not keep serving pre-switch rows.
|
||||
expect(KNOBS_HASH_VERSION).toBe(15);
|
||||
// #3515: 15→16 detail fold (det=).
|
||||
expect(KNOBS_HASH_VERSION).toBe(16);
|
||||
});
|
||||
|
||||
test('flipping unified_multimodal changes the hash', () => {
|
||||
|
||||
@@ -301,6 +301,41 @@ describe('hard-exclude cache isolation (#2825)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('detail cache isolation (#3515)', () => {
|
||||
// Hashes computed the way hybridSearchCached does: same resolved mode, ctx
|
||||
// carrying the effective detail level. A row written by a `--detail low`
|
||||
// call (compiled-truth-only result set) must not be served to a default
|
||||
// `medium` lookup, and vice versa.
|
||||
const lowHash = knobsHash(resolveSearchMode({ mode: 'balanced' }), { detail: 'low' });
|
||||
const mediumHash = knobsHash(resolveSearchMode({ mode: 'balanced' }), { detail: 'medium' });
|
||||
const unsetHash = knobsHash(resolveSearchMode({ mode: 'balanced' }));
|
||||
|
||||
test('detail=low write is NOT served to a default (medium) lookup', async () => {
|
||||
const cache = new SemanticQueryCache(engine);
|
||||
const emb = makeEmbedding(8);
|
||||
|
||||
// Simulate `query "X" --detail low` populating the cache with the
|
||||
// narrow compiled-truth-only result set.
|
||||
await cache.store('what is the deploy process', emb, makeResults('narrow', 2), {
|
||||
vector_enabled: true, detail_resolved: 'low', expansion_applied: false,
|
||||
}, { knobsHash: lowHash });
|
||||
|
||||
// Default-detail lookup inside the TTL → MISS (falls through to a
|
||||
// fresh, full search) instead of the narrow set.
|
||||
expect((await cache.lookup(emb, { knobsHash: mediumHash })).hit).toBe(false);
|
||||
|
||||
// The low-detail caller still hits its own row.
|
||||
const original = await cache.lookup(emb, { knobsHash: lowHash });
|
||||
expect(original.hit).toBe(true);
|
||||
expect(original.results?.length).toBe(2);
|
||||
});
|
||||
|
||||
test('undefined detail keys like the documented medium default', () => {
|
||||
expect(unsetHash).toBe(mediumHash);
|
||||
expect(unsetHash).not.toBe(lowHash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('FTS language cache isolation', () => {
|
||||
// GBRAIN_FTS_LANGUAGE retokenizes BOTH sides of the keyword arm (the
|
||||
// trigger-built search_vector and the query-side websearch_to_tsquery), so
|
||||
|
||||
@@ -89,7 +89,7 @@ describe('alias_resolved boost stage', () => {
|
||||
});
|
||||
|
||||
describe('KNOBS_HASH_VERSION', () => {
|
||||
it('is 15 (14→15 folds the resolved FTS configuration name, so rows written before a reindex-search-vector language switch become unreachable)', () => {
|
||||
expect(KNOBS_HASH_VERSION).toBe(15);
|
||||
it('is 16 (15→16 detail fold makes detail-contaminated rows unreachable, #3515)', () => {
|
||||
expect(KNOBS_HASH_VERSION).toBe(16);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -420,7 +420,23 @@ describe('knobsHash determinism + cross-mode separation (CDX-4)', () => {
|
||||
// GBRAIN_FTS_LANGUAGE retokenizes both the trigger-built search_vector and
|
||||
// the query-side tsquery, so rows written under the previous language must
|
||||
// not survive a `reindex-search-vector` switch.
|
||||
expect(KNOBS_HASH_VERSION).toBe(15);
|
||||
// #3515: bumped 15→16 to fold the effective detail level (det=) — a
|
||||
// detail=low write must not be served to a detail=medium lookup.
|
||||
expect(KNOBS_HASH_VERSION).toBe(16);
|
||||
});
|
||||
|
||||
test('#3515: detail set vs unset produces DIFFERENT hashes (cache contamination prevention)', () => {
|
||||
const knobs = resolveSearchMode({ mode: 'balanced' });
|
||||
const low = knobsHash(knobs, { detail: 'low' });
|
||||
const medium = knobsHash(knobs, { detail: 'medium' });
|
||||
const high = knobsHash(knobs, { detail: 'high' });
|
||||
const unset = knobsHash(knobs);
|
||||
expect(low).not.toBe(medium);
|
||||
expect(medium).not.toBe(high);
|
||||
expect(low).not.toBe(high);
|
||||
// Undefined falls back to 'medium' — the documented default — so legacy
|
||||
// callers that don't thread detail share the default-detail rows.
|
||||
expect(unset).toBe(medium);
|
||||
});
|
||||
|
||||
test('T1 (codex): floor_ratio set vs unset produces DIFFERENT hashes (cache contamination prevention)', () => {
|
||||
@@ -585,8 +601,8 @@ describe('v0.40.4 — graph_signals knob', () => {
|
||||
});
|
||||
|
||||
describe('v0.42.3.0 — autocut knobs', () => {
|
||||
test('KNOBS_HASH_VERSION is 15 (14→15 FTS language fold)', () => {
|
||||
expect(KNOBS_HASH_VERSION).toBe(15);
|
||||
test('KNOBS_HASH_VERSION is 16 (15→16 detail fold #3515)', () => {
|
||||
expect(KNOBS_HASH_VERSION).toBe(16);
|
||||
});
|
||||
|
||||
test('bundle defaults: conservative off, balanced/tokenmax on @0.20', () => {
|
||||
|
||||
@@ -44,7 +44,7 @@ function baseKnobs(): ResolvedSearchKnobs {
|
||||
}
|
||||
|
||||
describe('KNOBS_HASH_VERSION + version invariants', () => {
|
||||
test('version is 15 (…; 11→12 hard-excludes #2825; 12→13 embedding-provider migration #3390; 14→15 FTS language)', () => {
|
||||
test('version is 16 (…; 12→13 embedding-provider migration #3390; 14→15 FTS language; 15→16 detail fold #3515)', () => {
|
||||
// v0.35.0.0: 1→2 to fold reranker fields. v0.35.6.0: 2→3 to fold
|
||||
// floor_ratio. v0.36 wave: piggybacks on v=3 with 7 cross-modal knobs
|
||||
// (D2) PLUS column + provider context (D8/CDX-2 cross-column isolation).
|
||||
@@ -71,7 +71,9 @@ describe('KNOBS_HASH_VERSION + version invariants', () => {
|
||||
// name (fts=). It retokenizes both the trigger-built search_vector and
|
||||
// the query-side tsquery, so rows written under the previous language
|
||||
// must not survive a `reindex-search-vector` language switch.
|
||||
expect(KNOBS_HASH_VERSION).toBe(15);
|
||||
// #3515: 15→16 to fold the effective detail level (det=) — a detail=low
|
||||
// write must not be served to a detail=medium lookup.
|
||||
expect(KNOBS_HASH_VERSION).toBe(16);
|
||||
});
|
||||
|
||||
test('hash is 16 hex chars regardless of reranker config', () => {
|
||||
|
||||
Reference in New Issue
Block a user