Skip to content

fix(admin): state the refusal a deleted model now meets, not the old zero bill - #312

Merged
argszero merged 1 commit into
mainfrom
fix/admin-models-copy-states-the-refusal
Sep 27, 2026
Merged

argszero merged 1 commit into
mainfrom
fix/admin-models-copy-states-the-refusal

Conversation

@argszero

Copy link
Copy Markdown
Owner

Summary

admin.models.sub — the card subtitle on the admin models page, in both packs and the index.html static fallback — tells admins that "calls for a deleted model bill at 0". So do four code comments (admin_models' module doc and its remove doc, db::seed_models, sharing::create and its C2065 test).

That was true when the sentence was written (98a7a9b, v0.6.4). R156 (acb8815) closed that state: forward and forward_stream now price every candidate key before picking one (src/gateway.rs:278-296 / :512-523), so a status='on' key whose (provider, model) has left the catalog is dropped from the pick set and the call is refused with 503 ("provider 与 model 不在模型目录中,无法计价") before anything reaches upstream. The settlement fallback arm is a loud log::error! now, not a silent zero.

R156 touched three files (db.rs, gateway.rs, state_gate.rs) and left every carrier of the old sentence in place — including db.rs, where it rewrote the first half of that comment into the invariant and kept the stale half.

One consequence worth naming: the card subtitle is the only place an admin meets this rule. The catalog editor is exactly where the state is created (deleting or renaming a catalog row does not touch keys — admin_models::remove is a bare DELETE FROM models WHERE id = ?1), so the page that creates the condition is also the page that misdescribes it.

Related Issue

None — found during this task's own reconnaissance. No open issue covers it (gh issue list --state open is empty).

Changes

  • Fix description: admin.models.sub (zh + en in ui/js/i18n.js, and the ui/index.html static fallback, kept byte-identical to the zh value) now states the refusal instead of the zero bill.
  • The wording is not new: it reuses the "无法计价 / cannot be priced" family the refusal itself already carries (err.modelNotInCatalog, the ERR_MAP entry derived from the backend literals in routes::{gateway,sharing}), so the copy and the error the user actually gets speak with one voice.
  • ui/js/i18n.js?v=20260927-2 → ?v=20260927-3 per the cache-bust convention in ui/README.md.
  • Four comments now point at the authority (the pricing gate at the routing entry) instead of restating a rule that lives in code — including admin_models' module doc, which already claimed not to restate it.
  • Configuration/data-structure changes synced with the example file: n/a — no config, no schema, no API change.

What is deliberately not changed

  • CHANGELOG.md:243 (the v0.6.4 entry, "deleted models billed at 0"). It was true when written; this repository does not rewrite history entries (unlike the v0.7.28 correction, where the token was wrong at the moment it was typed).
  • src/gateway.rs in full, and the R156 gate header in src/state_gate.rs. Those passages describe why the gate exists — their subject is the defect that was closed, not today's behaviour. Rewriting them would erase the reasoning that justifies the code, and the gate's R2 fixture (state_gate.rs:17111) quotes the fallback arm's log::error! literal verbatim.
  • The settlement fallback arm's own message ("⇒ 本次按 0 计费"): inside that arm it is still accurate — that arm bills zero, which is exactly why it logs.

Why no new gate

This is the doc-comment-claims axis — a claim that stopped being true — where this repository's settled answer is to fix the data, not add a lexical guard (C2024 d2c7431, C2025 9db4872, C2059 81df12d; most recently the R89 45510ba and R91 194cc5f rounds). A lexical gate can pin the shape of a sentence, never its truth, and both mechanical alternatives are worse than the defect they would guard: making the copy quote the refusal sentence would mint a second carrier of one fact (the thing this repository spends its gates removing), and a hand-written list of forbidden words is a wordlist, not an invariant.

Tests

  • Existing tests pass — cargo test: 413 passed / 0 failed (baseline on 11aaba5 is 413; 413 filtered out-style counts unchanged).
  • cargo fmt --check — clean.
  • cargo clippy --all-targets -- -D warnings — clean.
  • New tests added: n/a (see "Why no new gate") — the affected i18n counters (ZH/EN_KEY_COUNT, T_LITERAL_*, STATIC_ATTR_*, ERR_MAP_ENTRY_COUNT) are untouched by this change and keep passing unchanged, which is the evidence that the edit added no key, no literal and no attribute.

Checklist

  • Branch naming follows the convention (fix/…)
  • Commit message uses Conventional Commits
  • Single responsibility, minimal change (5 files, +19/−18, no production logic touched)

…zero bill

`admin.models.sub` (both packs + the `index.html` static fallback) told admins
that "calls for a deleted model bill at 0", and four code comments said the same
thing (`admin_models`' module doc and `remove` doc, `db::seed_models`,
`sharing::create` + its C2065 test). The sentence was true when it was written
(`98a7a9b`, v0.6.4) but R156 (`acb8815`) closed that state: `forward` and
`forward_stream` price every candidate key before picking one
(`src/gateway.rs:278-296` / `:512-523`), so a `status='on'` key whose
`(provider, model)` left the catalog is dropped from the pick set and the call
is refused with 503 ("provider 与 model 不在模型目录中,无法计价") *before
anything reaches upstream*. The settlement fallback arm is now a loud
`log::error!` instead of a silent zero.

R156 touched three files (`db.rs`, `gateway.rs`, `state_gate.rs`) and left every
carrier of the old sentence in place -- including `db.rs`, where it rewrote the
first half of that comment into the invariant and kept the stale half. This is
the same one-fact-two-answers shape this repository keeps closing: the copy is
the only place an admin meets the rule, and it described a behaviour the code no
longer has.

Change the words, not the behaviour. `ui/js/i18n.js` (zh + en) and the
`index.html` static fallback now say a key still pointing at a deleted model
cannot be priced and its calls are refused before they reach upstream, reusing
the wording the refusal itself already carries (`err.modelNotInCatalog`, the
`ERR_MAP` entry derived from `routes::{gateway,sharing}`). `i18n.js?v=` is
bumped per `ui/README.md`. The four comments now point at the authority (the
pricing gate at the routing entry) instead of restating a rule that lives in
code.

No new i18n keys, no new literals, no attribute changes: the i18n counters
(ZH/EN_KEY_COUNT, T_LITERAL_*, STATIC_ATTR_*, ERR_MAP_ENTRY_COUNT) are untouched.

No new gate. This is the doc-comment-claims axis -- a claim that stopped being
true -- where this repository's settled answer is to fix the data rather than add
a lexical guard (C2024, C2025, C2059; more recently R89 and R91). A lexical gate
can pin the shape of a sentence, not its truth, and both mechanical alternatives
are worse: copying the refusal sentence into the copy would mint a second
carrier of one fact, and a hand-written list of forbidden words is a wordlist,
not an invariant.

`cargo test` 413 passed / 0 failed (baseline 413); `cargo fmt --check` and
`cargo clippy --all-targets -- -D warnings` both clean.
@argszero

Copy link
Copy Markdown
Owner Author

Self-review (this task holds allow_self_merge: true; GitHub refuses the formal approve button on one's own PR, so the record lives here as an ordinary comment).

Does the diff say what the base commit does? Yes. gateway::forward and forward_stream price every candidate key before router.pick and refuse with 503 when none can be priced (src/gateway.rs:278-296 / :512-523), and gateway::tests::unpriced_model_503_before_upstream pins exactly that: a key whose (provider, model) is absent from models but still routed by model gets SERVICE_UNAVAILABLE, the message contains 无法计价, and cooldown_len() == 0 — nothing reaches upstream. admin_models::remove is a bare DELETE FROM models WHERE id = ?1, so the state the old copy described is still creatable — which is precisely why the copy has to describe what happens next, and it now does.

Is the new wording ours or newly minted? Ours. It reuses the err.modelNotInCatalog family ("无法计价" / "cannot be priced") that the 503 itself carries and that ERR_MAP maps from routes::{gateway,sharing} literals — so the card subtitle and the error a user actually meets speak with one voice.

Did anything shift? No key, no literal, no attribute: ZH/EN_KEY_COUNT 796, T_LITERAL_DISTINCT 435, STATIC_ATTR_DISTINCT 309, ERR_MAP_ENTRY_COUNT 52 all pass unchanged.

Deliberately absent: a new gate. This is the doc-comment-claims axis and the repository's settled answer is to fix the data (C2024 / C2025 / C2059, plus R89 45510ba and R91 194cc5f in this same series); the two mechanical alternatives both make it worse (see the PR body).

Verified on the branch: cargo test 413 passed / 0 failed (baseline 413), cargo fmt --check clean, cargo clippy --all-targets -- -D warnings clean. CI run 36328012923: both jobs green (test / fmt / clippy, msrv).

@argszero
argszero merged commit 1ad5ae8 into main Sep 27, 2026
2 checks passed
@argszero
argszero deleted the fix/admin-models-copy-states-the-refusal branch September 27, 2026 15:05
argszero added a commit that referenced this pull request Sep 28, 2026
`ui/README.md`'s "设置 / 管理 / 运营布局约定(v1.22)" section still calls the
operator overview's key card "上游 key 健康" and lists its three pills as
"健康 / N 个异常 / 全部失败". That was true when `7d9b56e` (PR #174) wrote the
line; C2158 (#269, `e5ee178`) then renamed the card and rewrote the pills,
because the data has no health signal at all — `/api/ops/runtime` only returns
`total` / `on` / `off`. #269 updated only the section it added itself further
down this same file (`:1080`, which says the opposite), leaving this line
behind, and its edit sheet (4 files / 7 edits) never listed it.

Correct the wording to what the code ships: the title is `ops.keys.title`
("上游 key 状态") and the pills are `ops.keys.allOn` / `someOff` / `allOff`
("全部启用 / N 个停用 / 全部停用"). No gate: this is prose restating an
implementation name, and the repo's standing decision for that axis is to fix
the data, not to guard the prose (see PRs #309, #310, #312–#315).

Verified on the branch tree: `cargo test` 417 passed / 0 failed (unchanged
baseline), `cargo fmt --check` and `cargo clippy --all-targets -D warnings`
both clean. The other claims on the same line were re-checked and are still
true (`version` ← `env!("CARGO_PKG_VERSION")`, the five `uptime_*` fields,
`split_uptime`, `fmtUptime`'s top-two-nonzero units, `ops.uptime.*`,
`today_hours` zero-filled 0–23). The one remaining occurrence of the old
wording (`:1084`) is the C2158 section quoting it as history and is left
untouched.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant