diff --git a/test/entity-card-perf.slow.test.ts b/test/entity-card-perf.slow.test.ts index 831317be1..928bf1647 100644 --- a/test/entity-card-perf.slow.test.ts +++ b/test/entity-card-perf.slow.test.ts @@ -12,12 +12,15 @@ * loosen in CI only with evidence of runner noise). The protocol DOC * promises this number; the bound is op-layer latency (transport * excluded, as documented). - * 2. RATIO GUARD (machine-independent) — entity p99 ≤ 50× max(getPage p50, + * 2. RATIO GUARD (machine-independent) — entity p99 ≤ 100× max(getPage p50, * 1ms) on the same corpus. Calibration: the card is ~7 indexed reads + - * a keyword search on the miss path, measured ~21× a 1ms-floored - * getPage at 20K pages — an O(N) scan regression lands at 200ms+ - * (≥200×), far past the ceiling even on a slow runner, while the - * 2.4× headroom absorbs planner noise. + * a keyword search on the miss path. It measures ~21× a getPage p50 of + * ~2.5ms, but on a fast runner getPage p50 floors to 1ms and normal + * entity p99 (~50ms) reads as ~50×. An O(N) scan regression lands at + * 200ms+ (≥200×), far past the ceiling even on a slow runner. The ceiling + * is 100× (not 50×) so the guard is never STRICTER than the 100ms absolute + * budget when getPage floors to 1ms — the earlier 50× tripped on fast + * runners (a p99 tail ÷ a sub-ms median) while p99 stayed well under budget. * * The 200K-page validation is a documented MANUAL recipe in * docs/protocol/MEMORY_VERBS_v1.md — not CI-gated (seed time would dominate). @@ -40,8 +43,12 @@ const MEASURED = 200; const TARGET_ENTITIES = 50; // pages the measured calls rotate over const P99_BUDGET_MS = 100 * (Number(process.env.GBRAIN_PERF_BUDGET_MULTIPLIER) || 1); -// entity p99 ≤ 50× max(getPage p50, 1ms) — see the calibration note above. -const RATIO_CEILING = 50; +// entity p99 ≤ 100× max(getPage p50, 1ms) — see the calibration note above. +// (100×, not 50×: at the 1ms getPage floor, 50× would cap p99 at 50ms — stricter +// than the 100ms absolute budget — and tripped on fast runners where a p99 tail +// is divided by a sub-ms getPage median. 100× stays far below the ≥200× O(N) +// regression signal.) +const RATIO_CEILING = 100; function percentile(sorted: number[], p: number): number { const idx = Math.min(sorted.length - 1, Math.ceil((p / 100) * sorted.length) - 1);