# @arbiter/core — API Reference > The complete public interface of the Arbiter engine. For the package overview, quick start, and concepts, see the [README](../README.md). This document is the authoritative reference: every public export, method signature, option, result shape, and configuration format. ## Contents 1. [Exports](#1-exports) 2. [The `Arbiter` class](#2-the-arbiter-class) 3. [`check` options and result shape](#3-check-options-and-result-shape) 4. [Relation configurations](#4-relation-configurations) 5. [The `valueManager`](#5-the-valuemanager) 6. [Partial graphs](#6-partial-graphs) 7. [Snapshots](#7-snapshots) 8. [Reachability](#8-reachability) 9. [The `OWAFusion` utility](#9-the-owafusion-utility) 10. [Audit hook](#10-audit-hook) 11. [Errors](#11-errors) 12. [Reason codes](#12-reason-codes) 13. [Validity taxonomy](#13-validity-taxonomy) --- ## 1. Exports ```js import { Arbiter, // the engine — everything hangs off this GraphIndices, // persistent-relation index structure NodeManager, // node lifecycle RelationManager, // relation lifecycle + value access GraphData, GraphOperations, GraphAnalysis, GraphManager, AuthorizationChecker, // the check() implementation (low-level) RuleEvaluator, // rule dispatch RuleCollector, // rule flattening OWAFusion // interval-fusion utilities } from '@arbiter/core'; ``` In normal use you only need `Arbiter`. The other exports exist for advanced composition and are documented inline in source. --- ## 2. The `Arbiter` class ### `new Arbiter(options?)` **Parameters:** | Option | Type | Default | Description | |--------|------|---------|-------------| | `disableCaching` | `boolean` | `false` | Disable the direct-check, rule-result, and chain caches | | `disableChainCaching` | `boolean` | `false` | Disable only the chain result cache | | `directCheckCacheSize` | `number` | `10000` | Direct-check cache capacity | | `directCheckCacheTTL` | `number` (ms) | `60000` | Direct-check entry freshness | | `ruleResultCacheTTL` | `number` (ms) | `60000` | Rule-result entry freshness | | `enableRuleResultCache` | `boolean` | `true` | Master switch for the rule-result cache | | `clock` | `() => number` | `Date.now` | Time source for **unpinned** cache freshness. Per-check time always comes from `{ now }` / `partialGraph.now` | | `cacheFactory` | `function` | built-in LRU | DI hook for a custom cache implementation | | `audit` | `(record) => void` | `null` | Audit hook, invoked once per check (see [§10](#10-audit-hook)) | | `partialGraphPolicy` | `object` | — | Overlay admission limits (see [§6](#6-partial-graphs)) | | `keyManager` | `object` | built-in | Advanced: custom id/key mapping | ### Graph and relation management | Method | Signature | Description | |--------|-----------|-------------| | `addNode` | `(key, type, data?) => void` | Create a node. `key` is a string, `type` is a free-form string | | `getNodeData` | `(key) => object` | Node metadata | | `updateNodeData` | `(key, data) => void` | Patch node metadata | | `setRelationConfig` | `(relation, config) => void` | Define how a relation derives decisions (see [§4](#4-relation-configurations)). Re-defining invalidates caches for that relation | | `addRelation` | `(srcKey, relation, dstKey, options?) => void` | Add an edge. `options`: `{ possibility, reliability, value, validity, changed_last_at, decayConfig, source, layer_name }` | | `removeRelation` | `(srcKey, relation, dstKey) => void` | Remove an edge. Immediately revokes grants (no stale cache) | | `resolveNodeId` | `(key) => number \| undefined` | Key → numeric id (`undefined` when unknown) | | `resolveKey` | `(id) => string \| undefined` | Numeric id → key | | `invalidateRuleResultCacheByRelation` | `(relation) => void` | Drop cached rule results for a relation | | `invalidateAllRuleResultCache` | `() => void` | Drop all cached rule results | **Write semantics.** `addRelation` upserts: re-adding an existing `(src, relation, dst)` edge updates it. `changed_last_at` defaults to the write time when not supplied; values decay and expire against it (see [§5](#5-the-valuemanager)). All mutations invalidate the affected caches immediately — a removed edge's grant is gone on the next `check`. ### Check | Method | Signature | Description | |--------|-----------|-------------| | `check` | `(userKey, relation, objectKey, options?) => result` | The core question: derive the decision | | `explain` | `(userKey, relation, objectKey, options?) => result` | Alias for `check(..., { explain: true })` — enriches `meta` with the evaluation trace | See [§3](#3-check-options-and-result-shape) for options and result shape. ### Graph algorithms | Method | Signature | Description | |--------|-----------|-------------| | `estimateGraphDistance` | `(key1, key2) => number` | Estimated distance | | `shortestPathLength` | `(key1, key2) => number` | Exact shortest path | | `walk` | `(startKey, steps, stepFn?, neighborWeightsFn?) => void` | Traverse with a visitor | | `monteCarloWalk` | `(startKey, steps, options?) => void` | Randomized traversal | | `getSituationTree` | `(nodeId, maxDepth?) => tree` | Neighborhood snapshot | | `getReachableNodes` | `(sourceKey, maxResults?) => string[]` | Nodes reachable from source | | `getReachingNodes` | `(targetKey, maxResults?) => string[]` | Nodes that reach target | ### Reachability | Method | Signature | Description | |--------|-----------|-------------| | `initializeReachabilityChecker` | `(options?) => Promise` | Build the PLTC index (async) | | `isReachable` | `(sourceKey, targetKey) => boolean \| null` | `true`/`false` when the PLTC index decides; `null` = deferred to rule evaluation (documented contract, see [§8](#8-reachability)) | | `getReachabilityStats` | `() => object` | PLTC hit/miss/query statistics | | `adaptToFalsePositive` | `(sourceKey, targetKey, falsePositiveKey, conflictingKey) => void` | PLTC adaptation | | `getAdaptationStats` | `() => object` | Adaptation statistics | ### Snapshots | Method | Signature | Description | |--------|-----------|-------------| | `enableCondensedSnapshot` | `(options?) => void` | Switch to condensed binary mode. **Permanently makes the engine read-only** (writes throw) — call it last | | `toSnapshotBinary` | `(options?) => ArrayBuffer` | Serialize the graph | Restore is a static: `Arbiter.fromSnapshotBinary(buffer)` (see [§7](#7-snapshots)). --- ## 3. `check` options and result shape ### Options | Option | Type | Default | Meaning | |--------|------|---------|---------| | `binary` | `boolean` | `false` | Binary fast path with strict thresholds; results carry `binary: true` | | `includeMeta` | `boolean` | `false` | Attach full provenance in `meta` | | `explain` | `boolean` | `false` | Enrich `meta` with the evaluation trace | | `now` | `number` (ms) | engine clock | **Pinned temporal context.** Every TTL gate, decay computation, and proof expiry honors it; a rerun with the same `now` reproduces the decision | | `partialGraph` | `object` | `null` | Raw overlay spec — re-ingested into a `PartialGraphContext` (see [§6](#6-partial-graphs)) | | `partialGraphContext` | `PartialGraphContext` | `null` | Pre-built overlay context (preferred when reusing one) | | `collectValues` | `boolean` | `false` | Collect value evidence into `collectedValues` | | `returnHints` | `boolean` | `false` | On denial, attach `hints` (alternative access paths) | | `simpleBinary` | `boolean` | `false` | With `binary`, return the minimal `{ allow, deny, reason }` shape | | `fastPath` | `boolean` | `false` | Enable early-exit thresholds in rule evaluation | | `minAllowPossibility` / `maxDenyPossibility` | `number` | `0.8` / `0.8` | Binary-mode decision thresholds | | `clientStateId` | `string` | `null` | Pass-through client identifier | | `epsilon` / `delta` | `number` | — | Pass-through numerical tolerances | ### Result shape Every check returns the same core shape: ```js { possibility: 0.9, // number in [0,1] — the derived decision strength reliability: 1, // number in [0,1] — 0 for denials validity: { label: 'heuristic', // one of the validity taxonomy (see §13) operator: 'identity', // the fusion operator applied regime: 'arbitrary' // dependence assumption // includeMeta adds: sources[], conflictMass, validifiedPossibility, nonMaxitive }, reason: 'direct_match', // outcome class (see §12) // optional: meta: { ... }, // includeMeta / explain collectedValues: [...], // collectValues hints: [...], // returnHints on denial binary: true, // binary mode allow: true, // binary mode deny: false // binary mode } ``` **Contract invariants** (enforced by the rigor suite, `tests/rigor/public-contract.test.js`): - `possibility` and `reliability` are always finite numbers in `[0, 1]`. - Denials (`possibility === 0`) carry `reliability === 0` — a denied decision never leaks a source's reliability. - `reason` is always a known outcome class. - The result is JSON-serializable — no circular references, no undefined values. - `includeMeta` enriches `meta` but never changes the decision fields. ### Collected values ```js { value: 42, // the relation's value (or an interval for blurred paths) possibility: 0.9, path: ['u:1', 'doc:9'], // entity keys along the derivation source: { entityKey: 'u:1', relation: 'can_read', step: 0 }, metadata: { timestamp, reliability, ... } } ``` Collected values are **TTL-gated**: an expired value is never collected (see [§5](#5-the-valuemanager)). --- ## 4. Relation configurations `setRelationConfig(relation, config)` defines how decisions for a relation are derived. The config object is also the DSL's AST — it is compiled (validated) on registration; compile errors land in `config._compileErrors`. ### Direct ```js { type: 'direct' } ``` A single edge grants with exactly its possibility. The fastest path. ### Tuple-to-userset (group membership) ```js { type: 'tuple_to_userset', tuplesetRelation: 'owner', // object → intermediate computedRelation: 'member', // user → intermediate reverse: false, minPossibility: 0, owaWeights: [1, 0, ...], // fusion weights (default = max) earlyExitThreshold: 0.95, maxIntermediates: 20 } ``` User `u` gains `relation` on object `o` when there exists an intermediate `m` with `u -computedRelation-> m` and `m -tuplesetRelation-> o`. ### Chain (multi-step traversal) ```js { type: 'chain', steps: [ { relation: 'member_of', direction: 'out' }, { relation: 'owner', direction: 'out' } ], collectValues: false, valueAggregation: 'sum', // 'sum' | 'max' | 'min' | 'average' valueFilters: { relations: [...], minValue, maxValue } } ``` Possibility along a chain = min of the edge possibilities (weakest link); parallel paths disjunct (max). ### Multi-hop (path search) ```js { type: 'multi_hop', relation: 'owner', // relation to traverse maxDepth: 5, pathAggregation: 'max', // 'max' | 'sum' | 'owa' reverse: false, fallbackToBasicPaths: true, owaWeights: [...], collectValues: true, valueFilters: { relations: [...], minValue, maxValue }, valueAggregation: 'sum' } ``` Unbounded-depth path search over a single relation. ### Defeasible logic ```js { type: 'defeasible', when: , // grants unless: , // defeats when it fires never: , // absolute veto always: , // unconditional grant requires: // precondition } ``` Rules compose: `{ when, unless }` = "grants unless defeated"; `{ always, unless }` = "grants unconditionally unless defeated"; `{ never }` = absolute deny. ### Logical operators ```js { union: [rule, rule, ...] } // disjunction — max { intersection: [rule, rule, ...] } // conjunction — min / conflict-aware { exclusion: [grantRule, denyRule] } // grant minus deny ``` Union configs additionally support OWA fusion: ```js { union: { rules: [...], aggregator: 'owa', owaWeights: [0.7, 0.3], useBilattice: false, epistemicMode: 'hybrid' } } ``` Aggregators: `max`, `min`, `owa`, `top2`, `median`, `sum`. ### Relational comparator (ABAC) ```js { type: 'relational_comparator', comparator: '>=', // any JS comparison operator fallbackBehavior: 'deny', // 'deny' | 'allow' | 'unknown' left: { rule: , // e.g. { type: 'direct', relation: 'has_balance' } extractValue: true, valueRelation: 'has_balance', evaluateFrom: 'user', // 'user' | 'object' aggregator: 'owa', owaWeights: [...] }, right: { rule: , extractValue: true, valueRelation: 'min_age', evaluateFrom: 'object' } } ``` Compares value evidence from two operands (supports nested rules and OWA fusion of multi-source values). **Value-freshness gated**: expired operands deny. ### Qualitative relational comparator ```js { type: 'relational_comparator', qualitative: true, // routes to the qualitative implementation comparator: '>=', left: { rule: , scaleName: 'five-point', decayPeriod: 'HOUR', decaySteps: 1, ... }, right: { rule: , ... }, marginSteps: 0 } ``` The router (`RelationalComparatorRouter`) sends a comparator to the qualitative implementation when `rule.qualitative === true` or when either operand carries a `scaleName`. Qualitative scales decay: possibility decays over time periods, values blur by steps — all against the caller's pinned clock. ### Challenge (MFA-style) ```js { type: 'challenge', challenge: 'mfa', // proof name; defaults to rule name/relation withinMs: 300000 // proof freshness window } ``` Grants when the partial graph carries a proof for `challenge` within `withinMs`. Missing proofs produce structured `remediation` in the result. ### Parent ```js { type: 'parent', parentRelation: 'parent', relation: 'owner' } ``` Grants when the object's parent (via `parentRelation`) holds `relation` for the user. ### Computed ```js { type: 'computed', relation: 'has_balance' } ``` Defers to the referenced relation's own config. ### Composition Any rule position accepts a config object, so policies nest: unions of chains, exclusions of comparators, defeasible wrapping unions, etc. The `union` operator is flattened during collection (max semantics preserved); `intersection`/`exclusion` preserve their structure. --- ## 5. The `valueManager` Accessible as `arbiter.valueManager`. Values are the *evidence* attached to edges; TTL and decay govern their freshness. | Method | Signature | Description | |--------|-----------|-------------| | `setTTL` | `(relationType, ttlMs) => void` | Configure value freshness for a relation | | `getTTL` | `(relationType) => number` | Current TTL (default `24h`) | | `setDecayConfig` | `(relationType, config) => void` | Possibility/interval decay over time | | `getDecayConfig` | `(relationType) => config` | Current decay config | | `setDefaultDecayConfig` | `(config) => void` | Global decay default | | `getBlurredValue` | `(relation, now?) => { interval, possibility, reliability }` | TTL-gated value interval. `interval: null` when the value is absent or **expired** | | `getDecayedRelation` | `(relation, now?) => { pointValue, blurredInterval, currentPossibility, ... }` | Decay-aware value | | `invalidateCache` | `(keys) => void` | Drop cached value computations | | `refreshStaleValues` | `(count) => number` | Background recomputation of stale values | | `aggregateCrispValues` | `(values, aggregator?) => number` | Aggregate point values | | `aggregateBlurredValues` | `(values, aggregator?) => interval` | Aggregate intervals | | `compareIntervals` | `(left, right, comparator, epsilon?) => number` | Interval comparison | **TTL contract** (pinned by `tests/rigor/ttl-contract.test.js`): - TTL is a **value-freshness** gate, not an access-expiry mechanism. A direct relation's grant is timeless — `setTTL('can_read', ...)` does not expire the grant. - Expired values: deny comparators, drop from collected values, return `interval: null` from `getBlurredValue`. - Expiry is evaluated against the caller's pinned `now` when provided; the wall clock only applies to unpinned callers. --- ## 6. Partial graphs An overlay of caller-supplied evidence evaluated *on top of* the persistent graph. The caller provides facts — relations and proofs — that the check consumes as if they were persistent. ### Raw spec form ```js const result = arbiter.check(u, 'can_read', o, { partialGraph: { now: 1_700_000_000_000, // the overlay's temporal context relations: [ { src: 'u:1', relation: 'owner', dst: 'doc:9', possibility: 0.9, value: 42, layer_name: 'witness' } ], nodes: [{ key: 'u:1', type: 'user' }], challenges: [{ name: 'mfa', subject: 'u:1', issuedAt: 1_700_000_000_000, expiresAt: 1_700_000_300_000 }] // 'challengeProofs' is accepted as an alias for 'challenges' } }); ``` The raw form is validated against `partialGraphPolicy` before allocation (a DoS guard: `maxRelations` default `2000`, `maxNodes` default `1000`). `partialGraph.now`, when present, becomes the check's temporal context (it feeds `options.now`). ### Pre-built context form ```js import { PartialGraphContext } from '@arbiter/core'; const ctx = new PartialGraphContext(arbiter, { now: 1_700_000_000_000, relations: [{ src: 'u:1', relation: 'owner', dst: 'doc:9', possibility: 0.9 }] }); const result = arbiter.check(u, 'can_read', o, { partialGraphContext: ctx }); ``` **Use `partialGraphContext` for a pre-built context.** Passing a context under `partialGraph` re-ingests it as a raw spec (it would be treated as an empty overlay). **Precedence contract** (pinned by `tests/rigor/overlay-precedence.test.js`): persistent facts win over overlay facts for the same `(src, relation, dst)`; when the persistent fact is removed, the overlay surfaces. Overlay facts ride the *direct relations a policy consumes* — a TTU-derived relation does not consult the overlay directly. **Proofs** (challenge rules): `{ name, subject, issuedAt, expiresAt }`. `issuedAt`/`expiresAt` are explicit (epoch timestamps are valid — `0` means already expired). --- ## 7. Snapshots ### Serialize ```js arbiter.enableCondensedSnapshot(); // permanent read-only switch const buf = arbiter.toSnapshotBinary(); // ArrayBuffer ``` ### Restore ```js const restored = Arbiter.fromSnapshotBinary(buf); ``` **Format** (v2): outer header `ARB1` magic + version 2 + graph length; embedded condensed graph section (`CGB1` magic); trailing JSON payload carrying relation metadata (validity, decay configs) and value TTLs. ~170 bytes/node for typical authorization graphs. **Contracts**: - **Lossless**: possibility, reliability, validity, decay configs, and TTLs round-trip. A restored engine answers checks identically (within 16-bit quantization, ≤ 1/65535). - **Frozen**: the restored engine is read-only; mutations throw. - **Trust boundary**: `fromSnapshotBinary` accepts untrusted bytes. Malformed buffers fail fast with clean, bounded errors — never hangs, crashes, or silently corrupted data. Every count field is cross-validated before use (pinned by `tests/rigor/snapshot-adversarial-fuzz.test.js`). - **TTL caveat**: restore resets `changed_last_at` to access time, so post-restore TTL expiry is measured from the restore moment, not the original write. --- ## 8. Reachability ```js await arbiter.initializeReachabilityChecker(); const r = arbiter.isReachable('u:1', 'doc:9'); // true | false | null ``` - `true` — a path exists (PLTC index is exact for reachability). - `false` — no path exists; **sound fast-fail**: rule evaluation can skip. - `null` — the PLTC index is unavailable (not initialized, or bypassed); **defer to rule evaluation** — this is the documented contract, not an error. Chain rules use the PLTC index internally for fast-fail; `getReachabilityStats()` exposes hit/miss/query telemetry. --- ## 9. The `OWAFusion` utility Static interval-fusion functions used by OWA aggregators and exported for direct use: | Method | Signature | Description | |--------|-----------|-------------| | `fuseIntervalsWithMeta` | `(intervals, metas, weights, operator) => { interval, possibility, meta? }` | Fusion with validity metadata | | `maxInterval` | `(intervals, metas) => result` | Disjunctive (max) fusion | | `isWithinTTL` | `(timestamp, ttlMs, now?) => boolean` | TTL check honoring the caller clock | | `compareIntervals` | `(left, right, comparator, epsilon?) => number` | Interval comparison | Operators: `max`, `min`, `owa`, `sum`, `average`, `product`, `top2`, `median`. Conjunctive operators surface conflict mass instead of silently averaging. --- ## 10. Audit hook ```js const arbiter = new Arbiter({ audit(record) { // record: { timestamp, userKey, relation, objectKey, decision, // possibility, binary, partialGraphUsed, validityLabel, sources } } }); ``` The engine stores nothing — the caller owns persistence and retention. The hook fires once per `check` (not for internal recursive evaluations), at zero cost when absent. --- ## 11. Errors `check` and `addRelation` throw on invalid input; all other failures surface as `reason` codes in the result: | Error | Trigger | |-------|---------| | `Invalid relation config ...` | `setRelationConfig` with a non-object config | | `Invalid possibility: expected a finite number in [0, 1]` | `addRelation` with out-of-range possibility | | `Cannot add/remove relation while in snapshot read-only mode` | mutation after `enableCondensedSnapshot` | | `Partial graph exceeds max relations/nodes ...` | overlay larger than `partialGraphPolicy` limits | | `Invalid condensed graph binary ...` | malformed snapshot bytes (clean, bounded) | | `Invalid arbiter snapshot payload` | snapshot JSON payload not an object | --- ## 12. Reason codes | Code | Meaning | |------|---------| | `direct_match` | Direct edge granted | | `no_relation` | No edge for the relation | | `threshold_not_met` | Direct edge below the allow threshold | | `missing_node` | User or object unknown | | `no_config` | Relation has no configuration | | `cycle` | Recursion cycle detected | | `allow` / `deny` | Binary-mode decision | | `insufficient_confidence` | Binary mode, thresholds not decisive | | `chain_path_found` / `no_chain_path_found` | Chain traversal | | `multi_hop_path_found` | Multi-hop path | | `no_matching_rule` | No rule matched | | `logical_operator_evaluation` | Union/intersection/exclusion result | | `defeated_by_unless` / `never_rule_triggered` / `requirements_not_met` | Defeasible outcomes | | `challenge_missing` / `challenge_missing_context` / `challenge_satisfied` | Challenge rules | | `exists` / `relation_exists` / `no_direct_match` | Rule-internal outcomes | | `qualitative_interval_comparison` | Qualitative comparator | | `not_reachable` | PLTC fast-fail | | `unknown_rule_type` / `invalid_rule` | Unrecognized config | | `evaluation_error` / `error` | Evaluation failure (never crashes the process) | --- ## 13. Validity taxonomy Validity labels (weakest → strongest of *evidence*): ``` finite_sample < anytime < conformal < approximate < heuristic < unknown ``` - `finite_sample` — evidence from an explicit sample - `anytime` — anytime-algorithm guarantees - `conformal` — conformal prediction coverage - `approximate` — approximation with stated bounds - `heuristic` — unvalidated ranking (default for unlabeled relations) - `unknown` — unrecognized label `validity.label` is the **weakest** source label, downgraded by the fusion operator's class: identity/max preserve the weakest label; min surfaces `conflictMass = 1 − fused_possibility` and a `validifiedPossibility`; product-style and interior-OWA operators are always `heuristic` (no linear validification under arbitrary dependence).