/** * rigor/overlay-precedence.test.js — js-rigor property tests for partial * graph overlay semantics. * * Properties verified: * * - PERSISTENT OVER PARTIAL: when both a persistent fact and a partial * graph fact describe the same triple, the persistent fact wins by * trust precedence — the check reflects the persistent possibility * (even when it is 0). * - SURFACING: removing the persistent fact lets the partial fact * surface; the check then reflects the partial possibility. * - RE-ESTABLISHMENT: re-adding the persistent fact re-asserts its * precedence immediately (no stale partial-only state). * - LAYER PRECEDENCE: two partial facts for the same triple at * different layers resolve to the higher-trust layer. */ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { rigor } from '@rigor/core'; import { Arbiter } from '../../src/index.js'; const EPS = 1e-9; const POS = [0, 0.25, 0.5, 0.75, 1]; function fail(message) { throw new Error(message); } function buildArbiter() { const arbiter = new Arbiter(); arbiter.addNode('user:1', 'user'); arbiter.addNode('doc:1', 'doc'); arbiter.setRelationConfig('can_read', { type: 'direct' }); return arbiter; } function partialGraphWith(relation, possibility, layer = null) { const fact = { src: 'user:1', relation, dst: 'doc:1', possibility }; if (layer) fact.layer_name = layer; return { relations: [fact] }; } describe('Partial graph overlay precedence (rigor)', () => { it('PERSISTENT OVER PARTIAL: persistent facts win by trust precedence; partial surfaces on removal', async () => { async function check({ pPersistent, pPartial }) { const arbiter = buildArbiter(); arbiter.addRelation('user:1', 'can_read', 'doc:1', { possibility: pPersistent }); const partialGraph = partialGraphWith('can_read', pPartial); // Persistent present: persistent wins regardless of partial strength const withBoth = arbiter.check('user:1', 'can_read', 'doc:1', { partialGraph }); if (Math.abs(withBoth.possibility - pPersistent) > EPS) { fail(`persistent+partial: expected persistent ${pPersistent}, got ${withBoth.possibility}`); } // Remove persistent: partial surfaces arbiter.removeRelation('user:1', 'can_read', 'doc:1'); const partialOnly = arbiter.check('user:1', 'can_read', 'doc:1', { partialGraph }); if (Math.abs(partialOnly.possibility - pPartial) > EPS) { fail(`partial-only: expected ${pPartial}, got ${partialOnly.possibility}`); } // Re-add persistent: precedence re-asserts immediately arbiter.addRelation('user:1', 'can_read', 'doc:1', { possibility: pPersistent }); const reasserted = arbiter.check('user:1', 'can_read', 'doc:1', { partialGraph }); if (Math.abs(reasserted.possibility - pPersistent) > EPS) { fail(`re-asserted: expected ${pPersistent}, got ${reasserted.possibility}`); } return { withBoth, partialOnly, reasserted }; } const report = await rigor.campaign( [ rigor.fn('check', check, rigor.args( rigor.gen.object({ pPersistent: rigor.gen.oneOf(POS), pPartial: rigor.gen.oneOf(POS) }) )) ], rigor.crucible([ rigor.invariant('persistent-precedence', ({ error, errorMessage }) => !error && !errorMessage) ]) ).run({ effort: 500, seed: 'overlay-persistent-precedence' , artifacts: { dir: '', persist: 'never' }}); const inv = report.crucibleVerdict?.invariants?.find(i => i.name === 'persistent-precedence'); assert.ok(inv); assert.equal(inv.passed, true, `PERSISTENT OVER PARTIAL violated in ${inv.failureCount} cases`); }); it('LAYER PRECEDENCE: higher-trust layer wins between partial facts', async () => { async function check({ pHigh, pLow }) { const arbiter = buildArbiter(); // Two partial facts, same triple, different layers: // token_projection (trust 70) > request_observed (trust 50) const partialGraph = { relations: [ { src: 'user:1', relation: 'can_read', dst: 'doc:1', possibility: pHigh, layer_name: 'token_projection' }, { src: 'user:1', relation: 'can_read', dst: 'doc:1', possibility: pLow, layer_name: 'request_observed' } ] }; const result = arbiter.check('user:1', 'can_read', 'doc:1', { partialGraph }); if (Math.abs(result.possibility - pHigh) > EPS) { fail(`layer precedence: expected high-trust ${pHigh}, got ${result.possibility}`); } return result; } const report = await rigor.campaign( [ rigor.fn('check', check, rigor.args( rigor.gen.object({ pHigh: rigor.gen.oneOf(POS), pLow: rigor.gen.oneOf(POS) }) )) ], rigor.crucible([ rigor.invariant('layer-precedence', ({ error, errorMessage }) => !error && !errorMessage) ]) ).run({ effort: 400, seed: 'overlay-layer-precedence' , artifacts: { dir: '', persist: 'never' }}); const inv = report.crucibleVerdict?.invariants?.find(i => i.name === 'layer-precedence'); assert.ok(inv); assert.equal(inv.passed, true, `LAYER PRECEDENCE violated in ${inv.failureCount} cases`); }); });