@shipfox/api-workflows-dto 12.3.0 → 12.6.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipfox/api-workflows-dto",
3
3
  "license": "MIT",
4
- "version": "12.3.0",
4
+ "version": "12.6.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/ShipfoxHQ/shipfox.git",
@@ -139,6 +139,45 @@ describe('workflowsJobTerminatedSchema', () => {
139
139
  expect(result).toEqual(input);
140
140
  });
141
141
 
142
+ it('accepts a string status reason message', () => {
143
+ const input = {
144
+ ...validJobTerminated,
145
+ status: 'failed',
146
+ statusReason: 'output_too_large',
147
+ statusReasonMessage: 'Job output exceeded the configured size limit.',
148
+ };
149
+
150
+ const result = workflowsJobTerminatedSchema.parse(input);
151
+
152
+ expect(result).toEqual(input);
153
+ });
154
+
155
+ it('accepts a null status reason message', () => {
156
+ const input = {
157
+ ...validJobTerminated,
158
+ status: 'failed',
159
+ statusReason: 'unknown',
160
+ statusReasonMessage: null,
161
+ };
162
+
163
+ const result = workflowsJobTerminatedSchema.parse(input);
164
+
165
+ expect(result).toEqual(input);
166
+ });
167
+
168
+ it('rejects a non-string status reason message', () => {
169
+ const input = {
170
+ ...validJobTerminated,
171
+ status: 'failed',
172
+ statusReason: 'output_too_large',
173
+ statusReasonMessage: 413,
174
+ };
175
+
176
+ const parse = () => workflowsJobTerminatedSchema.parse(input);
177
+
178
+ expect(parse).toThrow();
179
+ });
180
+
142
181
  it('strips unknown keys (tolerant of forward-compatible producer additions)', () => {
143
182
  const input = {...validJobTerminated, addedLater: 'ignored'};
144
183
 
package/src/events.ts CHANGED
@@ -147,6 +147,7 @@ export const workflowsJobTerminatedSchema = z.object({
147
147
  workflowRunAttemptId: nonEmptyStringSchema,
148
148
  status: jobTerminalStatusSchema,
149
149
  statusReason: jobStatusReasonSchema.nullable(),
150
+ statusReasonMessage: z.string().nullable().optional(),
150
151
  });
151
152
  export type WorkflowsJobTerminatedEventDto = z.infer<typeof workflowsJobTerminatedSchema>;
152
153
 
package/src/index.ts CHANGED
@@ -26,6 +26,7 @@ export {
26
26
  type JobStatusDto,
27
27
  type JobStatusReasonDto,
28
28
  jobDtoSchema,
29
+ jobExecutionStatusSchema,
29
30
  jobListeningBatchSchema,
30
31
  jobListeningSchema,
31
32
  jobModeSchema,
@@ -95,6 +96,7 @@ export {
95
96
  workflowRunDetailResponseSchema,
96
97
  workflowRunDtoSchema,
97
98
  workflowRunJobDetailDtoSchema,
99
+ workflowRunJobDisplayStatusCountDtoSchema,
98
100
  workflowRunJobExecutionDetailDtoSchema,
99
101
  workflowRunJobStatusCountDtoSchema,
100
102
  workflowRunJobSummaryDtoSchema,
@@ -109,6 +111,7 @@ export {
109
111
  export {type StepSourceLocationDto, stepSourceLocationSchema} from '#schemas/step.js';
110
112
  export {
111
113
  WORKFLOW_RUN_JOB_PREVIEW_LIMIT,
114
+ type WorkflowRunJobDisplayStatusCountDto,
112
115
  type WorkflowRunJobStatusCountDto,
113
116
  type WorkflowRunJobSummaryDto,
114
117
  type WorkflowRunListItemDto,
@@ -81,6 +81,7 @@ export {
81
81
  stepStatusReasonSchema,
82
82
  } from './step.js';
83
83
  export {
84
+ jobExecutionStatusSchema,
84
85
  type RerunWorkflowRunBodyDto,
85
86
  rerunWorkflowRunBodySchema,
86
87
  WORKFLOW_RUN_JOB_PREVIEW_LIMIT,
@@ -89,6 +90,7 @@ export {
89
90
  type WorkflowRunAttemptDto,
90
91
  type WorkflowRunAttemptsResponseDto,
91
92
  type WorkflowRunDto,
93
+ type WorkflowRunJobDisplayStatusCountDto,
92
94
  type WorkflowRunJobStatusCountDto,
93
95
  type WorkflowRunJobSummaryDto,
94
96
  type WorkflowRunListItemDto,
@@ -104,6 +106,7 @@ export {
104
106
  workflowRunAttemptDtoSchema,
105
107
  workflowRunAttemptsResponseSchema,
106
108
  workflowRunDtoSchema,
109
+ workflowRunJobDisplayStatusCountDtoSchema,
107
110
  workflowRunJobStatusCountDtoSchema,
108
111
  workflowRunJobSummaryDtoSchema,
109
112
  workflowRunListItemSchema,
@@ -118,7 +121,6 @@ export {
118
121
  export {
119
122
  type JobExecutionDto,
120
123
  jobExecutionDtoSchema,
121
- jobExecutionStatusSchema,
122
124
  type StepAttemptDetailResponseDto,
123
125
  stepAttemptDetailResponseSchema,
124
126
  type WorkflowRunDetailResponseDto,
@@ -42,4 +42,17 @@ describe('job DTO schema', () => {
42
42
 
43
43
  expect(result.status_reason).toBe(statusReason);
44
44
  });
45
+
46
+ it.each([
47
+ 'output_too_large',
48
+ 'output_invalid',
49
+ ] as const)('accepts job failure reason "%s"', (statusReason) => {
50
+ const result = jobDtoSchema.parse({
51
+ ...baseJob,
52
+ status: 'failed',
53
+ status_reason: statusReason,
54
+ });
55
+
56
+ expect(result.status_reason).toBe(statusReason);
57
+ });
45
58
  });
@@ -26,8 +26,10 @@ export const jobStatusReasonSchema = z.enum([
26
26
  'run_cancelled',
27
27
  'timed_out',
28
28
  'runner_lost',
29
+ 'output_too_large',
29
30
  'step_failed',
30
31
  'unknown',
32
+ 'output_invalid',
31
33
  ]);
32
34
 
33
35
  export const jobDtoSchema = z.object({
@@ -3,15 +3,11 @@ import {evaluationTraceSchema} from './evaluation-trace.js';
3
3
  import {jobDtoSchema} from './job.js';
4
4
  import {workflowExecutionEventSchema} from './job-listening.js';
5
5
  import {stepAttemptDetailDtoSchema, stepAttemptDtoSchema, stepDtoSchema} from './step.js';
6
- import {workflowRunAttemptDtoSchema, workflowRunResponseSchema} from './workflow-run.js';
7
-
8
- export const jobExecutionStatusSchema = z.enum([
9
- 'pending',
10
- 'running',
11
- 'succeeded',
12
- 'failed',
13
- 'cancelled',
14
- ]);
6
+ import {
7
+ jobExecutionStatusSchema,
8
+ workflowRunAttemptDtoSchema,
9
+ workflowRunResponseSchema,
10
+ } from './workflow-run.js';
15
11
 
16
12
  export const jobExecutionDtoSchema = z.object({
17
13
  id: z.string().uuid(),
@@ -20,6 +16,7 @@ export const jobExecutionDtoSchema = z.object({
20
16
  name: z.string(),
21
17
  status: jobExecutionStatusSchema,
22
18
  status_reason: z.string().nullable(),
19
+ status_reason_message: z.string().nullable().optional(),
23
20
  runner: z.array(z.string()).nullable(),
24
21
  trigger_events: z.array(workflowExecutionEventSchema).default([]),
25
22
  outputs: z.record(z.string(), z.unknown()).nullable(),
@@ -107,6 +107,9 @@ describe('workflow run list item schema', () => {
107
107
  key: `job-${position}`,
108
108
  name: null,
109
109
  status: 'succeeded' as const,
110
+ mode: 'one_shot' as const,
111
+ listener_status: 'inactive' as const,
112
+ execution_status: null,
110
113
  position,
111
114
  };
112
115
  }
@@ -121,6 +124,55 @@ describe('workflow run list item schema', () => {
121
124
 
122
125
  expect(result.jobs).toHaveLength(1);
123
126
  expect(result.jobs[0]?.status).toBe('succeeded');
127
+ expect(result.jobs[0]?.execution_status).toBeNull();
128
+ });
129
+
130
+ test('carries execution evidence and listening state for display derivation', () => {
131
+ const result = workflowRunListItemSchema.parse({
132
+ ...baseRun,
133
+ source_snapshot: null,
134
+ jobs: [
135
+ {
136
+ ...jobDto(0),
137
+ mode: 'listening',
138
+ listener_status: 'listening',
139
+ execution_status: 'running',
140
+ },
141
+ ],
142
+ job_status_counts: [{status: 'running', count: 1}],
143
+ job_display_status_counts: [{status: 'listening', count: 1}],
144
+ });
145
+
146
+ expect(result.jobs[0]).toMatchObject({
147
+ mode: 'listening',
148
+ listener_status: 'listening',
149
+ execution_status: 'running',
150
+ });
151
+ expect(result.job_status_counts).toEqual([{status: 'running', count: 1}]);
152
+ expect(result.job_display_status_counts).toEqual([{status: 'listening', count: 1}]);
153
+ });
154
+
155
+ test('accepts a pre-display-state API response during a mixed-version rollout', () => {
156
+ const {
157
+ mode: _mode,
158
+ listener_status: _listenerStatus,
159
+ execution_status: _executionStatus,
160
+ ...legacyJob
161
+ } = jobDto(0);
162
+
163
+ const result = workflowRunListItemSchema.parse({
164
+ ...baseRun,
165
+ source_snapshot: null,
166
+ jobs: [legacyJob],
167
+ job_status_counts: [{status: 'running', count: 1}],
168
+ });
169
+
170
+ expect(result.jobs[0]).toMatchObject({
171
+ mode: 'one_shot',
172
+ listener_status: 'inactive',
173
+ execution_status: null,
174
+ });
175
+ expect(result.job_display_status_counts).toBeUndefined();
124
176
  });
125
177
 
126
178
  // The preview is a bounded slice, so counts describe jobs the payload never carried.
@@ -1,5 +1,6 @@
1
1
  import {z} from 'zod';
2
2
  import {jobStatusSchema} from './job.js';
3
+ import {jobModeSchema, listenerStatusSchema} from './job-listening.js';
3
4
 
4
5
  export const workflowRunStatusSchema = z.enum([
5
6
  'pending',
@@ -11,6 +12,14 @@ export const workflowRunStatusSchema = z.enum([
11
12
 
12
13
  export type WorkflowRunStatusDto = z.infer<typeof workflowRunStatusSchema>;
13
14
 
15
+ export const jobExecutionStatusSchema = z.enum([
16
+ 'pending',
17
+ 'running',
18
+ 'succeeded',
19
+ 'failed',
20
+ 'cancelled',
21
+ ]);
22
+
14
23
  export const workflowRunRerunModeSchema = z.enum(['all', 'failed']);
15
24
 
16
25
  export type WorkflowRunRerunModeDto = z.infer<typeof workflowRunRerunModeSchema>;
@@ -137,16 +146,22 @@ export const workflowRunAttemptsResponseSchema = z.object({
137
146
  export type WorkflowRunAttemptsResponseDto = z.infer<typeof workflowRunAttemptsResponseSchema>;
138
147
 
139
148
  // The run list renders a status glyph per job so a failing run can be read without being
140
- // opened, which needs the current attempt's jobs in graph order but none of their steps.
149
+ // opened. Runtime state comes from the selected execution rather than the job verdict, while
150
+ // mode and listener status let the client apply the same display rule as run detail. These
151
+ // fields default to the pre-display-state contract so a web client can roll out before or
152
+ // alongside an API deployment without rejecting an older response.
141
153
  export const workflowRunJobSummaryDtoSchema = z.object({
142
154
  id: z.string().uuid(),
143
155
  key: z.string(),
144
156
  name: z.string().nullable(),
145
157
  status: jobStatusSchema,
158
+ mode: jobModeSchema.optional().default('one_shot'),
159
+ listener_status: listenerStatusSchema.optional().default('inactive'),
160
+ execution_status: jobExecutionStatusSchema.nullable().optional().default(null),
146
161
  position: z.number().int().nonnegative(),
147
162
  });
148
163
 
149
- export type WorkflowRunJobSummaryDto = z.infer<typeof workflowRunJobSummaryDtoSchema>;
164
+ export type WorkflowRunJobSummaryDto = z.input<typeof workflowRunJobSummaryDtoSchema>;
150
165
 
151
166
  /**
152
167
  * How many jobs a run list row carries in graph order.
@@ -158,7 +173,7 @@ export type WorkflowRunJobSummaryDto = z.infer<typeof workflowRunJobSummaryDtoSc
158
173
  */
159
174
  export const WORKFLOW_RUN_JOB_PREVIEW_LIMIT = 16;
160
175
 
161
- /** One status and how many of the run's jobs hold it, counted over all of them. */
176
+ /** The persisted job verdict and how many of the run's jobs carry it, counted over all of them. */
162
177
  export const workflowRunJobStatusCountDtoSchema = z.object({
163
178
  status: jobStatusSchema,
164
179
  count: z.number().int().positive(),
@@ -166,14 +181,29 @@ export const workflowRunJobStatusCountDtoSchema = z.object({
166
181
 
167
182
  export type WorkflowRunJobStatusCountDto = z.infer<typeof workflowRunJobStatusCountDtoSchema>;
168
183
 
184
+ /** One display status and how many of the run's jobs render it, counted over all of them. */
185
+ export const workflowRunJobDisplayStatusCountDtoSchema = z.object({
186
+ status: jobStatusSchema.or(z.literal('listening')),
187
+ count: z.number().int().positive(),
188
+ });
189
+
190
+ export type WorkflowRunJobDisplayStatusCountDto = z.infer<
191
+ typeof workflowRunJobDisplayStatusCountDtoSchema
192
+ >;
193
+
169
194
  export const workflowRunListItemSchema = workflowRunResponseSchema.extend({
170
195
  /** Up to `WORKFLOW_RUN_JOB_PREVIEW_LIMIT` jobs in graph order, not the whole set. */
171
196
  jobs: z.array(workflowRunJobSummaryDtoSchema).max(WORKFLOW_RUN_JOB_PREVIEW_LIMIT),
172
- /** Counted over every job of the attempt, including those past the preview. */
197
+ /** Persisted verdict counts, kept stable so older web clients can consume new responses. */
173
198
  job_status_counts: z.array(workflowRunJobStatusCountDtoSchema),
199
+ /**
200
+ * Display-state counts over every job of the attempt, including those past the preview.
201
+ * Optional so a new web client can consume an older API response during rollout.
202
+ */
203
+ job_display_status_counts: z.array(workflowRunJobDisplayStatusCountDtoSchema).optional(),
174
204
  });
175
205
 
176
- export type WorkflowRunListItemDto = z.infer<typeof workflowRunListItemSchema>;
206
+ export type WorkflowRunListItemDto = z.input<typeof workflowRunListItemSchema>;
177
207
 
178
208
  export const workflowRunListResponseSchema = z.object({
179
209
  runs: z.array(workflowRunListItemSchema),
@@ -181,7 +211,7 @@ export const workflowRunListResponseSchema = z.object({
181
211
  filtered_total_count: z.number().int().nonnegative().nullable(),
182
212
  });
183
213
 
184
- export type WorkflowRunListResponseDto = z.infer<typeof workflowRunListResponseSchema>;
214
+ export type WorkflowRunListResponseDto = z.input<typeof workflowRunListResponseSchema>;
185
215
 
186
216
  const aggregateBucketSchema = z.object({
187
217
  value: z.string(),