fix(ui): stop reading a key's status count as a health verdict - #269
Conversation
The ops view's upstream-key card rendered enable/disable counts as health
verdicts: `Healthy` / `{n} failing` / `All failing`, with all-off painted
`pill-danger`. The data behind it has no health information at all — `keys`
has no health/error column and `/api/ops/runtime` reports only `total` / `on`
/ `off` — so a user pausing their own key showed the operator a red
"all failing" alarm.
Rename and reword the three verdict keys in both packs (the key names could
lie, so the gate could not pin them), drop `pill-danger` for all-off, and add
`i18n_pack::the_ops_key_health_pill_names_the_state_it_counts` (three rules,
each with its own tooth) plus its synthetic-input control. Measured scope in
`ui/README.md`.
|
Self-review (Committer; Verified locally, at the commit being merged (
Instrument readings taken against this exact tree:
Complementarity measured, not asserted: the competitor fix Scope notes: no backend change ( |
Ships the 10 PRs merged since v0.7.25 (#261-#270). No schema change, no config change, so the deployment-side config.toml needs no edit. One fact, one source / display must equal what it consumes (frontend, 6 places) - #261 read the spendable half of the wallet payload when refreshing your own balance; #262 the transactions payload signature covers the time range, with one reload trigger shared by the four controls; #263 the settings controls are either wired or explicitly inert; #265 the re-list outcome comes from the same entry as its action; #268 the sharing form shows a plan's label, not its config id; #269 the ops card stops reading a key's status count as a health verdict. i18n reachability - #266 every pack key must reach a consumer (the gate), and #270 drops the 23 keys that gate proved unreachable: ZH/EN key count 811 -> 788, sunset list 59 -> 36. Gateway - #267 applies the body limit where axum actually reads it (per-route DefaultBodyLimit, 8 MiB on the three gateway routes; unauthenticated endpoints keep the 2 MiB default). This is the application half of rant 2026-09-18T09:14:18. It also corrects the false v0.7.10 "raised to 70MB" CHANGELOG line, which described installing a layer rather than raising a limit. - Cargo.toml / Cargo.lock: 0.7.25 -> 0.7.26. - CHANGELOG.md: v0.7.26 entry plus the v0.7.10 correction. - ui/index.html cache-bust left as-is: this release touches no UI file; the live values are app.js 20260921-2 / i18n.js 20260921-2. cargo test 302 passed; cargo fmt --check clean; clippy -D warnings clean.
Summary
The ops view's upstream-key card renders enable/disable counts as health verdicts. Its three states read
Healthy,{n} failingandAll failing— and all-off is paintedpill-danger(red).The data behind it has no health information at all:
keyshas no health/error column and/api/ops/runtimereturns only
total/on/off(off = total − on, computed server-side). So an ordinary user action —pausing or re-listing their own key — turns the operator's card red and "all failing" for that provider.
The card's own subtitle and count line already name it correctly ("aggregated by provider · enable status and
total listings", "{total} keys listed · {on} enabled"), so this is drift, not a design choice: the verdict
vocabulary was written before the counters it sits next to.
This change makes the pill say what the data says — the state it counts — and drops the failure colour, because
red is the vocabulary of faults and the data cannot support a fault.
Related Issue
Changes
ui/js/i18n.js(both packs) — the three verdict keys are renamed and reworded, so the key names can nolonger lie either (a gate can only pin a name that means what it says):
ops.keys.healthy= 健康 / Healthyops.keys.allOn= 全部启用 / All enabledops.keys.abnormal= {n} 个异常 / {n} failingops.keys.someOff= {n} 个停用 / {n} disabledops.keys.failed= 全部失败 / All failingops.keys.allOff= 全部停用 / All disabledTitle
ops.keys.title: 上游 key 健康 → 上游 key 状态 / Upstream key health → Upstream key status."启用"is not a new word — the untouched count line already uses it.ui/js/app.js— the provider row renders the new keys; all-off now uses the neutralpill-mutedinstead ofpill-danger.off === 0keepspill-ok, partial stayspill-warn. The comment above the block iscorrected too (it described a "health / N abnormal / all failed" three-state that the data does not have).
ui/index.html— the static fallback text of the card title is synced with the pack (the two are compared byan existing gate);
app.js/i18n.jscache-bust bumped.src/i18n_pack.rs— new gatethe_ops_key_health_pill_names_the_state_it_counts, three rules, each with itsown tooth:
ops\.keys\.(healthy|abnormal|failed|fail|error|down|unhealthy). Rewording a value while keeping amisleading key name still fails.
ops.keys.*keys appearing in thekey_healthconsumer block must be a subset of
{allOn, someOff, allOff, count, empty}.rendered at least once.
Plus a negative control on synthetic input, and the measured scope in
ui/README.md.No config / data-structure changes. No backend change —
ops.rsreports the facts correctly; only the client'swording was wrong.
Deliberate behaviour notes (recorded, not hidden):
pill-warn("this provider has no usable key").pill-mutedis usedbecause red/warning is the vocabulary of faults and the data supports neither; if observability is wanted it
should come from the real health signal, not from painting "disabled" red. Either choice must avoid
pill-danger.pill-mutedalready exists instyle.css.KeyRoutercooling set) into/api/ops/runtime— that is a product/interface extension (new field, provider mapping, semantics for anin-memory set that resets on restart) and a separate change.
data. Stated in
ui/README.md.Tests
cargo testpasses — expected 299 passed / 0 failed (297 after the previous change; thischange adds two tests: the gate plus its negative control). The exact number is taken from
the run, not assumed.
cargo fmt --checkpassessrc/i18n_pack.rstest binary, one arm per rule): on the unfixed tree the new gate is theonly failure and it opens on rule 1 (24 passed / 1 failed); with the edit set applied the whole
module is green (25 passed / 0 failed); each rule also has its own isolating arm — consumer-only
(rule 1), an unregistered key (rule 2), and a deleted pill (rule 3) — and each opens exactly that rule
app.js; the threenew keys 0 → 1;
pill-dangeroccurrences inapp.js3 → 2 (the two remaining are legitimate users:PILL_CLSand the department-quota card);ops.keys.countuntouched; both packs gain/lose 3 keys each(net 0, pack key total unchanged at 811)
index.html+ four real scripts, ops session,fetchstubbed and accounted):the all-off row never shows "failing" in either language; the zero-off row still shows a positive
"All enabled"; the all-off pill is not
pill-danger; and a mechanism leg drives the real control —pausing a key on the sharing page then returning to ops must not turn the provider row into a red fault
Verified locally with the exact CI invocation:
cargo fmt --check(exit 0),cargo clippy --all-targets -- -D warnings(exit 0),cargo test(see above).Checklist
fix/…)