export const SERVER_JOB_FAULT_SCHEMA = 1; export const SERVER_JOB_FAULT_CODES = Object.freeze({ SUCCEEDED: "SERVER_JOB_SUCCEEDED", CANCELLED: "SERVER_JOB_CANCELLED", TIMEOUT: "SERVER_JOB_TIMEOUT", OOM: "SERVER_JOB_OOM", SIGNAL: "SERVER_JOB_SIGNAL", EXIT_FAILED: "SERVER_JOB_EXIT_FAILED", }); function invalid(message) { throw new Error(`SERVER_JOB_FAULT_INVALID: ${message}`); } function revision(value, field) { if (!Number.isSafeInteger(value) || value < 0) invalid(`${field} must be a non-negative revision`); return value; } function optionalBoolean(value, field) { if (value !== undefined && typeof value !== "boolean") invalid(`${field} must be boolean`); return value === true; } export function classifyServerJobFault(value) { if (!value || typeof value !== "object") invalid("input is invalid"); const cancelRequested = optionalBoolean(value.cancelRequested, "cancelRequested"); const timedOut = optionalBoolean(value.timedOut, "timedOut"); const oom = optionalBoolean(value.oom, "oom"); const memoryLimitBytes = value.memoryLimitBytes === undefined ? null : revision(value.memoryLimitBytes, "memoryLimitBytes"); const memoryBytes = value.memoryBytes === undefined ? null : revision(value.memoryBytes, "memoryBytes"); const memoryExceeded = oom || (memoryLimitBytes !== null && memoryBytes !== null && memoryBytes > memoryLimitBytes); const signal = value.signal === null || value.signal === undefined ? null : String(value.signal); const exitCode = value.code === null || value.code === undefined ? null : value.code; if (exitCode !== null && (!Number.isInteger(exitCode) || exitCode < 0)) invalid("code must be a non-negative integer"); let code = SERVER_JOB_FAULT_CODES.SUCCEEDED; let state = "SUCCEEDED"; if (cancelRequested || value.status === "CANCELLED") { code = SERVER_JOB_FAULT_CODES.CANCELLED; state = "CANCELLED"; } else if (timedOut) { code = SERVER_JOB_FAULT_CODES.TIMEOUT; state = "FAILED"; } else if (memoryExceeded) { code = SERVER_JOB_FAULT_CODES.OOM; state = "FAILED"; } else if (signal) { code = SERVER_JOB_FAULT_CODES.SIGNAL; state = "FAILED"; } else if (exitCode !== null && exitCode !== 0) { code = SERVER_JOB_FAULT_CODES.EXIT_FAILED; state = "FAILED"; } return Object.freeze({ schemaVersion: SERVER_JOB_FAULT_SCHEMA, state, code, exitCode, signal, timedOut, memoryExceeded, publish: state === "SUCCEEDED", revisionPreserved: state !== "SUCCEEDED" }); } export function createServerJobFaultReceipt(value) { if (!value || typeof value !== "object") invalid("receipt input is invalid"); const baseRevision = revision(value.baseRevision, "baseRevision"); const currentRevision = revision(value.currentRevision, "currentRevision"); const classification = classifyServerJobFault(value); if (classification.state !== "SUCCEEDED" && currentRevision !== baseRevision) { throw new Error("SERVER_JOB_REVISION_CONFLICT: failed job cannot replace a newer project revision"); } return Object.freeze({ schemaVersion: SERVER_JOB_FAULT_SCHEMA, state: classification.state, code: classification.code, stage: "PROCESS", exitCode: classification.exitCode, signal: classification.signal, timedOut: classification.timedOut, memoryExceeded: classification.memoryExceeded, baseRevision, currentRevision, committedRevision: currentRevision, publish: classification.publish, revisionPreserved: classification.revisionPreserved, execution: "DISABLED", }); }