Files
gbrain/test/doctor-hidden-by-search-policy.test.ts
T
bea2d3e6c9 v0.42.13.0 fix(search): archive/ content findable by default, demoted not hard-excluded (#1777) (#1797)
* fix(search): archive/ findable by default — demote not hard-exclude (#1777)

Move archive/ out of DEFAULT_HARD_EXCLUDES into a 0.5 source-boost demote so
archived historical content is findable by default, ranked below curated
content. Add a hidden_by_search_policy doctor check so the surviving exclude
policy (test/, attachments/, .raw/) is auditable. Bump KNOBS_HASH_VERSION 8->9
so the policy change invalidates archive-excluded query_cache rows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: bump version and changelog (v0.42.13.0)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: correct search-exclude.test.ts annotation for archive demote (#1777)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 06:28:21 -07:00

154 lines
5.5 KiB
TypeScript

/**
* hidden_by_search_policy doctor check (issue #1777)
*
* Counts CHUNKED pages withheld from default search by the hard-exclude prefix
* policy. Verifies:
* - default-only excludes (test/) → status ok with a prescriptive message
* - a NON-default env exclude hiding pages → status warn
* - multi-chunk page counted once (DISTINCT)
* - soft-deleted / quarantined pages NOT counted (visibility mirror)
* - archive/ never appears (it's demoted, not excluded)
* - SQL error → warn, not throw
* - the check name categorizes as a brain check
*/
import { describe, test, expect, beforeAll, afterAll, beforeEach } from 'bun:test';
import { PGLiteEngine } from '../src/core/pglite-engine.ts';
import { resetPgliteState } from './helpers/reset-pglite.ts';
import { withEnv } from './helpers/with-env.ts';
import { checkHiddenBySearchPolicy } from '../src/commands/doctor.ts';
import { categorizeCheck } from '../src/core/doctor-categories.ts';
import { buildQuarantineMarker } from '../src/core/quarantine.ts';
import type { BrainEngine } from '../src/core/engine.ts';
import type { ChunkInput } from '../src/core/types.ts';
let engine: PGLiteEngine;
function basisEmbedding(idx: number, dim = 1536): Float32Array {
const emb = new Float32Array(dim);
emb[idx % dim] = 1.0;
return emb;
}
async function seed(
slug: string,
opts: { chunks?: number; frontmatter?: Record<string, unknown> } = {},
): Promise<void> {
const chunks = opts.chunks ?? 1;
await engine.putPage(slug, {
type: 'note',
title: slug,
compiled_truth: `body for ${slug}`,
timeline: '',
...(opts.frontmatter ? { frontmatter: opts.frontmatter } : {}),
});
if (chunks > 0) {
const rows: ChunkInput[] = [];
for (let i = 0; i < chunks; i++) {
rows.push({
chunk_index: i,
chunk_text: `chunk ${i} of ${slug}`,
chunk_source: 'compiled_truth',
embedding: basisEmbedding(100 + i),
token_count: 4,
});
}
await engine.upsertChunks(slug, rows);
}
}
beforeAll(async () => {
engine = new PGLiteEngine();
await engine.connect({});
await engine.initSchema();
}, 60_000);
afterAll(async () => {
await engine.disconnect();
});
beforeEach(async () => {
await resetPgliteState(engine);
});
describe('checkHiddenBySearchPolicy', () => {
test('default-only excludes → ok, prescriptive message, names test/ not archive/concepts', async () => {
await seed('test/fixtures/widget');
await seed('concepts/widget-pattern');
await seed('archive/old/widget-2020');
const r = await checkHiddenBySearchPolicy(engine);
expect(r.name).toBe('hidden_by_search_policy');
expect(r.status).toBe('ok'); // test/ is a DEFAULT exclude → not a warn
expect(r.message).toContain("under 'test/'");
expect(r.message).toContain('chunked');
expect(r.message).not.toContain('searchable'); // honest superset, not "searchable"
expect(r.message).toContain('include_slug_prefixes'); // prescriptive guidance
// archive/ is demoted, not excluded — must never surface here.
expect(r.message).not.toContain('archive/');
// concepts/ is not excluded at all.
expect(r.message).not.toContain('concepts/');
const counts = (r.details?.counts ?? {}) as Record<string, number>;
expect(counts['test/']).toBe(1);
expect(counts['archive/']).toBeUndefined();
expect(counts['concepts/']).toBeUndefined();
});
test('a NON-default env exclude hiding pages → warn', async () => {
await withEnv({ GBRAIN_SEARCH_EXCLUDE: 'scratch/' }, async () => {
await seed('scratch/notes');
await seed('concepts/keeper');
const r = await checkHiddenBySearchPolicy(engine);
expect(r.status).toBe('warn');
expect(r.message).toContain("under 'scratch/'");
});
});
test('multi-chunk page counted once (DISTINCT)', async () => {
await seed('test/multi', { chunks: 3 });
const r = await checkHiddenBySearchPolicy(engine);
const counts = (r.details?.counts ?? {}) as Record<string, number>;
expect(counts['test/']).toBe(1);
});
test('soft-deleted and quarantined pages are NOT counted', async () => {
await seed('test/soft-deleted');
await engine.softDeletePage('test/soft-deleted');
await seed('test/quarantined', {
frontmatter: { quarantine: buildQuarantineMarker('junk_pattern', 'test fixture') },
});
await seed('concepts/keeper');
const r = await checkHiddenBySearchPolicy(engine);
// Both test/ pages are hidden by the visibility clause, so 0 remain counted.
expect(r.status).toBe('ok');
expect(r.message).toBe('No pages hidden by search-exclude policy.');
const counts = (r.details?.counts ?? {}) as Record<string, number>;
expect(counts['test/']).toBeUndefined();
});
test('zero hidden (only curated content) → ok', async () => {
await seed('concepts/keeper');
const r = await checkHiddenBySearchPolicy(engine);
expect(r.status).toBe('ok');
expect(r.message).toBe('No pages hidden by search-exclude policy.');
});
test('SQL error → warn, not throw', async () => {
const broken = {
executeRaw: async () => {
throw new Error('boom');
},
} as unknown as BrainEngine;
const r = await checkHiddenBySearchPolicy(broken);
expect(r.status).toBe('warn');
expect(r.message).toContain('Could not check');
});
});
describe('hidden_by_search_policy categorization', () => {
test('categorizes as a brain check', () => {
expect(categorizeCheck('hidden_by_search_policy')).toBe('brain');
});
});