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:
John Dvorak
2026-08-01 11:18:20 -07:00
parent 4fd4e20bd0
commit ff6e52111d
5 changed files with 229 additions and 38 deletions
@@ -48,7 +48,12 @@ function buildGraph(edges, values) {
]
});
for (let i = 0; i < edges.length; i++) {
arb.addRelation(nodeKey(edges[i][0]), edges[i][1], nodeKey(edges[i][2]), { possibility: values[i] });
arb.addRelation(nodeKey(edges[i][0]), edges[i][1], nodeKey(edges[i][2]), {
possibility: values[i],
// deterministic per-edge reliability so the round-trip pins the
// codec's reliability channel, not just possibility
reliability: 0.1 + ((i * 7 + 3) % 9) / 10
});
}
return arb;
}
@@ -64,12 +69,14 @@ for (const u of [0, 1]) {
function roundTripReport(edges, values) {
const live = buildGraph(edges, values);
const before = QUERIES.map(([rel, u, d]) => live.check(nodeKey(u), rel, nodeKey(d)).possibility);
const beforeReliability = QUERIES.map(([rel, u, d]) => live.check(nodeKey(u), rel, nodeKey(d)).reliability ?? 0);
live.enableCondensedSnapshot();
const buffer = serializeArbiterSnapshot(live);
const r1 = ArbiterSnapshot.fromSnapshotBinary(buffer, {}, () => new Arbiter());
const after1 = QUERIES.map(([rel, u, d]) => r1.check(nodeKey(u), rel, nodeKey(d)).possibility);
const after1Reliability = QUERIES.map(([rel, u, d]) => r1.check(nodeKey(u), rel, nodeKey(d)).reliability ?? 0);
const r1b = ArbiterSnapshot.fromSnapshotBinary(buffer, {}, () => new Arbiter());
const after1b = QUERIES.map(([rel, u, d]) => r1b.check(nodeKey(u), rel, nodeKey(d)).possibility);
@@ -78,7 +85,7 @@ function roundTripReport(edges, values) {
const r2 = ArbiterSnapshot.fromSnapshotBinary(buffer2, {}, () => new Arbiter());
const after2 = QUERIES.map(([rel, u, d]) => r2.check(nodeKey(u), rel, nodeKey(d)).possibility);
return { before, after1, after1b, after2, graphBytes: live.snapshotGraph.toBinary().byteLength };
return { before, after1, after1b, after2, beforeReliability, after1Reliability, graphBytes: live.snapshotGraph.toBinary().byteLength };
}
describe('Condensed snapshot quantization parity (rigor)', () => {
@@ -106,6 +113,16 @@ describe('Condensed snapshot quantization parity (rigor)', () => {
}
assert.deepEqual(r.after1, r.after1b, 'restoring the same buffer is deterministic');
assert.deepEqual(r.after1, r.after2, 'snapshot-of-snapshot preserves values');
// Reliability passes through the same 16-bit quantization; chain
// reliabilities multiply two quantized inputs, so the band is 4x the
// single-value tolerance (still far tighter than any codec loss).
const reliabilityTol = TOL * 4;
for (let i = 0; i < r.beforeReliability.length; i++) {
assert.ok(
Math.abs(r.after1Reliability[i] - r.beforeReliability[i]) <= reliabilityTol,
`query ${i}: live reliability=${r.beforeReliability[i]} restored=${r.after1Reliability[i]} exceeds tolerance ${reliabilityTol}`
);
}
const g1 = buildGraph(edges, values);
g1.enableCondensedSnapshot();