Files
HologramAgent-DSH-Memory-Port/test/phase-b-a.test.js

549 lines
25 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 (group A: vector computed, not yet used) ──────
//
// The earlier 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. It matters because group V must distinguish "vector computed
// but not in scoring" (group A) from "vector actually participating" (group V);
// if both say 'hybrid', nobody can tell which build they are looking at.
test('A3c: a healthy embedder reports "vector_ready_unused", NOT "hybrid"', 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,
'vector_ready_unused',
'group A computes the vector but does not score with it — the label must say that, ' +
'because group V will need a different label once vectors really participate',
)
assert.notEqual(hits.degraded, 'hybrid', "calling it 'hybrid' would be a lie until group V lands")
// And the result is still the pure-symbol ordering.
assert.equal(hits[0].text.includes('4324'), true)
} finally {
store.close()
}
})
test('A3c mutation: labelling the success path "hybrid" must fail', () => {
// Mirrors the exact one-line edit that used to slip through: with no assertion
// on this value, reverting the rename was invisible.
const LABELS = { groupA: 'vector_ready_unused', groupV: 'hybrid' }
// The test above asserts hits.degraded === LABELS.groupA. Prove that swapping
// the constant would break it:
assert.notEqual(LABELS.groupA, LABELS.groupV)
const simulatedDeployed = LABELS.groupV // what the mutated lib returns
assert.throws(
() => assert.equal(simulatedDeployed, LABELS.groupA),
(err) => err.code === 'ERR_ASSERTION',
'if lib returns "hybrid", the A3c assertion must throw',
)
})
// ── 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 })
}
})
}