/** * @module tests/integration/null-metadata-delete * @description The null-metadata delete skip is CLOSED. remove() used to * guard its index legs with `if (metadata)` — a row whose canonical * metadata was unreadable at delete time (torn, or a leg lost to an old * defect) kept its postings FOREVER, silently. Now: the JS index gets an * id-keyed cleanup (deleted bitmap + id mapper), the id-keyed native * contract is used when a provider offers it, and the one remaining * skip-shape (native without the contract) is narrated and tracked, never * silent. Pinned: a metadata-less row with live postings deletes cleanly * and leaves the query universe. */ import { describe, it, expect, afterEach } from 'vitest' import { mkdtempSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { Brainy } from '../../src/index.js' import { NounType } from '../../src/types/graphTypes.js' type RawBox = { storage: { readNounRaw(id: string): Promise<{ metadata: unknown; vector: unknown }> writeNounRaw(id: string, r: { metadata: unknown; vector: unknown }): Promise } } const dirs: string[] = [] const brains: Brainy[] = [] afterEach(async () => { for (const b of brains.splice(0)) await b.close().catch(() => {}) for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true }) }) describe('null-metadata delete', () => { it('a row whose metadata leg is gone still deletes — id-keyed cleanup, no silent skip, gone from the query universe', async () => { const dir = mkdtempSync(join(tmpdir(), 'brainy-nullmeta-del-')) dirs.push(dir) const brain = new Brainy({ storage: { type: 'filesystem', path: dir }, requireSubtype: false, silent: true }) await brain.init() brains.push(brain) const keep = await brain.add({ data: 'survivor', type: NounType.Document, metadata: { team: 'atlas' } }) const victim = await brain.add({ data: 'doomed', type: NounType.Document, metadata: { team: 'atlas' } }) await brain.flush() expect((await brain.find({ where: { team: 'atlas' } })).length).toBe(2) // Manufacture the shape: the victim's metadata leg vanishes behind the // engine's back (vector leg + postings stay live). const storage = (brain as unknown as RawBox).storage const raw = await storage.readNounRaw(victim) await storage.writeNounRaw(victim, { metadata: null, vector: raw.vector }) // THE PIN: the delete neither throws nor silently strands postings. await brain.remove(victim) await brain.flush() const after = await brain.find({ where: { team: 'atlas' } }) expect(after.length, 'victim left the query universe; survivor serves').toBe(1) expect(after[0].id).toBe(keep) expect(await brain.get(victim)).toBeNull() }, 120000) })