@shipfox/api-definitions 11.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 (148) hide show
  1. package/.turbo/turbo-build.log +13 -12
  2. package/CHANGELOG.md +81 -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 +4 -0
  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/parse-definition.d.ts +9 -7
  16. package/dist/core/parse-definition.d.ts.map +1 -1
  17. package/dist/core/parse-definition.js +14 -2
  18. package/dist/core/parse-definition.js.map +1 -1
  19. package/dist/core/sync-definitions.d.ts +2 -0
  20. package/dist/core/sync-definitions.d.ts.map +1 -1
  21. package/dist/core/sync-definitions.js +6 -5
  22. package/dist/core/sync-definitions.js.map +1 -1
  23. package/dist/core/validate-definition.d.ts +9 -5
  24. package/dist/core/validate-definition.d.ts.map +1 -1
  25. package/dist/core/validate-definition.js +23 -2
  26. package/dist/core/validate-definition.js.map +1 -1
  27. package/dist/core/workflow-model/index.d.ts +1 -1
  28. package/dist/core/workflow-model/index.d.ts.map +1 -1
  29. package/dist/core/workflow-model/index.js.map +1 -1
  30. package/dist/core/workflow-model/invalid-workflow-model-error.d.ts +3 -1
  31. package/dist/core/workflow-model/invalid-workflow-model-error.d.ts.map +1 -1
  32. package/dist/core/workflow-model/invalid-workflow-model-error.js.map +1 -1
  33. package/dist/core/workflow-model/normalize-if-condition.d.ts +0 -1
  34. package/dist/core/workflow-model/normalize-if-condition.d.ts.map +1 -1
  35. package/dist/core/workflow-model/normalize-if-condition.js +0 -1
  36. package/dist/core/workflow-model/normalize-if-condition.js.map +1 -1
  37. package/dist/core/workflow-model/normalize-job-checkout.d.ts +15 -3
  38. package/dist/core/workflow-model/normalize-job-checkout.d.ts.map +1 -1
  39. package/dist/core/workflow-model/normalize-job-checkout.js +83 -4
  40. package/dist/core/workflow-model/normalize-job-checkout.js.map +1 -1
  41. package/dist/core/workflow-model/normalize-job-listening.js +0 -1
  42. package/dist/core/workflow-model/normalize-job-listening.js.map +1 -1
  43. package/dist/core/workflow-model/normalize-job-success.d.ts.map +1 -1
  44. package/dist/core/workflow-model/normalize-job-success.js +0 -1
  45. package/dist/core/workflow-model/normalize-job-success.js.map +1 -1
  46. package/dist/core/workflow-model/normalize-jobs.d.ts.map +1 -1
  47. package/dist/core/workflow-model/normalize-jobs.js +310 -45
  48. package/dist/core/workflow-model/normalize-jobs.js.map +1 -1
  49. package/dist/core/workflow-model/normalize-step-gate.js +0 -1
  50. package/dist/core/workflow-model/normalize-step-gate.js.map +1 -1
  51. package/dist/core/workflow-model/normalize-triggers.d.ts.map +1 -1
  52. package/dist/core/workflow-model/normalize-triggers.js +0 -1
  53. package/dist/core/workflow-model/normalize-triggers.js.map +1 -1
  54. package/dist/core/workflow-model/normalize-workflow-document.d.ts +3 -0
  55. package/dist/core/workflow-model/normalize-workflow-document.d.ts.map +1 -1
  56. package/dist/core/workflow-model/normalize-workflow-document.js +34 -4
  57. package/dist/core/workflow-model/normalize-workflow-document.js.map +1 -1
  58. package/dist/core/workflow-model/parse-interpolation-field.d.ts +1 -1
  59. package/dist/core/workflow-model/parse-interpolation-field.d.ts.map +1 -1
  60. package/dist/core/workflow-model/parse-interpolation-field.js +27 -57
  61. package/dist/core/workflow-model/parse-interpolation-field.js.map +1 -1
  62. package/dist/core/workflow-model/validate-literal-name.d.ts +12 -0
  63. package/dist/core/workflow-model/validate-literal-name.d.ts.map +1 -0
  64. package/dist/core/workflow-model/validate-literal-name.js +36 -0
  65. package/dist/core/workflow-model/validate-literal-name.js.map +1 -0
  66. package/dist/core/workflow-model/validate-predicate-expression.d.ts +1 -2
  67. package/dist/core/workflow-model/validate-predicate-expression.d.ts.map +1 -1
  68. package/dist/core/workflow-model/validate-predicate-expression.js +31 -90
  69. package/dist/core/workflow-model/validate-predicate-expression.js.map +1 -1
  70. package/dist/core/workflow-model/validation-issue.d.ts +2 -1
  71. package/dist/core/workflow-model/validation-issue.d.ts.map +1 -1
  72. package/dist/core/workflow-model/validation-issue.js +8 -2
  73. package/dist/core/workflow-model/validation-issue.js.map +1 -1
  74. package/dist/core/workflow-model/workflow-field-label.d.ts.map +1 -1
  75. package/dist/core/workflow-model/workflow-field-label.js +16 -2
  76. package/dist/core/workflow-model/workflow-field-label.js.map +1 -1
  77. package/dist/db/db.d.ts +38 -0
  78. package/dist/db/db.d.ts.map +1 -1
  79. package/dist/db/schema/sync-states.d.ts +19 -0
  80. package/dist/db/schema/sync-states.d.ts.map +1 -1
  81. package/dist/db/schema/sync-states.js +3 -1
  82. package/dist/db/schema/sync-states.js.map +1 -1
  83. package/dist/db/sync-states.d.ts +2 -1
  84. package/dist/db/sync-states.d.ts.map +1 -1
  85. package/dist/db/sync-states.js +4 -0
  86. package/dist/db/sync-states.js.map +1 -1
  87. package/dist/presentation/dto/definition.d.ts.map +1 -1
  88. package/dist/presentation/dto/definition.js +2 -1
  89. package/dist/presentation/dto/definition.js.map +1 -1
  90. package/dist/presentation/routes/project-access.d.ts +1 -0
  91. package/dist/presentation/routes/project-access.d.ts.map +1 -1
  92. package/dist/presentation/routes/validate-definition.d.ts.map +1 -1
  93. package/dist/presentation/routes/validate-definition.js +5 -3
  94. package/dist/presentation/routes/validate-definition.js.map +1 -1
  95. package/dist/temporal/activities/sync-activities.d.ts +6 -2
  96. package/dist/temporal/activities/sync-activities.d.ts.map +1 -1
  97. package/dist/temporal/activities/sync-activities.js +8 -1
  98. package/dist/temporal/activities/sync-activities.js.map +1 -1
  99. package/dist/temporal/workflows/definition-sync.js +4 -1
  100. package/dist/temporal/workflows/definition-sync.js.map +1 -1
  101. package/dist/temporal/workflows/index.bundle.js +9029 -3
  102. package/dist/tsconfig.test.tsbuildinfo +1 -1
  103. package/drizzle/0002_lumpy_ben_parker.sql +1 -0
  104. package/drizzle/meta/0002_snapshot.json +534 -0
  105. package/drizzle/meta/_journal.json +7 -0
  106. package/package.json +14 -14
  107. package/src/core/entities/index.ts +1 -0
  108. package/src/core/entities/sync-state.ts +23 -0
  109. package/src/core/entities/validation-warning.ts +5 -0
  110. package/src/core/parse-definition-warnings.test.ts +33 -0
  111. package/src/core/parse-definition.test.ts +7 -1
  112. package/src/core/parse-definition.ts +28 -10
  113. package/src/core/sync-definitions.test.ts +39 -0
  114. package/src/core/sync-definitions.ts +14 -4
  115. package/src/core/validate-definition.test.ts +128 -0
  116. package/src/core/validate-definition.ts +39 -8
  117. package/src/core/workflow-model/README.md +2 -2
  118. package/src/core/workflow-model/index.ts +1 -0
  119. package/src/core/workflow-model/invalid-workflow-model-error.ts +8 -2
  120. package/src/core/workflow-model/normalize-if-condition.ts +0 -2
  121. package/src/core/workflow-model/normalize-job-checkout.ts +105 -8
  122. package/src/core/workflow-model/normalize-job-listening.ts +0 -1
  123. package/src/core/workflow-model/normalize-job-success.ts +0 -1
  124. package/src/core/workflow-model/normalize-jobs.ts +330 -49
  125. package/src/core/workflow-model/normalize-step-gate.ts +0 -1
  126. package/src/core/workflow-model/normalize-triggers.ts +0 -1
  127. package/src/core/workflow-model/normalize-workflow-document.test.ts +732 -185
  128. package/src/core/workflow-model/normalize-workflow-document.ts +29 -4
  129. package/src/core/workflow-model/parse-interpolation-field.ts +50 -79
  130. package/src/core/workflow-model/validate-literal-name.ts +64 -0
  131. package/src/core/workflow-model/validate-predicate-expression.test.ts +153 -15
  132. package/src/core/workflow-model/validate-predicate-expression.ts +52 -120
  133. package/src/core/workflow-model/validation-issue.ts +4 -0
  134. package/src/core/workflow-model/workflow-field-label.ts +16 -2
  135. package/src/core/workflow-yaml/README.md +2 -2
  136. package/src/db/schema/sync-states.ts +4 -1
  137. package/src/db/sync-states.test.ts +57 -0
  138. package/src/db/sync-states.ts +6 -0
  139. package/src/presentation/dto/definition.ts +1 -0
  140. package/src/presentation/routes/list-definitions.test.ts +1 -0
  141. package/src/presentation/routes/validate-definition.test.ts +1 -0
  142. package/src/presentation/routes/validate-definition.ts +7 -1
  143. package/src/temporal/activities/sync-activities.test.ts +16 -0
  144. package/src/temporal/activities/sync-activities.ts +18 -3
  145. package/src/temporal/workflows/definition-sync.ts +1 -1
  146. package/test/agent-validation-catalog.ts +17 -0
  147. package/test/fixtures/valid-listening-job.yml +2 -1
  148. 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
@@ -367,12 +390,15 @@ function isScalarExpressionType(type) {
367
390
  }
368
391
  function normalizeJobSteps(params) {
369
392
  const usedStepIds = new Map();
370
- return params.job.steps.map((step, index)=>normalizeStep({
393
+ return params.job.steps.flatMap((step, index)=>{
394
+ const normalized = normalizeStep({
371
395
  step,
372
396
  index,
373
397
  sourceName: params.sourceName,
374
398
  jobId: params.jobId,
375
399
  allSteps: params.job.steps,
400
+ workflowEnvKeys: params.workflowEnvKeys,
401
+ jobEnvKeys: params.jobEnvKeys,
376
402
  usedStepIds,
377
403
  issues: params.issues,
378
404
  stepSourceLocations: params.stepSourceLocations,
@@ -382,7 +408,11 @@ function normalizeJobSteps(params) {
382
408
  upstreamJobs: params.upstreamJobs,
383
409
  directNeedJobs: params.directNeedJobs,
384
410
  context: params.context
385
- }));
411
+ });
412
+ return normalized === undefined ? [] : [
413
+ normalized
414
+ ];
415
+ });
386
416
  }
387
417
  function normalizeStep(params) {
388
418
  const stepKey = params.step.key;
@@ -442,7 +472,6 @@ function normalizeStep(params) {
442
472
  const condition = normalizeIfCondition({
443
473
  field: 'step.if',
444
474
  source: params.step.if,
445
- site: 'step-dispatch',
446
475
  path: [
447
476
  'jobs',
448
477
  params.sourceName,
@@ -484,6 +513,21 @@ function normalizeStep(params) {
484
513
  allowedJobReferences: params.allowedJobReferences,
485
514
  typeOverlay
486
515
  });
516
+ const workingDirectory = params.step.working_directory === undefined ? undefined : parseInterpolationField({
517
+ field: 'step.working_directory',
518
+ source: params.step.working_directory,
519
+ path: [
520
+ 'jobs',
521
+ params.sourceName,
522
+ 'steps',
523
+ params.index,
524
+ 'working_directory'
525
+ ],
526
+ issues: params.issues,
527
+ fillSite: params.fillSite,
528
+ allowedJobReferences: params.allowedJobReferences,
529
+ typeOverlay
530
+ });
487
531
  const stepBase = {
488
532
  id: stepId,
489
533
  ...stepKey === undefined ? {} : {
@@ -492,6 +536,9 @@ function normalizeStep(params) {
492
536
  ...params.step.name === undefined ? {} : {
493
537
  name: params.step.name
494
538
  },
539
+ ...params.step.working_directory === undefined ? {} : {
540
+ workingDirectory: params.step.working_directory
541
+ },
495
542
  ...outputs === undefined ? {} : {
496
543
  outputs
497
544
  },
@@ -511,7 +558,10 @@ function normalizeStep(params) {
511
558
  stepBase,
512
559
  sourceName: params.sourceName,
513
560
  stepIndex: params.index,
561
+ workflowEnvKeys: params.workflowEnvKeys,
562
+ jobEnvKeys: params.jobEnvKeys,
514
563
  name,
564
+ workingDirectory,
515
565
  issues: params.issues,
516
566
  fillSite: params.fillSite,
517
567
  allowedJobReferences: params.allowedJobReferences,
@@ -525,6 +575,7 @@ function normalizeStep(params) {
525
575
  sourceName: params.sourceName,
526
576
  stepIndex: params.index,
527
577
  name,
578
+ workingDirectory,
528
579
  issues: params.issues,
529
580
  fillSite: params.fillSite,
530
581
  allowedJobReferences: params.allowedJobReferences,
@@ -532,9 +583,51 @@ function normalizeStep(params) {
532
583
  context: params.context
533
584
  });
534
585
  }
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`);
586
+ if (params.step.checkout !== undefined) {
587
+ return normalizeCheckoutStep({
588
+ step: params.step,
589
+ stepBase,
590
+ name,
591
+ sourceName: params.sourceName,
592
+ stepIndex: params.index,
593
+ issues: params.issues,
594
+ fillSite: params.fillSite,
595
+ allowedJobReferences: params.allowedJobReferences,
596
+ typeOverlay
597
+ });
598
+ }
599
+ // Keep the model-step union honest if callers bypass the document parser.
600
+ throw new Error(`Workflow step "${stepId}" is neither a run, agent, nor checkout step`);
601
+ }
602
+ function normalizeCheckoutStep(params) {
603
+ const checkout = params.step.checkout;
604
+ if (checkout === undefined) {
605
+ throw new Error('Checkout step normalization requires checkout settings');
606
+ }
607
+ const normalizedCheckout = normalizeCheckout({
608
+ checkout,
609
+ issues: params.issues,
610
+ path: [
611
+ 'jobs',
612
+ params.sourceName,
613
+ 'steps',
614
+ params.stepIndex,
615
+ 'checkout'
616
+ ],
617
+ fillSite: params.fillSite,
618
+ allowedJobReferences: params.allowedJobReferences,
619
+ typeOverlay: params.typeOverlay
620
+ });
621
+ return {
622
+ ...params.stepBase,
623
+ kind: 'checkout',
624
+ checkout: normalizedCheckout,
625
+ ...params.name === undefined ? {} : {
626
+ templates: {
627
+ name: params.name
628
+ }
629
+ }
630
+ };
538
631
  }
539
632
  function normalizeRunStep(params) {
540
633
  if (params.step.run === undefined) {
@@ -569,9 +662,25 @@ function normalizeRunStep(params) {
569
662
  allowedJobReferences: params.allowedJobReferences,
570
663
  typeOverlay: params.typeOverlay
571
664
  });
665
+ addReevaluatingCommandWarnings({
666
+ command: commandTemplate,
667
+ source: params.step.run,
668
+ workflowEnvKeys: params.workflowEnvKeys,
669
+ jobEnvKeys: params.jobEnvKeys,
670
+ stepEnvKeys: Object.keys(params.step.env ?? {}),
671
+ path: [
672
+ 'jobs',
673
+ params.sourceName,
674
+ 'steps',
675
+ params.stepIndex,
676
+ 'run'
677
+ ],
678
+ issues: params.issues
679
+ });
572
680
  const templates = optionalRunStepTemplates({
573
681
  command: commandTemplate,
574
682
  name: params.name,
683
+ workingDirectory: params.workingDirectory,
575
684
  env: stepEnv.templates?.env
576
685
  });
577
686
  return {
@@ -589,6 +698,90 @@ function normalizeRunStep(params) {
589
698
  }
590
699
  };
591
700
  }
701
+ function addReevaluatingCommandWarnings(params) {
702
+ const envKeys = new Set([
703
+ ...params.workflowEnvKeys,
704
+ ...params.jobEnvKeys,
705
+ ...params.stepEnvKeys
706
+ ]);
707
+ let hoisted;
708
+ try {
709
+ hoisted = hoistPlannedRunCommand({
710
+ field: {
711
+ segments: params.command ?? [
712
+ {
713
+ kind: 'literal',
714
+ value: params.source
715
+ }
716
+ ]
717
+ },
718
+ reservedNames: envKeys
719
+ });
720
+ } catch (error) {
721
+ if (!(error instanceof UnsafeRunInterpolationError)) return;
722
+ for (const occurrence of error.occurrences){
723
+ params.issues.push(issue({
724
+ code: 're-evaluating-command',
725
+ message: unsafeInterpolationWarningMessage(occurrence),
726
+ path: params.path,
727
+ severity: 'warning'
728
+ }));
729
+ }
730
+ if (error.partial === undefined) return;
731
+ hoisted = error.partial;
732
+ }
733
+ try {
734
+ const matches = classifyShellCodePosition({
735
+ command: hoisted.command,
736
+ workflowDataNames: [
737
+ ...envKeys,
738
+ ...hoisted.bindings.map((binding)=>binding.name)
739
+ ]
740
+ }).matches;
741
+ for (const match of matches){
742
+ params.issues.push(issue({
743
+ code: 're-evaluating-command',
744
+ message: reevaluatingCommandWarningMessage(match.construct, match.name),
745
+ path: params.path,
746
+ severity: 'warning'
747
+ }));
748
+ }
749
+ } catch {
750
+ return;
751
+ }
752
+ }
753
+ function unsafeInterpolationWarningMessage(error) {
754
+ const regionLabel = {
755
+ single: 'single-quoted shell text',
756
+ double: 'double-quoted shell text',
757
+ 'dollar-single': 'ANSI-C shell text',
758
+ 'dollar-double': 'dollar-double-quoted shell text',
759
+ 'paren-sub': 'command substitution',
760
+ arith: 'shell arithmetic',
761
+ backtick: 'backtick substitution',
762
+ 'param-brace': 'shell parameter expansion',
763
+ heredoc: 'a heredoc',
764
+ 'line-comment': 'a shell comment',
765
+ escape: 'a shell escape'
766
+ }[error.region];
767
+ 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.`;
768
+ }
769
+ function reevaluatingCommandWarningMessage(construct, name) {
770
+ const constructLabel = {
771
+ eval: 'eval',
772
+ 'sh-c': 'sh -c',
773
+ 'bash-c': 'bash -c',
774
+ source: 'source',
775
+ let: 'let',
776
+ 'declare-i': 'declare -i',
777
+ arithmetic: 'shell arithmetic',
778
+ awk: 'awk',
779
+ jq: 'jq',
780
+ sed: 'sed',
781
+ 'xargs-sh-c': 'xargs sh -c'
782
+ }[construct];
783
+ return `Value "$${name}" is passed to ${constructLabel} and re-executed as code. Hoisting cannot protect it; pass the value to a fixed program instead.`;
784
+ }
592
785
  function normalizeAgentStep(params) {
593
786
  if (params.step.prompt === undefined) {
594
787
  throw new Error('Agent step normalization requires a prompt');
@@ -638,12 +831,28 @@ function normalizeAgentStep(params) {
638
831
  allowedJobReferences: params.allowedJobReferences,
639
832
  typeOverlay: params.typeOverlay
640
833
  });
834
+ const thinkingTemplate = params.step.thinking === undefined ? undefined : parseInterpolationField({
835
+ field: 'agent.thinking',
836
+ source: params.step.thinking,
837
+ path: [
838
+ 'jobs',
839
+ params.sourceName,
840
+ 'steps',
841
+ params.stepIndex,
842
+ 'thinking'
843
+ ],
844
+ issues: params.issues,
845
+ fillSite: params.fillSite,
846
+ allowedJobReferences: params.allowedJobReferences,
847
+ typeOverlay: params.typeOverlay
848
+ });
641
849
  validateAgentStep({
642
850
  step: params.step,
643
851
  sourceName: params.sourceName,
644
852
  stepIndex: params.stepIndex,
645
853
  issues: params.issues,
646
- validateLiteralProvider: providerTemplate === undefined,
854
+ validateLiteralModel: params.step.model !== undefined && !hasInterpolationSyntax(params.step.model),
855
+ validateLiteralProvider: params.step.provider !== undefined && !hasInterpolationSyntax(params.step.provider),
647
856
  agentValidationCatalog: params.context.agentValidationCatalog
648
857
  });
649
858
  const integrations = normalizeAgentIntegrations({
@@ -657,7 +866,9 @@ function normalizeAgentStep(params) {
657
866
  prompt: promptTemplate,
658
867
  model: modelTemplate,
659
868
  provider: providerTemplate,
660
- name: params.name
869
+ thinking: thinkingTemplate,
870
+ name: params.name,
871
+ workingDirectory: params.workingDirectory
661
872
  });
662
873
  return {
663
874
  ...params.stepBase,
@@ -689,46 +900,88 @@ function normalizeAgentStep(params) {
689
900
  function validateAgentStep(params) {
690
901
  validateHarnessThinking(params);
691
902
  validateHarnessTools(params);
692
- if (!params.validateLiteralProvider) return;
693
903
  const providerId = params.step.provider;
694
- if (providerId === undefined) return;
695
- const provider = params.agentValidationCatalog.providers.find((entry)=>entry.id === providerId);
696
904
  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;
905
+ const provider = providerId === undefined ? undefined : params.agentValidationCatalog.providers.find((entry)=>entry.id === providerId);
906
+ if (params.validateLiteralProvider && providerId !== undefined) {
907
+ if (provider === undefined) {
908
+ if (harness === undefined || harness === 'pi') return;
909
+ params.issues.push(issue({
910
+ code: 'invalid-provider',
911
+ message: `Provider "${providerId}" is not supported.`,
912
+ path: [
913
+ 'jobs',
914
+ params.sourceName,
915
+ 'steps',
916
+ params.stepIndex,
917
+ 'provider'
918
+ ],
919
+ details: {
920
+ provider: providerId
921
+ }
922
+ }));
923
+ return;
924
+ }
925
+ if (provider.support_status !== 'supported') {
926
+ params.issues.push(issue({
927
+ code: 'invalid-provider',
928
+ message: `Provider "${providerId}" is not supported.`,
929
+ path: [
930
+ 'jobs',
931
+ params.sourceName,
932
+ 'steps',
933
+ params.stepIndex,
934
+ 'provider'
935
+ ],
936
+ details: {
937
+ provider: providerId
938
+ }
939
+ }));
940
+ return;
941
+ }
942
+ if (harness === undefined) return;
943
+ const descriptor = params.agentValidationCatalog.harnesses.find((entry)=>entry.id === harness);
944
+ if (!descriptor?.supported_provider_ids.includes(providerId)) {
945
+ params.issues.push(issue({
946
+ code: 'harness-provider-incompatible',
947
+ message: `Harness "${harness}" does not support provider: ${providerId}. Supported providers: ${descriptor?.supported_provider_ids.join(', ') ?? ''}.`,
948
+ path: [
949
+ 'jobs',
950
+ params.sourceName,
951
+ 'steps',
952
+ params.stepIndex,
953
+ 'provider'
954
+ ],
955
+ details: {
956
+ harness,
957
+ provider: providerId,
958
+ supportedProviders: descriptor?.supported_provider_ids ?? []
959
+ }
960
+ }));
961
+ return;
962
+ }
714
963
  }
715
- if (harness === undefined) return;
964
+ if (!params.validateLiteralModel || providerId === undefined || harness === undefined) return;
965
+ if (provider === undefined || provider.support_status !== 'supported') return;
966
+ const model = params.step.model;
967
+ if (model === undefined) return;
716
968
  const descriptor = params.agentValidationCatalog.harnesses.find((entry)=>entry.id === harness);
717
- if (descriptor?.supported_provider_ids.includes(providerId)) return;
969
+ const modelIds = descriptor?.model_ids_by_provider?.[providerId];
970
+ if (modelIds === undefined || modelIds.includes(model)) return;
718
971
  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(', ') ?? ''}.`,
972
+ code: 'invalid-model',
973
+ message: `Agent model "${model}" is not available for harness "${harness}" and provider "${providerId}".`,
721
974
  path: [
722
975
  'jobs',
723
976
  params.sourceName,
724
977
  'steps',
725
978
  params.stepIndex,
726
- 'provider'
979
+ 'model'
727
980
  ],
728
981
  details: {
729
982
  harness,
730
983
  provider: providerId,
731
- supportedProviders: descriptor?.supported_provider_ids ?? []
984
+ model
732
985
  }
733
986
  }));
734
987
  }
@@ -778,6 +1031,9 @@ function validateHarnessTools(params) {
778
1031
  function validateHarnessThinking(params) {
779
1032
  const { harness, thinking } = params.step;
780
1033
  if (harness === undefined || thinking === undefined) return;
1034
+ // An interpolated level is only known when the step dispatches, so the agent
1035
+ // module checks it against the resolved harness there.
1036
+ if (hasInterpolationSyntax(thinking)) return;
781
1037
  const supportedLevels = params.agentValidationCatalog.harnesses.find((entry)=>entry.id === harness)?.thinking_levels ?? [];
782
1038
  if (supportedLevels.includes(thinking)) return;
783
1039
  params.issues.push(issue({
@@ -895,7 +1151,7 @@ function validateRunnerLabels(params) {
895
1151
  }
896
1152
  }
897
1153
  function optionalRunStepTemplates(params) {
898
- if (params.command === undefined && params.name === undefined && params.env === undefined) {
1154
+ if (params.command === undefined && params.name === undefined && params.workingDirectory === undefined && params.env === undefined) {
899
1155
  return undefined;
900
1156
  }
901
1157
  return {
@@ -905,13 +1161,16 @@ function optionalRunStepTemplates(params) {
905
1161
  ...params.name === undefined ? {} : {
906
1162
  name: params.name
907
1163
  },
1164
+ ...params.workingDirectory === undefined ? {} : {
1165
+ workingDirectory: params.workingDirectory
1166
+ },
908
1167
  ...params.env === undefined ? {} : {
909
1168
  env: params.env
910
1169
  }
911
1170
  };
912
1171
  }
913
1172
  function optionalAgentStepTemplates(params) {
914
- if (params.prompt === undefined && params.model === undefined && params.provider === undefined && params.name === undefined) {
1173
+ if (params.prompt === undefined && params.model === undefined && params.provider === undefined && params.thinking === undefined && params.name === undefined && params.workingDirectory === undefined) {
915
1174
  return undefined;
916
1175
  }
917
1176
  return {
@@ -924,8 +1183,14 @@ function optionalAgentStepTemplates(params) {
924
1183
  ...params.provider === undefined ? {} : {
925
1184
  provider: params.provider
926
1185
  },
1186
+ ...params.thinking === undefined ? {} : {
1187
+ thinking: params.thinking
1188
+ },
927
1189
  ...params.name === undefined ? {} : {
928
1190
  name: params.name
1191
+ },
1192
+ ...params.workingDirectory === undefined ? {} : {
1193
+ workingDirectory: params.workingDirectory
929
1194
  }
930
1195
  };
931
1196
  }