fix(ui): let the marketplace peak gate name the fields it displays - #295
Merged
argszero merged 1 commit intoSep 24, 2026
Merged
Conversation
argszero
deleted the
fix/marketplace-peak-gate-names-the-displayed-fields
branch
September 24, 2026 17:05
argszero
added a commit
that referenced
this pull request
Sep 30, 2026
Release v0.7.29 — a version bump plus the CHANGELOG entry for the 41 commits merged since v0.7.28 (#295–#335). No production logic changes. The release PR now has to touch five files instead of three: #303 and #305 gave the copies in docker-compose.yml / Dockerfile / CHANGELOG.md an executor in src/deploy_gate.rs, so a bump that misses any of them turns `cargo test` red (this is the deliberate cost of those rules). - Cargo.toml / Cargo.lock: 0.7.28 -> 0.7.29 - docker-compose.yml: header comment and `image: aitokenpool:<v>` - Dockerfile: header comment, `docker build -t aitokenpool:<v>` and the `docker run` example - CHANGELOG.md: the `## v0.7.29` entry - src/deploy_gate.rs: the version-scanner self-check asserted against the literal "0.7.28"; it now uses the derived `declared_version()`, so the release number is not left behind as another stale copy in the test. Verified: `cargo test` 462 passed / 0 failed, `cargo fmt --check` rc=0, `cargo clippy --all-targets -- -D warnings` rc=0. A/B with the file bytes restored afterwards: bumping the manifest alone reddens exactly the two version rules and names all five copy sites; a stale CHANGELOG heading reddens only the heading rule; a builder image below the declared rust-version reddens only the >= rule.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
modelsToView()decided whether a marketplace row is peak-priced from one field:Every peak disclosure on the row (the price-cell badge, the capability tag, the detail row, the ×N ratio) is keyed on that single boolean. But the billing engine decides per field —
billing::effective_prices()askspeak_input_per_m > 0.0,peak_cache_hit_input_per_m > 0.0andpeak_output_per_m > 0.0independently, and keeps the off-peak price for whichever field is 0 (src/billing.rs:92-116). Its own unit test pins exactly that partial case ("高峰时段 + 部分缺省 → 缺省项沿用空闲价",src/billing.rs:350-353), and the three fields are independently optional by design (config#[serde(default)], three separate admin inputs,validate_commononly checks>= 0, PATCH falls back per field).So a model with only a peak output price is billed peak every output token in the peak window, while the marketplace shows no peak marker, no peak-price row and no peak-pricing tag; and a model with only a peak input price prints the output cell as
0where the engine actually charges the off-peak output price. Reachable in two clicks from the admin model form, unsignalled, and it never self-heals.The fix makes the trigger name the two fields the row displays (
peakInOn || peakOutOn) and makes each price slot take its own field's peak-or-off-peak price, so every number printed is the price the engine actually charges in the peak window.Related Issue
No linked issue — this repository's issue list is all CLOSED; the defect came from this task's own reconnaissance.
Changes
ui/js/app.js—modelsToView(): the trigger becomespeakInOn || peakOutOn;peakIn/peakOuttake each field's own peak-or-off-peak price;peakMultis the ratio of the enabled field (input first, else output) so it is never 0 while peak pricing applies; the stale comment above the function is corrected to the per-field rule and points atbilling::effective_prices.ui/index.html— cache-bustjs/app.js?v=20260924-2→20260924-3.src/state_gate.rs— new gatethe_marketplace_peak_trigger_names_the_fields_it_displays(R1 upper bound: the trigger names no fieldeffective_pricesignores; R2 lower bound: every peak field the row displays is named by the trigger; R3 neither set empty; R4 positive control:mk.peak.badgestill carries{n}), all four values derived from the sources, plusthe_r171_rules_have_teeth(four synthetic mutants, each flipping exactly one rule).Deliberately out of scope, and why: the sandwich is
displayed ⊆ trigger ⊆ engine, nottrigger == engine.peak_cache_hit_input_per_mis an engine field, but the row displays no cache-hit column andmk.peak.badgealways prints a{n}multiplier — pulling it into the trigger would make a cache-only-peak model read "Peak ×0", a worse lie than no marker. Registered as a separate open item.Tests
cargo test— 378 passed / 0 failed (baseline 376, +2 new tests)cargo fmt --check— cleancargo clippy --all-targets— cleanEvidence
Gate A/B (teeth). Appending the gate to
src/state_gate.rsand compiling it against the pre-fixui/js/app.jsmakes the axis test FAIL withr1=true r2=false r3=true r4=true | engine={"peak_cache_hit_input_per_m","peak_input_per_m","peak_output_per_m"} trigger={"peak_input_per_m"} displayed={"peak_input_per_m","peak_output_per_m"}— exactly the declared R2 violation. Against the fixed tree all four rules hold.jsdom probe.
r171_peak_gate_probe.js— the app's real four scripts, a real logged-in session, a fixture catalog holding the partial-peak shapes — reportsRESULT: ALL LEGS AS DECLAREDfor 6 variants × 2 languages = 12/12 runs:base(the defect) red onD3,A1,A2,A3,A4;fixall green; and four competitors each rejected by their own legs —m_always(mark everything peak) byA2,C2,D3,m_gate_any(fix the trigger only) byA2,m_drop_capsbyA4,m_bogus(trigger names an engine-ignored field) byD3.Byte-exactness. The three code edits are byte-identical to the probe's own
fixvariant (the generator reads the patch constants out of the probe source): the edited tree and the probe'sfixtree hash the same (md5 0d0869d3…, post-instrumentation). The fourth edit is comment-only — stripping//lines from the 4-edit tree reproduces the 3-edit tree exactly.Checklist
fix/…)