fix: caller clock everywhere — value TTL, decay, qualitative decay, cache TTL
The principle: time is caller-provided (options.now / partialGraph.now); the wall clock is only the fallback for unpinned callers, never a hidden decision input. Remaining clock leaks: - getBlurredValue gained an optional now param threaded to _isValueExpired; ChainRule (2 sites), MultiHopRule (2 sites), RelationManager (3 sites) now pass the caller clock. Previously a pinned-clock caller's chain/ multi-hop value TTL used the WALL clock (wall in 2026, pinned T0 in 2001 -> values wrongly expired). - MultiHopRule's TTL gate used valueFilters.ttl || 24h instead of the valueManager's per-relation TTL (inconsistent with chain/comparator); now valueManager.getTTL is the authority, valueFilters.ttl the override. - QualitativeRelationalComparatorRule decay (_calculatePeriodsElapsed) and value timestamps used the wall clock, so qualitative possibility decay ignored the pinned clock; now threaded through _evaluateOperand. - ValueManager decay internals (getDecayedRelation, _calculateSeparated Decay, _calculateBlurredValue) accept a now param (background worker still passes none -> wall clock is correct there). - PartialGraphContext._addChallengeProof/_addRelation used Date.now() instead of the context's own this.now (the partial graph's time). - Arbiter gained an injectable clock (options.clock) driving unpinned cache-entry freshness in DecisionCache, RuleEvaluator, ChainRule, and RelationalComparatorRule; DecisionCache explicit clock still wins. - Collected-value timestamps in DirectRule and RelationalComparatorRule honor the caller clock. Pinned-clock chain probe: values fresh at T0, expired at T0+61s, with the wall clock in 2026. Rigor 251/251, full suite 853/791/0.
This commit is contained in:
@@ -433,7 +433,8 @@ export class ChainRule extends BaseRule {
|
||||
const key = this._getChainResultCacheKey(userId, objectId, steps);
|
||||
const entry = this.chainResultCache.get(key);
|
||||
|
||||
if (entry && Date.now() - entry.timestamp < this.cacheTTL) {
|
||||
const cacheNow = this.arbiter.clock ? this.arbiter.clock() : Date.now();
|
||||
if (entry && cacheNow - entry.timestamp < this.cacheTTL) {
|
||||
return entry.result;
|
||||
}
|
||||
|
||||
@@ -451,9 +452,10 @@ export class ChainRule extends BaseRule {
|
||||
const key = this._getChainResultCacheKey(userId, objectId, steps);
|
||||
|
||||
// HyperbolicLRUCache handles eviction automatically based on frequency and recency
|
||||
const cacheNow = this.arbiter.clock ? this.arbiter.clock() : Date.now();
|
||||
this.chainResultCache.set(key, {
|
||||
result,
|
||||
timestamp: Date.now()
|
||||
timestamp: cacheNow
|
||||
});
|
||||
}
|
||||
|
||||
@@ -514,7 +516,8 @@ export class ChainRule extends BaseRule {
|
||||
return collectedValues;
|
||||
}
|
||||
|
||||
const blurred = this.arbiter.valueManager.getBlurredValue(relation);
|
||||
const callerNow = options && options.now !== undefined && options.now !== null ? options.now : null;
|
||||
const blurred = this.arbiter.valueManager.getBlurredValue(relation, callerNow);
|
||||
|
||||
if (blurred.interval) {
|
||||
const sourceEntity = direction === 'in' ?
|
||||
@@ -590,7 +593,8 @@ export class ChainRule extends BaseRule {
|
||||
Arbiter.DEBUG && Arbiter.log('ChainRule: relationManager or valueManager is undefined');
|
||||
return collectedValues;
|
||||
}
|
||||
const blurred = this.arbiter.relationManager.valueManager.getBlurredValue(tempRelation);
|
||||
const callerNow = options && options.now !== undefined && options.now !== null ? options.now : null;
|
||||
const blurred = this.arbiter.relationManager.valueManager.getBlurredValue(tempRelation, callerNow);
|
||||
|
||||
if (blurred.interval) {
|
||||
const collectedValue = this._createCollectedValue(
|
||||
|
||||
Reference in New Issue
Block a user