/** * @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 }); }); });