Files
value-graph/test/value-graph.rigor.test.js
T
Dvorak 1da6bc7842
CI / test (push) Successful in 20s
CI / publish (push) Has been skipped
value-graph 0.1.0: JSON-free, bigint-free binary snapshot layer
- src/snapshot.js: compact portable wire (VGST store / VGGP graph), binary maps for
  params/pattern/objects (no JSON), buffer values (no JS bigint), CRC-32 guarded.
- createFileStore: binary per-key files (encodeEntryBytes/decodeEntryBytes).
- ValueGraph snapshot/snapshotFile/restore/loadFile; keys base64url(params-bytes).
- 60 tests (node:test + rigor incl. 'flip ANY byte never decodes silently').
2026-08-04 17:37:00 -07:00

2018 lines
99 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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 });
});
});