js-rigor: batch cache staleness, tx-batch crash, TTL bypass; value freshness campaigns

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
This commit is contained in:
John Dvorak
2026-07-31 14:21:22 -07:00
parent 2de5faa7c9
commit 7ffa5045e6
4 changed files with 382 additions and 2 deletions
@@ -387,6 +387,7 @@ export class RelationalComparatorRule extends BaseRule {
}
const rel = this.arbiter.relationManager.getDirectRelation(srcId, relName, dstId, options);
if (!rel || typeof rel.value !== 'number') continue;
if (this.arbiter.valueManager && this.arbiter.valueManager._isValueExpired(rel)) continue;
valueResults.push({
value: rel.value,
@@ -780,6 +781,9 @@ export class RelationalComparatorRule extends BaseRule {
}
_getCachedDirectValue(relation, ttl) {
if (this.arbiter.valueManager && this.arbiter.valueManager._isValueExpired(relation)) {
return null;
}
const cacheKey = `${relation.src}|${relation.rel}|${relation.dst}`;
const cached = this._directValueCache.get(cacheKey);
if (cached && cached.stateId === relation.stateId) {