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:
@@ -243,3 +243,49 @@ describe('Persistence losslessness (rigor)', () => {
|
||||
assert.equal(restored2.relationManager.getDirectRelation(restored2.resolveNodeId('u:0'), 'owner', restored2.resolveNodeId('d:0')).validity, 'finite_sample');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Temporal replay (re-entrant diagnostics) (rigor)', () => {
|
||||
it('FIXED: pinned-clock checks are per-time, and the explain rerun reproduces the decision', () => {
|
||||
const a = new Arbiter();
|
||||
a.addNode('u:0', 'user');
|
||||
a.addNode('d:0', 'doc');
|
||||
a.setRelationConfig('premium', {
|
||||
type: 'relational_comparator', comparator: '>',
|
||||
left: { rule: { type: 'direct', relation: 'has_balance' }, extractValue: true },
|
||||
right: { evaluateFrom: 'object', rule: { type: 'direct', relation: 'has_price' }, extractValue: true }
|
||||
});
|
||||
a.valueManager.setTTL('has_balance', 60000);
|
||||
a.valueManager.setTTL('has_price', 60000);
|
||||
const t0 = 1000000;
|
||||
a.addRelation('u:0', 'has_balance', 'd:0', { value: 100, changed_last_at: t0 });
|
||||
a.addRelation('d:0', 'has_price', 'd:0', { value: 50, changed_last_at: t0 });
|
||||
|
||||
// Interleaved pinned-clock checks must each reflect their own clock —
|
||||
// no cross-contamination through any result cache.
|
||||
assert.equal(a.check('u:0', 'premium', 'd:0', { now: t0 + 120000 }).possibility, 0, 'expired at +120s');
|
||||
assert.equal(a.check('u:0', 'premium', 'd:0', { now: t0 + 1000 }).possibility, 1, 'fresh at +1s');
|
||||
assert.equal(a.check('u:0', 'premium', 'd:0', { now: t0 + 120000 }).possibility, 0, 'expired again');
|
||||
|
||||
// The explain rerun reproduces the pinned decision and records the
|
||||
// temporal context the caller must replay.
|
||||
const e = a.explain('u:0', 'premium', 'd:0', { now: t0 + 120000 });
|
||||
assert.equal(e.decision.possibility, 0, 'explain rerun reproduces the expired decision');
|
||||
assert.deepEqual(e.request.temporal, { now: t0 + 120000 }, 'temporal context recorded for replay');
|
||||
const e2 = a.explain('u:0', 'premium', 'd:0', { now: t0 + 1000 });
|
||||
assert.equal(e2.decision.possibility, 1, 'explain rerun reproduces the fresh decision');
|
||||
});
|
||||
|
||||
it('FIXED: challenge proof expiry is replayable via the pinned clock', () => {
|
||||
const a = new Arbiter();
|
||||
a.addNode('u:0', 'user');
|
||||
a.addNode('d:0', 'doc');
|
||||
a.setRelationConfig('can_download', { type: 'challenge', challenge: 'mfa', subject: 'user', withinMinutes: 5 });
|
||||
const issued = 500000;
|
||||
const proof = [{ name: 'mfa', subject: 'u:0', issuedAt: issued, expiresAt: null }];
|
||||
assert.equal(a.check('u:0', 'can_download', 'd:0', { partialGraph: { challenges: proof }, now: issued + 299000 }).possibility, 1, 'within window');
|
||||
assert.equal(a.check('u:0', 'can_download', 'd:0', { partialGraph: { challenges: proof }, now: issued + 301000 }).possibility, 0, 'past window');
|
||||
const e = a.explain('u:0', 'can_download', 'd:0', { partialGraph: { challenges: proof }, now: issued + 301000 });
|
||||
assert.equal(e.decision.possibility, 0, 'explain reproduces the expired proof');
|
||||
assert.deepEqual(e.request.temporal, { now: issued + 301000 }, 'challenge temporal recorded');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user