feat(runs): what a run cost — each step's, the run's so far and in all, the story's to date in its history and the list (slice 14a) #90
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/run-costs"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Slice 14a of Phase 2: what runs cost (stories/run-monitor-and-inspector.md, stories/story-list.md; ADR 0016, 0018, 0024). Slice 14 is split: 14a the Cost cells, 14b the inspector, 14c the transcripts. The ledger has kept each row's cost and each run's total since the engine; this slice shows them. No new storage, no migration.
What it does
Money.Dollars, for every dollar amount the app shows: the run views, a key's spending and limit in Settings, and the suggestions' calls. Dollars to the hundredth of a cent at most, thousands grouped, the same in every culture; an amount too small to show reads "under $0.0001", never "$0.00".GetRunPageandGetRunStripcount the cost of the rows they read, as their status is.ListStoryRunsgives each run's total and their sum.IRunHistorygainsCostsAsyncfor the list, summed in memory, since SQLite cannot sum a decimal.Decisions worth a look
Reviews
Verified
All six projects pass in full, under
TZ=UTCas the runner runs them and in the local zone, Europe/Berlin. Main measured at1c0622f:1c0622f100% line and branch coverage on the changed lines against main (65 lines, 46 branches), the Razor included.
Mutation probes, one at a time, under
TZ=UTC, every one killed on its first run:Browser pass against the checked-in stand-in on a fresh seed (Chrome headless):
Also in this branch
RecordingDispatcher, a test helper on main: it now counts under the lock its dispatches add under.RunChangedBridgeTests.A_publish_never_waits_for_the_circuit_to_take_a_batchfailed once here with "Collection was modified", the helper enumerating its list while a dispatch from another thread added to it. The bridge suite then passed 40 runs in a row.Known, not this slice
Wrapon Kagaku.UI'sColumnswould let both run headers drop their own flex rules; the history header now uses Kagaku.UI'skg-cluster.concurrent-settlescontract case failed once while verifying 13b (cause unknown); not seen since.🤖 Generated with Claude Code
Slice 14 is split: 14a the Cost cells, 14b the inspector, 14c the transcripts. The ledger has kept each row's cost and each run's total since the engine (ADR 0016); this slice shows them (stories/run-monitor-and-inspector.md, stories/story-list.md; ADR 0024). - The monitor gains a Cost column: each step's attempts, a failed one's included (ADR 0018); none for a step no attempt of which reported a cost — a context or commit step, or a first attempt still running. - The run's page says what the run cost, "so far" while it is live; its line in the history has a Cost column, and the history's header the story's cost to date beside Clear run history. The header wraps on a narrow pane, as the run's page's header does. - The status strip says what its run has cost beside its sentence, "so far" while it is live — never in what its live region says aloud, as it moves with every settled attempt. - The stories list's Cost cell is the story's runs' totals, summed in memory (SQLite cannot sum a decimal); a story that never ran has none, and a cleared run's cost leaves with it. The list reads again on a run's start or settle and on a clear. - RunWords.Cost words every cost the same way: dollars to the hundredth of a cent at most, as the provider's figures in Settings, the invariant culture's; one too small to show reads "under $0.0001". - GetRunStrip and GetRunPage count the cost of the rows they read, as their status is; ListStoryRuns the run's total and the runs' sum; IRunHistory gains CostsAsync for the list. - Docs: ADR 0024 amended (slice 14a, cost); the stories' cost lines ticked — the strip's question and the containers' rows split off —; the plan's 14 split into 14a ✅, 14b and 14c, slice 15 after both. Tests: BlazorAdapter 998 to 1015, Integration 941 to 942; the other four unchanged. All six projects pass in full under TZ=UTC and in the local zone; 100% line and branch coverage on the changed lines (51 lines, 32 branches), the Razor included. - One run through the ledger's own transitions — a writer's stream that broke off once begun, paid for, then a retry that succeeded at the seed's real cost —, read by its page, the history, the strip and the list beside a story that never ran: so far while live, in all once finished, and nothing left once cleared. - The cells: the monitor's (a refusal under the smallest figure), the page's (so far, in all, a paid failure), the history's column and its total — none for an empty history —, the strip's beside its sentence and never aloud, the list's for a story that ran and one that never did, updated by a run's settle. The list's reloads on a run's change and a clear, and none before its first load. The money words at their edges and in a culture that writes a decimal comma. Probes: 18 runs over 18 mutants, all compiling, all killed on their first run, under TZ=UTC. The browser pass, against the checked-in stand-in on a fresh seed: the list said Mira's Winter cost $0.0004 — the seeded run's $0.000428, the timed-out one nothing —, its history the same per run and as its cost to date, the run's page $0.0004 and its writer's row the same; a live run's strip said "$0.00 so far" beside its seconds, the live region saying only that it ran; the list, left open, moved to $0.0008 as the run settled; at 1280 px the history's header on one line, at 390 px wrapping within the story's pane; no console errors or warnings. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>Summary
Summary
Coverage
Kagaku.Flow - 99.7%
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__ColorToken_2
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__Socket_1
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__TypeId_0
Kagaku.Flow.OpenRouter - 100%
Kagaku.Flow.Testing - 100%
Tsumugi.BlazorAdapter - 97.4%
Tsumugi.Domain - 96.5%
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3
Tsumugi.Infrastructure - 98.8%
A55E0373ED22DC58420DBB40F103A46B68DC4D9CE5A49E00B__Pattern_0
on
tion
n
n
Tsumugi.Kernel - 90.9%
Tsumugi.Server - 99.6%
Tsumugi.UseCases - 94.8%
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__AttemptCount_1
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__HexColour_2
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__LineBreaks_0
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__Preface_3
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__SentenceEnd_4
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! Costs, costs everywhere~ ♪ The ledger has been quietly hoarding every row's price since the engine slice, and now it finally gets to tell the story — on the monitor, the page, the history, the strip, the list, even in Settings. A whole slice about money and not one hand-rolled
"$" + x.ToString(...)left standing — the old privateMoney()in ProviderSection was swallowed by the new one like a good little DRY snack~ And I did check every dollar figure in the app now goes throughMoney.Dollars: run views, key spending, key limit, suggestions. One wording to rule them all. Fufu~ wonderful.Verdict: ✅ Looks good to me~
I read the full diff against main and poked the riskiest new branches with three mutation probes — every one died on its first run. Nothing blocking. Not even a little~
What I verified beyond the green CI:
FlowEngine.Attempts.cs: a billed transient attempt (the broken-stream case, ADR 0018) settles throughFailAttemptAsyncand does announceNodeSettled, so the stories list'sRunStarted or NodeSettled or RunSettledfilter catches every event a cost can land with —NodeStarted/InputRequestedrightly bring none. Dismiss publishesEntityKinds.Run(Update) and Clear publishesRun(Delete), both now re-read byOnDomainChangesAsync. Nothing rings unheard~rows.Cost ?? 0mfrom the rows they read — the same read-the-rows discipline as their status — while the history reads the persistedTotalCost, whichRun.Refreshkeeps equal to the rows' sum. Both interleavings (settle between the run-read and the rows-read, and after) are seam-tested inRunPageTests. A disagreement can only be the one the ADR amendment already owns.CostsAsyncfollows its siblings: projected columns,AsNoTracking, in-memory decimal sum with the SQLite limitation named in a comment, and the{StoryId, StartedAt}index covering the filter. A story with no run kept is absent from the dictionary →null→ "—", never a made-up zero. ♡What_its_run_has_cost_follows_the_sentence_and_is_never_said_aloudpins the announcement count while the figure moves. Accessibility cared for properly~My probes (all killed on first run, under
TZas the runner sets it):Money.Dollars'sunder $0.0001branch neutered →MoneyTestsfailed (8/9). Guarded.StoriesEffectsreload filter reduced toRunSettledalone → 4 failures inStoriesEffectsTests, including the both-first-and-last batch positions. Guarded.so farconditional (run.Status.IsLive()) forced off → 4 failures inRunStripTests, including the "never said aloud" test. Guarded.💡 Little ideas (non-blocking)~
amount > 0m && ... == 0mreads as "positive but rounds to nothing"; a negative amount (unreachable today, costs are sums of provider figures) would render as$-0.01rather than something worded. ADebug.Assert(amount >= 0m)-flavored guard could freeze that assumption — but since no caller can produce one, it's purely a someday nicety.✅ What I liked~
MoneyTests's culture test — cloning invariant with a decimal comma and asserting the wording doesn't move. That's the kind of paranoia I adore ♡Factstest helper stripping the InfoTip's text before asserting labels — tests that assert what the reader reads, not what the DOM happens to contain.RecordingDispatcherlock fix, honestly documented with the failure that motivated it.The ledger kept its secrets long enough. Fufu~ merge it when you're ready~ ♡
Automated review by Jibril · 2026-10-06
CI: green for head SHA
53c4c8a· Mutation probes: 3 run, 0 survived · Local suite: skipped (CI green)