@shipfox/api-workflows 12.1.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.
Files changed (159) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +45 -0
  3. package/README.md +20 -0
  4. package/dist/config.d.ts +11 -0
  5. package/dist/config.d.ts.map +1 -0
  6. package/dist/config.js +56 -0
  7. package/dist/config.js.map +1 -0
  8. package/dist/core/entities/job.d.ts +1 -1
  9. package/dist/core/entities/job.d.ts.map +1 -1
  10. package/dist/core/entities/step.d.ts +2 -2
  11. package/dist/core/entities/step.d.ts.map +1 -1
  12. package/dist/core/errors.d.ts +18 -1
  13. package/dist/core/errors.d.ts.map +1 -1
  14. package/dist/core/errors.js +21 -2
  15. package/dist/core/errors.js.map +1 -1
  16. package/dist/core/step-config/assemble-run-context.d.ts +15 -5
  17. package/dist/core/step-config/assemble-run-context.d.ts.map +1 -1
  18. package/dist/core/step-config/assemble-run-context.js +77 -27
  19. package/dist/core/step-config/assemble-run-context.js.map +1 -1
  20. package/dist/core/step-config/fields.d.ts +5 -0
  21. package/dist/core/step-config/fields.d.ts.map +1 -1
  22. package/dist/core/step-config/fields.js +21 -3
  23. package/dist/core/step-config/fields.js.map +1 -1
  24. package/dist/core/step-config/index.d.ts +1 -1
  25. package/dist/core/step-config/index.d.ts.map +1 -1
  26. package/dist/core/step-config/index.js +1 -1
  27. package/dist/core/step-config/index.js.map +1 -1
  28. package/dist/core/step-config/job-output-limits.d.ts +11 -0
  29. package/dist/core/step-config/job-output-limits.d.ts.map +1 -0
  30. package/dist/core/step-config/job-output-limits.js +88 -0
  31. package/dist/core/step-config/job-output-limits.js.map +1 -0
  32. package/dist/core/step-config/materialize-workflow-model.d.ts +1 -1
  33. package/dist/core/step-config/materialize-workflow-model.d.ts.map +1 -1
  34. package/dist/core/step-config/materialize-workflow-model.js +47 -18
  35. package/dist/core/step-config/materialize-workflow-model.js.map +1 -1
  36. package/dist/db/db.d.ts +50 -50
  37. package/dist/db/index.d.ts +2 -2
  38. package/dist/db/index.d.ts.map +1 -1
  39. package/dist/db/index.js +1 -1
  40. package/dist/db/index.js.map +1 -1
  41. package/dist/db/job-listeners.d.ts.map +1 -1
  42. package/dist/db/job-listeners.js +6 -5
  43. package/dist/db/job-listeners.js.map +1 -1
  44. package/dist/db/schema/job-executions.d.ts +3 -3
  45. package/dist/db/schema/job-listener-events.d.ts +1 -1
  46. package/dist/db/schema/jobs.d.ts +7 -7
  47. package/dist/db/schema/outbox.d.ts +1 -1
  48. package/dist/db/schema/step-attempts.d.ts +2 -2
  49. package/dist/db/schema/steps.d.ts +5 -5
  50. package/dist/db/schema/workflow-run-attempts.d.ts +3 -3
  51. package/dist/db/schema/workflow-run-counters.d.ts +1 -1
  52. package/dist/db/schema/workflow-runs.d.ts +2 -2
  53. package/dist/db/workflow-runs/job-executions.d.ts.map +1 -1
  54. package/dist/db/workflow-runs/job-executions.js +4 -2
  55. package/dist/db/workflow-runs/job-executions.js.map +1 -1
  56. package/dist/db/workflow-runs/jobs.d.ts.map +1 -1
  57. package/dist/db/workflow-runs/jobs.js +33 -3
  58. package/dist/db/workflow-runs/jobs.js.map +1 -1
  59. package/dist/db/workflow-runs/outbox.d.ts +1 -0
  60. package/dist/db/workflow-runs/outbox.d.ts.map +1 -1
  61. package/dist/db/workflow-runs/outbox.js +1 -0
  62. package/dist/db/workflow-runs/outbox.js.map +1 -1
  63. package/dist/db/workflow-runs/steps.d.ts +28 -0
  64. package/dist/db/workflow-runs/steps.d.ts.map +1 -1
  65. package/dist/db/workflow-runs/steps.js +49 -1
  66. package/dist/db/workflow-runs/steps.js.map +1 -1
  67. package/dist/db/workflow-runs.d.ts +2 -2
  68. package/dist/db/workflow-runs.d.ts.map +1 -1
  69. package/dist/db/workflow-runs.js +1 -1
  70. package/dist/db/workflow-runs.js.map +1 -1
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +4 -2
  73. package/dist/index.js.map +1 -1
  74. package/dist/metrics/instance.d.ts +1 -0
  75. package/dist/metrics/instance.d.ts.map +1 -1
  76. package/dist/metrics/instance.js +8 -0
  77. package/dist/metrics/instance.js.map +1 -1
  78. package/dist/presentation/dto/evaluation-trace.d.ts +4 -0
  79. package/dist/presentation/dto/evaluation-trace.d.ts.map +1 -0
  80. package/dist/presentation/dto/evaluation-trace.js +39 -0
  81. package/dist/presentation/dto/evaluation-trace.js.map +1 -0
  82. package/dist/presentation/dto/index.d.ts +2 -1
  83. package/dist/presentation/dto/index.d.ts.map +1 -1
  84. package/dist/presentation/dto/index.js +2 -1
  85. package/dist/presentation/dto/index.js.map +1 -1
  86. package/dist/presentation/dto/job.d.ts.map +1 -1
  87. package/dist/presentation/dto/job.js +6 -0
  88. package/dist/presentation/dto/job.js.map +1 -1
  89. package/dist/presentation/dto/step.d.ts +2 -1
  90. package/dist/presentation/dto/step.d.ts.map +1 -1
  91. package/dist/presentation/dto/step.js +12 -0
  92. package/dist/presentation/dto/step.js.map +1 -1
  93. package/dist/presentation/index.d.ts +1 -1
  94. package/dist/presentation/index.d.ts.map +1 -1
  95. package/dist/presentation/index.js +1 -1
  96. package/dist/presentation/index.js.map +1 -1
  97. package/dist/presentation/routes/get-step-attempt-detail.d.ts +3 -0
  98. package/dist/presentation/routes/get-step-attempt-detail.d.ts.map +1 -0
  99. package/dist/presentation/routes/get-step-attempt-detail.js +38 -0
  100. package/dist/presentation/routes/get-step-attempt-detail.js.map +1 -0
  101. package/dist/presentation/routes/index.d.ts.map +1 -1
  102. package/dist/presentation/routes/index.js +2 -0
  103. package/dist/presentation/routes/index.js.map +1 -1
  104. package/dist/presentation/routes/project-access.d.ts +1 -1
  105. package/dist/presentation/subscribers/index.d.ts +1 -0
  106. package/dist/presentation/subscribers/index.d.ts.map +1 -1
  107. package/dist/presentation/subscribers/index.js +1 -0
  108. package/dist/presentation/subscribers/index.js.map +1 -1
  109. package/dist/presentation/subscribers/on-failure-annotations.d.ts +5 -0
  110. package/dist/presentation/subscribers/on-failure-annotations.d.ts.map +1 -0
  111. package/dist/presentation/subscribers/on-failure-annotations.js +160 -0
  112. package/dist/presentation/subscribers/on-failure-annotations.js.map +1 -0
  113. package/dist/temporal/activities/index.d.ts +1 -1
  114. package/dist/temporal/activities/index.d.ts.map +1 -1
  115. package/dist/temporal/activities/orchestration-activities.d.ts +1 -1
  116. package/dist/temporal/activities/orchestration-activities.d.ts.map +1 -1
  117. package/dist/temporal/workflows/test-env.d.ts +2 -2
  118. package/dist/tsconfig.test.tsbuildinfo +1 -1
  119. package/package.json +18 -16
  120. package/src/config.test.ts +85 -0
  121. package/src/config.ts +71 -0
  122. package/src/core/errors.ts +44 -2
  123. package/src/core/job-execution.test.ts +1 -0
  124. package/src/core/step-config/assemble-run-context.test.ts +232 -20
  125. package/src/core/step-config/assemble-run-context.ts +126 -28
  126. package/src/core/step-config/fields.ts +42 -3
  127. package/src/core/step-config/index.ts +2 -0
  128. package/src/core/step-config/job-output-limits.ts +130 -0
  129. package/src/core/step-config/materialize-workflow-model.catalog.test.ts +117 -0
  130. package/src/core/step-config/materialize-workflow-model.test.ts +306 -1
  131. package/src/core/step-config/materialize-workflow-model.ts +65 -18
  132. package/src/db/index.ts +4 -0
  133. package/src/db/job-listeners.ts +14 -3
  134. package/src/db/workflow-runs/job-executions.test.ts +207 -1
  135. package/src/db/workflow-runs/job-executions.ts +15 -2
  136. package/src/db/workflow-runs/job-status.test.ts +10 -3
  137. package/src/db/workflow-runs/jobs.test.ts +49 -0
  138. package/src/db/workflow-runs/jobs.ts +53 -6
  139. package/src/db/workflow-runs/outbox.ts +7 -1
  140. package/src/db/workflow-runs/run-create.test.ts +56 -0
  141. package/src/db/workflow-runs/steps.test.ts +148 -0
  142. package/src/db/workflow-runs/steps.ts +105 -1
  143. package/src/db/workflow-runs.ts +4 -0
  144. package/src/index.ts +9 -0
  145. package/src/metrics/instance.ts +10 -0
  146. package/src/presentation/dto/evaluation-trace.ts +27 -0
  147. package/src/presentation/dto/index.ts +2 -1
  148. package/src/presentation/dto/job.ts +6 -0
  149. package/src/presentation/dto/step.test.ts +96 -1
  150. package/src/presentation/dto/step.ts +17 -0
  151. package/src/presentation/index.ts +2 -0
  152. package/src/presentation/routes/get-step-attempt-detail.test.ts +159 -0
  153. package/src/presentation/routes/get-step-attempt-detail.ts +33 -0
  154. package/src/presentation/routes/index.ts +2 -0
  155. package/src/presentation/subscribers/index.ts +4 -0
  156. package/src/presentation/subscribers/on-failure-annotations.test.ts +510 -0
  157. package/src/presentation/subscribers/on-failure-annotations.ts +237 -0
  158. package/test/factories/workflow-model.ts +2 -0
  159. package/tsconfig.build.tsbuildinfo +1 -1
@@ -0,0 +1,159 @@
1
+ import {buildUserContext, setUserContext} from '@shipfox/api-auth-context';
2
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
3
+ import {ClientError} from '@shipfox/node-fastify';
4
+ import type {FastifyInstance} from 'fastify';
5
+ import Fastify from 'fastify';
6
+ import {serializerCompiler, validatorCompiler} from 'fastify-type-provider-zod';
7
+ import type {Step, StepAttempt} from '#core/entities/step.js';
8
+ import * as dbMocks from '#db/index.js';
9
+ import {getStepAttemptDetailRoute} from './get-step-attempt-detail.js';
10
+
11
+ vi.mock('#db/index.js', () => ({
12
+ getStepAttemptDetail: vi.fn(),
13
+ getWorkflowRunById: vi.fn(),
14
+ }));
15
+
16
+ const getProjectById = vi.fn();
17
+ const projects = {getProjectById} as unknown as ProjectsModuleClient;
18
+ const STEP_ID = crypto.randomUUID();
19
+ const getStepAttemptDetail = vi.mocked(dbMocks.getStepAttemptDetail);
20
+ const getWorkflowRunById = vi.mocked(dbMocks.getWorkflowRunById);
21
+
22
+ describe('GET /api/workflows/runs/steps/:stepId/attempts/:attempt', () => {
23
+ let app: FastifyInstance;
24
+ let workspaceId: string;
25
+ const workflowRunId = crypto.randomUUID();
26
+ const projectId = crypto.randomUUID();
27
+
28
+ beforeAll(async () => {
29
+ app = Fastify();
30
+ app.setValidatorCompiler(validatorCompiler);
31
+ app.setSerializerCompiler(serializerCompiler);
32
+ app.addHook('onRequest', (request, _reply, done) => {
33
+ setUserContext(
34
+ request,
35
+ buildUserContext({
36
+ userId: crypto.randomUUID(),
37
+ email: 'user@example.com',
38
+ memberships: [{workspaceId, role: 'admin', workspaceStatus: 'active'}],
39
+ }),
40
+ );
41
+ done();
42
+ });
43
+ app.get(
44
+ '/api/workflows/runs/steps/:stepId/attempts/:attempt',
45
+ getStepAttemptDetailRoute(projects),
46
+ );
47
+ await app.ready();
48
+ });
49
+
50
+ beforeEach(() => {
51
+ workspaceId = crypto.randomUUID();
52
+ vi.clearAllMocks();
53
+ getProjectById.mockResolvedValue({
54
+ project: {id: projectId, workspaceId, name: 'Project'},
55
+ });
56
+ getWorkflowRunById.mockResolvedValue({projectId} as never);
57
+ });
58
+
59
+ it('returns the requested attempt detail for an accessible run', async () => {
60
+ const step = stepEntity();
61
+ const attempt = stepAttemptEntity();
62
+ getStepAttemptDetail.mockResolvedValue({
63
+ workflowRunId,
64
+ workflowRunAttemptId: crypto.randomUUID(),
65
+ step,
66
+ attempt,
67
+ });
68
+
69
+ const response = await app.inject({
70
+ method: 'GET',
71
+ url: `/api/workflows/runs/steps/${STEP_ID}/attempts/1`,
72
+ });
73
+
74
+ expect(response.statusCode).toBe(200);
75
+ expect(response.json()).toMatchObject({
76
+ step_id: STEP_ID,
77
+ attempt: 1,
78
+ authored_config: {run: 'pnpm test'},
79
+ config: {run: 'pnpm test'},
80
+ });
81
+ expect(getProjectById).toHaveBeenCalledWith({projectId});
82
+ });
83
+
84
+ it('returns 404 for a missing attempt', async () => {
85
+ getStepAttemptDetail.mockResolvedValue(undefined);
86
+
87
+ const response = await app.inject({
88
+ method: 'GET',
89
+ url: `/api/workflows/runs/steps/${STEP_ID}/attempts/1`,
90
+ });
91
+
92
+ expect(response.statusCode).toBe(404);
93
+ expect(getProjectById).not.toHaveBeenCalled();
94
+ });
95
+
96
+ it('returns 404 for an attempt in an inaccessible run', async () => {
97
+ getStepAttemptDetail.mockResolvedValue({
98
+ workflowRunId,
99
+ workflowRunAttemptId: crypto.randomUUID(),
100
+ step: stepEntity(),
101
+ attempt: stepAttemptEntity(),
102
+ });
103
+ getProjectById.mockRejectedValueOnce(new ClientError('Forbidden', 'forbidden', {status: 403}));
104
+
105
+ const response = await app.inject({
106
+ method: 'GET',
107
+ url: `/api/workflows/runs/steps/${STEP_ID}/attempts/1`,
108
+ });
109
+
110
+ expect(response.statusCode).toBe(404);
111
+ expect(response.json().code).toBe('not-found');
112
+ });
113
+ });
114
+
115
+ function stepEntity(): Step {
116
+ return {
117
+ id: STEP_ID,
118
+ jobExecutionId: crypto.randomUUID(),
119
+ key: 'test',
120
+ name: 'Run tests',
121
+ sourceLocation: null,
122
+ status: 'failed',
123
+ statusReason: null,
124
+ evaluationTrace: null,
125
+ type: 'run',
126
+ config: {run: 'pnpm test'},
127
+ condition: null,
128
+ configPlan: null,
129
+ authoredConfig: {run: 'pnpm test'},
130
+ error: {reason: 'command_failed', message: 'Command failed'},
131
+ position: 1,
132
+ version: 1,
133
+ currentAttempt: 1,
134
+ createdAt: new Date('2026-08-05T12:00:00.000Z'),
135
+ updatedAt: new Date('2026-08-05T12:01:00.000Z'),
136
+ };
137
+ }
138
+
139
+ function stepAttemptEntity(): StepAttempt {
140
+ return {
141
+ id: crypto.randomUUID(),
142
+ stepId: STEP_ID,
143
+ attempt: 1,
144
+ executionOrder: 1,
145
+ status: 'failed',
146
+ config: {run: 'pnpm test'},
147
+ evaluationTrace: null,
148
+ output: {result: 'failed'},
149
+ response: null,
150
+ error: {reason: 'command_failed', message: 'Command failed'},
151
+ exitCode: 1,
152
+ gateResult: null,
153
+ restartFeedback: null,
154
+ logOutcome: 'drained',
155
+ startedAt: new Date('2026-08-05T12:00:00.000Z'),
156
+ finishedAt: new Date('2026-08-05T12:01:00.000Z'),
157
+ createdAt: new Date('2026-08-05T12:00:00.000Z'),
158
+ };
159
+ }
@@ -0,0 +1,33 @@
1
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
2
+ import {stepAttemptDetailResponseSchema} from '@shipfox/api-workflows-dto';
3
+ import {ClientError, defineRoute} from '@shipfox/node-fastify';
4
+ import {z} from 'zod';
5
+ import {getStepAttemptDetail} from '#db/index.js';
6
+ import {toStepAttemptDetailResponseDto} from '#presentation/dto/step.js';
7
+ import {requireAccessibleRun} from './require-accessible-run.js';
8
+
9
+ export function getStepAttemptDetailRoute(projects: ProjectsModuleClient) {
10
+ return defineRoute({
11
+ method: 'GET',
12
+ path: '/steps/:stepId/attempts/:attempt',
13
+ description: 'Get troubleshooting details for one step attempt',
14
+ schema: {
15
+ params: z.object({
16
+ stepId: z.string().uuid(),
17
+ attempt: z.coerce.number().int().positive(),
18
+ }),
19
+ response: {
20
+ 200: stepAttemptDetailResponseSchema,
21
+ },
22
+ },
23
+ handler: async (request) => {
24
+ const detail = await getStepAttemptDetail(request.params);
25
+ if (!detail) {
26
+ throw new ClientError('Step attempt not found', 'not-found', {status: 404});
27
+ }
28
+
29
+ await requireAccessibleRun({request, id: detail.workflowRunId, projects});
30
+ return toStepAttemptDetailResponseDto(detail.step, detail.attempt);
31
+ },
32
+ });
33
+ }
@@ -13,6 +13,7 @@ import {cancelRunRoute} from './cancel-run.js';
13
13
  import {createCheckoutTokenRoute} from './checkout-token.js';
14
14
  import {getRunRoute} from './get-run.js';
15
15
  import {getRunAggregatesRoute} from './get-run-aggregates.js';
16
+ import {getStepAttemptDetailRoute} from './get-step-attempt-detail.js';
16
17
  import {createGetStepSecretsRoute} from './get-step-secrets.js';
17
18
  import {listRunAttemptsRoute} from './list-run-attempts.js';
18
19
  import {listRunsRoute} from './list-runs.js';
@@ -62,6 +63,7 @@ export function createWorkflowRoutes(params: WorkflowRouteClients): RouteGroup[]
62
63
  getRunAggregatesRoute(params.projects),
63
64
  listRunAttemptsRoute(params.projects),
64
65
  getRunRoute(params.projects),
66
+ getStepAttemptDetailRoute(params.projects),
65
67
  cancelRunRoute(params.projects),
66
68
  rerunRunRoute(params.projects, params.workspaces),
67
69
  ],
@@ -1,3 +1,7 @@
1
+ export {
2
+ onJobTerminatedFailureAnnotation,
3
+ onStepAttemptTerminatedFailureAnnotation,
4
+ } from './on-failure-annotations.js';
1
5
  export {onJobEventDelivered} from './on-job-event-delivered.js';
2
6
  export {onJobStepsSettled} from './on-job-steps-settled.js';
3
7
  export {onRunnerJobClaimed} from './on-runner-job-claimed.js';