214 lines
6.1 KiB
JavaScript
214 lines
6.1 KiB
JavaScript
|
|
import { performance } from 'node:perf_hooks';
|
||
|
|
import { Arbiter } from '../src/core/Arbiter.js';
|
||
|
|
import { validateClaimsForLayer } from '../src/core/partial-graph/layer-registry.js';
|
||
|
|
|
||
|
|
function percentile(sorted, p) {
|
||
|
|
if (!sorted.length) return 0;
|
||
|
|
const idx = Math.min(sorted.length - 1, Math.max(0, Math.floor(sorted.length * p) - 1));
|
||
|
|
return sorted[idx];
|
||
|
|
}
|
||
|
|
|
||
|
|
function summarize(name, durations) {
|
||
|
|
const sorted = [...durations].sort((a, b) => a - b);
|
||
|
|
let total = 0;
|
||
|
|
for (let i = 0; i < durations.length; i++) total += durations[i];
|
||
|
|
const avg = total / durations.length;
|
||
|
|
const p95 = percentile(sorted, 0.95);
|
||
|
|
const p99 = percentile(sorted, 0.99);
|
||
|
|
console.log(`${name}: avg=${avg.toFixed(4)}ms p95=${p95.toFixed(4)}ms p99=${p99.toFixed(4)}ms`);
|
||
|
|
}
|
||
|
|
|
||
|
|
function runBenchmark(name, iterations, fn) {
|
||
|
|
const warmup = Math.min(1000, Math.max(100, Math.floor(iterations / 10)));
|
||
|
|
for (let i = 0; i < warmup; i++) fn();
|
||
|
|
|
||
|
|
const durations = new Array(iterations);
|
||
|
|
for (let i = 0; i < iterations; i++) {
|
||
|
|
const start = performance.now();
|
||
|
|
fn();
|
||
|
|
durations[i] = performance.now() - start;
|
||
|
|
}
|
||
|
|
summarize(name, durations);
|
||
|
|
}
|
||
|
|
|
||
|
|
function setupBusinessArbiter() {
|
||
|
|
const arbiter = new Arbiter({
|
||
|
|
partialGraphPolicy: {
|
||
|
|
conflict_mode: 'deterministic',
|
||
|
|
reducers: {
|
||
|
|
request_has_timestamp: 'latest',
|
||
|
|
caller_risk_hint: 'strongest'
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
arbiter.addNode('user:1', 'user');
|
||
|
|
arbiter.addNode('group:1', 'group');
|
||
|
|
arbiter.addNode('mid:1', 'intermediate');
|
||
|
|
arbiter.addNode('doc:1', 'document');
|
||
|
|
arbiter.addNode('feature:1', 'feature');
|
||
|
|
arbiter.addNode('value:1', 'value');
|
||
|
|
|
||
|
|
arbiter.setRelationConfig('request_has_timestamp', {
|
||
|
|
type: 'direct',
|
||
|
|
partial_graph: { reducer: 'latest' }
|
||
|
|
});
|
||
|
|
arbiter.setRelationConfig('caller_risk_hint', {
|
||
|
|
type: 'direct',
|
||
|
|
partial_graph: { reducer: 'strongest' }
|
||
|
|
});
|
||
|
|
arbiter.setRelationConfig('request_has_id', { type: 'direct' });
|
||
|
|
|
||
|
|
arbiter.setRelationConfig('can_chain', {
|
||
|
|
type: 'chain',
|
||
|
|
steps: [
|
||
|
|
{ relation: 'request_has_timestamp', direction: 'out' },
|
||
|
|
{ relation: 'caller_risk_hint', direction: 'out' }
|
||
|
|
]
|
||
|
|
});
|
||
|
|
|
||
|
|
arbiter.setRelationConfig('can_reach', {
|
||
|
|
type: 'multi_hop',
|
||
|
|
relation: 'caller_risk_hint',
|
||
|
|
maxDepth: 3
|
||
|
|
});
|
||
|
|
|
||
|
|
arbiter.setRelationConfig('can_access', {
|
||
|
|
type: 'tuple_to_userset',
|
||
|
|
tuplesetRelation: 'request_has_id',
|
||
|
|
computedRelation: 'request_has_timestamp',
|
||
|
|
reverse: false
|
||
|
|
});
|
||
|
|
|
||
|
|
arbiter.setRelationConfig('can_pay', {
|
||
|
|
type: 'relational_comparator',
|
||
|
|
left: {
|
||
|
|
rule: { type: 'direct', relation: 'request_has_timestamp' },
|
||
|
|
extractValue: true,
|
||
|
|
aggregation: 'max',
|
||
|
|
decayRate: 0,
|
||
|
|
decayFunction: 'rational'
|
||
|
|
},
|
||
|
|
right: {
|
||
|
|
rule: { type: 'direct', relation: 'request_has_id', evaluateFrom: 'object' },
|
||
|
|
extractValue: true,
|
||
|
|
aggregation: 'min',
|
||
|
|
decayRate: 0,
|
||
|
|
decayFunction: 'rational',
|
||
|
|
evaluateFrom: 'object'
|
||
|
|
},
|
||
|
|
comparator: '>=',
|
||
|
|
fallbackBehavior: 'deny'
|
||
|
|
});
|
||
|
|
|
||
|
|
arbiter.addRelation('group:1', 'caller_risk_hint', 'doc:1', 0.9);
|
||
|
|
arbiter.addRelation('feature:1', 'request_has_id', 'feature:1', { value: 50, possibility: 1 });
|
||
|
|
|
||
|
|
const partialGraph = {
|
||
|
|
options: {
|
||
|
|
reducers: {
|
||
|
|
request_has_timestamp: 'latest',
|
||
|
|
caller_risk_hint: 'strongest'
|
||
|
|
}
|
||
|
|
},
|
||
|
|
relations: [
|
||
|
|
{
|
||
|
|
src: 'user:1',
|
||
|
|
relation: 'request_has_timestamp',
|
||
|
|
dst: 'group:1',
|
||
|
|
possibility: 0.95,
|
||
|
|
value: 120,
|
||
|
|
updated_last_at: 100,
|
||
|
|
layer_name: 'request_observed',
|
||
|
|
source_class: 'gateway_observed'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
src: 'user:1',
|
||
|
|
relation: 'request_has_timestamp',
|
||
|
|
dst: 'group:1',
|
||
|
|
possibility: 0.2,
|
||
|
|
value: 20,
|
||
|
|
updated_last_at: 200,
|
||
|
|
layer_name: 'request_observed',
|
||
|
|
source_class: 'gateway_observed'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
src: 'group:1',
|
||
|
|
relation: 'caller_risk_hint',
|
||
|
|
dst: 'doc:1',
|
||
|
|
possibility: 0.8,
|
||
|
|
layer_name: 'caller_declared',
|
||
|
|
source_class: 'caller_input'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
src: 'doc:1',
|
||
|
|
relation: 'request_has_id',
|
||
|
|
dst: 'group:1',
|
||
|
|
possibility: 1,
|
||
|
|
layer_name: 'request_observed'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
src: 'user:1',
|
||
|
|
relation: 'request_has_timestamp',
|
||
|
|
dst: 'feature:1',
|
||
|
|
value: 20,
|
||
|
|
possibility: 1,
|
||
|
|
updated_last_at: 200,
|
||
|
|
layer_name: 'request_observed'
|
||
|
|
}
|
||
|
|
]
|
||
|
|
};
|
||
|
|
|
||
|
|
return { arbiter, partialGraph };
|
||
|
|
}
|
||
|
|
|
||
|
|
function run() {
|
||
|
|
const { arbiter, partialGraph } = setupBusinessArbiter();
|
||
|
|
|
||
|
|
const conformantClaims = [
|
||
|
|
{ relation: 'from_ip', object: 'ip:10.0.0.1' },
|
||
|
|
{ relation: 'request_has_id', object: 'request:abc' }
|
||
|
|
];
|
||
|
|
const nonConformantClaims = [
|
||
|
|
{ relation: 'delegated_authority', object: 'resource:x' }
|
||
|
|
];
|
||
|
|
|
||
|
|
console.log('Business operations benchmark');
|
||
|
|
runBenchmark('business.layer_conformance.accept', 15000, () => {
|
||
|
|
validateClaimsForLayer('request_observed', conformantClaims);
|
||
|
|
});
|
||
|
|
runBenchmark('business.layer_conformance.reject', 15000, () => {
|
||
|
|
validateClaimsForLayer('caller_declared', nonConformantClaims);
|
||
|
|
});
|
||
|
|
|
||
|
|
runBenchmark('business.partial_graph_policy.snapshot', 15000, () => {
|
||
|
|
arbiter.getPartialGraphPolicySnapshot();
|
||
|
|
});
|
||
|
|
|
||
|
|
runBenchmark('business.auth.direct_partial', 12000, () => {
|
||
|
|
arbiter.check('user:1', 'request_has_timestamp', 'group:1', { partialGraph });
|
||
|
|
});
|
||
|
|
runBenchmark('business.auth.chain_partial', 12000, () => {
|
||
|
|
arbiter.check('user:1', 'can_chain', 'doc:1', { partialGraph });
|
||
|
|
});
|
||
|
|
runBenchmark('business.auth.multi_hop_partial', 12000, () => {
|
||
|
|
arbiter.check('user:1', 'can_reach', 'doc:1', { partialGraph });
|
||
|
|
});
|
||
|
|
runBenchmark('business.auth.tuple_to_userset_partial', 12000, () => {
|
||
|
|
arbiter.check('user:1', 'can_access', 'doc:1', { partialGraph });
|
||
|
|
});
|
||
|
|
runBenchmark('business.auth.relational_comparator_partial', 12000, () => {
|
||
|
|
arbiter.check('user:1', 'can_pay', 'feature:1', { partialGraph });
|
||
|
|
});
|
||
|
|
|
||
|
|
runBenchmark('business.explain.chain_debug', 6000, () => {
|
||
|
|
arbiter.explain('user:1', 'can_chain', 'doc:1', {
|
||
|
|
partialGraph,
|
||
|
|
includeMeta: true,
|
||
|
|
collectValues: true
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
run();
|