Merge branch 'main' into fix/interrupted-embedding-recovery-3456

This commit is contained in:
Gergő Magyar 2026-10-03 18:30:58 +01:00 • committed by GitHub
commit 670f3db5c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 12 additions and 12 deletions

View file

@ -4614,9 +4614,11 @@ async function runFullAnalysisInner(
// re-read the on-disk meta immediately before writing (the shape the
// /api/embed checkpoint writer in server/api.ts already uses, which also
// keeps a concurrent writer's update from being reverted by a stale
// snapshot) and replace ONLY `embeddingCheckpoint`. In-place writers
// can also publish a measured live count; staged writers keep it in
// memory until the replacement is published.
// snapshot) and replace ONLY `embeddingCheckpoint` — plus
// `stats.embeddings` when the caller actually MEASURED the published
// index (the post-window `onCheckpoint` on an in-place build). A staging
// build's count is not published until the atomic swap succeeds. The
// window-start callback passes nothing, preserving the latest count.
const saveEmbeddingCheckpoint = async (
checkpoint: {
nodesProcessed: number;
@ -4888,9 +4890,8 @@ async function runFullAnalysisInner(
// already written to disk: prior meta says 0, a clean run inserts
// embeddings and checkpoints the real count, the final probe is
// unavailable, and finalization carries the stale 0 forward while reporting
// success. `loadMeta` never throws (it returns null), and the checkpoint
// writer already re-reads the same way, so this is the same freshness
// discipline applied to the same field.
// success. For a staged build, use its last measured count only in the
// final meta, written after the swap; never publish it at a checkpoint.
const latestMetaForCount =
embeddingCount === undefined ? ((await loadMeta(metaDir)) ?? existingMeta) : undefined;
const persistedEmbeddingCount = resolvePersistedEmbeddingCount(

View file

@ -2321,15 +2321,14 @@ describe('runFullAnalysis embedding-checkpoint meta write (#2790)', () => {
nodeIds: ['node-1', 'node-2'],
});
snapshots.windowStart = await loadMeta(storagePath);
// Post-window checkpoint — this one MEASURED the live count.
// Post-window checkpoint measures staging, not the published DB.
await pipelineOptions.onCheckpoint?.({
nodesProcessed: 2,
totalNodes: 4,
chunksProcessed: 4,
});
snapshots.postWindow = await loadMeta(storagePath);
// Window 2 — the old code restated the PREVIOUS run's count here and
// clobbered the live figure the post-window save had just written.
// Window 2 must retain the published count too.
await pipelineOptions.onCheckpointWindowStart?.({
nodesProcessed: 2,
totalNodes: 4,
@ -2381,7 +2380,7 @@ describe('runFullAnalysis embedding-checkpoint meta write (#2790)', () => {
});
expect(snapshots.windowStart?.lastCommit).not.toBe(currentCommit);
// The measured count belongs to the unpublished staging generation.
// ── Post-window: the staged count is not published yet ─────────────
expect(snapshots.postWindow).toMatchObject({
lastCommit: STALE_COMMIT,
fileHashes: STALE_HASHES,
@ -2394,7 +2393,7 @@ describe('runFullAnalysis embedding-checkpoint meta write (#2790)', () => {
unsafeNodeIds: [],
});
// ── Window 2: no stale restatement over the measured figure ────────
// ── Window 2: the published count remains unchanged ────────────────
expect(snapshots.secondWindow).toMatchObject({
lastCommit: STALE_COMMIT,
stats: { embeddings: 7 },
@ -2409,7 +2408,7 @@ describe('runFullAnalysis embedding-checkpoint meta write (#2790)', () => {
const finalMeta = JSON.parse(
await fs.readFile(`${storagePath}/meta.json`, 'utf-8'),
) as RepoMeta;
expect(finalMeta).toMatchObject({ lastCommit: currentCommit });
expect(finalMeta).toMatchObject({ lastCommit: currentCommit, stats: { embeddings: 42 } });
expect(finalMeta.embeddingCheckpoint).toBeUndefined();
expect(finalMeta.incrementalInProgress).toBeUndefined();
} finally {