Repository navigation
feat(OMN-21015): one look for the six local pages, with charts and run links - #399
Merged
Merged
Conversation
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.
…rder served The Workflow page still shows fields of the newest decision and says the full path waits on run-trace.v1. This adds the component that will show that path; it is not bound to the page yet, because the exposure is not served until omnimarket's node lands. - RunTimeline renders one run's steps exactly as given and sorts nothing: the order is the exposure's step sequence (WF-1). - The failing step is the one the terminal row names in failing_step_sequence; it is looked up, never inferred (WF-2). - A duration that was not measured reads "Not recorded", never 0 ms; an unknown step kind is shown as served; a run with no steps says so. - Ten component tests over a chain recorded from a real delegation on the lab: served order kept when rows arrive in another order, the timed-out run marks the provider call and its cause, a run that recovered marks nothing, a terminal naming an unserved step marks nothing. Not in this commit: the page binding, entry by id or newest run (WF-3), and removing the "waits on" text. Those follow the exposure.
The fixture carried the lab's model id in three rows, which the hardcoded-model-config hook refuses, and it had no final newline. - The model id is replaced by a neutral name. Every other field is as the lab run published it, and no test reads the model name. - The test file's header says so. The repository's pre-commit hook is not installed in this checkout, so the previous commit did not run it. Run by hand on the three files with the core repository path set: every hook passes.
…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.
…ce.v1 Until now the page showed four lines derived from the newest decision row and said the full path was not served. The node serves run-trace.v1 in omnimarket#3839, so the page now binds it. The exposure is read for exactly one run, with ?correlation_id=. Which run: the one the address names, else the newest by the decisions binding's written_at. That is also OMN-21015's AC9 (requirement WF-3): a run in the Runs list links to its own path, a pasted correlation id opens that run, and no id shows the newest run. Three things in the page loader had to change for "one run" to be safe: - reads in flight were shared per exposure. They are now shared per exposure and parameters; otherwise a second run would have been handed the first run's answer. - last good rows are kept across a failed read only for the same run. - run-trace.v1 is never read without an id. With no run to name it is not read at all, because unfiltered it serves every run's steps together. The page never falls back to another run. An id no run has says that run has no steps. Steps answered for another run are refused. A failed or unserved read is named as that, not as a run with no steps. A truncated answer says it is not the whole run. The placeholder steps derived from the decision row are gone. The decision's verdict, backend, host and tier stay on the page as a summary of the run shown, taken from that run's own row. Not green yet: two tests in src/pages/local/pages.test.ts refuse a binding to an exposure the captured lab catalogue does not list, and run-trace.v1 is not in it until omnimarket#3839 is served on a lab install. The catalogue capture is its own commit.
…ial chip Four things seen when the page was read in a browser at 1440x900 against a lab store holding two real runs: - The run ids in the Runs list are links to Workflow, but looked like the plain text beside them. They now take the brand colour and an underline. - The run summary's finish time wrapped over three lines in its field cell. The terminal step shows the same moment on one line, so the field is removed. - The summary's last field ran into the first step: there was no gap between the two. There is one now. - The sidebar still marked Workflow as partial, which it says of a page whose exposure is not served yet. Workflow reads run-trace.v1 now, so its chip goes. Usage, Credentials and API Keys keep theirs; those flags belong to their own tickets.
Read live against a lab store holding two real runs, the page was hard to
read: each step was one dot-separated line with a timestamp to the
microsecond, and nothing told a pass from a failure at a glance.
Each step is now a block on a rail. It carries its name, its served
outcome as a pill (green when it succeeded, red when it failed), its
duration at the right, its served time under the name, its details as
labelled chips ("backend: local-coder") and its reason as a line of its
own. The dot on the rail takes the outcome's colour, and the step the
terminal names as failing is ringed.
The run's decision row is a row of tiles with the verdict as a pill. The
run id is set in the mono face, with a tag when it is the newest run and
not one that was asked for.
What is shown did not change. Every value is a served field and the order
is the order served. A timestamp's text is the served string, character
for character; only its clock part is set in a stronger tone, and nothing
is parsed into a date. A duration that was not measured still reads Not
recorded.
…MN-21015-uniform-pages
Read live against a lab store, the six pages worked and looked like six different drafts: bare headings, figures in three sizes, plain-text verdicts, unframed tables. This is the shared layer, and it changes how things look only: - every page header carries the icon its sidebar entry uses, beside the title and what the page shows; - every panel has a small upper-case label with its own icon; - headline figures are set in the mono face at one size, and the two savings figures are green; - a run's verdict is a pill (green passed, red failed) in the Runs list and in Overview's recent runs, as it already is on Workflow; - tables are framed, with a quiet upper-case header row, and ids and times in the mono face; - a state that is not a figure (not served, not measured, no rows) is one framed line, and an error is a red-edged one. No figure is added, removed, reordered or reformatted. The icons are Lucide, which the sidebar already uses. Charts and the remaining pages' own parts follow in their own commits.
…d day rows The demo's Overview leads with baseline against actual spend over time. Ours carried the same rows as a table only. The chart is a second way of showing metering-summary.v1's day rows, so it plots only what those rows serve: one point per served day row, in the order served. spendByDayGroups shapes them and nothing else decides what is drawn: - the all-time row is not a day and is not a point; - a figure a row does not carry is a gap, never 0, and gaps are not bridged; - an unresolved baseline has no baseline points, and its actual spend is still plotted; - two baseline models are two charts, never one joined line, because their modelled figures are priced differently; - each value is the served decimal, and the tooltip shows the served text, not the number the axis needed. The table of the same rows stays under the chart. It is drawn with echarts, which the repository already depends on, through its SVG renderer; colours come from the theme's status tokens. Ten tests, written before the module and red until it existed (the type check could not resolve it). They mock the chart library and compare the option it is handed with the shaped rows.
Read live, the header took two tall lines to say what the sidebar entry and the breadcrumb already say: the page name, then a sentence under it. It is now a single row: a smaller icon, the title, and what the page shows on the same line, cut with an ellipsis when it does not fit. The text is unchanged and still in the page; only the space it takes is given back to the figures.
…te as a meter Two served fractions were shown only as bare numbers: a tier's share of the tier-routed runs read "1", and the quality pass rate read "1". The tier mix now leads with one bar, a segment per served tier in the order served, each as wide as its served pct_of_tier_routed, with a legend that names every segment and its share. The quality panel leads with a meter of the served overall_pass_rate. The table and the field list under them are unchanged, so neither adds a figure. Both fields are fractions between 0 and 1. That was read in the runtime, not assumed from the name: the Postgres view (migration 0055) and the local store's view both serve count / tier_routed_total with no factor of 100. So: - nothing is recomputed from counts; the bar follows the served share even when the counts would give another number; - a row that is not tier-routed is not a segment; - a tier with no served share is left out of the bar and named under it; - a share or a rate off the 0 to 1 scale means the page and the runtime disagree about the unit, and nothing is drawn: the bar says why, the meter is absent. An older tier-mix test fed the panel 71.43 and 28.57, a scale the runtime never serves. Its fixture now uses 0.7143 and 0.2857; what it asserts (the table shows the served value as served) is the same. Fifteen tests, written first and red until the module existed; ten mutants, each caught. On a lab store with two real runs the one segment measured the full width of its bar for a served share of 1.0, and the meter read 100.0% for a served rate of 1.0.
Lakshman, reading the pages live: the name of the page and a sentence about it at the top repeat what the sidebar entry and the breadcrumb already say, on every page, and push the figures down. The title and its description no longer take any space. They stay in the page for a screen reader and for anything that finds a page by its heading. What is left at the top is one toolbar row: the page's own control when it has one (Overview's window switch moves into it) and the runtime mode at the right.
…and names none itself The chart added two commits back chose its own series: it read counterfactual_usd and spend_usd by name. The savings-source guard (OMN-19980, rule R4) allows page code to read a savings figure only through a contract, and it failed on that file: R4 src/pages/local/charts.tsx:16 ServedSeries: counterfactual_usd I committed that chart after running its own tests and the Overview page tests, not the whole suite, so the guard never ran on it. The whole suite ran before this commit: 2,304 passed, 15 skipped, and the 2 known catalogue tests failed. The chart now takes its series from the panel's declared columns, the same ones its table renders: one series per declared column whose format is currency, in declared order, under its declared header. For Overview that is actual spend, baseline spend and savings. The module names no figure, a column the page does not declare is never drawn, and a declared count is not plotted as money. Colours go by a series' place in the declaration. The tests were changed first and went red (9 of 11) against the old module. One failure mode is added to them: the chart deciding for itself what to plot.
…nds copy, Workflow draws the steps Three things a reader could not do on the local pages. Open a run from Overview. The run id in Last run and in each Recent runs row is now the same link Runs already had, to that run's path on Workflow. A run with no recorded id has nothing to open and stays plain text. Copy what is shown. A small Copy button sits beside Workflow's run id and beside the two commands the empty states tell the reader to type. It copies exactly the text it is given and reports what happened: Copied only after the clipboard took it, Copy failed when there is no clipboard or it refused. See the run's shape. Workflow leads with the served steps as a row of nodes, one per served step in the order served, an arrow between each. It reads the same rows as the timeline under it and decides nothing: no step is sorted, merged or dropped, a kind the page does not know is a node named as served, and the marked node is the step the terminal names as failing. Each node leads to its own step in the timeline. Last run's status is now the same pill the tables use. Eleven tests, written first and red (11 of 11) before the code; thirteen mutants, each caught. Whole suite with this change: 2,304 passed, 15 skipped, and the 2 known catalogue tests failed. On a lab store with two real runs the page showed five nodes for each run's five served steps.
… token bars get colours In the two wide run tables a run id broke over eight lines and set the height of every row. The id link is now shown on one line and cut with an ellipsis. The whole id is still the link's text and its title, and Workflow shows it in full with a Copy button. Measured in Chromium at 1440x900 on a lab store with two real runs, before and after: Runs rows 168 px -> 130 px Overview rows 92 px -> 74 px both tables 1096 of 1096 px, before and after (no sideways scroll) A table timestamp may now break before its time and before its fraction. The text is the served text, character for character. A 13ch floor on that column was tried and measured to push the Runs table 17 px past its panel at 1440, the fault OMN-20759 fixed, so the column has no floor and the timestamp still wraps over several lines there. Fixing that needs a column dropped or the table scrolled, which is not this commit's call. Usage's token bars were two greys. Each series now has its own colour, the same on the segment and on its legend swatch. No segment's size changes. The timestamp cell was written before its test. Two of three controls on it were not caught by the existing tests; a test (I8) was then added and all three are caught.
…er a newer one Codex's review of efcf5e8 found it: reads of one page can overlap. The refresh button and the page's interval each start a read while an earlier one is still unanswered, and every read wrote its answer when it finished. On Workflow, with no id asked for, that put an older run back on screen: 1. the first read finds run A newest and waits on A's steps; 2. run B lands, the reader refreshes, B's steps are shown; 3. A's steps arrive late and replace B, under the "newest run" tag. The fault is in the page's load effect, so it held for every local page; Workflow is where a late answer names a different run. Only the read started last may write now. A read that was overtaken answers a question the page has already asked again, so its rows, its failure and its end of the loading state are all dropped, however late it answers. A read of another page or another asked-for run was already dropped; that check is unchanged. Four tests, written first. W17 (the sequence above) and W19 (an overtaken read that fails late shows its failure while the newer read is unanswered) failed on efcf5e8. W18 (later refreshes are still shown after one read was dropped) and W20 (an overtaken read whose catalogue request fails shows a page error) pass on efcf5e8 and exist for the fix: each is the only test that catches one way of getting it wrong. Seven mutants of the fix, each caught; W20 was added because the first run of them caught six. Workflow page tests: 26 passed, three runs in a row.
… dev f3ffde6) into the uniform pages
…says only what it copied Codex's review of 5831722 found both. Tier bar. The stylesheet gave every segment a 2px floor, so a small served share was drawn wider than it is: shares of 0.0001 and 0.9999 drew as 2px and 1,096px, eighteen times the served ratio. The floor is gone; a segment's width is its served share and nothing else. A tier too small to see is read from the figure written under the bar, so that figure may not round a share away: a share above zero is written <0.1% where it used to read 0.0%, and one below the whole >99.9% where it used to read 100.0%. Exactly 0 and exactly 1 are written as before. The pass-rate meter uses the same writer and follows the same rule. Copy button. It kept saying Copied after the text beside it changed. On Workflow the run id changes under a mounted button when a newer run arrives, so the button named an id the clipboard did not hold. A copy still unanswered at that moment marked the new id when it answered. The button now keeps what the last copy did together with the text it copied and says so only while that text is still the one shown; of overlapping copies only the one started last reports. Tests first. Eight failed on 5831722 with assertion failures (I9 x2, I10 x2, I12, H9, H10 x2), and the real-browser test of the drawn widths failed with the ratio Codex measured (0.00182 against 0.00010). Three more (I11 x2, one H10 case) pass on 5831722 and guard the fix against over-correcting. Twelve mutants of the fix, twelve caught. The browser test passes three runs in a row at 1440x900. The Usage screenshot under tests/e2e is rewritten by the same spec run; it now shows the page as this branch draws it.
…rk from the keyboard Stage 5's U4 asks that a legend toggle hides its series and works from the keyboard. The chart library draws its legend inside the drawing, where Tab never reaches it. So the legend is now page markup: one button per plotted series above the chart, each saying whether its series is shown, and the chart is handed only the series that are shown. What a reader's choice may not change: - a series keeps its colour, which belongs to its place in the page's declaration and not to its place among the shown ones; - the tooltip still pairs each name with its own served text (the chart numbers the series it was handed, so the lookup is among the shown ones); - a refresh of the served rows keeps what was hidden hidden; - each baseline's chart has its own legend; - with every series hidden no empty chart is drawn; the figure says why. Two things seen in the browser and fixed with it. The status green and the brand lime were too alike to tell two lines apart, so the series use the theme's chart colours (blue, orange-red, teal, yellow). The chart's own text fell back to the browser's serif, so it is handed the page's font. Tests first: ten legend tests failed before the legend existed (the buttons were not there), and the font test failed before its change. Twelve mutants of the legend, twelve caught. One of them, state shared across baselines, is a weak control: it breaks seven tests at once, so it shows the test for that case can fail but not that it fails alone. In a real browser (tests/e2e, run by hand, 1440x900): Tab reaches the second toggle, Enter removes that series' line from the drawing and leaves the other two, Space brings it back. With a toggle that hides nothing the test fails. The browser test was written after the legend; that control is what shows it can fail.
…s, and no number that was not served Stage 5 names two properties of the six local pages that nothing held yet. U1, one look. A page is a header row and a grid of panels; a panel is a heading with an icon and, under it, only the shared parts (headline figure, caption, field list, table, chart, bar of shares, meter, typed state, filter row, pager, the run parts). The new page test reads every piece of text a page draws and fails on any that sits outside them. Each page is read twice: with every exposure it binds served, over constructed rows, and with nothing served, because the typed empty states are parts too. So that the guard is not looking at an empty page, each served page is also held to one value only the constructed rows carry. U6, no sample data. The pages were redesigned beside a demo that shows sample figures. The new source guard reads the code that draws the pages and refuses a figure typed into the markup, a figure kept as text the page can show, a missing value turned into a number (?? 0, || 0), and a number put straight into the markup. Six fallbacks to a number that are not figures (a decimal precision, the refresh interval twice, a page size, a panel width, a tooltip index) are on a read list with a reason each; the list can only shrink. It cannot see a number computed at run time from served values; that is the per-figure comparison's job. Both guards pass on the pages as they are: they pin a property, they do not drive a change. What shows they can fail: - one look: nine mutants of the page code, nine caught. The first run caught eight: a mutant that keeps the served rows from the panels was missed, because the check that a page drew something accepted a headline that read "Not measured". That check now asks for a served value. - no sample data: ten planted snippets refused, eight honest ones let through, and five faults planted in the real page code each reported. The first run flagged two sentences that carry a digit inside a name (run-trace.v1, a ticket id); the markup rule now looks for a word that is a figure on its own. No product code changes.
…ard that sees through brackets Lakshman asked for a bar chart and a pie chart on the dashboard (chat, 2026-10-13 about 14:55Z). Both draw served values only. Spend per day is a bar chart. A day is one figure, not a point on a slope: with one served day a line was three loose dots, and a line between two days suggested values in between that nothing served. Each series is a bar per day, side by side and never stacked, because the sum of actual, baseline and savings is no figure the runtime serves. The legend buttons, the tooltip and the colours are unchanged. Tier mix is a donut. Each slice is one served tier and sweeps exactly its served share of the ring, clockwise from twelve o'clock in the order served. Shares that add up to less than the whole leave the rest of the ring empty; shares that add up to more than the whole are not a split of one whole, so nothing is drawn and the reason is said (a hair over 1 from the runtime's own rounding is allowed). Pointing at a tier, in the list or on the ring, marks it and names it in the middle; the list takes keyboard focus for the same. The flat bar this replaces is gone, so the share is drawn once. A slice is a real arc. The first version laid a dash on a circle, and the real-browser test showed why that is wrong: Chromium measures that circle as 99.36 units where the markup assumed 100, and drew a quarter turn 0.5% short, with or without a declared path length. An arc it draws to within one part in 36,000. The no-sample-figures guard missed three planted faults in review of 2dc622d: a signed number, a bracketed number and a missing value read as a bracketed 0. The scanner now looks through brackets, casts, non-null marks and a leading sign. It then refused a layout string this same commit had added to the bar chart, which was removed. Tests first, except where said: - bars: one test RED on its assertion (line, expected bar); - donut: its tests were RED because the component did not exist, and the arc version's six were RED against the dash version; - guard: six planted controls RED before the scanner changed; - in Chromium at 1440x900, 36,000 points round the ring are each asked which slice they lie in: RED against the dash version (a 0.2499 share off by 0.0012), green three runs against arcs. The legend test now counts bars. Sixteen mutants of the donut and the bars, sixteen caught; the first run caught eleven of thirteen, and two fixtures were sharpened (shares whose sum rounds over 1, and 0.1 + 0.2). The review's three plants and the five earlier ones are each reported in the real page code. Existing tests that changed, and why: the chart test that pinned line series now pins bars; the legend test reads a series' colour from its bars; the tier tests read a slice's start and sweep where they read a bar width.
Lakshman asked for bar charts on the dashboard (chat, 2026-10-13 about 14:55Z). Usage had none: its savings per day was a table only. The panel now mounts the same chart Overview uses, above the table that was already there. It plots the money columns the page declares for this panel, so usage.page.yaml now marks the Savings column as currency; the chart names no field itself and the savings-source guard passes unchanged. One bar per served day, the all-time row is no day, a day with no served figure has no bar, and with no day rows there is no chart and the panel's typed state stays. The table is untouched. Usage gets no pie. A pie of models needs each model's share, and the runtime serves counts per model and day, not shares; working a share out in the browser is what the ticket's AC4 rules out until an exposure serves it. Tests first: two of four RED before the change (no figure, no option handed to the chart); the other two pin the table and the empty state and pass before and after. Four mutants, four caught. One expectation of mine was wrong and was corrected before the change: the table writes a served 0.300000 as $0.30, not $0.300000.
A series' colour followed its place among a chart's plotted columns. Savings is third on Overview (teal) and the only series on Usage, where it came out first (blue): one figure, two colours, on pages that are meant to share one colour scheme. A panel can now say which of the four chart colours each of its plotted columns takes. Usage's savings panel gives its one series the third, the colour the same figure has on Overview. A panel that says nothing colours by place, as before, and a place off the palette is ignored. The colour was first put in the page file, as an attribute of the declared column. The page-file validator refuses a field it does not know (three of its tests failed), so that was taken out again and the page files are unchanged by this commit. Tests first for the page: a test that renders Overview, then Usage, and compares the Savings colour failed (blue against teal) and passes now. The chart's own two tests of the new prop were written with it. Five mutants, five caught.
…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.
2 tasks done
…ages Two conflicts. src/pages/LocalDashboardPage.tsx: this branch's panel markup (icon, data-component, data-kind) is kept and #396's line for panels outside the chosen window is added under the heading. tests/e2e/local-dashboard-contracts.spec.ts: two comments worded differently on each side; this branch's wording is kept, no assertion differs. On the merged tree: tsc exits 0; whole suite 2,533 passed, 15 skipped, 2 failed, the 2 being the catalogue tests inherited from #397.
…serves run-trace.v1 The Workflow page binds onex.snapshot.projection.run-trace.v1, and the two catalogue tests in pages.test.ts check every binding against the captured lab catalogue. The capture of 2026-10-07 predates the run-trace node, so they failed. Recaptured read-only with GET /projections from the lakshman lab lane at 2026-10-14T04:30:11Z, after that lane was upgraded to omnibase_infra dev ba12aff1 with omnimarket dev 7acb97b7 (0.4.313, which contains omnimarket#3839). It is a dev build, not a released tag. Same shape and order as before: the four fields per exposure, sorted by topic. Rendering the lane's pre-upgrade response the same way reproduces the 2026-10-07 file byte for byte. The new capture only adds: five topics (run-trace.v1 among them, status ok) and new columns on eight; no topic removed and no status changed.
… the OMN-21015 design stage
2 tasks done
…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.
…logue lists all three columns The 2026-10-14 recapture (omnidash#397) lists requested_model, queue_wait_ms and execution_ms on delegation.decisions.v1, so T10 required their entries to go.
…gn's own tests The contract carried only #396's four bindings, so repo-evidence found no evidence of this change's own. It now also binds: - AC5 to the OMN-19980 savings-source guard plus the chart tests (each chart plots only served values, nothing computed in the browser); - AC9 to the run-link tests (a run named on Overview or Runs opens that run, and Workflow links to its neighbours in served order). Both criteria are copied byte for byte from the ticket. On the lab, each check fails at dev 0ba6c5a with only its tests laid over (the run-link and run-chart tests fail on assertions) and passes at this head: AC5 77/77, AC9 24/24.
repo-evidence refused AC5 as always-pass. Its control lays only paths that match its test-side pattern (tests/ trees and Python test files) over the merge base, so this repo's co-located src/**/*.test.tsx files are not laid over. At the base only the OMN-19980 guard existed, and it passed alone. The check now runs the two chart tests, which do not exist at the base (exit 1 there) and pass at this head (36/36 on the lab). The guard still runs in the full suite.
Contributor
Author
|
Merged at 19:49:24Z as
Thank you! |
This was referenced Oct 15, 2026
Merged
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
Stage 5 of OMN-21015: the six local pages (Overview, Runs, Workflow, Usage, Credentials, API Keys) get one look, and the figures the runtime already serves are drawn as charts and made easier to act on. Nothing new is served and nothing is computed in the browser: every chart, bar and meter draws a served value, and a value that is not served is still named, never shown as 0.
This is a draft. #396 merged on 2026-10-15 as
bb804fffand #397 on 2026-10-14 as89f1ad8c, so this diff againstdevis now this branch's own part only. Head:b558da7e, level with devbb804fff.Update of 2026-10-15 (claude-070f1c89)
7e2dce61merges feat(OMN-21015): Overview spend per day and window switch, Runs backend filter, Usage token series #396's final head02af97fe.LocalDashboardPage.tsxkeeps this branch's version plus dev's one change there: RunPicker remounts onkey={requested}instead of copying the id in an effect. The two navigation screenshots keep this branch's capture.0264bcedthen merges devbb804fffwith the ours strategy. That is valid only becausebb804fff's tree equals02af97fe's, which the parent already merges; both facts were checked withgit rev-parseandgit merge-base.0ede655b: the served-since list is empty. The 2026-10-14T04:30:11Z recapture (feat(OMN-19987): the Workflow page reads one run's steps from run-trace.v1 #397) listsrequested_model,queue_wait_msandexecution_ms, so run-timing T10 required their entries to go. This clears "the 2 inherited catalogue failures".b558da7e: Workflow declaredworkflow-run-path-stepstwice. The merge in7e2dce61kept both sides' copy. An independent review found it. The copy is removed, and a page test now fails when any local page repeats a binding id: on the lab it failed on0ede655band passes onb558da7e.b558da7e(.201):tsc --noEmitexits 0. The whole vitest suite at0264bcedgave 2,535 passed, 15 skipped and 2 failed. One failure is the T10 tripwire that0ede655bfixes; the other issynthetic-fixtures-hygiene, which needs git metadata an archive does not carry. Run-timing then passed 21/21 andpages.test.ts76/76. Browser specs (tests/e2e/local-dashboard*.spec.ts, Chromium, one worker, 14:42Z): 39 expected, 0 unexpected, 0 flaky.7e2dce61,0264bcedand0ede655bwere reviewed; the one finding is fixed inb558da7e, and the review ofb558da7eand the dev mergebcbc4e04passed. The eleven commits not independently reviewed before were reviewed from source on 2026-10-15. Two findings are already fixed at this head (a tooltip HTML path, fixed by104a0365; an array read as a number, fixed by5e7a519b). One stands, recorded under Not in this change (typography). Their browser behaviour is covered by the 39 browser specs above.What changed
Workflow startup and browser assertion repair (codex-01a13694, signed commits
fa8dcf64and687f1ba6): the runner-IP reusable caller now grantspull-requests: read, matching the nested OCC preflight it calls. GitHub rejected the previous caller before any job started (run 38445173636). The run-chart browser test now compares the full resolved link URL as text, replacing the partial regex escaping that CodeQL rejected at line472.One look for the six pages (
d443b2d2,82511d8c,9c9903bd): shared panel, heading, table, empty-state and chip styles insrc/styles/local-dashboard.css, with lucide icons on panel headings. The page name and description block at the top is gone from view on all six pages (the heading stays for screen readers); one toolbar row holds Overview's window switch and the runtime chip.Spend per day as a bar chart (
e6b4b3b5,69e5600f):src/pages/local/charts.tsxdraws one series per currency column the page declares for the panel, in the declared order, over the served day rows: a bar per day and series, side by side and never stacked (e34d580b). It names no figure itself, so rule R4 of the savings-source guard holds. The table under it is unchanged.Tier mix as a donut, pass rate as a meter (
6cc0ce7d,29d2d2f0):src/pages/local/shares.tsx. A slice sweeps exactly the servedpct_of_tier_routedof the ring, a fraction between 0 and 1, clockwise from twelve o'clock in served order (e34d580b); pointing at a tier names it in the middle. Shares that add up to more than the whole are not drawn and the reason is said; the meter reads the servedoverall_pass_rate. A value off that scale draws nothing and says why. A share above zero is never written as 0.0%, and one below the whole never as 100.0%.Runs open from where they are named, ids and commands copy, Workflow draws the steps (
c8668a3e): a run id in Overview's last run and recent runs opens that run on Workflow; a Copy button sits beside the Workflow run id and beside the commands the empty states tell the reader to type; Workflow shows the served steps as a row of nodes above the timeline, in the order served, each leading to its step.Run tables (
96f2d677): a run id takes one line, so a row is shorter; Usage's token bars are coloured per series.A chart legend that works from the keyboard (
25eddedf): the spend chart's legend is a row of buttons above the chart, one per plotted series, each saying whether its series is shown. A series keeps its colour when another is hidden, a refresh keeps what was hidden hidden, and with every series hidden the figure says so. The series use the theme's chart colours, and the chart's own text uses the page's font.Savings per day on Usage as bars (
e55d8ebe,f94e18f4): the same chart above the table that was already there, plotting the one money column the panel declares, in the colour the same figure has on Overview. Usage gets no pie: a pie of models needs each model's share, and the runtime serves counts, not shares.The first screen (
783fb6e5,eb5b1b51): Overview opens with the four headline tiles in one row, then the spend chart, the tier donut and the newest run in the next; Usage opens with its two charts side by side, then the table. The Last run card leads with status, cause, model and age. This is what the dashboard requirements ask of the Overview without scrolling at 1440x900 (tokens, spend and savings; the newest run's status, model and age), with the charts added. The page files declare the same arrangement. Recent runs now starts below the first screen.A run's summary says more (
eb5b1b51): the Workflow run summary also shows Required bar, Tokens in, Tokens out, Finish reason, Truncated and Escalations, six columnsdelegation.decisions.v1already serves and no page showed. Each is shown as served; the Workflow contract declares them.Where a run's time went (
b4ecb26c,eed935a4): the Workflow run summary also shows Requested model, Queue wait and Execution, and draws the run's three timings (queue wait, execution, duration) as bars, side by side and never stacked. A timing the row does not carry has no bar and is named under the chart; a served zero keeps its row. The three columns are served since omnimarket v0.4.309 (OMN-19448); the captured lab catalogue is older, so they are on a served-since list that a test forces to shrink once a recapture lists them. The three timing tiles stand together, last in the summary, in the order their bars are drawn in (5e7a519b).Runs draws the runs it lists (
b4686d79): above the list, Duration per run (one bar per listed run, coloured by its status) and Tokens per run (in and out side by side, never stacked). Both draw exactly the rows the table lists, after the same filters and on the same page of the list, in its order. A click on a bar opens that run on Workflow; a key of buttons hides a status from the drawing and not from the list. Nothing is added up or averaged.A chart reads only a number or decimal text as a point (
5e7a519b): a value served as a list holding one number was read as that number and drawn; it is a gap now, on every chart. Found in review ofeed935a4.A run's model and backend filter from its row (
529452d8): on Runs the model and the backend in a row are buttons that set that one filter and go back to the first page; Clear filters is shown while any filter is set and resets all five. They are the filters the selects already have, so the charts, the pager and the address follow.From one run to the next (
0dfc9ef7): on Workflow a run that is among the served runs links to the newer and the older run, in the Runs list's newest-first order. A pasted id that is not among them gets no links.A tooltip writes served text as text (
104a0365): the chart library puts a tooltip formatter's result into the page as HTML, and the Runs charts' tooltips (sinceb4686d79) and the spend chart's tooltip (since it was added on this branch) wrote a served run id, model or day into it unescaped, so markup in a served value was rendered. Every piece of text a tooltip writes is escaped now. Found in review ofb4686d79from the source; reproduced before the fix; nothing ondevis affected.Run tables fit again (same commit): see the regression under How it was verified.
Two guards, no product change (
2dc622da): a page test that every piece of text on the six pages sits in the header or in a panel's shared parts, and a source guard that page code holds no typed figure and turns no missing value into a number.Overlap-Reviewed: #396 — this lane's own change for the same ticket, and the base of this stack: its head
f83bfd7cis an ancestor of this branch, so this diff contains it unchanged.Overlap-Reviewed: #397 — this lane's own change for OMN-19987, also in the stack: its head
9680b968is merged into this branch (signed merge5831722e). This change adds the step diagram above its timeline and an anchor on each timeline step; it changes no step rendering.Overlap-Reviewed: #384 — this lane's own change for OMN-20008: it adds a percentage line under the Savings figure in
LocalDashboardPage.tsxand one required field inoverview.contracts.yaml. A trial merge of its headfdfdc3c7into this branch is clean (2026-10-13, 16:55Z).Overlap-Reviewed: #337 — a parked draft for OMN-19730, last pushed 2026-09-28: in
src/App.tsxit adds a lazily loaded execution-graph page and an early return when the address carriesview=execution-graph. The one line of that file in this diff comes from #397 (the Workflow arm of the page switch turns address sync on); none of this branch's own commits touchessrc/App.tsx. The two do not meet.How it was verified
The three merges of 2026-10-14 (claude-e014f5ba): dev and #397's head merged with no conflict (signed
d644ac09,e3329f61). #396's head conflicted in two files (signeda351ebc0):src/pages/LocalDashboardPage.tsx, one hunk: this branch's panel markup is kept and feat(OMN-21015): Overview spend per day and window switch, Runs backend filter, Usage token series #396's new line for Overview panels outside the chosen window ("All time: this panel does not follow the window above.") is added under the panel heading. That line is feat(OMN-21015): Overview spend per day and window switch, Runs backend filter, Usage token series #396's change, described there; the one-look page guard of this branch passes with it.tests/e2e/local-dashboard-contracts.spec.ts, two hunks: comments worded differently on each side; this branch's wording is kept and no assertion differs.tsc --noEmitexits 0; the whole suite gives 2,533 passed, 15 skipped and 2 failed, the 2 being the catalogue tests inherited from feat(OMN-19987): the Workflow page reads one run's steps from run-trace.v1 #397.a351ebc0(Chromium, one worker, one retry allowed and none used, 01:49Z to 01:50Z): all 39 local-dashboard tests passed; the 27 that exist on dev passed there. With retries off these specs are intermittent on that host, on dev too: see feat(OMN-21015): Overview spend per day and window switch, Runs backend filter, Usage token series #396's Open defects, 1. That measurement was not repeated on this branch.Fresh correction verification, codex-01a13694 (2026-10-13): the URL control shows the old regex accepts
/bogus/workflow?...and the exact resolved URL rejects it. All 12 tests intests/e2e/local-dashboard-omn21015.spec.tspass in lab Chromium147 with 0 skipped and 0 flaky (19:49:07Z,18.1s); JSON, source hashes and1440x900 Runs screenshot are retained inOmniNode-Workflow/records/readbacks/2026-10-14-seven-dashboard-codex-01a13694/. The lab source hashes match the committed workflow and browser spec. All applicable declared pre-commit checks pass for each changed file, including YAML, TypeScript, normalization symmetry, script references and the required-check skip-vector check. The first hook attempt lacked sibling-source environment paths; those inputs were configured, then every hook re-ran successfully. The first lab attempt could not launch its missing browser; the official Playwright headless shell was installed only in this isolated test directory, and the suite then ran successfully. Hosted startup and CodeQL results on the new head remain pending publication. No whole-ticket or current-release acceptance claim.On head
104a0365, 2026-10-13, unless a line names an earlier head.f94e18f4, 7 of 27 failed where dev passes all 27. Four were this branch's doing:local-dashboard-omn20759.spec.ts) on rows with a long host and model, and Overview's Recent runs by 103px in the contracts spec's fixture. Cause: the one-line run id (a 9ch floor), letter-spaced headers and a padded pill. Now a 7ch cut, no header letter spacing and a slimmer pill in the two wide tables: no sideways scroll, and a row 149px tall with that fixture, as before the redesign (it had grown to 187px);The other three were already failing on feat(OMN-21015): Overview spend per day and window switch, Runs backend filter, Usage token series #396 and feat(OMN-19987): the Workflow page reads one run's steps from run-trace.v1 #397 as pushed (stale expectations: a fifth filter, Workflow no longer "partial", the savings total shown twice). All are fixed in
783fb6e5; all 39 local-dashboard browser tests pass, run twice at104a0365. Running them before that commit found one more of the same kind: the new tokens axis wrote "0" at its origin, which the older check for an unmeasured zero on Runs refuses; fixed in the same commit. Ten tests in three other specs (delegation-*,mobile-crawl) fail identically on dev and are untouched.src/pages/local/pages.test.tschecks the Workflow page against a captured lab catalogue that does not holdrun-trace.v1yet. They are not caused by this change and are not fixed here.tsc --noEmitexits 0;eslint --max-warnings=0onsrc/pagesexits 0.charts.omn21015.test.tsx,charts.omn21015.legend.test.tsx,shares.omn21015.test.tsx,LocalDashboardPage.omn21015.interactive.test.tsx,LocalDashboardPage.omn21015.one-look.test.tsx,LocalDashboardPage.omn21015.usage-savings-chart.test.tsx,LocalDashboardPage.omn21015.layout.test.tsx,LocalDashboardPage.omn21015.run-summary.test.tsx,omn21015.no-sample-figures.test.ts.tests/e2e/local-dashboard-omn21015.spec.ts, 1440x900): 36,000 points round the ring are each asked which slice's stroke they lie in. Shares of 0.0001, 0.2499 and 0.75 each cover their share to within ten samples, no point lies in two slices, none is left bare, and each slice starts where the one before it ended. The first version laid a dash on a circle and failed this test: Chromium drew a 0.2499 share off by 0.0012, because it measures that circle as 99.36 units where the markup assumed 100. Slices are real arcs now.5831722e: a 2px floor on a tier segment drew a small share too wide; the Copy button kept saying Copied after the text beside it changed. Fixed in29d2d2f0, passed on re-review.2dc622da: the no-sample-figures guard missed a signed number, a bracketed number and a missing value read as a bracketed 0. The scanner now looks through brackets, casts, non-null marks and a leading sign; six planted controls failed first. Fixed ine34d580b, passed on re-review atf94e18f4.eb5b1b51were drawn over one capture of the runtime's answers. 147 figures were compared (every field, table cell, headline, caption, typed state and step text; Overview 85, Runs 34, Workflow 13, Usage 11, Credentials 1, API Keys 3) and all 147 are the same; the six new run-summary fields are listed as new and equal the served row. The four drawings that have a served counterpart (donut slices, pass-rate meter, step diagram and its links) equal the served rows. Two planted wrong values in what the new page is served were reported in all 5 places they are shown. At 14:20Z the 254 served fields equalled the store's own rows, and two values planted in a copy of the store rows were both reported; no delegation was run since.eb5b1b51in the dark and the light theme, with 0 console errors; all six pages were captured in both themes atf94e18f4.eb5b1b51was served from the lab store and walked at 1440x900 at 16:51Z (the all-pages walk was not repeated ateed935a4; the Workflow page was, below). All six pages rendered with 0 console errors and 0 failed requests, the Workflow walk passed, and the run tables and the tier-mix table fit their panels with no sideways scroll.b4686d79, lab store, 17:52Z, dark and light theme, the theme in force read from the page): the newest real run's row serves its duration (4960 ms) and neither its queue wait nor its execution time. The page shows exactly that: one bar, the two tiles as Not recorded, and both named under the chart. 0 console errors, no sideways scroll, the run's steps start 625 px down. A run carrying all three timings is measured in Chromium over constructed rows only, not on a real run. An earlier version of this line said a 17:23Z read was in both themes; that read was the dark theme twice, and this one replaces it.104a0365, 18:16Z): pressing a row's backend put it in the address and the select and showed Clear filters, which undid all three; on Workflow the Older run and Newer run links walked the two real runs there and back. 0 console errors. The store's two runs share one backend and model, so the filter narrowed nothing there; narrowing is shown over constructed rows.Not verified:
Failure paths
n/a: this change renders served rows; it writes no receipt, verdict or report, so there is no size cap, unanswered request or incomplete verdict to test.
What the drawings do when the data is not there is tested instead: no day rows, no declared currency column, a tier with no served share, a share or pass rate off the 0 to 1 scale, a run with no id, a run with no steps, a step kind the page does not know, no clipboard, and a clipboard that refuses. None of them draws 0 or an empty chart.
Open defects
None.
Resolved since the last update
Timestamps in the Runs table wrap over several linesKept: the existing layout checks require it. Measured on the lab atb558da7e(Chromium, 1440x900):local-dashboard-omn20759andlocal-dashboard-contractspass 21/21 as built. WithCellTimerendering the timestamp on one line (no<wbr>), 3 fail, among them "the Runs table fits its panel at 1440x900 with every column" and "Overview and Runs scroll to their last row while the header stays reachable". RU-1 asks for the time column, not one line.Recent runs on Overview starts below the first screenNot a defect against the requirement. Q2's pass condition is "the newest run's status, model and age are visible; a failure shows its typed cause". The Last run card (OV-4) carries those, andlocal-dashboard-omn21015.spec.ts:247asserts it is on the first screen at 1440x900, with a failed newest run's cause. That passed atb558da7e.feat(OMN-21015): Overview spend per day and window switch, Runs backend filter, Usage token series #396 and feat(OMN-19987): the Workflow page reads one run's steps from run-trace.v1 #397 failed browser tests that dev passesResolved: both have landed, and all 39 local-dashboard browser specs pass atb558da7e.Eleven commits not independently reviewedReviewed from source on 2026-10-15: no outstanding finding beyond the typography note under Not in this change.The 2 inherited catalogue failuresResolved: feat(OMN-19987): the Workflow page reads one run's steps from run-trace.v1 #397 recaptured the catalogue, and0ede655bempties the served-since list.No evidence companion yetResolved in9f6d8f82: the in-repo contract now binds AC5 (the OMN-19980 savings-source guard plus the chart tests) and AC9 (the run-link tests) to this change's own tests. Both criteria are copied byte for byte from the ticket. On the lab each check fails at dev0ba6c5acwith only its tests laid over, and passes at this head (AC5 77/77, AC9 24/24).Local gates
--no-verify, noSKIP=, no--no-gpg-sign, nocore.hooksPathoverride. Every pre-commit and pre-push hook ran.Three things a reader should know, each also in its commit message:
e6b4b3b5named two savings fields in page code and failed the savings-source guard's rule R4. Only that commit's own tests had been run before it.69e5600ffixes it; the whole ofsrc/pagesis run before every commit since.d443b2d2was committed after a hook run that had failed; the hooks were then re-run over it and passed.CellTime) was written before its test. The test (I8) was added before the commit and catches 3 of 3 mutants.2dc622dawere written against code that already existed. Each rests on a control instead: a mutant or a planted fault it is shown to catch.f94e18f4the chart's two tests of its new colour prop were written with the prop; the page test that needed it failed first.783fb6e5, each to say the same thing about the page as it is now: intests/e2e/local-dashboard-contracts.spec.ts, the Basis column is looked up in the table that holds the runs; "quality and tier mix below Recent runs" and "Recent runs starting above the fold" are dropped (no panel cut off is still held); the Overview test holds the four tiles and the newest run's status, model and age inside the window (ineb5b1b51), where it held the whole Last run panel; Workflow carries no "partial" chip; the savings total is read in its own card; Runs has five filters. Insrc/pages/local/pages.test.ts, Usage's table is found by its id, not as the first component. The 25 screenshots those specs rewrite are committed as regenerated.e34d580b, and why: the chart test that pinned line series now pins bars; the legend test reads a series' colour from its bars; the tier tests read a slice's start and sweep where they read a bar width.The clone these commits were made in has no pre-commit hook installed, so the repository's declared pre-commit stage was run by hand before each commit: 12 or 13 hooks passed on each of the last eight commits. On
25eddedf,783fb6e5andeb5b1b51the first run failed on the end-of-file fixer, which corrected the files; the hooks were run again over the corrected files and passed before each commit was made.Not in this change
The local pages do not use the shared typography. Independent review (2026-10-15, records/handoffs/20261015T144311Z-agent-to-codex.md) found that new captions, legend labels, filter buttons and run links in
b4ecb26c,b4686d79,529452d8and0dfc9ef7render text withoutText, and.local-run-neighbours asetsfont-size: 0.8125rem. A change to the shared typography tokens does not reach them. The six local pages have been built this way on dev from the start:LocalDashboardPage.tsxhas 0<Text>uses beside 60 raw text elements, andlocal-dashboard.cssholds 23 literal font sizes and no token. This branch follows that convention (0 and 69; 47).eslint --max-warnings=0onsrc/pagespasses, solocal/no-typography-inlineis not broken. Fix: moving all six pages ontoTextand the tokens, a change to pages already on dev, which is not made here and is not decided.Tier mix by model (AC2), savings per run (AC3) and the table half of AC4: each needs a field the runtime does not serve yet. That is stage 2.
The 20-delegation lab walk (AC6) and the dashboard-access line on API Keys (AC10).
The stale "PARTIAL" chips in the sidebar on Usage, Credentials and API Keys, which belong to other tickets.
1 sources read: the ticket thread (12 comments, one page), this PR's body, #396's and #397's merge records, the lab runs at
0264bcedandb558da7e, the two independent reviews of 2026-10-15.2 live at 2026-10-15 18:05 UTC: PR draft, head
9f6d8f82pushed, level with dev0ba6c5ac.3 weakest claim checked: "the ours-strategy merge drops nothing", by comparing
bb804fff's tree with02af97fe's (equal) and checking02af97feis an ancestor of7e2dce61.