Files

383 lines
16 KiB
JavaScript
Raw Permalink Normal View History

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}`);