fix(communities): canonicalize projection order

This commit is contained in:
Eva 2026-07-14 03:20:54 +07:00
parent 3bd496ded3
commit b9ddfda945
2 changed files with 29 additions and 1 deletions

View file

@ -302,6 +302,7 @@ export const buildCommunityProjection = (knowledgeGraph: KnowledgeGraph): Commun
const nodes: CommunityProjectionNode[] = [];
const nodeIndexById = new Map<string, number>();
const eligibleNodes: GraphNode[] = [];
knowledgeGraph.forEachNode((node) => {
if (!isCommunitySymbol(node) || !connectedNodes.has(node.id)) return;
@ -309,6 +310,12 @@ export const buildCommunityProjection = (knowledgeGraph: KnowledgeGraph): Commun
// get absorbed into their single neighbor's community, but cost iteration time.
if (isLarge && (nodeDegree.get(node.id) || 0) < 2) return;
eligibleNodes.push(node);
});
eligibleNodes.sort((left, right) => (left.id < right.id ? -1 : left.id > right.id ? 1 : 0));
for (const node of eligibleNodes) {
nodeIndexById.set(node.id, nodes.length);
nodes.push({
id: node.id,
@ -316,7 +323,7 @@ export const buildCommunityProjection = (knowledgeGraph: KnowledgeGraph): Commun
filePath: node.properties.filePath,
type: node.label,
});
});
}
const seenEdges = new Set<string>();
const edges: Array<readonly [number, number]> = [];
@ -338,6 +345,7 @@ export const buildCommunityProjection = (knowledgeGraph: KnowledgeGraph): Commun
seenEdges.add(edgeKey);
edges.push([a, b]);
});
edges.sort(([leftA, leftB], [rightA, rightB]) => leftA - rightA || leftB - rightB);
return { nodes, edges, symbolCount, isLarge };
};

View file

@ -102,6 +102,26 @@ describe('community-processor', () => {
expect(projection.symbolCount).toBe(3);
});
it('produces the same projection regardless of graph insertion order', () => {
const first = createKnowledgeGraph();
for (const id of ['fn:c', 'fn:a', 'fn:b']) {
first.addNode(makeNode(id, id.slice(3)));
}
first.addRelationship(makeRel('rel:ac', 'fn:a', 'fn:c'));
first.addRelationship(makeRel('rel:ab', 'fn:a', 'fn:b'));
first.addRelationship(makeRel('rel:bc', 'fn:b', 'fn:c'));
const second = createKnowledgeGraph();
for (const id of ['fn:b', 'fn:c', 'fn:a']) {
second.addNode(makeNode(id, id.slice(3)));
}
second.addRelationship(makeRel('rel:bc', 'fn:c', 'fn:b'));
second.addRelationship(makeRel('rel:ab', 'fn:b', 'fn:a'));
second.addRelationship(makeRel('rel:ac', 'fn:c', 'fn:a'));
expect(buildCommunityProjection(second)).toEqual(buildCommunityProjection(first));
});
it('exports a deterministic undirected CSR adjacency', () => {
const projection = {
nodes: [