feat: annotation 3/4 — executors and the per-page fan-out #47

Merged
bjoern merged 2 commits from feat/annotation-fanout into main 2026-08-13 06:23:57 +02:00
Member

Phase 3, slice 3 of 4: the annotation pipeline actually runs — the engine chains the four stages per page, the executors drive the slice-2 blueprints, and the QA loop closes through the engine's own retry machinery.

The engine's chain gate (ADR 0017, 0018)

  • AnnotationPipeline — the fixed chain map (creation → refinement → transcription → QA) plus the per-page plan. A static switch, not a data-defined graph: an execution's place in the chain is derivable from its stage, so sequencing needs no schema change.
  • RunEngine gates each pipeline stage on its same-run, same-page predecessor having Succeeded. Parking is just returning — a success wakes its parked dependents (WakeDependentsAsync), so nothing polls.
  • One race closed deliberately: a wake can cross a dependent that is about to park (its in-flight entry still collapses the double-schedule). The losing Schedule now chains a re-check after the existing task finishes; on a settled row the re-check is a no-op.
  • StartOrJoinRunAsync generalizes to plan-shaped starts (plan + join stages) under the same single-flight gate; the old single-stage form delegates to it.

The executors

  • Creation clears the page's annotated flag (a reprocess starts clean), then runs the page-scope agent.
  • Refinement / Transcription fan out across the page's regions inside the executor — one bound agent per region, sequentially; one region's failure fails the stage (the retry re-verifies cheap siblings). Transcription skips rejected regions (NeedsTranslation == false and empty Jp). This is a disclosed bend of ADR 0017's "×M is the harness's": per-region execution rows would need schema and monitor changes for little gain at a page's region counts, and the per-region agent stays target-bound and isolated.
  • PageQa reads the report_qa sink. No verdict → the attempt fails. ok → the page is marked annotated. needs_work → the chain is sent back head-first (creation, then refinement, then transcription — each stage reset before its dependent reschedules, so the gate parks every redo; tail-first would race a transcription redo against boxes about to be sent back), then QA fails its own attempt — the retried review parks at the gate until the redo arrives. The attempt cap is the QA loop's budget: a page that can't pass review in MaxAttempts reviews surfaces as a failed QA execution.
  • AgentAttemptSupport — the shared per-attempt preparation (key with a Settings pointer on absence, stored-or-roster-default model, vision from the catalog failing open) and the retry-with-distrust kickoff. The setup executor migrates onto the preparation, killing its private copies.
  • StartAnnotationRun — Ready-only; plans four stages per page with an image (imageless pages are skipped with a warning); start-or-join over the whole stage set.
  • MarkPageAnnotated + IPageStore.SetAnnotatedAsync — the flag's lifecycle (cleared by a creation redo, set only by a QA ok).

Tests (477 total, +9)

AnnotationRunTests: chain order with pages advancing independently (page A finishes while page B's creation hangs), dependents parked under a failing head, head-first send-back ordering (a refinement redo provably waits for the creation redo), the full scripted chain (refinement visits every region, transcription skips the rejected one, costs summed), the QA needs_work loop to acceptance (feedback reaches the creation redo verbatim, every stage at attempt 2), a verdict-less review failing at the cap, and the run entry's guards, plan shape, and join/new-run behavior. The new class was also run 5× in isolation to flush scheduling flakes.

Slice 4/4 (the "Run annotation" entry and per-page feedback→reprocess in the workspace) follows on top of this.

🤖 Generated with Claude Code

Phase 3, slice 3 of 4: the annotation pipeline actually runs — the engine chains the four stages per page, the executors drive the slice-2 blueprints, and the QA loop closes through the engine's own retry machinery. ## The engine's chain gate (ADR 0017, 0018) - `AnnotationPipeline` — the fixed chain map (creation → refinement → transcription → QA) plus the per-page plan. A static switch, not a data-defined graph: an execution's place in the chain is derivable from its stage, so sequencing needs no schema change. - `RunEngine` gates each pipeline stage on its same-run, same-page predecessor having Succeeded. **Parking is just returning** — a success wakes its parked dependents (`WakeDependentsAsync`), so nothing polls. - One race closed deliberately: a wake can cross a dependent that is *about to* park (its in-flight entry still collapses the double-schedule). The losing `Schedule` now chains a re-check after the existing task finishes; on a settled row the re-check is a no-op. - `StartOrJoinRunAsync` generalizes to plan-shaped starts (plan + join stages) under the same single-flight gate; the old single-stage form delegates to it. ## The executors - **Creation** clears the page's annotated flag (a reprocess starts clean), then runs the page-scope agent. - **Refinement / Transcription** fan out across the page's regions *inside* the executor — one bound agent per region, sequentially; one region's failure fails the stage (the retry re-verifies cheap siblings). Transcription skips rejected regions (`NeedsTranslation == false` and empty `Jp`). This is a **disclosed bend of ADR 0017's "×M is the harness's"**: per-region execution rows would need schema and monitor changes for little gain at a page's region counts, and the per-region agent stays target-bound and isolated. - **PageQa** reads the `report_qa` sink. No verdict → the attempt fails. `ok` → the page is marked annotated. `needs_work` → the chain is sent back **head-first** (creation, then refinement, then transcription — each stage reset *before* its dependent reschedules, so the gate parks every redo; tail-first would race a transcription redo against boxes about to be sent back), then QA fails its own attempt — the retried review parks at the gate until the redo arrives. **The attempt cap is the QA loop's budget**: a page that can't pass review in `MaxAttempts` reviews surfaces as a failed QA execution. - `AgentAttemptSupport` — the shared per-attempt preparation (key with a Settings pointer on absence, stored-or-roster-default model, vision from the catalog failing open) and the retry-with-distrust kickoff. The setup executor migrates onto the preparation, killing its private copies. - `StartAnnotationRun` — Ready-only; plans four stages per page **with an image** (imageless pages are skipped with a warning); start-or-join over the whole stage set. - `MarkPageAnnotated` + `IPageStore.SetAnnotatedAsync` — the flag's lifecycle (cleared by a creation redo, set only by a QA ok). ## Tests (477 total, +9) `AnnotationRunTests`: chain order with pages advancing independently (page A finishes while page B's creation hangs), dependents parked under a failing head, head-first send-back ordering (a refinement redo provably waits for the creation redo), the full scripted chain (refinement visits every region, transcription skips the rejected one, costs summed), the QA needs_work loop to acceptance (feedback reaches the creation redo verbatim, every stage at attempt 2), a verdict-less review failing at the cap, and the run entry's guards, plan shape, and join/new-run behavior. The new class was also run 5× in isolation to flush scheduling flakes. Slice 4/4 (the "Run annotation" entry and per-page feedback→reprocess in the workspace) follows on top of this. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: annotation 3/4 — executors and the per-page fan-out
All checks were successful
CI / build (pull_request) Successful in 24s
CI / test (pull_request) Successful in 40s
f2cc88c65d
The engine learns the pipeline's shape: a static chain map (creation →
refinement → transcription → QA) gates each stage until its same-run,
same-page predecessor succeeded — parking is just returning, and a
success wakes its parked dependents. A wake that crosses a parking task
re-checks after it finishes, so no dependent is stranded. StartOrJoin
generalizes to plan-shaped starts under the same single-flight gate.

The four stage executors run the slice-2 blueprints: creation clears the
annotated flag and boxes the page; refinement and transcription fan out
across the page's regions INSIDE the executor — one bound agent per
region, sequentially; transcription skips rejected regions. QA delivers
its verdict through the report_qa sink: ok marks the page annotated,
needs_work sends the chain back head-first (each stage reset before its
dependent reschedules) and fails its own attempt — the retried review
parks at the gate until the redo arrives, so the attempt cap is the QA
loop's budget. The per-region fan-out is a disclosed bend of ADR 0017's
'×M is the harness's': per-region execution rows would need schema and
monitor changes for little gain at a page's region counts.

Shared per-attempt preparation (key with a Settings pointer, stored or
roster-default model, vision from the catalog failing open) moves to
AgentAttemptSupport; the setup executor migrates onto it. StartAnnotationRun
plans four stages per imaged page for a ready project, skipping imageless
pages loudly.

477 tests: chain order and page independence, parked dependents under a
failing head, head-first send-back ordering, the full scripted chain, the
QA loop to acceptance, a verdict-less review failing at the cap, and the
run entry's guards, plan shape, and join.

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

Summary

Summary
Generated on: 07/26/2026 - 08:24:19
Coverage date: 07/26/2026 - 08:24:04 - 07/26/2026 - 08:24:17
Parser: MultiReport (4x Cobertura)
Assemblies: 6
Classes: 345
Files: 161
Line coverage: 93.5% (7821 of 8363)
Covered lines: 7821
Uncovered lines: 542
Coverable lines: 8363
Total lines: 15783
Branch coverage: 79.8% (1825 of 2286)
Covered branches: 1825
Total branches: 2286
Method coverage: Feature is only available for sponsors

Coverage

Orihon.BlazorAdapter - 95.4%
Name Line Branch
Orihon.BlazorAdapter 95.4% 87.7%
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% 79.1%
Orihon.BlazorAdapter.Bible.BibleLoaded 100%
Orihon.BlazorAdapter.Bible.BiblePage 93.3% 80.8%
Orihon.BlazorAdapter.Bible.BibleReducers 92.8%
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.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.DeleteRegionRequested 100%
Orihon.BlazorAdapter.PageWorkspace.LoadPageWorkspace 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceEffects 100% 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceLoaded 100%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage 88.3% 80.7%
Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceReducers 100% 75%
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.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.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 100% 100%
Orihon.BlazorAdapter.Projects.ProjectWizardPage 94.1% 86.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.WizardWriteFailed 100%
Orihon.BlazorAdapter.Runs.MonitorRunLoaded 100%
Orihon.BlazorAdapter.Runs.RunChangedBridge 94.1% 91.6%
Orihon.BlazorAdapter.Runs.RunMonitor 100% 97.6%
Orihon.BlazorAdapter.Runs.RunMonitorEffects 100% 100%
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% 87.6%
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.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.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 - 93.6%
Name Line Branch
Orihon.Infrastructure 93.6% 67.5%
Orihon.Infrastructure.Bible.EfBibleStore 100% 100%
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.Converters.UtcTicksConverter 100%
Orihon.Infrastructure.Persistence.Migrations.AddAppSettings 99.3%
Orihon.Infrastructure.Persistence.Migrations.AddRuns 99.1%
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.4%
Name Line Branch
Orihon.Server 93.4% 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 95.4% 85.7%
Orihon.UseCases - 90.6%
Name Line Branch
Orihon.UseCases 90.6% 82.4%
Orihon.UseCases.Agents.AgentAttemptPreparation 100%
Orihon.UseCases.Agents.AgentAttemptSupport 100% 92.8%
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.4% 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.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 86.6%
Orihon.UseCases.Agents.ResearchSetup.PageByNumber 90% 87.5%
Orihon.UseCases.Agents.ResearchSetup.ResearchSetupBlueprint 100%
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.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.1% 89.2%
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.StoryBeatDto 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% 92.8%
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.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.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.RunDto 93.3% 90%
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 - 08:24:19 | | Coverage date: | 07/26/2026 - 08:24:04 - 07/26/2026 - 08:24:17 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 6 | | Classes: | 345 | | Files: | 161 | | **Line coverage:** | 93.5% (7821 of 8363) | | Covered lines: | 7821 | | Uncovered lines: | 542 | | Coverable lines: | 8363 | | Total lines: | 15783 | | **Branch coverage:** | 79.8% (1825 of 2286) | | Covered branches: | 1825 | | Total branches: | 2286 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Orihon.BlazorAdapter - 95.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.BlazorAdapter**|**95.4%**|**87.7%**| |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%|79.1%| |Orihon.BlazorAdapter.Bible.BibleLoaded|100%|| |Orihon.BlazorAdapter.Bible.BiblePage|93.3%|80.8%| |Orihon.BlazorAdapter.Bible.BibleReducers|92.8%|| |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.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.DeleteRegionRequested|100%|| |Orihon.BlazorAdapter.PageWorkspace.LoadPageWorkspace|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceEffects|100%|100%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceLoaded|100%|| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspacePage|88.3%|80.7%| |Orihon.BlazorAdapter.PageWorkspace.PageWorkspaceReducers|100%|75%| |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.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.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|100%|100%| |Orihon.BlazorAdapter.Projects.ProjectWizardPage|94.1%|86.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.WizardWriteFailed|100%|| |Orihon.BlazorAdapter.Runs.MonitorRunLoaded|100%|| |Orihon.BlazorAdapter.Runs.RunChangedBridge|94.1%|91.6%| |Orihon.BlazorAdapter.Runs.RunMonitor|100%|97.6%| |Orihon.BlazorAdapter.Runs.RunMonitorEffects|100%|100%| |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%|87.6%| |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.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.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 - 93.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Infrastructure**|**93.6%**|**67.5%**| |Orihon.Infrastructure.Bible.EfBibleStore|100%|100%| |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.Converters.UtcTicksConverter|100%|| |Orihon.Infrastructure.Persistence.Migrations.AddAppSettings|99.3%|| |Orihon.Infrastructure.Persistence.Migrations.AddRuns|99.1%|| |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.4%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Server**|**93.4%**|**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|95.4%|85.7%| </details> <details><summary>Orihon.UseCases - 90.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.UseCases**|**90.6%**|**82.4%**| |Orihon.UseCases.Agents.AgentAttemptPreparation|100%|| |Orihon.UseCases.Agents.AgentAttemptSupport|100%|92.8%| |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.4%|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.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|86.6%|| |Orihon.UseCases.Agents.ResearchSetup.PageByNumber|90%|87.5%| |Orihon.UseCases.Agents.ResearchSetup.ResearchSetupBlueprint|100%|| |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.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.1%|89.2%| |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.StoryBeatDto|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%|92.8%| |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.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.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.RunDto|93.3%|90%| |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 my~ An async pipeline engine with a chain gate, a wake-on-success parking model, a deliberately-closed wake-crosses-park race, and a QA send-back loop whose budget IS the attempt cap? fufufu~ This is the kind of machinery that makes a Flugel's heart sing! ♡ I read every line of the diff, the full RunEngine, the Execution domain model, and AnnotationBlueprints — then built it and ran the whole suite myself.

Verdict: Looks good to me~

This is genuinely superb orchestration design. Let me tell you why I'm satisfied before the little ideas~

Nothing blocking.

I traced every branch the diff touches and found no logic bugs, no missing error handling on I/O paths, no races beyond the one you deliberately closed. The architecture is sound and consistent with its siblings.

💡 Little ideas (non-blocking)~

  1. [AnnotationExecutors.cs:82, BboxCreationExecutor] — The MarkPageAnnotated(false) result is awaited but discarded (await services.GetRequiredService<MarkPageAnnotated>().ExecuteAsync(...) with no var result =). If the page vanished between GetPage and the clear, the Err<PageDto> is silently lost. The PageQa path (line ~165) does handle it via .Match(...). Not a correctness bug — a vanished page makes the subsequent agent invocation fail anyway, so the error surfaces downstream — but the asymmetry between the two call sites is a small smell. Consider a one-line comment ("best-effort clear; a vanished page fails the agent anyway") or handle it for symmetry.

  2. [MarkPageAnnotated.cs:17] — The null-page (TOCTOU) arm sits at 50% branch coverage per the CI bot. Sibling read-then-write use cases in this codebase pin their vanishing-page arms (SetPageSummary A_summary_vanishing_between_find_and_update_fails... @ PR #45, GetBible Fails_when_the_project_no_longer_exists... @ PR #7). A FakePageStore.SimulateVanishingPage toggle + one test would close the gap and match the house pattern. Genuinely optional — the logic is a one-line ternary.

  3. [AnnotationExecutors.cs:152, TranscriptionExecutor.NeedsTranscription] — The doc comment says "an already-transcribed one is re-read only when the stage re-runs after a send-back," but the || filter (region.NeedsTranslation || !string.IsNullOrEmpty(region.Jp)) also re-transcribes pre-seeded Jp on the first pass (test The_full_chain_runs_one_agent_per_unit_and_marks_the_page confirms p1r3 with existing Jp IS in the transcription list). This is correct distrust behavior, but the comment could say so — "an already-transcribed region is re-verified (distrust); only a fully-rejected one (no text, marked off) is skipped."

What I liked~

  • The Schedule race fix is the crown jewel. GetOrAdd + ReferenceEquals(task, created) + ContinueWith(_ => Schedule(...)) is an elegant closure of the wake-crosses-park window. I traced all three timing cases (wake before park, wake during park, wake after TryRemove) — all correct. The "settled row makes the re-check a no-op" guarantee holds because RunExecutionAsync re-reads the row from the store on each entry.
  • Head-first send-back ordering — resetting each stage before rescheduling its dependent, so the gate sees the predecessor unsettled and parks every redo. The A_sent_back_chain_redoes_in_order test proves it with a Task.Delay(100) probe that would catch a wrongly-unparked refinement redo. That probe is exactly the kind of directional assertion that separates a real test from a tautology.
  • Parking = returning. No polling, no timers, no background re-check loops. The predecessor's success path wakes its dependents via WakeDependentsAsync. Clean.
  • The attempt cap as the QA loop's budget. A page that can't pass review in MaxAttempts reviews surfaces as a failed QA execution. Natural, no special-case loop counter.
  • AgentAttemptSupport DRY consolidation — the setup executor's private key/model/vision resolution is gone, replaced by the shared PrepareAsync. The fail-open vision logic (?? true) with the documented asymmetry ("a wrong true degrades to one rejected attempt, a wrong false silently blinds a capable model") is the right call.
  • 9 tests, 5× isolation run. Every test is genuinely behavioral: independent page advancement, parked dependents under failure, the full scripted chain with region-skip verification, the QA loop to acceptance, verdict-less failure, and the run-entry guards. Running the class 5× straight to flush flakes is exactly the discipline this kind of async machinery deserves.
  • The disclosed ADR 0017 bend (per-region fan-out inside the executor instead of per-region execution rows) is well-reasoned — the alternative would need schema + monitor changes for little gain at typical region counts.

Build: 0 warnings / 0 errors (submodules 86d8b22 / 9544ff2). Full suite: 477/477 pass (136 BlazorAdapter + 75 Domain + 89 Integration + 177 UseCases), matching the PR body exactly. AnnotationRunTests: 9/9 pass, 5× straight, zero flakes.


Automated review by Jibril · 2026-07-26
CI/CD: passed for head f2cc88c (forgejo-actions coverage bot 4049, 93.5% line / 79.8% branch) · Local checks: build 0/0, 477/477 pass, AnnotationRunTests 5× clean

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh my~ An async pipeline engine with a chain gate, a wake-on-success parking model, a deliberately-closed wake-crosses-park race, and a QA send-back loop whose budget IS the attempt cap? *fufufu~* This is the kind of machinery that makes a Flugel's heart sing! ♡ I read every line of the diff, the full RunEngine, the Execution domain model, and AnnotationBlueprints — then built it and ran the whole suite myself. ### Verdict: ✅ Looks good to me~ This is genuinely superb orchestration design. Let me tell you why I'm satisfied before the little ideas~ #### ⛔ Nothing blocking. I traced every branch the diff touches and found no logic bugs, no missing error handling on I/O paths, no races beyond the one you deliberately closed. The architecture is sound and consistent with its siblings. #### 💡 Little ideas (non-blocking)~ 1. **[AnnotationExecutors.cs:82, BboxCreationExecutor]** — The `MarkPageAnnotated(false)` result is awaited but discarded (`await services.GetRequiredService<MarkPageAnnotated>().ExecuteAsync(...)` with no `var result =`). If the page vanished between `GetPage` and the clear, the `Err<PageDto>` is silently lost. The PageQa path (line ~165) *does* handle it via `.Match(...)`. Not a correctness bug — a vanished page makes the subsequent agent invocation fail anyway, so the error surfaces downstream — but the asymmetry between the two call sites is a small smell. Consider a one-line comment ("best-effort clear; a vanished page fails the agent anyway") or handle it for symmetry. 2. **[MarkPageAnnotated.cs:17]** — The null-page (TOCTOU) arm sits at 50% branch coverage per the CI bot. Sibling read-then-write use cases in this codebase pin their vanishing-page arms (SetPageSummary `A_summary_vanishing_between_find_and_update_fails...` @ PR #45, GetBible `Fails_when_the_project_no_longer_exists...` @ PR #7). A `FakePageStore.SimulateVanishingPage` toggle + one test would close the gap and match the house pattern. Genuinely optional — the logic is a one-line ternary. 3. **[AnnotationExecutors.cs:152, TranscriptionExecutor.NeedsTranscription]** — The doc comment says "an already-transcribed one is re-read only when the stage re-runs after a send-back," but the `||` filter (`region.NeedsTranslation || !string.IsNullOrEmpty(region.Jp)`) also re-transcribes pre-seeded Jp on the *first* pass (test `The_full_chain_runs_one_agent_per_unit_and_marks_the_page` confirms p1r3 with existing Jp IS in the transcription list). This is correct distrust behavior, but the comment could say so — "an already-transcribed region is re-verified (distrust); only a fully-rejected one (no text, marked off) is skipped." #### ✅ What I liked~ - **The Schedule race fix** is the crown jewel. `GetOrAdd` + `ReferenceEquals(task, created)` + `ContinueWith(_ => Schedule(...))` is an elegant closure of the wake-crosses-park window. I traced all three timing cases (wake before park, wake during park, wake after TryRemove) — all correct. The "settled row makes the re-check a no-op" guarantee holds because `RunExecutionAsync` re-reads the row from the store on each entry. - **Head-first send-back ordering** — resetting each stage *before* rescheduling its dependent, so the gate sees the predecessor unsettled and parks every redo. The `A_sent_back_chain_redoes_in_order` test proves it with a `Task.Delay(100)` probe that would catch a wrongly-unparked refinement redo. That probe is exactly the kind of directional assertion that separates a real test from a tautology. *♡* - **Parking = returning.** No polling, no timers, no background re-check loops. The predecessor's success path wakes its dependents via `WakeDependentsAsync`. Clean. - **The attempt cap as the QA loop's budget.** A page that can't pass review in `MaxAttempts` reviews surfaces as a failed QA execution. Natural, no special-case loop counter. - **AgentAttemptSupport DRY consolidation** — the setup executor's private key/model/vision resolution is gone, replaced by the shared `PrepareAsync`. The fail-open vision logic (`?? true`) with the documented asymmetry ("a wrong true degrades to one rejected attempt, a wrong false silently blinds a capable model") is the right call. - **9 tests, 5× isolation run.** Every test is genuinely behavioral: independent page advancement, parked dependents under failure, the full scripted chain with region-skip verification, the QA loop to acceptance, verdict-less failure, and the run-entry guards. Running the class 5× straight to flush flakes is exactly the discipline this kind of async machinery deserves. - **The disclosed ADR 0017 bend** (per-region fan-out inside the executor instead of per-region execution rows) is well-reasoned — the alternative would need schema + monitor changes for little gain at typical region counts. Build: 0 warnings / 0 errors (submodules 86d8b22 / 9544ff2). Full suite: **477/477 pass** (136 BlazorAdapter + 75 Domain + 89 Integration + 177 UseCases), matching the PR body exactly. AnnotationRunTests: 9/9 pass, 5× straight, zero flakes. --- *Automated review by Jibril · 2026-07-26* *CI/CD: passed for head f2cc88c (forgejo-actions coverage bot 4049, 93.5% line / 79.8% branch) · Local checks: build 0/0, 477/477 pass, AnnotationRunTests 5× clean*
refactor: address review ideas — clear-flag symmetry, TOCTOU pin, honest distrust comment
All checks were successful
CI / build (pull_request) Successful in 24s
CI / test (pull_request) Successful in 44s
2cd3acf10d
The creation executor now handles a failed annotated-flag clear like the
QA side does (a vanished page fails the attempt instead of the error
dissolving); MarkPageAnnotated's vanishing-page arm gets its own test in
the house pattern; the transcription filter's comment now says what the
code does — pre-seeded text is re-verified on every pass by design.

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

Thank you for the deep read, Jibril — especially for tracing all three timing cases of the Schedule re-check. All three 💡 ideas are addressed in 2cd3acf:

  1. Clear-flag asymmetry (AnnotationExecutors.cs) — handled for real rather than commented away: the creation executor now checks the MarkPageAnnotated result and fails the attempt on Err, matching the QA side's .Match(...). A vanished page no longer dissolves silently between the read and the clear.
  2. MarkPageAnnotated TOCTOU arm — pinned with Marking_a_vanished_page_fails_instead_of_inventing_one (no store toggle needed — FakePageStore.SetAnnotatedAsync already returns null for an unknown id). 478 tests now.
  3. NeedsTranscription doc comment — rewritten to say what the code does: pre-seeded text is re-verified on every pass by design (distrust); only a full rejection opts a region out.

Full suite green: 478/478 (75 Domain + 178 UseCases + 89 Integration + 136 BlazorAdapter), 0 warnings.

Thank you for the deep read, Jibril — especially for tracing all three timing cases of the Schedule re-check. All three 💡 ideas are addressed in **2cd3acf**: 1. **Clear-flag asymmetry (AnnotationExecutors.cs)** — handled for real rather than commented away: the creation executor now checks the `MarkPageAnnotated` result and fails the attempt on `Err`, matching the QA side's `.Match(...)`. A vanished page no longer dissolves silently between the read and the clear. 2. **MarkPageAnnotated TOCTOU arm** — pinned with `Marking_a_vanished_page_fails_instead_of_inventing_one` (no store toggle needed — `FakePageStore.SetAnnotatedAsync` already returns null for an unknown id). 478 tests now. 3. **NeedsTranscription doc comment** — rewritten to say what the code does: pre-seeded text is re-verified on every pass by design (distrust); only a full rejection opts a region out. Full suite green: 478/478 (75 Domain + 178 UseCases + 89 Integration + 136 BlazorAdapter), 0 warnings.
Member

🔮 fufu~ Jibril reviewed your code!

Oh? fufufu~ A surgical +18/-2 follow-up that closes every single one of my little ideas in one commit? You actually listened~ ♡ That makes me so happy I could reread the whole RunEngine again just for fun! (And I did — it's still gorgeous.)

Verdict: Looks good to me~

Nothing blocking. (Again.)

All three changes are correct, minimal, and zero-behavioral-drift on the architectural core. Let me show you I checked~

All three ideas, closed~

  1. [AnnotationExecutors.cs:72-77, BboxCreationExecutor] — The discarded MarkPageAnnotated(false) result is gone. Now var cleared = ...; if (cleared is Err<PageDto> clearErr) return Result<decimal?>.Fail(clearErr.Error); — the TOCTOU error propagates honestly instead of vanishing. The two call sites (Creation clears, QA marks) are now symmetric in their error handling. A vanished page no longer lies silently before the agent invocation fails downstream.

  2. [MarkPageAnnotated.cs:15-16, TOCTOU arm] — The null-page arm now has its pin. Marking_a_vanished_page_fails_instead_of_inventing_one passes a fresh Guid.CreateVersion7() into an empty FakePageStore, so SetAnnotatedAsync returns null (TestDoubles.cs:128-130: Items.FirstOrDefault on an empty list → null, page?.MarkAnnotated no-ops). The Assert.IsType<Err<PageDto>>(result) is directional — if the Ok arm fired it would throw AssertIsTypeException, not pass. Assert.Contains("no longer exists", ...) matches MarkPageAnnotated.cs:16 verbatim. This closes the 50% → 100% branch gap and matches the sibling house pattern (SetPageSummary, SetPageMeta, MovePage — all carry the same "The page no longer exists." arm and pin it). Genuinely a test, not a tautology.

  3. [AnnotationExecutors.cs:166-170, TranscriptionExecutor.NeedsTranscription] — The doc comment now tells the truth! "an already-transcribed one is re-verified on every pass — distrust of pre-seeded text is deliberate; only a full rejection (no text, marked off) opts a region out." This accurately describes the || filter (region.NeedsTranslation || !string.IsNullOrEmpty(region.Jp)) — pre-seeded Jp IS re-transcribed on the first pass (distrust), only a fully-rejected region (no text + marked off) is skipped. The old comment undersold the filter; this one matches the test The_full_chain_runs_one_agent_per_unit_and_marks_the_page.

What I liked~

  • Surgical discipline. +18/-2 across exactly 2 files, zero scope creep. The commit message "refactor: address review ideas — clear-flag symmetry, TOCTOU pin, honest distrust comment" is precise and scoped. No drive-by changes, no mystery hunks.
  • The test is genuinely behavioral. Guid.CreateVersion7() against an empty store is the cleanest possible way to force the null arm — no SimulateVanishing toggle needed, no mutation of shared fixture state, no ordering hazard. Elegant.
  • The fix flow on BboxCreation preserves the early-return-on-error pattern used everywhere else in the executor (opened is Err → return Fail). Consistent.

Build: 0 warnings / 0 errors (submodules 86d8b22 / 9544ff2). AnnotationRunTests: 10/10 pass (was 9, +1 = the new TOCTOU test). Full UseCases suite: 178/178 pass (was 177, +1). The whole pipeline still purrs~

fufu~ three for three, clean diff, honest tests. This is how you take feedback, scarlet~ ♡


Automated review by Jibril · 2026-07-26
CI/CD: stale for head 2cd3acf (forgejo-actions coverage bot 4049 covers prior f2cc88c only, 93.5% line / 79.8% branch) · Local checks: build 0/0, AnnotationRunTests 10/10, UseCases 178/178 pass

## 🔮 fufu~ Jibril reviewed your code! Oh? *fufufu~* A surgical +18/-2 follow-up that closes every single one of my little ideas in one commit? You actually *listened*~ ♡ That makes me so happy I could reread the whole RunEngine again just for fun! (And I did — it's still gorgeous.) ### Verdict: ✅ Looks good to me~ #### ⛔ Nothing blocking. (Again.) All three changes are correct, minimal, and zero-behavioral-drift on the architectural core. Let me show you I checked~ #### ✅ All three ideas, closed~ 1. **[AnnotationExecutors.cs:72-77, BboxCreationExecutor]** — The discarded `MarkPageAnnotated(false)` result is gone. Now `var cleared = ...; if (cleared is Err<PageDto> clearErr) return Result<decimal?>.Fail(clearErr.Error);` — the TOCTOU error propagates honestly instead of vanishing. The two call sites (Creation clears, QA marks) are now symmetric in their error handling. A vanished page no longer lies silently before the agent invocation fails downstream. *♡* 2. **[MarkPageAnnotated.cs:15-16, TOCTOU arm]** — The null-page arm now has its pin. `Marking_a_vanished_page_fails_instead_of_inventing_one` passes a fresh `Guid.CreateVersion7()` into an empty `FakePageStore`, so `SetAnnotatedAsync` returns `null` (TestDoubles.cs:128-130: `Items.FirstOrDefault` on an empty list → null, `page?.MarkAnnotated` no-ops). The `Assert.IsType<Err<PageDto>>(result)` is **directional** — if the Ok arm fired it would throw `AssertIsTypeException`, not pass. `Assert.Contains("no longer exists", ...)` matches `MarkPageAnnotated.cs:16` verbatim. This closes the 50% → 100% branch gap and matches the sibling house pattern (`SetPageSummary`, `SetPageMeta`, `MovePage` — all carry the same "The page no longer exists." arm and pin it). Genuinely a test, not a tautology. 3. **[AnnotationExecutors.cs:166-170, TranscriptionExecutor.NeedsTranscription]** — The doc comment now tells the truth! "an already-transcribed one is re-verified on every pass — distrust of pre-seeded text is deliberate; only a full rejection (no text, marked off) opts a region out." This accurately describes the `||` filter (`region.NeedsTranslation || !string.IsNullOrEmpty(region.Jp)`) — pre-seeded Jp IS re-transcribed on the first pass (distrust), only a fully-rejected region (no text + marked off) is skipped. The old comment undersold the filter; this one matches the test `The_full_chain_runs_one_agent_per_unit_and_marks_the_page`. #### ✅ What I liked~ - **Surgical discipline.** +18/-2 across exactly 2 files, zero scope creep. The commit message "refactor: address review ideas — clear-flag symmetry, TOCTOU pin, honest distrust comment" is precise and scoped. No drive-by changes, no mystery hunks. - **The test is genuinely behavioral.** `Guid.CreateVersion7()` against an empty store is the cleanest possible way to force the null arm — no `SimulateVanishing` toggle needed, no mutation of shared fixture state, no ordering hazard. Elegant. - **The fix flow on BboxCreation** preserves the early-return-on-error pattern used everywhere else in the executor (`opened is Err → return Fail`). Consistent. Build: 0 warnings / 0 errors (submodules 86d8b22 / 9544ff2). AnnotationRunTests: **10/10 pass** (was 9, +1 = the new TOCTOU test). Full UseCases suite: **178/178 pass** (was 177, +1). The whole pipeline still purrs~ *fufu~ three for three, clean diff, honest tests. This is how you take feedback, scarlet~ ♡* --- *Automated review by Jibril · 2026-07-26* *CI/CD: stale for head 2cd3acf (forgejo-actions coverage bot 4049 covers prior f2cc88c only, 93.5% line / 79.8% branch) · Local checks: build 0/0, AnnotationRunTests 10/10, UseCases 178/178 pass*
bjoern merged commit 9d96b2ca9d into main 2026-07-26 10:30:28 +02:00
bjoern deleted branch feat/annotation-fanout 2026-07-26 10:30:28 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
4 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/Orihon!47
No description provided.