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:
Fusion
2026-04-21 09:02:42 -07:00
committed by gsxdsm
parent 9033f7ada7
commit c21e6fef15
33 changed files with 414 additions and 90 deletions

View File

@@ -670,13 +670,21 @@ describe("runDaemon", () => {
await triggerSignal("SIGINT");
});
it("prints banner with full token at startup", async () => {
it("prints banner with masked token at startup (full token never hits stdout)", async () => {
const providedToken = "fn_fulltoken12345678901234567890";
await runDaemon({ token: providedToken });
// Banner should contain the full token
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(providedToken));
// Banner should contain a MASKED form, not the raw token. The full token
// is persisted to ~/.fusion/settings.json (chmod 0600) and retrievable via
// `fn daemon --token-only` — printing it here would leak it to terminal
// scrollback and CI logs.
const allBannerArgs = logSpy.mock.calls.map((args) => String(args[0] ?? ""));
const banner = allBannerArgs.join("\n");
expect(banner).not.toContain(providedToken);
expect(banner).toContain("fn_ful");
expect(banner).toContain("7890");
expect(banner).toContain("fn daemon --token-only");
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("Fusion Daemon"));
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("bearer token required"));
@@ -701,7 +709,7 @@ describe("runDaemon", () => {
expect(mocks.listenCalls[0]).toMatchObject({
port: 0,
host: "0.0.0.0",
host: "127.0.0.1",
});
await triggerSignal("SIGINT");

View File

@@ -729,18 +729,18 @@ describe("runServe", () => {
expect(mocks.taskStores[0].close).toHaveBeenCalledTimes(1);
});
it("listens on 0.0.0.0 by default and respects a custom host", async () => {
it("listens on 127.0.0.1 by default and respects a custom host", async () => {
await runServe(3010, {});
expect(mocks.listenCalls[0]).toMatchObject({
port: 3010,
host: "0.0.0.0",
host: "127.0.0.1",
});
await triggerSignal("SIGINT");
await runServe(3020, { host: "127.0.0.1" });
await runServe(3020, { host: "0.0.0.0" });
expect(mocks.listenCalls[1]).toMatchObject({
port: 3020,
host: "127.0.0.1",
host: "0.0.0.0",
});
await triggerSignal("SIGINT");
});