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:
@@ -304,6 +304,8 @@ describe("acquireReuseHandoff", () => {
|
|||||||
kind: "executor",
|
kind: "executor",
|
||||||
ownerKey: "FN-5279",
|
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({
|
await acquireReuseHandoff({
|
||||||
task: await store.getTask("FN-5279"),
|
task: await store.getTask("FN-5279"),
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ import type {
|
|||||||
Task,
|
Task,
|
||||||
TaskStore,
|
TaskStore,
|
||||||
} from "@fusion/core";
|
} 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 { attemptBranchAutocorrect } from "./branch-autocorrect.js";
|
||||||
import { MeshLeaseManager } from "./mesh-lease-manager.js";
|
import { MeshLeaseManager } from "./mesh-lease-manager.js";
|
||||||
import {
|
import {
|
||||||
@@ -294,14 +298,24 @@ export async function acquireReuseHandoff(input: ReuseHandoffInput): Promise<Han
|
|||||||
|
|
||||||
const activeRecord = activeSessionRegistry.lookupByPath(worktreePath);
|
const activeRecord = activeSessionRegistry.lookupByPath(worktreePath);
|
||||||
if (activeRecord) {
|
if (activeRecord) {
|
||||||
if (activeRecord.taskId === input.task.id && !executingTaskLock.has(input.task.id)) {
|
if (activeRecord.taskId === input.task.id) {
|
||||||
const reconciled = activeSessionRegistry.reconcileStaleSelfOwned(worktreePath, input.task.id);
|
// FN-5256: route through the hardened helper so the minIdleMs window and
|
||||||
if (!reconciled.reconciled) {
|
// 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", {
|
throw new MergeHandoffRefusedError("active-session-binding", "active-session-present", {
|
||||||
taskId: input.task.id,
|
taskId: input.task.id,
|
||||||
worktreePath,
|
worktreePath,
|
||||||
activeRecord,
|
activeRecord,
|
||||||
executingTaskLockHeld: executingTaskLock.has(input.task.id),
|
executingTaskLockHeld: executingTaskLock.has(input.task.id),
|
||||||
|
reconcileOutcome: outcome.action,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user