@shipfox/api-workflows 12.3.0 → 12.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +21 -0
- package/dist/core/entities/job-execution.d.ts +1 -0
- package/dist/core/entities/job-execution.d.ts.map +1 -1
- package/dist/core/entities/job-execution.js.map +1 -1
- package/dist/core/entities/job.d.ts +1 -1
- package/dist/core/entities/job.d.ts.map +1 -1
- package/dist/core/entities/job.js +3 -1
- package/dist/core/entities/job.js.map +1 -1
- package/dist/core/errors.d.ts +3 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +4 -2
- package/dist/core/errors.js.map +1 -1
- package/dist/core/step-config/job-output-limits.d.ts.map +1 -1
- package/dist/core/step-config/job-output-limits.js +2 -2
- package/dist/core/step-config/job-output-limits.js.map +1 -1
- package/dist/core/step-config/materialize-workflow-model.js +2 -2
- package/dist/core/step-config/materialize-workflow-model.js.map +1 -1
- package/dist/db/db.d.ts +42 -8
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/schema/job-executions.d.ts +19 -2
- package/dist/db/schema/job-executions.d.ts.map +1 -1
- package/dist/db/schema/job-executions.js +2 -0
- package/dist/db/schema/job-executions.js.map +1 -1
- package/dist/db/schema/jobs.d.ts +3 -3
- package/dist/db/schema/jobs.d.ts.map +1 -1
- package/dist/db/workflow-runs/job-executions.d.ts +7 -0
- package/dist/db/workflow-runs/job-executions.d.ts.map +1 -1
- package/dist/db/workflow-runs/job-executions.js +26 -4
- package/dist/db/workflow-runs/job-executions.js.map +1 -1
- package/dist/db/workflow-runs/jobs.d.ts.map +1 -1
- package/dist/db/workflow-runs/jobs.js +4 -2
- package/dist/db/workflow-runs/jobs.js.map +1 -1
- package/dist/presentation/dto/job.d.ts.map +1 -1
- package/dist/presentation/dto/job.js +1 -0
- package/dist/presentation/dto/job.js.map +1 -1
- package/dist/presentation/subscribers/on-failure-annotations.d.ts.map +1 -1
- package/dist/presentation/subscribers/on-failure-annotations.js +6 -3
- package/dist/presentation/subscribers/on-failure-annotations.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0002_output_failure_details.sql +2 -0
- package/drizzle/0003_output_invalid_reason.sql +1 -0
- package/drizzle/meta/0002_snapshot.json +1718 -0
- package/drizzle/meta/0003_snapshot.json +1719 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +3 -3
- package/src/core/entities/job-execution.ts +1 -0
- package/src/core/entities/job.ts +2 -0
- package/src/core/errors.test.ts +18 -0
- package/src/core/errors.ts +9 -2
- package/src/core/step-config/job-output-limits.ts +2 -2
- package/src/core/step-config/materialize-workflow-model.test.ts +67 -3
- package/src/core/step-config/materialize-workflow-model.ts +2 -2
- package/src/db/schema/job-executions.ts +2 -0
- package/src/db/workflow-runs/job-executions.test.ts +149 -4
- package/src/db/workflow-runs/job-executions.ts +44 -9
- package/src/db/workflow-runs/job-status.test.ts +30 -0
- package/src/db/workflow-runs/jobs.ts +5 -1
- package/src/presentation/dto/job.ts +1 -0
- package/src/presentation/subscribers/on-failure-annotations.test.ts +47 -1
- package/src/presentation/subscribers/on-failure-annotations.ts +10 -2
- package/test/helpers/workflow-runs.ts +1 -0
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"on-failure-annotations.d.ts","sourceRoot":"","sources":["../../../src/presentation/subscribers/on-failure-annotations.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,4BAA4B,EAElC,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EACV,8BAA8B,EAC9B,sCAAsC,EACvC,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"on-failure-annotations.d.ts","sourceRoot":"","sources":["../../../src/presentation/subscribers/on-failure-annotations.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,4BAA4B,EAElC,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EACV,8BAA8B,EAC9B,sCAAsC,EACvC,MAAM,4BAA4B,CAAC;AAmBpC,wBAAgB,wCAAwC,CACtD,WAAW,EAAE,4BAA4B,aAElB,sCAAsC,KAAG,OAAO,CAAC,IAAI,CAAC,CA8D9E;AAED,wBAAgB,gCAAgC,CAAC,WAAW,EAAE,4BAA4B,aACjE,8BAA8B,KAAG,OAAO,CAAC,IAAI,CAAC,CA2CtE"}
|
|
@@ -6,7 +6,9 @@ import { recordWorkflowFailureAnnotationFailed } from '#metrics/instance.js';
|
|
|
6
6
|
const JOB_FAILURE_ANNOTATION_REASONS = new Set([
|
|
7
7
|
'timed_out',
|
|
8
8
|
'runner_lost',
|
|
9
|
-
'condition_errored'
|
|
9
|
+
'condition_errored',
|
|
10
|
+
'output_too_large',
|
|
11
|
+
'output_invalid'
|
|
10
12
|
]);
|
|
11
13
|
export function onStepAttemptTerminatedFailureAnnotation(annotations) {
|
|
12
14
|
return async (payload)=>{
|
|
@@ -88,7 +90,7 @@ export function onJobTerminatedFailureAnnotation(annotations) {
|
|
|
88
90
|
},
|
|
89
91
|
context: failureContext('job', payload.jobId),
|
|
90
92
|
failed: true,
|
|
91
|
-
body: jobFailureBody(payload.statusReason, origin)
|
|
93
|
+
body: jobFailureBody(payload.statusReason, payload.statusReasonMessage, origin)
|
|
92
94
|
});
|
|
93
95
|
} catch (error) {
|
|
94
96
|
recordFailureAnnotationFailure(error, 'lookup', {
|
|
@@ -134,7 +136,7 @@ function stepFailureBody(name, error, exitCode) {
|
|
|
134
136
|
message
|
|
135
137
|
].join('\n');
|
|
136
138
|
}
|
|
137
|
-
function jobFailureBody(reason, origin) {
|
|
139
|
+
function jobFailureBody(reason, statusReasonMessage, origin) {
|
|
138
140
|
const progress = origin.attemptStatus ? `The job stopped while processing **${origin.stepName}**.` : `The job stopped before **${origin.stepName}** started.`;
|
|
139
141
|
const error = origin.attemptError ?? origin.stepError;
|
|
140
142
|
const message = typeof error?.message === 'string' && error.message.trim() ? error.message : null;
|
|
@@ -144,6 +146,7 @@ function jobFailureBody(reason, origin) {
|
|
|
144
146
|
'',
|
|
145
147
|
progress,
|
|
146
148
|
`Reason: \`${reason ?? 'unknown'}\``,
|
|
149
|
+
statusReasonMessage ? `Failure: ${statusReasonMessage}` : null,
|
|
147
150
|
message ? `Failure: ${message}` : null,
|
|
148
151
|
exitCode
|
|
149
152
|
].filter(Boolean).join('\n');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/subscribers/on-failure-annotations.ts"],"sourcesContent":["import {\n type AnnotationsInterModuleClient,\n annotationsInterModuleContract,\n} from '@shipfox/annotations-dto/inter-module';\nimport type {\n WorkflowsJobTerminatedEventDto,\n WorkflowsStepAttemptTerminatedEventDto,\n} from '@shipfox/api-workflows-dto';\nimport {isInterModuleKnownError} from '@shipfox/inter-module';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {\n getJobExecutionFailureOrigin,\n getJobScope,\n getStepAttemptDetail,\n getWorkflowRunAttemptById,\n} from '#db/index.js';\nimport {recordWorkflowFailureAnnotationFailed} from '#metrics/instance.js';\n\nconst JOB_FAILURE_ANNOTATION_REASONS = new Set(['timed_out', 'runner_lost', 'condition_errored']);\n\nexport function onStepAttemptTerminatedFailureAnnotation(\n annotations: AnnotationsInterModuleClient,\n) {\n return async (payload: WorkflowsStepAttemptTerminatedEventDto): Promise<void> => {\n // A first successful/cancelled attempt cannot have a stale failure annotation.\n // Keep later terminal attempts on the lookup path so recovery removes the\n // annotation created by an earlier failed attempt.\n if (payload.status !== undefined && payload.status !== 'failed' && payload.attempt === 1) {\n return;\n }\n\n try {\n const initialDetail = await getStepAttemptDetail({\n stepId: payload.stepId,\n attempt: payload.attempt,\n });\n if (!initialDetail) return;\n\n // The detail query joins the requested attempt to the step's current projection. A\n // delayed event can therefore return an old attempt alongside a newer step status. Read\n // the canonical current attempt before deciding whether to replace or remove the card.\n const detail =\n initialDetail.attempt.attempt === initialDetail.step.currentAttempt\n ? initialDetail\n : await getStepAttemptDetail({\n stepId: initialDetail.step.id,\n attempt: initialDetail.step.currentAttempt,\n });\n if (!detail || detail.attempt.attempt !== detail.step.currentAttempt) return;\n\n const runAttempt = await getWorkflowRunAttemptById(payload.workflowRunAttemptId);\n if (!runAttempt) return;\n\n await writeFailureAnnotation({\n annotations,\n target: {\n workspaceId: payload.workspaceId,\n projectId: payload.projectId,\n workflowRunId: payload.workflowRunId,\n workflowRunAttempt: runAttempt.attempt,\n workflowRunAttemptId: payload.workflowRunAttemptId,\n jobId: payload.jobId,\n jobExecutionId: detail.step.jobExecutionId,\n originStepId: detail.step.id,\n originStepAttempt: detail.attempt.attempt,\n },\n context: failureContext('step', detail.step.id),\n failed:\n detail.step.status === 'failed' ||\n (detail.attempt.status === 'failed' &&\n detail.step.status !== 'succeeded' &&\n detail.step.status !== 'cancelled'),\n body: stepFailureBody(\n detail.step.name,\n detail.attempt.error ?? detail.step.error,\n detail.attempt.exitCode,\n ),\n });\n } catch (error) {\n recordFailureAnnotationFailure(error, 'lookup', {\n stepId: payload.stepId,\n jobId: payload.jobId,\n });\n }\n };\n}\n\nexport function onJobTerminatedFailureAnnotation(annotations: AnnotationsInterModuleClient) {\n return async (payload: WorkflowsJobTerminatedEventDto): Promise<void> => {\n // Step failures already have a step-scoped annotation. Job-scoped annotations\n // are reserved for terminal causes where no step-level failure card exists.\n const isConditionEvaluationFailure =\n payload.status === 'skipped' && payload.statusReason === 'condition_errored';\n if (\n (payload.status !== 'failed' && !isConditionEvaluationFailure) ||\n !JOB_FAILURE_ANNOTATION_REASONS.has(payload.statusReason ?? '')\n ) {\n return;\n }\n\n try {\n const [scope, runAttempt] = await Promise.all([\n getJobScope(payload.jobId),\n getWorkflowRunAttemptById(payload.workflowRunAttemptId),\n ]);\n if (!scope || !runAttempt || !payload.jobExecutionId) return;\n\n const origin = await getJobExecutionFailureOrigin(payload.jobExecutionId);\n if (!origin) return;\n\n await writeFailureAnnotation({\n annotations,\n target: {\n workspaceId: scope.workspaceId,\n projectId: scope.projectId,\n workflowRunId: payload.workflowRunId,\n workflowRunAttempt: runAttempt.attempt,\n workflowRunAttemptId: payload.workflowRunAttemptId,\n jobId: payload.jobId,\n jobExecutionId: origin.jobExecutionId,\n originStepId: origin.stepId,\n originStepAttempt: origin.stepAttempt,\n },\n context: failureContext('job', payload.jobId),\n failed: true,\n body: jobFailureBody(payload.statusReason, origin),\n });\n } catch (error) {\n recordFailureAnnotationFailure(error, 'lookup', {jobId: payload.jobId});\n }\n };\n}\n\ntype FailureAnnotationTarget = {\n workspaceId: string;\n projectId: string;\n workflowRunId: string;\n workflowRunAttempt: number;\n workflowRunAttemptId: string;\n jobId: string;\n jobExecutionId: string;\n originStepId: string;\n originStepAttempt: number;\n};\n\n/**\n * Failure annotations are a best-effort projection. The workflow terminal fact is authoritative;\n * projection lookup and writes are swallowed so they cannot change the workflow outcome. Every\n * swallowed error emits a reason-labelled metric and a structured warning for operations.\n */\nasync function writeFailureAnnotation(params: {\n annotations: AnnotationsInterModuleClient;\n target: FailureAnnotationTarget;\n context: string;\n failed: boolean;\n body: string;\n}): Promise<void> {\n try {\n await params.annotations.replaceOrRemoveAnnotation({\n ...params.target,\n context: params.context,\n annotation: params.failed\n ? {op: 'replace', style: 'error', body: params.body}\n : {op: 'remove'},\n });\n } catch (error) {\n const reason = isInterModuleKnownError(\n annotationsInterModuleContract.methods.replaceOrRemoveAnnotation,\n error,\n )\n ? 'budget'\n : 'write';\n recordFailureAnnotationFailure(error, reason, params.target);\n }\n}\n\nfunction failureContext(kind: 'job' | 'step', id: string): string {\n return `failure:${kind}:${id}`;\n}\n\nfunction stepFailureBody(\n name: string,\n error: Record<string, unknown> | null,\n exitCode: number | null,\n): string {\n const reason = typeof error?.reason === 'string' ? error.reason : 'unknown';\n const message =\n typeof error?.message === 'string' && error.message.trim()\n ? error.message\n : 'No failure message was recorded.';\n return [\n `**${name} failed**`,\n '',\n `Reason: \\`${reason}\\``,\n `Exit code: \\`${exitCode ?? 'none'}\\``,\n '',\n message,\n ].join('\\n');\n}\n\nfunction jobFailureBody(\n reason: string | null,\n origin: {\n stepName: string;\n attemptStatus: string | null;\n stepError: Record<string, unknown> | null;\n attemptError: Record<string, unknown> | null;\n attemptExitCode: number | null;\n },\n): string {\n const progress = origin.attemptStatus\n ? `The job stopped while processing **${origin.stepName}**.`\n : `The job stopped before **${origin.stepName}** started.`;\n const error = origin.attemptError ?? origin.stepError;\n const message = typeof error?.message === 'string' && error.message.trim() ? error.message : null;\n const exitCode =\n origin.attemptExitCode === null ? null : `Exit code: \\`${origin.attemptExitCode}\\``;\n return [\n `**Job failed before completion**`,\n '',\n progress,\n `Reason: \\`${reason ?? 'unknown'}\\``,\n message ? `Failure: ${message}` : null,\n exitCode,\n ]\n .filter(Boolean)\n .join('\\n');\n}\n\nfunction recordFailureAnnotationFailure(\n error: unknown,\n reason: 'lookup' | 'budget' | 'write',\n context: Record<string, string | number>,\n): void {\n recordWorkflowFailureAnnotationFailed(reason);\n logger().warn({error, reason, ...context}, 'Failed to project workflow failure annotation');\n}\n"],"names":["annotationsInterModuleContract","isInterModuleKnownError","logger","getJobExecutionFailureOrigin","getJobScope","getStepAttemptDetail","getWorkflowRunAttemptById","recordWorkflowFailureAnnotationFailed","JOB_FAILURE_ANNOTATION_REASONS","Set","onStepAttemptTerminatedFailureAnnotation","annotations","payload","status","undefined","attempt","initialDetail","stepId","detail","step","currentAttempt","id","runAttempt","workflowRunAttemptId","writeFailureAnnotation","target","workspaceId","projectId","workflowRunId","workflowRunAttempt","jobId","jobExecutionId","originStepId","originStepAttempt","context","failureContext","failed","body","stepFailureBody","name","error","exitCode","recordFailureAnnotationFailure","onJobTerminatedFailureAnnotation","isConditionEvaluationFailure","statusReason","has","scope","Promise","all","origin","stepAttempt","jobFailureBody","params","replaceOrRemoveAnnotation","annotation","op","style","reason","methods","kind","message","trim","join","progress","attemptStatus","stepName","attemptError","stepError","attemptExitCode","filter","Boolean","warn"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/subscribers/on-failure-annotations.ts"],"sourcesContent":["import {\n type AnnotationsInterModuleClient,\n annotationsInterModuleContract,\n} from '@shipfox/annotations-dto/inter-module';\nimport type {\n WorkflowsJobTerminatedEventDto,\n WorkflowsStepAttemptTerminatedEventDto,\n} from '@shipfox/api-workflows-dto';\nimport {isInterModuleKnownError} from '@shipfox/inter-module';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {\n getJobExecutionFailureOrigin,\n getJobScope,\n getStepAttemptDetail,\n getWorkflowRunAttemptById,\n} from '#db/index.js';\nimport {recordWorkflowFailureAnnotationFailed} from '#metrics/instance.js';\n\nconst JOB_FAILURE_ANNOTATION_REASONS = new Set([\n 'timed_out',\n 'runner_lost',\n 'condition_errored',\n 'output_too_large',\n 'output_invalid',\n]);\n\nexport function onStepAttemptTerminatedFailureAnnotation(\n annotations: AnnotationsInterModuleClient,\n) {\n return async (payload: WorkflowsStepAttemptTerminatedEventDto): Promise<void> => {\n // A first successful/cancelled attempt cannot have a stale failure annotation.\n // Keep later terminal attempts on the lookup path so recovery removes the\n // annotation created by an earlier failed attempt.\n if (payload.status !== undefined && payload.status !== 'failed' && payload.attempt === 1) {\n return;\n }\n\n try {\n const initialDetail = await getStepAttemptDetail({\n stepId: payload.stepId,\n attempt: payload.attempt,\n });\n if (!initialDetail) return;\n\n // The detail query joins the requested attempt to the step's current projection. A\n // delayed event can therefore return an old attempt alongside a newer step status. Read\n // the canonical current attempt before deciding whether to replace or remove the card.\n const detail =\n initialDetail.attempt.attempt === initialDetail.step.currentAttempt\n ? initialDetail\n : await getStepAttemptDetail({\n stepId: initialDetail.step.id,\n attempt: initialDetail.step.currentAttempt,\n });\n if (!detail || detail.attempt.attempt !== detail.step.currentAttempt) return;\n\n const runAttempt = await getWorkflowRunAttemptById(payload.workflowRunAttemptId);\n if (!runAttempt) return;\n\n await writeFailureAnnotation({\n annotations,\n target: {\n workspaceId: payload.workspaceId,\n projectId: payload.projectId,\n workflowRunId: payload.workflowRunId,\n workflowRunAttempt: runAttempt.attempt,\n workflowRunAttemptId: payload.workflowRunAttemptId,\n jobId: payload.jobId,\n jobExecutionId: detail.step.jobExecutionId,\n originStepId: detail.step.id,\n originStepAttempt: detail.attempt.attempt,\n },\n context: failureContext('step', detail.step.id),\n failed:\n detail.step.status === 'failed' ||\n (detail.attempt.status === 'failed' &&\n detail.step.status !== 'succeeded' &&\n detail.step.status !== 'cancelled'),\n body: stepFailureBody(\n detail.step.name,\n detail.attempt.error ?? detail.step.error,\n detail.attempt.exitCode,\n ),\n });\n } catch (error) {\n recordFailureAnnotationFailure(error, 'lookup', {\n stepId: payload.stepId,\n jobId: payload.jobId,\n });\n }\n };\n}\n\nexport function onJobTerminatedFailureAnnotation(annotations: AnnotationsInterModuleClient) {\n return async (payload: WorkflowsJobTerminatedEventDto): Promise<void> => {\n // Step failures already have a step-scoped annotation. Job-scoped annotations\n // are reserved for terminal causes where no step-level failure card exists.\n const isConditionEvaluationFailure =\n payload.status === 'skipped' && payload.statusReason === 'condition_errored';\n if (\n (payload.status !== 'failed' && !isConditionEvaluationFailure) ||\n !JOB_FAILURE_ANNOTATION_REASONS.has(payload.statusReason ?? '')\n ) {\n return;\n }\n\n try {\n const [scope, runAttempt] = await Promise.all([\n getJobScope(payload.jobId),\n getWorkflowRunAttemptById(payload.workflowRunAttemptId),\n ]);\n if (!scope || !runAttempt || !payload.jobExecutionId) return;\n\n const origin = await getJobExecutionFailureOrigin(payload.jobExecutionId);\n if (!origin) return;\n\n await writeFailureAnnotation({\n annotations,\n target: {\n workspaceId: scope.workspaceId,\n projectId: scope.projectId,\n workflowRunId: payload.workflowRunId,\n workflowRunAttempt: runAttempt.attempt,\n workflowRunAttemptId: payload.workflowRunAttemptId,\n jobId: payload.jobId,\n jobExecutionId: origin.jobExecutionId,\n originStepId: origin.stepId,\n originStepAttempt: origin.stepAttempt,\n },\n context: failureContext('job', payload.jobId),\n failed: true,\n body: jobFailureBody(payload.statusReason, payload.statusReasonMessage, origin),\n });\n } catch (error) {\n recordFailureAnnotationFailure(error, 'lookup', {jobId: payload.jobId});\n }\n };\n}\n\ntype FailureAnnotationTarget = {\n workspaceId: string;\n projectId: string;\n workflowRunId: string;\n workflowRunAttempt: number;\n workflowRunAttemptId: string;\n jobId: string;\n jobExecutionId: string;\n originStepId: string;\n originStepAttempt: number;\n};\n\n/**\n * Failure annotations are a best-effort projection. The workflow terminal fact is authoritative;\n * projection lookup and writes are swallowed so they cannot change the workflow outcome. Every\n * swallowed error emits a reason-labelled metric and a structured warning for operations.\n */\nasync function writeFailureAnnotation(params: {\n annotations: AnnotationsInterModuleClient;\n target: FailureAnnotationTarget;\n context: string;\n failed: boolean;\n body: string;\n}): Promise<void> {\n try {\n await params.annotations.replaceOrRemoveAnnotation({\n ...params.target,\n context: params.context,\n annotation: params.failed\n ? {op: 'replace', style: 'error', body: params.body}\n : {op: 'remove'},\n });\n } catch (error) {\n const reason = isInterModuleKnownError(\n annotationsInterModuleContract.methods.replaceOrRemoveAnnotation,\n error,\n )\n ? 'budget'\n : 'write';\n recordFailureAnnotationFailure(error, reason, params.target);\n }\n}\n\nfunction failureContext(kind: 'job' | 'step', id: string): string {\n return `failure:${kind}:${id}`;\n}\n\nfunction stepFailureBody(\n name: string,\n error: Record<string, unknown> | null,\n exitCode: number | null,\n): string {\n const reason = typeof error?.reason === 'string' ? error.reason : 'unknown';\n const message =\n typeof error?.message === 'string' && error.message.trim()\n ? error.message\n : 'No failure message was recorded.';\n return [\n `**${name} failed**`,\n '',\n `Reason: \\`${reason}\\``,\n `Exit code: \\`${exitCode ?? 'none'}\\``,\n '',\n message,\n ].join('\\n');\n}\n\nfunction jobFailureBody(\n reason: string | null,\n statusReasonMessage: string | null | undefined,\n origin: {\n stepName: string;\n attemptStatus: string | null;\n stepError: Record<string, unknown> | null;\n attemptError: Record<string, unknown> | null;\n attemptExitCode: number | null;\n },\n): string {\n const progress = origin.attemptStatus\n ? `The job stopped while processing **${origin.stepName}**.`\n : `The job stopped before **${origin.stepName}** started.`;\n const error = origin.attemptError ?? origin.stepError;\n const message = typeof error?.message === 'string' && error.message.trim() ? error.message : null;\n const exitCode =\n origin.attemptExitCode === null ? null : `Exit code: \\`${origin.attemptExitCode}\\``;\n return [\n `**Job failed before completion**`,\n '',\n progress,\n `Reason: \\`${reason ?? 'unknown'}\\``,\n statusReasonMessage ? `Failure: ${statusReasonMessage}` : null,\n message ? `Failure: ${message}` : null,\n exitCode,\n ]\n .filter(Boolean)\n .join('\\n');\n}\n\nfunction recordFailureAnnotationFailure(\n error: unknown,\n reason: 'lookup' | 'budget' | 'write',\n context: Record<string, string | number>,\n): void {\n recordWorkflowFailureAnnotationFailed(reason);\n logger().warn({error, reason, ...context}, 'Failed to project workflow failure annotation');\n}\n"],"names":["annotationsInterModuleContract","isInterModuleKnownError","logger","getJobExecutionFailureOrigin","getJobScope","getStepAttemptDetail","getWorkflowRunAttemptById","recordWorkflowFailureAnnotationFailed","JOB_FAILURE_ANNOTATION_REASONS","Set","onStepAttemptTerminatedFailureAnnotation","annotations","payload","status","undefined","attempt","initialDetail","stepId","detail","step","currentAttempt","id","runAttempt","workflowRunAttemptId","writeFailureAnnotation","target","workspaceId","projectId","workflowRunId","workflowRunAttempt","jobId","jobExecutionId","originStepId","originStepAttempt","context","failureContext","failed","body","stepFailureBody","name","error","exitCode","recordFailureAnnotationFailure","onJobTerminatedFailureAnnotation","isConditionEvaluationFailure","statusReason","has","scope","Promise","all","origin","stepAttempt","jobFailureBody","statusReasonMessage","params","replaceOrRemoveAnnotation","annotation","op","style","reason","methods","kind","message","trim","join","progress","attemptStatus","stepName","attemptError","stepError","attemptExitCode","filter","Boolean","warn"],"mappings":"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"}
|