fix(deps): bump @ladybugdb/core to ^0.18.3 — rel-property IN-predicate fix (#2508) (#2634)

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 <noreply@anthropic.com>
This commit is contained in:
Gergő Magyar 2026-07-22 16:31:56 +01:00 committed by GitHub
parent 7f7255aef8
commit e814e28f1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 134 additions and 25 deletions

View file

@ -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"
],

View file

@ -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",

View file

@ -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
* FunctionFunction caller and a FileFunction 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<typeof import('../../src/storage/repo-manager.js')>()),
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;
},
},
);