33dd15bba5
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.
560 lines
24 KiB
Markdown
560 lines
24 KiB
Markdown
# @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<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](#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: <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
|
||
|
||
```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: <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
|
||
|
||
```js
|
||
{
|
||
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)
|
||
|
||
```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).
|