diff --git a/src/commands/sync.ts b/src/commands/sync.ts index 55177421b..ed4448d23 100644 --- a/src/commands/sync.ts +++ b/src/commands/sync.ts @@ -576,9 +576,16 @@ async function runInlineCostGate( // Stale backlog: cheap single SQL; fail-open to 0 so a transient DB hiccup // never blocks the sync. Signature-aware (model/dims swap surfaces here). + // #1262: follow the write-side embedding column — otherwise an alt-column + // brain's fully-embedded corpus counts as phantom backlog on every gate. let staleChars = 0; try { - staleChars = await engine.sumStaleChunkChars({ signature: currentEmbeddingSignature() }); + const { resolveWriteColumnForEngine } = await import('../core/search/embedding-column.ts'); + const embeddingColumn = await resolveWriteColumnForEngine(engine); + staleChars = await engine.sumStaleChunkChars({ + signature: currentEmbeddingSignature(), + ...(embeddingColumn && { embeddingColumn }), + }); } catch { staleChars = 0; } diff --git a/src/core/engine.ts b/src/core/engine.ts index 3cbf6299d..c1ed2be76 100644 --- a/src/core/engine.ts +++ b/src/core/engine.ts @@ -1031,8 +1031,13 @@ export interface BrainEngine { * model signature (a model/dims swap). NULL signature is GRANDFATHERED * (never counted) so the post-migration corpus isn't flagged en masse. * Omit `signature` for the legacy `embedding IS NULL`-only count. + * + * `opts.embeddingColumn` switches the staleness predicate to the resolved + * write-side column (#1262) — same contract as countStaleChunks — so the + * sync cost gate doesn't count an alt-column brain's fully-embedded corpus + * as phantom backlog. */ - sumStaleChunkChars(opts?: { sourceId?: string; signature?: string }): Promise; + sumStaleChunkChars(opts?: { sourceId?: string; signature?: string; embeddingColumn?: ResolvedColumn }): Promise; /** * Stamp `pages.embedding_signature = signature` for one page. Called after * a page's chunks are (re)embedded so a later model swap can detect it as diff --git a/src/core/pglite-engine.ts b/src/core/pglite-engine.ts index b662d7d0e..566ddf9e8 100644 --- a/src/core/pglite-engine.ts +++ b/src/core/pglite-engine.ts @@ -2424,7 +2424,7 @@ export class PGLiteEngine implements BrainEngine { return Number(count); } - async sumStaleChunkChars(opts?: { sourceId?: string; signature?: string }): Promise { + async sumStaleChunkChars(opts?: { sourceId?: string; signature?: string; embeddingColumn?: ResolvedColumn }): Promise { // Sibling of countStaleChunks: same stale predicate, summing chunk_text // length for the sync cost preview. ::bigint guards int4 overflow. const { where, params } = this.buildStaleChunkWhere(opts); diff --git a/src/core/postgres-engine.ts b/src/core/postgres-engine.ts index 59fc70174..363e0bba2 100644 --- a/src/core/postgres-engine.ts +++ b/src/core/postgres-engine.ts @@ -2579,7 +2579,7 @@ export class PostgresEngine implements BrainEngine { }); } - async sumStaleChunkChars(opts?: { sourceId?: string; signature?: string }): Promise { + async sumStaleChunkChars(opts?: { sourceId?: string; signature?: string; embeddingColumn?: ResolvedColumn }): Promise { // Sibling of countStaleChunks: same stale predicate, summing chunk_text // length for the sync cost preview. ::bigint guards int4 overflow. const { where, params } = this.buildStaleChunkWhere(opts); diff --git a/test/e2e/embedding-column-pglite.test.ts b/test/e2e/embedding-column-pglite.test.ts index 7dac210ee..d5fb6a65a 100644 --- a/test/e2e/embedding-column-pglite.test.ts +++ b/test/e2e/embedding-column-pglite.test.ts @@ -355,6 +355,9 @@ describe('PGLite: embed --stale converges on an alt-column brain (#1262)', () => // the alt-column predicate does not. expect(await local.countStaleChunks()).toBe(1); expect(await local.countStaleChunks({ embeddingColumn: descriptor })).toBe(0); + // sumStaleChunkChars feeds the sync cost gate — same predicate contract. + expect(await local.sumStaleChunkChars()).toBeGreaterThan(0); + expect(await local.sumStaleChunkChars({ embeddingColumn: descriptor })).toBe(0); expect(await local.listStaleChunks({ embeddingColumn: descriptor, batchSize: 100 })).toHaveLength(0); expect(await local.listStaleChunks({ batchSize: 100 })).toHaveLength(1);