feat(FN-4648): complete Step 2 — auto-finalize landed-content self-healing paths

Fusion-Task-Id: FN-4648
Fusion-Task-Lineage: 82289e69-7e72-40e4-acaf-b98ad82ad354
This commit is contained in:
Fusion
2026-05-15 11:06:26 -07:00
committed by gsxdsm
parent 12bfe63aa0
commit 8e865dcc65

View File

@@ -17,7 +17,7 @@ import { exec, execSync } from "node:child_process";
import { promisify } from "node:util";
import { existsSync, mkdirSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
import { isAbsolute, join, relative, resolve } from "node:path";
import { getInReviewStallReason, getStalePausedReviewSignal, getTaskMergeBlocker, isEphemeralAgent, type AgentStore, type TaskStore, type Settings, type Task, type MergeDetails } from "@fusion/core";
import { getInReviewStallReason, getStalePausedReviewSignal, getTaskHardMergeBlocker, getTaskMergeBlocker, isEphemeralAgent, type AgentStore, type TaskStore, type Settings, type Task, type MergeDetails } from "@fusion/core";
import type { MeshLeaseManager } from "./mesh-lease-manager.js";
import { createLogger } from "./logger.js";
import { getRegisteredWorktreePaths, isUsableTaskWorktree, scanIdleWorktrees, scanOrphanedBranches } from "./worktree-pool.js";
@@ -2822,7 +2822,6 @@ export class SelfHealingManager {
const mergedButNotDone = tasks.filter((t) =>
t.column === "in-review" &&
!t.paused &&
t.mergeDetails?.mergeConfirmed === true,
);
@@ -2833,26 +2832,30 @@ export class SelfHealingManager {
let recovered = 0;
for (const task of mergedButNotDone) {
try {
const blocker = getTaskMergeBlocker({
const hardBlocker = getTaskHardMergeBlocker({
...task,
status: undefined,
error: undefined,
steps: task.steps ?? [],
workflowStepResults: task.workflowStepResults,
});
if (blocker) {
if (hardBlocker) {
await this.store.updateTask(task.id, {
status: "failed",
error: `Merge confirmed but finalization blocked: ${blocker}`,
error: `Merge confirmed but finalization blocked: ${hardBlocker}`,
});
await this.store.logEntry(
task.id,
`Auto-recovery skipped: merge confirmed but finalization blocked — ${blocker}`,
`Auto-recovery skipped: merge confirmed but finalization blocked — ${hardBlocker}`,
);
continue;
}
const clearedFlags = {
paused: Boolean(task.paused),
status: Boolean(task.status),
error: Boolean(task.error),
};
await this.store.updateTask(task.id, {
paused: false,
status: null,
error: null,
mergeRetries: 0,
@@ -2860,8 +2863,29 @@ export class SelfHealingManager {
await this.store.moveTask(task.id, "done");
await this.store.logEntry(
task.id,
"Auto-recovered: merge already confirmed — moved from in-review to done",
`Auto-finalized from in-review/paused: content proven via mergeConfirmed metadata. Cleared soft state paused=${clearedFlags.paused}, status=${clearedFlags.status}, error=${clearedFlags.error}`,
);
try {
const auditor = createRunAuditor(this.store, {
runId: generateSyntheticRunId("self-heal", task.id),
agentId: "self-healing",
taskId: task.id,
taskLineageId: task.lineageId,
phase: "recover-merged-review",
});
await auditor.database({
type: "task:auto-recover-finalize-already-on-main",
target: task.id,
metadata: {
mergeSha: task.mergeDetails?.commitSha ?? null,
baseBranch: task.baseBranch || task.executionStartBranch || "main",
clearedFlags,
},
});
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : String(err);
log.warn(`recoverMergedReviewTasks: failed to record run-audit event for ${task.id}: ${errorMessage}`);
}
log.log(`Recovered merged task ${task.id}: moved to done`);
recovered++;
} catch (err: unknown) { const errorMessage = err instanceof Error ? err.message : String(err);
@@ -3043,7 +3067,6 @@ export class SelfHealingManager {
const tasks = await this.store.listTasks({ column: "in-review", slim: true });
const candidates = tasks.filter((task) =>
task.column === "in-review" &&
!task.paused &&
task.status === "failed" &&
task.scopeOverride !== true &&
task.mergeDetails?.mergeConfirmed !== true &&
@@ -3094,27 +3117,31 @@ export class SelfHealingManager {
resolutionStrategy: "orphan-discard-no-op",
};
const blocker = getTaskMergeBlocker({
const hardBlocker = getTaskHardMergeBlocker({
...task,
status: undefined,
error: undefined,
steps: task.steps ?? [],
workflowStepResults: task.workflowStepResults,
});
if (blocker) {
if (hardBlocker) {
await this.store.updateTask(task.id, {
status: "failed",
error: `Merge confirmed but finalization blocked: ${blocker}`,
error: `Merge confirmed but finalization blocked: ${hardBlocker}`,
mergeDetails,
});
await this.store.logEntry(
task.id,
`Auto-recovery parked task in in-review: merged content found on ${baseBranch} (${landed.sha.slice(0, 8)}) but finalization blocked — ${blocker}`,
`Auto-recovery parked task in in-review: merged content found on ${baseBranch} (${landed.sha.slice(0, 8)}) but finalization blocked — ${hardBlocker}`,
);
continue;
}
const clearedFlags = {
paused: Boolean(task.paused),
status: Boolean(task.status),
error: Boolean(task.error),
};
await this.store.updateTask(task.id, {
paused: false,
status: null,
error: null,
mergeRetries: 0,
@@ -3123,9 +3150,31 @@ export class SelfHealingManager {
await this.store.moveTask(task.id, "done");
await this.store.logEntry(
task.id,
`Auto-recovered: FileScopeViolationError treated as orphan-only — task work already on main (commit: ${landed.sha.slice(0, 8)} via ${landed.strategy}; rejected orphans: ${orphanFiles.join(", ")})`,
`Auto-finalized from in-review/paused: content proven on ${baseBranch} (${landed.sha.slice(0, 8)}). Cleared soft state paused=${clearedFlags.paused}, status=${clearedFlags.status}, error=${clearedFlags.error}`,
);
await this.cleanupWorktreeOnly(task);
try {
const auditor = createRunAuditor(this.store, {
runId: generateSyntheticRunId("self-heal", task.id),
agentId: "self-healing",
taskId: task.id,
taskLineageId: task.lineageId,
phase: "recover-orphan-only-scope-violations",
});
await auditor.database({
type: "task:auto-recover-finalize-already-on-main",
target: task.id,
metadata: {
mergeSha: landed.sha,
baseBranch,
mergeStrategy: landed.strategy,
clearedFlags,
},
});
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : String(err);
log.warn(`recoverOrphanOnlyScopeViolations: failed to record run-audit event for ${task.id}: ${errorMessage}`);
}
recovered++;
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : String(err);
@@ -3204,27 +3253,31 @@ export class SelfHealingManager {
prNumber: task.prInfo?.number,
};
const blocker = getTaskMergeBlocker({
const hardBlocker = getTaskHardMergeBlocker({
...task,
status: undefined,
error: undefined,
steps: task.steps ?? [],
workflowStepResults: task.workflowStepResults,
});
if (blocker) {
if (hardBlocker) {
await this.store.updateTask(task.id, {
status: "failed",
error: `Merge confirmed but finalization blocked: ${blocker}`,
error: `Merge confirmed but finalization blocked: ${hardBlocker}`,
mergeDetails,
});
await this.store.logEntry(
task.id,
`Auto-recovery parked task in in-review: merged content found on ${baseBranch} (${landed.sha.slice(0, 8)}) but finalization blocked — ${blocker}`,
`Auto-recovery parked task in in-review: merged content found on ${baseBranch} (${landed.sha.slice(0, 8)}) but finalization blocked — ${hardBlocker}`,
);
continue;
}
const clearedFlags = {
paused: Boolean(task.paused),
status: Boolean(task.status),
error: Boolean(task.error),
};
await this.store.updateTask(task.id, {
paused: false,
status: null,
error: null,
mergeRetries: 0,
@@ -3234,7 +3287,7 @@ export class SelfHealingManager {
await this.store.moveTask(task.id, "done");
await this.store.logEntry(
task.id,
`Auto-recovered: phantom-merge-guard false positive — content found on ${baseBranch} at ${landed.sha.slice(0, 8)} via ${landed.strategy}`,
`Auto-finalized from in-review/paused: content proven on ${baseBranch} (${landed.sha.slice(0, 8)}). Cleared soft state paused=${clearedFlags.paused}, status=${clearedFlags.status}, error=${clearedFlags.error}`,
);
await this.reconcileCompletedTask(task.id, { worktreeHint });
try {
@@ -3246,13 +3299,14 @@ export class SelfHealingManager {
phase: "recover-already-merged-review",
});
await auditor.database({
type: "task:auto-recover-already-merged",
type: "task:auto-recover-finalize-already-on-main",
target: task.id,
metadata: {
mergeSha: landed.sha,
mergeStrategy: landed.strategy,
baseBranch,
mergeRetries: task.mergeRetries ?? 0,
clearedFlags,
},
});
} catch (err: unknown) {