@shipfox/api-workflows 12.2.0 → 12.3.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 +16 -0
- package/README.md +20 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +56 -0
- package/dist/config.js.map +1 -0
- package/dist/core/errors.d.ts +2 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +3 -2
- package/dist/core/errors.js.map +1 -1
- package/dist/core/step-config/materialize-workflow-model.d.ts.map +1 -1
- package/dist/core/step-config/materialize-workflow-model.js +6 -4
- package/dist/core/step-config/materialize-workflow-model.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/workflow-runs/jobs.d.ts.map +1 -1
- package/dist/db/workflow-runs/jobs.js +4 -0
- package/dist/db/workflow-runs/jobs.js.map +1 -1
- package/dist/db/workflow-runs/outbox.d.ts +1 -0
- package/dist/db/workflow-runs/outbox.d.ts.map +1 -1
- package/dist/db/workflow-runs/outbox.js +1 -0
- package/dist/db/workflow-runs/outbox.js.map +1 -1
- package/dist/db/workflow-runs/steps.d.ts +28 -0
- package/dist/db/workflow-runs/steps.d.ts.map +1 -1
- package/dist/db/workflow-runs/steps.js +49 -1
- package/dist/db/workflow-runs/steps.js.map +1 -1
- package/dist/db/workflow-runs.d.ts +2 -2
- package/dist/db/workflow-runs.d.ts.map +1 -1
- package/dist/db/workflow-runs.js +1 -1
- package/dist/db/workflow-runs.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/metrics/instance.d.ts +1 -0
- package/dist/metrics/instance.d.ts.map +1 -1
- package/dist/metrics/instance.js +8 -0
- package/dist/metrics/instance.js.map +1 -1
- package/dist/presentation/dto/evaluation-trace.d.ts +4 -0
- package/dist/presentation/dto/evaluation-trace.d.ts.map +1 -0
- package/dist/presentation/dto/evaluation-trace.js +39 -0
- package/dist/presentation/dto/evaluation-trace.js.map +1 -0
- package/dist/presentation/dto/index.d.ts +2 -1
- package/dist/presentation/dto/index.d.ts.map +1 -1
- package/dist/presentation/dto/index.js +2 -1
- package/dist/presentation/dto/index.js.map +1 -1
- package/dist/presentation/dto/job.d.ts.map +1 -1
- package/dist/presentation/dto/job.js +6 -0
- package/dist/presentation/dto/job.js.map +1 -1
- package/dist/presentation/dto/step.d.ts +2 -1
- package/dist/presentation/dto/step.d.ts.map +1 -1
- package/dist/presentation/dto/step.js +12 -0
- package/dist/presentation/dto/step.js.map +1 -1
- package/dist/presentation/index.d.ts +1 -1
- package/dist/presentation/index.d.ts.map +1 -1
- package/dist/presentation/index.js +1 -1
- package/dist/presentation/index.js.map +1 -1
- package/dist/presentation/routes/get-step-attempt-detail.d.ts +3 -0
- package/dist/presentation/routes/get-step-attempt-detail.d.ts.map +1 -0
- package/dist/presentation/routes/get-step-attempt-detail.js +38 -0
- package/dist/presentation/routes/get-step-attempt-detail.js.map +1 -0
- package/dist/presentation/routes/index.d.ts.map +1 -1
- package/dist/presentation/routes/index.js +2 -0
- package/dist/presentation/routes/index.js.map +1 -1
- package/dist/presentation/subscribers/index.d.ts +1 -0
- package/dist/presentation/subscribers/index.d.ts.map +1 -1
- package/dist/presentation/subscribers/index.js +1 -0
- package/dist/presentation/subscribers/index.js.map +1 -1
- package/dist/presentation/subscribers/on-failure-annotations.d.ts +5 -0
- package/dist/presentation/subscribers/on-failure-annotations.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-failure-annotations.js +160 -0
- package/dist/presentation/subscribers/on-failure-annotations.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +10 -8
- package/src/config.test.ts +85 -0
- package/src/config.ts +71 -0
- package/src/core/errors.ts +9 -2
- package/src/core/job-execution.test.ts +1 -0
- package/src/core/step-config/materialize-workflow-model.catalog.test.ts +117 -0
- package/src/core/step-config/materialize-workflow-model.ts +5 -3
- package/src/db/index.ts +4 -0
- package/src/db/workflow-runs/job-status.test.ts +10 -3
- package/src/db/workflow-runs/jobs.ts +7 -0
- package/src/db/workflow-runs/outbox.ts +7 -1
- package/src/db/workflow-runs/steps.test.ts +148 -0
- package/src/db/workflow-runs/steps.ts +105 -1
- package/src/db/workflow-runs.ts +4 -0
- package/src/index.ts +9 -0
- package/src/metrics/instance.ts +10 -0
- package/src/presentation/dto/evaluation-trace.ts +27 -0
- package/src/presentation/dto/index.ts +2 -1
- package/src/presentation/dto/job.ts +6 -0
- package/src/presentation/dto/step.test.ts +96 -1
- package/src/presentation/dto/step.ts +17 -0
- package/src/presentation/index.ts +2 -0
- package/src/presentation/routes/get-step-attempt-detail.test.ts +159 -0
- package/src/presentation/routes/get-step-attempt-detail.ts +33 -0
- package/src/presentation/routes/index.ts +2 -0
- package/src/presentation/subscribers/index.ts +4 -0
- package/src/presentation/subscribers/on-failure-annotations.test.ts +510 -0
- package/src/presentation/subscribers/on-failure-annotations.ts +237 -0
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/subscribers/index.ts"],"sourcesContent":["export {onJobEventDelivered} from './on-job-event-delivered.js';\nexport {onJobStepsSettled} from './on-job-steps-settled.js';\nexport {onRunnerJobClaimed} from './on-runner-job-claimed.js';\nexport {onRunnerJobLeaseExpired} from './on-runner-job-lease-expired.js';\nexport {onRunnerJobQueued} from './on-runner-job-queued.js';\nexport {onWorkflowRunAttemptCreated} from './on-workflow-run-attempt-created.js';\nexport {onWorkflowRunCancelled} from './on-workflow-run-cancelled.js';\n"],"names":["onJobEventDelivered","onJobStepsSettled","onRunnerJobClaimed","onRunnerJobLeaseExpired","onRunnerJobQueued","onWorkflowRunAttemptCreated","onWorkflowRunCancelled"],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/subscribers/index.ts"],"sourcesContent":["export {\n onJobTerminatedFailureAnnotation,\n onStepAttemptTerminatedFailureAnnotation,\n} from './on-failure-annotations.js';\nexport {onJobEventDelivered} from './on-job-event-delivered.js';\nexport {onJobStepsSettled} from './on-job-steps-settled.js';\nexport {onRunnerJobClaimed} from './on-runner-job-claimed.js';\nexport {onRunnerJobLeaseExpired} from './on-runner-job-lease-expired.js';\nexport {onRunnerJobQueued} from './on-runner-job-queued.js';\nexport {onWorkflowRunAttemptCreated} from './on-workflow-run-attempt-created.js';\nexport {onWorkflowRunCancelled} from './on-workflow-run-cancelled.js';\n"],"names":["onJobTerminatedFailureAnnotation","onStepAttemptTerminatedFailureAnnotation","onJobEventDelivered","onJobStepsSettled","onRunnerJobClaimed","onRunnerJobLeaseExpired","onRunnerJobQueued","onWorkflowRunAttemptCreated","onWorkflowRunCancelled"],"mappings":"AAAA,SACEA,gCAAgC,EAChCC,wCAAwC,QACnC,8BAA8B;AACrC,SAAQC,mBAAmB,QAAO,8BAA8B;AAChE,SAAQC,iBAAiB,QAAO,4BAA4B;AAC5D,SAAQC,kBAAkB,QAAO,6BAA6B;AAC9D,SAAQC,uBAAuB,QAAO,mCAAmC;AACzE,SAAQC,iBAAiB,QAAO,4BAA4B;AAC5D,SAAQC,2BAA2B,QAAO,uCAAuC;AACjF,SAAQC,sBAAsB,QAAO,iCAAiC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type AnnotationsInterModuleClient } from '@shipfox/annotations-dto/inter-module';
|
|
2
|
+
import type { WorkflowsJobTerminatedEventDto, WorkflowsStepAttemptTerminatedEventDto } from '@shipfox/api-workflows-dto';
|
|
3
|
+
export declare function onStepAttemptTerminatedFailureAnnotation(annotations: AnnotationsInterModuleClient): (payload: WorkflowsStepAttemptTerminatedEventDto) => Promise<void>;
|
|
4
|
+
export declare function onJobTerminatedFailureAnnotation(annotations: AnnotationsInterModuleClient): (payload: WorkflowsJobTerminatedEventDto) => Promise<void>;
|
|
5
|
+
//# sourceMappingURL=on-failure-annotations.d.ts.map
|
|
@@ -0,0 +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;AAapC,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"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { annotationsInterModuleContract } from '@shipfox/annotations-dto/inter-module';
|
|
2
|
+
import { isInterModuleKnownError } from '@shipfox/inter-module';
|
|
3
|
+
import { logger } from '@shipfox/node-opentelemetry';
|
|
4
|
+
import { getJobExecutionFailureOrigin, getJobScope, getStepAttemptDetail, getWorkflowRunAttemptById } from '#db/index.js';
|
|
5
|
+
import { recordWorkflowFailureAnnotationFailed } from '#metrics/instance.js';
|
|
6
|
+
const JOB_FAILURE_ANNOTATION_REASONS = new Set([
|
|
7
|
+
'timed_out',
|
|
8
|
+
'runner_lost',
|
|
9
|
+
'condition_errored'
|
|
10
|
+
]);
|
|
11
|
+
export function onStepAttemptTerminatedFailureAnnotation(annotations) {
|
|
12
|
+
return async (payload)=>{
|
|
13
|
+
// A first successful/cancelled attempt cannot have a stale failure annotation.
|
|
14
|
+
// Keep later terminal attempts on the lookup path so recovery removes the
|
|
15
|
+
// annotation created by an earlier failed attempt.
|
|
16
|
+
if (payload.status !== undefined && payload.status !== 'failed' && payload.attempt === 1) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const initialDetail = await getStepAttemptDetail({
|
|
21
|
+
stepId: payload.stepId,
|
|
22
|
+
attempt: payload.attempt
|
|
23
|
+
});
|
|
24
|
+
if (!initialDetail) return;
|
|
25
|
+
// The detail query joins the requested attempt to the step's current projection. A
|
|
26
|
+
// delayed event can therefore return an old attempt alongside a newer step status. Read
|
|
27
|
+
// the canonical current attempt before deciding whether to replace or remove the card.
|
|
28
|
+
const detail = initialDetail.attempt.attempt === initialDetail.step.currentAttempt ? initialDetail : await getStepAttemptDetail({
|
|
29
|
+
stepId: initialDetail.step.id,
|
|
30
|
+
attempt: initialDetail.step.currentAttempt
|
|
31
|
+
});
|
|
32
|
+
if (!detail || detail.attempt.attempt !== detail.step.currentAttempt) return;
|
|
33
|
+
const runAttempt = await getWorkflowRunAttemptById(payload.workflowRunAttemptId);
|
|
34
|
+
if (!runAttempt) return;
|
|
35
|
+
await writeFailureAnnotation({
|
|
36
|
+
annotations,
|
|
37
|
+
target: {
|
|
38
|
+
workspaceId: payload.workspaceId,
|
|
39
|
+
projectId: payload.projectId,
|
|
40
|
+
workflowRunId: payload.workflowRunId,
|
|
41
|
+
workflowRunAttempt: runAttempt.attempt,
|
|
42
|
+
workflowRunAttemptId: payload.workflowRunAttemptId,
|
|
43
|
+
jobId: payload.jobId,
|
|
44
|
+
jobExecutionId: detail.step.jobExecutionId,
|
|
45
|
+
originStepId: detail.step.id,
|
|
46
|
+
originStepAttempt: detail.attempt.attempt
|
|
47
|
+
},
|
|
48
|
+
context: failureContext('step', detail.step.id),
|
|
49
|
+
failed: detail.step.status === 'failed' || detail.attempt.status === 'failed' && detail.step.status !== 'succeeded' && detail.step.status !== 'cancelled',
|
|
50
|
+
body: stepFailureBody(detail.step.name, detail.attempt.error ?? detail.step.error, detail.attempt.exitCode)
|
|
51
|
+
});
|
|
52
|
+
} catch (error) {
|
|
53
|
+
recordFailureAnnotationFailure(error, 'lookup', {
|
|
54
|
+
stepId: payload.stepId,
|
|
55
|
+
jobId: payload.jobId
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function onJobTerminatedFailureAnnotation(annotations) {
|
|
61
|
+
return async (payload)=>{
|
|
62
|
+
// Step failures already have a step-scoped annotation. Job-scoped annotations
|
|
63
|
+
// are reserved for terminal causes where no step-level failure card exists.
|
|
64
|
+
const isConditionEvaluationFailure = payload.status === 'skipped' && payload.statusReason === 'condition_errored';
|
|
65
|
+
if (payload.status !== 'failed' && !isConditionEvaluationFailure || !JOB_FAILURE_ANNOTATION_REASONS.has(payload.statusReason ?? '')) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const [scope, runAttempt] = await Promise.all([
|
|
70
|
+
getJobScope(payload.jobId),
|
|
71
|
+
getWorkflowRunAttemptById(payload.workflowRunAttemptId)
|
|
72
|
+
]);
|
|
73
|
+
if (!scope || !runAttempt || !payload.jobExecutionId) return;
|
|
74
|
+
const origin = await getJobExecutionFailureOrigin(payload.jobExecutionId);
|
|
75
|
+
if (!origin) return;
|
|
76
|
+
await writeFailureAnnotation({
|
|
77
|
+
annotations,
|
|
78
|
+
target: {
|
|
79
|
+
workspaceId: scope.workspaceId,
|
|
80
|
+
projectId: scope.projectId,
|
|
81
|
+
workflowRunId: payload.workflowRunId,
|
|
82
|
+
workflowRunAttempt: runAttempt.attempt,
|
|
83
|
+
workflowRunAttemptId: payload.workflowRunAttemptId,
|
|
84
|
+
jobId: payload.jobId,
|
|
85
|
+
jobExecutionId: origin.jobExecutionId,
|
|
86
|
+
originStepId: origin.stepId,
|
|
87
|
+
originStepAttempt: origin.stepAttempt
|
|
88
|
+
},
|
|
89
|
+
context: failureContext('job', payload.jobId),
|
|
90
|
+
failed: true,
|
|
91
|
+
body: jobFailureBody(payload.statusReason, origin)
|
|
92
|
+
});
|
|
93
|
+
} catch (error) {
|
|
94
|
+
recordFailureAnnotationFailure(error, 'lookup', {
|
|
95
|
+
jobId: payload.jobId
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Failure annotations are a best-effort projection. The workflow terminal fact is authoritative;
|
|
102
|
+
* projection lookup and writes are swallowed so they cannot change the workflow outcome. Every
|
|
103
|
+
* swallowed error emits a reason-labelled metric and a structured warning for operations.
|
|
104
|
+
*/ async function writeFailureAnnotation(params) {
|
|
105
|
+
try {
|
|
106
|
+
await params.annotations.replaceOrRemoveAnnotation({
|
|
107
|
+
...params.target,
|
|
108
|
+
context: params.context,
|
|
109
|
+
annotation: params.failed ? {
|
|
110
|
+
op: 'replace',
|
|
111
|
+
style: 'error',
|
|
112
|
+
body: params.body
|
|
113
|
+
} : {
|
|
114
|
+
op: 'remove'
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
} catch (error) {
|
|
118
|
+
const reason = isInterModuleKnownError(annotationsInterModuleContract.methods.replaceOrRemoveAnnotation, error) ? 'budget' : 'write';
|
|
119
|
+
recordFailureAnnotationFailure(error, reason, params.target);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function failureContext(kind, id) {
|
|
123
|
+
return `failure:${kind}:${id}`;
|
|
124
|
+
}
|
|
125
|
+
function stepFailureBody(name, error, exitCode) {
|
|
126
|
+
const reason = typeof error?.reason === 'string' ? error.reason : 'unknown';
|
|
127
|
+
const message = typeof error?.message === 'string' && error.message.trim() ? error.message : 'No failure message was recorded.';
|
|
128
|
+
return [
|
|
129
|
+
`**${name} failed**`,
|
|
130
|
+
'',
|
|
131
|
+
`Reason: \`${reason}\``,
|
|
132
|
+
`Exit code: \`${exitCode ?? 'none'}\``,
|
|
133
|
+
'',
|
|
134
|
+
message
|
|
135
|
+
].join('\n');
|
|
136
|
+
}
|
|
137
|
+
function jobFailureBody(reason, origin) {
|
|
138
|
+
const progress = origin.attemptStatus ? `The job stopped while processing **${origin.stepName}**.` : `The job stopped before **${origin.stepName}** started.`;
|
|
139
|
+
const error = origin.attemptError ?? origin.stepError;
|
|
140
|
+
const message = typeof error?.message === 'string' && error.message.trim() ? error.message : null;
|
|
141
|
+
const exitCode = origin.attemptExitCode === null ? null : `Exit code: \`${origin.attemptExitCode}\``;
|
|
142
|
+
return [
|
|
143
|
+
`**Job failed before completion**`,
|
|
144
|
+
'',
|
|
145
|
+
progress,
|
|
146
|
+
`Reason: \`${reason ?? 'unknown'}\``,
|
|
147
|
+
message ? `Failure: ${message}` : null,
|
|
148
|
+
exitCode
|
|
149
|
+
].filter(Boolean).join('\n');
|
|
150
|
+
}
|
|
151
|
+
function recordFailureAnnotationFailure(error, reason, context) {
|
|
152
|
+
recordWorkflowFailureAnnotationFailed(reason);
|
|
153
|
+
logger().warn({
|
|
154
|
+
error,
|
|
155
|
+
reason,
|
|
156
|
+
...context
|
|
157
|
+
}, 'Failed to project workflow failure annotation');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
//# sourceMappingURL=on-failure-annotations.js.map
|
|
@@ -0,0 +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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"}
|