John Dvorak f0aefe4ba6 rigor: complexity-class + benchmark crucibles over complex graphs
Activates js-rigor's normally-dormant complexity and benchmark verdicts
against the complex-graph generators:

COMPLEXITY (e-process verified, deterministic cost signals):
- direct/chain/ttu lookups declared O(1) in graph size — verified with a
  deterministic engine-lookup counter as the cost metric (wall-clock at
  sub-ms scale is pure jitter for the spread check); a regression to
  linear scans would grow the counter with n and trip the e-process
- union evaluated O(k) in rule count in normal mode vs O(1) in binary
  mode (threshold early exit) — declared cost = rule evaluations

BENCHMARK (percentile assertions over auto-collected samples):
- direct/chain/ttu single-check actions stay under p50=0.2ms p95=1.0ms
  budgets on the complex graphs

Debugging along the way surfaced two rigor API facts worth pinning:
metric readers receive the raw generated-args ARRAY (fns get the spread
values), and a missing module import silently degrades actions into
'no observations' vacuous verdicts (ReferenceError swallowed by the
runner). Full rigor 237/237.
2026-08-02 14:20:50 -07:00

@arbiter/core

Possibilistic authorization engine: graph indices, relation/reachability queries, rule evaluation over a DSL, and lossless condensed snapshots.

Why

Authorization policies live on a graph: users hold relations to objects, groups, and roles, and rules derive decisions from those relations. @arbiter/core answers one question — may user U perform relation R on object O? — with a possibility ranking, not a boolean. Callers supply evidence with strengths; the engine fuses it through rule operators (union, intersection, exclusion, defeasible, chain, multi-hop, relational comparator) and returns the strongest derivable possibility, the reliability of the decision, and the validity provenance of the ranking.

The engine does not police caller-supplied evidence: you supply validated relation strengths and proofs; the engine derives and fuses. It is a library, not a service — no storage, no transport, no policy source of truth.

Install

npm install @arbiter/core

The package is ESM-only and requires Node 22 or newer.

Quick Start

import { Arbiter } from '@arbiter/core';

const arbiter = new Arbiter();

// Nodes: an id and a type.
arbiter.addNode('user:1', 'user');
arbiter.addNode('doc:9', 'doc');

// Relations: a config says how decisions for that relation are derived.
arbiter.setRelationConfig('owner', { type: 'direct' });
arbiter.addRelation('user:1', 'owner', 'doc:9', { possibility: 0.9 });

// The core question.
const result = arbiter.check('user:1', 'owner', 'doc:9');
// { possibility: 0.9, reliability: 1,
//   validity: { label: 'heuristic', operator: 'identity', regime: 'arbitrary' },
//   reason: 'direct_match' }

Concepts

Possibility, not probability

Every check returns a possibility in [0, 1] — a maxitive ranking supplied by the caller through relation strengths. 1 means derivable, 0 means not derivable. Fusions take the maximum under union, and enforce thresholds and conflicts under intersection, exclusion, and defeasible operators.

Result shape

Every check result carries the same four fields:

Field Type Meaning
possibility number in [0,1] Derived possibility of the decision
reliability number in [0,1] Reliability of the decision; always 0 for denials
validity object Validity provenance: label, operator, regime (minimal form)
reason string Outcome class: direct_match, no_relation, threshold_not_met, missing_node, no_config, cycle, ...

Denied decisions never leak a source's reliability. includeMeta: true adds meta with the full provenance (allow/deny blocks, rule traces, thresholds) and the full validity block (sources, conflictMass, validifiedPossibility, nonMaxitive).

The caller owns evidence and time

  • Evidence: relation strengths and validity labels come from the caller. The engine derives and fuses but never judges.
  • Time: TTL-gated evidence uses the caller's clock. Pass { now } (or partialGraph.now) to pin the temporal context; a rerun with the same context reproduces the decision.

Overlays and partial graphs

check accepts a PartialGraphContext overlay. Overlay relations take precedence over the base graph, letting you answer "what changes if this evidence appears?" without mutating the graph.

API

The public surface is the Arbiter class:

  • Graph: addNode, addRelation, removeRelation, setRelationConfig, getNodeData, resolveNodeId, resolveKey
  • Check: check(userKey, relation, objectKey, options), explain (enriches meta), binary mode (fast path, marks results binary: true)
  • Reachability: isReachable, getReachableNodes, getReachingNodes, shortestPathLength, estimateGraphDistance (isReachable returns null when no PLTC index is available — a signal to defer to rule evaluation)
  • Snapshots: enableCondensedSnapshot, toSnapshotBinary, Arbiter.fromSnapshotBinary (lossless: carries relation metadata, TTLs, and validity; malformed buffers fail fast with clean errors)
  • Value context: valueManager (TTL-gated evidence), getSituationTree, monteCarloWalk

Check options:

Option Type Default Meaning
includeMeta boolean false Attach full provenance in meta
explain boolean false Enrich meta with the evaluation trace
binary boolean false Binary fast path; results marked binary: true
now number engine clock Pinned temporal context for TTL gates
partialGraphContext PartialGraphContext none Overlay taking precedence over the base graph

Development

npm install          # install dependencies
npm test             # full suite
npm run test:rigor   # js-rigor campaigns (property-based + fuzzing)
npm run benchmark    # compare against the committed perf baseline
npm run benchmark:save  # record a new perf baseline

CI runs the full suite, the rigor campaigns, and the benchmark on every push; v* tags additionally publish the package to the @arbiter registry.

Design Notes

  • Possibility is a maxitive ranking. Fusions preserve the weakest validity label under arbitrary dependence; conjunctive operators surface conflict mass instead of silently averaging it.
  • One evaluation path. The rule engine has a single, uncompiled evaluator — parity between normal, binary, partial-graph, and snapshot-restored checks is structural, and the rigor campaigns enforce it.
  • Snapshots are a trust boundary. Restoring untrusted bytes must produce a clean, bounded error — never a hang, a crash, or silently corrupted data. The deserializer cross-validates every count field before use.
S
Description
Arbiter core engine: graph indices, authorization rule evaluator, DSL/AST, condensed & sharded snapshots, evidence fusion.
Readme 2.6 MiB
Languages
JavaScript 100%