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:
@@ -86,7 +86,8 @@ export class AuthorizationChecker {
|
||||
// Check cache first using composite key (if caching is enabled)
|
||||
let cachedResult = null;
|
||||
let cacheHint = null;
|
||||
if (!hasPartialGraph && this.decisionCache.directEnabled && !includeMeta) {
|
||||
const temporalPinned = options.now !== undefined && options.now !== null;
|
||||
if (!hasPartialGraph && this.decisionCache.directEnabled && !includeMeta && !temporalPinned) {
|
||||
const cacheKey = this._getDirectCheckCacheKey(userKey, relation, objectKey);
|
||||
const [hitResult, status] = this.decisionCache.peekDirect(cacheKey);
|
||||
cachedResult = status === 'hit' || status === 'expired' ? { result: hitResult, timestamp: 0 } : null;
|
||||
@@ -112,8 +113,9 @@ export class AuthorizationChecker {
|
||||
};
|
||||
|
||||
// Cache the result (only when no partial graph and the default
|
||||
// meta-less form — includeMeta callers get a fresh full evaluation)
|
||||
if (!explain && !hasPartialGraph && !includeMeta) {
|
||||
// meta-less form — includeMeta callers and pinned-clock callers get
|
||||
// a fresh evaluation)
|
||||
if (!explain && !hasPartialGraph && !includeMeta && !temporalPinned) {
|
||||
this._cacheDirectCheckResult(userKey, relation, objectKey, result);
|
||||
}
|
||||
return result;
|
||||
@@ -227,7 +229,7 @@ export class AuthorizationChecker {
|
||||
}
|
||||
|
||||
// Cache the result using composite key (meta-less form only)
|
||||
if (!explain && !hasPartialGraph && !includeMeta) {
|
||||
if (!explain && !hasPartialGraph && !includeMeta && !temporalPinned) {
|
||||
this._cacheDirectCheckResult(userKey, relation, objectKey, result);
|
||||
}
|
||||
return result;
|
||||
@@ -264,8 +266,9 @@ export class AuthorizationChecker {
|
||||
};
|
||||
}
|
||||
|
||||
const temporalPinned = options.now !== undefined && options.now !== null;
|
||||
const canCacheRuleResult = !hasPartialGraph && !explain && !includeMeta &&
|
||||
!binary && options.cacheRuleResult !== false && this.decisionCache.ruleEnabled;
|
||||
!binary && !temporalPinned && options.cacheRuleResult !== false && this.decisionCache.ruleEnabled;
|
||||
const ruleCacheKey = canCacheRuleResult
|
||||
? this._getRuleResultCacheKey(userId, relation, objectId)
|
||||
: null;
|
||||
|
||||
Reference in New Issue
Block a user