From e814e28f1f428b7e236f154d5177939a589f4bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20Magyar?= Date: Wed, 22 Jul 2026 16:31:56 +0100 Subject: [PATCH] =?UTF-8?q?fix(deps):=20bump=20@ladybugdb/core=20to=20^0.1?= =?UTF-8?q?8.3=20=E2=80=94=20rel-property=20IN-predicate=20fix=20(#2508)?= =?UTF-8?q?=20(#2634)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LadybugDB ≤0.18.2 mis-evaluated `r.type IN [...]` on relationship table groups: the boolean-filter fallback skipped writing selection buffers for single-row unflat chunks, dropping/duplicating callers in context() and impact() (upstream LadybugDB#692, fixed by LadybugDB#699, shipped in 0.18.3). Floor the dependency at ^0.18.3 and lock core + all five platform packages. Resurrect the caller-identity regression test from PR #2553 (closed as superseded by the upstream fix): it pins context()/impact() to exact caller IDs across CodeRelation sub-table pairs so any future predicate regression fails loudly. Note: with CREATE-seeded data the test also passes on 0.18.2 (the upstream repro needs COPY-written chunk layouts) — it is a behavioural pin, not a bug reproduction. Fixes #2508 Co-authored-by: Claude Fable 5 --- gitnexus/package-lock.json | 48 ++++---- gitnexus/package.json | 2 +- .../caller-identity-regression.test.ts | 109 ++++++++++++++++++ 3 files changed, 134 insertions(+), 25 deletions(-) create mode 100644 gitnexus/test/integration/caller-identity-regression.test.ts diff --git a/gitnexus/package-lock.json b/gitnexus/package-lock.json index e8b4557e8..7b1d22ad1 100644 --- a/gitnexus/package-lock.json +++ b/gitnexus/package-lock.json @@ -10,7 +10,7 @@ "hasInstallScript": true, "license": "PolyForm-Noncommercial-1.0.0", "dependencies": { - "@ladybugdb/core": "^0.18.0", + "@ladybugdb/core": "^0.18.3", "@modelcontextprotocol/sdk": "^1.0.0", "@scarf/scarf": "^1.4.0", "busboy": "^1.6.0", @@ -1252,9 +1252,9 @@ } }, "node_modules/@ladybugdb/core": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/@ladybugdb/core/-/core-0.18.2.tgz", - "integrity": "sha512-222FjGciEO5Z+/MRQGU+b4IaGAjOgSQzj7fMpOuhMQN4F8nf654kuKRk1iybSiNy6XSw69hIJ0mKwUeBQ8y6Fg==", + "version": "0.18.3", + "resolved": "https://registry.npmjs.org/@ladybugdb/core/-/core-0.18.3.tgz", + "integrity": "sha512-XjpPKW4MrL28D2gYGTZuIjiEcPx12L21lx58QggrdrItw8o/e9Lmg/Ejoo4Kz08lZj+rIcC1Fu9thzIYOTUlJw==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -1263,17 +1263,17 @@ "node-addon-api": "^6.0.0" }, "optionalDependencies": { - "@ladybugdb/core-darwin-arm64": "0.18.2", - "@ladybugdb/core-darwin-x64": "0.18.2", - "@ladybugdb/core-linux-arm64": "0.18.2", - "@ladybugdb/core-linux-x64": "0.18.2", - "@ladybugdb/core-win32-x64": "0.18.2" + "@ladybugdb/core-darwin-arm64": "0.18.3", + "@ladybugdb/core-darwin-x64": "0.18.3", + "@ladybugdb/core-linux-arm64": "0.18.3", + "@ladybugdb/core-linux-x64": "0.18.3", + "@ladybugdb/core-win32-x64": "0.18.3" } }, "node_modules/@ladybugdb/core-darwin-arm64": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/@ladybugdb/core-darwin-arm64/-/core-darwin-arm64-0.18.2.tgz", - "integrity": "sha512-gAwxsdijBFTz4aZ9ITG6zdQw3lAki0eY33hNBLCfKXjKJLvW/8wCvgCVBglqfqBF5WyI7icFUt+wfy/Fbdfl5A==", + "version": "0.18.3", + "resolved": "https://registry.npmjs.org/@ladybugdb/core-darwin-arm64/-/core-darwin-arm64-0.18.3.tgz", + "integrity": "sha512-DGZTOlvSS4esEb1vTekY5IDoAvZAeYzR5cXVkECtQj9BVkk05zsvCAdTPo1Rz1BuI0qvqUVF+2WlIerI67iA2g==", "cpu": [ "arm64" ], @@ -1284,9 +1284,9 @@ ] }, "node_modules/@ladybugdb/core-darwin-x64": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/@ladybugdb/core-darwin-x64/-/core-darwin-x64-0.18.2.tgz", - "integrity": "sha512-oUjYLc1fW3ntCrO9te55PoPfvhFo8AKeNa/sU66fiQyEAZB7qZJxeHnnLgl/bLueTF2os3RSawq46ZftoD/9Eg==", + "version": "0.18.3", + "resolved": "https://registry.npmjs.org/@ladybugdb/core-darwin-x64/-/core-darwin-x64-0.18.3.tgz", + "integrity": "sha512-Qp6j0CM/orBlK6KD0p/s4ofkIhNUwi1hdCgMw+fj81UHugWHkVLiYV4grRBdHhyplw+snchZpTxvfpxFbkG1Cw==", "cpu": [ "x64" ], @@ -1297,9 +1297,9 @@ ] }, "node_modules/@ladybugdb/core-linux-arm64": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/@ladybugdb/core-linux-arm64/-/core-linux-arm64-0.18.2.tgz", - "integrity": "sha512-UppokeTaPl9pN0xOsdMa+hmM68zbN2eKReTZhZNYM16qX0d2OlgbS/NlXi09Wdot+w5qvlZ9Q0iCCPfr7qvPaw==", + "version": "0.18.3", + "resolved": "https://registry.npmjs.org/@ladybugdb/core-linux-arm64/-/core-linux-arm64-0.18.3.tgz", + "integrity": "sha512-F9miYjBuS43I7uNG199FNMqwdHJ98WA6dU3v2SZCeLXmXCdRzmYcuHQWlbNr2Tba9CX58w2XvBZoUaXZKJ/yKQ==", "cpu": [ "arm64" ], @@ -1310,9 +1310,9 @@ ] }, "node_modules/@ladybugdb/core-linux-x64": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/@ladybugdb/core-linux-x64/-/core-linux-x64-0.18.2.tgz", - "integrity": "sha512-GypOxCnP2ix/FWM8YhQ41aQYlS+ruoNMJp7pmaF5laJHhL/a+P/apywNTE+9N41Walsl+Emgg9xwxwTC93slow==", + "version": "0.18.3", + "resolved": "https://registry.npmjs.org/@ladybugdb/core-linux-x64/-/core-linux-x64-0.18.3.tgz", + "integrity": "sha512-AfG5RDp/f/IDctDMpTAT5+2MYNtlWT191xiQNjSaWD4X85DhY3Dzps8Qu5VteIAPih5d6mmoaKGs8q0XIjfkFA==", "cpu": [ "x64" ], @@ -1323,9 +1323,9 @@ ] }, "node_modules/@ladybugdb/core-win32-x64": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/@ladybugdb/core-win32-x64/-/core-win32-x64-0.18.2.tgz", - "integrity": "sha512-hvFwjhTYdwG2sijapx963a27jP9mlLW0ZFv5Yfj19e0B3T/FqD9CaKULPy23mU2XlkISN2LUkY5qdgvCFztl/g==", + "version": "0.18.3", + "resolved": "https://registry.npmjs.org/@ladybugdb/core-win32-x64/-/core-win32-x64-0.18.3.tgz", + "integrity": "sha512-bHuFk0m9cnq0WGd9I4D8or8g6cC/BS58iatMtilqM3JpDPIQIFk6MQl6exL7P4xyWbkLwQgsrv2ToDnyoQNKvg==", "cpu": [ "x64" ], diff --git a/gitnexus/package.json b/gitnexus/package.json index 114b71ada..6c438f879 100644 --- a/gitnexus/package.json +++ b/gitnexus/package.json @@ -56,7 +56,7 @@ "version": "node scripts/sync-plugin-manifests.mjs" }, "dependencies": { - "@ladybugdb/core": "^0.18.0", + "@ladybugdb/core": "^0.18.3", "@modelcontextprotocol/sdk": "^1.0.0", "@scarf/scarf": "^1.4.0", "busboy": "^1.6.0", diff --git a/gitnexus/test/integration/caller-identity-regression.test.ts b/gitnexus/test/integration/caller-identity-regression.test.ts new file mode 100644 index 000000000..b72693c69 --- /dev/null +++ b/gitnexus/test/integration/caller-identity-regression.test.ts @@ -0,0 +1,109 @@ +/** + * Regression test for #2508 — context()/impact() must return EXACT caller + * identities, never counts alone. + * + * The #2508 failure shape: a target Function with two CALLS edges arriving + * through two different CodeRelation sub-table pairs — a production + * Function→Function caller and a File→Function test-file caller. On affected + * LadybugDB versions (≤0.18.2), `r.type IN [...]` predicates could drop the + * production caller and duplicate the test caller: the boolean-filter + * fallback skipped writing selection buffers for single-row unflat chunks + * (LadybugDB#692). Fixed upstream in LadybugDB#699, shipped in + * @ladybugdb/core 0.18.3. These assertions pin the IN-predicate query paths + * to exact caller IDs so any future predicate regression that drops or + * duplicates a caller fails loudly here. + */ +import { describe, it, expect, beforeAll, vi } from 'vitest'; +import { LocalBackend } from '../../src/mcp/local/local-backend.js'; +import { listRegisteredRepos } from '../../src/storage/repo-manager.js'; +import { withTestLbugDB, type IndexedDBHandle } from '../helpers/test-indexed-db.js'; + +vi.mock('../../src/storage/repo-manager.js', async (importActual) => ({ + ...(await importActual()), + listRegisteredRepos: vi.fn().mockResolvedValue([]), + cleanupOldKuzuFiles: vi.fn().mockResolvedValue({ found: false, needsReindex: false }), + findSiblingClones: vi.fn().mockResolvedValue([]), +})); + +type BackendHandle = IndexedDBHandle & { _backend?: LocalBackend }; + +const TARGET_ID = 'func:classifyOutcome'; +const PROD_CALLER_ID = 'func:resilientFetch'; +const TEST_CALLER_ID = 'file:resilient-fetch.test'; + +withTestLbugDB( + 'caller-identity-2508', + (handle) => { + describe('caller identity across CodeRelation sub-table pairs (#2508)', () => { + let backend: LocalBackend; + + beforeAll(() => { + const ext = handle as BackendHandle; + if (!ext._backend) { + throw new Error('LocalBackend not initialized — afterSetup did not attach _backend'); + } + backend = ext._backend; + }); + + it('context() lists the Function caller and the File caller exactly once each', async () => { + const result = await backend.callTool('context', { name: 'classifyOutcome' }); + expect(result).not.toHaveProperty('error'); + expect(result.status).toBe('found'); + const callerUids = (result.incoming?.calls ?? []).map((c: { uid: string }) => c.uid); + expect(callerUids.sort()).toEqual([TEST_CALLER_ID, PROD_CALLER_ID].sort()); + }); + + it('impact(upstream) returns the production caller by exact id with tests excluded', async () => { + const result = await backend.callTool('impact', { + target: 'classifyOutcome', + direction: 'upstream', + }); + expect(result).not.toHaveProperty('error'); + expect(result.impactedCount).toBeGreaterThanOrEqual(1); + const directIds = (result.byDepth?.[1] ?? []).map((d: { id: string }) => d.id); + expect(directIds).toContain(PROD_CALLER_ID); + expect(directIds).not.toContain(TEST_CALLER_ID); + }); + + it('impact(upstream, includeTests) returns both callers by exact id', async () => { + const result = await backend.callTool('impact', { + target: 'classifyOutcome', + direction: 'upstream', + includeTests: true, + }); + expect(result).not.toHaveProperty('error'); + const directIds = (result.byDepth?.[1] ?? []).map((d: { id: string }) => d.id); + expect(directIds).toContain(PROD_CALLER_ID); + expect(directIds).toContain(TEST_CALLER_ID); + expect(directIds.filter((id: string) => id === TEST_CALLER_ID)).toHaveLength(1); + }); + }); + }, + { + seed: [ + `CREATE (t:Function {id: '${TARGET_ID}', name: 'classifyOutcome', filePath: 'src/integrations/resilient-fetch.ts', startLine: 10, endLine: 20, isExported: true, content: 'function classifyOutcome() {}', description: 'classifies fetch outcomes'})`, + `CREATE (p:Function {id: '${PROD_CALLER_ID}', name: 'resilientFetch', filePath: 'src/integrations/resilient-fetch.ts', startLine: 30, endLine: 60, isExported: true, content: 'function resilientFetch() {}', description: 'production caller'})`, + `CREATE (f:File {id: '${TEST_CALLER_ID}', name: 'resilient-fetch.test.ts', filePath: 'test/unit/resilient-fetch.test.ts', content: 'test module'})`, + `MATCH (a:Function), (b:Function) WHERE a.id = '${PROD_CALLER_ID}' AND b.id = '${TARGET_ID}' + CREATE (a)-[:CodeRelation {type: 'CALLS', confidence: 0.85, reason: 'direct', step: 0}]->(b)`, + `MATCH (a:File), (b:Function) WHERE a.id = '${TEST_CALLER_ID}' AND b.id = '${TARGET_ID}' + CREATE (a)-[:CodeRelation {type: 'CALLS', confidence: 0.9, reason: 'direct', step: 0}]->(b)`, + ], + poolAdapter: true, + afterSetup: async (h) => { + vi.mocked(listRegisteredRepos).mockResolvedValue([ + { + name: 'caller-identity-repo', + path: '/caller-identity/repo', + storagePath: h.tmpHandle.dbPath, + indexedAt: new Date().toISOString(), + lastCommit: 'abc123', + stats: { files: 2, nodes: 3, communities: 0, processes: 0 }, + }, + ]); + const backend = new LocalBackend(); + await backend.init(); + (h as BackendHandle)._backend = backend; + }, + }, +);