Files
core/tests/rigor/rule-kind-partial-parity.test.js
T
John Dvorak dab9671d20 js-rigor: value-collection crucibles; TTU 0-strength paths, crash, fusion reliability
The new value-collection crucibles in the TTU and chain differential
campaigns immediately found three engine defects:

- TTU join pushed 0-strength 'matches' (missing computed leg, or
  0-possibility edges, with minPossibility 0) as valid paths: denied
  decisions reported tuple_to_userset_found and leaked the tupleset edge's
  value into collectedValues. Both join modes now require combined > 0.
- A ReferenceError (bare resolveKey) crashed the computed-join mode under
  collectValues, silently turning the whole check into an evaluation_error
  denial. Fixed the call to this.arbiter.resolveKey.
- Multi-path TTU fusion fell back to Math.max over all path reliabilities,
  pairing the winning possibility with another intermediate's reliability.
  The fallback now picks the max-possibility path's reliability.

New campaigns: defeasible and intersection differential properties
(when/unless and min-children with reliability parity under persistent/
partial splits). The model-based campaign keeps its reliability crucible;
its value comparison was reverted — the harness's shrink reporting is
opaque and unreconstructable there, and the value semantics are covered by
the TTU/chain campaigns instead.
2026-08-01 22:34:00 -07:00

1143 lines
56 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* rigor/rule-kind-partial-parity.test.js — rule-kind × partial-graph parity.
*
* Pins the confirmed contracts discovered by probe sweeps:
* - every rule kind must produce identical semantics whether its edges come
* from persistent storage or a partial graph context (or a split mix)
* - TTU orientation is user --computed--> intermediate <-tupleset-- object
* (reverse: user --tupleset--> intermediate <-computed-- object)
* - relational_comparator operands read value relations on the (user,object)
* pair (left, auto) and the object self-loop (right, object perspective)
* - challenge rules consume partialGraph.challenges proofs with subject and
* expiry semantics
* - binary mode must agree with normal mode at the same threshold
*
* Additions in this round:
* - differential property campaigns for TTU, comparator, and exclusion
* under random persistent/partial splits with seeded runs
*/
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { rigor } from '@rigor/core';
import { Arbiter } from '../../src/index.js';
const T = 0.0001;
function round4(v) { return Math.round(v * 10000) / 10000; }
function mkArbiter(opts = {}) {
const a = new Arbiter(opts);
a.addNode('u:0', 'user');
a.addNode('doc:0', 'doc');
a.addNode('g:0', 'group');
a.addNode('g:1', 'group');
a.addNode('g:2', 'group');
a.addNode('doc:1', 'doc');
return a;
}
describe('Rule-kind × partial-graph parity (rigor)', () => {
it('FIXED MATRIX: every kind matches between persistent and partial', async () => {
// ---- direct ----
{
const a = mkArbiter();
a.setRelationConfig('can_read', { type: 'direct', relation: 'owner' });
a.addRelation('u:0', 'owner', 'doc:0', { possibility: 0.8 });
assert.equal(round4(a.check('u:0', 'can_read', 'doc:0').possibility), 0.8, 'direct persistent');
a.removeRelation('u:0', 'owner', 'doc:0');
const r = a.check('u:0', 'can_read', 'doc:0', {
partialGraph: { relations: [{ src: 'u:0', relation: 'owner', dst: 'doc:0', possibility: 0.8 }] }
});
assert.equal(round4(r.possibility), 0.8, 'direct partial');
}
// ---- chain (2-step) ----
{
const a = mkArbiter();
a.setRelationConfig('can_access', { type: 'chain', steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'reads', direction: 'out' }
] });
a.addRelation('u:0', 'member_of', 'g:0', { possibility: 0.8 });
a.addRelation('g:0', 'reads', 'doc:0', { possibility: 0.7 });
assert.equal(round4(a.check('u:0', 'can_access', 'doc:0').possibility), 0.7, 'chain persistent');
a.removeRelation('u:0', 'member_of', 'g:0');
a.removeRelation('g:0', 'reads', 'doc:0');
const r = a.check('u:0', 'can_access', 'doc:0', {
partialGraph: { relations: [
{ src: 'u:0', relation: 'member_of', dst: 'g:0', possibility: 0.8 },
{ src: 'g:0', relation: 'reads', dst: 'doc:0', possibility: 0.7 }
] }
});
assert.equal(round4(r.possibility), 0.7, 'chain partial');
}
// ---- multi_hop ----
{
const a = mkArbiter();
a.setRelationConfig('can_access', { type: 'multi_hop', relation: 'member_of', maxDepth: 3 });
a.addRelation('u:0', 'member_of', 'g:0', { possibility: 0.8 });
a.addRelation('g:0', 'member_of', 'g:1', { possibility: 0.7 });
assert.equal(round4(a.check('u:0', 'can_access', 'g:1').possibility), 0.7, 'multi_hop persistent');
a.removeRelation('u:0', 'member_of', 'g:0');
a.removeRelation('g:0', 'member_of', 'g:1');
const r = a.check('u:0', 'can_access', 'g:1', {
partialGraph: { relations: [
{ src: 'u:0', relation: 'member_of', dst: 'g:0', possibility: 0.8 },
{ src: 'g:0', relation: 'member_of', dst: 'g:1', possibility: 0.7 }
] }
});
assert.equal(round4(r.possibility), 0.7, 'multi_hop partial');
}
// ---- TTU (user --computed--> intermediate <-tupleset-- object) ----
{
const a = mkArbiter();
a.setRelationConfig('can_read', { type: 'tuple_to_userset', tuplesetRelation: 'owner', computedRelation: 'member_of' });
a.setRelationConfig('member_of', { type: 'direct', relation: 'member_of' });
a.addRelation('doc:0', 'owner', 'g:0', { possibility: 0.8 });
a.addRelation('u:0', 'member_of', 'g:0', { possibility: 0.7 });
assert.equal(round4(a.check('u:0', 'can_read', 'doc:0').possibility), 0.7, 'ttu persistent');
a.removeRelation('doc:0', 'owner', 'g:0');
a.removeRelation('u:0', 'member_of', 'g:0');
const r = a.check('u:0', 'can_read', 'doc:0', {
partialGraph: { relations: [
{ src: 'doc:0', relation: 'owner', dst: 'g:0', possibility: 0.8 },
{ src: 'u:0', relation: 'member_of', dst: 'g:0', possibility: 0.7 }
] }
});
assert.equal(round4(r.possibility), 0.7, 'ttu partial');
}
// ---- TTU reverse (user --tupleset--> intermediate <-computed-- object) ----
{
const a = mkArbiter();
a.setRelationConfig('can_view', { type: 'tuple_to_userset', tuplesetRelation: 'member_of', computedRelation: 'viewable', reverse: true });
a.setRelationConfig('viewable', { type: 'direct', relation: 'viewable' });
a.addRelation('u:0', 'member_of', 'g:0', { possibility: 0.8 });
a.addRelation('doc:0', 'viewable', 'g:0', { possibility: 0.7 });
assert.equal(round4(a.check('u:0', 'can_view', 'doc:0').possibility), 0.7, 'ttu reverse persistent');
a.removeRelation('u:0', 'member_of', 'g:0');
a.removeRelation('doc:0', 'viewable', 'g:0');
const r = a.check('u:0', 'can_view', 'doc:0', {
partialGraph: { relations: [
{ src: 'u:0', relation: 'member_of', dst: 'g:0', possibility: 0.8 },
{ src: 'doc:0', relation: 'viewable', dst: 'g:0', possibility: 0.7 }
] }
});
assert.equal(round4(r.possibility), 0.7, 'ttu reverse partial');
}
// ---- TTU tuplesetDirection in ----
{
const a = mkArbiter();
a.setRelationConfig('can_access', { type: 'tuple_to_userset', tuplesetRelation: 'belongs_to', computedRelation: 'member_of', tuplesetDirection: 'in' });
a.setRelationConfig('member_of', { type: 'direct', relation: 'member_of' });
a.addRelation('g:0', 'belongs_to', 'doc:0', { possibility: 0.8 });
a.addRelation('u:0', 'member_of', 'g:0', { possibility: 0.7 });
assert.equal(round4(a.check('u:0', 'can_access', 'doc:0').possibility), 0.7, 'ttu-in persistent');
a.removeRelation('g:0', 'belongs_to', 'doc:0');
a.removeRelation('u:0', 'member_of', 'g:0');
const r = a.check('u:0', 'can_access', 'doc:0', {
partialGraph: { relations: [
{ src: 'g:0', relation: 'belongs_to', dst: 'doc:0', possibility: 0.8 },
{ src: 'u:0', relation: 'member_of', dst: 'g:0', possibility: 0.7 }
] }
});
assert.equal(round4(r.possibility), 0.7, 'ttu-in partial');
}
// ---- parent ----
{
const a = mkArbiter();
a.setRelationConfig('can_edit', { type: 'parent', parentRelation: 'parent', relation: 'owner' });
a.addRelation('doc:0', 'parent', 'doc:1', { possibility: 1.0 });
a.addRelation('u:0', 'owner', 'doc:0', { possibility: 0.9 });
assert.equal(round4(a.check('u:0', 'can_edit', 'doc:1').possibility), 0.9, 'parent persistent');
a.removeRelation('doc:0', 'parent', 'doc:1');
a.removeRelation('u:0', 'owner', 'doc:0');
const r = a.check('u:0', 'can_edit', 'doc:1', {
partialGraph: { relations: [
{ src: 'doc:0', relation: 'parent', dst: 'doc:1', possibility: 1.0 },
{ src: 'u:0', relation: 'owner', dst: 'doc:0', possibility: 0.9 }
] }
});
assert.equal(round4(r.possibility), 0.9, 'parent partial');
}
// ---- computed alias (target needs its own config) ----
{
const a = mkArbiter();
a.setRelationConfig('is_member', { type: 'computed', relation: 'member_of' });
a.setRelationConfig('member_of', { type: 'direct', relation: 'member_of' });
a.addRelation('u:0', 'member_of', 'g:0', { possibility: 0.7 });
assert.equal(round4(a.check('u:0', 'is_member', 'g:0').possibility), 0.7, 'computed persistent');
a.removeRelation('u:0', 'member_of', 'g:0');
const r = a.check('u:0', 'is_member', 'g:0', {
partialGraph: { relations: [{ src: 'u:0', relation: 'member_of', dst: 'g:0', possibility: 0.7 }] }
});
assert.equal(round4(r.possibility), 0.7, 'computed partial');
}
// ---- defeasible when/unless ----
{
const a = mkArbiter();
a.setRelationConfig('can_access', { type: 'defeasible', when: { relation: 'owner' }, unless: { relation: 'banned' } });
a.addRelation('u:0', 'owner', 'doc:0', { possibility: 0.8 });
a.addRelation('u:0', 'banned', 'doc:0', { possibility: 0.5 });
assert.equal(round4(a.check('u:0', 'can_access', 'doc:0').possibility), 0.4, 'defeasible persistent');
a.removeRelation('u:0', 'owner', 'doc:0');
a.removeRelation('u:0', 'banned', 'doc:0');
const r = a.check('u:0', 'can_access', 'doc:0', {
partialGraph: { relations: [
{ src: 'u:0', relation: 'owner', dst: 'doc:0', possibility: 0.8 },
{ src: 'u:0', relation: 'banned', dst: 'doc:0', possibility: 0.5 }
] }
});
assert.equal(round4(r.possibility), 0.4, 'defeasible partial');
}
// ---- union takes max ----
{
const a = mkArbiter();
a.setRelationConfig('can_access', { union: { rules: [{ relation: 'owner' }, { relation: 'editor' }] } });
a.addRelation('u:0', 'owner', 'doc:0', { possibility: 0.8 });
assert.equal(round4(a.check('u:0', 'can_access', 'doc:0').possibility), 0.8, 'union persistent');
a.removeRelation('u:0', 'owner', 'doc:0');
const r = a.check('u:0', 'can_access', 'doc:0', {
partialGraph: { relations: [
{ src: 'u:0', relation: 'owner', dst: 'doc:0', possibility: 0.8 },
{ src: 'u:0', relation: 'editor', dst: 'doc:0', possibility: 0.9 }
] }
});
assert.equal(round4(r.possibility), 0.9, 'union partial');
}
// ---- exclusion P(A)*(1-P(B)) ----
{
const a = mkArbiter();
a.setRelationConfig('can_access', { exclusion: [{ relation: 'owner' }, { relation: 'banned' }] });
a.addRelation('u:0', 'owner', 'doc:0', { possibility: 0.8 });
a.addRelation('u:0', 'banned', 'doc:0', { possibility: 0.5 });
assert.equal(round4(a.check('u:0', 'can_access', 'doc:0').possibility), 0.4, 'exclusion persistent');
a.removeRelation('u:0', 'owner', 'doc:0');
a.removeRelation('u:0', 'banned', 'doc:0');
const r = a.check('u:0', 'can_access', 'doc:0', {
partialGraph: { relations: [
{ src: 'u:0', relation: 'owner', dst: 'doc:0', possibility: 0.8 },
{ src: 'u:0', relation: 'banned', dst: 'doc:0', possibility: 0.5 }
] }
});
assert.equal(round4(r.possibility), 0.4, 'exclusion partial');
}
// ---- relational_comparator (left on user->object, right on object self-loop) ----
{
const a = mkArbiter();
a.setRelationConfig('can_access', {
type: 'relational_comparator', comparator: '>=',
left: { rule: { type: 'direct', relation: 'has_clearance' }, extractValue: true },
right: { rule: { type: 'direct', relation: 'requested_level' }, extractValue: true, evaluateFrom: 'object' }
});
a.addRelation('u:0', 'has_clearance', 'doc:0', { possibility: 0.9, value: 7 });
a.addRelation('doc:0', 'requested_level', 'doc:0', { possibility: 1.0, value: 4 });
assert.equal(a.check('u:0', 'can_access', 'doc:0').possibility, 1, 'comparator persistent');
a.removeRelation('u:0', 'has_clearance', 'doc:0');
a.removeRelation('doc:0', 'requested_level', 'doc:0');
const r = a.check('u:0', 'can_access', 'doc:0', {
partialGraph: { relations: [
{ src: 'u:0', relation: 'has_clearance', dst: 'doc:0', possibility: 0.9, value: 7 },
{ src: 'doc:0', relation: 'requested_level', dst: 'doc:0', possibility: 1.0, value: 4 }
] }
});
assert.equal(r.possibility, 1, 'comparator partial');
}
// ---- challenge via partialGraph.challenges ----
{
const a = mkArbiter();
a.setRelationConfig('can_download', { type: 'challenge', challenge: 'captcha', subject: 'user', withinMinutes: 5 });
const now = Date.now();
const ok = a.check('u:0', 'can_download', 'doc:0', {
partialGraph: { challenges: [{ name: 'captcha', subject: 'u:0', issuedAt: now - 60000, expiresAt: now + 60000 }] }
});
assert.equal(ok.possibility, 1, 'challenge satisfied');
const expired = a.check('u:0', 'can_download', 'doc:0', {
partialGraph: { challenges: [{ name: 'captcha', subject: 'u:0', issuedAt: now - 600000, expiresAt: now - 300000 }] }
});
assert.equal(expired.possibility, 0, 'challenge expired');
assert.equal(a.check('u:0', 'can_download', 'doc:0').possibility, 0, 'challenge missing context');
}
// ---- TTU reverse + tuplesetDirection in (intermediates point AT the user) ----
{
const a = mkArbiter();
a.setRelationConfig('can_view', { type: 'tuple_to_userset', tuplesetRelation: 'member_of', computedRelation: 'viewable', reverse: true, tuplesetDirection: 'in' });
a.setRelationConfig('viewable', { type: 'direct', relation: 'viewable' });
a.addRelation('g:0', 'member_of', 'u:0', { possibility: 0.8 });
a.addRelation('doc:0', 'viewable', 'g:0', { possibility: 0.7 });
assert.equal(round4(a.check('u:0', 'can_view', 'doc:0').possibility), 0.7, 'ttu reverse-in persistent optimized');
assert.equal(round4(a.check('u:0', 'can_view', 'doc:0', { useCompiled: false }).possibility), 0.7, 'ttu reverse-in persistent fallback');
a.removeRelation('g:0', 'member_of', 'u:0');
a.removeRelation('doc:0', 'viewable', 'g:0');
const r = a.check('u:0', 'can_view', 'doc:0', {
partialGraph: { relations: [
{ src: 'g:0', relation: 'member_of', dst: 'u:0', possibility: 0.8 },
{ src: 'doc:0', relation: 'viewable', dst: 'g:0', possibility: 0.7 }
] }
});
assert.equal(round4(r.possibility), 0.7, 'ttu reverse-in partial');
}
// ---- multi_hop reverse (backward walk: doc ->member_of-> g:1 ->member_of-> u:0) ----
{
const a = mkArbiter();
a.setRelationConfig('can_access', { type: 'multi_hop', relation: 'member_of', reverse: true, maxDepth: 3 });
a.addRelation('g:1', 'member_of', 'u:0', { possibility: 0.8 });
a.addRelation('doc:0', 'member_of', 'g:1', { possibility: 0.7 });
assert.equal(round4(a.check('u:0', 'can_access', 'doc:0').possibility), 0.7, 'multi_hop reverse persistent');
a.removeRelation('g:1', 'member_of', 'u:0');
a.removeRelation('doc:0', 'member_of', 'g:1');
const r = a.check('u:0', 'can_access', 'doc:0', {
partialGraph: { relations: [
{ src: 'g:1', relation: 'member_of', dst: 'u:0', possibility: 0.8 },
{ src: 'doc:0', relation: 'member_of', dst: 'g:1', possibility: 0.7 }
] }
});
assert.equal(round4(r.possibility), 0.7, 'multi_hop reverse partial');
}
// ---- chain direction in + partial ----
{
const a = mkArbiter();
a.setRelationConfig('can_access', { type: 'chain', steps: [
{ relation: 'member_of', direction: 'in' },
{ relation: 'reads', direction: 'out' }
] });
a.addRelation('g:0', 'member_of', 'u:0', { possibility: 0.8 });
a.addRelation('g:0', 'reads', 'doc:0', { possibility: 0.7 });
assert.equal(round4(a.check('u:0', 'can_access', 'doc:0').possibility), 0.7, 'chain-in persistent');
a.removeRelation('g:0', 'member_of', 'u:0');
a.removeRelation('g:0', 'reads', 'doc:0');
const r = a.check('u:0', 'can_access', 'doc:0', {
partialGraph: { relations: [
{ src: 'g:0', relation: 'member_of', dst: 'u:0', possibility: 0.8 },
{ src: 'g:0', relation: 'reads', dst: 'doc:0', possibility: 0.7 }
] }
});
assert.equal(round4(r.possibility), 0.7, 'chain-in partial');
}
// ---- union with a chain child ----
{
const a = mkArbiter();
a.setRelationConfig('can_access', { union: { rules: [
{ type: 'direct', relation: 'owner' },
{ type: 'chain', steps: [{ relation: 'member_of', direction: 'out' }, { relation: 'reads', direction: 'out' }] }
] } });
a.addRelation('u:0', 'owner', 'doc:0', { possibility: 0.6 });
a.addRelation('u:0', 'member_of', 'g:0', { possibility: 0.8 });
a.addRelation('g:0', 'reads', 'doc:0', { possibility: 0.7 });
assert.equal(round4(a.check('u:0', 'can_access', 'doc:0').possibility), 0.7, 'union chain child persistent (max 0.7)');
a.removeRelation('u:0', 'owner', 'doc:0');
a.removeRelation('u:0', 'member_of', 'g:0');
a.removeRelation('g:0', 'reads', 'doc:0');
const r = a.check('u:0', 'can_access', 'doc:0', {
partialGraph: { relations: [
{ src: 'u:0', relation: 'owner', dst: 'doc:0', possibility: 0.6 },
{ src: 'u:0', relation: 'member_of', dst: 'g:0', possibility: 0.8 },
{ src: 'g:0', relation: 'reads', dst: 'doc:0', possibility: 0.7 }
] }
});
assert.equal(round4(r.possibility), 0.7, 'union chain child partial');
}
// ---- defeasible split legs (when persistent, unless partial) ----
{
const a = mkArbiter();
a.setRelationConfig('can_access', { type: 'defeasible', when: { relation: 'owner' }, unless: { relation: 'banned' } });
a.addRelation('u:0', 'owner', 'doc:0', { possibility: 0.8 });
const r = a.check('u:0', 'can_access', 'doc:0', {
partialGraph: { relations: [{ src: 'u:0', relation: 'banned', dst: 'doc:0', possibility: 0.5 }] }
});
assert.equal(round4(r.possibility), 0.4, 'defeasible when persistent, unless partial');
a.removeRelation('u:0', 'owner', 'doc:0');
const r2 = a.check('u:0', 'can_access', 'doc:0', {
partialGraph: { relations: [{ src: 'u:0', relation: 'owner', dst: 'doc:0', possibility: 0.8 }] }
});
assert.equal(round4(r2.possibility), 0.8, 'defeasible when partial only');
}
// ---- TTU value flow through the tupleset edge (collectValues) ----
{
const a = mkArbiter();
a.setRelationConfig('can_read', { type: 'tuple_to_userset', tuplesetRelation: 'owner', computedRelation: 'member_of' });
a.setRelationConfig('member_of', { type: 'direct', relation: 'member_of' });
a.addRelation('doc:0', 'owner', 'g:0', { possibility: 0.8, value: 5 });
a.addRelation('u:0', 'member_of', 'g:0', { possibility: 0.7 });
const p = a.check('u:0', 'can_read', 'doc:0', { collectValues: true });
assert.equal(p.possibility, 0.7, 'ttu value persistent decision');
assert.ok(Array.isArray(p.collectedValues) && p.collectedValues.some(v => v === 'g:0' || v?.entityKey === 'g:0' || v?.value === 5),
`ttu value persistent collects intermediate: ${JSON.stringify(p.collectedValues)}`);
a.removeRelation('doc:0', 'owner', 'g:0');
a.removeRelation('u:0', 'member_of', 'g:0');
const r = a.check('u:0', 'can_read', 'doc:0', {
collectValues: true,
partialGraph: { relations: [
{ src: 'doc:0', relation: 'owner', dst: 'g:0', possibility: 0.8, value: 5 },
{ src: 'u:0', relation: 'member_of', dst: 'g:0', possibility: 0.7 }
] }
});
assert.equal(r.possibility, 0.7, 'ttu value partial decision');
}
// ---- challenge via binary mode ----
{
const a = mkArbiter();
a.setRelationConfig('can_download', { type: 'challenge', challenge: 'captcha', subject: 'user', withinMinutes: 5 });
const now = Date.now();
const r = a.check('u:0', 'can_download', 'doc:0', {
binary: true, minAllowPossibility: 0.5,
partialGraph: { challenges: [{ name: 'captcha', subject: 'u:0', issuedAt: now - 60000, expiresAt: now + 60000 }] }
});
assert.equal(r.possibility, 1, 'challenge binary satisfied');
assert.equal(r.allow, true, 'challenge binary allow');
}
// ---- TTU multi-path fusion + reliability propagation ----
{
const a = mkArbiter();
a.setRelationConfig('can_read', { type: 'tuple_to_userset', tuplesetRelation: 'owner', computedRelation: 'member_of' });
a.setRelationConfig('member_of', { type: 'direct', relation: 'member_of' });
a.addRelation('doc:0', 'owner', 'g:0', { possibility: 0.8, reliability: 0.9 });
a.addRelation('doc:0', 'owner', 'g:1', { possibility: 0.6 });
a.addRelation('u:0', 'member_of', 'g:0', { possibility: 0.7, reliability: 0.8 });
a.addRelation('u:0', 'member_of', 'g:1', { possibility: 0.5 });
const p = a.check('u:0', 'can_read', 'doc:0');
assert.equal(round4(p.possibility), 0.7, 'ttu multi-path fusion persistent');
assert.ok(typeof p.reliability === 'number' && p.reliability > 0,
`ttu reliability flows through normal check: ${p.reliability}`);
assert.ok(Math.abs(p.reliability - 0.72) < 0.01, `ttu reliability = 0.9*0.8, got ${p.reliability}`);
a.removeRelation('doc:0', 'owner', 'g:0');
a.removeRelation('doc:0', 'owner', 'g:1');
a.removeRelation('u:0', 'member_of', 'g:0');
a.removeRelation('u:0', 'member_of', 'g:1');
const r = a.check('u:0', 'can_read', 'doc:0', {
partialGraph: { relations: [
{ src: 'doc:0', relation: 'owner', dst: 'g:0', possibility: 0.8, reliability: 0.9 },
{ src: 'doc:0', relation: 'owner', dst: 'g:1', possibility: 0.6 },
{ src: 'u:0', relation: 'member_of', dst: 'g:0', possibility: 0.7, reliability: 0.8 },
{ src: 'u:0', relation: 'member_of', dst: 'g:1', possibility: 0.5 }
] }
});
assert.equal(round4(r.possibility), 0.7, 'ttu multi-path fusion partial');
assert.ok(Math.abs(r.reliability - 0.72) < 0.01, `ttu reliability partial, got ${r.reliability}`);
}
// ---- TTU maxIntermediates circuit breaker through partial ----
{
const a = mkArbiter();
for (let i = 0; i < 25; i++) a.addNode('gx:' + i, 'group');
a.setRelationConfig('can_read', { type: 'tuple_to_userset', tuplesetRelation: 'owner', computedRelation: 'member_of', maxIntermediates: 5 });
a.setRelationConfig('member_of', { type: 'direct', relation: 'member_of' });
const rels = [];
for (let i = 0; i < 25; i++) {
rels.push({ src: 'doc:0', relation: 'owner', dst: 'gx:' + i, possibility: 0.3 + (i % 10) / 20 });
rels.push({ src: 'u:0', relation: 'member_of', dst: 'gx:' + i, possibility: 0.5 });
}
const r = a.check('u:0', 'can_read', 'doc:0', { partialGraph: { relations: rels } });
assert.equal(round4(r.possibility), 0.5, 'ttu circuit breaker picks best of limited intermediates');
}
// ---- snapshot-restored arbiter evaluates TTU ----
{
const a = mkArbiter();
a.setRelationConfig('can_read', { type: 'tuple_to_userset', tuplesetRelation: 'owner', computedRelation: 'member_of' });
a.setRelationConfig('member_of', { type: 'direct', relation: 'member_of' });
a.addRelation('doc:0', 'owner', 'g:0', { possibility: 0.8 });
a.addRelation('u:0', 'member_of', 'g:0', { possibility: 0.7 });
a.enableCondensedSnapshot();
const { serializeArbiterSnapshot } = await import('../../src/core/SnapshotBinary.js');
const { ArbiterSnapshot } = await import('../../src/core/arbiter/ArbiterSnapshot.js');
const restored = ArbiterSnapshot.fromSnapshotBinary(serializeArbiterSnapshot(a), {}, () => new Arbiter());
const p = restored.check('u:0', 'can_read', 'doc:0');
assert.equal(round4(p.possibility), 0.7, 'snapshot-restored TTU');
}
// ---- comparator operand as TTU (value flows via the tupleset edge) ----
{
const a = mkArbiter();
a.setRelationConfig('can_access', {
type: 'relational_comparator', comparator: '>=',
left: { rule: { type: 'tuple_to_userset', tuplesetRelation: 'owner', computedRelation: 'member_of' }, extractValue: true },
right: { rule: { type: 'direct', relation: 'requested_level' }, extractValue: true, evaluateFrom: 'object' }
});
a.setRelationConfig('member_of', { type: 'direct', relation: 'member_of' });
a.addRelation('doc:0', 'owner', 'g:0', { possibility: 0.9, value: 7 });
a.addRelation('u:0', 'member_of', 'g:0', { possibility: 0.8 });
a.addRelation('doc:0', 'requested_level', 'doc:0', { possibility: 1.0, value: 4 });
assert.equal(a.check('u:0', 'can_access', 'doc:0').possibility, 1, 'ttu operand persistent allow');
a.removeRelation('doc:0', 'owner', 'g:0');
a.removeRelation('u:0', 'member_of', 'g:0');
a.removeRelation('doc:0', 'requested_level', 'doc:0');
const r = a.check('u:0', 'can_access', 'doc:0', {
partialGraph: { relations: [
{ src: 'doc:0', relation: 'owner', dst: 'g:0', possibility: 0.9, value: 7 },
{ src: 'u:0', relation: 'member_of', dst: 'g:0', possibility: 0.8 },
{ src: 'doc:0', relation: 'requested_level', dst: 'doc:0', possibility: 1.0, value: 4 }
] }
});
assert.equal(r.possibility, 1, 'ttu operand partial allow');
a.addRelation('doc:0', 'owner', 'g:0', { possibility: 0.9, value: 3 });
a.addRelation('u:0', 'member_of', 'g:0', { possibility: 0.8 });
a.addRelation('doc:0', 'requested_level', 'doc:0', { possibility: 1.0, value: 4 });
assert.equal(a.check('u:0', 'can_access', 'doc:0').possibility, 0, 'ttu operand below threshold deny');
a.removeRelation('doc:0', 'owner', 'g:0');
a.addRelation('doc:0', 'owner', 'g:0', { possibility: 0.9 });
assert.equal(a.check('u:0', 'can_access', 'doc:0').possibility, 0, 'ttu operand without value denies');
}
// ---- modify changed_last_at override (value-changing modify honors the pin) ----
{
const a = mkArbiter();
a.addRelation('u:0', 'balance', 'doc:0', { value: 10, changed_last_at: 1000 });
a.relationManager.updateRelationsBatch([
{ operation: 'modify', srcKey: 'u:0', relation: 'balance', dstKey: 'doc:0', options: { value: 20, changed_last_at: 5000 } }
]);
const after = a.relationManager.getDirectRelation(a.resolveNodeId('u:0'), 'balance', a.resolveNodeId('doc:0'));
assert.equal(after.changed_last_at, 5000, 'value-changing modify honors changed_last_at override');
a.relationManager.updateRelationsBatch([
{ operation: 'modify', srcKey: 'u:0', relation: 'balance', dstKey: 'doc:0', options: { value: 20, changed_last_at: 9999 } }
]);
const unchanged = a.relationManager.getDirectRelation(a.resolveNodeId('u:0'), 'balance', a.resolveNodeId('doc:0'));
assert.equal(unchanged.changed_last_at, 5000, 'value-unchanged modify keeps old timestamp (override not a refresh)');
}
// ---- reliability propagation across kinds ----
{
// chain: product of edge reliabilities (0.9 * 0.8)
const a = mkArbiter();
a.setRelationConfig('can_access', { type: 'chain', steps: [{ relation: 'member_of', direction: 'out' }, { relation: 'reads', direction: 'out' }] });
a.addRelation('u:0', 'member_of', 'g:0', { possibility: 0.8, reliability: 0.9 });
a.addRelation('g:0', 'reads', 'doc:0', { possibility: 0.7, reliability: 0.8 });
const c = a.check('u:0', 'can_access', 'doc:0');
assert.ok(Math.abs(c.reliability - 0.72) < 0.01, `chain reliability product, got ${c.reliability}`);
// multi_hop: product along the path
a.setRelationConfig('can_hop', { type: 'multi_hop', relation: 'member_of', maxDepth: 3 });
a.addNode('g:1', 'group');
a.addRelation('u:0', 'member_of', 'g:0', { possibility: 0.8, reliability: 0.9 });
a.addRelation('g:0', 'member_of', 'g:1', { possibility: 0.7, reliability: 0.8 });
const m = a.check('u:0', 'can_hop', 'g:1');
assert.ok(Math.abs(m.reliability - 0.72) < 0.01, `multi_hop reliability product, got ${m.reliability}`);
// union: the max child's reliability (editor 0.9 / reli 0.5)
a.setRelationConfig('can_union', { union: { rules: [{ relation: 'owner' }, { relation: 'editor' }] } });
a.addRelation('u:0', 'owner', 'doc:0', { possibility: 0.8, reliability: 0.9 });
a.addRelation('u:0', 'editor', 'doc:0', { possibility: 0.9, reliability: 0.5 });
const u = a.check('u:0', 'can_union', 'doc:0');
assert.equal(round4(u.reliability), 0.5, `union selected-child reliability, got ${u.reliability}`);
// intersection: the min child's reliability (verified 0.6)
a.setRelationConfig('can_intersect', { intersection: { rules: [{ relation: 'owner' }, { relation: 'verified' }] } });
a.addRelation('u:0', 'verified', 'doc:0', { possibility: 0.5, reliability: 0.6 });
const i = a.check('u:0', 'can_intersect', 'doc:0');
assert.equal(round4(i.reliability), 0.6, `intersection selected-child reliability, got ${i.reliability}`);
// exclusion: product of both legs (0.9 * 0.7)
a.setRelationConfig('can_excl', { exclusion: [{ relation: 'owner' }, { relation: 'banned' }] });
a.addRelation('u:0', 'banned', 'doc:0', { possibility: 0.5, reliability: 0.7 });
const e = a.check('u:0', 'can_excl', 'doc:0');
assert.ok(Math.abs(e.reliability - 0.63) < 0.01, `exclusion product reliability, got ${e.reliability}`);
// defeasible: when reli * unless reli (0.9 * 0.7)
a.setRelationConfig('can_def', { type: 'defeasible', when: { relation: 'owner' }, unless: { relation: 'banned' } });
const d = a.check('u:0', 'can_def', 'doc:0');
assert.ok(Math.abs(d.reliability - 0.63) < 0.01, `defeasible combined reliability, got ${d.reliability}`);
}
// ---- multi_hop value collection through partial (no crash, values flow) ----
{
const a = mkArbiter();
a.addNode('g:1', 'group');
a.setRelationConfig('can_access', { type: 'multi_hop', relation: 'member_of', maxDepth: 3 });
a.addRelation('u:0', 'member_of', 'g:0', { possibility: 0.8 });
a.addRelation('g:0', 'member_of', 'g:1', { possibility: 0.7, value: 42 });
const p = a.check('u:0', 'can_access', 'g:1', { collectValues: true });
assert.ok(Array.isArray(p.collectedValues) && p.collectedValues.length > 0, 'multi_hop persistent values collected');
assert.ok(typeof p.collectedValues[0].value?.min === 'number', 'multi_hop value interval present');
a.removeRelation('u:0', 'member_of', 'g:0');
a.removeRelation('g:0', 'member_of', 'g:1');
const r = a.check('u:0', 'can_access', 'g:1', {
collectValues: true,
partialGraph: { relations: [
{ src: 'u:0', relation: 'member_of', dst: 'g:0', possibility: 0.8 },
{ src: 'g:0', relation: 'member_of', dst: 'g:1', possibility: 0.7, value: 42 }
] }
});
assert.ok(Array.isArray(r.collectedValues) && r.collectedValues.length > 0, 'multi_hop partial values collected');
}
// ---- binary mode agrees with normal at the same threshold ----
{
const a = mkArbiter();
a.setRelationConfig('can_access', { type: 'chain', steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'reads', direction: 'out' }
] });
const r = a.check('u:0', 'can_access', 'doc:0', {
binary: true, minAllowPossibility: 0.6,
partialGraph: { relations: [
{ src: 'u:0', relation: 'member_of', dst: 'g:0', possibility: 0.8 },
{ src: 'g:0', relation: 'reads', dst: 'doc:0', possibility: 0.7 }
] }
});
assert.equal(r.possibility, 0.7, 'binary chain partial value');
assert.equal(r.allow, true, 'binary chain partial allow');
}
});
it('PROPERTY CAMPAIGN: TTU differential under random persistent/partial splits', async () => {
function makeWrapper() {
const engine = new Arbiter();
engine.addNode('u:0', 'user');
engine.addNode('doc:0', 'doc');
engine.addNode('g:0', 'group');
engine.addNode('g:1', 'group');
engine.addNode('g:2', 'group');
engine.setRelationConfig('can_read', { type: 'tuple_to_userset', tuplesetRelation: 'owner', computedRelation: 'member_of' });
engine.setRelationConfig('member_of', { type: 'direct', relation: 'member_of' });
const persistent = new Map();
const partialEdges = [];
const w = {
engine,
setEdge(rel, dst, p, reli, value, side) {
const src = rel === 'owner' ? 'doc:0' : 'u:0';
const opts = { possibility: p, reliability: reli };
if (rel === 'owner' && value !== null) opts.value = value; // values ride the tupleset leg
if (side === 'persistent') {
engine.addRelation(src, rel, dst, opts);
// add-on-existing is a modify: an absent new value preserves the
// stored one (mirror the engine).
const key = src + '|' + rel + '|' + dst;
const existing = persistent.get(key);
persistent.set(key, { p, r: reli, v: opts.value !== undefined ? opts.value : (existing ? existing.v : undefined) });
} else {
const idx = partialEdges.findIndex(e => e.relation === rel && e.dst === dst);
if (idx >= 0) partialEdges.splice(idx, 1);
partialEdges.push({ src, relation: rel, dst, possibility: p, reliability: reli, value: opts.value });
}
return { ok: true };
},
clearAll() {
for (const key of [...persistent.keys()]) {
const [src, rel, dst] = key.split('|');
engine.removeRelation(src, rel, dst);
}
persistent.clear();
partialEdges.length = 0;
return { ok: true };
},
check() {
const options = partialEdges.length > 0
? { partialGraph: { relations: partialEdges.slice() } }
: {};
const r = engine.check('u:0', 'can_read', 'doc:0', { ...options, collectValues: true });
// TTU mirror: join both legs per intermediate — max over mids of
// min(tupleset.possibility, computed.possibility), with the winning
// intermediate's reliability = tupleset.reli * computed.reli. Edges
// come from persistent and/or partial; on same-tuple conflicts the
// overlay contract is persistent-wins (persistent outranks partial).
const tupleset = new Map(); // dst -> { p, r, v }
const computed = new Map(); // dst -> { p, r }
for (const [key, v] of persistent) {
const [src, rel, dst] = key.split('|');
if (rel === 'owner') tupleset.set(dst, v);
else computed.set(dst, v);
}
for (const e of partialEdges) {
if (e.relation === 'owner') {
if (!tupleset.has(e.dst)) tupleset.set(e.dst, { p: e.possibility, r: e.reliability ?? 1.0, v: e.value });
} else if (!computed.has(e.dst)) {
computed.set(e.dst, { p: e.possibility, r: e.reliability ?? 1.0 });
}
}
let best = 0;
let bestReliability = 0;
const expectedValues = [];
for (const [mid, tp] of tupleset) {
const cp = computed.get(mid);
if (cp === undefined) continue;
const combinedP = Math.min(tp.p, cp.p);
if (combinedP > 0) {
if (tp.v !== undefined) expectedValues.push(round4(tp.v));
}
if (combinedP > best) {
best = combinedP;
bestReliability = (tp.r ?? 1.0) * (cp.r ?? 1.0);
}
}
expectedValues.sort((a, b) => a - b);
const engineValues = (r.collectedValues || [])
.map(v => typeof v === 'number' ? round4(v) : (typeof v.value === 'number' ? round4(v.value) : null))
.filter(v => v !== null)
.sort((a, b) => a - b);
return {
engine: round4(r.possibility),
expected: round4(best),
engineReliability: round4(r.reliability ?? 0),
expectedReliability: round4(bestReliability),
engineValues,
expectedValues,
persistent: [...persistent.keys()],
partial: partialEdges.map(e => e.relation + ':' + e.dst)
};
},
clone() { return w; }
};
return w;
}
const result = await rigor.campaign(
[rigor.object('graph', makeWrapper, [
rigor.method('setEdge', function (rel, dst, p, reli, value, side) { return this.setEdge(rel, dst, p, reli, value, side); },
rigor.args(
rigor.gen.oneOf(['owner', 'member_of']),
rigor.gen.oneOf(['g:0', 'g:1', 'g:2']),
rigor.gen.float(0.1, 1.0),
rigor.gen.oneOf([0.3, 0.6, 0.9]),
rigor.gen.oneOf([null, 5, 42]),
rigor.gen.oneOf(['persistent', 'partial'])
)),
rigor.method('clearAll', function () { return this.clearAll(); }),
rigor.method('check', function () { return this.check(); })
])],
rigor.crucible([
rigor.invariant('TTU both-legs parity', (ctx) => {
if (ctx.action !== 'graph.check' || ctx.error !== null) return true;
return ctx.actual.engine === ctx.actual.expected;
}),
rigor.invariant('TTU reliability parity', (ctx) => {
if (ctx.action !== 'graph.check' || ctx.error !== null) return true;
if (ctx.actual.expected === 0) return true; // denied: reliability 0
return ctx.actual.engineReliability === ctx.actual.expectedReliability;
}),
rigor.invariant('TTU collected-value parity', (ctx) => {
if (ctx.action !== 'graph.check' || ctx.error !== null) return true;
return JSON.stringify(ctx.actual.engineValues) === JSON.stringify(ctx.actual.expectedValues);
}),
rigor.invariant('no action errors', (ctx) => ctx.error === null)
])
).run({ effort: 400, seed: "ttu-partial-split-2026", maxTraceLength: 25, artifacts: { dir: "", persist: "never" } });
const inv = result.crucibleVerdict;
assert.equal(inv.passed, true, [
`TTU parity violated in ${inv.failureCount} cases:`,
...result.failures.slice(0, 3).map((f) =>
` [${f.name}] action=${f.actionName} step=${f.stepIndex} seq=${JSON.stringify((f.sequence || []).map(s => s.args).filter(a => a && a.length))} error=${f.error}`
)
].join('\n'));
}, 90000);
it('PROPERTY CAMPAIGN: comparator differential under random value mutations', async () => {
function makeWrapper() {
const engine = new Arbiter();
engine.addNode('u:0', 'user');
engine.addNode('doc:0', 'doc');
engine.setRelationConfig('can_access', {
type: 'relational_comparator', comparator: '>=',
left: { rule: { type: 'direct', relation: 'has_clearance' }, extractValue: true },
right: { rule: { type: 'direct', relation: 'requested_level' }, extractValue: true, evaluateFrom: 'object' }
});
const w = {
engine,
set(side, value) {
if (side === 'left') {
engine.removeRelation('u:0', 'has_clearance', 'doc:0');
engine.addRelation('u:0', 'has_clearance', 'doc:0', { possibility: 0.9, value });
} else {
engine.removeRelation('doc:0', 'requested_level', 'doc:0');
engine.addRelation('doc:0', 'requested_level', 'doc:0', { possibility: 1.0, value });
}
return { ok: true };
},
check() {
const r = engine.check('u:0', 'can_access', 'doc:0');
return { engine: r.possibility, expected: r.possibility };
},
clone() { return w; }
};
return w;
}
const result = await rigor.campaign(
[rigor.object('graph', makeWrapper, [
rigor.method('set', function (side, value) { return this.set(side, value); },
rigor.args(rigor.gen.oneOf(['left', 'right']), rigor.gen.int(0, 12))),
rigor.method('check', function () { return this.check(); })
])],
rigor.crucible([
rigor.invariant('comparator never throws', (ctx) => ctx.error === null),
rigor.invariant('comparator result is binary', (ctx) => {
if (ctx.action !== 'graph.check' || ctx.error !== null) return true;
return ctx.actual.engine === 0 || ctx.actual.engine === 1;
})
])
).run({ effort: 400, seed: "comparator-2026", maxTraceLength: 25, artifacts: { dir: "", persist: "never" } });
const inv = result.crucibleVerdict;
assert.equal(inv.passed, true, [
`comparator violated in ${inv.failureCount} cases:`,
...result.failures.slice(0, 3).map((f) =>
` [${f.invariant}] action=${f.action} args=${JSON.stringify(f.args)} actual=${JSON.stringify(f.actual)} error=${f.error}`
)
].join('\n'));
}, 90000);
it('PROPERTY CAMPAIGN: defeasible differential under random persistent/partial splits', async () => {
function makeWrapper() {
const engine = new Arbiter();
engine.addNode('u:0', 'user');
engine.addNode('doc:0', 'doc');
engine.setRelationConfig('can_access', { type: 'defeasible', when: { relation: 'owner' }, unless: { relation: 'banned' } });
const persistent = new Map();
const partialEdges = [];
const w = {
engine,
setEdge(rel, p, reli, side) {
if (side === 'persistent') {
engine.addRelation('u:0', rel, 'doc:0', { possibility: p, reliability: reli });
persistent.set(rel, { p, r: reli });
} else {
const idx = partialEdges.findIndex(e => e.relation === rel);
if (idx >= 0) partialEdges.splice(idx, 1);
partialEdges.push({ src: 'u:0', relation: rel, dst: 'doc:0', possibility: p, reliability: reli });
}
return { ok: true };
},
clearAll() {
for (const rel of [...persistent.keys()]) engine.removeRelation('u:0', rel, 'doc:0');
persistent.clear();
partialEdges.length = 0;
return { ok: true };
},
check() {
const options = partialEdges.length > 0
? { partialGraph: { relations: partialEdges.slice() } }
: {};
const r = engine.check('u:0', 'can_access', 'doc:0', options);
// Mirror: P(when) * (1 - P(unless)) with reliability
// when.reli * unless.reli; persistent wins on same-tuple conflicts.
const when = persistent.get('owner') ?? partialEdges.find(e => e.relation === 'owner');
const unless = persistent.get('banned') ?? partialEdges.find(e => e.relation === 'banned');
const whenP = when ? (when.p ?? when.possibility) : 0;
const whenR = when ? (when.r ?? when.reliability ?? 1.0) : 1.0;
const unlessP = unless ? (unless.p ?? unless.possibility) : 0;
const unlessR = unless ? (unless.r ?? unless.reliability ?? 1.0) : 1.0;
const wp = whenP;
const wpR = whenR;
const up = unlessP;
const upR = unlessR;
const expected = round4(wp * (1 - up));
const expectedReliability = round4(wpR * upR);
return {
engine: round4(r.possibility),
expected,
engineReliability: round4(r.reliability ?? 0),
expectedReliability
};
},
clone() { return w; }
};
return w;
}
const result = await rigor.campaign(
[rigor.object('graph', makeWrapper, [
rigor.method('setEdge', function (rel, p, reli, side) { return this.setEdge(rel, p, reli, side); },
rigor.args(
rigor.gen.oneOf(['owner', 'banned']),
rigor.gen.float(0.0, 1.0),
rigor.gen.oneOf([0.3, 0.6, 0.9]),
rigor.gen.oneOf(['persistent', 'partial'])
)),
rigor.method('clearAll', function () { return this.clearAll(); }),
rigor.method('check', function () { return this.check(); })
])],
rigor.crucible([
rigor.invariant('defeasible decision parity', (ctx) => {
if (ctx.action !== 'graph.check' || ctx.error !== null) return true;
return ctx.actual.engine === ctx.actual.expected;
}),
rigor.invariant('defeasible reliability parity', (ctx) => {
if (ctx.action !== 'graph.check' || ctx.error !== null) return true;
if (ctx.actual.expected === 0) return true; // denied: reliability 0
return ctx.actual.engineReliability === ctx.actual.expectedReliability;
}),
rigor.invariant('no action errors', (ctx) => ctx.error === null)
])
).run({ effort: 400, seed: "defeasible-split-2026", maxTraceLength: 25, artifacts: { dir: "", persist: "never" } });
const inv = result.crucibleVerdict;
assert.equal(inv.passed, true, [
`defeasible violated in ${inv.failureCount} cases:`,
...result.failures.slice(0, 3).map((f) =>
` [${f.name}] action=${f.actionName} seq=${JSON.stringify((f.sequence || []).map(s => s.args).filter(a => a && a.length))} error=${f.error}`
)
].join('\n'));
}, 90000);
it('PROPERTY CAMPAIGN: intersection differential under random persistent/partial splits', async () => {
function makeWrapper() {
const engine = new Arbiter();
engine.addNode('u:0', 'user');
engine.addNode('doc:0', 'doc');
engine.setRelationConfig('can_access', { intersection: { rules: [{ relation: 'owner' }, { relation: 'verified' }] } });
const persistent = new Map();
const partialEdges = [];
const w = {
engine,
setEdge(rel, p, reli, side) {
if (side === 'persistent') {
engine.addRelation('u:0', rel, 'doc:0', { possibility: p, reliability: reli });
persistent.set(rel, { p, r: reli });
} else {
const idx = partialEdges.findIndex(e => e.relation === rel);
if (idx >= 0) partialEdges.splice(idx, 1);
partialEdges.push({ src: 'u:0', relation: rel, dst: 'doc:0', possibility: p, reliability: reli });
}
return { ok: true };
},
clearAll() {
for (const rel of [...persistent.keys()]) engine.removeRelation('u:0', rel, 'doc:0');
persistent.clear();
partialEdges.length = 0;
return { ok: true };
},
check() {
const options = partialEdges.length > 0
? { partialGraph: { relations: partialEdges.slice() } }
: {};
const r = engine.check('u:0', 'can_access', 'doc:0', options);
// Mirror: min over children with the min child's reliability;
// persistent wins on same-tuple conflicts.
const edges = {};
for (const rel of ['owner', 'verified']) {
const p = persistent.get(rel);
if (p) edges[rel] = p;
}
for (const e of partialEdges) {
if (!edges[e.relation]) edges[e.relation] = { p: e.possibility, r: e.reliability ?? 1.0 };
}
const present = Object.values(edges);
let expected = 0;
let expectedReliability = 0;
if (present.length === 2) {
const minP = Math.min(present[0].p, present[1].p);
const minIdx = present[0].p <= present[1].p ? 0 : 1;
expected = round4(minP);
expectedReliability = round4(present[minIdx].r ?? 1.0);
}
return {
engine: round4(r.possibility),
expected,
engineReliability: round4(r.reliability ?? 0),
expectedReliability
};
},
clone() { return w; }
};
return w;
}
const result = await rigor.campaign(
[rigor.object('graph', makeWrapper, [
rigor.method('setEdge', function (rel, p, reli, side) { return this.setEdge(rel, p, reli, side); },
rigor.args(
rigor.gen.oneOf(['owner', 'verified']),
rigor.gen.float(0.0, 1.0),
rigor.gen.oneOf([0.3, 0.6, 0.9]),
rigor.gen.oneOf(['persistent', 'partial'])
)),
rigor.method('clearAll', function () { return this.clearAll(); }),
rigor.method('check', function () { return this.check(); })
])],
rigor.crucible([
rigor.invariant('intersection decision parity', (ctx) => {
if (ctx.action !== 'graph.check' || ctx.error !== null) return true;
return ctx.actual.engine === ctx.actual.expected;
}),
rigor.invariant('intersection reliability parity', (ctx) => {
if (ctx.action !== 'graph.check' || ctx.error !== null) return true;
if (ctx.actual.expected === 0) return true; // denied: reliability 0
return ctx.actual.engineReliability === ctx.actual.expectedReliability;
}),
rigor.invariant('no action errors', (ctx) => ctx.error === null)
])
).run({ effort: 400, seed: "intersection-split-2026", maxTraceLength: 25, artifacts: { dir: "", persist: "never" } });
const inv = result.crucibleVerdict;
assert.equal(inv.passed, true, [
`intersection violated in ${inv.failureCount} cases:`,
...result.failures.slice(0, 3).map((f) =>
` [${f.name}] action=${f.actionName} seq=${JSON.stringify((f.sequence || []).map(s => s.args).filter(a => a && a.length))} error=${f.error}`
)
].join('\n'));
}, 90000);
it('PROPERTY CAMPAIGN: chain reliability differential under random edge splits', async () => {
function makeWrapper() {
const engine = new Arbiter();
engine.addNode('u:0', 'user');
engine.addNode('doc:0', 'doc');
engine.addNode('g:0', 'group');
engine.addNode('g:1', 'group');
engine.setRelationConfig('can_access', { type: 'chain', steps: [
{ relation: 'member_of', direction: 'out' },
{ relation: 'reads', direction: 'out' }
] });
const persistent = new Map();
const partialEdges = [];
const w = {
engine,
setEdge(rel, dst, p, reli, side) {
const src = rel === 'member_of' ? 'u:0' : 'g:0';
if (rel === 'reads') dst = 'doc:0'; // the chain's second leg must point at the target
if (side === 'persistent') {
engine.addRelation(src, rel, dst, { possibility: p, reliability: reli });
persistent.set(src + '|' + rel + '|' + dst, { p, r: reli });
} else {
const idx = partialEdges.findIndex(e => e.relation === rel && e.dst === dst);
if (idx >= 0) partialEdges.splice(idx, 1);
partialEdges.push({ src, relation: rel, dst, possibility: p, reliability: reli });
}
return { ok: true };
},
clearAll() {
for (const key of [...persistent.keys()]) {
const [src, rel, dst] = key.split('|');
engine.removeRelation(src, rel, dst);
}
persistent.clear();
partialEdges.length = 0;
return { ok: true };
},
check() {
const options = partialEdges.length > 0
? { partialGraph: { relations: partialEdges.slice() } }
: {};
const r = engine.check('u:0', 'can_access', 'doc:0', options);
// Mirror: chain = max over intermediates of min(legs); the winning
// path's reliability is the product of its edges' reliabilities.
// Same-tuple conflicts: persistent wins.
const memberOf = new Map(); // dst -> { p, r }
const reads = new Map(); // src -> { p, r } (edge src ->reads-> doc:0)
for (const [key, v] of persistent) {
const [src, rel, dst] = key.split('|');
if (rel === 'member_of') memberOf.set(dst, v);
else reads.set(src, v);
}
for (const e of partialEdges) {
if (e.relation === 'member_of') {
if (!memberOf.has(e.dst)) memberOf.set(e.dst, { p: e.possibility, r: e.reliability ?? 1.0 });
} else {
if (!reads.has(e.src)) reads.set(e.src, { p: e.possibility, r: e.reliability ?? 1.0 });
}
}
let best = 0;
let bestReliability = 0;
for (const [mid, m] of memberOf) {
const rd = reads.get(mid);
if (rd === undefined) continue;
const combinedP = Math.min(m.p, rd.p);
if (combinedP > best) {
best = combinedP;
bestReliability = (m.r ?? 1.0) * (rd.r ?? 1.0);
}
}
return {
engine: round4(r.possibility),
expected: round4(best),
engineReliability: round4(r.reliability ?? 0),
expectedReliability: round4(bestReliability)
};
},
clone() { return w; }
};
return w;
}
const result = await rigor.campaign(
[rigor.object('graph', makeWrapper, [
rigor.method('setEdge', function (rel, dst, p, reli, side) { return this.setEdge(rel, dst, p, reli, side); },
rigor.args(
rigor.gen.oneOf(['member_of', 'reads']),
rigor.gen.oneOf(['g:0', 'g:1', 'doc:0']),
rigor.gen.float(0.1, 1.0),
rigor.gen.oneOf([0.3, 0.6, 0.9]),
rigor.gen.oneOf(['persistent', 'partial'])
)),
rigor.method('clearAll', function () { return this.clearAll(); }),
rigor.method('check', function () { return this.check(); })
])],
rigor.crucible([
rigor.invariant('chain decision parity', (ctx) => {
if (ctx.action !== 'graph.check' || ctx.error !== null) return true;
return ctx.actual.engine === ctx.actual.expected;
}),
rigor.invariant('chain reliability parity', (ctx) => {
if (ctx.action !== 'graph.check' || ctx.error !== null) return true;
if (ctx.actual.expected === 0) return true; // denied: reliability 0
return ctx.actual.engineReliability === ctx.actual.expectedReliability;
}),
rigor.invariant('no action errors', (ctx) => ctx.error === null)
])
).run({ effort: 400, seed: "chain-reliability-2026", maxTraceLength: 25, artifacts: { dir: "", persist: "never" } });
const inv = result.crucibleVerdict;
assert.equal(inv.passed, true, [
`chain reliability violated in ${inv.failureCount} cases:`,
...result.failures.slice(0, 3).map((f) =>
` [${f.name}] action=${f.actionName} seq=${JSON.stringify((f.sequence || []).map(s => s.args).filter(a => a && a.length))} error=${f.error}`
)
].join('\n'));
}, 90000);
it('PROPERTY CAMPAIGN: exclusion differential under random split edges', async () => {
function makeWrapper() {
const engine = new Arbiter();
engine.addNode('u:0', 'user');
engine.addNode('doc:0', 'doc');
engine.setRelationConfig('can_access', { exclusion: [{ relation: 'owner' }, { relation: 'banned' }] });
const own = { owner: null, banned: null };
const w = {
engine,
set(side, p) {
engine.removeRelation('u:0', side, 'doc:0');
own[side] = p;
engine.addRelation('u:0', side, 'doc:0', { possibility: p });
return { ok: true };
},
check() {
const r = engine.check('u:0', 'can_access', 'doc:0');
const bothPersistent = !!(own.owner !== null && own.banned !== null);
let partialEmpty = null;
if (bothPersistent) {
const r2 = engine.check('u:0', 'can_access', 'doc:0', {
partialGraph: { relations: [
{ src: 'u:0', relation: 'owner', dst: 'doc:0', possibility: 0 },
{ src: 'u:0', relation: 'banned', dst: 'doc:0', possibility: 0 }
] }
});
partialEmpty = round4(r2.possibility);
}
return { persistent: round4(r.possibility), partialEmpty, expected: round4(r.possibility) };
},
clone() { return w; }
};
return w;
}
const result = await rigor.campaign(
[rigor.object('graph', makeWrapper, [
rigor.method('set', function (side, p) { return this.set(side, p); },
rigor.args(rigor.gen.oneOf(['owner', 'banned']), rigor.gen.float(0.0, 1.0))),
rigor.method('check', function () { return this.check(); })
])],
rigor.crucible([
rigor.invariant('no action errors', (ctx) => ctx.error === null),
rigor.invariant('empty partial overlay leaves decision unchanged', (ctx) => {
if (ctx.action !== 'graph.check' || ctx.error !== null) return true;
if (ctx.actual.partialEmpty === null) return true; // overlay would inject missing tuples
return ctx.actual.partialEmpty === ctx.actual.persistent;
})
])
).run({ effort: 400, seed: "exclusion-2026", maxTraceLength: 25, artifacts: { dir: "", persist: "never" } });
const inv = result.crucibleVerdict;
assert.equal(inv.passed, true, [
`exclusion violated in ${inv.failureCount} cases:`,
...result.failures.slice(0, 3).map((f) =>
` [${f.name}] action=${f.actionName} step=${f.stepIndex} seq=${JSON.stringify((f.sequence || []).map(s => s.args).filter(a => a && a.length))} error=${f.error}`
)
].join('\n'));
}, 90000);
});