1.13.0: measure + evidence compose; value-graph now a registry dep
CI / test (push) Successful in 24s
CI / publish (push) Failing after 10s

- DSLValueGraph integrates the evidence DSL with @arbiter/value-graph: measures
  become typed value-graph nodes; attach() wires runtime.measure through the graph.
- DSLRuntime.check() now retrieves required MEASURE values and injects them as
  value-carrying self-edges, so an evidence comparator over a measure (e.g.
  budget_used(user) <= budget_limit(user)) evaluates — measure and evidence
  compose (provider-sourced AND value-graph-sourced), with 3 new composition tests.
- BUILTIN_TYPES: bigint → buffer (the value-graph wire is JSON-free/bigint-free).
- @arbiter/value-graph: file:../value-graph → ^0.1.0 (registry); CI auth adds
  @push-stream-std registry for the transitive dep.
- rigor core ^3.1.2 / probe ^0.0.8.
This commit is contained in:
2026-08-04 17:51:13 -07:00
parent b145c979ab
commit 7898a7990a
14 changed files with 1421 additions and 87 deletions
+62
View File
@@ -12,6 +12,8 @@ import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { Arbiter } from '@arbiter/core';
import { DSLCompiler } from '../src/DSLCompiler.js';
import { DSLRuntime } from '../src/runtime/DSLRuntime.js';
import { DSLValueGraph } from '../src/value-graph/DSLValueGraph.js';
const DEFS = `
definition Employee { id: string }
@@ -151,4 +153,64 @@ describe('Evidence composition', () => {
assert.equal(arb.check('u:1', 'can_read', 'doc:9').possibility, 0.5);
assert.equal(arb.check('u:1', 'can_browse', 'doc:9').possibility, 0.5);
});
describe('measure + evidence composition', () => {
const M_DEFS = `
definition Employee { id: string }
measure budget_used(user: Employee) { } PROVIDES number
measure budget_limit(user: Employee) { } PROVIDES number
evidence can_use(user: Employee, doc: string) { budget_used(user) <= budget_limit(user) }
`;
it('compiles an evidence comparator over measure valueRelations', () => {
const { arb, result } = compile(M_DEFS, 'm+e-compile');
assert.ok(result.success, JSON.stringify(result.errors));
const cfg = arb.relationConfigs.get('can_use');
assert.equal(cfg.type, 'relational_comparator');
assert.equal(cfg.left.valueRelation, 'budget_used');
assert.equal(cfg.right.valueRelation, 'budget_limit');
assert.deepEqual(cfg.dependsOn, ['budget_used', 'budget_limit']);
assert.equal(cfg._needsValues, true);
});
it('evaluates with measure values from registered providers (measure → evidence)', async () => {
const rt = new DSLRuntime(new Arbiter()).compile(M_DEFS, 'm+e-provider');
const dvg = new DSLValueGraph(rt);
dvg.attach(); // wires runtime.measure through the value-graph
rt.registerMeasure('budget_used', async () => ({ value: 900 }));
rt.registerMeasure('budget_limit', async () => ({ value: 1000 }));
rt.arbiter.addNode('u:1', 'Employee');
// Both systems live: the measure resolves standalone...
assert.equal((await rt.measure('budget_used', { user: 'u:1' })).value, 900);
// ...and composes into the evidence comparator.
const allow = await rt.check('u:1', 'can_use', 'doc:9');
assert.equal(allow.possibility, 1);
assert.equal(allow.reason, 'allow_rule_matched');
assert.ok(allow.providedFacts.includes('budget_used'), 'measure injected as a partial-graph edge');
});
it('evaluates with measure values stored in the value-graph (setValue)', async () => {
const rt = new DSLRuntime(new Arbiter()).compile(M_DEFS, 'm+e-vg');
const dvg = new DSLValueGraph(rt);
dvg.attach();
dvg.setValue('budget_used', { __subject: 'u:1', user: 'u:1' }, 900);
dvg.setValue('budget_limit', { __subject: 'u:1', user: 'u:1' }, 1000);
rt.arbiter.addNode('u:1', 'Employee');
const allow = await rt.check('u:1', 'can_use', 'doc:9');
assert.equal(allow.possibility, 1, 'in-budget evidence allowed from graph-sourced measures');
// Over budget → the comparator denies.
const rt2 = new DSLRuntime(new Arbiter()).compile(M_DEFS, 'm+e-vg2');
const dvg2 = new DSLValueGraph(rt2);
dvg2.attach();
dvg2.setValue('budget_used', { __subject: 'u:1', user: 'u:1' }, 1200);
dvg2.setValue('budget_limit', { __subject: 'u:1', user: 'u:1' }, 1000);
rt2.arbiter.addNode('u:1', 'Employee');
const deny = await rt2.check('u:1', 'can_use', 'doc:9');
assert.equal(deny.possibility, 0);
assert.equal(deny.reason, 'values_compared_comparison_false');
});
});
});