feat: tuple_to_userset intermediate reachability; ratio-based benchmark gate
CI / publish (push) Successful in 15s
CI / test (push) Successful in 5m7s
CI / benchmark (push) Successful in 53s

- ChainRule._expandRuleFromSrc now expands tuple_to_userset configs: from a
  source node the reachable set is the objects sharing an intermediate with
  the source (src ->computed-> intermediate ->tupleset-> object, direction
  aware, weakest-link combined). Lets a TTU evidence serve as an intermediate
  condition step in a chain.
- scripts/benchmark.js: ratio-based self-calibration. Comparing each action's
  RATIO to a cheap reference action (default check[direct-hit]) cancels
  machine-load swings that scale all actions proportionally, so the gate only
  fails on code regressions that shift a single action's ratio. The reference
  is still checked absolutely with a loose bound. Verified: stable across
  runs, and a simulated union-ttu slowdown is caught (+76.8% ratio).

Tests: chain-condition-step intermediate TTU expansion.
This commit is contained in:
John Dvorak
2026-08-03 15:43:48 -07:00
parent ab0e569552
commit bd9c74fb0e
5 changed files with 167 additions and 69 deletions
+29
View File
@@ -106,6 +106,35 @@ describe('ChainRule condition step (rule-based final hop)', () => {
assert.equal(denied.possibility, 0);
});
it('expands a tuple_to_userset evidence as an intermediate condition step', () => {
arbiter.setRelationConfig('member_of', { type: 'direct' });
arbiter.setRelationConfig('owner', { type: 'direct' });
arbiter.setRelationConfig('doc_route', { type: 'direct' });
arbiter.addNode('group:g', 'group');
arbiter.addNode('doc:1', 'doc');
arbiter.addNode('doc:2', 'doc');
arbiter.addRelation('user:u', 'member_of', 'group:g', { possibility: 1.0 });
arbiter.addRelation('group:g', 'owner', 'doc:1', { possibility: 0.8 });
arbiter.addRelation('doc:1', 'doc_route', 'doc:2', { possibility: 0.9 });
const ttuConfig = {
type: 'tuple_to_userset',
tuplesetRelation: 'owner',
computedRelation: 'member_of',
tuplesetDirection: 'in'
};
const rule = {
type: 'chain',
steps: [{ rule: ttuConfig, conditionStep: true }, 'doc_route']
};
// TTU reachable from user = doc:1 (min(1.0, 0.8) = 0.8), then doc_route → 0.9
const res = evalRule('user:u', 'doc:2', rule);
assert.ok(Math.abs(res.possibility - 0.8) < 1e-9, `expected 0.8, got ${res.possibility} (${res.reason})`);
// Direct expansion unit check
const expanded = chainRule._expandRuleFromSrc(arbiter.resolveNodeId('user:u'), ttuConfig, {});
assert.equal(expanded.size, 1);
assert.equal(arbiter.resolveKey([...expanded.keys()][0]), 'doc:1');
});
it('combines across multiple parallel intermediates (max aggregation)', () => {
arbiter.addNode('group:g2', 'group');
arbiter.addRelation('user:u', 'member_of', 'group:g', { possibility: 0.5 });