Add the FN-5756 ratification addendum to the executor/merger decoupling RFC. - Record approval for dual-run dependency satisfaction via in-review or completion_handoff_accepted marker - Require audit diff logging when legacy and new dependency signals disagree during dual-run - Record approval to make merge back-pressure branch-group aware by default with global safety caps retained Files changed: docs/rfcs/FN-5719-decouple-executor-merger.md | 4 ++++ 1 file changed, 4 insertions(+) Fusion-Task-Id: FN-5756 Fusion-Task-Lineage: 54077cc9-a4ae-4462-99b0-4c28d06652cc
11 KiB
FN-5719 RFC: Decouple Executor and Merger Lifecycle Stages
Status
Ratified by CEO gate (FN-5746, 2026-05-30). No implementation in this task.
1) Current coupling and failure modes
Current coupling in code
The executor-to-merger handoff is currently coupled through shared task lifecycle state (in-progress -> in-review -> done) plus shared engine event/scheduler surfaces:
- Executor completion hands off by calling
store.handoffToReview(...)(packages/engine/src/executor.ts:1434-1444, used at completion paths4528-4532,2791-2804). - TaskStore performs the authoritative transition to
in-reviewthroughmoveTaskInternal(...)(packages/core/src/store.ts:5187-5231). - ProjectEngine listens to
task:movedand, whento === "in-review", schedules merge queue handoff after grace delay (packages/engine/src/project-engine.ts:2391-2456). - Scheduler treats
in-reviewas dependency-satisfying (packages/engine/src/scheduler.ts:170-174,1249-1251,665-668), includes live in-review worktrees in overlap leases viasetActiveScopeLease(..., "in-review")(1194-1209), and runs PR-monitor lifecycle keyed to in-review transitions (471-507,588-634,938-949). - Merger finalizes
in-review -> doneincompleteTask(...)(packages/engine/src/merger.ts:12032-12043) and also performs retry rebounds totodowith progress preserved (8028,8090,8226).
Failure modes caused/exacerbated by coupling
-
Merge retry/rebound churn can hold lifecycle and scope surfaces
- Symptom: repeated
in-review <-> todorebounds or prolonged review-state occupancy while transient merge paths retry (merger.ts:8028/8090/8226; transient classifiertransient-merge-error-classifier.ts:37-46). - Coupling cause: same lifecycle states and scheduling surfaces represent both execution completion and merge orchestration progress.
- Symptom: repeated
-
Phantom-merge/already-on-main ambiguity stalls
- Symptom: tasks stall in review/merge recovery while already-landed detection and audit recovery determine ownership/survival (
already-merged-detector.ts:37-223,merger-audit-recovery.ts:29-111). - Coupling cause: merge proof/classification is entangled with the same completion lifecycle that executor dispatch and dependency logic inspect.
- Symptom: tasks stall in review/merge recovery while already-landed detection and audit recovery determine ownership/survival (
-
Resume-limbo interactions spill across execution and merge semantics (FN-5704)
- Symptom: repeated no-progress resume loops needing escalation to preserve-work
todorebound withtask:resume-limbo-escalated(self-healing.ts:2345-2396). - Coupling cause: one lifecycle surface tries to encode both “execution ownership” and “merge/verification ownership,” so reclaim/recovery paths can oscillate.
- Symptom: repeated no-progress resume loops needing escalation to preserve-work
-
In-review lease/dependency behavior can starve or serialize unrelated execution
- Symptom: in-review tasks with worktrees participate in overlap blocking and base-branch resolution (
scheduler.ts:1194-1209,962-976,1439-1444). - Coupling cause: merge-phase tasks still consume executor-adjacent scheduling semantics.
- Symptom: in-review tasks with worktrees participate in overlap blocking and base-branch resolution (
2) Proposed decoupled model
Introduce an explicit Merge Request Handoff Contract between executor completion and merger orchestration.
- Executor owns task until completion handoff accepted.
- On completion, executor emits
completion_handoffwith immutable payload (taskId, branch/worktree refs, merge target, evidence/run ids, automerge policy snapshot). - Store persists a separate merge-request record/queue entry (authoritative seam), then transitions task to review-visible state.
- Merger lane independently dequeues merge requests and mutates merge lifecycle metadata without requiring executor lane ownership.
State ownership
- Executor-owned phase:
in-progress+ execution statuses. - Review-owned phase:
in-reviewas human/review visibility state. - Merger-owned phase: separate merge request state machine (queued/running/retry/exhausted/succeeded) not inferred from executor state.
FN-5147 preservation
autoMerge:false: task remains terminal inin-reviewuntil human merge. Self-healing must not move backward/re-enqueue/pause-fail these rows (existing invariant preserved).autoMerge:true:in-reviewcreation also creates merge-request entry; merger consumes asynchronously.
Worked flow: autoMerge:true
- Executor finishes implementation and calls handoff (
executor.ts:1434-1444). - Store atomically records
completion_handoff_accepted+ merge-request row, then moves task toin-review. - Merge lane receives
merge:request-enqueuedevent (plus periodic poll fallback) and dequeues independently. - Merge attempts run; retries mutate merge-request state (
retrying) without reclaiming executor ownership. - On success, merger moves
in-review -> done(merger.ts:12032-12043) and marks merge-requestsucceeded.
Worked flow: autoMerge:false
- Executor finishes and handoff is accepted; task moves to
in-review. - Merge-request row is either not created, or created as
manual-requiredand never auto-dequeued. - Self-healing/recovery sweeps must treat this state as terminal-human-owned (FN-5147): no backward move to
todo, no auto pause/fail mutation, no auto re-enqueue. - Human merge action (dashboard/CLI/manual) is the only path to done.
Queueing and leases
- Dequeue contract: event-driven enqueue (
merge:request-enqueued) with poll-based reconciliation fallback for crash recovery. - State machine:
queued -> running -> (retrying -> queued)* -> succeeded | exhausted | cancelled. - Lease boundary: active overlap/scope leases are released when handoff is accepted; merger uses separate merge concurrency + integration-branch locks.
- Back-pressure: queue depth/age thresholds throttle new merge dequeues, not executor dispatch; executor lane continues scheduling unrelated work.
3) Impact on reliability invariants and backstops
| Invariant / backstop | Status under proposal | Notes |
|---|---|---|
FN-5147 autoMerge:false terminal-until-merged |
Preserved unchanged | in-review remains terminal for human merge when auto-merge disabled; merge-request lane cannot mutate lifecycle backward. |
| FN-5704 resume-limbo escalation | Preserved via new mechanism | Existing FN-5704 remains executor-phase (self-healing.ts:2345-2396); merger lane adds analogous no-progress cap (running/retrying age + attempt ceiling -> exhausted without executor rebound). |
| Self-healing stuck-budget terminalization | Preserved via new mechanism | checkStuckBudget/reclaim handoff paths that currently park via review handoff (self-healing.ts stuck-budget/reclaim paths) must emit handoff-accepted marker and only enqueue merge when policy allows. |
Recovery policy (recovery-policy.ts) bounded retries |
Preserved via new mechanism | computeRecoveryDecision(...) remains authoritative for executor/triage retries; merge retries use merge-request state transitions instead of column rebounds. |
Move-task hard-cancel (in-progress -> todo) |
Preserved unchanged | Cancels executor session/subprocesses; if handoff not accepted, no merge request exists; if accepted, cancel path explicitly tombstones/cancels pending merge request. |
Scheduler dependency semantics (in-review satisfies deps) |
Requires modification | Migration from dep.column === in-review checks (scheduler.ts:170-174, 1249-1251) to explicit handoff-complete marker. |
| In-review overlap leases | Requires modification | Migration from setActiveScopeLease(..., "in-review") (scheduler.ts:1194-1209) to executor-phase-only scope leasing; merger gets independent merge locks. |
| Run-audit continuity | Preserved via new mechanism | Existing events remain (task:resume-limbo-escalated, task move/handoff events) with additive seam events (task:completion-handoff-accepted, merge:request-enqueued/dequeued/cancelled). |
| File-scope/squash invariants | Preserved unchanged | Merge policy and squash/file-scope guards remain in merger pipeline. |
Enforcement-point migration notes
- Dependency satisfaction: cutover from implicit
column === in-reviewto explicitcompletion_handoff_acceptedindicator; during dual-run, accept either signal with audit diff logging. - Overlap leasing: stop leasing in-review scopes once handoff accepted; keep backward-compatible shadow metrics on old/new blocker decisions before final cutover.
- Retry ownership: transient merge classification continues (
transient-merge-error-classifier.ts), but actions mutate merge-request state (queued/running/retrying/exhausted) instead of forcingin-review -> todorebound.
4) Migration/rollout, test strategy, non-goals
Phased rollout
- Add merge-request contract behind feature flag (write-only shadow mode).
- Dual-write/dual-observe: existing coupling path remains authoritative; compare outcomes.
- Cutover dequeue authority to merge-request queue; retain fallback.
- Remove legacy implicit handoff wiring once parity is proven.
Each phase must be independently revertable.
Test strategy (follow-on implementation)
Add/extend reliability-interaction tests under packages/engine/src/__tests__/reliability-interactions/:
- executor completion while merge lane busy still allows unrelated executor dispatch
- transient merge retry does not block executor lane
autoMerge:falseremains terminal-until-merged- FN-5704 no-progress resume-limbo non-oscillation across seam
- move-task hard-cancel during handoff is deterministic
- phantom/already-merged advance path remains non-stalling
Per FN-5048: narrow seams, in-memory fakes, fake timers, no slow polling/network tests.
Non-goals
- No change to AI merge arbitration policy.
- No change to PR monitor external integration semantics.
- No worktree pooling redesign.
- No dashboard UX redesign beyond lifecycle truth required by implementation.
5) Open questions and CEO ratification
Open questions
- Should dependency satisfaction switch from
column === in-reviewto an explicit completion-handoff marker? - Should merge-request queue be persisted in existing task row JSON vs dedicated table?
- What is the authoritative cancel behavior when user moves a review task back to todo after handoff acceptance?
- Should merge back-pressure be global or branch-group aware by default?
Recommendation for ratification
Recommend: explicit persisted merge-request queue/table + explicit handoff-accepted marker as the executor/merger seam.
Rationale: strongest observability, replayability after restarts, clean ownership boundaries, and reduced coupling to overloaded in-review semantics.
CEO Ratification Gate
Per CEO directive, implementation must be coordinated with agent-4ec1ff85 before any engine change lands. This RFC is the gating artifact.
FN-5746 Ratification Response (explicit):
- Dedicated persisted merge-request record/table (not task-row JSON): APPROVED.
- Phase-1 explicit persisted
completion_handoff_acceptedmarker keyed totaskId: APPROVED.
FN-5756 Ratification Addendum (explicit, 2026-05-30):
- Q1 dual-run dependency signal: APPROVED to treat dependency as satisfied when either (
dep.column === "in-review") or a persistedcompletion_handoff_acceptedmarker is present; require audit diff logging whenever old/new signals disagree during dual-run. - Q4 merge back-pressure default scope: APPROVED as branch-group aware by default (instead of global), with global throttles retained only as safety caps.
Follow-on task filed: FN-5723 (unblocked by this ratification).