Repository navigation
feat(OMN-20006): Usage page shows measured cost, unmeasured runs and the savings series - #358
Merged
Merged
Conversation
…the savings series The Usage page showed cost_usd, which sums estimated and unrecorded cost with measured cost, and filtered rows by tenant in the browser. It now renders usage-by-model-day.v1 as served: tokens in and out, the measured cost (Not measured, never $0, when no run was measured), the count of unmeasured runs beside it, and calls. The exposure is tenant-scoped on the server, so every served row renders and the browser computes nothing. The savings-per-day series (US-3) renders metering-summary.v1 day rows, naming the baseline in the series title, with Not measured for a null saving and no summed total. That exposure is not served yet (dev lane answers 404 unknown_topic, 15:21Z), and a page may bind only exposures the lab catalogue lists as served, so the panel stays unbound and names what it waits on until omnimarket#3368 serves it. Both renderers are registry components, declared as the readers of the two topics. omnibase_infra's exposure-reader-coverage gate resolves readers only from the registry, so this is what lets usage-by-model-day drop its consumers opt-out and lets metering-summary pass with none. Palette classes come from the same probe: usage visible and degraded (200, no rows yet), savings hidden until it answers 200. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
Contributor
|
OCC autobind did not mint a companion for this PR: no changed-file candidate could be proven RED against the merge base, and emitting a PR-existence probe instead would be non-falsifiable evidence (OMN-15247). Hand-authored evidence is required. Considered 20 changed file(s):
This verdict is occ-autobind's, derived from this head and its merge base alone. A second producer, the occ-companion-effect backstop, consumes its own command on the lab bus and may still mint a companion under the generic binding. Whether it does depends on that consumer running, not on this diff (OMN-18876). |
This was referenced Oct 5, 2026
This was referenced Oct 5, 2026
Brings in dev bad7af9 (#357, #355, #353; #354 already merged at b70cb08). Conflicts: LocalDashboardPage.tsx imports keep UsageByModelDayTable and dev's TenantNotConfiguredState; resolveConfiguredTenant is dropped because the Usage table no longer filters by tenant in the browser. local-dashboard-contracts.spec.ts keeps the Usage scenario and dev's OMN-20225 backend/host/tier and quality/tier-mix scenarios. Screenshots regenerated by running local-dashboard-contracts.spec.ts on the merged tree. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
…t change The dev merge re-rendered every local-dashboard screenshot. Thirteen are of pages this PR does not change and differed from dev's files only by machine rendering, so they are restored to dev's bytes. The Usage screenshot, the one page this PR changes, keeps its new render. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
This was referenced Oct 6, 2026
One conflict, PENDING_SOURCES in LocalDashboardPage.tsx: dev bound overview-tokens to metering-summary.v1 and dropped it; this branch's usage-savings-series stays pending until the next commit binds it. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
…1 now that it is served (US-3) omnidash#365 put metering-summary.v1 in the served catalogue, so the series this branch held pending binds now: usage-savings-series reads metering-summary.v1 (window_kind, window_start, baseline_model, baseline_state, savings_usd, runs_total) and renders through the US-3 day-series component, which shows the day rows as served and never the all row (the Overview headline). PENDING_SOURCES is empty again, as on dev. The component is renamed SavingsSeries -> MeteringDaySeries, after the exposure it renders as MeteringTotalCard is: OMN-19980's R4 guard refuses any savings field name in page code, and it read the component identifier as one (savings_series). No field is read by name in the page. Tests: the Usage render test now serves day rows plus the all row and asserts the two day rows and no all row (red before this change); pages.test asserts the binding; the unserved case still names metering-summary.v1. Full omnidash suite: 2078 passed, 0 failed; tsc and eslint clean. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
…the savings series, never the all row The 1440x900 Usage scenario asserted the old pending state. It now asserts the series table names its baseline, shows the served day row (2026-10-02, $0.40, 46 runs), has no all row ($1.2184) and no pending text. Red when the page stops routing usage-savings-series to MeteringDaySeries. Chromium: 19 of 19 passed. Only the Usage screenshot is updated; the other pages are unchanged by this PR. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
…names each allowed savings read Codex's #358 review (2026-10-06 12:07Z) found R4 scanning only LocalDashboardPage.tsx and the loader, so the Usage savings series' own module, which reads savings_usd, was outside it, and 051fb54 had presented the MeteringDaySeries rename as satisfying R4. The rename was only a better name. R4 now also reads every @/components/dashboard/ module the page imports, taken from the page's import declarations so a new one is scanned too; an import that resolves to no file fails the read. A savings read is allowed only as a named entry (file, owner, component), and Step B proves each entry is live (R4 reports it when unallowed) and that its component binds metering-summary.v1 and nothing else. The two entries are the US-3 series: MeteringSummaryRow and savings() in SavingsSeriesWidget.tsx, reading usage-savings-series' rows. Red first: with no entries the widened scan reports the three reads at SavingsSeriesWidget.tsx lines 22 and 36. Mutation: binding usage-savings-series to delegation.savings.v1 makes Step B fail, naming both entries. Planted cases cover a stale entry, a wrong-exposure component and a missing component. Full omnidash vitest: 2079 passed, 15 skipped, 0 failed; tsc and eslint clean. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
…raph, and match the binding Codex's re-review of 4a8bbae (2026-10-06 12:28Z) found three gaps: an allowance exempted any savings field read by its owner (row['counterfactual_baseline_usd'] inside savings() passed), the allowed field was not checked against the component's binding, and discovery read only the page's direct imports, so a helper imported by a dashboard module was outside the scan. - An allowance now lists the exact fields it may read; any other savings field in the same owner, nested or by string key, is still reported. - Step B reports an allowed field that is stale or is not a required field of the component's binding, and an allowance with no field. - Discovery walks import and re-export declarations transitively from the page, keeping every module under src/components/dashboard/; an unresolvable import still fails the read. Planted tests cover the nested and string-key reads, an unbound field, an empty field list and a transitive helper. Mutations, each reverting one fix: allowing any field in an owner, dropping the transitive walk, and dropping the required_fields check each fail the suite. Full omnidash vitest: 2081 passed, 15 skipped, 0 failed; tsc and eslint clean. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
…nly the dashboard directory Codex's third review (15b4716, 2026-10-06 12:36Z): discovery kept only modules under src/components/dashboard/, so a widget importing a savings-reading helper from src/lib/ escaped. - Discovery now walks every @/, @shared/ and relative import and re-export from the page, transitively (47 files today, src/ and shared/). Package imports are not followed, asset imports (the page's stylesheet) are skipped, and a code import that resolves to no file still fails the read. - Widening the scan surfaced shared/types/topics.ts, whose TOPICS keys costSavingsOverview and delegationSavings name exposures. A property key whose value is an onex topic string names that exposure and is not a read; a read of the key elsewhere (TOPICS.delegationSavings) is still reported. Planted tests: a helper in src/lib read only through a dashboard widget is reached and its counterfactual_baseline_usd read reported; an unresolvable import fails; an exposure-name key is exempt while a savings key beside it and a TOPICS.x read are reported. Mutations: discovery limited to the dashboard directory, and exempting any property key, each fail the suite. Full omnidash vitest: 2082 passed, 15 skipped, 0 failed; tsc and eslint clean. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
… and refuses one it cannot resolve Codex's fourth review (72e0fe7, 2026-10-06 12:43Z; it ran the file, 36 of 36): discovery read import and re-export declarations only, so a reachable widget could load a savings-reading helper through a dynamic import('./x') and keep it outside the scan. moduleSpecifiers() now walks the whole file: import and re-export declarations, import x = require('y'), and anywhere a dynamic import() or require() of a string literal. One whose argument is not a literal cannot be followed, so it throws and the scan fails rather than miss what it loads. Today's page graph has none. Planted test: a widget that dynamic-imports a helper and requires another reaches both, the helper's counterfactual_baseline_usd read is reported, and import(name) with a variable fails naming its line. Mutation: dropping the dynamic walk fails it. Full omnidash vitest: 2083 passed, 15 skipped, 0 failed; tsc and eslint clean. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
jake-b-omni
enabled auto-merge (squash)
October 6, 2026 23:44
omnidash#359 landed first and added one R4 savings-read exception for the Avg saving / call card, as an owner-name map. This branch had replaced the R4 allowlist with typed allowances bound to a file, owner, field set and component, each checked against that component's metering-summary.v1 binding. The two collided in omn19980.savings-source.test.ts. The merge keeps the typed allowances and gives each a state. #359's card reads savings_per_measured_run_usd before any exposure serves it, so its component binds nothing; it enters as 'pending', which is valid only while that component has no binding and fails, naming the fix, the moment one appears. Both name exemptions are kept: topic-key properties from this branch and JSX tag names from #359. #359's own planted test now passes its allowance explicitly, since allowances are scoped to a file. Mutation: dropping the pending check turns the new planted test red. Onex-Lane: omn20006-usage-page Onex-Session: d99c149d-35af-4f1e-b791-28a28187bfc4
Contributor
Author
|
Close-out for OMN-20006: merged 2026-10-07 as
Thank you! |
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.
Update 2026-10-05 08:20Z (head
40d944aaa)40d944aaa. State read 2026-10-05 08:16-08:17Z: merge state BLOCKED;verify / verifyFAILURE (2 contexts, cause not read), 2 CANCELLED, 35 SUCCESS.Summary
The local Usage page for OMN-20006. It renders
usage-by-model-day.v1as served: tokens in, tokens out, the measured cost (Not measuredinstead of$0when no run was measured), and the unmeasured runs counted beside it. It also adds the savings-per-day series (US-3), which readsmetering-summary.v1. Both renderers are declared in the component registry as the readers of those two topics.What changed
src/components/dashboard/usage/UsageByModelDayWidget.tsx(UsageByModelDayTable) shows Day, Model, Tokens in, Tokens out, Cost (measured), Unmeasured and Calls.measured_cost_usd. It never showscost_usd, which also sums estimated cost.Not measured. Money is formatted only, with 2 to 8 decimals, so a Postgres0.25000000reads$0.25and a measured zero$0.00.N unmeasured, ornoneat 0; a missing value rendersNot recorded.tenant_column, so the server scopes the read and the page renders every served row.src/components/dashboard/usage/SavingsSeriesWidget.tsx(MeteringDaySeries, named for the exposure it renders, as Overview'sMeteringTotalCardis; it wasSavingsSeriesbefore051fb54) renders the metering-summarydayrows.Savings vs <baseline>).Not measuredfor a null saving andBaseline unresolvedfor an unresolved baseline.allrow is not drawn and there is no total. The "Modelled" caption is the same as on Overview.metering-summary.v1(since051fb54).usage-savings-seriesreadswindow_kind,window_start,baseline_model,baseline_state,savings_usdandruns_total, and the page routes it toMeteringDaySeries.metering-summary.v1in the served catalogue ondev, and once dev was merged in (4c2a33b) this branch's pending-state tests went red.SavingsSeriesassavings_series. The rename alone did not satisfy R4, because R4 scanned only the page and the loader while this module readssavings_usd(found in review).4a8bbae,15b4716,72e0fe7,2246648, one review finding each):import x = require(), and literalimport()andrequire()calls (47 files today, insrc/andshared/). A dynamic import with a non-literal argument fails the scan, as does an import that resolves to no file. Package and asset imports are not followed.required_fieldsentry of the component's binding, and that the component bindsmetering-summary.v1and nothing else. The entries areMeteringSummaryRow.savings_usdandsavings()readingsavings_usd, inSavingsSeriesWidget.tsx.onex.topic string names an exposure (theTOPICSkeys) and is not a read. A read of the key elsewhere (TOPICS.delegationSavings) is still reported.scripts/generate-registry.tsaddsusage-by-model-dayandusage-savings-series, with implementation keys insrc/components/dashboard/index.tsand topics inshared/types/topics.ts.npm run generate:registryadded exactly these two components and changed no existing one.consumers: noneopt-out, and lets metering-summary.v1 (declared by omnimarket#3368 with no opt-out) pass.visible/degraded(200, 0 rows) and usage-savings-serieshidden/hidden(404), recorded inshared/types/palette-visibility.ts.EmptyandPopulatedstories.src/integration.part2.test.tsx(component count 43 to 45 and the names), the live page test,pages.test.tsand the OMN-17199 reader-declaration test.How it was verified
Update 2026-10-06 12:50Z: dev
250bcacmerged, the savings series bound, R4 widened (head2246648)Supersedes the counts below for the current head. Seven signed commits on top of
28d6184:4c2a33b: merge of dev250bcac(omnidash#365). One conflict,PENDING_SOURCES: dev's removal ofoverview-tokensis kept, and this branch'susage-savings-seriesentry was kept pending until the next commit.051fb54: binds the savings series (above). After the merge, 3 tests were red: the OMN-19980 guard's R2 and AC2c, and this PR'spages.test.tsassertion that metering-summary.v1 is not served. The new Usage render test (served day rows plus the all row, asserting the two days and no all row) was red before this commit.316a557: the 1440x900 Usage E2E asserts the series instead of the pending text.4a8bbae,15b4716,72e0fe7,2246648: R4 widened (above), test file only. Each commit closes one escape a review found:src/components/dashboard/;Each has a planted negative, and a mutation reverting each fix fails the suite. The reviewer ran the R4 file at
2246648(37 of 37) and the dynamic-import mutation (1 failed, then 37 of 37 restored) and passed it.Measured:
2246648: 214 files passed, 2 skipped; 2083 tests passed, 15 skipped, 0 failed. Related suites (src/pages src/components/dashboard/usage src/registry src/layout src/integration.part2.test.tsx) on dev250bcac: 20 files, 289 passed; on the branch at316a557: 22 files, 308 passed.tsc --noEmitandeslinton the changed files: exit 0.316a557with omnimarket#3378 atcb28b12, 8 real delegations, read 12:12-12:15Z). Usage page:2026-10-06 · Qwen3.8-27B · 2128 in · 387 out · $0.00 · none · 8 calls, equal to the served usage row. Token totals equal the CLI (onex metering --json2128/387) and the 8 per-call rows. Measured cost$0.00equals the Overview spend$0.0000. Savings per day2026-10-06 · $0.008126 · 8. A dead-port build shows HTTP 502 and no panels. Planted controls (CLI tokens off by one, page cost $0.01) both make the result FAIL. Limits: every run was local (cost 0 = 0), and none was unmeasured, so AC3 and AC4 rest on the tests. the four commits after it change only a test file.local-dashboard-contracts.spec.ts, Chromium: 19 of 19 passed.usage-savings-seriesaway fromMeteringDaySeriesmakes the Usage E2E scenario fail (1 failed); restored, it passes.local-dashboard-usage-1440x900.png: Savings per day shows2026-10-02 · $0.40 · 46againstclaude-sonnet-5-5, with no all row. It is the only image this PR changes; the regenerated images of other pages are not committed.Update 2026-10-06 03:41Z: dev brought in (head
28d6184)The verification above is from the earlier head
929e232; the counts below supersede it for the current head.bad7af9(omnidash#353, feat(OMN-20225): run rows show the served verdict, backend, host and tier; Overview gains quality and tier-mix panels #355, test(OMN-19921): verify current local dashboard default #357), asa7ac55e; fast-forward over the previous head929e232.src/pages/LocalDashboardPage.tsx(imports): kept this PR'sUsageByModelDayTableand dev'sTenantNotConfiguredState; droppedresolveConfiguredTenant, whose only dev use was the oldUsageTabletenant filter this PR removes. Dev's quality, tier-mix and tenant-not-configured states are kept.tests/e2e/local-dashboard-contracts.spec.ts: kept both, this PR's Usage scenario and dev's two OMN-20225 scenarios.28d6184, so the only image this PR changes islocal-dashboard-usage-1440x900.png.npm run check,npm run lintandnpm run buildeach exit 0.local-dashboard-contracts.spec.ts, Chromium): 19 passed.pre-commit run --fileson the changed files: 0 failed.Head
40d944a(signed), on dev58b2f89df.58b2f89dfwith the new and changed tests overlaid: 6 tests fail (reader declaration 2, pages 2, live page 2), and the two new component test files fail to import. On the branch the focused set passes; after the money formatter, 120 of 120 in the usage, page and registry files.58b2f89df: 1898 passed, 1 failed, 15 skipped. The dev failure (synthetic-fixtures-hygiene) asks git for tracked files and cannot pass in thegit archiveexport used for dev; it passes on the branch.server/routes.test.tsfailed once in 3 branch full runs and passed alone (29 of 29) and in the other 2 runs. It is untouched here.npm run checkandnpm run lint: exit 0.scripts/check-hardcoded-delegation-refs.shandscripts/check-no-env-contamination.sh: clean.local-dashboard-contracts.spec.ts, the new Usage scenario plus "every local page opens in order": 2 passed (re-run on the final head). Screenshot:tests/e2e/screenshots/local-dashboard-usage-1440x900.png($0.25beside1 unmeasured,Not measured, no$0, the savings panel naming what it waits on).python -m omnibase_infra.validators.bus_backed_exposure_readers, with this branch's registry and omnimarket's branch contracts plus omnimarket#3368's metering-summary contract:OK: 22 served bus_backed exposure(s), 0 without a reader;Failure paths
n/a: this change renders served rows and writes no receipt, verdict or report. A read that fails or is not served shows the page's typed per-widget state; with
metering-summary.v1unserved, the savings panel names that exposure (covered byLocalDashboardPage.live.test.tsx).Open defects
shared/types/palette-visibility.tsstill recordsusage-savings-seriesashidden/hiddenfrom the 2026-10-04 probe, which got a 404.metering-summary.v1is served now, and the palette class has not been re-probed. This affects the builder palette only, not the local Usage page.Local gates
--no-verify, noSKIP=, no--no-gpg-sign, nocore.hooksPathoverride. Every pre-commit and pre-push hook ran.Not in this change
tenant_column, the local usage write) is the omnimarket PR on this ticket.Overlap-Reviewed: #355 — omnidash#355 (OMN-20225) changes the Overview and Runs renderers, their page contracts and their tests. This PR changes only the two Usage branches of
TableComponent(UsageByModelDayTableandMeteringDaySeries), the Usage contract and its tests inpages.test.ts, and one Usage E2E scenario. Different pages, so both are needed; whichever merges second rebases the shared files.Overlap-Reviewed: #359 — omnidash#359 (OMN-20009) also changes
src/pages/local/omn19980.savings-source.test.ts: it allowsSavingsPerRunCardto readsavings_per_measured_run_usd(an owner-and-field map) and exempts JSX tag names. Run against #359's headf65ea8b, this PR's guard reports that read and the<SavingsPerRunCard>tag (4 tests fail; checked 12:51Z). Both are needed. Whichever merges second carries #359's read as anR4_ALLOWEDentry (file, owner, field, component, with the field in that component'srequired_fields) and keeps the JSX-tag exemption.Overlap-Reviewed: #354 — omnidash#354 (OMN-20008) adds provenance to Overview and Runs in the same three shared files. No Usage code is touched there and no Overview or Runs code here, so both are needed; whichever merges second rebases.
Evidence-Ticket: OMN-20006
Evidence-Source: OCC#12823