import fs from "fs"; import path from "path"; import type { Signer } from "ethers"; import { artifacts, config as hardhatConfig, ethers, network } from "hardhat"; import { loadConfig, resolveAdmin, resolveTarget, taskDir as resolveTaskDir, TargetConfig, } from "./lib/config"; import { buildExecutionId, validateExecutionId } from "./lib/executionId"; import { assertParameterLockExecutable, markParameterLockSimulationSucceeded, ParameterLockExecution, readParameterLockExecution, writeParameterLock, } from "./lib/lock"; import { loadManifest, loadParameterSchema, loadParameters, loadTaskMetadata, targetParams, assertExistingDeploymentPoliciesMatchTarget, ExistingDeploymentPolicies, validateExistingDeploymentPolicies, WorkflowStep, } from "./lib/parameters"; import { findDeploymentAddress } from "./lib/deployments"; import { assertReleaseNetworkReady, ArtifactFingerprintInput, buildReleaseFingerprint, loadRelease, ReleaseFingerprint, } from "./lib/release"; import { resolveExecutionParameters, } from "./lib/releaseParameters"; import { WorkflowReporter } from "./lib/reporter"; import { ExternalCall, ExternalExecutionRequired, writeExternalExecutionPackage, } from "./lib/external"; import { classifyWorkflowError } from "./lib/errors"; import { resolveWorkflowMode, setupSimulationFork, SimulationFork, WorkflowMode, } from "./lib/simulation"; import { impersonateWorkflowSimulationSigner, resolveSimulationSignerAddress, resolveWorkflowSigner, } from "./lib/signer"; import { loadSimulationCallerNetwork } from "./lib/simulationCallers"; import { WorkflowState } from "./lib/state"; import { DeploymentRecorder } from "./lib/deploymentRecords"; import { runDeployStep } from "./steps/deploy"; import { runCallStep } from "./steps/call"; import { runUpgradeStep } from "./steps/upgrade"; import { runViewStep } from "./steps/view"; import { runCheckStep } from "./steps/check"; import { runCustomStep } from "./steps/custom"; import { ExecutionSegmentDeferred, loadReleaseExecutionPlan, ReleaseExecutionPlan, ReleaseExecutionPlanner, ReleaseKind, releaseExecutionPlanRelativePath, writeReleaseExecutionPlan, } from "./lib/executionPlan"; import { writeOperatorExecutionResult } from "./lib/operatorExecution"; import { loadCompletedExecutionPrefix } from "./lib/executionProgress"; export interface WorkflowContext { root: string; taskId: string; executionId: string; taskDir: string; targetId: string; target: TargetConfig & Record & { id: string }; targetParams: Record; existingDeploymentPolicies: ExistingDeploymentPolicies; releaseArtifactHash?: string; parameterLockExecutionId?: string; parameterLockOperator?: string; config: ReturnType; mode: WorkflowMode; simulation: SimulationFork; signer?: Signer; signerAddress?: string; configuredSimulationCaller?: boolean; state: WorkflowState; reporter: WorkflowReporter; deploymentRecorder?: DeploymentRecorder; executionPlanner?: ReleaseExecutionPlanner; lockedExecutionPlan?: ReleaseExecutionPlan; externalCalls: ExternalCall[]; operatorReceipts: Array<{ transactionId: string; transactionHash: string }>; } const parseArgs = (argv: string[]): Record => { const args: Record = {}; for (let index = 0; index < argv.length; index += 1) { const item = argv[index]; if (!item.startsWith("--")) continue; const equalIndex = item.indexOf("="); if (equalIndex > 2) { args[item.slice(2, equalIndex)] = item.slice(equalIndex + 1); continue; } const key = item.slice(2); const next = argv[index + 1]; if (!next || next.startsWith("--")) { args[key] = true; } else { args[key] = next; index += 1; } } return args; }; const parseEnvArgs = (): Record => { const raw = process.env.WORKFLOW_ARGS_JSON; if (!raw) return {}; let parsed: unknown; try { parsed = JSON.parse(raw); } catch (error) { throw new Error(`WORKFLOW_ARGS_JSON must be valid JSON: ${error instanceof Error ? error.message : String(error)}`); } if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { throw new Error("WORKFLOW_ARGS_JSON must be a JSON object"); } const args: Record = {}; for (const [key, value] of Object.entries(parsed)) { if (typeof value !== "string" && typeof value !== "boolean") { throw new Error(`WORKFLOW_ARGS_JSON.${key} must be a string or boolean`); } args[key] = value; } return args; }; const assertWalletCredentialForExecution = (mode: WorkflowMode, targetId: string): void => { if (mode !== "execute") return; if (targetId === "hardhat" || targetId === "localhost") return; if (process.env.WALLET_CREDENTIAL?.trim()) return; if (process.env.PRIVATE_KEY?.trim()) return; throw new Error( `Missing wallet credential in project .env or process env. Execute mode on "${targetId}" requires a deployment signer.`, ); }; const selectSteps = ( steps: WorkflowStep[], args: Record, ): WorkflowStep[] => { if (typeof args.step === "string") { return steps.filter((step) => step.id === args.step); } if (typeof args.from === "string") { const index = steps.findIndex((step) => step.id === args.from); if (index < 0) throw new Error(`Unknown --from step: ${args.from}`); return steps.slice(index); } return steps; }; const runStep = async (context: WorkflowContext, step: WorkflowStep): Promise => { switch (step.kind) { case "deploy": await runDeployStep(context, step); return; case "call": await runCallStep(context, step); return; case "upgrade": await runUpgradeStep(context, step); return; case "view": await runViewStep(context, step); return; case "check": await runCheckStep(context, step); return; case "custom": await runCustomStep(context, step); return; default: throw new Error(`Unsupported step kind: ${step.kind}`); } }; interface PreparedReleaseExecution { sourceSuperTaskId: string; mergeCommit: string; baselineParameters: Record; overrides: Record; resolvedParameters: Record; releaseFingerprint: ReleaseFingerprint; existingDeploymentPolicies: ExistingDeploymentPolicies; lockedExecution?: ParameterLockExecution; } const booleanArg = ( args: Record, key: string, ): boolean => { const value = args[key]; return value === true || value === "true"; }; const requiredStringArg = ( args: Record, key: string, ): string => { const value = args[key]; if (typeof value !== "string" || !value.trim()) { throw new Error("Missing --" + key + " for release execution"); } return value.trim(); }; const optionalStringArg = ( args: Record, key: string, ): string | undefined => { const value = args[key]; if (value === undefined) return undefined; if (typeof value !== "string" || !value.trim()) { throw new Error("--" + key + " requires a non-empty value"); } return value.trim(); }; const releaseKindArg = ( args: Record, ): ReleaseKind => { const value = requiredStringArg(args, "releaseKind"); if (value !== "development" && value !== "admin_release") { throw new Error("--releaseKind must be development or admin_release"); } return value; }; const parseParameterOverrides = ( args: Record, ): Record => { const value = args.parameterOverrides; if (value === undefined) return {}; if (typeof value !== "string") { throw new Error("--parameterOverrides must be a JSON object"); } let parsed: unknown; try { parsed = JSON.parse(value); } catch (error) { throw new Error( "--parameterOverrides must be valid JSON: " + (error instanceof Error ? error.message : String(error)), ); } if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { throw new Error("--parameterOverrides must be a JSON object"); } return parsed as Record; }; const parseExistingDeploymentPolicies = ( args: Record, ): ExistingDeploymentPolicies => { const value = args.existingDeploymentPolicies; if (value === undefined) return {}; if (typeof value !== "string") { throw new Error("--existingDeploymentPolicies must be a JSON object"); } let parsed: unknown; try { parsed = JSON.parse(value); } catch (error) { throw new Error( "--existingDeploymentPolicies must be valid JSON: " + (error instanceof Error ? error.message : String(error)), ); } return validateExistingDeploymentPolicies(parsed); }; const manifestContractReferences = (steps: WorkflowStep[]): string[] => { const references = new Set(); for (const step of steps) { if ((step.kind === "deploy" || step.kind === "upgrade") && typeof step.contract === "string") { references.add(step.contract); } if (step.kind === "deploy" && step.firstUpgrade && typeof step.firstUpgrade === "object") { const contract = (step.firstUpgrade as Record).contract; if (typeof contract === "string") references.add(contract); } if (step.kind === "deploy" && step.upgradeableStrategy === "uups-placeholder-proxy") { references.add( "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol:ERC1967Proxy", ); } } return [...references].sort(); }; const loadArtifactFingerprintInputs = async ( manifest: { steps: WorkflowStep[] }, ): Promise => { const inputs: ArtifactFingerprintInput[] = []; for (const contractReference of manifestContractReferences(manifest.steps)) { const artifact = await artifacts.readArtifact(contractReference); const fullyQualifiedName = artifact.sourceName + ":" + artifact.contractName; const buildInfo = await artifacts.getBuildInfo(fullyQualifiedName); if (!buildInfo) { throw new Error("Missing Hardhat build info for " + fullyQualifiedName); } const settings = buildInfo.input.settings as Record; inputs.push({ contract: fullyQualifiedName, bytecode: artifact.bytecode, deployedBytecode: artifact.deployedBytecode, linkReferences: artifact.linkReferences, deployedLinkReferences: artifact.deployedLinkReferences, solcVersion: buildInfo.solcVersion, optimizer: settings.optimizer || {}, evmVersion: typeof settings.evmVersion === "string" ? settings.evmVersion : undefined, }); } return inputs; }; const targetChainId = (targetId: string): number | undefined => { const networks = hardhatConfig.networks as Record; return networks[targetId]?.chainId; }; const equalStringArrays = (left: string[], right: string[]): boolean => { return left.length === right.length && left.every((value, index) => value === right[index]); }; const prepareReleaseExecution = async ( args: Record, mode: WorkflowMode, taskDir: string, targetId: string, target: TargetConfig & Record, manifest: { steps: WorkflowStep[] }, existingDeploymentInstanceIds: string[], parameters: ReturnType, executionId: string, selectedStepIds: string[], ): Promise => { if (!booleanArg(args, "useLock")) return undefined; if ( mode !== "simulate" && mode !== "execute" && mode !== "verify" ) { throw new Error("--useLock only supports simulate, execute, and verify modes"); } const release = loadRelease(taskDir); const baselineParameters = targetParams(parameters, targetId); const releaseFingerprint = buildReleaseFingerprint({ manifest: fs.readFileSync(path.join(taskDir, "manifest.yaml"), "utf8"), artifacts: await loadArtifactFingerprintInputs(manifest), parameters: baselineParameters, network: targetId, chainId: targetChainId(targetId), target, }); assertReleaseNetworkReady(release, targetId, releaseFingerprint); if (mode === "execute" || mode === "verify") { const lockedExecution = readParameterLockExecution(taskDir, executionId); if (lockedExecution.sourceSuperTaskId !== release.sourceSuperTaskId) { throw new Error("Parameter lock sourceSuperTaskId does not match release.json"); } const verifySelectionStart = mode === "verify" ? lockedExecution.selectedStepIds.indexOf(selectedStepIds[0]) : -1; const selectedStepsMatch = mode === "verify" ? selectedStepIds.length > 0 && verifySelectionStart >= 0 && equalStringArrays( lockedExecution.selectedStepIds.slice(verifySelectionStart), selectedStepIds, ) : equalStringArrays(lockedExecution.selectedStepIds, selectedStepIds); if (!selectedStepsMatch) { throw new Error("Parameter lock selectedStepIds do not match this execution"); } assertParameterLockExecutable(lockedExecution, targetId, releaseFingerprint); const existingDeploymentPolicies = validateExistingDeploymentPolicies( lockedExecution.existingDeploymentPolicies, ); assertExistingDeploymentPoliciesMatchTarget( existingDeploymentPolicies, targetId, existingDeploymentInstanceIds, ); return { sourceSuperTaskId: lockedExecution.sourceSuperTaskId, mergeCommit: lockedExecution.mergeCommit, baselineParameters: lockedExecution.baselineParameters, overrides: lockedExecution.overrides, resolvedParameters: lockedExecution.resolvedParameters, releaseFingerprint, existingDeploymentPolicies, lockedExecution, }; } const sourceSuperTaskId = requiredStringArg(args, "sourceSuperTaskId"); if (sourceSuperTaskId !== release.sourceSuperTaskId) { throw new Error("--sourceSuperTaskId does not match release.json"); } const mergeCommit = requiredStringArg(args, "mergeCommit"); if (!/^[0-9a-fA-F]{40}$/.test(mergeCommit)) { throw new Error("--mergeCommit must be a 40 character git commit"); } const overrides = parseParameterOverrides(args); const existingDeploymentPolicies = parseExistingDeploymentPolicies(args); assertExistingDeploymentPoliciesMatchTarget( existingDeploymentPolicies, targetId, existingDeploymentInstanceIds, ); const resolvedParameters = resolveExecutionParameters( parameters, targetId, overrides, loadParameterSchema(taskDir), ); return { sourceSuperTaskId, mergeCommit, baselineParameters, overrides, resolvedParameters, releaseFingerprint, existingDeploymentPolicies, }; }; async function main(): Promise { const args = { ...parseEnvArgs(), ...parseArgs(process.argv.slice(2)), }; const root = process.cwd(); const taskId = typeof args.task === "string" ? args.task : process.env.WORKFLOW_TASK; if (!taskId) { throw new Error("Missing task id. Pass --task or set WORKFLOW_TASK= before running workflow."); } const taskDir = resolveTaskDir(taskId, root); const mode = resolveWorkflowMode(args); const targetId = mode === "simulate" ? typeof args.target === "string" ? args.target : "" : network.name; if (!targetId) { throw new Error("Missing target network. Run execute with --network , or simulate with --network hardhat --target ."); } const requestedReleaseKind = args.releaseKind === undefined ? undefined : releaseKindArg(args); const gateFlowNodeKey = process.env.GATEFLOW_NODE_KEY?.trim(); const gateFlowDevelopmentNode = gateFlowNodeKey === "DevelopmentDeploySimulate" || gateFlowNodeKey === "DevelopmentDeployExecute"; if (gateFlowDevelopmentNode && requestedReleaseKind !== "development") { throw new Error("GateFlow development deployment nodes require --releaseKind development"); } if ( gateFlowDevelopmentNode && (typeof args.executionId !== "string" || !args.executionId.trim()) ) { throw new Error( "GateFlow development deployment nodes require an explicit --executionId reused across simulate and execute", ); } const executionId = validateExecutionId( args.executionId === undefined ? buildExecutionId(targetId) : args.executionId, ); const reporter = new WorkflowReporter(taskDir, taskId, executionId, targetId, mode); reporter.initializeResults(); let contextForFailure: WorkflowContext | undefined; try { const config = loadConfig(root); const manifest = loadManifest(taskDir); const parameters = loadParameters(taskDir); const taskMetadata = loadTaskMetadata(taskDir); assertWalletCredentialForExecution(mode, targetId); const target = resolveTarget(config, targetId); const steps = selectSteps(manifest.steps, args); const existingDeploymentInstanceIds = manifest.steps .filter((step) => step.kind === "deploy" && typeof step.saveAs === "string") .map((step) => String(step.saveAs)) .filter((instanceId) => Boolean( findDeploymentAddress(root, config, targetId, instanceId), )); const releaseTargetSimulation = mode === "simulate" && network.name === "hardhat" && typeof args.target === "string" && (taskMetadata.releaseTargetNetworks ?? []).includes(targetId) && !booleanArg(args, "useLock") && requestedReleaseKind === undefined; if ( releaseTargetSimulation && (args.step !== undefined || args.from !== undefined) ) { throw new Error( "Full release-target simulation does not support partial --step or --from selection", ); } const simulationCallerBinding = releaseTargetSimulation ? loadSimulationCallerNetwork({ taskDir, targetNetworks: taskMetadata.releaseTargetNetworks!, network: targetId, manifestText: fs.readFileSync(path.join(taskDir, "manifest.yaml"), "utf8"), manifestSteps: manifest.steps, }) : undefined; if (mode === "simulate" && steps.length === 0) { throw new Error("Simulation must select at least one workflow step"); } if (mode === "verify") { const unsupported = steps.find((step) => step.kind !== "view" && step.kind !== "check"); if (unsupported) { throw new Error(`verify mode only supports view and check steps; ${unsupported.id} is ${unsupported.kind}`); } } const releaseExecution = await prepareReleaseExecution( args, mode, taskDir, targetId, target, manifest, existingDeploymentInstanceIds, parameters, executionId, steps.map((step) => step.id), ); if (!releaseExecution && requestedReleaseKind === "admin_release") { throw new Error("admin_release execution requires --useLock"); } const developmentPlanExecution = !releaseExecution && requestedReleaseKind === "development"; if ( developmentPlanExecution && mode !== "simulate" && mode !== "execute" ) { throw new Error("development releaseKind only supports simulate and execute modes"); } const lockedExecutionPlan = releaseExecution?.lockedExecution ? loadReleaseExecutionPlan({ root, taskDir, target: targetId, executionId, expectedHash: releaseExecution.lockedExecution.finalSimulation.status === "succeeded" ? releaseExecution.lockedExecution.finalSimulation.executionPlanHash : undefined, }) : developmentPlanExecution && mode === "execute" ? loadReleaseExecutionPlan({ root, taskDir, target: targetId, executionId, }) : undefined; const releaseKind = releaseExecution ? mode === "simulate" ? requestedReleaseKind : lockedExecutionPlan?.releaseKind : developmentPlanExecution ? "development" : undefined; const completedExecutionPrefix = mode === "execute" && lockedExecutionPlan ? loadCompletedExecutionPrefix({ taskDir, plan: lockedExecutionPlan }) : []; const simulationSignerAddress = mode === "simulate" ? simulationCallerBinding?.defaultAddress ?? resolveSimulationSignerAddress({ explicitAddress: optionalStringArg(args, "simulationSigner"), taskAddress: taskMetadata.deploymentSignerAddress, useLock: booleanArg(args, "useLock"), }) : undefined; const simulation = mode === "simulate" ? await setupSimulationFork(targetId) : {}; const signer = mode === "simulate" ? simulationCallerBinding ? undefined : await impersonateWorkflowSimulationSigner(simulationSignerAddress!) : await resolveWorkflowSigner(mode, { taskAddress: taskMetadata.deploymentSignerAddress, }); const signerAddress = simulationCallerBinding?.defaultAddress ?? (signer ? await signer.getAddress() : undefined); const resolvedTarget = { id: targetId, ...target, ...(signerAddress ? { signer: signerAddress, admin: resolveAdmin(target, signerAddress), } : {}), }; if ( lockedExecutionPlan && mode === "execute" && signerAddress && lockedExecutionPlan.operator.toLowerCase() !== signerAddress.toLowerCase() ) { throw new Error("Execution plan operator does not match the execution signer"); } if (releaseExecution && mode === "simulate") { if (!signerAddress || !resolvedTarget.admin) { throw new Error("Final simulation requires a resolved signer and admin"); } const create2Config = resolvedTarget.create2 && typeof resolvedTarget.create2 === "object" && !Array.isArray(resolvedTarget.create2) ? resolvedTarget.create2 as Record : {}; const uupsConfig = resolvedTarget.uups && typeof resolvedTarget.uups === "object" && !Array.isArray(resolvedTarget.uups) ? resolvedTarget.uups as Record : {}; const multicallConfig = resolvedTarget.multicall && typeof resolvedTarget.multicall === "object" && !Array.isArray(resolvedTarget.multicall) ? resolvedTarget.multicall as Record : {}; writeParameterLock({ taskDir, deploymentId: taskId, executionId, sourceSuperTaskId: releaseExecution.sourceSuperTaskId, mergeCommit: releaseExecution.mergeCommit, selectedExecuteNetworks: [targetId], selectedStepIds: steps.map((step) => step.id), operator: signerAddress, baselineParameters: releaseExecution.baselineParameters, overrides: releaseExecution.overrides, resolvedParameters: releaseExecution.resolvedParameters, releaseFingerprint: releaseExecution.releaseFingerprint, finalSimulation: { status: "pending" }, existingDeploymentPolicies: releaseExecution.existingDeploymentPolicies, targetSnapshot: { network: targetId, signer: signerAddress, admin: resolvedTarget.admin, chainId: targetChainId(targetId), create2Factory: typeof create2Config.factory === "string" ? create2Config.factory : undefined, create2Caller: typeof create2Config.caller === "string" ? create2Config.caller : undefined, controlledMulticall: typeof multicallConfig.controlledMulticall === "string" ? multicallConfig.controlledMulticall : undefined, uupsPlaceholderImplementation: typeof uupsConfig.placeholderImplementation === "string" ? uupsConfig.placeholderImplementation : undefined, leadingZeroBytes: typeof create2Config.leadingZeroBytes === "number" ? create2Config.leadingZeroBytes : undefined, }, }); } const context: WorkflowContext = { root, taskId, executionId, taskDir, targetId, target: resolvedTarget, targetParams: releaseExecution?.resolvedParameters || targetParams(parameters, targetId), existingDeploymentPolicies: releaseExecution ? releaseExecution.existingDeploymentPolicies : parseExistingDeploymentPolicies(args), releaseArtifactHash: releaseExecution?.releaseFingerprint.artifactHash, parameterLockExecutionId: releaseExecution ? executionId : undefined, parameterLockOperator: releaseExecution?.lockedExecution?.operator, config, mode, simulation, signer, signerAddress, state: new WorkflowState(), reporter, executionPlanner: (releaseExecution || developmentPlanExecution) && releaseKind && (mode === "simulate" || mode === "execute") ? new ReleaseExecutionPlanner({ taskId, target: targetId, executionId, releaseKind, operator: lockedExecutionPlan?.operator || releaseExecution?.lockedExecution?.operator || signerAddress!, expectedPlan: lockedExecutionPlan, segmentedExecution: mode === "execute" && lockedExecutionPlan?.releaseKind === "admin_release", completedTransactions: completedExecutionPrefix, }) : undefined, lockedExecutionPlan, externalCalls: [], operatorReceipts: [], }; contextForFailure = context; reporter.status("running", { mode, stepCount: steps.length }); reporter.event("workflow.started", { mode, network: network.name, target: targetId, steps: steps.map((step) => step.id) }); if ( mode === "execute" && ( Boolean(releaseExecution) || steps.some((step) => step.kind === "deploy" || step.kind === "upgrade") ) ) { context.deploymentRecorder = new DeploymentRecorder({ root, taskDir, config, taskId, executionId, network: targetId, awaitRegistryCommit: Boolean(releaseExecution || developmentPlanExecution), resume: completedExecutionPrefix.length > 0, }); } let executionSegmentDeferred = false; stepLoop: for (const step of steps) { if ( context.mode === "execute" && context.externalCalls.length > 0 && (step.kind === "view" || step.kind === "check" || step.kind === "custom") ) { reporter.event("step.deferred_until_external_execution", { stepId: step.id, kind: step.kind, }); break; } const configuredCaller = simulationCallerBinding?.resolved.find( (entry) => entry.stepId === step.id, ); const stepContext: WorkflowContext = configuredCaller ? { ...context, signer: await impersonateWorkflowSimulationSigner(configuredCaller.callerAddress), signerAddress: configuredCaller.callerAddress, configuredSimulationCaller: true, } : context; if (configuredCaller) { reporter.setSimulationCallerAddress(configuredCaller.callerAddress); } try { reporter.event("step.started", { stepId: step.id, kind: step.kind }); try { await runStep(stepContext, step); } catch (error) { if (context.mode === "execute" && error instanceof ExecutionSegmentDeferred) { executionSegmentDeferred = true; reporter.event("execution.segment.deferred", { transactionId: error.transactionId, }); break stepLoop; } if ( context.mode === "execute" && error instanceof ExternalExecutionRequired ) { if (!context.executionPlanner) { throw new Error( "External execution requires a locked mixed-authority plan; run final simulation and execute mode", ); } for (const call of error.calls) { let planned; try { planned = context.executionPlanner.add({ stepId: call.stepId, executor: call.executor, to: call.to, value: call.value, data: call.data, operation: call.operation, }); } catch (planError) { if (planError instanceof ExecutionSegmentDeferred) { executionSegmentDeferred = true; reporter.event("execution.segment.deferred", { transactionId: planError.transactionId, }); break; } throw planError; } if (planned.route !== "external") { throw new Error( `Step ${call.stepId} requested external execution for an operator route`, ); } if (planned.executionAction !== "replay") { context.externalCalls.push({ ...call, transactionId: planned.transactionId, stepId: planned.stepId, executor: planned.executor, to: planned.to, value: planned.value, data: planned.data, operation: planned.operation, }); } } if (executionSegmentDeferred) break stepLoop; continue; } throw error; } reporter.event("step.completed", { stepId: step.id, kind: step.kind }); } finally { if (configuredCaller) reporter.clearSimulationCallerAddress(); } } const plannedBody = context.executionPlanner?.complete({ allowPartial: executionSegmentDeferred, }); const completedPlan = mode === "simulate" && plannedBody ? writeReleaseExecutionPlan({ root, taskDir, plan: plannedBody }) : lockedExecutionPlan; if (completedPlan && mode === "execute") { writeOperatorExecutionResult({ taskDir, plan: completedPlan, receipts: context.operatorReceipts, }); } if (releaseExecution && mode === "simulate") { if (!completedPlan) throw new Error("Final simulation did not produce an execution plan"); reporter.result("execution", { kind: "release-final-simulation", status: "succeeded", selectedStepIds: steps.map((step) => step.id), releaseFingerprint: releaseExecution.releaseFingerprint, releaseKind: completedPlan.releaseKind, executionPlanHash: completedPlan.planHash, executionPlanPath: releaseExecutionPlanRelativePath(taskDir, completedPlan), }); } let deploymentStatus; if ( mode === "execute" && completedPlan && context.externalCalls.length > 0 ) { const externalPackage = writeExternalExecutionPackage({ taskDir, plan: completedPlan, handoffBlock: await ethers.provider.getBlockNumber(), calls: context.externalCalls, }); const externalSegment = externalPackage.package.segments.at(-1)!; context.deploymentRecorder?.markWaitingExternal({ packagePath: path.relative(root, externalPackage.filePath).split(path.sep).join("/"), executionPlanHash: completedPlan.planHash, handoffBlock: externalSegment.handoffBlock, transactionIds: externalSegment.transactionIds, }); deploymentStatus = "waiting_external"; } else { deploymentStatus = context.deploymentRecorder?.complete(); } const finalStatus = deploymentStatus === "waiting_code_check" ? "waiting_code_check" : deploymentStatus === "waiting_external" ? "waiting_external" : deploymentStatus === "partial" ? "partial" : "completed"; if (mode !== "simulate") { reporter.status(finalStatus, { mode, stepCount: steps.length }); } reporter.report(mode === "simulate" ? "simulation-report.md" : "execution-report.md", [ `# Execution Report`, ``, `Task: ${taskId}`, `Target: ${targetId}`, `Network: ${network.name}`, `Mode: ${mode}`, `Status: ${finalStatus}`, ``, `Steps:`, ...steps.map((step) => `- ${step.id} (${step.kind})`), ]); if (mode === "simulate") { if (!signerAddress) { throw new Error("Simulation completed without a selected signer address"); } const simulationCallers = simulationCallerBinding?.resolved.map((entry) => ({ stepId: entry.stepId, callerAddress: entry.callerAddress, })); reporter.result("execution", simulationCallerBinding ? { kind: "workflow-simulation-completed", status: "succeeded", selectedStepIds: steps.map((step) => step.id), simulationCallerNetworkHash: simulationCallerBinding.networkHash, simulationCallers, } : { kind: "workflow-simulation-completed", status: "succeeded", selectedStepIds: steps.map((step) => step.id), signerAddress, }); const simulationResult = simulationCallerBinding ? reporter.simulationExecutionResultPath({ simulationCallerNetworkHash: simulationCallerBinding.networkHash, simulationCallers: simulationCallers!, }) : reporter.simulationExecutionResultPath(signerAddress); reporter.status(finalStatus, { mode, stepCount: steps.length, simulationResult, }); if (releaseExecution) { if (!completedPlan) throw new Error("Final simulation execution plan is missing"); markParameterLockSimulationSucceeded( taskDir, executionId, simulationResult, new Date().toISOString(), signerAddress, completedPlan.planHash, releaseExecutionPlanRelativePath(taskDir, completedPlan), ); } } } catch (error) { if (contextForFailure?.deploymentRecorder) { try { contextForFailure.deploymentRecorder.markPartial(error); } catch (recordError) { reporter.event("deployment.record.failed", { message: recordError instanceof Error ? recordError.message : String(recordError), }); } } if (mode === "simulate") { try { reporter.clearSimulationCompletionEvidence(); } catch { // Preserve the original workflow failure if evidence cleanup also fails. } } const classification = classifyWorkflowError(error); reporter.status("failed", { mode, errorType: classification.errorType, retryable: classification.retryable, message: error instanceof Error ? error.message : String(error), }); reporter.event("workflow.failed", { mode, errorType: classification.errorType, retryable: classification.retryable, message: error instanceof Error ? error.message : String(error), }); throw error; } } main().catch((error) => { console.error(error); process.exitCode = 1; });