mirror of
https://github.com/garrytan/gbrain.git
synced 2026-08-14 00:48:18 +00:00
putPage's INSERT used COALESCE(<chunkerVersion>, 1), so callers that don't supply chunker_version (no MCP/subagent caller does — it's internal metadata) landed new pages at version 1. Dream subagents write through putPage directly, so their pages got v1 and doctor's contextual_retrieval_coverage check flagged them as "older chunker_version" forever, even though they were chunked and embedded with the current chunker. Default the INSERT to MARKDOWN_CHUNKER_VERSION on both engines. The ON CONFLICT UPDATE still COALESCE-preserves an explicitly supplied version. Add an engine-level regression test.
This commit is contained in:
@@ -23,6 +23,7 @@ import { runMigrations } from './migrate.ts';
|
||||
import { PGLITE_SCHEMA_SQL, getPGLiteSchema } from './pglite-schema.ts';
|
||||
import { DEFAULT_EMBEDDING_MODEL, DEFAULT_EMBEDDING_DIMENSIONS } from './ai/defaults.ts';
|
||||
import { DELETE_BATCH_SIZE } from './engine-constants.ts';
|
||||
import { MARKDOWN_CHUNKER_VERSION } from './chunkers/recursive.ts';
|
||||
import { acquireLock, releaseLock, type LockHandle } from './pglite-lock.ts';
|
||||
import { getFtsLanguage } from './fts-language.ts';
|
||||
import type {
|
||||
@@ -1035,7 +1036,7 @@ export class PGLiteEngine implements BrainEngine {
|
||||
const ingestedAt = (sourceKind || sourceUri || ingestedVia) ? new Date().toISOString() : null;
|
||||
const { rows } = await this.db.query(
|
||||
`INSERT INTO pages (source_id, slug, type, page_kind, title, compiled_truth, timeline, frontmatter, content_hash, updated_at, effective_date, effective_date_source, import_filename, chunker_version, source_path, source_kind, source_uri, ingested_via, ingested_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9, now(), $10::timestamptz, $11, $12, COALESCE($13, 1), $14, $15, $16, $17, $18::timestamptz)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9, now(), $10::timestamptz, $11, $12, COALESCE($13, ${MARKDOWN_CHUNKER_VERSION}), $14, $15, $16, $17, $18::timestamptz)
|
||||
ON CONFLICT (source_id, slug) DO UPDATE SET
|
||||
type = EXCLUDED.type,
|
||||
page_kind = EXCLUDED.page_kind,
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
EmbeddingColumnNotRegisteredError,
|
||||
} from './search/embedding-column.ts';
|
||||
import { getFtsLanguage } from './fts-language.ts';
|
||||
import { MARKDOWN_CHUNKER_VERSION } from './chunkers/recursive.ts';
|
||||
import type {
|
||||
Page, PageInput, PageFilters, PageType,
|
||||
Chunk, ChunkInput, StaleChunkRow, StalePageRow,
|
||||
@@ -1097,7 +1098,7 @@ export class PostgresEngine implements BrainEngine {
|
||||
const ingestedAt = (sourceKind || sourceUri || ingestedVia) ? new Date() : null;
|
||||
const rows = await sql`
|
||||
INSERT INTO pages (source_id, slug, type, page_kind, title, compiled_truth, timeline, frontmatter, content_hash, updated_at, effective_date, effective_date_source, import_filename, chunker_version, source_path, source_kind, source_uri, ingested_via, ingested_at)
|
||||
VALUES (${sourceId}, ${slug}, ${page.type}, ${pageKind}, ${page.title}, ${page.compiled_truth}, ${page.timeline || ''}, ${sql.json(frontmatter as Parameters<typeof sql.json>[0])}, ${hash}, now(), ${effectiveDate}, ${effectiveDateSource}, ${importFilename}, COALESCE(${chunkerVersion}::smallint, 1), ${sourcePath}, ${sourceKind}, ${sourceUri}, ${ingestedVia}, ${ingestedAt})
|
||||
VALUES (${sourceId}, ${slug}, ${page.type}, ${pageKind}, ${page.title}, ${page.compiled_truth}, ${page.timeline || ''}, ${sql.json(frontmatter as Parameters<typeof sql.json>[0])}, ${hash}, now(), ${effectiveDate}, ${effectiveDateSource}, ${importFilename}, COALESCE(${chunkerVersion}::smallint, ${MARKDOWN_CHUNKER_VERSION}), ${sourcePath}, ${sourceKind}, ${sourceUri}, ${ingestedVia}, ${ingestedAt})
|
||||
ON CONFLICT (source_id, slug) DO UPDATE SET
|
||||
type = EXCLUDED.type,
|
||||
page_kind = EXCLUDED.page_kind,
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* #2807 — putPage INSERT default for chunker_version.
|
||||
*
|
||||
* postgres-engine / pglite-engine used `COALESCE(<chunkerVersion>, 1)` in the
|
||||
* pages INSERT. Callers that don't supply chunker_version (no MCP/subagent
|
||||
* caller does — it's internal metadata) landed new pages at version 1, so
|
||||
* doctor's contextual_retrieval_coverage check flagged dream-written pages as
|
||||
* "older chunker_version" forever, even though they were chunked/embedded with
|
||||
* the current chunker.
|
||||
*
|
||||
* Fix: default the INSERT to MARKDOWN_CHUNKER_VERSION. The ON CONFLICT UPDATE
|
||||
* still COALESCE-preserves an explicitly supplied version.
|
||||
*/
|
||||
|
||||
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
||||
import { PGLiteEngine } from '../src/core/pglite-engine.ts';
|
||||
import { MARKDOWN_CHUNKER_VERSION } from '../src/core/chunkers/recursive.ts';
|
||||
|
||||
describe('#2807 — putPage chunker_version INSERT default', () => {
|
||||
let engine: PGLiteEngine;
|
||||
|
||||
beforeAll(async () => {
|
||||
engine = new PGLiteEngine();
|
||||
await engine.connect({});
|
||||
await engine.initSchema();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await engine.disconnect();
|
||||
}, 30_000);
|
||||
|
||||
async function readChunkerVersion(slug: string): Promise<number> {
|
||||
const { rows } = await (engine as any).db.query(
|
||||
`SELECT chunker_version FROM pages WHERE slug = $1`,
|
||||
[slug],
|
||||
);
|
||||
return Number((rows[0] as { chunker_version: number }).chunker_version);
|
||||
}
|
||||
|
||||
test('page written without chunker_version defaults to MARKDOWN_CHUNKER_VERSION, not 1', async () => {
|
||||
await engine.putPage('dream/no-chunker-version', {
|
||||
type: 'concept',
|
||||
title: 'Dream synthesized page',
|
||||
compiled_truth: 'Written directly through putPage, like a dream subagent.',
|
||||
timeline: '',
|
||||
});
|
||||
expect(await readChunkerVersion('dream/no-chunker-version')).toBe(MARKDOWN_CHUNKER_VERSION);
|
||||
expect(MARKDOWN_CHUNKER_VERSION).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
test('explicit chunker_version is honored on INSERT', async () => {
|
||||
await engine.putPage('dream/explicit-chunker-version', {
|
||||
type: 'concept',
|
||||
title: 'Explicit version',
|
||||
compiled_truth: 'Caller supplied a version.',
|
||||
timeline: '',
|
||||
chunker_version: 2,
|
||||
});
|
||||
expect(await readChunkerVersion('dream/explicit-chunker-version')).toBe(2);
|
||||
});
|
||||
|
||||
test('re-put without chunker_version does not lower an already-current version', async () => {
|
||||
await engine.putPage('dream/preserve-version', {
|
||||
type: 'concept',
|
||||
title: 'Preserve me',
|
||||
compiled_truth: 'first write',
|
||||
timeline: '',
|
||||
chunker_version: MARKDOWN_CHUNKER_VERSION,
|
||||
});
|
||||
// Second write omits chunker_version; the UPDATE branch must not drop it
|
||||
// below the current chunker version.
|
||||
await engine.putPage('dream/preserve-version', {
|
||||
type: 'concept',
|
||||
title: 'Preserve me',
|
||||
compiled_truth: 'second write',
|
||||
timeline: '',
|
||||
});
|
||||
expect(await readChunkerVersion('dream/preserve-version')).toBe(MARKDOWN_CHUNKER_VERSION);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user