1.13.0: measure + evidence compose; value-graph now a registry dep
- 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:
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user