/** * rigor/complex-graph-overlay-crucible.test.js — partial-graph overlay over * the complex graphs (community block model + org hierarchy). * * An overlay is a caller-supplied set of facts consulted alongside the * persistent graph. The check option key for a pre-built * PartialGraphContext is `partialGraphContext` (AuthorizationChecker * reads that key; passing the context under `partialGraph` would be * re-ingested as a raw spec and silently empty). The persistent relation * is ORed into the direct lookup, so it wins when both are present. * * OVERLAY-SURFACES — with no persistent edge, an overlay fact * grants exactly its possibility on a direct * relation. * PERSISTENT-WINS — persistent + overlay -> persistent value; * removing the persistent edge surfaces the * overlay. * OVERLAY-BINARY-PARITY — binary and normal agree on the same overlay * (binary.allow === (normal >= 0.8), and the * direct path returns the overlay possibility * in both modes). * OVERLAY-ON-COMPLEX — an overlay fact on a DIRECT relation that a * complex policy consumes (union: direct_access; * chain: member/parent/owns) surfaces through * that policy. Overlay facts on a relation name * that is itself configured tuple_to_userset * are ignored by the TTU evaluator, so the * overlay must ride the direct edge. */ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { rigor } from '@rigor/core'; import { PartialGraphContext } from '../../src/core/PartialGraphContext.js'; import { makeCommunityGraph, makeHierarchyGraph } from './complex-graphs.js'; const EPS = 1e-9; const P_OVERLAY = [0.2, 0.4, 0.6, 0.8, 0.9]; const P_PERSISTENT = 0.85; const GENERATORS = [ { name: 'community', make: makeCommunityGraph, directRel: 'direct_access', complexRel: 'can_read_with_direct' }, { name: 'hierarchy', make: makeHierarchyGraph, directRel: 'owns', complexRel: 'can_access_org' } ]; function fail(message) { throw new Error(message); } describe('Complex-graph overlay crucibles (rigor)', () => { it('OVERLAY-SURFACES / PERSISTENT-WINS / BINARY-PARITY / ON-COMPLEX hold across complex graphs', async () => { async function check(args) { const { genKind, seed, layer } = args; const spec = GENERATORS.find(g => g.name === genKind); const g = spec.make(seed); const arbiter = g.arbiter; const u = g.users[0]; const pOverlay = P_OVERLAY[layer % P_OVERLAY.length]; let o; if (genKind === 'community') { // Pick a resource with NO persistent can_read_with_direct path, so // the overlay is the only source for the complex-policy check. o = g.resources.find(r => arbiter.check(u, spec.complexRel, r).possibility === 0); if (!o) fail(`[surfaces] no zero-persistent resource on community seed=${seed}`); // Drop any persistent direct_access edge on the triple. for (const r of (g.relations || [])) { if (r.src === u && r.rel === 'direct_access' && r.dst === o) arbiter.removeRelation(u, 'direct_access', o); } } else { // Hierarchy: users never hold persistent 'owns' edges, so any // resource is overlay-clean on the direct relation. o = g.resources[0]; } const ctx = new PartialGraphContext(arbiter, { relations: [{ src: u, relation: spec.directRel, dst: o, possibility: pOverlay }] }); const checkWithOverlay = (rel, object, ctxFor) => arbiter.check(u, rel, object, { partialGraphContext: ctxFor }); // OVERLAY-SURFACES: no persistent edge -> overlay grants exactly pOverlay. const surfaced = checkWithOverlay(spec.directRel, o, ctx).possibility; if (Math.abs(surfaced - pOverlay) > EPS) { fail(`[surfaces] overlay ${pOverlay} did not surface on ${genKind} ${u}->${o}: ${surfaced}`); } // PERSISTENT-WINS: persistent edge wins; removal surfaces the overlay. arbiter.addRelation(u, spec.directRel, o, { possibility: P_PERSISTENT }); const withBoth = checkWithOverlay(spec.directRel, o, ctx).possibility; if (Math.abs(withBoth - P_PERSISTENT) > EPS) { fail(`[wins] persistent ${P_PERSISTENT} did not win over overlay ${pOverlay}: ${withBoth}`); } arbiter.removeRelation(u, spec.directRel, o); const resurfaced = checkWithOverlay(spec.directRel, o, ctx).possibility; if (Math.abs(resurfaced - pOverlay) > EPS) { fail(`[wins] overlay did not resurface after persistent removal: ${resurfaced}`); } // OVERLAY-BINARY-PARITY on the direct relation. const normal = checkWithOverlay(spec.directRel, o, ctx); const binary = arbiter.check(u, spec.directRel, o, { partialGraphContext: ctx, binary: true }); if (Math.abs(binary.possibility - normal.possibility) > EPS) { fail(`[binary] direct overlay binary=${binary.possibility} normal=${normal.possibility} disagree`); } if (binary.allow !== (normal.possibility >= 0.8)) { fail(`[binary] binary.allow=${binary.allow} != normal>=0.8 (${normal.possibility})`); } // OVERLAY-ON-COMPLEX: the overlay rides a direct relation the policy // consumes and surfaces through the complex relation. if (genKind === 'community') { const viaUnion = checkWithOverlay(spec.complexRel, o, ctx).possibility; if (Math.abs(viaUnion - pOverlay) > EPS) { fail(`[complex] overlay ${pOverlay} did not surface through union ${spec.complexRel}: ${viaUnion}`); } } else { // can_access_org = member -> parent -> owns. The overlay provides the // full chain; the last hop carries the overlay strength. const team = g.teams[0]; const dept = team.split(':team:')[0]; const org = 'org:0'; const chainCtx = new PartialGraphContext(arbiter, { relations: [ { src: u, relation: 'member', dst: team, possibility: 1 }, { src: team, relation: 'parent', dst: dept, possibility: 1 }, { src: dept, relation: 'owns', dst: org, possibility: pOverlay } ] }); const viaChain = checkWithOverlay(spec.complexRel, org, chainCtx).possibility; if (Math.abs(viaChain - pOverlay) > EPS) { fail(`[complex] overlay ${pOverlay} did not surface through chain ${spec.complexRel}: ${viaChain}`); } } return { ok: 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, 6), layer: rigor.gen.int(0, 4) }) )) ], rigor.crucible([ rigor.invariant('overlay-surfaces', ({ actual }) => actual !== undefined), rigor.invariant('persistent-wins', ({ actual }) => actual !== undefined), rigor.invariant('overlay-binary-parity', ({ actual }) => actual !== undefined), rigor.invariant('overlay-on-complex', ({ actual }) => actual !== undefined) ]) ).run({ effort: 200, seed: 'complex-graph-overlay-crucible', artifacts: { dir: '', persist: 'never' } }); for (const name of ['overlay-surfaces', 'persistent-wins', 'overlay-binary-parity', 'overlay-on-complex']) { const inv = report.crucibleVerdict?.invariants?.find(i => i.name === name); assert.ok(inv, `invariant ${name} missing`); assert.equal(inv.passed, true, `overlay ${name} violated in ${inv.failureCount} cases`); } }); });