From 0cdc3dfe39be3091f2e0955191bedd0488cdc2f8 Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Wed, 14 Oct 2026 05:00:00 +0000 Subject: [PATCH] fix(lbug): bump @ladybugdb/core to 0.21.2 to stop doubled rows after delete and re-copy LadybugDB 0.21.0 and 0.21.1 return every row of a table that spans more than one native vector twice once the incremental writeback has DETACH DELETEd it and a COPY into an earlier table has run. NodeGroup::scan let a morsel that ended before the group's first live row wrap an unsigned subtraction and rescan to the end of the group (LadybugDB #1085, fixed in 0.21.2). No primary key error fires. The identity guard from #3442 stops the run with "unexpected or duplicate ID", so a Swift edit that pulls every importer into the write set failed on repos with more than 2048 structs. Only unreleased main was affected: 1.6.12 ships 0.18.3. Pin 0.21.2 in package.json, update the six @ladybugdb/core entries in the lockfile, and move coreVersion in the vendored FTS manifest. The FTS extension stays at 0.21.0 because no v0.21.1 or v0.21.2 artifact is published; the pairing assertion and its unit tests pass. Add a regression to the delete-nodes-for-files suite: 2049 structs with CONTAINS edges are loaded, every file is deleted, the graph is loaded again, and each id and edge must appear exactly once. Verification (Linux, Node 22.18): - lbug-delete-nodes-for-files: 11/11 on 0.21.2. With the 0.21.1 native packages swapped in, the new test fails with 4098 rows instead of 2049. - Reporter recipe end to end on the built CLI: incremental analyze exits 0 with no duplicate ids on a Swift-heavy fixture and on a 70-file Python fixture, with and without FTS. On 0.21.1 the Swift fixture exits 1. - A 0.21.1-written database opens and reads under 0.21.2. - prettier, eslint and typecheck:tests are clean on the changed files. - The full lbug-db project had not finished when this was committed. Co-Authored-By: Claude Sonnet 5.5 --- gitnexus/package-lock.json | 48 +++++------ gitnexus/package.json | 2 +- .../lbug-delete-nodes-for-files.test.ts | 79 +++++++++++++++++++ gitnexus/vendor/lbug-fts/manifest.json | 2 +- 4 files changed, 105 insertions(+), 26 deletions(-) diff --git a/gitnexus/package-lock.json b/gitnexus/package-lock.json index b9a2b9dd6..f226aadf2 100644 --- a/gitnexus/package-lock.json +++ b/gitnexus/package-lock.json @@ -23,7 +23,7 @@ "hasInstallScript": true, "license": "PolyForm-Noncommercial-1.0.0", "dependencies": { - "@ladybugdb/core": "0.21.1", + "@ladybugdb/core": "0.21.2", "@modelcontextprotocol/sdk": "^1.0.0", "@scarf/scarf": "^1.4.0", "busboy": "^1.6.0", @@ -737,9 +737,9 @@ } }, "node_modules/@ladybugdb/core": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@ladybugdb/core/-/core-0.21.1.tgz", - "integrity": "sha512-G0LWyn/dIqX9yHlyjpR12Rvou2o1dvCB9EkuzGa5ykCYbzur8EQhRUbi4fw3FlbckWvR6rK/tVbnFpBcRLwiHg==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@ladybugdb/core/-/core-0.21.2.tgz", + "integrity": "sha512-pnD1p5AaxEdmqJsLtF6bYuSmbe+pHzmaxDPNhROFOx+f7eE/AUPJFhW+rebMD6PQxK40B2NhqXbdsPCJ+FLuxg==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -748,17 +748,17 @@ "node-addon-api": "^6.0.0" }, "optionalDependencies": { - "@ladybugdb/core-darwin-arm64": "0.21.1", - "@ladybugdb/core-darwin-x64": "0.21.1", - "@ladybugdb/core-linux-arm64": "0.21.1", - "@ladybugdb/core-linux-x64": "0.21.1", - "@ladybugdb/core-win32-x64": "0.21.1" + "@ladybugdb/core-darwin-arm64": "0.21.2", + "@ladybugdb/core-darwin-x64": "0.21.2", + "@ladybugdb/core-linux-arm64": "0.21.2", + "@ladybugdb/core-linux-x64": "0.21.2", + "@ladybugdb/core-win32-x64": "0.21.2" } }, "node_modules/@ladybugdb/core-darwin-arm64": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@ladybugdb/core-darwin-arm64/-/core-darwin-arm64-0.21.1.tgz", - "integrity": "sha512-TEFYNqBdbIojf1t29p3esgRhMDm56lD5eK91dwWFZJTVWAZoGXn+yLKs0NUQgj3lW2ZquuRTLZGglPhFEsDL1Q==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@ladybugdb/core-darwin-arm64/-/core-darwin-arm64-0.21.2.tgz", + "integrity": "sha512-xJrPBfdh+i51SVuPlj0qgve2HQloCqPomMh01RHKpRKl/pmrwj7dU+wnK+PDVb1OCAq5MAjt8SOiChvY89Wfiw==", "cpu": [ "arm64" ], @@ -769,9 +769,9 @@ ] }, "node_modules/@ladybugdb/core-darwin-x64": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@ladybugdb/core-darwin-x64/-/core-darwin-x64-0.21.1.tgz", - "integrity": "sha512-w9p3oKrqeMOeWpX3xdCc/hQJHfrZtpB9We+Ir+0qyls68dSQoeXXR1GZYzzTElOtxr4OM5USwYbVyxADx+CMZg==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@ladybugdb/core-darwin-x64/-/core-darwin-x64-0.21.2.tgz", + "integrity": "sha512-zF+iONU8WOtzj9OLNr1CewQMcoJ+gwO3TJ0H4Lz+TA9D7JEdcTiHcdIQD4HQoJKEnKOwhL33MkWBdst8SfcV0g==", "cpu": [ "x64" ], @@ -782,9 +782,9 @@ ] }, "node_modules/@ladybugdb/core-linux-arm64": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@ladybugdb/core-linux-arm64/-/core-linux-arm64-0.21.1.tgz", - "integrity": "sha512-TodWmzOHxmmKvXXo9buBXGeyolxf/NqBUbo/LcR/tuMViKhGH8TBzIXi8DfVVwmsJ+CQCU065TvXgv2JoHAlZg==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@ladybugdb/core-linux-arm64/-/core-linux-arm64-0.21.2.tgz", + "integrity": "sha512-t/Z2ESuwx6RaEruZzGFrPHX19PEz5vKRVCm5fmSr85DqLd8/W5KWcw4f/RUpQtHdU2gdYLKkBur8yEpn36wNZA==", "cpu": [ "arm64" ], @@ -795,9 +795,9 @@ ] }, "node_modules/@ladybugdb/core-linux-x64": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@ladybugdb/core-linux-x64/-/core-linux-x64-0.21.1.tgz", - "integrity": "sha512-hCcLYv8ds0x4fkTaRkuc4f75KLdH9e0EV51ne6uBNdQSY7kyc0RgSoUG3utUGfFDMMkfgIJAeIxQY4ck9jQiXg==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@ladybugdb/core-linux-x64/-/core-linux-x64-0.21.2.tgz", + "integrity": "sha512-U4cAfkmPjixDURzyrkYG9NTzjEJpitaTOOATGWLTl78coc3oLpIil71wN+y2e0UuM4yd7450iLTyzL2QFzUazg==", "cpu": [ "x64" ], @@ -808,9 +808,9 @@ ] }, "node_modules/@ladybugdb/core-win32-x64": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@ladybugdb/core-win32-x64/-/core-win32-x64-0.21.1.tgz", - "integrity": "sha512-nBS1XSRJfpexdlhkezmEiRtkUt2qEDp4zBjIhH+oxgRdD7UsRz1Jyit8foJuZAmUsoQGukN5LO+YeyDU2WZTyA==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@ladybugdb/core-win32-x64/-/core-win32-x64-0.21.2.tgz", + "integrity": "sha512-O4rQve2RcXPbEPv222N2OwM5p6TgWYeQ0vT+BlhBfo8cOxvFrXkI45gl6qbH5tKlBdGDLV2ijKyrb1hoAFx4Zg==", "cpu": [ "x64" ], diff --git a/gitnexus/package.json b/gitnexus/package.json index be3ad56f9..992a891e0 100644 --- a/gitnexus/package.json +++ b/gitnexus/package.json @@ -67,7 +67,7 @@ "version": "node scripts/sync-plugin-manifests.mjs" }, "dependencies": { - "@ladybugdb/core": "0.21.1", + "@ladybugdb/core": "0.21.2", "@modelcontextprotocol/sdk": "^1.0.0", "@scarf/scarf": "^1.4.0", "busboy": "^1.6.0", diff --git a/gitnexus/test/integration/lbug-delete-nodes-for-files.test.ts b/gitnexus/test/integration/lbug-delete-nodes-for-files.test.ts index 5686ace62..dffed4eda 100644 --- a/gitnexus/test/integration/lbug-delete-nodes-for-files.test.ts +++ b/gitnexus/test/integration/lbug-delete-nodes-for-files.test.ts @@ -566,3 +566,82 @@ withTestLbugDB('embedding-row-dml-vector-gate', (handle) => { }, 120_000); }); }); + +/** + * Delete-all-then-recopy of a table that spans more than one native vector. + * + * @ladybugdb/core 0.21.0 and 0.21.1 returned every row of such a table twice + * once the incremental writeback had DETACH DELETEd it and a COPY into an + * earlier table had run: `NodeGroup::scan` let a morsel that ended before the + * group's first live row wrap an unsigned subtraction and rescan to the end of + * the group (LadybugDB #1085, fixed in 0.21.2). No primary-key error fires, so + * the only symptom was `Graph identity reconciliation failed … duplicate ID` + * on repos with more than 2048 symbols of one label (Swift structs in the + * report that found it), and 2×-inflated counts for anything that scanned. + * + * The statement order below is the one `runFullAnalysisInner` issues: delete the + * write set's rows, then COPY File before the symbol tables. + */ +withTestLbugDB('delete-recopy-multi-vector-table', (handle) => { + describe('deleteNodesForFiles then loadGraphToLbug on a multi-vector table', () => { + // One native vector holds 2048 rows; 2049 is the smallest table that spans two. + const STRUCT_COUNT = 2049; + const STRUCTS_PER_FILE = 64; + const fileOf = (i: number): string => `Sources/S${Math.floor(i / STRUCTS_PER_FILE)}.swift`; + + const buildStructGraph = () => { + const nodes: TestNodeInput[] = []; + const rels: TestRelInput[] = []; + const files = new Set(); + for (let i = 0; i < STRUCT_COUNT; i++) { + const fp = fileOf(i); + if (!files.has(fp)) { + files.add(fp); + nodes.push({ id: `File:${fp}`, label: 'File', name: path.basename(fp), filePath: fp }); + } + const id = `Struct:${fp}:S${i}`; + nodes.push({ + id, + label: 'Struct', + name: `S${i}`, + filePath: fp, + startLine: 1, + endLine: 3, + }); + rels.push({ sourceId: `File:${fp}`, targetId: id, type: 'CONTAINS' }); + } + return { graph: buildTestGraph(nodes, rels), filePaths: [...files] }; + }; + + it('returns each struct and its edge exactly once after the rows were deleted and re-copied', async () => { + const { loadGraphToLbug, deleteNodesForFiles, executeQuery } = + await import('../../src/core/lbug/lbug-adapter.js'); + const storagePath = path.dirname(handle.dbPath); + const structIds = async (): Promise => + ((await executeQuery('MATCH (n:Struct) RETURN n.id AS id')) as Array<{ id: string }>).map( + (r) => String(r.id), + ); + const containsEdges = async (): Promise => { + const rows = (await executeQuery( + `MATCH (:File)-[r:CodeRelation]->(:Struct) WHERE r.type = 'CONTAINS' RETURN count(r) AS c`, + )) as Array<{ c: number | bigint }>; + return Number(rows[0]?.c ?? 0); + }; + + const { graph, filePaths } = buildStructGraph(); + await loadGraphToLbug(graph, '/tmp/repo', storagePath); + expect(new Set(await structIds()).size).toBe(STRUCT_COUNT); + + // Every file is in the write set, as when a Swift edit pulls in all importers. + await deleteNodesForFiles(filePaths); + expect(await structIds()).toEqual([]); + + await loadGraphToLbug(graph, '/tmp/repo', storagePath); + + const ids = await structIds(); + expect(ids).toHaveLength(STRUCT_COUNT); + expect(new Set(ids).size).toBe(STRUCT_COUNT); + expect(await containsEdges()).toBe(STRUCT_COUNT); + }, 120_000); + }); +}); diff --git a/gitnexus/vendor/lbug-fts/manifest.json b/gitnexus/vendor/lbug-fts/manifest.json index 62c4da0c7..21e8d8b08 100644 --- a/gitnexus/vendor/lbug-fts/manifest.json +++ b/gitnexus/vendor/lbug-fts/manifest.json @@ -1,5 +1,5 @@ { - "coreVersion": "0.21.1", + "coreVersion": "0.21.2", "extensionVersion": "0.21.0", "filename": "libfts.lbug_extension", "officialRepo": "https://extension.ladybugdb.com/",