diff --git a/.changeset/scope-aware-verification-timeout.md b/.changeset/scope-aware-verification-timeout.md new file mode 100644 index 0000000000..4af0737b8c --- /dev/null +++ b/.changeset/scope-aware-verification-timeout.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Verification (merge/step gate) timeout now scales with command scope instead of a flat 10 minutes. +category: fix +dev: verification-utils runVerificationCommand derives its default from the command — package-scoped (pnpm --filter/-F) gets 300s, workspace-scoped gets 900s — matching fn_run_verification (DEFAULT_TIMEOUT_PACKAGE_SEC/WORKSPACE_SEC). Project verificationCommandTimeoutMs still overrides; the 1800s hard cap still applies. Fixes workspace-scoped suites being killed as a 10-min infra timeout during merge/step verification. diff --git a/packages/engine/src/__tests__/verification-utils.test.ts b/packages/engine/src/__tests__/verification-utils.test.ts index 57b890ec8b..0f8fbf9fd2 100644 --- a/packages/engine/src/__tests__/verification-utils.test.ts +++ b/packages/engine/src/__tests__/verification-utils.test.ts @@ -3,11 +3,39 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { setTimeout as delay } from "node:timers/promises"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { detectMissingWorkspaceEntry, execWithProcessGroup } from "../verification-utils.js"; +import { + classifyVerificationScope, + defaultVerificationTimeoutMs, + detectMissingWorkspaceEntry, + execWithProcessGroup, + VERIFICATION_TIMEOUT_PACKAGE_MS, + VERIFICATION_TIMEOUT_WORKSPACE_MS, +} from "../verification-utils.js"; const onPosix = process.platform !== "win32"; const itPosix = onPosix ? it : it.skip; +describe("scope-aware verification default timeout", () => { + it("classifies pnpm --filter / -F commands as package-scoped", () => { + expect(classifyVerificationScope("pnpm --filter @fusion/dashboard test")).toBe("package"); + expect(classifyVerificationScope("pnpm -w --filter @fusion/core exec vitest run src/a.test.ts")).toBe("package"); + expect(classifyVerificationScope("pnpm -F @runfusion/fusion test")).toBe("package"); + }); + + it("classifies root-level commands as workspace-scoped", () => { + expect(classifyVerificationScope("pnpm test")).toBe("workspace"); + expect(classifyVerificationScope("pnpm test:full")).toBe("workspace"); + expect(classifyVerificationScope("npm run verify")).toBe("workspace"); + }); + + it("derives the default budget from scope (package 300s, workspace 900s)", () => { + expect(defaultVerificationTimeoutMs("pnpm --filter @fusion/dashboard test")).toBe(VERIFICATION_TIMEOUT_PACKAGE_MS); + expect(VERIFICATION_TIMEOUT_PACKAGE_MS).toBe(300_000); + expect(defaultVerificationTimeoutMs("pnpm test")).toBe(VERIFICATION_TIMEOUT_WORKSPACE_MS); + expect(VERIFICATION_TIMEOUT_WORKSPACE_MS).toBe(900_000); + }); +}); + describe("execWithProcessGroup", { timeout: 10_000 }, () => { let tempDir: string; diff --git a/packages/engine/src/verification-utils.ts b/packages/engine/src/verification-utils.ts index 64c438c752..4159dc11d1 100644 --- a/packages/engine/src/verification-utils.ts +++ b/packages/engine/src/verification-utils.ts @@ -9,10 +9,50 @@ import type { SandboxBackend, SandboxRunStreamingOptions, SandboxStreamingResult // ── Constants ────────────────────────────────────────────────────────── export const VERIFICATION_COMMAND_MAX_BUFFER = 50 * 1024 * 1024; +/** + * Legacy flat default. Retained for back-compat; the merger/executor gate now + * derives its default from command scope (see VERIFICATION_TIMEOUT_*_MS below). + */ export const VERIFICATION_COMMAND_TIMEOUT_MS = 600_000; export const VERIFICATION_COMMAND_HARD_CAP_MS = 1_800_000; export const VERIFICATION_LOG_MAX_CHARS = 20_000; +/* +FNXC:Verification 2026-06-25-13:55: +The merger/executor verification gate used a flat 10-min default for ANY configured +test/build command, while the fn_run_verification tool already derived its default +from command scope. A workspace-scoped command (a full suite, ~10+ min) hit the flat +10-min wall and was killed as an infra timeout; a package-scoped command got a too- +generous bound. Make the shared runner scope-aware to match the tool: a package-scoped +command (pnpm --filter/-F ...) defaults to 300s, anything else (root/workspace command) +to 900s. An explicit project verificationCommandTimeoutMs still overrides, and the 30-min +hard cap (VERIFICATION_COMMAND_HARD_CAP_MS) still clamps the result. These mirror +run-verification-tool's DEFAULT_TIMEOUT_PACKAGE_SEC (300) / DEFAULT_TIMEOUT_WORKSPACE_SEC (900). +*/ +export const VERIFICATION_TIMEOUT_PACKAGE_MS = 300_000; +export const VERIFICATION_TIMEOUT_WORKSPACE_MS = 900_000; + +/** + * Classify a configured verification command by scope. A command that targets a + * single workspace package via pnpm's `--filter`/`-F` is "package"-scoped; every + * other shape (a root-level command such as `pnpm test`) is "workspace"-scoped. + */ +export function classifyVerificationScope(command: string): "package" | "workspace" { + const tokens = command.split(/\s+/).filter(Boolean); + return tokens.some((token) => token === "--filter" || token === "-F") ? "package" : "workspace"; +} + +/** + * The default per-command verification budget for a command when no explicit + * project `verificationCommandTimeoutMs` override is provided — scope-aware, + * matching the fn_run_verification tool. + */ +export function defaultVerificationTimeoutMs(command: string): number { + return classifyVerificationScope(command) === "package" + ? VERIFICATION_TIMEOUT_PACKAGE_MS + : VERIFICATION_TIMEOUT_WORKSPACE_MS; +} + // ── Types ────────────────────────────────────────────────────────────── /** Result of running a single verification command */ @@ -359,10 +399,12 @@ export async function runVerificationCommand( /* * FNXC:Verification 2026-06-17-14:38: * Configured test/build commands share the same project verification budget as fn_run_verification so merge/step verification cannot run marathon subprocesses outside the engine-level guardrail. + * FNXC:Verification 2026-06-25-13:55: + * The default is now scope-aware (defaultVerificationTimeoutMs): package-scoped commands get 300s, workspace-scoped 900s — matching the tool — instead of a flat 10-min budget that killed workspace-scoped suites as infra timeouts. */ const rawTimeoutMs = typeof timeoutMsOverride === "number" && timeoutMsOverride > 0 ? timeoutMsOverride - : VERIFICATION_COMMAND_TIMEOUT_MS; + : defaultVerificationTimeoutMs(command); const timeoutMs = Math.min(rawTimeoutMs, VERIFICATION_COMMAND_HARD_CAP_MS); try { const { stdout, stderr, bufferOverflow } = await execWithProcessGroup(