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
@@ -0,0 +1,333 @@
/**
* rigor/relational-comparator-router.test.js — js-rigor property tests for
* RelationalComparatorRouter._isQualitativeRule.
*
* The router dispatches to either the numeric or qualitative implementation
* based on the rule config. Properties:
* - rule.qualitative === true → qualitative (regardless of other fields)
* - any operand scaleName → qualitative
* - any operand decaySteps/baseBlurSteps (defined, non-null) → qualitative
* - rule.marginSteps (defined, non-null) → qualitative
* - otherwise → numeric
*
* getImplementationType is a pure pass-through to _isQualitativeRule.
*/
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { rigor } from '@rigor/core';
import { RelationalComparatorRouter } from '../../src/authorization/rules/RelationalComparatorRouter.js';
const SCALE_NAMES = ['five-point', 'ternary', 'ordinal-7'];
/**
* Build a stub arbiter that satisfies the rule's constructor.
* Neither _evaluateRule is called by these tests, but the constructor
* stores references that the rule may touch on dispatch.
*/
function makeStubArbiter() {
return {
nodeIdByKey: new Map(),
keyByNodeId: new Map(),
relations: [],
nodes: new Map()
};
}
function makeRouter() {
// _evaluateRule is what we test; pass a stub arbiter so the
// constructor doesn't crash. We never call _evaluateRule in these
// tests — we test _isQualitativeRule and getImplementationType
// directly.
return new RelationalComparatorRouter(makeStubArbiter(), {});
}
describe('RelationalComparatorRouter._isQualitativeRule (rigor)', () => {
it('rule.qualitative=true always wins', async () => {
async function check(rule) {
const router = makeRouter();
const result = router._isQualitativeRule(rule);
if (!result) {
throw new Error(
`expected qualitative=true to win, got numeric. rule=${JSON.stringify(rule)}`
);
}
return result;
}
const report = await rigor.campaign(
[
rigor.fn('check', check,
rigor.args(
rigor.gen.object({
qualitative: rigor.gen.constant(true),
// random other fields that should not matter
left: rigor.gen.option(rigor.gen.object({
scaleName: rigor.gen.option(rigor.gen.enum(SCALE_NAMES))
})),
right: rigor.gen.option(rigor.gen.object({
scaleName: rigor.gen.option(rigor.gen.enum(SCALE_NAMES))
})),
marginSteps: rigor.gen.option(rigor.gen.int(0, 5)),
fallbackBehavior: rigor.gen.option(rigor.gen.enum(['allow', 'deny']))
})
)
)
],
rigor.crucible([
rigor.invariant('qualitative-wins', ({ 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 === 'qualitative-wins');
assert.ok(inv);
assert.equal(inv.passed, true,
`qualitative=true did not win in ${inv.failureCount} cases`);
});
it('scaleName on either operand triggers qualitative', async () => {
async function check(operand, side) {
const router = makeRouter();
const rule = {
left: side === 'left' ? operand : {},
right: side === 'right' ? operand : {}
};
const result = router._isQualitativeRule(rule);
if (!result) {
throw new Error(
`expected scaleName on ${side} to trigger qualitative, got numeric. ` +
`operand=${JSON.stringify(operand)}`
);
}
return result;
}
const report = await rigor.campaign(
[
rigor.fn('check', check,
rigor.args(
rigor.gen.object({
scaleName: rigor.gen.enum(SCALE_NAMES)
}),
rigor.gen.enum(['left', 'right'])
)
)
],
rigor.crucible([
rigor.invariant('scaleName-triggers', ({ 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 === 'scaleName-triggers');
assert.ok(inv);
assert.equal(inv.passed, true,
`scaleName did not trigger qualitative in ${inv.failureCount} cases`);
});
it('decaySteps/baseBlurSteps on either operand triggers qualitative', async () => {
async function check(operand, side) {
const router = makeRouter();
const rule = {
left: side === 'left' ? operand : {},
right: side === 'right' ? operand : {}
};
const result = router._isQualitativeRule(rule);
if (!result) {
throw new Error(
`expected ${JSON.stringify(Object.keys(operand))} on ${side} to trigger qualitative`
);
}
return result;
}
const report = await rigor.campaign(
[
rigor.fn('check', check,
rigor.args(
rigor.gen.oneOf([
rigor.gen.object({ decaySteps: rigor.gen.int(0, 10) }),
rigor.gen.object({ baseBlurSteps: rigor.gen.int(0, 10) })
]),
rigor.gen.enum(['left', 'right'])
)
)
],
rigor.crucible([
rigor.invariant('decay-blur-triggers', ({ 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 === 'decay-blur-triggers');
assert.ok(inv);
assert.equal(inv.passed, true,
`decaySteps/baseBlurSteps did not trigger qualitative in ${inv.failureCount} cases`);
});
it('marginSteps triggers qualitative (when defined and non-null)', async () => {
async function check(marginSteps) {
const router = makeRouter();
const rule = { marginSteps };
const result = router._isQualitativeRule(rule);
if (marginSteps === undefined || marginSteps === null) {
// marginSteps not present — should not trigger
if (result) throw new Error(`marginSteps=${marginSteps} should not trigger qualitative`);
} else {
if (!result) throw new Error(`marginSteps=${marginSteps} should trigger qualitative`);
}
return result;
}
const report = await rigor.campaign(
[
rigor.fn('check', check,
rigor.args(
rigor.gen.option(rigor.gen.int(0, 10))
)
)
],
rigor.crucible([
rigor.invariant('marginSteps-correct', ({ 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 === 'marginSteps-correct');
assert.ok(inv);
assert.equal(inv.passed, true,
`marginSteps handling was wrong in ${inv.failureCount} cases`);
});
it('plain numeric rule (no qualitative flags) routes to numeric', async () => {
async function check(leftValue, rightValue, comparator) {
const router = makeRouter();
const rule = {
left: { rule: { type: 'direct' }, extractValue: true },
right: { rule: { type: 'direct' }, extractValue: true },
comparator,
leftValue, // injected for testing only — production ignores
rightValue
};
const result = router._isQualitativeRule(rule);
if (result) {
throw new Error(
`expected numeric, got qualitative. rule=${JSON.stringify(rule)}`
);
}
return result;
}
const report = await rigor.campaign(
[
rigor.fn('check', check,
rigor.args(
rigor.gen.int(-1000, 1000),
rigor.gen.int(-1000, 1000),
rigor.gen.enum(['>', '>=', '<', '<=', '==', '!='])
)
)
],
rigor.crucible([
rigor.invariant('plain-numeric', ({ 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 === 'plain-numeric');
assert.ok(inv);
assert.equal(inv.passed, true,
`plain numeric rule routed to qualitative in ${inv.failureCount} cases`);
});
it('getImplementationType matches _isQualitativeRule', async () => {
async function check(rule) {
const router = makeRouter();
const fromPrivate = router._isQualitativeRule(rule);
const fromPublic = router.getImplementationType(rule);
const expected = fromPrivate ? 'qualitative' : 'numeric';
if (fromPublic !== expected) {
throw new Error(
`getImplementationType=${fromPublic} but _isQualitativeRule=${fromPrivate}`
);
}
return fromPublic;
}
const report = await rigor.campaign(
[
rigor.fn('check', check,
rigor.args(
rigor.gen.object({
qualitative: rigor.gen.option(rigor.gen.boolean()),
left: rigor.gen.option(rigor.gen.object({
scaleName: rigor.gen.option(rigor.gen.enum(SCALE_NAMES)),
decaySteps: rigor.gen.option(rigor.gen.int(0, 10))
})),
right: rigor.gen.option(rigor.gen.object({
scaleName: rigor.gen.option(rigor.gen.enum(SCALE_NAMES)),
decaySteps: rigor.gen.option(rigor.gen.int(0, 10))
})),
marginSteps: rigor.gen.option(rigor.gen.int(0, 10))
})
)
)
],
rigor.crucible([
rigor.invariant('getImplType-consistent', ({ error, errorMessage }) => !error && !errorMessage)
])
).run({ effort: 1500 });
if (process.env.TEST_DEBUG === '1') console.log(report.toTAP());
const inv = report.crucibleVerdict?.invariants?.find(i => i.name === 'getImplType-consistent');
assert.ok(inv);
assert.equal(inv.passed, true,
`getImplementationType disagreed with _isQualitativeRule in ${inv.failureCount} cases`);
});
it('_hasValidQualitativeProperty: undefined/null → false, anything else → true', async () => {
async function check(value) {
const router = makeRouter();
const result = router._hasValidQualitativeProperty(value);
if (value === undefined || value === null) {
if (result) throw new Error(`_hasValidQualitativeProperty(${value}) should be false`);
} else {
if (!result) throw new Error(`_hasValidQualitativeProperty(${value}) should be true`);
}
return result;
}
const report = await rigor.campaign(
[
rigor.fn('check', check,
rigor.args(
// Exclude undefined/null from explicit inputs — the
// generator treats them as "absent". The campaign will
// also exercise the absent path via undefined when the
// record is shrunk to {}.
rigor.gen.option(
rigor.gen.oneOf([
rigor.gen.int(),
rigor.gen.float(),
rigor.gen.boolean(),
rigor.gen.string(),
rigor.gen.constant(0),
rigor.gen.constant(''),
rigor.gen.constant(false)
])
)
)
)
],
rigor.crucible([
rigor.invariant('hasValidProperty', ({ error, errorMessage }) => !error && !errorMessage)
])
).run({ effort: 1000 });
if (process.env.TEST_DEBUG === '1') console.log(report.toTAP());
const inv = report.crucibleVerdict?.invariants?.find(i => i.name === 'hasValidProperty');
assert.ok(inv);
assert.equal(inv.passed, true,
`_hasValidQualitativeProperty contract violated in ${inv.failureCount} cases`);
});
});