@shipfox/api-workflows-dto 10.0.0 → 12.0.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 (52) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +34 -0
  3. package/dist/index.d.ts +3 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +3 -2
  6. package/dist/index.js.map +1 -1
  7. package/dist/inter-module.d.ts +53 -1
  8. package/dist/inter-module.d.ts.map +1 -1
  9. package/dist/inter-module.js +30 -2
  10. package/dist/inter-module.js.map +1 -1
  11. package/dist/schemas/checkout-token.d.ts +9 -0
  12. package/dist/schemas/checkout-token.d.ts.map +1 -1
  13. package/dist/schemas/checkout-token.js +7 -0
  14. package/dist/schemas/checkout-token.js.map +1 -1
  15. package/dist/schemas/index.d.ts +3 -3
  16. package/dist/schemas/index.d.ts.map +1 -1
  17. package/dist/schemas/index.js +3 -3
  18. package/dist/schemas/index.js.map +1 -1
  19. package/dist/schemas/job-execution.d.ts +13 -0
  20. package/dist/schemas/job-execution.d.ts.map +1 -1
  21. package/dist/schemas/job-execution.js +7 -0
  22. package/dist/schemas/job-execution.js.map +1 -1
  23. package/dist/schemas/job-listening.d.ts +18 -0
  24. package/dist/schemas/job-listening.d.ts.map +1 -1
  25. package/dist/schemas/job-listening.js +6 -0
  26. package/dist/schemas/job-listening.js.map +1 -1
  27. package/dist/schemas/workflow-run-detail.d.ts +32 -0
  28. package/dist/schemas/workflow-run-detail.d.ts.map +1 -1
  29. package/dist/schemas/workflow-run.d.ts +155 -0
  30. package/dist/schemas/workflow-run.d.ts.map +1 -1
  31. package/dist/schemas/workflow-run.js +39 -1
  32. package/dist/schemas/workflow-run.js.map +1 -1
  33. package/dist/tsconfig.test.tsbuildinfo +1 -1
  34. package/dist/working-directory.d.ts +9 -0
  35. package/dist/working-directory.d.ts.map +1 -0
  36. package/dist/working-directory.js +23 -0
  37. package/dist/working-directory.js.map +1 -0
  38. package/package.json +3 -3
  39. package/src/index.ts +16 -0
  40. package/src/inter-module.test.ts +17 -0
  41. package/src/inter-module.ts +27 -1
  42. package/src/schemas/checkout-token.test.ts +44 -2
  43. package/src/schemas/checkout-token.ts +13 -0
  44. package/src/schemas/index.ts +15 -0
  45. package/src/schemas/job-execution.test.ts +15 -0
  46. package/src/schemas/job-execution.ts +13 -0
  47. package/src/schemas/job-listening.test.ts +4 -0
  48. package/src/schemas/job-listening.ts +4 -0
  49. package/src/schemas/workflow-run.test.ts +106 -1
  50. package/src/schemas/workflow-run.ts +56 -1
  51. package/src/working-directory.ts +28 -0
  52. package/tsconfig.build.tsbuildinfo +1 -1
@@ -0,0 +1,9 @@
1
+ export declare class InvalidWorkingDirectoryError extends Error {
2
+ constructor(value: unknown);
3
+ }
4
+ /**
5
+ * Checks the path shape shared by the server dispatch boundary and the runner.
6
+ * Filesystem containment is checked by the runner after resolving symlinks.
7
+ */
8
+ export declare function assertWorkingDirectory(value: unknown): asserts value is string;
9
+ //# sourceMappingURL=working-directory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"working-directory.d.ts","sourceRoot":"","sources":["../src/working-directory.ts"],"names":[],"mappings":"AAGA,qBAAa,4BAA6B,SAAQ,KAAK;gBACzC,KAAK,EAAE,OAAO;CAM3B;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,MAAM,CAW9E"}
@@ -0,0 +1,23 @@
1
+ const WINDOWS_ABSOLUTE_PATH = /^[A-Za-z]:[\\/]/;
2
+ const PATH_SEPARATOR = /[\\/]/;
3
+ export class InvalidWorkingDirectoryError extends Error {
4
+ constructor(value){
5
+ super(`Invalid working_directory ${JSON.stringify(value)}: it must be a relative path without '..' segments or absolute path syntax.`);
6
+ this.name = 'InvalidWorkingDirectoryError';
7
+ }
8
+ }
9
+ /**
10
+ * Checks the path shape shared by the server dispatch boundary and the runner.
11
+ * Filesystem containment is checked by the runner after resolving symlinks.
12
+ */ export function assertWorkingDirectory(value) {
13
+ if (typeof value !== 'string' || value.length === 0) {
14
+ throw new InvalidWorkingDirectoryError(value);
15
+ }
16
+ const isAbsolute = value.startsWith('/') || value.startsWith('\\') || WINDOWS_ABSOLUTE_PATH.test(value);
17
+ const hasParentSegment = value.split(PATH_SEPARATOR).some((segment)=>segment === '..');
18
+ if (isAbsolute || hasParentSegment) {
19
+ throw new InvalidWorkingDirectoryError(value);
20
+ }
21
+ }
22
+
23
+ //# sourceMappingURL=working-directory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/working-directory.ts"],"sourcesContent":["const WINDOWS_ABSOLUTE_PATH = /^[A-Za-z]:[\\\\/]/;\nconst PATH_SEPARATOR = /[\\\\/]/;\n\nexport class InvalidWorkingDirectoryError extends Error {\n constructor(value: unknown) {\n super(\n `Invalid working_directory ${JSON.stringify(value)}: it must be a relative path without '..' segments or absolute path syntax.`,\n );\n this.name = 'InvalidWorkingDirectoryError';\n }\n}\n\n/**\n * Checks the path shape shared by the server dispatch boundary and the runner.\n * Filesystem containment is checked by the runner after resolving symlinks.\n */\nexport function assertWorkingDirectory(value: unknown): asserts value is string {\n if (typeof value !== 'string' || value.length === 0) {\n throw new InvalidWorkingDirectoryError(value);\n }\n\n const isAbsolute =\n value.startsWith('/') || value.startsWith('\\\\') || WINDOWS_ABSOLUTE_PATH.test(value);\n const hasParentSegment = value.split(PATH_SEPARATOR).some((segment) => segment === '..');\n if (isAbsolute || hasParentSegment) {\n throw new InvalidWorkingDirectoryError(value);\n }\n}\n"],"names":["WINDOWS_ABSOLUTE_PATH","PATH_SEPARATOR","InvalidWorkingDirectoryError","Error","value","JSON","stringify","name","assertWorkingDirectory","length","isAbsolute","startsWith","test","hasParentSegment","split","some","segment"],"mappings":"AAAA,MAAMA,wBAAwB;AAC9B,MAAMC,iBAAiB;AAEvB,OAAO,MAAMC,qCAAqCC;IAChD,YAAYC,KAAc,CAAE;QAC1B,KAAK,CACH,CAAC,0BAA0B,EAAEC,KAAKC,SAAS,CAACF,OAAO,2EAA2E,CAAC;QAEjI,IAAI,CAACG,IAAI,GAAG;IACd;AACF;AAEA;;;CAGC,GACD,OAAO,SAASC,uBAAuBJ,KAAc;IACnD,IAAI,OAAOA,UAAU,YAAYA,MAAMK,MAAM,KAAK,GAAG;QACnD,MAAM,IAAIP,6BAA6BE;IACzC;IAEA,MAAMM,aACJN,MAAMO,UAAU,CAAC,QAAQP,MAAMO,UAAU,CAAC,SAASX,sBAAsBY,IAAI,CAACR;IAChF,MAAMS,mBAAmBT,MAAMU,KAAK,CAACb,gBAAgBc,IAAI,CAAC,CAACC,UAAYA,YAAY;IACnF,IAAIN,cAAcG,kBAAkB;QAClC,MAAM,IAAIX,6BAA6BE;IACzC;AACF"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipfox/api-workflows-dto",
3
3
  "license": "MIT",
4
- "version": "10.0.0",
4
+ "version": "12.0.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/ShipfoxHQ/shipfox.git",
@@ -23,8 +23,8 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "zod": "^4.4.3",
26
- "@shipfox/api-agent-dto": "10.0.0",
27
- "@shipfox/inter-module": "0.2.2"
26
+ "@shipfox/api-agent-dto": "12.0.0",
27
+ "@shipfox/inter-module": "0.2.3"
28
28
  },
29
29
  "imports": {
30
30
  "#*": "./dist/*"
package/src/index.ts CHANGED
@@ -4,10 +4,16 @@ export {
4
4
  agentConfigIssueSchema,
5
5
  agentRuntimeConfigQuerySchema,
6
6
  type CheckoutIntentDto,
7
+ type CheckoutResultDto,
7
8
  type CheckoutTokenAuthDto,
9
+ type CheckoutTokenParamsDto,
10
+ type CheckoutTokenQueryDto,
8
11
  type CheckoutTokenResponseDto,
9
12
  checkoutIntentSchema,
13
+ checkoutResultSchema,
10
14
  checkoutTokenAuthSchema,
15
+ checkoutTokenParamsSchema,
16
+ checkoutTokenQuerySchema,
11
17
  checkoutTokenResponseSchema,
12
18
  type JobDto,
13
19
  type JobExecutionDto,
@@ -79,6 +85,9 @@ export {
79
85
  workflowRunDtoSchema,
80
86
  workflowRunJobDetailDtoSchema,
81
87
  workflowRunJobExecutionDetailDtoSchema,
88
+ workflowRunJobStatusCountDtoSchema,
89
+ workflowRunJobSummaryDtoSchema,
90
+ workflowRunListItemSchema,
82
91
  workflowRunListQuerySchema,
83
92
  workflowRunListResponseSchema,
84
93
  workflowRunRerunModeSchema,
@@ -88,7 +97,13 @@ export {
88
97
  } from '#schemas/index.js';
89
98
  export {type StepSourceLocationDto, stepSourceLocationSchema} from '#schemas/step.js';
90
99
  export {
100
+ WORKFLOW_RUN_JOB_PREVIEW_LIMIT,
101
+ type WorkflowRunJobStatusCountDto,
102
+ type WorkflowRunJobSummaryDto,
103
+ type WorkflowRunListItemDto,
104
+ type WorkflowRunTriggerReferenceDto,
91
105
  type WorkflowSourceSnapshotDto,
106
+ workflowRunTriggerReferenceSchema,
92
107
  workflowSourceSnapshotSchema,
93
108
  } from '#schemas/workflow-run.js';
94
109
  export {
@@ -128,3 +143,4 @@ export {
128
143
  workflowsWorkflowRunCancelledSchema,
129
144
  workflowsWorkflowRunTerminatedSchema,
130
145
  } from './events.js';
146
+ export {assertWorkingDirectory, InvalidWorkingDirectoryError} from './working-directory.js';
@@ -13,6 +13,7 @@ describe('workflowsInterModuleContract', () => {
13
13
  deliveryId: 'delivery-1',
14
14
  data: {ref: 'refs/heads/main'},
15
15
  },
16
+ triggerConnectionId: '00000000-0000-4000-8000-000000000005',
16
17
  idempotencyKey: 'subscription-1:event-1',
17
18
  });
18
19
  const delivery = workflowsInterModuleContract.methods.deliverEventToJobListener.input.parse({
@@ -23,12 +24,28 @@ describe('workflowsInterModuleContract', () => {
23
24
  source: 'github',
24
25
  event: 'push',
25
26
  provider: 'github',
27
+ triggerConnectionId: '00000000-0000-4000-8000-000000000005',
26
28
  payload: {ref: 'refs/heads/main'},
27
29
  receivedAt: '2026-07-20T12:00:00.000Z',
28
30
  });
31
+ const reference =
32
+ workflowsInterModuleContract.methods.resolveWorkflowRunTriggerReference.input.parse({
33
+ workspaceId: '00000000-0000-4000-8000-000000000001',
34
+ triggerConnectionId: '00000000-0000-4000-8000-000000000005',
35
+ triggerPayload: {
36
+ provider: 'github',
37
+ source: 'github',
38
+ event: 'push',
39
+ deliveryId: 'delivery-1',
40
+ data: {ref: 'refs/heads/main'},
41
+ },
42
+ });
29
43
 
30
44
  expect(start.idempotencyKey).toBe('subscription-1:event-1');
45
+ expect(start.triggerConnectionId).toBe('00000000-0000-4000-8000-000000000005');
31
46
  expect(delivery.disposition).toBe('fire');
47
+ expect(reference.triggerConnectionId).toBe('00000000-0000-4000-8000-000000000005');
48
+ expect(delivery.triggerConnectionId).toBe('00000000-0000-4000-8000-000000000005');
32
49
  });
33
50
 
34
51
  test('accepts the minimal Logs and agent-tools query payloads', () => {
@@ -3,6 +3,13 @@ import {defineInterModuleContract, type InterModuleClient} from '@shipfox/inter-
3
3
  import {z} from 'zod';
4
4
 
5
5
  const idSchema = z.string().uuid();
6
+ const workflowRunTriggerReferenceSchema = z.object({
7
+ project: z.object({id: idSchema}).nullable(),
8
+ repository: z.string().nullable(),
9
+ ref: z.string().nullable(),
10
+ commit: z.string().nullable(),
11
+ actor: z.string().nullable(),
12
+ });
6
13
  const triggerPayloadSchema = z.union([
7
14
  z.object({
8
15
  provider: z.literal('manual').optional(),
@@ -32,11 +39,19 @@ const interpolationFieldSchema = z.enum([
32
39
  'agent.prompt',
33
40
  'agent.model',
34
41
  'agent.provider',
42
+ 'agent.thinking',
35
43
  'job.runner',
36
44
  'job.outputs',
37
- 'job.name',
45
+ 'job.execution_name',
46
+ 'workflow.run_name',
38
47
  'step.name',
48
+ 'step.working_directory',
39
49
  'step.feedback',
50
+ 'checkout.project',
51
+ 'checkout.connection',
52
+ 'checkout.repository',
53
+ 'checkout.ref',
54
+ 'checkout.path',
40
55
  ]);
41
56
 
42
57
  /**
@@ -51,6 +66,7 @@ export const workflowsInterModuleContract = defineInterModuleContract({
51
66
  workspaceId: idSchema,
52
67
  projectId: idSchema,
53
68
  definitionId: idSchema,
69
+ triggerConnectionId: idSchema.optional(),
54
70
  triggerPayload: triggerPayloadSchema,
55
71
  inputs: z.record(z.string(), z.unknown()).optional(),
56
72
  idempotencyKey: z.string().min(1),
@@ -73,6 +89,14 @@ export const workflowsInterModuleContract = defineInterModuleContract({
73
89
  'invalid-job-runner-labels': z.object({labels: z.array(z.string())}),
74
90
  },
75
91
  },
92
+ resolveWorkflowRunTriggerReference: {
93
+ input: z.object({
94
+ workspaceId: idSchema,
95
+ triggerConnectionId: idSchema,
96
+ triggerPayload: triggerPayloadSchema,
97
+ }),
98
+ output: workflowRunTriggerReferenceSchema.nullable(),
99
+ },
76
100
  deliverEventToJobListener: {
77
101
  input: z.object({
78
102
  jobId: idSchema,
@@ -82,6 +106,8 @@ export const workflowsInterModuleContract = defineInterModuleContract({
82
106
  source: z.string().min(1),
83
107
  event: z.string().min(1),
84
108
  provider: z.string().min(1),
109
+ triggerConnectionId: idSchema.optional(),
110
+ triggerReference: workflowRunTriggerReferenceSchema.nullable().optional(),
85
111
  payload: z.unknown(),
86
112
  receivedAt: z.string().datetime(),
87
113
  }),
@@ -1,8 +1,33 @@
1
- import {checkoutTokenResponseSchema} from './checkout-token.js';
1
+ import {
2
+ checkoutTokenParamsSchema,
3
+ checkoutTokenQuerySchema,
4
+ checkoutTokenResponseSchema,
5
+ } from './checkout-token.js';
6
+
7
+ describe('checkout token request schemas', () => {
8
+ it('accepts a UUID step id', () => {
9
+ const stepId = crypto.randomUUID();
10
+
11
+ expect(checkoutTokenParamsSchema.parse({stepId})).toEqual({stepId});
12
+ });
13
+
14
+ it('rejects a non-UUID step id', () => {
15
+ expect(() => checkoutTokenParamsSchema.parse({stepId: 'step-1'})).toThrow();
16
+ });
17
+
18
+ it('coerces a positive integer attempt', () => {
19
+ expect(checkoutTokenQuerySchema.parse({attempt: '2'})).toEqual({attempt: 2});
20
+ });
21
+
22
+ it.each([0, -1, 'not-a-number'])('rejects invalid attempt %p', (attempt) => {
23
+ expect(() => checkoutTokenQuerySchema.parse({attempt})).toThrow();
24
+ });
25
+ });
2
26
 
3
27
  const bearerResponse = {
4
28
  repository_url: 'https://github.com/acme/repo.git',
5
29
  ref: 'main',
30
+ fetch_depth: 1,
6
31
  auth: {
7
32
  kind: 'bearer',
8
33
  token: 'gh-token',
@@ -16,6 +41,7 @@ const bearerResponse = {
16
41
  const basicResponse = {
17
42
  repository_url: 'https://github.com/acme/repo.git',
18
43
  ref: 'main',
44
+ fetch_depth: 1,
19
45
  auth: {
20
46
  kind: 'basic',
21
47
  username: 'x-access-token',
@@ -55,7 +81,11 @@ describe('checkoutTokenResponseSchema', () => {
55
81
  });
56
82
 
57
83
  it('accepts a credential-free response with no auth (debug provider)', () => {
58
- const input = {repository_url: 'https://github.com/acme/repo.git', ref: 'main'};
84
+ const input = {
85
+ repository_url: 'https://github.com/acme/repo.git',
86
+ ref: 'main',
87
+ fetch_depth: 1,
88
+ };
59
89
 
60
90
  const result = checkoutTokenResponseSchema.parse(input);
61
91
 
@@ -159,4 +189,16 @@ describe('checkoutTokenResponseSchema', () => {
159
189
 
160
190
  expect(parse).toThrow();
161
191
  });
192
+
193
+ it('accepts zero for a full-history fetch', () => {
194
+ const result = checkoutTokenResponseSchema.parse({...bearerResponse, fetch_depth: 0});
195
+
196
+ expect(result.fetch_depth).toBe(0);
197
+ });
198
+
199
+ it('rejects a negative fetch depth', () => {
200
+ const parse = () => checkoutTokenResponseSchema.parse({...bearerResponse, fetch_depth: -1});
201
+
202
+ expect(parse).toThrow();
203
+ });
162
204
  });
@@ -33,9 +33,22 @@ export const checkoutTokenAuthSchema = z.discriminatedUnion('kind', [
33
33
 
34
34
  export type CheckoutTokenAuthDto = z.infer<typeof checkoutTokenAuthSchema>;
35
35
 
36
+ export const checkoutTokenParamsSchema = z.object({
37
+ stepId: z.string().uuid(),
38
+ });
39
+
40
+ export type CheckoutTokenParamsDto = z.infer<typeof checkoutTokenParamsSchema>;
41
+
42
+ export const checkoutTokenQuerySchema = z.object({
43
+ attempt: z.coerce.number().int().positive(),
44
+ });
45
+
46
+ export type CheckoutTokenQueryDto = z.infer<typeof checkoutTokenQuerySchema>;
47
+
36
48
  export const checkoutTokenResponseSchema = z.object({
37
49
  repository_url: z.string().min(1),
38
50
  ref: z.string().min(1),
51
+ fetch_depth: z.number().int().nonnegative(),
39
52
  git_author: checkoutGitAuthorSchema.optional(),
40
53
  // Optional: credential-free providers (e.g. the debug source control) return a
41
54
  // public clone URL with no auth material, so the runner clones without a token.
@@ -5,8 +5,12 @@ export {
5
5
  export {type CheckoutIntentDto, checkoutIntentSchema} from './checkout.js';
6
6
  export {
7
7
  type CheckoutTokenAuthDto,
8
+ type CheckoutTokenParamsDto,
9
+ type CheckoutTokenQueryDto,
8
10
  type CheckoutTokenResponseDto,
9
11
  checkoutTokenAuthSchema,
12
+ checkoutTokenParamsSchema,
13
+ checkoutTokenQuerySchema,
10
14
  checkoutTokenResponseSchema,
11
15
  } from './checkout-token.js';
12
16
  export {
@@ -18,6 +22,8 @@ export {
18
22
  jobStatusSchema,
19
23
  } from './job.js';
20
24
  export {
25
+ type CheckoutResultDto,
26
+ checkoutResultSchema,
21
27
  type NextStepResponseDto,
22
28
  nextStepResponseSchema,
23
29
  type ReportStepBodyDto,
@@ -66,27 +72,36 @@ export {
66
72
  export {
67
73
  type RerunWorkflowRunBodyDto,
68
74
  rerunWorkflowRunBodySchema,
75
+ WORKFLOW_RUN_JOB_PREVIEW_LIMIT,
69
76
  type WorkflowRunAggregatesQueryDto,
70
77
  type WorkflowRunAggregatesResponseDto,
71
78
  type WorkflowRunAttemptDto,
72
79
  type WorkflowRunAttemptsResponseDto,
73
80
  type WorkflowRunDto,
81
+ type WorkflowRunJobStatusCountDto,
82
+ type WorkflowRunJobSummaryDto,
83
+ type WorkflowRunListItemDto,
74
84
  type WorkflowRunListQueryDto,
75
85
  type WorkflowRunListResponseDto,
76
86
  type WorkflowRunRerunModeDto,
77
87
  type WorkflowRunResponseDto,
78
88
  type WorkflowRunStatusDto,
89
+ type WorkflowRunTriggerReferenceDto,
79
90
  type WorkflowSourceSnapshotDto,
80
91
  workflowRunAggregatesQuerySchema,
81
92
  workflowRunAggregatesResponseSchema,
82
93
  workflowRunAttemptDtoSchema,
83
94
  workflowRunAttemptsResponseSchema,
84
95
  workflowRunDtoSchema,
96
+ workflowRunJobStatusCountDtoSchema,
97
+ workflowRunJobSummaryDtoSchema,
98
+ workflowRunListItemSchema,
85
99
  workflowRunListQuerySchema,
86
100
  workflowRunListResponseSchema,
87
101
  workflowRunRerunModeSchema,
88
102
  workflowRunResponseSchema,
89
103
  workflowRunStatusSchema,
104
+ workflowRunTriggerReferenceSchema,
90
105
  workflowSourceSnapshotSchema,
91
106
  } from './workflow-run.js';
92
107
  export {
@@ -24,4 +24,19 @@ describe('reportStepBodySchema', () => {
24
24
 
25
25
  expect(result.success).toBe(false);
26
26
  });
27
+ it('accepts resolved checkout details as a dedicated report field', () => {
28
+ const parsed = reportStepBodySchema.parse({
29
+ status: 'succeeded',
30
+ attempt: 1,
31
+ exit_code: 0,
32
+ log_outcome: 'drained',
33
+ checkout: {
34
+ repository: 'https://github.com/acme/api.git',
35
+ ref: 'refs/pull/412/head',
36
+ commit: '9f2c000000000000000000000000000000000000',
37
+ path: '/runner/workspace/job-1',
38
+ },
39
+ });
40
+ expect(parsed.checkout?.ref).toBe('refs/pull/412/head');
41
+ });
27
42
  });
@@ -36,6 +36,16 @@ export const nextStepResponseSchema = z.discriminatedUnion('kind', [
36
36
 
37
37
  export type NextStepResponseDto = z.infer<typeof nextStepResponseSchema>;
38
38
 
39
+ export const checkoutResultSchema = z
40
+ .object({
41
+ repository: z.string().min(1),
42
+ ref: z.string().min(1),
43
+ commit: z.string().min(1),
44
+ path: z.string().min(1),
45
+ })
46
+ .describe('Resolved repository checkout details reported by the runner.');
47
+ export type CheckoutResultDto = z.infer<typeof checkoutResultSchema>;
48
+
39
49
  export const reportStepBodySchema = z
40
50
  .object({
41
51
  status: z.enum(['succeeded', 'failed']).describe('Final status reported for the step attempt.'),
@@ -65,6 +75,9 @@ export const reportStepBodySchema = z
65
75
  .describe(
66
76
  'Structured output captured for attempt history. Large textual logs are stored separately.',
67
77
  ),
78
+ checkout: checkoutResultSchema
79
+ .optional()
80
+ .describe('Resolved repository checkout details captured for attempt history.'),
68
81
  response: z
69
82
  .string()
70
83
  .max(STEP_RESPONSE_MAX_LENGTH)
@@ -31,6 +31,10 @@ describe('execution context schemas', () => {
31
31
  event: 'pull_request_review',
32
32
  delivery_id: 'delivery-1',
33
33
  received_at: '2026-06-25T00:00:00.000Z',
34
+ project: null,
35
+ repository: null,
36
+ ref: null,
37
+ commit: null,
34
38
  data: {body: 'LGTM'},
35
39
  };
36
40
 
@@ -33,6 +33,10 @@ export const workflowExecutionEventSchema = z.object({
33
33
  event: z.string(),
34
34
  delivery_id: z.string(),
35
35
  received_at: z.string(),
36
+ project: z.object({id: z.string().uuid()}).nullable(),
37
+ repository: z.string().nullable(),
38
+ ref: z.string().nullable(),
39
+ commit: z.string().nullable(),
36
40
  data: z.unknown(),
37
41
  });
38
42
 
@@ -1,10 +1,17 @@
1
- import {workflowRunDtoSchema, workflowSourceSnapshotSchema} from './workflow-run.js';
1
+ import {
2
+ WORKFLOW_RUN_JOB_PREVIEW_LIMIT,
3
+ workflowRunDtoSchema,
4
+ workflowRunListItemSchema,
5
+ workflowSourceSnapshotSchema,
6
+ } from './workflow-run.js';
2
7
 
3
8
  const baseRun = {
4
9
  id: '11111111-1111-4111-8111-111111111111',
5
10
  project_id: '22222222-2222-4222-8222-222222222222',
6
11
  definition_id: '33333333-3333-4333-8333-333333333333',
12
+ number: 1,
7
13
  name: 'Build',
14
+ workflow_name: 'Build',
8
15
  status: 'pending',
9
16
  source_run_id: null,
10
17
  root_run_id: null,
@@ -16,6 +23,7 @@ const baseRun = {
16
23
  trigger_source: 'manual',
17
24
  trigger_event: 'fire',
18
25
  trigger_payload: {source: 'manual', event: 'fire'},
26
+ trigger_reference: null,
19
27
  inputs: null,
20
28
  created_at: '2026-06-16T00:00:00.000Z',
21
29
  updated_at: '2026-06-16T00:00:00.000Z',
@@ -46,6 +54,8 @@ describe('workflow source snapshot schemas', () => {
46
54
  const result = workflowRunDtoSchema.parse({...baseRun, source_snapshot: null});
47
55
 
48
56
  expect(result.source_snapshot).toBeNull();
57
+ expect(result.name).toBe('Build');
58
+ expect(result.workflow_name).toBe('Build');
49
59
  });
50
60
 
51
61
  test('accepts run DTOs with source snapshots', () => {
@@ -57,3 +67,98 @@ describe('workflow source snapshot schemas', () => {
57
67
  expect(result.source_snapshot).toEqual({content: 'name: Build\njobs: {}\n', format: 'yaml'});
58
68
  });
59
69
  });
70
+
71
+ describe('workflow run trigger reference schema', () => {
72
+ test('accepts a partially resolved reference', () => {
73
+ const result = workflowRunDtoSchema.parse({
74
+ ...baseRun,
75
+ source_snapshot: null,
76
+ trigger_reference: {
77
+ repository: 'acme/api',
78
+ ref: 'refs/heads/main',
79
+ commit: null,
80
+ actor: null,
81
+ },
82
+ });
83
+
84
+ expect(result.trigger_reference).toEqual({
85
+ repository: 'acme/api',
86
+ ref: 'refs/heads/main',
87
+ commit: null,
88
+ actor: null,
89
+ });
90
+ });
91
+
92
+ test('rejects a reference missing a field rather than defaulting it', () => {
93
+ const result = workflowRunDtoSchema.safeParse({
94
+ ...baseRun,
95
+ source_snapshot: null,
96
+ trigger_reference: {repository: 'acme/api', ref: 'refs/heads/main', commit: null},
97
+ });
98
+
99
+ expect(result.success).toBe(false);
100
+ });
101
+ });
102
+
103
+ describe('workflow run list item schema', () => {
104
+ function jobDto(position: number) {
105
+ return {
106
+ id: `44444444-4444-4444-8444-${String(position).padStart(12, '0')}`,
107
+ key: `job-${position}`,
108
+ name: null,
109
+ status: 'succeeded' as const,
110
+ position,
111
+ };
112
+ }
113
+
114
+ test('carries job status glyphs alongside the run', () => {
115
+ const result = workflowRunListItemSchema.parse({
116
+ ...baseRun,
117
+ source_snapshot: null,
118
+ jobs: [jobDto(0)],
119
+ job_status_counts: [{status: 'succeeded', count: 1}],
120
+ });
121
+
122
+ expect(result.jobs).toHaveLength(1);
123
+ expect(result.jobs[0]?.status).toBe('succeeded');
124
+ });
125
+
126
+ // The preview is a bounded slice, so counts describe jobs the payload never carried.
127
+ test('accepts counts larger than the preview it ships', () => {
128
+ const result = workflowRunListItemSchema.parse({
129
+ ...baseRun,
130
+ source_snapshot: null,
131
+ jobs: [jobDto(0)],
132
+ job_status_counts: [
133
+ {status: 'succeeded', count: 40},
134
+ {status: 'failed', count: 2},
135
+ ],
136
+ });
137
+
138
+ expect(result.job_status_counts).toHaveLength(2);
139
+ });
140
+
141
+ test('rejects a preview longer than the documented bound', () => {
142
+ const result = workflowRunListItemSchema.safeParse({
143
+ ...baseRun,
144
+ source_snapshot: null,
145
+ jobs: Array.from({length: WORKFLOW_RUN_JOB_PREVIEW_LIMIT + 1}, (_, index) => jobDto(index)),
146
+ job_status_counts: [],
147
+ });
148
+
149
+ expect(result.success).toBe(false);
150
+ });
151
+
152
+ test.each([
153
+ ['a jobs array', {job_status_counts: []}],
154
+ ['job status counts', {jobs: []}],
155
+ ])('rejects a run list item without %s', (_missing, partial) => {
156
+ const result = workflowRunListItemSchema.safeParse({
157
+ ...baseRun,
158
+ source_snapshot: null,
159
+ ...partial,
160
+ });
161
+
162
+ expect(result.success).toBe(false);
163
+ });
164
+ });
@@ -1,4 +1,5 @@
1
1
  import {z} from 'zod';
2
+ import {jobStatusSchema} from './job.js';
2
3
 
3
4
  export const workflowRunStatusSchema = z.enum([
4
5
  'pending',
@@ -75,11 +76,25 @@ export const workflowSourceSnapshotSchema = z.object({
75
76
 
76
77
  export type WorkflowSourceSnapshotDto = z.infer<typeof workflowSourceSnapshotSchema>;
77
78
 
79
+ // Provider-neutral trigger facts captured at run creation. Every field is nullable because
80
+ // only source-control triggers resolve one at all, and a given payload may name a ref
81
+ // without naming an actor.
82
+ export const workflowRunTriggerReferenceSchema = z.object({
83
+ repository: z.string().nullable(),
84
+ ref: z.string().nullable(),
85
+ commit: z.string().nullable(),
86
+ actor: z.string().nullable(),
87
+ });
88
+
89
+ export type WorkflowRunTriggerReferenceDto = z.infer<typeof workflowRunTriggerReferenceSchema>;
90
+
78
91
  export const workflowRunDtoSchema = z.object({
79
92
  id: z.string().uuid(),
80
93
  project_id: z.string().uuid(),
81
94
  definition_id: z.string().uuid(),
95
+ number: z.number().int().positive(),
82
96
  name: z.string(),
97
+ workflow_name: z.string(),
83
98
  status: workflowRunStatusSchema,
84
99
  current_attempt: z.number().int().positive(),
85
100
  latest_attempt: z.number().int().positive(),
@@ -87,6 +102,7 @@ export const workflowRunDtoSchema = z.object({
87
102
  trigger_source: z.string(),
88
103
  trigger_event: z.string(),
89
104
  trigger_payload: z.record(z.string(), z.unknown()),
105
+ trigger_reference: workflowRunTriggerReferenceSchema.nullable(),
90
106
  inputs: z.record(z.string(), z.unknown()).nullable(),
91
107
  source_snapshot: workflowSourceSnapshotSchema.nullable(),
92
108
  created_at: z.string(),
@@ -120,8 +136,47 @@ export const workflowRunAttemptsResponseSchema = z.object({
120
136
 
121
137
  export type WorkflowRunAttemptsResponseDto = z.infer<typeof workflowRunAttemptsResponseSchema>;
122
138
 
139
+ // 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.
141
+ export const workflowRunJobSummaryDtoSchema = z.object({
142
+ id: z.string().uuid(),
143
+ key: z.string(),
144
+ name: z.string().nullable(),
145
+ status: jobStatusSchema,
146
+ position: z.number().int().nonnegative(),
147
+ });
148
+
149
+ export type WorkflowRunJobSummaryDto = z.infer<typeof workflowRunJobSummaryDtoSchema>;
150
+
151
+ /**
152
+ * How many jobs a run list row carries in graph order.
153
+ *
154
+ * A workflow has no job limit, and the list is polled while runs are active, so the row
155
+ * cannot carry every job of every run on the page. This bound is the API's, deliberately set
156
+ * above what any current surface draws: a client is free to show fewer without a server
157
+ * change, and `job_status_counts` still describes the jobs beyond it.
158
+ */
159
+ export const WORKFLOW_RUN_JOB_PREVIEW_LIMIT = 16;
160
+
161
+ /** One status and how many of the run's jobs hold it, counted over all of them. */
162
+ export const workflowRunJobStatusCountDtoSchema = z.object({
163
+ status: jobStatusSchema,
164
+ count: z.number().int().positive(),
165
+ });
166
+
167
+ export type WorkflowRunJobStatusCountDto = z.infer<typeof workflowRunJobStatusCountDtoSchema>;
168
+
169
+ export const workflowRunListItemSchema = workflowRunResponseSchema.extend({
170
+ /** Up to `WORKFLOW_RUN_JOB_PREVIEW_LIMIT` jobs in graph order, not the whole set. */
171
+ jobs: z.array(workflowRunJobSummaryDtoSchema).max(WORKFLOW_RUN_JOB_PREVIEW_LIMIT),
172
+ /** Counted over every job of the attempt, including those past the preview. */
173
+ job_status_counts: z.array(workflowRunJobStatusCountDtoSchema),
174
+ });
175
+
176
+ export type WorkflowRunListItemDto = z.infer<typeof workflowRunListItemSchema>;
177
+
123
178
  export const workflowRunListResponseSchema = z.object({
124
- runs: z.array(workflowRunResponseSchema),
179
+ runs: z.array(workflowRunListItemSchema),
125
180
  next_cursor: z.string().nullable(),
126
181
  filtered_total_count: z.number().int().nonnegative().nullable(),
127
182
  });