@shipfox/api-definitions 11.0.0 → 12.2.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 (174) hide show
  1. package/.turbo/turbo-build.log +13 -12
  2. package/CHANGELOG.md +106 -0
  3. package/dist/core/entities/index.d.ts +1 -0
  4. package/dist/core/entities/index.d.ts.map +1 -1
  5. package/dist/core/entities/index.js +1 -0
  6. package/dist/core/entities/index.js.map +1 -1
  7. package/dist/core/entities/sync-state.d.ts +5 -1
  8. package/dist/core/entities/sync-state.d.ts.map +1 -1
  9. package/dist/core/entities/sync-state.js +10 -0
  10. package/dist/core/entities/sync-state.js.map +1 -1
  11. package/dist/core/entities/validation-warning.d.ts +6 -0
  12. package/dist/core/entities/validation-warning.d.ts.map +1 -0
  13. package/dist/core/entities/validation-warning.js +3 -0
  14. package/dist/core/entities/validation-warning.js.map +1 -0
  15. package/dist/core/errors.d.ts +2 -2
  16. package/dist/core/errors.d.ts.map +1 -1
  17. package/dist/core/integrations.d.ts +1 -1
  18. package/dist/core/parse-definition.d.ts +9 -7
  19. package/dist/core/parse-definition.d.ts.map +1 -1
  20. package/dist/core/parse-definition.js +14 -2
  21. package/dist/core/parse-definition.js.map +1 -1
  22. package/dist/core/sync-definitions.d.ts +2 -0
  23. package/dist/core/sync-definitions.d.ts.map +1 -1
  24. package/dist/core/sync-definitions.js +6 -5
  25. package/dist/core/sync-definitions.js.map +1 -1
  26. package/dist/core/validate-definition.d.ts +9 -5
  27. package/dist/core/validate-definition.d.ts.map +1 -1
  28. package/dist/core/validate-definition.js +23 -2
  29. package/dist/core/validate-definition.js.map +1 -1
  30. package/dist/core/workflow-model/index.d.ts +1 -1
  31. package/dist/core/workflow-model/index.d.ts.map +1 -1
  32. package/dist/core/workflow-model/index.js.map +1 -1
  33. package/dist/core/workflow-model/invalid-workflow-model-error.d.ts +3 -1
  34. package/dist/core/workflow-model/invalid-workflow-model-error.d.ts.map +1 -1
  35. package/dist/core/workflow-model/invalid-workflow-model-error.js.map +1 -1
  36. package/dist/core/workflow-model/normalize-if-condition.d.ts +0 -1
  37. package/dist/core/workflow-model/normalize-if-condition.d.ts.map +1 -1
  38. package/dist/core/workflow-model/normalize-if-condition.js +0 -1
  39. package/dist/core/workflow-model/normalize-if-condition.js.map +1 -1
  40. package/dist/core/workflow-model/normalize-job-checkout.d.ts +15 -3
  41. package/dist/core/workflow-model/normalize-job-checkout.d.ts.map +1 -1
  42. package/dist/core/workflow-model/normalize-job-checkout.js +83 -4
  43. package/dist/core/workflow-model/normalize-job-checkout.js.map +1 -1
  44. package/dist/core/workflow-model/normalize-job-listening.js +0 -1
  45. package/dist/core/workflow-model/normalize-job-listening.js.map +1 -1
  46. package/dist/core/workflow-model/normalize-job-success.d.ts.map +1 -1
  47. package/dist/core/workflow-model/normalize-job-success.js +0 -1
  48. package/dist/core/workflow-model/normalize-job-success.js.map +1 -1
  49. package/dist/core/workflow-model/normalize-jobs.d.ts.map +1 -1
  50. package/dist/core/workflow-model/normalize-jobs.js +315 -74
  51. package/dist/core/workflow-model/normalize-jobs.js.map +1 -1
  52. package/dist/core/workflow-model/normalize-step-gate.js +0 -1
  53. package/dist/core/workflow-model/normalize-step-gate.js.map +1 -1
  54. package/dist/core/workflow-model/normalize-triggers.d.ts.map +1 -1
  55. package/dist/core/workflow-model/normalize-triggers.js +0 -1
  56. package/dist/core/workflow-model/normalize-triggers.js.map +1 -1
  57. package/dist/core/workflow-model/normalize-workflow-document.d.ts +3 -0
  58. package/dist/core/workflow-model/normalize-workflow-document.d.ts.map +1 -1
  59. package/dist/core/workflow-model/normalize-workflow-document.js +34 -4
  60. package/dist/core/workflow-model/normalize-workflow-document.js.map +1 -1
  61. package/dist/core/workflow-model/parse-interpolation-field.d.ts +1 -1
  62. package/dist/core/workflow-model/parse-interpolation-field.d.ts.map +1 -1
  63. package/dist/core/workflow-model/parse-interpolation-field.js +27 -57
  64. package/dist/core/workflow-model/parse-interpolation-field.js.map +1 -1
  65. package/dist/core/workflow-model/validate-literal-name.d.ts +12 -0
  66. package/dist/core/workflow-model/validate-literal-name.d.ts.map +1 -0
  67. package/dist/core/workflow-model/validate-literal-name.js +36 -0
  68. package/dist/core/workflow-model/validate-literal-name.js.map +1 -0
  69. package/dist/core/workflow-model/validate-predicate-expression.d.ts +1 -2
  70. package/dist/core/workflow-model/validate-predicate-expression.d.ts.map +1 -1
  71. package/dist/core/workflow-model/validate-predicate-expression.js +31 -90
  72. package/dist/core/workflow-model/validate-predicate-expression.js.map +1 -1
  73. package/dist/core/workflow-model/validation-issue.d.ts +2 -1
  74. package/dist/core/workflow-model/validation-issue.d.ts.map +1 -1
  75. package/dist/core/workflow-model/validation-issue.js +8 -2
  76. package/dist/core/workflow-model/validation-issue.js.map +1 -1
  77. package/dist/core/workflow-model/workflow-field-label.d.ts.map +1 -1
  78. package/dist/core/workflow-model/workflow-field-label.js +16 -2
  79. package/dist/core/workflow-model/workflow-field-label.js.map +1 -1
  80. package/dist/core/workflow-yaml/invalid-workflow-yaml-error.d.ts.map +1 -1
  81. package/dist/db/db.d.ts +48 -10
  82. package/dist/db/db.d.ts.map +1 -1
  83. package/dist/db/schema/definitions.d.ts +1 -1
  84. package/dist/db/schema/outbox.d.ts +1 -1
  85. package/dist/db/schema/sync-states.d.ts +22 -3
  86. package/dist/db/schema/sync-states.d.ts.map +1 -1
  87. package/dist/db/schema/sync-states.js +3 -1
  88. package/dist/db/schema/sync-states.js.map +1 -1
  89. package/dist/db/sync-states.d.ts +2 -1
  90. package/dist/db/sync-states.d.ts.map +1 -1
  91. package/dist/db/sync-states.js +4 -0
  92. package/dist/db/sync-states.js.map +1 -1
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +3 -1
  95. package/dist/index.js.map +1 -1
  96. package/dist/presentation/dto/definition.d.ts.map +1 -1
  97. package/dist/presentation/dto/definition.js +2 -1
  98. package/dist/presentation/dto/definition.js.map +1 -1
  99. package/dist/presentation/routes/project-access.d.ts +1 -0
  100. package/dist/presentation/routes/project-access.d.ts.map +1 -1
  101. package/dist/presentation/routes/validate-definition.d.ts.map +1 -1
  102. package/dist/presentation/routes/validate-definition.js +5 -3
  103. package/dist/presentation/routes/validate-definition.js.map +1 -1
  104. package/dist/presentation/subscribers/index.d.ts +1 -0
  105. package/dist/presentation/subscribers/index.d.ts.map +1 -1
  106. package/dist/presentation/subscribers/index.js +1 -0
  107. package/dist/presentation/subscribers/index.js.map +1 -1
  108. package/dist/presentation/subscribers/on-integration-connection-available.d.ts +5 -0
  109. package/dist/presentation/subscribers/on-integration-connection-available.d.ts.map +1 -0
  110. package/dist/presentation/subscribers/on-integration-connection-available.js +32 -0
  111. package/dist/presentation/subscribers/on-integration-connection-available.js.map +1 -0
  112. package/dist/presentation/subscribers/start-definition-sync.d.ts +1 -0
  113. package/dist/presentation/subscribers/start-definition-sync.d.ts.map +1 -1
  114. package/dist/presentation/subscribers/start-definition-sync.js +30 -17
  115. package/dist/presentation/subscribers/start-definition-sync.js.map +1 -1
  116. package/dist/temporal/activities/sync-activities.d.ts +6 -2
  117. package/dist/temporal/activities/sync-activities.d.ts.map +1 -1
  118. package/dist/temporal/activities/sync-activities.js +8 -1
  119. package/dist/temporal/activities/sync-activities.js.map +1 -1
  120. package/dist/temporal/workflows/definition-sync.js +4 -1
  121. package/dist/temporal/workflows/definition-sync.js.map +1 -1
  122. package/dist/temporal/workflows/index.bundle.js +9029 -3
  123. package/dist/tsconfig.test.tsbuildinfo +1 -1
  124. package/drizzle/0002_lumpy_ben_parker.sql +1 -0
  125. package/drizzle/meta/0002_snapshot.json +534 -0
  126. package/drizzle/meta/_journal.json +7 -0
  127. package/package.json +17 -17
  128. package/src/core/entities/index.ts +1 -0
  129. package/src/core/entities/sync-state.ts +23 -0
  130. package/src/core/entities/validation-warning.ts +5 -0
  131. package/src/core/parse-definition-warnings.test.ts +33 -0
  132. package/src/core/parse-definition.test.ts +7 -1
  133. package/src/core/parse-definition.ts +28 -10
  134. package/src/core/sync-definitions.test.ts +39 -0
  135. package/src/core/sync-definitions.ts +14 -4
  136. package/src/core/validate-definition.test.ts +128 -0
  137. package/src/core/validate-definition.ts +39 -8
  138. package/src/core/workflow-model/README.md +2 -2
  139. package/src/core/workflow-model/index.ts +1 -0
  140. package/src/core/workflow-model/invalid-workflow-model-error.ts +8 -3
  141. package/src/core/workflow-model/normalize-if-condition.ts +0 -2
  142. package/src/core/workflow-model/normalize-job-checkout.ts +105 -8
  143. package/src/core/workflow-model/normalize-job-listening.ts +0 -1
  144. package/src/core/workflow-model/normalize-job-success.ts +0 -1
  145. package/src/core/workflow-model/normalize-jobs.ts +335 -90
  146. package/src/core/workflow-model/normalize-step-gate.ts +0 -1
  147. package/src/core/workflow-model/normalize-triggers.ts +0 -1
  148. package/src/core/workflow-model/normalize-workflow-document.test.ts +876 -202
  149. package/src/core/workflow-model/normalize-workflow-document.ts +29 -4
  150. package/src/core/workflow-model/parse-interpolation-field.ts +50 -79
  151. package/src/core/workflow-model/validate-literal-name.ts +64 -0
  152. package/src/core/workflow-model/validate-predicate-expression.test.ts +153 -15
  153. package/src/core/workflow-model/validate-predicate-expression.ts +52 -120
  154. package/src/core/workflow-model/validation-issue.ts +4 -0
  155. package/src/core/workflow-model/workflow-field-label.ts +16 -2
  156. package/src/core/workflow-yaml/README.md +2 -2
  157. package/src/db/schema/sync-states.ts +4 -1
  158. package/src/db/sync-states.test.ts +57 -0
  159. package/src/db/sync-states.ts +6 -0
  160. package/src/index.ts +12 -1
  161. package/src/presentation/dto/definition.ts +1 -0
  162. package/src/presentation/routes/list-definitions.test.ts +1 -0
  163. package/src/presentation/routes/validate-definition.test.ts +1 -0
  164. package/src/presentation/routes/validate-definition.ts +7 -1
  165. package/src/presentation/subscribers/index.ts +1 -0
  166. package/src/presentation/subscribers/on-integration-connection-available.test.ts +132 -0
  167. package/src/presentation/subscribers/on-integration-connection-available.ts +44 -0
  168. package/src/presentation/subscribers/start-definition-sync.ts +35 -19
  169. package/src/temporal/activities/sync-activities.test.ts +16 -0
  170. package/src/temporal/activities/sync-activities.ts +18 -3
  171. package/src/temporal/workflows/definition-sync.ts +1 -1
  172. package/test/agent-validation-catalog.ts +17 -0
  173. package/test/fixtures/valid-listening-job.yml +2 -1
  174. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,9 +1,9 @@
1
- import { buildTypedRootsEnvironment } from '@shipfox/expression';
1
+ import { buildTypedRootsEnvironment, classifyShellCodePosition, hoistPlannedRunCommand, UnsafeRunInterpolationError } from '@shipfox/expression';
2
2
  import { canonicalizeLabels, findInvalidLabels, MAX_RUNNER_LABEL_LENGTH, MAX_RUNNER_LABELS, RUNNER_LABEL_PATTERN } from '@shipfox/runner-labels';
3
3
  import { normalizeAgentIntegrations } from './normalize-agent-integrations.js';
4
4
  import { normalizeEnv } from './normalize-env.js';
5
5
  import { normalizeIfCondition } from './normalize-if-condition.js';
6
- import { normalizeJobCheckout } from './normalize-job-checkout.js';
6
+ import { normalizeCheckout, normalizeJobCheckout } from './normalize-job-checkout.js';
7
7
  import { normalizeJobListening } from './normalize-job-listening.js';
8
8
  import { normalizeJobSuccess } from './normalize-job-success.js';
9
9
  import { normalizeNeeds } from './normalize-needs.js';
@@ -12,6 +12,7 @@ import { normalizeStepOutputs } from './normalize-step-outputs.js';
12
12
  import { parseDurationMs } from './parse-duration-ms.js';
13
13
  import { parseInterpolationField } from './parse-interpolation-field.js';
14
14
  import { stableId } from './stable-id.js';
15
+ import { unescapeLiteralName, validateLiteralName } from './validate-literal-name.js';
15
16
  import { issue } from './validation-issue.js';
16
17
  export function normalizeJobs(document, jobIdBySourceName, issues, stepSourceLocations, context) {
17
18
  const entries = Object.entries(document.jobs);
@@ -109,6 +110,8 @@ function normalizeJob(params) {
109
110
  sourceName: params.sourceName,
110
111
  jobId: id,
111
112
  job: params.job,
113
+ workflowEnvKeys: Object.keys(params.document.env ?? {}),
114
+ jobEnvKeys: Object.keys(params.job.env ?? {}),
112
115
  issues: params.issues,
113
116
  stepSourceLocations: params.stepSourceLocations,
114
117
  fillSite: stepFillSite,
@@ -125,7 +128,9 @@ function normalizeJob(params) {
125
128
  issues: params.issues,
126
129
  defaultRunnerLabels: params.context.defaultRunnerLabels
127
130
  });
128
- const checkout = normalizeJobCheckout(params.job.checkout);
131
+ const checkout = normalizeJobCheckout({
132
+ checkout: params.job.checkout
133
+ });
129
134
  const jobEnv = normalizeEnv({
130
135
  env: params.job.env,
131
136
  path: [
@@ -147,7 +152,6 @@ function normalizeJob(params) {
147
152
  const condition = normalizeIfCondition({
148
153
  field: 'job.if',
149
154
  source: params.job.if,
150
- site: 'job-activation',
151
155
  path: [
152
156
  'jobs',
153
157
  params.sourceName,
@@ -185,21 +189,37 @@ function normalizeJob(params) {
185
189
  issues: params.issues,
186
190
  allowedJobReferences
187
191
  });
188
- const name = params.job.name === undefined ? undefined : parseInterpolationField({
189
- field: 'job.name',
190
- source: params.job.name,
192
+ if (params.job.name !== undefined) {
193
+ validateLiteralName({
194
+ field: 'job.name',
195
+ dynamicField: 'execution_name',
196
+ source: params.job.name,
197
+ path: [
198
+ 'jobs',
199
+ params.sourceName,
200
+ 'name'
201
+ ],
202
+ message: 'Job name must be literal. Move runtime interpolation to execution_name.',
203
+ issues: params.issues
204
+ });
205
+ }
206
+ const name = params.job.name === undefined ? undefined : unescapeLiteralName(params.job.name);
207
+ const executionName = params.job.execution_name === undefined ? undefined : parseInterpolationField({
208
+ field: 'job.execution_name',
209
+ source: params.job.execution_name,
191
210
  path: [
192
211
  'jobs',
193
212
  params.sourceName,
194
- 'name'
213
+ 'execution_name'
195
214
  ],
196
215
  issues: params.issues,
216
+ fillSite: 'execution-creation',
197
217
  allowedJobReferences,
198
218
  typeOverlay: upstreamJobsTypeOverlay
199
219
  }) ?? [
200
220
  {
201
221
  kind: 'literal',
202
- value: params.job.name
222
+ value: params.job.execution_name
203
223
  }
204
224
  ];
205
225
  return {
@@ -232,6 +252,9 @@ function normalizeJob(params) {
232
252
  ...name === undefined ? {} : {
233
253
  name
234
254
  },
255
+ ...executionName === undefined ? {} : {
256
+ executionName
257
+ },
235
258
  ...jobEnv,
236
259
  dependencies,
237
260
  steps
@@ -326,53 +349,32 @@ function normalizeJobOutputs(params) {
326
349
  }
327
350
  ];
328
351
  templates[key] = template;
329
- types[key] = inferJobOutputType({
330
- sourceName: params.sourceName,
331
- key,
332
- source,
333
- template,
334
- issues: params.issues
335
- });
352
+ types[key] = inferJobOutputType(template);
336
353
  }
337
354
  return {
338
355
  templates,
339
356
  types
340
357
  };
341
358
  }
342
- function inferJobOutputType(params) {
343
- if (params.template.length !== 1) return 'string';
344
- const [segment] = params.template;
359
+ function inferJobOutputType(template) {
360
+ if (template.length !== 1) return 'string';
361
+ const [segment] = template;
345
362
  if (segment?.kind !== 'deferred') return 'string';
346
363
  const resultType = segment.expression.resultType;
347
364
  if (resultType === undefined) return 'string';
348
- if (isScalarExpressionType(resultType)) return resultType;
349
- params.issues.push(issue({
350
- code: 'invalid-job-output',
351
- message: `Job output "${params.key}" must resolve to a scalar value.`,
352
- path: [
353
- 'jobs',
354
- params.sourceName,
355
- 'outputs',
356
- params.key
357
- ],
358
- details: {
359
- output: params.key,
360
- source: params.source
361
- }
362
- }));
363
- return 'string';
364
- }
365
- function isScalarExpressionType(type) {
366
- return type === 'string' || type === 'int' || type === 'double' || type === 'bool' || type === 'null' || type === 'timestamp';
365
+ return resultType;
367
366
  }
368
367
  function normalizeJobSteps(params) {
369
368
  const usedStepIds = new Map();
370
- return params.job.steps.map((step, index)=>normalizeStep({
369
+ return params.job.steps.flatMap((step, index)=>{
370
+ const normalized = normalizeStep({
371
371
  step,
372
372
  index,
373
373
  sourceName: params.sourceName,
374
374
  jobId: params.jobId,
375
375
  allSteps: params.job.steps,
376
+ workflowEnvKeys: params.workflowEnvKeys,
377
+ jobEnvKeys: params.jobEnvKeys,
376
378
  usedStepIds,
377
379
  issues: params.issues,
378
380
  stepSourceLocations: params.stepSourceLocations,
@@ -382,7 +384,11 @@ function normalizeJobSteps(params) {
382
384
  upstreamJobs: params.upstreamJobs,
383
385
  directNeedJobs: params.directNeedJobs,
384
386
  context: params.context
385
- }));
387
+ });
388
+ return normalized === undefined ? [] : [
389
+ normalized
390
+ ];
391
+ });
386
392
  }
387
393
  function normalizeStep(params) {
388
394
  const stepKey = params.step.key;
@@ -442,7 +448,6 @@ function normalizeStep(params) {
442
448
  const condition = normalizeIfCondition({
443
449
  field: 'step.if',
444
450
  source: params.step.if,
445
- site: 'step-dispatch',
446
451
  path: [
447
452
  'jobs',
448
453
  params.sourceName,
@@ -484,6 +489,21 @@ function normalizeStep(params) {
484
489
  allowedJobReferences: params.allowedJobReferences,
485
490
  typeOverlay
486
491
  });
492
+ const workingDirectory = params.step.working_directory === undefined ? undefined : parseInterpolationField({
493
+ field: 'step.working_directory',
494
+ source: params.step.working_directory,
495
+ path: [
496
+ 'jobs',
497
+ params.sourceName,
498
+ 'steps',
499
+ params.index,
500
+ 'working_directory'
501
+ ],
502
+ issues: params.issues,
503
+ fillSite: params.fillSite,
504
+ allowedJobReferences: params.allowedJobReferences,
505
+ typeOverlay
506
+ });
487
507
  const stepBase = {
488
508
  id: stepId,
489
509
  ...stepKey === undefined ? {} : {
@@ -492,6 +512,9 @@ function normalizeStep(params) {
492
512
  ...params.step.name === undefined ? {} : {
493
513
  name: params.step.name
494
514
  },
515
+ ...params.step.working_directory === undefined ? {} : {
516
+ workingDirectory: params.step.working_directory
517
+ },
495
518
  ...outputs === undefined ? {} : {
496
519
  outputs
497
520
  },
@@ -511,7 +534,10 @@ function normalizeStep(params) {
511
534
  stepBase,
512
535
  sourceName: params.sourceName,
513
536
  stepIndex: params.index,
537
+ workflowEnvKeys: params.workflowEnvKeys,
538
+ jobEnvKeys: params.jobEnvKeys,
514
539
  name,
540
+ workingDirectory,
515
541
  issues: params.issues,
516
542
  fillSite: params.fillSite,
517
543
  allowedJobReferences: params.allowedJobReferences,
@@ -525,6 +551,7 @@ function normalizeStep(params) {
525
551
  sourceName: params.sourceName,
526
552
  stepIndex: params.index,
527
553
  name,
554
+ workingDirectory,
528
555
  issues: params.issues,
529
556
  fillSite: params.fillSite,
530
557
  allowedJobReferences: params.allowedJobReferences,
@@ -532,9 +559,51 @@ function normalizeStep(params) {
532
559
  context: params.context
533
560
  });
534
561
  }
535
- // workflowDocumentStepSchema requires either `run` or an agent `prompt`; this
536
- // keeps the model-step union honest if callers bypass the document parser.
537
- throw new Error(`Workflow step "${stepId}" is neither a run nor an agent step`);
562
+ if (params.step.checkout !== undefined) {
563
+ return normalizeCheckoutStep({
564
+ step: params.step,
565
+ stepBase,
566
+ name,
567
+ sourceName: params.sourceName,
568
+ stepIndex: params.index,
569
+ issues: params.issues,
570
+ fillSite: params.fillSite,
571
+ allowedJobReferences: params.allowedJobReferences,
572
+ typeOverlay
573
+ });
574
+ }
575
+ // Keep the model-step union honest if callers bypass the document parser.
576
+ throw new Error(`Workflow step "${stepId}" is neither a run, agent, nor checkout step`);
577
+ }
578
+ function normalizeCheckoutStep(params) {
579
+ const checkout = params.step.checkout;
580
+ if (checkout === undefined) {
581
+ throw new Error('Checkout step normalization requires checkout settings');
582
+ }
583
+ const normalizedCheckout = normalizeCheckout({
584
+ checkout,
585
+ issues: params.issues,
586
+ path: [
587
+ 'jobs',
588
+ params.sourceName,
589
+ 'steps',
590
+ params.stepIndex,
591
+ 'checkout'
592
+ ],
593
+ fillSite: params.fillSite,
594
+ allowedJobReferences: params.allowedJobReferences,
595
+ typeOverlay: params.typeOverlay
596
+ });
597
+ return {
598
+ ...params.stepBase,
599
+ kind: 'checkout',
600
+ checkout: normalizedCheckout,
601
+ ...params.name === undefined ? {} : {
602
+ templates: {
603
+ name: params.name
604
+ }
605
+ }
606
+ };
538
607
  }
539
608
  function normalizeRunStep(params) {
540
609
  if (params.step.run === undefined) {
@@ -569,9 +638,25 @@ function normalizeRunStep(params) {
569
638
  allowedJobReferences: params.allowedJobReferences,
570
639
  typeOverlay: params.typeOverlay
571
640
  });
641
+ addReevaluatingCommandWarnings({
642
+ command: commandTemplate,
643
+ source: params.step.run,
644
+ workflowEnvKeys: params.workflowEnvKeys,
645
+ jobEnvKeys: params.jobEnvKeys,
646
+ stepEnvKeys: Object.keys(params.step.env ?? {}),
647
+ path: [
648
+ 'jobs',
649
+ params.sourceName,
650
+ 'steps',
651
+ params.stepIndex,
652
+ 'run'
653
+ ],
654
+ issues: params.issues
655
+ });
572
656
  const templates = optionalRunStepTemplates({
573
657
  command: commandTemplate,
574
658
  name: params.name,
659
+ workingDirectory: params.workingDirectory,
575
660
  env: stepEnv.templates?.env
576
661
  });
577
662
  return {
@@ -589,6 +674,90 @@ function normalizeRunStep(params) {
589
674
  }
590
675
  };
591
676
  }
677
+ function addReevaluatingCommandWarnings(params) {
678
+ const envKeys = new Set([
679
+ ...params.workflowEnvKeys,
680
+ ...params.jobEnvKeys,
681
+ ...params.stepEnvKeys
682
+ ]);
683
+ let hoisted;
684
+ try {
685
+ hoisted = hoistPlannedRunCommand({
686
+ field: {
687
+ segments: params.command ?? [
688
+ {
689
+ kind: 'literal',
690
+ value: params.source
691
+ }
692
+ ]
693
+ },
694
+ reservedNames: envKeys
695
+ });
696
+ } catch (error) {
697
+ if (!(error instanceof UnsafeRunInterpolationError)) return;
698
+ for (const occurrence of error.occurrences){
699
+ params.issues.push(issue({
700
+ code: 're-evaluating-command',
701
+ message: unsafeInterpolationWarningMessage(occurrence),
702
+ path: params.path,
703
+ severity: 'warning'
704
+ }));
705
+ }
706
+ if (error.partial === undefined) return;
707
+ hoisted = error.partial;
708
+ }
709
+ try {
710
+ const matches = classifyShellCodePosition({
711
+ command: hoisted.command,
712
+ workflowDataNames: [
713
+ ...envKeys,
714
+ ...hoisted.bindings.map((binding)=>binding.name)
715
+ ]
716
+ }).matches;
717
+ for (const match of matches){
718
+ params.issues.push(issue({
719
+ code: 're-evaluating-command',
720
+ message: reevaluatingCommandWarningMessage(match.construct, match.name),
721
+ path: params.path,
722
+ severity: 'warning'
723
+ }));
724
+ }
725
+ } catch {
726
+ return;
727
+ }
728
+ }
729
+ function unsafeInterpolationWarningMessage(error) {
730
+ const regionLabel = {
731
+ single: 'single-quoted shell text',
732
+ double: 'double-quoted shell text',
733
+ 'dollar-single': 'ANSI-C shell text',
734
+ 'dollar-double': 'dollar-double-quoted shell text',
735
+ 'paren-sub': 'command substitution',
736
+ arith: 'shell arithmetic',
737
+ backtick: 'backtick substitution',
738
+ 'param-brace': 'shell parameter expansion',
739
+ heredoc: 'a heredoc',
740
+ 'line-comment': 'a shell comment',
741
+ escape: 'a shell escape'
742
+ }[error.region];
743
+ return `Value "${error.source}" is inside ${regionLabel} and will be re-executed as code. Hoisting cannot protect it; move the interpolation to a normal quoted argument instead.`;
744
+ }
745
+ function reevaluatingCommandWarningMessage(construct, name) {
746
+ const constructLabel = {
747
+ eval: 'eval',
748
+ 'sh-c': 'sh -c',
749
+ 'bash-c': 'bash -c',
750
+ source: 'source',
751
+ let: 'let',
752
+ 'declare-i': 'declare -i',
753
+ arithmetic: 'shell arithmetic',
754
+ awk: 'awk',
755
+ jq: 'jq',
756
+ sed: 'sed',
757
+ 'xargs-sh-c': 'xargs sh -c'
758
+ }[construct];
759
+ return `Value "$${name}" is passed to ${constructLabel} and re-executed as code. Hoisting cannot protect it; pass the value to a fixed program instead.`;
760
+ }
592
761
  function normalizeAgentStep(params) {
593
762
  if (params.step.prompt === undefined) {
594
763
  throw new Error('Agent step normalization requires a prompt');
@@ -638,12 +807,28 @@ function normalizeAgentStep(params) {
638
807
  allowedJobReferences: params.allowedJobReferences,
639
808
  typeOverlay: params.typeOverlay
640
809
  });
810
+ const thinkingTemplate = params.step.thinking === undefined ? undefined : parseInterpolationField({
811
+ field: 'agent.thinking',
812
+ source: params.step.thinking,
813
+ path: [
814
+ 'jobs',
815
+ params.sourceName,
816
+ 'steps',
817
+ params.stepIndex,
818
+ 'thinking'
819
+ ],
820
+ issues: params.issues,
821
+ fillSite: params.fillSite,
822
+ allowedJobReferences: params.allowedJobReferences,
823
+ typeOverlay: params.typeOverlay
824
+ });
641
825
  validateAgentStep({
642
826
  step: params.step,
643
827
  sourceName: params.sourceName,
644
828
  stepIndex: params.stepIndex,
645
829
  issues: params.issues,
646
- validateLiteralProvider: providerTemplate === undefined,
830
+ validateLiteralModel: params.step.model !== undefined && !hasInterpolationSyntax(params.step.model),
831
+ validateLiteralProvider: params.step.provider !== undefined && !hasInterpolationSyntax(params.step.provider),
647
832
  agentValidationCatalog: params.context.agentValidationCatalog
648
833
  });
649
834
  const integrations = normalizeAgentIntegrations({
@@ -657,7 +842,9 @@ function normalizeAgentStep(params) {
657
842
  prompt: promptTemplate,
658
843
  model: modelTemplate,
659
844
  provider: providerTemplate,
660
- name: params.name
845
+ thinking: thinkingTemplate,
846
+ name: params.name,
847
+ workingDirectory: params.workingDirectory
661
848
  });
662
849
  return {
663
850
  ...params.stepBase,
@@ -689,46 +876,88 @@ function normalizeAgentStep(params) {
689
876
  function validateAgentStep(params) {
690
877
  validateHarnessThinking(params);
691
878
  validateHarnessTools(params);
692
- if (!params.validateLiteralProvider) return;
693
879
  const providerId = params.step.provider;
694
- if (providerId === undefined) return;
695
- const provider = params.agentValidationCatalog.providers.find((entry)=>entry.id === providerId);
696
880
  const harness = params.step.harness;
697
- if (provider === undefined && harness === 'pi') return;
698
- if (provider === undefined || provider.support_status !== 'supported') {
699
- params.issues.push(issue({
700
- code: 'invalid-provider',
701
- message: `Provider "${providerId}" is not supported.`,
702
- path: [
703
- 'jobs',
704
- params.sourceName,
705
- 'steps',
706
- params.stepIndex,
707
- 'provider'
708
- ],
709
- details: {
710
- provider: providerId
711
- }
712
- }));
713
- return;
881
+ const provider = providerId === undefined ? undefined : params.agentValidationCatalog.providers.find((entry)=>entry.id === providerId);
882
+ if (params.validateLiteralProvider && providerId !== undefined) {
883
+ if (provider === undefined) {
884
+ if (harness === undefined || harness === 'pi') return;
885
+ params.issues.push(issue({
886
+ code: 'invalid-provider',
887
+ message: `Provider "${providerId}" is not supported.`,
888
+ path: [
889
+ 'jobs',
890
+ params.sourceName,
891
+ 'steps',
892
+ params.stepIndex,
893
+ 'provider'
894
+ ],
895
+ details: {
896
+ provider: providerId
897
+ }
898
+ }));
899
+ return;
900
+ }
901
+ if (provider.support_status !== 'supported') {
902
+ params.issues.push(issue({
903
+ code: 'invalid-provider',
904
+ message: `Provider "${providerId}" is not supported.`,
905
+ path: [
906
+ 'jobs',
907
+ params.sourceName,
908
+ 'steps',
909
+ params.stepIndex,
910
+ 'provider'
911
+ ],
912
+ details: {
913
+ provider: providerId
914
+ }
915
+ }));
916
+ return;
917
+ }
918
+ if (harness === undefined) return;
919
+ const descriptor = params.agentValidationCatalog.harnesses.find((entry)=>entry.id === harness);
920
+ if (!descriptor?.supported_provider_ids.includes(providerId)) {
921
+ params.issues.push(issue({
922
+ code: 'harness-provider-incompatible',
923
+ message: `Harness "${harness}" does not support provider: ${providerId}. Supported providers: ${descriptor?.supported_provider_ids.join(', ') ?? ''}.`,
924
+ path: [
925
+ 'jobs',
926
+ params.sourceName,
927
+ 'steps',
928
+ params.stepIndex,
929
+ 'provider'
930
+ ],
931
+ details: {
932
+ harness,
933
+ provider: providerId,
934
+ supportedProviders: descriptor?.supported_provider_ids ?? []
935
+ }
936
+ }));
937
+ return;
938
+ }
714
939
  }
715
- if (harness === undefined) return;
940
+ if (!params.validateLiteralModel || providerId === undefined || harness === undefined) return;
941
+ if (provider === undefined || provider.support_status !== 'supported') return;
942
+ const model = params.step.model;
943
+ if (model === undefined) return;
716
944
  const descriptor = params.agentValidationCatalog.harnesses.find((entry)=>entry.id === harness);
717
- if (descriptor?.supported_provider_ids.includes(providerId)) return;
945
+ const modelIds = descriptor?.model_ids_by_provider?.[providerId];
946
+ if (modelIds === undefined || modelIds.includes(model)) return;
718
947
  params.issues.push(issue({
719
- code: 'harness-provider-incompatible',
720
- message: `Harness "${harness}" does not support provider: ${providerId}. Supported providers: ${descriptor?.supported_provider_ids.join(', ') ?? ''}.`,
948
+ code: 'invalid-model',
949
+ message: `Agent model "${model}" is not available for harness "${harness}" and provider "${providerId}".`,
721
950
  path: [
722
951
  'jobs',
723
952
  params.sourceName,
724
953
  'steps',
725
954
  params.stepIndex,
726
- 'provider'
955
+ 'model'
727
956
  ],
728
957
  details: {
729
958
  harness,
730
959
  provider: providerId,
731
- supportedProviders: descriptor?.supported_provider_ids ?? []
960
+ model
732
961
  }
733
962
  }));
734
963
  }
@@ -778,6 +1007,9 @@ function validateHarnessTools(params) {
778
1007
  function validateHarnessThinking(params) {
779
1008
  const { harness, thinking } = params.step;
780
1009
  if (harness === undefined || thinking === undefined) return;
1010
+ // An interpolated level is only known when the step dispatches, so the agent
1011
+ // module checks it against the resolved harness there.
1012
+ if (hasInterpolationSyntax(thinking)) return;
781
1013
  const supportedLevels = params.agentValidationCatalog.harnesses.find((entry)=>entry.id === harness)?.thinking_levels ?? [];
782
1014
  if (supportedLevels.includes(thinking)) return;
783
1015
  params.issues.push(issue({
@@ -895,7 +1127,7 @@ function validateRunnerLabels(params) {
895
1127
  }
896
1128
  }
897
1129
  function optionalRunStepTemplates(params) {
898
- if (params.command === undefined && params.name === undefined && params.env === undefined) {
1130
+ if (params.command === undefined && params.name === undefined && params.workingDirectory === undefined && params.env === undefined) {
899
1131
  return undefined;
900
1132
  }
901
1133
  return {
@@ -905,13 +1137,16 @@ function optionalRunStepTemplates(params) {
905
1137
  ...params.name === undefined ? {} : {
906
1138
  name: params.name
907
1139
  },
1140
+ ...params.workingDirectory === undefined ? {} : {
1141
+ workingDirectory: params.workingDirectory
1142
+ },
908
1143
  ...params.env === undefined ? {} : {
909
1144
  env: params.env
910
1145
  }
911
1146
  };
912
1147
  }
913
1148
  function optionalAgentStepTemplates(params) {
914
- if (params.prompt === undefined && params.model === undefined && params.provider === undefined && params.name === undefined) {
1149
+ if (params.prompt === undefined && params.model === undefined && params.provider === undefined && params.thinking === undefined && params.name === undefined && params.workingDirectory === undefined) {
915
1150
  return undefined;
916
1151
  }
917
1152
  return {
@@ -924,8 +1159,14 @@ function optionalAgentStepTemplates(params) {
924
1159
  ...params.provider === undefined ? {} : {
925
1160
  provider: params.provider
926
1161
  },
1162
+ ...params.thinking === undefined ? {} : {
1163
+ thinking: params.thinking
1164
+ },
927
1165
  ...params.name === undefined ? {} : {
928
1166
  name: params.name
1167
+ },
1168
+ ...params.workingDirectory === undefined ? {} : {
1169
+ workingDirectory: params.workingDirectory
929
1170
  }
930
1171
  };
931
1172
  }