Files
HologramAgent-DSH-Memory-Port/test/phase-b-a.test.js
jianf 07f8438964 fix(memory): count every dropped candidate, not just the two upstream ones (D2)
`recallBlocks` could make a candidate disappear in three downstream places, and
none of them incremented anything:

    · MinScore gate        it scored, but below the threshold
    · structural gate      neither a usable vector score nor a symbol hit
    · caller topK          survived everything, fell outside the requested count

Measured before this change, 6 blocks in one space, `minScore: 0.99`:

    hits=0  skipped=0  skippedDim=0

That reading is indistinguishable from "the space holds one block". An operator
whose threshold is swallowing the whole corpus is told "nothing was excluded",
and goes looking at the wrong subsystem. Only the space filter and the width
filter were ever counted — the two that are decided before scoring.

## What this adds

Three non-enumerable counters, each named after its own remedy:

    skippedScore       lower the threshold
    skippedNoEvidence  re-embed: there was nothing to score
    truncated          not a defect — reported so the totals reconcile

Plus the invariant that makes the next silent path impossible:
**hits + skipped + skippedDim + skippedScore + skippedNoEvidence + truncated
=== the candidate pool.** A future `continue` that forgets a counter breaks it.

## Criterion

`test/recall-accounting.test.js`, one fixture per path, asserting each counter's
own value (not a merged total — "scored too low" and "nothing to score" have
opposite remedies). The fourth test is the reconciliation above.

Mutations, both in a scratch copy:

    remove `gatedByScore += 1`     -> criteria 1 and 4 red
    remove `gatedNoEvidence += 1`  -> criterion 2 red

## Collateral: two static mutation anchors moved

`test/phase-b-a.test.js` pins the V5 gate and the caller-topK truncation by
rewriting `lib/recall.js` text. Making the gate multi-line (to hold a counter)
and threading `drops` through `withSkipped` changed those exact strings, so the
anchors are updated to the new text — same mutations, same properties pinned.
Running the file confirms 38/38 afterwards; without the update three V5†
mutations failed on a stale anchor rather than on their subject.

Gates: npm test 298/298 (0 skipped), npm run check 12/12. Frozen faces untouched.
2026-11-05 11:42:06 +08:00

1093 lines
50 KiB
JavaScript

/**
* Phase B · group A — preconditions.
*
* A1: `recallBlocks` stays SYNCHRONOUS and unmodified; the async surface is a
* *new* wrapper beside it. This is the structural guarantee that phase B
* cannot regress phase A: existing callers keep calling a sync function and
* 9 test call sites need no edit.
* A2: with no embedder, the wrapper's output is byte-identical to the sync
* function — same order, same scores, same `why`.
* A3: hybrid and sync agree field-for-field when the embedder is null.
*
* A2/A3 are pinned by a GOLDEN FIXTURE (`test/fixtures/golden-phase-a.json`)
* captured from the frozen phase-A implementation over a 12-block corpus and 15
* queries at topK 1/3/5. Scores are compared exactly, not approximately — a
* tolerance-based check would let a scoring change through, which is precisely
* what this group exists to prevent.
*
* @module dsh-memory/test/phase-b-a
*/
import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import test from 'node:test'
import { BlockStore } from '../lib/store.js'
import { recallBlocks, recallBlocksHybrid } from '../lib/recall.js'
const GOLDEN = JSON.parse(
readFileSync(new URL('./fixtures/golden-phase-a.json', import.meta.url), 'utf8'),
)
/** Build a store seeded with the fixture corpus. */
function seededStore() {
const store = new BlockStore({ path: ':memory:' }).open()
for (const block of GOLDEN.corpus) store.putBlock(block)
return store
}
test('A1: recallBlocks is synchronous and takes no embedder', () => {
const store = seededStore()
try {
const result = recallBlocks(store, '4324', { topK: 1 })
// A synchronous function returns a value, not a thenable. If phase B ever
// "helpfully" makes this async, every existing caller breaks silently
// (a Promise is never `.length === 0`), so pin it structurally.
assert.equal(typeof result?.then, 'undefined', 'recallBlocks must NOT return a thenable')
assert.ok(Array.isArray(result), 'recallBlocks must return an array synchronously')
assert.equal(result.length, 1)
} finally {
store.close()
}
})
// A1b originally pinned `recallBlocks` BYTE-FOR-BYTE against the frozen phase-A
// commit. That was the right guard while phase B was additive, but F2 (space
// filtering on the symbol path) legitimately has to edit the function — so a byte
// comparison would now forbid correct work.
//
// What the byte-lock actually protected was narrower: **phase-A behaviour must not
// drift**. That is already pinned, and better, by the golden fixture (A2), whose
// 12-block corpus carries no fingerprints — so the F2 filter cannot fire on it.
// The property to keep is therefore: *without a fingerprint argument, recall is
// untouched.* Asserted behaviourally below, plus a static check that the filter is
// gated on the argument rather than running unconditionally.
test('A1b: recallBlocks is unchanged for callers that pass no fingerprint', () => {
const source = readFileSync(new URL('../lib/recall.js', import.meta.url), 'utf8')
const body = source.slice(source.indexOf('export function recallBlocks('))
const fn = body.slice(0, body.indexOf('\n}\n'))
// The space filter must be conditional on an explicitly supplied fingerprint.
// If it ran unconditionally, phase-A callers (and the golden fixture) would
// change behaviour — the exact regression the byte-lock was protecting against.
assert.match(
fn,
/if \(typeof fingerprint === 'string' && fingerprint !== ''\) \{/,
'the F2 space filter must be gated on an explicitly supplied fingerprint',
)
assert.match(
fn,
/fingerprint[,\s}]/,
'recallBlocks must accept a `fingerprint` option',
)
})
test('A1b-behaviour: omitting fingerprint yields the golden results exactly', () => {
// Behavioural twin of the check above: the fixture corpus has no fingerprints,
// and no fingerprint is passed, so the F2 filter is inert here. If it ever fires
// in this configuration, results diverge and this fails.
//
// ★ KNOWN WEAKNESS — do not read this test alone as sufficient.
// It walks only the FIRST FIVE golden cases (`slice(0, 5)`), and under mutation
// A (a scoring change in the non-exact band) those five can stay green while
// later cases drift. The conclusion that phase-A behaviour is intact must come
// from **A2**, which walks all 45 cases and compares exact scores. A1b-behaviour
// is a cheap smoke check on the no-fingerprint path, not the authority.
//
// History: A1b used to pin `recallBlocks` byte-for-byte against the frozen
// phase-A commit. F2 legitimately has to edit that function, so the guard was
// narrowed to "behaviour without a fingerprint is unchanged" — with A2 carrying
// the full-coverage claim.
const store = seededStore()
try {
for (const testCase of GOLDEN.cases.slice(0, 5)) {
const actual = recallBlocks(store, testCase.query, { topK: testCase.topK })
assert.deepEqual(
actual.map((hit) => [hit.id, hit.score, hit.exact, hit.why]),
testCase.hits.map((hit) => [hit.id, hit.score, hit.exact, hit.why]),
`no-fingerprint recall drifted for ${testCase.name}`,
)
}
} finally {
store.close()
}
})
test('A2: golden fixture — every case reproduces exactly (order, score, why)', () => {
const store = seededStore()
try {
const failures = []
for (const testCase of GOLDEN.cases) {
const actual = recallBlocks(store, testCase.query, { topK: testCase.topK })
const label = `${testCase.name} q=${JSON.stringify(testCase.query)} topK=${testCase.topK}`
if (actual.length !== testCase.hits.length) {
failures.push(`${label}: hit count ${actual.length} != ${testCase.hits.length}`)
continue
}
for (let i = 0; i < actual.length; i += 1) {
const a = actual[i]
const e = testCase.hits[i]
// Exact identity of the ordered result: position, id, score, exact flag,
// and the human-readable attribution string.
if (a.id !== e.id) failures.push(`${label} [${i}]: id ${a.id} != ${e.id}`)
if (a.score !== e.score) failures.push(`${label} [${i}]: score ${a.score} != ${e.score}`)
if (a.exact !== e.exact) failures.push(`${label} [${i}]: exact ${a.exact} != ${e.exact}`)
if (a.why !== e.why) failures.push(`${label} [${i}]: why ${JSON.stringify(a.why)} != ${JSON.stringify(e.why)}`)
if (a.text !== e.text) failures.push(`${label} [${i}]: text differs`)
}
if (failures.length > 8) break
}
assert.deepEqual(failures, [], `golden mismatch:\n${failures.slice(0, 8).join('\n')}`)
} finally {
store.close()
}
})
test('A3: recallBlocksHybrid with no embedder equals recallBlocks field-for-field', async () => {
const store = seededStore()
try {
const failures = []
for (const testCase of GOLDEN.cases) {
const sync = recallBlocks(store, testCase.query, { topK: testCase.topK })
const hybrid = await recallBlocksHybrid(store, null, testCase.query, { topK: testCase.topK })
if (JSON.stringify(sync) !== JSON.stringify(hybrid)) {
failures.push(`${testCase.name} topK=${testCase.topK}`)
}
if (failures.length > 5) break
}
assert.deepEqual(failures, [], `hybrid≠sync for: ${failures.join(', ')}`)
} finally {
store.close()
}
})
test('A3b: hybrid reports why it degraded, so "no vectors" is visible not silent', async () => {
const store = seededStore()
try {
const noEmbedder = await recallBlocksHybrid(store, null, '4324', { topK: 1 })
// The result array carries an attached diagnosis rather than a second shape.
assert.equal(noEmbedder.degraded, 'no_embedder', 'null embedder must be reported')
assert.ok(noEmbedder.length >= 1, 'and must still recall via the symbol path')
const failing = {
async embedText() {
throw new Error('boom')
},
info: () => ({ fingerprint: 'x', dimension: 3 }),
}
const withError = await recallBlocksHybrid(store, failing, '4324', { topK: 1 })
assert.equal(withError.degraded, 'embed_error', 'a failing embedder must be reported')
assert.ok(withError.length >= 1, 'and must still fall back to FTS5')
} finally {
store.close()
}
})
// ── mutations ─────────────────────────────────────────────────────────────
test('A2 mutation: any scoring change must break the golden fixture', () => {
const store = seededStore()
try {
// Reproduce what a careless phase-B edit does: nudge the non-exact band.
// We cannot mutate lib code from a test, so instead verify the fixture is
// *sensitive* to a one-ULP change by comparing a deliberately altered score.
const testCase = GOLDEN.cases.find((c) => c.hits.length > 0)
const tampered = testCase.hits.map((h, i) =>
i === 0 ? { ...h, score: h.score + Number.EPSILON } : h,
)
const original = testCase.hits
assert.notEqual(
JSON.stringify(tampered),
JSON.stringify(original),
'a 1-ULP score change must be observable — otherwise exact comparison is meaningless',
)
} finally {
store.close()
}
})
test('A3 mutation: a wrapper that adds any suffix to `why` must break A3', async () => {
// Simulate the classic drift: the hybrid path decorates `why` for debugging.
const store = seededStore()
try {
const sync = recallBlocks(store, '4324', { topK: 1 })
const decorated = sync.map((h) => ({ ...h, why: `${h.why}|via-hybrid` }))
assert.notEqual(
JSON.stringify(sync),
JSON.stringify(decorated),
'A3 compares `why` deliberately: a decorated copy must be detectable',
)
} finally {
store.close()
}
})
// ── the SUCCESS path's label (P1 flips it: vectors now participate) ────────
//
// A3b only pinned the two failure labels. Changing the success label back to
// 'hybrid' left all 76 tests green — a label nobody asserts is a label that will
// drift. The two success labels answer DIFFERENT questions and both still matter:
//
// `no_vector_space` the space exists but holds no comparable vector (a
// fresh space). Says "nothing was scored" without
// claiming a provider failure.
// `hybrid` P1: the vector route really contributed scores to the
// same call that produced these hits.
//
// A build that reports `hybrid` while never passing `vectorScores` is EXACTLY the
// defect P1 exists for, so the assertion below (with a populated space) is the
// one that must never drift.
test('A3c: a healthy embedder over an EMPTY space reports "no_vector_space"', async () => {
const store = seededStore()
try {
let embedCalls = 0
const healthy = {
async embedText() {
embedCalls += 1
return [0.1, 0.2, 0.3]
},
info: () => ({ fingerprint: 'fp-healthy', dimension: 3 }),
}
const hits = await recallBlocksHybrid(store, healthy, '4324', { topK: 1 })
assert.equal(embedCalls, 1, 'the embedder must actually be called')
assert.equal(
hits.degraded,
'no_vector_space',
'the fixture corpus carries no vectors in `fp-healthy`, so nothing can be ' +
'scored: the honest label says so instead of pretending a vector participated',
)
assert.notEqual(hits.degraded, 'hybrid', "'hybrid' without a scored candidate would be a lie")
// And the result is still the pure-symbol ordering.
assert.equal(hits[0].text.includes('4324'), true)
} finally {
store.close()
}
})
test('A3d: once the space holds vectors, the same recall reports "hybrid"', async () => {
const store = new BlockStore({ path: ':memory:' }).open()
try {
const fingerprint = 'fp-healthy'
store.putBlock({ text: '值班室分机号 4324', vector: [0.1, 0.2, 0.3], fingerprint })
const healthy = {
async embedText() { return [0.1, 0.2, 0.3] },
info: () => ({ fingerprint, dimension: 3 }),
}
const hits = await recallBlocksHybrid(store, healthy, '4324', { topK: 1 })
assert.equal(
hits.degraded,
'hybrid',
'a scored vector candidate is participating — reporting the group-A label here ' +
'would hide exactly the regression P1 pins',
)
assert.equal(hits.length, 1)
assert.ok(hits[0].vectorHit > 0, 'and the hit really carries a vector contribution')
} finally {
store.close()
}
})
test('A3c†: A3d really bites — drop the scoring pass and "hybrid" becomes unreachable', async (t) => {
// ★ A real mutation of lib/, not a re-assertion of local constants. The earlier
// version of this test compared two string literals it had just defined, which
// can never fail for a reason that matters (the same defect the mutation
// discipline note calls out: a guard that cannot fail is not a guard).
//
// The mutation empties the candidate scan. If A3d still reported `hybrid`, the
// label would be decoration rather than evidence — which is exactly the bug P1
// fixes one layer up.
await v5Mutant(
` const rows = store.vectorCandidates(fingerprint, dimension)`,
` const rows = [] // MUTATED: scoring pass produces no candidates`,
async (mod) => {
const store = new BlockStore({ path: ':memory:' }).open()
try {
const fingerprint = 'fp-healthy'
store.putBlock({ text: '值班室分机号 4324', vector: [0.1, 0.2, 0.3], fingerprint })
const healthy = {
async embedText() { return [0.1, 0.2, 0.3] },
info: () => ({ fingerprint, dimension: 3 }),
}
const hits = await mod.recallBlocksHybrid(store, healthy, '4324', { topK: 1 })
assert.throws(
() => assert.equal(hits.degraded, 'hybrid'),
(err) => {
assert.equal(err.code, 'ERR_ASSERTION', 'only the criterion may bite')
t.diagnostic(`A3c† RAW RED >>> ${String(err.message).split('\n')[0]}`)
return true
},
'A3d must go red when nothing is scored',
)
} finally {
store.close()
}
},
)
})
// ── V1: exact hits stay BOOLEAN-pinned, vector only adjusts inside the band ─
//
// Ported from Go `internal/memory/fusion.go:425-428`:
//
// if f.ExactHit.mutated { f.Score = 1.0 + f.VectorHit*0.01 }
//
// The blocked copy that must never come back is the one HA measured and
// recorded (`fusion.go:42-44`): folding a 1.0 exact hit into the weighted sum
// gives it 0.30 and lets a noise candidate at 0.534 win.
test('V1: an exact hit keeps the pin band and only takes a vector tie adjustment', () => {
const store = seededStore()
try {
// A query with an exact numeric symbol, so it is a real exact-band hit.
const query = '4324'
const plain = recallBlocks(store, query, { topK: 5 })
const exact = plain.filter((hit) => hit.exact === true)
assert.ok(exact.length > 0, `the corpus must yield an exact hit for ${query}; got ${JSON.stringify(plain.map((h) => [h.id, h.exact]))}`)
// Vector scores supplied for the exact hits only: same band, tiny nudge.
const scores = new Map(exact.map((hit) => [hit.id, 1]))
const withVector = recallBlocks(store, query, { topK: 5, vectorScores: scores })
const before = new Map(exact.map((hit) => [hit.id, hit.score]))
for (const hit of withVector.filter((h) => h.exact === true)) {
const previous = before.get(hit.id)
if (previous === undefined) continue
assert.equal(hit.score, 1.0 + 1 * 0.01, 'exact score must be 1.0 + vector*0.01 (go:fusion.go:427)')
assert.ok(hit.score >= 1.0, 'and stays in the pin band, never below 1.0')
}
// The vector nudge must never outrank an exact hit: every exact hit still
// sorts above every non-exact hit.
const firstNonExact = withVector.findIndex((hit) => hit.exact !== true)
if (firstNonExact !== -1) {
for (let i = firstNonExact; i < withVector.length; i += 1) {
assert.ok(withVector[i].score < 1.0, 'non-exact hits stay strictly below the pin band')
}
}
} finally {
store.close()
}
})
test('V1: a low vector score cannot pull an exact hit out of the pin band', () => {
const store = seededStore()
try {
const query = '4324'
const plain = recallBlocks(store, query, { topK: 5 })
const exact = plain.find((hit) => hit.exact === true)
assert.ok(exact, 'precondition: the corpus must produce an exact hit')
const zero = recallBlocks(store, query, { topK: 5, vectorScores: new Map([[exact.id, 0]]) })
const hit = zero.find((row) => row.id === exact.id)
assert.equal(hit.score, 1.0, 'vector 0 still pins at 1.0 — the pin never depends on the vector')
} finally {
store.close()
}
})
// M-V1 mutation: fold exact into weights; the unchanged positive predicate must bite.
test('V1†: folding an exact hit into a weighted sum turns V1 red', async (t) => {
const { readFileSync, writeFileSync, mkdtempSync, rmSync, cpSync } = await import('node:fs')
const { tmpdir } = await import('node:os')
const { join } = await import('node:path')
const { fileURLToPath, pathToFileURL } = await import('node:url')
const libDir = fileURLToPath(new URL('../lib/', import.meta.url))
const pkgRoot = fileURLToPath(new URL('..', import.meta.url))
const dir = mkdtempSync(join(tmpdir(), 'mv1-'))
try {
cpSync(libDir, join(dir, 'lib'), { recursive: true })
cpSync(join(pkgRoot, 'package.json'), join(dir, 'package.json'))
const target = join(dir, 'lib', 'recall.js')
let source = readFileSync(target, 'utf8')
const anchor = ` const exactScore = vectorScores instanceof Map
? 1.0 + (vectorScores.get(candidate.id) ?? 0) * 0.01
: 1.0 + candidate.lexical * 0.01 + boundaryBonus * 1e-4`
assert.equal(source.split(anchor).length - 1, 1, 'M-V1 anchor must occur exactly once')
source = source.replace(anchor,
' const exactScore = 0.7 * (vectorScores instanceof Map ? (vectorScores.get(candidate.id) ?? 0) : 0) + 0.3 * (exact ? 1 : 0) // MUTATED')
writeFileSync(target, source)
const mod = await import(pathToFileURL(target).href)
const store = seededStore()
try {
const plain = mod.recallBlocks(store, '4324', { topK: 5 })
const exact = plain.filter((hit) => hit.exact === true)
assert.ok(exact.length > 0, 'precondition: mutant corpus has an exact hit')
assert.throws(() => {
assert.ok(exact.every((hit) => hit.score >= 1.0), 'exact must stay in the pin band')
}, (err) => {
assert.equal(err.code, 'ERR_ASSERTION')
t.diagnostic(`V1† RAW RED >>> ${err.message}`)
return true
}, 'M-V1 must break the unchanged V1 positive predicate')
} finally { store.close() }
} finally { rmSync(dir, { recursive: true, force: true }) }
})
// ── V2: non-exact records combine vector*0.7 + symbol*0.3 ───────────────
//
// Ported from Go `internal/memory/fusion.go:61-66` (defaultWeights) and
// `:430-434` (the non-exact weighted sum). V2 scores records that are ALREADY
// in the symbol pool: appending symbol-only records is V5, and the asymmetric
// MinScore gate is V5's too — neither is opened here.
//
// ★ The criterion below is shared verbatim by the positive case and the mutant,
// so a mutant can only pass by satisfying the same requirement. The V1† lesson
// was that re-writing the requirement inside the mutant's body tests the mutant,
// not the code.
function assertV2Weights(recall, store) {
// A query from the fixture corpus that yields non-exact records. `QQ 插件`
// does not: FTS returns nothing for it, so the assertion has no subject.
const query = '分机号'
const plain = recall(store, query, { topK: 5 })
const candidate = plain.find((hit) => hit.exact === false)
assert.ok(candidate, `precondition: a non-exact record exists for ${query}`)
const pick = (score) => recall(store, query, { topK: 5, vectorScores: new Map([[candidate.id, score]]) })
.find((hit) => hit.id === candidate.id)
const zero = pick(0)
const one = pick(1)
assert.ok(zero && one, 'the scored record must remain in the candidate pool')
// Go: f.Score = f.VectorHit*w.Vector + f.SymbolHit*w.Symbol with Vector = 0.7.
assert.equal(one.score - zero.score, 0.7,
'vector 0→1 must move the fused score by exactly Go Vector weight 0.7')
// The symbol term must still be present: with no vector score the record keeps
// its symbol-only score rather than collapsing to 0 (Go's `SymbolHit*w.Symbol`).
const noMap = recall(store, query, { topK: 5 }).find((hit) => hit.id === candidate.id)
const emptyMap = recall(store, query, { topK: 5, vectorScores: new Map() })
.find((hit) => hit.id === candidate.id)
assert.equal(emptyMap.score, noMap.score, 'a missing vector score must not invent a contribution')
}
test('V2: a non-exact record uses the Go 0.7/0.3 weighted score', () => {
const store = seededStore()
try {
assertV2Weights(recallBlocks, store)
} finally {
store.close()
}
})
test('V2†: changing the Go weights to 0.5/0.5 bites the unchanged criterion red', async (t) => {
const { readFileSync, writeFileSync, mkdtempSync, rmSync, cpSync } = await import('node:fs')
const { tmpdir } = await import('node:os')
const { join } = await import('node:path')
const { fileURLToPath, pathToFileURL } = await import('node:url')
const libDir = fileURLToPath(new URL('../lib/', import.meta.url))
const pkgRoot = fileURLToPath(new URL('..', import.meta.url))
const dir = mkdtempSync(join(tmpdir(), 'mv2-'))
try {
cpSync(libDir, join(dir, 'lib'), { recursive: true })
cpSync(join(pkgRoot, 'package.json'), join(dir, 'package.json'))
const target = join(dir, 'lib', 'recall.js')
let source = readFileSync(target, 'utf8')
const anchor = 'vectorScore * 0.7 + nonExact * 0.3'
assert.equal(source.split(anchor).length - 1, 1, 'M-V2 anchor must occur exactly once')
source = source.replace(anchor, 'vectorScore * 0.5 + nonExact * 0.5 // MUTATED: weights')
writeFileSync(target, source)
const mod = await import(pathToFileURL(target).href)
const store = seededStore()
try {
assert.throws(() => assertV2Weights(mod.recallBlocks, store), (err) => {
assert.equal(err.code, 'ERR_ASSERTION', 'only the criterion may bite')
t.diagnostic(`V2† RAW RED >>> ${String(err.message).split('\n')[0]}`)
return true
}, 'M-V2 must break the unchanged V2 criterion')
} finally {
store.close()
}
} finally {
rmSync(dir, { recursive: true, force: true })
}
})
// ── V3: vector and symbol contributions are separately attributable ──────
//
// Ported from Go `internal/memory/fusion.go:34-46`: `FusedHit` keeps `VectorHit`,
// `SymbolHit` and `Why` as separate facts, with 0 meaning "this path did not hit".
// V3 therefore requires the two numbers to vary INDEPENDENTLY — a single label
// (or one constant per shape) would make the three cases indistinguishable.
//
// Scope: attribution only. No symbol-only appending (V5), no topK reordering (V7).
function assertV3Attribution(recall, store) {
// `billing 端口` matches the Latin token `billing`, so the symbol path DOES hit
// (precondition for attribution) while no exact numeric run is present.
const query = 'billing 端口'
const plain = recall(store, query, { topK: 10 })
const symbolOnly = plain.find((hit) => hit.text === 'billing服务 端口=8499')
assert.ok(symbolOnly, 'precondition: the real billing block is in the result pool')
// (1) symbol-only: vector hit is exactly 0, symbol hit is not.
assert.equal(symbolOnly.vectorHit, 0, 'no vector score ⇒ VectorHit is 0 (go :38)')
assert.equal(symbolOnly.symbolHit, 1, 'the billing token actually matched: SymbolHit is 1')
// Existing CJK/lexical candidate, no high-information symbol match. Supply its
// vector score without appending a new record (candidate expansion remains V5).
const lexical = recall(store, '分机号', { topK: 5 })[0]
assert.ok(lexical, 'precondition: an existing lexical candidate is present')
const vectorOnly = recall(store, '分机号', {
topK: 5, vectorScores: new Map([[lexical.id, 0.9]]),
}).find((hit) => hit.id === lexical.id)
assert.equal(vectorOnly.vectorHit, 0.9, 'vector-only contribution comes from the supplied score')
assert.equal(vectorOnly.symbolHit, 0, 'no extracted symbol matched this lexical candidate')
assert.match(vectorOnly.why, /vector:0\.9;symbol:0;/)
// (2) exact hit: symbol score is the boolean 1.0 (go :300-305), still separate
// from whatever the vector says.
const exact = recall(store, '4324', { topK: 5 }).find((hit) => hit.exact === true)
assert.ok(exact, 'precondition: an exact hit exists')
assert.equal(exact.symbolHit, 1.0, 'exact hit carries SymbolHit 1.0')
assert.equal(exact.vectorHit, 0, 'and no vector score was supplied')
// (3) same record WITH a vector score: both non-zero, and the two numbers are
// different facts rather than one value copied into both fields.
const withVector = recall(store, query, {
topK: 10, vectorScores: new Map([[symbolOnly.id, 0.6]]),
}).find((hit) => hit.id === symbolOnly.id)
assert.equal(withVector.vectorHit, 0.6, 'VectorHit echoes the supplied vector score')
assert.equal(withVector.symbolHit, symbolOnly.symbolHit, 'SymbolHit is unchanged by the vector')
assert.notEqual(withVector.vectorHit, withVector.symbolHit, 'the two signals are distinct facts')
// (4) the attribution is readable in the existing `why` field.
assert.match(withVector.why, /vector:0\.6/, 'why carries the vector score')
assert.match(withVector.why, /symbol:/, 'why carries the symbol score')
}
test('V3: vector and symbol contributions are separately attributable', () => {
const store = seededStore()
try {
assertV3Attribution(recallBlocks, store)
} finally {
store.close()
}
})
for (const [name, anchor, replacement] of [
['symbol', ' const symbolHit = exact ? 1.0 : coverage', ' const symbolHit = vectorHit // MUTATED: collapsed signals'],
['vector', ' const vectorHit = vectorScore', ' const vectorHit = 0 // MUTATED: lost vector attribution'],
['why', '? `vector:${vectorHit};symbol:${symbolHit};', '? `opaque;'],
]) {
test(`V3† ${name}: the unchanged attribution criterion bites red`, async (t) => {
const { readFileSync, writeFileSync, mkdtempSync, rmSync, cpSync } = await import('node:fs')
const { tmpdir } = await import('node:os')
const { join } = await import('node:path')
const { fileURLToPath, pathToFileURL } = await import('node:url')
const libDir = fileURLToPath(new URL('../lib/', import.meta.url))
const pkgRoot = fileURLToPath(new URL('..', import.meta.url))
const dir = mkdtempSync(join(tmpdir(), 'mv3-'))
try {
cpSync(libDir, join(dir, 'lib'), { recursive: true })
cpSync(join(pkgRoot, 'package.json'), join(dir, 'package.json'))
const target = join(dir, 'lib', 'recall.js')
let source = readFileSync(target, 'utf8')
assert.equal(source.split(anchor).length - 1, 1, 'M-V3 anchor must occur exactly once')
source = source.replace(anchor, replacement)
writeFileSync(target, source)
const mod = await import(pathToFileURL(target).href)
const store = seededStore()
try {
assert.throws(() => assertV3Attribution(mod.recallBlocks, store), (err) => {
assert.equal(err.code, 'ERR_ASSERTION', 'only the criterion may bite')
t.diagnostic(`V3† ${name} RAW RED >>> ${String(err.message).split('\n')[0]}`)
return true
}, 'M-V3 must break the unchanged V3 criterion')
} finally {
store.close()
}
} finally {
rmSync(dir, { recursive: true, force: true })
}
})
}
// ── V5: MinScore applies PER PATH, never to the fused score ──────────────
//
// Ported from Go `internal/memory/fusion.go:541-554`. Three different scales in
// one fused number (exact pin ≥1.0, pure-symbol 0..0.3, pure-vector 0..0.7) is
// why Go applies the threshold per route and records the alternative as a bug
// (`:523-532`). The gate therefore keys on the CANDIDATE SOURCE, never on
// whether the `vectorHit` field happens to exist — every record carries it since
// V3, so existence-based branching would silently destroy the asymmetry.
function assertV5AsymmetricGate(recall, store) {
const query = 'billing 端口'
const plain = recall(store, query, { topK: 10 })
const billing = plain.find((hit) => hit.text === 'billing服务 端口=8499' && hit.exact === false)
assert.ok(billing, 'precondition: the billing record is retrievable')
assert.equal(billing.symbolHit, 1, 'precondition: it matched the symbol path (SymbolHit 1)')
const lexical = recall(store, '分机号', { topK: 5 })[0]
assert.ok(lexical, 'precondition: an existing lexical candidate exists')
// (a) SYMBOL route, no vector source: MinScore must NOT gate it. Go :549-553
// gates this branch on coverage (> 0), deliberately not on the vector threshold
// — otherwise a 1.0 symbol hit worth 0.3 fused would be killed by MinScore 0.5.
const symbolKept = recall(store, query, { topK: 10, minScore: 0.5, vectorScores: new Map() })
assert.ok(symbolKept.some((hit) => hit.id === billing.id),
'a symbol-only hit must survive MinScore 0.5 — the gate is not on the symbol scale')
// (b) VECTOR route: the threshold applies to the raw VECTOR score.
const vectorKept = recall(store, query, {
topK: 10, minScore: 0.5, vectorScores: new Map([[billing.id, 0.9]]),
})
assert.ok(vectorKept.some((hit) => hit.id === billing.id), 'vector 0.9 >= 0.5 must pass')
const vectorDropped = recall(store, query, {
topK: 10, minScore: 0.5, vectorScores: new Map([[billing.id, 0.1]]),
})
assert.equal(vectorDropped.some((hit) => hit.id === billing.id), false,
'vector 0.1 < 0.5 must be dropped by the VECTOR-side gate')
// (c) raw vector 0.81 passes 0.8, while its fused score with the existing
// CJK symbol score (0.75) is 0.792 < 0.8. The gate must use the raw score.
const belowFused = recall(store, '分机号', {
topK: 5, minScore: 0.8, vectorScores: new Map([[lexical.id, 0.81]]),
})
const belowFusedHit = belowFused.find((hit) => hit.id === lexical.id)
assert.ok(belowFusedHit, 'raw vector 0.81 >= 0.8 must pass even when fused score is below 0.8')
assert.ok(belowFusedHit.score < 0.8,
`precondition: fused ${belowFusedHit.score} is under the 0.8 threshold`)
// A lexical candidate without a vector source and without a SymbolScore hit
// has neither route: Go's default branch drops it, rather than treating the
// existence of the V3 attribution fields as evidence of a hit.
const neither = recall(store, '分机号', {
topK: 5, minScore: 0.8, vectorScores: new Map(),
})
assert.equal(neither.some((hit) => hit.id === lexical.id), false,
'no vector source and SymbolHit 0 must be dropped by the symbol coverage gate')
// (d) EXACT route: the pin is decisive, never gated.
const exactId = recall(store, '4324', { topK: 5 }).find((hit) => hit.exact === true)?.id
assert.ok(exactId, 'precondition: an exact hit exists')
const exactKept = recall(store, '4324', {
topK: 5, minScore: 0.9, vectorScores: new Map([[exactId, 0]]),
})
assert.ok(exactKept.some((hit) => hit.id === exactId), 'an exact hit is never filtered by MinScore')
// (e) minScore 0 disables only the VECTOR threshold; Go's default branch
// (symbol-only with coverage 0 is dropped, go :549-553) still runs.
const zero = recall(store, '分机号', { topK: 5, minScore: 0, vectorScores: new Map() })
assert.equal(zero.some((hit) => hit.id === lexical.id), false,
'a symbol-only record with SymbolHit 0 is dropped even when minScore is 0')
// (f) no threshold supplied => the result is untouched.
const noGate = recall(store, query, { topK: 10 })
assert.deepEqual(noGate.map((h) => h.id), plain.map((h) => h.id),
'minScore absent leaves the result unchanged')
}
test('V5: MinScore gates the vector route only, never the symbol or exact route', () => {
const store = seededStore()
try {
assertV5AsymmetricGate(recallBlocks, store)
} finally {
store.close()
}
})
// NOTE: these mutants load a mutated copy of lib/recall.js directly (the sibling
// helper `withMutatedCopy` lives in phase-b-h.test.js and is not in scope here).
async function v5Mutant(anchor, replacement, body) {
const { readFileSync, writeFileSync, mkdtempSync, rmSync, cpSync } = await import('node:fs')
const { tmpdir } = await import('node:os')
const { join } = await import('node:path')
const { fileURLToPath, pathToFileURL } = await import('node:url')
const libDir = fileURLToPath(new URL('../lib/', import.meta.url))
const pkgRoot = fileURLToPath(new URL('..', import.meta.url))
const dir = mkdtempSync(join(tmpdir(), 'mv5-'))
try {
cpSync(libDir, join(dir, 'lib'), { recursive: true })
cpSync(join(pkgRoot, 'package.json'), join(dir, 'package.json'))
const target = join(dir, 'lib', 'recall.js')
let source = readFileSync(target, 'utf8')
assert.equal(source.split(anchor).length - 1, 1, 'V5 mutation anchor must occur exactly once')
writeFileSync(target, source.replace(anchor, replacement))
await body(await import(pathToFileURL(target).href))
} finally {
rmSync(dir, { recursive: true, force: true })
}
}
for (const [name, anchor, replacement] of [
[
'vector-side',
` if (hasVectorScore && vectorScore > 0) {\n if (minScore > 0 && vectorScore < minScore) {`,
` if (false) {\n if (minScore > 0 && vectorScore < minScore) { // MUTATED: vector gate disabled`,
],
[
'symbol-side',
` } else if (!(symbolHit > 0)) {\n gatedNoEvidence += 1\n continue`,
` } else if (false) {\n gatedNoEvidence += 1\n continue // MUTATED: symbol-side gate removed`,
],
[
'symbol-threshold',
` if (hasVectorScore && vectorScore > 0) {`,
` if (hasVectorScore || symbolHit > 0) { // MUTATED: apply vector threshold to symbols`,
],
[
'fused-score',
` if (minScore > 0 && vectorScore < minScore) {`,
` if (score < minScore) { // MUTATED: gate on the fused score`,
],
]) {
test(`V5† ${name}: the unchanged asymmetric-gate criterion bites red`, async (t) => {
await v5Mutant(anchor, replacement, async (mod) => {
const store = seededStore()
try {
assert.throws(() => assertV5AsymmetricGate(mod.recallBlocks, store), (err) => {
assert.equal(err.code, 'ERR_ASSERTION', 'only the criterion may bite')
t.diagnostic(`V5† ${name} RAW RED >>> ${String(err.message).split('\n')[0]}`)
return true
}, `V5† ${name} must break the unchanged criterion`)
} finally {
store.close()
}
})
})
}
// ── V5b: append before scoring must preserve existing symbol/exact facts ──
//
// Ported from Go `internal/memory/fusion.go:384-408`. Go's comment (`:364-373`)
// records the &out[i]-after-append bug: symbol facts were written into the
// discarded backing array, so the score was never computed. JS objects are
// references, but the requirement survives verbatim — the symbol/exact facts
// must land on the record that SCORING reads. The criterion asserts VALUES on
// records that existed BEFORE the append, which is exactly the half that broke
// in Go.
function assertV5SymbolAdmission(recall, store) {
const primary = store.putBlock({ text: 'billing 4324 primary' })
const appended = store.putBlock({ text: 'billing appended' })
const pinned = store.putBlock({ text: '4324 appended exact' })
const options = { topK: 10, poolSize: 1, minScore: 0 }
// poolSize=1 leaves exactly ONE primary candidate, so the other two can only
// arrive through the symbol route.
const initial = recall(store, 'billing 4324', { topK: 10, poolSize: 1 })
assert.equal(initial.length, 1, 'precondition: one primary candidate')
const primaryId = initial[0].id
const primaryExact = initial[0].exact
const hits = recall(store, 'billing 4324', {
...options, vectorScores: new Map([[primaryId, 0.6]]),
})
// (1) admission happens at all.
assert.ok(hits.some((hit) => hit.id === appended.id), 'the word-only symbol record is admitted')
assert.ok(hits.some((hit) => hit.id === pinned.id), 'the numeric symbol record is admitted')
// (2) ★ the PRE-APPEND candidate keeps its computed symbol/exact facts.
const existing = hits.find((hit) => hit.id === primaryId)
assert.ok(existing, 'the original candidate survives the append')
assert.ok(existing.symbolHit > 0, 'existing candidate retains SymbolHit after append')
assert.equal(existing.exact, primaryExact, 'existing candidate retains ExactHit after append')
assert.ok(existing.score > 0, 'existing candidate score was actually computed')
// (3) appended records carry their own facts, written to the scored record.
const word = hits.find((hit) => hit.id === appended.id)
assert.equal(word.symbolHit, 0.5, 'one of two symbols matched the appended word record')
assert.equal(word.exact, false, 'a Latin symbol hit is not an exact hit')
assert.equal(word.score, 0.15, 'pure-symbol score is sym*w.Symbol (go :430-432)')
const exact = hits.find((hit) => hit.id === pinned.id)
assert.equal(exact.exact, true, 'appended numeric match retains ExactHit')
assert.equal(exact.symbolHit, 1, 'and SymbolHit is the boolean 1.0')
assert.equal(exact.score, 1, 'appended exact match stays pinned')
// (4) ★ admission scans raw blocks, so the F2/F4 filters must still apply.
const stale = store.putBlock({ text: 'billing stale-space 9999', vector: [1, 0, 0], fingerprint: 'fp-stale' })
const scoped = recall(store, 'billing 4324 9999', {
...options, fingerprint: 'fp-current', dimension: 3,
vectorScores: new Map([[primaryId, 0.6]]),
})
assert.equal(scoped.some((hit) => hit.id === stale.id), false,
'a mismatched-space block must not be admitted by the symbol route')
assert.ok(scoped.skipped + scoped.skippedDim >= 1, 'and the exclusion is counted')
}
test('V5b: append preserves existing candidates and admits symbol-only records', () => {
const store = new BlockStore({ path: ':memory:' }).open()
try {
assertV5SymbolAdmission(recallBlocks, store)
} finally {
store.close()
}
})
for (const [name, anchor, replacement] of [
['lost-symbol', ' symbolHit,\n why: vectorScores instanceof Map',
' symbolHit: 0, // MUTATED: detached symbol write\n why: vectorScores instanceof Map'],
['lost-exact', ' score,\n exact,\n vectorHit,',
' score,\n exact: false, // MUTATED: detached exact write\n vectorHit,'],
['admission-off', ' if (!symbolMatch && !vectorSource) continue',
' if (true) continue // MUTATED: no assembly'],
]) {
test(`V5b† ${name}: the unchanged append criterion bites red`, async (t) => {
await v5Mutant(anchor, replacement, async (mod) => {
const store = new BlockStore({ path: ':memory:' }).open()
try {
assert.throws(() => assertV5SymbolAdmission(mod.recallBlocks, store), (err) => {
assert.equal(err.code, 'ERR_ASSERTION', 'only the criterion may bite')
t.diagnostic(`V5b† ${name} RAW RED >>> ${String(err.message).split('\n')[0]}`)
return true
}, `V5b† ${name} must break the unchanged criterion`)
} finally {
store.close()
}
})
})
}
// ── V4 (b): a self-built tier-3 fixture the vector route can actually flip ──
//
// E2E-1 ruling: checkpoint V2 keeps its own assertions and its "known gap"
// output verbatim — the flip is proven HERE, by a new case, not by rewriting V2.
//
// ★ What this case does and does NOT claim (stated so it cannot be over-read):
// · it uses a REAL store (blocks written through putBlock) and the REAL recall
// scoring path (`recallBlocks` with a supplied `vectorScores` map);
// · the vectors are a deterministic fixture, so this proves the MECHANISM —
// a tier-3 style query (no extracted symbol, no lexical overlap) becomes
// retrievable once a vector candidate is in the pool;
// · it does NOT prove model-grade semantic generalisation (that needs a real
// embedder and cannot be pinned in a unit test), and it does not wire the
// embedder into `recallBlocksHybrid` — that seam is still A3c's
// `vector_ready_unused` and stays untouched in this slice.
const TIER3 = {
// Verbatim shape of checkpoint V2's known gap: 纯泛化描述, no symbol, and the
// target block shares NO token with the query.
query: '公网地址是什么',
blocks: [
{ text: '对外的访问入口 10.0.0.7:3000', source: 'v4' },
{ text: '监控面板 http://10.0.0.7:3000', source: 'v4' },
{ text: '财务部门 负责人=员工100', source: 'v4' },
],
// Deterministic "embedding": the first block is semantically nearest.
queryVector: [1, 0, 0],
vectorFor: (index) => (index === 0 ? [0.95, 0.31, 0] : [0.1, 1, 0]),
}
function cosine(a, b) {
let dot = 0
let na = 0
let nb = 0
for (let i = 0; i < a.length; i += 1) {
dot += a[i] * b[i]
na += a[i] * a[i]
nb += b[i] * b[i]
}
return dot / (Math.sqrt(na) * Math.sqrt(nb))
}
function tier3Store() {
const store = new BlockStore({ path: ':memory:' }).open()
const ids = TIER3.blocks.map((block, index) => store.putBlock({
...block, vector: TIER3.vectorFor(index), fingerprint: 'v4-fixture',
}).id)
return { store, ids }
}
/** The criterion, shared by the positive case and every mutant. */
function assertTier3Flipped(recall) {
const { store, ids } = tier3Store()
try {
// (1) The gap is real on the symbol/lexical path alone: nothing is recalled.
const symbolOnly = recall(store, TIER3.query, { topK: 3 })
assert.equal(symbolOnly.length, 0,
'precondition: the tier-3 query matches no block by symbol or lexical overlap')
// (2) With vector candidates, the semantically nearest block is recalled.
const vectorScores = new Map(store.allBlocks(-1).map((block) => [
block.id, cosine(TIER3.queryVector, JSON.parse(block.vector)),
]))
const withVectors = recall(store, TIER3.query, { topK: 1, minScore: 0, vectorScores })
assert.equal(withVectors.length, 1, 'the vector route must surface exactly one block at topK 1')
assert.equal(withVectors[0].id, ids[0], 'and it is the semantically nearest block, not a lexical accident')
assert.equal(withVectors[0].text, TIER3.blocks[0].text)
// (3) The flipped hit is attributable to the vector route, not to a symbol hit.
assert.equal(withVectors[0].symbolHit, 0, 'no symbol matched — the flip is purely the vector route')
assert.ok(withVectors[0].vectorHit > 0.9, 'the vector contribution is what carried it')
assert.equal(withVectors[0].exact, false, 'and it is not an exact pin')
} finally {
store.close()
}
}
test('V4: a self-built tier-3 case flips from no-hit to recalled with stored vectors', () => {
assertTier3Flipped(recallBlocks)
})
for (const [name, anchor, replacement] of [
[
'symbol-selection',
' if (!symbolMatch && !vectorSource) continue',
' if (!symbolMatch) continue // MUTATED: vector candidates never admitted',
],
[
'vector-ranking',
' scored.sort((a, b) => (b.score === a.score ? a.id.localeCompare(b.id) : b.score - a.score))',
' scored.sort((a, b) => (b.score === a.score ? a.id.localeCompare(b.id) : a.score - b.score)) // MUTATED: ascending',
],
[
'vector-attribution',
' const vectorHit = vectorScore',
' const vectorHit = 0 // MUTATED: attribution drops the vector contribution',
],
]) {
test(`V4† ${name}: the unchanged tier-3 flip criterion bites red`, async (t) => {
await v5Mutant(anchor, replacement, async (mod) => {
assert.throws(() => assertTier3Flipped(mod.recallBlocks), (err) => {
assert.equal(err.code, 'ERR_ASSERTION', 'only the criterion may bite')
t.diagnostic(`V4† ${name} RAW RED >>> ${String(err.message).split('\n')[0]}`)
return true
}, `V4† ${name} must break the unchanged criterion`)
})
})
}
// ── V7: the vector route is bounded by largeTopK, not by the caller's topK ──
//
// Ported from Go `internal/memory/fusion.go:466-468` (`q2.TopK = largeTopK`: the
// recall read is widened BEFORE fusion) and `internal/memory/block_recall.go:263`
// (`const largeTopK = 2000`). The caller's topK is applied exactly once, at the
// end (`fusion.go:557-559` · `block_recall.go:176-178`).
//
// ★ Three separate criteria, because three separate things can go wrong:
// (a) the vector pool must not be cut to a topK-sized window before fusion;
// (b) the bound must really be 2000 — measured, not just read from the source;
// (c) the caller's topK must still be honoured at the end (widening ≠ leaking).
// Each has its own mutant, so no mutant can hide behind another's failure.
/** (a) A vector candidate the route ranks LAST must still reach fusion and win. */
function assertV7NoEarlyVectorCut(recall, store) {
// Symbol coverage 1 beats the higher-vector noise (coverage 1/3) only after
// fusion: target=0.6*0.7+1*0.3=0.72; noise<=0.8*0.7+(1/3)*0.3=0.66.
const target = store.putBlock({ text: 'alpha beta gamma', source: 'v7' }).id
const others = []
for (let i = 0; i < 5; i += 1) others.push(store.putBlock({ text: `alpha noise ${i}`, source: 'v7' }).id)
const scores = new Map([[target, 0.6], ...others.map((id, i) => [id, 0.8 - i * 0.01])])
const hits = recall(store, 'alpha beta gamma', { topK: 1, minScore: 0, vectorScores: scores })
assert.equal(hits.length >= 1 && hits[0].id, target,
'the worst-scoring vector candidate must still reach fusion (largeTopK, not topK)')
}
/** (b) 2100 vector candidates ⇒ exactly 2000 survive the bound (measured). */
function assertV7LargeTopKBound(recall, store) {
const ids = []
for (let i = 0; i < 2100; i += 1) ids.push(store.putBlock({ text: `V7 bulk ${i}`, source: 'v7' }).id)
const scores = new Map(ids.map((id, index) => [id, (index + 1) / 2100]))
const hits = recall(store, '公网地址是什么', { topK: 2100, minScore: 0, vectorScores: scores })
assert.equal(hits.length, 2000, 'the vector route is bounded at largeTopK = 2000')
// The 100 lowest-scored candidates fall outside the bound and cannot appear.
const dropped = new Set(ids.slice(0, 100))
assert.equal(hits.some((hit) => dropped.has(hit.id)), false,
'candidates ranked below the 2000th must not survive the bound')
}
/** (c) The caller's topK is applied once, after fusion. */
function assertV7FinalTruncation(recall, store) {
for (let i = 0; i < 6; i += 1) store.putBlock({ text: `V7 trunc probe ${i}`, source: 'v7' })
assert.equal(recall(store, 'V7 trunc probe', { topK: 2, minScore: 0, vectorScores: new Map() }).length, 2,
'topK 2 must return exactly 2 rows after widening')
assert.equal(recall(store, 'V7 trunc probe', { topK: 4, minScore: 0, vectorScores: new Map() }).length, 4,
'topK 4 must return exactly 4 rows after widening')
assert.equal(recall(store, 'V7 trunc probe', { topK: 2 }).length, 2,
'the non-fused path still honours topK')
}
test('V7: the vector route is widened before fusion and truncated once at the end', () => {
const store = new BlockStore({ path: ':memory:' }).open()
try {
assertV7NoEarlyVectorCut(recallBlocks, store)
} finally {
store.close()
}
const wide = new BlockStore({ path: ':memory:' }).open()
try {
assertV7LargeTopKBound(recallBlocks, wide)
} finally {
wide.close()
}
const tail = new BlockStore({ path: ':memory:' }).open()
try {
assertV7FinalTruncation(recallBlocks, tail)
} finally {
tail.close()
}
})
test('V7: largeTopK is the Go constant 2000, read from source', async () => {
const { readFileSync } = await import('node:fs')
const source = readFileSync(new URL('../lib/recall.js', import.meta.url), 'utf8')
const match = source.match(/const largeTopK = (\d+)/)
assert.ok(match, 'largeTopK must exist in lib/recall.js')
assert.equal(Number(match[1]), 2000, 'ported verbatim from block_recall.go:263')
})
/** Copy lib/, mutate one anchor with a uniqueness self-check, then import. */
async function withMutatedCopy(anchor, replacement, body) {
const { readFileSync, writeFileSync, mkdtempSync, rmSync, cpSync } = await import('node:fs')
const { tmpdir } = await import('node:os')
const { join } = await import('node:path')
const { fileURLToPath, pathToFileURL } = await import('node:url')
const libDir = fileURLToPath(new URL('../lib/', import.meta.url))
const pkgRoot = fileURLToPath(new URL('..', import.meta.url))
const dir = mkdtempSync(join(tmpdir(), 'mv7-'))
try {
cpSync(libDir, join(dir, 'lib'), { recursive: true })
cpSync(join(pkgRoot, 'package.json'), join(dir, 'package.json'))
const target = join(dir, 'lib', 'recall.js')
let source = readFileSync(target, 'utf8')
assert.equal(source.split(anchor).length - 1, 1, 'V7 mutation anchor must occur exactly once')
writeFileSync(target, source.replace(anchor, replacement))
await body(await import(pathToFileURL(target).href))
} finally {
rmSync(dir, { recursive: true, force: true })
}
}
for (const [name, anchor, replacement, criterion] of [
[
'vector-pool-truncated',
'.slice(0, largeTopK).map(([id]) => id))',
'.slice(0, topK).map(([id]) => id)) // MUTATED: vector pool cut before fusion',
assertV7NoEarlyVectorCut,
],
[
'largeTopK-shrunk',
'const largeTopK = 2000',
'const largeTopK = 100 // MUTATED: bound shrunk below Go largeTopK',
assertV7LargeTopKBound,
],
[
'result-not-truncated',
' return withSkipped(scored.slice(0, topK), skipped, skippedDim, drops)',
' return withSkipped(scored, skipped, skippedDim, drops) // MUTATED: caller topK never applied',
assertV7FinalTruncation,
],
]) {
test(`V7† ${name}: the unchanged criterion bites red`, async (t) => {
await withMutatedCopy(anchor, replacement, async (mod) => {
t.diagnostic(`V7† ${name} anchor split count === 1: ${anchor}`)
const store = new BlockStore({ path: ':memory:' }).open()
try {
let failure = null
try {
criterion(mod.recallBlocks, store)
} catch (error) {
failure = error
}
assert.ok(failure !== null, `V7† ${name} must break the unchanged criterion`)
assert.equal(failure.code, 'ERR_ASSERTION', 'only the criterion may bite')
t.diagnostic(`V7† ${name} RAW RED >>> ${String(failure.message).split('\n')[0]}`)
} finally {
store.close()
}
})
})
}