Skip to content

feat(OMN-21015): the Runs list shows each run's modelled saving against its named baseline - #411

Merged
Patel230 merged 32 commits into
devfrom
lakshman/OMN-21015-runs-savings
Oct 16, 2026
Merged

Patel230 merged 32 commits into
devfrom
lakshman/OMN-21015-runs-savings

Conversation

@Patel230

@Patel230 Patel230 commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

OMN-21015 AC3: "The Runs list shows savings per run against the named baseline (requirement RU-1), and a typed not-measured state for an unmeasured run". This is the page half. The served half is omnimarket#4001, which serves modelled_savings_usd with its baseline and measurement state on each row of delegation.decisions.v1, written in one transaction by omnibase_infra#5016 (with migration 0059).

Merge order: after #5016 (with 0059 applied) and #4001. Until they merge, the served rows carry no figure, and every run reads "Not measured".

What changed

  • Runs list (src/pages/LocalDashboardPage.tsx): a "Modelled saving" column beside "Local cost". It shows each run's served modelled_savings_usd against the baseline it was priced against, for example "$0.010 vs fixture-baseline". The page formats the served figure and computes nothing.
  • Not measured, never 0: a run with no figure names why, from the served savings_measurement: tokens not recorded, cost not recorded, baseline not resolved, or time not recorded. A run served with no state reads "Not measured". A measured zero is shown as zero.
  • Exact digits: the served decimal is shown digit for digit, padded to two decimals, and never passed through a binary float. A negative saving keeps its sign. A value that is not a plain decimal is not shown as a figure.
  • Guard (OMN-19980) in src/pages/local/omn19980.savings-source.test.ts: one exception, as Jonah ruled on the ticket (2026-10-13 06:59Z, and 2026-10-14 04:50Z: "the same one-field exception in R1, R3 and R4"). It allows exactly modelled_savings_usd, from exactly the Runs decisions binding, formatted only by runModelledCell. Planted controls show any other savings field on that binding, or the same field anywhere else, is still refused.
  • Page declarations: runs.contracts.yaml requires the figure and its two labels on the decisions binding; runs.page.yaml lays the column out.
  • contracts/OMN-21015.yaml: binds AC3 to its page test and AC5 to the guard, beside feat(OMN-21015): one look for the six local pages, with charts and run links #399's items.

How it was verified

On the lab host, at the head 2c48ef15 (a signed merge of dev a2fb9b6b):

  • src/pages/LocalDashboardPage.omn21015.run-saving.test.tsx, R1 to R12: the served saving with its baseline; each of the four not-measured states; no state reads Not measured; a measured zero; exact digits; a negative sign; a non-decimal; the column's place; the page declarations.
  • RED at dev: dev a2fb9b6b's code with this change's three test files laid over it fails all three files: 15 failed, 63 passed.
  • GREEN: the target set (the run-saving test, the Runs test, the savings-source guard, feat(OMN-21015): one look for the six local pages, with charts and run links #399's chart and interactive tests, the page tests): 192 passed. tsc --noEmit: 0.
  • Full suite: 2,562 passed, 15 skipped, 1 failed. The failure is server/__tests__/synthetic-fixtures-hygiene.test.ts, which runs git ls-files and the lab's archive copy is not a git repository. In a git copy of the same tree it passes (1 passed).

Not in this change

The served figure is omnimarket#4001's and omnibase_infra#5016's. AC2 (Tier mix, omnidash#409) and AC4's by-model table land in their own PRs.

Failure paths

  • A measured run with no saving cell: R1 and R6 fail.
  • An unmeasured run that shows 0: R2 to R4, R11 and R12 fail.
  • A figure recomputed or rounded through a float: R8 fails.
  • A second savings field let through by the exception: the guard's planted controls fail.
  • size: n/a, one column, one formatter and its tests.
  • no answer: until the backend serves the figure, every run reads "Not measured", never 0.
  • incomplete verdict: each test asserts the exact cell text, so a missing or changed cell fails.

Open defects

None.

Local gates

  • Pre-commit on every signed commit, including the dev merge: every hook passed.
  • 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.

Overlap-Reviewed: #409 — both are OMN-21015: #409 is AC2 (Overview's Tier mix, TierMixPanel), this PR is AC3 (the Runs list's saving column, RunsTable and runModelledCell). Different components of LocalDashboardPage.tsx; a trial merge of the two heads auto-merges it and conflicts only in contracts/OMN-21015.yaml, where each adds its own items. Both are needed; whichever lands second merges dev and keeps both sets of items.

Requirement RU-1 lists backend among the Runs filters, and the page had
status, cause, model and window only. The backend is the backend_id that
delegation.decisions.v1 already serves and the table already shows, so
this is a filter over a served field and nothing is derived.

A run served without a backend is filtered as Not recorded, the same word
its cell shows, so it is never matched under another backend and never
unreachable. The filter is carried in the address beside the other four,
and an address naming a backend the data no longer has says so in the
select instead of showing an empty choice.
…UTC) and all time

Requirement OV-3 asks for the headline figures in a selectable window, and
the page read the all-time row only. The fold already serves one row per
UTC day beside the all-time row, so "today" is the day row whose
window_start is today's UTC date: a row the runtime served, never a sum
made in the browser.

The page keeps the chosen window and hands it to every card that reads
metering-summary.v1, so the cards cannot show different windows side by
side. A UTC day with no runs has no row; the cards say so instead of
showing 0 or another day's figures. The baseline rule the all-time row
already followed (the row refreshed last wins, a tie is refused by name)
applies to a day's rows in the same way.
… UTC day

The demo shows spend over time against its baseline; the local Overview
had headline totals only. metering-summary.v1 already serves one row per
UTC day with the actual and the baseline spend, so the panel is a new
declared table over rows that are already there.

The panel renders the columns the page declares and nothing else: the page
code names no savings field, the money columns are written as served, and
the last line of each table is the served all-time row, the same row the
headline cards read. It adds nothing up. A figure that was not measured
says so, a day under an unresolved baseline shows no baseline figure, and
after a baseline change each baseline keeps its own table.

The test's two-day fixture is a real record: one install's three stored
metering rows, read on 2026-10-13, without its tenant id.
…acked by model

Requirement US-1 asks for tokens per day as a stacked series split by
model; the page had the per-model-per-day table only. The rows are the
ones usage-by-model-day.v1 already serves.

Each segment is one served row's count and grows by that count, so the
stacking is done by the layout: nothing is added up in the browser and no
total is written. Tokens in and tokens out are separate bars and never one
figure. A model whose count was not recorded is named under its bar rather
than drawn as zero, and a served zero is drawn as a zero.

The series has its own empty-state sentence. With the table's sentence it
would have said the same thing twice on the Usage page, which an existing
test of that page rightly reads as one statement.
…d order

usage-by-model-day.v1 serves the newest day first, then models ascending.
The fixture had the oldest day first while its comment called that the
served order, and the one order test would have passed a component that
sorted days newest first.

The fixture now matches the exposure, and the order test is two: rows in
the served order keep it, and rows served oldest first are rendered oldest
first. A re-sort in either direction now fails exactly one of them.
On the Overview's Spend per day table a missing cell read "Baseline unresolved"
whenever the row's baseline was unresolved, including actual spend and the run
count, which are measured from the runs and do not depend on a baseline. The
metering fold serves exactly that row when no run was measured and no baseline
resolves.

The cell now says "Not measured" for the run-measured figures and keeps
"Baseline unresolved" for the rest on an unresolved row. The page names the
run-measured fields only, so the savings-source guard's rule R4 still holds.
A test pins the grouping to the captured lab catalogue's column list, so a
column served later has to be placed before the suite passes again.

Found in review of the previous head.
… count

Each segment of the Tokens per day bars carried its label, padding and a
border and grew from its content width, so the drawn widths did not follow the
counts: in the review's Chromium fixture 1 token beside 1,000 drew at a ratio
of 0.129 instead of 0.001.

The drawn segments are now empty and grow from a zero basis, so a segment's
width is its count's share of the bar and nothing else. Every figure is
written in a list beside the bar, where a very small count still reads; the
bar itself is hidden from assistive technology.

The unit tests assert the zero basis, the empty segment and a stylesheet rule
with no padding, border, margin or width. jsdom lays nothing out, so a
Playwright spec measures the widths in Chromium on the Usage page: 1 against
1,000 and 300 against 1,200, each within 0.0005 of the counts' ratio. With
padding and a border put back on the segment both tests fail (the spec reads
0.0153 for 1 against 1,000).

Found in review of the previous head.
…ackend filter

The existing browser run fails because the all-time Savings figure is now shown twice and Backend adds a fifth select. Scope the Refresh assertion to its headline card and retain the themed-control checks for every select. Lab browser baseline: 18 pass, 2 fail; corrected: 20 pass with no skips or flaky results.
After dev's OMN-20008 percentage line merged into this branch, the Savings tile reads its figure and its percentage line from the row picked for the chosen window. Nothing pinned that: the OMN-20008 tests run on the default window only.

Five tests, on the card and on the Overview page: today shows today's served ratio beside today's saving; all time shows the all-time ratio; a today row with no ratio shows no line and never the all-time one; a day with no row says not measured and shows no line; on the page the switch moves the figure and its line together and all time brings both back.

Each planted fault is caught and the file restored after it: the line always read from the all-time row (3 fail); the line read from a day row (3 fail); a null ratio falling back to all time (1 fails); a missing day shown as all time (1 fails).

The comment on that line in MeteringTotalCard said the same all-time row; it now says the same row as the figure. No behaviour change.
…today is chosen

Seen on a lab store of two real days (2026-10-14, omnimarket dev 07fec7b8): with Figures for set to Today (UTC) the metering tiles showed today's 4 runs while Run locally read 6 of 6 runs local and Tier mix read 6, with nothing saying those two were all time. Only the panels bound to metering-summary.v1 have a row per window; the decisions, model-routing and quality-gate exposures serve no day rows.

With today chosen, each Overview panel not bound to metering-summary.v1 now carries one line: All time: this panel does not follow the window above. Nothing is shown when all time is chosen, so the all-time page is unchanged, and no figure moves.

Two tests, written first. Q6 failed before the change (no note on Run locally) and passes after it. Q7 holds the other side: no note on a panel that follows the window, and none after all time is chosen again. Planted faults, each caught and the file restored: the note on every panel (Q7 fails), the note whatever the window (Q7 fails), the note never shown (Q6 fails).

My first version of Q6 listed Compression and Cache hit rate as outside the window. They are bound to metering-summary.v1 and follow it; the lists in the test now follow overview.contracts.yaml.
…the served modelled saving

Jonah's rulings on OMN-21015: 2026-10-13T06:59Z "serve one per-run savings
figure from the metering compute on delegation.decisions.v1 ... and amend R3
to allow exactly that one served field", and 2026-10-14T04:50Z "amending R3
does mean the same one-field exception in R1, R3 and R4 with its labels
classed as labels".

- PER_RUN_SAVING names the exception: modelled_savings_usd, from
  delegation.decisions.v1, in the runs-decisions binding of recent-runs.
  R1, R2 and R3 skip exactly that; R4's allowance check accepts an
  allowance whose field that binding requires. The R4 allowance itself
  lands with the page code that reads the field.
- modelled_savings_usd is classed as a savings figure; its labels
  savings_baseline_model, savings_pricing_manifest_version and
  savings_measurement are classed as labels.
- Planted controls: the exact exception passes; another savings field on
  the same binding, the same field in another component, the same field
  from another exposure, and an R4 allowance for another field are each
  still refused.

Guard tests 46 passed. Planted faults: the exception switched off fails
the exact-exception test; the exception widened to any field fails the
same-binding control.
…st its named baseline

AC3. A new "Modelled saving" column beside Local cost reads the run's own
served figure on delegation.decisions.v1 (modelled_savings_usd, with its
labels savings_baseline_model and savings_measurement) and computes nothing:
- a measured run: the figure, exact digits, "vs <baseline model>";
- tokens not recorded: "Not measured: tokens not recorded";
- cost not recorded: "Not measured: cost not recorded";
- no figure and no state: "Not measured", never 0; a measured zero is $0.00.

The runs-decisions binding requires the three fields, the Runs page
declares the column, and the guard's R4 allowance names the one reader,
runModelledCell (the PER_RUN_SAVING exception of the previous commit).

Tests first: 7 new page tests failed before the column existed and pass
now. Planted faults, each caught: an unstamped run shown as $0.00 (1 fails),
the baseline left unnamed (2 fail). Three existing tests changed, each for a
reason this change makes true:
- the savings guard's "every bound savings figure reads metering-summary.v1"
  now sets aside exactly the PER_RUN_SAVING field and asserts it appears in
  that one binding and nowhere else;
- the Runs "Baseline model label" test asserted no "Not measured" in the row,
  which encoded the old no-per-run-saving rule; it now asserts the new cell's
  typed state and still that the session's savings_usd is never shown;
- the fixture-badge test's row selector /measured/ now also matched the new
  "Not measured" text; it is anchored to the run id.

Whole suite: 233 files passed, 2 skipped; 2,275 tests passed, 15 skipped.
tsc --noEmit 0; eslint --max-warnings=0 on the changed files 0.

Not shown here: the served columns. They come from the omnimarket half of
AC3, not yet released, so this lands after it.
…or digit

Found by independent review of 1e4e947: runModelledCell passed the served
decimal through Number and back, so 123456789.123456789 rendered as
123456789.12345679. It now matches the served text as a plain decimal
(optional minus, digits, optional fraction), pads the fraction to two places
and never converts to a binary float. A negative saving keeps its sign before
the dollar; a value that is not a plain decimal (1e3) is not shown as a figure.

Tests first: three new cases (precision, negative, not a decimal) failed
before the fix and pass after it. Runs and local page tests 382 passed; whole
suite 2,278 passed, 15 skipped; tsc 0; eslint 0.
…ced figure

omnidash#397 re-captured the lab catalogue (captured_at 2026-10-14T04:30:11Z),
and metering-summary.v1 now lists savings_pct_of_counterfactual. S9 failed on
the merge with dev, as designed, until the new column is placed in a group.

It is the ratio of the modelled saving to the baseline's spend, so it is
baseline-priced: on an unresolved-baseline day it reads Baseline unresolved,
which is what declaredCell already gives a column outside RUN_MEASURED_FIELDS.
…AC7 and AC8

omnidash#394 made repo evidence required: dod-verify on #396 refused with
"pull request cites OMN-21015 but carries no contracts/OMN-21015.yaml".

contracts/OMN-21015.yaml copies the four criteria from the ticket body and binds
each to the page test that proves it (test_passes, run with npx vitest from the
omnidash tree). It validates against omnibase_core's ModelTicketContract, and
omnimarket 0.4.310's node_dod_verify (the pinned verifier), hosted audience, on a
clean copy with no node_modules, returns verified: 4 verified, 0 failed, 0
skipped, no unbound criterion.
Extends contracts/OMN-21015.yaml (from #396) with the criteria this change
proves, each bound to its page test with test_passes. Validated against
omnibase_core's ModelTicketContract; omnimarket 0.4.310's node_dod_verify,
hosted audience, on a clean copy returns verified with every check verified,
none failed or skipped, and no unbound criterion.
bb804ff is the squash of omnidash#396; its tree equals 02af97f, which the
parent commit already merges, so this branch's tree is kept unchanged.
The backend serves savings_measurement baseline_unresolved (the run has tokens and
cost but no baseline to price against) and unknown_timestamp (no time to pick a
pricing manifest), each with no figure. The cell said a bare Not measured; it now
says why, as it already did for unknown_tokens and unknown_spend, and still shows
no number. R11 and R12 failed first and pass on the lab; tsc 0; the full suite
passes but for the synthetic-fixtures check that needs git metadata.
…s-savings

# Conflicts:
#	contracts/OMN-21015.yaml
…ard under tests/

The hosted evidence check refused both AC3 and AC5 on this PR with "no
test-side change at the merge base": a test colocated under src/ is not
read as a test-side change, so the base control ran dev's code and dev's
copy of the guard. Both bound files move to tests/ unchanged except their
imports, which now use the @ alias; the contract's two commands follow.
Their relative imports pointed into src/ from the old location; from tests/ they resolve through the @ alias instead. No assertion changes.
@Patel230
Patel230 marked this pull request as ready for review October 16, 2026 20:15
@Patel230
Patel230 merged commit 5e1e992 into dev Oct 16, 2026
41 checks passed
@Patel230
Patel230 deleted the lakshman/OMN-21015-runs-savings branch October 16, 2026 20:22
@Patel230

Copy link
Copy Markdown
Contributor Author

Close-out for OMN-21015 AC3, the omnidash page half: merged at 2026-10-16 20:22:01Z as 5e1e992d (a signed squash), from reviewed head 13762ff1.

What landed

  • The Runs list has a "Modelled saving" column beside "Local cost". It shows each run's served modelled_savings_usd against the baseline it was priced with, and a typed "Not measured" state for an unmeasured run, never $0.00.
  • The OMN-19980 savings-source guard allows exactly that one served field, with its labels classed as labels, per Jonah's rulings on the ticket (2026-10-13 06:59Z and 2026-10-14 04:50Z).
  • The figure is served by omnimarket#4001 (merged as 71d01704), whose migration 0059 is vendored by omnibase_infra#5016.

How it was verified

  • Lab host, at the merged head's tree (1,202 files, matching git ls-tree): the run-saving test, the Runs page test, the savings-source guard and the two captured-catalogue suites give 155 passed; tsc --noEmit exits 0. The full vitest suite gives 2,562 passed and 15 skipped. Its one failure, the synthetic-fixtures hygiene test, runs git ls-files and fails the same way on dev 98b8098b in a lab copy without .git; hosted CI runs it green.
  • The captured lab catalogue predates #4001, and the catalogue tests pass without an excuse entry, so no catalogue change was needed.
  • Hosted CI at the head: 37 checks green, CLEAN. Independent review and merge by codex-01a13eed on Lakshman's YES for this head.

1 sources read: this PR's body, checks and merge record, the OMN-21015 thread · 2 live at 20:23Z: MERGED, omnidash dev identical to 5e1e992d · 3 weakest claim checked: "fails the same way on dev", by running that one test on an archive of dev 98b8098b on the lab host ("not a git repository").

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.

1 participant