test(memory-b): make F8 mutants bite the positive criteria

This commit is contained in:
2026-11-01 11:11:37 +08:00
parent 96adb172eb
commit 8bdd9f5ce0

View File

@ -2645,6 +2645,21 @@ test('D5†: dropping the unavailable sentence makes D5 red', async () => {
// ── F8: a mis-sized vector is a HARD error when the width is declared ──────
//
// ★ The positive predicate below is shared by the unmutated case and by every
// F8 mutant, so a mutant can only pass by satisfying the SAME requirement the
// real code satisfies. Re-writing the requirement inside the mutant's body
// ("under the mutation the bad vector lands") is a self-test: it asserts the
// mutated behaviour and therefore stays green exactly when the criterion should
// fail.
function assertExplicitWidthRefused(store) {
assert.throws(
() => store.putBlock({ text: 'x', vector: [1, 2], fingerprint: 'fp-a', expectedDimension: 3 }),
(err) => /dimension 2 does not match the active space's 3/.test(err.message),
'a 2-wide vector in a 3-wide space must be refused, not stored and counted later',
)
assert.equal(store.countBlocks(), 0, 'nothing was written')
}
//
// criteria:165 says "provider returns a dimension ≠ the configured one ⇒ hard
// error, never truncate or pad". Until now only the adapter layer enforced it
// (H3b); the write path silently degraded, so a bad vector could land and be
@ -2655,12 +2670,7 @@ test('D5†: dropping the unavailable sentence makes D5 red', async () => {
test('F8: putBlock refuses a vector whose width differs from the declared space', () => {
const store = new BlockStore({ path: ':memory:' }).open()
try {
assert.throws(
() => store.putBlock({ text: 'x', vector: [1, 2], fingerprint: 'fp-a', expectedDimension: 3 }),
/dimension 2 does not match the active space's 3/,
'a 2-wide vector in a 3-wide space must be refused, not stored and counted later',
)
assert.equal(store.countBlocks(), 0, 'nothing was written')
assertExplicitWidthRefused(store)
// The matching width is accepted.
assert.equal(
@ -2710,13 +2720,28 @@ test('F8: a provider returning the wrong width still degrades (not a caller bug)
try {
const r = await mem.commit({ text: 'x' })
assert.equal(r.embedded, false, 'a misbehaving provider degrades, it does not throw')
// ★ Counting rows, not picking one: `allBlocks(1)[0]` is satisfied by any
// single row, so a regression that stopped writing on the degraded path
// entirely would still be green here. F8/W4 require the block to LAND.
assert.equal(mem.store.countBlocks(), 1, 'the block is still written (degrade ≠ drop)')
assert.equal(mem.store.allBlocks(1)[0].vector, '', 'and no bad vector is stored')
} finally {
await done()
}
})
test('F8†: softening the write-side width check lets a bad vector land', async () => {
test('F8†: softening the write-side width check bites the positive criterion red', async (t) => {
// ★ The anchor must appear EXACTLY ONCE in lib/store.js. `withMutatedCopy`
// already fails loudly when it is missing; the count check makes "appears
// twice, first match mutated, second silently left intact" impossible too.
const { readFileSync } = await import('node:fs')
const source = readFileSync(new URL('../lib/store.js', import.meta.url), 'utf8')
assert.equal(
source.split(' if (actual !== expectedDimension) {').length - 1,
1,
'the F8† anchor must occur exactly once in lib/store.js',
)
await withMutatedCopy(
'store.js',
[[
@ -2726,14 +2751,19 @@ test('F8†: softening the write-side width check lets a bad vector land', async
async (mod) => {
const store = new mod.BlockStore({ path: ':memory:' }).open()
try {
// Under the mutation the bad vector is accepted, which is the state F8 forbids.
const written = store.putBlock({ text: 'x', vector: [1, 2], fingerprint: 'fp-a', expectedDimension: 3 })
assert.equal(written.created, true, 'the mutation really admits the wrong width')
assert.equal(
JSON.parse(store.allBlocks(1)[0].vector).length,
2,
'and stores it — so F8 (which requires a throw) fails on the mutated module',
)
// ★ The SAME positive criterion, run on the mutant. It must throw: on the
// mutant the write is admitted, so `assert.throws` sees no error and
// fails with ERR_ASSERTION.
assert.throws(() => assertExplicitWidthRefused(store), (err) => {
assert.equal(err.code, 'ERR_ASSERTION', 'the criterion must fail as an assertion')
t.diagnostic(`F8† RAW RED >>> ${String(err.message).split('\n')[0]}`)
return true
}, 'F8† must break the unchanged F8 criterion')
// Corroboration: the mutation really does admit the bad vector. This is
// reported as a diagnostic, never as the thing that "bites" — asserting
// it would be the self-test that made the previous version useless.
const probe = store.putBlock({ text: 'probe', vector: [1, 2], fingerprint: 'fp-a', expectedDimension: 3 })
t.diagnostic(`F8† mutant admitted the bad vector: created=${probe.created}, blocks=${store.countBlocks()}`)
} finally {
store.close()
}
@ -2741,6 +2771,60 @@ test('F8†: softening the write-side width check lets a bad vector land', async
)
})
test('F8†b: a degraded provider write that drops the block bites countBlocks red', async (t) => {
// ★ Mutation on lib/index.js: the degraded branch (provider returned a wrong
// width) stops persisting. `embedded === false` and `vector === ''` both stay
// green under this mutation — only the row count catches it, which is exactly
// what the `allBlocks(1)[0]` shape could not do.
await withMutatedCopy(
'index.js',
[[
` if (!Array.isArray(vector) || vector.length !== space.dimension) {
return { ...this.store.putBlock(input), embedded: false }
}`,
` if (!Array.isArray(vector) || vector.length !== space.dimension) {
return { id: '', created: false, embedded: false } // MUTATED: degrade by dropping
}`,
]],
async (mod) => {
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 { mkdtempSync, rmSync } = await import('node:fs')
const { tmpdir } = await import('node:os')
const { join } = await import('node:path')
const dir = mkdtempSync(join(tmpdir(), 'f8b-'))
const ctx = new Context()
new SystemPrompt(ctx, {})
new ToolRuntime(ctx, {})
try {
const mem = mod.apply(ctx, {
path: join(dir, 'm.db'),
embedder: { async embedText() { return [1, 2] }, info: () => ({ fingerprint: 'fp-x', dimension: 3 }) },
})
const r = await mem.commit({ text: 'x' })
// The two assertions that survive the mutation — reported, not relied on.
t.diagnostic(`F8†b still-green under mutation: embedded=${r.embedded}, blocks=${mem.store.countBlocks()}`)
assert.throws(() => {
assert.equal(
mem.store.countBlocks(),
1,
'the block must still be written on a degraded provider write',
)
assert.equal(mem.store.allBlocks(1)[0]?.vector, '', 'and carry no bad vector')
}, (err) => {
assert.equal(err.code, 'ERR_ASSERTION', 'only the row-count criterion may bite')
t.diagnostic(`F8†b RAW RED >>> ${String(err.message).split('\n')[0]}`)
return true
}, 'F8†b must break the unchanged provider-degradation criterion')
} finally {
await ctx.fiber.dispose()
rmSync(dir, { recursive: true, force: true })
}
},
)
})
test('F3 performance guard: blockStates source does not parse vector payloads on reads', async () => {
// The old implementation did `JSON.parse(row.vector).length` for every row;
// at 2000 blocks x 2048 dims that measured ~354ms per renderIndex call, ~255ms