js-rigor: challenge proofs treat zero timestamps as valid; flake root-caused
The transient full-suite flake (fail 1 in ~5-10% of runs, only visible on some invocations) was finally captured: challenge-proof's most-recent-proof property failed once in 800 cases. Root cause: _addChallengeProof built issuedAt with proof.issuedAt || proof.issued_at || proof.timestamp || Date.now() — a generated issuedAt of 0 (epoch) was replaced with the wall clock, so an epoch-issued proof became the most recent one and the lookup returned a timestamp (1.7e12) that could never match the expectation. The same ||-chain corrupted expiresAt: 0 into null, making epoch-expired proofs never expire. All three chains (issuedAt, expiresAt, proofId) now use nullish coalescing so zero is a valid timestamp. Fixed pin added; the challenge-proof file went from ~10% flake to 20/20 clean, and the full suite is stable at 801 tests / 739 pass / 0 fail.
This commit is contained in:
@@ -120,15 +120,18 @@ export class PartialGraphContext {
|
|||||||
_addChallengeProof(proof) {
|
_addChallengeProof(proof) {
|
||||||
const subjectId = this._resolveNodeId(proof.subject);
|
const subjectId = this._resolveNodeId(proof.subject);
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const issuedAt = proof.issuedAt || proof.issued_at || proof.timestamp || now;
|
// 0 is a valid timestamp (epoch-issued / already-expired); `||` would
|
||||||
const expiresAt = proof.expiresAt || proof.expires_at || null;
|
// replace it with the wall clock or null, making an epoch-issued proof
|
||||||
|
// the most recent one and an epoch-expired proof never expire.
|
||||||
|
const issuedAt = proof.issuedAt ?? proof.issued_at ?? proof.timestamp ?? now;
|
||||||
|
const expiresAt = proof.expiresAt ?? proof.expires_at ?? null;
|
||||||
const record = {
|
const record = {
|
||||||
name: proof.name,
|
name: proof.name,
|
||||||
subject: subjectId,
|
subject: subjectId,
|
||||||
issuedAt,
|
issuedAt,
|
||||||
expiresAt,
|
expiresAt,
|
||||||
source: proof.source || 'partial',
|
source: proof.source || 'partial',
|
||||||
proofId: proof.proofId || proof.proof_id || null,
|
proofId: proof.proofId ?? proof.proof_id ?? null,
|
||||||
metadata: proof.metadata || null
|
metadata: proof.metadata || null
|
||||||
};
|
};
|
||||||
const key = this._challengeKey(record.name, subjectId);
|
const key = this._challengeKey(record.name, subjectId);
|
||||||
|
|||||||
@@ -163,6 +163,23 @@ describe('PartialGraphContext.getChallengeProof (rigor)', () => {
|
|||||||
`getChallengeProof returned an expired proof in ${inv.failureCount} cases`);
|
`getChallengeProof returned an expired proof in ${inv.failureCount} cases`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('FIXED: zero timestamps are valid, not sentinels', () => {
|
||||||
|
const ctx = buildContext({ subjectIds: ['user:abc'], proofs: [
|
||||||
|
{ name: 'mfa', subject: 'user:abc', issuedAt: 42802, expiresAt: null },
|
||||||
|
{ name: 'mfa', subject: 'user:abc', issuedAt: 0, expiresAt: null }
|
||||||
|
] });
|
||||||
|
const subjectId = ctx._resolveNodeId('user:abc');
|
||||||
|
// issuedAt: 0 must not be replaced with the wall clock
|
||||||
|
const best = ctx.getChallengeProof('mfa', subjectId, null, 167844);
|
||||||
|
assert.equal(best.issuedAt, 42802, 'epoch-issued proof must not win via wall-clock stamping');
|
||||||
|
// expiresAt: 0 is an already-expired proof, not "no expiry"
|
||||||
|
const ctx2 = buildContext({ subjectIds: ['user:abc'], proofs: [
|
||||||
|
{ name: 'mfa', subject: 'user:abc', issuedAt: 100, expiresAt: 0 }
|
||||||
|
] });
|
||||||
|
const best2 = ctx2.getChallengeProof('mfa', subjectId, null, 167844);
|
||||||
|
assert.equal(best2, null, 'epoch-expired proof must stay expired');
|
||||||
|
});
|
||||||
|
|
||||||
it('returns the most-recently-issued proof when withinMs=null', async () => {
|
it('returns the most-recently-issued proof when withinMs=null', async () => {
|
||||||
async function recentCheck(proofs, name, subject, now) {
|
async function recentCheck(proofs, name, subject, now) {
|
||||||
const ctx = buildContext({ subjectIds: [subject], proofs });
|
const ctx = buildContext({ subjectIds: [subject], proofs });
|
||||||
@@ -218,7 +235,8 @@ describe('PartialGraphContext.getChallengeProof (rigor)', () => {
|
|||||||
const inv = report.crucibleVerdict?.invariants?.find(i => i.name === 'most-recent');
|
const inv = report.crucibleVerdict?.invariants?.find(i => i.name === 'most-recent');
|
||||||
assert.ok(inv);
|
assert.ok(inv);
|
||||||
assert.equal(inv.passed, true,
|
assert.equal(inv.passed, true,
|
||||||
`getChallengeProof did not return the most-recent proof in ${inv.failureCount} cases`);
|
`getChallengeProof did not return the most-recent proof in ${inv.failureCount} cases: ` +
|
||||||
|
JSON.stringify((report.failures || []).slice(0, 2).map(f => ({ name: f.name, msg: f.message, seq: (f.sequence || []).map(s => s.args) }))));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('enforces withinMs freshness window', async () => {
|
it('enforces withinMs freshness window', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user