/** * rigor/complex-graph-crucible.test.js — js-rigor crucibles over realistic * complex graphs (community block model, scale-free, org hierarchy, dense * adversarial). * * Unlike the toy graphs used by other campaigns, these graphs are shaped * like production communities. The crucibles verify, on every generated * graph and across seeds: * * PARITY — normal, binary, and snapshot-restored evaluation agree * on allow/deny and on possibility (within quantization) * BOUNDS — every result possibility/reliability ∈ [0,1] * SHAPE — generators produce the claimed structure (node/edge * counts, policy configs present) * NO-DIRECT — complex policies exist and are reachable (the engine * is not just serving direct hits) */ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { rigor } from '@rigor/core'; import { Arbiter } from '../../src/index.js'; import { makeCommunityGraph, makeScaleFreeGraph, makeHierarchyGraph, makeDenseAdversarial } from './complex-graphs.js'; const EPS = 1e-4; const GENERATORS = [ { name: 'community', make: makeCommunityGraph }, { name: 'scale-free', make: makeScaleFreeGraph }, { name: 'hierarchy', make: makeHierarchyGraph }, { name: 'dense-adversarial', make: makeDenseAdversarial } ]; function nodeKeys(graph) { return [...graph.users, ...(graph.resources || [])]; } function fail(message) { throw new Error(message); } function checkModes(arbiter, user, relation, object) { const normal = arbiter.check(user, relation, object); const binary = arbiter.check(user, relation, object, { binary: true }); arbiter.enableCondensedSnapshot(); const buf = arbiter.toSnapshotBinary(); const restored = Arbiter.fromSnapshotBinary(buf); const snapshot = restored.check(user, relation, object); return { normal, binary, snapshot }; } describe('Complex-graph crucibles (rigor)', () => { it('SHAPE: generators produce the claimed structure', async () => { const checks = { community: (g) => g.meta.communities >= 3 && g.arbiter.relations.length > 50, 'scale-free': (g) => g.meta.users >= 100 && g.meta.edges >= 200, hierarchy: (g) => g.meta.departments >= 2 && g.arbiter.relations.length > 50, 'dense-adversarial': (g) => g.meta.users >= 4 && g.meta.resources >= 4 }; for (const { name, make } of GENERATORS) { for (const seed of [1, 2, 3, 4, 5]) { const g = make(seed); if (!checks[name](g)) fail(`generator ${name} (seed ${seed}) did not produce claimed shape`); } } }); it('PARITY + BOUNDS: normal/binary/snapshot agree on every query across all generators', async () => { const queries = []; for (const { make } of GENERATORS) { const g = make(1); const keys = nodeKeys(g); const relations = ['can_read', 'can_write', 'can_access', 'can_view', 'can_view_with_direct', 'can_view_not_blocked', 'can_access_org']; for (let i = 0; i < 40; i++) { queries.push({ user: keys[Math.floor(Math.random() * keys.length)], relation: relations[Math.floor(Math.random() * relations.length)], object: keys[Math.floor(Math.random() * keys.length)] }); } } for (const q of queries) { const results = []; for (const { make } of GENERATORS) { const g = make(1); results.push(checkModes(g.arbiter, q.user, q.relation, q.object)); } for (const { normal, binary, snapshot } of results) { if (normal.possibility < 0 || normal.possibility > 1 || normal.reliability < 0 || normal.reliability > 1) { fail(`BOUNDS violated: ${JSON.stringify(normal)}`); } if (normal.possibility > 0 !== binary.possibility > 0) { fail(`binary mismatch: normal=${normal.possibility} binary=${binary.possibility}`); } if (Math.abs(normal.possibility - snapshot.possibility) > EPS) { fail(`snapshot mismatch: normal=${normal.possibility} snapshot=${snapshot.possibility}`); } } } }); it('PARITY via rigor fuzz: seeded generator fuzz over normal/binary/snapshot agreement', async () => { async function check(args) { const { genKind, seed, user, relation, object } = args; const make = GENERATORS.find(g => g.name === genKind).make; const g = make(seed); const { normal, binary, snapshot } = checkModes(g.arbiter, user, relation, object); const ok = normal.possibility >= 0 && normal.possibility <= 1 && normal.possibility > 0 === binary.possibility > 0 && Math.abs(normal.possibility - snapshot.possibility) <= EPS; if (!ok) { fail(`mode disagreement on ${genKind} seed=${seed} ${user} ${relation} ${object}: normal=${normal.possibility} binary=${binary.possibility} snapshot=${snapshot.possibility}`); } return true; } const report = await rigor.campaign( [ rigor.fn('check', check, rigor.args( rigor.gen.object({ genKind: rigor.gen.oneOf(GENERATORS.map(g => g.name)), seed: rigor.gen.int(1, 8), user: rigor.gen.string({ minLength: 1, maxLength: 20 }), relation: rigor.gen.string({ minLength: 1, maxLength: 20 }), object: rigor.gen.string({ minLength: 1, maxLength: 20 }) }) )) ], rigor.crucible([ rigor.invariant('parity', ({ actual }) => actual !== undefined) ]) ).run({ effort: 300, seed: 'complex-graph-parity', artifacts: { dir: '', persist: 'never' } }); const inv = report.crucibleVerdict?.invariants?.find(i => i.name === 'parity'); assert.ok(inv); assert.equal(inv.passed, true, `complex-graph parity violated in ${inv.failureCount} cases`); }); it('NO-DIRECT: complex policies are actually reachable (non-toy coverage)', async () => { const g = makeCommunityGraph(1); // Derive a real member -> owns chain on the SAME sub-group (membership // and ownership target random sub-groups independently). const ownsBySrc = new Map(); for (const r of g.relations) if (r.rel === 'owns') ownsBySrc.set(r.src, r.dst); const memberEdge = g.relations.find(r => r.rel === 'member' && ownsBySrc.has(r.dst)); const viaTTU = g.arbiter.check(memberEdge.src, 'can_read', ownsBySrc.get(memberEdge.dst)); if (viaTTU.possibility <= 0) fail(`community TTU path not reachable: ${viaTTU.possibility}`); const h = makeHierarchyGraph(1); const hUser = h.users[0]; const hRes = h.resources[0]; const viaChain = h.arbiter.check(hUser, 'can_access_org', hRes); if (viaChain.possibility <= 0) fail(`hierarchy chain path not reachable: ${viaChain.possibility}`); }); });