perf(executor): recover approved steps on engine restart
When the engine restarts mid-step, an in-progress step may have already passed plan + code review but not yet been flipped to done by the agent's next task_update call. Previously, the next executor pass re-entered the step and replayed both reviews — measured at 5-20 min of pure waste per restart (observed in FN-2215 Step 1 and FN-2207 Step 6). recoverApprovedStepsOnResume scans the task log for any in-progress step whose most recent "code review Step N: APPROVE" entry is newer than its most recent "Step N → pending" transition, and marks those steps done before execute() runs. Safely skips steps that were reset after approval (e.g. by a workflow revision) or only received REVISE verdicts. Called from both the engine-restart path (resumeOrphaned) and the unpause path, matching the two places the task log shows as vulnerable to this race. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import http from "node:http";
|
||||
import { createHmac } from "node:crypto";
|
||||
import express from "express";
|
||||
import { createServer, setupTerminalWebSocket } from "./server.js";
|
||||
import type { TaskStore } from "@fusion/core";
|
||||
@@ -1137,11 +1138,17 @@ describe("createServer scoped scheduling resolver regressions", () => {
|
||||
});
|
||||
|
||||
it("POST /api/routines/:id/webhook is scope-independent (uses routine's own scope)", async () => {
|
||||
const secret = "test-secret-test-secret";
|
||||
const payload = JSON.stringify({});
|
||||
const signature =
|
||||
"sha256=" +
|
||||
createHmac("sha256", secret).update(payload).digest("hex");
|
||||
|
||||
const globalStore = createMockRoutineStore("global");
|
||||
const routineRunner = createMockRoutineRunner();
|
||||
globalStore.getRoutine.mockResolvedValue({
|
||||
...FAKE_PROJECT_ROUTINE,
|
||||
trigger: { type: "webhook" as const, webhookPath: "/trigger/test" },
|
||||
trigger: { type: "webhook" as const, webhookPath: "/trigger/test", secret },
|
||||
});
|
||||
|
||||
const store = createMockStore();
|
||||
@@ -1151,7 +1158,10 @@ describe("createServer scoped scheduling resolver regressions", () => {
|
||||
});
|
||||
|
||||
// Webhook without scope param - should work regardless of scope
|
||||
const res = await REQUEST(app, "POST", "/api/routines/routine-proj-a-1/webhook", JSON.stringify({}), { "Content-Type": "application/json" });
|
||||
const res = await REQUEST(app, "POST", "/api/routines/routine-proj-a-1/webhook", payload, {
|
||||
"Content-Type": "application/json",
|
||||
"x-hub-signature-256": signature,
|
||||
});
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(routineRunner.triggerWebhook).toHaveBeenCalled();
|
||||
|
||||
Reference in New Issue
Block a user