/** * tests/rigor/dsl-value-graph-robustness.test.js — js-rigor invariants for the * DSLValueGraph wrapper's robustness: attach() semantics, resolver failure modes, * stored-value lifecycle (overwrite / invalidation isolation / TTL), binding * edge cases, strict-mode behavior, and concurrent retrieval. */ import { describe, it } from 'node:test'; import { rigor, reducers } from '@rigor/core'; import { Arbiter } from '@arbiter/core'; import { ValueGraph } from '@arbiter/value-graph'; import { DSLRuntime, DSLValueGraph } from '../../src/index.js'; const DSL = ` definition Tenant { id: string } measure budget(tenant: Tenant, feature: string) { } PROVIDES number measure label(user: string) { } PROVIDES string `; function makeDvg() { const rt = new DSLRuntime(new Arbiter()).compile(DSL, 'vg-robust'); return new DSLValueGraph(rt); } async function expectPass(name, actions, checks, config = {}) { const report = await rigor.campaign(actions, rigor.crucible(checks)) .run({ seed: `dsl-vg-robust-${name}`, effort: 100, artifacts: { dir: '', persist: 'never' }, ...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 })); throw new Error(`campaign '${name}' failed.\n${detail.join('\n')}`); } return report; } describe('DSLValueGraph robustness (rigor)', () => { it('attach() is idempotent and a manual registerMeasure overrides it', async () => { const actions = [ rigor.fn('attach_idempotent', (value, cb) => { const rt = new DSLRuntime(new Arbiter()).compile(DSL, 'r'); const dvg = new DSLValueGraph(rt); dvg.attach(); dvg.attach(); // idempotent dvg.setValue('budget', { __subject: 't', tenant: 't', feature: 'x' }, value); rt.measure('budget', { __subject: 't', tenant: 't', feature: 'x' }).then( (r) => cb(null, r.value), (e) => cb(e) ); }, rigor.args(rigor.gen.int(0, 100), rigor.handler(reducers.first()))), rigor.fn('attach_override', (cb) => { const rt = new DSLRuntime(new Arbiter()).compile(DSL, 'r'); const dvg = new DSLValueGraph(rt); dvg.attach(); rt.registerMeasure('budget', async () => ({ value: 12345, unit: 'manual' })); rt.measure('budget', { __subject: 't', tenant: 't', feature: 'x' }).then( (r) => cb(null, r.value), (e) => cb(e) ); }, rigor.args(rigor.handler(reducers.first()))), ]; const checks = [ rigor.after('attach_idempotent', (ctx) => ctx.error == null && ctx.actual === ctx.args[0]), rigor.after('attach_override', (ctx) => ctx.error == null && ctx.actual === 12345), ]; await expectPass('attach', actions, checks); }); it('resolver failure modes: sync-return, error propagation, wrong-typed result', async () => { const actions = [ rigor.fn('resolver_sync', (value, cb) => { const dvg = makeDvg(); dvg.resolve('budget', (s, p) => value * 2); // sync return dvg.measure('budget', { __subject: 't', tenant: 't', feature: 'x' }).then( (r) => cb(null, r.value), (e) => cb(e) ); }, rigor.args(rigor.gen.int(0, 50), rigor.handler(reducers.first()))), rigor.fn('resolver_error', (cb) => { const dvg = makeDvg(); dvg.resolve('budget', (s, p, ctx, done) => { done(new Error('provider down')); }); dvg.measure('budget', { __subject: 't', tenant: 't', feature: 'x' }).then( (r) => cb(null, { ok: true, v: r.value }), (e) => cb(null, { ok: false, msg: e.message }) ); }, rigor.args(rigor.handler(reducers.first()))), rigor.fn('resolver_wrong_type', (cb) => { const dvg = makeDvg(); dvg.resolve('budget', (s, p, ctx, done) => { done(null, 'not-a-number'); }); dvg.measure('budget', { __subject: 't', tenant: 't', feature: 'x' }).then( (r) => cb(null, { ok: true, v: r.value }), (e) => cb(null, { ok: false, msg: e.message }) ); }, rigor.args(rigor.handler(reducers.first()))), ]; const checks = [ rigor.after('resolver_sync', (ctx) => ctx.error == null && ctx.actual === ctx.args[0] * 2), rigor.after('resolver_error', (ctx) => ctx.actual.ok === false && /provider down/.test(ctx.actual.msg)), rigor.after('resolver_wrong_type', (ctx) => ctx.actual.ok === false && /must match declared type 'number'/.test(ctx.actual.msg)), ]; await expectPass('resolvers', actions, checks); }); it('stored-value lifecycle: overwrite wins, cross-measure invalidation is isolated, TTL expires', async () => { const actions = [ rigor.fn('overwrite', (a, b) => { const dvg = makeDvg(); dvg.setValue('budget', { __subject: 't', tenant: 't', feature: 'x' }, a); dvg.setValue('budget', { __subject: 't', tenant: 't', feature: 'x' }, b); // last-write-wins return dvg.getValue('budget', { __subject: 't', tenant: 't', feature: 'x' }).value; }, rigor.args(rigor.gen.int(0, 100), rigor.gen.int(0, 100))), rigor.fn('cross_measure_invalidate', (a, b) => { const dvg = makeDvg(); dvg.setValue('budget', { __subject: 't', tenant: 't', feature: 'x' }, a); dvg.setValue('label', { __subject: 't', user: 'u' }, String(b)); dvg.vg.invalidate('t', 'budget', { tenant: 't', feature: 'x' }); const budget = dvg.getValue('budget', { __subject: 't', tenant: 't', feature: 'x' }); const label = dvg.getValue('label', { __subject: 't', user: 'u' }); return { budget: budget === null ? 'null' : budget.value, label: label.value }; }, rigor.args(rigor.gen.int(0, 100), rigor.gen.int(0, 100))), rigor.fn('ttl_expiry', (value, cb) => { let t = 0; const rt = new DSLRuntime(new Arbiter()).compile(DSL, 'r'); const dvg = new DSLValueGraph(rt, { valueGraph: new ValueGraph({ clock: () => t, defaultTTL: 100 }) }); dvg.setValue('budget', { __subject: 't', tenant: 't', feature: 'x' }, value); const before = dvg.getValue('budget', { __subject: 't', tenant: 't', feature: 'x' }).value; t = 200; const after = dvg.getValue('budget', { __subject: 't', tenant: 't', feature: 'x' }); cb(null, { before, after: after === null ? 'null' : after.value }); }, rigor.args(rigor.gen.int(0, 100), rigor.handler(reducers.first()))), ]; const checks = [ rigor.after('overwrite', (ctx) => ctx.actual === ctx.args[1]), rigor.after('cross_measure_invalidate', (ctx) => ctx.actual.budget === 'null' && ctx.actual.label === String(ctx.args[1])), rigor.after('ttl_expiry', (ctx) => ctx.actual.before === ctx.args[0] && ctx.actual.after === 'null'), ]; await expectPass('lifecycle', actions, checks); }); it('binding edge cases: array params, extra keys preserved, positional arity, unit/source', async () => { const actions = [ rigor.fn('array_param', (arr, cb) => { const rt = new DSLRuntime(new Arbiter()).compile(`measure tags(user: string, xs: array) { } PROVIDES number`, 'r'); const dvg = new DSLValueGraph(rt); dvg.setValue('tags', { __subject: 't', user: 'u', xs: arr }, 7); const e = dvg.getValue('tags', { __subject: 't', user: 'u', xs: arr }); cb(null, e ? e.value : 'MISSING'); }, rigor.args(rigor.gen.array(rigor.gen.int(0, 9), 0, 4), rigor.handler(reducers.first()))), rigor.fn('extra_keys', (v) => { const dvg = makeDvg(); dvg.setValue('budget', { __subject: 't', tenant: 't', feature: 'x', extra: 'zzz' }, v); return dvg.getValue('budget', { __subject: 't', tenant: 't', feature: 'x', extra: 'zzz' }).value; }, rigor.args(rigor.gen.int(0, 100))), rigor.fn('positional_arity', (v) => { const dvg = makeDvg(); dvg.setValue('budget', ['t', 'x'], v); return dvg.getValue('budget', ['t', 'x']).value; }, rigor.args(rigor.gen.int(0, 100))), rigor.fn('unit_preserved', (v) => { const dvg = makeDvg(); dvg.setValue('budget', { __subject: 't', tenant: 't', feature: 'x' }, v, { unit: 'tokens', source: 'ledger' }); const e = dvg.getValue('budget', { __subject: 't', tenant: 't', feature: 'x' }); return { value: e.value, unit: e.unit, source: e.source }; }, rigor.args(rigor.gen.int(0, 100))), ]; const checks = [ rigor.after('array_param', (ctx) => ctx.error == null && ctx.actual === 7), rigor.after('extra_keys', (ctx) => ctx.actual === ctx.args[0]), rigor.after('positional_arity', (ctx) => ctx.actual === ctx.args[0]), rigor.after('unit_preserved', (ctx) => ctx.actual.value === ctx.args[0] && ctx.actual.unit === 'tokens' && ctx.actual.source === 'ledger'), ]; await expectPass('bindings', actions, checks); }); it('strict:false degrades gracefully for unknown measures; strict mode throws', async () => { const actions = [ rigor.fn('strict_false', (cb) => { const rt = new DSLRuntime(new Arbiter()).compile(DSL, 'r'); const dvg = new DSLValueGraph(rt, { strict: false }); dvg.setValue('nope', {}, 1); // no-op dvg.resolve('nope', () => 1); // no-op const g = dvg.getValue('nope', {}); // null dvg.measure('nope', {}).then( (r) => cb(null, { get: g, measure: r.value }), () => cb(null, { get: g, measure: 'rejected' }) ); }, rigor.args(rigor.handler(reducers.first()))), rigor.fn('no_value_null', () => { const dvg = makeDvg(); return dvg.getValue('budget', { __subject: 't', tenant: 't', feature: 'x' }) === null ? 'null' : 'value'; }, rigor.args()), ]; const checks = [ rigor.after('strict_false', (ctx) => ctx.actual.get === null && ctx.actual.measure === null), rigor.after('no_value_null', (ctx) => ctx.actual === 'null'), ]; await expectPass('strict', actions, checks); }); it('concurrent measure retrievals all resolve to the same stored value', async () => { const actions = [ rigor.fn('concurrent_measure', (value, cb) => { const dvg = makeDvg(); dvg.setValue('budget', { __subject: 't', tenant: 't', feature: 'x' }, value); const N = 5; const calls = []; for (let i = 0; i < N; i++) { calls.push(dvg.measure('budget', { __subject: 't', tenant: 't', feature: 'x' }).then((r) => r.value)); } Promise.all(calls).then((values) => cb(null, values), (e) => cb(e)); }, rigor.args(rigor.gen.int(0, 100), rigor.handler(reducers.first()))), ]; const checks = [ rigor.after('concurrent_measure', (ctx) => ctx.error == null && Array.isArray(ctx.actual) && ctx.actual.length === 5 && ctx.actual.every((v) => v === ctx.args[0])), ]; await expectPass('concurrent', actions, checks); }); it('stored values and resolvers coexist per subject (stored wins on its own subject)', async () => { const actions = [ rigor.fn('subject_resolver_mix', (a, b, cb) => { const dvg = makeDvg(); dvg.setValue('budget', { __subject: 'A', tenant: 'A', feature: 'x' }, a); dvg.resolve('budget', (s) => b); dvg.measure('budget', { __subject: 'A', tenant: 'A', feature: 'x' }).then( (r1) => dvg.measure('budget', { __subject: 'B', tenant: 'B', feature: 'x' }).then( (r2) => cb(null, { a: r1.value, b: r2.value }), (e) => cb(e) ), (e) => cb(e) ); }, rigor.args(rigor.gen.int(0, 100), rigor.gen.int(0, 100), rigor.handler(reducers.first()))), ]; const checks = [ rigor.after('subject_resolver_mix', (ctx) => ctx.error == null && ctx.actual.a === ctx.args[0] && ctx.actual.b === ctx.args[1]), ]; await expectPass('subject-resolver-mix', actions, checks); }); it('final confirmation: positional arity enforced, zero-param measures, control keys stripped', async () => { const actions = [ rigor.fn('arity_too_many', (v) => { const dvg = makeDvg(); try { dvg.setValue('budget', ['t', 'x', 'extra'], v); return { ok: true }; } catch (e) { return { ok: false, msg: e.message }; } }, rigor.args(rigor.gen.int(0, 100))), rigor.fn('arity_too_few', (v) => { const dvg = makeDvg(); try { dvg.setValue('budget', ['t'], v); return { ok: true }; } catch (e) { return { ok: false, msg: e.message }; } }, rigor.args(rigor.gen.int(0, 100))), rigor.fn('arity_exact', (v) => { const dvg = makeDvg(); dvg.setValue('budget', ['t', 'x'], v); return dvg.getValue('budget', ['t', 'x']).value; }, rigor.args(rigor.gen.int(0, 100))), rigor.fn('zero_param_measure', (v) => { const rt = new DSLRuntime(new Arbiter()).compile(`measure heartbeat() { } PROVIDES number`, 'r'); const dvg = new DSLValueGraph(rt); dvg.setValue('heartbeat', {}, v); const e = dvg.getValue('heartbeat', {}); return e ? e.value : 'MISSING'; }, rigor.args(rigor.gen.int(0, 100))), rigor.fn('actor_stripped', (v) => { const dvg = makeDvg(); // __actor is a control key → stripped from the cache key, so differing // actors hit the SAME stored entry. dvg.setValue('budget', { __actor: 'actor:1', __subject: 't', tenant: 't', feature: 'x' }, v); return dvg.getValue('budget', { __actor: 'actor:2', __subject: 't', tenant: 't', feature: 'x' }).value; }, rigor.args(rigor.gen.int(0, 100))), ]; const checks = [ rigor.after('arity_too_many', (ctx) => ctx.actual.ok === false && /expects 2 positional argument\(s\), got 3/.test(ctx.actual.msg)), rigor.after('arity_too_few', (ctx) => ctx.actual.ok === false && /expects 2 positional argument\(s\), got 1/.test(ctx.actual.msg)), rigor.after('arity_exact', (ctx) => ctx.actual === ctx.args[0]), rigor.after('zero_param_measure', (ctx) => ctx.actual === ctx.args[0]), rigor.after('actor_stripped', (ctx) => ctx.actual === ctx.args[0]), ]; await expectPass('final-confirm', actions, checks); }); it('hardening: retrieval paths, sync-throw resolvers, unregister, param-driven resolvers, NaN', async () => { const actions = [ rigor.fn('get_sync_vs_measure_async', (value, cb) => { const dvg = makeDvg(); dvg.resolve('budget', (s, p, ctx, done) => { setTimeout(() => done(null, value), 1); }); const syncPath = dvg.getValue('budget', { __subject: 't', tenant: 't', feature: 'x' }); dvg.measure('budget', { __subject: 't', tenant: 't', feature: 'x' }).then( (r) => cb(null, { sync: syncPath === null ? 'null' : syncPath.value, async: r.value }), (e) => cb(e) ); }, rigor.args(rigor.gen.int(0, 100), rigor.handler(reducers.first()))), rigor.fn('resolver_throws_sync', (cb) => { const dvg = makeDvg(); dvg.resolve('budget', () => { throw new Error('boom'); }); dvg.measure('budget', { __subject: 't', tenant: 't', feature: 'x' }).then( (r) => cb(null, { ok: true, v: r.value }), (e) => cb(null, { ok: false, msg: e.message }) ); }, rigor.args(rigor.handler(reducers.first()))), rigor.fn('unregister_after_attach', (cb) => { const rt = new DSLRuntime(new Arbiter()).compile(DSL, 'r'); const dvg = new DSLValueGraph(rt); dvg.attach(); rt.unregisterMeasure('budget'); rt.measure('budget', { __subject: 't', tenant: 't', feature: 'x' }).then( (r) => cb(null, { ok: true, v: r.value }), (e) => cb(null, { ok: false, msg: e.message }) ); }, rigor.args(rigor.handler(reducers.first()))), rigor.fn('resolver_from_params', (x, y, cb) => { const dvg = makeDvg(); dvg.resolve('budget', (s, p, ctx, done) => done(null, p.feature === 'a' ? x : y)); dvg.measure('budget', { __subject: 't', tenant: 't', feature: 'a' }).then( (r1) => dvg.measure('budget', { __subject: 't', tenant: 't', feature: 'b' }).then( (r2) => cb(null, { a: r1.value, b: r2.value }), (e) => cb(e) ), (e) => cb(e) ); }, rigor.args(rigor.gen.int(0, 100), rigor.gen.int(0, 100), rigor.handler(reducers.first()))), rigor.fn('nan_rejected', () => { const dvg = makeDvg(); try { dvg.setValue('budget', { __subject: 't', tenant: 't', feature: 'x' }, Number.NaN); return { ok: true }; } catch (e) { return { ok: false, msg: e.message }; } }, rigor.args()), ]; const checks = [ rigor.after('get_sync_vs_measure_async', (ctx) => ctx.error == null && ctx.actual.sync === 'null' && ctx.actual.async === ctx.args[0]), rigor.after('resolver_throws_sync', (ctx) => ctx.actual.ok === false && /boom/.test(ctx.actual.msg)), rigor.after('unregister_after_attach', (ctx) => ctx.actual.ok === false && /no provider registered/.test(ctx.actual.msg)), rigor.after('resolver_from_params', (ctx) => ctx.error == null && ctx.actual.a === ctx.args[0] && ctx.actual.b === ctx.args[1]), rigor.after('nan_rejected', (ctx) => ctx.actual.ok === false && /must match declared type 'number'/.test(ctx.actual.msg)), ]; await expectPass('hardening', actions, checks); }); });