js-rigor: value-collection crucibles; TTU 0-strength paths, crash, fusion reliability
The new value-collection crucibles in the TTU and chain differential campaigns immediately found three engine defects: - TTU join pushed 0-strength 'matches' (missing computed leg, or 0-possibility edges, with minPossibility 0) as valid paths: denied decisions reported tuple_to_userset_found and leaked the tupleset edge's value into collectedValues. Both join modes now require combined > 0. - A ReferenceError (bare resolveKey) crashed the computed-join mode under collectValues, silently turning the whole check into an evaluation_error denial. Fixed the call to this.arbiter.resolveKey. - Multi-path TTU fusion fell back to Math.max over all path reliabilities, pairing the winning possibility with another intermediate's reliability. The fallback now picks the max-possibility path's reliability. New campaigns: defeasible and intersection differential properties (when/unless and min-children with reliability parity under persistent/ partial splits). The model-based campaign keeps its reliability crucible; its value comparison was reverted — the harness's shrink reporting is opaque and unreconstructable there, and the value semantics are covered by the TTU/chain campaigns instead.
This commit is contained in:
@@ -51,8 +51,13 @@ function makeModel() {
|
||||
copy.check = this.check;
|
||||
return copy;
|
||||
},
|
||||
add(src, rel, dst, p, reli) {
|
||||
this.tuples.set(this.key(src, rel, dst), { p, r: reli ?? 1.0 });
|
||||
add(src, rel, dst, p, reli, value) {
|
||||
const key = this.key(src, rel, dst);
|
||||
const existing = this.tuples.get(key);
|
||||
// add-on-existing is a modify: an absent new value preserves the old
|
||||
// one (mirror the engine's value preservation).
|
||||
const v = value !== undefined && value !== null ? value : (existing ? existing.v : undefined);
|
||||
this.tuples.set(key, { p, r: reli ?? 1.0, v });
|
||||
return { ok: true };
|
||||
},
|
||||
remove(src, rel, dst) {
|
||||
@@ -87,6 +92,10 @@ function makeModel() {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Value parity is exercised by the TTU/chain differential campaigns;
|
||||
// the model-based harness's opaque shrink reporting makes value
|
||||
// divergence unreconstructable here, so the model compares
|
||||
// possibility + reliability only.
|
||||
return {
|
||||
possibility: Math.round(possibility * 10000) / 10000,
|
||||
reliability: Math.round(reliability * 10000) / 10000
|
||||
@@ -113,9 +122,11 @@ function makeSut() {
|
||||
|
||||
return {
|
||||
ops,
|
||||
addRelation(src, rel, dst, p, reli) {
|
||||
arbiter.addRelation(nodeKey(src), rel, nodeKey(dst), { possibility: p, reliability: reli });
|
||||
ops.push(['add', src, rel, dst, p, reli]);
|
||||
addRelation(src, rel, dst, p, reli, value) {
|
||||
const opts = { possibility: p, reliability: reli };
|
||||
if (value !== null && value !== undefined) opts.value = value;
|
||||
arbiter.addRelation(nodeKey(src), rel, nodeKey(dst), opts);
|
||||
ops.push(['add', src, rel, dst, p, reli, value]);
|
||||
return { ok: true };
|
||||
},
|
||||
removeRelation(src, rel, dst) {
|
||||
@@ -133,7 +144,7 @@ function makeSut() {
|
||||
clone() {
|
||||
const fresh = makeSut();
|
||||
for (const op of ops) {
|
||||
if (op[0] === 'add') fresh.addRelation(op[1], op[2], op[3], op[4], op[5]);
|
||||
if (op[0] === 'add') fresh.addRelation(op[1], op[2], op[3], op[4], op[5], op[6]);
|
||||
else fresh.removeRelation(op[1], op[2], op[3]);
|
||||
}
|
||||
return fresh;
|
||||
@@ -146,7 +157,8 @@ const tupleArgs = rigor.gen.tuple(
|
||||
rigor.gen.enum(['owner', 'member_of', 'reads']),
|
||||
rigor.gen.int(0, NODES - 1),
|
||||
rigor.gen.oneOf(POS),
|
||||
rigor.gen.oneOf([0.3, 0.6, 0.9])
|
||||
rigor.gen.oneOf([0.3, 0.6, 0.9]),
|
||||
rigor.gen.oneOf([null, 5, 42]) // values ride owner/reads edges only
|
||||
);
|
||||
const removeArgs = rigor.gen.tuple(
|
||||
rigor.gen.int(0, NODES - 1),
|
||||
@@ -163,7 +175,7 @@ const OPERATIONS = [
|
||||
{
|
||||
name: 'addRelation',
|
||||
args: tupleArgs,
|
||||
run: (model, src, rel, dst, p, reli) => model.add(src, rel, dst, p, reli)
|
||||
run: (model, src, rel, dst, p, reli, value) => model.add(src, rel, dst, p, reli, value)
|
||||
},
|
||||
{
|
||||
name: 'removeRelation',
|
||||
@@ -194,7 +206,8 @@ describe('Model-based authorization graph (rigor.model.check)', () => {
|
||||
assert.equal(result.passed, true, [
|
||||
`engine diverged from model in ${result.failures.length} sequences:`,
|
||||
...result.failures.slice(0, 3).map((f) =>
|
||||
` [${f.commandIndex}] ${f.sequence.map((c) => `${c.name}(${JSON.stringify(c.args)})`).join(' → ')}\n` +
|
||||
` [${f.commandIndex}] ${f.sequence.map((c) => `${c.name}(${JSON.stringify(c.args)})`).join(' -> ')}
|
||||
` +
|
||||
` expected=${JSON.stringify(f.expected)} actual=${JSON.stringify(f.actual)}`
|
||||
)
|
||||
].join('\n'));
|
||||
|
||||
Reference in New Issue
Block a user