From 02d7a5dd759f6f0f6f5ad5450f59103e788421c1 Mon Sep 17 00:00:00 2001 From: John Dvorak Date: Fri, 31 Jul 2026 23:52:48 -0700 Subject: [PATCH] js-rigor: check() no longer strips reliability; TTU reliability pins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug 34: ArbiterChecks.check destructured { reliability, ...rest } out of every normal (and binary) check result since the initial commit — the engine computes reliability faithfully (TTU tupleset.reli * computed.reli, direct relation reliability) and explain() preserved it, but the public check() API silently dropped it. explain() kept it, so exposing it in check() is the intended contract. The destructuring is removed; direct and TTU checks now return reliability (0.6 / 0.72 in the pins). Campaign pins added: TTU multi-path fusion (max over intermediates of min(legs)) with reliability propagation through both persistent and partial contexts, the maxIntermediates circuit breaker through partial, and snapshot-restored arbiters evaluating TTU. --- src/core/arbiter/ArbiterChecks.js | 3 +- tests/rigor/rule-kind-partial-parity.test.js | 59 +++++++++++++++++++- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/core/arbiter/ArbiterChecks.js b/src/core/arbiter/ArbiterChecks.js index b8bad85..259b9f0 100644 --- a/src/core/arbiter/ArbiterChecks.js +++ b/src/core/arbiter/ArbiterChecks.js @@ -71,8 +71,7 @@ export class ArbiterChecks { } if (options.explain) return result; - const { reliability, ...rest } = result; - return rest; + return result; } explain(userKey, relation, objectKey, options = {}) { diff --git a/tests/rigor/rule-kind-partial-parity.test.js b/tests/rigor/rule-kind-partial-parity.test.js index 2b5538a..17983b1 100644 --- a/tests/rigor/rule-kind-partial-parity.test.js +++ b/tests/rigor/rule-kind-partial-parity.test.js @@ -38,7 +38,7 @@ function mkArbiter(opts = {}) { describe('Rule-kind × partial-graph parity (rigor)', () => { - it('FIXED MATRIX: every kind matches between persistent and partial', () => { + it('FIXED MATRIX: every kind matches between persistent and partial', async () => { // ---- direct ---- { const a = mkArbiter(); @@ -385,6 +385,63 @@ describe('Rule-kind × partial-graph parity (rigor)', () => { 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'); + } // ---- binary mode agrees with normal at the same threshold ---- { const a = mkArbiter();