From f3ee2d1cb03c0412438614341790f2700f69fa26 Mon Sep 17 00:00:00 2001 From: jianf <2198972886@qq.com> Date: Thu, 5 Nov 2026 12:39:06 +0800 Subject: [PATCH] test(memory): pin that the WRITE path marks stale, not the test (C4 follow-up) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit C4j asserted that a stale-marked centroid falls back to uncorrected vectors — but it called `store.markCentroidStale(fp)` **itself** to set up the state. That proves the recall side honours the flag; it says nothing about whether production ever sets it. Measured: deleting the one line in `#ensureCentroid` that persists the verdict if (status?.stale === true) this.store.markCentroidStale(fingerprint) left **all 306 criteria green**. The wiring had no guard at all. This is the fifth instance of one shape in this branch: W8 saveSpaceCentroid defined, zero production callers C4d centroid built by the test's own rebuildCentroid call P1 vectorScores hand-built in the fixture D2 counters computed, never printed C4j stale flag set by the criterion, not by the write path A hand-fed input cannot prove a production path. So C4k drives only the production entry points — `apply()` + `commit()` — and marks nothing itself: after a settled rebuild stats.stale === undefined after 3 more commits stats.stale === 1 (drift 3/8 = 37.5% > 20%) Mutation — deleting that line in `#ensureCentroid`: not ok 12 - C4k: the write path itself marks a drifted centroid stale error: C4k: the WRITE path must persist the stale verdict ... undefined !== 1 ## Why the drift is generated rather than forced 3 commits on top of a settled 8 puts the delta at 37.5%, past the 20% ratio — the same path an ordinary growing space takes. Forcing `markCentroidStale` directly is exactly what C4j does and exactly what this criterion exists to complement. Gates: npm test 307/307 (0 skipped), npm run check 12/12, frozen faces untouched. --- test/phase-b-c4-centering.test.js | 65 +++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) 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 }) + } +})