@shipfox/api-definitions 12.3.0 → 14.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 (223) hide show
  1. package/.turbo/turbo-build.log +11 -11
  2. package/CHANGELOG.md +45 -0
  3. package/dist/core/entities/index.d.ts +1 -1
  4. package/dist/core/entities/index.d.ts.map +1 -1
  5. package/dist/core/entities/index.js +1 -1
  6. package/dist/core/entities/index.js.map +1 -1
  7. package/dist/core/entities/integration-context.d.ts +4 -0
  8. package/dist/core/entities/integration-context.d.ts.map +1 -1
  9. package/dist/core/entities/integration-context.js.map +1 -1
  10. package/dist/core/entities/sync-state.d.ts +10 -4
  11. package/dist/core/entities/sync-state.d.ts.map +1 -1
  12. package/dist/core/entities/sync-state.js +26 -7
  13. package/dist/core/entities/sync-state.js.map +1 -1
  14. package/dist/core/entities/validation-diagnostic.d.ts +8 -0
  15. package/dist/core/entities/validation-diagnostic.d.ts.map +1 -0
  16. package/dist/core/entities/validation-diagnostic.js +3 -0
  17. package/dist/core/entities/validation-diagnostic.js.map +1 -0
  18. package/dist/core/entities/workflow-definition.d.ts +2 -0
  19. package/dist/core/entities/workflow-definition.d.ts.map +1 -1
  20. package/dist/core/entities/workflow-definition.js.map +1 -1
  21. package/dist/core/errors.d.ts +10 -0
  22. package/dist/core/errors.d.ts.map +1 -1
  23. package/dist/core/errors.js +9 -0
  24. package/dist/core/errors.js.map +1 -1
  25. package/dist/core/index.d.ts +2 -1
  26. package/dist/core/index.d.ts.map +1 -1
  27. package/dist/core/index.js +2 -1
  28. package/dist/core/index.js.map +1 -1
  29. package/dist/core/integrations.d.ts +3 -1
  30. package/dist/core/integrations.d.ts.map +1 -1
  31. package/dist/core/integrations.js +10 -2
  32. package/dist/core/integrations.js.map +1 -1
  33. package/dist/core/parse-definition.d.ts +4 -4
  34. package/dist/core/parse-definition.d.ts.map +1 -1
  35. package/dist/core/parse-definition.js +5 -5
  36. package/dist/core/parse-definition.js.map +1 -1
  37. package/dist/core/resolve-definition-at-ref.d.ts +66 -0
  38. package/dist/core/resolve-definition-at-ref.d.ts.map +1 -0
  39. package/dist/core/resolve-definition-at-ref.js +374 -0
  40. package/dist/core/resolve-definition-at-ref.js.map +1 -0
  41. package/dist/core/sync-definitions.d.ts +2 -2
  42. package/dist/core/sync-definitions.d.ts.map +1 -1
  43. package/dist/core/sync-definitions.js +5 -5
  44. package/dist/core/sync-definitions.js.map +1 -1
  45. package/dist/core/validate-definition.d.ts +3 -3
  46. package/dist/core/validate-definition.d.ts.map +1 -1
  47. package/dist/core/validate-definition.js +17 -12
  48. package/dist/core/validate-definition.js.map +1 -1
  49. package/dist/core/workflow-model/cron-trigger.d.ts +1 -1
  50. package/dist/core/workflow-model/cron-trigger.d.ts.map +1 -1
  51. package/dist/core/workflow-model/cron-trigger.js +9 -5
  52. package/dist/core/workflow-model/cron-trigger.js.map +1 -1
  53. package/dist/core/workflow-model/index.d.ts +1 -1
  54. package/dist/core/workflow-model/index.d.ts.map +1 -1
  55. package/dist/core/workflow-model/index.js.map +1 -1
  56. package/dist/core/workflow-model/invalid-workflow-model-error.d.ts +4 -2
  57. package/dist/core/workflow-model/invalid-workflow-model-error.d.ts.map +1 -1
  58. package/dist/core/workflow-model/invalid-workflow-model-error.js.map +1 -1
  59. package/dist/core/workflow-model/normalize-job-listening.d.ts.map +1 -1
  60. package/dist/core/workflow-model/normalize-job-listening.js +51 -30
  61. package/dist/core/workflow-model/normalize-job-listening.js.map +1 -1
  62. package/dist/core/workflow-model/normalize-triggers.d.ts +1 -1
  63. package/dist/core/workflow-model/normalize-triggers.d.ts.map +1 -1
  64. package/dist/core/workflow-model/normalize-triggers.js +48 -18
  65. package/dist/core/workflow-model/normalize-triggers.js.map +1 -1
  66. package/dist/core/workflow-model/normalize-workflow-document.d.ts +1 -1
  67. package/dist/core/workflow-model/normalize-workflow-document.d.ts.map +1 -1
  68. package/dist/core/workflow-model/normalize-workflow-document.js +3 -3
  69. package/dist/core/workflow-model/normalize-workflow-document.js.map +1 -1
  70. package/dist/core/workflow-model/parse-interpolation-field.d.ts.map +1 -1
  71. package/dist/core/workflow-model/parse-interpolation-field.js +1 -15
  72. package/dist/core/workflow-model/parse-interpolation-field.js.map +1 -1
  73. package/dist/core/workflow-model/validate-predicate-expression.d.ts +3 -1
  74. package/dist/core/workflow-model/validate-predicate-expression.d.ts.map +1 -1
  75. package/dist/core/workflow-model/validate-predicate-expression.js +3 -0
  76. package/dist/core/workflow-model/validate-predicate-expression.js.map +1 -1
  77. package/dist/core/workflow-model/validation-issue.d.ts +2 -1
  78. package/dist/core/workflow-model/validation-issue.d.ts.map +1 -1
  79. package/dist/core/workflow-model/validation-issue.js +12 -15
  80. package/dist/core/workflow-model/validation-issue.js.map +1 -1
  81. package/dist/db/db.d.ts +196 -4
  82. package/dist/db/db.d.ts.map +1 -1
  83. package/dist/db/db.js +3 -1
  84. package/dist/db/db.js.map +1 -1
  85. package/dist/db/definition-triggers.js +3 -1
  86. package/dist/db/definition-triggers.js.map +1 -1
  87. package/dist/db/definitions.d.ts +17 -0
  88. package/dist/db/definitions.d.ts.map +1 -1
  89. package/dist/db/definitions.js +190 -23
  90. package/dist/db/definitions.js.map +1 -1
  91. package/dist/db/index.d.ts +2 -1
  92. package/dist/db/index.d.ts.map +1 -1
  93. package/dist/db/index.js +2 -1
  94. package/dist/db/index.js.map +1 -1
  95. package/dist/db/schema/definitions.d.ts +17 -0
  96. package/dist/db/schema/definitions.d.ts.map +1 -1
  97. package/dist/db/schema/definitions.js +8 -0
  98. package/dist/db/schema/definitions.js.map +1 -1
  99. package/dist/db/schema/sync-states.d.ts +7 -3
  100. package/dist/db/schema/sync-states.d.ts.map +1 -1
  101. package/dist/db/schema/sync-states.js +5 -1
  102. package/dist/db/schema/sync-states.js.map +1 -1
  103. package/dist/db/schema/workflows.d.ts +78 -0
  104. package/dist/db/schema/workflows.d.ts.map +1 -0
  105. package/dist/db/schema/workflows.js +17 -0
  106. package/dist/db/schema/workflows.js.map +1 -0
  107. package/dist/db/sync-states.d.ts +2 -2
  108. package/dist/db/sync-states.d.ts.map +1 -1
  109. package/dist/db/sync-states.js +4 -4
  110. package/dist/db/sync-states.js.map +1 -1
  111. package/dist/index.d.ts.map +1 -1
  112. package/dist/index.js +5 -1
  113. package/dist/index.js.map +1 -1
  114. package/dist/metrics/index.d.ts +2 -0
  115. package/dist/metrics/index.d.ts.map +1 -0
  116. package/dist/metrics/index.js +3 -0
  117. package/dist/metrics/index.js.map +1 -0
  118. package/dist/metrics/instance.d.ts +4 -0
  119. package/dist/metrics/instance.d.ts.map +1 -0
  120. package/dist/metrics/instance.js +19 -0
  121. package/dist/metrics/instance.js.map +1 -0
  122. package/dist/presentation/dto/definition.d.ts.map +1 -1
  123. package/dist/presentation/dto/definition.js +11 -4
  124. package/dist/presentation/dto/definition.js.map +1 -1
  125. package/dist/presentation/inter-module.d.ts +9 -1
  126. package/dist/presentation/inter-module.d.ts.map +1 -1
  127. package/dist/presentation/inter-module.js +66 -2
  128. package/dist/presentation/inter-module.js.map +1 -1
  129. package/dist/presentation/routes/create-definition.d.ts.map +1 -1
  130. package/dist/presentation/routes/create-definition.js +16 -3
  131. package/dist/presentation/routes/create-definition.js.map +1 -1
  132. package/dist/presentation/routes/get-definition.d.ts.map +1 -1
  133. package/dist/presentation/routes/get-definition.js +18 -4
  134. package/dist/presentation/routes/get-definition.js.map +1 -1
  135. package/dist/presentation/routes/project-access.d.ts +1 -0
  136. package/dist/presentation/routes/project-access.d.ts.map +1 -1
  137. package/dist/presentation/routes/validate-definition.js +3 -3
  138. package/dist/presentation/routes/validate-definition.js.map +1 -1
  139. package/dist/temporal/activities/sync-activities.d.ts +3 -3
  140. package/dist/temporal/activities/sync-activities.d.ts.map +1 -1
  141. package/dist/temporal/activities/sync-activities.js +7 -4
  142. package/dist/temporal/activities/sync-activities.js.map +1 -1
  143. package/dist/temporal/workflows/definition-sync.js +1 -1
  144. package/dist/temporal/workflows/definition-sync.js.map +1 -1
  145. package/dist/temporal/workflows/index.bundle.js +50 -22
  146. package/dist/tsconfig.test.tsbuildinfo +1 -1
  147. package/drizzle/0003_nebulous_nemesis.sql +11 -0
  148. package/drizzle/0004_workflow_ref_lookup.sql +1 -0
  149. package/drizzle/meta/0003_snapshot.json +627 -0
  150. package/drizzle/meta/0004_snapshot.json +649 -0
  151. package/drizzle/meta/_journal.json +14 -0
  152. package/drizzle.config.ts +1 -0
  153. package/package.json +7 -7
  154. package/src/core/entities/index.ts +1 -1
  155. package/src/core/entities/integration-context.ts +4 -0
  156. package/src/core/entities/sync-state.test.ts +49 -0
  157. package/src/core/entities/sync-state.ts +35 -12
  158. package/src/core/entities/validation-diagnostic.ts +8 -0
  159. package/src/core/entities/workflow-definition.ts +2 -0
  160. package/src/core/errors.ts +26 -0
  161. package/src/core/index.ts +12 -0
  162. package/src/core/integrations.test.ts +38 -0
  163. package/src/core/integrations.ts +10 -1
  164. package/src/core/parse-definition-diagnostics.test.ts +47 -0
  165. package/src/core/parse-definition.test.ts +29 -5
  166. package/src/core/parse-definition.ts +7 -7
  167. package/src/core/resolve-definition-at-ref.test.ts +792 -0
  168. package/src/core/resolve-definition-at-ref.ts +551 -0
  169. package/src/core/sync-definitions.test.ts +61 -2
  170. package/src/core/sync-definitions.ts +7 -7
  171. package/src/core/validate-definition.test.ts +31 -22
  172. package/src/core/validate-definition.ts +21 -13
  173. package/src/core/workflow-model/cron-trigger.ts +6 -2
  174. package/src/core/workflow-model/index.ts +1 -0
  175. package/src/core/workflow-model/invalid-workflow-model-error.ts +5 -1
  176. package/src/core/workflow-model/normalize-job-listening.ts +55 -23
  177. package/src/core/workflow-model/normalize-triggers.ts +44 -15
  178. package/src/core/workflow-model/normalize-workflow-document.test.ts +494 -36
  179. package/src/core/workflow-model/normalize-workflow-document.ts +8 -4
  180. package/src/core/workflow-model/parse-interpolation-field.ts +1 -23
  181. package/src/core/workflow-model/validate-predicate-expression.ts +6 -0
  182. package/src/core/workflow-model/validation-issue.ts +8 -13
  183. package/src/db/db.ts +7 -1
  184. package/src/db/definition-triggers.test.ts +44 -0
  185. package/src/db/definition-triggers.ts +1 -1
  186. package/src/db/definitions.test.ts +300 -1
  187. package/src/db/definitions.ts +282 -28
  188. package/src/db/index.ts +2 -0
  189. package/src/db/schema/definitions.test.ts +1 -0
  190. package/src/db/schema/definitions.ts +11 -0
  191. package/src/db/schema/sync-states.ts +12 -3
  192. package/src/db/schema/workflows.ts +23 -0
  193. package/src/db/sync-states.test.ts +87 -18
  194. package/src/db/sync-states.ts +6 -6
  195. package/src/db/workflow-lineage-migration.test.ts +158 -0
  196. package/src/index.ts +3 -1
  197. package/src/metrics/index.ts +1 -0
  198. package/src/metrics/instance.test.ts +50 -0
  199. package/src/metrics/instance.ts +23 -0
  200. package/src/presentation/dto/definition.test.ts +89 -1
  201. package/src/presentation/dto/definition.ts +8 -5
  202. package/src/presentation/inter-module.test.ts +177 -0
  203. package/src/presentation/inter-module.ts +77 -4
  204. package/src/presentation/routes/create-definition.test.ts +38 -0
  205. package/src/presentation/routes/create-definition.ts +20 -3
  206. package/src/presentation/routes/get-definition.test.ts +125 -11
  207. package/src/presentation/routes/get-definition.ts +21 -4
  208. package/src/presentation/routes/list-definitions.test.ts +1 -1
  209. package/src/presentation/routes/validate-definition.test.ts +1 -1
  210. package/src/presentation/routes/validate-definition.ts +3 -3
  211. package/src/presentation/subscribers/on-integration-connection-available.test.ts +3 -0
  212. package/src/temporal/activities/sync-activities.test.ts +46 -2
  213. package/src/temporal/activities/sync-activities.ts +9 -7
  214. package/src/temporal/workflows/definition-sync.ts +1 -1
  215. package/test/factories/definition.ts +1 -0
  216. package/test/globalSetup.ts +2 -0
  217. package/tsconfig.build.tsbuildinfo +1 -1
  218. package/dist/core/entities/validation-warning.d.ts +0 -6
  219. package/dist/core/entities/validation-warning.d.ts.map +0 -1
  220. package/dist/core/entities/validation-warning.js +0 -3
  221. package/dist/core/entities/validation-warning.js.map +0 -1
  222. package/src/core/entities/validation-warning.ts +0 -5
  223. package/src/core/parse-definition-warnings.test.ts +0 -33
@@ -24,7 +24,7 @@ jobs:
24
24
 
25
25
  expect(result.valid).toBe(true);
26
26
  if (result.valid) {
27
- expect(result.warnings).toEqual([]);
27
+ expect(result.diagnostics).toEqual([]);
28
28
  expect(result.definition.document.name).toBe('Test');
29
29
  expect(result.definition.document.jobs.build?.steps).toHaveLength(1);
30
30
  expect(result.definition.model.jobs[0]?.id).toBe('build');
@@ -36,7 +36,7 @@ jobs:
36
36
 
37
37
  expect(result.valid).toBe(false);
38
38
  if (!result.valid) {
39
- expect(result).not.toHaveProperty('warnings');
39
+ expect(result).not.toHaveProperty('diagnostics');
40
40
  expect(result.errors).toHaveLength(1);
41
41
  expect(result.errors[0]?.message).toContain('Invalid workflow YAML syntax');
42
42
  }
@@ -159,14 +159,15 @@ jobs:
159
159
 
160
160
  expect(result.valid).toBe(true);
161
161
  if (result.valid) {
162
- expect(result.warnings).toHaveLength(1);
163
- expect(result.warnings[0]).toMatchObject({
162
+ expect(result.diagnostics).toHaveLength(1);
163
+ expect(result.diagnostics[0]).toMatchObject({
164
164
  code: 're-evaluating-command',
165
165
  path: 'jobs.build.steps.0.run',
166
+ severity: 'warning',
166
167
  });
167
- expect(result.warnings[0]?.message).toContain(`$${valueName}`);
168
- expect(result.warnings[0]?.message).toContain('eval');
169
- expect(result.warnings[0]?.message).toContain('re-executed as code');
168
+ expect(result.diagnostics[0]?.message).toContain(`$${valueName}`);
169
+ expect(result.diagnostics[0]?.message).toContain('eval');
170
+ expect(result.diagnostics[0]?.message).toContain('re-executed as code');
170
171
  expect(result.definition.model.jobs[0]?.steps).toHaveLength(1);
171
172
  }
172
173
  });
@@ -189,7 +190,7 @@ jobs:
189
190
  ${run}
190
191
  `);
191
192
 
192
- expect(result).toMatchObject({valid: true, warnings: []});
193
+ expect(result).toMatchObject({valid: true, diagnostics: []});
193
194
  });
194
195
 
195
196
  test.each([
@@ -209,13 +210,14 @@ jobs:
209
210
 
210
211
  expect(result.valid).toBe(true);
211
212
  if (result.valid) {
212
- expect(result.warnings).toHaveLength(1);
213
- expect(result.warnings[0]).toMatchObject({
213
+ expect(result.diagnostics).toHaveLength(1);
214
+ expect(result.diagnostics[0]).toMatchObject({
214
215
  code: 're-evaluating-command',
215
216
  path: 'jobs.build.steps.0.run',
217
+ severity: 'warning',
216
218
  });
217
- expect(result.warnings[0]?.message).toContain('event.ref');
218
- expect(result.warnings[0]?.message).toContain(construct);
219
+ expect(result.diagnostics[0]?.message).toContain('event.ref');
220
+ expect(result.diagnostics[0]?.message).toContain(construct);
219
221
  }
220
222
  });
221
223
 
@@ -234,18 +236,25 @@ jobs:
234
236
 
235
237
  expect(result.valid).toBe(true);
236
238
  if (result.valid) {
237
- expect(result.warnings).toHaveLength(5);
238
- expect(result.warnings.every((warning) => warning.code === 're-evaluating-command')).toBe(
239
- true,
240
- );
241
- expect(result.warnings.every((warning) => warning.path === 'jobs.build.steps.0.run')).toBe(
242
- true,
243
- );
244
- expect(result.warnings.filter((warning) => warning.message.includes('eval'))).toHaveLength(3);
245
- expect(result.warnings.some((warning) => warning.message.includes('event.first'))).toBe(true);
246
- expect(result.warnings.some((warning) => warning.message.includes('event.second'))).toBe(
239
+ expect(result.diagnostics).toHaveLength(5);
240
+ expect(
241
+ result.diagnostics.every((diagnostic) => diagnostic.code === 're-evaluating-command'),
242
+ ).toBe(true);
243
+ expect(
244
+ result.diagnostics.every((diagnostic) => diagnostic.path === 'jobs.build.steps.0.run'),
245
+ ).toBe(true);
246
+ expect(result.diagnostics.every((diagnostic) => diagnostic.severity === 'warning')).toBe(
247
247
  true,
248
248
  );
249
+ expect(
250
+ result.diagnostics.filter((diagnostic) => diagnostic.message.includes('eval')),
251
+ ).toHaveLength(3);
252
+ expect(
253
+ result.diagnostics.some((diagnostic) => diagnostic.message.includes('event.first')),
254
+ ).toBe(true);
255
+ expect(
256
+ result.diagnostics.some((diagnostic) => diagnostic.message.includes('event.second')),
257
+ ).toBe(true);
249
258
  }
250
259
  });
251
260
 
@@ -2,7 +2,7 @@ import type {AgentValidationCatalog} from '@shipfox/api-agent-dto/inter-module';
2
2
  import {InvalidWorkflowDocumentError} from '@shipfox/workflow-document';
3
3
  import {definitionDefaultRunnerLabels} from '../config.js';
4
4
  import type {IntegrationValidationContext} from './entities/integration-context.js';
5
- import type {ValidationWarning} from './entities/validation-warning.js';
5
+ import type {ValidationDiagnostic} from './entities/validation-diagnostic.js';
6
6
  import type {WorkflowDefinitionPayload} from './entities/workflow-definition.js';
7
7
  import {
8
8
  InvalidWorkflowModelError,
@@ -12,7 +12,7 @@ import {
12
12
  import {InvalidWorkflowYamlError, parseWorkflowYamlWithLocations} from './workflow-yaml/index.js';
13
13
 
14
14
  export type ValidationError = {message: string; path?: string | undefined};
15
- export type {ValidationWarning} from './entities/validation-warning.js';
15
+ export type {ValidationDiagnostic} from './entities/validation-diagnostic.js';
16
16
 
17
17
  export interface DefinitionValidationOptions {
18
18
  defaultRunnerLabels?: readonly string[];
@@ -21,7 +21,7 @@ export interface DefinitionValidationOptions {
21
21
  }
22
22
 
23
23
  export type ValidationResult =
24
- | {valid: true; definition: WorkflowDefinitionPayload; warnings: ValidationWarning[]}
24
+ | {valid: true; definition: WorkflowDefinitionPayload; diagnostics: ValidationDiagnostic[]}
25
25
  | {valid: false; errors: ValidationError[]};
26
26
 
27
27
  export function validateDefinition(
@@ -30,28 +30,33 @@ export function validateDefinition(
30
30
  ): ValidationResult {
31
31
  try {
32
32
  const {document, stepSourceLocations} = parseWorkflowYamlWithLocations(yamlContent);
33
- const warnings: WorkflowModelValidationIssue[] = [];
33
+ const diagnostics: WorkflowModelValidationIssue[] = [];
34
34
  const model = normalizeWorkflowDocument(document, {
35
35
  defaultRunnerLabels: options.defaultRunnerLabels ?? definitionDefaultRunnerLabels,
36
36
  agentValidationCatalog: options.agentValidationCatalog,
37
37
  integrationValidationContext: options.integrationValidationContext,
38
38
  stepSourceLocations,
39
- warnings,
39
+ diagnostics,
40
40
  });
41
- return {valid: true, definition: {document, model}, warnings: validationWarningsFor(warnings)};
41
+ return {
42
+ valid: true,
43
+ definition: {document, model},
44
+ diagnostics: validationDiagnosticsFor(diagnostics),
45
+ };
42
46
  } catch (error) {
43
47
  return {valid: false, errors: validationErrorsFor(error)};
44
48
  }
45
49
  }
46
50
 
47
- function validationWarningsFor(
51
+ function validationDiagnosticsFor(
48
52
  issues: readonly WorkflowModelValidationIssue[],
49
- ): ValidationWarning[] {
53
+ ): ValidationDiagnostic[] {
50
54
  return issues.map((issue) =>
51
- validationWarning({
55
+ validationDiagnostic({
52
56
  code: issue.code,
53
57
  message: issue.message,
54
58
  path: issue.path.join('.') || undefined,
59
+ severity: issue.severity,
55
60
  }),
56
61
  );
57
62
  }
@@ -92,11 +97,14 @@ function validationError(params: {message: string; path?: string | undefined}):
92
97
  return {message: params.message, path: params.path};
93
98
  }
94
99
 
95
- function validationWarning(params: {
100
+ function validationDiagnostic(params: {
96
101
  code: string;
97
102
  message: string;
98
103
  path?: string | undefined;
99
- }): ValidationWarning {
100
- if (params.path === undefined) return {code: params.code, message: params.message};
101
- return {code: params.code, message: params.message, path: params.path};
104
+ severity: 'error' | 'warning';
105
+ }): ValidationDiagnostic {
106
+ if (params.path === undefined) {
107
+ return {code: params.code, message: params.message, severity: params.severity};
108
+ }
109
+ return {code: params.code, message: params.message, path: params.path, severity: params.severity};
102
110
  }
@@ -8,7 +8,7 @@ const cronFieldSeparatorRegex = /\s+/;
8
8
  export function validateCronTrigger(params: {
9
9
  readonly sourceKey: string;
10
10
  readonly trigger: {
11
- readonly event: string;
11
+ readonly event?: string | undefined;
12
12
  };
13
13
  readonly config: {
14
14
  readonly schedule?: string | undefined;
@@ -18,13 +18,14 @@ export function validateCronTrigger(params: {
18
18
  }): void {
19
19
  const {sourceKey, trigger, config, issues} = params;
20
20
 
21
- if (trigger.event !== 'tick') {
21
+ if (trigger.event !== undefined && trigger.event !== 'tick') {
22
22
  issues.push(
23
23
  issue({
24
24
  code: 'invalid-cron-event',
25
25
  message: `A cron trigger must use event "tick"; found "${trigger.event}".`,
26
26
  path: ['triggers', sourceKey, 'event'],
27
27
  details: {event: trigger.event},
28
+ scope: 'trigger',
28
29
  }),
29
30
  );
30
31
  }
@@ -35,6 +36,7 @@ export function validateCronTrigger(params: {
35
36
  code: 'missing-cron-schedule',
36
37
  message: 'A cron trigger requires a schedule.',
37
38
  path: ['triggers', sourceKey, 'config', 'schedule'],
39
+ scope: 'trigger',
38
40
  }),
39
41
  );
40
42
  } else if (!isValidCronExpression(config.schedule)) {
@@ -44,6 +46,7 @@ export function validateCronTrigger(params: {
44
46
  message: 'Cron trigger schedule must be a valid 5-field cron expression.',
45
47
  path: ['triggers', sourceKey, 'config', 'schedule'],
46
48
  details: {schedule: config.schedule},
49
+ scope: 'trigger',
47
50
  }),
48
51
  );
49
52
  }
@@ -55,6 +58,7 @@ export function validateCronTrigger(params: {
55
58
  message: 'Cron trigger timezone must be a valid IANA time zone.',
56
59
  path: ['triggers', sourceKey, 'config', 'timezone'],
57
60
  details: {timezone: config.timezone},
61
+ scope: 'trigger',
58
62
  }),
59
63
  );
60
64
  }
@@ -5,6 +5,7 @@ export {
5
5
  type WorkflowModelValidationIssue,
6
6
  type WorkflowModelValidationIssueCode,
7
7
  type WorkflowModelValidationIssuePathSegment,
8
+ type WorkflowModelValidationIssueScope,
8
9
  type WorkflowModelValidationIssueSeverity,
9
10
  } from './invalid-workflow-model-error.js';
10
11
  export {normalizeWorkflowDocument} from './normalize-workflow-document.js';
@@ -34,6 +34,7 @@ export type WorkflowModelValidationIssueCode =
34
34
  | 'invalid-step-if'
35
35
  | 'job-dependency-cycle'
36
36
  | 'listening-job-missing-resolution-source'
37
+ | 'listening-job-no-active-matcher'
37
38
  | 'listening-timeout-exceeds-run-timeout'
38
39
  | 'missing-harness-for-tools'
39
40
  | 'missing-connection-for-integration'
@@ -58,12 +59,15 @@ export type WorkflowModelValidationIssuePathSegment = string | number;
58
59
 
59
60
  export type WorkflowModelValidationIssueSeverity = 'error' | 'warning';
60
61
 
62
+ export type WorkflowModelValidationIssueScope = 'definition' | 'trigger';
63
+
61
64
  export interface WorkflowModelValidationIssue {
62
65
  readonly code: WorkflowModelValidationIssueCode;
63
66
  readonly message: string;
64
67
  readonly path: readonly WorkflowModelValidationIssuePathSegment[];
65
68
  readonly details?: Readonly<Record<string, unknown>>;
66
- readonly severity?: WorkflowModelValidationIssueSeverity;
69
+ readonly severity: WorkflowModelValidationIssueSeverity;
70
+ readonly scope: WorkflowModelValidationIssueScope;
67
71
  }
68
72
 
69
73
  export class InvalidWorkflowModelError extends Error {
@@ -39,8 +39,49 @@ export function normalizeJobListening(params: {
39
39
  issues: params.issues,
40
40
  });
41
41
 
42
+ const on = listening.on
43
+ .map((trigger, index) =>
44
+ normalizeListeningTrigger({
45
+ trigger,
46
+ field: 'listener.on',
47
+ path: [...path, 'listening', 'on', index, 'filter'],
48
+ issues: params.issues,
49
+ allowedJobReferences: params.allowedJobReferences,
50
+ }),
51
+ )
52
+ .filter((matcher): matcher is WorkflowModelJobListening['on'][number] => matcher !== undefined);
53
+
54
+ if (on.length === 0) {
55
+ params.issues.push(
56
+ issue({
57
+ code: 'listening-job-no-active-matcher',
58
+ message: `Listening job "${params.sourceName}" has no active "on" matcher; it can never execute.`,
59
+ path: [...path, 'listening', 'on'],
60
+ }),
61
+ );
62
+ }
63
+
64
+ const until =
65
+ listening.until === undefined
66
+ ? undefined
67
+ : listening.until
68
+ .map((trigger, index) =>
69
+ normalizeListeningTrigger({
70
+ trigger,
71
+ field: 'listener.until',
72
+ path: [...path, 'listening', 'until', index, 'filter'],
73
+ issues: params.issues,
74
+ allowedJobReferences: params.allowedJobReferences,
75
+ }),
76
+ )
77
+ .filter(
78
+ (matcher): matcher is WorkflowModelJobListening['on'][number] => matcher !== undefined,
79
+ );
80
+
81
+ // Inert `until` matchers do not resolve the listening job; the check runs on
82
+ // the matchers that stay active.
42
83
  if (
43
- listening.until === undefined &&
84
+ (until === undefined || until.length === 0) &&
44
85
  listening.timeout === undefined &&
45
86
  listening.max_executions === undefined
46
87
  ) {
@@ -63,27 +104,11 @@ export function normalizeJobListening(params: {
63
104
  };
64
105
 
65
106
  return {
66
- on: listening.on.map((trigger, index) =>
67
- normalizeListeningTrigger({
68
- trigger,
69
- field: 'listener.on',
70
- path: [...path, 'listening', 'on', index, 'filter'],
71
- issues: params.issues,
72
- allowedJobReferences: params.allowedJobReferences,
73
- }),
74
- ),
75
- ...(listening.until === undefined
107
+ on,
108
+ ...(until === undefined || until.length === 0
76
109
  ? {}
77
110
  : {
78
- until: listening.until.map((trigger, index) =>
79
- normalizeListeningTrigger({
80
- trigger,
81
- field: 'listener.until',
82
- path: [...path, 'listening', 'until', index, 'filter'],
83
- issues: params.issues,
84
- allowedJobReferences: params.allowedJobReferences,
85
- }),
86
- ),
111
+ until,
87
112
  }),
88
113
  ...(timeoutMs === undefined ? {} : {timeoutMs}),
89
114
  ...(listening.max_executions === undefined ? {} : {maxExecutions: listening.max_executions}),
@@ -95,7 +120,7 @@ export function normalizeJobListening(params: {
95
120
  function normalizeListeningTrigger(params: {
96
121
  trigger: {
97
122
  readonly source: string;
98
- readonly event: string;
123
+ readonly event?: string | undefined;
99
124
  readonly with?: Readonly<Record<string, unknown>> | undefined;
100
125
  readonly filter?: string | undefined;
101
126
  };
@@ -103,7 +128,8 @@ function normalizeListeningTrigger(params: {
103
128
  path: readonly (string | number)[];
104
129
  issues: WorkflowModelValidationIssue[];
105
130
  allowedJobReferences: ReadonlySet<string>;
106
- }): WorkflowModelJobListening['on'][number] {
131
+ }): WorkflowModelJobListening['on'][number] | undefined {
132
+ const matcherIssues: WorkflowModelValidationIssue[] = [];
107
133
  if (params.trigger.filter !== undefined) {
108
134
  validatePredicateExpression({
109
135
  field: params.field,
@@ -111,10 +137,16 @@ function normalizeListeningTrigger(params: {
111
137
  path: params.path,
112
138
  invalidCode: 'invalid-listener-filter',
113
139
  invalidMessage: `${params.field === 'listener.on' ? 'Listener on' : 'Listener until'} filter must be a valid CEL boolean expression.`,
114
- issues: params.issues,
140
+ issues: matcherIssues,
115
141
  allowedJobReferences: params.allowedJobReferences,
142
+ scope: 'trigger',
116
143
  });
117
144
  }
145
+ params.issues.push(...matcherIssues);
146
+
147
+ // A matcher with a trigger-scoped issue is inert: excluded from the matcher
148
+ // list while the authored document entry stays untouched.
149
+ if (matcherIssues.some((candidate) => candidate.scope === 'trigger')) return undefined;
118
150
 
119
151
  return normalizeTriggerEntry(params.trigger);
120
152
  }
@@ -22,17 +22,7 @@ export function normalizeTriggers(
22
22
  .filter(([, trigger]) => trigger.source === manualTriggerSource)
23
23
  .map(([sourceKey]) => sourceKey);
24
24
  const usedTriggerIds = new Map<string, string>();
25
-
26
- if (manualTriggerKeys.length > 1) {
27
- issues.push(
28
- issue({
29
- code: 'multiple-manual-triggers',
30
- message: `A workflow may declare at most one manual trigger; found ${manualTriggerKeys.length}: ${manualTriggerKeys.join(', ')}.`,
31
- path: ['triggers'],
32
- details: {manualTriggerKeys},
33
- }),
34
- );
35
- }
25
+ let activeManualTriggerSeen = false;
36
26
 
37
27
  return Object.entries(triggers).flatMap(([sourceKey, trigger]) => {
38
28
  const id = stableId(sourceKey);
@@ -44,16 +34,37 @@ export function normalizeTriggers(
44
34
  message: `Trigger keys "${existingSourceKey}" and "${sourceKey}" resolve to the same stable id "${id}".`,
45
35
  path: ['triggers', sourceKey],
46
36
  details: {id, sourceKeys: [existingSourceKey, sourceKey]},
37
+ scope: 'trigger',
47
38
  }),
48
39
  );
49
40
  return [];
50
41
  }
51
42
  usedTriggerIds.set(id, sourceKey);
52
43
 
53
- validateTriggerFilter({sourceKey, trigger, issues});
44
+ if (trigger.source === manualTriggerSource) {
45
+ if (activeManualTriggerSeen) {
46
+ issues.push(
47
+ issue({
48
+ code: 'multiple-manual-triggers',
49
+ message: `A workflow may declare at most one manual trigger; found ${manualTriggerKeys.length}: ${manualTriggerKeys.join(', ')}. This trigger is inert because it is not the first manual trigger in document order.`,
50
+ path: ['triggers', sourceKey],
51
+ details: {manualTriggerKeys},
52
+ scope: 'trigger',
53
+ }),
54
+ );
55
+ return [];
56
+ }
57
+ }
58
+
59
+ const triggerIssues: WorkflowModelValidationIssue[] = [];
60
+ validateTriggerFilter({sourceKey, trigger, issues: triggerIssues});
61
+ issues.push(...triggerIssues);
54
62
 
55
63
  const normalizedTrigger = normalizeTriggerEntry(trigger);
64
+ const triggerIsInert = triggerIssues.some((candidate) => candidate.scope === 'trigger');
56
65
  if (trigger.source !== cronTriggerSource) {
66
+ if (triggerIsInert) return [];
67
+ if (trigger.source === manualTriggerSource) activeManualTriggerSeen = true;
57
68
  return [
58
69
  {
59
70
  id,
@@ -70,7 +81,12 @@ export function normalizeTriggers(
70
81
  timezone: cronConfig.timezone ?? cronTriggerDefaultTimezone,
71
82
  };
72
83
 
73
- validateCronTrigger({trigger, config: cronConfig, sourceKey, issues});
84
+ const cronIssues: WorkflowModelValidationIssue[] = [];
85
+ validateCronTrigger({trigger, config: cronConfig, sourceKey, issues: cronIssues});
86
+ issues.push(...cronIssues);
87
+
88
+ const cronIsInert = cronIssues.some((candidate) => candidate.scope === 'trigger');
89
+ if (triggerIsInert || cronIsInert) return [];
74
90
 
75
91
  return [
76
92
  {
@@ -99,6 +115,7 @@ function validateTriggerFilter(params: {
99
115
  message: `A ${trigger.source} trigger cannot define a filter because it does not receive an event payload.`,
100
116
  path,
101
117
  details: {source: trigger.filter, triggerSource: trigger.source},
118
+ scope: 'trigger',
102
119
  }),
103
120
  );
104
121
  return;
@@ -111,19 +128,31 @@ function validateTriggerFilter(params: {
111
128
  invalidCode: 'invalid-trigger-filter',
112
129
  invalidMessage: 'Trigger filter must be a valid boolean predicate.',
113
130
  issues,
131
+ scope: 'trigger',
114
132
  });
115
133
  }
116
134
 
117
135
  export function normalizeTriggerEntry(trigger: {
118
136
  readonly source: string;
119
- readonly event: string;
137
+ readonly event?: string | undefined;
120
138
  readonly with?: Readonly<Record<string, unknown>> | undefined;
121
139
  readonly filter?: string | undefined;
122
140
  }): WorkflowModelListeningTrigger {
141
+ // `manual` and `cron` have no delivered event to resolve against at fire
142
+ // time, so materialize their built-in name when the author omitted it.
143
+ // Integration sources keep the event absent, which becomes a source
144
+ // subscription (NULL row) on the write path.
145
+ const event = builtinEventForSource(trigger.source, trigger.event);
123
146
  return {
124
147
  source: trigger.source,
125
- event: trigger.event,
148
+ ...(event === undefined ? {} : {event}),
126
149
  ...(trigger.with === undefined ? {} : {inputs: trigger.with}),
127
150
  ...(trigger.filter === undefined ? {} : {filter: trigger.filter}),
128
151
  };
129
152
  }
153
+
154
+ function builtinEventForSource(source: string, event: string | undefined): string | undefined {
155
+ if (source === manualTriggerSource) return event ?? 'fire';
156
+ if (source === cronTriggerSource) return event ?? 'tick';
157
+ return event;
158
+ }