From 9749092f0d8ed5cd797f3fbf1bb60e116e483aa4 Mon Sep 17 00:00:00 2001 From: Shining Date: Thu, 30 Jul 2026 11:58:10 +0800 Subject: [PATCH] refactor(lbug): keep relation pair parsing in router --- gitnexus/src/core/lbug/csv-generator.ts | 8 +++++--- gitnexus/src/core/lbug/rel-pair-routing.ts | 16 ++++++++++++++++ gitnexus/src/core/lbug/schema.ts | 15 --------------- gitnexus/test/unit/rel-pair-routing.test.ts | 20 +++++++++++++++++++- gitnexus/test/unit/schema.test.ts | 2 -- 5 files changed, 40 insertions(+), 21 deletions(-) diff --git a/gitnexus/src/core/lbug/csv-generator.ts b/gitnexus/src/core/lbug/csv-generator.ts index 0d8ac043c7..18f91e723d 100644 --- a/gitnexus/src/core/lbug/csv-generator.ts +++ b/gitnexus/src/core/lbug/csv-generator.ts @@ -17,12 +17,14 @@ import { createWriteStream, WriteStream } from 'fs'; import path from 'path'; import type { GraphNode, GraphRelationship } from 'gitnexus-shared'; import { KnowledgeGraph } from '../graph/types.js'; -import { NodeTableName, NODE_TABLES, RELATION_SCHEMA_PAIRS } from './schema.js'; -import { RelPairRouter } from './rel-pair-routing.js'; +import { NodeTableName, NODE_TABLES, RELATION_SCHEMA } from './schema.js'; +import { parseRelationSchemaPairs, RelPairRouter } from './rel-pair-routing.js'; import { parseTruthyEnv } from '../ingestion/utils/env.js'; import { SYMBOL_NODE_LABELS } from '../ingestion/utils/symbol-labels.js'; import { applyCjkSegmentationIfEnabled } from '../search/cjk-segmentation.js'; +const DECLARED_RELATION_PAIRS = parseRelationSchemaPairs(RELATION_SCHEMA); + /** * Deterministic output ordering — optional (out-of-core / windowed-resolve * enabler). When `GITNEXUS_SORT_GRAPH_OUTPUT` is set, nodes and relationships @@ -789,7 +791,7 @@ export const streamAllCSVsToDisk = async ( csvDir, REL_CSV_HEADER, new Set(NODE_TABLES), - RELATION_SCHEMA_PAIRS, + DECLARED_RELATION_PAIRS, ); try { let emitted = 0; diff --git a/gitnexus/src/core/lbug/rel-pair-routing.ts b/gitnexus/src/core/lbug/rel-pair-routing.ts index 12553f4919..9729b5bec2 100644 --- a/gitnexus/src/core/lbug/rel-pair-routing.ts +++ b/gitnexus/src/core/lbug/rel-pair-routing.ts @@ -48,6 +48,22 @@ export const getNodeLabel = (nodeId: string): string => { return nodeId.split(':')[0]; }; +/** + * Extract the FROM→TO pairs accepted by a relationship DDL. + * + * This belongs at the routing boundary: schema.ts owns the DDL, while the CSV + * router owns the fail-fast check that prevents writing a pair LadybugDB cannot + * COPY. Backticks quote schema labels and are not part of the graph label. + */ +export const parseRelationSchemaPairs = (relationSchema: string): ReadonlySet => + new Set( + [ + ...relationSchema.matchAll( + /\bFROM\s+`?([A-Za-z][A-Za-z0-9_]*)`?\s+TO\s+`?([A-Za-z][A-Za-z0-9_]*)`?/g, + ), + ].map((match) => `${match[1]}|${match[2]}`), + ); + export interface RelPairMeta { csvPath: string; rows: number; diff --git a/gitnexus/src/core/lbug/schema.ts b/gitnexus/src/core/lbug/schema.ts index 1b94237b87..5803776fa9 100644 --- a/gitnexus/src/core/lbug/schema.ts +++ b/gitnexus/src/core/lbug/schema.ts @@ -462,21 +462,6 @@ CREATE REL TABLE ${REL_TABLE_NAME} ( step INT32 )`; -/** - * Declared FROM→TO label pairs for the single relationship table. - * - * Derived from the DDL itself so the CSV router and LadybugDB schema cannot - * drift into separate allowlists. Backticks are schema quoting, not part of a - * node label. - */ -export const RELATION_SCHEMA_PAIRS: ReadonlySet = new Set( - [ - ...RELATION_SCHEMA.matchAll( - /\bFROM\s+`?([A-Za-z][A-Za-z0-9_]*)`?\s+TO\s+`?([A-Za-z][A-Za-z0-9_]*)`?/g, - ), - ].map((match) => `${match[1]}|${match[2]}`), -); - // ============================================================================ // EMBEDDING TABLE SCHEMA // Separate table for vector storage to avoid copy-on-write overhead diff --git a/gitnexus/test/unit/rel-pair-routing.test.ts b/gitnexus/test/unit/rel-pair-routing.test.ts index ab48e761e5..2dd89d9696 100644 --- a/gitnexus/test/unit/rel-pair-routing.test.ts +++ b/gitnexus/test/unit/rel-pair-routing.test.ts @@ -3,7 +3,11 @@ import { EventEmitter } from 'events'; import fs from 'fs'; import path from 'path'; import os from 'os'; -import { RelPairRouter, getNodeLabel } from '../../src/core/lbug/rel-pair-routing.js'; +import { + RelPairRouter, + getNodeLabel, + parseRelationSchemaPairs, +} from '../../src/core/lbug/rel-pair-routing.js'; /** * Unit tests for RelPairRouter (#2203 U2) — the production per-pair emit path. @@ -98,6 +102,20 @@ describe('getNodeLabel', () => { }); }); +describe('parseRelationSchemaPairs', () => { + it('extracts plain and quoted FROM→TO labels for router validation', () => { + expect( + parseRelationSchemaPairs(` + CREATE REL TABLE CodeRelation( + FROM Class TO CodeElement, + FROM \`Enum\` TO \`TypeAlias\`, + type STRING + ) + `), + ).toEqual(new Set(['Class|CodeElement', 'Enum|TypeAlias'])); + }); +}); + describe('RelPairRouter', () => { it('routes valid edges to per-pair files (header first) and skips invalid-label edges', async () => { const streams: MockWriteStream[] = []; diff --git a/gitnexus/test/unit/schema.test.ts b/gitnexus/test/unit/schema.test.ts index 014c835f5c..43e6cb7ba1 100644 --- a/gitnexus/test/unit/schema.test.ts +++ b/gitnexus/test/unit/schema.test.ts @@ -19,7 +19,6 @@ import { PROCESS_SCHEMA, BASICBLOCK_SCHEMA, RELATION_SCHEMA, - RELATION_SCHEMA_PAIRS, EMBEDDING_SCHEMA, CREATE_VECTOR_INDEX_QUERY, } from '../../src/core/lbug/schema.js'; @@ -211,7 +210,6 @@ describe('LadybugDB Schema', () => { it('persists consumer Class injection edges to synthetic provider declarations', () => { expect(RELATION_SCHEMA).toContain('FROM Class TO CodeElement'); - expect(RELATION_SCHEMA_PAIRS.has('Class|CodeElement')).toBe(true); }); it('has all FROM/TO pairs needed for HAS_METHOD edges', () => {