fix(worker): propagate unit-merge failures instead of reporting success
A child merge that came back blocked/verify_failed/untracked_collision during a parent/children unit merge used to vanish: DrainAsync only logged it server-side, PlanningMergeAborted carried no reason, and ApproveReview/review_task always reported StatusMerged for a task with children regardless of the real outcome, so a failed unit merge left the parent stuck with no visible error. - PlanningMergeOrchestrator.StartAsync/ContinueAsync/DrainAsync now return a PlanningMergeResult(Status, Reason) instead of void, and PlanningMergeAborted carries that reason to the UI. - WorkerHub.ApproveReview and ExternalMcpService.ReviewTask's approve branch propagate the real status/reason for a parent with children instead of hardcoding "merged" (or masking a non-conflict failure as "conflict"). - StartAsync now requires the parent to already be WaitingForReview for improvement parents too, not just planning ones, so a stale caller can no longer trigger a partial child merge. - HasActiveMerge now also covers the window between the last child merging and FinalizeParentDoneAsync completing, closing a gap where a concurrent Cancel could race the parent's own approve-to-Done transition. - IslandsShellViewModel.OnPlanningMergeAborted flashes the reason via FlashFooterError instead of only clearing the external-merge banner.
This commit is contained in:
@@ -76,6 +76,23 @@ DB write. Cycle note: `TaskStateService` can't take a direct constructor depende
|
||||
handed to `PlanningChainCoordinator`. `IActiveMergeState` (`Planning/Interfaces/`) is `PlanningMergeOrchestrator`'s
|
||||
only public surface `TaskStateService` needs.
|
||||
|
||||
`HasActiveMerge` also stays true through the window between the last child merging and
|
||||
`FinalizeParentDoneAsync` returning (a separate `State.IsFinalizing` flag — `CurrentSubtaskId` is
|
||||
already null there since no subtask is left to merge). Without it, a `Cancel` racing the finalize
|
||||
call's own `ApproveReviewAsync` could slip past the guard for that whole call. `StartAsync` also now
|
||||
requires the parent to already be `WaitingForReview` — for planning **and** improvement parents —
|
||||
before touching anything; previously only planning parents got an (indirect, children-only) check,
|
||||
so a stale UI click or a second caller on an improvement parent that had already left
|
||||
`WaitingForReview` could still drive a partial child merge before the final approve refused.
|
||||
|
||||
Unit-merge failures used to vanish silently: a child merge that came back
|
||||
`blocked`/`verify_failed`/`untracked_collision` (not `conflict`) was only logged server-side, and
|
||||
`ApproveReview`/`review_task` approve always reported success (`StatusMerged`) regardless.
|
||||
`StartAsync`/`ContinueAsync`/the private `DrainAsync` now return a `PlanningMergeResult(Status,
|
||||
Reason)` that both hub paths propagate as the real status, and `PlanningMergeAborted` carries that
|
||||
reason so the UI's `IslandsShellViewModel.OnPlanningMergeAborted` can `FlashFooterError` it instead
|
||||
of only clearing the banner.
|
||||
|
||||
The UI mirrors this as polish: `DetailsIslandViewModel.IsMergeDraining` (set on
|
||||
`PlanningMergeStartedEvent`, cleared on `PlanningMergeAborted`/`PlanningCompleted` for the bound
|
||||
task) gates `CancelReviewCommand`'s `CanExecute` — same shape as `WorktreesOverviewModalViewModel.IsMerging`
|
||||
|
||||
Reference in New Issue
Block a user