initial commit: @arbiter/core authorization engine with js-rigor hardening

Zanzibar-style authorization graph engine (direct/chain/TTU/defeasible/
binary modes, condensed snapshots, value relations) with 39 rigor test
campaigns. Includes fixes for snapshot binary writer/reader format
mismatch (snapshot-of-snapshot corruption), possibility write-boundary
validation, empty-graph snapshot serialization, relation lookup cache
direction collision, config-redefinition cache invalidation, binary
threshold semantics, defeasible compiled routing, and comparator
reason whitelisting.
This commit is contained in:
John Dvorak
2026-07-31 13:44:06 -07:00
commit 717ae1031e
373 changed files with 654131 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
import { Arbiter } from '../../src/core/Arbiter.js';
export function createTestArbiter(options = {}) {
const defaults = {
embeddingDimensions: 256,
directCheckCacheSize: 10000,
directCheckCacheTTL: 60000,
disableCaching: false,
disableChainCaching: false,
disableDirectCaching: false
};
return new Arbiter({ ...defaults, ...options });
}
export function seedBasicGraph(arbiter) {
const nodes = [
['user:alice', 'user'],
['user:bob', 'user'],
['user:charlie', 'user'],
['doc:report', 'document'],
['doc:invoice', 'document'],
['project:web-app', 'project'],
['group:engineering', 'group'],
['group:management', 'group'],
['account:main', 'account'],
['session:sess-1', 'session']
];
for (const [key, type] of nodes) {
arbiter.addNode(key, type);
}
const relations = [
['user:alice', 'member_of', 'group:engineering', 1.0],
['user:bob', 'member_of', 'group:management', 1.0],
['group:engineering', 'can_read', 'doc:report', 0.8],
['group:engineering', 'can_read', 'project:web-app', 0.9],
['group:management', 'can_read', 'doc:invoice', 1.0],
['user:alice', 'controls', 'account:main', 1.0],
['session:sess-1', 'authenticated_as', 'user:alice', 1.0]
];
for (const [src, rel, dst, possibility, metadata] of relations) {
arbiter.addRelation(src, rel, dst, possibility, metadata);
}
const relationConfigs = [
['member_of', { type: 'direct' }],
['can_read', { type: 'direct' }],
['controls', { type: 'direct' }],
['authenticated_as', { type: 'direct' }]
];
for (const [rel, config] of relationConfigs) {
arbiter.setRelationConfig(rel, config);
}
return arbiter;
}
export function seedRelation(arbiter, src, rel, dst, possibility = 1.0, metadata = {}) {
arbiter.addRelation(src, rel, dst, possibility, metadata);
if (!arbiter.relationConfigs.has(rel)) {
arbiter.setRelationConfig(rel, { type: 'direct' });
}
return arbiter;
}
export function createPartialGraph(nodes = [], relations = []) {
return { nodes, relations };
}
export function seedPartialRelation(arbiter, src, rel, dst, possibility = 1.0, value = undefined) {
const relObj = { src, relation: rel, dst, possibility };
if (value !== undefined) {
relObj.value = value;
}
return { nodes: [], relations: [relObj] };
}
+598
View File
@@ -0,0 +1,598 @@
import { Arbiter } from '../../src/core/Arbiter.js';
/**
* Big Graph Generator for Enterprise Authorization Testing
* Generates realistic authorization graphs with complex multi-hop relationships
*/
export class BigGraphGenerator {
constructor(config = {}) {
this.config = {
scale: 'small',
seed: 12345,
...config
};
// Scale configurations - reduced for testing
this.scaleConfigs = {
small: {
users: 100,
documents: 500,
relations: 1000,
complexChains: 200, // Multi-hop authorization chains
targetQPS: 100,
maxMemoryMB: 128,
maxLatencyMs: 50
},
medium: {
users: 500,
documents: 2000,
relations: 5000,
complexChains: 1000,
targetQPS: 200,
maxMemoryMB: 256,
maxLatencyMs: 100
},
large: {
users: 1000,
documents: 5000,
relations: 10000,
complexChains: 2000,
targetQPS: 500,
maxMemoryMB: 512,
maxLatencyMs: 200
},
enterprise: {
users: 2000,
documents: 10000,
relations: 20000,
complexChains: 5000,
targetQPS: 1000,
maxMemoryMB: 1024,
maxLatencyMs: 500
},
million: {
users: 100000,
documents: 900000,
relations: 2000000,
complexChains: 10000, // Reduced complexity to avoid stack overflow
targetQPS: 100,
maxMemoryMB: 8192,
maxLatencyMs: 1000
}
};
// Initialize random number generator with seed
this.rng = this._createSeededRNG(this.config.seed);
}
/**
* Create seeded random number generator for reproducible results
*/
_createSeededRNG(seed) {
let state = seed;
return {
next: () => {
state = (state * 1664525 + 1013904223) % 4294967296;
return state / 4294967296;
}
};
}
/**
* Generate authorization graph for specified model
*/
generateGraph(model = 'enterprise') {
const scale = this.scaleConfigs[this.config.scale];
const graphData = {
users: [],
documents: [],
relations: [],
enterprises: [],
saasTenants: [],
metadata: {
model,
scale: this.config.scale,
generatedAt: new Date().toISOString(),
seed: this.config.seed
}
};
// Generate users
graphData.users = this._generateUsers(scale);
// Generate documents
graphData.documents = this._generateDocuments(scale);
// Generate basic user-document relationships
const basicRelations = this._generateBasicRelations(scale, graphData.users, graphData.documents);
graphData.relations.push(...basicRelations);
// Generate complex authorization chains (multi-hop relationships)
const { complexChains, roleNodes, departmentNodes } = this._generateComplexAuthorizationChains(scale, graphData.users, graphData.documents);
graphData.relations.push(...complexChains);
// Add the generated nodes to the graph data
graphData.roles = roleNodes;
graphData.departments = departmentNodes;
// Generate organizational hierarchies
const orgRelations = this._generateOrganizationalHierarchies(scale, graphData.users);
graphData.relations.push(...orgRelations);
// Generate long organizational chains for multi-hop testing
const longChains = this._generateLongOrganizationalChains(scale, graphData.users, graphData.documents);
graphData.relations.push(...longChains);
// Generate role-based access chains
const roleChains = this._generateRoleBasedChains(scale, graphData.users, graphData.documents);
graphData.relations.push(...roleChains);
return graphData;
}
/**
* Load graph data into Arbiter instance
*/
loadIntoArbiter(graphData, options = {}) {
const arbiter = new Arbiter({
fastConstructionMode: true,
...options
});
// Add all nodes
graphData.users.forEach(user => {
arbiter.addNode(user.id, 'user');
});
graphData.documents.forEach(doc => {
arbiter.addNode(doc.id, 'document');
});
// Add role nodes if they exist
if (graphData.roles) {
graphData.roles.forEach(role => {
arbiter.addNode(role.id, 'role');
});
}
// Add department nodes if they exist
if (graphData.departments) {
graphData.departments.forEach(dept => {
arbiter.addNode(dept.id, 'department');
});
}
// Add intermediate nodes for long chains
const longChainRelations = graphData.relations.filter(r => r.metadata?.type === 'long_chain');
const intermediateNodes = new Set();
longChainRelations.forEach(relation => {
// Add all intermediate nodes (teams, departments, divisions, companies)
if (relation.src.startsWith('team:') || relation.src.startsWith('dept:') ||
relation.src.startsWith('division:') || relation.src.startsWith('company:')) {
intermediateNodes.add(relation.src);
}
if (relation.dst.startsWith('team:') || relation.dst.startsWith('division:') || relation.dst.startsWith('company:')) {
intermediateNodes.add(relation.dst);
}
});
intermediateNodes.forEach(nodeId => {
const nodeType = nodeId.startsWith('team:') ? 'team' :
nodeId.startsWith('dept:') ? 'department' :
nodeId.startsWith('division:') ? 'division' : 'company';
arbiter.addNode(nodeId, nodeType);
});
// Add all relations
graphData.relations.forEach(relation => {
arbiter.addRelation(relation.src, relation.relation, relation.dst, relation.possibility);
});
// Disable fast construction mode and build indices now that loading is complete
arbiter.setFastConstructionMode(false);
// Configure relation types with proper ReBAC patterns
const relationTypes = ['can_read', 'can_write', 'can_delete', 'can_share', 'can_admin'];
relationTypes.forEach(relType => {
// Configure direct relations
arbiter.setRelationConfig(relType, { type: 'direct' });
// Configure role-based access using chain rule for proper role-based access
const roleBasedRel = `${relType}_via_role`;
arbiter.setRelationConfig(roleBasedRel, {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: relType, direction: 'out' }
]
});
});
// Configure membership relations as direct
const membershipTypes = ['member_of', 'manager_of', 'reports_to'];
membershipTypes.forEach(relType => {
arbiter.setRelationConfig(relType, { type: 'direct' });
});
// Configure department-based access using chain rule
const departmentBasedRel = 'can_write_via_department';
arbiter.setRelationConfig(departmentBasedRel, {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'can_write', direction: 'out' }
]
});
// Configure multi-hop access for long chain traversal (computationally intensive)
// Use chain rule for complex multi-step authorization
arbiter.setRelationConfig('can_access_multi_hop', {
type: 'chain',
steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'member_of', direction: 'out' },
{ relation: 'member_of', direction: 'out' },
{ relation: 'member_of', direction: 'out' },
{ relation: 'can_read', direction: 'out' }
],
collectValues: true,
valueAggregation: 'sum'
});
return arbiter;
}
/**
* Generate users
*/
_generateUsers(scale) {
const users = [];
const names = ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve', 'Frank', 'Grace', 'Henry', 'Ivy', 'Jack'];
const surnames = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller', 'Davis', 'Rodriguez', 'Martinez'];
for (let i = 0; i < scale.users; i++) {
const firstName = names[Math.floor(this.rng.next() * names.length)];
const lastName = surnames[Math.floor(this.rng.next() * surnames.length)];
users.push({
id: `user:${firstName} ${lastName}-${i}`,
type: 'employee',
name: `${firstName} ${lastName}`,
email: `${firstName.toLowerCase()} ${lastName.toLowerCase()}@company.com`,
role: ['manager', 'employee', 'contractor', 'intern'][Math.floor(this.rng.next() * 4)],
clearance: ['public', 'internal', 'confidential', 'secret', 'top_secret'][Math.floor(this.rng.next() * 5)],
department: `dept:${this._generateDepartmentName()}-${i}`,
organization: `org:company-${Math.floor(i / 50)}`,
manager: null,
startDate: new Date(Date.now() - Math.floor(this.rng.next() * 365 * 24 * 60 * 60 * 1000)).toISOString(),
isActive: true
});
}
return users;
}
/**
* Generate documents
*/
_generateDocuments(scale) {
const documents = [];
const docTypes = ['report', 'analysis', 'proposal', 'contract', 'policy', 'spec', 'budget'];
for (let i = 0; i < scale.documents; i++) {
const docType = docTypes[Math.floor(this.rng.next() * docTypes.length)];
documents.push({
id: `doc:${docType}-${i}`,
type: 'document',
name: `${docType}_${i}`,
classification: ['public', 'internal', 'confidential', 'secret', 'top_secret'][Math.floor(this.rng.next() * 5)],
department: `dept:${this._generateDepartmentName()}-${i}`,
organization: `org:company-${Math.floor(i / 100)}`,
requiredClearance: ['public', 'internal', 'confidential', 'secret', 'top_secret'][Math.floor(this.rng.next() * 5)],
cost: Math.floor(this.rng.next() * 10000),
createdDate: new Date(Date.now() - Math.floor(this.rng.next() * 365 * 24 * 60 * 60 * 1000)).toISOString(),
lastModified: new Date(Date.now() - Math.floor(this.rng.next() * 30 * 24 * 60 * 60 * 1000)).toISOString(),
size: Math.floor(this.rng.next() * 1000000),
tags: this._generateTags()
});
}
return documents;
}
/**
* Generate basic user-document relationships
* FIXED: Reduce direct relations to avoid conflicts with chain relations
*/
_generateBasicRelations(scale, users, documents) {
const relations = [];
const operations = ['can_read', 'can_write', 'can_delete', 'can_share'];
// Reduce the number of direct relations to avoid conflicts with chain relations
const reducedRelations = Math.floor(scale.relations * 0.3); // Only 30% of original
for (let i = 0; i < reducedRelations; i++) {
const user = users[Math.floor(this.rng.next() * users.length)];
const document = documents[Math.floor(this.rng.next() * documents.length)];
const operation = operations[Math.floor(this.rng.next() * operations.length)];
const possibility = 0.5 + (this.rng.next() * 0.5); // 0.5 to 1.0
relations.push({
src: user.id,
relation: operation,
dst: document.id,
possibility,
metadata: {
role: user.role,
clearance: user.clearance,
department: user.department,
grantedDate: new Date().toISOString(),
expiresDate: null
}
});
}
return relations;
}
/**
* Generate complex authorization chains (multi-hop relationships)
* These create realistic enterprise authorization scenarios
*/
_generateComplexAuthorizationChains(scale, users, documents) {
const relations = [];
const roleNodes = [];
const departmentNodes = [];
const chainCount = scale.complexChains || Math.floor(scale.relations * 0.2);
// Create role nodes first
const roleTypes = ['admin', 'manager', 'employee', 'contractor', 'intern'];
const createdRoles = new Set();
for (let i = 0; i < chainCount; i++) {
const user = users[Math.floor(this.rng.next() * users.length)];
const document = documents[Math.floor(this.rng.next() * documents.length)];
// Chain 1: User -> Role -> Document (Role-based access)
const roleType = roleTypes[Math.floor(this.rng.next() * roleTypes.length)];
const roleId = `role:${roleType}-${Math.floor(this.rng.next() * 10)}`;
// Create role node if it doesn't exist
if (!createdRoles.has(roleId)) {
roleNodes.push({
id: roleId,
type: 'role',
name: `${roleType} role`,
roleType: roleType,
permissions: ['can_read', 'can_write', 'can_share']
});
createdRoles.add(roleId);
}
relations.push({
src: user.id,
relation: 'member_of',
dst: roleId,
possibility: 0.9,
metadata: { type: 'role_membership' }
});
relations.push({
src: roleId,
relation: 'can_read',
dst: document.id,
possibility: 0.8,
metadata: { type: 'role_permission' }
});
// Chain 2: User -> Department -> Document (Department-based access)
const deptName = this._generateDepartmentName();
const deptId = `dept:${deptName}-${Math.floor(this.rng.next() * 20)}`;
// Create department node if it doesn't exist
if (!createdRoles.has(deptId)) {
departmentNodes.push({
id: deptId,
type: 'department',
name: `${deptName} department`,
departmentName: deptName,
permissions: ['can_read', 'can_write']
});
createdRoles.add(deptId);
}
relations.push({
src: user.id,
relation: 'member_of',
dst: deptId,
possibility: 0.95,
metadata: { type: 'department_membership' }
});
relations.push({
src: deptId,
relation: 'can_write',
dst: document.id,
possibility: 0.7,
metadata: { type: 'department_permission' }
});
// Chain 3: User -> Manager -> Document (Manager approval chain)
const manager = users[Math.floor(this.rng.next() * users.length)];
relations.push({
src: user.id,
relation: 'reports_to',
dst: manager.id,
possibility: 0.9,
metadata: { type: 'reporting_chain' }
});
relations.push({
src: manager.id,
relation: 'can_share',
dst: document.id,
possibility: 0.6,
metadata: { type: 'manager_permission' }
});
}
return { complexChains: relations, roleNodes, departmentNodes };
}
/**
* Generate long organizational chains for multi-hop testing
* These create deep hierarchies that will be computationally intensive to traverse
*/
_generateLongOrganizationalChains(scale, users, documents) {
const relations = [];
const chainCount = Math.floor(scale.users * 0.1); // 10% of users get long chains
for (let i = 0; i < chainCount; i++) {
const user = users[Math.floor(this.rng.next() * users.length)];
const document = documents[Math.floor(this.rng.next() * documents.length)];
// Create a long chain: User -> Team -> Department -> Division -> Company -> Document
const teamId = `team:${this._generateTeamName()}-${Math.floor(this.rng.next() * 50)}`;
const deptId = `dept:${this._generateDepartmentName()}-${Math.floor(this.rng.next() * 100)}`;
const divisionId = `division:${this._generateDivisionName()}-${Math.floor(this.rng.next() * 20)}`;
const companyId = `company:${this._generateCompanyName()}-${Math.floor(this.rng.next() * 10)}`;
// Create the long chain with decreasing possibility (realistic for deep hierarchies)
relations.push({
src: user.id,
relation: 'member_of',
dst: teamId,
possibility: 0.95,
metadata: { type: 'long_chain', step: 1, chainId: `chain-${i}` }
});
relations.push({
src: teamId,
relation: 'member_of',
dst: deptId,
possibility: 0.9,
metadata: { type: 'long_chain', step: 2, chainId: `chain-${i}` }
});
relations.push({
src: deptId,
relation: 'member_of',
dst: divisionId,
possibility: 0.85,
metadata: { type: 'long_chain', step: 3, chainId: `chain-${i}` }
});
relations.push({
src: divisionId,
relation: 'member_of',
dst: companyId,
possibility: 0.8,
metadata: { type: 'long_chain', step: 4, chainId: `chain-${i}` }
});
relations.push({
src: companyId,
relation: 'can_read',
dst: document.id,
possibility: 0.75,
metadata: { type: 'long_chain', step: 5, chainId: `chain-${i}` }
});
}
return relations;
}
/**
* Generate organizational hierarchies
*/
_generateOrganizationalHierarchies(scale, users) {
const relations = [];
// Create department hierarchies
for (let i = 0; i < Math.floor(scale.users / 10); i++) {
const deptId = `dept:${this._generateDepartmentName()}-${i}`;
const parentDeptId = `dept:${this._generateDepartmentName()}-${Math.floor(i / 3)}`;
if (i > 0) {
relations.push({
src: deptId,
relation: 'reports_to',
dst: parentDeptId,
possibility: 0.9,
metadata: { type: 'department_hierarchy' }
});
}
}
return relations;
}
/**
* Generate role-based access chains
*/
_generateRoleBasedChains(scale, users, documents) {
const relations = [];
// Create role hierarchies
const roles = ['admin', 'manager', 'senior', 'employee', 'contractor', 'intern'];
for (let i = 0; i < roles.length - 1; i++) {
const currentRole = `role:${roles[i]}`;
const parentRole = `role:${roles[i + 1]}`;
relations.push({
src: currentRole,
relation: 'inherits_from',
dst: parentRole,
possibility: 0.8,
metadata: { type: 'role_inheritance' }
});
}
return relations;
}
/**
* Generate department name
*/
_generateDepartmentName() {
const departments = ['engineering', 'finance', 'hr', 'legal', 'operations', 'marketing', 'sales'];
return departments[Math.floor(this.rng.next() * departments.length)];
}
_generateTeamName() {
const teams = ['Frontend', 'Backend', 'DevOps', 'QA', 'Design', 'Analytics', 'Security', 'Mobile'];
return teams[Math.floor(this.rng.next() * teams.length)];
}
_generateDivisionName() {
const divisions = ['Product', 'Engineering', 'Sales', 'Marketing', 'Operations', 'Finance', 'Legal', 'HR'];
return divisions[Math.floor(this.rng.next() * divisions.length)];
}
_generateCompanyName() {
const companies = ['AcmeCorp', 'TechGiant', 'InnovateLabs', 'DataFlow', 'CloudSystems', 'NextGen', 'FutureTech', 'SmartSolutions'];
return companies[Math.floor(this.rng.next() * companies.length)];
}
/**
* Generate document tags
*/
_generateTags() {
const allTags = ['important', 'urgent', 'confidential', 'draft', 'final', 'reviewed', 'approved'];
const tagCount = Math.floor(this.rng.next() * 3) + 1;
const tags = [];
for (let i = 0; i < tagCount; i++) {
const tag = allTags[Math.floor(this.rng.next() * allTags.length)];
if (!tags.includes(tag)) {
tags.push(tag);
}
}
return tags;
}
}
+107
View File
@@ -0,0 +1,107 @@
import assert from 'node:assert/strict';
function toContain(arrayLike, expected) {
if (!Array.isArray(arrayLike) && typeof arrayLike !== 'string') {
throw new assert.AssertionError({ message: 'toContain expects array or string' });
}
if (typeof arrayLike === 'string') {
assert.equal(arrayLike.includes(String(expected)), true);
return;
}
assert.equal(arrayLike.includes(expected), true);
}
function toContainEqual(arrayLike, expected) {
assert.equal(Array.isArray(arrayLike), true);
const found = arrayLike.some((entry) => {
try {
if (expected && expected.__matcher === 'objectContaining') {
for (const [key, value] of Object.entries(expected.value || {})) {
assert.deepEqual(entry?.[key], value);
}
} else {
assert.deepEqual(entry, expected);
}
return true;
} catch {
return false;
}
});
assert.equal(found, true);
}
function toHaveProperty(value, key) {
assert.equal(value != null, true);
assert.equal(Object.prototype.hasOwnProperty.call(value, key), true);
}
export function expect(actual) {
const api = {
toBe(expected) {
assert.equal(actual, expected);
},
toBeDefined() {
assert.notEqual(actual, undefined);
},
toBeNull() {
assert.equal(actual, null);
},
toBeGreaterThan(expected) {
assert.equal(Number(actual) > Number(expected), true);
},
toBeLessThan(expected) {
assert.equal(Number(actual) < Number(expected), true);
},
toBeLessThanOrEqual(expected) {
assert.equal(Number(actual) <= Number(expected), true);
},
toContain(expected) {
toContain(actual, expected);
},
toContainEqual(expected) {
toContainEqual(actual, expected);
},
toHaveProperty(key) {
toHaveProperty(actual, key);
},
not: {
toContain(expected) {
if (typeof actual === 'string') {
assert.equal(actual.includes(String(expected)), false);
return;
}
assert.equal(Array.isArray(actual), true);
assert.equal(actual.includes(expected), false);
}
}
};
Object.defineProperty(api, 'rejects', {
get() {
return {
async toThrow(expectedMessage) {
let thrown = null;
try {
await actual;
} catch (error) {
thrown = error;
}
assert.notEqual(thrown, null);
if (expectedMessage !== undefined) {
const text = String(thrown?.message || thrown || '');
assert.equal(text.includes(String(expectedMessage)), true);
}
}
};
}
});
return api;
}
expect.objectContaining = function objectContaining(value) {
return {
__matcher: 'objectContaining',
value
};
};
+506
View File
@@ -0,0 +1,506 @@
/**
* Performance Metrics Collection and Analysis
*
* Comprehensive performance metrics collection for zanzibar-graph
* performance testing with statistical analysis and reporting.
*/
export class PerformanceMetrics {
constructor() {
this.metrics = new Map();
this.startTime = Date.now();
this.testCount = 0;
}
/**
* Record a performance metric
*/
record(testName, data) {
if (!this.metrics.has(testName)) {
this.metrics.set(testName, []);
}
const metric = {
timestamp: Date.now(),
testName,
data,
testId: ++this.testCount
};
this.metrics.get(testName).push(metric);
}
/**
* Get metrics for a specific test
*/
getMetrics(testName) {
return this.metrics.get(testName) || [];
}
/**
* Get all metrics
*/
getAllMetrics() {
const allMetrics = {};
for (const [testName, metrics] of this.metrics) {
allMetrics[testName] = metrics;
}
return allMetrics;
}
/**
* Calculate statistical summary for a test
*/
calculateSummary(testName) {
const metrics = this.getMetrics(testName);
if (metrics.length === 0) return null;
const values = metrics.map(m => m.data);
// Extract numeric values for statistical analysis
const numericValues = this._extractNumericValues(values);
if (numericValues.length === 0) return null;
return {
count: numericValues.length,
min: Math.min(...numericValues),
max: Math.max(...numericValues),
mean: this._calculateMean(numericValues),
median: this._calculateMedian(numericValues),
p95: this._calculatePercentile(numericValues, 95),
p99: this._calculatePercentile(numericValues, 99),
stdDev: this._calculateStdDev(numericValues),
variance: this._calculateVariance(numericValues)
};
}
/**
* Generate comprehensive performance report
*/
generateReport() {
const report = {
summary: this._generateSummary(),
testResults: {},
performanceTargets: this._getPerformanceTargets(),
recommendations: this._generateRecommendations(),
generatedAt: new Date().toISOString(),
duration: Date.now() - this.startTime
};
// Generate test-specific results
for (const [testName, metrics] of this.metrics) {
report.testResults[testName] = {
summary: this.calculateSummary(testName),
rawData: metrics,
analysis: this._analyzeTest(testName, metrics)
};
}
return report;
}
/**
* Generate overall summary
*/
_generateSummary() {
const allMetrics = this.getAllMetrics();
const totalTests = Object.keys(allMetrics).length;
// Calculate overall performance metrics
let totalLatency = 0;
let totalMemory = 0;
let totalQueries = 0;
let testCount = 0;
for (const [testName, metrics] of Object.entries(allMetrics)) {
for (const metric of metrics) {
if (metric.data.avgLatency) {
totalLatency += metric.data.avgLatency;
testCount++;
}
if (metric.data.memoryUsed) {
totalMemory += metric.data.memoryUsed;
}
if (metric.data.queryCount) {
totalQueries += metric.data.queryCount;
}
}
}
return {
totalTests,
avgLatency: testCount > 0 ? totalLatency / testCount : 0,
totalMemoryUsage: totalMemory,
totalQueries,
testCount,
duration: Date.now() - this.startTime
};
}
/**
* Get performance targets
*/
_getPerformanceTargets() {
return {
latency: {
avg: 100, // ms
p95: 200, // ms
p99: 500 // ms
},
memory: {
small: 512, // MB
medium: 1024, // MB
large: 2048, // MB
enterprise: 4096 // MB
},
throughput: {
qps: 1000, // queries per second
tps: 500 // transactions per second
},
cache: {
hitRate: 0.8, // 80%
evictionRate: 0.1 // 10%
}
};
}
/**
* Generate performance recommendations
*/
_generateRecommendations() {
const recommendations = [];
const summary = this._generateSummary();
const targets = this._getPerformanceTargets();
// Latency recommendations
if (summary.avgLatency > targets.latency.avg) {
recommendations.push({
type: 'latency',
severity: 'high',
message: `Average latency ${summary.avgLatency}ms exceeds target ${targets.latency.avg}ms`,
suggestion: 'Consider optimizing authorization logic or increasing cache size'
});
}
// Memory recommendations
if (summary.totalMemoryUsage > targets.memory.medium) {
recommendations.push({
type: 'memory',
severity: 'medium',
message: `Memory usage ${summary.totalMemoryUsage}MB exceeds target ${targets.memory.medium}MB`,
suggestion: 'Consider implementing memory optimization or increasing heap size'
});
}
// Throughput recommendations
if (summary.totalQueries > 0) {
const avgQPS = (summary.totalQueries / summary.duration) * 1000;
if (avgQPS < targets.throughput.qps * 0.8) {
recommendations.push({
type: 'throughput',
severity: 'medium',
message: `Average QPS ${avgQPS} below target ${targets.throughput.qps}`,
suggestion: 'Consider optimizing query performance or increasing concurrency'
});
}
}
return recommendations;
}
/**
* Analyze specific test results
*/
_analyzeTest(testName, metrics) {
const analysis = {
performance: 'good',
issues: [],
suggestions: []
};
// Analyze based on test type
switch (testName) {
case 'graph_loading':
this._analyzeGraphLoading(metrics, analysis);
break;
case 'authorization_qps':
this._analyzeAuthorizationQPS(metrics, analysis);
break;
case 'memory_leak_test':
this._analyzeMemoryLeak(metrics, analysis);
break;
case 'cache_performance':
this._analyzeCachePerformance(metrics, analysis);
break;
default:
this._analyzeGeneric(metrics, analysis);
}
return analysis;
}
/**
* Analyze graph loading performance
*/
_analyzeGraphLoading(metrics, analysis) {
for (const metric of metrics) {
const data = metric.data;
if (data.loadTime > 30000) {
analysis.issues.push('Graph loading time exceeds 30s limit');
analysis.suggestions.push('Consider optimizing graph construction or using lazy loading');
analysis.performance = 'poor';
}
if (data.memoryUsed > 1024) {
analysis.issues.push('Memory usage exceeds 1GB limit');
analysis.suggestions.push('Consider implementing memory optimization or reducing graph size');
analysis.performance = 'poor';
}
}
}
/**
* Analyze authorization QPS performance
*/
_analyzeAuthorizationQPS(metrics, analysis) {
for (const metric of metrics) {
const data = metric.data;
if (data.actualQPS < data.targetQPS * 0.8) {
analysis.issues.push(`QPS ${data.actualQPS} below 80% of target ${data.targetQPS}`);
analysis.suggestions.push('Consider optimizing authorization logic or increasing concurrency');
analysis.performance = 'poor';
}
if (data.avgLatency > 100) {
analysis.issues.push(`Average latency ${data.avgLatency}ms exceeds 100ms limit`);
analysis.suggestions.push('Consider optimizing query performance or increasing cache size');
analysis.performance = 'poor';
}
if (data.p95Latency > 200) {
analysis.issues.push(`P95 latency ${data.p95Latency}ms exceeds 200ms limit`);
analysis.suggestions.push('Consider optimizing worst-case performance or reducing query complexity');
analysis.performance = 'poor';
}
}
}
/**
* Analyze memory leak test results
*/
_analyzeMemoryLeak(metrics, analysis) {
for (const metric of metrics) {
const data = metric.data;
if (data.totalGrowth > 200) {
analysis.issues.push(`Memory growth ${data.totalGrowth}MB exceeds 200MB limit`);
analysis.suggestions.push('Investigate potential memory leaks in authorization logic');
analysis.performance = 'poor';
}
if (data.totalGrowth > 100) {
analysis.issues.push(`Memory growth ${data.totalGrowth}MB exceeds 100MB limit`);
analysis.suggestions.push('Monitor memory usage and consider implementing garbage collection');
analysis.performance = 'fair';
}
}
}
/**
* Analyze cache performance
*/
_analyzeCachePerformance(metrics, analysis) {
for (const metric of metrics) {
const data = metric.data;
if (data.speedup < 1.5) {
analysis.issues.push(`Cache speedup ${data.speedup}x below 1.5x threshold`);
analysis.suggestions.push('Consider optimizing cache implementation or increasing cache size');
analysis.performance = 'poor';
}
if (data.speedup < 2.0) {
analysis.issues.push(`Cache speedup ${data.speedup}x below 2.0x threshold`);
analysis.suggestions.push('Consider optimizing cache hit rate or cache eviction strategy');
analysis.performance = 'fair';
}
}
}
/**
* Generic analysis for unknown test types
*/
_analyzeGeneric(metrics, analysis) {
const summary = this.calculateSummary(metrics[0]?.testName);
if (!summary) return;
if (summary.mean > 1000) {
analysis.issues.push(`Average performance ${summary.mean}ms exceeds 1000ms threshold`);
analysis.suggestions.push('Consider optimizing performance or reducing complexity');
analysis.performance = 'poor';
}
}
/**
* Extract numeric values from metric data
*/
_extractNumericValues(values) {
const numericValues = [];
for (const value of values) {
if (typeof value === 'number') {
numericValues.push(value);
} else if (typeof value === 'object' && value !== null) {
// Extract numeric values from objects
for (const [key, val] of Object.entries(value)) {
if (typeof val === 'number') {
numericValues.push(val);
}
}
}
}
return numericValues;
}
/**
* Calculate mean
*/
_calculateMean(values) {
return values.reduce((sum, val) => sum + val, 0) / values.length;
}
/**
* Calculate median
*/
_calculateMedian(values) {
const sorted = [...values].sort((a, b) => a - b);
const mid = Math.floor(sorted.length / 2);
return sorted.length % 2 === 0
? (sorted[mid - 1] + sorted[mid]) / 2
: sorted[mid];
}
/**
* Calculate percentile
*/
_calculatePercentile(values, percentile) {
const sorted = [...values].sort((a, b) => a - b);
const index = Math.ceil((percentile / 100) * sorted.length) - 1;
return sorted[Math.max(0, index)];
}
/**
* Calculate standard deviation
*/
_calculateStdDev(values) {
const mean = this._calculateMean(values);
const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;
return Math.sqrt(variance);
}
/**
* Calculate variance
*/
_calculateVariance(values) {
const mean = this._calculateMean(values);
return values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;
}
/**
* Export metrics to JSON
*/
exportToJSON() {
return JSON.stringify(this.generateReport(), null, 2);
}
/**
* Export metrics to CSV
*/
exportToCSV() {
const csv = [];
csv.push('TestName,Timestamp,TestId,Data');
for (const [testName, metrics] of this.metrics) {
for (const metric of metrics) {
csv.push(`${testName},${metric.timestamp},${metric.testId},"${JSON.stringify(metric.data)}"`);
}
}
return csv.join('\n');
}
/**
* Clear all metrics
*/
clear() {
this.metrics.clear();
this.startTime = Date.now();
this.testCount = 0;
}
/**
* Get metrics for a specific time range
*/
getMetricsInRange(startTime, endTime) {
const filteredMetrics = new Map();
for (const [testName, metrics] of this.metrics) {
const filtered = metrics.filter(m =>
m.timestamp >= startTime && m.timestamp <= endTime
);
if (filtered.length > 0) {
filteredMetrics.set(testName, filtered);
}
}
return filteredMetrics;
}
/**
* Get performance trends over time
*/
getPerformanceTrends(testName, windowSize = 1000) {
const metrics = this.getMetrics(testName);
if (metrics.length === 0) return null;
const trends = [];
const window = Math.min(windowSize, metrics.length);
for (let i = window; i <= metrics.length; i++) {
const windowMetrics = metrics.slice(i - window, i);
const trend = this._calculateTrend(windowMetrics);
trends.push({
timestamp: metrics[i - 1].timestamp,
trend: trend
});
}
return trends;
}
/**
* Calculate trend for a window of metrics
*/
_calculateTrend(metrics) {
if (metrics.length < 2) return null;
const values = this._extractNumericValues(metrics.map(m => m.data));
if (values.length < 2) return null;
const firstHalf = values.slice(0, Math.floor(values.length / 2));
const secondHalf = values.slice(Math.floor(values.length / 2));
const firstMean = this._calculateMean(firstHalf);
const secondMean = this._calculateMean(secondHalf);
return {
direction: secondMean > firstMean ? 'increasing' : 'decreasing',
change: secondMean - firstMean,
changePercent: ((secondMean - firstMean) / firstMean) * 100
};
}
}
+383
View File
@@ -0,0 +1,383 @@
import fs from 'node:fs';
function randomInt(n) {
return Math.floor(Math.random() * n);
}
function generateBusinessGraph(numUsers = 5000, numGroups = 200, numDocs = 2000) {
const nodes = [];
const relations = [];
const adminKeys = [];
const newEmployeeKeys = [];
const testUserKeys = [];
console.log('🏗️ Generating realistic business authorization graph...');
console.log(`📊 Scale: ${numUsers} users, ${numGroups} groups, ${numDocs} documents`);
// Add organizational groups with realistic structure
const departments = ['engineering', 'product', 'design', 'marketing', 'sales', 'hr', 'finance', 'legal', 'operations', 'security', 'data', 'research'];
const levels = ['intern', 'junior', 'mid', 'senior', 'staff', 'principal', 'lead', 'manager', 'director', 'vp'];
const clearanceLevels = ['public', 'internal', 'confidential', 'restricted', 'secret', 'top_secret'];
const documentTypes = ['manual', 'specification', 'budget', 'contract', 'policy', 'report', 'plan'];
const currencies = ['usd', 'eur', 'gbp'];
console.log('📁 Creating organizational structure...');
// Create department groups
for (let i = 0; i < departments.length; i++) {
const dept = departments[i];
nodes.push({ key: `group:${dept}`, type: 'group' });
// Create level-based subgroups within each department
for (let j = 0; j < levels.length; j++) {
const level = levels[j];
const groupKey = `group:${dept}_${level}`;
nodes.push({ key: groupKey, type: 'group' });
// Create hierarchy: department contains level groups
relations.push({ src: `group:${dept}`, rel: 'contains', dst: groupKey });
// Create management hierarchy (for computed_userset rules)
if (j < levels.length - 1) {
const higherLevel = levels[j + 1];
const higherGroupKey = `group:${dept}_${higherLevel}`;
relations.push({ src: higherGroupKey, rel: 'manages', dst: groupKey });
}
}
}
// Add projects and teams
const projectTypes = ['project', 'team', 'committee', 'guild', 'workgroup'];
for (let i = 0; i < numGroups - (departments.length * (levels.length + 1)); i++) {
const type = projectTypes[i % projectTypes.length];
nodes.push({ key: `group:${type}_${i}`, type: 'group' });
}
// Add clearance levels, document types, and currencies
for (const clearance of clearanceLevels) {
nodes.push({ key: `clearance:${clearance}`, type: 'clearance' });
}
for (const docType of documentTypes) {
nodes.push({ key: `doctype:${docType}`, type: 'document_type' });
}
for (const currency of currencies) {
nodes.push({ key: `currency:${currency}`, type: 'currency' });
}
// Add budget and cost tracking nodes for financial rules
for (let i = 0; i < departments.length; i++) {
const dept = departments[i];
nodes.push({ key: `budget:${dept}_2024`, type: 'budget' });
nodes.push({ key: `account:${dept}_operational`, type: 'account' });
}
console.log('👥 Creating users with realistic attributes...');
// Add users with realistic department/level assignments
for (let i = 0; i < numUsers; i++) {
const userKey = `user:${i}`;
nodes.push({ key: userKey, type: 'user' });
// Assign to department and level with realistic distribution
const dept = departments[randomInt(departments.length)];
const level = levels[Math.min(levels.length - 1, Math.floor(Math.abs(gaussianRandom()) * 3) + 2)]; // Bias toward mid-level
const deptGroupKey = `group:${dept}`;
const levelGroupKey = `group:${dept}_${level}`;
relations.push({ src: userKey, rel: 'member_of', dst: deptGroupKey });
relations.push({ src: userKey, rel: 'member_of', dst: levelGroupKey });
// Add user accounts with balances (for relational_comparator rules)
const accountKey = `account:user_${i}`;
nodes.push({ key: accountKey, type: 'account' });
relations.push({ src: userKey, rel: 'owns_account', dst: accountKey });
// Assign random balance (10K - 100K USD)
const balance = 10000 + randomInt(90000);
relations.push({ src: accountKey, rel: 'has_balance', dst: 'currency:usd', value: balance });
// Assign clearance level (higher levels get higher clearance)
const clearanceIndex = Math.min(
clearanceLevels.length - 1,
levels.indexOf(level) + randomInt(3)
);
const clearance = clearanceLevels[clearanceIndex];
relations.push({ src: userKey, rel: 'has_clearance', dst: `clearance:${clearance}` });
// Users join multiple cross-functional groups (realistic for large orgs)
const numGroups = Math.floor(Math.abs(gaussianRandom()) * 3) + 1; // 1-4 groups
for (let g = 0; g < numGroups; g++) {
if (Math.random() < 0.4) {
const projectGroup = `group:project_${randomInt(numGroups - (departments.length * (levels.length + 1)))}`;
relations.push({ src: userKey, rel: 'member_of', dst: projectGroup });
}
}
if (i % 500 === 0) console.log(` 👤 Added ${i} users...`);
}
// Add system admins with full access
console.log('🔐 Creating system administrators...');
for (let i = 0; i < 10; i++) {
const adminKey = `admin:${i}`;
nodes.push({ key: adminKey, type: 'admin' });
adminKeys.push(adminKey);
// Admins have top secret clearance and access to all groups
relations.push({ src: adminKey, rel: 'has_clearance', dst: 'clearance:top_secret' });
for (const dept of departments) {
relations.push({ src: adminKey, rel: 'superadmin', dst: `group:${dept}` });
}
// Admins have large budgets
const adminAccount = `account:admin_${i}`;
nodes.push({ key: adminAccount, type: 'account' });
relations.push({ src: adminKey, rel: 'owns_account', dst: adminAccount });
relations.push({ src: adminAccount, rel: 'has_balance', dst: 'currency:usd', value: 1000000 });
}
// Add new employees (for inference testing - no direct access)
console.log('🆕 Creating new employees for inference testing...');
for (let i = 0; i < 50; i++) {
const newEmpKey = `newbie:${i}`;
nodes.push({ key: newEmpKey, type: 'user' });
newEmployeeKeys.push(newEmpKey);
// Give them basic attributes but limited document access
const dept = departments[randomInt(departments.length)];
const level = 'intern'; // New employees start as interns
relations.push({ src: newEmpKey, rel: 'member_of', dst: `group:${dept}` });
relations.push({ src: newEmpKey, rel: 'member_of', dst: `group:${dept}_${level}` });
relations.push({ src: newEmpKey, rel: 'has_clearance', dst: 'clearance:internal' });
// Give them small budgets
const newEmpAccount = `account:newbie_${i}`;
nodes.push({ key: newEmpAccount, type: 'account' });
relations.push({ src: newEmpKey, rel: 'owns_account', dst: newEmpAccount });
relations.push({ src: newEmpAccount, rel: 'has_balance', dst: 'currency:usd', value: 5000 + randomInt(10000) });
}
// Add test users with specific patterns for inference validation
console.log('🧪 Creating test users for inference validation...');
for (let i = 0; i < 100; i++) {
const testUserKey = `test_user:${i}`;
nodes.push({ key: testUserKey, type: 'user' });
testUserKeys.push(testUserKey);
// Create similar patterns to existing users
const dept = departments[i % departments.length];
const level = levels[Math.floor(i / departments.length) % levels.length];
relations.push({ src: testUserKey, rel: 'member_of', dst: `group:${dept}` });
relations.push({ src: testUserKey, rel: 'member_of', dst: `group:${dept}_${level}` });
const clearanceIndex = Math.min(clearanceLevels.length - 1, levels.indexOf(level) + 1);
relations.push({ src: testUserKey, rel: 'has_clearance', dst: `clearance:${clearanceLevels[clearanceIndex]}` });
// Give them moderate budgets
const testAccount = `account:test_${i}`;
nodes.push({ key: testAccount, type: 'account' });
relations.push({ src: testUserKey, rel: 'owns_account', dst: testAccount });
relations.push({ src: testAccount, rel: 'has_balance', dst: 'currency:usd', value: 15000 + randomInt(50000) });
}
console.log('📄 Creating documents with complex access patterns...');
// Create documents with rich metadata
const docKeys = [];
for (let i = 0; i < numDocs; i++) {
const docKey = `doc:${i}`;
nodes.push({ key: docKey, type: 'doc' });
docKeys.push(docKey);
// Classify document with bias toward lower classifications
const classificationIndex = Math.min(
clearanceLevels.length - 1,
Math.floor(Math.abs(gaussianRandom()) * 2) + 1
);
const classification = clearanceLevels[classificationIndex];
relations.push({ src: docKey, rel: 'classified_as', dst: `clearance:${classification}` });
// Assign document type
const docType = documentTypes[randomInt(documentTypes.length)];
relations.push({ src: docKey, rel: 'has_type', dst: `doctype:${docType}` });
// Assign document to departments
const owningDept = departments[randomInt(departments.length)];
relations.push({ src: docKey, rel: 'owned_by', dst: `group:${owningDept}` });
// Add cost information for budget documents (for relational_comparator rules)
if (docType === 'budget' || docType === 'contract') {
const cost = 1000 + randomInt(50000); // $1K - $50K
relations.push({ src: docKey, rel: 'has_cost', dst: 'currency:usd', value: cost });
}
}
// Generate realistic access patterns
console.log('🔥 Generating complex authorization patterns...');
// Sort documents by "popularity" with more realistic distribution
const superPopularDocs = docKeys.slice(0, Math.floor(numDocs * 0.05)); // Top 5% - super popular
const popularDocs = docKeys.slice(Math.floor(numDocs * 0.05), Math.floor(numDocs * 0.25)); // Next 20% - popular
const commonDocs = docKeys.slice(Math.floor(numDocs * 0.25), Math.floor(numDocs * 0.70)); // Next 45% - common
const rareDocs = docKeys.slice(Math.floor(numDocs * 0.70)); // Bottom 30% - rare
console.log(` 📊 Super popular: ${superPopularDocs.length}, Popular: ${popularDocs.length}, Common: ${commonDocs.length}, Rare: ${rareDocs.length}`);
// Get all regular users (not admins/newbies/test users)
const regularUsers = Array.from({ length: numUsers }, (_, i) => `user:${i}`);
// Create GROUP-BASED access patterns (for tuple_to_userset rules)
console.log('👥 Creating group-based access patterns...');
for (const dept of departments) {
const deptGroupKey = `group:${dept}`;
// Department groups get access to documents they own
const deptDocs = docKeys.filter((_, i) => i % departments.length === departments.indexOf(dept));
for (const docKey of deptDocs.slice(0, 20)) { // Limit for performance
relations.push({ src: deptGroupKey, rel: 'can_read', dst: docKey });
}
}
// SUPER POPULAR DOCS: Mix of direct and group access
for (const docKey of superPopularDocs) {
// 50% direct access, 50% group access
if (Math.random() < 0.5) {
// Direct user access
const numUsersWithAccess = Math.floor(regularUsers.length * 0.4);
const usersWithAccess = new Set();
for (let i = 0; i < numUsersWithAccess; i++) {
const userIndex = Math.floor(Math.random() * regularUsers.length);
const userKey = regularUsers[userIndex];
if (!usersWithAccess.has(userKey)) {
usersWithAccess.add(userKey);
relations.push({ src: userKey, rel: 'can_read', dst: docKey });
}
}
} else {
// Group-based access (will require tuple_to_userset rule)
const owningDept = departments[randomInt(departments.length)];
relations.push({ src: `group:${owningDept}`, rel: 'can_read', dst: docKey });
}
}
// POPULAR DOCS: Mostly group access
for (const docKey of popularDocs) {
if (Math.random() < 0.8) {
// Group access
const owningDept = departments[randomInt(departments.length)];
relations.push({ src: `group:${owningDept}`, rel: 'can_read', dst: docKey });
} else {
// Limited direct access
const numUsersWithAccess = Math.floor(regularUsers.length * 0.1);
const usersWithAccess = new Set();
for (let i = 0; i < numUsersWithAccess; i++) {
const userIndex = Math.floor(Math.random() * regularUsers.length);
const userKey = regularUsers[userIndex];
if (!usersWithAccess.has(userKey)) {
usersWithAccess.add(userKey);
relations.push({ src: userKey, rel: 'can_read', dst: docKey });
}
}
}
}
// COMMON and RARE DOCS: Mostly group access
for (const docKey of [...commonDocs, ...rareDocs]) {
if (Math.random() < 0.9) {
// Group access
const owningDept = departments[randomInt(departments.length)];
relations.push({ src: `group:${owningDept}`, rel: 'can_read', dst: docKey });
}
}
// Give admins direct access to all documents
console.log('🔐 Granting admin access...');
for (const adminKey of adminKeys) {
for (const docKey of docKeys) {
relations.push({ src: adminKey, rel: 'can_read', dst: docKey });
}
}
// Create patterns for new employees and test users
console.log('🎯 Creating inference-friendly patterns...');
// Give new employees limited direct access
for (const newEmpKey of newEmployeeKeys) {
const docsToAccess = Math.floor(superPopularDocs.length * 0.1);
const accessedDocs = new Set();
for (let i = 0; i < docsToAccess; i++) {
const docKey = superPopularDocs[Math.floor(Math.random() * superPopularDocs.length)];
if (!accessedDocs.has(docKey)) {
accessedDocs.add(docKey);
relations.push({ src: newEmpKey, rel: 'can_read', dst: docKey });
}
}
}
// Give test users mixed access patterns
for (const testUserKey of testUserKeys) {
const superPopularAccess = Math.floor(superPopularDocs.length * 0.2);
const accessedDocs = new Set();
for (let i = 0; i < superPopularAccess; i++) {
const docKey = superPopularDocs[Math.floor(Math.random() * superPopularDocs.length)];
if (!accessedDocs.has(docKey)) {
accessedDocs.add(docKey);
relations.push({ src: testUserKey, rel: 'can_read', dst: docKey });
}
}
}
console.log('✅ Complex graph generation complete!');
console.log(`📊 Final stats:`);
console.log(`${nodes.length} nodes`);
console.log(`${relations.length} relations`);
console.log(`${adminKeys.length} admins`);
console.log(`${newEmployeeKeys.length} new employees`);
console.log(`${testUserKeys.length} test users`);
console.log(`${departments.length} departments with ${levels.length} levels each`);
console.log(`${clearanceLevels.length} clearance levels`);
console.log(`${documentTypes.length} document types`);
return {
nodes,
relations,
adminKeys,
newEmployeeKeys,
testUserKeys,
departments,
levels,
clearanceLevels,
documentTypes,
currencies
};
}
// Gaussian random number generator for more realistic distributions
function gaussianRandom() {
let u = 0, v = 0;
while(u === 0) u = Math.random(); // Converting [0,1) to (0,1)
while(v === 0) v = Math.random();
return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
}
// Generate different sized graphs for testing - MUCH BIGGER
const configs = [
{ name: 'small', users: 1000, groups: 100, docs: 500 },
{ name: 'medium', users: 5000, groups: 250, docs: 2000 },
{ name: 'large', users: 10000, groups: 500, docs: 5000 },
{ name: 'huge', users: 25000, groups: 1000, docs: 10000 }
];
const configName = process.argv[2] || 'medium';
const config = configs.find(c => c.name === configName) || configs[1];
console.log(`🎯 Generating ${config.name} graph configuration...`);
const data = generateBusinessGraph(config.users, config.groups, config.docs);
const filename = `big-graph-data-${config.name}.json`;
fs.writeFileSync(filename, JSON.stringify(data, null, 2));
console.log(`💾 Saved to ${filename}`);
console.log(`🚀 Run: node big-graph.test.js ${config.name}`);