import fs from "fs"; import path from "path"; import { readJsonFile, writeJsonFile } from "./config"; import { WorkflowMode } from "./simulation"; import { WORKFLOW_RESULT_NAMES, WorkflowResultName, workflowResultRelativeDirectory, workflowResultRelativePath, } from "./resultPaths"; const RUNTIME_STATUS_FIELDS = [ "blockedReasons", "errorType", "retryable", "message", "stepId", "simulationResult", ] as const; const STATUS_LOCK_STALE_MS = 30_000; const STATUS_LOCK_WAIT_MS = 35_000; const STATUS_LOCK_POLL_MS = 10; const STATUS_LOCK_SLEEP = new Int32Array(new SharedArrayBuffer(4)); const isNonZeroEvmAddress = (value: unknown): value is string => { return typeof value === "string" && /^0x[0-9a-fA-F]{40}$/.test(value) && value.toLowerCase() !== "0x0000000000000000000000000000000000000000"; }; export class WorkflowReporter { private readonly initializedResults = new Set(); private simulationCallerAddress?: string; constructor( private readonly taskDir: string, private readonly taskId: string, private readonly executionId: string, private readonly targetId: string, private readonly mode: WorkflowMode = "execute", ) {} initializeResults(names: readonly WorkflowResultName[] = WORKFLOW_RESULT_NAMES): void { const directory = this.resultDirectoryPath(); fs.mkdirSync(path.dirname(directory), { recursive: true }); try { fs.mkdirSync(directory); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error; this.retryFailedVerification(directory, names); return; } this.writeInitialResults(names); } private writeInitialResults(names: readonly WorkflowResultName[]): void { for (const name of names) { writeJsonFile(this.resultPath(name), this.resultEnvelope([])); this.initializedResults.add(name); } } private retryFailedVerification( directory: string, names: readonly WorkflowResultName[], ): void { const claimedError = (): Error => new Error( `Workflow result namespace already claimed for target "${this.targetId}", executionId "${this.executionId}", mode "${this.mode}"; use a new executionId`, ); if (this.mode === "execute" && this.resumeSegmentedAdminExecution(names)) return; if (this.mode !== "verify") throw claimedError(); const parent = path.dirname(directory); const lockPath = path.join(parent, ".verify-retry.lock"); let lock: number; try { lock = fs.openSync(lockPath, "wx"); } catch { throw claimedError(); } let archivePath: string | undefined; try { const status = this.readStatusFile(path.join(this.taskDir, "status.json")); const networkStatus = status.networks?.[this.targetId]; if ( networkStatus?.lastExecutionId !== this.executionId || networkStatus.lastExecutionMode !== "verify" || networkStatus.verifyStatus !== "failed" ) { throw claimedError(); } const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); const archiveBase = path.join(parent, `verify.failed.${timestamp}`); archivePath = archiveBase; for (let suffix = 1; fs.existsSync(archivePath); suffix += 1) { archivePath = `${archiveBase}.${suffix}`; } fs.renameSync(directory, archivePath); fs.mkdirSync(directory); this.writeInitialResults(names); this.status("running", { mode: this.mode }); } catch (error) { if (archivePath && fs.existsSync(archivePath)) { fs.rmSync(directory, { recursive: true, force: true }); fs.renameSync(archivePath, directory); } throw error; } finally { fs.closeSync(lock); fs.unlinkSync(lockPath); } } private resumeSegmentedAdminExecution(names: readonly WorkflowResultName[]): boolean { const executionRoot = path.join( this.taskDir, "results", this.targetId, this.executionId, ); const planPath = path.join(executionRoot, "execution-plan.json"); const reconciliationPath = path.join(executionRoot, "external-reconciliation.json"); if (!fs.existsSync(planPath) || !fs.existsSync(reconciliationPath)) return false; let plan: Record; let reconciliation: Record; try { plan = readJsonFile>(planPath); reconciliation = readJsonFile>(reconciliationPath); } catch { return false; } const status = this.readStatusFile(path.join(this.taskDir, "status.json")); const networkStatus = status.networks?.[this.targetId]; if ( plan.version !== 1 || plan.taskId !== this.taskId || plan.target !== this.targetId || plan.executionId !== this.executionId || plan.releaseKind !== "admin_release" || typeof plan.planHash !== "string" || reconciliation.version !== 1 || reconciliation.status !== "waiting_operator" || reconciliation.taskId !== this.taskId || reconciliation.target !== this.targetId || reconciliation.executionId !== this.executionId || reconciliation.executionPlanHash !== plan.planHash || networkStatus?.lastExecutionId !== this.executionId || networkStatus.lastExecutionMode !== "execute" ) { return false; } for (const name of names) { if (!fs.existsSync(this.resultPath(name))) return false; const result = readJsonFile>(this.resultPath(name)); if ( result.taskId !== this.taskId || result.executionId !== this.executionId || result.target !== this.targetId || result.mode !== "execute" || !Array.isArray(result.steps) ) { return false; } this.initializedResults.add(name); } return true; } event(type: string, payload: Record = {}): void { const filePath = path.join(this.taskDir, "events.jsonl"); fs.appendFileSync( filePath, `${JSON.stringify({ taskId: this.taskId, executionId: this.executionId, target: this.targetId, mode: this.mode, type, timestamp: new Date().toISOString(), ...payload, })}\n`, ); } status(status: string, payload: Record = {}): void { const filePath = path.join(this.taskDir, "status.json"); this.withStatusFileLock(filePath, () => { const existing = this.readStatusFile(filePath); const baseStatus = this.clearRuntimeStatusFields(existing); const targetNetworks = this.targetNetworks(existing); const networks = { ...(existing.networks || {}), [this.targetId]: this.updateNetworkStatus( existing.networks?.[this.targetId] || {}, status, ), }; writeJsonFile(filePath, { ...baseStatus, targetNetworks, networks, status: this.globalStatus(status, targetNetworks, networks), target: this.targetId, executionId: this.executionId, mode: this.mode, updatedAt: new Date().toISOString(), ...payload, }); }); } result(name: WorkflowResultName, payload: Record): void { if (!this.initializedResults.has(name)) { throw new Error(`Workflow result ${name} was not initialized for this execution`); } const filePath = this.resultPath(name); const existing = readJsonFile<{ steps?: unknown }>(filePath); if (!Array.isArray(existing.steps)) { throw new Error(`Workflow result ${name} has an invalid steps array`); } writeJsonFile(filePath, this.resultEnvelope([ ...(existing.steps as Record[]), { timestamp: new Date().toISOString(), ...payload, ...(this.simulationCallerAddress ? { simulationCallerAddress: this.simulationCallerAddress } : {}), }, ])); this.initializedResults.add(name); } setSimulationCallerAddress(callerAddress: string): void { if (this.mode !== "simulate" || !isNonZeroEvmAddress(callerAddress)) { throw new Error("Simulation caller scope requires a valid simulate-mode address"); } this.simulationCallerAddress = callerAddress; } clearSimulationCallerAddress(): void { this.simulationCallerAddress = undefined; } simulationExecutionResultPath(expected: string | { simulationCallerNetworkHash: string; simulationCallers: Array<{ stepId: string; callerAddress: string }>; }): string { if (this.mode !== "simulate") { throw new Error("Simulation execution results require simulate mode"); } const result = readJsonFile<{ executionId?: unknown; target?: unknown; mode?: unknown; steps?: unknown; }>(this.resultPath("execution")); if ( result.executionId !== this.executionId || result.target !== this.targetId || result.mode !== "simulate" || !Array.isArray(result.steps) ) { throw new Error("Simulation execution result does not match this workflow run"); } const terminal = result.steps.at(-1) as Record | undefined; if ( terminal?.kind !== "workflow-simulation-completed" || terminal.status !== "succeeded" || !Array.isArray(terminal.selectedStepIds) || terminal.selectedStepIds.length === 0 ) { throw new Error("Simulation execution result is missing canonical completion evidence"); } if (typeof expected === "string") { if ( !isNonZeroEvmAddress(terminal.signerAddress) || !isNonZeroEvmAddress(expected) || terminal.signerAddress.toLowerCase() !== expected.toLowerCase() ) { throw new Error("Simulation execution result is missing canonical completion evidence"); } } else { const actualCallers = terminal.simulationCallers; if ( !/^[0-9a-f]{64}$/.test(expected.simulationCallerNetworkHash) || terminal.simulationCallerNetworkHash !== expected.simulationCallerNetworkHash || terminal.signerAddress !== undefined || !Array.isArray(actualCallers) || actualCallers.length !== expected.simulationCallers.length || expected.simulationCallers.some((caller, index) => { const actual = actualCallers[index] as Record | undefined; return !actual || actual.stepId !== caller.stepId || !isNonZeroEvmAddress(actual.callerAddress) || actual.callerAddress.toLowerCase() !== caller.callerAddress.toLowerCase(); }) ) { throw new Error("Simulation execution result is missing canonical caller completion evidence"); } } return workflowResultRelativePath({ target: this.targetId, executionId: this.executionId, mode: this.mode, name: "execution", }); } clearSimulationCompletionEvidence(): void { if (this.mode !== "simulate") return; const filePath = this.resultPath("execution"); if (!fs.existsSync(filePath)) return; const result = readJsonFile<{ steps?: unknown }>(filePath); if (!Array.isArray(result.steps)) return; const terminal = result.steps.at(-1) as Record | undefined; if (terminal?.kind !== "workflow-simulation-completed") return; writeJsonFile( filePath, this.resultEnvelope(result.steps.slice(0, -1) as Record[]), ); } report(name: string, lines: string[]): void { const filePath = path.join(this.taskDir, "docs", name); fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.writeFileSync(filePath, `${lines.join("\n")}\n`); } private readStatusFile(filePath: string): { status?: string; targetNetworks?: string[]; networks?: Record>; [key: string]: unknown; } { if (!fs.existsSync(filePath)) return {}; try { const parsed = JSON.parse(fs.readFileSync(filePath, "utf8")); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {}; return parsed as { status?: string; targetNetworks?: string[]; networks?: Record>; [key: string]: unknown; }; } catch { return {}; } } private withStatusFileLock(filePath: string, operation: () => T): T { const lockPath = `${filePath}.lock`; const token = `${process.pid}:${Date.now()}:${Math.random().toString(16).slice(2)}`; const deadline = Date.now() + STATUS_LOCK_WAIT_MS; while (true) { let descriptor: number | undefined; try { descriptor = fs.openSync(lockPath, "wx"); fs.writeFileSync(descriptor, `${token}\n`); fs.fsyncSync(descriptor); fs.closeSync(descriptor); descriptor = undefined; break; } catch (error) { if (descriptor !== undefined) fs.closeSync(descriptor); if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error; try { if (Date.now() - fs.statSync(lockPath).mtimeMs > STATUS_LOCK_STALE_MS) { fs.unlinkSync(lockPath); continue; } } catch (lockError) { if ((lockError as NodeJS.ErrnoException).code === "ENOENT") continue; throw lockError; } if (Date.now() >= deadline) { throw new Error(`Timed out waiting for task status lock: ${lockPath}`); } Atomics.wait(STATUS_LOCK_SLEEP, 0, 0, STATUS_LOCK_POLL_MS); } } try { return operation(); } finally { try { if (fs.readFileSync(lockPath, "utf8").trim() === token) fs.unlinkSync(lockPath); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; } } } private resultEnvelope(steps: Record[]): Record { return { taskId: this.taskId, executionId: this.executionId, target: this.targetId, mode: this.mode, updatedAt: new Date().toISOString(), steps, }; } private resultPath(name: WorkflowResultName): string { return path.join(this.taskDir, workflowResultRelativePath({ target: this.targetId, executionId: this.executionId, mode: this.mode, name, })); } private resultDirectoryPath(): string { return path.join(this.taskDir, workflowResultRelativeDirectory({ target: this.targetId, executionId: this.executionId, mode: this.mode, })); } private clearRuntimeStatusFields>(status: T): T { const next = { ...status }; for (const field of RUNTIME_STATUS_FIELDS) { delete next[field]; } return next as T; } private targetNetworks(existing: { targetNetworks?: string[]; networks?: Record }): string[] { if (Array.isArray(existing.targetNetworks) && existing.targetNetworks.length > 0) { return existing.targetNetworks.map((item) => String(item)); } const networkKeys = existing.networks ? Object.keys(existing.networks) : []; return Array.from(new Set([...networkKeys, this.targetId])); } private updateNetworkStatus( current: Record, status: string, ): Record { const next = { ...current, lastExecutionId: this.executionId, lastExecutionMode: this.mode, updatedAt: new Date().toISOString(), }; if (this.mode === "simulate") { return { ...next, simulationStatus: status === "completed" ? "succeeded" : status, }; } if (this.mode === "verify") { if (status === "completed") return { ...next, verifyStatus: "verified" }; if (status === "failed") return { ...next, verifyStatus: "failed" }; if (status === "running") return { ...next, verifyStatus: "running" }; return next; } if (status === "completed") { return { ...next, deployStatus: "deployed" }; } if (status === "waiting_external") { return { ...next, deployStatus: "waiting_external" }; } if (status === "waiting_code_check") { return { ...next, deployStatus: "waiting_code_check" }; } if (status === "failed") { return { ...next, deployStatus: "failed" }; } return next; } private globalStatus( fallback: string, targetNetworks: string[], networks: Record>, ): string { if (fallback === "waiting_external") return "externalPending"; if (fallback !== "completed") return fallback; if (this.mode === "simulate") return "simulated"; const targetStates = targetNetworks.map((network) => networks[network] || {}); const verifiedCount = targetStates.filter((state) => state.verifyStatus === "verified").length; if (verifiedCount === targetNetworks.length && targetNetworks.length > 0) return "verified"; if (verifiedCount > 0) return "partiallyVerified"; const deployedCount = targetStates.filter((state) => state.deployStatus === "deployed").length; if (deployedCount === targetNetworks.length && targetNetworks.length > 0) return "deployed"; if (deployedCount > 0) return "partiallyDeployed"; return fallback; } }