js-rigor: reliability flows through every rule kind; multi_hop value collection fixed
Systemic reliability gap found by the probe sweep: the compiled evaluation paths never emitted the reliability the engine computes. - Compiled _evaluateDirect omitted the relation's reliability, and the chain/multi_hop rules hardcoded reliability: 1.0 — so check() results reported 1.0 for any rule whose decision came through a chain, multi_hop, union, intersection, exclusion, or defeasible combination. - The chain and multi_hop traversals now track per-path reliability (product of edge reliabilities) and report the winning path's value; the compiled and fallback logical operators (union/intersection/exclusion, direct_list fast path, early exits) report the selected child's reliability (max/min child or OWA trace index; exclusion multiplies both legs), and normal-mode defeasible combines base x requires x defeater reliabilities. - The checker's logical fast path dropped collectedValues from union/ intersection/exclusion results; it now passes them through. - MultiHopRule.valueManager was read off relationManager where the real arbiter keeps it on the arbiter — collectValues: true on a multi_hop rule with a value-carrying edge crashed the evaluation (error result, silent denial). Now resolved at the arbiter level with a relationManager fallback for stubs. Campaign pins: reliability per kind (chain/multi_hop product, union/intersection selected child, exclusion/defeasible product), and multi_hop value collection through persistent and partial contexts.
This commit is contained in:
@@ -489,6 +489,64 @@ describe('Rule-kind × partial-graph parity (rigor)', () => {
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user