• v1.0.4 ed34df4474

    feat: intermediate chain condition steps, graph-version cache invalidation, rolling-hash chain keys; fix vacuous rigor invariants
    CI / benchmark (push) Successful in 48s
    CI / test (push) Successful in 5m26s
    CI / publish (push) Has been skipped

    Dvorak released this 2026-08-03 20:26:42 +00:00 | 5 commits to master since this release

    Chain intermediates (rule-based reachability):

    • ChainRule: a condition step ({ rule, conditionStep }) at an INTERMEDIATE
      position is now EXPANDED from the current node — the rule's base edges'
      destinations, filtered by its defeaters/requirements — and traversal
      continues from each discovered node. Adds _expandRuleFromSrc / direct /
      logical(union/intersection) / defeasible / nested-chain expansion.
    • RuleEvaluator: _subjectIsObject flag for unary predicate calls whose subject
      entity IS the object parameter (trusted(other) inside peer_trusted(user,
      other)); previously only subject-var unary calls (_subjectAsObject) were
      handled, so object-var unary defeaters never fired.

    Graph-version cache invalidation:

    • Arbiter gains a monotonic _graphVersion, incremented on every relation
      mutation. ChainRule result cache, RuleEvaluator rule-result cache, and
      DecisionCache rule cache now stamp entries with the graph version and treat
      any mismatch as a miss — graph mutations can no longer serve stale
      chain/authorization results.

    Rolling-hash cache keys:

    • UnifiedKeyManager.createChainKey now builds a 53-bit rolling hash (dual
      FNV-1a lanes, exact for ints/floats/strings/nested configs) instead of
      JSON.stringify — no string allocation or serialization on the chain-cache
      hot path. Composite keys stay structured strings because the direct-check
      cache pattern-invalidates by relation ID.

    Rigor invariant migration (correctness):

    • All 43 rigor test files' throw-based invariants ({ error, errorMessage } =>
      !error && !errorMessage) never saw fn throws — vacuous. Migrated to
      ({ actual }) => actual !== undefined, which fails on any thrown violation
      while passing legitimate null-skips. The migration immediately surfaced
      two latent bugs, now fixed:
      • node-manager/graph-indices skip paths returned bare undefined (falsy
        sentinel) — return { skipped: true }.
      • complex-graph-values-crucible expiry section rewrote values equal to the
        mutation loop's last write; the engine (by design) keeps the old
        timestamp on same-value rewrites so the pre-expiry grant never
        materialized. Now writes guaranteed-different values.
    Downloads