FN-6276: suppress repeated queued overlap recovery logs

Deduplicate self-healing log entries for queued tasks that remain blocked by the same active file-scope overlap.

- Track the last active overlap blocker logged per task and skip duplicate preserved-queued log entries across self-healing passes.
- Clear the memo when blockers change, resolve, or the manager stops so legitimate future recoveries are still logged.
- Cover unchanged blockers, blocker changes, resolved blockers, stop/reset behavior, and FN-5488 stale-blockedBy preservation with regression tests.

Files changed:
 packages/engine/src/__tests__/self-healing.test.ts | 123 +++++++++++++++++++++
 packages/engine/src/self-healing.ts                |  61 ++++++++--
 2 files changed, 177 insertions(+), 7 deletions(-)

Fusion-Task-Id: FN-6276

Fusion-Task-Lineage: 169d4402-ec4e-4fa9-8225-2f545c11a3f7
This commit is contained in:
gsxdsm
2026-06-11 22:25:41 -07:00
parent d73d8f4036
commit 6f5be09edb
2 changed files with 177 additions and 7 deletions

View File

@@ -6941,6 +6941,129 @@ describe("FN-4538 overlapBlockedBy self-healing", () => {
manager.stop();
});
it("FN-6276: clearStaleBlockedBy logs unchanged active overlap blocker only once across passes", async () => {
const overlapBlocker = makeTask("FN-ACTIVE", { column: "in-progress" });
const target = makeTask("FN-TARGET", {
column: "todo",
status: "queued",
blockedBy: undefined,
overlapBlockedBy: "FN-ACTIVE",
dependencies: [],
});
const store = makeStore([target, overlapBlocker]);
const manager = new SelfHealingManager(store, { rootDir: "/tmp/test-project" });
const message = "Auto-recovered: preserved queued status — still blocked by file scope overlap with FN-ACTIVE";
await manager.clearStaleBlockedBy();
await manager.clearStaleBlockedBy();
await manager.clearStaleBlockedBy();
expect(store.updateTask).toHaveBeenCalledTimes(3);
expect(store.updateTask).toHaveBeenNthCalledWith(1, "FN-TARGET", { blockedBy: null, status: "queued" });
expect((store.logEntry as ReturnType<typeof vi.fn>).mock.calls.filter((call) => call[0] === "FN-TARGET" && call[1] === message)).toHaveLength(1);
manager.stop();
});
it("FN-6276: clearStaleBlockedBy logs again when active overlap blocker changes", async () => {
const overlapBlockerA = makeTask("FN-ACTIVE-A", { column: "in-progress" });
const overlapBlockerB = makeTask("FN-ACTIVE-B", { column: "in-progress" });
const target = makeTask("FN-TARGET", {
column: "todo",
status: "queued",
blockedBy: undefined,
overlapBlockedBy: "FN-ACTIVE-A",
dependencies: [],
});
const store = makeStore([target, overlapBlockerA, overlapBlockerB]);
const manager = new SelfHealingManager(store, { rootDir: "/tmp/test-project" });
await manager.clearStaleBlockedBy();
target.overlapBlockedBy = "FN-ACTIVE-B";
await manager.clearStaleBlockedBy();
expect(store.logEntry).toHaveBeenCalledWith(
"FN-TARGET",
"Auto-recovered: preserved queued status — still blocked by file scope overlap with FN-ACTIVE-A",
);
expect(store.logEntry).toHaveBeenCalledWith(
"FN-TARGET",
"Auto-recovered: preserved queued status — still blocked by file scope overlap with FN-ACTIVE-B",
);
expect((store.logEntry as ReturnType<typeof vi.fn>).mock.calls.filter((call) => call[0] === "FN-TARGET" && String(call[1]).includes("preserved queued status"))).toHaveLength(2);
manager.stop();
});
it("FN-6276: clearStaleBlockedBy resets preserved queued memo after blocker resolves", async () => {
const overlapBlocker = makeTask("FN-ACTIVE", { column: "in-progress" });
const target = makeTask("FN-TARGET", {
column: "todo",
status: "queued",
blockedBy: undefined,
overlapBlockedBy: "FN-ACTIVE",
dependencies: [],
});
const store = makeStore([target, overlapBlocker]);
const manager = new SelfHealingManager(store, { rootDir: "/tmp/test-project" });
const message = "Auto-recovered: preserved queued status — still blocked by file scope overlap with FN-ACTIVE";
await manager.clearStaleBlockedBy();
overlapBlocker.column = "done";
await manager.clearStaleBlockedBy();
target.status = null;
target.overlapBlockedBy = null;
await manager.clearStaleBlockedBy();
target.status = "queued";
target.overlapBlockedBy = "FN-ACTIVE";
overlapBlocker.column = "in-progress";
await manager.clearStaleBlockedBy();
expect((store.logEntry as ReturnType<typeof vi.fn>).mock.calls.filter((call) => call[0] === "FN-TARGET" && call[1] === message)).toHaveLength(2);
manager.stop();
});
it("FN-6276: stop clears preserved queued memo so next pass logs again", async () => {
const overlapBlocker = makeTask("FN-ACTIVE", { column: "in-progress" });
const target = makeTask("FN-TARGET", {
column: "todo",
status: "queued",
blockedBy: undefined,
overlapBlockedBy: "FN-ACTIVE",
dependencies: [],
});
const store = makeStore([target, overlapBlocker]);
const manager = new SelfHealingManager(store, { rootDir: "/tmp/test-project" });
const message = "Auto-recovered: preserved queued status — still blocked by file scope overlap with FN-ACTIVE";
await manager.clearStaleBlockedBy();
manager.stop();
await manager.clearStaleBlockedBy();
expect((store.logEntry as ReturnType<typeof vi.fn>).mock.calls.filter((call) => call[0] === "FN-TARGET" && call[1] === message)).toHaveLength(2);
manager.stop();
});
it("FN-6276: FN-5488 stale blockedBy overlap-preservation log is idempotent", async () => {
const staleBlocker = makeTask("FN-DONE", { column: "done" });
const overlapBlocker = makeTask("FN-ACTIVE", { column: "in-progress" });
const target = makeTask("FN-TARGET", {
column: "todo",
status: "queued",
blockedBy: "FN-DONE",
overlapBlockedBy: "FN-ACTIVE",
dependencies: [],
});
const store = makeStore([target, staleBlocker, overlapBlocker]);
const manager = new SelfHealingManager(store, { rootDir: "/tmp/test-project" });
const message = "Auto-recovered (FN-5488): preserved queued status — blocker=FN-DONE blockerStatus=none reason=blocker-done; still blocked by file scope overlap with FN-ACTIVE";
await manager.clearStaleBlockedBy();
await manager.clearStaleBlockedBy();
expect(store.updateTask).toHaveBeenCalledTimes(2);
expect((store.logEntry as ReturnType<typeof vi.fn>).mock.calls.filter((call) => call[0] === "FN-TARGET" && call[1] === message)).toHaveLength(1);
manager.stop();
});
it("FN-4538: clearStaleBlockedBy clears overlapBlockedBy when overlap blocker is done", async () => {
const overlapBlocker = makeTask("FN-DONE", { column: "done" });
const target = makeTask("FN-TARGET", {

View File

@@ -653,6 +653,7 @@ export class SelfHealingManager {
private finalizeUnprovenWarned = new Set<string>();
private metaResolvedSkipAuditMemo = new Map<string, string>();
private metaStalledSkipAuditMemo = new Map<string, string>();
private preservedQueuedOverlapLogged = new Map<string, string>();
private maintenanceTickCounter = 0;
private readonly processBootStartedAt = Date.now();
private dependencyBlockedTodoReporter: DependencyBlockedTodoReporter | null = null;
@@ -1098,6 +1099,7 @@ export class SelfHealingManager {
this.finalizeUnprovenWarned.clear();
this.metaResolvedSkipAuditMemo.clear();
this.metaStalledSkipAuditMemo.clear();
this.preservedQueuedOverlapLogged.clear();
log.log("Stopped");
}
@@ -4018,6 +4020,18 @@ export class SelfHealingManager {
memo.delete(taskId);
}
private shouldLogPreservedQueuedOverlap(taskId: string, overlapBlockedBy: string | null | undefined): overlapBlockedBy is string {
if (!overlapBlockedBy) return false;
const previous = this.preservedQueuedOverlapLogged.get(taskId);
if (previous === overlapBlockedBy) return false;
this.preservedQueuedOverlapLogged.set(taskId, overlapBlockedBy);
return true;
}
private clearPreservedQueuedOverlapMemo(taskId: string): void {
this.preservedQueuedOverlapLogged.delete(taskId);
}
async autoArchiveResolvedMetaTasks(reboundedTargets?: Set<string>): Promise<number> {
const tasks = await this.store.listTasks({ slim: false, includeArchived: true });
const byId = new Map(tasks.map((task) => [task.id.toUpperCase(), task]));
@@ -4354,7 +4368,10 @@ export class SelfHealingManager {
(task) => task.status === "queued" && (task.dependencies.length > 0 || Boolean(task.overlapBlockedBy)),
);
if (blockedTasks.length === 0 && queuedDependencyTasks.length === 0) return 0;
if (blockedTasks.length === 0 && queuedDependencyTasks.length === 0) {
this.preservedQueuedOverlapLogged.clear();
return 0;
}
const allTasks = await this.store.listTasks({ includeArchived: true });
const taskById = new Map(allTasks.map((task) => [task.id, task]));
@@ -4367,6 +4384,24 @@ export class SelfHealingManager {
for (const task of blockedTasks) candidates.set(task.id, task);
for (const task of queuedDependencyTasks) candidates.set(task.id, task);
for (const [taskId, lastLoggedBlockerId] of this.preservedQueuedOverlapLogged) {
const memoTask = taskById.get(taskId);
const memoOverlapBlocker = memoTask?.overlapBlockedBy ? taskById.get(memoTask.overlapBlockedBy) : undefined;
const memoHasActiveOverlapBlocker = Boolean(
memoOverlapBlocker
&& (memoOverlapBlocker.column === "in-progress" || (memoOverlapBlocker.column === "in-review" && !memoOverlapBlocker.paused)),
);
if (
!candidates.has(taskId)
|| memoTask?.column !== "todo"
|| memoTask.status !== "queued"
|| memoTask.overlapBlockedBy !== lastLoggedBlockerId
|| !memoHasActiveOverlapBlocker
) {
this.clearPreservedQueuedOverlapMemo(taskId);
}
}
for (const task of candidates.values()) {
const blockerId = task.blockedBy;
@@ -4462,26 +4497,36 @@ export class SelfHealingManager {
if (reason) {
try {
let didRecover = false;
if (todoTaskIds.has(task.id)) {
if (unresolvedDeps.length > 0) {
this.clearPreservedQueuedOverlapMemo(task.id);
const nextBlocker = unresolvedDeps[0]!;
if (nextBlocker === blockerId) {
continue;
}
await this.store.updateTask(task.id, { blockedBy: nextBlocker, status: "queued" });
await this.store.logEntry(task.id, `Auto-recovered (FN-5488): refreshed stale blockedBy — blocker=${blockerId} blockerStatus=${blocker?.status ?? "none"} reason=${reasonCode ?? "unspecified"}; ${reason}; now blocked by ${nextBlocker}`);
didRecover = true;
} else if (hasActiveOverlapBlocker) {
await this.store.updateTask(task.id, { blockedBy: null, status: "queued" });
await this.store.logEntry(task.id, `Auto-recovered (FN-5488): preserved queued status — blocker=${blockerId} blockerStatus=${blocker?.status ?? "none"} reason=${reasonCode ?? "unspecified"}; still blocked by file scope overlap with ${task.overlapBlockedBy}`);
if (this.shouldLogPreservedQueuedOverlap(task.id, task.overlapBlockedBy)) {
await this.store.logEntry(task.id, `Auto-recovered (FN-5488): preserved queued status — blocker=${blockerId} blockerStatus=${blocker?.status ?? "none"} reason=${reasonCode ?? "unspecified"}; still blocked by file scope overlap with ${task.overlapBlockedBy}`);
didRecover = true;
}
} else {
this.clearPreservedQueuedOverlapMemo(task.id);
await this.store.updateTask(task.id, { blockedBy: null, overlapBlockedBy: null, status: null });
await this.store.logEntry(task.id, `Auto-recovered (FN-5488): cleared stale blockedBy — blocker=${blockerId} blockerStatus=${blocker?.status ?? "none"} reason=${reasonCode ?? "unspecified"}; ${reason}`);
didRecover = true;
}
} else {
this.clearPreservedQueuedOverlapMemo(task.id);
await this.store.updateTask(task.id, { blockedBy: null });
await this.store.logEntry(task.id, `Auto-recovered (FN-4091): cleared stale blockedBy — ${reason}`);
didRecover = true;
}
recovered++;
if (didRecover) recovered++;
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : String(err);
log.error(`Failed to clear stale blockedBy for ${task.id}: ${errorMessage}`);
@@ -4499,14 +4544,15 @@ export class SelfHealingManager {
try {
if (hasActiveOverlapBlocker) {
await this.store.updateTask(task.id, { blockedBy: null, status: "queued" });
await this.store.logEntry(task.id, `Auto-recovered: preserved queued status — still blocked by file scope overlap with ${task.overlapBlockedBy}`);
if (this.shouldLogPreservedQueuedOverlap(task.id, task.overlapBlockedBy)) {
await this.store.logEntry(task.id, `Auto-recovered: preserved queued status — still blocked by file scope overlap with ${task.overlapBlockedBy}`);
recovered++;
}
} else {
this.clearPreservedQueuedOverlapMemo(task.id);
// FN-5434: routine scheduler↔self-healing queued-status churn should stay silent; keep state cleanup only.
await this.store.updateTask(task.id, { blockedBy: null, overlapBlockedBy: null, status: null });
}
if (hasActiveOverlapBlocker) {
recovered++;
}
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : String(err);
log.error(`Failed to clear stale queued status for ${task.id}: ${errorMessage}`);
@@ -4515,6 +4561,7 @@ export class SelfHealingManager {
continue;
}
this.clearPreservedQueuedOverlapMemo(task.id);
const nextBlocker = unresolvedDeps[0] ?? null;
if (nextBlocker && task.blockedBy !== nextBlocker) {
try {