Bug 34: ArbiterChecks.check destructured { reliability, ...rest } out of
every normal (and binary) check result since the initial commit — the
engine computes reliability faithfully (TTU tupleset.reli * computed.reli,
direct relation reliability) and explain() preserved it, but the public
check() API silently dropped it. explain() kept it, so exposing it in
check() is the intended contract. The destructuring is removed; direct
and TTU checks now return reliability (0.6 / 0.72 in the pins).
Campaign pins added: TTU multi-path fusion (max over intermediates of
min(legs)) with reliability propagation through both persistent and
partial contexts, the maxIntermediates circuit breaker through partial,
and snapshot-restored arbiters evaluating TTU.
The reverse branch of TupleToUsersetRule ignored tuplesetDirection: it
looked up the tupleset relation as outgoing-from-user even when 'in' was
set, while the join honored 'in' by using t.src as the intermediate —
the documented shape (intermediates hold the relation TO the user) never
matched, and only a degenerate join-on-user shape produced results. The
same gap existed in the compiled direct_join optimization.
Fixed the fallback tupleset lookup (including the graph-neighbor path and
the computed-join tupleEdge direction) and the optimized direct_join to
honor tuplesetDirection in reverse mode. Matrix pins reverse-in (both
evaluation paths, persistent + partial), multi_hop reverse, chain-in,
union-with-chain-child, defeasible split legs, TTU value flow, and
challenge-via-binary.
Two real bugs found by the new rule-kind x partial-graph parity campaign:
- TupleToUsersetRule 'computed' join mode (computed side has fewer
intermediates than the tupleset side) pushed path objects carrying
combinedPossibility, but _buildFinalResult reads path.possibility —
every valid TTU grant in that mode silently returned 0, in persistent
and partial contexts alike.
- _collectRelationUsages only registered explicit type:'direct' children,
so shorthand logical operands ({ relation: 'owner' } inside union/
intersection/exclusion) left the dependency index empty: writes to a
base relation never invalidated cached logical decisions, and a check
performed before an add kept serving its stale result forever.
New campaign rule-kind-partial-parity.test.js pins the full kind x
persistent/partial matrix (direct, chain, multi_hop, TTU out/in/reverse,
parent, computed, defeasible, union, exclusion, comparator, challenge,
binary) plus seeded differential properties for TTU, comparator, and
exclusion; artifact persistence disabled to avoid disk bloat.
- Arbiter constructor and setPartialGraphPolicy dropped maxNodes,
maxRelations, and reservedRelations — the partial-graph context always
saw the 1000/2000 defaults, silently disabling configured DoS guards.
Both paths now carry the limits through; policy limits test pins
constructor + setter enforcement and custom reservedRelations.
The binary branch of AuthorizationChecker.check rebuilt its options with a
fixed six-field object, silently discarding partialGraphContext (and any
other caller option) — binary checks denied grants the normal path
allowed. Now spreads all caller options through. binary-partial-parity
pins: partial grants above the threshold allow, below deny, persistent
wins over partial, and binary decisions agree with normal decisions on
the same overlay.
- invalidateRuleResultCacheByRelation now walks the transitive closure of
the dependency index (owner -> is_owner -> computed can_read): mutating
a base relation left computed/multi_hop results stale
- _collectRelationUsages registers multi_hop and parent rule relations
(parentRelation + subject relation) so their caches invalidate on
base-relation mutations
- advanced-rule-kinds.test.js: multi_hop (min-over-path, depth-limited),
computed (userset alias), parent (subject relation on the target's
parent) through the full check() path under random mutations
- _addRelationInternal now validates possibility like the single-add path
(batch adds could store out-of-range values and make the transactional
wrapper report success:true after a partial apply)
- tx-rollback-parity.test.js: poison at every batch position rolls back
fully; clean batches apply in order; batch+PLTC reachability reflects
batch writes immediately
updateRelationsBatch previously pre-sorted ops remove->modify->add, which
changed the final state whenever one tuple was touched by mixed kinds:
[add, remove] left the tuple present, [modify, add, modify] ended with
the middle value. Now ops apply strictly in the given order via the
dedup-aware _addRelationInternal (in-place last-write-wins) with upfront
validation, post-batch PLTC edge updates, and per-relation arbiter-level
cache invalidation.
batch-order-parity.test.js pins the contract with an in-order mirror
(modify-of-missing is a silent no-op).
Engine fixes:
- RelationUpdates.updateRelationsBatch: invalidate arbiter-level caches
(rule result cache, ChainRule caches, direct-check cache) per affected
relation — batch updates bypassed Arbiter.addRelation and served stale
decisions after batch modify/swap with warm caches
- updateRelationsBatchTransactional rollback: new Map(Set) crashed with
'Iterator value is not an entry object' — fixed to new Set
- RelationalComparatorRule: value extraction (direct-list and cached
direct paths) now gates on valueManager._isValueExpired — TTL-expired
values no longer feed comparator decisions
Campaigns:
- value-freshness-parity.test.js: batch modify/swap/tx rollback freshness
with comparator mirror (batch MODIFY of a missing relation is a silent
no-op — pinned)
- ttl-expiry-parity.test.js: injected-clock TTL expiry through the
comparator path (exact parity with caching off; bounded staleness with
caching on), faithful changed_last_at mirror semantics