refactor: complete rolling-hash rollout — composite/srcRel/dstRel keys + key-tracked cache invalidation
Completes the rolling-hash rollout (previously only createChainKey hashed): - createCompositeKey, createSrcRelKey, createDstRelKey, and the valueRelationsBySrc/Dst keys now produce 53-bit rolling hashes instead of `src|rel|dst` string concatenation. Direction markers keep srcRel vs dstRel distinct; the composite/chain keys are exact integers usable as Map keys. - Direct-check cache invalidation is now key-TRACKED instead of pattern- matched: every direct-check result is registered under the checked relation, its base relations (reverse dependency index), and the subject/object node ids (Arbiter._trackDirectCheckKey). Relation-level invalidation deletes the tracked keys for each affected relation (covering config-override checks); node-level invalidation (node removal / updateNodeData) deletes by node id. This replaces the pipe-delimited-string regex matching that required the old key format. - DecisionCache.invalidateByNodeKey and invalidateAll route through the tracked indexes; tracking maps are cleared on full flush. Tests updated to the tracked contract (register injected keys via _trackDirectCheckKey); full suite green.
This commit is contained in:
@@ -174,32 +174,9 @@ export class DecisionCache {
|
||||
if (!a?.directCheckCache) return;
|
||||
const nodeId = a.nodeIdByKey?.get(nodeKey);
|
||||
if (nodeId === undefined) return;
|
||||
const cache = a.directCheckCache;
|
||||
|
||||
// Pattern-based invalidation is the safest path — the cache file
|
||||
// owns its storage layout and decides how to enumerate keys.
|
||||
if (typeof cache.invalidateByPattern === 'function') {
|
||||
// Match a nodeId that appears in any position of the composite
|
||||
// key. The composite key format is `${srcId}|${rel}|${dstId}` and
|
||||
// components are pipe-delimited, so a digit-boundary regex is
|
||||
// safer than a plain substring match.
|
||||
const escaped = String(nodeId).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const pattern = new RegExp(`(?:^|\\|)${escaped}(?:\\||$)`);
|
||||
cache.invalidateByPattern(pattern);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: explicit key iteration. HyperbolicLRUCache does not
|
||||
// expose `keys()`, so this branch is unreachable for that cache
|
||||
// family. If a future cache implementation exposes iteration,
|
||||
// we walk it without leaking storage-layout details.
|
||||
if (typeof cache.keys === 'function') {
|
||||
for (const key of cache.keys()) {
|
||||
if (typeof key === 'string' && key.includes(String(nodeId))) {
|
||||
cache.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Keys are rolling hashes (not pipe-delimited strings), so node-level
|
||||
// invalidation uses the per-node key index populated at cache-set time.
|
||||
a._invalidateDirectCheckCacheByNode?.(nodeId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,6 +187,9 @@ export class DecisionCache {
|
||||
const a = this.arbiter;
|
||||
if (!a) return;
|
||||
a.directCheckCache?.clear?.();
|
||||
a.directCheckCacheKeysByRelation?.clear?.();
|
||||
a.directCheckCacheKeysByNode?.clear?.();
|
||||
a.directKeyToRelations?.clear?.();
|
||||
a.invalidateAllRuleResultCache?.();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user