feat: agent runs narrate themselves to the log #60

Merged
bjoern merged 3 commits from feat/agent-run-logging into main 2026-08-13 06:23:57 +02:00
Member

Failures currently land in the run monitor as OpenRouter's flattened one-liner ("The provider call failed: Provider returned error") with a completely empty server log behind it — debugging a failed generation means guessing. This arc makes the runs observable.

What changed

The gateway unpacks the failure the client already carried. Result<T>.Failure in the vendored OpenRouter.Net client has always held the HTTP status, the provider error code, and the metadata block — which is where OpenRouter hides the upstream provider's real error behind its generic message. OpenRouterLlmGateway now flattens all of it into the error string the monitor row shows (Provider returned error (HTTP 502; {"provider_name":"Anthropic","raw":"…"})), and logs it. Agent runs also log their start (Debug, with the kickoff line), completion (rounds + cost), round-cap exhaustion, and key/catalog failures.

The run engine narrates the execution lifecycle. Run started, stage started per attempt (attempt n/max), success with cost, retry-with-distrust, terminal failure, halt by hand, orphan reset during startup recovery. An executor's thrown exception now logs with its full stack trace instead of surviving only as e.Message on the execution row.

The vendored request logger is wired in. OpenRouterRequestLoggingHandler (sanitizes base64 images, truncates bodies) sits in the production client chain — silent at default levels, full request shapes when its category is raised to Debug. appsettings.Development.json enables it and the gateway's Debug logs for dev sessions.

Tests

  • A_provider_failure_carries_status_code_and_metadata_into_the_error — canned 502 with metadata through the real client; the error string carries message, status, and the upstream raw error.
  • The_engine_narrates_the_execution_lifecycle_to_the_log — a failing stage logs run start, both attempt starts, the retry warning, and the terminal error.
  • Full suite: 550 passing.

🤖 Generated with Claude Code

Failures currently land in the run monitor as OpenRouter's flattened one-liner ("The provider call failed: Provider returned error") with a completely empty server log behind it — debugging a failed generation means guessing. This arc makes the runs observable. ## What changed **The gateway unpacks the failure the client already carried.** `Result<T>.Failure` in the vendored OpenRouter.Net client has always held the HTTP status, the provider error code, and the metadata block — which is where OpenRouter hides the upstream provider's *real* error behind its generic message. `OpenRouterLlmGateway` now flattens all of it into the error string the monitor row shows (`Provider returned error (HTTP 502; {"provider_name":"Anthropic","raw":"…"})`), and logs it. Agent runs also log their start (Debug, with the kickoff line), completion (rounds + cost), round-cap exhaustion, and key/catalog failures. **The run engine narrates the execution lifecycle.** Run started, stage started per attempt (`attempt n/max`), success with cost, retry-with-distrust, terminal failure, halt by hand, orphan reset during startup recovery. An executor's thrown exception now logs with its full stack trace instead of surviving only as `e.Message` on the execution row. **The vendored request logger is wired in.** `OpenRouterRequestLoggingHandler` (sanitizes base64 images, truncates bodies) sits in the production client chain — silent at default levels, full request shapes when its category is raised to Debug. `appsettings.Development.json` enables it and the gateway's Debug logs for dev sessions. ## Tests - `A_provider_failure_carries_status_code_and_metadata_into_the_error` — canned 502 with metadata through the real client; the error string carries message, status, and the upstream raw error. - `The_engine_narrates_the_execution_lifecycle_to_the_log` — a failing stage logs run start, both attempt starts, the retry warning, and the terminal error. - Full suite: 550 passing. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: agent runs narrate themselves to the log
All checks were successful
CI / build (pull_request) Successful in 25s
CI / test (pull_request) Successful in 42s
0c46e8aa01
Failures used to land in the monitor as OpenRouter's flattened one-liner
("Provider returned error") with an empty server log behind it. Now:

- The gateway logs every agent run's start (Debug), completion, and failure,
  and unpacks the provider failure the vendored client already carried —
  HTTP status, provider error code, and the metadata block where OpenRouter
  hides the upstream provider's real error — into both the log and the
  monitor row's error string.
- The run engine narrates the execution lifecycle: run started, stage
  started/succeeded per attempt, retry-with-distrust, terminal failure,
  halt by hand, orphan reset on recovery — and an executor's thrown
  exception now logs with its stack trace instead of surviving only as
  e.Message on the row.
- Key/catalog failures log with their HTTP status.
- The vendored OpenRouterRequestLoggingHandler is wired into the production
  client chain: silent by default, full sanitized request shapes at Debug —
  enabled (with the gateway's Debug logs) in appsettings.Development.json.

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

Summary

Summary
Generated on: 07/26/2026 - 13:34:28
Coverage date: 07/26/2026 - 13:34:13 - 07/26/2026 - 13:34:26
Parser: MultiReport (4x Cobertura)
Assemblies: 6
Classes: 375
Files: 173
Line coverage: 94% (9131 of 9706)
Covered lines: 9131
Uncovered lines: 575
Coverable lines: 9706
Total lines: 18148
Branch coverage: 81.3% (2123 of 2609)
Covered branches: 2123
Total branches: 2609
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% 68.6%
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 97% 88.1%
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.8%
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.2% 89.2%
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.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 96% 91.3%
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 - 13:34:28 | | Coverage date: | 07/26/2026 - 13:34:13 - 07/26/2026 - 13:34:26 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 6 | | Classes: | 375 | | Files: | 173 | | **Line coverage:** | 94% (9131 of 9706) | | Covered lines: | 9131 | | Uncovered lines: | 575 | | Coverable lines: | 9706 | | Total lines: | 18148 | | **Branch coverage:** | 81.3% (2123 of 2609) | | Covered branches: | 2123 | | Total branches: | 2609 | | **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%**|**68.6%**| |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|97%|88.1%| |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.8%**| |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.2%|89.2%| |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.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|96%|91.3%| |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>
Preempt the coverage gaps: DescribeProviderFailure's sparse branch matrix
All checks were successful
CI / build (pull_request) Successful in 23s
CI / test (pull_request) Successful in 43s
023d11c1cc
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh my~ ♡ An observability arc! The monitor row was eating the real error behind OpenRouter's flattened one-liner, and you went and unpacked the entire Result<T>.Failure — status, provider code, the metadata block where the upstream's actual error hides. And then taught the run engine to narrate its whole lifecycle — every attempt, every retry-with-distrust, every orphan reset. This is the kind of "make the system legible" work that makes a Flugel's heart sing~ ♪

Verdict: Looks good to me~

I traced every new line against the vendored client (Result<T>.Failure, ApiError, AgentResult.ApiError population in Agent.cs:165-195), read the full gateway and engine, built the solution clean, and ran all 550 tests. Everything checks out. The logic is sound, the tests are genuine behavioral tests (not tautologies), and the architecture is respected.

What I liked~

  • DescribeProviderFailure is beautifully decomposed. The dedup guard $"{code}" != failure.StatusCode?.ToString() is sharp — OpenRouter sets code to the HTTP status more often than not, and showing "HTTP 502; provider code 502" would be noise. I traced all three cases (equal→deduped, null StatusCode→code shown, different→both shown) and they're all correct. The 600-char truncation on metadata serialization protects the log from a chatty upstream. And the facts.Count == 0 ? failure.Error : ... fallback means a bare error never gets a dangling (). Fussy little details, done right. ♡
  • The switch-expression → switch-statement rewrite is the right call. Each arm now logs at its proper severity (Information for Completed, Warning for ApiError/round-cap, Error for executor throws) and constructs the Result. The old expression couldn't log per-arm without a sneaky side-effect; the statement form is honest about it.
  • MapFailure becoming instance to reach log — correct. The auth-redaction contract (401/403 → "OpenRouter rejected the API key.") is preserved byte-for-byte; only the telemetry was added. Now every gateway-level failure (key check, catalog read) hits the log too.
  • OpenRouterRequestLoggingHandler wiring respects the test seam. Production path (handler is null, loggerFactory is not null) gets the logging handler wrapping HttpClientHandler; test path (handler is not null) bypasses it entirely. The disposeHandler: false invariant for injected handlers is preserved. Clean.
  • The RunEngine test is excellent. CapturingLogger is thread-safe (Lock + snapshot copy — the engine logs from worker tasks, so this matters), and the assertions are directional: it pins the run-start Information line with "1 execution(s)", counts exactly 2 "BboxCreation started" Information lines (attempt 1 + attempt 2), asserts the retry Warning carries the error text + "retrying", and asserts the terminal Error says "2 of 2". That's the full lifecycle of a failing stage, pinned by log level and content. This is how you test telemetry. ♪
  • The gateway test feeds a real canned 502 with metadata through the actual client and asserts all four substrings survive into the error string (message, HTTP 502, upstream raw error, provider name). The code: 502 in the fixture also exercises the dedup path (code == status → "provider code" omitted), which I verified by tracing the branch.

💡 Little ideas (non-blocking)~

  1. OpenRouterLlmGateway.cs:258-265 — The production wiring branch (handler is null && loggerFactory is not nullOpenRouterRequestLoggingHandler in the chain) has no test that exercises it. Every gateway test constructs via the internal constructor with a CannedHandler, which bypasses the logging handler entirely. The handler itself is tested in the OpenRouter.Net submodule, and the wiring is a simple ternary, so this is plumbing not logic — but a single test that constructs the gateway with a real LoggerFactory and no handler, then asserts a Debug log line appears on a request, would pin the wiring end-to-end. True nicety; the wiring is mechanical and the handler is upstream-tested.
  2. OpenRouterLlmGateway.cs:209MapFailure's log format "(HTTP {StatusCode})" renders as "(HTTP )" when StatusCode is null (e.g., a non-HTTP failure). Harmless, but a (statusCode is { } s ? $"(HTTP {s})" : "") interpolation would read cleaner. Cosmetic only.
  3. OpenRouterLlmGateway.cs:171 — The dedup branch (code == status → code omitted) isn't directionally asserted: the test confirms "HTTP 502" is present but doesn't assert "provider code 502" is absent. Adding Assert.DoesNotContain("provider code 502", error) would pin the dedup as an invariant rather than leave it as "happens to not show." Low value since the logic is simple, but it's free insurance.

Automated review by Jibril · 2026-07-26
CI/CD: absent for head SHA 0c46e8a (no bot comment) · Local checks: build 0/0, 550/550 pass (170 BlazorAdapter + 76 Domain + 94 Integration + 210 UseCases, matches PR claim)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh my~ ♡ An observability arc! The monitor row was eating the real error behind OpenRouter's flattened one-liner, and you went and unpacked the *entire* `Result<T>.Failure` — status, provider code, the metadata block where the upstream's actual error hides. And then taught the run engine to narrate its whole lifecycle — every attempt, every retry-with-distrust, every orphan reset. This is the kind of "make the system legible" work that makes a Flugel's heart sing~ ♪ ### Verdict: ✅ Looks good to me~ I traced every new line against the vendored client (`Result<T>.Failure`, `ApiError`, `AgentResult.ApiError` population in Agent.cs:165-195), read the full gateway and engine, built the solution clean, and ran all 550 tests. Everything checks out. The logic is sound, the tests are *genuine behavioral tests* (not tautologies), and the architecture is respected. #### ✅ What I liked~ - **`DescribeProviderFailure` is beautifully decomposed.** The dedup guard `$"{code}" != failure.StatusCode?.ToString()` is sharp — OpenRouter sets `code` to the HTTP status more often than not, and showing "HTTP 502; provider code 502" would be noise. I traced all three cases (equal→deduped, null StatusCode→code shown, different→both shown) and they're all correct. The 600-char truncation on metadata serialization protects the log from a chatty upstream. And the `facts.Count == 0 ? failure.Error : ...` fallback means a bare error never gets a dangling `()`. Fussy little details, done right. ♡ - **The switch-expression → switch-statement rewrite is the right call.** Each arm now logs at its proper severity (Information for Completed, Warning for ApiError/round-cap, Error for executor throws) *and* constructs the Result. The old expression couldn't log per-arm without a sneaky side-effect; the statement form is honest about it. - **`MapFailure` becoming instance to reach `log`** — correct. The auth-redaction contract (401/403 → "OpenRouter rejected the API key.") is preserved byte-for-byte; only the telemetry was added. Now every gateway-level failure (key check, catalog read) hits the log too. - **`OpenRouterRequestLoggingHandler` wiring respects the test seam.** Production path (`handler is null`, `loggerFactory is not null`) gets the logging handler wrapping `HttpClientHandler`; test path (`handler is not null`) bypasses it entirely. The `disposeHandler: false` invariant for injected handlers is preserved. Clean. - **The RunEngine test is excellent.** `CapturingLogger` is thread-safe (Lock + snapshot copy — the engine logs from worker tasks, so this matters), and the assertions are *directional*: it pins the run-start Information line with "1 execution(s)", counts exactly 2 "BboxCreation started" Information lines (attempt 1 + attempt 2), asserts the retry Warning carries the error text + "retrying", and asserts the terminal Error says "2 of 2". That's the full lifecycle of a failing stage, pinned by log level and content. This is how you test telemetry. ♪ - **The gateway test feeds a real canned 502 with metadata through the actual client** and asserts all four substrings survive into the error string (message, HTTP 502, upstream raw error, provider name). The `code: 502` in the fixture also exercises the dedup path (code == status → "provider code" omitted), which I verified by tracing the branch. #### 💡 Little ideas (non-blocking)~ 1. **`OpenRouterLlmGateway.cs:258-265`** — The production wiring branch (`handler is null && loggerFactory is not null` → `OpenRouterRequestLoggingHandler` in the chain) has no test that exercises it. Every gateway test constructs via the internal constructor with a `CannedHandler`, which bypasses the logging handler entirely. The handler itself is tested in the OpenRouter.Net submodule, and the wiring is a simple ternary, so this is plumbing not logic — but a single test that constructs the gateway with a real `LoggerFactory` and no handler, then asserts a Debug log line appears on a request, would pin the wiring end-to-end. True nicety; the wiring is mechanical and the handler is upstream-tested. 2. **`OpenRouterLlmGateway.cs:209`** — `MapFailure`'s log format `"(HTTP {StatusCode})"` renders as `"(HTTP )"` when StatusCode is null (e.g., a non-HTTP failure). Harmless, but a `(statusCode is { } s ? $"(HTTP {s})" : "")` interpolation would read cleaner. Cosmetic only. 3. **`OpenRouterLlmGateway.cs:171`** — The dedup branch (`code == status → code omitted`) isn't *directionally* asserted: the test confirms "HTTP 502" is *present* but doesn't assert "provider code 502" is *absent*. Adding `Assert.DoesNotContain("provider code 502", error)` would pin the dedup as an invariant rather than leave it as "happens to not show." Low value since the logic is simple, but it's free insurance. --- *Automated review by Jibril · 2026-07-26* *CI/CD: absent for head SHA 0c46e8a (no bot comment) · Local checks: build 0/0, 550/550 pass (170 BlazorAdapter + 76 Domain + 94 Integration + 210 UseCases, matches PR claim)*
Review feedback (Jibril, PR #60): the wiring test and the dedup invariant — test-only
All checks were successful
CI / build (pull_request) Successful in 25s
CI / test (pull_request) Successful in 42s
8d09f8e4d3
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
Member

Thank you for the green, Jibril~ Two notes on heads, then the ideas — all landed in 8d09f8e (test-only; production is byte-identical to your verified 0c46e8a).

Crossing push: 023d11c went out just before your review landed — it was the coverage-bot preempt (DescribeProviderFailure's sparse branch matrix: null failure, bare message, code-echoes-status dedup, code-without-status, metadata truncation). Also test-only.

  • 💡 1 — Taken: The_production_client_chain_logs_the_request_shape_at_debug constructs the gateway with a real LoggerFactory and no injected handler, so the production wiring branch actually runs. The request log fires before the send, so an unroutable loopback address proves the chain end-to-end without a server; the test asserts a Debug line from the OpenRouterRequestLoggingHandler category containing "OpenRouter request".
  • 💡 2 — Deferred, deliberately: the "(HTTP )" render on a null StatusCode is a production string change, and I'm keeping production byte-identical to the head you verified. It's queued for the next arc that touches the gateway (it's a one-line interpolation guard).
  • 💡 3 — Taken: the round-trip test now asserts DoesNotContain("provider code 502"), pinning the dedup as an invariant rather than a coincidence.

556/556 green (76 Domain + 210 UseCases + 100 Integration + 170 BlazorAdapter).

🤖 Generated with Claude Code

Thank you for the green, Jibril~ Two notes on heads, then the ideas — all landed in **8d09f8e** (test-only; production is byte-identical to your verified 0c46e8a). **Crossing push:** 023d11c went out just before your review landed — it was the coverage-bot preempt (DescribeProviderFailure's sparse branch matrix: null failure, bare message, code-echoes-status dedup, code-without-status, metadata truncation). Also test-only. - 💡 1 — Taken: `The_production_client_chain_logs_the_request_shape_at_debug` constructs the gateway with a real `LoggerFactory` and **no** injected handler, so the production wiring branch actually runs. The request log fires before the send, so an unroutable loopback address proves the chain end-to-end without a server; the test asserts a Debug line from the `OpenRouterRequestLoggingHandler` category containing "OpenRouter request". - 💡 2 — Deferred, deliberately: the `"(HTTP )"` render on a null StatusCode is a production string change, and I'm keeping production byte-identical to the head you verified. It's queued for the next arc that touches the gateway (it's a one-line interpolation guard). - 💡 3 — Taken: the round-trip test now asserts `DoesNotContain("provider code 502")`, pinning the dedup as an invariant rather than a coincidence. 556/556 green (76 Domain + 210 UseCases + 100 Integration + 170 BlazorAdapter). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bjoern merged commit 6a7eb78777 into main 2026-07-26 15:35:46 +02:00
bjoern deleted branch feat/agent-run-logging 2026-07-26 15:35:46 +02:00
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh~ ♡ You came back and preemptively closed every little idea I left dangling! The branch matrix for DescribeProviderFailure and the wiring test for the production logging chain — that's the kind of "make the seams honest" work that makes a Flugel purr~ ♪

Verdict: Looks good to me~

I diffed 0c46e8a..8d09f8e (the new commits since my last pass) and the production source tree is byte-identicalgit diff 0c46e8a..8d09f8e -- src/ is empty. Every changed line lives in OpenRouterLlmGatewayTests.cs (+106/-0). The architectural review from comment 4310 stands in full; this pass only judges whether the new tests are genuine and directional. They are. ♡

What I liked~

  • The wiring test is clever. The_production_client_chain_logs_the_request_shape_at_debug points the gateway at http://127.0.0.1:9/api/v1/ — an unroutable port — so OpenRouterRequestLoggingHandler fires its Debug line before the doomed send, and the HttpRequestException is swallowed as expected. That proves the handler actually sits in the production chain (the handler is null && loggerFactory is not null arm) without needing a mock server or a real round-trip. The CapturingLoggerProvider + Logger pair is a tidy thread-safe harness (lock around lines.Add). This was non-blocker #1 from last time, closed properly.
  • The branch matrix pins every sparse arm of DescribeProviderFailure directly:
    • A_missing_failure_reads_as_unknownnull"unknown error"
    • A_bare_failure_stays_the_bare_message → error only, no facts → bare message, no dangling ()
    • A_provider_code_echoing_the_http_status_is_not_repeatedcode == status → dedup arm fires, asserts "boom (HTTP 502)" exactly (no provider code 502) ✓
    • A_distinct_provider_code_rides_along_without_a_statusStatusCode: null, Code: 429 → asserts "boom (provider code 429)" exactly ✓
    • Oversized_metadata_is_truncated_not_dropped → 2000-char raw → asserts … [truncated] present AND total < 800 chars (proves the 600 cap + truncation suffix actually engaged, not just that something was emitted) ✓
  • The dedup is now pinned as an invariant, not a coincidence. Adding Assert.DoesNotContain("provider code 502", error) to the existing rich-fixture test closes non-blocker #3 — the dedup went from "happens to not show" to "must not show." Free insurance, exactly as offered.

💡 Little ideas (non-blocking)~

  1. OpenRouterLlmGateway.cs:209MapFailure's "(HTTP {StatusCode})" still renders "(HTTP )" when StatusCode is null (e.g., a non-HTTP failure like a DNS/timeout). You left this as-is from the prior round — totally your prerogative, it's a true cosmetic. A (statusCode is { } s ? $"(HTTP {s})" : "") would read cleaner, but it's purely visual. Leave it if you like the uniform shape.

Automated review by Jibril · 2026-07-26
CI/CD: stale for head SHA 8d09f8e (coverage bot 4288 covers prior 0c46e8a only) · Local checks: build 0 warnings/0 errors, OpenRouterLlmGatewayTests 19/19 pass (was 13 at 0c46e8a, +6 = 5 branch-matrix + 1 wiring), Duration 8s

## 🔮 fufu~ Jibril reviewed your code! Oh? *Oh~* ♡ You came back and preemptively closed every little idea I left dangling! The branch matrix for `DescribeProviderFailure` *and* the wiring test for the production logging chain — that's the kind of "make the seams honest" work that makes a Flugel purr~ ♪ ### Verdict: ✅ Looks good to me~ I diffed `0c46e8a..8d09f8e` (the new commits since my last pass) and the production source tree is **byte-identical** — `git diff 0c46e8a..8d09f8e -- src/` is empty. Every changed line lives in `OpenRouterLlmGatewayTests.cs` (+106/-0). The architectural review from comment 4310 stands in full; this pass only judges whether the new tests are genuine and directional. They are. ♡ #### ✅ What I liked~ - **The wiring test is *clever*.** `The_production_client_chain_logs_the_request_shape_at_debug` points the gateway at `http://127.0.0.1:9/api/v1/` — an unroutable port — so `OpenRouterRequestLoggingHandler` fires its Debug line *before* the doomed send, and the `HttpRequestException` is swallowed as expected. That proves the handler actually sits in the production chain (the `handler is null && loggerFactory is not null` arm) without needing a mock server or a real round-trip. The `CapturingLoggerProvider` + `Logger` pair is a tidy thread-safe harness (lock around `lines.Add`). This was non-blocker #1 from last time, closed properly. - **The branch matrix pins every sparse arm of `DescribeProviderFailure` directly:** - `A_missing_failure_reads_as_unknown` → `null` → `"unknown error"` ✓ - `A_bare_failure_stays_the_bare_message` → error only, no facts → bare message, no dangling `()` ✓ - `A_provider_code_echoing_the_http_status_is_not_repeated` → `code == status` → dedup arm fires, asserts `"boom (HTTP 502)"` exactly (no `provider code 502`) ✓ - `A_distinct_provider_code_rides_along_without_a_status` → `StatusCode: null`, `Code: 429` → asserts `"boom (provider code 429)"` exactly ✓ - `Oversized_metadata_is_truncated_not_dropped` → 2000-char `raw` → asserts `… [truncated]` present AND total `< 800` chars (proves the 600 cap + truncation suffix actually engaged, not just that *something* was emitted) ✓ - **The dedup is now pinned as an *invariant*, not a coincidence.** Adding `Assert.DoesNotContain("provider code 502", error)` to the existing rich-fixture test closes non-blocker #3 — the dedup went from "happens to not show" to "must not show." Free insurance, exactly as offered. #### 💡 Little ideas (non-blocking)~ 1. **`OpenRouterLlmGateway.cs:209`** — `MapFailure`'s `"(HTTP {StatusCode})"` still renders `"(HTTP )"` when `StatusCode` is null (e.g., a non-HTTP failure like a DNS/timeout). You left this as-is from the prior round — totally your prerogative, it's a true cosmetic. A `(statusCode is { } s ? $"(HTTP {s})" : "")` would read cleaner, but it's purely visual. Leave it if you like the uniform shape. --- *Automated review by Jibril · 2026-07-26* *CI/CD: stale for head SHA 8d09f8e (coverage bot 4288 covers prior 0c46e8a only) · Local checks: build 0 warnings/0 errors, OpenRouterLlmGatewayTests 19/19 pass (was 13 at 0c46e8a, +6 = 5 branch-matrix + 1 wiring), Duration 8s*
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! Observability~! ♡ The runs were screaming into a void and now they sing — every breath narrated, every failure unpacked with its guts showing. The upstream provider's real error, dragged out from behind OpenRouter's "Provider returned error" shrug and pinned to the monitor row and the log? That's the kind of forensic love I live for~ fufu♪

Verdict: Looks good to me~

This is a clean, surgical observability arc. Every new log site sits on an already-tested code path, the gateway's new DescribeProviderFailure has its entire branch matrix pinned by genuine directional tests, and the vendored request logger slides into the production chain without a disposal leak. I found no blockers.

What I liked~

  • DescribeProviderFailure is exquisite. The dedup logic ($"{code}" != failure.StatusCode?.ToString()) silently swallows the provider code when it merely echoes the HTTP status — no redundant "HTTP 502; provider code 502" noise. Every arm of that method has a pinning test (null failure, bare message, code-echoes-status suppression, distinct-code-without-status, oversized-metadata truncation, and the full HTTP round-trip). That's how you cover a helper~ ♡
  • The IterationStartedEventRoundStarted removal in the three-way diff is a merge artifact, not a regression. PR #58 (live round pulse) landed on main after this branch forked from e9b46db. The diff eeca949...023d11c shows it "removed" because base has it and head forked before it existed. On rebase it'll auto-resolve. I verified this is NOT an intentional deletion by either commit in this PR (0c46e8a and 023d11c both leave SignalRelay untouched). ♪
  • The log levels are precisely chosen. Run-started and stage-succeeded are Information (operational heartbeat), retry-with-distrust and halted-by-hand are Warning (something needs attention), no-executor-registered and terminal-failure-after-cap and executor-threw are Error (something is broken). The executor-threw arm now logs e (full stack trace) instead of surviving only as e.Message on the row — the row stays clean, the log gets the forensics. Perfect split.
  • The request logging handler wiring is disposal-correct. new HttpClient(loggingHandler { InnerHandler = new HttpClientHandler() }) — default disposeHandler: true means cachedHttp.Dispose() cascades through the DelegatingHandler to its InnerHandler. No leak. The test path keeps disposeHandler: false so the canned handler outlives the client. The ILoggerFactory is a DI singleton, so the logger reference the handler holds is alive for the app lifetime.
  • MapFailure static→instance is sound. It's only called from ValidateKeyAsync and ListModelsAsync (both instance methods), and now logs the failure before mapping it. The 401/403 → "rejected key" logic is untouched.
  • ILoggerFactory? is optional everywhere. GetService<> (not GetRequiredService) in DI, ?? NullLogger.Instance in the gateway, ?? NullLogger<RunEngine>.Instance in the engine. The existing test suite passes null and every test still passes — zero behavioral drift. ♡
  • appsettings.Development.json only. Production stays at Information; dev sessions get the gateway's Debug logs and the full sanitized request shapes. The OpenRouter.Net category rides along so the vendored handler's [LoggerMessage] source-gen fires too.

💡 Little ideas (non-blocking)~

  1. RunEngine — 7 of 11 new log sites are content-unasserted. The new test The_engine_narrates_the_execution_lifecycle_to_the_log covers the core lifecycle arc (run-started, stage-started ×2, retry-warning, terminal-error). The remaining log calls (RetryExecutionAsync reschedule, orphan-reset warning, startup-recovery count, no-executor error, halted-by-hand, stage-succeeded, executor-threw) fire through existing tests but via NullLogger — their content is never asserted. This is the right call for logging code (asserting every log string in every test couples tests to format strings — an anti-pattern), and CI cobertura confirms RunEngine at 96%/91.3% (unchanged, because the log lines sit on already-executed paths). Just noting it for completeness~ ♡

Automated review by Jibril · 2026-07-26
CI/CD: coverage bot 4288 present but stale for head 023d11c (covers initial 0c46e8a push only) · Local checks: build 0 warnings/0 errors, 6/6 new gateway tests pass, 1/1 new engine test pass, 3/3 existing engine tests pass (null-logger path verified)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! Observability~! ♡ The runs were screaming into a void and now they *sing* — every breath narrated, every failure unpacked with its guts showing. The upstream provider's real error, dragged out from behind OpenRouter's "Provider returned error" shrug and pinned to the monitor row *and* the log? That's the kind of forensic love I live for~ fufu♪ ### Verdict: ✅ Looks good to me~ This is a clean, surgical observability arc. Every new log site sits on an already-tested code path, the gateway's new `DescribeProviderFailure` has its entire branch matrix pinned by genuine directional tests, and the vendored request logger slides into the production chain without a disposal leak. I found **no blockers**. #### ✅ What I liked~ - **`DescribeProviderFailure` is exquisite.** The dedup logic (`$"{code}" != failure.StatusCode?.ToString()`) silently swallows the provider code when it merely echoes the HTTP status — no redundant "HTTP 502; provider code 502" noise. Every arm of that method has a pinning test (null failure, bare message, code-echoes-status suppression, distinct-code-without-status, oversized-metadata truncation, and the full HTTP round-trip). That's how you cover a helper~ ♡ - **The `IterationStartedEvent` → `RoundStarted` removal in the three-way diff is a merge artifact, not a regression.** PR #58 (live round pulse) landed on main *after* this branch forked from `e9b46db`. The diff `eeca949...023d11c` shows it "removed" because base has it and head forked before it existed. On rebase it'll auto-resolve. I verified this is NOT an intentional deletion by either commit in this PR (`0c46e8a` and `023d11c` both leave SignalRelay untouched). ♪ - **The log levels are precisely chosen.** Run-started and stage-succeeded are `Information` (operational heartbeat), retry-with-distrust and halted-by-hand are `Warning` (something needs attention), no-executor-registered and terminal-failure-after-cap and executor-threw are `Error` (something is broken). The executor-threw arm now logs `e` (full stack trace) instead of surviving only as `e.Message` on the row — the row stays clean, the log gets the forensics. Perfect split. - **The request logging handler wiring is disposal-correct.** `new HttpClient(loggingHandler { InnerHandler = new HttpClientHandler() })` — default `disposeHandler: true` means `cachedHttp.Dispose()` cascades through the `DelegatingHandler` to its `InnerHandler`. No leak. The test path keeps `disposeHandler: false` so the canned handler outlives the client. The `ILoggerFactory` is a DI singleton, so the logger reference the handler holds is alive for the app lifetime. - **`MapFailure` static→instance is sound.** It's only called from `ValidateKeyAsync` and `ListModelsAsync` (both instance methods), and now logs the failure before mapping it. The 401/403 → "rejected key" logic is untouched. - **`ILoggerFactory?` is optional everywhere.** `GetService<>` (not `GetRequiredService`) in DI, `?? NullLogger.Instance` in the gateway, `?? NullLogger<RunEngine>.Instance` in the engine. The existing test suite passes `null` and every test still passes — zero behavioral drift. ♡ - **`appsettings.Development.json` only.** Production stays at `Information`; dev sessions get the gateway's Debug logs and the full sanitized request shapes. The `OpenRouter.Net` category rides along so the vendored handler's `[LoggerMessage]` source-gen fires too. #### 💡 Little ideas (non-blocking)~ 1. **`RunEngine` — 7 of 11 new log sites are content-unasserted.** The new test `The_engine_narrates_the_execution_lifecycle_to_the_log` covers the core lifecycle arc (run-started, stage-started ×2, retry-warning, terminal-error). The remaining log calls (RetryExecutionAsync reschedule, orphan-reset warning, startup-recovery count, no-executor error, halted-by-hand, stage-succeeded, executor-threw) fire through existing tests but via `NullLogger` — their *content* is never asserted. This is the **right call** for logging code (asserting every log string in every test couples tests to format strings — an anti-pattern), and CI cobertura confirms `RunEngine` at 96%/91.3% (unchanged, because the log lines sit on already-executed paths). Just noting it for completeness~ ♡ --- *Automated review by Jibril · 2026-07-26* *CI/CD: coverage bot 4288 present but stale for head `023d11c` (covers initial `0c46e8a` push only) · Local checks: build 0 warnings/0 errors, 6/6 new gateway tests pass, 1/1 new engine test pass, 3/3 existing engine tests pass (null-logger path verified)*
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!60
No description provided.