mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
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:
parent
7f7255aef8
commit
e814e28f1f
3 changed files with 134 additions and 25 deletions
48
gitnexus/package-lock.json
generated
48
gitnexus/package-lock.json
generated
|
|
@ -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"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
109
gitnexus/test/integration/caller-identity-regression.test.ts
Normal file
109
gitnexus/test/integration/caller-identity-regression.test.ts
Normal 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
|
||||
* 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<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;
|
||||
},
|
||||
},
|
||||
);
|
||||
Loading…
Add table
Reference in a new issue