companion-loopback-guard-performance.test.mjs
83 lines 3.4 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 1 day ago
1 /**
2 * Tier 6 — PERFORMANCE: the guard must add negligible, bounded overhead.
3 *
4 * The loopback endpoint will call verifyLoopbackRequest on EVERY request before any model work,
5 * so its cost must be tiny and must not degrade with request volume (gate §10 performance:
6 * endpoint overhead bounds; no event-loop starvation). These are generous CI-safe thresholds —
7 * they catch accidental O(n^2) / blocking regressions, not micro-fluctuations.
8 *
9 * Reference: docs/COMPANION-APP-DESIGN-AND-AUTHORIZATION-GATE.md §10 (performance).
10 */
11 import { describe, it } from 'node:test';
12 import assert from 'node:assert/strict';
13 import {
14 verifyLoopbackRequest,
15 createLoopbackRateState,
16 recordLoopbackRequest,
17 shouldCountTowardRateLimit,
18 } from '../lib/companion-loopback-guard.mjs';
19
20 const PORT = '54000';
21 const TOKEN = 'perf-' + 'a'.repeat(40);
22 const ALLOWED_HOSTS = [`127.0.0.1:${PORT}`, `localhost:${PORT}`];
23
24 function base(overrides = {}, rateState) {
25 return {
26 method: 'POST',
27 headers: { Host: `127.0.0.1:${PORT}`, Origin: `http://127.0.0.1:${PORT}`, 'Sec-Fetch-Site': 'same-origin' },
28 token: TOKEN,
29 expectedToken: TOKEN,
30 allowedHosts: ALLOWED_HOSTS,
31 now: 0,
32 rateState,
33 ...overrides,
34 };
35 }
36
37 describe('Performance — per-decision overhead', () => {
38 it('100k admit decisions complete well under 2 seconds', () => {
39 const rateState = { windowMs: 60_000, maxRequests: 1_000_000, timestamps: [] };
40 const start = performance.now();
41 for (let i = 0; i < 100_000; i++) {
42 verifyLoopbackRequest(base({ now: i }, rateState));
43 }
44 const elapsed = performance.now() - start;
45 assert.ok(elapsed < 2000, `100k decisions took ${elapsed.toFixed(1)}ms, expected < 2000ms`);
46 });
47
48 it('mean per-decision cost is sub-millisecond', () => {
49 const rateState = { windowMs: 60_000, maxRequests: 1_000_000, timestamps: [] };
50 const N = 50_000;
51 const start = performance.now();
52 for (let i = 0; i < N; i++) verifyLoopbackRequest(base({ now: i }, rateState));
53 const perCall = (performance.now() - start) / N;
54 assert.ok(perCall < 0.5, `mean ${perCall.toFixed(4)}ms/call, expected < 0.5ms`);
55 });
56 });
57
58 describe('Performance — no super-linear blowup with rate window size', () => {
59 it('decision cost with a steady-state window does not explode', () => {
60 // Fill a window to its cap, then time decisions against the full window.
61 let state = createLoopbackRateState({ windowMs: 10_000, maxRequests: 1000 });
62 for (let i = 0; i < 1000; i++) {
63 const v = verifyLoopbackRequest(base({ now: i }, state));
64 if (shouldCountTowardRateLimit(v)) state = recordLoopbackRequest(state, i);
65 }
66 const start = performance.now();
67 for (let i = 0; i < 20_000; i++) verifyLoopbackRequest(base({ now: 1000 + i }, state));
68 const elapsed = performance.now() - start;
69 assert.ok(elapsed < 2000, `20k decisions over a 1000-entry window took ${elapsed.toFixed(1)}ms`);
70 });
71 });
72
73 describe('Performance — constant-time compare is not pathologically slow', () => {
74 it('100k token comparisons (hash + timingSafeEqual) stay bounded', () => {
75 const rateState = { windowMs: 60_000, maxRequests: 1_000_000, timestamps: [] };
76 const start = performance.now();
77 for (let i = 0; i < 100_000; i++) {
78 verifyLoopbackRequest(base({ token: `wrong-${i}` }, rateState));
79 }
80 const elapsed = performance.now() - start;
81 assert.ok(elapsed < 3000, `100k constant-time compares took ${elapsed.toFixed(1)}ms`);
82 });
83 });
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 1 day ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 1 day ago