Files
gbrain/test/search-alias-resolved-boost.test.ts
Garry TanandClaude Opus 5 683b7665f2 fix(search): fold detail into the query-cache key (#3515)
`detail` is result-affecting by design — it gates dedup, chunk-source
filtering, and the compiled_truth boost — but was absent from knobsHash,
the only thing that varies the query-cache key. A `--detail low` write
(compiled-truth-only result set) was served to a default `medium` lookup
for the whole TTL (3600s), silently, looking like a relevance problem.
Same contamination class as [CDX-4], the v=2→3 floor_ratio/col/prov
additions, and the v=9→10 relationalRetrieval fold.

Fix: append `det=` to the knobsHash parts list (append-only, per the
list's own convention) and bump KNOBS_HASH_VERSION 13→15. detail is a
per-call SearchOpts value, not a mode knob, so it threads through
KnobsHashContext the way col=/prov= do; hybridSearchCached passes the
EFFECTIVE level (opts.detail ?? autoDetectDetail(query)) so an
auto-detected `high` query keys like an explicit `high` one. Undefined
falls back to 'medium' (the documented default).

v=14 is claimed by in-flight PR #3514 (#3430); this lands as v=15 per
the established D8 sequencing convention. All five KNOBS_HASH_VERSION
pin sites updated. One-time cache cold-miss on upgrade, refills within
cache.ttl_seconds — same as every prior bump.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 13:31:55 -07:00

96 lines
3.8 KiB
TypeScript

// v0.42 Type Unification (T32) — alias_resolved search boost stage.
//
// Coverage: pages whose slug is a canonical_slug in slug_aliases get 1.05x
// score multiplier; non-alias-canonical pages unchanged; stage stamps
// alias_resolved_boost field for --explain; KNOBS_HASH_VERSION bumped.
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'bun:test';
import { PGLiteEngine } from '../src/core/pglite-engine.ts';
import { resetPgliteState } from './helpers/reset-pglite.ts';
import { runPostFusionStages, type PostFusionOpts } from '../src/core/search/hybrid.ts';
import { KNOBS_HASH_VERSION } from '../src/core/search/mode.ts';
import type { SearchResult } from '../src/core/types.ts';
let engine: PGLiteEngine;
beforeAll(async () => {
engine = new PGLiteEngine();
await engine.connect({});
await engine.initSchema();
});
afterAll(async () => {
await engine.disconnect();
});
beforeEach(async () => {
await resetPgliteState(engine);
});
const noopPostFusionOpts: PostFusionOpts = {
applyBacklinks: false,
salience: 'off',
recency: 'off',
graphSignalsEnabled: false,
};
describe('alias_resolved boost stage', () => {
it('applies 1.05x multiplier to pages that are canonicals of aliases', async () => {
// Insert an alias pointing at canonical-page
await engine.executeRaw(
`INSERT INTO slug_aliases (source_id, alias_slug, canonical_slug) VALUES ('default', 'old-name', 'canonical-page')`,
);
const results: SearchResult[] = [
{
slug: 'canonical-page', source_id: 'default', score: 1.0,
chunk_id: 1, page_id: 1, chunk_text: '', chunk_index: 0,
title: 'Canonical', type: 'concept' as never, slug_lower: 'canonical-page',
} as unknown as SearchResult,
{
slug: 'plain-page', source_id: 'default', score: 1.0,
chunk_id: 2, page_id: 2, chunk_text: '', chunk_index: 0,
title: 'Plain', type: 'concept' as never, slug_lower: 'plain-page',
} as unknown as SearchResult,
];
await runPostFusionStages(engine, results, noopPostFusionOpts);
// canonical-page gets 1.05x boost
expect(results[0].score).toBeCloseTo(1.05, 5);
expect(results[0].alias_resolved_boost).toBe(1.05);
// plain-page unchanged
expect(results[1].score).toBeCloseTo(1.0, 5);
expect(results[1].alias_resolved_boost).toBeUndefined();
});
it('does not boost when no aliases exist', async () => {
const results: SearchResult[] = [{
slug: 'plain', source_id: 'default', score: 1.0,
chunk_id: 1, page_id: 1, chunk_text: '', chunk_index: 0,
title: 'p', type: 'concept' as never, slug_lower: 'plain',
} as unknown as SearchResult];
await runPostFusionStages(engine, results, noopPostFusionOpts);
expect(results[0].score).toBeCloseTo(1.0, 5);
expect(results[0].alias_resolved_boost).toBeUndefined();
});
it('is source-scoped (F9): alias in source A does not boost in source B', async () => {
await engine.executeRaw(`INSERT INTO sources (id, name) VALUES ('alt', 'alt') ON CONFLICT DO NOTHING`);
await engine.executeRaw(
`INSERT INTO slug_aliases (source_id, alias_slug, canonical_slug) VALUES ('alt', 'old', 'shared')`,
);
// Same slug, different source — should NOT be boosted (alias is in 'alt')
const results: SearchResult[] = [{
slug: 'shared', source_id: 'default', score: 1.0,
chunk_id: 1, page_id: 1, chunk_text: '', chunk_index: 0,
title: 's', type: 'concept' as never, slug_lower: 'shared',
} as unknown as SearchResult];
await runPostFusionStages(engine, results, noopPostFusionOpts);
expect(results[0].alias_resolved_boost).toBeUndefined();
});
});
describe('KNOBS_HASH_VERSION', () => {
it('is 15 (13→15 detail fold makes detail-contaminated rows unreachable, #3515; v=14 claimed by in-flight #3514)', () => {
expect(KNOBS_HASH_VERSION).toBe(15);
});
});