rigor: six complex-graph crucibles + seed the TTU campaigns
Extends the complex-graph suite to the remaining uncovered surfaces:
- complex-graph-ttl-crucible: value-TTL expiry over community/scale-free
graphs (pinned changed_last_at writes, {now} reads, mirror freshness
rule), mutation-with-time binary parity, snapshot round-trip preserves
the TTL gate. Notable find: snapshot restore resets changed_last_at to
access time (RelationSnapshotAccess.js:91), so TTL assertions compare
each engine against its own effective write clock.
- complex-graph-overlay-crucible: partial-graph overlay over complex
graphs. Key discovery: pre-built PartialGraphContext must be passed as
partialGraphContext (partialGraph is a raw spec re-ingested at
ArbiterChecks.js:15); overlay rides the direct relations a policy
consumes (TTU-derived can_read ignores it, verified by probe).
- complex-graph-values-crucible: relational-comparator over value-carrying
relations with pinned clocks — comparator parity, value-mutation flips
the decision immediately, TTL expiry on the comparator denies.
- complex-graph-reachability-crucible: PLTC reachability vs ground-truth
BFS on scale-free/community graphs — verdict parity, fast-fail
soundness (no false positives), null-defer contract honored.
- complex-graph-batch-crucible: addRelationsBatch vs sequential build
parity, mutation parity across both, cache-freshness after mutation.
- complex-graph-quantization-crucible: 16-bit quantization band over real
possibility spreads, allow/deny agreement outside the band, snapshot-of-
snapshot semantic identity.
Also seeds the two unseeded campaigns in tuple-to-userset-rule.test.js
(flagged flake ~1/15 — nondeterministic runs on a deterministic engine).
Rigor 245/245, full suite 847/785/0.
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* rigor/complex-graph-quantization-crucible.test.js — condensed-snapshot
|
||||
* quantization parity over the community graph.
|
||||
*
|
||||
* The generator emits varied non-dyadic possibilities (0.3..1.0). The
|
||||
* query set is drawn from the graph's OWN edges (direct_access, member-fed
|
||||
* TTU can_read, delegate-fed chain can_delegate_read) so the sampled
|
||||
* queries are granted and actually carry quantization error — random
|
||||
* (user, resource) pairs are mostly denied (live == restored == 0) and
|
||||
* would make the band vacuous.
|
||||
*
|
||||
* QUANT-BAND — |live - restored| <= 2 * QUANT_STEP (the chain path
|
||||
* multiplies two quantized inputs, so twice the single
|
||||
* value's band).
|
||||
* DECISION — outside the quantization band of the threshold the
|
||||
* allow/deny decision must agree; inside it a flip is
|
||||
* allowed (the existing snapshot-quantization-parity rule).
|
||||
* RE-SERIALIZE— snapshot-of-snapshot (serialize the restored engine,
|
||||
* restore again) is semantically identical.
|
||||
*
|
||||
* Deterministic fixed loop over seeds — no campaign needed.
|
||||
*/
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { Arbiter } from '../../src/index.js';
|
||||
import { makeCommunityGraph } from './complex-graphs.js';
|
||||
|
||||
const QUANT_STEP = 0.5 / 65535;
|
||||
const TOL = QUANT_STEP * 2;
|
||||
const SAMPLES = 40;
|
||||
|
||||
function mulberry32(seed) {
|
||||
let a = seed >>> 0;
|
||||
return function () {
|
||||
a |= 0;
|
||||
a = (a + 0x6d2b79f5) | 0;
|
||||
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
||||
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
||||
};
|
||||
}
|
||||
|
||||
function grantedQuerySet(g) {
|
||||
const qs = [];
|
||||
const ownsBySrc = new Map();
|
||||
for (const r of g.relations) if (r.rel === 'owns') ownsBySrc.set(r.src, r.dst);
|
||||
for (const r of g.relations) {
|
||||
if (r.rel === 'direct_access') qs.push([r.src, 'direct_access', r.dst]);
|
||||
if (r.rel === 'member') {
|
||||
const obj = ownsBySrc.get(r.dst);
|
||||
if (obj) {
|
||||
qs.push([r.src, 'can_read', obj]);
|
||||
qs.push([r.src, 'can_read_not_blocked', obj]);
|
||||
}
|
||||
}
|
||||
if (r.rel === 'delegate') {
|
||||
for (const m of g.relations) {
|
||||
if (m.rel === 'member' && m.dst === r.src) {
|
||||
qs.push([m.src, 'can_delegate_read', r.dst]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return qs;
|
||||
}
|
||||
|
||||
function sample(querySet, seed) {
|
||||
const rng = mulberry32(seed * 997);
|
||||
const out = [];
|
||||
for (let i = 0; i < SAMPLES; i++) {
|
||||
out.push(querySet[Math.floor(rng() * querySet.length)]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
describe('Complex-graph snapshot quantization crucible', () => {
|
||||
it('QUANT-BAND + DECISION + RE-SERIALIZE hold across seeds', () => {
|
||||
for (const seed of [1, 2, 3, 4]) {
|
||||
const g = makeCommunityGraph(seed);
|
||||
const arbiter = g.arbiter;
|
||||
const queries = sample(grantedQuerySet(g), seed);
|
||||
assert.ok(queries.length > 0, `seed ${seed}: empty granted query set`);
|
||||
|
||||
const live = queries.map(([u, rel, o]) => arbiter.check(u, rel, o).possibility);
|
||||
|
||||
arbiter.enableCondensedSnapshot();
|
||||
const buf = arbiter.toSnapshotBinary();
|
||||
const restored = Arbiter.fromSnapshotBinary(buf);
|
||||
const restored2 = Arbiter.fromSnapshotBinary(restored.toSnapshotBinary());
|
||||
|
||||
for (let i = 0; i < queries.length; i++) {
|
||||
const [u, rel, o] = queries[i];
|
||||
const lv = live[i];
|
||||
const rv = restored.check(u, rel, o).possibility;
|
||||
const r2v = restored2.check(u, rel, o).possibility;
|
||||
|
||||
assert.ok(
|
||||
Math.abs(lv - rv) <= TOL,
|
||||
`seed ${seed} ${u} ${rel} ${o}: live=${lv} restored=${rv} exceeds ${TOL}`
|
||||
);
|
||||
|
||||
// Allow/deny agreement on the 0.5 threshold, except inside the
|
||||
// quantization band where a flip is permitted.
|
||||
const inBand = Math.abs(lv - 0.5) < QUANT_STEP;
|
||||
if (!inBand) {
|
||||
assert.equal(
|
||||
rv >= 0.5, lv >= 0.5,
|
||||
`seed ${seed} ${u} ${rel} ${o}: decision flipped outside band (live=${lv} restored=${rv})`
|
||||
);
|
||||
}
|
||||
// Grant parity (possibility > 0): granted queries all carry
|
||||
// possibility >= 0.3, so a flip here would be a real loss.
|
||||
assert.equal(rv > 0, lv > 0, `seed ${seed} ${u} ${rel} ${o}: grant lost in restore`);
|
||||
|
||||
// Snapshot-of-snapshot is semantically identical.
|
||||
assert.ok(
|
||||
Math.abs(r2v - rv) <= TOL,
|
||||
`seed ${seed} ${u} ${rel} ${o}: re-serialized=${r2v} != first restore=${rv}`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user