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