js-rigor: reliability crucibles across the campaigns; denied-decision leak fixed
The reliability gap found last round was invisible to every parity mirror (they compared possibility only). Hardened the existing campaigns so the mirrors carry reliability too: - batch-order-parity: batch ops carry reliability; the mirror tracks last-write-wins reliability and the crucible asserts engine reliability parity (mirror corrected: add-on-existing preserves reliability, it does not reset it). - rule-kind-partial-parity: the TTU differential property now generates per-edge reliabilities and asserts the winning intermediate's reliability (tupleset.reli * computed.reli); a new chain reliability differential property does the same for 2-step chains. - snapshot-quantization-parity: edges carry deterministic reliabilities and the round-trip pins the codec's reliability channel (product-aware tolerance: chain reliability multiplies two quantized inputs). - model-based-graph: the reference model tracks reliability per tuple and checks it alongside possibility for direct and chain queries. The model crucible immediately caught a real bug: the direct-check fast path returned the relation's reliability on a DENIED decision (possibility 0), while the rule-collection path zeroes it — denied results leaked reliability. Both fast-path branches (direct match and threshold_not_met) now report reliability 0 when the decision is denied.
This commit is contained in:
@@ -582,15 +582,15 @@ describe('Rule-kind × partial-graph parity (rigor)', () => {
|
||||
|
||||
const w = {
|
||||
engine,
|
||||
setEdge(rel, dst, p, side) {
|
||||
setEdge(rel, dst, p, reli, side) {
|
||||
const src = rel === 'owner' ? 'doc:0' : 'u:0';
|
||||
if (side === 'persistent') {
|
||||
engine.addRelation(src, rel, dst, { possibility: p });
|
||||
persistent.set(src + '|' + rel + '|' + dst, { p });
|
||||
engine.addRelation(src, rel, dst, { possibility: p, reliability: reli });
|
||||
persistent.set(src + '|' + rel + '|' + dst, { p, r: reli });
|
||||
} else {
|
||||
const idx = partialEdges.findIndex(e => e.relation === rel && e.dst === dst);
|
||||
if (idx >= 0) partialEdges.splice(idx, 1);
|
||||
partialEdges.push({ src, relation: rel, dst, possibility: p });
|
||||
partialEdges.push({ src, relation: rel, dst, possibility: p, reliability: reli });
|
||||
}
|
||||
return { ok: true };
|
||||
},
|
||||
@@ -609,25 +609,37 @@ describe('Rule-kind × partial-graph parity (rigor)', () => {
|
||||
: {};
|
||||
const r = engine.check('u:0', 'can_read', 'doc:0', options);
|
||||
// TTU mirror: join both legs per intermediate — max over mids of
|
||||
// min(tupleset.possibility, computed.possibility). Edges come from
|
||||
// persistent and/or partial; on same-tuple conflicts the overlay
|
||||
// contract is persistent-wins (persistent outranks partial trust).
|
||||
const tupleset = new Map(); // dst -> p
|
||||
const computed = new Map(); // dst -> p
|
||||
// min(tupleset.possibility, computed.possibility), with the winning
|
||||
// intermediate's reliability = tupleset.reli * computed.reli. Edges
|
||||
// come from persistent and/or partial; on same-tuple conflicts the
|
||||
// overlay contract is persistent-wins (persistent outranks partial).
|
||||
const tupleset = new Map(); // dst -> { p, r }
|
||||
const computed = new Map(); // dst -> { p, r }
|
||||
for (const [key, v] of persistent) {
|
||||
const [src, rel, dst] = key.split('|');
|
||||
(rel === 'owner' ? tupleset : computed).set(dst, v.p);
|
||||
(rel === 'owner' ? tupleset : computed).set(dst, v);
|
||||
}
|
||||
for (const e of partialEdges) {
|
||||
const merged = e.relation === 'owner' ? tupleset : computed;
|
||||
if (!merged.has(e.dst)) merged.set(e.dst, e.possibility);
|
||||
if (!merged.has(e.dst)) merged.set(e.dst, { p: e.possibility, r: e.reliability ?? 1.0 });
|
||||
}
|
||||
let best = 0;
|
||||
let bestReliability = 0;
|
||||
for (const [mid, tp] of tupleset) {
|
||||
const cp = computed.get(mid);
|
||||
if (cp !== undefined && Math.min(tp, cp) > best) best = Math.min(tp, cp);
|
||||
if (cp === undefined) continue;
|
||||
const combinedP = Math.min(tp.p, cp.p);
|
||||
if (combinedP > best) {
|
||||
best = combinedP;
|
||||
bestReliability = (tp.r ?? 1.0) * (cp.r ?? 1.0);
|
||||
}
|
||||
}
|
||||
return { engine: round4(r.possibility), expected: round4(best) };
|
||||
return {
|
||||
engine: round4(r.possibility),
|
||||
expected: round4(best),
|
||||
engineReliability: round4(r.reliability ?? 0),
|
||||
expectedReliability: round4(bestReliability)
|
||||
};
|
||||
},
|
||||
clone() { return w; }
|
||||
};
|
||||
@@ -636,11 +648,12 @@ describe('Rule-kind × partial-graph parity (rigor)', () => {
|
||||
|
||||
const result = await rigor.campaign(
|
||||
[rigor.object('graph', makeWrapper, [
|
||||
rigor.method('setEdge', function (rel, dst, p, side) { return this.setEdge(rel, dst, p, side); },
|
||||
rigor.method('setEdge', function (rel, dst, p, reli, side) { return this.setEdge(rel, dst, p, reli, side); },
|
||||
rigor.args(
|
||||
rigor.gen.oneOf(['owner', 'member_of']),
|
||||
rigor.gen.oneOf(['g:0', 'g:1', 'g:2']),
|
||||
rigor.gen.float(0.1, 1.0),
|
||||
rigor.gen.oneOf([0.3, 0.6, 0.9]),
|
||||
rigor.gen.oneOf(['persistent', 'partial'])
|
||||
)),
|
||||
rigor.method('clearAll', function () { return this.clearAll(); }),
|
||||
@@ -651,6 +664,11 @@ describe('Rule-kind × partial-graph parity (rigor)', () => {
|
||||
if (ctx.action !== 'graph.check' || ctx.error !== null) return true;
|
||||
return ctx.actual.engine === ctx.actual.expected;
|
||||
}),
|
||||
rigor.invariant('TTU reliability parity', (ctx) => {
|
||||
if (ctx.action !== 'graph.check' || ctx.error !== null) return true;
|
||||
if (ctx.actual.expected === 0) return true; // denied: reliability 0
|
||||
return ctx.actual.engineReliability === ctx.actual.expectedReliability;
|
||||
}),
|
||||
rigor.invariant('no action errors', (ctx) => ctx.error === null)
|
||||
])
|
||||
).run({ effort: 400, seed: "ttu-partial-split-2026", maxTraceLength: 25, artifacts: { dir: "", persist: "never" } });
|
||||
@@ -720,6 +738,126 @@ describe('Rule-kind × partial-graph parity (rigor)', () => {
|
||||
].join('\n'));
|
||||
}, 90000);
|
||||
|
||||
it('PROPERTY CAMPAIGN: chain reliability differential under random edge splits', async () => {
|
||||
function makeWrapper() {
|
||||
const engine = new Arbiter();
|
||||
engine.addNode('u:0', 'user');
|
||||
engine.addNode('doc:0', 'doc');
|
||||
engine.addNode('g:0', 'group');
|
||||
engine.addNode('g:1', 'group');
|
||||
engine.setRelationConfig('can_access', { type: 'chain', steps: [
|
||||
{ relation: 'member_of', direction: 'out' },
|
||||
{ relation: 'reads', direction: 'out' }
|
||||
] });
|
||||
|
||||
const persistent = new Map();
|
||||
const partialEdges = [];
|
||||
|
||||
const w = {
|
||||
engine,
|
||||
setEdge(rel, dst, p, reli, side) {
|
||||
const src = rel === 'member_of' ? 'u:0' : 'g:0';
|
||||
if (rel === 'reads') dst = 'doc:0'; // the chain's second leg must point at the target
|
||||
if (side === 'persistent') {
|
||||
engine.addRelation(src, rel, dst, { possibility: p, reliability: reli });
|
||||
persistent.set(src + '|' + rel + '|' + dst, { p, r: reli });
|
||||
} else {
|
||||
const idx = partialEdges.findIndex(e => e.relation === rel && e.dst === dst);
|
||||
if (idx >= 0) partialEdges.splice(idx, 1);
|
||||
partialEdges.push({ src, relation: rel, dst, possibility: p, reliability: reli });
|
||||
}
|
||||
return { ok: true };
|
||||
},
|
||||
clearAll() {
|
||||
for (const key of [...persistent.keys()]) {
|
||||
const [src, rel, dst] = key.split('|');
|
||||
engine.removeRelation(src, rel, dst);
|
||||
}
|
||||
persistent.clear();
|
||||
partialEdges.length = 0;
|
||||
return { ok: true };
|
||||
},
|
||||
check() {
|
||||
const options = partialEdges.length > 0
|
||||
? { partialGraph: { relations: partialEdges.slice() } }
|
||||
: {};
|
||||
const r = engine.check('u:0', 'can_access', 'doc:0', options);
|
||||
// Mirror: chain = max over intermediates of min(legs); the winning
|
||||
// path's reliability is the product of its edges' reliabilities.
|
||||
// Same-tuple conflicts: persistent wins.
|
||||
const memberOf = new Map(); // dst -> { p, r }
|
||||
const reads = new Map(); // src -> { p, r } (edge src ->reads-> doc:0)
|
||||
for (const [key, v] of persistent) {
|
||||
const [src, rel, dst] = key.split('|');
|
||||
if (rel === 'member_of') memberOf.set(dst, v);
|
||||
else reads.set(src, v);
|
||||
}
|
||||
for (const e of partialEdges) {
|
||||
if (e.relation === 'member_of') {
|
||||
if (!memberOf.has(e.dst)) memberOf.set(e.dst, { p: e.possibility, r: e.reliability ?? 1.0 });
|
||||
} else {
|
||||
if (!reads.has(e.src)) reads.set(e.src, { p: e.possibility, r: e.reliability ?? 1.0 });
|
||||
}
|
||||
}
|
||||
let best = 0;
|
||||
let bestReliability = 0;
|
||||
for (const [mid, m] of memberOf) {
|
||||
const rd = reads.get(mid);
|
||||
if (rd === undefined) continue;
|
||||
const combinedP = Math.min(m.p, rd.p);
|
||||
if (combinedP > best) {
|
||||
best = combinedP;
|
||||
bestReliability = (m.r ?? 1.0) * (rd.r ?? 1.0);
|
||||
}
|
||||
}
|
||||
return {
|
||||
engine: round4(r.possibility),
|
||||
expected: round4(best),
|
||||
engineReliability: round4(r.reliability ?? 0),
|
||||
expectedReliability: round4(bestReliability)
|
||||
};
|
||||
},
|
||||
clone() { return w; }
|
||||
};
|
||||
return w;
|
||||
}
|
||||
|
||||
const result = await rigor.campaign(
|
||||
[rigor.object('graph', makeWrapper, [
|
||||
rigor.method('setEdge', function (rel, dst, p, reli, side) { return this.setEdge(rel, dst, p, reli, side); },
|
||||
rigor.args(
|
||||
rigor.gen.oneOf(['member_of', 'reads']),
|
||||
rigor.gen.oneOf(['g:0', 'g:1', 'doc:0']),
|
||||
rigor.gen.float(0.1, 1.0),
|
||||
rigor.gen.oneOf([0.3, 0.6, 0.9]),
|
||||
rigor.gen.oneOf(['persistent', 'partial'])
|
||||
)),
|
||||
rigor.method('clearAll', function () { return this.clearAll(); }),
|
||||
rigor.method('check', function () { return this.check(); })
|
||||
])],
|
||||
rigor.crucible([
|
||||
rigor.invariant('chain decision parity', (ctx) => {
|
||||
if (ctx.action !== 'graph.check' || ctx.error !== null) return true;
|
||||
return ctx.actual.engine === ctx.actual.expected;
|
||||
}),
|
||||
rigor.invariant('chain reliability parity', (ctx) => {
|
||||
if (ctx.action !== 'graph.check' || ctx.error !== null) return true;
|
||||
if (ctx.actual.expected === 0) return true; // denied: reliability 0
|
||||
return ctx.actual.engineReliability === ctx.actual.expectedReliability;
|
||||
}),
|
||||
rigor.invariant('no action errors', (ctx) => ctx.error === null)
|
||||
])
|
||||
).run({ effort: 400, seed: "chain-reliability-2026", maxTraceLength: 25, artifacts: { dir: "", persist: "never" } });
|
||||
|
||||
const inv = result.crucibleVerdict;
|
||||
assert.equal(inv.passed, true, [
|
||||
`chain reliability violated in ${inv.failureCount} cases:`,
|
||||
...result.failures.slice(0, 3).map((f) =>
|
||||
` [${f.name}] action=${f.actionName} seq=${JSON.stringify((f.sequence || []).map(s => s.args).filter(a => a && a.length))} error=${f.error}`
|
||||
)
|
||||
].join('\n'));
|
||||
}, 90000);
|
||||
|
||||
it('PROPERTY CAMPAIGN: exclusion differential under random split edges', async () => {
|
||||
function makeWrapper() {
|
||||
const engine = new Arbiter();
|
||||
|
||||
Reference in New Issue
Block a user