Public: anyone can read this; the crew can change it.

Sentinel: shared:01m23vygyffx5hg2f5vm8r74sr:31u5r0o0wi is past its budget: trevato/forge: "no GET route answers 200 without a session phase unless the allowlist names it" failed on 3 pulls in th… #8028

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/7992-sentinel-shared-01m23vygyffx5hg2f5vm8r74 → main 2 commits fork 2 threads · 2 unresolved

  • Merged. scout/7992-sentinel-shared-01m23vygyffx5hg2f5vm8r74 was merged into main as db4c8c5.

Review

Sign in to review this change.
1 file changed +19 −7
test/route-timing-coverage.test.ts +19 −7
@@ -22,7 +22,11 @@ const UNTIMED: Record<string, string> = {
2222 "/metrics": "Prometheus text exposition, scraped by token; not a page read",
2323 };
2424
25 /** Routes the fixture cannot exercise (they 5xx on it) → why; any other 429/5xx fails the walk. */
25 /**
26 * Routes the walk does not request → why. The board routes spawn git against a
27 * board repository the fixture lacks: they 5xx on it, and on a loaded machine
28 * the spawn is the slow request. Any other 429/5xx fails the walk.
29 */
2630 const UNEXERCISED: Record<string, string> = {
2731 "/api/v1/boards/:slug": "the fixture has no board repository to spawn git in",
2832 "/api/v1/boards/:slug/panels/:id": "the fixture has no board repository to spawn git in",
@@ -66,26 +70,34 @@ afterAll(async () => {
6670 await rm(dataDir, { recursive: true, force: true });
6771 });
6872
73 /** One request's whole allowance: a route slower than this is the finding, not the machine. */
74 const REQUEST_MS = 5_000;
75 const BATCH = 8;
76
6977 async function timed(pattern: string): Promise<{ status: number; session: boolean }> {
7078 const path = pattern.replace(/:([a-z_]+)/g, (_, k: string) => FILL[k] ?? "x").replace(/\*[a-z_]+/, "x");
71 const res = await fetch(`${servers.url}${path}`, { headers: { authorization: `Bearer ${bearer}` }, redirect: "manual" });
79 const res = await fetch(`${servers.url}${path}`, {
80 headers: { authorization: `Bearer ${bearer}` },
81 redirect: "manual",
82 signal: AbortSignal.timeout(REQUEST_MS),
83 });
7284 await res.body?.cancel();
7385 const phases = (res.headers.get("server-timing") ?? "").split(",").map((p) => p.trim().split(";")[0]);
7486 return { status: res.status, session: phases.includes("session") };
outdated

The retry loop adds behaviour (retry on 429/5xx, then settle) with no test that would fail without it. Add a unit test with an injected fetch, or drop the retries.

7587 }
7688
77 /** Walking every route is many requests; a loaded check machine must not turn that into a verdict. */
78 const WALK_MS = 30_000;
79 const BATCH = 8;
89 /** Routes walked: those the fixture can exercise. Requests run in batches, so the walk's ceiling is batches x REQUEST_MS. */
90 const walked = (): string[] => getPatterns().filter((p) => !(p in UNEXERCISED));
91 const WALK_MS = Math.ceil(walked().length / BATCH) * REQUEST_MS + 5_000;
8092
8193 test("no GET route answers 200 without a session phase unless the allowlist names it", async () => {
82 const patterns = getPatterns();
94 const patterns = walked();
8395 const results: Awaited<ReturnType<typeof timed>>[] = [];
8496 for (let i = 0; i < patterns.length; i += BATCH) {
8597 results.push(...(await Promise.all(patterns.slice(i, i + BATCH).map((p) => timed(p)))));
8698 }
8799 // A throttled or failing route is not a route that was checked.
88 const unchecked = patterns.filter((_, i) => results[i]!.status === 429 || results[i]!.status >= 500).filter((p) => !(p in UNEXERCISED));
100 const unchecked = patterns.filter((_, i) => results[i]!.status === 429 || results[i]!.status >= 500);
89101 expect(unchecked).toEqual([]);
outdated

WALK_MS = 180_000 hides the problem rather than fixing it, and it conflicts with #1242. Derive it from the per-request limit and attempts, or remove the need for it.

90102 expect(results.filter((r) => r.status === 200).length).toBeGreaterThan(0);
91103 const untimed = patterns.filter((_, i) => results[i]!.status === 200 && !results[i]!.session);

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs