fix(FN-5256): route merger-handoff reconcile through hardened gates

Code-review follow-up to cf0101be7. The merger integration worktree path
was still calling bare `activeSessionRegistry.reconcileStaleSelfOwned` for
same-task entries, bypassing the minIdleMs window introduced by the main
fix. A merger-handoff that races a warming-down session could clear a
registry entry < 5s old. Route through `reconcileSelfOwnedActiveSessionForRemoval`
with the `executingTaskLock` process probe so all reconcile sites enforce
the same liveness contract. Test updated to backdate `registeredAt`,
matching the pattern used for the other reliability-interactions tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-21 09:17:54 -07:00
parent cf0101be7c
commit b4fc54bb20
2 changed files with 20 additions and 4 deletions

View File

@@ -304,6 +304,8 @@ describe("acquireReuseHandoff", () => {
kind: "executor",
ownerKey: "FN-5279",
});
// FN-5256: backdate so the new min-idle window doesn't refuse the reconcile.
(activeSessionRegistry.lookupByPath("/tmp/task-worktree") as any).registeredAt = 0;
await acquireReuseHandoff({
task: await store.getTask("FN-5279"),

View File

@@ -8,7 +8,11 @@ import type {
Task,
TaskStore,
} from "@fusion/core";
import { activeSessionRegistry, executingTaskLock } from "./active-session-registry.js";
import {
activeSessionRegistry,
executingTaskLock,
reconcileSelfOwnedActiveSessionForRemoval,
} from "./active-session-registry.js";
import { attemptBranchAutocorrect } from "./branch-autocorrect.js";
import { MeshLeaseManager } from "./mesh-lease-manager.js";
import {
@@ -294,14 +298,24 @@ export async function acquireReuseHandoff(input: ReuseHandoffInput): Promise<Han
const activeRecord = activeSessionRegistry.lookupByPath(worktreePath);
if (activeRecord) {
if (activeRecord.taskId === input.task.id && !executingTaskLock.has(input.task.id)) {
const reconciled = activeSessionRegistry.reconcileStaleSelfOwned(worktreePath, input.task.id);
if (!reconciled.reconciled) {
if (activeRecord.taskId === input.task.id) {
// FN-5256: route through the hardened helper so the minIdleMs window and
// processActiveProbe (executingTaskLock) gates apply — bare
// `reconcileStaleSelfOwned` would race a warming-down session.
const outcome = reconcileSelfOwnedActiveSessionForRemoval(
activeSessionRegistry,
worktreePath,
input.task.id,
() => false,
{ processActiveProbe: (probeTaskId) => executingTaskLock.has(probeTaskId) },
);
if (outcome.action !== "reconciled") {
throw new MergeHandoffRefusedError("active-session-binding", "active-session-present", {
taskId: input.task.id,
worktreePath,
activeRecord,
executingTaskLockHeld: executingTaskLock.has(input.task.id),
reconcileOutcome: outcome.action,
});
}
} else {