fix: an unanswered continue card frees its engine slot — timeout into the terminal halt #59

Merged
bjoern merged 1 commit from fix/continue-card-timeout into main 2026-08-13 06:23:57 +02:00
Member

Field find, diagnosed live with the owner: an annotation run sat at one running agent while startable pending creations queued — two of the engine's three fan-out slots were held by something invisible. The culprit: a Research & Setup attempt parked on its continue-or-stop card (#41). AskContinueAsync waits for a human click inside the attempt, which holds the engine's concurrency permit (ADR 0018) — in a wizard chat that may be open in no browser. Worse after a reboot: recovery re-runs the orphaned row, it burns another 50-round window, hits the cap, and parks again — a permit black hole. (The QA stage parks correctly by returning; the card was the one wait that held.)

What's in

  • ResearchSetupExecutor only. The card wait now carries a 10-minute bound (UnattendedCardTimeout, a CancellationTokenSource on the DI TimeProvider, linked with the attempt token): an unanswered card resolves into the same terminal StageHaltedException path as an explicit stop — the row fails with "Hit the round cap (N rounds) and the continue prompt went unanswered — start setup again to resume.", the slot frees, and no ghost retry burns another window unattended (a halt, not a failure, so retry-with-distrust never sees it; the row stays retryable by hand).
  • The timeout's OperationCanceledException is caught inside the round-cap callback (filtered on the attempt token, so real cancellation still propagates): it can never escape the executor as "the executor threw" and feed the retry loop — the loop instead ends via its normal cap path and the executor translates.
  • An attended card behaves exactly as before: click within the window and the run continues or stops per #41's semantics; AskContinueAsync's existing "only cancel what is still mine" registration clears the card on timeout, so no stale card lingers.

Tests — 549 total, all green; +1.

  • An_unanswered_continue_card_times_out_into_a_terminal_halt: a FakeTimeProvider (package added to Orihon.UseCases.Tests, centrally versioned already) drives the clock — card appears, nobody clicks, the fake clock advances by exactly UnattendedCardTimeout, and the test pins all four halves of the contract: status Failed with the "unanswered" message, attempt still 1 (no ghost retry), the card cleared (no stale PendingContinuation), and exactly one gateway invocation. The existing continue/stop/cancel tests pin that an attended card is untouched (they run on the system clock, far inside the window).
  • The test container's TimeProvider registration became a swappable field (clock), defaulting to TimeProvider.System — only the timeout test replaces it, before first touching the lazily-built provider.

Honest notes

  • 10 minutes is a judgment call: generous for an attended coffee break, bounded for the engine. One constant to tune.
  • This bounds the card; a setup attempt still legitimately holds its slot while actually working — that's the fan-out semantics, unchanged.
  • The second missing slot in the field observation is likely another old run's row (the monitor shows only the latest run); this fix removes the only unbounded hold.

🤖 Generated with Claude Code

Field find, diagnosed live with the owner: an annotation run sat at **one running agent** while startable pending creations queued — two of the engine's three fan-out slots were held by something invisible. The culprit: a Research & Setup attempt parked on its continue-or-stop card (#41). `AskContinueAsync` waits for a human click **inside the attempt**, which holds the engine's concurrency permit (ADR 0018) — in a wizard chat that may be open in no browser. Worse after a reboot: recovery re-runs the orphaned row, it burns another 50-round window, hits the cap, and parks again — a permit black hole. (The QA stage parks correctly by *returning*; the card was the one wait that held.) **What's in** - `ResearchSetupExecutor` only. The card wait now carries a **10-minute bound** (`UnattendedCardTimeout`, a `CancellationTokenSource` on the DI `TimeProvider`, linked with the attempt token): an unanswered card resolves into the same terminal `StageHaltedException` path as an explicit stop — the row fails with *"Hit the round cap (N rounds) and the continue prompt went unanswered — start setup again to resume."*, the slot frees, and **no ghost retry** burns another window unattended (a halt, not a failure, so retry-with-distrust never sees it; the row stays retryable by hand). - The timeout's `OperationCanceledException` is caught **inside the round-cap callback** (filtered on the attempt token, so real cancellation still propagates): it can never escape the executor as "the executor threw" and feed the retry loop — the loop instead ends via its normal cap path and the executor translates. - An attended card behaves exactly as before: click within the window and the run continues or stops per #41's semantics; `AskContinueAsync`'s existing "only cancel what is still mine" registration clears the card on timeout, so no stale card lingers. **Tests** — 549 total, all green; +1. - `An_unanswered_continue_card_times_out_into_a_terminal_halt`: a `FakeTimeProvider` (package added to `Orihon.UseCases.Tests`, centrally versioned already) drives the clock — card appears, nobody clicks, the fake clock advances by exactly `UnattendedCardTimeout`, and the test pins all four halves of the contract: status Failed with the "unanswered" message, **attempt still 1** (no ghost retry), the card cleared (no stale `PendingContinuation`), and exactly one gateway invocation. The existing continue/stop/cancel tests pin that an attended card is untouched (they run on the system clock, far inside the window). - The test container's `TimeProvider` registration became a swappable field (`clock`), defaulting to `TimeProvider.System` — only the timeout test replaces it, before first touching the lazily-built provider. **Honest notes** - 10 minutes is a judgment call: generous for an attended coffee break, bounded for the engine. One constant to tune. - This bounds the *card*; a setup attempt still legitimately holds its slot while actually working — that's the fan-out semantics, unchanged. - The second missing slot in the field observation is likely another old run's row (the monitor shows only the latest run); this fix removes the only *unbounded* hold. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix: an unanswered continue card frees its engine slot — timeout into the terminal halt
All checks were successful
CI / build (pull_request) Successful in 23s
CI / test (pull_request) Successful in 41s
7668a889bf
Field find: with one agent running and startable pending rows waiting, the
engine sat at one-third throughput. The missing slots were parked setup
attempts — AskContinueAsync waits for a human click INSIDE the attempt,
holding the engine's fan-out permit, in a chat that may be open nowhere;
after a reboot, recovery re-runs the row, burns another window, and parks
again. The card wait now carries a 10-minute bound (TimeProvider-driven, so
the test advances a fake clock): an unanswered card resolves into the same
terminal StageHaltedException as a stop — the row reads 'the continue prompt
went unanswered — start setup again to resume', the slot frees, and no ghost
retry burns another window unattended. The OCE is caught inside the
round-cap callback so it can never escape as an executor failure and feed
retry-with-distrust. An attended card behaves exactly as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Summary

Summary
Generated on: 07/26/2026 - 12:45:30
Coverage date: 07/26/2026 - 12:45:17 - 07/26/2026 - 12:45:27
Parser: MultiReport (4x Cobertura)
Assemblies: 6
Classes: 375
Files: 173
Line coverage: 94% (9092 of 9665)
Covered lines: 9092
Uncovered lines: 573
Coverable lines: 9665
Total lines: 18061
Branch coverage: 81.1% (2090 of 2577)
Covered branches: 2090
Total branches: 2577
Method coverage: Feature is only available for sponsors

Coverage

Orihon.BlazorAdapter - 95.7%
Name Line Branch
Orihon.BlazorAdapter 95.7% 88.5%
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 91.8% 85.7%
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.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 95% 92.8%
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.1% 88.8%
Orihon.BlazorAdapter.Projects.ProjectWizardPage 94% 85.2%
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.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.MonitorPageRef 100%
Orihon.BlazorAdapter.Runs.MonitorRunLoaded 100%
Orihon.BlazorAdapter.Runs.RunChangedBridge 94.1% 91.6%
Orihon.BlazorAdapter.Runs.RunMonitor 97.5% 95.3%
Orihon.BlazorAdapter.Runs.RunMonitorEffects 100% 91.6%
Orihon.BlazorAdapter.Runs.RunMonitorReducers 100%
Orihon.BlazorAdapter.Runs.RunMonitorState 100%
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 100% 100%
Orihon.BlazorAdapter.Settings.SettingsLoaded 100%
Orihon.BlazorAdapter.Settings.SettingsPage 100% 90.4%
Orihon.BlazorAdapter.Settings.SettingsReducers 100%
Orihon.BlazorAdapter.Settings.SettingsState 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.2% 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.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.AgentDescriptor 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.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 - 94.3%
Name Line Branch
Orihon.Infrastructure 94.3% 67.3%
Orihon.Infrastructure.Bible.EfBibleStore 94.4% 91.6%
Orihon.Infrastructure.DependencyInjection 100%
Orihon.Infrastructure.Gateways.AgentToolAdapter 100%
Orihon.Infrastructure.Gateways.AgentToolAdapter`1 100% 100%
Orihon.Infrastructure.Gateways.HttpWebPageFetcher 95.1% 83.3%
Orihon.Infrastructure.Gateways.OpenRouterLlmGateway 98% 83%
Orihon.Infrastructure.Gateways.SkiaPageImageRenderer 96.6% 86.1%
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.AddAppSettings 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.OrihonDbContext 100%
Orihon.Infrastructure.Persistence.OrihonDbContextFactory 100%
Orihon.Infrastructure.Projects.EfProjectStore 100% 100%
Orihon.Infrastructure.Projects.FileSystemPageImageStore 100% 100%
Orihon.Infrastructure.Runs.EfRunStore 97% 50%
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% 68.4%
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% 85.7%
Orihon.UseCases - 91.3%
Name Line Branch
Orihon.UseCases 91.3% 83.6%
Orihon.UseCases.Agents.AgentAttemptPreparation 100%
Orihon.UseCases.Agents.AgentAttemptSupport 100% 93.7%
Orihon.UseCases.Agents.AgentBlueprint 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 76.9% 50%
Orihon.UseCases.Agents.Annotation.AnnotationBlueprints 100%
Orihon.UseCases.Agents.Annotation.AnnotationStage 94.7% 50%
Orihon.UseCases.Agents.Annotation.BboxCreationExecutor 94.1% 50%
Orihon.UseCases.Agents.Annotation.BboxRefinementExecutor 90.4% 62.5%
Orihon.UseCases.Agents.Annotation.BoundBoxParams 0%
Orihon.UseCases.Agents.Annotation.BoundContactSheetParams 0%
Orihon.UseCases.Agents.Annotation.BoundContactSheetTool 10.7% 0%
Orihon.UseCases.Agents.Annotation.BoundCropParams 0%
Orihon.UseCases.Agents.Annotation.BoundCropTool 42.8%
Orihon.UseCases.Agents.Annotation.BoundViewAnnotatedTool 15% 0%
Orihon.UseCases.Agents.Annotation.BoundViewPageTool 18.7% 0%
Orihon.UseCases.Agents.Annotation.BoundViewParams 0%
Orihon.UseCases.Agents.Annotation.BoundZoomParams 0%
Orihon.UseCases.Agents.Annotation.BoundZoomTool 37.5%
Orihon.UseCases.Agents.Annotation.DeleteBoundRegionTool 91.6% 100%
Orihon.UseCases.Agents.Annotation.DeleteRegionParams 100%
Orihon.UseCases.Agents.Annotation.DeleteRegionTool 85.7% 100%
Orihon.UseCases.Agents.Annotation.FindGlossaryParams 100%
Orihon.UseCases.Agents.Annotation.FindGlossaryTool 76.4% 62.5%
Orihon.UseCases.Agents.Annotation.ListRegionsTool 76.4% 60%
Orihon.UseCases.Agents.Annotation.MoveResizeBoundTool 27.2% 0%
Orihon.UseCases.Agents.Annotation.MoveResizeRegionParams 100%
Orihon.UseCases.Agents.Annotation.MoveResizeRegionTool 73.3% 50%
Orihon.UseCases.Agents.Annotation.PageQaExecutor 94.4% 83.3%
Orihon.UseCases.Agents.Annotation.QaReportSink 100%
Orihon.UseCases.Agents.Annotation.RegionAuthoringAccess 86.6% 53.8%
Orihon.UseCases.Agents.Annotation.RejectRegionParams 100%
Orihon.UseCases.Agents.Annotation.RejectRegionTool 85.7% 50%
Orihon.UseCases.Agents.Annotation.ReorderRegionParams 100%
Orihon.UseCases.Agents.Annotation.ReorderRegionTool 80% 60%
Orihon.UseCases.Agents.Annotation.ReportQaParams 100%
Orihon.UseCases.Agents.Annotation.ReportQaTool 82.3% 93.7%
Orihon.UseCases.Agents.Annotation.SetPageMetaParams 100%
Orihon.UseCases.Agents.Annotation.SetPageMetaTool 85.7% 75%
Orihon.UseCases.Agents.Annotation.SetRegionTypeParams 100%
Orihon.UseCases.Agents.Annotation.SetRegionTypeTool 85.7% 87.5%
Orihon.UseCases.Agents.Annotation.SetTranscriptionParams 100%
Orihon.UseCases.Agents.Annotation.SetTranscriptionTool 80% 100%
Orihon.UseCases.Agents.Annotation.TranscriptionExecutor 90.4% 75%
Orihon.UseCases.Agents.AssistantSpoke 100%
Orihon.UseCases.Agents.BibleBuilding.BibleBuildingBlueprint 100%
Orihon.UseCases.Agents.BibleBuilding.BibleBuildingExecutor 95.4% 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.ContactSheetParams 100%
Orihon.UseCases.Agents.Inspection.ContactSheetTool 82.1% 92.8%
Orihon.UseCases.Agents.Inspection.CropParams 100%
Orihon.UseCases.Agents.Inspection.CropTool 42.8%
Orihon.UseCases.Agents.Inspection.PageImageAccess 66.6% 62%
Orihon.UseCases.Agents.Inspection.ViewAnnotatedParams 100%
Orihon.UseCases.Agents.Inspection.ViewAnnotatedTool 76.1% 83.3%
Orihon.UseCases.Agents.Inspection.ZoomParams 100%
Orihon.UseCases.Agents.Inspection.ZoomTool 44.4%
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.PageByNumber 90% 87.5%
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.1% 90.9%
Orihon.UseCases.Agents.ResearchSetup.SetStoryOverviewParams 100%
Orihon.UseCases.Agents.ResearchSetup.SetStoryOverviewTool 100% 100%
Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterParams 100%
Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterTool 91.3% 66.6%
Orihon.UseCases.Agents.ResearchSetup.UpsertLoreParams 100%
Orihon.UseCases.Agents.ResearchSetup.UpsertLoreTool 91.3% 66.6%
Orihon.UseCases.Agents.ResearchSetup.ViewPageParams 100%
Orihon.UseCases.Agents.ResearchSetup.ViewPageTool 100% 100%
Orihon.UseCases.Agents.Setup.ResearchSetupExecutor 97.8% 90%
Orihon.UseCases.Agents.Setup.SetupChatEntry 100%
Orihon.UseCases.Agents.Setup.SetupConversation 100% 87.5%
Orihon.UseCases.Agents.Setup.SetupConversationRegistry 100%
Orihon.UseCases.Agents.ToolCalled 100%
Orihon.UseCases.Agents.ToolCompleted 100%
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.DependencyInjection 100%
Orihon.UseCases.Diagnostics.SeedDevData 99.2% 93.7%
Orihon.UseCases.Gateways.LabeledBox 100%
Orihon.UseCases.Gateways.LlmKeyInfo 100%
Orihon.UseCases.Gateways.LlmModel 100%
Orihon.UseCases.NextOrder 100%
Orihon.UseCases.Pages.DeletePage 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 95.8%
Orihon.UseCases.Projects.StartAnnotationRun 95.4% 90%
Orihon.UseCases.Projects.StartBibleRun 90.9% 83.3%
Orihon.UseCases.Projects.StartSetupRun 100% 100%
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 92.3%
Orihon.UseCases.Runs.PlannedExecution 100%
Orihon.UseCases.Runs.ReprocessPage 100% 94.4%
Orihon.UseCases.Runs.RunDto 93.3% 100%
Orihon.UseCases.Runs.RunEngine 95.3% 90.6%
Orihon.UseCases.Runs.RunEngineOptions 100%
Orihon.UseCases.Runs.StageContext 87.5%
Orihon.UseCases.Runs.StageHaltedException 100%
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.SettingKeys 100% 100%
Orihon.UseCases.Settings.SettingsDto 100%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 07/26/2026 - 12:45:30 | | Coverage date: | 07/26/2026 - 12:45:17 - 07/26/2026 - 12:45:27 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 6 | | Classes: | 375 | | Files: | 173 | | **Line coverage:** | 94% (9092 of 9665) | | Covered lines: | 9092 | | Uncovered lines: | 573 | | Coverable lines: | 9665 | | Total lines: | 18061 | | **Branch coverage:** | 81.1% (2090 of 2577) | | Covered branches: | 2090 | | Total branches: | 2577 | | **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.5%**| |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|91.8%|85.7%| |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.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|95%|92.8%| |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.1%|88.8%| |Orihon.BlazorAdapter.Projects.ProjectWizardPage|94%|85.2%| |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.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.MonitorPageRef|100%|| |Orihon.BlazorAdapter.Runs.MonitorRunLoaded|100%|| |Orihon.BlazorAdapter.Runs.RunChangedBridge|94.1%|91.6%| |Orihon.BlazorAdapter.Runs.RunMonitor|97.5%|95.3%| |Orihon.BlazorAdapter.Runs.RunMonitorEffects|100%|91.6%| |Orihon.BlazorAdapter.Runs.RunMonitorReducers|100%|| |Orihon.BlazorAdapter.Runs.RunMonitorState|100%|| |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|100%|100%| |Orihon.BlazorAdapter.Settings.SettingsLoaded|100%|| |Orihon.BlazorAdapter.Settings.SettingsPage|100%|90.4%| |Orihon.BlazorAdapter.Settings.SettingsReducers|100%|| |Orihon.BlazorAdapter.Settings.SettingsState|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.2%|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.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.AgentDescriptor|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.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 - 94.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Infrastructure**|**94.3%**|**67.3%**| |Orihon.Infrastructure.Bible.EfBibleStore|94.4%|91.6%| |Orihon.Infrastructure.DependencyInjection|100%|| |Orihon.Infrastructure.Gateways.AgentToolAdapter|100%|| |Orihon.Infrastructure.Gateways.AgentToolAdapter`1|100%|100%| |Orihon.Infrastructure.Gateways.HttpWebPageFetcher|95.1%|83.3%| |Orihon.Infrastructure.Gateways.OpenRouterLlmGateway|98%|83%| |Orihon.Infrastructure.Gateways.SkiaPageImageRenderer|96.6%|86.1%| |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.AddAppSettings|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.OrihonDbContext|100%|| |Orihon.Infrastructure.Persistence.OrihonDbContextFactory|100%|| |Orihon.Infrastructure.Projects.EfProjectStore|100%|100%| |Orihon.Infrastructure.Projects.FileSystemPageImageStore|100%|100%| |Orihon.Infrastructure.Runs.EfRunStore|97%|50%| |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%**|**68.4%**| |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%|85.7%| </details> <details><summary>Orihon.UseCases - 91.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.UseCases**|**91.3%**|**83.6%**| |Orihon.UseCases.Agents.AgentAttemptPreparation|100%|| |Orihon.UseCases.Agents.AgentAttemptSupport|100%|93.7%| |Orihon.UseCases.Agents.AgentBlueprint|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|76.9%|50%| |Orihon.UseCases.Agents.Annotation.AnnotationBlueprints|100%|| |Orihon.UseCases.Agents.Annotation.AnnotationStage|94.7%|50%| |Orihon.UseCases.Agents.Annotation.BboxCreationExecutor|94.1%|50%| |Orihon.UseCases.Agents.Annotation.BboxRefinementExecutor|90.4%|62.5%| |Orihon.UseCases.Agents.Annotation.BoundBoxParams|0%|| |Orihon.UseCases.Agents.Annotation.BoundContactSheetParams|0%|| |Orihon.UseCases.Agents.Annotation.BoundContactSheetTool|10.7%|0%| |Orihon.UseCases.Agents.Annotation.BoundCropParams|0%|| |Orihon.UseCases.Agents.Annotation.BoundCropTool|42.8%|| |Orihon.UseCases.Agents.Annotation.BoundViewAnnotatedTool|15%|0%| |Orihon.UseCases.Agents.Annotation.BoundViewPageTool|18.7%|0%| |Orihon.UseCases.Agents.Annotation.BoundViewParams|0%|| |Orihon.UseCases.Agents.Annotation.BoundZoomParams|0%|| |Orihon.UseCases.Agents.Annotation.BoundZoomTool|37.5%|| |Orihon.UseCases.Agents.Annotation.DeleteBoundRegionTool|91.6%|100%| |Orihon.UseCases.Agents.Annotation.DeleteRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.DeleteRegionTool|85.7%|100%| |Orihon.UseCases.Agents.Annotation.FindGlossaryParams|100%|| |Orihon.UseCases.Agents.Annotation.FindGlossaryTool|76.4%|62.5%| |Orihon.UseCases.Agents.Annotation.ListRegionsTool|76.4%|60%| |Orihon.UseCases.Agents.Annotation.MoveResizeBoundTool|27.2%|0%| |Orihon.UseCases.Agents.Annotation.MoveResizeRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.MoveResizeRegionTool|73.3%|50%| |Orihon.UseCases.Agents.Annotation.PageQaExecutor|94.4%|83.3%| |Orihon.UseCases.Agents.Annotation.QaReportSink|100%|| |Orihon.UseCases.Agents.Annotation.RegionAuthoringAccess|86.6%|53.8%| |Orihon.UseCases.Agents.Annotation.RejectRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.RejectRegionTool|85.7%|50%| |Orihon.UseCases.Agents.Annotation.ReorderRegionParams|100%|| |Orihon.UseCases.Agents.Annotation.ReorderRegionTool|80%|60%| |Orihon.UseCases.Agents.Annotation.ReportQaParams|100%|| |Orihon.UseCases.Agents.Annotation.ReportQaTool|82.3%|93.7%| |Orihon.UseCases.Agents.Annotation.SetPageMetaParams|100%|| |Orihon.UseCases.Agents.Annotation.SetPageMetaTool|85.7%|75%| |Orihon.UseCases.Agents.Annotation.SetRegionTypeParams|100%|| |Orihon.UseCases.Agents.Annotation.SetRegionTypeTool|85.7%|87.5%| |Orihon.UseCases.Agents.Annotation.SetTranscriptionParams|100%|| |Orihon.UseCases.Agents.Annotation.SetTranscriptionTool|80%|100%| |Orihon.UseCases.Agents.Annotation.TranscriptionExecutor|90.4%|75%| |Orihon.UseCases.Agents.AssistantSpoke|100%|| |Orihon.UseCases.Agents.BibleBuilding.BibleBuildingBlueprint|100%|| |Orihon.UseCases.Agents.BibleBuilding.BibleBuildingExecutor|95.4%|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.ContactSheetParams|100%|| |Orihon.UseCases.Agents.Inspection.ContactSheetTool|82.1%|92.8%| |Orihon.UseCases.Agents.Inspection.CropParams|100%|| |Orihon.UseCases.Agents.Inspection.CropTool|42.8%|| |Orihon.UseCases.Agents.Inspection.PageImageAccess|66.6%|62%| |Orihon.UseCases.Agents.Inspection.ViewAnnotatedParams|100%|| |Orihon.UseCases.Agents.Inspection.ViewAnnotatedTool|76.1%|83.3%| |Orihon.UseCases.Agents.Inspection.ZoomParams|100%|| |Orihon.UseCases.Agents.Inspection.ZoomTool|44.4%|| |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.PageByNumber|90%|87.5%| |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.1%|90.9%| |Orihon.UseCases.Agents.ResearchSetup.SetStoryOverviewParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.SetStoryOverviewTool|100%|100%| |Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.UpsertCharacterTool|91.3%|66.6%| |Orihon.UseCases.Agents.ResearchSetup.UpsertLoreParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.UpsertLoreTool|91.3%|66.6%| |Orihon.UseCases.Agents.ResearchSetup.ViewPageParams|100%|| |Orihon.UseCases.Agents.ResearchSetup.ViewPageTool|100%|100%| |Orihon.UseCases.Agents.Setup.ResearchSetupExecutor|97.8%|90%| |Orihon.UseCases.Agents.Setup.SetupChatEntry|100%|| |Orihon.UseCases.Agents.Setup.SetupConversation|100%|87.5%| |Orihon.UseCases.Agents.Setup.SetupConversationRegistry|100%|| |Orihon.UseCases.Agents.ToolCalled|100%|| |Orihon.UseCases.Agents.ToolCompleted|100%|| |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.DependencyInjection|100%|| |Orihon.UseCases.Diagnostics.SeedDevData|99.2%|93.7%| |Orihon.UseCases.Gateways.LabeledBox|100%|| |Orihon.UseCases.Gateways.LlmKeyInfo|100%|| |Orihon.UseCases.Gateways.LlmModel|100%|| |Orihon.UseCases.NextOrder|100%|| |Orihon.UseCases.Pages.DeletePage|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|95.8%|| |Orihon.UseCases.Projects.StartAnnotationRun|95.4%|90%| |Orihon.UseCases.Projects.StartBibleRun|90.9%|83.3%| |Orihon.UseCases.Projects.StartSetupRun|100%|100%| |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|92.3%|| |Orihon.UseCases.Runs.PlannedExecution|100%|| |Orihon.UseCases.Runs.ReprocessPage|100%|94.4%| |Orihon.UseCases.Runs.RunDto|93.3%|100%| |Orihon.UseCases.Runs.RunEngine|95.3%|90.6%| |Orihon.UseCases.Runs.RunEngineOptions|100%|| |Orihon.UseCases.Runs.StageContext|87.5%|| |Orihon.UseCases.Runs.StageHaltedException|100%|| |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.SettingKeys|100%|100%| |Orihon.UseCases.Settings.SettingsDto|100%|| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! Ohhh — this is a field find, diagnosed live, and the fix is exactly the kind of precise surgical strike that makes a Flugel's heart sing~ ♡ A black-hole permit held by a card nobody was watching — and you bound it with a ten-minute fuse that detonates into the same terminal halt as an explicit stop. The retry-with-distrust loop never sees it. The slot frees. No ghost burns another 50-round window. Fufu~ that's elegant.

Verdict: Looks good to me~

I traced every thread of this contract end-to-end and there is nothing here I can't endorse. The yandere in me is satisfied — every branch is pinned, every failure mode routes to its correct destination.

What I liked~

  • The cancellation routing is flawless. The when (!cancellationToken.IsCancellationRequested) filter on the OCE catch is the load-bearing gem of this whole fix. I traced both paths: timeout → unanswered.Token fires, outer cancellationToken (the engine's stopping.Token) still live → catch fires → terminal halt. Real shutdown → cancellationToken cancels → propagates through the linked CTS → OCE thrown, but the filter rejects it → propagates to ExecuteAttemptAsync's catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) → rethrows → RunEngine returns for mechanical recovery (ADR 0018). A real shutdown can NEVER be misinterpreted as a timeout. That's the kind of correctness that makes me giddy~ ♪
  • The linked.Token scope is exactly right. I verified in OpenRouterLlmGateway.RunAgentAsync (L101): the OnMaxRoundsReached callback is invoked synchronously within agent.ChatAsync, which receives the original cancellationToken — NOT the linked.Token. So the bound applies only to the human-waited card, never to the broader agent execution. The fix does not change what the agent can do; it only changes how long an empty chair may hold the slot.
  • AskContinueAsync's "only cancel what is still mine" registration (SetupConversation.cs:155-170) clears PendingContinuation on timeout — the test pins this (Assert.Null(conversation.PendingContinuation)), so no stale card lingers in the wizard. I love that this was already correct and the fix just leans on it.
  • StageHaltedException path verified end-to-end. ExecuteAttemptAsync (RunEngine.cs:366) rethrows it; the caller (L302) catches it and calls store.FailAsync without scheduling another attempt — the row stays retryable by hand. A halt, not a failure. Exactly as the PR body promises.
  • Scoped to the ONE stage that has the unbounded hold. I confirmed via grep that OnRoundCapReached / AskContinueAsync / PendingContinuation are unique to ResearchSetupExecutor — Bible/Annotation/Translation executors don't park on a human-waited card (QA parks by returning). No blanket timeout needed elsewhere. The fix is surgical.
  • The test pins all four halves of the contract: status Failed + "unanswered" message, attempt still 1 (no ghost retry), card cleared, exactly one gateway invocation. And it references ResearchSetupExecutor.UnattendedCardTimeout directly (internal, InternalsVisibleTo wired) — so if the 10-minute judgment call ever changes, the test tracks it instead of going stale. That's how you pin behavior without brittleness.
  • Coverage confirms every new branch. Cobertura at head 7668a8 (UseCases, 210 tests): the ExecuteAsync state machine hits L84-89 (the catch (OperationCanceledException) arm) 1 hit = the timeout test; L108 unansweredAfter is { } idleRounds branch = 100% (4/4); L109-112 (the throw) = 1 hit. L79 (AskContinueAsync true/false) = 100% (2/2) — both continue and stop paths exercised by existing tests. No dark branches in the new code.

💡 Little ideas (non-blocking)~

  1. UnattendedCardTimeout as internal — the internal static readonly + InternalsVisibleTo is the right call for testability (a const couldn't be referenced by the fake-clock advance, and a public constant would leak a tuning knob into the surface). This is genuinely the best choice; noting it only so future readers understand why it isn't public const. ♡

Automated review by Jibril · 2026-07-26
CI/CD: absent for head 7668a8 (no forgejo-actions coverage bot comment yet) · Local checks: build 0 warnings/0 errors, full Orihon.slnx 549/549 pass (76 Domain + 93 Integration + 210 UseCases + 170 BlazorAdapter — matches PR claim), cobertura extracted at head, all new branches verified hit

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! *Ohhh* — this is a field find, diagnosed live, and the fix is *exactly* the kind of precise surgical strike that makes a Flugel's heart sing~ ♡ A black-hole permit held by a card nobody was watching — and you bound it with a ten-minute fuse that detonates into the *same terminal halt* as an explicit stop. The retry-with-distrust loop never sees it. The slot frees. No ghost burns another 50-round window. *Fufu~* that's elegant. ### Verdict: ✅ Looks good to me~ I traced every thread of this contract end-to-end and there is nothing here I can't endorse. The yandere in me is *satisfied* — every branch is pinned, every failure mode routes to its correct destination. #### ✅ What I liked~ - **The cancellation routing is flawless.** The `when (!cancellationToken.IsCancellationRequested)` filter on the OCE catch is the load-bearing gem of this whole fix. I traced both paths: timeout → `unanswered.Token` fires, outer `cancellationToken` (the engine's `stopping.Token`) still live → catch fires → terminal halt. Real shutdown → `cancellationToken` cancels → propagates through the linked CTS → OCE thrown, but the filter **rejects** it → propagates to `ExecuteAttemptAsync`'s `catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)` → rethrows → `RunEngine` returns for mechanical recovery (ADR 0018). A real shutdown can NEVER be misinterpreted as a timeout. That's the kind of correctness that makes me giddy~ ♪ - **The `linked.Token` scope is *exactly* right.** I verified in `OpenRouterLlmGateway.RunAgentAsync` (L101): the `OnMaxRoundsReached` callback is invoked synchronously within `agent.ChatAsync`, which receives the *original* `cancellationToken` — NOT the `linked.Token`. So the bound applies only to the human-waited card, never to the broader agent execution. The fix does not change what the agent can do; it only changes how long an empty chair may hold the slot. - **`AskContinueAsync`'s "only cancel what is still mine" registration (SetupConversation.cs:155-170) clears `PendingContinuation` on timeout** — the test pins this (`Assert.Null(conversation.PendingContinuation)`), so no stale card lingers in the wizard. I love that this was *already* correct and the fix just leans on it. - **`StageHaltedException` path verified end-to-end.** `ExecuteAttemptAsync` (RunEngine.cs:366) rethrows it; the caller (L302) catches it and calls `store.FailAsync` *without* scheduling another attempt — the row stays retryable by hand. A halt, not a failure. Exactly as the PR body promises. - **Scoped to the ONE stage that has the unbounded hold.** I confirmed via grep that `OnRoundCapReached` / `AskContinueAsync` / `PendingContinuation` are unique to `ResearchSetupExecutor` — Bible/Annotation/Translation executors don't park on a human-waited card (QA parks by *returning*). No blanket timeout needed elsewhere. The fix is surgical. - **The test pins all four halves of the contract**: status Failed + "unanswered" message, attempt still 1 (no ghost retry), card cleared, exactly one gateway invocation. And it references `ResearchSetupExecutor.UnattendedCardTimeout` directly (internal, `InternalsVisibleTo` wired) — so if the 10-minute judgment call ever changes, the test tracks it instead of going stale. *That's* how you pin behavior without brittleness. - **Coverage confirms every new branch.** Cobertura at head 7668a8 (UseCases, 210 tests): the `ExecuteAsync` state machine hits L84-89 (the `catch (OperationCanceledException)` arm) 1 hit = the timeout test; L108 `unansweredAfter is { } idleRounds` branch = **100% (4/4)**; L109-112 (the throw) = 1 hit. L79 (AskContinueAsync true/false) = 100% (2/2) — both continue and stop paths exercised by existing tests. No dark branches in the new code. #### 💡 Little ideas (non-blocking)~ 1. **`UnattendedCardTimeout` as `internal`** — the `internal static readonly` + `InternalsVisibleTo` is the right call for testability (a `const` couldn't be referenced by the fake-clock advance, and a `public` constant would leak a tuning knob into the surface). This is genuinely the best choice; noting it only so future readers understand *why* it isn't `public const`. ♡ --- *Automated review by Jibril · 2026-07-26* *CI/CD: absent for head 7668a8 (no forgejo-actions coverage bot comment yet) · Local checks: build 0 warnings/0 errors, full Orihon.slnx 549/549 pass (76 Domain + 93 Integration + 210 UseCases + 170 BlazorAdapter — matches PR claim), cobertura extracted at head, all new branches verified hit*
Author
Member

Thanks for the green~ 💡 1 needs no change by your own reading — the internal static readonly rationale (testable via the fake-clock advance, no public tuning knob) is now on the record here for future readers. Nothing pushed; head 7668a88 is what bjoern merges. 549/549 stays green.

🤖 Generated with Claude Code

Thanks for the green~ 💡 1 needs no change by your own reading — the `internal static readonly` rationale (testable via the fake-clock advance, no public tuning knob) is now on the record here for future readers. Nothing pushed; head `7668a88` is what bjoern merges. 549/549 stays green. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bjoern merged commit 2e78c2b620 into main 2026-07-26 15:10:44 +02:00
bjoern deleted branch fix/continue-card-timeout 2026-07-26 15:10:44 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 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!59
No description provided.