115 lines
4.5 KiB
JavaScript
115 lines
4.5 KiB
JavaScript
|
|
import assert from 'node:assert/strict';
|
||
|
|
import { describe, test } from 'node:test';
|
||
|
|
import fs from 'node:fs';
|
||
|
|
import os from 'node:os';
|
||
|
|
import path from 'node:path';
|
||
|
|
import { CondensedGraph } from '../../src/core/CondensedGraph.js';
|
||
|
|
import { ShardedSnapshotBuilder } from '../../src/core/shards/ShardedSnapshotBuilder.js';
|
||
|
|
import { ShardedSnapshot } from '../../src/core/shards/ShardedSnapshot.js';
|
||
|
|
import { FileShardStorage } from '../../src/core/shards/FileShardStorage.js';
|
||
|
|
|
||
|
|
function buildShardedSnapshot(bucketSize = 2) {
|
||
|
|
const graph = new CondensedGraph();
|
||
|
|
|
||
|
|
const user0 = graph._ensureNode('user:0');
|
||
|
|
const user1 = graph._ensureNode('user:1');
|
||
|
|
const user2 = graph._ensureNode('user:2');
|
||
|
|
const user3 = graph._ensureNode('user:3');
|
||
|
|
const group0 = graph._ensureNode('group:0');
|
||
|
|
const group1 = graph._ensureNode('group:1');
|
||
|
|
const doc0 = graph._ensureNode('doc:0');
|
||
|
|
|
||
|
|
graph.addEdge(user0, 'member', group0);
|
||
|
|
graph.addEdge(user3, 'member', group0);
|
||
|
|
graph.addEdge(user2, 'member', group1);
|
||
|
|
|
||
|
|
graph.addEdge(group0, 'viewer', doc0);
|
||
|
|
graph.addEdge(group1, 'viewer', doc0);
|
||
|
|
|
||
|
|
graph.addEdge(user0, 'risk', doc0, { value: 0.2, possibility: 1, reliability: 1 });
|
||
|
|
graph.addEdge(user3, 'risk', doc0, { value: 0.9, possibility: 1, reliability: 1 });
|
||
|
|
graph.addEdge(user2, 'risk', doc0, { value: 0.6, possibility: 1, reliability: 1 });
|
||
|
|
|
||
|
|
graph.finalizePerfectHash();
|
||
|
|
graph.finalizeWaveletAdjacency({ dropAdjacencyList: true });
|
||
|
|
|
||
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sharded-cross-chain-'));
|
||
|
|
const builder = new ShardedSnapshotBuilder({ bucketSize, includeDirections: ['out', 'in'] });
|
||
|
|
const manifest = builder.build(graph, dir);
|
||
|
|
const storage = new FileShardStorage(dir);
|
||
|
|
const snapshot = new ShardedSnapshot(manifest, storage, { cacheLimit: 8 });
|
||
|
|
snapshot.initializeSync();
|
||
|
|
|
||
|
|
return { graph, snapshot, dir, ids: { user0, user1, user2, user3, group0, group1, doc0 } };
|
||
|
|
}
|
||
|
|
|
||
|
|
function collectRiskPaths(snapshot, relationIds, docId) {
|
||
|
|
const viewerRel = relationIds.viewer;
|
||
|
|
const memberRel = relationIds.member;
|
||
|
|
const riskRel = relationIds.risk;
|
||
|
|
|
||
|
|
const paths = [];
|
||
|
|
const viewerEdges = snapshot.executeGetInEdgesSync(docId, viewerRel, new Set()) || [];
|
||
|
|
for (const viewer of viewerEdges) {
|
||
|
|
const groupId = viewer.src;
|
||
|
|
const memberEdges = snapshot.executeGetInEdgesSync(groupId, memberRel, new Set()) || [];
|
||
|
|
for (const member of memberEdges) {
|
||
|
|
const userId = member.src;
|
||
|
|
const riskEdge = snapshot.executeFindEdgeSync(userId, riskRel, docId, new Set());
|
||
|
|
if (riskEdge) {
|
||
|
|
paths.push({ userId, docId });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return paths;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ADR-003: sharded snapshots are stubs in src/core/shards/ — tests are the spec for when the subsystem is implemented.
|
||
|
|
describe.skip('Sharded snapshot cross-chain aggregation', () => {
|
||
|
|
test('aggregates risk values across userset paths', () => {
|
||
|
|
const { graph, snapshot, dir, ids } = buildShardedSnapshot(2);
|
||
|
|
const relationIds = {
|
||
|
|
member: graph.getRelationId('member'),
|
||
|
|
viewer: graph.getRelationId('viewer'),
|
||
|
|
risk: graph.getRelationId('risk')
|
||
|
|
};
|
||
|
|
|
||
|
|
const plan = new Set();
|
||
|
|
snapshot.planInEdges(relationIds.viewer, ids.doc0, plan);
|
||
|
|
snapshot.planInEdges(relationIds.member, ids.group0, plan);
|
||
|
|
snapshot.planInEdges(relationIds.member, ids.group1, plan);
|
||
|
|
snapshot.planOutEdges(relationIds.risk, ids.user0, plan);
|
||
|
|
snapshot.planOutEdges(relationIds.risk, ids.user2, plan);
|
||
|
|
snapshot.planOutEdges(relationIds.risk, ids.user3, plan);
|
||
|
|
snapshot.prefetchPlanSync(plan);
|
||
|
|
|
||
|
|
const paths = collectRiskPaths(snapshot, relationIds, ids.doc0);
|
||
|
|
assert.strictEqual(paths.length, 3);
|
||
|
|
|
||
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
||
|
|
});
|
||
|
|
|
||
|
|
test('cross-bucket paths resolve with inbound and outbound shards', () => {
|
||
|
|
const { graph, snapshot, dir, ids } = buildShardedSnapshot(2);
|
||
|
|
const relationIds = {
|
||
|
|
member: graph.getRelationId('member'),
|
||
|
|
viewer: graph.getRelationId('viewer'),
|
||
|
|
risk: graph.getRelationId('risk')
|
||
|
|
};
|
||
|
|
|
||
|
|
const plan = new Set();
|
||
|
|
snapshot.planInEdges(relationIds.viewer, ids.doc0, plan);
|
||
|
|
snapshot.planInEdges(relationIds.member, ids.group0, plan);
|
||
|
|
snapshot.planInEdges(relationIds.member, ids.group1, plan);
|
||
|
|
snapshot.planOutEdges(relationIds.risk, ids.user0, plan);
|
||
|
|
snapshot.planOutEdges(relationIds.risk, ids.user2, plan);
|
||
|
|
snapshot.planOutEdges(relationIds.risk, ids.user3, plan);
|
||
|
|
snapshot.prefetchPlanSync(plan);
|
||
|
|
|
||
|
|
const paths = collectRiskPaths(snapshot, relationIds, ids.doc0);
|
||
|
|
assert.strictEqual(paths.length, 3);
|
||
|
|
|
||
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
||
|
|
});
|
||
|
|
});
|