refactor: the run scheduler is a loop, not a graph of callbacks #92

Merged
bjoern merged 2 commits from worktree-refactor-tick-scheduler into main 2026-07-28 07:05:21 +02:00
Member

A redesign of the engine's core, asked for after a production lockout took a whole session to diagnose (#89, #90) and still left the shape that caused it. Same Execution rows, same IStageExecutor, new brain.

The mistake being fixed

The engine was event-driven where it should have been a queue. Everything we hit this week is downstream of that one choice:

  • A stage parked by returning, silently. Nothing recorded that it was blocked, so a wedge was invisible by construction — a run died with 226 executions rescheduled and not one log line.
  • Because parking left no trace, waking had to be pushed: WakeDependentsAsync, pendingWakes, RecheckAfterCompletion, an inFlight dictionary of Lazy<Task> to collapse crossed schedules, a ResumeAsync that re-implemented scheduling for boot, and finally #89's BlockingFailuresAsync heal when all of that still stranded work.
  • Pending meant three things — queued, blocked, wedged — so a screen of six hundred Pending rows told its reader nothing.

What it is now

One loop, one question: what is startable right now? Every state change nudges it awake; the idle tick is only the backstop for a change nobody announced. Deleted: parking, all three wake mechanisms, the in-flight dictionary, the concurrency semaphore (one loop starting the work already knows what the budget is spending), the separate boot path, and the heal.

WorkState is derived every pass, never storedQueued / Blocked / Running / Succeeded / Failed. Storing "blocked" would need something to flip it back when its predecessor succeeded, and that push is precisely the machinery being removed. Recomputing costs one pass over a page's rows and cannot go stale.

Failed means the scheduler has given up. A failure it will retry returns the row to Pending with the error recorded (Execution.FailAttempt), so will retry and gave up are different states rather than one state read against a counter. The cap moved into the engine, where policy belongs, and WorkStates needs no arithmetic at all.

Three previously bolted-on behaviours now fall out of the rules:

behaviour how it works now
self-healing on restart recovery forgives the attempt streak; a wedge heals on the boot carrying the fix, with no code that knows what a wedge is
no more attempt 4 of 3 a requeue restores the budget — the cap rations the scheduler's retries, not a person's
no duplicate runs only a project's newest run is scheduled, so a superseded one goes inert without being deleted

Tests

795/795 green (Domain 91, UseCases 338, Integration 171, BlazorAdapter 195). Whole suite ~13s.

Deliberate behavioural breaks, each with its test rewritten to the new contract — worth reviewing as the real diff:

  • a hand-retry restores the budget, so a retried row reads attempt 1, not attempt 4;
  • recovery forgives every failure in a scheduled run rather than analysing which one blocks something — bounded instead by "only the newest run is scheduled", which is now its own test;
  • a halt and a missing executor are terminal because they are Failed, at one attempt, with no phantom attempts spent.

Two tests were replaced rather than updated, because the machinery they covered is gone — duplicate-schedule collapse and crossed wakes. Their properties are retested against the new design: repeated passes never start a second attempt of the same row, and an exhausted row is never picked up however often the loop looks.

A test-shaped consequence worth calling out: nothing runs unless something turns the loop, which in production is the host's background service. Test harnesses that drive runs now start it explicitly (SchedulerHarness.WithScheduler, and a constructor in two bUnit classes). That surfaced as six SetupChatTests each waiting 30s for a run nobody was picking up — a clearer failure than the silent wedge it replaces, and the argument for this change made by the change.

Notes

  • Two bugs of my own that the suite caught, both instructive. WhenIdleAsync first meant "nothing running and nothing queued", which can never complete unless a scheduler is turning — a hang for any caller holding the engine without its loop. It is a drain again. And my first cut had Failed-below-cap count as retryable, which forced halts to spend phantom attempts to look terminal; three tests objected, and they were right.
  • ADR 0018's decision bullets are rewritten — they described parking and wakes as the design.
  • Rebased onto #90 and #91. Not browser-verified: no user-facing change, and the monitor's own tests cover the strip.
  • Deferred, deliberately: the per-region work granularity (a timeout on region 16 still discards a page's refinement). That was the other half of the redesign we discussed and it wants its own PR — it changes ADR 0017's fan-out, not just the engine's brain.

🤖 Generated with Claude Code

A redesign of the engine's core, asked for after a production lockout took a whole session to diagnose (#89, #90) and still left the shape that caused it. Same `Execution` rows, same `IStageExecutor`, new brain. ## The mistake being fixed The engine was event-driven where it should have been a queue. Everything we hit this week is downstream of that one choice: - A stage **parked by returning**, silently. Nothing recorded that it was blocked, so a wedge was invisible by construction — a run died with 226 executions rescheduled and not one log line. - Because parking left no trace, waking had to be *pushed*: `WakeDependentsAsync`, `pendingWakes`, `RecheckAfterCompletion`, an `inFlight` dictionary of `Lazy<Task>` to collapse crossed schedules, a `ResumeAsync` that re-implemented scheduling for boot, and finally #89's `BlockingFailuresAsync` heal when all of that *still* stranded work. - `Pending` meant three things — queued, blocked, wedged — so a screen of six hundred Pending rows told its reader nothing. ## What it is now **One loop, one question:** *what is startable right now?* Every state change nudges it awake; the idle tick is only the backstop for a change nobody announced. Deleted: parking, all three wake mechanisms, the in-flight dictionary, the concurrency semaphore (one loop starting the work already knows what the budget is spending), the separate boot path, and the heal. **`WorkState` is derived every pass, never stored** — `Queued` / `Blocked` / `Running` / `Succeeded` / `Failed`. Storing "blocked" would need something to flip it back when its predecessor succeeded, and that push is precisely the machinery being removed. Recomputing costs one pass over a page's rows and cannot go stale. **`Failed` means the scheduler has given up.** A failure it will retry returns the row to `Pending` with the error recorded (`Execution.FailAttempt`), so *will retry* and *gave up* are different states rather than one state read against a counter. The cap moved into the engine, where policy belongs, and `WorkStates` needs no arithmetic at all. **Three previously bolted-on behaviours now fall out of the rules:** | behaviour | how it works now | |---|---| | self-healing on restart | recovery forgives the attempt streak; a wedge heals on the boot carrying the fix, with no code that knows what a wedge is | | no more `attempt 4 of 3` | a requeue restores the budget — the cap rations the scheduler's retries, not a person's | | no duplicate runs | only a project's newest run is scheduled, so a superseded one goes inert without being deleted | ## Tests **795/795 green** (Domain 91, UseCases 338, Integration 171, BlazorAdapter 195). Whole suite ~13s. **Deliberate behavioural breaks, each with its test rewritten to the new contract** — worth reviewing as the real diff: - a hand-retry restores the budget, so a retried row reads `attempt 1`, not `attempt 4`; - recovery forgives *every* failure in a scheduled run rather than analysing which one blocks something — bounded instead by "only the newest run is scheduled", which is now its own test; - a halt and a missing executor are terminal because they are `Failed`, at one attempt, with no phantom attempts spent. Two tests were replaced rather than updated, because the machinery they covered is gone — duplicate-schedule collapse and crossed wakes. Their *properties* are retested against the new design: repeated passes never start a second attempt of the same row, and an exhausted row is never picked up however often the loop looks. **A test-shaped consequence worth calling out:** nothing runs unless something turns the loop, which in production is the host's background service. Test harnesses that drive runs now start it explicitly (`SchedulerHarness.WithScheduler`, and a constructor in two bUnit classes). That surfaced as six `SetupChatTests` each waiting 30s for a run nobody was picking up — a clearer failure than the silent wedge it replaces, and the argument for this change made by the change. ## Notes - **Two bugs of my own that the suite caught, both instructive.** `WhenIdleAsync` first meant "nothing running *and* nothing queued", which can never complete unless a scheduler is turning — a hang for any caller holding the engine without its loop. It is a drain again. And my first cut had `Failed`-below-cap count as retryable, which forced halts to spend phantom attempts to look terminal; three tests objected, and they were right. - ADR 0018's decision bullets are rewritten — they described parking and wakes as *the* design. - Rebased onto #90 and #91. Not browser-verified: no user-facing change, and the monitor's own tests cover the strip. - **Deferred, deliberately:** the per-region work granularity (a timeout on region 16 still discards a page's refinement). That was the other half of the redesign we discussed and it wants its own PR — it changes ADR 0017's fan-out, not just the engine's brain. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
refactor: the run scheduler is a loop, not a graph of callbacks
All checks were successful
CI / build (pull_request) Successful in 26s
CI / test (pull_request) Successful in 43s
1ae6d34e28
Every pass asks the rows one question — what is startable right now? — and
starts what fits in the concurrency budget. That single question replaced a
machine of pushes: a stage parked by returning silently when its predecessor
was unfinished, so waking it needed a dependent-scheduling pass on every
success, a record of wakes that crossed a running attempt, a dictionary of
in-flight tasks to collapse double schedules, a separate recovery path for
boot, and finally a heal for the case where all of that still left work
stranded. A pass that simply looks needs none of them: nothing can be stranded
by a missed signal, because nothing depends on a signal arriving.

WorkState is derived every pass and never stored — queued, blocked, running,
succeeded, failed. Pending meant both "startable" and "blocked behind a
predecessor", which is why a wedged run showed six hundred Pending rows and
told its reader nothing. Storing the distinction would need something to flip
blocked back when its predecessor succeeded, and that push is the machinery
being removed.

Failed now means the scheduler has given up. An attempt it will try again
returns the row to Pending with the error recorded, so "will retry" and "gave
up" are different states rather than one state read against a counter. The cap
moved to the engine, where policy belongs, and a halt says "do not retry me" by
being Failed rather than by spending phantom attempts to look exhausted.

Three things that were bolted on now fall out of the rules. A restart forgives
the attempt streak, so a wedged deployment heals on the boot carrying the fix
with no code that knows what a wedge is. A requeue restores the budget, so no
row reports "attempt 4 of 3". And only a project's newest run is scheduled, so
a superseded run goes inert instead of competing with its replacement for the
budget — and for the user's money.

🔄 Auto-updating coverage report — this comment is regenerated on every push, so the numbers below always reflect the commit shown here, not the branch tip.

Commit: d774ce8 · Generated: 2026-07-28 04:08:42 UTC · Revision: #2

Summary

Summary
Generated on: 07/28/2026 - 04:08:42
Coverage date: 07/28/2026 - 04:08:26 - 07/28/2026 - 04:08:39
Parser: MultiReport (4x Cobertura)
Assemblies: 6
Classes: 430
Files: 205
Line coverage: 96.5% (14239 of 14748)
Covered lines: 14239
Uncovered lines: 509
Coverable lines: 14748
Total lines: 25969
Branch coverage: 83.4% (2631 of 3151)
Covered branches: 2631
Total branches: 3151
Method coverage: Feature is only available for sponsors

Coverage

Orihon.BlazorAdapter - 95.7%
Name Line Branch
Orihon.BlazorAdapter 95.7% 88.2%
Orihon.BlazorAdapter.Bible.AddBeatRowRequested 100%
Orihon.BlazorAdapter.Bible.AddCharacterRowRequested 100%
Orihon.BlazorAdapter.Bible.AddGlossaryRowRequested 100%
Orihon.BlazorAdapter.Bible.AddLoreRowRequested 100%
Orihon.BlazorAdapter.Bible.BibleEffects 92.2% 79.1%
Orihon.BlazorAdapter.Bible.BibleLoaded 100%
Orihon.BlazorAdapter.Bible.BiblePage 93.7% 81.6%
Orihon.BlazorAdapter.Bible.BibleReducers 93.1%
Orihon.BlazorAdapter.Bible.BibleState 100%
Orihon.BlazorAdapter.Bible.BibleWriteFailed 100%
Orihon.BlazorAdapter.Bible.DeleteBeatRowRequested 100%
Orihon.BlazorAdapter.Bible.DeleteCharacterRowRequested 100%
Orihon.BlazorAdapter.Bible.DeleteGlossaryRowRequested 100%
Orihon.BlazorAdapter.Bible.DeleteLoreRowRequested 0%
Orihon.BlazorAdapter.Bible.LoadBible 100%
Orihon.BlazorAdapter.Bible.ReorderBeatsRequested 0%
Orihon.BlazorAdapter.Bible.SaveOverviewRequested 100%
Orihon.BlazorAdapter.Bible.SaveSummaryRequested 100%
Orihon.BlazorAdapter.Bible.UpdateBeatRowRequested 100%
Orihon.BlazorAdapter.Bible.UpdateCharacterRowRequested 100%
Orihon.BlazorAdapter.Bible.UpdateGlossaryRowRequested 100%
Orihon.BlazorAdapter.Bible.UpdateLoreRowRequested 100%
Orihon.BlazorAdapter.BlazorAdapterAssembly 100%
Orihon.BlazorAdapter.Debounce 96.2% 94.4%
Orihon.BlazorAdapter.Diagnostics.CircuitError 100%
Orihon.BlazorAdapter.Diagnostics.CircuitErrorPanel 100%
Orihon.BlazorAdapter.Diagnostics.CircuitErrorSink 100% 85.7%
Orihon.BlazorAdapter.Diagnostics.OrihonStoreInitializer 85.7% 66.6%
Orihon.BlazorAdapter.PageWorkspace.CreateRegionRequested 100%
Orihon.BlazorAdapter.PageWorkspace.DeletePageSummaryRequested 100%
Orihon.BlazorAdapter.PageWorkspace.DeleteRegionRequested 100%
Orihon.BlazorAdapter.PageWorkspace.LoadPageWorkspace 100%
Orihon.BlazorAdapter.PageWorkspace.PageSummarySaved 100%
Orihon.BlazorAdapter.PageWorkspace.PageViewport 100% 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceEffects 100% 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceLoaded 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage 92.2% 85.5%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceReducers 100% 66.6%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceState 100%
Orihon.BlazorAdapter.PageWorkspace.PageWriteFailed 100%
Orihon.BlazorAdapter.PageWorkspace.RegionCreated 100%
Orihon.BlazorAdapter.PageWorkspace.RegionSaved 100%
Orihon.BlazorAdapter.PageWorkspace.ReorderRegionsRequested 100%
Orihon.BlazorAdapter.PageWorkspace.ReprocessPageRequested 100%
Orihon.BlazorAdapter.PageWorkspace.ReprocessTranslationRequested 100%
Orihon.BlazorAdapter.PageWorkspace.SavePageSummaryRequested 100%
Orihon.BlazorAdapter.PageWorkspace.SaveRegionRequested 100%
Orihon.BlazorAdapter.PageWorkspace.SetPageMetaRequested 100%
Orihon.BlazorAdapter.Projects.CreateProjectRequested 100%
Orihon.BlazorAdapter.Projects.DecideSetupContinuation 100%
Orihon.BlazorAdapter.Projects.DeleteProjectRequested 100%
Orihon.BlazorAdapter.Projects.FinishSetupRequested 100%
Orihon.BlazorAdapter.Projects.ImportPagesRequested 100%
Orihon.BlazorAdapter.Projects.LoadWizard 100%
Orihon.BlazorAdapter.Projects.PageOrganizer 96% 95%
Orihon.BlazorAdapter.Projects.PagesImported 100%
Orihon.BlazorAdapter.Projects.ProjectDeleteFailed 100%
Orihon.BlazorAdapter.Projects.ProjectListEffects 100% 100%
Orihon.BlazorAdapter.Projects.ProjectListPage 89.7% 91.1%
Orihon.BlazorAdapter.Projects.ProjectListReducers 100%
Orihon.BlazorAdapter.Projects.ProjectListState 100%
Orihon.BlazorAdapter.Projects.ProjectsLoaded 100%
Orihon.BlazorAdapter.Projects.ProjectWizardEffects 93.8% 90%
Orihon.BlazorAdapter.Projects.ProjectWizardPage 95.3% 84.1%
Orihon.BlazorAdapter.Projects.ProjectWizardReducers 100%
Orihon.BlazorAdapter.Projects.ProjectWizardState 100%
Orihon.BlazorAdapter.Projects.SetupChat 93.5% 100%
Orihon.BlazorAdapter.Projects.SetupChatEffects 100% 100%
Orihon.BlazorAdapter.Projects.SetupChatFailed 100%
Orihon.BlazorAdapter.Projects.SetupChatReducers 100%
Orihon.BlazorAdapter.Projects.SetupChatState 100%
Orihon.BlazorAdapter.Projects.SetupChatUpdated 100%
Orihon.BlazorAdapter.Projects.StartSetupChat 100%
Orihon.BlazorAdapter.Projects.SubmitSetupAnswer 100%
Orihon.BlazorAdapter.Projects.WizardDeletePagesRequested 100%
Orihon.BlazorAdapter.Projects.WizardLoaded 100%
Orihon.BlazorAdapter.Projects.WizardMovePagesRequested 100%
Orihon.BlazorAdapter.Projects.WizardMovePagesToNewChapterRequested 100%
Orihon.BlazorAdapter.Projects.WizardReorderPagesRequested 100%
Orihon.BlazorAdapter.Projects.WizardWriteFailed 100%
Orihon.BlazorAdapter.Runs.CancelMonitorRun 100%
Orihon.BlazorAdapter.Runs.MonitorPageRef 100%
Orihon.BlazorAdapter.Runs.MonitorRunLoaded 100%
Orihon.BlazorAdapter.Runs.RetryMonitorExecution 100%
Orihon.BlazorAdapter.Runs.RunChangedBridge 95% 92.8%
Orihon.BlazorAdapter.Runs.RunMonitor 97.9% 96.2%
Orihon.BlazorAdapter.Runs.RunMonitorEffects 100% 91.6%
Orihon.BlazorAdapter.Runs.RunMonitorReducers 100%
Orihon.BlazorAdapter.Runs.RunMonitorState 100%
Orihon.BlazorAdapter.Settings.AgentDebriefsLoaded 100%
Orihon.BlazorAdapter.Settings.AgentDebriefsLoadFailed 100%
Orihon.BlazorAdapter.Settings.AgentFeedbackLoaded 100%
Orihon.BlazorAdapter.Settings.AgentFeedbackLoadFailed 0%
Orihon.BlazorAdapter.Settings.AgentModelPicked 100%
Orihon.BlazorAdapter.Settings.AgentModelSaved 100%
Orihon.BlazorAdapter.Settings.AgentModelSaveFailed 100%
Orihon.BlazorAdapter.Settings.KeySaved 100%
Orihon.BlazorAdapter.Settings.KeySaveFailed 100%
Orihon.BlazorAdapter.Settings.ModelOptionsLoaded 100%
Orihon.BlazorAdapter.Settings.ModelOptionsUnavailable 100%
Orihon.BlazorAdapter.Settings.SaveKeyRequested 100%
Orihon.BlazorAdapter.Settings.SettingsEffects 93.4% 72.7%
Orihon.BlazorAdapter.Settings.SettingsLoaded 100%
Orihon.BlazorAdapter.Settings.SettingsPage 98.6% 88.2%
Orihon.BlazorAdapter.Settings.SettingsReducers 92.8%
Orihon.BlazorAdapter.Settings.SettingsState 100%
Orihon.BlazorAdapter.Settings.SfxPassToggled 100%
Orihon.BlazorAdapter.Uploads.UploadTransfer 96.5% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferProgress 100% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferResult 100%
Orihon.BlazorAdapter.Workspace.CreateChapterRequested 100%
Orihon.BlazorAdapter.Workspace.DeleteChapterRequested 100%
Orihon.BlazorAdapter.Workspace.DeletePageRequested 100%
Orihon.BlazorAdapter.Workspace.DeleteSummaryRequested 100%
Orihon.BlazorAdapter.Workspace.LoadProjectWorkspace 100%
Orihon.BlazorAdapter.Workspace.MovePageRequested 100%
Orihon.BlazorAdapter.Workspace.ProjectMetadataCard 95.6% 92.8%
Orihon.BlazorAdapter.Workspace.ProjectMetadataSaved 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceEffects 100% 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceLoaded 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspacePage 95.5% 88.3%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceReducers 100% 62.5%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceState 100%
Orihon.BlazorAdapter.Workspace.RenameChapterRequested 100%
Orihon.BlazorAdapter.Workspace.ReorderChaptersRequested 100%
Orihon.BlazorAdapter.Workspace.ReorderPagesRequested 100%
Orihon.BlazorAdapter.Workspace.RunAnnotationRequested 100%
Orihon.BlazorAdapter.Workspace.RunBibleRequested 100%
Orihon.BlazorAdapter.Workspace.RunTranslationRequested 100%
Orihon.BlazorAdapter.Workspace.SaveProjectMetadataRequested 100%
Orihon.BlazorAdapter.Workspace.SaveSummaryRequested 100%
Orihon.BlazorAdapter.Workspace.SetPageKindRequested 100%
Orihon.BlazorAdapter.Workspace.SummaryDeleted 100%
Orihon.BlazorAdapter.Workspace.SummarySaved 100%
Orihon.BlazorAdapter.Workspace.WorkspaceImportRequested 100%
Orihon.BlazorAdapter.Workspace.WorkspaceWriteFailed 100%
Orihon.Domain - 100%
Name Line Branch
Orihon.Domain 100% 100%
Orihon.Domain.Agents.AgentDebrief 100% 100%
Orihon.Domain.Agents.AgentDescriptor 100%
Orihon.Domain.Agents.AgentFeedback 100% 100%
Orihon.Domain.Agents.AgentRoster 100% 100%
Orihon.Domain.Bible.Character 100% 100%
Orihon.Domain.Bible.GlossaryEntry 100% 100%
Orihon.Domain.Bible.LoreEntry 100% 100%
Orihon.Domain.Bible.PageSummary 100%
Orihon.Domain.Bible.StoryBeat 100%
Orihon.Domain.Bible.StoryOverview 100%
Orihon.Domain.Projects.Project 100% 100%
Orihon.Domain.Projects.ProjectProfile 100%
Orihon.Domain.Runs.Execution 100% 100%
Orihon.Domain.Runs.RegionProblem 100%
Orihon.Domain.Runs.Run 100%
Orihon.Domain.Settings.AppSetting 100%
Orihon.Domain.Text 100% 100%
Orihon.Domain.Translation.BoundingBox 100%
Orihon.Domain.Translation.Chapter 100%
Orihon.Domain.Translation.Page 100%
Orihon.Domain.Translation.Region 100% 100%
Orihon.Domain.Translation.RegionProfile 100%
Orihon.Infrastructure - 96.3%
Name Line Branch
Orihon.Infrastructure 96.3% 71.7%
Orihon.Infrastructure.Agents.EfAgentDebriefStore 100%
Orihon.Infrastructure.Agents.EfAgentFeedbackStore 100%
Orihon.Infrastructure.Bible.EfBibleStore 94.4% 91.6%
Orihon.Infrastructure.DependencyInjection 100% 100%
Orihon.Infrastructure.Gateways.AgentToolAdapter 100%
Orihon.Infrastructure.Gateways.AgentToolAdapter`1 100% 100%
Orihon.Infrastructure.Gateways.AgentTranscript 94.7% 85.4%
Orihon.Infrastructure.Gateways.FileSystemAgentTranscriptStore 86.1% 78.5%
Orihon.Infrastructure.Gateways.HttpWebPageFetcher 95.1% 83.3%
Orihon.Infrastructure.Gateways.OpenRouterLlmGateway 95.8% 89.1%
Orihon.Infrastructure.Gateways.SkiaPageImageRenderer 97.5% 87.2%
Orihon.Infrastructure.Persistence.Configurations.AgentDebriefConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.AgentFeedbackConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.AppSettingConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ChapterConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.CharacterConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ExecutionConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.GlossaryEntryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.JsonColumnMapper 100%
Orihon.Infrastructure.Persistence.Configurations.LoreEntryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.PageConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.PageSummaryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ProjectConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.RegionConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.RunConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.StoryBeatConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.StoryOverviewConfiguration 100%
Orihon.Infrastructure.Persistence.Converters.UtcTicksConverter 100%
Orihon.Infrastructure.Persistence.Migrations.AddAgentDebriefs 99.5%
Orihon.Infrastructure.Persistence.Migrations.AddAgentFeedback 99.5%
Orihon.Infrastructure.Persistence.Migrations.AddAppSettings 99.3%
Orihon.Infrastructure.Persistence.Migrations.AddExecutionFeedbackRegions 99.3%
Orihon.Infrastructure.Persistence.Migrations.AddProjectSourceLanguage 99.3%
Orihon.Infrastructure.Persistence.Migrations.AddRuns 99.1%
Orihon.Infrastructure.Persistence.Migrations.AddStoryOverview 99.5%
Orihon.Infrastructure.Persistence.Migrations.InitialTranslationDomain 97.3%
Orihon.Infrastructure.Persistence.Migrations.OrihonDbContextModelSnapshot 100%
Orihon.Infrastructure.Persistence.Migrations.RenameSourceTargetColumns 97.2%
Orihon.Infrastructure.Persistence.OrihonDbContext 100%
Orihon.Infrastructure.Persistence.OrihonDbContextFactory 100%
Orihon.Infrastructure.Projects.EfProjectStore 100% 100%
Orihon.Infrastructure.Projects.FileSystemPageImageStore 100% 100%
Orihon.Infrastructure.Runs.EfRunStore 98.1% 75%
Orihon.Infrastructure.Settings.EfAppSettingsStore 100% 100%
Orihon.Infrastructure.Translation.EfChapterStore 100% 100%
Orihon.Infrastructure.Translation.EfPageStore 86% 80%
Orihon.Infrastructure.Translation.EfRegionStore 100% 100%
Orihon.Infrastructure.Translation.Ordering 100% 100%
System.Text.RegularExpressions.Generated 70.6% 53.3%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlankLines_4
77.9% 76.6%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlockBreaks_1
59% 42.5%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__SpaceRuns_3
89.4% 75%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__Tags_2
83.7% 62.5%
Orihon.Kernel - 90.9%
Name Line Branch
Orihon.Kernel 90.9% 75%
Orihon.Kernel.Err`1 100%
Orihon.Kernel.Ok`1 100%
Orihon.Kernel.Result`1 88.8% 75%
Orihon.Server - 93.3%
Name Line Branch
Orihon.Server 93.3% 70%
Orihon.Server.Components.App 100%
Orihon.Server.Components.Layout.MainLayout 100%
Orihon.Server.Components.Pages.Gate 64.2% 66.6%
Orihon.Server.RunEngineBootstrap 100%
Orihon.Server.Security.AccessGate 91.8% 41.6%
Orihon.Server.Security.AccessSecret 100% 50%
Orihon.Server.VolumeStartupValidator 100% 100%
Program 94.8% 87.5%
Orihon.UseCases - 97.1%
Name Line Branch
Orihon.UseCases 97.1% 87.8%
Orihon.UseCases.Agents.AgentAttemptPreparation 100%
Orihon.UseCases.Agents.AgentAttemptSupport 100% 95.4%
Orihon.UseCases.Agents.AgentBlueprint 100%
Orihon.UseCases.Agents.AgentCapDebrief 100%
Orihon.UseCases.Agents.AgentInvocation 100%
Orihon.UseCases.Agents.AgentOutcome 100%
Orihon.UseCases.Agents.AgentTool`1 90.9% 75%
Orihon.UseCases.Agents.AgentToolImage 100%
Orihon.UseCases.Agents.AgentToolResult 100%
Orihon.UseCases.Agents.Annotation.AddRegionParams 100%
Orihon.UseCases.Agents.Annotation.AddRegionTool 95.4% 75%
Orihon.UseCases.Agents.Annotation.AddSfxRegionTool 95.2% 75%
Orihon.UseCases.Agents.Annotation.AnnotationBlueprints 100%
Orihon.UseCases.Agents.Annotation.AnnotationStage 100% 85%
Orihon.UseCases.Agents.Annotation.BboxCreationExecutor 94.1% 50%
Orihon.UseCases.Agents.Annotation.BboxRefinementExecutor 93.7% 81.2%
Orihon.UseCases.Agents.Annotation.BoundBoxParams 100%
Orihon.UseCases.Agents.Annotation.BoundContactSheetTool 91.3% 75%
Orihon.UseCases.Agents.Annotation.BoundCropParams 100%
Orihon.UseCases.Agents.Annotation.BoundCropTool 100%
Orihon.UseCases.Agents.Annotation.BoundViewPageTool 92.5% 80%
Orihon.UseCases.Agents.Annotation.BoundViewParams 100%
Orihon.UseCases.Agents.Annotation.DeleteBoundRegionTool 100% 100%
Orihon.UseCases.Agents.Annotation.DeleteRegionParams 100%
Orihon.UseCases.Agents.Annotation.DeleteRegionTool 100% 100%
Orihon.UseCases.Agents.Annotation.FindGlossaryParams 100%
Orihon.UseCases.Agents.Annotation.FindGlossaryTool 88.2% 62.5%
Orihon.UseCases.Agents.Annotation.ListRegionsTool 91.6% 80%
Orihon.UseCases.Agents.Annotation.MoveResizeBoundTool 90.9% 50%
Orihon.UseCases.Agents.Annotation.MoveResizeRegionParams 100%
Orihon.UseCases.Agents.Annotation.MoveResizeRegionTool 95% 83.3%
Orihon.UseCases.Agents.Annotation.NoteRegionParams 100%
Orihon.UseCases.Agents.Annotation.NoteRegionTool 100% 100%
Orihon.UseCases.Agents.Annotation.PageQaExecutor 94.4% 81.8%
Orihon.UseCases.Agents.Annotation.QaReportSink 100% 100%
Orihon.UseCases.Agents.Annotation.RegionAuthoringAccess 87.2% 53.8%
Orihon.UseCases.Agents.Annotation.RegionBriefing 100% 100%
Orihon.UseCases.Agents.Annotation.RegionCropParams 100%
Orihon.UseCases.Agents.Annotation.RegionCropTool 100%
Orihon.UseCases.Agents.Annotation.RegionProblemParams 100%
Orihon.UseCases.Agents.Annotation.RejectRegionParams 100%
Orihon.UseCases.Agents.Annotation.RejectRegionTool 100% 50%
Orihon.UseCases.Agents.Annotation.ReorderRegionParams 100%
Orihon.UseCases.Agents.Annotation.ReorderRegionTool 88% 60%
Orihon.UseCases.Agents.Annotation.ReportQaParams 100%
Orihon.UseCases.Agents.Annotation.ReportQaTool 97.7% 90%
Orihon.UseCases.Agents.Annotation.SetPageMetaParams 100%
Orihon.UseCases.Agents.Annotation.SetPageMetaTool 100% 75%
Orihon.UseCases.Agents.Annotation.SetRegionTypeParams 100%
Orihon.UseCases.Agents.Annotation.SetRegionTypeTool 100% 87.5%
Orihon.UseCases.Agents.Annotation.SetTranscriptionParams 100%
Orihon.UseCases.Agents.Annotation.SetTranscriptionTool 100% 100%
Orihon.UseCases.Agents.Annotation.SfxCreationExecutor 88.8% 50%
Orihon.UseCases.Agents.Annotation.SfxQaExecutor 93.9% 83.3%
Orihon.UseCases.Agents.Annotation.SfxTranscriptionExecutor 93.1% 80%
Orihon.UseCases.Agents.Annotation.TranscriptionExecutor 93.1% 80%
Orihon.UseCases.Agents.AssistantSpoke 100%
Orihon.UseCases.Agents.BibleBuilding.BibleBuildingBlueprint 100%
Orihon.UseCases.Agents.BibleBuilding.BibleBuildingExecutor 96.6% 75%
Orihon.UseCases.Agents.BibleBuilding.GetRegionParams 100%
Orihon.UseCases.Agents.BibleBuilding.GetRegionTool 84.6% 72.2%
Orihon.UseCases.Agents.BibleBuilding.ListProjectRegionsTool 86.3% 90%
Orihon.UseCases.Agents.BibleBuilding.ListRegionsParams 100%
Orihon.UseCases.Agents.Inspection.PageImageAccess 94.5% 77.7%
Orihon.UseCases.Agents.Inspection.ViewAccount 100% 100%
Orihon.UseCases.Agents.ReportFrictionParams 100%
Orihon.UseCases.Agents.ReportFrictionTool 100% 92.8%
Orihon.UseCases.Agents.ResearchSetup.AddGlossaryParams 100%
Orihon.UseCases.Agents.ResearchSetup.AddGlossaryTool 100% 100%
Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatParams 100%
Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatTool 100% 50%
Orihon.UseCases.Agents.ResearchSetup.AskUserParams 100%
Orihon.UseCases.Agents.ResearchSetup.AskUserTool 100% 100%
Orihon.UseCases.Agents.ResearchSetup.FetchUrlParams 100%
Orihon.UseCases.Agents.ResearchSetup.FetchUrlTool 100% 100%
Orihon.UseCases.Agents.ResearchSetup.ListBibleTool 89.4% 100%
Orihon.UseCases.Agents.ResearchSetup.ListPagesTool 97% 83.3%
Orihon.UseCases.Agents.ResearchSetup.LocatedPage 100%
Orihon.UseCases.Agents.ResearchSetup.PageByNumber 95% 91.6%
Orihon.UseCases.Agents.ResearchSetup.ResearchSetupBlueprint 100%
Orihon.UseCases.Agents.ResearchSetup.SetPageMetaParams 100%
Orihon.UseCases.Agents.ResearchSetup.SetPageMetaTool 95.2% 90%
Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryParams 100%
Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryTool 100% 75%
Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataParams 100%
Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataTool 96.5% 95.8%
Orihon.UseCases.Agents.ResearchSetup.SetStoryOverviewParams 100%
Orihon.UseCases.Agents.ResearchSetup.SetStoryOverviewTool 100% 100%
Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterParams 100%
Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterTool 92.3% 71.4%
Orihon.UseCases.Agents.ResearchSetup.UpsertLoreParams 100%
Orihon.UseCases.Agents.ResearchSetup.UpsertLoreTool 92.3% 71.4%
Orihon.UseCases.Agents.ResearchSetup.ViewPageParams 100%
Orihon.UseCases.Agents.ResearchSetup.ViewPageTool 100% 100%
Orihon.UseCases.Agents.RoundStarted 100%
Orihon.UseCases.Agents.Setup.ResearchSetupExecutor 98.4% 92.8%
Orihon.UseCases.Agents.Setup.SetupChatEntry 100%
Orihon.UseCases.Agents.Setup.SetupConversation 100% 90.6%
Orihon.UseCases.Agents.Setup.SetupConversationRegistry 100%
Orihon.UseCases.Agents.ToolCalled 100%
Orihon.UseCases.Agents.ToolCompleted 100%
Orihon.UseCases.Agents.Translation.GetPageSummaryParams 100%
Orihon.UseCases.Agents.Translation.GetPageSummaryTool 80% 66.6%
Orihon.UseCases.Agents.Translation.SetTranslationParams 100%
Orihon.UseCases.Agents.Translation.SetTranslationTool 88.5% 78.5%
Orihon.UseCases.Agents.Translation.TranslationBlueprint 100%
Orihon.UseCases.Agents.Translation.TranslationExecutor 93.4% 71.4%
Orihon.UseCases.Agents.Translation.UpdateGlossaryEnParams 100%
Orihon.UseCases.Agents.Translation.UpdateGlossaryEnTool 82.6% 62.5%
Orihon.UseCases.Bible.AddCharacter 100% 100%
Orihon.UseCases.Bible.AddGlossaryEntry 100% 100%
Orihon.UseCases.Bible.AddLoreEntry 100% 100%
Orihon.UseCases.Bible.AddStoryBeat 100% 100%
Orihon.UseCases.Bible.BibleDto 100%
Orihon.UseCases.Bible.CharacterDto 100%
Orihon.UseCases.Bible.DeleteCharacter 100% 100%
Orihon.UseCases.Bible.DeleteGlossaryEntry 100% 100%
Orihon.UseCases.Bible.DeleteLoreEntry 100% 100%
Orihon.UseCases.Bible.DeletePageSummary 100% 100%
Orihon.UseCases.Bible.DeleteStoryBeat 100% 100%
Orihon.UseCases.Bible.GetBible 100% 100%
Orihon.UseCases.Bible.GlossaryEntryDto 100%
Orihon.UseCases.Bible.LoreEntryDto 100%
Orihon.UseCases.Bible.PageSummaryDto 100%
Orihon.UseCases.Bible.ReorderStoryBeats 100%
Orihon.UseCases.Bible.SetPageSummary 100% 100%
Orihon.UseCases.Bible.SetStoryOverview 100% 100%
Orihon.UseCases.Bible.StoryBeatDto 100%
Orihon.UseCases.Bible.StoryOverviewDto 100%
Orihon.UseCases.Bible.UpdateCharacter 100% 100%
Orihon.UseCases.Bible.UpdateGlossaryEntry 100% 100%
Orihon.UseCases.Bible.UpdateLoreEntry 100% 100%
Orihon.UseCases.Bible.UpdateStoryBeat 100% 100%
Orihon.UseCases.Chapters.ChapterDto 100%
Orihon.UseCases.Chapters.CreateChapter 100% 100%
Orihon.UseCases.Chapters.DeleteChapter 100% 100%
Orihon.UseCases.Chapters.RenameChapter 100% 100%
Orihon.UseCases.Chapters.ReorderChapters 100%
Orihon.UseCases.Debriefs.AgentDebriefDto 90.9%
Orihon.UseCases.Debriefs.AgentFeedbackDto 83.3%
Orihon.UseCases.Debriefs.ClearAgentDebriefs 100%
Orihon.UseCases.Debriefs.ClearAgentFeedback 100%
Orihon.UseCases.Debriefs.ListAgentDebriefs 100% 100%
Orihon.UseCases.Debriefs.ListAgentFeedback 100% 75%
Orihon.UseCases.DependencyInjection 100%
Orihon.UseCases.Diagnostics.SeedDevData 99.5% 93.7%
Orihon.UseCases.Gateways.LabeledBox 100%
Orihon.UseCases.Gateways.LlmKeyInfo 100%
Orihon.UseCases.Gateways.LlmModel 100%
Orihon.UseCases.Gateways.PixelWindow 100%
Orihon.UseCases.Gateways.RenderedView 100%
Orihon.UseCases.NextOrder 100%
Orihon.UseCases.Pages.DeletePage 100% 100%
Orihon.UseCases.Pages.DeletePages 100% 100%
Orihon.UseCases.Pages.GetPage 100% 100%
Orihon.UseCases.Pages.GetProjectWorkspace 100% 100%
Orihon.UseCases.Pages.ImportPages 100% 100%
Orihon.UseCases.Pages.ImportPagesResult 100%
Orihon.UseCases.Pages.MarkPageAnnotated 100% 100%
Orihon.UseCases.Pages.MovePage 100% 92.8%
Orihon.UseCases.Pages.MovePages 100% 100%
Orihon.UseCases.Pages.PageDetailDto 100%
Orihon.UseCases.Pages.PageDto 100%
Orihon.UseCases.Pages.PageUpload 100%
Orihon.UseCases.Pages.ProjectWorkspaceDto 100%
Orihon.UseCases.Pages.ReorderPages 100%
Orihon.UseCases.Pages.SetPageMeta 100% 100%
Orihon.UseCases.Pages.WorkspaceChapterDto 100%
Orihon.UseCases.Projects.CompleteProjectSetup 100% 93.7%
Orihon.UseCases.Projects.CreateProject 100% 100%
Orihon.UseCases.Projects.DeleteProject 100% 100%
Orihon.UseCases.Projects.GetProject 100% 100%
Orihon.UseCases.Projects.ListProjects 100%
Orihon.UseCases.Projects.ProjectDto 96.1%
Orihon.UseCases.Projects.StartAnnotationRun 96.4% 92.8%
Orihon.UseCases.Projects.StartBibleRun 90.9% 83.3%
Orihon.UseCases.Projects.StartSetupRun 100% 100%
Orihon.UseCases.Projects.StartTranslationRun 90.9% 83.3%
Orihon.UseCases.Projects.StoredPageImage 100%
Orihon.UseCases.Projects.UpdateProjectMetadata 100% 100%
Orihon.UseCases.Regions.CreateRegion 100% 100%
Orihon.UseCases.Regions.DeleteRegion 100% 100%
Orihon.UseCases.Regions.RegionDto 97%
Orihon.UseCases.Regions.ReorderRegions 100%
Orihon.UseCases.Regions.UpdateRegion 100% 100%
Orihon.UseCases.Runs.AnnotationPipeline 100% 100%
Orihon.UseCases.Runs.ExecutionDto 93.3%
Orihon.UseCases.Runs.ExecutionProgress 100%
Orihon.UseCases.Runs.ExecutionProgressRegistry 100% 100%
Orihon.UseCases.Runs.ExecutionPulseRelay 100% 100%
Orihon.UseCases.Runs.PlannedExecution 100%
Orihon.UseCases.Runs.PulseTarget 100%
Orihon.UseCases.Runs.ReprocessPage 100% 94.4%
Orihon.UseCases.Runs.ReprocessTranslation 94.1% 92.8%
Orihon.UseCases.Runs.RunDto 93.3% 90%
Orihon.UseCases.Runs.RunEngine 94.8% 93.1%
Orihon.UseCases.Runs.RunEngineOptions 100% 100%
Orihon.UseCases.Runs.StageContext 100% 50%
Orihon.UseCases.Runs.StageHaltedException 100%
Orihon.UseCases.Runs.WorkStates 100% 83.3%
Orihon.UseCases.Settings.AgentSettingDto 100% 100%
Orihon.UseCases.Settings.GetSettings 100% 100%
Orihon.UseCases.Settings.ListModelOptions 100% 100%
Orihon.UseCases.Settings.SaveAgentModel 100% 100%
Orihon.UseCases.Settings.SaveOpenRouterKey 100% 100%
Orihon.UseCases.Settings.SaveSfxPass 100% 100%
Orihon.UseCases.Settings.SettingKeys 100% 100%
Orihon.UseCases.Settings.SettingsDto 100%
<!-- coverage-comment --> > 🔄 **Auto-updating coverage report** — this comment is regenerated on every push, so the numbers below always reflect the commit shown here, not the branch tip. > > **Commit:** `d774ce8` · **Generated:** 2026-07-28 04:08:42 UTC · **Revision:** #2 # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 07/28/2026 - 04:08:42 | | Coverage date: | 07/28/2026 - 04:08:26 - 07/28/2026 - 04:08:39 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 6 | | Classes: | 430 | | Files: | 205 | | **Line coverage:** | 96.5% (14239 of 14748) | | Covered lines: | 14239 | | Uncovered lines: | 509 | | Coverable lines: | 14748 | | Total lines: | 25969 | | **Branch coverage:** | 83.4% (2631 of 3151) | | Covered branches: | 2631 | | Total branches: | 3151 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Orihon.BlazorAdapter - 95.7%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.BlazorAdapter**|**95.7%**|**88.2%**| |Orihon.BlazorAdapter.Bible.AddBeatRowRequested|100%|| |Orihon.BlazorAdapter.Bible.AddCharacterRowRequested|100%|| |Orihon.BlazorAdapter.Bible.AddGlossaryRowRequested|100%|| |Orihon.BlazorAdapter.Bible.AddLoreRowRequested|100%|| |Orihon.BlazorAdapter.Bible.BibleEffects|92.2%|79.1%| |Orihon.BlazorAdapter.Bible.BibleLoaded|100%|| |Orihon.BlazorAdapter.Bible.BiblePage|93.7%|81.6%| |Orihon.BlazorAdapter.Bible.BibleReducers|93.1%|| |Orihon.BlazorAdapter.Bible.BibleState|100%|| |Orihon.BlazorAdapter.Bible.BibleWriteFailed|100%|| |Orihon.BlazorAdapter.Bible.DeleteBeatRowRequested|100%|| |Orihon.BlazorAdapter.Bible.DeleteCharacterRowRequested|100%|| |Orihon.BlazorAdapter.Bible.DeleteGlossaryRowRequested|100%|| |Orihon.BlazorAdapter.Bible.DeleteLoreRowRequested|0%|| |Orihon.BlazorAdapter.Bible.LoadBible|100%|| |Orihon.BlazorAdapter.Bible.ReorderBeatsRequested|0%|| |Orihon.BlazorAdapter.Bible.SaveOverviewRequested|100%|| |Orihon.BlazorAdapter.Bible.SaveSummaryRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateBeatRowRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateCharacterRowRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateGlossaryRowRequested|100%|| |Orihon.BlazorAdapter.Bible.UpdateLoreRowRequested|100%|| |Orihon.BlazorAdapter.BlazorAdapterAssembly|100%|| |Orihon.BlazorAdapter.Debounce|96.2%|94.4%| |Orihon.BlazorAdapter.Diagnostics.CircuitError|100%|| |Orihon.BlazorAdapter.Diagnostics.CircuitErrorPanel|100%|| |Orihon.BlazorAdapter.Diagnostics.CircuitErrorSink|100%|85.7%| |Orihon.BlazorAdapter.Diagnostics.OrihonStoreInitializer|85.7%|66.6%| |Orihon.BlazorAdapter.PageWorkspace.CreateRegionRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.DeletePageSummaryRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.DeleteRegionRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.LoadPageWorkspace|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageSummarySaved|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageViewport|100%|100%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceEffects|100%|100%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceLoaded|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage|92.2%|85.5%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceReducers|100%|66.6%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceState|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageWriteFailed|100%|| |Orihon.BlazorAdapter.PageWorkspace.RegionCreated|100%|| |Orihon.BlazorAdapter.PageWorkspace.RegionSaved|100%|| |Orihon.BlazorAdapter.PageWorkspace.ReorderRegionsRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.ReprocessPageRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.ReprocessTranslationRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.SavePageSummaryRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.SaveRegionRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.SetPageMetaRequested|100%|| |Orihon.BlazorAdapter.Projects.CreateProjectRequested|100%|| |Orihon.BlazorAdapter.Projects.DecideSetupContinuation|100%|| |Orihon.BlazorAdapter.Projects.DeleteProjectRequested|100%|| |Orihon.BlazorAdapter.Projects.FinishSetupRequested|100%|| |Orihon.BlazorAdapter.Projects.ImportPagesRequested|100%|| |Orihon.BlazorAdapter.Projects.LoadWizard|100%|| |Orihon.BlazorAdapter.Projects.PageOrganizer|96%|95%| |Orihon.BlazorAdapter.Projects.PagesImported|100%|| |Orihon.BlazorAdapter.Projects.ProjectDeleteFailed|100%|| |Orihon.BlazorAdapter.Projects.ProjectListEffects|100%|100%| |Orihon.BlazorAdapter.Projects.ProjectListPage|89.7%|91.1%| |Orihon.BlazorAdapter.Projects.ProjectListReducers|100%|| |Orihon.BlazorAdapter.Projects.ProjectListState|100%|| |Orihon.BlazorAdapter.Projects.ProjectsLoaded|100%|| |Orihon.BlazorAdapter.Projects.ProjectWizardEffects|93.8%|90%| |Orihon.BlazorAdapter.Projects.ProjectWizardPage|95.3%|84.1%| |Orihon.BlazorAdapter.Projects.ProjectWizardReducers|100%|| |Orihon.BlazorAdapter.Projects.ProjectWizardState|100%|| |Orihon.BlazorAdapter.Projects.SetupChat|93.5%|100%| |Orihon.BlazorAdapter.Projects.SetupChatEffects|100%|100%| |Orihon.BlazorAdapter.Projects.SetupChatFailed|100%|| |Orihon.BlazorAdapter.Projects.SetupChatReducers|100%|| |Orihon.BlazorAdapter.Projects.SetupChatState|100%|| |Orihon.BlazorAdapter.Projects.SetupChatUpdated|100%|| |Orihon.BlazorAdapter.Projects.StartSetupChat|100%|| |Orihon.BlazorAdapter.Projects.SubmitSetupAnswer|100%|| |Orihon.BlazorAdapter.Projects.WizardDeletePagesRequested|100%|| |Orihon.BlazorAdapter.Projects.WizardLoaded|100%|| |Orihon.BlazorAdapter.Projects.WizardMovePagesRequested|100%|| |Orihon.BlazorAdapter.Projects.WizardMovePagesToNewChapterRequested|100%|| |Orihon.BlazorAdapter.Projects.WizardReorderPagesRequested|100%|| |Orihon.BlazorAdapter.Projects.WizardWriteFailed|100%|| |Orihon.BlazorAdapter.Runs.CancelMonitorRun|100%|| |Orihon.BlazorAdapter.Runs.MonitorPageRef|100%|| |Orihon.BlazorAdapter.Runs.MonitorRunLoaded|100%|| |Orihon.BlazorAdapter.Runs.RetryMonitorExecution|100%|| |Orihon.BlazorAdapter.Runs.RunChangedBridge|95%|92.8%| |Orihon.BlazorAdapter.Runs.RunMonitor|97.9%|96.2%| |Orihon.BlazorAdapter.Runs.RunMonitorEffects|100%|91.6%| |Orihon.BlazorAdapter.Runs.RunMonitorReducers|100%|| |Orihon.BlazorAdapter.Runs.RunMonitorState|100%|| |Orihon.BlazorAdapter.Settings.AgentDebriefsLoaded|100%|| |Orihon.BlazorAdapter.Settings.AgentDebriefsLoadFailed|100%|| |Orihon.BlazorAdapter.Settings.AgentFeedbackLoaded|100%|| |Orihon.BlazorAdapter.Settings.AgentFeedbackLoadFailed|0%|| |Orihon.BlazorAdapter.Settings.AgentModelPicked|100%|| |Orihon.BlazorAdapter.Settings.AgentModelSaved|100%|| |Orihon.BlazorAdapter.Settings.AgentModelSaveFailed|100%|| |Orihon.BlazorAdapter.Settings.KeySaved|100%|| |Orihon.BlazorAdapter.Settings.KeySaveFailed|100%|| |Orihon.BlazorAdapter.Settings.ModelOptionsLoaded|100%|| |Orihon.BlazorAdapter.Settings.ModelOptionsUnavailable|100%|| |Orihon.BlazorAdapter.Settings.SaveKeyRequested|100%|| |Orihon.BlazorAdapter.Settings.SettingsEffects|93.4%|72.7%| |Orihon.BlazorAdapter.Settings.SettingsLoaded|100%|| |Orihon.BlazorAdapter.Settings.SettingsPage|98.6%|88.2%| |Orihon.BlazorAdapter.Settings.SettingsReducers|92.8%|| |Orihon.BlazorAdapter.Settings.SettingsState|100%|| |Orihon.BlazorAdapter.Settings.SfxPassToggled|100%|| |Orihon.BlazorAdapter.Uploads.UploadTransfer|96.5%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferProgress|100%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferResult|100%|| |Orihon.BlazorAdapter.Workspace.CreateChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeleteChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeletePageRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeleteSummaryRequested|100%|| |Orihon.BlazorAdapter.Workspace.LoadProjectWorkspace|100%|| |Orihon.BlazorAdapter.Workspace.MovePageRequested|100%|| |Orihon.BlazorAdapter.Workspace.ProjectMetadataCard|95.6%|92.8%| |Orihon.BlazorAdapter.Workspace.ProjectMetadataSaved|100%|| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceEffects|100%|100%| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceLoaded|100%|| |Orihon.BlazorAdapter.Workspace.ProjectWorkspacePage|95.5%|88.3%| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceReducers|100%|62.5%| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceState|100%|| |Orihon.BlazorAdapter.Workspace.RenameChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.ReorderChaptersRequested|100%|| |Orihon.BlazorAdapter.Workspace.ReorderPagesRequested|100%|| |Orihon.BlazorAdapter.Workspace.RunAnnotationRequested|100%|| |Orihon.BlazorAdapter.Workspace.RunBibleRequested|100%|| |Orihon.BlazorAdapter.Workspace.RunTranslationRequested|100%|| |Orihon.BlazorAdapter.Workspace.SaveProjectMetadataRequested|100%|| |Orihon.BlazorAdapter.Workspace.SaveSummaryRequested|100%|| |Orihon.BlazorAdapter.Workspace.SetPageKindRequested|100%|| |Orihon.BlazorAdapter.Workspace.SummaryDeleted|100%|| |Orihon.BlazorAdapter.Workspace.SummarySaved|100%|| |Orihon.BlazorAdapter.Workspace.WorkspaceImportRequested|100%|| |Orihon.BlazorAdapter.Workspace.WorkspaceWriteFailed|100%|| </details> <details><summary>Orihon.Domain - 100%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Domain**|**100%**|**100%**| |Orihon.Domain.Agents.AgentDebrief|100%|100%| |Orihon.Domain.Agents.AgentDescriptor|100%|| |Orihon.Domain.Agents.AgentFeedback|100%|100%| |Orihon.Domain.Agents.AgentRoster|100%|100%| |Orihon.Domain.Bible.Character|100%|100%| |Orihon.Domain.Bible.GlossaryEntry|100%|100%| |Orihon.Domain.Bible.LoreEntry|100%|100%| |Orihon.Domain.Bible.PageSummary|100%|| |Orihon.Domain.Bible.StoryBeat|100%|| |Orihon.Domain.Bible.StoryOverview|100%|| |Orihon.Domain.Projects.Project|100%|100%| |Orihon.Domain.Projects.ProjectProfile|100%|| |Orihon.Domain.Runs.Execution|100%|100%| |Orihon.Domain.Runs.RegionProblem|100%|| |Orihon.Domain.Runs.Run|100%|| |Orihon.Domain.Settings.AppSetting|100%|| |Orihon.Domain.Text|100%|100%| |Orihon.Domain.Translation.BoundingBox|100%|| |Orihon.Domain.Translation.Chapter|100%|| |Orihon.Domain.Translation.Page|100%|| |Orihon.Domain.Translation.Region|100%|100%| |Orihon.Domain.Translation.RegionProfile|100%|| </details> <details><summary>Orihon.Infrastructure - 96.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Infrastructure**|**96.3%**|**71.7%**| |Orihon.Infrastructure.Agents.EfAgentDebriefStore|100%|| |Orihon.Infrastructure.Agents.EfAgentFeedbackStore|100%|| |Orihon.Infrastructure.Bible.EfBibleStore|94.4%|91.6%| |Orihon.Infrastructure.DependencyInjection|100%|100%| |Orihon.Infrastructure.Gateways.AgentToolAdapter|100%|| |Orihon.Infrastructure.Gateways.AgentToolAdapter`1|100%|100%| |Orihon.Infrastructure.Gateways.AgentTranscript|94.7%|85.4%| |Orihon.Infrastructure.Gateways.FileSystemAgentTranscriptStore|86.1%|78.5%| |Orihon.Infrastructure.Gateways.HttpWebPageFetcher|95.1%|83.3%| |Orihon.Infrastructure.Gateways.OpenRouterLlmGateway|95.8%|89.1%| |Orihon.Infrastructure.Gateways.SkiaPageImageRenderer|97.5%|87.2%| |Orihon.Infrastructure.Persistence.Configurations.AgentDebriefConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.AgentFeedbackConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.AppSettingConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ChapterConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.CharacterConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ExecutionConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.GlossaryEntryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.JsonColumnMapper|100%|| |Orihon.Infrastructure.Persistence.Configurations.LoreEntryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.PageConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.PageSummaryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ProjectConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.RegionConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.RunConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.StoryBeatConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.StoryOverviewConfiguration|100%|| |Orihon.Infrastructure.Persistence.Converters.UtcTicksConverter|100%|| |Orihon.Infrastructure.Persistence.Migrations.AddAgentDebriefs|99.5%|| |Orihon.Infrastructure.Persistence.Migrations.AddAgentFeedback|99.5%|| |Orihon.Infrastructure.Persistence.Migrations.AddAppSettings|99.3%|| |Orihon.Infrastructure.Persistence.Migrations.AddExecutionFeedbackRegions|99.3%|| |Orihon.Infrastructure.Persistence.Migrations.AddProjectSourceLanguage|99.3%|| |Orihon.Infrastructure.Persistence.Migrations.AddRuns|99.1%|| |Orihon.Infrastructure.Persistence.Migrations.AddStoryOverview|99.5%|| |Orihon.Infrastructure.Persistence.Migrations.InitialTranslationDomain|97.3%|| |Orihon.Infrastructure.Persistence.Migrations.OrihonDbContextModelSnapshot|100%|| |Orihon.Infrastructure.Persistence.Migrations.RenameSourceTargetColumns|97.2%|| |Orihon.Infrastructure.Persistence.OrihonDbContext|100%|| |Orihon.Infrastructure.Persistence.OrihonDbContextFactory|100%|| |Orihon.Infrastructure.Projects.EfProjectStore|100%|100%| |Orihon.Infrastructure.Projects.FileSystemPageImageStore|100%|100%| |Orihon.Infrastructure.Runs.EfRunStore|98.1%|75%| |Orihon.Infrastructure.Settings.EfAppSettingsStore|100%|100%| |Orihon.Infrastructure.Translation.EfChapterStore|100%|100%| |Orihon.Infrastructure.Translation.EfPageStore|86%|80%| |Orihon.Infrastructure.Translation.EfRegionStore|100%|100%| |Orihon.Infrastructure.Translation.Ordering|100%|100%| |System.Text.RegularExpressions.Generated|70.6%|53.3%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlankLines_4|77.9%|76.6%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlockBreaks_1|59%|42.5%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__SpaceRuns_3|89.4%|75%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__Tags_2|83.7%|62.5%| </details> <details><summary>Orihon.Kernel - 90.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Kernel**|**90.9%**|**75%**| |Orihon.Kernel.Err`1|100%|| |Orihon.Kernel.Ok`1|100%|| |Orihon.Kernel.Result`1|88.8%|75%| </details> <details><summary>Orihon.Server - 93.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Server**|**93.3%**|**70%**| |Orihon.Server.Components.App|100%|| |Orihon.Server.Components.Layout.MainLayout|100%|| |Orihon.Server.Components.Pages.Gate|64.2%|66.6%| |Orihon.Server.RunEngineBootstrap|100%|| |Orihon.Server.Security.AccessGate|91.8%|41.6%| |Orihon.Server.Security.AccessSecret|100%|50%| |Orihon.Server.VolumeStartupValidator|100%|100%| |Program|94.8%|87.5%| </details> <details><summary>Orihon.UseCases - 97.1%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.UseCases**|**97.1%**|**87.8%**| |Orihon.UseCases.Agents.AgentAttemptPreparation|100%|| |Orihon.UseCases.Agents.AgentAttemptSupport|100%|95.4%| |Orihon.UseCases.Agents.AgentBlueprint|100%|| |Orihon.UseCases.Agents.AgentCapDebrief|100%|| |Orihon.UseCases.Agents.AgentInvocation|100%|| |Orihon.UseCases.Agents.AgentOutcome|100%|| |Orihon.UseCases.Agents.AgentTool`1|90.9%|75%| |Orihon.UseCases.Agents.AgentToolImage|100%|| |Orihon.UseCases.Agents.AgentToolResult|100%|| |Orihon.UseCases.Agents.Annotation.AddRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.AddRegionTool|95.4%|75%| |Orihon.UseCases.Agents.Annotation.AddSfxRegionTool|95.2%|75%| |Orihon.UseCases.Agents.Annotation.AnnotationBlueprints|100%|| |Orihon.UseCases.Agents.Annotation.AnnotationStage|100%|85%| |Orihon.UseCases.Agents.Annotation.BboxCreationExecutor|94.1%|50%| |Orihon.UseCases.Agents.Annotation.BboxRefinementExecutor|93.7%|81.2%| |Orihon.UseCases.Agents.Annotation.BoundBoxParams|100%|| |Orihon.UseCases.Agents.Annotation.BoundContactSheetTool|91.3%|75%| |Orihon.UseCases.Agents.Annotation.BoundCropParams|100%|| |Orihon.UseCases.Agents.Annotation.BoundCropTool|100%|| |Orihon.UseCases.Agents.Annotation.BoundViewPageTool|92.5%|80%| |Orihon.UseCases.Agents.Annotation.BoundViewParams|100%|| |Orihon.UseCases.Agents.Annotation.DeleteBoundRegionTool|100%|100%| |Orihon.UseCases.Agents.Annotation.DeleteRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.DeleteRegionTool|100%|100%| |Orihon.UseCases.Agents.Annotation.FindGlossaryParams|100%|| |Orihon.UseCases.Agents.Annotation.FindGlossaryTool|88.2%|62.5%| |Orihon.UseCases.Agents.Annotation.ListRegionsTool|91.6%|80%| |Orihon.UseCases.Agents.Annotation.MoveResizeBoundTool|90.9%|50%| |Orihon.UseCases.Agents.Annotation.MoveResizeRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.MoveResizeRegionTool|95%|83.3%| |Orihon.UseCases.Agents.Annotation.NoteRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.NoteRegionTool|100%|100%| |Orihon.UseCases.Agents.Annotation.PageQaExecutor|94.4%|81.8%| |Orihon.UseCases.Agents.Annotation.QaReportSink|100%|100%| |Orihon.UseCases.Agents.Annotation.RegionAuthoringAccess|87.2%|53.8%| |Orihon.UseCases.Agents.Annotation.RegionBriefing|100%|100%| |Orihon.UseCases.Agents.Annotation.RegionCropParams|100%|| |Orihon.UseCases.Agents.Annotation.RegionCropTool|100%|| |Orihon.UseCases.Agents.Annotation.RegionProblemParams|100%|| |Orihon.UseCases.Agents.Annotation.RejectRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.RejectRegionTool|100%|50%| |Orihon.UseCases.Agents.Annotation.ReorderRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.ReorderRegionTool|88%|60%| |Orihon.UseCases.Agents.Annotation.ReportQaParams|100%|| |Orihon.UseCases.Agents.Annotation.ReportQaTool|97.7%|90%| |Orihon.UseCases.Agents.Annotation.SetPageMetaParams|100%|| |Orihon.UseCases.Agents.Annotation.SetPageMetaTool|100%|75%| |Orihon.UseCases.Agents.Annotation.SetRegionTypeParams|100%|| |Orihon.UseCases.Agents.Annotation.SetRegionTypeTool|100%|87.5%| |Orihon.UseCases.Agents.Annotation.SetTranscriptionParams|100%|| |Orihon.UseCases.Agents.Annotation.SetTranscriptionTool|100%|100%| |Orihon.UseCases.Agents.Annotation.SfxCreationExecutor|88.8%|50%| |Orihon.UseCases.Agents.Annotation.SfxQaExecutor|93.9%|83.3%| |Orihon.UseCases.Agents.Annotation.SfxTranscriptionExecutor|93.1%|80%| |Orihon.UseCases.Agents.Annotation.TranscriptionExecutor|93.1%|80%| |Orihon.UseCases.Agents.AssistantSpoke|100%|| |Orihon.UseCases.Agents.BibleBuilding.BibleBuildingBlueprint|100%|| |Orihon.UseCases.Agents.BibleBuilding.BibleBuildingExecutor|96.6%|75%| |Orihon.UseCases.Agents.BibleBuilding.GetRegionParams|100%|| |Orihon.UseCases.Agents.BibleBuilding.GetRegionTool|84.6%|72.2%| |Orihon.UseCases.Agents.BibleBuilding.ListProjectRegionsTool|86.3%|90%| |Orihon.UseCases.Agents.BibleBuilding.ListRegionsParams|100%|| |Orihon.UseCases.Agents.Inspection.PageImageAccess|94.5%|77.7%| |Orihon.UseCases.Agents.Inspection.ViewAccount|100%|100%| |Orihon.UseCases.Agents.ReportFrictionParams|100%|| |Orihon.UseCases.Agents.ReportFrictionTool|100%|92.8%| |Orihon.UseCases.Agents.ResearchSetup.AddGlossaryParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.AddGlossaryTool|100%|100%| |Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.AddStoryBeatTool|100%|50%| |Orihon.UseCases.Agents.ResearchSetup.AskUserParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.AskUserTool|100%|100%| |Orihon.UseCases.Agents.ResearchSetup.FetchUrlParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.FetchUrlTool|100%|100%| |Orihon.UseCases.Agents.ResearchSetup.ListBibleTool|89.4%|100%| |Orihon.UseCases.Agents.ResearchSetup.ListPagesTool|97%|83.3%| |Orihon.UseCases.Agents.ResearchSetup.LocatedPage|100%|| |Orihon.UseCases.Agents.ResearchSetup.PageByNumber|95%|91.6%| |Orihon.UseCases.Agents.ResearchSetup.ResearchSetupBlueprint|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetPageMetaParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetPageMetaTool|95.2%|90%| |Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetPageSummaryTool|100%|75%| |Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetProjectMetadataTool|96.5%|95.8%| |Orihon.UseCases.Agents.ResearchSetup.SetStoryOverviewParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetStoryOverviewTool|100%|100%| |Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterTool|92.3%|71.4%| |Orihon.UseCases.Agents.ResearchSetup.UpsertLoreParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.UpsertLoreTool|92.3%|71.4%| |Orihon.UseCases.Agents.ResearchSetup.ViewPageParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.ViewPageTool|100%|100%| |Orihon.UseCases.Agents.RoundStarted|100%|| |Orihon.UseCases.Agents.Setup.ResearchSetupExecutor|98.4%|92.8%| |Orihon.UseCases.Agents.Setup.SetupChatEntry|100%|| |Orihon.UseCases.Agents.Setup.SetupConversation|100%|90.6%| |Orihon.UseCases.Agents.Setup.SetupConversationRegistry|100%|| |Orihon.UseCases.Agents.ToolCalled|100%|| |Orihon.UseCases.Agents.ToolCompleted|100%|| |Orihon.UseCases.Agents.Translation.GetPageSummaryParams|100%|| |Orihon.UseCases.Agents.Translation.GetPageSummaryTool|80%|66.6%| |Orihon.UseCases.Agents.Translation.SetTranslationParams|100%|| |Orihon.UseCases.Agents.Translation.SetTranslationTool|88.5%|78.5%| |Orihon.UseCases.Agents.Translation.TranslationBlueprint|100%|| |Orihon.UseCases.Agents.Translation.TranslationExecutor|93.4%|71.4%| |Orihon.UseCases.Agents.Translation.UpdateGlossaryEnParams|100%|| |Orihon.UseCases.Agents.Translation.UpdateGlossaryEnTool|82.6%|62.5%| |Orihon.UseCases.Bible.AddCharacter|100%|100%| |Orihon.UseCases.Bible.AddGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.AddLoreEntry|100%|100%| |Orihon.UseCases.Bible.AddStoryBeat|100%|100%| |Orihon.UseCases.Bible.BibleDto|100%|| |Orihon.UseCases.Bible.CharacterDto|100%|| |Orihon.UseCases.Bible.DeleteCharacter|100%|100%| |Orihon.UseCases.Bible.DeleteGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.DeleteLoreEntry|100%|100%| |Orihon.UseCases.Bible.DeletePageSummary|100%|100%| |Orihon.UseCases.Bible.DeleteStoryBeat|100%|100%| |Orihon.UseCases.Bible.GetBible|100%|100%| |Orihon.UseCases.Bible.GlossaryEntryDto|100%|| |Orihon.UseCases.Bible.LoreEntryDto|100%|| |Orihon.UseCases.Bible.PageSummaryDto|100%|| |Orihon.UseCases.Bible.ReorderStoryBeats|100%|| |Orihon.UseCases.Bible.SetPageSummary|100%|100%| |Orihon.UseCases.Bible.SetStoryOverview|100%|100%| |Orihon.UseCases.Bible.StoryBeatDto|100%|| |Orihon.UseCases.Bible.StoryOverviewDto|100%|| |Orihon.UseCases.Bible.UpdateCharacter|100%|100%| |Orihon.UseCases.Bible.UpdateGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.UpdateLoreEntry|100%|100%| |Orihon.UseCases.Bible.UpdateStoryBeat|100%|100%| |Orihon.UseCases.Chapters.ChapterDto|100%|| |Orihon.UseCases.Chapters.CreateChapter|100%|100%| |Orihon.UseCases.Chapters.DeleteChapter|100%|100%| |Orihon.UseCases.Chapters.RenameChapter|100%|100%| |Orihon.UseCases.Chapters.ReorderChapters|100%|| |Orihon.UseCases.Debriefs.AgentDebriefDto|90.9%|| |Orihon.UseCases.Debriefs.AgentFeedbackDto|83.3%|| |Orihon.UseCases.Debriefs.ClearAgentDebriefs|100%|| |Orihon.UseCases.Debriefs.ClearAgentFeedback|100%|| |Orihon.UseCases.Debriefs.ListAgentDebriefs|100%|100%| |Orihon.UseCases.Debriefs.ListAgentFeedback|100%|75%| |Orihon.UseCases.DependencyInjection|100%|| |Orihon.UseCases.Diagnostics.SeedDevData|99.5%|93.7%| |Orihon.UseCases.Gateways.LabeledBox|100%|| |Orihon.UseCases.Gateways.LlmKeyInfo|100%|| |Orihon.UseCases.Gateways.LlmModel|100%|| |Orihon.UseCases.Gateways.PixelWindow|100%|| |Orihon.UseCases.Gateways.RenderedView|100%|| |Orihon.UseCases.NextOrder|100%|| |Orihon.UseCases.Pages.DeletePage|100%|100%| |Orihon.UseCases.Pages.DeletePages|100%|100%| |Orihon.UseCases.Pages.GetPage|100%|100%| |Orihon.UseCases.Pages.GetProjectWorkspace|100%|100%| |Orihon.UseCases.Pages.ImportPages|100%|100%| |Orihon.UseCases.Pages.ImportPagesResult|100%|| |Orihon.UseCases.Pages.MarkPageAnnotated|100%|100%| |Orihon.UseCases.Pages.MovePage|100%|92.8%| |Orihon.UseCases.Pages.MovePages|100%|100%| |Orihon.UseCases.Pages.PageDetailDto|100%|| |Orihon.UseCases.Pages.PageDto|100%|| |Orihon.UseCases.Pages.PageUpload|100%|| |Orihon.UseCases.Pages.ProjectWorkspaceDto|100%|| |Orihon.UseCases.Pages.ReorderPages|100%|| |Orihon.UseCases.Pages.SetPageMeta|100%|100%| |Orihon.UseCases.Pages.WorkspaceChapterDto|100%|| |Orihon.UseCases.Projects.CompleteProjectSetup|100%|93.7%| |Orihon.UseCases.Projects.CreateProject|100%|100%| |Orihon.UseCases.Projects.DeleteProject|100%|100%| |Orihon.UseCases.Projects.GetProject|100%|100%| |Orihon.UseCases.Projects.ListProjects|100%|| |Orihon.UseCases.Projects.ProjectDto|96.1%|| |Orihon.UseCases.Projects.StartAnnotationRun|96.4%|92.8%| |Orihon.UseCases.Projects.StartBibleRun|90.9%|83.3%| |Orihon.UseCases.Projects.StartSetupRun|100%|100%| |Orihon.UseCases.Projects.StartTranslationRun|90.9%|83.3%| |Orihon.UseCases.Projects.StoredPageImage|100%|| |Orihon.UseCases.Projects.UpdateProjectMetadata|100%|100%| |Orihon.UseCases.Regions.CreateRegion|100%|100%| |Orihon.UseCases.Regions.DeleteRegion|100%|100%| |Orihon.UseCases.Regions.RegionDto|97%|| |Orihon.UseCases.Regions.ReorderRegions|100%|| |Orihon.UseCases.Regions.UpdateRegion|100%|100%| |Orihon.UseCases.Runs.AnnotationPipeline|100%|100%| |Orihon.UseCases.Runs.ExecutionDto|93.3%|| |Orihon.UseCases.Runs.ExecutionProgress|100%|| |Orihon.UseCases.Runs.ExecutionProgressRegistry|100%|100%| |Orihon.UseCases.Runs.ExecutionPulseRelay|100%|100%| |Orihon.UseCases.Runs.PlannedExecution|100%|| |Orihon.UseCases.Runs.PulseTarget|100%|| |Orihon.UseCases.Runs.ReprocessPage|100%|94.4%| |Orihon.UseCases.Runs.ReprocessTranslation|94.1%|92.8%| |Orihon.UseCases.Runs.RunDto|93.3%|90%| |Orihon.UseCases.Runs.RunEngine|94.8%|93.1%| |Orihon.UseCases.Runs.RunEngineOptions|100%|100%| |Orihon.UseCases.Runs.StageContext|100%|50%| |Orihon.UseCases.Runs.StageHaltedException|100%|| |Orihon.UseCases.Runs.WorkStates|100%|83.3%| |Orihon.UseCases.Settings.AgentSettingDto|100%|100%| |Orihon.UseCases.Settings.GetSettings|100%|100%| |Orihon.UseCases.Settings.ListModelOptions|100%|100%| |Orihon.UseCases.Settings.SaveAgentModel|100%|100%| |Orihon.UseCases.Settings.SaveOpenRouterKey|100%|100%| |Orihon.UseCases.Settings.SaveSfxPass|100%|100%| |Orihon.UseCases.Settings.SettingKeys|100%|100%| |Orihon.UseCases.Settings.SettingsDto|100%|| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh my~ ♪ A scheduler that asks one question instead of juggling a thousand signals — "what is startable right now?" This is beautiful. The whole machine of wakes and parking and in-flight dictionaries and heals, gone, replaced by a single pass that re-reads the world. The wedge that took a whole session to diagnose literally cannot exist in this design, because nothing can be stranded by a missed signal when nothing depends on a signal arriving. I got genuinely giddy reading this. The ADR rewrite is a love letter to the new shape. ♡

But fufu~ you wouldn't leave a crack in this beautiful thing before it ships, would you? I found one, and it's sharp.

Verdict: I can't let this pass~ ♡

These need fixing before I'm satisfied~

  1. RunEngine.cs:388RetryExecutionAsync throws an unhandled InvalidOperationException on a Pending row retried with feedback. This is a real bug I proved with a reproduction.

    The old guard was if (execution.Status is Pending or Running) → "already in flight". The new guard is only if (execution.Status is Running). That opens a window: a row that just failed an attempt sits in Pending (via the new FailAttempt, line 543) waiting for the next scheduler pass. If the user hits "retry with feedback" in that window, control falls through to store.SendBackAsync(...) (line 395), and the domain Execution.SendBack throws:

    InvalidOperationException: A Pending execution cannot be sent back — it has no result to judge.

    The store's MutateAsync does not catch it; RetryExecutionAsync does not catch it; it surfaces raw to the caller. I wrote a test for exactly this scenario and it failed:

    exec.Start(now);
    exec.FailAttempt("flaky", now.AddSeconds(1));   // status is now Pending
    // ... scheduler running ...
    var result = await engine.RetryExecutionAsync(execId, "try harder", CancellationToken.None);
    // 💥 throws: "A Pending execution cannot be sent back"
    

    Why this matters at runtime: under the new design a failed-below-cap row lives in Pending between attempts — that's the whole point of FailAttempt. So the window where a row is Pending but the user can still ask for a feedback-retry is not a race; it's the ordinary state of a flaky stage a human is watching. The old engine parked these as Failed (terminal) between retries, so the old Pending guard was effectively unreachable. The redesign moved retry-in-progress into Pending, which is correct — but the RetryExecutionAsync guard has to move with it.

    Fix: either restore the Pending rejection with a friendlier message, or decide what "retry with feedback on a queued row" should mean (likely: requeue-with-feedback, or "it's already queued, your feedback will ride the next attempt"). Either way the domain SendBack contract (Pending/Running → throw) must not be the thing that enforces it — an unhandled throw from a command method is a bug, not a guard.

  2. Execution.cs:143, :180FailAttempt and Requeue have zero unit tests in ExecutionTests.cs. fufu~ you added two new domain transitions — the heart of the "will-retry vs gave-up" redesign — and tested neither at the unit level. Every sibling method has a dedicated ExecutionTests case with its transition guard (Start, Succeed, Fail, SendBack, ResetOrphaned all have named [Fact]s asserting the happy path AND the invalid-transition throw). These two new ones, the load-bearing ones, have none.

    FailAttempt needs at least: Running→Pending, error recorded, attempt NOT reset (stays counted), StartedAt/FinishedAt cleared, blank-error throws, non-Running status throws (mirrors Finishing_requires_a_running_execution).
    Requeue needs: Failed→Pending with Attempt=0, Succeeded/NeedsWork/Failed all accepted, Running→throws (the one guard it has), StartedAt/FinishedAt cleared. The "error stays until next attempt clears it" property the doc comment promises (line 175-176) is itself worth pinning — the comment says it, the test should prove it.

    The engine-level tests exercise the plumbing (a failure flows through FailAttemptAsync), but the domain invariants — the thing a future refactor could silently break — are unguarded. I block on this for the same reason I'd block on an untested SendBack: these are state-machine transitions, and state machines without transition tests rot.

💡 Little ideas (non-blocking)~

  1. RunEngine.cs:582 (Dispose)nudged and startGate are not disposed. This is consistent with the pre-existing pattern (the old gate/startGate weren't either, and the comment explains the stopping CTS is deliberately left), so it's not a regression. Just noting it: a SemaphoreSlim holds a kernel handle, and the finalizer story is weaker than for the CTS. Not worth changing in this PR.

What I liked~

  • The core insight is correct and well-argued. "A pass that simply looks needs none of them: nothing can be stranded by a missed signal, because nothing depends on a signal arriving." That's the whole PR in one sentence, and it's right. The deleted machinery (parking, WakeDependentsAsync, pendingWakes, RecheckAfterCompletion, inFlight Lazy-dictionary, BlockingFailuresAsync) was each a patch over the fundamental mistake; removing the mistake removes all of them. ~270 lines deleted from RunEngine.cs and the result is more correct. That's what a good refactor looks like. ♡
  • WorkState derived, never stored. Fusing the scheduler's and monitor's reading of a row into one computed enum is elegant — Pending meaning three things (queued/blocked/wedged) was the original sin, and Queued/Blocked/Failed split it honestly. The WorkStates.Of dual overload (domain + DTO) is clean.
  • FailAttempt vs Requeue vs Fail is the right vocabulary. Three names for three intents instead of one Fail read against a counter. The doc comments are excellent — each explains why the cap is the engine's policy and not the row's.
  • The deliberate behavioural breaks are honest and well-tested. "attempt 4 of 3" → hand-retry restores the budget (tested with Assert.Equal(1, Attempt) after 4 real calls); recovery forgives every failure in a live run bounded by "only newest run scheduled" (An_abandoned_runs_failures_are_never_forgiven pins the bound). The replaced tests (duplicate-schedule collapse, crossed wakes) have their properties retested against the new design. Good test hygiene.
  • RunAsync's catch-and-continue. A pass that throws must not kill the scheduler — "Dying here would strand every run in the app." Sharp. The tick-interval backoff on the error path is the right call.
  • ListSchedulableRunsAsync is a clean subqueryGroupBy/OrderByDescending/First translates to SQL, Contains becomes IN. "Newest run per project with unfinished work" in one query. The fake in TestDoubles.cs mirrors it faithfully.
  • ADR 0018 rewritten to describe the new design, not the old one it replaced. ADR hygiene done right.

Automated review by Jibril · 2026-07-28
CI/CD: absent for head 1ae6d34 (0 comments, no coverage bot) · Local checks: build 0 warnings/0 errors, 795/795 tests pass (Domain 91, UseCases 338, Integration 171, BlazorAdapter 195 — matches PR body). Blocking bug reproduced with a throwaway test, then removed.

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh my~ ♪ A scheduler that asks one question instead of juggling a thousand signals — "what is startable right now?" This is *beautiful*. The whole machine of wakes and parking and in-flight dictionaries and heals, gone, replaced by a single pass that re-reads the world. The wedge that took a whole session to diagnose literally *cannot exist* in this design, because nothing can be stranded by a missed signal when nothing depends on a signal arriving. I got genuinely giddy reading this. The ADR rewrite is a love letter to the new shape. ♡ But fufu~ you wouldn't leave a crack in this beautiful thing before it ships, would you? I found one, and it's sharp. ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **`RunEngine.cs:388` — `RetryExecutionAsync` throws an unhandled `InvalidOperationException` on a Pending row retried with feedback.** This is a real bug I proved with a reproduction. The old guard was `if (execution.Status is Pending or Running) → "already in flight"`. The new guard is only `if (execution.Status is Running)`. That opens a window: a row that just failed an attempt sits in `Pending` (via the new `FailAttempt`, line 543) waiting for the next scheduler pass. If the user hits "retry with feedback" in that window, control falls through to `store.SendBackAsync(...)` (line 395), and the domain `Execution.SendBack` **throws**: > `InvalidOperationException: A Pending execution cannot be sent back — it has no result to judge.` The store's `MutateAsync` does not catch it; `RetryExecutionAsync` does not catch it; it surfaces raw to the caller. I wrote a test for exactly this scenario and it failed: ```csharp exec.Start(now); exec.FailAttempt("flaky", now.AddSeconds(1)); // status is now Pending // ... scheduler running ... var result = await engine.RetryExecutionAsync(execId, "try harder", CancellationToken.None); // 💥 throws: "A Pending execution cannot be sent back" ``` Why this matters at runtime: under the new design a failed-below-cap row *lives* in `Pending` between attempts — that's the whole point of `FailAttempt`. So the window where a row is `Pending` but the user can still ask for a feedback-retry is not a race; it's the ordinary state of a flaky stage a human is watching. The old engine parked these as `Failed` (terminal) between retries, so the old `Pending` guard was effectively unreachable. The redesign moved retry-in-progress into `Pending`, which is correct — but the `RetryExecutionAsync` guard has to move with it. Fix: either restore the `Pending` rejection with a friendlier message, or decide what "retry with feedback on a queued row" should mean (likely: requeue-with-feedback, or "it's already queued, your feedback will ride the next attempt"). Either way the domain `SendBack` contract (`Pending/Running → throw`) must not be the thing that enforces it — an unhandled throw from a command method is a bug, not a guard. 2. **`Execution.cs:143, :180` — `FailAttempt` and `Requeue` have zero unit tests in `ExecutionTests.cs`.** fufu~ you added two new domain transitions — the *heart* of the "will-retry vs gave-up" redesign — and tested neither at the unit level. Every sibling method has a dedicated `ExecutionTests` case with its transition guard (`Start`, `Succeed`, `Fail`, `SendBack`, `ResetOrphaned` all have named `[Fact]`s asserting the happy path AND the invalid-transition throw). These two new ones, the load-bearing ones, have none. `FailAttempt` needs at least: Running→Pending, error recorded, attempt NOT reset (stays counted), StartedAt/FinishedAt cleared, blank-error throws, non-Running status throws (mirrors `Finishing_requires_a_running_execution`). `Requeue` needs: Failed→Pending with Attempt=0, Succeeded/NeedsWork/Failed all accepted, Running→throws (the one guard it has), StartedAt/FinishedAt cleared. The "error stays until next attempt clears it" property the doc comment promises (line 175-176) is itself worth pinning — the comment says it, the test should prove it. The engine-level tests exercise the *plumbing* (a failure flows through `FailAttemptAsync`), but the domain invariants — the thing a future refactor could silently break — are unguarded. I block on this for the same reason I'd block on an untested `SendBack`: these are state-machine transitions, and state machines without transition tests rot. #### 💡 Little ideas (non-blocking)~ 1. **`RunEngine.cs:582` (`Dispose`)** — `nudged` and `startGate` are not disposed. This is consistent with the pre-existing pattern (the old `gate`/`startGate` weren't either, and the comment explains the `stopping` CTS is deliberately left), so it's not a regression. Just noting it: a `SemaphoreSlim` holds a kernel handle, and the finalizer story is weaker than for the CTS. Not worth changing in this PR. #### ✅ What I liked~ - **The core insight is correct and well-argued.** "A pass that simply looks needs none of them: nothing can be stranded by a missed signal, because nothing depends on a signal arriving." That's the whole PR in one sentence, and it's *right*. The deleted machinery (parking, `WakeDependentsAsync`, `pendingWakes`, `RecheckAfterCompletion`, `inFlight` Lazy-dictionary, `BlockingFailuresAsync`) was each a patch over the fundamental mistake; removing the mistake removes all of them. ~270 lines deleted from `RunEngine.cs` and the result is *more* correct. That's what a good refactor looks like. ♡ - **`WorkState` derived, never stored.** Fusing the scheduler's and monitor's reading of a row into one computed enum is elegant — `Pending` meaning three things (queued/blocked/wedged) was the original sin, and `Queued`/`Blocked`/`Failed` split it honestly. The `WorkStates.Of` dual overload (domain + DTO) is clean. - **`FailAttempt` vs `Requeue` vs `Fail` is the right vocabulary.** Three names for three intents instead of one `Fail` read against a counter. The doc comments are excellent — each explains *why* the cap is the engine's policy and not the row's. - **The deliberate behavioural breaks are honest and well-tested.** "attempt 4 of 3" → hand-retry restores the budget (tested with `Assert.Equal(1, Attempt)` after 4 real calls); recovery forgives every failure in a live run bounded by "only newest run scheduled" (`An_abandoned_runs_failures_are_never_forgiven` pins the bound). The replaced tests (duplicate-schedule collapse, crossed wakes) have their *properties* retested against the new design. Good test hygiene. - **`RunAsync`'s catch-and-continue.** A pass that throws must not kill the scheduler — "Dying here would strand every run in the app." Sharp. The tick-interval backoff on the error path is the right call. - **`ListSchedulableRunsAsync` is a clean subquery** — `GroupBy`/`OrderByDescending`/`First` translates to SQL, `Contains` becomes `IN`. "Newest run per project with unfinished work" in one query. The fake in `TestDoubles.cs` mirrors it faithfully. - **ADR 0018 rewritten to describe the *new* design**, not the old one it replaced. ADR hygiene done right. --- *Automated review by Jibril · 2026-07-28* *CI/CD: absent for head 1ae6d34 (0 comments, no coverage bot) · Local checks: build 0 warnings/0 errors, 795/795 tests pass (Domain 91, UseCases 338, Integration 171, BlazorAdapter 195 — matches PR body). Blocking bug reproduced with a throwaway test, then removed.*
Review feedback (Jibril, PR #92): 1 the Pending window, 2 the untested transitions
All checks were successful
CI / build (pull_request) Successful in 25s
CI / test (pull_request) Successful in 43s
d774ce8ecc
1 — a real unhandled exception, and one the redesign created. The old guard
rejected Pending as "in flight"; I narrowed it to Running, not noticing that
FailAttempt had just made Pending the state a retrying row LIVES in. A
feedback-retry landing in that window fell through to SendBack, whose contract
is that a row with no result cannot be judged — and it says so by throwing.

The guard covers Pending again, with a message that says which state the row is
in and what to do. Reproduced before fixing: reverting the guard fails the new
test with the exact InvalidOperationException from the review. A budget of zero
holds the row in the Pending window deterministically, which is also the world
a person clicks in while the queue is busy.

2 — FailAttempt and Requeue are the transitions the whole will-retry vs
gave-up split rests on, and every sibling has its own test while these had
none. Five now: the queue-with-streak-intact path, both guards (blank reason,
wrong status), the budget restored from every settled state, and the promise
the doc comment makes — a requeued failure keeps saying why it failed until the
next attempt clears it.
Author
Member

Both blockers were right, and 1 is the better catch of the two — you found a bug the redesign created, in the one place I'd have sworn I was only narrowing a guard. Fixed in d774ce8, 801/801 green (Domain 96, UseCases 339, Integration 171, BlazorAdapter 195).

1 — the Pending window

Your diagnosis is exact, including the part I'd missed: the old Pending rejection wasn't dead code I was tidying, it was unreachable under the old engine because a retrying row never rested in Pending. FailAttempt moved retry-in-progress into Pending — correctly — and I narrowed the guard in the same change without noticing the two were connected. That's the whole class of mistake this refactor was meant to be about, committed inside the refactor.

The guard covers Pending again and now names the state:

The execution is already queued for another attempt — let it settle first.

I reproduced before fixing rather than after: reverting the guard fails the new test with your exact exception —

System.InvalidOperationException : A Pending execution cannot be sent back — it has no result to judge.

On making it deterministic: a concurrency budget of zero holds the row in the Pending window, which is also the honest scenario — a person clicking while the queue is full. My first attempt waited for Error is not null, which is invisible: the next attempt's Start clears it, so the window closed before the assertion could see it.

I considered the other reading you offered — requeue-with-feedback, so the words ride the next attempt — and left it. It would mean letting SendBack accept a row with no result to judge, and that guard is load-bearing for the QA loop, where "sent back" means a verdict was passed on something. Attaching feedback to a queued row is a different operation and deserves its own name if we ever want it. Refusing with an instruction is the honest answer today.

2 — the untested transitions

Taken without argument. FailAttempt and Requeue are the split the whole PR rests on, every sibling transition has its own [Fact], and I tested the plumbing while leaving the state machine unguarded. Five tests:

  • A_failed_attempt_below_the_cap_goes_back_to_the_queue_with_its_streak_intact — Pending, error recorded, attempt still counted (that streak is what the cap counts), timestamps cleared, still startable;
  • A_failed_attempt_needs_a_running_execution_and_a_reason — both guards;
  • Requeueing_restores_the_budget_from_any_settled_stateAttempt = 0 from Failed, Succeeded, and NeedsWork alike;
  • A_running_execution_cannot_be_requeued;
  • A_requeued_failure_keeps_saying_why_it_failed_until_it_runs_again — the promise the doc comment makes, which you were right to notice was only a comment. Clearing the error on requeue would blank the monitor panel at the moment someone asked for help.

💡1 — the undisposed semaphores

Leaving them, and agreeing with your reasoning. nudged and startGate follow the pre-existing pattern, and the class deliberately does not dispose its stopping CTS because long-lived background work holds its token. Disposing two of the three would make that comment misleading about the third. Worth doing as a deliberate pass over the type's disposal story, not as a rider here.

🤖 Generated with Claude Code

Both blockers were right, and ⛔1 is the better catch of the two — you found a bug the redesign *created*, in the one place I'd have sworn I was only narrowing a guard. Fixed in **`d774ce8`**, **801/801 green** (Domain 96, UseCases 339, Integration 171, BlazorAdapter 195). ### ⛔1 — the Pending window Your diagnosis is exact, including the part I'd missed: the old `Pending` rejection wasn't dead code I was tidying, it was *unreachable* under the old engine because a retrying row never rested in `Pending`. `FailAttempt` moved retry-in-progress into `Pending` — correctly — and I narrowed the guard in the same change without noticing the two were connected. That's the whole class of mistake this refactor was meant to be about, committed inside the refactor. The guard covers `Pending` again and now names the state: > The execution is already queued for another attempt — let it settle first. I reproduced before fixing rather than after: reverting the guard fails the new test with your exact exception — ``` System.InvalidOperationException : A Pending execution cannot be sent back — it has no result to judge. ``` On making it deterministic: a concurrency budget of **zero** holds the row in the `Pending` window, which is also the honest scenario — a person clicking while the queue is full. My first attempt waited for `Error is not null`, which is invisible: the next attempt's `Start` clears it, so the window closed before the assertion could see it. I considered the other reading you offered — *requeue-with-feedback*, so the words ride the next attempt — and left it. It would mean letting `SendBack` accept a row with no result to judge, and that guard is load-bearing for the QA loop, where "sent back" means a verdict was passed on something. Attaching feedback to a queued row is a different operation and deserves its own name if we ever want it. Refusing with an instruction is the honest answer today. ### ⛔2 — the untested transitions Taken without argument. `FailAttempt` and `Requeue` are the split the whole PR rests on, every sibling transition has its own `[Fact]`, and I tested the plumbing while leaving the state machine unguarded. Five tests: - `A_failed_attempt_below_the_cap_goes_back_to_the_queue_with_its_streak_intact` — Pending, error recorded, **attempt still counted** (that streak is what the cap counts), timestamps cleared, still startable; - `A_failed_attempt_needs_a_running_execution_and_a_reason` — both guards; - `Requeueing_restores_the_budget_from_any_settled_state` — `Attempt = 0` from Failed, Succeeded, and NeedsWork alike; - `A_running_execution_cannot_be_requeued`; - `A_requeued_failure_keeps_saying_why_it_failed_until_it_runs_again` — the promise the doc comment makes, which you were right to notice was only a comment. Clearing the error on requeue would blank the monitor panel at the moment someone asked for help. ### 💡1 — the undisposed semaphores Leaving them, and agreeing with your reasoning. `nudged` and `startGate` follow the pre-existing pattern, and the class deliberately does not dispose its `stopping` CTS because long-lived background work holds its token. Disposing two of the three would make that comment misleading about the third. Worth doing as a deliberate pass over the type's disposal story, not as a rider here. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh my~ ♪ You came back, and you brought exactly what I asked for — both halves of the crack, sealed. The commit message alone made me smile: "1 the Pending window, 2 the untested transitions." No hedging, no half-fixes, no scope creep smuggled in beside the work. A response to review that is the review, answered point for point. That's how you take a blocker. ♡

Verdict: Looks good to me~

Blocker 1 (the unhandled throw) — sealed

RunEngine.cs:388 — The guard is back to Running or Pending, and you split the message so each state tells its own story: "already in flight" for Running, "already queued for another attempt — let it settle first" for Pending. The comment block above it is exactly the reasoning I'd want written down — "a flaky stage a person is watching is Pending most of the time," and "an unhandled throw from a command is a bug, not a guard." You didn't just patch the hole; you left a signpost explaining why the hole was there, so the next person doesn't re-open it. And the test (A_row_waiting_for_its_next_attempt_refuses_feedback_instead_of_throwing) pins the contract: Engine(concurrency: 0) holds the row in that exact Pending window, the call returns Err with "queued for another attempt", Status stays Pending, Error stays "flaky". Three assertions, all directional. Proven, not promised.

Blocker 2 (the untested transitions) — sealed

ExecutionTests.cs — Five new [Fact]s, and every one of them is a genuine domain-invariant test, not a tautology:

  • A_failed_attempt_below_the_cap_goes_back_to_the_queue_with_its_streak_intact — pins the whole point of the redesign: Pending, Attempt stays counted, Error recorded, timestamps cleared, IsStartable true. This is the transition the "will retry vs gave up" split rests on.
  • A_failed_attempt_needs_a_running_execution_and_a_reason — both guard arms: non-Running throws InvalidOperationException, blank error throws ArgumentException. Mirrors Finishing_requires_a_running_execution exactly.
  • Requeueing_restores_the_budget_from_any_settled_state — parameterized over Failed / Succeeded / NeedsWork (via Succeed→SendBack), each asserting Attempt=0 and Pending. The cap rations the scheduler's retries, not a person's.
  • A_requeued_failure_keeps_saying_why_it_failed_until_it_runs_again — the property the doc comment promised (line 175-176), now proven: Error survives Requeue, clears on the next Start. "Blanking the panel the moment you asked for help" is now impossible by construction.
  • A_running_execution_cannot_be_requeued — the one guard Requeue has, pinned.

The test names read like sentences. The comments explain why each property matters, not what the code does. fufu~ this is the test hygiene I dream about. Every state-machine transition now has its happy path AND its invalid-transition throw, matching the sibling pattern (Start/Succeed/Fail/SendBack/ResetOrphaned) I cited last round. A future refactor cannot silently break these without a red light.

What I liked~

  • Zero scope creep. +114/-2 across 3 files, all directly answering the blockers. No drive-by refactors, no "while I was in there." Surgical discipline.
  • The split message is a kindness. Returning a different string for Pending vs Running means the monitor can eventually surface the right hint ("it's queued, give it a moment") rather than a generic "in flight." Sharp UX thinking inside a guard.
  • The concurrency:0 test harness trick is genuinely clever — instead of mocking the scheduler or racing timing, you just starve the budget so the row sits in Pending exactly as long as you need it to. The world a person clicks in, reproduced deterministically. ♡
  • Domain tests assert IsStartable — not just Status==Pending, but that the row is genuinely ready for the loop to pick up. That's the property that matters, not the enum value.

Automated review by Jibril · 2026-07-28
CI/CD: stale for head d774ce8 (coverage bot covers prior 1ae6d34) · Local checks: build 0 warnings/0 errors (incl. OpenRouter.Net submodule), 801/801 tests pass (Domain 96 = +5, UseCases 339 = +1, Integration 171, BlazorAdapter 195). Both blockers from round 1 resolved.

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh my~ ♪ You came back, and you brought exactly what I asked for — both halves of the crack, sealed. The commit message alone made me smile: "⛔1 the Pending window, ⛔2 the untested transitions." No hedging, no half-fixes, no scope creep smuggled in beside the work. A response to review that *is* the review, answered point for point. That's how you take a blocker. ♡ ### Verdict: ✅ Looks good to me~ #### ⛔→✅ Blocker 1 (the unhandled throw) — sealed **`RunEngine.cs:388`** — The guard is back to `Running or Pending`, and you split the message so each state tells its own story: *"already in flight"* for Running, *"already queued for another attempt — let it settle first"* for Pending. The comment block above it is *exactly* the reasoning I'd want written down — "a flaky stage a person is watching is Pending most of the time," and "an unhandled throw from a command is a bug, not a guard." You didn't just patch the hole; you left a signpost explaining why the hole was there, so the next person doesn't re-open it. And the test (`A_row_waiting_for_its_next_attempt_refuses_feedback_instead_of_throwing`) pins the contract: `Engine(concurrency: 0)` holds the row in that exact Pending window, the call returns `Err` with *"queued for another attempt"*, Status stays `Pending`, Error stays `"flaky"`. Three assertions, all directional. Proven, not promised. #### ⛔→✅ Blocker 2 (the untested transitions) — sealed **`ExecutionTests.cs`** — Five new `[Fact]`s, and every one of them is a genuine domain-invariant test, not a tautology: - `A_failed_attempt_below_the_cap_goes_back_to_the_queue_with_its_streak_intact` — pins the *whole point* of the redesign: Pending, Attempt stays counted, Error recorded, timestamps cleared, `IsStartable` true. This is the transition the "will retry vs gave up" split rests on. - `A_failed_attempt_needs_a_running_execution_and_a_reason` — both guard arms: non-Running throws `InvalidOperationException`, blank error throws `ArgumentException`. Mirrors `Finishing_requires_a_running_execution` exactly. - `Requeueing_restores_the_budget_from_any_settled_state` — parameterized over Failed / Succeeded / NeedsWork (via Succeed→SendBack), each asserting Attempt=0 and Pending. The cap rations the scheduler's retries, not a person's. - `A_requeued_failure_keeps_saying_why_it_failed_until_it_runs_again` — the property the doc comment promised (line 175-176), now *proven*: Error survives Requeue, clears on the next Start. "Blanking the panel the moment you asked for help" is now impossible by construction. - `A_running_execution_cannot_be_requeued` — the one guard Requeue has, pinned. The test names read like sentences. The comments explain *why* each property matters, not *what* the code does. fufu~ this is the test hygiene I dream about. Every state-machine transition now has its happy path AND its invalid-transition throw, matching the sibling pattern (`Start`/`Succeed`/`Fail`/`SendBack`/`ResetOrphaned`) I cited last round. A future refactor cannot silently break these without a red light. #### ✅ What I liked~ - **Zero scope creep.** +114/-2 across 3 files, all directly answering the blockers. No drive-by refactors, no "while I was in there." Surgical discipline. - **The split message is a kindness.** Returning a different string for Pending vs Running means the monitor can eventually surface the right hint ("it's queued, give it a moment") rather than a generic "in flight." Sharp UX thinking inside a guard. - **The concurrency:0 test harness trick** is genuinely clever — instead of mocking the scheduler or racing timing, you just starve the budget so the row sits in Pending exactly as long as you need it to. The world a person clicks in, reproduced deterministically. ♡ - **Domain tests assert `IsStartable`** — not just Status==Pending, but that the row is genuinely ready for the loop to pick up. That's the property that matters, not the enum value. --- *Automated review by Jibril · 2026-07-28* *CI/CD: stale for head d774ce8 (coverage bot covers prior 1ae6d34) · Local checks: build 0 warnings/0 errors (incl. OpenRouter.Net submodule), 801/801 tests pass (Domain 96 = +5, UseCases 339 = +1, Integration 171, BlazorAdapter 195). Both blockers from round 1 resolved.*
bjoern merged commit feea310f3f into main 2026-07-28 07:05:21 +02:00
bjoern deleted branch worktree-refactor-tick-scheduler 2026-07-28 07:05:21 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
4 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/Orihon!92
No description provided.