query: filter since/until on effective date, not updated_at (#1706)

since/until range filters were applied against updated_at, so edited-but-old
entries leaked into time-bounded queries. Filter on the effective date instead.

Closes #1520

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
This commit is contained in:
Matt Van Horn
2026-07-16 20:47:38 -07:00
committed by GitHub
co-authored by Matt Van Horn
parent 414940204a
commit d33aee843b
2 changed files with 27 additions and 9 deletions
+9 -9
View File
@@ -1614,16 +1614,16 @@ export class PostgresEngine implements BrainEngine {
params.push(symbolKind);
symbolKindClause = `AND cc.symbol_type = $${params.length}`;
}
// v0.27.0: date filtering support
// v0.29.1: since/until filter by effective date, with import-time fallback.
let afterDateClause = '';
if (opts?.afterDate) {
params.push(opts.afterDate);
afterDateClause = `AND COALESCE(p.updated_at, p.created_at) > $${params.length}::timestamptz`;
afterDateClause = `AND COALESCE(p.effective_date, p.updated_at, p.created_at) > $${params.length}::timestamptz`;
}
let beforeDateClause = '';
if (opts?.beforeDate) {
params.push(opts.beforeDate);
beforeDateClause = `AND COALESCE(p.updated_at, p.created_at) < $${params.length}::timestamptz`;
beforeDateClause = `AND COALESCE(p.effective_date, p.updated_at, p.created_at) < $${params.length}::timestamptz`;
}
// v0.34.1 (#861 — P0 leak seal): source-isolation filter. When the
// caller's auth scope is set, narrow the inner CTE candidate set so
@@ -1763,16 +1763,16 @@ export class PostgresEngine implements BrainEngine {
params.push(symbolKind);
symbolKindClause = `AND cc.symbol_type = $${params.length}`;
}
// v0.27.0: date filtering support
// v0.29.1: since/until filter by effective date, with import-time fallback.
let afterDateClause = '';
if (opts?.afterDate) {
params.push(opts.afterDate);
afterDateClause = `AND COALESCE(p.updated_at, p.created_at) > $${params.length}::timestamptz`;
afterDateClause = `AND COALESCE(p.effective_date, p.updated_at, p.created_at) > $${params.length}::timestamptz`;
}
let beforeDateClause = '';
if (opts?.beforeDate) {
params.push(opts.beforeDate);
beforeDateClause = `AND COALESCE(p.updated_at, p.created_at) < $${params.length}::timestamptz`;
beforeDateClause = `AND COALESCE(p.effective_date, p.updated_at, p.created_at) < $${params.length}::timestamptz`;
}
// v0.34.1 (#861 — P0 leak seal): source-isolation. Anchor primitive
// for two-pass retrieval, so cross-source anchors would let the walk
@@ -1885,16 +1885,16 @@ export class PostgresEngine implements BrainEngine {
params.push(symbolKind);
symbolKindClause = `AND cc.symbol_type = $${params.length}`;
}
// v0.27.0: date filtering support
// v0.29.1: since/until filter by effective date, with import-time fallback.
let afterDateClause = '';
if (opts?.afterDate) {
params.push(opts.afterDate);
afterDateClause = `AND COALESCE(p.updated_at, p.created_at) > $${params.length}::timestamptz`;
afterDateClause = `AND COALESCE(p.effective_date, p.updated_at, p.created_at) > $${params.length}::timestamptz`;
}
let beforeDateClause = '';
if (opts?.beforeDate) {
params.push(opts.beforeDate);
beforeDateClause = `AND COALESCE(p.updated_at, p.created_at) < $${params.length}::timestamptz`;
beforeDateClause = `AND COALESCE(p.effective_date, p.updated_at, p.created_at) < $${params.length}::timestamptz`;
}
// v0.34.1 (#861, F2 — P0 leak seal): source-isolation in the INNER CTE
// specifically. Pushing the filter inside narrows the HNSW candidate set
+18
View File
@@ -94,12 +94,30 @@ describe('postgres-engine / search path timeout isolation', () => {
});
});
describe('postgres-engine / search date filtering', () => {
test('search paths filter since/until on effective_date before import-time fallback', () => {
const expectedDateExpr = 'COALESCE(p.effective_date, p.updated_at, p.created_at)';
const staleDatePredicate = /COALESCE\(p\.updated_at,\s*p\.created_at\)\s*[<>]\s*\$/;
for (const methodName of ['searchKeyword', 'searchKeywordChunks', 'searchVector']) {
const fn = stripComments(extractMethod(SRC, methodName));
expect(countOccurrences(fn, expectedDateExpr)).toBe(2);
expect(fn).not.toMatch(staleDatePredicate);
}
});
});
function stripComments(s: string): string {
return s
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/(^|\s)\/\/[^\n]*/g, '$1');
}
function countOccurrences(s: string, needle: string): number {
return s.split(needle).length - 1;
}
// extractMethod grabs the body of a class method by brace-matching from
// its opening line. Returns the method body up to the matching closing
// brace. Good enough for the small number of methods in this file.