mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-28 06:23:01 +00:00
Salvage of PR #2875 (which subsumes the base projection from #2873), rebased onto current master with the release bookkeeping (VERSION / package.json / CHANGELOG) dropped, plus one hot-path hardening fix. Salvaged (verified on this head): - project trusted message_id / thread_id / Message-ID-gated source_subject through keyword, chunk-keyword, CJK, and vector paths in BOTH engines - preserve the citation DTO through alias injection, relational recall/fanout, two-pass hydration, vector fusion/reranking, and semantic-cache hits - raw source_subject is never trusted; only allowlisted `subject` may supply it, and only when a nonblank Message-ID proves the page is an email; malformed/non-object frontmatter fails closed (no double-decode) - source visibility / quarantine / deletion rechecked across indirect retrieval paths (alias hop, relational hydrate, two-pass expansion, graph walk, cache-hit gate) - typed JSON cache scope keys (scalar/set/all) — injective encoding, no forged-key collisions; store-side write gate skips writeback when the page-generation clock advanced during the producing search - KNOBS_HASH_VERSION 12 -> 13 so pre-projection cached DTOs miss instead of replaying the old shape Fixed on top of the original head (the flagged hot-path defect): - cacheScopeKey's forged-id rejection was evaluated inline at the cache lookup/store call sites inside hybridSearchCached, outside any catch — an invalid scope id broke the whole search instead of skipping the cache. The key is now computed once, fail-open: invalid scope => cache skipped, search unaffected. Pinned by test/search/hybrid-cache-scope-failopen.serial.test.ts (fails on the original head, passes here). Verified on this exact head: typecheck clean; 379 touched unit tests, 3 serial files (one process each), pglite cache-gate/source-isolation e2e, and real-PostgreSQL engine-parity (26 pass) + source-routing — all green. jsonb-pattern/params, key-files-current-state, test-isolation, progress-to-stdout guards clean. Closes #2962 Co-authored-by: amtagrwl <amtagrwl@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
83 lines
3.5 KiB
TypeScript
83 lines
3.5 KiB
TypeScript
import { describe, test, expect } from 'bun:test';
|
|
import {
|
|
QUARANTINE_KEY,
|
|
QUARANTINE_FILTER_FRAGMENT,
|
|
quarantineFilterFragment,
|
|
buildQuarantineMarker,
|
|
isQuarantined,
|
|
filterOutQuarantined,
|
|
CONTENT_FLAG_KEY,
|
|
buildContentFlagMarker,
|
|
getContentFlag,
|
|
hasContentFlag,
|
|
} from '../src/core/quarantine.ts';
|
|
|
|
describe('quarantine marker (hides)', () => {
|
|
test('buildQuarantineMarker shape', () => {
|
|
const m = buildQuarantineMarker('junk_pattern', 'cloudflare_ray_id', { bytes: 1234, now: new Date('2026-06-01T00:00:00Z') });
|
|
expect(m.reason).toBe('junk_pattern');
|
|
expect(m.detail).toBe('cloudflare_ray_id');
|
|
expect(m.bytes).toBe(1234);
|
|
expect(m.assessed_at).toBe('2026-06-01T00:00:00.000Z');
|
|
});
|
|
|
|
test('isQuarantined: true only when key present + non-null', () => {
|
|
expect(isQuarantined({ [QUARANTINE_KEY]: { reason: 'junk_pattern' } })).toBe(true);
|
|
expect(isQuarantined({})).toBe(false);
|
|
expect(isQuarantined(null)).toBe(false);
|
|
expect(isQuarantined(undefined)).toBe(false);
|
|
expect(isQuarantined({ [QUARANTINE_KEY]: null })).toBe(false);
|
|
});
|
|
|
|
test('filterOutQuarantined drops quarantined pages', () => {
|
|
const pages = [
|
|
{ slug: 'a', frontmatter: {} },
|
|
{ slug: 'b', frontmatter: { [QUARANTINE_KEY]: { reason: 'junk_pattern' } } },
|
|
{ slug: 'c', frontmatter: null },
|
|
];
|
|
expect(filterOutQuarantined(pages).map((p) => p.slug)).toEqual(['a', 'c']);
|
|
});
|
|
|
|
test('QUARANTINE_FILTER_FRAGMENT requires object-shaped JSONB and excludes the marker', () => {
|
|
expect(QUARANTINE_FILTER_FRAGMENT).toContain('p.frontmatter');
|
|
expect(QUARANTINE_FILTER_FRAGMENT).toContain("jsonb_typeof");
|
|
expect(QUARANTINE_FILTER_FRAGMENT).toContain("= 'object'");
|
|
expect(QUARANTINE_FILTER_FRAGMENT).toContain("? 'quarantine'");
|
|
expect(QUARANTINE_FILTER_FRAGMENT).toContain('AND NOT');
|
|
});
|
|
|
|
test('quarantineFilterFragment(alias) parameterizes the page alias; constant is the p-instance', () => {
|
|
expect(quarantineFilterFragment('p')).toBe(QUARANTINE_FILTER_FRAGMENT);
|
|
expect(quarantineFilterFragment('xx')).toContain('xx.frontmatter');
|
|
expect(quarantineFilterFragment('xx')).toContain("? 'quarantine'");
|
|
});
|
|
});
|
|
|
|
describe('content_flag marker (warns, does NOT hide)', () => {
|
|
test('buildContentFlagMarker shape (markup_heavy)', () => {
|
|
const m = buildContentFlagMarker('markup_heavy', 'ratio 0.91', { markup_ratio: 0.91, now: new Date('2026-06-01T00:00:00Z') });
|
|
expect(m.reason).toBe('markup_heavy');
|
|
expect(m.markup_ratio).toBe(0.91);
|
|
expect(m.bytes).toBeUndefined();
|
|
});
|
|
|
|
test('getContentFlag returns reason+detail or null', () => {
|
|
expect(getContentFlag({ [CONTENT_FLAG_KEY]: { reason: 'oversized', detail: 'big' } })).toEqual({ reason: 'oversized', detail: 'big' });
|
|
expect(getContentFlag({ [CONTENT_FLAG_KEY]: { detail: 'no reason' } })).toBeNull();
|
|
expect(getContentFlag({})).toBeNull();
|
|
expect(getContentFlag(null)).toBeNull();
|
|
});
|
|
|
|
test('hasContentFlag mirrors getContentFlag presence', () => {
|
|
expect(hasContentFlag({ [CONTENT_FLAG_KEY]: { reason: 'markup_heavy' } })).toBe(true);
|
|
expect(hasContentFlag({})).toBe(false);
|
|
});
|
|
|
|
test('there is deliberately NO content_flag SQL filter fragment exported', async () => {
|
|
// Flagged pages stay searchable by design — the module must not export a
|
|
// QUARANTINE_FILTER_FRAGMENT analog for content_flag.
|
|
const mod = await import('../src/core/quarantine.ts');
|
|
expect('CONTENT_FLAG_FILTER_FRAGMENT' in mod).toBe(false);
|
|
});
|
|
});
|