feat(FN-4518): complete Step 4 — carry rebase base through self-healing

Fusion-Task-Id: FN-4518
Fusion-Task-Lineage: 48749dec-b2c9-484b-a13e-ffacc1fa9014
This commit is contained in:
Fusion
2026-05-14 16:53:47 -07:00
committed by gsxdsm
parent abb7838d3c
commit d9f81099fb
3 changed files with 128 additions and 0 deletions

View File

@@ -5443,6 +5443,108 @@ describe("recoverDoneTaskMergeMetadata", () => {
manager.stop();
});
it("populates rebaseBaseSha from landed commit when missing", async () => {
const store = createMockStore();
const manager = new SelfHealingManager(store, { rootDir: "/tmp/test-project" });
(store.listTasks as ReturnType<typeof vi.fn>).mockResolvedValue([
{
id: "FN-4518-A",
column: "done",
paused: false,
mergeDetails: { commitSha: "merge1", mergeConfirmed: false },
},
]);
vi.spyOn(manager as any, "findLandedTaskCommit").mockResolvedValue({
sha: "merge1",
subject: "fix(FN-4518): landed",
filesChanged: 2,
insertions: 4,
deletions: 1,
rebaseBaseSha: "base1",
});
const repaired = await manager.recoverDoneTaskMergeMetadata();
expect(repaired).toBe(1);
expect(store.updateTask).toHaveBeenCalledWith("FN-4518-A", {
mergeDetails: expect.objectContaining({
commitSha: "merge1",
rebaseBaseSha: "base1",
}),
});
manager.stop();
});
it("does not overwrite existing rebaseBaseSha during reconciliation", async () => {
const store = createMockStore();
const manager = new SelfHealingManager(store, { rootDir: "/tmp/test-project" });
(store.listTasks as ReturnType<typeof vi.fn>).mockResolvedValue([
{
id: "FN-4518-B",
column: "done",
paused: false,
mergeDetails: { commitSha: "merge1", mergeConfirmed: false, rebaseBaseSha: "existing-base" },
},
]);
vi.spyOn(manager as any, "findLandedTaskCommit").mockResolvedValue({
sha: "merge1",
subject: "fix(FN-4518): landed",
filesChanged: 2,
insertions: 4,
deletions: 1,
rebaseBaseSha: "incoming-base",
});
const repaired = await manager.recoverDoneTaskMergeMetadata();
expect(repaired).toBe(1);
expect(store.updateTask).toHaveBeenCalledWith("FN-4518-B", {
mergeDetails: expect.objectContaining({
rebaseBaseSha: "existing-base",
}),
});
manager.stop();
});
it("keeps rebaseBaseSha undefined when neither stored nor landed provides it", async () => {
const store = createMockStore();
const manager = new SelfHealingManager(store, { rootDir: "/tmp/test-project" });
(store.listTasks as ReturnType<typeof vi.fn>).mockResolvedValue([
{
id: "FN-4518-C",
column: "done",
paused: false,
mergeDetails: { commitSha: "merge1", mergeConfirmed: false },
},
]);
vi.spyOn(manager as any, "findLandedTaskCommit").mockResolvedValue({
sha: "merge1",
subject: "fix(FN-4518): landed",
filesChanged: 2,
insertions: 4,
deletions: 1,
});
const repaired = await manager.recoverDoneTaskMergeMetadata();
expect(repaired).toBe(1);
expect(store.updateTask).toHaveBeenCalledWith("FN-4518-C", {
mergeDetails: expect.not.objectContaining({
rebaseBaseSha: expect.any(String),
}),
});
manager.stop();
});
it("FN-3862: confirmed task with unreachable stored SHA is preserved with warning", async () => {
const store = createMockStore();
const manager = new SelfHealingManager(store, { rootDir: "/tmp/test-project" });

View File

@@ -238,6 +238,7 @@ interface LandedTaskCommit {
filesChanged?: number;
insertions?: number;
deletions?: number;
rebaseBaseSha?: string;
}
type AlreadyMergedDetectionStrategy = "trailer" | "ancestry" | "patch-id" | "tree-equal";
@@ -2580,6 +2581,7 @@ export class SelfHealingManager {
if (landedCommit) {
const mergeDetails: MergeDetails = {
commitSha: landedCommit.sha,
rebaseBaseSha: landedCommit.rebaseBaseSha,
filesChanged: landedCommit.filesChanged,
insertions: landedCommit.insertions,
deletions: landedCommit.deletions,
@@ -2704,6 +2706,7 @@ export class SelfHealingManager {
insertions: nextInsertions,
deletions: nextDeletions,
mergeCommitMessage: task.mergeDetails?.mergeCommitMessage ?? landed.subject,
rebaseBaseSha: task.mergeDetails?.rebaseBaseSha ?? landed.rebaseBaseSha,
mergedAt: task.mergeDetails?.mergedAt ?? new Date().toISOString(),
prNumber: task.prInfo?.number,
},
@@ -2756,6 +2759,7 @@ export class SelfHealingManager {
insertions: landedStats.insertions,
deletions: landedStats.deletions,
mergeCommitMessage: landed.subject,
rebaseBaseSha: task.mergeDetails?.rebaseBaseSha ?? landed.rebaseBaseSha,
mergedAt: task.mergeDetails?.mergedAt ?? new Date().toISOString(),
mergeConfirmed: true,
prNumber: task.prInfo?.number,
@@ -2898,6 +2902,7 @@ export class SelfHealingManager {
if (landedCommit) {
const mergeDetails: MergeDetails = {
commitSha: landedCommit.sha,
rebaseBaseSha: landedCommit.rebaseBaseSha,
filesChanged: landedCommit.filesChanged,
insertions: landedCommit.insertions,
deletions: landedCommit.deletions,