import fs from "fs"; import path from "path"; import type { CompletedExecutionTransaction, ReleaseExecutionPlan, } from "./executionPlan"; import { loadOperatorExecutionResult } from "./operatorExecution"; const TRANSACTION_HASH = /^0x[0-9a-fA-F]{64}$/; const reconciliationPath = ( taskDir: string, plan: ReleaseExecutionPlan, ): string => path.join( taskDir, "results", plan.target, plan.executionId, "external-reconciliation.json", ); const readReconciliationMatches = ( taskDir: string, plan: ReleaseExecutionPlan, ): Array> => { const filePath = reconciliationPath(taskDir, plan); if (!fs.existsSync(filePath)) return []; let parsed: unknown; try { parsed = JSON.parse(fs.readFileSync(filePath, "utf8")); } catch (error) { throw new Error( `external reconciliation report is invalid: ${error instanceof Error ? error.message : String(error)}`, ); } if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { throw new Error("external reconciliation report must be an object"); } const report = parsed as Record; if ( report.version !== 1 || report.taskId !== plan.taskId || report.target !== plan.target || report.executionId !== plan.executionId || report.executionPlanHash !== plan.planHash || !Array.isArray(report.matches) ) { throw new Error("external reconciliation report does not match the execution plan"); } if (report.status === "failed") { throw new Error("failed external reconciliation cannot be resumed"); } return report.matches as Array>; }; export const loadCompletedExecutionPrefix = (input: { taskDir: string; plan: ReleaseExecutionPlan; }): CompletedExecutionTransaction[] => { const operatorPath = path.join( input.taskDir, "results", input.plan.target, input.plan.executionId, "operator-execution.json", ); const operatorResult = fs.existsSync(operatorPath) ? loadOperatorExecutionResult({ taskDir: input.taskDir, plan: input.plan }) : undefined; const evidence = new Map(); for (const receipt of operatorResult?.receipts ?? []) { evidence.set(receipt.transactionId, receipt); } const seenMatches = new Set(); for (const match of readReconciliationMatches(input.taskDir, input.plan)) { const transactionIndex = match.transactionIndex; const expected = Number.isSafeInteger(transactionIndex) ? input.plan.transactions[transactionIndex as number] : undefined; if ( !expected || match.transactionId !== expected.transactionId || match.route !== expected.route || String(match.executor).toLowerCase() !== expected.executor || typeof match.transactionHash !== "string" || !TRANSACTION_HASH.test(match.transactionHash) || (match.receiptStatus !== 0 && match.receiptStatus !== 1) || typeof match.finalized !== "boolean" || seenMatches.has(expected.transactionId) ) { throw new Error("external reconciliation matches do not bind the execution plan"); } seenMatches.add(expected.transactionId); if (match.receiptStatus === 0) { throw new Error(`reverted transaction ${expected.transactionId} cannot be resumed`); } if (expected.route === "operator") { const receipt = evidence.get(expected.transactionId); if (receipt && receipt.transactionHash !== match.transactionHash.toLowerCase()) { throw new Error(`operator reconciliation hash differs for ${expected.transactionId}`); } continue; } if (match.finalized) { evidence.set(expected.transactionId, { transactionId: expected.transactionId, transactionHash: match.transactionHash.toLowerCase(), }); } } const completed: CompletedExecutionTransaction[] = []; let prefixEnded = false; for (const transaction of input.plan.transactions) { const item = evidence.get(transaction.transactionId); if (!item) { prefixEnded = true; continue; } if (prefixEnded) { throw new Error("execution evidence must form a contiguous plan prefix"); } completed.push(item); } return completed; };