Files
gbrain/test/quarantine.test.ts
3381dd7658 fix(search): preserve email citation metadata across result paths (takeover of #2875)
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>
2026-07-21 15:07:56 -07:00

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);
});
});