From 02105fc120b44e57cc5ebb7a7600b50cd2d53746 Mon Sep 17 00:00:00 2001 From: Chris Portscheller Date: Thu, 30 Jul 2026 18:11:28 -0500 Subject: [PATCH] test(agent): assert the warm path does no I/O, not that it takes under 5ms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The test named "does not fetch the network on the warm path and verifies in <5ms p95" asserted only the second half. It never checked whether anything was fetched — the claim in its own name, and the one that matters. What it did assert was a wall-clock p95 over 200 Ed25519 verifications, on whatever hardware happened to run it. That failed CI at 5.92ms against a bound of 5. Nothing was wrong: a shared runner measures its neighbours as much as the code under test, so the assertion was going to fail on someone eventually, and "re-run it" is not a fix. The invariant worth protecting is that a warm verify touches no network. That is what makes signature verification usable on a request path, and unlike a duration it is exact — fetchImpl is injectable, so the calls can simply be counted: one cold fetch to populate the directory cache, then fifty verifies that add none. Timing is not asserted at all now. A number that fails for reasons unrelated to the code is worse than no number, because the failure teaches you to ignore it. --- .../webdecoy/src/agent/web-bot-auth.test.ts | 55 ++++++++++++++----- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/packages/webdecoy/src/agent/web-bot-auth.test.ts b/packages/webdecoy/src/agent/web-bot-auth.test.ts index c419f1b..8a021cf 100644 --- a/packages/webdecoy/src/agent/web-bot-auth.test.ts +++ b/packages/webdecoy/src/agent/web-bot-auth.test.ts @@ -222,24 +222,53 @@ describe('AgentVerifier — Web Bot Auth', () => { expect(verdict.status).toBe('verified'); }); - it('does not fetch the network on the warm path and verifies in <5ms p95', async () => { - const { privateKey, keyid, verifier } = await fixture(); - const headers = await signHeaders({ privateKey, keyid, url: 'https://bot.example/foo' }); + it('does not fetch the network on the warm path', async () => { + // This used to assert a <5ms p95 wall-clock bound and nothing else — so it + // never checked the claim in its own name, and it failed in CI at 5.92ms on + // a contended runner. A wall clock on shared hardware measures the + // neighbours as much as the code. + // + // The invariant worth protecting is that a warm verify touches no network: + // that is what makes verification usable on a request path, and unlike a + // duration it is exact. fetchImpl is injectable, so it can simply be + // counted. + const kp = (await crypto.subtle.generateKey({ name: 'Ed25519' }, true, [ + 'sign', + 'verify', + ])) as CryptoKeyPair; + const publicJwk = await crypto.subtle.exportKey('jwk', kp.publicKey); + const dirJwk = { kty: 'OKP', crv: 'Ed25519', x: publicJwk.x }; + const keyid = await jwkThumbprint(dirJwk); + + let fetches = 0; + const counting = mockDirectory({ keys: [dirJwk] }); + const verifier = new AgentVerifier({ + directories: [TEST_DIR], + fetchImpl: ((...args: Parameters) => { + fetches++; + return counting(...args); + }) as typeof counting, + }); + + const headers = await signHeaders({ + privateKey: kp.privateKey, + keyid, + url: 'https://bot.example/foo', + }); const req = new Request('https://bot.example/foo', { headers }); - // Warm the directory cache (one cold fetch), then verify repeatedly. + // One cold fetch to populate the directory cache. await verifier.verify(req); + expect(fetches).toBe(1); - const N = 200; - const samples: number[] = []; - for (let i = 0; i < N; i++) { - const t0 = performance.now(); - await verifier.verify(req); - samples.push(performance.now() - t0); + const cold = fetches; + for (let i = 0; i < 50; i++) { + const verdict = await verifier.verify(req); + expect(verdict.status).toBe('verified'); } - samples.sort((a, b) => a - b); - const p95 = samples[Math.floor(N * 0.95)]; - expect(p95).toBeLessThan(5); + + // The whole point: warm verifies added no network calls at all. + expect(fetches).toBe(cold); }); });