diff --git a/test/phase-b-c4-centering.test.js b/test/phase-b-c4-centering.test.js index 60c1871..519b6b0 100644 --- a/test/phase-b-c4-centering.test.js +++ b/test/phase-b-c4-centering.test.js @@ -684,3 +684,68 @@ test('C4j: a centroid marked stale falls back to uncorrected vectors', async (t) store.close() } }) + +// ── C4k: 落标记这件事必须是写入侧自己干的,不是测试替它干 ─────────────────── +// +// C4j 里有一行 `store.markCentroidStale(fp)` —— 那是判据**手工**打的标记,它只 +// 证明「召回侧会尊重标记」,不证明「生产路径会打这个标记」。 +// +// 实测:把 `lib/index.js` 里那行 `if (status?.stale === true) this.store +// .markCentroidStale(...)` 整个删掉,**全套 306 条判据全绿**。也就是说这条接线 +// 此前没有任何判据守着 —— 与 W8(saveSpaceCentroid 零生产调用点)、C4d(中心 +// 靠测试手工 rebuildCentroid)是同一个病:手工喂进去的输入,证明不了生产路径。 +// +// 这条判据只用生产入口:`apply()` + `commit()`,自己不打任何标记。 +test('C4k: the write path itself marks a drifted centroid stale', async () => { + const { Context } = await import('@deepseek-ai/cordis') + const { SystemPrompt } = await import('@deepseek-ai/dsh-system-prompt') + const { ToolRuntime } = await import('@deepseek-ai/dsh-tools') + const { apply: applyMemory } = await import('../lib/index.js') + const { mkdtempSync, rmSync } = await import('node:fs') + const { tmpdir } = await import('node:os') + const { join } = await import('node:path') + + const DIM = 4 + const dir = mkdtempSync(join(tmpdir(), 'c4k-')) + const ctx = new Context() + new SystemPrompt(ctx, {}) + new ToolRuntime(ctx, {}) + try { + const mem = applyMemory(ctx, { + path: join(dir, 'm.db'), + embedder: { + async embedText() { return unit([1, 0.1, 0, 0]) }, + info: () => ({ fingerprint: 'c4k:write-marks', dimension: DIM }), + }, + }) + const fp = mem.space().fingerprint + + // Build a centroid, then let the scheduled rebuild land so the count is settled. + for (let i = 0; i < 8; i += 1) await mem.commit({ text: `块 ${i}` }) + await new Promise((resolve) => setTimeout(resolve, 3000)) + const settled = mem.store.loadCentroid(fp) + assert.ok(settled !== undefined, 'C4k precondition: a centroid must exist') + assert.equal( + settled.stats?.stale, + undefined, + 'C4k precondition: a freshly rebuilt centroid carries no stale flag', + ) + + // Drift it past the 20% ratio through the production write path only. + for (let i = 8; i < 11; i += 1) await mem.commit({ text: `块 ${i}` }) + + const drifted = mem.store.loadCentroid(fp) + assert.equal( + drifted.stats?.stale, + 1, + 'C4k: the WRITE path must persist the stale verdict — the recall side is not ' + + 'allowed to judge it (C3-k), so if this flag is missing, recall keeps ' + + 'centring with a mean that no longer describes the space. Deleting that one ' + + 'line in #ensureCentroid left all 306 criteria green, which is why this ' + + 'criterion exists.', + ) + } finally { + await ctx.fiber.dispose() + rmSync(dir, { recursive: true, force: true }) + } +})