From 3dd8214ef246b9834997942c39d778eb9dea3d4d Mon Sep 17 00:00:00 2001 From: m5r Date: Wed, 19 Aug 2026 17:54:02 +0200 Subject: [PATCH] dashboard: a success rate never rounds up to 100.0% while failures stand --- CHANGELOG.md | 3 +- .../dashboard/UpstreamHealthTable.test.tsx | 29 +++++++++++++++++++ .../dashboard/UpstreamHealthTable.tsx | 11 ++++++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9e521c..c7b4fe8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ Sections are written by hand. Nothing here is generated from commit messages: th ### Fixed -- **A query log set aside by a schema change is no longer named `corrupt`.** Every recreate wrote the old file to `querylog.db.corrupt-`, whatever sent it there — including the fingerprint mismatch an upgrade causes, where the file is a healthy database this build simply cannot read. The name is the only account of the reason that outlives the log line, so it read as an accusation. This is not hypothetical: on the 0.0.6 upgrade it cost a real query log, deleted during a post-deploy cleanup on the strength of the word `corrupt`. The name now says which of the four cases it hit: `querylog.db.corrupt-…`, `.not-a-database-…`, `.quick-check-failed-…` or `.schema-changed-…`. The 0.0.6 upgrade produces `schema-changed`. Nothing else about the recreate changed, and no existing aside file is renamed. +- **An upstream success rate no longer rounds up to 100.0% while failures stand.** One decimal place cannot hold 12,696 successes out of 12,698 attempts: it rounded to `100.0%`, so the row claimed perfect reliability next to a failure count of 2. Neither end of the scale is reachable by rounding any more — `100.0%` needs an actual absence of failures and `0.0%` an actual absence of successes, and a rate a hair off either end shows `99.9%` or `0.1%` instead. +- **A query log set aside by a schema change is no longer named `corrupt`.** Every recreate wrote the old file to `querylog.db.corrupt-`, whatever sent it there — including the fingerprint mismatch an upgrade causes, where the file is a healthy database this build simply cannot read. The name is the only account of the reason that outlives the log line, so it read as an accusation and invited operators to delete an intact file. The name now says which of the four cases it hit: `querylog.db.corrupt-…`, `.not-a-database-…`, `.quick-check-failed-…` or `.schema-changed-…`. The 0.0.6 upgrade produces `schema-changed`. Nothing else about the recreate changed, and no existing aside file is renamed. ## [0.0.6] - 2026-08-17 diff --git a/admin/src/features/dashboard/UpstreamHealthTable.test.tsx b/admin/src/features/dashboard/UpstreamHealthTable.test.tsx index 1bc724f..8283613 100644 --- a/admin/src/features/dashboard/UpstreamHealthTable.test.tsx +++ b/admin/src/features/dashboard/UpstreamHealthTable.test.tsx @@ -149,3 +149,32 @@ test("an empty pool says so instead of drawing a table", () => { expect(screen.getByText("No upstreams configured.")).toBeTruthy(); expect(screen.queryByRole("table")).toBeNull(); }); + +test("a rate a hair under perfect never rounds up to 100.0% while failures stand", () => { + // The real row that produced this: 12,698 attempts, 2 failures, 99.984%. + renderTable([ + entry({ + period: period({ attempts: 12_698, successes: 12_696, failures: 2, success_rate: 12_696 / 12_698 }), + }), + ]); + + expect(screen.queryByText("100.0%")).toBeNull(); + expect(screen.getByText("99.9%")).toBeTruthy(); +}); + +test("a rate a hair above nothing never rounds down to 0.0% while successes stand", () => { + renderTable([ + entry({ + period: period({ attempts: 12_698, successes: 2, failures: 12_696, success_rate: 2 / 12_698 }), + }), + ]); + + expect(screen.queryByText("0.0%")).toBeNull(); + expect(screen.getByText("0.1%")).toBeTruthy(); +}); + +test("a window with no failures at all still reads 100.0%", () => { + renderTable([entry({ period: period({ attempts: 500, successes: 500, failures: 0, success_rate: 1 }) })]); + + expect(screen.getByText("100.0%")).toBeTruthy(); +}); diff --git a/admin/src/features/dashboard/UpstreamHealthTable.tsx b/admin/src/features/dashboard/UpstreamHealthTable.tsx index 1d6e308..5646b60 100644 --- a/admin/src/features/dashboard/UpstreamHealthTable.tsx +++ b/admin/src/features/dashboard/UpstreamHealthTable.tsx @@ -122,9 +122,18 @@ function statusNow(upstream: UpstreamHealthEntry): "Available" | "Backing off" | /** * `success_rate` is null exactly when the window holds no attempt, and that must * not read as perfect reliability — hence the em-dash rather than `100.0%`. + * + * One decimal place cannot hold 12,696 of 12,698: it rounds to `100.0%`, and the + * row then claims perfection beside a failure count of 2. Neither endpoint may + * be reached by rounding — only by actually having no failure, or no success. */ function successRate(period: UpstreamPeriodStats): string { - return period.success_rate === null ? "—" : `${(period.success_rate * 100).toFixed(1)}%`; + if (period.success_rate === null) return "—"; + + const rounded = period.success_rate * 100; + if (rounded > 99.9 && period.failures > 0) return "99.9%"; + if (rounded < 0.1 && period.successes > 0) return "0.1%"; + return `${rounded.toFixed(1)}%`; } /**