Files

2018 lines
99 KiB
JavaScript
Raw Permalink Normal View History

/**
* @arbiter/value-graph — js-rigor deep tests.
*
* Covers the same behavior as value-graph.test.js but through js-rigor's
* property / handler / model campaigns:
* - `owa` + `weightFor` (OWA, ADR-000) — pure-function properties + oracles
* - ValueGraph caching / TTL / invalidation / set / blackbox — independent
* property cases (fresh graph per call, synchronous facade)
* - the CALLBACK resolver contract — handler-based tests (sync return,
* async resolver, error, promise rejection, query duplex pull, stale push)
* - the store contract (map + disk-backed) — model-based conformance
*
* Resolver contract reminder: resolvers call cb(err, result) asynchronously or
* return synchronously; a returned Promise is rejected by the graph. The
* graph itself is promise-free; the test harness adapts the callback API.
*/
import { describe, it } from 'node:test';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { rigor, reducers } from '@rigor/core';
import { ValueGraph, createMapStore, createFileStore, owa, weightFor, encodeSnapshot, decodeSnapshot } from '../src/index.js';
function syncGet(vg, subject, rel, params = {}) {
let out = undefined;
let errOut = null;
vg.get(subject, rel, params, (err, result) => { errOut = err; out = result; });
if (errOut) throw errOut;
return out;
}
async function expectPass(name, actions, ...args) {
// Accepts MULTIPLE crucible arrays (they are collected into one campaign) plus
// an optional trailing config object: expectPass(name, actions, checksA, checksB, { effort }).
const checkArrays = [];
let config = {};
for (const a of args) {
if (Array.isArray(a)) checkArrays.push(a);
else if (a && typeof a === 'object') config = { ...config, ...a };
}
const report = await rigor.campaign(actions, rigor.crucible(...checkArrays))
.run({ effort: 300, seed: `vg-rigor-${name}`, ...config });
if (report.status !== 'passed') {
const detail = (report.failures || []).slice(0, 5).map((f) =>
JSON.stringify({ action: f.actionName || f.action, inv: f.name, args: f.args, msg: f.message, err: f.error && f.error.message }));
throw new Error(`rigor campaign '${name}' failed (${report.failures.length} failures).\n${report.toTAP()}\n${detail.join('\n')}`);
}
return report;
}
// ─────────────────────────────────────────────────────────────────────────────
// OWA + weightFor — pure-function properties
// ─────────────────────────────────────────────────────────────────────────────
const intArr = rigor.gen.array(rigor.gen.int(-50, 50), 0, 12);
const twoInts = rigor.gen.array(rigor.gen.int(-20, 20), 2, 2);
const strArr = rigor.gen.array(rigor.gen.oneOf(['low', 'med', 'high']), 0, 8);
const OWA_ACTIONS = [
rigor.fn('owa_sum', (values) => owa(values, 'sum', {}), rigor.args(intArr)),
rigor.fn('owa_avg', (values) => owa(values, 'average', {}), rigor.args(intArr)),
rigor.fn('owa_max', (values) => owa(values, 'max', {}), rigor.args(intArr)),
rigor.fn('owa_min', (values) => owa(values, 'min', {}), rigor.args(intArr)),
rigor.fn('owa_median', (values) => owa(values, 'median', {}), rigor.args(intArr)),
rigor.fn('owa_top2', (values) => owa(values, 'top2', {}), rigor.args(intArr)),
rigor.fn('owa_product', (values) => owa(values, 'product', {}), rigor.args(intArr)),
rigor.fn('owa_optimistic', (values) => owa(values, 'optimistic', {}), rigor.args(intArr)),
rigor.fn('owa_pessimistic', (values) => owa(values, 'pessimistic', {}), rigor.args(intArr)),
rigor.fn('owa_custom', (values) => owa(values, 'custom', { weights: [0.75, 0.25] }), rigor.args(twoInts)),
rigor.fn('owa_majority_str', (values) => owa(values, 'majority', {}), rigor.args(strArr)),
rigor.fn('owa_max_str', (values) => owa(values, 'max', {}), rigor.args(strArr)),
];
const OWA_CHECKS = [
rigor.after('owa_sum', (ctx) => ctx.actual === ctx.args[0].reduce((a, b) => a + b, 0)),
rigor.after('owa_avg', (ctx) => {
const v = ctx.args[0];
if (v.length === 0) return ctx.actual === 0;
return Math.abs(ctx.actual - v.reduce((a, b) => a + b, 0) / v.length) < 1e-9;
}),
rigor.after('owa_max', (ctx) => {
const v = ctx.args[0];
if (v.length === 0) return ctx.actual === 0;
return ctx.actual === Math.max(...v);
}),
rigor.after('owa_min', (ctx) => {
const v = ctx.args[0];
if (v.length === 0) return ctx.actual === 0;
return ctx.actual === Math.min(...v);
}),
rigor.after('owa_median', (ctx) => {
const v = ctx.args[0];
if (v.length === 0) return ctx.actual === 0;
const s = [...v].sort((a, b) => b - a);
return ctx.actual === s[Math.floor((s.length - 1) / 2)];
}),
rigor.after('owa_top2', (ctx) => {
const v = ctx.args[0];
if (v.length === 0) return ctx.actual === 0;
const s = [...v].sort((a, b) => b - a);
if (s.length === 1) return ctx.actual === s[0];
return Math.abs(ctx.actual - (s[0] + s[1]) / 2) < 1e-9;
}),
rigor.after('owa_product', (ctx) => {
const v = ctx.args[0];
if (v.length === 0) return ctx.actual === 0;
return ctx.actual === v.reduce((a, b) => a * b, 1);
}),
rigor.after('owa_optimistic', (ctx) => {
const v = ctx.args[0];
if (v.length === 0) return ctx.actual === 0;
return ctx.actual >= Math.min(...v) && ctx.actual <= Math.max(...v);
}),
rigor.after('owa_pessimistic', (ctx) => {
const v = ctx.args[0];
if (v.length === 0) return ctx.actual === 0;
return ctx.actual >= Math.min(...v) && ctx.actual <= Math.max(...v);
}),
rigor.after('owa_custom', (ctx) => {
const v = ctx.args[0];
const s = [...v].sort((a, b) => b - a);
return Math.abs(ctx.actual - (0.75 * s[0] + 0.25 * s[1])) < 1e-9;
}),
rigor.after('owa_majority_str', (ctx) => {
const v = ctx.args[0];
if (v.length === 0) return ctx.actual === 0;
return v.includes(ctx.actual);
}),
rigor.after('owa_max_str', (ctx) => {
const v = ctx.args[0];
if (v.length === 0) return ctx.actual === 0;
return v.includes(ctx.actual);
}),
];
describe('OWA math (ADR-000) — properties, weightFor, oracles, universal, algebraic', () => {
it('every OWA invariant in ONE campaign (many crucibles)', async () => {
const opGen = rigor.gen.oneOf(['max', 'min', 'average', 'sum', 'sum_unbounded', 'majority', 'median', 'optimistic', 'pessimistic', 'top2', 'top3']);
const weights = rigor.fn('weights', (op, n) => weightFor(op, n), rigor.args(opGen, rigor.gen.int(0, 12)));
const weightChecks = [
rigor.invariant('length = n', (ctx) => ctx.actual.length === ctx.args[1]),
rigor.invariant('sum/sum_unbounded all ones; others sum to 1', (ctx) => {
const op = ctx.args[0];
const w = ctx.actual;
if (op === 'sum' || op === 'sum_unbounded') return w.every((x) => x === 1);
if (w.length === 0) return true;
return Math.abs(w.reduce((a, b) => a + b, 0) - 1) < 1e-9;
}),
rigor.invariant('max puts all weight on head', (ctx) =>
ctx.args[0] !== 'max' || ctx.args[1] === 0 || ctx.actual[0] === 1),
rigor.invariant('min puts all weight on tail', (ctx) =>
ctx.args[0] !== 'min' || ctx.args[1] === 0 || ctx.actual[ctx.args[1] - 1] === 1),
rigor.invariant('median puts all weight on the middle', (ctx) =>
ctx.args[0] !== 'median' || ctx.args[1] === 0 || ctx.actual[Math.floor((ctx.args[1] - 1) / 2)] === 1),
rigor.invariant('optimistic is monotonically non-increasing', (ctx) => {
if (ctx.args[0] !== 'optimistic' || ctx.args[1] < 2) return true;
for (let i = 1; i < ctx.actual.length; i++) if (ctx.actual[i] > ctx.actual[i - 1]) return false;
return true;
}),
rigor.invariant('top2 splits 50/50', (ctx) => {
if (ctx.args[0] !== 'top2' || ctx.args[1] === 0) return true;
const w = ctx.actual;
if (w.length === 1) return w[0] === 1;
return Math.abs(w[0] - 0.5) < 1e-9 && Math.abs(w[1] - 0.5) < 1e-9;
}),
rigor.invariant('majority spreads over the top 60%', (ctx) => {
if (ctx.args[0] !== 'majority' || ctx.args[1] === 0) return true;
const w = ctx.actual;
const top = Math.ceil(ctx.args[1] * 0.6);
return w.slice(0, top).every((x) => Math.abs(x - 1 / top) < 1e-9) && w.slice(top).every((x) => x === 0);
}),
];
await expectPass('owa-math',
[
...OWA_ACTIONS,
weights,
rigor.fn('owa_sum_oracle', (values) => owa(values, 'sum', {}), rigor.args(intArr)),
...OWA_ORACLE_ACTIONS,
UNIVERSAL_ACTIONS[0], UNIVERSAL_ACTIONS[1],
ALGEBRAIC_ACTIONS[0], ALGEBRAIC_ACTIONS[1],
],
OWA_CHECKS,
weightChecks,
[rigor.oracle('owa_sum_oracle', (values) => values.reduce((a, b) => a + b, 0))],
OWA_ORACLE_CHECKS,
[UNIVERSAL_CHECKS[0], UNIVERSAL_CHECKS[1]],
[ALGEBRAIC_CHECKS[0], ALGEBRAIC_CHECKS[1]],
{ effort: 1200 });
});
});
// ─────────────────────────────────────────────────────────────────────────────
// ValueGraph — caching / TTL / invalidation / set / blackbox (fresh graph per case)
// ─────────────────────────────────────────────────────────────────────────────
const GRAPH_ACTIONS = [
rigor.fn('get_defined', (subject, value) => {
const vg = new ValueGraph();
vg.compute('v', () => value);
const entry = syncGet(vg, subject, 'v', {});
return entry ? entry.value : 'NO_ENTRY';
}, rigor.args(rigor.gen.string(), rigor.gen.int(-100, 100))),
rigor.fn('get_undefined', (subject) => {
const vg = new ValueGraph();
return syncGet(vg, subject, 'nope', {});
}, rigor.args(rigor.gen.string())),
rigor.fn('cache_once', (subject, value) => {
let calls = 0;
const vg = new ValueGraph();
vg.compute('v', () => { calls++; return value; });
const a = syncGet(vg, subject, 'v', {}).value;
const b = syncGet(vg, subject, 'v', {}).value;
return { a, b, calls };
}, rigor.args(rigor.gen.string(), rigor.gen.int(0, 100))),
rigor.fn('invalidate_recompute', (subject, a, b) => {
let base = a;
const vg = new ValueGraph();
vg.compute('base', () => base);
vg.compute('double', (s, p, { deps }) => deps.base * 2, { dependsOn: ['base'] });
const first = syncGet(vg, subject, 'double', {}).value;
base = b;
vg.invalidate(subject, 'base', {});
const second = syncGet(vg, subject, 'double', {}).value;
return { first, second };
}, rigor.args(rigor.gen.string(), rigor.gen.int(1, 20), rigor.gen.int(1, 20))),
rigor.fn('set_overrides', (subject, value) => {
const vg = new ValueGraph();
vg.compute('v', () => 5);
vg.set(subject, 'v', {}, { value });
return syncGet(vg, subject, 'v', {}).value;
}, rigor.args(rigor.gen.string(), rigor.gen.int(-50, 50))),
rigor.fn('plan_prunes_cached', (subject, value) => {
const vg = new ValueGraph();
vg.compute('v', () => value);
syncGet(vg, subject, 'v', {});
return vg.plan(subject, 'v', {}).size;
}, rigor.args(rigor.gen.string(), rigor.gen.int(0, 100))),
rigor.fn('ttl_expiry', (subject, value) => {
let t = 0;
const vg = new ValueGraph({ clock: () => t, defaultTTL: 100 });
vg.compute('v', () => value);
syncGet(vg, subject, 'v', {});
const before = vg.plan(subject, 'v', {}).nodes.get('v').trivial;
t = 200;
const after = vg.plan(subject, 'v', {}).nodes.get('v').trivial;
return { before, after };
}, rigor.args(rigor.gen.string(), rigor.gen.int(0, 100))),
rigor.fn('blackbox_over_edges', (subject, a, b, c) => {
const vg = new ValueGraph();
const edges = [a, b, c];
vg.define('related', { operator: 'pattern', pattern: { relation: 'r' }, fn: (s, p, ctx, cb) => cb(null, edges) });
vg.define('total', { operator: 'blackbox', parents: ['related'], fn: (s, p, { deps }) => deps.related.reduce((x, y) => x + y, 0) });
return syncGet(vg, subject, 'total', {}).value;
}, rigor.args(rigor.gen.string(), rigor.gen.int(0, 10), rigor.gen.int(0, 10), rigor.gen.int(0, 10))),
rigor.fn('blackbox_entries_full', (subject, value) => {
const vg = new ValueGraph();
vg.define('base', { operator: 'source', fn: (s, p, ctx, cb) => cb(null, { value, unit: 'usd_cents' }) });
let seen = null;
vg.define('total', {
operator: 'blackbox',
parents: ['base'],
fn: (s, p, ctx, cb) => { seen = { value: ctx.deps.base, unit: ctx.entries.base.unit, fresh: ctx.entries.base.fresh }; cb(null, ctx.deps.base); }
});
syncGet(vg, subject, 'total', {});
return seen;
}, rigor.args(rigor.gen.string(), rigor.gen.int(0, 100))),
];
const GRAPH_CHECKS = [
rigor.after('get_defined', (ctx) => ctx.actual === ctx.args[1]),
rigor.after('get_undefined', (ctx) => ctx.actual === null),
rigor.after('cache_once', (ctx) => ctx.actual.a === ctx.args[1] && ctx.actual.b === ctx.args[1] && ctx.actual.calls === 1),
rigor.after('invalidate_recompute', (ctx) =>
ctx.actual.first === ctx.args[1] * 2 && ctx.actual.second === ctx.args[2] * 2 &&
(ctx.args[1] === ctx.args[2] || ctx.actual.first !== ctx.actual.second)),
rigor.after('set_overrides', (ctx) => ctx.actual === ctx.args[1]),
rigor.after('plan_prunes_cached', (ctx) => ctx.actual === 1),
rigor.after('ttl_expiry', (ctx) => ctx.actual.before === true && ctx.actual.after === false),
rigor.after('blackbox_over_edges', (ctx) => ctx.actual === ctx.args[1] + ctx.args[2] + ctx.args[3]),
rigor.after('blackbox_entries_full', (ctx) => ctx.actual.value === ctx.args[1] && ctx.actual.unit === 'usd_cents' && ctx.actual.fresh === true),
];
// ─────────────────────────────────────────────────────────────────────────────
// Callback resolver contract — handler-based
// ─────────────────────────────────────────────────────────────────────────────
const CB_ACTIONS = [
rigor.fn('cb_get_sync', (subject, value, cb) => {
const vg = new ValueGraph();
vg.compute('v', () => value);
vg.get(subject, 'v', {}, cb);
}, rigor.args(rigor.gen.string(), rigor.gen.int(0, 100), rigor.handler(reducers.first()))),
rigor.fn('cb_get_async', (subject, value, cb) => {
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, done) => { setTimeout(() => done(null, value), 1); });
vg.get(subject, 'v', {}, cb);
}, rigor.args(rigor.gen.string(), rigor.gen.int(0, 100), rigor.handler(reducers.first()))),
rigor.fn('cb_error', (subject, cb) => {
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, done) => { done(new Error('boom')); });
vg.get(subject, 'v', {}, cb);
}, rigor.args(rigor.gen.string(), rigor.handler(reducers.first()))),
rigor.fn('cb_reject_promise', (subject, cb) => {
const vg = new ValueGraph();
vg.compute('v', async () => 1);
vg.get(subject, 'v', {}, cb);
}, rigor.args(rigor.gen.string(), rigor.handler(reducers.first()))),
rigor.fn('cb_query_pull', (subject, value, cb) => {
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, done) => done(null, value));
const q = vg.query(subject, 'v', {});
q.source.pipe(cb);
q.sink.write({ get: true });
q.sink.write({ get: true });
q.sink.end();
}, rigor.args(rigor.gen.string(), rigor.gen.int(0, 100), rigor.handler(reducers.array()))),
rigor.fn('cb_query_stale', (subject, value, cb) => {
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, done) => done(null, value));
const q = vg.query(subject, 'v', {});
q.source.pipe(cb);
q.sink.write({ get: true });
vg.invalidate(subject, 'v', {});
q.sink.write({ get: true });
q.sink.end();
}, rigor.args(rigor.gen.string(), rigor.gen.int(0, 100), rigor.handler(reducers.array()))),
];
const CB_CHECKS = [
rigor.after('cb_get_sync', (ctx) => ctx.error == null && ctx.actual && ctx.actual.value === ctx.args[1]),
rigor.after('cb_get_async', (ctx) => ctx.error == null && ctx.actual && ctx.actual.value === ctx.args[1]),
rigor.after('cb_error', (ctx) => ctx.error != null && /boom/.test(ctx.error.message)),
rigor.after('cb_reject_promise', (ctx) => ctx.error != null && /Promise/.test(ctx.error.message)),
rigor.after('cb_query_pull', (ctx) =>
Array.isArray(ctx.actual) && ctx.actual.length === 2 &&
ctx.actual[0].value === ctx.args[1] && ctx.actual[1].value === ctx.args[1]),
rigor.after('cb_query_stale', (ctx) =>
Array.isArray(ctx.actual) && ctx.actual.length === 3 &&
ctx.actual[0].value === ctx.args[1] &&
ctx.actual[1] && ctx.actual[1].stale === true &&
ctx.actual[2].value === ctx.args[1]),
];
// ─────────────────────────────────────────────────────────────────────────────
// Store contract — model-based conformance (map + disk-backed)
// ─────────────────────────────────────────────────────────────────────────────
const STORE_OPS = [
{ name: 'set', args: rigor.gen.tuple(rigor.gen.string(), rigor.gen.int(0, 1000)), run: (m, k, v) => { m.map.set(k, v); return v; } },
{ name: 'get', args: rigor.gen.string(), run: (m, k) => (m.map.has(k) ? m.map.get(k) : null) },
{ name: 'delete', args: rigor.gen.string(), run: (m, k) => m.map.delete(k) },
{ name: 'clear', args: rigor.gen.constant(null), run: (m) => { m.map.clear(); return 'ok'; } },
{ name: 'deletePrefix', args: rigor.gen.string(), run: (m, p) => {
let n = 0;
for (const k of Array.from(m.map.keys())) if (k.includes(p)) { m.map.delete(k); n++; }
return n;
} },
];
function storeSut(createStore) {
const store = createStore();
return {
set(k, v) { store.set(k, v); return v; },
get(k) { const v = store.get(k); return v === undefined ? null : v; },
delete(k) { return store.delete(k); },
clear() { store.clear(); return 'ok'; },
keys() { return Array.from(store.keys()); },
deletePrefix(p) { return store.deletePrefix(p); },
clone() {
const c = storeSut(createStore);
for (const k of store.keys()) c.set(k, store.get(k));
return c;
}
};
}
async function expectModelPass(name, createStore) {
const sut = storeSut(createStore);
const result = rigor.model.check(`store-${name}`, { map: new Map() }, sut, {
operations: STORE_OPS,
effort: 200,
maxSequenceLength: 40,
seed: `store-${name}`,
});
if (result.status !== 'passed') {
const detail = (result.failures || []).slice(0, 5).map((f) =>
JSON.stringify({ seq: f.sequence, at: f.commandIndex, expected: f.expected, actual: f.actual, shrunk: f.shrunk }));
throw new Error(`model campaign 'store-${name}' failed.\n${detail.join('\n')}`);
}
return result;
}
describe('Store contract (model-based conformance)', () => {
it('createMapStore matches the reference model', async () => {
await expectModelPass('map', () => createMapStore());
});
it('createFileStore matches the reference model (larger-than-memory store)', async () => {
const root = mkdtempSync(join(tmpdir(), 'vg-rigor-file-'));
let n = 0;
try {
await expectModelPass('file', () => createFileStore(join(root, `s${n++}`)));
} finally {
rmSync(root, { recursive: true, force: true });
}
});
});
// ─────────────────────────────────────────────────────────────────────────────
// Oracle conformance — every operator + arbitrary operator combinations.
// refOwaExact is an INDEPENDENT reimplementation of the ADR-000 OWA formula
// (weight distribution + weighted sum, mirrored arithmetic so bit-exact `===`
// conformance holds). It is written separately from value-graph's `owa`/
// `weightFor`, so it guards against refactoring drift; the semantic meaning of
// each operator is already covered by the epsilon invariants above.
// ─────────────────────────────────────────────────────────────────────────────
function refWeightsExact(op, n, weights, priorities) {
const k = Math.max(0, n);
if (k === 0) return [];
const zeros = (m) => Array(Math.max(0, m)).fill(0);
const fill = (m, v) => Array(m).fill(v);
const norm = (w) => { const s = w.reduce((a, b) => a + b, 0); return s > 0 ? w.map((x) => x / s) : w; };
switch (op) {
case 'max': return [1, ...zeros(Math.max(0, k - 1))];
case 'min': return [...zeros(Math.max(0, k - 1)), 1];
case 'average': return fill(k, 1 / Math.max(1, k));
case 'sum':
case 'sum_unbounded': return fill(k, 1);
case 'majority': {
const top = Math.ceil(k * 0.6);
const w = zeros(k);
for (let i = 0; i < top; i++) w[i] = 1 / Math.max(1, top);
return w;
}
case 'median': { const w = zeros(k); w[Math.floor((k - 1) / 2)] = 1; return w; }
case 'optimistic': { const w = []; let x = 0.5; for (let i = 0; i < k; i++) { w.push(x); x /= 2; } return norm(w); }
case 'pessimistic': return norm(refWeightsExact('optimistic', k, weights, priorities).reverse());
case 'top2': return norm([0.5, 0.5, ...zeros(Math.max(0, k - 2))].slice(0, k));
case 'top3': return norm([1 / 3, 1 / 3, 1 / 3, ...zeros(Math.max(0, k - 3))].slice(0, k));
case 'priority': {
if (priorities && k > 0) {
const ws = fill(k, 0);
for (let i = 0; i < k; i++) ws[i] = priorities[i] ?? 1;
return norm(ws);
}
return fill(k, 1 / Math.max(1, k));
}
case 'custom': return norm(weights && weights.length ? weights.slice(0, k) : fill(k, 1 / Math.max(1, k)));
default: return fill(k, 1 / Math.max(1, k));
}
}
function refOwaExact(values, op, { weights, priorities, capSum } = {}) {
const n = values.length;
if (n === 0) return 0;
const allNumeric = values.every((v) => typeof v === 'number');
if (op === 'product') {
if (!allNumeric) throw new Error('refOwaExact: product requires numeric values');
return values.reduce((a, b) => a * b, 1);
}
if (!allNumeric) {
switch (op) {
case 'max': return values.reduce((a, b) => (a > b ? a : b));
case 'min': return values.reduce((a, b) => (a < b ? a : b));
case 'majority': {
const m = new Map();
for (const v of values) m.set(v, (m.get(v) || 0) + 1);
let best = values[0]; let bc = 0;
for (const [v, c] of m) if (c > bc) { bc = c; best = v; }
return best;
}
default: throw new Error(`refOwaExact: operator ${op} requires numeric values`);
}
}
const sorted = [...values].sort((a, b) => b - a);
const w = refWeightsExact(op, n, weights, priorities);
let result = 0;
for (let i = 0; i < n; i++) result += (w[i] ?? 0) * sorted[i];
if (op === 'sum' && capSum) result = Math.min(1, result);
return result;
}
const ANY_OPS = rigor.gen.frequency(
[3, rigor.gen.constant('sum')], [2, rigor.gen.constant('sum_unbounded')],
[2, rigor.gen.constant('max')], [2, rigor.gen.constant('min')],
[3, rigor.gen.constant('average')], [2, rigor.gen.constant('median')],
[2, rigor.gen.constant('majority')], [2, rigor.gen.constant('optimistic')],
[2, rigor.gen.constant('pessimistic')], [2, rigor.gen.constant('top2')],
[2, rigor.gen.constant('top3')], [3, rigor.gen.constant('custom')],
[2, rigor.gen.constant('priority')], [2, rigor.gen.constant('product')],
);
const weightArr = rigor.gen.array(rigor.gen.int(-10, 10), 0, 6);
const OWA_ORACLE_ACTIONS = [
rigor.fn('owa_sum_ref', (values) => owa(values, 'sum', {}), rigor.args(intArr)),
rigor.fn('owa_sum_unbounded_ref', (values) => owa(values, 'sum_unbounded', {}), rigor.args(intArr)),
rigor.fn('owa_max_ref', (values) => owa(values, 'max', {}), rigor.args(intArr)),
rigor.fn('owa_min_ref', (values) => owa(values, 'min', {}), rigor.args(intArr)),
rigor.fn('owa_product_ref', (values) => owa(values, 'product', {}), rigor.args(intArr)),
rigor.fn('owa_median_ref', (values) => owa(values, 'median', {}), rigor.args(intArr)),
rigor.fn('owa_top2_ref', (values) => owa(values, 'top2', {}), rigor.args(twoInts)),
rigor.fn('owa_sum_capped', (values) => owa(values, 'sum', { capSum: true }), rigor.args(intArr)),
rigor.fn('owa_max_str_ref', (values) => owa(values, 'max', {}), rigor.args(strArr)),
rigor.fn('owa_min_str_ref', (values) => owa(values, 'min', {}), rigor.args(strArr)),
rigor.fn('owa_majority_str_ref', (values) => owa(values, 'majority', {}), rigor.args(strArr)),
rigor.fn('owa_any', (op, values, weights, priorities) => {
const opts = {};
if (op === 'custom') opts.weights = weights;
if (op === 'priority') opts.priorities = priorities;
return owa(values, op, opts);
}, rigor.args(ANY_OPS, intArr, weightArr, weightArr)),
];
const OWA_ORACLE_CHECKS = [
rigor.oracle('owa_sum_ref', (values) => values.reduce((a, b) => a + b, 0)),
rigor.oracle('owa_sum_unbounded_ref', (values) => values.reduce((a, b) => a + b, 0)),
rigor.oracle('owa_max_ref', (values) => (values.length === 0 ? 0 : Math.max(...values))),
rigor.oracle('owa_min_ref', (values) => (values.length === 0 ? 0 : Math.min(...values))),
rigor.oracle('owa_product_ref', (values) => (values.length === 0 ? 0 : values.reduce((a, b) => a * b, 1))),
rigor.oracle('owa_median_ref', (values) => {
if (values.length === 0) return 0;
const s = [...values].sort((a, b) => b - a);
return s[Math.floor((s.length - 1) / 2)];
}),
rigor.oracle('owa_top2_ref', (values) => {
const s = [...values].sort((a, b) => b - a);
return 0.5 * s[0] + 0.5 * s[1];
}),
rigor.after('owa_sum_capped', (ctx) => ctx.actual === Math.min(1, ctx.args[0].reduce((a, b) => a + b, 0))),
rigor.oracle('owa_max_str_ref', (values) => (values.length === 0 ? 0 : values.reduce((a, b) => (a > b ? a : b)))),
rigor.oracle('owa_min_str_ref', (values) => (values.length === 0 ? 0 : values.reduce((a, b) => (a < b ? a : b)))),
rigor.oracle('owa_majority_str_ref', (values) => {
if (values.length === 0) return 0;
const m = new Map();
for (const v of values) m.set(v, (m.get(v) || 0) + 1);
let best = values[0]; let bc = 0;
for (const [v, c] of m) if (c > bc) { bc = c; best = v; }
return best;
}),
rigor.after('owa_any', (ctx) => {
const [op, values, weights, priorities] = ctx.args;
const expected = refOwaExact(values, op, {
weights: op === 'custom' ? weights : undefined,
priorities: op === 'priority' ? priorities : undefined,
});
return ctx.actual === expected || (Number.isNaN(ctx.actual) && Number.isNaN(expected));
}),
];
// ─────────────────────────────────────────────────────────────────────────────
// Fusion DAG oracles — the graph's fusion path must equal raw owa, and composed
// fusion-of-fusion DAGs must equal the bottom-up reference.
// ─────────────────────────────────────────────────────────────────────────────
const FUSION_ACTIONS = [
rigor.fn('fusion_any', (op, values, weights, priorities) => {
const vg = new ValueGraph();
values.forEach((v, i) => vg.define(`p${i}`, { operator: 'source', fn: () => v }));
const spec = { operator: `fusion:${op}`, parents: values.map((_, i) => `p${i}`) };
if (op === 'custom') spec.weights = weights;
if (op === 'priority') spec.priorities = priorities;
vg.define('out', spec);
const entry = syncGet(vg, 't', 'out', {});
return entry ? entry.value : 'NO_ENTRY';
}, rigor.args(ANY_OPS, intArr, weightArr, weightArr)),
rigor.fn('dag_any', (op1, op2, op3, a, b, c, d) => {
const vg = new ValueGraph();
vg.define('a', { operator: 'source', fn: () => a });
vg.define('b', { operator: 'source', fn: () => b });
vg.define('c', { operator: 'source', fn: () => c });
vg.define('d', { operator: 'source', fn: () => d });
vg.define('m1', { operator: `fusion:${op1}`, parents: ['a', 'b'] });
vg.define('m2', { operator: `fusion:${op2}`, parents: ['c', 'd'] });
vg.define('out', { operator: `fusion:${op3}`, parents: ['m1', 'm2'] });
const entry = syncGet(vg, 't', 'out', {});
return entry ? entry.value : 'NO_ENTRY';
}, rigor.args(ANY_OPS, ANY_OPS, ANY_OPS, rigor.gen.int(-50, 50), rigor.gen.int(-50, 50), rigor.gen.int(-50, 50), rigor.gen.int(-50, 50))),
rigor.fn('fusion_compute_chain', (a, b, op) => {
const vg = new ValueGraph();
vg.define('a', { operator: 'source', fn: () => a });
vg.define('b', { operator: 'source', fn: () => b });
vg.define('ab', { operator: 'compute', parents: ['a', 'b'], fn: (s, p, { deps }) => deps.a * deps.b });
vg.define('out', { operator: `fusion:${op}`, parents: ['ab'] });
const entry = syncGet(vg, 't', 'out', {});
return entry ? entry.value : 'NO_ENTRY';
}, rigor.args(rigor.gen.int(-20, 20), rigor.gen.int(-20, 20), ANY_OPS)),
];
const FUSION_CHECKS = [
rigor.after('fusion_any', (ctx) => {
const [op, values, weights, priorities] = ctx.args;
const expected = refOwaExact(values, op, {
weights: op === 'custom' ? weights : undefined,
priorities: op === 'priority' ? priorities : undefined,
});
return ctx.actual === expected || (Number.isNaN(ctx.actual) && Number.isNaN(expected));
}),
rigor.after('dag_any', (ctx) => {
const [op1, op2, op3, a, b, c, d] = ctx.args;
const m1 = refOwaExact([a, b], op1);
const m2 = refOwaExact([c, d], op2);
const expected = refOwaExact([m1, m2], op3);
return ctx.actual === expected || (Number.isNaN(ctx.actual) && Number.isNaN(expected));
}),
rigor.after('fusion_compute_chain', (ctx) => {
const [a, b, op] = ctx.args;
const expected = refOwaExact([a * b], op);
return ctx.actual === expected || (Number.isNaN(ctx.actual) && Number.isNaN(expected));
}),
];
// ─────────────────────────────────────────────────────────────────────────────
// More correctness oracles — mixed-topology DAGs, selective invalidation,
// cache-key isolation, stale-on-error fallback, value-type round-trips,
// blackbox-over-fusion.
// ─────────────────────────────────────────────────────────────────────────────
const MORE_ACTIONS = [
// Mixed topology: m1 = fusion op1(a,b), m2 = compute (c+d), m3 = fusion op2(a,c),
// out = fusion op3(m1,m2,m3). Bottom-up reference must match.
rigor.fn('mixed_dag_ref', (a, b, c, d, op1, op2, op3) => {
const vg = new ValueGraph();
vg.define('a', { operator: 'source', fn: () => a });
vg.define('b', { operator: 'source', fn: () => b });
vg.define('c', { operator: 'source', fn: () => c });
vg.define('d', { operator: 'source', fn: () => d });
vg.define('m1', { operator: `fusion:${op1}`, parents: ['a', 'b'] });
vg.define('m2', { operator: 'compute', parents: ['c', 'd'], fn: (s, p, { deps }) => deps.c + deps.d });
vg.define('m3', { operator: `fusion:${op2}`, parents: ['a', 'c'] });
vg.define('out', { operator: `fusion:${op3}`, parents: ['m1', 'm2', 'm3'] });
const entry = syncGet(vg, 't', 'out', {});
return entry ? entry.value : 'NO_ENTRY';
}, rigor.args(rigor.gen.int(-20, 20), rigor.gen.int(-20, 20), rigor.gen.int(-20, 20), rigor.gen.int(-20, 20), ANY_OPS, ANY_OPS, ANY_OPS)),
// Selective invalidation: changing b and invalidating b must produce the
// correct new root value (dependent subtree recomputed, values correct).
rigor.fn('invalidate_correctness', (a, b1, b2, c, op, op2) => {
let b = b1;
const vg = new ValueGraph();
vg.define('a', { operator: 'source', fn: () => a });
vg.define('b', { operator: 'source', fn: () => b });
vg.define('c', { operator: 'source', fn: () => c });
vg.define('m', { operator: `fusion:${op}`, parents: ['a', 'b'] });
vg.define('r', { operator: `fusion:${op2}`, parents: ['m', 'c'] });
const first = syncGet(vg, 't', 'r', {}).value;
b = b2;
vg.invalidate('t', 'b', {});
const aAfter = syncGet(vg, 't', 'a', {}).value;
const second = syncGet(vg, 't', 'r', {}).value;
return { first, second, aAfter };
}, rigor.args(rigor.gen.int(-20, 20), rigor.gen.int(-20, 20), rigor.gen.int(-20, 20), rigor.gen.int(-20, 20), ANY_OPS, ANY_OPS)),
// Cache keys include subject: A and B share no entries; A computes once.
rigor.fn('subject_isolation', (va, vb) => {
let callsA = 0;
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, cb) => { if (s === 'A') callsA++; cb(null, s === 'A' ? va : vb); });
const a1 = syncGet(vg, 'A', 'v', {}).value;
const b1 = syncGet(vg, 'B', 'v', {}).value;
const a2 = syncGet(vg, 'A', 'v', {}).value;
const b2 = syncGet(vg, 'B', 'v', {}).value;
return { a1, a2, b1, b2, callsA };
}, rigor.args(rigor.gen.int(-100, 100), rigor.gen.int(-100, 100))),
// Stale-on-error: after TTL expiry the entry is still present, so a failing
// resolver returns the cached value marked fresh:false instead of erroring.
rigor.fn('stale_on_error', (value) => {
let t = 0;
let mode = 'ok';
const vg = new ValueGraph({ clock: () => t, defaultTTL: 100 });
vg.compute('v', (s, p, ctx, cb) => { if (mode === 'err') return cb(new Error('resolver down')); cb(null, value); });
const first = syncGet(vg, 't', 'v', {});
t = 200;
mode = 'err';
const second = syncGet(vg, 't', 'v', {});
return { firstValue: first.value, firstFresh: first.fresh, secondValue: second.value, secondFresh: second.fresh };
}, rigor.args(rigor.gen.int(0, 100))),
// Value types (array / interval / buffer) round-trip through the disk-backed
// store into a brand-new graph instance.
rigor.fn('file_roundtrip_types', (a, b, c) => {
const dir = mkdtempSync(join(tmpdir(), 'vg-rigor-rt-'));
try {
const vg1 = new ValueGraph({ store: createFileStore(dir) });
vg1.define('arr', { operator: 'source', fn: () => [a, b, c] });
vg1.define('interval', { operator: 'source', fn: () => ({ lower: a, upper: b }) });
vg1.define('buf', { operator: 'source', fn: () => new Uint8Array([a & 0xff, b & 0xff, c & 0xff]) });
const e1 = syncGet(vg1, 't', 'arr', {}).value;
const e2 = syncGet(vg1, 't', 'interval', {}).value;
const e3 = syncGet(vg1, 't', 'buf', {}).value;
const vg2 = new ValueGraph({ store: createFileStore(dir) });
const r1 = syncGet(vg2, 't', 'arr', {}).value;
const r2 = syncGet(vg2, 't', 'interval', {}).value;
const r3 = syncGet(vg2, 't', 'buf', {}).value;
return { e1, e2, e3, r1, r2, r3 };
} finally {
rmSync(dir, { recursive: true, force: true });
}
}, rigor.args(rigor.gen.int(-50, 50), rigor.gen.int(-50, 50), rigor.gen.int(-50, 50))),
// blackbox over a fusion parent: ctx.deps carries the fused value, so
// out = fusion(a,b) * k must equal the reference times k.
rigor.fn('blackbox_fusion', (a, b, op, k) => {
const vg = new ValueGraph();
vg.define('a', { operator: 'source', fn: () => a });
vg.define('b', { operator: 'source', fn: () => b });
vg.define('m', { operator: `fusion:${op}`, parents: ['a', 'b'] });
vg.define('out', { operator: 'blackbox', parents: ['m'], fn: (s, p, { deps }) => deps.m * k });
const entry = syncGet(vg, 't', 'out', {});
return entry ? entry.value : 'NO_ENTRY';
}, rigor.args(rigor.gen.int(-20, 20), rigor.gen.int(-20, 20), ANY_OPS, rigor.gen.int(-5, 5))),
// Selective invalidation: invalidating an UNRELATED relation must leave the
// cached entry untouched (per-relation version stamps).
rigor.fn('selective_keep_unrelated', (va, vb) => {
let aCalls = 0;
const vg = new ValueGraph();
vg.compute('a', (s, p, ctx, cb) => { aCalls++; cb(null, va); });
vg.compute('b', (s, p, ctx, cb) => cb(null, vb));
syncGet(vg, 't', 'a', {});
const before = vg.plan('t', 'a', {}).nodes.get('a').trivial;
vg.invalidate('t', 'b', {});
const after = vg.plan('t', 'a', {}).nodes.get('a').trivial;
return { before, after, aCalls, aValue: syncGet(vg, 't', 'a', {}).value };
}, rigor.args(rigor.gen.int(0, 100), rigor.gen.int(0, 100))),
// Selective invalidation down a chain: two independent subtrees share no
// versions — invalidating one branch leaves the sibling branch cached.
rigor.fn('selective_chain', (a1, b1, c1, a2, b2, op) => {
const vg = new ValueGraph();
vg.define('a1', { operator: 'source', fn: () => a1 });
vg.define('b1', { operator: 'source', fn: () => b1 });
vg.define('c1', { operator: 'source', fn: () => c1 });
vg.define('a2', { operator: 'source', fn: () => a2 });
vg.define('b2', { operator: 'source', fn: () => b2 });
vg.define('m1', { operator: `fusion:${op}`, parents: ['a1', 'b1'] });
vg.define('m2', { operator: `fusion:${op}`, parents: ['a2', 'b2'] });
vg.define('r1', { operator: `fusion:${op}`, parents: ['m1', 'c1'] });
syncGet(vg, 't', 'r1', {});
syncGet(vg, 't', 'm2', {});
const m2Before = vg.plan('t', 'm2', {}).nodes.get('m2').trivial;
vg.invalidate('t', 'a1', {});
const m2After = vg.plan('t', 'm2', {}).nodes.get('m2').trivial;
const r1After = vg.plan('t', 'r1', {}).nodes.get('r1').trivial;
return { m2Before, m2After, r1After };
}, rigor.args(rigor.gen.int(-20, 20), rigor.gen.int(-20, 20), rigor.gen.int(-20, 20), rigor.gen.int(-20, 20), rigor.gen.int(-20, 20), ANY_OPS)),
// Selective set: setting an unrelated relation leaves the cached entry fresh.
rigor.fn('set_keep_unrelated', (va, vb) => {
let aCalls = 0;
const vg = new ValueGraph();
vg.compute('a', (s, p, ctx, cb) => { aCalls++; cb(null, va); });
vg.compute('b', (s, p, ctx, cb) => cb(null, vb));
syncGet(vg, 't', 'a', {});
vg.set('t', 'b', {}, { value: 999 });
const aCached = vg.plan('t', 'a', {}).nodes.get('a').trivial;
return { aCached, aCalls, aValue: syncGet(vg, 't', 'a', {}).value, bValue: syncGet(vg, 't', 'b', {}).value };
}, rigor.args(rigor.gen.int(0, 100), rigor.gen.int(0, 100))),
];
const MORE_CHECKS = [
rigor.after('mixed_dag_ref', (ctx) => {
const [a, b, c, d, op1, op2, op3] = ctx.args;
const m1 = refOwaExact([a, b], op1);
const m2 = c + d;
const m3 = refOwaExact([a, c], op2);
const expected = refOwaExact([m1, m2, m3], op3);
return ctx.actual === expected || (Number.isNaN(ctx.actual) && Number.isNaN(expected));
}),
rigor.after('invalidate_correctness', (ctx) => {
const [a, b1, b2, c, op, op2] = ctx.args;
const { first, second, aAfter } = ctx.actual;
const expectedFirst = refOwaExact([refOwaExact([a, b1], op), c], op2);
const expectedSecond = refOwaExact([refOwaExact([a, b2], op), c], op2);
return first === expectedFirst && second === expectedSecond && aAfter === a;
}),
rigor.after('subject_isolation', (ctx) => {
const [va, vb] = ctx.args;
const { a1, a2, b1, b2, callsA } = ctx.actual;
return a1 === va && a2 === va && b1 === vb && b2 === vb && callsA === 1;
}),
rigor.after('stale_on_error', (ctx) => {
const { firstValue, firstFresh, secondValue, secondFresh } = ctx.actual;
return firstValue === ctx.args[0] && firstFresh === true &&
secondValue === ctx.args[0] && secondFresh === false;
}),
rigor.after('file_roundtrip_types', (ctx) => {
const { e1, e2, e3, r1, r2, r3 } = ctx.actual;
return JSON.stringify(e1) === JSON.stringify(r1) &&
e2.lower === r2.lower && e2.upper === r2.upper &&
r3.length === e3.length && r3.every((b, i) => b === e3[i]);
}),
rigor.after('blackbox_fusion', (ctx) => {
const [a, b, op, k] = ctx.args;
const expected = refOwaExact([a, b], op) * k;
return ctx.actual === expected || (Number.isNaN(ctx.actual) && Number.isNaN(expected));
}),
rigor.after('selective_keep_unrelated', (ctx) => {
const { before, after, aCalls, aValue } = ctx.actual;
return before === true && after === true && aCalls === 1 && aValue === ctx.args[0];
}),
rigor.after('selective_chain', (ctx) => {
const { m2Before, m2After, r1After } = ctx.actual;
return m2Before === true && m2After === true && r1After === false;
}),
rigor.after('set_keep_unrelated', (ctx) => {
const { aCached, aCalls, aValue, bValue } = ctx.actual;
return aCached === true && aCalls === 1 && aValue === ctx.args[0] && bValue === 999;
}),
];
// ─────────────────────────────────────────────────────────────────────────────
// Universal invariants — every property that should hold for ALL operators /
// graph behaviors, verified against generated inputs.
// ─────────────────────────────────────────────────────────────────────────────
const MONO_OPS = rigor.gen.frequency(
[3, rigor.gen.constant('sum')], [2, rigor.gen.constant('sum_unbounded')],
[2, rigor.gen.constant('max')], [2, rigor.gen.constant('min')],
[3, rigor.gen.constant('average')], [2, rigor.gen.constant('median')],
[2, rigor.gen.constant('majority')], [2, rigor.gen.constant('optimistic')],
[2, rigor.gen.constant('pessimistic')], [2, rigor.gen.constant('top2')],
[2, rigor.gen.constant('top3')], [2, rigor.gen.constant('priority')],
);
const BOUNDED_OPS = ['max', 'min', 'average', 'median', 'majority', 'optimistic', 'pessimistic', 'top2', 'top3', 'priority', 'custom'];
const UNIVERSAL_ACTIONS = [
// Every operator: empty→0, singleton→element, sum/product exact, weighted
// operators stay within [min,max].
rigor.fn('owa_universal', (op, values) => owa(values, op, {}), rigor.args(ANY_OPS, intArr)),
// Monotonicity: bumping one element by +1 never decreases the result.
rigor.fn('owa_monotone', (op, values, idx) => {
const n = Math.max(1, values.length);
const bumped = values.map((v, i) => (i === idx % n ? v + 1 : v));
return { a: owa(values, op, {}), b: owa(bumped, op, {}) };
}, rigor.args(MONO_OPS, intArr, rigor.gen.int(0, 200))),
// Plan exactness: fully-cached plan is a single leaf; invalidation expands to
// exactly the affected closure (a,m,r recompute) while the unrelated fresh
// leaves b,c stay trivial in the plan.
rigor.fn('plan_exactness', (op, a, b, c) => {
const vg = new ValueGraph();
vg.define('a', { operator: 'source', fn: () => a });
vg.define('b', { operator: 'source', fn: () => b });
vg.define('c', { operator: 'source', fn: () => c });
vg.define('m', { operator: `fusion:${op}`, parents: ['a', 'b'] });
vg.define('r', { operator: `fusion:${op}`, parents: ['m', 'c'] });
syncGet(vg, 't', 'r', {});
const allCached = vg.plan('t', 'r', {}).size;
const mCached = vg.plan('t', 'm', {}).size;
vg.invalidate('t', 'a', {});
const planAfter = vg.plan('t', 'r', {});
const aAfter = vg.plan('t', 'a', {}).size;
return {
allCached,
mCached,
rAfter: planAfter.size,
aAfter,
aTriv: planAfter.nodes.get('a').trivial,
mTriv: planAfter.nodes.get('m').trivial,
rTriv: planAfter.nodes.get('r').trivial,
bTriv: planAfter.nodes.get('b').trivial,
cTriv: planAfter.nodes.get('c').trivial
};
}, rigor.args(ANY_OPS, rigor.gen.int(-20, 20), rigor.gen.int(-20, 20), rigor.gen.int(-20, 20))),
// Async resolver + two concurrent gets: serialized, both delivered, cached
// (resolver called once — encoded by value+calls).
rigor.fn('query_async_serialized', (subject, value, cb) => {
let calls = 0;
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, done) => { calls++; setTimeout(() => done(null, value + calls), 1); });
const q = vg.query(subject, 'v', {});
q.source.pipe(cb);
q.sink.write({ get: true });
q.sink.write({ get: true });
setTimeout(() => q.sink.end(), 25);
}, rigor.args(rigor.gen.string(), rigor.gen.int(0, 100), rigor.handler(reducers.array()))),
// Params participate in the cache key: different params never share entries.
rigor.fn('params_isolation', (va, vb) => {
let callsA = 0;
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, cb) => { if (p.which === 'a') callsA++; cb(null, p.which === 'a' ? va : vb); });
const a1 = syncGet(vg, 't', 'v', { which: 'a' }).value;
const b1 = syncGet(vg, 't', 'v', { which: 'b' }).value;
const a2 = syncGet(vg, 't', 'v', { which: 'a' }).value;
return { a1, a2, b1, callsA };
}, rigor.args(rigor.gen.int(-100, 100), rigor.gen.int(-100, 100))),
// Cross-subject invalidation: relation-level versioning — B's dependents may
// recompute but must stay CORRECT.
rigor.fn('cross_subject_invalidate', (xa, xb) => {
const vg = new ValueGraph();
vg.compute('x', (s, p, ctx, cb) => cb(null, s === 'A' ? xa : xb));
vg.compute('m', (s, p, { deps }) => deps.x * 2, { dependsOn: ['x'] });
const aM = syncGet(vg, 'A', 'm', {}).value;
const bM = syncGet(vg, 'B', 'm', {}).value;
vg.invalidate('A', 'x', {});
const aM2 = syncGet(vg, 'A', 'm', {}).value;
const bM2 = syncGet(vg, 'B', 'm', {}).value;
return { aM, bM, aM2, bM2 };
}, rigor.args(rigor.gen.int(-50, 50), rigor.gen.int(-50, 50))),
// invalidateAll clears every subject's entry for the relation.
rigor.fn('invalidate_all_subjects', (va, vb) => {
let calls = 0;
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, cb) => { calls++; cb(null, s === 'A' ? va : vb); });
syncGet(vg, 'A', 'v', {});
syncGet(vg, 'B', 'v', {});
const callsBefore = calls;
vg.invalidateAll('v');
const callsAfterInvalidate = calls;
const a2 = syncGet(vg, 'A', 'v', {}).value;
const b2 = syncGet(vg, 'B', 'v', {}).value;
return { callsBefore, callsAfterInvalidate, callsAfter: calls, a2, b2 };
}, rigor.args(rigor.gen.int(0, 100), rigor.gen.int(0, 100))),
// An undefined (ghost) parent in a fusion is treated as missing: filtered out.
rigor.fn('ghost_parent_fusion', (a, op) => {
const vg = new ValueGraph();
vg.define('a', { operator: 'source', fn: () => a });
vg.define('out', { operator: `fusion:${op}`, parents: ['a', 'ghost'] });
const entry = syncGet(vg, 't', 'out', {});
return entry ? entry.value : 'NO_ENTRY';
}, rigor.args(rigor.gen.int(-50, 50), ANY_OPS)),
// Attribute hook receives (nodeKey=subject, path, params).
rigor.fn('attribute_hook_args', (subject, balance) => {
const seen = [];
const graph = new Map([[subject, { balance }]]);
const vg = new ValueGraph({
resolveAttribute: (nodeKey, path, params, ctx, cb) => { seen.push({ nodeKey, path, params }); cb(null, graph.get(nodeKey)?.[path]); }
});
vg.define('balance', { operator: 'attribute', attribute: 'balance' });
const value = syncGet(vg, subject, 'balance', {}).value;
return { value, seen: seen[0] };
}, rigor.args(rigor.gen.string(), rigor.gen.int(-100, 100))),
// Pattern hook receives (pattern, subject, params).
rigor.fn('pattern_hook_args', (subject, relation, count) => {
const seen = [];
const vg = new ValueGraph({
resolvePattern: (pattern, s, params, ctx, cb) => { seen.push({ pattern, s, params }); cb(null, count); }
});
vg.define('degree', { operator: 'pattern', pattern: { relation } });
const value = syncGet(vg, subject, 'degree', {}).value;
return { value, seen: seen[0] };
}, rigor.args(rigor.gen.string(), rigor.gen.string(), rigor.gen.int(0, 10))),
// TTL boundary: fresh strictly inside the window, stale AT the window.
rigor.fn('ttl_boundary', (value) => {
let t = 0;
const vg = new ValueGraph({ clock: () => t, defaultTTL: 100 });
vg.compute('v', (s, p, ctx, cb) => cb(null, value));
syncGet(vg, 't', 'v', {});
const freshAt0 = vg.plan('t', 'v', {}).nodes.get('v').trivial;
t = 99;
const freshAt99 = vg.plan('t', 'v', {}).nodes.get('v').trivial;
t = 100;
const staleAt100 = vg.plan('t', 'v', {}).nodes.get('v').trivial;
return { freshAt0, freshAt99, staleAt100 };
}, rigor.args(rigor.gen.int(0, 100))),
// ttl: 0 means "never expires".
rigor.fn('ttl_zero_never_expires', (value) => {
let t = 0;
const vg = new ValueGraph({ clock: () => t });
vg.compute('v', (s, p, ctx, cb) => cb(null, value), { ttl: 0 });
syncGet(vg, 't', 'v', {});
t = 1e12;
return vg.plan('t', 'v', {}).nodes.get('v').trivial;
}, rigor.args(rigor.gen.int(0, 100))),
// set preserves the written unit and source on the returned entry.
rigor.fn('set_preserves_unit', (value) => {
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, cb) => cb(null, 5));
vg.set('t', 'v', {}, { value, unit: 'usd_cents', source: 'ledger' });
const entry = syncGet(vg, 't', 'v', {});
return { value: entry.value, unit: entry.unit, source: entry.source };
}, rigor.args(rigor.gen.int(0, 100))),
// In-graph fusion over string sources: max/min/majority fallback.
rigor.fn('graph_string_fusion', (x, y, op) => {
const vg = new ValueGraph();
vg.define('x', { operator: 'source', fn: () => x });
vg.define('y', { operator: 'source', fn: () => y });
vg.define('out', { operator: `fusion:${op}`, parents: ['x', 'y'] });
const entry = syncGet(vg, 't', 'out', {});
return entry ? entry.value : 'NO_ENTRY';
}, rigor.args(rigor.gen.oneOf(['low', 'med', 'high']), rigor.gen.oneOf(['low', 'med', 'high']), rigor.gen.oneOf(['max', 'min', 'majority']))),
// In-graph fusion:sum with capSum clamps at 1.0.
rigor.fn('graph_sum_capped', (a, b) => {
const vg = new ValueGraph();
vg.define('a', { operator: 'source', fn: () => a });
vg.define('b', { operator: 'source', fn: () => b });
vg.define('out', { operator: 'fusion:sum', parents: ['a', 'b'], capSum: true });
const entry = syncGet(vg, 't', 'out', {});
return entry ? entry.value : 'NO_ENTRY';
}, rigor.args(rigor.gen.int(-10, 10), rigor.gen.int(-10, 10))),
// Duplicate parents are treated as a SINGLE dependency (parents are a set).
rigor.fn('fusion_dup_parents', (a, op) => {
const vg = new ValueGraph();
vg.define('a', { operator: 'source', fn: () => a });
vg.define('out', { operator: `fusion:${op}`, parents: ['a', 'a'] });
const entry = syncGet(vg, 't', 'out', {});
return entry ? entry.value : 'NO_ENTRY';
}, rigor.args(rigor.gen.int(-50, 50), ANY_OPS)),
];
const UNIVERSAL_CHECKS = [
rigor.after('owa_universal', (ctx) => {
const [op, values] = ctx.args;
const actual = ctx.actual;
if (values.length === 0) return actual === 0;
if (values.length === 1) return actual === values[0];
if (values.every((v) => v === values[0]) && BOUNDED_OPS.includes(op)) return actual === values[0];
if (op === 'sum' || op === 'sum_unbounded') return actual === values.reduce((a, b) => a + b, 0);
if (op === 'product') return actual === values.reduce((a, b) => a * b, 1);
if (BOUNDED_OPS.includes(op)) {
return actual >= Math.min(...values) && actual <= Math.max(...values);
}
return true;
}),
rigor.after('owa_monotone', (ctx) => ctx.actual.a <= ctx.actual.b),
rigor.after('plan_exactness', (ctx) => {
const { allCached, mCached, rAfter, aAfter, aTriv, mTriv, rTriv, bTriv, cTriv } = ctx.actual;
return allCached === 1 && mCached === 1 &&
rAfter === 5 && aAfter === 1 &&
aTriv === false && mTriv === false && rTriv === false &&
bTriv === true && cTriv === true;
}),
rigor.after('query_async_serialized', (ctx) =>
Array.isArray(ctx.actual) && ctx.actual.length === 2 &&
ctx.actual[0].value === ctx.args[1] + 1 && ctx.actual[1].value === ctx.args[1] + 1),
rigor.after('params_isolation', (ctx) => {
const { a1, a2, b1, callsA } = ctx.actual;
return a1 === ctx.args[0] && a2 === ctx.args[0] && b1 === ctx.args[1] && callsA === 1;
}),
rigor.after('cross_subject_invalidate', (ctx) => {
const [xa, xb] = ctx.args;
const { aM, bM, aM2, bM2 } = ctx.actual;
return aM === xa * 2 && bM === xb * 2 && aM2 === xa * 2 && bM2 === xb * 2;
}),
rigor.after('invalidate_all_subjects', (ctx) => {
const [va, vb] = ctx.args;
const { callsBefore, callsAfterInvalidate, callsAfter, a2, b2 } = ctx.actual;
return callsBefore === 2 && callsAfterInvalidate === 2 && callsAfter === 4 && a2 === va && b2 === vb;
}),
rigor.after('ghost_parent_fusion', (ctx) => {
const [a, op] = ctx.args;
const expected = refOwaExact([a], op);
return ctx.actual === expected || (Number.isNaN(ctx.actual) && Number.isNaN(expected));
}),
rigor.after('attribute_hook_args', (ctx) => {
const { value, seen } = ctx.actual;
return value === ctx.args[1] && seen.nodeKey === ctx.args[0] && seen.path === 'balance';
}),
rigor.after('pattern_hook_args', (ctx) => {
const { value, seen } = ctx.actual;
return value === ctx.args[2] && seen.s === ctx.args[0] && seen.pattern.relation === ctx.args[1];
}),
rigor.after('ttl_boundary', (ctx) => {
const { freshAt0, freshAt99, staleAt100 } = ctx.actual;
return freshAt0 === true && freshAt99 === true && staleAt100 === false;
}),
rigor.after('ttl_zero_never_expires', (ctx) => ctx.actual === true),
rigor.after('set_preserves_unit', (ctx) =>
ctx.actual.value === ctx.args[0] && ctx.actual.unit === 'usd_cents' && ctx.actual.source === 'ledger'),
rigor.after('graph_string_fusion', (ctx) => {
const [x, y, op] = ctx.args;
if (op === 'max') return ctx.actual === (x > y ? x : y);
if (op === 'min') return ctx.actual === (x < y ? x : y);
return ctx.actual === x; // majority over {x, y} → first mode encountered
}),
rigor.after('graph_sum_capped', (ctx) => ctx.actual === Math.min(1, ctx.args[0] + ctx.args[1])),
rigor.after('fusion_dup_parents', (ctx) => {
const [a, op] = ctx.args;
const expected = refOwaExact([a], op);
return ctx.actual === expected || (Number.isNaN(ctx.actual) && Number.isNaN(expected));
}),
];
// ─────────────────────────────────────────────────────────────────────────────
// Arbitrary random-topology DAGs vs the independent reference, complexity
// verification, and the error contract.
// ─────────────────────────────────────────────────────────────────────────────
const TOPOLOGY_ACTIONS = [
// Three mid fusion nodes over randomly-picked source pairs, then a root
// fusion over all three mids + one source. Full random topology × operators
// (scalar args so shrinking can't collapse array lengths).
rigor.fn('random_dag_ref', (s0, s1, s2, op0, op1, op2, p0a, p0b, p1a, p1b, p2a, p2b, opRoot, extra) => {
const sources = [s0, s1, s2];
const ops = [op0, op1, op2];
const pairs = [[p0a, p0b], [p1a, p1b], [p2a, p2b]];
const vg = new ValueGraph();
vg.define('s0', { operator: 'source', fn: () => s0 });
vg.define('s1', { operator: 'source', fn: () => s1 });
vg.define('s2', { operator: 'source', fn: () => s2 });
const mids = [];
for (let i = 0; i < 3; i++) {
const rel = `m${i}`;
const [a, b] = pairs[i];
vg.define(rel, { operator: `fusion:${ops[i]}`, parents: [`s${a}`, `s${b}`] });
mids.push(rel);
}
vg.define('out', { operator: `fusion:${opRoot}`, parents: [...mids, `s${extra}`] });
const entry = syncGet(vg, 't', 'out', {});
return entry ? entry.value : 'NO_ENTRY';
}, rigor.args(rigor.gen.int(-20, 20), rigor.gen.int(-20, 20), rigor.gen.int(-20, 20),
ANY_OPS, ANY_OPS, ANY_OPS,
rigor.gen.int(0, 2), rigor.gen.int(0, 2), rigor.gen.int(0, 2), rigor.gen.int(0, 2), rigor.gen.int(0, 2), rigor.gen.int(0, 2),
ANY_OPS, rigor.gen.int(0, 2))),
// A compute chain of depth n evaluates in O(n) resolver calls (source + n-1
// computes = exactly n, so the cost metric is offset-free for the e-process).
rigor.fn('chain_run', (n) => {
const vg = new ValueGraph();
let calls = 0;
let prev = 's';
vg.define(prev, { operator: 'source', fn: (s, p, ctx, cb) => { calls++; cb(null, 0); } });
for (let i = 1; i < n; i++) {
const rel = `c${i}`;
const parent = prev;
vg.define(rel, { operator: 'compute', parents: [parent], fn: (s, p, ctx, cb) => { calls++; cb(null, ctx.deps[parent] + 1); } });
prev = rel;
}
syncGet(vg, 't', prev, {});
return { ops: calls };
}, rigor.args(rigor.gen.int(1, 600)),
rigor.metric('n', ({ args }) => args[0]),
rigor.metric('cost', ({ result }) => result.ops)),
// plan() over a chain of depth n is O(n).
rigor.fn('plan_chain', (n) => {
const vg = new ValueGraph();
let prev = 's';
vg.define(prev, { operator: 'source', fn: () => 0 });
for (let i = 1; i < n; i++) {
const rel = `c${i}`;
vg.define(rel, { operator: 'compute', parents: [prev], fn: (s, p, { deps }) => deps[prev] + 1 });
prev = rel;
}
return { ops: vg.plan('t', prev, {}).size };
}, rigor.args(rigor.gen.int(1, 600)),
rigor.metric('n', ({ args }) => args[0]),
rigor.metric('cost', ({ result }) => result.ops)),
// weightFor allocates O(n) weights.
rigor.fn('weightFor_alloc', (op, n) => weightFor(op, n),
rigor.args(ANY_OPS, rigor.gen.int(0, 2000)),
rigor.metric('n', ({ args }) => args[1]),
rigor.metric('cost', ({ result }) => result.length)),
];
function tryGet(vg, subject, rel, params = {}) {
try {
const e = syncGet(vg, subject, rel, params);
return { ok: true, value: e ? e.value : null };
} catch (err) {
return { ok: false, error: err.message };
}
}
const ERROR_ACTIONS = [
rigor.fn('err_source_no_fn', () => {
const vg = new ValueGraph();
vg.define('x', { operator: 'source' });
return tryGet(vg, 't', 'x', {});
}, rigor.args()),
rigor.fn('err_unknown_op', () => {
const vg = new ValueGraph();
vg.define('x', { operator: 'bogus' });
return tryGet(vg, 't', 'x', {});
}, rigor.args()),
rigor.fn('err_product_nonnumeric', () => {
const vg = new ValueGraph();
vg.define('a', { operator: 'source', fn: () => 'x' });
vg.define('b', { operator: 'source', fn: () => 'y' });
vg.define('out', { operator: 'fusion:product', parents: ['a', 'b'] });
return tryGet(vg, 't', 'out', {});
}, rigor.args()),
rigor.fn('err_run_requires_cb', () => {
const vg = new ValueGraph();
vg.compute('x', () => 1);
const plan = vg.plan('t', 'x', {});
try { vg.run(plan); return { ok: true }; } catch (err) { return { ok: false, error: err.message }; }
}, rigor.args()),
rigor.fn('err_get_requires_cb', () => {
const vg = new ValueGraph();
vg.compute('x', () => 1);
try { vg.get('t', 'x', {}); return { ok: true }; } catch (err) { return { ok: false, error: err.message }; }
}, rigor.args()),
];
const TOPOLOGY_CHECKS = [
rigor.after('random_dag_ref', (ctx) => {
const [s0, s1, s2, op0, op1, op2, p0a, p0b, p1a, p1b, p2a, p2b, opRoot, extra] = ctx.args;
const sources = [s0, s1, s2];
const ops = [op0, op1, op2];
const pairs = [[p0a, p0b], [p1a, p1b], [p2a, p2b]];
const mids = [];
for (let i = 0; i < 3; i++) {
const [a, b] = pairs[i];
// Duplicate parents are a SET in the plan (deduplicated) — mirror that.
mids.push(refOwaExact([...new Set([a, b])].map((idx) => sources[idx]), ops[i]));
}
const expected = refOwaExact([...mids, sources[extra]], opRoot);
return ctx.actual === expected || (Number.isNaN(ctx.actual) && Number.isNaN(expected));
}),
rigor.after('err_source_no_fn', (ctx) => ctx.actual.ok === false && /needs an fn/.test(ctx.actual.error)),
rigor.after('err_unknown_op', (ctx) => ctx.actual.ok === false && /unknown operator/.test(ctx.actual.error)),
rigor.after('err_product_nonnumeric', (ctx) => ctx.actual.ok === false && /numeric/.test(ctx.actual.error)),
rigor.after('err_run_requires_cb', (ctx) => ctx.actual.ok === false && /requires a callback/.test(ctx.actual.error)),
rigor.after('err_get_requires_cb', (ctx) => ctx.actual.ok === false && /requires a callback/.test(ctx.actual.error)),
];
describe('Complexity verification (linear run/plan/weightFor)', () => {
it('run/plan/weightFor are linear (complexity campaign)', async () => {
await expectPass('complexity',
[TOPOLOGY_ACTIONS[1], TOPOLOGY_ACTIONS[2], TOPOLOGY_ACTIONS[3]],
[rigor.complexity('chain_run', 'O(n)'), rigor.complexity('plan_chain', 'O(n)'), rigor.complexity('weightFor_alloc', 'O(n)')],
{ effort: 300 });
});
});
// ─────────────────────────────────────────────────────────────────────────────
// Compile-time optimization — plan metadata + ghost-parent elimination.
// ─────────────────────────────────────────────────────────────────────────────
const OPTIMIZE_ACTIONS = [
// A fusion with three ghost parents: they are eliminated at compile time and
// the result is exactly the reference over the real parents.
rigor.fn('optimize_ghost_random', (a, b, c, op) => {
const vg = new ValueGraph();
vg.define('a', { operator: 'source', fn: () => a });
vg.define('b', { operator: 'source', fn: () => b });
vg.define('c', { operator: 'source', fn: () => c });
vg.define('out', { operator: `fusion:${op}`, parents: ['a', 'ghost1', 'b', 'ghost2', 'c', 'ghost3'] });
const plan = vg.plan('t', 'out', {});
const value = syncGet(vg, 't', 'out', {}).value;
return { value, ghostPruned: plan.ghostPruned, size: plan.size, hasGhost: plan.nodes.has('ghost1') };
}, rigor.args(rigor.gen.int(-20, 20), rigor.gen.int(-20, 20), rigor.gen.int(-20, 20), ANY_OPS)),
// Fully-cached plan: one trivial leaf, zero recomputes.
rigor.fn('optimize_cached', (a, op) => {
const vg = new ValueGraph();
vg.define('a', { operator: 'source', fn: () => a });
vg.define('out', { operator: `fusion:${op}`, parents: ['a'] });
syncGet(vg, 't', 'out', {});
const plan = vg.plan('t', 'out', {});
return { size: plan.size, recomputeCount: plan.recomputeCount, prunedCount: plan.prunedCount };
}, rigor.args(rigor.gen.int(-50, 50), ANY_OPS)),
// After invalidating a parent: plan recomputes exactly the affected closure and
// serves the untouched sibling from cache.
rigor.fn('optimize_invalidated', (a, b, op) => {
const vg = new ValueGraph();
vg.define('a', { operator: 'source', fn: () => a });
vg.define('b', { operator: 'source', fn: () => b });
vg.define('m', { operator: `fusion:${op}`, parents: ['a', 'b'] });
syncGet(vg, 't', 'm', {});
vg.invalidate('t', 'a', {});
const plan = vg.plan('t', 'm', {});
return { size: plan.size, recomputeCount: plan.recomputeCount, prunedCount: plan.prunedCount, bTriv: plan.nodes.get('b').trivial, aTriv: plan.nodes.get('a').trivial };
}, rigor.args(rigor.gen.int(-20, 20), rigor.gen.int(-20, 20), ANY_OPS)),
];
const OPTIMIZE_CHECKS = [
rigor.after('optimize_ghost_random', (ctx) => {
const [a, b, c, op] = ctx.args;
const { value, ghostPruned, size, hasGhost } = ctx.actual;
return ghostPruned === 3 && hasGhost === false && size === 4 &&
(value === refOwaExact([a, b, c], op) || (Number.isNaN(value) && Number.isNaN(refOwaExact([a, b, c], op))));
}),
rigor.after('optimize_cached', (ctx) => {
const { size, recomputeCount, prunedCount } = ctx.actual;
return size === 1 && recomputeCount === 0 && prunedCount === 1;
}),
rigor.after('optimize_invalidated', (ctx) => {
const { size, recomputeCount, prunedCount, bTriv, aTriv } = ctx.actual;
return size === 3 && recomputeCount === 2 && prunedCount === 1 && bTriv === true && aTriv === false;
}),
];
describe('Compile-time optimization', () => {
it('ghost elimination, cached-leaf pruning, and invalidated-closure plans (ONE campaign)', async () => {
await expectPass('optimize', OPTIMIZE_ACTIONS, OPTIMIZE_CHECKS, { effort: 900 });
});
});
// ─────────────────────────────────────────────────────────────────────────────
// Model-based conformance of the graph's caching/versioning contract, and
// query-duplex lifecycle invariants.
// ─────────────────────────────────────────────────────────────────────────────
function makeGraphSut(shared) {
const vg = new ValueGraph({ defaultTTL: 0 });
vg.compute('v', (s, p, ctx, cb) => cb(null, shared.get(s) ?? 0));
return {
mutate(subject, value) { shared.set(subject, value); return value; },
get(subject) { const e = syncGet(vg, subject, 'v', {}); return e ? e.value : null; },
invalidate(subject) { vg.invalidate(subject, 'v', {}); return 'ok'; },
set(subject, value) { vg.set(subject, 'v', {}, { value }); return value; },
clone() { return makeGraphSut(shared); }
};
}
function graphCacheModelOps(shared) {
return [
{ name: 'mutate', args: rigor.gen.tuple(rigor.gen.string(), rigor.gen.int(-100, 100)), run: (m, subject, value) => { shared.set(subject, value); return value; } },
{ name: 'get', args: rigor.gen.string(), run: (m, subject) => {
const e = m.cache.get(subject);
if (e && e.version === m.version) return e.value;
const v = shared.get(subject) ?? 0;
m.cache.set(subject, { value: v, version: m.version });
return v;
} },
{ name: 'invalidate', args: rigor.gen.string(), run: (m, subject) => { m.version++; m.cache.delete(subject); return 'ok'; } },
{ name: 'set', args: rigor.gen.tuple(rigor.gen.string(), rigor.gen.int(-100, 100)), run: (m, subject, value) => { m.cache.set(subject, { value, version: m.version }); return value; } },
];
}
const QUERY_LIFECYCLE_ACTIONS = [
// A randomized get/invalidate stream: every delivered value is correct and
// every stale trigger is well-formed.
rigor.fn('query_stream', (subject, value, n, cb) => {
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, done) => done(null, value));
const q = vg.query(subject, 'v', {});
q.source.pipe(cb);
for (let i = 0; i < n; i++) {
q.sink.write({ get: true });
if (i % 2 === 1) vg.invalidate(subject, 'v', {});
}
setTimeout(() => q.sink.end(), 30);
}, rigor.args(rigor.gen.string(), rigor.gen.int(0, 100), rigor.gen.int(2, 6), rigor.handler(reducers.array()))),
// After end(), further writes are no-ops: exactly one value, then silence.
rigor.fn('query_after_end', (subject, value, cb) => {
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, done) => done(null, value));
const q = vg.query(subject, 'v', {});
q.source.pipe(cb);
q.sink.write({ get: true });
q.sink.end();
q.sink.write({ get: true });
vg.invalidate(subject, 'v', {});
q.sink.write({ get: true });
}, rigor.args(rigor.gen.string(), rigor.gen.int(0, 100), rigor.handler(reducers.array()))),
// abort() ends the source with the given error.
rigor.fn('query_abort', (subject, value, cb) => {
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, done) => done(null, value));
const q = vg.query(subject, 'v', {});
q.source.pipe(cb);
q.sink.write({ get: true });
q.sink.write({ abort: 'gone' });
}, rigor.args(rigor.gen.string(), rigor.gen.int(0, 100), rigor.handler(reducers.array()))),
];
const QUERY_LIFECYCLE_CHECKS = [
rigor.after('query_stream', (ctx) => {
const stream = ctx.actual;
if (!Array.isArray(stream) || stream.length === 0) return false;
const values = stream.filter((i) => i && i.value !== undefined);
const stales = stream.filter((i) => i && i.stale === true);
const errors = stream.filter((i) => i && i.error !== undefined);
return values.length > 0 &&
values.every((i) => i.value === ctx.args[1]) &&
stales.every((i) => i.relation === 'v') &&
errors.length === 0;
}),
rigor.after('query_after_end', (ctx) =>
Array.isArray(ctx.actual) && ctx.actual.length === 1 && ctx.actual[0].value === ctx.args[1]),
rigor.after('query_abort', (ctx) => ctx.error != null && /gone/.test(ctx.error.message)),
];
// ─────────────────────────────────────────────────────────────────────────────
// Store-fault robustness — a throwing store must never hang or crash the graph.
// ─────────────────────────────────────────────────────────────────────────────
function failingStore(opts = {}) {
return {
get: () => { if (opts.failReads) throw new Error('io read'); return undefined; },
set: () => { if (opts.failWrites) throw new Error('disk full'); },
delete: () => false,
clear: () => {},
keys: () => [],
deletePrefix: () => 0,
};
}
const STORE_FAULT_ACTIONS = [
// Store writes always fail → every get still returns the correct value
// (uncached serve), never hangs, never throws synchronously.
rigor.fn('store_write_fails', (subject, value) => {
const vg = new ValueGraph({ store: failingStore({ failWrites: true }) });
vg.compute('v', (s, p, ctx, cb) => cb(null, value));
const a = syncGet(vg, subject, 'v', {});
const b = syncGet(vg, subject, 'v', {});
return { a: a.value, b: b.value };
}, rigor.args(rigor.gen.string(), rigor.gen.int(0, 100))),
// Store writes fail the first time, then succeed → value is correct and the
// second get is served from cache.
rigor.fn('store_write_flaky', (subject, value) => {
let fail = true;
const vg = new ValueGraph({
store: { ...failingStore(), set: () => { if (fail) { fail = false; throw new Error('disk full'); } } }
});
vg.compute('v', (s, p, ctx, cb) => cb(null, value));
const a = syncGet(vg, subject, 'v', {});
const cachedAfterFirst = vg.plan(subject, 'v', {}).nodes.get('v').trivial;
const b = syncGet(vg, subject, 'v', {});
return { a: a.value, b: b.value, cachedAfterFirst };
}, rigor.args(rigor.gen.string(), rigor.gen.int(0, 100))),
// Store reads fail → get surfaces the error via cb (not a hang/throw).
rigor.fn('store_read_fails', (subject, cb) => {
const vg = new ValueGraph({ store: failingStore({ failReads: true }) });
vg.compute('v', (s, p, ctx, c) => c(null, 1));
vg.get(subject, 'v', {}, cb);
}, rigor.args(rigor.gen.string(), rigor.handler(reducers.first()))),
];
const STORE_FAULT_CHECKS = [
rigor.after('store_write_fails', (ctx) => ctx.actual.a === ctx.args[1] && ctx.actual.b === ctx.args[1]),
rigor.after('store_write_flaky', (ctx) => ctx.actual.a === ctx.args[1] && ctx.actual.b === ctx.args[1] && ctx.actual.cachedAfterFirst === false),
rigor.after('store_read_fails', (ctx) => ctx.error != null && /io read/.test(ctx.error.message)),
];
// ─────────────────────────────────────────────────────────────────────────────
// Algebraic OWA invariants, params-scoped invalidation, CSE, set→invalidate,
// and action-level fault tolerance.
// ─────────────────────────────────────────────────────────────────────────────
const ALGEBRAIC_ACTIONS = [
// Order invariance: results depend only on the multiset (sort-based), so a
// deterministic shuffle of the inputs never changes the result — any operator.
rigor.fn('owa_order_invariant', (op, values, seed) => {
const shuffled = [...values];
let s = seed >>> 0;
const rnd = () => { s = (s * 1664525 + 1013904223) >>> 0; return s / 0xffffffff; };
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(rnd() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return { a: owa(values, op, {}), b: owa(shuffled, op, {}) };
}, rigor.args(ANY_OPS, intArr, rigor.gen.int(1, 1e9))),
// Duplication invariance: duplicating every element leaves max/min/average/
// median unchanged.
rigor.fn('owa_dup_invariant', (op, values) => {
const dup = values.concat(values);
return { a: owa(values, op, {}), b: owa(dup, op, {}) };
}, rigor.args(rigor.gen.oneOf(['max', 'min', 'average', 'median']), intArr)),
// Params-scoped invalidation: invalidating {which:'a'} deletes that params
// entry; every subject/params value stays correct (version bump is
// relation-level, so the sibling also recomputes — but always correctly).
rigor.fn('params_scoped_invalidate', (va, vb) => {
let calls = 0;
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, cb) => { calls++; cb(null, p.which === 'a' ? va : vb); });
const a1 = syncGet(vg, 't', 'v', { which: 'a' }).value;
const b1 = syncGet(vg, 't', 'v', { which: 'b' }).value;
const callsBefore = calls;
vg.invalidate('t', 'v', { which: 'a' });
const aCachedAfter = vg.plan('t', 'v', { which: 'a' }).nodes.get('v').trivial;
const bCachedAfter = vg.plan('t', 'v', { which: 'b' }).nodes.get('v').trivial;
const a2 = syncGet(vg, 't', 'v', { which: 'a' }).value;
const b2 = syncGet(vg, 't', 'v', { which: 'b' }).value;
return { a1, a2, b1, b2, callsBefore, aCachedAfter, bCachedAfter };
}, rigor.args(rigor.gen.int(-100, 100), rigor.gen.int(-100, 100))),
// Shared-subtree CSE: a diamond DAG where two roots share mid node m. Across
// the first two gets AND across the post-invalidation recompute, m is computed
// exactly once each (2 total calls, not 3).
rigor.fn('shared_subtree_cse', (a, b, op) => {
let mCalls = 0;
const vg = new ValueGraph();
vg.define('a', { operator: 'source', fn: () => a });
vg.define('b', { operator: 'source', fn: () => b });
vg.define('m', { operator: 'compute', parents: ['a', 'b'], fn: (s, p, ctx, cb) => { mCalls++; cb(null, ctx.deps.a + ctx.deps.b); } });
vg.define('r1', { operator: `fusion:${op}`, parents: ['m'] });
vg.define('r2', { operator: `fusion:${op}`, parents: ['m'] });
syncGet(vg, 't', 'r1', {});
syncGet(vg, 't', 'r2', {});
const callsAfterBoth = mCalls;
vg.invalidate('t', 'a', {});
const callsAfterInvalidate = mCalls;
const r1v = syncGet(vg, 't', 'r1', {}).value;
const r2v = syncGet(vg, 't', 'r2', {}).value;
return { callsAfterBoth, callsAfterInvalidate, callsAfter: mCalls, r1v, r2v };
}, rigor.args(rigor.gen.int(-20, 20), rigor.gen.int(-20, 20), ANY_OPS)),
// set→invalidate: invalidate clears the eager value; the next get recomputes
// from the resolver, not the set value.
rigor.fn('set_then_invalidate', (value) => {
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, cb) => cb(null, 5));
vg.set('t', 'v', {}, { value });
const afterSet = syncGet(vg, 't', 'v', {}).value;
vg.invalidate('t', 'v', {});
const afterInvalidate = syncGet(vg, 't', 'v', {}).value;
return { afterSet, afterInvalidate };
}, rigor.args(rigor.gen.int(-50, 50))),
];
const ALGEBRAIC_CHECKS = [
rigor.after('owa_order_invariant', (ctx) => ctx.actual.a === ctx.actual.b || (Number.isNaN(ctx.actual.a) && Number.isNaN(ctx.actual.b))),
rigor.after('owa_dup_invariant', (ctx) => {
const a = ctx.actual.a;
const b = ctx.actual.b;
if (Number.isNaN(a) && Number.isNaN(b)) return true;
return typeof a === 'number' && typeof b === 'number' && Math.abs(a - b) < 1e-6;
}),
rigor.after('params_scoped_invalidate', (ctx) => {
const [va, vb] = ctx.args;
const { a1, a2, b1, b2, callsBefore, aCachedAfter, bCachedAfter } = ctx.actual;
return a1 === va && a2 === va && b1 === vb && b2 === vb &&
callsBefore === 2 && aCachedAfter === false && bCachedAfter === false;
}),
rigor.after('shared_subtree_cse', (ctx) => {
const { callsAfterBoth, callsAfterInvalidate, callsAfter, r1v, r2v } = ctx.actual;
return callsAfterBoth === 1 && callsAfterInvalidate === 1 && callsAfter === 2 &&
r1v === ctx.args[0] + ctx.args[1] && r2v === ctx.args[0] + ctx.args[1];
}),
rigor.after('set_then_invalidate', (ctx) => ctx.actual.afterSet === ctx.args[0] && ctx.actual.afterInvalidate === 5),
];
// Action-level fault injection: under injected faults the graph either serves the
// correct value or surfaces the error — never hangs, never corrupts.
const FAULT_ACTIONS = [
rigor.fn('cb_get_faults', (subject, value, cb) => {
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, done) => done(null, value));
vg.get(subject, 'v', {}, cb);
}, rigor.args(rigor.gen.string(), rigor.gen.int(0, 100), rigor.handler(reducers.first()))),
];
describe('Action-level fault injection', () => {
it('under injected action faults the callback contract still holds (correct-or-error, never hang)', async () => {
const vocabulary = rigor.faults([rigor.fault.at('action:cb_get_faults', { kinds: ['throw'] })]);
await expectPass('cb-faults', FAULT_ACTIONS, [
rigor.after('cb_get_faults', (ctx) => ctx.error != null || (ctx.actual && ctx.actual.value === ctx.args[1])),
], { effort: 200, faults: { enabled: true, vocabulary, maxDepth: 2 } });
});
});
// ─────────────────────────────────────────────────────────────────────────────
// Parallel firing of callback resolvers + shared-subtree CSE for ASYNC
// resolvers (the Overlay REST-call case).
// ─────────────────────────────────────────────────────────────────────────────
const PARALLEL_ACTIONS = [
// N independent async source resolvers must ALL be in flight simultaneously
// (maxInFlight === N) and each fired exactly once; the fused sum is exact.
rigor.fn('parallel_fanout', (n, value, cb) => {
let inFlight = 0;
let maxInFlight = 0;
const vg = new ValueGraph();
const parents = [];
for (let i = 0; i < n; i++) {
const rel = `p${i}`;
vg.define(rel, { operator: 'source', fn: (s, p, ctx, done) => {
inFlight++; maxInFlight = Math.max(maxInFlight, inFlight);
setTimeout(() => { inFlight--; done(null, value + i); }, 2);
} });
parents.push(rel);
}
vg.define('root', { operator: 'fusion:sum_unbounded', parents });
vg.get('t', 'root', {}, (err, entry) => {
const expected = parents.reduce((a, r, i) => a + (value + i), 0);
cb(err, { maxInFlight, value: entry ? entry.value : null, expected });
});
}, rigor.args(rigor.gen.int(2, 6), rigor.gen.int(0, 100), rigor.handler(reducers.first()))),
// Diamond DAG with async resolvers: the shared node fires exactly once (CSE
// across paths within a single run — no duplicate REST calls).
rigor.fn('shared_async_cse', (value, cb) => {
let sCalls = 0;
const vg = new ValueGraph();
vg.define('s', { operator: 'source', fn: (x, p, ctx, done) => { sCalls++; setTimeout(() => done(null, value), 2); } });
vg.define('a', { operator: 'compute', parents: ['s'], fn: (x, p, ctx, done) => { setTimeout(() => done(null, ctx.deps.s + 1), 2); } });
vg.define('b', { operator: 'compute', parents: ['s'], fn: (x, p, ctx, done) => { setTimeout(() => done(null, ctx.deps.s + 2), 2); } });
vg.define('root', { operator: 'fusion:sum_unbounded', parents: ['a', 'b'] });
vg.get('t', 'root', {}, (err, entry) => cb(err, { sCalls, value: entry ? entry.value : null }));
}, rigor.args(rigor.gen.int(0, 100), rigor.handler(reducers.first()))),
// A shared subtree whose shared node is itself non-trivial with async parents:
// waiter fan-out must join, not re-fire, at every level.
rigor.fn('shared_async_deep', (value, cb) => {
let sCalls = 0;
let mCalls = 0;
const vg = new ValueGraph();
vg.define('s', { operator: 'source', fn: (x, p, ctx, done) => { sCalls++; setTimeout(() => done(null, value), 2); } });
vg.define('m', { operator: 'compute', parents: ['s'], fn: (x, p, ctx, done) => { mCalls++; setTimeout(() => done(null, ctx.deps.s * 2), 2); } });
vg.define('r1', { operator: 'compute', parents: ['m'], fn: (x, p, ctx, done) => { setTimeout(() => done(null, ctx.deps.m + 1), 2); } });
vg.define('r2', { operator: 'compute', parents: ['m'], fn: (x, p, ctx, done) => { setTimeout(() => done(null, ctx.deps.m + 2), 2); } });
vg.define('root', { operator: 'fusion:sum_unbounded', parents: ['r1', 'r2'] });
vg.get('t', 'root', {}, (err, entry) => cb(err, { sCalls, mCalls, value: entry ? entry.value : null }));
}, rigor.args(rigor.gen.int(0, 100), rigor.handler(reducers.first()))),
];
const PARALLEL_CHECKS = [
rigor.after('parallel_fanout', (ctx) =>
ctx.error == null && ctx.actual.maxInFlight === ctx.args[0] && ctx.actual.value === ctx.actual.expected),
rigor.after('shared_async_cse', (ctx) =>
ctx.error == null && ctx.actual.sCalls === 1 && ctx.actual.value === (ctx.args[0] + 1) + (ctx.args[0] + 2)),
rigor.after('shared_async_deep', (ctx) =>
ctx.error == null && ctx.actual.sCalls === 1 && ctx.actual.mCalls === 1 &&
ctx.actual.value === (ctx.args[0] * 2 + 1) + (ctx.args[0] * 2 + 2)),
];
// ─────────────────────────────────────────────────────────────────────────────
// Typed values — a DSL-declared returnType is enforced on set and resolver
// results.
// ─────────────────────────────────────────────────────────────────────────────
const TYPED_ACTIONS = [
rigor.fn('typed_set_ok', (value) => {
const vg = new ValueGraph();
vg.define('n', { operator: 'source', returnType: 'number', fn: () => null });
vg.set('t', 'n', {}, { value });
return syncGet(vg, 't', 'n', {}).value;
}, rigor.args(rigor.gen.int(-1000, 1000))),
rigor.fn('typed_set_reject', (value) => {
const vg = new ValueGraph();
vg.define('s', { operator: 'source', returnType: 'string', fn: () => null });
try { vg.set('t', 's', {}, { value }); return { ok: true }; }
catch (e) { return { ok: false, error: e.message }; }
}, rigor.args(rigor.gen.int(0, 100))),
rigor.fn('typed_resolver_reject', (value) => {
const vg = new ValueGraph();
vg.define('s', { operator: 'source', returnType: 'string', fn: () => value });
return tryGet(vg, 't', 's', {});
}, rigor.args(rigor.gen.int(0, 100))),
];
const TYPED_CHECKS = [
rigor.after('typed_set_ok', (ctx) => ctx.actual === ctx.args[0]),
rigor.after('typed_set_reject', (ctx) => ctx.actual.ok === false && /must match declared type 'string'/.test(ctx.actual.error)),
rigor.after('typed_resolver_reject', (ctx) => ctx.actual.ok === false && /must match declared type 'string'/.test(ctx.actual.error)),
];
// NaN is not a meaningful derived value — it must be rejected at both the set
// and resolver-result boundaries (it would poison downstream OWA).
const NAN_ACTIONS = [
rigor.fn('nan_set_reject', () => {
const vg = new ValueGraph();
try { vg.set('t', 'v', {}, { value: Number.NaN }); return { ok: true }; }
catch (e) { return { ok: false, msg: e.message }; }
}, rigor.args()),
rigor.fn('nan_resolver_reject', () => {
const vg = new ValueGraph();
vg.compute('v', () => Number.NaN);
return tryGet(vg, 't', 'v', {});
}, rigor.args()),
rigor.fn('nan_interval_ok', (a, b) => {
const vg = new ValueGraph();
vg.define('est', { operator: 'source', returnType: 'interval', fn: () => null });
vg.set('t', 'est', {}, { value: { lower: a, upper: b } });
return syncGet(vg, 't', 'est', {}).value;
}, rigor.args(rigor.gen.int(-50, 50), rigor.gen.int(-50, 50))),
];
const NAN_CHECKS = [
rigor.after('nan_set_reject', (ctx) => ctx.actual.ok === false && /requires a value/.test(ctx.actual.msg)),
rigor.after('nan_resolver_reject', (ctx) => ctx.actual.ok === false && /must return a value/.test(ctx.actual.error)),
rigor.after('nan_interval_ok', (ctx) => ctx.actual.lower === ctx.args[0] && ctx.actual.upper === ctx.args[1]),
];
// Race safety: a slow read (async resolver) racing an authoritative set() or an
// invalidate() must not clobber the write or cache a pre-mutation snapshot.
// ─────────────────────────────────────────────────────────────────────────────
// Stateful rigor.object protocol campaign, concurrent direct gets, stream
// error pushes, contract edges, and a benchmark smoke.
// ─────────────────────────────────────────────────────────────────────────────
function graphFacade(value) {
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, cb) => cb(null, value));
return {
vg,
factoryValue: value,
lastSet: null,
get() { const e = syncGet(vg, 't', 'v', {}); return e ? e.value : null; },
set(v) { this.lastSet = v; vg.set('t', 'v', {}, { value: v }); return v; },
invalidate() { this.lastSet = null; vg.invalidate('t', 'v', {}); return 'ok'; },
clone() { const c = graphFacade(value); c.lastSet = this.lastSet; return c; }
};
}
const CONTRACT_EDGE_ACTIONS = [
// N concurrent direct gets on the SAME relation (async resolver): every get
// succeeds independently, each fires its own run, all values correct.
rigor.fn('concurrent_gets', (value, n, cb) => {
let calls = 0;
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, done) => { calls++; setTimeout(() => done(null, value), 1); });
const results = [];
let remaining = n;
for (let i = 0; i < n; i++) {
vg.get('t', 'v', {}, (err, e) => {
results.push(err ? 'ERR' : (e ? e.value : null));
if (--remaining === 0) cb(null, { calls, results });
});
}
}, rigor.args(rigor.gen.int(0, 100), rigor.gen.int(2, 5), rigor.handler(reducers.first()))),
// A run error inside a query duplex is pushed down as { error }.
rigor.fn('query_error_push', (subject, cb) => {
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, done) => { done(new Error('boom')); });
const q = vg.query(subject, 'v', {});
q.source.pipe(cb);
q.sink.write({ get: true });
setTimeout(() => q.sink.end(), 20);
}, rigor.args(rigor.gen.string(), rigor.handler(reducers.array()))),
// set() rejects unsupported value shapes with the documented error.
rigor.fn('err_set_invalid', () => {
const vg = new ValueGraph();
try {
vg.set('t', 'v', {}, { value: { nested: true } });
return { ok: true };
} catch (e) {
return { ok: false, error: e.message };
}
}, rigor.args()),
// A resolver returning { value, unit, source } propagates the metadata onto
// the delivered entry, and does not mutate its input.
rigor.fn('resolver_meta', (value, cb) => {
const vg = new ValueGraph();
vg.define('v', { operator: 'source', fn: (s, p, ctx, done) => done(null, { value, unit: 'usd_cents', source: 'overlay:balances' }) });
vg.get('t', 'v', {}, (err, e) => cb(err, { value: e && e.value, unit: e && e.unit, source: e && e.source }));
}, rigor.args(rigor.gen.int(0, 100), rigor.handler(reducers.first()))),
// Benchmark smoke: get on a tiny graph is fast.
rigor.fn('bench_get', (value) => {
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, cb) => cb(null, value));
const e = syncGet(vg, 't', 'v', {});
return e ? e.value : null;
}, rigor.args(rigor.gen.int(0, 100))),
];
const CONTRACT_EDGE_CHECKS = [
rigor.after('concurrent_gets', (ctx) =>
ctx.error == null && ctx.actual.results.length === ctx.args[1] &&
ctx.actual.calls === ctx.args[1] &&
ctx.actual.results.every((v) => v === ctx.args[0])),
rigor.after('query_error_push', (ctx) =>
Array.isArray(ctx.actual) && ctx.actual.some((i) => i && i.error && /boom/.test(i.error))),
rigor.after('err_set_invalid', (ctx) => ctx.actual.ok === false && /requires a value/.test(ctx.actual.error)),
rigor.after('resolver_meta', (ctx) =>
ctx.error == null && ctx.actual.value === ctx.args[0] &&
ctx.actual.unit === 'usd_cents' && ctx.actual.source === 'overlay:balances'),
rigor.benchmark('bench_get', { p50: { max: 5, unit: 'ms' }, p95: { max: 50, unit: 'ms' }, p99: { max: 200, unit: 'ms' } }),
];
describe('Stateful protocol (rigor.object) + benchmark', () => {
it('a rigor.object protocol campaign: after set(v) every get returns v until invalidate', async () => {
const obj = rigor.object('graph', () => graphFacade(7), [
rigor.method('get', function () { return this.get(); }),
rigor.method('set', function (v) { return this.set(v); }, rigor.args(rigor.gen.int(0, 100))),
rigor.method('invalidate', function () { return this.invalidate(); }),
]);
const checks = [
rigor.after('graph.get', (ctx) => ctx.error == null && typeof ctx.actual === 'number'),
rigor.after('graph.set', (ctx) => {
const g = ctx.objects.graph;
return g.get() === ctx.args[0];
}),
rigor.between('graph.set', 'graph.invalidate', (ctx) => {
if (ctx.action !== 'graph.get') return true;
const g = ctx.objects.graph;
return g.lastSet === null || ctx.actual === g.lastSet;
}),
rigor.before('graph.set', (ctx) => ctx.objects.graph !== undefined),
];
await expectPass('object-protocol', [obj], checks, { effort: 150 });
});
it('get on a tiny graph meets a loose latency bound (benchmark smoke)', async () => {
await expectPass('benchmark', [CONTRACT_EDGE_ACTIONS[4]], [CONTRACT_EDGE_CHECKS[4]], { effort: 60 });
});
});
describe('Graph caching model (rigor.model)', () => {
it('get/set/invalidate/mutate conformance with the reference caching model', async () => {
const shared = new Map();
const result = rigor.model.check('graph-cache', { version: 0, cache: new Map() }, makeGraphSut(shared), {
operations: graphCacheModelOps(shared),
effort: 300,
maxSequenceLength: 50,
seed: 'graph-cache',
});
if (result.status !== 'passed') {
const detail = (result.failures || []).slice(0, 5).map((f) =>
JSON.stringify({ seq: f.sequence, at: f.commandIndex, expected: f.expected, actual: f.actual, shrunk: f.shrunk }));
throw new Error(`graph-cache model failed.\n${detail.join('\n')}`);
}
});
});
// ─────────────────────────────────────────────────────────────────────────────
// Mega-campaigns — the remaining plain-invariant families consolidated into two
// combined campaigns (many crucible arrays each; every check action-gated).
// ─────────────────────────────────────────────────────────────────────────────
const RACE_ACTIONS = [
rigor.fn('race_set_preserved', (value, cb) => {
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, done) => { setTimeout(() => done(null, 10), 5); });
const slow = new Promise((res) => vg.get('t', 'v', {}, (e, v) => res(e ? null : (v && v.value))));
vg.set('t', 'v', {}, { value });
slow.then(() => vg.get('t', 'v', {}, (e, v) => cb(e, v ? v.value : null)));
}, rigor.args(rigor.gen.int(0, 100), rigor.handler(reducers.first()))),
rigor.fn('race_invalidate_recomputes', (a, b, cb) => {
let state = a;
const vg = new ValueGraph();
vg.compute('v', (s, p, ctx, done) => { const snap = state; setTimeout(() => done(null, snap), 5); });
const slow = new Promise((res) => vg.get('t', 'v', {}, (e, v) => res(e ? null : (v && v.value))));
state = b;
vg.invalidate('t', 'v', {});
vg.get('t', 'v', {}, (e, v) => {
const after = v ? v.value : null;
slow.then(() => vg.get('t', 'v', {}, (e2, v2) => cb(e2, { after, final: v2 ? v2.value : null })));
});
}, rigor.args(rigor.gen.int(0, 100), rigor.gen.int(0, 100), rigor.handler(reducers.first()))),
rigor.fn('set_notifies_watchers', (value, cb) => {
const vg = new ValueGraph();
vg.compute('v', () => 1);
const q = vg.query('t', 'v', {});
const events = [];
q.source.pipe({ write: (x) => events.push(x), paused: false, ended: false, source: null, end: () => {}, abort: () => {} });
q.sink.write({ get: true });
vg.set('t', 'v', {}, { value });
setTimeout(() => { q.sink.end(); cb(null, events); }, 20);
}, rigor.args(rigor.gen.int(0, 100), rigor.handler(reducers.first()))),
];
const RACE_CHECKS = [
rigor.after('race_set_preserved', (ctx) => ctx.error == null && ctx.actual === ctx.args[0]),
rigor.after('race_invalidate_recomputes', (ctx) =>
ctx.error == null && ctx.actual.after === ctx.args[1] && ctx.actual.final === ctx.args[1]),
rigor.after('set_notifies_watchers', (ctx) =>
ctx.error == null && ctx.actual.some((e) => e && e.stale === true && e.relation === 'v' && e.via === undefined)),
];
describe('Mega: graph semantics (sync)', () => {
it('semantics + oracles + universal + optimize + typed + NaN + algebraic + random-DAG + errors + fusion-DAGs (ONE campaign)', async () => {
await expectPass('mega-graph-sync',
[
...GRAPH_ACTIONS,
...MORE_ACTIONS,
...UNIVERSAL_ACTIONS.slice(2),
...OPTIMIZE_ACTIONS,
...TYPED_ACTIONS,
...NAN_ACTIONS,
ALGEBRAIC_ACTIONS[2], ALGEBRAIC_ACTIONS[3], ALGEBRAIC_ACTIONS[4],
TOPOLOGY_ACTIONS[0],
...ERROR_ACTIONS,
...FUSION_ACTIONS,
],
GRAPH_CHECKS,
MORE_CHECKS,
UNIVERSAL_CHECKS.slice(2),
OPTIMIZE_CHECKS,
TYPED_CHECKS,
NAN_CHECKS,
[ALGEBRAIC_CHECKS[2], ALGEBRAIC_CHECKS[3], ALGEBRAIC_CHECKS[4]],
TOPOLOGY_CHECKS,
FUSION_CHECKS,
{ effort: 2200 });
});
});
describe('Mega: graph async + handlers + store faults + streams', () => {
it('callback contract + parallel/async CSE + races + store faults + lifecycle + contract edges (ONE campaign)', async () => {
await expectPass('mega-graph-async',
[
...CB_ACTIONS,
...STORE_FAULT_ACTIONS,
...QUERY_LIFECYCLE_ACTIONS,
...PARALLEL_ACTIONS,
...RACE_ACTIONS,
CONTRACT_EDGE_ACTIONS[0], CONTRACT_EDGE_ACTIONS[1], CONTRACT_EDGE_ACTIONS[2], CONTRACT_EDGE_ACTIONS[3],
],
CB_CHECKS,
STORE_FAULT_CHECKS,
QUERY_LIFECYCLE_CHECKS,
PARALLEL_CHECKS,
RACE_CHECKS,
[CONTRACT_EDGE_CHECKS[0], CONTRACT_EDGE_CHECKS[1], CONTRACT_EDGE_CHECKS[2], CONTRACT_EDGE_CHECKS[3]],
{ effort: 1200 });
});
});
// ─────────────────────────────────────────────────────────────────────────────
// Snapshot persistence & transport — compact portable binary round-trips.
// ─────────────────────────────────────────────────────────────────────────────
const SNAPSHOT_VAL = rigor.gen.oneOf(
rigor.gen.int(-1000, 1000),
rigor.gen.float(0, 100, { fractionDigits: 3 }),
rigor.gen.string(0, 12),
rigor.gen.boolean()
);
const SNAPSHOT_VALUE = rigor.gen.oneOf(
SNAPSHOT_VAL,
rigor.gen.array(SNAPSHOT_VAL, 0, 5),
rigor.gen.record({ lower: rigor.gen.float(-100, 100), upper: rigor.gen.float(-100, 100) })
);
const SNAPSHOT_ACTIONS = [
// Wire format: any typed value survives encodeValue/decodeValue exactly.
// (Synchronous actions: NO rigor.handler — a handler would wait on a
// callback this action never calls. rigor now settles a sync-returning
// action with a declared handler instead of hanging, but we keep the
// actions honest: no handler on sync actions.)
rigor.fn('value_wire_roundtrip', (value) => {
const buf = encodeSnapshot([['k', { value, unit: 'u', at: 1234, source: 's', version: 2, deps: {} }]]);
const decoded = decodeSnapshot(buf);
return { input: value, output: decoded.entries[0][1].value };
}, rigor.args(SNAPSHOT_VALUE)),
// Graph transport: set → snapshot → restore into a FRESH graph → get.
rigor.fn('graph_snapshot_roundtrip', (value) => {
const vg = new ValueGraph({ defaultTTL: 0 });
vg.define('v', { operator: 'source', fn: () => null });
vg.set('t:1', 'v', { k: 1 }, { value });
const restored = ValueGraph.restore(vg.snapshot());
let out;
restored.get('t:1', 'v', { k: 1 }, (e, r) => { if (!e) out = r ? r.value : null; });
return { input: value, output: out };
}, rigor.args(SNAPSHOT_VALUE)),
// THE transportability invariant: flipping ANY byte of a snapshot must NEVER
// silently decode — parse-guard or CRC-guard rejects every corruption.
rigor.fn('snapshot_flip_never_silent', (index, bit) => {
const vg = new ValueGraph({ defaultTTL: 0 });
vg.set('t', 'v', {}, { value: 1234 });
vg.set('t', 's', {}, { value: 'x' });
const buf = vg.snapshot();
const flipped = Buffer.from(buf);
const i = index % flipped.length;
flipped[i] = flipped[i] ^ (1 << (bit % 8));
let silentlyDecoded = false;
try { decodeGraphSnapshot(flipped); silentlyDecoded = true; } catch (e) { /* expected */ }
return { silent: silentlyDecoded, index: i };
}, rigor.args(rigor.gen.int(0, 256), rigor.gen.int(0, 7))),
];
const SNAPSHOT_CHECKS = [
rigor.after('value_wire_roundtrip', (ctx) =>
ctx.error == null && deepEqualValues(ctx.actual.input, ctx.actual.output)),
rigor.after('graph_snapshot_roundtrip', (ctx) =>
ctx.error == null && deepEqualValues(ctx.actual.input, ctx.actual.output)),
rigor.after('snapshot_flip_never_silent', (ctx) => ctx.error == null && ctx.actual.silent === false),
];
// Deep equality across number/string/boolean/array/interval.
function deepEqualValues(a, b) {
if (typeof a === 'bigint' || typeof b === 'bigint') return a === b;
if (Array.isArray(a) && Array.isArray(b)) {
return a.length === b.length && a.every((x, i) => deepEqualValues(x, b[i]));
}
if (a !== null && b !== null && typeof a === 'object' && typeof b === 'object') {
const ka = Object.keys(a);
const kb = Object.keys(b);
return ka.length === kb.length && ka.every((k) => deepEqualValues(a[k], b[k]));
}
return a === b;
}
describe('Snapshot persistence & transport (rigor)', () => {
it('wire round-trips are exact; restored graphs serve stored values; corruption NEVER decodes silently', async () => {
await expectPass('snapshot-transport', SNAPSHOT_ACTIONS, SNAPSHOT_CHECKS, { effort: 800 });
});
});