FN-5830: re-land shared branch promotion gate and API
Reintroduce shared-branch-group completion gating and promotion endpoints with reliability coverage. - add GroupMergeCoordinator promotion gate behavior and shared branch group promotion logic updates - expose promotion flow wiring through engine index and project engine APIs - expand coordinator tests and add reliability interaction coverage for branch-group promotion - document the new reliability backstop and add a changeset for @runfusion/fusion Files changed: .changeset/fn-5830-branch-group-promotion.md | 5 + AGENTS.md | 1 + docs/architecture.md | 3 +- .../src/__tests__/group-merge-coordinator.test.ts | 170 ++++++++++++++++++- .../branch-group-promotion.test.ts | 166 +++++++++++++++++++ packages/engine/src/group-merge-coordinator.ts | 183 ++++++++++++++++++++- packages/engine/src/index.ts | 4 + packages/engine/src/project-engine.ts | 37 +++++ 8 files changed, 566 insertions(+), 3 deletions(-) Fusion-Task-Id: FN-5830 Fusion-Task-Lineage: e9823f51-df0e-4da8-8f51-58dfefdc293d
This commit is contained in:
@@ -27,6 +27,7 @@ import { CronRunner, createAiPromptExecutor } from "./cron-runner.js";
|
||||
import type { RoutineRunner } from "./routine-runner.js";
|
||||
import { aiMergeTask, sweepStaleAutostashes, VerificationError } from "./merger.js";
|
||||
import { runAiMerge } from "./merger-ai.js";
|
||||
import { promoteBranchGroup } from "./group-merge-coordinator.js";
|
||||
import { PRIORITY_MERGE } from "./concurrency.js";
|
||||
import { runtimeLog } from "./logger.js";
|
||||
import type { HeartbeatTriggerScheduler } from "./agent-heartbeat.js";
|
||||
@@ -1782,6 +1783,39 @@ export class ProjectEngine {
|
||||
}
|
||||
|
||||
const mergeStrategy = this.options.getMergeStrategy?.(settings) ?? "direct";
|
||||
const promotionSettings = {
|
||||
autoMerge: settings.autoMerge,
|
||||
globalPause: settings.globalPause,
|
||||
enginePaused: settings.enginePaused,
|
||||
mergeStrategy: settings.mergeStrategy,
|
||||
integrationBranch: settings.integrationBranch,
|
||||
baseBranch: settings.baseBranch,
|
||||
};
|
||||
const attemptBranchGroupPromotion = async (taskForPromotion: Task | null): Promise<void> => {
|
||||
if (!taskForPromotion || !isSharedBranchGroupMemberIntegration(taskForPromotion)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await promoteBranchGroup({
|
||||
store,
|
||||
rootDir: cwd,
|
||||
groupId: taskForPromotion.branchContext!.groupId,
|
||||
settings: promotionSettings,
|
||||
recordAudit: async (event) => {
|
||||
await store.recordRunAuditEvent({
|
||||
domain: event.domain as any,
|
||||
mutationType: event.mutationType,
|
||||
target: event.target,
|
||||
metadata: event.metadata,
|
||||
} as any);
|
||||
},
|
||||
});
|
||||
} catch (promotionError) {
|
||||
runtimeLog.warn(
|
||||
`Branch-group promotion evaluation failed for ${taskId}: ${promotionError instanceof Error ? promotionError.message : String(promotionError)}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (mergeStrategy === "pull-request" && this.options.processPullRequestMerge) {
|
||||
this.activeMergeTaskId = taskId;
|
||||
@@ -1807,6 +1841,7 @@ export class ProjectEngine {
|
||||
mergeTargetBranch: mergedTask.mergeDetails?.mergeTargetBranch,
|
||||
} as MergeResult);
|
||||
}
|
||||
await attemptBranchGroupPromotion(mergedTask);
|
||||
} else if (result === "waiting") {
|
||||
runtimeLog.log(`${manualResolver ? "Manual" : "Auto"}-merge PR waiting: ${taskId}`);
|
||||
}
|
||||
@@ -1882,6 +1917,8 @@ export class ProjectEngine {
|
||||
if (latestTask?.mergeRetries && latestTask.mergeRetries > 0) {
|
||||
await store.updateTask(taskId, { mergeRetries: 0 });
|
||||
}
|
||||
|
||||
await attemptBranchGroupPromotion(latestTask);
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
this.activeMergeSession = null;
|
||||
|
||||
Reference in New Issue
Block a user