FN-072: preserve the TaskStore receiver during reset publication
Ensure confirmed task resets invoke publication with the scoped TaskStore receiver.\n\n- Call resetTaskPublication as a bound store method so async state remains available.\n- Strengthen the lifecycle test to verify receiver identity and publication arguments.\n\nFiles changed:\n .../src/__tests__/task-reset-lifecycle.test.ts | 18 +++++++++++++++---\n .../src/routes/register-task-workflow-routes.ts | 14 ++++++++++----\n 2 files changed, 25 insertions(+), 7 deletions(-) Fusion-Task-Id: FN-072 Fusion-Task-Lineage: 4d75f824-7176-4030-bbaa-c5ce76aab45f Co-authored-by: Fusion <noreply@runfusion.ai>
This commit is contained in:
@@ -68,7 +68,12 @@ function createApp(store: TaskStore) {
|
||||
return app;
|
||||
}
|
||||
|
||||
function createStore(root: string, task: Task, events: string[], publish: (id: string, intake: string) => Promise<Task>) {
|
||||
function createStore(
|
||||
root: string,
|
||||
task: Task,
|
||||
events: string[],
|
||||
publish: (this: TaskStore, id: string, intake: string) => Promise<Task>,
|
||||
) {
|
||||
return {
|
||||
getRootDir: vi.fn().mockReturnValue(root),
|
||||
getSettings: vi.fn().mockResolvedValue({ worktreesDir: ".worktrees" }),
|
||||
@@ -89,7 +94,7 @@ function createStore(root: string, task: Task, events: string[], publish: (id: s
|
||||
describe("POST /tasks/:id/reset", () => {
|
||||
afterEach(() => vi.restoreAllMocks());
|
||||
|
||||
it("fences cancellation, removes worktree and plan, then publishes Planning atomically", async () => {
|
||||
it("preserves the TaskStore receiver while publishing the confirmed reset", async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), "fusion-reset-route-"));
|
||||
const worktree = join(root, ".worktrees", "fn-400");
|
||||
const taskDir = join(root, ".fusion", "tasks", "FN-400");
|
||||
@@ -100,7 +105,12 @@ describe("POST /tasks/:id/reset", () => {
|
||||
const task = taskFixture(worktree);
|
||||
vi.mocked(getRegisteredWorktreeBranches).mockResolvedValue([{ branch: task.branch!, worktreePath: worktree }]);
|
||||
const reset = { ...task, column: "triage", status: "needs-replan", worktree: undefined, branch: undefined, steps: task.steps.map((step) => ({ ...step, status: "pending" as const })) };
|
||||
const store = createStore(root, task, events, async () => {
|
||||
let store!: TaskStore;
|
||||
store = createStore(root, task, events, async function (this: TaskStore, id, intake) {
|
||||
void (this as unknown as { asyncLayer: unknown }).asyncLayer;
|
||||
expect(this).toBe(store);
|
||||
expect(id).toBe("FN-400");
|
||||
expect(intake).toBe("triage");
|
||||
events.push("published");
|
||||
return reset;
|
||||
});
|
||||
@@ -111,6 +121,8 @@ describe("POST /tasks/:id/reset", () => {
|
||||
try {
|
||||
const res = await performRequest(createApp(store), "POST", "/api/tasks/FN-400/reset", JSON.stringify({ confirm: true }), { "content-type": "application/json" });
|
||||
expect(res.status).toBe(200);
|
||||
expect(vi.mocked(store.resetTaskPublication)).toHaveBeenCalledWith("FN-400", "triage");
|
||||
expect(vi.mocked(store.resetTaskPublication).mock.contexts).toEqual([store]);
|
||||
expect(events).toEqual(["cancelled", "published"]);
|
||||
await expect(readFile(join(taskDir, "PROMPT.md"), "utf8")).rejects.toMatchObject({ code: "ENOENT" });
|
||||
await expect(readFile(worktree, "utf8")).rejects.toMatchObject({ code: "ENOENT" });
|
||||
|
||||
@@ -3765,13 +3765,19 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork
|
||||
throw conflict(`Reset incomplete; runtime finalization failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
|
||||
const publish = (scopedStore as TaskStore & {
|
||||
const storeWithPublisher = scopedStore as TaskStore & {
|
||||
resetTaskPublication?: (taskId: string, intake: string) => Promise<Task>;
|
||||
}).resetTaskPublication;
|
||||
if (typeof publish !== "function") {
|
||||
};
|
||||
if (typeof storeWithPublisher.resetTaskPublication !== "function") {
|
||||
throw new Error("Atomic task reset publication is unavailable");
|
||||
}
|
||||
return publish(req.params.id, intakeColumn);
|
||||
/*
|
||||
FNXC:TaskReset 2026-08-20-05:53:
|
||||
Reset publication is a TaskStore instance method whose PostgreSQL implementation reads
|
||||
`this.asyncLayer`. Invoke it through the scoped store so the atomic publisher retains its
|
||||
project-scoped receiver after cleanup and runtime finalization.
|
||||
*/
|
||||
return storeWithPublisher.resetTaskPublication(req.params.id, intakeColumn);
|
||||
} finally {
|
||||
if (reservation?.state === "held") {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user