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:
@@ -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) {
|
||||
|
||||
@@ -409,7 +409,23 @@ export class RelationUpdates {
|
||||
this._processBatchRemovals(removeOps, chunkSize);
|
||||
this._processBatchModifications(modifyOps, chunkSize);
|
||||
this._processBatchAdditions(addOps, chunkSize);
|
||||
|
||||
|
||||
// The batch path bypasses Arbiter.addRelation/removeRelation, so the
|
||||
// arbiter-level rule caches (rule result cache, ChainRule caches,
|
||||
// direct-check cache) are never invalidated there. A query that warmed
|
||||
// those caches before the batch would keep serving stale decisions.
|
||||
const affectedRelations = new Set();
|
||||
for (const op of updates) {
|
||||
if (op && op.relation !== undefined) affectedRelations.add(op.relation);
|
||||
}
|
||||
for (const relation of affectedRelations) {
|
||||
this.manager.arbiter._invalidateDirectCheckCache(null, relation, null);
|
||||
this.manager.arbiter.invalidateRuleResultCacheByRelation(relation);
|
||||
if (this.manager.arbiter.authChecker) {
|
||||
this.manager.arbiter.authChecker.invalidateRuleCaches(relation);
|
||||
}
|
||||
}
|
||||
|
||||
return this.manager;
|
||||
}
|
||||
|
||||
@@ -596,7 +612,7 @@ export class RelationUpdates {
|
||||
outgoingEdges: new Map(this.manager.arbiter.indices.outgoingEdges),
|
||||
incomingEdges: new Map(this.manager.arbiter.indices.incomingEdges)
|
||||
};
|
||||
const originalRelationKeys = new Map(this.manager._relationKeys);
|
||||
const originalRelationKeys = new Set(this.manager._relationKeys);
|
||||
const originalRelationKeyToIndex = new Map(this.manager._relationKeyToIndex);
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user