fix(FN-8778): preserve planning lock receiver

Fusion-Task-Id: FN-8778
This commit is contained in:
gsxdsm
2026-08-04 01:06:44 -07:00
parent 9939897aab
commit dd2cb0c8ba
3 changed files with 23 additions and 2 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Prevent planning finalization from crashing after PROMPT.md writes.
category: fix
dev: Preserves the TaskStore receiver when invoking the planning lifecycle lock.

View File

@@ -3278,9 +3278,23 @@ describe("specified triage recovery", () => {
it("fences a stale finalizer after dependency re-seed commits before lifecycle-lock acquisition", async () => { it("fences a stale finalizer after dependency re-seed commits before lifecycle-lock acquisition", async () => {
const stalePlannerSnapshot = createTriageTask({ status: "planning" }); const stalePlannerSnapshot = createTriageTask({ status: "planning" });
const store = createMockStore({ const store = createMockStore({
withPlanningLifecycleLock: vi.fn(async (_id, callback) => callback()),
getTask: vi.fn().mockResolvedValue({ ...stalePlannerSnapshot, status: "needs-replan" }), getTask: vi.fn().mockResolvedValue({ ...stalePlannerSnapshot, status: "needs-replan" }),
}); });
/*
* FNXC:PlanningDependencyReseed 2026-08-04-08:04:
* Finalization may feature-detect the lifecycle method, but it must invoke
* that extracted method with TaskStore as its receiver. The production
* method reads instance state (`asyncLayer`); an arrow-function mock hid the
* unbound-call crash that stranded FN-8778 after every successful prompt write.
*/
store.withPlanningLifecycleLock = vi.fn(async function (
this: TaskStore,
_id: string,
callback: () => Promise<unknown>,
) {
expect(this).toBe(store);
return await callback();
}) as typeof store.withPlanningLifecycleLock;
const processor = new TriageProcessor(store, rootDir); const processor = new TriageProcessor(store, rootDir);
await (processor as unknown as { await (processor as unknown as {

View File

@@ -4138,7 +4138,7 @@ export class TriageProcessor {
*/ */
try { try {
return lifecycleLock return lifecycleLock
? await lifecycleLock(task.id, finalizeUnderLock) ? await this.store.withPlanningLifecycleLock(task.id, finalizeUnderLock)
: await finalizeUnderLock(); : await finalizeUnderLock();
} finally { } finally {
this.finalizing.delete(task.id); this.finalizing.delete(task.id);