The new value-collection crucibles in the TTU and chain differential
campaigns immediately found three engine defects:
- TTU join pushed 0-strength 'matches' (missing computed leg, or
0-possibility edges, with minPossibility 0) as valid paths: denied
decisions reported tuple_to_userset_found and leaked the tupleset edge's
value into collectedValues. Both join modes now require combined > 0.
- A ReferenceError (bare resolveKey) crashed the computed-join mode under
collectValues, silently turning the whole check into an evaluation_error
denial. Fixed the call to this.arbiter.resolveKey.
- Multi-path TTU fusion fell back to Math.max over all path reliabilities,
pairing the winning possibility with another intermediate's reliability.
The fallback now picks the max-possibility path's reliability.
New campaigns: defeasible and intersection differential properties
(when/unless and min-children with reliability parity under persistent/
partial splits). The model-based campaign keeps its reliability crucible;
its value comparison was reverted — the harness's shrink reporting is
opaque and unreconstructable there, and the value semantics are covered by
the TTU/chain campaigns instead.
Systemic reliability gap found by the probe sweep: the compiled evaluation
paths never emitted the reliability the engine computes.
- Compiled _evaluateDirect omitted the relation's reliability, and the
chain/multi_hop rules hardcoded reliability: 1.0 — so check() results
reported 1.0 for any rule whose decision came through a chain, multi_hop,
union, intersection, exclusion, or defeasible combination.
- The chain and multi_hop traversals now track per-path reliability (product
of edge reliabilities) and report the winning path's value; the compiled
and fallback logical operators (union/intersection/exclusion, direct_list
fast path, early exits) report the selected child's reliability
(max/min child or OWA trace index; exclusion multiplies both legs), and
normal-mode defeasible combines base x requires x defeater reliabilities.
- The checker's logical fast path dropped collectedValues from union/
intersection/exclusion results; it now passes them through.
- MultiHopRule.valueManager was read off relationManager where the real
arbiter keeps it on the arbiter — collectValues: true on a multi_hop rule
with a value-carrying edge crashed the evaluation (error result, silent
denial). Now resolved at the arbiter level with a relationManager
fallback for stubs.
Campaign pins: reliability per kind (chain/multi_hop product, union/intersection
selected child, exclusion/defeasible product), and multi_hop value collection
through persistent and partial contexts.
Two issues found by the extended probe sweep:
- A relational_comparator operand backed by a tuple_to_userset rule always
denied: the TTU rule collected only the intermediate KEY, which the
operand extraction skips as non-numeric, so no value was ever available.
The TTU rule now emits a value-carrying collected entry when the
tupleset edge carries a numeric value (entityKey = tuple src, relation =
tupleset relation), keeping the bare intermediate key when there is no
value. Comparator-with-TTU-operand now allows/denies on the tuple value
through both persistent and partial contexts.
- _modifyRelation ignored the changed_last_at override that the add path
honors: value-changing modifies stamped fresh Date.now() regardless of
the pin, so replay/restore tools pinning timestamps got different
semantics via modify vs add. The override now applies to refresh events
(value/reliability/possibility change) and is ignored for value-unchanged
writes, preserving the TTL parity contract that identical replays never
un-expire old values.
Verified clean: intersection through partial, defeasible with logical
when, challenge subject object/session with sessionKey, non-binary
minAllowPossibility threshold, batch value updates, explain agreement
under partial.
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.
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