Skip to content

feat(OMN-20006): Usage page shows measured cost, unmeasured runs and the savings series - #358

Merged
jake-b-omni merged 12 commits into
devfrom
lakshman/OMN-20006-usage-page-dash
Oct 7, 2026
Merged

jake-b-omni merged 12 commits into
devfrom
lakshman/OMN-20006-usage-page-dash

Conversation

@Patel230

@Patel230 Patel230 commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Update 2026-10-05 08:20Z (head 40d944aaa)

  • Head is unchanged, 40d944aaa. State read 2026-10-05 08:16-08:17Z: merge state BLOCKED; verify / verify FAILURE (2 contexts, cause not read), 2 CANCELLED, 35 SUCCESS.
  • Merge order, superseding the lines about order under "Not in this change": omnibase_infra#4559 merged and an omnibase_infra release that vendors usage-by-model-day 0002; then omnimarket#3378; then this PR, after omnidash#350. omnibase_infra#4559 and omnidash#350 are both OPEN at this read.

Summary

The local Usage page for OMN-20006. It renders usage-by-model-day.v1 as served: tokens in, tokens out, the measured cost (Not measured instead of $0 when no run was measured), and the unmeasured runs counted beside it. It also adds the savings-per-day series (US-3), which reads metering-summary.v1. Both renderers are declared in the component registry as the readers of those two topics.

What changed

  • Usage table. src/components/dashboard/usage/UsageByModelDayWidget.tsx (UsageByModelDayTable) shows Day, Model, Tokens in, Tokens out, Cost (measured), Unmeasured and Calls.
    • Cost is the served measured_cost_usd. It never shows cost_usd, which also sums estimated cost.
    • A null measured cost renders Not measured. Money is formatted only, with 2 to 8 decimals, so a Postgres 0.25000000 reads $0.25 and a measured zero $0.00.
    • The unmeasured count renders N unmeasured, or none at 0; a missing value renders Not recorded.
    • It sums nothing.
  • No browser filtering. The page used to filter rows by tenant in the browser. The exposure now declares tenant_column, so the server scopes the read and the page renders every served row.
  • Savings series. src/components/dashboard/usage/SavingsSeriesWidget.tsx (MeteringDaySeries, named for the exposure it renders, as Overview's MeteringTotalCard is; it was SavingsSeries before 051fb54) renders the metering-summary day rows.
    • Days are grouped by baseline, with the baseline named in the visible column title (Savings vs <baseline>).
    • It shows Not measured for a null saving and Baseline unresolved for an unresolved baseline.
    • The all row is not drawn and there is no total. The "Modelled" caption is the same as on Overview.
  • The savings panel is bound to metering-summary.v1 (since 051fb54). usage-savings-series reads window_kind, window_start, baseline_model, baseline_state, savings_usd and runs_total, and the page routes it to MeteringDaySeries.
    • It was held unbound until the exposure was served. omnidash#365 put metering-summary.v1 in the served catalogue on dev, and once dev was merged in (4c2a33b) this branch's pending-state tests went red.
    • The rename: OMN-19980's R4 guard refuses a savings field name in page code, and it read the identifier SavingsSeries as savings_series. The rename alone did not satisfy R4, because R4 scanned only the page and the loader while this module reads savings_usd (found in review).
  • R4 covers everything the page reaches (4a8bbae, 15b4716, 72e0fe7, 2246648, one review finding each):
    • What it scans: the page and every repository source file it reaches, transitively, through static imports, re-exports, import x = require(), and literal import() and require() calls (47 files today, in src/ and shared/). 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.
    • What is allowed: a savings read only as a named entry (file, owner, exact fields, component). Step B proves each allowed field is live and is a required_fields entry of the component's binding, and that the component binds metering-summary.v1 and nothing else. The entries are MeteringSummaryRow.savings_usd and savings() reading savings_usd, in SavingsSeriesWidget.tsx.
    • What is exempt: a property key whose value is an onex. topic string names an exposure (the TOPICS keys) and is not a read. A read of the key elsewhere (TOPICS.delegationSavings) is still reported.
  • Registry entries. scripts/generate-registry.ts adds usage-by-model-day and usage-savings-series, with implementation keys in src/components/dashboard/index.ts and topics in shared/types/topics.ts. npm run generate:registry added exactly these two components and changed no existing one.
    • omnibase_infra's exposure-reader-coverage gate resolves readers only from this registry. That is what lets the omnimarket PR delete usage-by-model-day's consumers: none opt-out, and lets metering-summary.v1 (declared by omnimarket#3368 with no opt-out) pass.
    • Palette classes come from the same probe: usage-by-model-day visible/degraded (200, 0 rows) and usage-savings-series hidden/hidden (404), recorded in shared/types/palette-visibility.ts.
  • Stories. Both renderers ship Empty and Populated stories.
  • Updated tests. src/integration.part2.test.tsx (component count 43 to 45 and the names), the live page test, pages.test.ts and the OMN-17199 reader-declaration test.

How it was verified

Update 2026-10-06 12:50Z: dev 250bcac merged, the savings series bound, R4 widened (head 2246648)

Supersedes the counts below for the current head. Seven signed commits on top of 28d6184:

  • 4c2a33b: merge of dev 250bcac (omnidash#365). One conflict, PENDING_SOURCES: dev's removal of overview-tokens is kept, and this branch's usage-savings-series entry 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's pages.test.ts assertion 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:

    • the series module outside the scan;
    • an allowance covering any savings field of its owner;
    • helpers outside src/components/dashboard/;
    • dynamic imports.

    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:

  • Full vitest at 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 dev 250bcac: 20 files, 289 passed; on the branch at 316a557: 22 files, 308 passed.
  • tsc --noEmit and eslint on the changed files: exit 0.
  • Lab readback on .201 (lakshman home, fresh install of this head 316a557 with omnimarket#3378 at cb28b12, 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 --json 2128/387) and the 8 per-call rows. Measured cost $0.00 equals the Overview spend $0.0000. Savings per day 2026-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.
  • Playwright local-dashboard-contracts.spec.ts, Chromium: 19 of 19 passed.
  • Mutation: routing usage-savings-series away from MeteringDaySeries makes the Usage E2E scenario fail (1 failed); restored, it passes.
  • Screenshot local-dashboard-usage-1440x900.png: Savings per day shows 2026-10-02 · $0.40 · 46 against claude-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.

  • Merge: signed merge of dev 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), as a7ac55e; fast-forward over the previous head 929e232.
  • Conflicts:
    • src/pages/LocalDashboardPage.tsx (imports): kept this PR's UsageByModelDayTable and dev's TenantNotConfiguredState; dropped resolveConfiguredTenant, whose only dev use was the old UsageTable tenant 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.
  • Screenshots: the e2e run re-rendered all 14. The 13 of pages this PR does not change are restored to dev's files in 28d6184, so the only image this PR changes is local-dashboard-usage-1440x900.png.
  • Verification on the merged tree:
    • This PR's tests plus dev's new tenant and backend page tests: 8 files, 142/142 passed.
    • Full vitest: 209 files passed, 2 skipped; 1985 tests passed, 15 skipped, 0 failed.
    • npm run check, npm run lint and npm run build each exit 0.
    • Playwright (local-dashboard-contracts.spec.ts, Chromium): 19 passed.
    • pre-commit run --files on the changed files: 0 failed.

Head 40d944a (signed), on dev 58b2f89df.

  • Red, then green. At dev 58b2f89df with 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.
  • Mutations: 10 of 10 caught. Each undoes one behaviour and a test goes red:
    • shows cost_usd;
    • a null measured cost renders $0;
    • the browser tenant filter is restored;
    • 0 unmeasured reads "0 unmeasured";
    • a null saving renders $0;
    • the all row is drawn as a day;
    • the baseline is missing from the visible title (caught after a test was strengthened; the first run let it survive);
    • the savings pending source is dropped;
    • the registry drops the metering reader;
    • the page passes no rows.
  • Full vitest:
    • branch: 1919 passed, 15 skipped, 0 failed;
    • dev 58b2f89df: 1898 passed, 1 failed, 15 skipped. The dev failure (synthetic-fixtures-hygiene) asks git for tracked files and cannot pass in the git archive export used for dev; it passes on the branch.
    • server/routes.test.ts failed once in 3 branch full runs and passed alone (29 of 29) and in the other 2 runs. It is untouched here.
  • npm run check and npm run lint: exit 0. scripts/check-hardcoded-delegation-refs.sh and scripts/check-no-env-contamination.sh: clean.
  • E2E, Chromium at 1440x900. 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.25 beside 1 unmeasured, Not measured, no $0, the savings panel naming what it waits on).
  • Reader gate, run locally. 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;
    • with dev's registry: FAIL on exactly usage-by-model-day.v1 and metering-summary.v1.

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.v1 unserved, the savings panel names that exposure (covered by LocalDashboardPage.live.test.tsx).

Open defects

  • shared/types/palette-visibility.ts still records usage-savings-series as hidden/hidden from the 2026-10-04 probe, which got a 404. metering-summary.v1 is 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 gate was bypassed — no --no-verify, no SKIP=, no --no-gpg-sign, no core.hooksPath override. Every pre-commit and pre-push hook ran.
  • Any hook that failed was fixed at the input, not worked around.

Not in this change

  • The data half (measured cost, tenant_column, the local usage write) is the omnimarket PR on this ticket.
  • AC1 and AC2's lab readback ran on this PR's head with the omnimarket PR, not on merged dev (see Update 12:25Z).
  • Merge order: omnibase_infra vendoring, then this PR, then omnimarket, so the registry reader exists before omnimarket deletes the opt-out.
  • Evidence for AC1-AC4 is in onex_change_control#12823: test_passes checks that fail at the merge base and pass at this head.

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 (UsageByModelDayTable and MeteringDaySeries), the Usage contract and its tests in pages.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 allows SavingsPerRunCard to read savings_per_measured_run_usd (an owner-and-field map) and exempts JSX tag names. Run against #359's head f65ea8b, 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 an R4_ALLOWED entry (file, owner, field, component, with the field in that component's required_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

…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
@onexbot-occ-writer

Copy link
Copy Markdown
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):

  • scripts/generate-registry.ts: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)
  • shared/types/palette-visibility.ts: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)
  • shared/types/topics.ts: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)
  • src/components/dashboard/index.ts: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)
  • src/components/dashboard/usage/SavingsSeriesWidget.stories.tsx: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)
  • src/components/dashboard/usage/SavingsSeriesWidget.test.tsx: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)
  • src/components/dashboard/usage/SavingsSeriesWidget.tsx: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)
  • src/components/dashboard/usage/UsageByModelDayWidget.stories.tsx: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)
  • src/components/dashboard/usage/UsageByModelDayWidget.test.tsx: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)
  • src/components/dashboard/usage/UsageByModelDayWidget.tsx: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)
  • src/integration.part2.test.tsx: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)
  • src/pages/LocalDashboardPage.live.test.tsx: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)
  • src/pages/LocalDashboardPage.tsx: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)
  • src/pages/local/pages.test.ts: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)
  • src/pages/local/usage.contracts.yaml: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)
  • src/pages/local/usage.page.yaml: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)
  • src/registry/component-registry.json: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)
  • src/registry/omn17199-reader-declaration.test.ts: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)
  • tests/e2e/local-dashboard-contracts.spec.ts: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)
  • tests/e2e/screenshots/local-dashboard-usage-1440x900.png: no candidate grammar reads this file type (only Python declarations, uv.lock lines, release-artefact lines and contract-pin lines are proposed)

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).

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
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
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
@jake-b-omni
jake-b-omni merged commit 70c1a23 into dev Oct 7, 2026
36 checks passed
@jake-b-omni
jake-b-omni deleted the lakshman/OMN-20006-usage-page-dash branch October 7, 2026 01:35
@Patel230

Patel230 commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Close-out for OMN-20006: merged 2026-10-07 as 70c1a23032.

  • Landed: the Usage page shows measured cost apart from estimates, the unmeasured-run count, and the savings series, each from a served exposure.
  • Verified: lab readback on .201 (AC1 and AC2 met, with planted controls), in the OMN-20006 comment of 2026-10-06 12:34Z.
  • Left: the exposures it reads are served by omnimarket#3378, which is green and in review. AC3 and AC4 still need a run with unmeasured cost to show on the lab.

Thank you!

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.

2 participants