dashboard: a success rate never rounds up to 100.0% while failures stand
Gates / frontend (push) Successful in 1m27s
Gates / test (push) Successful in 1m53s
Gates / test-aarch64 (push) Successful in 7m7s
Gates / package (push) Successful in 3m57s
Gates / container (push) Successful in 15s
CI / gates (push) Successful in 13m15s

This commit is contained in:
2026-08-19 17:54:02 +02:00
parent 64c0d723a6
commit 3dd8214ef2
3 changed files with 41 additions and 2 deletions
+2 -1
View File
@@ -8,7 +8,8 @@ Sections are written by hand. Nothing here is generated from commit messages: th
### Fixed ### 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-<unix seconds>`, 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-<unix seconds>`, 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 ## [0.0.6] - 2026-08-17
@@ -149,3 +149,32 @@ test("an empty pool says so instead of drawing a table", () => {
expect(screen.getByText("No upstreams configured.")).toBeTruthy(); expect(screen.getByText("No upstreams configured.")).toBeTruthy();
expect(screen.queryByRole("table")).toBeNull(); 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();
});
@@ -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 * `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%`. * 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 { 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)}%`;
} }
/** /**