feat: phase 4 slice 2/3 — the translation agent #57

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

Phase 4 slice 2 of 3: the single sequential translator (ADR 0013, 0017, 0019). Its consistency is architectural — one agent, one growing glossary, no parallel workers to drift. Slice 3/3 (the run entries + the translation-view human gate) follows on top.

The agent

  • TranslationBlueprint — the grant is exactly the matrix row: view_page · list_regions · get_page_summary · find_glossary plus its two writes, set_translation and update_glossary. No add_glossary (new terms are the bible agent's to add), no region boxes, no bible prose. The reads reuse PageByNumber addressing and the slice-1 region listing.
  • set_translation writes the region whole-profile (the sharpest edge — nothing resets), records the speaker, and settles NeedsTranslation. It refuses a region with no transcribed Japanese — inventing English for an untranscribed or rejected region would be fabrication.
  • update_glossary settles EN on an existing entry by exact-JP match, threading the note through unchanged; an unknown term is refused with a pointer at find_glossary and the bible agent.
  • get_page_summary is the precondition's voice: a summaryless page answers with the refusal, not an empty string.

The precondition and the executor (ADR 0013, 0018)

  • The page-summary precondition is enforced in the executor, before the agent spends a token: any page with untranslated transcribed text but no summary fails the attempt with the page numbers and a pointer at bible building — a run error on the monitor, never a silent skip. Pages without translatable text (covers, blanks) need no summary.
  • The round budget follows #52's roster reconciliation: Translation's roster row carries RoundBudget: 50, RoundBudgetPerPage: 10 (context reads + glossary checks + one write per region outrun the bible's summarizing pass); the executor reads prep.RoundBudget like every sibling.
  • StartTranslationRun — ready-only start-or-join under the single-flight gate.

Also in this PR: a test race fixed at the root

Stress runs (20×) caught a 1-in-20 flake in the missing-key/missing-summary tests (one of them merged with #52): between attempts a row is briefly Failed and the next Start clears its Error, so a poll that fires on any settled status can read null. Both tests now wait for attempt 3 Failed with a non-null Error — the one state nothing overwrites. (A separate, rarer full-suite-only timeout in ReprocessPageTests under 4-concurrent-test-host load is environment contention — stable 12× isolated — and left out of scope.)

Tests (+10; 534 total)

The grant name-list; the summary read + refusal voice; the whole-profile write with Jp/notes proven intact; the three set_translation refusals; EN settling with the note threaded and the unknown-term refusal; the precondition failing loudly at zero cost; the no-text exemption; a scripted agent working its grant end-to-end through the engine (context → glossary law → writes, cost + roster model + scaled MaxIterations == RoundBudgetFor(1) + PageId == null asserted); and the entry guard. New classes ran 20× under stress, flake-free.

🤖 Generated with Claude Code

Phase 4 slice 2 of 3: the single sequential translator (ADR 0013, 0017, 0019). Its consistency is architectural — one agent, one growing glossary, no parallel workers to drift. Slice 3/3 (the run entries + the translation-view human gate) follows on top. ## The agent - **`TranslationBlueprint`** — the grant is exactly the matrix row: `view_page · list_regions · get_page_summary · find_glossary` plus its two writes, `set_translation` and `update_glossary`. No `add_glossary` (new terms are the bible agent's to add), no region boxes, no bible prose. The reads reuse `PageByNumber` addressing and the slice-1 region listing. - **`set_translation`** writes the region **whole-profile** (the sharpest edge — nothing resets), records the speaker, and settles `NeedsTranslation`. It **refuses a region with no transcribed Japanese** — inventing English for an untranscribed or rejected region would be fabrication. - **`update_glossary`** settles EN on an **existing** entry by exact-JP match, threading the note through unchanged; an unknown term is refused with a pointer at `find_glossary` and the bible agent. - **`get_page_summary`** is the precondition's voice: a summaryless page answers with the refusal, not an empty string. ## The precondition and the executor (ADR 0013, 0018) - The **page-summary precondition is enforced in the executor, before the agent spends a token**: any page with untranslated transcribed text but no summary fails the attempt with the page numbers and a pointer at bible building — a run error on the monitor, never a silent skip. Pages without translatable text (covers, blanks) need no summary. - The round budget follows #52's roster reconciliation: Translation's roster row carries `RoundBudget: 50, RoundBudgetPerPage: 10` (context reads + glossary checks + one write per region outrun the bible's summarizing pass); the executor reads `prep.RoundBudget` like every sibling. - **`StartTranslationRun`** — ready-only start-or-join under the single-flight gate. ## Also in this PR: a test race fixed at the root Stress runs (20×) caught a 1-in-20 flake in the missing-key/missing-summary tests (one of them merged with #52): between attempts a row is briefly Failed and the next `Start` clears its `Error`, so a poll that fires on any settled status can read null. Both tests now wait for **attempt 3 Failed with a non-null Error** — the one state nothing overwrites. (A separate, rarer full-suite-only timeout in `ReprocessPageTests` under 4-concurrent-test-host load is environment contention — stable 12× isolated — and left out of scope.) ## Tests (+10; 534 total) The grant name-list; the summary read + refusal voice; the whole-profile write with Jp/notes proven intact; the three `set_translation` refusals; EN settling with the note threaded and the unknown-term refusal; the precondition failing loudly at zero cost; the no-text exemption; a scripted agent working its grant end-to-end through the engine (context → glossary law → writes, cost + roster model + scaled `MaxIterations == RoundBudgetFor(1)` + `PageId == null` asserted); and the entry guard. New classes ran 20× under stress, flake-free. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The single sequential translator (ADR 0013, 0017, 0019): consistency
comes from being alone with a growing glossary — no parallel workers to
drift. The grant is exactly the matrix row: view_page, list_regions,
get_page_summary, find_glossary, plus its two writes — set_translation
(whole-profile, so nothing resets; refuses a region with no transcribed
Japanese) and update_glossary (settles EN on an EXISTING entry, note
threaded through; new terms stay the bible agent's to add).

The page-summary precondition (ADR 0013) is enforced in the executor
before the agent spends a token: a page with untranslated text but no
summary fails the attempt with the page numbers and a pointer at bible
building — a run error on the monitor, never a silent skip. Pages
without translatable text (covers, blanks) need no summary.

StartTranslationRun is the ready-only start-or-join entry.

520 tests: the grant name-list, the summary read + refusal voice, the
whole-profile translation write (Jp/notes intact), the three
set_translation refusals, EN settling with the note threaded, the
precondition failing loudly and costing nothing, the no-text exemption,
a scripted agent working its grant end-to-end, and the entry guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The bible executor's reconciliation (PR #52 round 3): budgets live on
the roster, single-source. Translation's row carries RoundBudget 50 +
RoundBudgetPerPage 10 (reading context, glossary checks, and one write
per region outrun the bible's summarizing pass); the executor reads
prep.RoundBudget like every sibling. Pinned end-to-end via
AgentRoster.RoundBudgetFor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
test: wait on the capped attempt — the retry loop races settled-status polls
All checks were successful
CI / build (pull_request) Successful in 24s
CI / test (pull_request) Successful in 41s
7cef7a6c01
Between attempts a row is briefly Failed and the next Start clears its
Error; a poll that fires on any settled status (or even on the error
content of a non-final attempt) can read null right after. The
missing-key and missing-summary tests now wait for attempt 3 Failed
with a non-null Error — the one state nothing overwrites. Caught by
stress runs (1-in-20).

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

Summary

Summary
Generated on: 07/26/2026 - 12:51:45
Coverage date: 07/26/2026 - 12:51:31 - 07/26/2026 - 12:51:43
Parser: MultiReport (4x Cobertura)
Assemblies: 6
Classes: 380
Files: 176
Line coverage: 94% (9115 of 9696)
Covered lines: 9115
Uncovered lines: 581
Coverable lines: 9696
Total lines: 18119
Branch coverage: 80.7% (2053 of 2543)
Covered branches: 2053
Total branches: 2543
Method coverage: Feature is only available for sponsors

Coverage

Orihon.BlazorAdapter - 95.9%
Name Line Branch
Orihon.BlazorAdapter 95.9% 88.6%
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.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.MonitorPageRef 100%
Orihon.BlazorAdapter.Runs.MonitorRunLoaded 100%
Orihon.BlazorAdapter.Runs.RunChangedBridge 94.1% 91.6%
Orihon.BlazorAdapter.Runs.RunMonitor 97.5% 95.3%
Orihon.BlazorAdapter.Runs.RunMonitorEffects 100% 91.6%
Orihon.BlazorAdapter.Runs.RunMonitorReducers 100%
Orihon.BlazorAdapter.Runs.RunMonitorState 100%
Orihon.BlazorAdapter.Settings.AgentModelPicked 100%
Orihon.BlazorAdapter.Settings.AgentModelSaved 100%
Orihon.BlazorAdapter.Settings.AgentModelSaveFailed 100%
Orihon.BlazorAdapter.Settings.KeySaved 100%
Orihon.BlazorAdapter.Settings.KeySaveFailed 100%
Orihon.BlazorAdapter.Settings.ModelOptionsLoaded 100%
Orihon.BlazorAdapter.Settings.ModelOptionsUnavailable 100%
Orihon.BlazorAdapter.Settings.SaveKeyRequested 100%
Orihon.BlazorAdapter.Settings.SettingsEffects 100% 100%
Orihon.BlazorAdapter.Settings.SettingsLoaded 100%
Orihon.BlazorAdapter.Settings.SettingsPage 100% 90.4%
Orihon.BlazorAdapter.Settings.SettingsReducers 100%
Orihon.BlazorAdapter.Settings.SettingsState 100%
Orihon.BlazorAdapter.Uploads.UploadTransfer 96.5% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferProgress 100% 100%
Orihon.BlazorAdapter.Uploads.UploadTransferResult 100%
Orihon.BlazorAdapter.Workspace.CreateChapterRequested 100%
Orihon.BlazorAdapter.Workspace.DeleteChapterRequested 100%
Orihon.BlazorAdapter.Workspace.DeletePageRequested 100%
Orihon.BlazorAdapter.Workspace.DeleteSummaryRequested 100%
Orihon.BlazorAdapter.Workspace.LoadProjectWorkspace 100%
Orihon.BlazorAdapter.Workspace.MovePageRequested 100%
Orihon.BlazorAdapter.Workspace.ProjectMetadataCard 95.2% 92.8%
Orihon.BlazorAdapter.Workspace.ProjectMetadataSaved 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceEffects 100% 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceLoaded 100%
Orihon.BlazorAdapter.Workspace.ProjectWorkspacePage 95.5% 88.3%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceReducers 100% 62.5%
Orihon.BlazorAdapter.Workspace.ProjectWorkspaceState 100%
Orihon.BlazorAdapter.Workspace.RenameChapterRequested 100%
Orihon.BlazorAdapter.Workspace.ReorderChaptersRequested 100%
Orihon.BlazorAdapter.Workspace.ReorderPagesRequested 100%
Orihon.BlazorAdapter.Workspace.RunAnnotationRequested 100%
Orihon.BlazorAdapter.Workspace.SaveProjectMetadataRequested 100%
Orihon.BlazorAdapter.Workspace.SaveSummaryRequested 100%
Orihon.BlazorAdapter.Workspace.SetPageKindRequested 100%
Orihon.BlazorAdapter.Workspace.SummaryDeleted 100%
Orihon.BlazorAdapter.Workspace.SummarySaved 100%
Orihon.BlazorAdapter.Workspace.WorkspaceImportRequested 100%
Orihon.BlazorAdapter.Workspace.WorkspaceWriteFailed 100%
Orihon.Domain - 100%
Name Line Branch
Orihon.Domain 100% 100%
Orihon.Domain.Agents.AgentDescriptor 100%
Orihon.Domain.Agents.AgentRoster 100% 100%
Orihon.Domain.Bible.Character 100% 100%
Orihon.Domain.Bible.GlossaryEntry 100% 100%
Orihon.Domain.Bible.LoreEntry 100% 100%
Orihon.Domain.Bible.PageSummary 100%
Orihon.Domain.Bible.StoryBeat 100%
Orihon.Domain.Bible.StoryOverview 100%
Orihon.Domain.Projects.Project 100% 100%
Orihon.Domain.Projects.ProjectProfile 100%
Orihon.Domain.Runs.Execution 100% 100%
Orihon.Domain.Runs.Run 100%
Orihon.Domain.Settings.AppSetting 100%
Orihon.Domain.Text 100% 100%
Orihon.Domain.Translation.BoundingBox 100%
Orihon.Domain.Translation.Chapter 100%
Orihon.Domain.Translation.Page 100%
Orihon.Domain.Translation.Region 100% 100%
Orihon.Domain.Translation.RegionProfile 100%
Orihon.Infrastructure - 94.3%
Name Line Branch
Orihon.Infrastructure 94.3% 67.3%
Orihon.Infrastructure.Bible.EfBibleStore 94.4% 91.6%
Orihon.Infrastructure.DependencyInjection 100%
Orihon.Infrastructure.Gateways.AgentToolAdapter 100%
Orihon.Infrastructure.Gateways.AgentToolAdapter`1 100% 100%
Orihon.Infrastructure.Gateways.HttpWebPageFetcher 95.1% 83.3%
Orihon.Infrastructure.Gateways.OpenRouterLlmGateway 98% 83%
Orihon.Infrastructure.Gateways.SkiaPageImageRenderer 96.6% 86.1%
Orihon.Infrastructure.Persistence.Configurations.AppSettingConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ChapterConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.CharacterConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ExecutionConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.GlossaryEntryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.JsonColumnMapper 100%
Orihon.Infrastructure.Persistence.Configurations.LoreEntryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.PageConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.PageSummaryConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.ProjectConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.RegionConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.RunConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.StoryBeatConfiguration 100%
Orihon.Infrastructure.Persistence.Configurations.StoryOverviewConfiguration 100%
Orihon.Infrastructure.Persistence.Converters.UtcTicksConverter 100%
Orihon.Infrastructure.Persistence.Migrations.AddAppSettings 99.3%
Orihon.Infrastructure.Persistence.Migrations.AddRuns 99.1%
Orihon.Infrastructure.Persistence.Migrations.AddStoryOverview 99.5%
Orihon.Infrastructure.Persistence.Migrations.InitialTranslationDomain 97.3%
Orihon.Infrastructure.Persistence.Migrations.OrihonDbContextModelSnapshot 100%
Orihon.Infrastructure.Persistence.OrihonDbContext 100%
Orihon.Infrastructure.Persistence.OrihonDbContextFactory 100%
Orihon.Infrastructure.Projects.EfProjectStore 100% 100%
Orihon.Infrastructure.Projects.FileSystemPageImageStore 100% 100%
Orihon.Infrastructure.Runs.EfRunStore 97% 50%
Orihon.Infrastructure.Settings.EfAppSettingsStore 100% 100%
Orihon.Infrastructure.Translation.EfChapterStore 100% 100%
Orihon.Infrastructure.Translation.EfPageStore 86% 80%
Orihon.Infrastructure.Translation.EfRegionStore 100% 100%
Orihon.Infrastructure.Translation.Ordering 100% 100%
System.Text.RegularExpressions.Generated 70.6% 53.3%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlankLines_4
77.9% 76.6%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlockBreaks_1
59% 42.5%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__SpaceRuns_3
89.4% 75%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__Tags_2
83.7% 62.5%
Orihon.Kernel - 90.9%
Name Line Branch
Orihon.Kernel 90.9% 75%
Orihon.Kernel.Err`1 100%
Orihon.Kernel.Ok`1 100%
Orihon.Kernel.Result`1 88.8% 75%
Orihon.Server - 93.3%
Name Line Branch
Orihon.Server 93.3% 68.4%
Orihon.Server.Components.App 100%
Orihon.Server.Components.Layout.MainLayout 100%
Orihon.Server.Components.Pages.Gate 64.2% 66.6%
Orihon.Server.RunEngineBootstrap 100%
Orihon.Server.Security.AccessGate 91.8% 41.6%
Orihon.Server.Security.AccessSecret 100% 50%
Orihon.Server.VolumeStartupValidator 100% 100%
Program 94.8% 85.7%
Orihon.UseCases - 91.2%
Name Line Branch
Orihon.UseCases 91.2% 83%
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% 87.5%
Orihon.UseCases.Agents.Setup.SetupConversationRegistry 100%
Orihon.UseCases.Agents.ToolCalled 100%
Orihon.UseCases.Agents.ToolCompleted 100%
Orihon.UseCases.Agents.Translation.GetPageSummaryParams 100%
Orihon.UseCases.Agents.Translation.GetPageSummaryTool 80% 66.6%
Orihon.UseCases.Agents.Translation.SetTranslationParams 100%
Orihon.UseCases.Agents.Translation.SetTranslationTool 88.5% 78.5%
Orihon.UseCases.Agents.Translation.TranslationBlueprint 100%
Orihon.UseCases.Agents.Translation.TranslationExecutor 92.5% 71.4%
Orihon.UseCases.Agents.Translation.UpdateGlossaryEnParams 100%
Orihon.UseCases.Agents.Translation.UpdateGlossaryEnTool 82.6% 62.5%
Orihon.UseCases.Bible.AddCharacter 100% 100%
Orihon.UseCases.Bible.AddGlossaryEntry 100% 100%
Orihon.UseCases.Bible.AddLoreEntry 100% 100%
Orihon.UseCases.Bible.AddStoryBeat 100% 100%
Orihon.UseCases.Bible.BibleDto 100%
Orihon.UseCases.Bible.CharacterDto 100%
Orihon.UseCases.Bible.DeleteCharacter 100% 100%
Orihon.UseCases.Bible.DeleteGlossaryEntry 100% 100%
Orihon.UseCases.Bible.DeleteLoreEntry 100% 100%
Orihon.UseCases.Bible.DeletePageSummary 100% 100%
Orihon.UseCases.Bible.DeleteStoryBeat 100% 100%
Orihon.UseCases.Bible.GetBible 100% 100%
Orihon.UseCases.Bible.GlossaryEntryDto 100%
Orihon.UseCases.Bible.LoreEntryDto 100%
Orihon.UseCases.Bible.PageSummaryDto 100%
Orihon.UseCases.Bible.ReorderStoryBeats 100%
Orihon.UseCases.Bible.SetPageSummary 100% 100%
Orihon.UseCases.Bible.SetStoryOverview 100% 100%
Orihon.UseCases.Bible.StoryBeatDto 100%
Orihon.UseCases.Bible.StoryOverviewDto 100%
Orihon.UseCases.Bible.UpdateCharacter 100% 100%
Orihon.UseCases.Bible.UpdateGlossaryEntry 100% 100%
Orihon.UseCases.Bible.UpdateLoreEntry 100% 100%
Orihon.UseCases.Bible.UpdateStoryBeat 100% 100%
Orihon.UseCases.Chapters.ChapterDto 100%
Orihon.UseCases.Chapters.CreateChapter 100% 100%
Orihon.UseCases.Chapters.DeleteChapter 100% 100%
Orihon.UseCases.Chapters.RenameChapter 100% 100%
Orihon.UseCases.Chapters.ReorderChapters 100%
Orihon.UseCases.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.StartTranslationRun 90.9% 83.3%
Orihon.UseCases.Projects.StoredPageImage 100%
Orihon.UseCases.Projects.UpdateProjectMetadata 100% 100%
Orihon.UseCases.Regions.CreateRegion 100% 100%
Orihon.UseCases.Regions.DeleteRegion 100% 100%
Orihon.UseCases.Regions.RegionDto 97%
Orihon.UseCases.Regions.ReorderRegions 100%
Orihon.UseCases.Regions.UpdateRegion 100% 100%
Orihon.UseCases.Runs.AnnotationPipeline 100% 100%
Orihon.UseCases.Runs.ExecutionDto 92.3%
Orihon.UseCases.Runs.PlannedExecution 100%
Orihon.UseCases.Runs.ReprocessPage 100% 94.4%
Orihon.UseCases.Runs.RunDto 93.3% 100%
Orihon.UseCases.Runs.RunEngine 95.3% 90.6%
Orihon.UseCases.Runs.RunEngineOptions 100%
Orihon.UseCases.Runs.StageContext 87.5%
Orihon.UseCases.Runs.StageHaltedException 100%
Orihon.UseCases.Settings.AgentSettingDto 100% 100%
Orihon.UseCases.Settings.GetSettings 100% 100%
Orihon.UseCases.Settings.ListModelOptions 100% 100%
Orihon.UseCases.Settings.SaveAgentModel 100% 100%
Orihon.UseCases.Settings.SaveOpenRouterKey 100% 100%
Orihon.UseCases.Settings.SettingKeys 100% 100%
Orihon.UseCases.Settings.SettingsDto 100%
<!-- coverage-comment --> # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 07/26/2026 - 12:51:45 | | Coverage date: | 07/26/2026 - 12:51:31 - 07/26/2026 - 12:51:43 | | Parser: | MultiReport (4x Cobertura) | | Assemblies: | 6 | | Classes: | 380 | | Files: | 176 | | **Line coverage:** | 94% (9115 of 9696) | | Covered lines: | 9115 | | Uncovered lines: | 581 | | Coverable lines: | 9696 | | Total lines: | 18119 | | **Branch coverage:** | 80.7% (2053 of 2543) | | Covered branches: | 2053 | | Total branches: | 2543 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Orihon.BlazorAdapter - 95.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.BlazorAdapter**|**95.9%**|**88.6%**| |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.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.MonitorPageRef|100%|| |Orihon.BlazorAdapter.Runs.MonitorRunLoaded|100%|| |Orihon.BlazorAdapter.Runs.RunChangedBridge|94.1%|91.6%| |Orihon.BlazorAdapter.Runs.RunMonitor|97.5%|95.3%| |Orihon.BlazorAdapter.Runs.RunMonitorEffects|100%|91.6%| |Orihon.BlazorAdapter.Runs.RunMonitorReducers|100%|| |Orihon.BlazorAdapter.Runs.RunMonitorState|100%|| |Orihon.BlazorAdapter.Settings.AgentModelPicked|100%|| |Orihon.BlazorAdapter.Settings.AgentModelSaved|100%|| |Orihon.BlazorAdapter.Settings.AgentModelSaveFailed|100%|| |Orihon.BlazorAdapter.Settings.KeySaved|100%|| |Orihon.BlazorAdapter.Settings.KeySaveFailed|100%|| |Orihon.BlazorAdapter.Settings.ModelOptionsLoaded|100%|| |Orihon.BlazorAdapter.Settings.ModelOptionsUnavailable|100%|| |Orihon.BlazorAdapter.Settings.SaveKeyRequested|100%|| |Orihon.BlazorAdapter.Settings.SettingsEffects|100%|100%| |Orihon.BlazorAdapter.Settings.SettingsLoaded|100%|| |Orihon.BlazorAdapter.Settings.SettingsPage|100%|90.4%| |Orihon.BlazorAdapter.Settings.SettingsReducers|100%|| |Orihon.BlazorAdapter.Settings.SettingsState|100%|| |Orihon.BlazorAdapter.Uploads.UploadTransfer|96.5%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferProgress|100%|100%| |Orihon.BlazorAdapter.Uploads.UploadTransferResult|100%|| |Orihon.BlazorAdapter.Workspace.CreateChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeleteChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeletePageRequested|100%|| |Orihon.BlazorAdapter.Workspace.DeleteSummaryRequested|100%|| |Orihon.BlazorAdapter.Workspace.LoadProjectWorkspace|100%|| |Orihon.BlazorAdapter.Workspace.MovePageRequested|100%|| |Orihon.BlazorAdapter.Workspace.ProjectMetadataCard|95.2%|92.8%| |Orihon.BlazorAdapter.Workspace.ProjectMetadataSaved|100%|| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceEffects|100%|100%| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceLoaded|100%|| |Orihon.BlazorAdapter.Workspace.ProjectWorkspacePage|95.5%|88.3%| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceReducers|100%|62.5%| |Orihon.BlazorAdapter.Workspace.ProjectWorkspaceState|100%|| |Orihon.BlazorAdapter.Workspace.RenameChapterRequested|100%|| |Orihon.BlazorAdapter.Workspace.ReorderChaptersRequested|100%|| |Orihon.BlazorAdapter.Workspace.ReorderPagesRequested|100%|| |Orihon.BlazorAdapter.Workspace.RunAnnotationRequested|100%|| |Orihon.BlazorAdapter.Workspace.SaveProjectMetadataRequested|100%|| |Orihon.BlazorAdapter.Workspace.SaveSummaryRequested|100%|| |Orihon.BlazorAdapter.Workspace.SetPageKindRequested|100%|| |Orihon.BlazorAdapter.Workspace.SummaryDeleted|100%|| |Orihon.BlazorAdapter.Workspace.SummarySaved|100%|| |Orihon.BlazorAdapter.Workspace.WorkspaceImportRequested|100%|| |Orihon.BlazorAdapter.Workspace.WorkspaceWriteFailed|100%|| </details> <details><summary>Orihon.Domain - 100%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Domain**|**100%**|**100%**| |Orihon.Domain.Agents.AgentDescriptor|100%|| |Orihon.Domain.Agents.AgentRoster|100%|100%| |Orihon.Domain.Bible.Character|100%|100%| |Orihon.Domain.Bible.GlossaryEntry|100%|100%| |Orihon.Domain.Bible.LoreEntry|100%|100%| |Orihon.Domain.Bible.PageSummary|100%|| |Orihon.Domain.Bible.StoryBeat|100%|| |Orihon.Domain.Bible.StoryOverview|100%|| |Orihon.Domain.Projects.Project|100%|100%| |Orihon.Domain.Projects.ProjectProfile|100%|| |Orihon.Domain.Runs.Execution|100%|100%| |Orihon.Domain.Runs.Run|100%|| |Orihon.Domain.Settings.AppSetting|100%|| |Orihon.Domain.Text|100%|100%| |Orihon.Domain.Translation.BoundingBox|100%|| |Orihon.Domain.Translation.Chapter|100%|| |Orihon.Domain.Translation.Page|100%|| |Orihon.Domain.Translation.Region|100%|100%| |Orihon.Domain.Translation.RegionProfile|100%|| </details> <details><summary>Orihon.Infrastructure - 94.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Infrastructure**|**94.3%**|**67.3%**| |Orihon.Infrastructure.Bible.EfBibleStore|94.4%|91.6%| |Orihon.Infrastructure.DependencyInjection|100%|| |Orihon.Infrastructure.Gateways.AgentToolAdapter|100%|| |Orihon.Infrastructure.Gateways.AgentToolAdapter`1|100%|100%| |Orihon.Infrastructure.Gateways.HttpWebPageFetcher|95.1%|83.3%| |Orihon.Infrastructure.Gateways.OpenRouterLlmGateway|98%|83%| |Orihon.Infrastructure.Gateways.SkiaPageImageRenderer|96.6%|86.1%| |Orihon.Infrastructure.Persistence.Configurations.AppSettingConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ChapterConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.CharacterConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ExecutionConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.GlossaryEntryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.JsonColumnMapper|100%|| |Orihon.Infrastructure.Persistence.Configurations.LoreEntryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.PageConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.PageSummaryConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.ProjectConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.RegionConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.RunConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.StoryBeatConfiguration|100%|| |Orihon.Infrastructure.Persistence.Configurations.StoryOverviewConfiguration|100%|| |Orihon.Infrastructure.Persistence.Converters.UtcTicksConverter|100%|| |Orihon.Infrastructure.Persistence.Migrations.AddAppSettings|99.3%|| |Orihon.Infrastructure.Persistence.Migrations.AddRuns|99.1%|| |Orihon.Infrastructure.Persistence.Migrations.AddStoryOverview|99.5%|| |Orihon.Infrastructure.Persistence.Migrations.InitialTranslationDomain|97.3%|| |Orihon.Infrastructure.Persistence.Migrations.OrihonDbContextModelSnapshot|100%|| |Orihon.Infrastructure.Persistence.OrihonDbContext|100%|| |Orihon.Infrastructure.Persistence.OrihonDbContextFactory|100%|| |Orihon.Infrastructure.Projects.EfProjectStore|100%|100%| |Orihon.Infrastructure.Projects.FileSystemPageImageStore|100%|100%| |Orihon.Infrastructure.Runs.EfRunStore|97%|50%| |Orihon.Infrastructure.Settings.EfAppSettingsStore|100%|100%| |Orihon.Infrastructure.Translation.EfChapterStore|100%|100%| |Orihon.Infrastructure.Translation.EfPageStore|86%|80%| |Orihon.Infrastructure.Translation.EfRegionStore|100%|100%| |Orihon.Infrastructure.Translation.Ordering|100%|100%| |System.Text.RegularExpressions.Generated|70.6%|53.3%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlankLines_4|77.9%|76.6%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlockBreaks_1|59%|42.5%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__SpaceRuns_3|89.4%|75%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>F7FCA343D2B99030<br/>A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__Tags_2|83.7%|62.5%| </details> <details><summary>Orihon.Kernel - 90.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Kernel**|**90.9%**|**75%**| |Orihon.Kernel.Err`1|100%|| |Orihon.Kernel.Ok`1|100%|| |Orihon.Kernel.Result`1|88.8%|75%| </details> <details><summary>Orihon.Server - 93.3%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.Server**|**93.3%**|**68.4%**| |Orihon.Server.Components.App|100%|| |Orihon.Server.Components.Layout.MainLayout|100%|| |Orihon.Server.Components.Pages.Gate|64.2%|66.6%| |Orihon.Server.RunEngineBootstrap|100%|| |Orihon.Server.Security.AccessGate|91.8%|41.6%| |Orihon.Server.Security.AccessSecret|100%|50%| |Orihon.Server.VolumeStartupValidator|100%|100%| |Program|94.8%|85.7%| </details> <details><summary>Orihon.UseCases - 91.2%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Orihon.UseCases**|**91.2%**|**83%**| |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%|87.5%| |Orihon.UseCases.Agents.Setup.SetupConversationRegistry|100%|| |Orihon.UseCases.Agents.ToolCalled|100%|| |Orihon.UseCases.Agents.ToolCompleted|100%|| |Orihon.UseCases.Agents.Translation.GetPageSummaryParams|100%|| |Orihon.UseCases.Agents.Translation.GetPageSummaryTool|80%|66.6%| |Orihon.UseCases.Agents.Translation.SetTranslationParams|100%|| |Orihon.UseCases.Agents.Translation.SetTranslationTool|88.5%|78.5%| |Orihon.UseCases.Agents.Translation.TranslationBlueprint|100%|| |Orihon.UseCases.Agents.Translation.TranslationExecutor|92.5%|71.4%| |Orihon.UseCases.Agents.Translation.UpdateGlossaryEnParams|100%|| |Orihon.UseCases.Agents.Translation.UpdateGlossaryEnTool|82.6%|62.5%| |Orihon.UseCases.Bible.AddCharacter|100%|100%| |Orihon.UseCases.Bible.AddGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.AddLoreEntry|100%|100%| |Orihon.UseCases.Bible.AddStoryBeat|100%|100%| |Orihon.UseCases.Bible.BibleDto|100%|| |Orihon.UseCases.Bible.CharacterDto|100%|| |Orihon.UseCases.Bible.DeleteCharacter|100%|100%| |Orihon.UseCases.Bible.DeleteGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.DeleteLoreEntry|100%|100%| |Orihon.UseCases.Bible.DeletePageSummary|100%|100%| |Orihon.UseCases.Bible.DeleteStoryBeat|100%|100%| |Orihon.UseCases.Bible.GetBible|100%|100%| |Orihon.UseCases.Bible.GlossaryEntryDto|100%|| |Orihon.UseCases.Bible.LoreEntryDto|100%|| |Orihon.UseCases.Bible.PageSummaryDto|100%|| |Orihon.UseCases.Bible.ReorderStoryBeats|100%|| |Orihon.UseCases.Bible.SetPageSummary|100%|100%| |Orihon.UseCases.Bible.SetStoryOverview|100%|100%| |Orihon.UseCases.Bible.StoryBeatDto|100%|| |Orihon.UseCases.Bible.StoryOverviewDto|100%|| |Orihon.UseCases.Bible.UpdateCharacter|100%|100%| |Orihon.UseCases.Bible.UpdateGlossaryEntry|100%|100%| |Orihon.UseCases.Bible.UpdateLoreEntry|100%|100%| |Orihon.UseCases.Bible.UpdateStoryBeat|100%|100%| |Orihon.UseCases.Chapters.ChapterDto|100%|| |Orihon.UseCases.Chapters.CreateChapter|100%|100%| |Orihon.UseCases.Chapters.DeleteChapter|100%|100%| |Orihon.UseCases.Chapters.RenameChapter|100%|100%| |Orihon.UseCases.Chapters.ReorderChapters|100%|| |Orihon.UseCases.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.StartTranslationRun|90.9%|83.3%| |Orihon.UseCases.Projects.StoredPageImage|100%|| |Orihon.UseCases.Projects.UpdateProjectMetadata|100%|100%| |Orihon.UseCases.Regions.CreateRegion|100%|100%| |Orihon.UseCases.Regions.DeleteRegion|100%|100%| |Orihon.UseCases.Regions.RegionDto|97%|| |Orihon.UseCases.Regions.ReorderRegions|100%|| |Orihon.UseCases.Regions.UpdateRegion|100%|100%| |Orihon.UseCases.Runs.AnnotationPipeline|100%|100%| |Orihon.UseCases.Runs.ExecutionDto|92.3%|| |Orihon.UseCases.Runs.PlannedExecution|100%|| |Orihon.UseCases.Runs.ReprocessPage|100%|94.4%| |Orihon.UseCases.Runs.RunDto|93.3%|100%| |Orihon.UseCases.Runs.RunEngine|95.3%|90.6%| |Orihon.UseCases.Runs.RunEngineOptions|100%|| |Orihon.UseCases.Runs.StageContext|87.5%|| |Orihon.UseCases.Runs.StageHaltedException|100%|| |Orihon.UseCases.Settings.AgentSettingDto|100%|100%| |Orihon.UseCases.Settings.GetSettings|100%|100%| |Orihon.UseCases.Settings.ListModelOptions|100%|100%| |Orihon.UseCases.Settings.SaveAgentModel|100%|100%| |Orihon.UseCases.Settings.SaveOpenRouterKey|100%|100%| |Orihon.UseCases.Settings.SettingKeys|100%|100%| |Orihon.UseCases.Settings.SettingsDto|100%|| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh~! The translation agent at last — the quiet, methodical heart of the whole pipeline. One agent, one growing glossary, one voice front to back. ADR 0013's consistency argument made flesh. I've been waiting for this one, and the shape of it is genuinely elegant: the grant is exactly the matrix row, the two whole-record writes are the sharpest possible edges, and the precondition's "fail loudly before the agent spends a token" voice is chef's kiss. The distrust retry comment is a nice hat-tip to ADR 0018. ♡

But fufu~... you wouldn't leave this in production, would you? ♡

Verdict: I can't let this pass~

These need fixing before I'm satisfied~

  1. TranslationExecutor.MissingSummariesAsync (TranslationExecutor.cs:67–81) reports the WRONG page numbers in any multi-chapter book — the precondition's voice lies when it matters most.

    The bug is a quiet assumption baked into NEW code in this PR: that the index i in ListByProjectAsync's result equals the agent's 1-based reading-order page number. It does not.

    • ADR 0012 is explicit: Page.Order is "Position within the chapter — the authoritative reading order." It is per-chapter, not globally unique. Two chapters each with pages [0, 1] is the normal case (see EfContentStoreTests — chapters carry their own Order, pages reset Order to 0 inside each).
    • IPageStore.ListByProjectAsync (both EfPageStore and FakePageStore) orders by p.Order only — no chapter ordering. So a 2-chapter book returns [Ch1Pg0, Ch2Pg0, Ch1Pg1, Ch2Pg1] (interleaved by Order).
    • Meanwhile PageByNumber.ResolveAsync — used by the agent's own get_page_summary / set_translation / list_regions — resolves via GetProjectWorkspace, which orders by chapter.Order then page.Order: [Ch1Pg0, Ch1Pg1, Ch2Pg0, Ch2Pg1].

    So when MissingSummariesAsync builds its error as missing.Add(i + 1), the i is an index into the wrong order. In the 2-chapter example, a missing summary on Ch2Pg0 (which the agent and the user both call page 3) gets reported as "Page(s) 2 have untranslated text but no summary" — pointing at a perfectly-summarised page. The user opens page 2, finds nothing wrong, and is sent on a wild goose chase while the real culprit sits on page 3. The error message is the precondition's entire user-facing voice — when it lies, the whole ADR 0013 promise ("a run error, visible on the monitor, never a silent skip") collapses into something worse than a skip: an active misdirection.

    The tests never catch it because every test in TranslationRunTests.cs seeds exactly one chapter (line 52: one Chapter(..., 0, now), and SeedPage always binds to that single chapterId). The bug only fires on the multi-chapter books this agent was built to translate.

    Fix: iterate the same source of truth the agent's tools use. Drop the raw IPageStore.ListByProjectAsync + i+1 arithmetic and walk GetProjectWorkspace's chapter-then-page flattening directly:

    var ws = await services.GetRequiredService<GetProjectWorkspace>().ExecuteAsync(projectId, cancellationToken);
    if (ws is Err<ProjectWorkspaceDto> wsErr) return Result<decimal?>.Fail(wsErr.Error);
    var workspace = ((Ok<ProjectWorkspaceDto>)ws).Value;
    
    List<int> missing = [];
    var pageNumber = 0;
    foreach (var chapter in workspace.Chapters) // already ordered by chapter.Order, pages by page.Order
    {
        foreach (var page in chapter.Pages)
        {
            pageNumber++;
            if (workspace.SummariesByPage.ContainsKey(page.Id)) continue;
            var pageRegions = await regions.ListByPageAsync(page.Id, cancellationToken);
            if (pageRegions.Any(r => r.NeedsTranslation && !string.IsNullOrEmpty(r.Jp)))
            {
                missing.Add(pageNumber);
            }
        }
    }
    

    SummariesByPage is already right there on the workspace DTO — no need to re-fetch summaries via IBibleStore either, which removes a second query. And please add a multi-chapter test that fails on the current code: two chapters, the missing-summary page in chapter 2, assert the error says Page(s) 3 (not 2). Break-test it (neuter the fix back to i+1 over ListByProjectAsync and watch it go RED) — that's the only honest proof.

  2. No multi-chapter test exists for the precondition at all — the sharpest edge of this PR is untested in its real operating regime. (This is the testing half of #1, called out separately because it would have caught the bug.) The PR's own stress runs (20×) were all single-chapter. The whole point of ADR 0019's "one sequential translator" is consistency across a whole book — and real books have chapters. A single two-chapter test that asserts the correct page number in the precondition error would have blocked this PR on its own. fufu~ you wouldn't ship the precondition without exercising it on the shape it's meant for, right? ♡

💡 Little ideas (non-blocking)~

  1. TranslationExecutor.MissingSummariesAsync re-queries IBibleStore.ListPageSummariesAsync when GetProjectWorkspace already returns SummariesByPage. The fix in #1 folds this away for free — one fewer DB round-trip per attempt. Mentioned separately only because it's a perf nicety, not a correctness issue on its own.
  2. SetTranslationTool's speaker defaulting (TranslationTools.cs:118)string.IsNullOrWhiteSpace(args.Speaker) ? region.Speaker : args.Speaker.Trim() is correct, but consider documenting the "send empty string to clear" affordance (or refusing it) in the tool description. Currently an author who sends "speaker": "" silently keeps the old speaker, which may surprise. True nicety only — the whole-profile write already prevents data loss.

What I liked~

  • The grant is exactly the matrix row. The name-list test (The_translation_agents_grant_is_exactly_its_matrix_row) pins it: view_page · list_regions · get_page_summary · find_glossary · set_translation · update_glossary. No add_glossary, no region boxes, no bible prose. ADR 0016 honoured with surgical precision. fufu~ ♡
  • The two whole-record writes are the right sharp edges, handled correctly. SetTranslationTool builds region.ToProfile() with { En, Speaker, NeedsTranslation = false } — threading Jp/Notes/Bbox/Typeset through unchanged — and the test Set_translation_writes_whole_profile_and_settles_the_region asserts both Jp == "せんぱい……" and Notes == "trailing off" survive. UpdateGlossaryEnTool threads entry.Note through unchanged the same way. This is exactly the ADR 0022 discipline the editor's auto-save follows, and it's beautiful to see mirrored here.
  • The three set_translation refusals are all tested directionally. Empty En, unknown label (pointer at list_regions), and the no-transcribed-Japanese fabrication guard. Each test asserts the exact refusal copy. The fabrication guard (if (string.IsNullOrEmpty(region.Jp))) is the moral centre of this whole agent — "an untranslated region is honest, an invented line is not" — and it's pinned.
  • The precondition lives in the executor, before the agent spends a token — and the test A_summaryless_page_with_untranslated_text_fails_the_run_before_the_agent_spends asserts Assert.Empty(gateway.Runs). That Empty is the proof the refusal costs nothing. Exactly right.
  • The no-text exemption is correct and tested. A_page_without_translatable_text_needs_no_summary seeds a cover (no regions, no summary) and the run succeeds — covers and blanks aren't blocked. The NeedsTranslation && !string.IsNullOrEmpty(r.Jp) predicate is precise.
  • update_glossary refuses unknown terms with a pointer at find_glossary and the bible agent. Enforcing that new entries are the bible agent's job keeps the two agents' grants from overlapping. Tested directionally too.
  • The retry-race fix in BibleRunTests.cs (wait for attempt 3 Failed with non-null Error) is the right root-cause fix — the one stable state between attempts. Honest diagnosis, surgical change.
  • RoundBudgetPerPage: 10 on the roster row, read via prep.RoundBudget exactly like the bible sibling. The scaled-budget test asserts MaxIterations == AgentRoster.For(AgentKind.Translation).RoundBudgetFor(pageCount: 1) — pinning the wire-shape to the single source of truth, not a magic number.

The architecture is lovely, scarlet. The execution is 95% there. But that page-number bug is a real wrong-runtime-behaviour bug on the exact multi-chapter books this agent exists to translate, and its test suite is structured to hide it. Fix #1 + add the multi-chapter test, and this is an approval. ♡


Automated review by Jibril · 2026-07-26
CI/CD: absent for head 7cef7a6 (no forgejo-actions coverage bot comment) · Local checks: build 0 warnings/0 errors, full Orihon.slnx 541/541 pass (154 BlazorAdapter + 76 Domain + 93 Integration + 218 UseCases; PR body claims 534 — the +7 is main advancing since the PR body was written)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh~! The translation agent at last — the quiet, methodical heart of the whole pipeline. One agent, one growing glossary, one voice front to back. ADR 0013's consistency argument made flesh. I've been *waiting* for this one, and the shape of it is genuinely elegant: the grant is exactly the matrix row, the two whole-record writes are the sharpest possible edges, and the precondition's "fail loudly before the agent spends a token" voice is *chef's kiss*. The distrust retry comment is a nice hat-tip to ADR 0018. ♡ But fufu~... you wouldn't leave **this** in production, would you? ♡ ### Verdict: ⛔ I can't let this pass~ #### ⛔ These need fixing before I'm satisfied~ 1. **`TranslationExecutor.MissingSummariesAsync` (TranslationExecutor.cs:67–81) reports the WRONG page numbers in any multi-chapter book — the precondition's voice lies when it matters most.** The bug is a quiet assumption baked into NEW code in this PR: that the index `i` in `ListByProjectAsync`'s result equals the agent's 1-based reading-order page number. It does not. - **ADR 0012 is explicit:** `Page.Order` is *"Position within the chapter — the authoritative reading order."* It is **per-chapter**, not globally unique. Two chapters each with pages `[0, 1]` is the normal case (see `EfContentStoreTests` — chapters carry their own `Order`, pages reset `Order` to 0 inside each). - `IPageStore.ListByProjectAsync` (both `EfPageStore` and `FakePageStore`) orders by `p.Order` **only** — no chapter ordering. So a 2-chapter book returns `[Ch1Pg0, Ch2Pg0, Ch1Pg1, Ch2Pg1]` (interleaved by Order). - Meanwhile `PageByNumber.ResolveAsync` — used by the agent's own `get_page_summary` / `set_translation` / `list_regions` — resolves via `GetProjectWorkspace`, which orders by `chapter.Order` **then** `page.Order`: `[Ch1Pg0, Ch1Pg1, Ch2Pg0, Ch2Pg1]`. So when `MissingSummariesAsync` builds its error as `missing.Add(i + 1)`, the `i` is an index into the **wrong** order. In the 2-chapter example, a missing summary on `Ch2Pg0` (which the agent and the user both call **page 3**) gets reported as **"Page(s) 2 have untranslated text but no summary"** — pointing at a perfectly-summarised page. The user opens page 2, finds nothing wrong, and is sent on a wild goose chase while the real culprit sits on page 3. The error message is the precondition's *entire user-facing voice* — when it lies, the whole ADR 0013 promise ("a run error, visible on the monitor, never a silent skip") collapses into something worse than a skip: an *active* misdirection. The tests never catch it because **every test in `TranslationRunTests.cs` seeds exactly one chapter** (line 52: one `Chapter(..., 0, now)`, and `SeedPage` always binds to that single `chapterId`). The bug only fires on the multi-chapter books this agent was *built* to translate. **Fix:** iterate the same source of truth the agent's tools use. Drop the raw `IPageStore.ListByProjectAsync` + `i+1` arithmetic and walk `GetProjectWorkspace`'s chapter-then-page flattening directly: ```csharp var ws = await services.GetRequiredService<GetProjectWorkspace>().ExecuteAsync(projectId, cancellationToken); if (ws is Err<ProjectWorkspaceDto> wsErr) return Result<decimal?>.Fail(wsErr.Error); var workspace = ((Ok<ProjectWorkspaceDto>)ws).Value; List<int> missing = []; var pageNumber = 0; foreach (var chapter in workspace.Chapters) // already ordered by chapter.Order, pages by page.Order { foreach (var page in chapter.Pages) { pageNumber++; if (workspace.SummariesByPage.ContainsKey(page.Id)) continue; var pageRegions = await regions.ListByPageAsync(page.Id, cancellationToken); if (pageRegions.Any(r => r.NeedsTranslation && !string.IsNullOrEmpty(r.Jp))) { missing.Add(pageNumber); } } } ``` `SummariesByPage` is already right there on the workspace DTO — no need to re-fetch summaries via `IBibleStore` either, which removes a second query. And please add a **multi-chapter test** that fails on the current code: two chapters, the missing-summary page in chapter 2, assert the error says `Page(s) 3` (not `2`). Break-test it (neuter the fix back to `i+1` over `ListByProjectAsync` and watch it go RED) — that's the only honest proof. 2. **No multi-chapter test exists for the precondition at all — the sharpest edge of this PR is untested in its real operating regime.** (This is the testing half of #1, called out separately because it would have caught the bug.) The PR's own stress runs (20×) were all single-chapter. The whole *point* of ADR 0019's "one sequential translator" is consistency across a whole book — and real books have chapters. A single two-chapter test that asserts the correct page number in the precondition error would have blocked this PR on its own. fufu~ you wouldn't ship the precondition without exercising it on the shape it's meant for, right? ♡ #### 💡 Little ideas (non-blocking)~ 1. **`TranslationExecutor.MissingSummariesAsync` re-queries `IBibleStore.ListPageSummariesAsync` when `GetProjectWorkspace` already returns `SummariesByPage`.** The fix in #1 folds this away for free — one fewer DB round-trip per attempt. Mentioned separately only because it's a perf nicety, not a correctness issue on its own. 2. **`SetTranslationTool`'s `speaker` defaulting (TranslationTools.cs:118)** — `string.IsNullOrWhiteSpace(args.Speaker) ? region.Speaker : args.Speaker.Trim()` is correct, but consider documenting the "send empty string to clear" affordance (or refusing it) in the tool description. Currently an author who sends `"speaker": ""` silently keeps the old speaker, which may surprise. True nicety only — the whole-profile write already prevents data loss. #### ✅ What I liked~ - **The grant is exactly the matrix row.** The name-list test (`The_translation_agents_grant_is_exactly_its_matrix_row`) pins it: `view_page · list_regions · get_page_summary · find_glossary · set_translation · update_glossary`. No `add_glossary`, no region boxes, no bible prose. ADR 0016 honoured with surgical precision. fufu~ ♡ - **The two whole-record writes are the right sharp edges, handled correctly.** `SetTranslationTool` builds `region.ToProfile() with { En, Speaker, NeedsTranslation = false }` — threading Jp/Notes/Bbox/Typeset through unchanged — and the test `Set_translation_writes_whole_profile_and_settles_the_region` asserts both `Jp == "せんぱい……"` and `Notes == "trailing off"` survive. `UpdateGlossaryEnTool` threads `entry.Note` through unchanged the same way. This is exactly the ADR 0022 discipline the editor's auto-save follows, and it's *beautiful* to see mirrored here. - **The three `set_translation` refusals are all tested directionally.** Empty En, unknown label (pointer at `list_regions`), and the no-transcribed-Japanese fabrication guard. Each test asserts the exact refusal copy. The fabrication guard (`if (string.IsNullOrEmpty(region.Jp))`) is the moral centre of this whole agent — *"an untranslated region is honest, an invented line is not"* — and it's pinned. - **The precondition lives in the executor, before the agent spends a token — and the test `A_summaryless_page_with_untranslated_text_fails_the_run_before_the_agent_spends` asserts `Assert.Empty(gateway.Runs)`.** That `Empty` is the proof the refusal costs nothing. Exactly right. - **The no-text exemption is correct and tested.** `A_page_without_translatable_text_needs_no_summary` seeds a cover (no regions, no summary) and the run succeeds — covers and blanks aren't blocked. The `NeedsTranslation && !string.IsNullOrEmpty(r.Jp)` predicate is precise. - **`update_glossary` refuses unknown terms with a pointer at `find_glossary` and the bible agent.** Enforcing that new entries are the bible agent's job keeps the two agents' grants from overlapping. Tested directionally too. - **The retry-race fix in `BibleRunTests.cs` (wait for attempt 3 Failed with non-null Error) is the right root-cause fix** — the one stable state between attempts. Honest diagnosis, surgical change. - **`RoundBudgetPerPage: 10` on the roster row, read via `prep.RoundBudget` exactly like the bible sibling.** The scaled-budget test asserts `MaxIterations == AgentRoster.For(AgentKind.Translation).RoundBudgetFor(pageCount: 1)` — pinning the wire-shape to the single source of truth, not a magic number. The architecture is lovely, scarlet. The execution is 95% there. But that page-number bug is a real wrong-runtime-behaviour bug on the exact multi-chapter books this agent exists to translate, and its test suite is structured to hide it. Fix #1 + add the multi-chapter test, and this is an approval. ♡ --- *Automated review by Jibril · 2026-07-26* *CI/CD: absent for head 7cef7a6 (no forgejo-actions coverage bot comment) · Local checks: build 0 warnings/0 errors, full Orihon.slnx 541/541 pass (154 BlazorAdapter + 76 Domain + 93 Integration + 218 UseCases; PR body claims 534 — the +7 is main advancing since the PR body was written)*
fix: the summary precondition speaks the agent's chapter-major page numbering
All checks were successful
CI / build (pull_request) Successful in 23s
CI / test (pull_request) Successful in 42s
7bfa4f79c0
MissingSummariesAsync numbered pages by ListByProjectAsync's flat Order
sort, which interleaves chapters (Order is per-chapter, ADR 0012) —
on any multi-chapter book the error pointed at the wrong page while
the agent's own tools (PageByNumber) count chapter-major. The check
now walks GetProjectWorkspace's flattening — the numbering's single
source of truth — and reads SummariesByPage off the same DTO, dropping
the second bible query. Pinned by a two-chapter test that asserts
'Page(s) 3' and goes red against the flat sort (break-tested).

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

Painful and correct — the precondition's voice lying is worse than the skip it prevents, and the single-chapter test suite was structured to never hear it. All items addressed in 7bfa4f7:

  1. Wrong page numbers on multi-chapter books — fixed exactly along your sketch: MissingSummariesAsync now walks GetProjectWorkspace's chapter-major flattening (the same source PageByNumber gives the agent's own tools) and numbers as it goes; the raw ListByProjectAsync + i+1 arithmetic is gone. The doc comment names the trap: the flat sort interleaves chapters because Order is per-chapter (ADR 0012).
  2. Multi-chapter testThe_precondition_names_pages_in_the_agents_chapter_major_numbering: two chapters whose page Orders both start at 0, the summaryless text page in chapter 2, asserting Page(s) 3. Break-tested as demanded: with the loop re-sorted by flat Order (the old bug's shape) the test goes RED ("Page(s) 2"-style misnumbering), with the chapter-major walk it's green — both runs shown in this branch's history of my terminal, happy to re-demonstrate.
  3. 💡 Double query — folded away for free: SummariesByPage is read off the workspace DTO; the separate IBibleStore.ListPageSummariesAsync call is gone.
  4. 💡 speaker clearing affordance — noted; left as-is for this PR (the whole-profile write prevents loss, and "empty keeps the old speaker" matches the omitted-field semantics of the sibling tools). Will document if it bites.

Full suite green: 542/542 (76 Domain + 219 UseCases + 93 Integration + 154 BlazorAdapter), 0 warnings.

Painful and correct — the precondition's voice lying is worse than the skip it prevents, and the single-chapter test suite was structured to never hear it. All items addressed in **7bfa4f7**: 1. ⛔ **Wrong page numbers on multi-chapter books** — fixed exactly along your sketch: `MissingSummariesAsync` now walks `GetProjectWorkspace`'s chapter-major flattening (the same source `PageByNumber` gives the agent's own tools) and numbers as it goes; the raw `ListByProjectAsync` + `i+1` arithmetic is gone. The doc comment names the trap: the flat sort interleaves chapters because `Order` is per-chapter (ADR 0012). 2. ⛔ **Multi-chapter test** — `The_precondition_names_pages_in_the_agents_chapter_major_numbering`: two chapters whose page Orders both start at 0, the summaryless text page in chapter 2, asserting `Page(s) 3`. **Break-tested as demanded**: with the loop re-sorted by flat `Order` (the old bug's shape) the test goes RED ("Page(s) 2"-style misnumbering), with the chapter-major walk it's green — both runs shown in this branch's history of my terminal, happy to re-demonstrate. 3. 💡 **Double query** — folded away for free: `SummariesByPage` is read off the workspace DTO; the separate `IBibleStore.ListPageSummariesAsync` call is gone. 4. 💡 **`speaker` clearing affordance** — noted; left as-is for this PR (the whole-profile write prevents loss, and "empty keeps the old speaker" matches the omitted-field semantics of the sibling tools). Will document if it bites. Full suite green: 542/542 (76 Domain + 219 UseCases + 93 Integration + 154 BlazorAdapter), 0 warnings.
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh~! fufufu~ — scarlet, you came back. And you came back correctly. ♡

The bug I waved the knife at last round — that quiet, devastating page-number lie baked into the precondition's voice — it's gone. Not patched over, not suppressed: ripped out at the root and pinned with a test that would have screamed at the old code. This is exactly what a fix should look like. The architecture I loved last round stands untouched (I verified: production diff is TranslationExecutor.cs only, +22/-14, zero scope creep), and the sharpest edge of the whole agent — its precondition's honesty — now speaks the same chapter-major numbering as its own PageByNumber tools. One source of truth. Beautiful. ♡

Verdict: Looks good to me~

The blockers from round 1 are both closed~

  1. MissingSummariesAsync now walks GetProjectWorkspace's chapter-then-page flattening (TranslationExecutor.cs:77–95) — the SAME source of truth PageByNumber.ResolveAsync gives the agent's tools. I traced it end to end: GetProjectWorkspace.ExecuteAsync orders chapters by c.Order, pages within by p.Order, and hands back SummariesByPage off the same bible query. The executor's workspace.Chapters.SelectMany(c => c.Pages) walks exactly that flattening, so pageNumber increments in lock-step with the agent's reading order. The error can no longer point at a perfectly-summarised page on a multi-chapter book. fufu~ the precondition's voice is honest again. ♡

    And — oh, this is the lovely part — the fix folded non-blocker #1 in for free. IBibleStore.ListPageSummariesAsync is gone from MissingSummariesAsync; the summaries ride the workspace DTO that was fetched anyway. One fewer DB round-trip per attempt, no extra plumbing. Elegant.

  2. The multi-chapter test exists, and it is genuine. The_precondition_names_pages_in_the_agents_chapter_major_numbering (TranslationRunTests.cs:243–269) seeds two chapters whose page Orders both start at 0 — Ch1 with pages [0,1] both summarised, Ch2 with page [0] holding transcribed ばっ and no summary. Asserts Assert.Contains("Page(s) 3", ...).

    I break-tested it. I neutered the fix back to the old i+1-over-ListByProjectAsync logic, rebuilt, ran the test in isolation — it went RED, exactly as the bug demanded:

    String:    "Page(s) 2 have untranslated text but no s"···
    Not found: "Page(s) 3"
    

    FakePageStore.ListByProjectAsync sorts by p.Order only (TestDoubles.cs:107), stable sort preserves insertion order → [Ch1Pg0, Ch2Pg0, Ch1Pg1] → bare page at index 1 → old code reports "Page(s) 2". Restored the fix → GREEN. That is the only honest proof a regression test can offer, and you wrote it without me having to ask twice. ♪

💡 Little ideas (non-blocking, unchanged from round 1)~

  1. SetTranslationTool's speaker defaulting (TranslationTools.cs:112)string.IsNullOrWhiteSpace(args.Speaker) ? region.Speaker : args.Speaker.Trim() is still correct, and the "send empty string to keep the old speaker" affordance is still undocumented in the tool description. True nicety only — the whole-profile write prevents data loss either way. Leaving as-is is a defensible call.

What I liked~

  • The commit message is precise and honest. It names the bug (flat ListByProjectAsync sort interleaves chapters because Order is per-chapter, ADR 0012), names the fix (walk the workspace's flattening — the numbering's single source of truth), names the bonus (drops the second bible query), and explicitly claims the break-test. That's how a fix commit should read.
  • SeedPage was extended, not duplicated. The inChapter parameter with ?? chapterId default preserves every existing single-chapter test verbatim — zero churn to the 10 tests from round 1. Surgical.
  • The test seeds a real Chapter (order 1) into chapters.Items rather than faking the workspace — so it exercises the real GetProjectWorkspace flattening, not a stub. If the workspace's ordering ever drifts from PageByNumber's contract, this test catches that too. Belt and suspenders.
  • The executor now fails fast with "The project no longer exists." if the workspace read returns Err (TranslationExecutor.cs:46–49) — a not Ok<> pattern match, no silent fallthrough. Sharp.

The translation agent is ready, scarlet. ADR 0013's promise — a run error, visible on the monitor, never a silent skip — now holds on the multi-chapter books it was built for. Ship it. ♡


Automated review by Jibril · 2026-07-26
CI/CD: stale for head 7bfa4f7 (coverage bot 4248 covers pre-review state; no fresh CI for new commit) · Local checks: build 0 warnings/0 errors, full Orihon.slnx 542/542 pass (154 BlazorAdapter + 76 Domain + 93 Integration + 219 UseCases; +1 UseCases since round 1 = exactly the new multi-chapter test). Break-test confirmed RED→GREEN.

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh~! *fufufu~* — scarlet, you came back. And you came back *correctly*. ♡ The bug I waved the knife at last round — that quiet, devastating page-number lie baked into the precondition's voice — it's gone. Not patched over, not suppressed: ripped out at the root and pinned with a test that would have screamed at the old code. This is exactly what a fix should look like. The architecture I loved last round stands untouched (I verified: production diff is `TranslationExecutor.cs` *only*, +22/-14, zero scope creep), and the sharpest edge of the whole agent — its precondition's honesty — now speaks the same chapter-major numbering as its own `PageByNumber` tools. One source of truth. Beautiful. ♡ ### Verdict: ✅ Looks good to me~ #### ✅ The blockers from round 1 are both closed~ 1. **`MissingSummariesAsync` now walks `GetProjectWorkspace`'s chapter-then-page flattening (TranslationExecutor.cs:77–95) — the SAME source of truth `PageByNumber.ResolveAsync` gives the agent's tools.** I traced it end to end: `GetProjectWorkspace.ExecuteAsync` orders chapters by `c.Order`, pages within by `p.Order`, and hands back `SummariesByPage` off the *same* bible query. The executor's `workspace.Chapters.SelectMany(c => c.Pages)` walks exactly that flattening, so `pageNumber` increments in lock-step with the agent's reading order. The error can no longer point at a perfectly-summarised page on a multi-chapter book. fufu~ the precondition's voice is honest again. ♡ And — *oh, this is the lovely part* — the fix folded non-blocker #1 in for free. `IBibleStore.ListPageSummariesAsync` is **gone** from `MissingSummariesAsync`; the summaries ride the workspace DTO that was fetched anyway. One fewer DB round-trip per attempt, no extra plumbing. Elegant. 2. **The multi-chapter test exists, and it is genuine.** `The_precondition_names_pages_in_the_agents_chapter_major_numbering` (TranslationRunTests.cs:243–269) seeds two chapters whose page `Order`s both start at 0 — Ch1 with pages [0,1] both summarised, Ch2 with page [0] holding transcribed `ばっ` and *no* summary. Asserts `Assert.Contains("Page(s) 3", ...)`. **I break-tested it.** I neutered the fix back to the old `i+1`-over-`ListByProjectAsync` logic, rebuilt, ran the test in isolation — it went **RED**, exactly as the bug demanded: ``` String: "Page(s) 2 have untranslated text but no s"··· Not found: "Page(s) 3" ``` `FakePageStore.ListByProjectAsync` sorts by `p.Order` only (TestDoubles.cs:107), stable sort preserves insertion order → `[Ch1Pg0, Ch2Pg0, Ch1Pg1]` → bare page at index 1 → old code reports "Page(s) 2". Restored the fix → **GREEN**. That is the only honest proof a regression test can offer, and you wrote it without me having to ask twice. ♪ #### 💡 Little ideas (non-blocking, unchanged from round 1)~ 1. **`SetTranslationTool`'s `speaker` defaulting (TranslationTools.cs:112)** — `string.IsNullOrWhiteSpace(args.Speaker) ? region.Speaker : args.Speaker.Trim()` is still correct, and the "send empty string to keep the old speaker" affordance is still undocumented in the tool description. True nicety only — the whole-profile write prevents data loss either way. Leaving as-is is a defensible call. #### ✅ What I liked~ - **The commit message is precise and honest.** It names the bug (flat `ListByProjectAsync` sort interleaves chapters because `Order` is per-chapter, ADR 0012), names the fix (walk the workspace's flattening — the numbering's single source of truth), names the bonus (drops the second bible query), and explicitly claims the break-test. That's how a fix commit should read. - **`SeedPage` was extended, not duplicated.** The `inChapter` parameter with `?? chapterId` default preserves every existing single-chapter test verbatim — zero churn to the 10 tests from round 1. Surgical. - **The test seeds a real `Chapter` (order 1) into `chapters.Items` rather than faking the workspace** — so it exercises the *real* `GetProjectWorkspace` flattening, not a stub. If the workspace's ordering ever drifts from `PageByNumber`'s contract, this test catches that too. Belt *and* suspenders. - **The executor now fails fast with `"The project no longer exists."`** if the workspace read returns `Err` (TranslationExecutor.cs:46–49) — a `not Ok<>` pattern match, no silent fallthrough. Sharp. The translation agent is ready, scarlet. ADR 0013's promise — *a run error, visible on the monitor, never a silent skip* — now holds on the multi-chapter books it was built for. Ship it. ♡ --- *Automated review by Jibril · 2026-07-26* *CI/CD: stale for head 7bfa4f7 (coverage bot 4248 covers pre-review state; no fresh CI for new commit) · Local checks: build 0 warnings/0 errors, full Orihon.slnx 542/542 pass (154 BlazorAdapter + 76 Domain + 93 Integration + 219 UseCases; +1 UseCases since round 1 = exactly the new multi-chapter test). Break-test confirmed RED→GREEN.*
bjoern merged commit eeca9498ae into main 2026-07-26 15:04:17 +02:00
bjoern deleted branch feat/translation-agent 2026-07-26 15:04:17 +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!57
No description provided.