import crypto from "crypto"; import fs from "fs"; import path from "path"; export type ReleaseKind = "development" | "admin_release"; export type ExecutionRoute = "operator" | "external"; export interface ReleaseExecutionTransaction { transactionId: string; stepId: string; transactionIndex: number; route: ExecutionRoute; executor: string; to: string; value: string; data: string; operation: 0 | 1; } export interface ReleaseExecutionPlan { version: 1; taskId: string; target: string; executionId: string; releaseKind: ReleaseKind; operator: string; transactions: ReleaseExecutionTransaction[]; planHash: string; createdAt: string; } type PlanBody = Omit; export interface ReleaseExecutionTransactionInput { stepId: string; executor: string; to: string; value: string; data: string; operation: 0 | 1; } export interface CompletedExecutionTransaction { transactionId: string; transactionHash: string; } export interface PlannedExecutionTransaction extends ReleaseExecutionTransaction { executionAction: "execute" | "package" | "replay"; transactionHash?: string; } export class ExecutionSegmentDeferred extends Error { constructor(readonly transactionId: string) { super(`Execution is waiting for the preceding external segment before ${transactionId}`); this.name = "ExecutionSegmentDeferred"; } } const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,199}$/; const ADDRESS = /^0x[0-9a-fA-F]{40}$/; const DATA = /^0x(?:[0-9a-fA-F]{2})*$/; const HASH = /^[0-9a-f]{64}$/; const safeId = (value: unknown, label: string): string => { if (typeof value !== "string" || !SAFE_ID.test(value)) { throw new Error(`${label} must match ${SAFE_ID}`); } return value; }; const address = (value: unknown, label: string): string => { if (typeof value !== "string" || !ADDRESS.test(value)) { throw new Error(`${label} must be an EVM address`); } if (value.toLowerCase() === "0x0000000000000000000000000000000000000000") { throw new Error(`${label} must be non-zero`); } return value.toLowerCase(); }; const value = (raw: unknown, label: string): string => { if (typeof raw !== "string" || !/^(?:0[xX][0-9a-fA-F]+|[0-9]+)$/.test(raw)) { throw new Error(`${label} must be a nonnegative integer string`); } return BigInt(raw).toString(10); }; const data = (raw: unknown, label: string): string => { if (typeof raw !== "string" || !DATA.test(raw)) { throw new Error(`${label} must be canonical byte calldata`); } return raw.toLowerCase(); }; const exactKeys = (value: Record, expected: string[], label: string): void => { const actual = Object.keys(value).sort(); const wanted = [...expected].sort(); if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) { throw new Error(`${label} contains unsupported or missing fields`); } }; const record = (raw: unknown, label: string): Record => { if (!raw || typeof raw !== "object" || Array.isArray(raw)) { throw new Error(`${label} must be an object`); } return raw as Record; }; const normalizeTransaction = ( raw: unknown, index: number, strict: boolean, ): ReleaseExecutionTransaction => { const input = record(raw, `transactions[${index}]`); if (strict) { exactKeys(input, [ "transactionId", "stepId", "transactionIndex", "route", "executor", "to", "value", "data", "operation", ], `transactions[${index}]`); } if (input.transactionIndex !== index) { throw new Error(`transactions[${index}].transactionIndex must equal ${index}`); } if (input.route !== "operator" && input.route !== "external") { throw new Error(`transactions[${index}].route is invalid`); } if (input.operation !== 0 && input.operation !== 1) { throw new Error(`transactions[${index}].operation must be 0 or 1`); } return { transactionId: safeId(input.transactionId, `transactions[${index}].transactionId`), stepId: safeId(input.stepId, `transactions[${index}].stepId`), transactionIndex: index, route: input.route, executor: address(input.executor, `transactions[${index}].executor`), to: address(input.to, `transactions[${index}].to`), value: value(input.value, `transactions[${index}].value`), data: data(input.data, `transactions[${index}].data`), operation: input.operation, }; }; const normalizeBody = (raw: unknown, strict: boolean): PlanBody => { const input = record(raw, "execution plan"); if (strict) { exactKeys(input, [ "version", "taskId", "target", "executionId", "releaseKind", "operator", "transactions", ], "execution plan body"); } if (input.version !== 1) throw new Error("execution plan version must be 1"); if (input.releaseKind !== "development" && input.releaseKind !== "admin_release") { throw new Error("execution plan releaseKind is invalid"); } if (!Array.isArray(input.transactions)) { throw new Error("execution plan transactions must be an array"); } const operator = address(input.operator, "execution plan operator"); const transactions = input.transactions.map((transaction, index) => ( normalizeTransaction(transaction, index, strict) )); const transactionIds = new Set(); for (const transaction of transactions) { if (transactionIds.has(transaction.transactionId)) { throw new Error(`execution plan contains duplicate transactionId ${transaction.transactionId}`); } transactionIds.add(transaction.transactionId); if (transaction.route === "operator" && transaction.executor !== operator) { throw new Error(`operator transaction ${transaction.transactionId} must use the plan operator`); } if (transaction.route === "external" && transaction.executor === operator) { throw new Error(`external transaction ${transaction.transactionId} cannot use the plan operator`); } } if ( input.releaseKind === "development" && transactions.some((transaction) => transaction.route === "external") ) { throw new Error("development execution plans cannot contain external transactions"); } return { version: 1, taskId: safeId(input.taskId, "execution plan taskId"), target: safeId(input.target, "execution plan target"), executionId: safeId(input.executionId, "execution plan executionId"), releaseKind: input.releaseKind, operator, transactions, }; }; const hashBody = (body: PlanBody): string => { return crypto.createHash("sha256").update(JSON.stringify(body)).digest("hex"); }; const confinedPath = ( root: string, taskDir: string, target: string, executionId: string, ): string => { const resolvedRoot = path.resolve(root); const resolvedTask = path.resolve(taskDir); const relativeTask = path.relative(resolvedRoot, resolvedTask); if (relativeTask === "" || relativeTask.startsWith("..") || path.isAbsolute(relativeTask)) { throw new Error("execution plan task directory must be inside the project root"); } const filePath = path.join(resolvedTask, "results", target, executionId, "execution-plan.json"); const relativeFile = path.relative(resolvedTask, filePath); if (relativeFile.startsWith("..") || path.isAbsolute(relativeFile)) { throw new Error("execution plan path escapes the task directory"); } return filePath; }; const assertNoSymlinks = (root: string, target: string): void => { const resolvedRoot = path.resolve(root); const resolvedTarget = path.resolve(target); const relative = path.relative(resolvedRoot, resolvedTarget); if (relative.startsWith("..") || path.isAbsolute(relative)) { throw new Error("execution plan path escapes its trusted root"); } let current = resolvedRoot; if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink()) { throw new Error("execution plan path contains a symlink"); } for (const segment of relative.split(path.sep).filter(Boolean)) { current = path.join(current, segment); if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink()) { throw new Error("execution plan path contains a symlink"); } } }; const writeExclusive = (filePath: string, value: unknown): void => { fs.mkdirSync(path.dirname(filePath), { recursive: true }); let descriptor: number; try { descriptor = fs.openSync(filePath, "wx", 0o600); } catch (error) { throw error; } try { fs.writeFileSync(descriptor, JSON.stringify(value, null, 2) + "\n"); fs.fsyncSync(descriptor); } catch (error) { fs.closeSync(descriptor); fs.unlinkSync(filePath); throw error; } fs.closeSync(descriptor); }; export class ReleaseExecutionPlanner { private readonly body: Omit; private readonly expected?: PlanBody; private readonly transactions: ReleaseExecutionTransaction[] = []; private readonly segmentedExecution: boolean; private readonly completedTransactions: CompletedExecutionTransaction[]; private readonly segmentEnd: number; constructor(input: { taskId: string; target: string; executionId: string; releaseKind: ReleaseKind; operator: string; expectedPlan?: ReleaseExecutionPlan; segmentedExecution?: boolean; completedTransactions?: CompletedExecutionTransaction[]; }) { const body = normalizeBody({ version: 1, taskId: input.taskId, target: input.target, executionId: input.executionId, releaseKind: input.releaseKind, operator: input.operator, transactions: [], }, false); this.body = { version: 1, taskId: body.taskId, target: body.target, executionId: body.executionId, releaseKind: body.releaseKind, operator: body.operator, }; if (input.expectedPlan) { const expected = normalizeBody(input.expectedPlan, false); for (const key of ["taskId", "target", "executionId", "releaseKind", "operator"] as const) { if (expected[key] !== this.body[key]) { throw new Error(`locked execution plan ${key} does not match the runtime`); } } this.expected = expected; } this.segmentedExecution = input.segmentedExecution === true; this.completedTransactions = input.completedTransactions ?? []; if (this.segmentedExecution && !this.expected) { throw new Error("segmented execution requires a locked execution plan"); } if (!this.segmentedExecution && this.completedTransactions.length > 0) { throw new Error("completed transactions require segmented execution"); } if (this.expected) { if (this.completedTransactions.length > this.expected.transactions.length) { throw new Error("completed transactions exceed the execution plan"); } for (const [index, completed] of this.completedTransactions.entries()) { if ( completed.transactionId !== this.expected.transactions[index]?.transactionId || typeof completed.transactionHash !== "string" || !/^0x[0-9a-fA-F]{64}$/.test(completed.transactionHash) ) { throw new Error("completed transactions must form a contiguous plan prefix"); } } } const expectedTransactions = this.expected?.transactions ?? []; let segmentEnd = this.completedTransactions.length; if (this.segmentedExecution && segmentEnd < expectedTransactions.length) { if (expectedTransactions[segmentEnd].route === "operator") { while ( segmentEnd < expectedTransactions.length && expectedTransactions[segmentEnd].route === "operator" ) segmentEnd += 1; } while ( segmentEnd < expectedTransactions.length && expectedTransactions[segmentEnd].route === "external" ) segmentEnd += 1; } else if (!this.segmentedExecution) { segmentEnd = Number.MAX_SAFE_INTEGER; } this.segmentEnd = segmentEnd; } add(input: ReleaseExecutionTransactionInput): PlannedExecutionTransaction { const index = this.transactions.length; const stepId = safeId(input.stepId, "planned transaction stepId"); const executor = address(input.executor, "planned transaction executor"); const transaction = normalizeTransaction({ transactionId: `tx-${String(index + 1).padStart(4, "0")}-${stepId}`, stepId, transactionIndex: index, route: executor === this.body.operator ? "operator" : "external", executor, to: input.to, value: input.value, data: input.data, operation: input.operation, }, index, false); if (this.body.releaseKind === "development" && transaction.route === "external") { throw new Error("development execution plans cannot contain external transactions"); } const expected = this.expected?.transactions[index]; if (this.expected && (!expected || JSON.stringify(expected) !== JSON.stringify(transaction))) { throw new Error( `transaction ${transaction.transactionId} does not match locked execution plan`, ); } if (this.segmentedExecution && index >= this.segmentEnd) { throw new ExecutionSegmentDeferred(transaction.transactionId); } this.transactions.push(transaction); const completed = this.completedTransactions[index]; if (completed) { return { ...transaction, executionAction: "replay", transactionHash: completed.transactionHash.toLowerCase(), }; } return { ...transaction, executionAction: transaction.route === "external" ? "package" : "execute", }; } complete(options: { allowPartial?: boolean } = {}): PlanBody { if ( this.expected && this.transactions.length !== this.expected.transactions.length && options.allowPartial !== true ) { throw new Error("execution did not consume every locked transaction"); } if (options.allowPartial === true && !this.segmentedExecution) { throw new Error("partial execution plan completion requires segmented execution"); } return normalizeBody({ ...this.body, transactions: this.transactions, }, false); } } export const writeReleaseExecutionPlan = (input: { root: string; taskDir: string; plan: PlanBody; }): ReleaseExecutionPlan => { const body = normalizeBody(input.plan, false); const filePath = confinedPath(input.root, input.taskDir, body.target, body.executionId); assertNoSymlinks(input.root, filePath); const plan: ReleaseExecutionPlan = { ...body, planHash: hashBody(body), createdAt: new Date().toISOString(), }; if (fs.existsSync(filePath)) { const existing = loadReleaseExecutionPlan({ root: input.root, taskDir: input.taskDir, target: body.target, executionId: body.executionId, }); if (existing.planHash === plan.planHash) return existing; throw new Error("execution plan already exists with different content"); } writeExclusive(filePath, plan); return plan; }; export const loadReleaseExecutionPlan = (input: { root: string; taskDir: string; target: string; executionId: string; expectedHash?: string; }): ReleaseExecutionPlan => { const target = safeId(input.target, "execution plan target"); const executionId = safeId(input.executionId, "execution plan executionId"); const filePath = confinedPath(input.root, input.taskDir, target, executionId); assertNoSymlinks(input.root, filePath); let parsed: unknown; try { parsed = JSON.parse(fs.readFileSync(filePath, "utf8")); } catch (error) { throw new Error( `Cannot load execution plan: ${error instanceof Error ? error.message : String(error)}`, ); } const inputPlan = record(parsed, "execution plan"); exactKeys(inputPlan, [ "version", "taskId", "target", "executionId", "releaseKind", "operator", "transactions", "planHash", "createdAt", ], "execution plan"); const body = normalizeBody({ version: inputPlan.version, taskId: inputPlan.taskId, target: inputPlan.target, executionId: inputPlan.executionId, releaseKind: inputPlan.releaseKind, operator: inputPlan.operator, transactions: inputPlan.transactions, }, true); if (body.target !== target || body.executionId !== executionId) { throw new Error("execution plan path identity does not match its contents"); } if (typeof inputPlan.createdAt !== "string" || !Number.isFinite(Date.parse(inputPlan.createdAt))) { throw new Error("execution plan createdAt must be an ISO timestamp"); } const calculatedHash = hashBody(body); if (typeof inputPlan.planHash !== "string" || !HASH.test(inputPlan.planHash)) { throw new Error("execution plan planHash is invalid"); } if (inputPlan.planHash !== calculatedHash) { throw new Error("execution plan hash does not match its contents"); } if (input.expectedHash !== undefined && input.expectedHash !== calculatedHash) { throw new Error("execution plan hash does not match the parameter lock"); } return { ...body, planHash: calculatedHash, createdAt: inputPlan.createdAt, }; }; export const releaseExecutionPlanRelativePath = ( taskDir: string, plan: Pick, ): string => path.relative( taskDir, path.join(taskDir, "results", plan.target, plan.executionId, "execution-plan.json"), ).split(path.sep).join("/");