Files
core/docs/API.md
T
John Dvorak 33dd15bba5
CI / test (push) Successful in 5m32s
CI / benchmark (push) Successful in 40s
CI / publish (push) Has been skipped
docs: full API reference + capabilities; export PartialGraphContext
docs/API.md is the authoritative interface reference: exports, Arbiter
methods, check options/result shape with contract invariants, all ten
relation configuration formats, valueManager, partial graphs (raw spec
+ pre-built context, precedence contract), snapshots (format, frozen
restore, trust boundary), reachability (null-defer contract), OWAFusion,
audit hook, errors, reason codes, and the validity taxonomy.

README gains a capabilities section (what the engine does and does not
do) and links the reference, per the map-vs-manual split.

The reference pass surfaced one interface gap: PartialGraphContext —
a first-class public type used in the pre-built overlay form — was not
exported. It is now part of the public surface.

Verified claims against source (overlay spec keys: challenges/
challengeProofs not proofs; qualitative routing via qualitative: true /
scaleName; simpleBinary shape; reason enum; config formats). Rigor
251/251, full suite 853/791/0.
2026-08-02 20:50:19 -07:00

24 KiB
Raw Blame History

@arbiter/core — API Reference

The complete public interface of the Arbiter engine. For the package overview, quick start, and concepts, see the README. This document is the authoritative reference: every public export, method signature, option, result shape, and configuration format.

Contents

  1. Exports
  2. The Arbiter class
  3. check options and result shape
  4. Relation configurations
  5. The valueManager
  6. Partial graphs
  7. Snapshots
  8. Reachability
  9. The OWAFusion utility
  10. Audit hook
  11. Errors
  12. Reason codes
  13. Validity taxonomy

1. Exports

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)
partialGraphPolicy object Overlay admission limits (see §6)
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). 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). 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 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<void> 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)
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).


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)
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:

{
  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

{
  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).


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

{ type: 'direct' }

A single edge grants with exactly its possibility. The fastest path.

Tuple-to-userset (group membership)

{
  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)

{
  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).

{
  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

{
  type: 'defeasible',
  when: <rule>,      // grants
  unless: <rule>,    // defeats when it fires
  never: <rule>,     // absolute veto
  always: <rule>,    // unconditional grant
  requires: <rule>   // precondition
}

Rules compose: { when, unless } = "grants unless defeated"; { always, unless } = "grants unconditionally unless defeated"; { never } = absolute deny.

Logical operators

{ union: [rule, rule, ...] }        // disjunction — max
{ intersection: [rule, rule, ...] } // conjunction — min / conflict-aware
{ exclusion: [grantRule, denyRule] } // grant minus deny

Union configs additionally support OWA fusion:

{
  union: { rules: [...], aggregator: 'owa', owaWeights: [0.7, 0.3], useBilattice: false, epistemicMode: 'hybrid' }
}

Aggregators: max, min, owa, top2, median, sum.

Relational comparator (ABAC)

{
  type: 'relational_comparator',
  comparator: '>=',            // any JS comparison operator
  fallbackBehavior: 'deny',    // 'deny' | 'allow' | 'unknown'
  left: {
    rule: <rule>,              // e.g. { type: 'direct', relation: 'has_balance' }
    extractValue: true,
    valueRelation: 'has_balance',
    evaluateFrom: 'user',      // 'user' | 'object'
    aggregator: 'owa', owaWeights: [...]
  },
  right: {
    rule: <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

{
  type: 'relational_comparator',
  qualitative: true,             // routes to the qualitative implementation
  comparator: '>=',
  left: { rule: <rule>, scaleName: 'five-point', decayPeriod: 'HOUR', decaySteps: 1, ... },
  right: { rule: <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)

{
  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

{ type: 'parent', parentRelation: 'parent', relation: 'owner' }

Grants when the object's parent (via parentRelation) holds relation for the user.

Computed

{ 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

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

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

arbiter.enableCondensedSnapshot();   // permanent read-only switch
const buf = arbiter.toSnapshotBinary();  // ArrayBuffer

Restore

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

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

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).