initial commit: @arbiter/core authorization engine with js-rigor hardening

Zanzibar-style authorization graph engine (direct/chain/TTU/defeasible/
binary modes, condensed snapshots, value relations) with 39 rigor test
campaigns. Includes fixes for snapshot binary writer/reader format
mismatch (snapshot-of-snapshot corruption), possibility write-boundary
validation, empty-graph snapshot serialization, relation lookup cache
direction collision, config-redefinition cache invalidation, binary
threshold semantics, defeasible compiled routing, and comparator
reason whitelisting.
This commit is contained in:
John Dvorak
2026-07-31 13:44:06 -07:00
commit 717ae1031e
373 changed files with 654131 additions and 0 deletions
+321
View File
@@ -0,0 +1,321 @@
/**
* rigor/computed-rule.test.js — js-rigor property tests for ComputedRule.
*
* ComputedRule delegates to arbiter.authChecker.check(userKey, computedRelation,
* objectKey, options) and adapts the result. Properties verified:
*
* - result.possibility equals the delegated authChecker.check result.possibility
* - result.reason defaults to 'computed_delegation' if delegated has no reason
* - result.reason passes through the delegated reason when present
* - meta.ruleType='computed' and meta.computedRelation=rule.relation
* - meta.delegated=true
* - collectedValues pass through from delegated result
* - trackEvaluation=true produces a populated result.evaluation block
* - result.shape is stable across many inputs
*/
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { rigor } from '@rigor/core';
import { ComputedRule } from '../../src/authorization/rules/ComputedRule.js';
const RELATIONS = ['owner', 'viewer', 'editor', 'member', 'parent'];
/**
* Build an arbiter stub whose authChecker.check returns a programmable value.
* Records all calls for assertions.
*/
function makeArbiter(delegate) {
const calls = [];
const arbiter = {
authChecker: {
check(userKey, computedRelation, objectKey, options) {
calls.push({ userKey, computedRelation, objectKey, hasVisited: !!options._visited, hasCurrentRel: !!options._currentRelation });
return delegate(userKey, computedRelation, objectKey, options);
}
}
};
return { arbiter, calls };
}
describe('ComputedRule evaluation (rigor)', () => {
it('result.possibility equals delegated authChecker.check result.possibility', async () => {
async function check(userKey, computedRel, objectKey, possibility) {
const { arbiter, calls } = makeArbiter(() => ({ possibility, reliability: 1.0 }));
const rule = new ComputedRule(arbiter);
const result = rule.evaluate(
0, userKey, 1, objectKey,
{ type: 'computed', relation: computedRel },
new Set(),
'unused',
{}
);
if (result.possibility !== possibility) {
throw new Error(`result.possibility=${result.possibility}, expected ${possibility}`);
}
// Delegation must have happened with the rule.relation as the computed relation
if (calls.length !== 1) throw new Error(`expected 1 authChecker.check call, got ${calls.length}`);
if (calls[0].userKey !== userKey) throw new Error(`userKey=${calls[0].userKey}, expected ${userKey}`);
if (calls[0].computedRelation !== computedRel) throw new Error(`computedRelation=${calls[0].computedRelation}, expected ${computedRel}`);
if (calls[0].objectKey !== objectKey) throw new Error(`objectKey=${calls[0].objectKey}, expected ${objectKey}`);
return result;
}
const report = await rigor.campaign(
[rigor.fn('check', check,
rigor.args(
rigor.gen.string(1, 30),
rigor.gen.enum(RELATIONS),
rigor.gen.string(1, 30),
rigor.gen.float({ min: 0, max: 1 })
)
)],
rigor.crucible([
rigor.invariant('possibility-passthrough', ({ error, errorMessage }) => !error && !errorMessage)
])
).run({ effort: 800 });
if (process.env.TEST_DEBUG === '1') console.log(report.toTAP());
const inv = report.crucibleVerdict?.invariants?.find(i => i.name === 'possibility-passthrough');
assert.ok(inv);
assert.equal(inv.passed, true, `possibility passthrough violated in ${inv.failureCount} cases`);
});
it('result.reason defaults to "computed_delegation" when delegated has no reason', async () => {
async function check(userKey, computedRel, objectKey) {
const { arbiter } = makeArbiter(() => ({ possibility: 0.5, reliability: 1.0 }));
const rule = new ComputedRule(arbiter);
const result = rule.evaluate(
0, userKey, 1, objectKey,
{ type: 'computed', relation: computedRel },
new Set(),
'unused',
{}
);
if (result.reason !== 'computed_delegation') {
throw new Error(`reason=${result.reason}, expected 'computed_delegation'`);
}
return result;
}
const report = await rigor.campaign(
[rigor.fn('check', check,
rigor.args(
rigor.gen.string(1, 30),
rigor.gen.enum(RELATIONS),
rigor.gen.string(1, 30)
)
)],
rigor.crucible([
rigor.invariant('reason-default', ({ error, errorMessage }) => !error && !errorMessage)
])
).run({ effort: 500 });
if (process.env.TEST_DEBUG === '1') console.log(report.toTAP());
const inv = report.crucibleVerdict?.invariants?.find(i => i.name === 'reason-default');
assert.ok(inv);
assert.equal(inv.passed, true, `reason default violated in ${inv.failureCount} cases`);
});
it('result.reason passes through the delegated reason when present', async () => {
async function check(userKey, computedRel, objectKey, reason) {
const { arbiter } = makeArbiter(() => ({ possibility: 0.5, reliability: 1.0, reason }));
const rule = new ComputedRule(arbiter);
const result = rule.evaluate(
0, userKey, 1, objectKey,
{ type: 'computed', relation: computedRel },
new Set(),
'unused',
{}
);
if (result.reason !== reason) {
throw new Error(`reason=${result.reason}, expected ${reason}`);
}
return result;
}
const report = await rigor.campaign(
[rigor.fn('check', check,
rigor.args(
rigor.gen.string(1, 30),
rigor.gen.enum(RELATIONS),
rigor.gen.string(1, 30),
rigor.gen.enum(['direct_match', 'no_relation', 'inferred', 'chain_match', 'computed_delegation'])
)
)],
rigor.crucible([
rigor.invariant('reason-passthrough', ({ error, errorMessage }) => !error && !errorMessage)
])
).run({ effort: 800 });
if (process.env.TEST_DEBUG === '1') console.log(report.toTAP());
const inv = report.crucibleVerdict?.invariants?.find(i => i.name === 'reason-passthrough');
assert.ok(inv);
assert.equal(inv.passed, true, `reason passthrough violated in ${inv.failureCount} cases`);
});
it('meta.ruleType="computed" and meta.computedRelation=rule.relation', async () => {
async function check(userKey, computedRel, objectKey) {
const { arbiter } = makeArbiter(() => ({ possibility: 0.5, reliability: 1.0 }));
const rule = new ComputedRule(arbiter);
const result = rule.evaluate(
0, userKey, 1, objectKey,
{ type: 'computed', relation: computedRel },
new Set(),
'unused',
{ includeMeta: true, trackEvaluation: false }
);
if (!result.meta) throw new Error(`result.meta is missing (full result: ${JSON.stringify(result)})`);
if (result.meta.ruleType !== 'computed') {
throw new Error(`meta.ruleType=${result.meta.ruleType}, expected 'computed' (full meta: ${JSON.stringify(result.meta)})`);
}
if (result.meta.computedRelation !== computedRel) {
throw new Error(`meta.computedRelation=${result.meta.computedRelation}, expected ${computedRel}`);
}
if (result.meta.delegated !== true) {
throw new Error(`meta.delegated=${result.meta.delegated}, expected true`);
}
return result;
}
const report = await rigor.campaign(
[rigor.fn('check', check,
rigor.args(
rigor.gen.string(1, 30),
rigor.gen.enum(RELATIONS),
rigor.gen.string(1, 30)
)
)],
rigor.crucible([
rigor.invariant('meta-contract', ({ error, errorMessage }) => !error && !errorMessage)
])
).run({ effort: 800 });
if (process.env.TEST_DEBUG === '1') console.log(report.toTAP());
const inv = report.crucibleVerdict?.invariants?.find(i => i.name === 'meta-contract');
assert.ok(inv);
assert.equal(inv.passed, true, `meta contract violated in ${inv.failureCount} cases`);
});
it('collectedValues pass through from delegated result', async () => {
async function check(userKey, computedRel, objectKey, nValues) {
const values = Array.from({ length: nValues }, (_, i) => ({
value: i + 1,
possibility: 0.5,
path: [userKey, objectKey],
source: { entityKey: userKey, relation: computedRel, step: 0 },
metadata: { timestamp: 1000, reliability: 1.0 }
}));
const { arbiter } = makeArbiter(() => ({ possibility: 0.5, reliability: 1.0, collectedValues: values }));
const rule = new ComputedRule(arbiter);
const result = rule.evaluate(
0, userKey, 1, objectKey,
{ type: 'computed', relation: computedRel },
new Set(),
'unused',
{}
);
if (result.collectedValues.length !== nValues) {
throw new Error(`collectedValues.length=${result.collectedValues.length}, expected ${nValues}`);
}
for (let i = 0; i < nValues; i++) {
if (result.collectedValues[i].value !== values[i].value) {
throw new Error(`collectedValues[${i}].value=${result.collectedValues[i].value}, expected ${values[i].value}`);
}
}
return result;
}
const report = await rigor.campaign(
[rigor.fn('check', check,
rigor.args(
rigor.gen.string(1, 30),
rigor.gen.enum(RELATIONS),
rigor.gen.string(1, 30),
rigor.gen.int(0, 5)
)
)],
rigor.crucible([
rigor.invariant('collected-values-passthrough', ({ error, errorMessage }) => !error && !errorMessage)
])
).run({ effort: 800 });
if (process.env.TEST_DEBUG === '1') console.log(report.toTAP());
const inv = report.crucibleVerdict?.invariants?.find(i => i.name === 'collected-values-passthrough');
assert.ok(inv);
assert.equal(inv.passed, true, `collected values passthrough violated in ${inv.failureCount} cases`);
});
it('result.possibility defaults to 0 when delegated has no possibility', async () => {
async function check(userKey, computedRel, objectKey) {
const { arbiter } = makeArbiter(() => ({ reliability: 1.0 })); // no possibility
const rule = new ComputedRule(arbiter);
const result = rule.evaluate(
0, userKey, 1, objectKey,
{ type: 'computed', relation: computedRel },
new Set(),
'unused',
{}
);
if (result.possibility !== 0) {
throw new Error(`result.possibility=${result.possibility}, expected 0 (fallback when delegated is undefined)`);
}
return result;
}
const report = await rigor.campaign(
[rigor.fn('check', check,
rigor.args(
rigor.gen.string(1, 30),
rigor.gen.enum(RELATIONS),
rigor.gen.string(1, 30)
)
)],
rigor.crucible([
rigor.invariant('possibility-fallback-zero', ({ error, errorMessage }) => !error && !errorMessage)
])
).run({ effort: 500 });
if (process.env.TEST_DEBUG === '1') console.log(report.toTAP());
const inv = report.crucibleVerdict?.invariants?.find(i => i.name === 'possibility-fallback-zero');
assert.ok(inv);
assert.equal(inv.passed, true, `possibility fallback violated in ${inv.failureCount} cases`);
});
it('authChecker.check is called with the visited set and currentRelation passed through options', async () => {
async function check(userKey, computedRel, objectKey, currentRel) {
const { arbiter, calls } = makeArbiter(() => ({ possibility: 0.5, reliability: 1.0 }));
const rule = new ComputedRule(arbiter);
const visited = new Set([`visited:1`, `visited:2`]);
rule.evaluate(
0, userKey, 1, objectKey,
{ type: 'computed', relation: computedRel },
visited,
currentRel,
{}
);
if (calls.length !== 1) throw new Error(`expected 1 call, got ${calls.length}`);
if (!calls[0].hasVisited) throw new Error('authChecker.check did not receive options._visited');
if (!calls[0].hasCurrentRel) throw new Error('authChecker.check did not receive options._currentRelation');
return true;
}
const report = await rigor.campaign(
[rigor.fn('check', check,
rigor.args(
rigor.gen.string(1, 30),
rigor.gen.enum(RELATIONS),
rigor.gen.string(1, 30),
rigor.gen.enum(RELATIONS)
)
)],
rigor.crucible([
rigor.invariant('options-passthrough', ({ error, errorMessage }) => !error && !errorMessage)
])
).run({ effort: 800 });
if (process.env.TEST_DEBUG === '1') console.log(report.toTAP());
const inv = report.crucibleVerdict?.invariants?.find(i => i.name === 'options-passthrough');
assert.ok(inv);
assert.equal(inv.passed, true, `options passthrough violated in ${inv.failureCount} cases`);
});
});