| name | stress-test |
| description | Load-test the API with stress.mjs scenarios (read-heavy, cold-heavy, ingest-heavy, mixed, rate-limit-storm), snapshot /metrics before and after, and write a findings report naming any failing budget. Use for stress testing, load testing, soak checks, or verifying perf budgets under concurrency. |
stress-test — orchestrate the asserting backend layer
scripts/stress.mjs is the gate: it asserts perf/budgets.json from
/metrics deltas (the client can't see event-loop lag or pool
saturation) and exits 1 on any breach. This skill wraps it with raw
snapshots and a findings report.
Preconditions
Same as perf-verify: docker up, HEAD build on :3001 started with
RATE_LIMIT_PER_MIN=10000000 — a full run is ~600k requests, and a raise
that still trips mid-run measures the limiter, not the API (stress fails
any non-storm scenario that saw a 429). The rate-limit-storm scenario
still exercises the limiter via POST /ask's own fixed 10/min window.
Confirm curl -s localhost:3001/metrics answers before running anything.
Run
- Raw snapshot before:
mkdir -p .perf/<label> && curl -s localhost:3001/metrics > .perf/<label>/metrics-before.prom
node scripts/stress.mjs --scenario all --duration 8 --label <label>
— scenarios: read-heavy, cold-heavy (fresh:true — cache-busting by
varying bodies was 98.8% Redis hits), ingest-heavy, mixed,
rate-limit-storm; isolate one with --scenario <name>. Results in
.perf/<label>/stress.json.
- Raw snapshot after:
curl -s localhost:3001/metrics > .perf/<label>/metrics-after.prom
- Note the exit code — 1 means a budget breached; the report must name it.
Findings report
From stress.json + the snapshots, report per scenario:
- requests served, faults (5xx minus the documented 503 LLM_UNAVAILABLE
carve-out — no provider until M8), 429s where the storm expects them;
- each asserted budget: measured vs
perf/budgets.json value, PASS/FAIL —
name the failing budget id, never just "stress failed";
- dedup correctness: the same insertId fired from concurrent workers was
accepted exactly once (stress asserts this; surface its verdict).
A FAIL is investigated, never re-run until it passes: budgets are
ratcheted to a measured basis (DECISIONS 2026-08-12), so a breach is a
regression until proven flaky — and if it IS flaky, that finding goes in
the report too.
Cleanup
stress prints its psql delete line for the synthetic stress-* insertIds —
run it (since M6 it deletes from BOTH events and outbox: kafka is the
default transport, so every receipt also left an outbox row). Restart the
API before any follow-up error-path probing: a just-hammered process
answers differently.