js-rigor: re-entrant temporal diagnostics — pinned-clock checks + explain replay

The errors-skill's two-pass model applied without a journal: the caller
owns the temporal context. Every decision-flipping temporal feature (the
clock behind TTL gates, challenge-proof expiry, value decay) is now
parameterized as options.now, so an explain rerun that replays the
original temporal parameters reproduces the original decision exactly.

Threaded now through: the comparator's operand value paths
(_getCachedDirectValue/_extractValues/isWithinTTL), the challenge proof
lookup, and the multi_hop value collection (isWithinTTL + blur). All four
result caches bypass cached decisions when the clock is pinned (rule-
result cache, the checker's rule/direct caches, and the comparator's
derived operand cache) — interleaved pinned-clock checks are per-time
with no cross-contamination.

The explain serializer records request.temporal.now so the caller knows
exactly what to replay. The happy path stays minimal and fast (no now ->
no parameter, no cache changes); the rerun (explain with the temporal
context) carries the full diagnostics.

Pins: interleaved fresh/expired/expired-again comparator checks, the
explain replay of both decisions + the recorded temporal context, and
challenge-proof expiry replay.
This commit is contained in:
John Dvorak
2026-08-02 10:51:50 -07:00
parent 8037b97bea
commit f1167d19fc
9 changed files with 87 additions and 23 deletions
+4 -1
View File
@@ -43,8 +43,11 @@ export class RuleEvaluator {
const collectValues = options.collectValues !== undefined ? options.collectValues : (rule._needsValues ?? needsValueContext);
const enhancedOptions = { ...options, minPossibility: options.minPossibility ?? options.minAllowPossibility, valueContext: finalValueContext, collectValues, includeMeta };
// A caller-pinned clock (options.now) makes the result per-clock: the
// result caches must not serve a decision evaluated at another time.
const temporalPinned = options.now !== undefined && options.now !== null;
const canCacheRuleResult = !!this.arbiter.ruleResultCache && currentRelation &&
!binary && !options.partialGraphContext && !includeMeta &&
!binary && !options.partialGraphContext && !includeMeta && !temporalPinned &&
options.cacheRuleResult !== false;
const ruleCacheKey = canCacheRuleResult
? this._getRuleResultCacheKey(numericUserId, currentRelation, numericObjectId, rule)