import fs from 'node:fs'; // Zipfian distribution generator function zipfian(n, s = 1.0) { const harmonic = Array.from({ length: n }, (_, i) => 1 / Math.pow(i + 1, s)) .reduce((sum, val) => sum + val, 0); return function() { const r = Math.random() * harmonic; let sum = 0; for (let i = 0; i < n; i++) { sum += 1 / Math.pow(i + 1, s); if (sum >= r) return i; } return n - 1; }; } // Preferential attachment with aging class PreferentialAttachment { constructor() { this.degrees = new Map(); this.totalDegree = 0; this.ageDecay = 0.95; // Older connections become less likely } addNode(nodeKey) { this.degrees.set(nodeKey, 1); // Start with degree 1 this.totalDegree += 1; } selectNode() { if (this.totalDegree === 0) return null; let r = Math.random() * this.totalDegree; for (const [nodeKey, degree] of this.degrees) { r -= degree; if (r <= 0) return nodeKey; } return Array.from(this.degrees.keys())[0]; // Fallback } addEdge(srcKey, dstKey) { this.degrees.set(srcKey, (this.degrees.get(srcKey) || 0) + 1); this.degrees.set(dstKey, (this.degrees.get(dstKey) || 0) + 1); this.totalDegree += 2; } decay() { // Age all connections slightly for (const [nodeKey, degree] of this.degrees) { const newDegree = Math.max(1, degree * this.ageDecay); this.totalDegree += newDegree - degree; this.degrees.set(nodeKey, newDegree); } } } function generateCanonicalBenchmarkGraph(config = {}) { const { numUsers = 10000, numGroups = 100, numRoles = 20, numProjects = 200, numDocs = 5000, numDevices = 1000, numLocations = 50, zipfianExponent = 1.2, preferentialStrength = 0.8 } = config; console.log('šŸ—ļø Generating Canonical Benchmark Graph'); console.log(`Users: ${numUsers}, Groups: ${numGroups}, Docs: ${numDocs}`); console.log(`Zipfian exponent: ${zipfianExponent}, Preferential strength: ${preferentialStrength}`); const nodes = []; const relations = []; const metadata = { generation: { timestamp: new Date().toISOString(), config, stats: {} }, testCases: { highConfidenceAllow: [], mediumConfidenceAllow: [], lowConfidenceAllow: [], shouldDeny: [], uncertain: [], admins: [], newEmployees: [] }, relationConfigs: {} }; // Zipfian generators for realistic distributions const groupZipf = zipfian(numGroups, zipfianExponent); const roleZipf = zipfian(numRoles, zipfianExponent); const projectZipf = zipfian(numProjects, zipfianExponent); const docZipf = zipfian(numDocs, zipfianExponent * 0.8); // Slightly flatter for docs // Preferential attachment trackers const groupPA = new PreferentialAttachment(); const projectPA = new PreferentialAttachment(); const userPA = new PreferentialAttachment(); console.log('Creating nodes...'); // Create organizational structure for (let i = 0; i < numGroups; i++) { const groupKey = `group:${i}`; nodes.push({ key: groupKey, type: 'group' }); groupPA.addNode(groupKey); } for (let i = 0; i < numRoles; i++) { nodes.push({ key: `role:${i}`, type: 'role' }); } for (let i = 0; i < numProjects; i++) { const projectKey = `project:${i}`; nodes.push({ key: projectKey, type: 'project' }); projectPA.addNode(projectKey); } for (let i = 0; i < numLocations; i++) { nodes.push({ key: `location:${i}`, type: 'location' }); } for (let i = 0; i < numDevices; i++) { nodes.push({ key: `device:${i}`, type: 'device' }); } // Create users with realistic distributions console.log('Creating users and basic relations...'); for (let i = 0; i < numUsers; i++) { const userKey = `user:${i}`; nodes.push({ key: userKey, type: 'user' }); userPA.addNode(userKey); // Zipfian group membership (some groups are much more popular) const numGroupMemberships = Math.min(5, Math.floor(Math.random() * 3) + 1); for (let j = 0; j < numGroupMemberships; j++) { const groupIdx = groupZipf(); const groupKey = `group:${groupIdx}`; relations.push({ src: userKey, rel: 'member_of', dst: groupKey, possibility: 0.95 + Math.random() * 0.05 // High confidence }); groupPA.addEdge(userKey, groupKey); } // Role assignment (Zipfian - some roles much more common) if (Math.random() < 0.8) { // 80% of users have roles const roleIdx = roleZipf(); relations.push({ src: userKey, rel: 'has_role', dst: `role:${roleIdx}`, possibility: 0.9 + Math.random() * 0.1 }); } // Project assignment (preferential attachment) const numProjects = Math.floor(Math.random() * 3) + 1; for (let j = 0; j < numProjects; j++) { let projectKey; if (Math.random() < preferentialStrength) { projectKey = projectPA.selectNode(); } else { projectKey = `project:${Math.floor(Math.random() * numProjects)}`; } if (projectKey) { relations.push({ src: userKey, rel: 'works_on', dst: projectKey, possibility: 0.85 + Math.random() * 0.15 }); projectPA.addEdge(userKey, projectKey); } } // Location assignment if (Math.random() < 0.9) { // 90% have locations const locationIdx = Math.floor(Math.random() * numLocations); relations.push({ src: userKey, rel: 'located_at', dst: `location:${locationIdx}`, possibility: 0.98 }); } // Device assignment if (Math.random() < 0.7) { // 70% have assigned devices const deviceIdx = Math.floor(Math.random() * numDevices); relations.push({ src: userKey, rel: 'uses_device', dst: `device:${deviceIdx}`, possibility: 0.9 + Math.random() * 0.1 }); } if (i % 1000 === 0) console.log(` Created ${i} users...`); } // Create documents with realistic access patterns console.log('Creating documents and access patterns...'); for (let i = 0; i < numDocs; i++) { const docKey = `doc:${i}`; nodes.push({ key: docKey, type: 'doc' }); // Document ownership (Zipfian - some groups own many docs) const ownerGroupIdx = groupZipf(); const ownerGroupKey = `group:${ownerGroupIdx}`; relations.push({ src: ownerGroupKey, rel: 'owns', dst: docKey, possibility: 1.0 }); // Direct user access (some users have direct access) if (Math.random() < 0.3) { // 30% of docs have direct user access const numDirectUsers = Math.floor(Math.random() * 5) + 1; for (let j = 0; j < numDirectUsers; j++) { const userIdx = Math.floor(Math.random() * numUsers); const confidence = Math.random(); let possibility; if (confidence < 0.3) possibility = 0.95 + Math.random() * 0.05; // High confidence else if (confidence < 0.7) possibility = 0.7 + Math.random() * 0.2; // Medium confidence else possibility = 0.4 + Math.random() * 0.3; // Lower confidence relations.push({ src: `user:${userIdx}`, rel: 'can_read', dst: docKey, possibility }); } } // Project-based access if (Math.random() < 0.4) { // 40% of docs are project-related const projectIdx = Math.floor(Math.random() * numProjects); relations.push({ src: `project:${projectIdx}`, rel: 'can_read', dst: docKey, possibility: 0.8 + Math.random() * 0.2 }); } if (i % 500 === 0) console.log(` Created ${i} documents...`); } // Create hierarchical relationships console.log('Creating hierarchical relationships...'); // Group hierarchies (some groups are parents of others) for (let i = 0; i < numGroups; i++) { if (Math.random() < 0.3) { // 30% chance of having a parent const parentIdx = Math.floor(Math.random() * numGroups); if (parentIdx !== i) { relations.push({ src: `group:${parentIdx}`, rel: 'parent_of', dst: `group:${i}`, possibility: 1.0 }); } } } // Role hierarchies for (let i = 1; i < numRoles; i++) { if (Math.random() < 0.4) { // 40% chance of role inheritance const parentRoleIdx = Math.floor(Math.random() * i); relations.push({ src: `role:${parentRoleIdx}`, rel: 'inherits_from', dst: `role:${i}`, possibility: 1.0 }); } } // Create special test users console.log('Creating special test users...'); // Super admins (high access to everything) for (let i = 0; i < 3; i++) { const adminKey = `admin:${i}`; nodes.push({ key: adminKey, type: 'admin' }); metadata.testCases.admins.push(adminKey); // Admin access to all groups and many docs for (let g = 0; g < numGroups; g++) { relations.push({ src: adminKey, rel: 'admin_of', dst: `group:${g}`, possibility: 1.0 }); } // Admin access to many documents for (let d = 0; d < Math.min(1000, numDocs); d++) { relations.push({ src: adminKey, rel: 'can_read', dst: `doc:${d}`, possibility: 0.98 }); } } // New employees (minimal access) for (let i = 0; i < 10; i++) { const newEmpKey = `newbie:${i}`; nodes.push({ key: newEmpKey, type: 'user' }); metadata.testCases.newEmployees.push(newEmpKey); // Only basic group membership relations.push({ src: newEmpKey, rel: 'member_of', dst: 'group:0', // Everyone group possibility: 1.0 }); } // Generate test cases for different confidence levels console.log('Generating test cases...'); // High confidence cases (direct access) for (let i = 0; i < 100; i++) { const userIdx = Math.floor(Math.random() * numUsers); const docIdx = Math.floor(Math.random() * numDocs); metadata.testCases.highConfidenceAllow.push([`user:${userIdx}`, 'can_read', `doc:${docIdx}`]); } // Medium confidence cases (group-based access) for (let i = 0; i < 100; i++) { const userIdx = Math.floor(Math.random() * numUsers); const docIdx = Math.floor(Math.random() * numDocs); metadata.testCases.mediumConfidenceAllow.push([`user:${userIdx}`, 'can_read', `doc:${docIdx}`]); } // Low confidence cases (similarity-based) for (let i = 0; i < 100; i++) { const userIdx = Math.floor(Math.random() * numUsers); const docIdx = Math.floor(Math.random() * numDocs); metadata.testCases.lowConfidenceAllow.push([`user:${userIdx}`, 'can_read', `doc:${docIdx}`]); } // Should deny cases (cross-tenant, etc.) for (let i = 0; i < 50; i++) { const newEmpIdx = Math.floor(Math.random() * 10); const docIdx = Math.floor(Math.random() * 1000) + 1000; // Higher doc numbers metadata.testCases.shouldDeny.push([`newbie:${newEmpIdx}`, 'can_read', `doc:${docIdx}`]); } // Uncertain cases (edge cases) for (let i = 0; i < 50; i++) { const userIdx = Math.floor(Math.random() * numUsers); const docIdx = Math.floor(Math.random() * numDocs); metadata.testCases.uncertain.push([`user:${userIdx}`, 'can_read', `doc:${docIdx}`]); } // Define complex relation configurations metadata.relationConfigs = { can_read: { union: [ { type: 'direct', priority: 10, weight: 1.0 }, { type: 'tuple_to_userset', tuplesetRelation: 'member_of', computedRelation: 'can_read', priority: 8, weight: 0.9 }, { type: 'tuple_to_userset', tuplesetRelation: 'works_on', computedRelation: 'can_read', priority: 7, weight: 0.8 }, { type: 'similar_to', relation: 'can_read', k: 5, similarityThreshold: 0.4, priority: 5, weight: 0.6 }, { type: 'multi_hop', path: ['has_role', 'can_read'], priority: 4, weight: 0.7 } ] }, admin_of: { union: [ { type: 'direct', priority: 10 }, { type: 'tuple_to_userset', tuplesetRelation: 'inherits_from', computedRelation: 'admin_of', priority: 8 } ] } }; // Calculate statistics metadata.generation.stats = { totalNodes: nodes.length, totalRelations: relations.length, nodeTypes: { users: nodes.filter(n => n.type === 'user').length, groups: nodes.filter(n => n.type === 'group').length, docs: nodes.filter(n => n.type === 'doc').length, roles: nodes.filter(n => n.type === 'role').length, projects: nodes.filter(n => n.type === 'project').length, devices: nodes.filter(n => n.type === 'device').length, locations: nodes.filter(n => n.type === 'location').length, admins: nodes.filter(n => n.type === 'admin').length }, relationTypes: relations.reduce((acc, rel) => { acc[rel.rel] = (acc[rel.rel] || 0) + 1; return acc; }, {}), avgPossibility: relations.filter(r => r.possibility).reduce((sum, r) => sum + r.possibility, 0) / relations.filter(r => r.possibility).length }; console.log('\\nšŸ“Š Graph Statistics:'); console.log(`Total nodes: ${metadata.generation.stats.totalNodes}`); console.log(`Total relations: ${metadata.generation.stats.totalRelations}`); console.log(`Node types:`, metadata.generation.stats.nodeTypes); console.log(`Relation types:`, metadata.generation.stats.relationTypes); console.log(`Average possibility: ${metadata.generation.stats.avgPossibility.toFixed(3)}`); return { nodes, relations, metadata }; } // Generate the canonical benchmark graph console.log('šŸš€ Generating Canonical Benchmark Graph...'); const canonicalGraph = generateCanonicalBenchmarkGraph({ numUsers: 10000, numGroups: 100, numRoles: 20, numProjects: 200, numDocs: 5000, numDevices: 1000, numLocations: 50, zipfianExponent: 1.2, preferentialStrength: 0.8 }); // Save to file const filename = 'canonical-benchmark-graph.json'; console.log(`\\nšŸ’¾ Saving to ${filename}...`); fs.writeFileSync(filename, JSON.stringify(canonicalGraph, null, 2)); console.log('\\nāœ… Canonical benchmark graph generated successfully!'); console.log(`šŸ“ Saved to: ${filename}`); console.log(`šŸ“ Size: ${(fs.statSync(filename).size / 1024 / 1024).toFixed(2)} MB`);