@shipfox/api-workflows 7.1.0 → 9.0.1

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 (153) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +80 -0
  3. package/dist/core/agent-defaults.d.ts +2 -1
  4. package/dist/core/agent-defaults.d.ts.map +1 -1
  5. package/dist/core/agent-defaults.js.map +1 -1
  6. package/dist/core/agent-tools.d.ts +5 -2
  7. package/dist/core/agent-tools.d.ts.map +1 -1
  8. package/dist/core/agent-tools.js.map +1 -1
  9. package/dist/core/checkout.d.ts +2 -2
  10. package/dist/core/checkout.d.ts.map +1 -1
  11. package/dist/core/checkout.js.map +1 -1
  12. package/dist/core/entities/step.d.ts +2 -1
  13. package/dist/core/entities/step.d.ts.map +1 -1
  14. package/dist/core/entities/step.js.map +1 -1
  15. package/dist/core/errors.d.ts +0 -4
  16. package/dist/core/errors.d.ts.map +1 -1
  17. package/dist/core/errors.js +0 -6
  18. package/dist/core/errors.js.map +1 -1
  19. package/dist/core/job-execution.d.ts +0 -2
  20. package/dist/core/job-execution.d.ts.map +1 -1
  21. package/dist/core/job-execution.js +3 -7
  22. package/dist/core/job-execution.js.map +1 -1
  23. package/dist/core/run-workflow.d.ts +2 -2
  24. package/dist/core/run-workflow.d.ts.map +1 -1
  25. package/dist/core/run-workflow.js.map +1 -1
  26. package/dist/core/step-config/agent.d.ts +1 -1
  27. package/dist/core/step-config/agent.d.ts.map +1 -1
  28. package/dist/core/step-config/agent.js.map +1 -1
  29. package/dist/db/job-listeners.d.ts +2 -2
  30. package/dist/db/job-listeners.d.ts.map +1 -1
  31. package/dist/db/job-listeners.js.map +1 -1
  32. package/dist/db/workflow-runs/job-executions.d.ts +0 -5
  33. package/dist/db/workflow-runs/job-executions.d.ts.map +1 -1
  34. package/dist/db/workflow-runs/job-executions.js +0 -7
  35. package/dist/db/workflow-runs/job-executions.js.map +1 -1
  36. package/dist/db/workflow-runs/run-create.d.ts +2 -2
  37. package/dist/db/workflow-runs/run-create.d.ts.map +1 -1
  38. package/dist/db/workflow-runs/run-create.js.map +1 -1
  39. package/dist/db/workflow-runs.d.ts +1 -1
  40. package/dist/db/workflow-runs.d.ts.map +1 -1
  41. package/dist/db/workflow-runs.js +1 -1
  42. package/dist/db/workflow-runs.js.map +1 -1
  43. package/dist/index.d.ts +2 -2
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +2 -1
  46. package/dist/index.js.map +1 -1
  47. package/dist/presentation/inter-module.d.ts +2 -2
  48. package/dist/presentation/inter-module.d.ts.map +1 -1
  49. package/dist/presentation/inter-module.js +2 -1
  50. package/dist/presentation/inter-module.js.map +1 -1
  51. package/dist/presentation/routes/cancel-run.d.ts +1 -1
  52. package/dist/presentation/routes/cancel-run.d.ts.map +1 -1
  53. package/dist/presentation/routes/cancel-run.js.map +1 -1
  54. package/dist/presentation/routes/checkout-token.d.ts +2 -2
  55. package/dist/presentation/routes/checkout-token.d.ts.map +1 -1
  56. package/dist/presentation/routes/checkout-token.js +1 -1
  57. package/dist/presentation/routes/checkout-token.js.map +1 -1
  58. package/dist/presentation/routes/get-run-aggregates.d.ts +1 -1
  59. package/dist/presentation/routes/get-run-aggregates.d.ts.map +1 -1
  60. package/dist/presentation/routes/get-run-aggregates.js.map +1 -1
  61. package/dist/presentation/routes/get-run.d.ts +1 -1
  62. package/dist/presentation/routes/get-run.d.ts.map +1 -1
  63. package/dist/presentation/routes/get-run.js.map +1 -1
  64. package/dist/presentation/routes/index.d.ts +2 -2
  65. package/dist/presentation/routes/index.d.ts.map +1 -1
  66. package/dist/presentation/routes/index.js.map +1 -1
  67. package/dist/presentation/routes/list-run-attempts.d.ts +1 -1
  68. package/dist/presentation/routes/list-run-attempts.d.ts.map +1 -1
  69. package/dist/presentation/routes/list-run-attempts.js.map +1 -1
  70. package/dist/presentation/routes/list-runs.d.ts +1 -1
  71. package/dist/presentation/routes/list-runs.d.ts.map +1 -1
  72. package/dist/presentation/routes/list-runs.js.map +1 -1
  73. package/dist/presentation/routes/next-step.d.ts.map +1 -1
  74. package/dist/presentation/routes/next-step.js +11 -8
  75. package/dist/presentation/routes/next-step.js.map +1 -1
  76. package/dist/presentation/routes/project-access.d.ts +1 -1
  77. package/dist/presentation/routes/project-access.d.ts.map +1 -1
  78. package/dist/presentation/routes/project-access.js.map +1 -1
  79. package/dist/presentation/routes/require-accessible-run.d.ts +1 -1
  80. package/dist/presentation/routes/require-accessible-run.d.ts.map +1 -1
  81. package/dist/presentation/routes/require-accessible-run.js.map +1 -1
  82. package/dist/presentation/routes/rerun-run.d.ts +1 -1
  83. package/dist/presentation/routes/rerun-run.d.ts.map +1 -1
  84. package/dist/presentation/routes/rerun-run.js.map +1 -1
  85. package/dist/temporal/activities/index.d.ts +2 -2
  86. package/dist/temporal/activities/index.d.ts.map +1 -1
  87. package/dist/temporal/activities/index.js.map +1 -1
  88. package/dist/temporal/activities/orchestration-activities.d.ts +2 -2
  89. package/dist/temporal/activities/orchestration-activities.d.ts.map +1 -1
  90. package/dist/temporal/activities/orchestration-activities.js.map +1 -1
  91. package/dist/tsconfig.test.tsbuildinfo +1 -1
  92. package/package.json +26 -28
  93. package/src/core/agent-defaults.ts +2 -1
  94. package/src/core/agent-tool-capability-warning.test.ts +26 -56
  95. package/src/core/agent-tools.ts +9 -7
  96. package/src/core/checkout.test.ts +2 -2
  97. package/src/core/checkout.ts +2 -2
  98. package/src/core/entities/step.ts +1 -2
  99. package/src/core/errors.ts +0 -7
  100. package/src/core/job-execution.ts +3 -10
  101. package/src/core/run-workflow.test.ts +22 -55
  102. package/src/core/run-workflow.ts +2 -2
  103. package/src/core/step-config/agent.ts +1 -1
  104. package/src/core/step-config/materialize-job-execution-steps.test.ts +1 -2
  105. package/src/core/step-config/materialize-workflow-model.test.ts +10 -13
  106. package/src/db/job-listeners.ts +2 -2
  107. package/src/db/workflow-runs/job-executions.ts +0 -21
  108. package/src/db/workflow-runs/run-create.test.ts +30 -23
  109. package/src/db/workflow-runs/run-create.ts +2 -2
  110. package/src/db/workflow-runs.ts +0 -1
  111. package/src/index.ts +3 -3
  112. package/src/presentation/dto/checkout-token.test.ts +7 -1
  113. package/src/presentation/inter-module.ts +4 -7
  114. package/src/presentation/routes/agent-runtime-config.test.ts +40 -132
  115. package/src/presentation/routes/cancel-run.test.ts +1 -1
  116. package/src/presentation/routes/cancel-run.ts +1 -1
  117. package/src/presentation/routes/checkout-token.test.ts +4 -37
  118. package/src/presentation/routes/checkout-token.ts +2 -2
  119. package/src/presentation/routes/get-run-aggregates.test.ts +1 -1
  120. package/src/presentation/routes/get-run-aggregates.ts +1 -1
  121. package/src/presentation/routes/get-run.test.ts +1 -1
  122. package/src/presentation/routes/get-run.ts +1 -1
  123. package/src/presentation/routes/get-step-secrets.test.ts +2 -2
  124. package/src/presentation/routes/index.test.ts +3 -3
  125. package/src/presentation/routes/index.ts +2 -2
  126. package/src/presentation/routes/list-run-attempts.test.ts +1 -1
  127. package/src/presentation/routes/list-run-attempts.ts +1 -1
  128. package/src/presentation/routes/list-runs.test.ts +1 -1
  129. package/src/presentation/routes/list-runs.ts +1 -1
  130. package/src/presentation/routes/next-step.test.ts +27 -69
  131. package/src/presentation/routes/next-step.ts +9 -5
  132. package/src/presentation/routes/project-access.ts +1 -1
  133. package/src/presentation/routes/report-step.test.ts +2 -3
  134. package/src/presentation/routes/require-accessible-run.ts +1 -1
  135. package/src/presentation/routes/rerun-run.test.ts +1 -1
  136. package/src/presentation/routes/rerun-run.ts +1 -1
  137. package/src/temporal/activities/index.ts +2 -2
  138. package/src/temporal/activities/orchestration-activities.ts +2 -2
  139. package/test/factories/workflow-model.ts +3 -1
  140. package/test/fixtures/active-lease-token.ts +5 -32
  141. package/test/fixtures/agent-inter-module.ts +32 -1
  142. package/test/fixtures/auth-inter-module.ts +2 -13
  143. package/test/fixtures/lease-token.ts +43 -29
  144. package/test/fixtures/projects-inter-module.ts +1 -1
  145. package/test/fixtures/runners-inter-module.ts +40 -48
  146. package/test/globalSetup.ts +0 -38
  147. package/test/setup.ts +2 -0
  148. package/tsconfig.build.tsbuildinfo +1 -1
  149. package/dist/db/runner-lease-table.d.ts +0 -469
  150. package/dist/db/runner-lease-table.d.ts.map +0 -1
  151. package/dist/db/runner-lease-table.js +0 -43
  152. package/dist/db/runner-lease-table.js.map +0 -1
  153. package/src/db/runner-lease-table.ts +0 -39
@@ -17,7 +17,6 @@ import {
17
17
  } from '#metrics/instance.js';
18
18
  import {db, type Tx} from '../db.js';
19
19
  import {writeWorkflowsOutboxEvent} from '../outbox-writes.js';
20
- import {runningJobExecutions} from '../runner-lease-table.js';
21
20
  import {jobExecutions, toJobExecution} from '../schema/job-executions.js';
22
21
  import {jobs, toJob} from '../schema/jobs.js';
23
22
  import {stepAttempts, toStepAttempt} from '../schema/step-attempts.js';
@@ -33,26 +32,6 @@ import {
33
32
  } from './shared.js';
34
33
  import {bulkUpdateStepStatuses, getStepsByJobExecutionIdForUpdate} from './steps.js';
35
34
 
36
- export async function lockActiveJobExecutionLeaseForUpdate(
37
- params: {jobId: string; jobExecutionId: string; runnerSessionId: string},
38
- tx: Tx,
39
- ): Promise<boolean> {
40
- const rows = await tx
41
- .select({id: runningJobExecutions.id})
42
- .from(runningJobExecutions)
43
- .where(
44
- and(
45
- eq(runningJobExecutions.jobId, params.jobId),
46
- eq(runningJobExecutions.jobExecutionId, params.jobExecutionId),
47
- eq(runningJobExecutions.runnerSessionId, params.runnerSessionId),
48
- ),
49
- )
50
- .limit(1)
51
- .for('update');
52
-
53
- return rows.length > 0;
54
- }
55
-
56
35
  export async function getJobExecutionById(id: string, tx?: Tx): Promise<JobExecution | undefined> {
57
36
  const rows = await (tx ?? db())
58
37
  .select()
@@ -1,4 +1,3 @@
1
- import {normalizeWorkflowDocument} from '@shipfox/api-definitions';
2
1
  import {WORKFLOWS_WORKFLOW_RUN_ATTEMPT_CREATED} from '@shipfox/api-workflows-dto';
3
2
  import {and, eq, sql} from 'drizzle-orm';
4
3
  import type {AgentDefaultsResolver} from '#core/agent-defaults.js';
@@ -7,6 +6,7 @@ import {nextStepForJob, recordStepResult} from '#core/job-execution.js';
7
6
  import {resolveTestAgentDefaults} from '#test/fixtures/agent-inter-module.js';
8
7
  import {createTestSecretsClient} from '#test/fixtures/secrets-inter-module.js';
9
8
  import {buildModel, expression, shellRef, template} from '#test/helpers/workflow-runs.js';
9
+ import {workflowModel} from '#test/index.js';
10
10
  import {db} from '../db.js';
11
11
  import {workflowsOutbox} from '../schema/outbox.js';
12
12
  import {workflowRuns} from '../schema/workflow-runs.js';
@@ -330,7 +330,7 @@ describe('workflow run queries', () => {
330
330
  const displayNameSource = ['Review batch $', '{{ execution.index }}'].join('');
331
331
  const stepNameSource = ['Review $', '{{ execution.index }}'].join('');
332
332
  const promptSource = ['Review $', '{{ execution.events[0].data.body }}'].join('');
333
- const model = normalizeWorkflowDocument({
333
+ const model = workflowModel({
334
334
  name: 'Listening workflow',
335
335
  runner: 'ubuntu-latest',
336
336
  jobs: {
@@ -339,10 +339,10 @@ describe('workflow run queries', () => {
339
339
  listening: {
340
340
  on: [{source: 'github', event: 'pull_request_review'}],
341
341
  until: [{source: 'github', event: 'pull_request'}],
342
- timeout: '30d',
343
- max_executions: 3,
344
- batch: {debounce: '5s', max_size: 10, max_wait: '1h'},
345
- on_resolve: 'cancel',
342
+ timeoutMs: 30 * 24 * 60 * 60 * 1000,
343
+ maxExecutions: 3,
344
+ batch: {debounceMs: 5000, maxSize: 10, maxWaitMs: 60 * 60 * 1000},
345
+ onResolve: 'cancel',
346
346
  },
347
347
  steps: [{name: stepNameSource, prompt: promptSource}],
348
348
  },
@@ -400,7 +400,7 @@ describe('workflow run queries', () => {
400
400
  test('persists the listening workflow model on the run attempt', async () => {
401
401
  const displayNameSource = ['Review batch $', '{{ execution.index }}'].join('');
402
402
  const promptSource = ['Review $', '{{ execution.events[0].data.body }}'].join('');
403
- const model = normalizeWorkflowDocument({
403
+ const model = workflowModel({
404
404
  name: 'Listening workflow',
405
405
  runner: 'ubuntu-latest',
406
406
  jobs: {
@@ -409,10 +409,10 @@ describe('workflow run queries', () => {
409
409
  listening: {
410
410
  on: [{source: 'github', event: 'pull_request_review'}],
411
411
  until: [{source: 'github', event: 'pull_request'}],
412
- timeout: '30d',
413
- max_executions: 3,
414
- batch: {debounce: '5s', max_size: 10, max_wait: '1h'},
415
- on_resolve: 'cancel',
412
+ timeoutMs: 30 * 24 * 60 * 60 * 1000,
413
+ maxExecutions: 3,
414
+ batch: {debounceMs: 5000, maxSize: 10, maxWaitMs: 60 * 60 * 1000},
415
+ onResolve: 'cancel',
416
416
  },
417
417
  steps: [{prompt: promptSource}],
418
418
  },
@@ -441,7 +441,7 @@ describe('workflow run queries', () => {
441
441
  });
442
442
 
443
443
  test('does not load variables referenced only by listening job executions at run creation', async () => {
444
- const model = normalizeWorkflowDocument({
444
+ const model = workflowModel({
445
445
  name: 'Listening vars workflow',
446
446
  runner: 'ubuntu-latest',
447
447
  jobs: {
@@ -449,6 +449,7 @@ describe('workflow run queries', () => {
449
449
  listening: {
450
450
  on: [{source: 'github', event: 'pull_request_review'}],
451
451
  until: [{source: 'github', event: 'pull_request'}],
452
+ onResolve: 'finish',
452
453
  },
453
454
  steps: [
454
455
  {
@@ -483,7 +484,7 @@ describe('workflow run queries', () => {
483
484
  {
484
485
  field: 'run',
485
486
  model: () =>
486
- normalizeWorkflowDocument({
487
+ workflowModel({
487
488
  name: 'Missing run var',
488
489
  runner: 'ubuntu-latest',
489
490
  jobs: {build: {steps: [{run: `echo ${template('vars.REQUIRED')}`}]}},
@@ -493,7 +494,7 @@ describe('workflow run queries', () => {
493
494
  {
494
495
  field: 'env',
495
496
  model: () =>
496
- normalizeWorkflowDocument({
497
+ workflowModel({
497
498
  name: 'Missing env var',
498
499
  runner: 'ubuntu-latest',
499
500
  jobs: {
@@ -507,7 +508,7 @@ describe('workflow run queries', () => {
507
508
  {
508
509
  field: 'agent.prompt',
509
510
  model: () =>
510
- normalizeWorkflowDocument({
511
+ workflowModel({
511
512
  name: 'Missing prompt var',
512
513
  runner: 'ubuntu-latest',
513
514
  jobs: {fix: {steps: [{prompt: template('vars.REQUIRED')}]}},
@@ -517,7 +518,7 @@ describe('workflow run queries', () => {
517
518
  {
518
519
  field: 'agent.model',
519
520
  model: () =>
520
- normalizeWorkflowDocument({
521
+ workflowModel({
521
522
  name: 'Missing model var',
522
523
  runner: 'ubuntu-latest',
523
524
  jobs: {fix: {steps: [{prompt: 'Fix it', model: template('vars.REQUIRED')}]}},
@@ -527,7 +528,7 @@ describe('workflow run queries', () => {
527
528
  {
528
529
  field: 'agent.provider',
529
530
  model: () =>
530
- normalizeWorkflowDocument({
531
+ workflowModel({
531
532
  name: 'Missing provider var',
532
533
  runner: 'ubuntu-latest',
533
534
  jobs: {fix: {steps: [{prompt: 'Fix it', provider: template('vars.REQUIRED')}]}},
@@ -537,17 +538,23 @@ describe('workflow run queries', () => {
537
538
  {
538
539
  field: 'job.runner',
539
540
  model: () =>
540
- normalizeWorkflowDocument({
541
+ workflowModel({
541
542
  name: 'Missing runner var',
542
543
  runner: 'ubuntu-latest',
543
- jobs: {build: {runner: template('vars.REQUIRED'), steps: [{run: 'echo ok'}]}},
544
+ jobs: {
545
+ build: {
546
+ runner: [],
547
+ runnerTemplates: [template('vars.REQUIRED')],
548
+ steps: [{run: 'echo ok'}],
549
+ },
550
+ },
544
551
  }),
545
552
  expected: {field: 'job.runner', source: 'vars.REQUIRED'},
546
553
  },
547
554
  {
548
555
  field: 'step.name',
549
556
  model: () =>
550
- normalizeWorkflowDocument({
557
+ workflowModel({
551
558
  name: 'Missing step name var',
552
559
  runner: 'ubuntu-latest',
553
560
  jobs: {build: {steps: [{name: template('vars.REQUIRED'), run: 'echo ok'}]}},
@@ -614,7 +621,7 @@ describe('workflow run queries', () => {
614
621
  });
615
622
 
616
623
  test('persists resolved step config and authored step config separately', async () => {
617
- const model = normalizeWorkflowDocument({
624
+ const model = workflowModel({
618
625
  name: 'Interpolated workflow',
619
626
  runner: 'ubuntu-latest',
620
627
  env: {RUN_ID: template('run.id'), REF: template('event.ref')},
@@ -665,7 +672,7 @@ describe('workflow run queries', () => {
665
672
  });
666
673
 
667
674
  test('resolves webhook trigger payload body and headers into step config', async () => {
668
- const model = normalizeWorkflowDocument({
675
+ const model = workflowModel({
669
676
  name: 'Webhook workflow',
670
677
  runner: 'ubuntu-latest',
671
678
  env: {
@@ -711,7 +718,7 @@ describe('workflow run queries', () => {
711
718
  });
712
719
 
713
720
  test('fails for missing available untrusted interpolation paths', async () => {
714
- const model = normalizeWorkflowDocument({
721
+ const model = workflowModel({
715
722
  name: 'Diagnostic workflow',
716
723
  runner: 'ubuntu-latest',
717
724
  env: {REF: template('event.ref')},
@@ -1,6 +1,6 @@
1
1
  import {createWorkflowModelSnapshot, type WorkflowModel} from '@shipfox/api-definitions-dto';
2
- import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto';
3
- import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
2
+ import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto/inter-module';
3
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
4
4
  import type {SecretsInterModuleClient} from '@shipfox/api-secrets-dto/inter-module';
5
5
  import {analyzeContextKeyAccess, type ResolvedFieldSegment} from '@shipfox/expression';
6
6
  import {logger} from '@shipfox/node-opentelemetry';
@@ -9,7 +9,6 @@ export {
9
9
  getJobExecutionsByJobId,
10
10
  getJobExecutionsByWorkflowRunAttemptId,
11
11
  getLatestJobExecutionByJobId,
12
- lockActiveJobExecutionLeaseForUpdate,
13
12
  recordJobExecutionQueuedAt,
14
13
  recordJobExecutionStartedAt,
15
14
  resolveJobExecutionAfterLeaseExpiry,
package/src/index.ts CHANGED
@@ -4,8 +4,8 @@ import type {AnnotationsInterModuleClient} from '@shipfox/annotations-dto/inter-
4
4
  import type {AgentInterModuleClient} from '@shipfox/api-agent-dto/inter-module';
5
5
  import type {AuthInterModuleClient} from '@shipfox/api-auth-dto/inter-module';
6
6
  import type {DefinitionsInterModuleClient} from '@shipfox/api-definitions-dto/inter-module';
7
- import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto';
8
- import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
7
+ import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto/inter-module';
8
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
9
9
  import {
10
10
  RUNNER_JOB_CLAIMED,
11
11
  RUNNER_JOB_LEASE_EXPIRED,
@@ -95,7 +95,7 @@ export function createWorkflowsModule({
95
95
  }): ShipfoxModule {
96
96
  return {
97
97
  name: 'workflows',
98
- database: {db, migrationsPath},
98
+ database: {db, migrationsPath, databaseNamespace: 'workflows'},
99
99
  routes: createWorkflowRoutes({
100
100
  agent,
101
101
  annotations,
@@ -1,6 +1,12 @@
1
- import type {CheckoutSpec} from '@shipfox/api-integration-core';
2
1
  import {toCheckoutTokenDto} from './checkout-token.js';
3
2
 
3
+ type CheckoutSpec = {
4
+ repositoryUrl: string;
5
+ ref: string;
6
+ credentials?: {username: string; token: string; expiresAt: Date};
7
+ gitAuthor?: {name: string; email: string};
8
+ };
9
+
4
10
  describe('toCheckoutTokenDto', () => {
5
11
  it('maps credentials to basic auth with an ISO expiry', () => {
6
12
  const spec: CheckoutSpec = {
@@ -1,12 +1,8 @@
1
- import {
2
- DEFAULT_HARNESS,
3
- harnessSchema,
4
- materializedAgentStepConfigSchema,
5
- } from '@shipfox/api-agent-dto';
1
+ import {materializedAgentStepConfigSchema} from '@shipfox/api-agent-dto';
6
2
  import type {AgentInterModuleClient} from '@shipfox/api-agent-dto/inter-module';
7
3
  import type {DefinitionsInterModuleClient} from '@shipfox/api-definitions-dto/inter-module';
8
- import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto';
9
- import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
4
+ import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto/inter-module';
5
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
10
6
  import type {RunnersInterModuleClient} from '@shipfox/api-runners-dto/inter-module';
11
7
  import type {SecretsInterModuleClient} from '@shipfox/api-secrets-dto/inter-module';
12
8
  import {workflowsInterModuleContract} from '@shipfox/api-workflows-dto/inter-module';
@@ -15,6 +11,7 @@ import {
15
11
  defineInterModulePresentation,
16
12
  type InterModulePresentation,
17
13
  } from '@shipfox/inter-module';
14
+ import {DEFAULT_HARNESS, harnessSchema} from '@shipfox/workflow-document';
18
15
  import {InvalidJobRunnerLabelsError} from '#core/errors.js';
19
16
  import {
20
17
  AgentConfigUnresolvableError,
@@ -1,20 +1,11 @@
1
1
  import {
2
- createAgentModule,
3
- createCustomModelProviderConfig,
4
- testAndSaveModelProviderConfig,
5
- } from '@shipfox/api-agent';
6
- import {resolveRuntimeCredentials} from '@shipfox/api-agent/core/resolve-runtime-credentials';
7
- import {agentInterModuleContract} from '@shipfox/api-agent-dto/inter-module';
8
- import {createLeaseTokenAuthMethod} from '@shipfox/api-auth';
2
+ type AgentInterModuleClient,
3
+ agentInterModuleContract,
4
+ } from '@shipfox/api-agent-dto/inter-module';
9
5
  import type {WorkflowModel} from '@shipfox/api-definitions-dto';
10
- import {secretsInterModuleContract} from '@shipfox/api-secrets-dto/inter-module';
11
6
  import {createInterModuleKnownError} from '@shipfox/inter-module';
12
7
  import {closeApp, createApp, type FastifyInstance} from '@shipfox/node-fastify';
13
8
  import {createCapturingLogger} from '@shipfox/node-log/test';
14
- import {
15
- createInMemoryInterModuleTransport,
16
- registerInterModulePresentations,
17
- } from '@shipfox/node-module/inter-module';
18
9
  import {eq} from 'drizzle-orm';
19
10
  import type {StepStatus} from '#core/entities/step.js';
20
11
  import {db} from '#db/db.js';
@@ -26,29 +17,39 @@ import {insertRunningJobLease, mintActiveLeaseToken} from '#test/fixtures/active
26
17
  import {resolveTestAgentDefaults} from '#test/fixtures/agent-inter-module.js';
27
18
  import {annotationsTestClient} from '#test/fixtures/annotations-inter-module.js';
28
19
  import {workflowsTestAuthClient} from '#test/fixtures/auth-inter-module.js';
29
- import {mintLeaseToken} from '#test/fixtures/lease-token.js';
20
+ import {fakeLeaseTokenAuthMethod, mintLeaseToken} from '#test/fixtures/lease-token.js';
30
21
  import {projectsTestClient} from '#test/fixtures/projects-inter-module.js';
31
22
  import {runnersTestClient} from '#test/fixtures/runners-inter-module.js';
32
23
  import {createTestSecretsClient} from '#test/fixtures/secrets-inter-module.js';
33
24
  import {createLeaseTokenRouteGroup} from './index.js';
34
25
 
35
26
  const {captureExceptionMock} = vi.hoisted(() => ({captureExceptionMock: vi.fn()}));
36
- vi.mock('@shipfox/api-agent/core/resolve-runtime-credentials', async (importOriginal) => {
37
- const actual =
38
- await importOriginal<typeof import('@shipfox/api-agent/core/resolve-runtime-credentials')>();
39
- return {...actual, resolveRuntimeCredentials: vi.fn(actual.resolveRuntimeCredentials)};
40
- });
41
27
  vi.mock('@shipfox/node-error-monitoring', () => ({captureException: captureExceptionMock}));
42
28
 
43
29
  const URL = '/runs/jobs/current/agent-runtime-config';
44
30
  const secrets = createTestSecretsClient();
45
- const agentTransport = createInMemoryInterModuleTransport();
46
- const agentTestClient = agentTransport.createClient(agentInterModuleContract);
47
- registerInterModulePresentations({
48
- transport: agentTransport,
49
- modules: [createAgentModule({secrets})],
50
- });
51
- agentTransport.seal();
31
+ const runtimeConfigs = new Map<
32
+ string,
33
+ Awaited<ReturnType<AgentInterModuleClient['resolveRuntimeCredentials']>>
34
+ >();
35
+ const resolveRuntimeCredentials = vi.fn<AgentInterModuleClient['resolveRuntimeCredentials']>(
36
+ ({workspaceId}) => {
37
+ const runtimeConfig = runtimeConfigs.get(workspaceId);
38
+ if (runtimeConfig) return Promise.resolve(runtimeConfig);
39
+ return Promise.reject(
40
+ createInterModuleKnownError(
41
+ agentInterModuleContract.methods.resolveRuntimeCredentials,
42
+ 'model-provider-not-configured',
43
+ {},
44
+ ),
45
+ );
46
+ },
47
+ );
48
+ const agentTestClient: AgentInterModuleClient = {
49
+ getValidationCatalog: vi.fn(),
50
+ resolveAgentConfig: vi.fn(),
51
+ resolveRuntimeCredentials,
52
+ };
52
53
 
53
54
  describe('GET /runs/jobs/current/agent-runtime-config', () => {
54
55
  let app: FastifyInstance;
@@ -56,7 +57,7 @@ describe('GET /runs/jobs/current/agent-runtime-config', () => {
56
57
 
57
58
  beforeAll(async () => {
58
59
  app = await createApp({
59
- auth: [createLeaseTokenAuthMethod()],
60
+ auth: [fakeLeaseTokenAuthMethod],
60
61
  routes: [
61
62
  createLeaseTokenRouteGroup({
62
63
  agent: agentTestClient,
@@ -77,7 +78,8 @@ describe('GET /runs/jobs/current/agent-runtime-config', () => {
77
78
  beforeEach(() => {
78
79
  clearLogLines();
79
80
  captureExceptionMock.mockReset();
80
- vi.mocked(resolveRuntimeCredentials).mockClear();
81
+ runtimeConfigs.clear();
82
+ resolveRuntimeCredentials.mockClear();
81
83
  });
82
84
 
83
85
  afterAll(async () => {
@@ -91,38 +93,6 @@ describe('GET /runs/jobs/current/agent-runtime-config', () => {
91
93
  expect(res.statusCode).toBe(401);
92
94
  expect(res.json().code).toBe('unauthorized');
93
95
  });
94
-
95
- test('rejects an expired token', async () => {
96
- const token = await mintLeaseToken({
97
- jobId: crypto.randomUUID(),
98
- jobExecutionId: crypto.randomUUID(),
99
- expiresIn: '-1s',
100
- });
101
-
102
- const res = await app.inject({
103
- method: 'GET',
104
- url: runtimeConfigUrl(crypto.randomUUID(), 1),
105
- headers: {authorization: `Bearer ${token}`},
106
- });
107
-
108
- expect(res.statusCode).toBe(401);
109
- });
110
-
111
- test('rejects a token with the wrong audience', async () => {
112
- const token = await mintLeaseToken({
113
- jobId: crypto.randomUUID(),
114
- jobExecutionId: crypto.randomUUID(),
115
- audience: 'user-session',
116
- });
117
-
118
- const res = await app.inject({
119
- method: 'GET',
120
- url: runtimeConfigUrl(crypto.randomUUID(), 1),
121
- headers: {authorization: `Bearer ${token}`},
122
- });
123
-
124
- expect(res.statusCode).toBe(401);
125
- });
126
96
  });
127
97
 
128
98
  test('returns decrypted runtime credentials for a running leased agent step', async () => {
@@ -193,67 +163,6 @@ describe('GET /runs/jobs/current/agent-runtime-config', () => {
193
163
  expect(res.json().credentials.api_key).toBe('sk-gated-workspace-secret');
194
164
  });
195
165
 
196
- test('returns custom provider descriptors for a running leased custom agent step', async () => {
197
- const workspaceId = crypto.randomUUID();
198
- await createCustomModelProviderConfig(
199
- {
200
- workspaceId,
201
- body: {
202
- slug: 'local-vllm',
203
- display_name: 'Local vLLM',
204
- api: 'openai-responses',
205
- base_url: 'http://127.0.0.1:11434/v1',
206
- api_key: 'sk-local-secret',
207
- headers: [{name: 'authorization', value: 'Bearer local', secret: true}],
208
- models: [{id: 'llama-3.1', label: 'Llama 3.1'}],
209
- },
210
- },
211
- {probe: async () => undefined, secrets},
212
- );
213
- const {job, step} = await createRunningAgentStep({
214
- workspaceId,
215
- resolveAgentDefaults: () => ({
216
- harness: 'pi',
217
- provider: 'local-vllm',
218
- model: 'llama-3.1',
219
- thinking: 'high',
220
- }),
221
- steps: [
222
- {
223
- provider: 'local-vllm',
224
- model: 'llama-3.1',
225
- thinking: 'high',
226
- prompt: 'Fix the tests.',
227
- },
228
- ],
229
- });
230
- const token = await mintActiveLeaseToken({jobId: job.id});
231
-
232
- const res = await app.inject({
233
- method: 'GET',
234
- url: runtimeConfigUrl(step.id, step.currentAttempt),
235
- headers: {authorization: `Bearer ${token}`},
236
- });
237
-
238
- expect(res.statusCode).toBe(200);
239
- expect(res.json()).toMatchObject({
240
- harness: 'pi',
241
- provider_id: 'local-vllm',
242
- model: 'llama-3.1',
243
- credentials: {
244
- api_key: 'sk-local-secret',
245
- 'header:authorization': 'Bearer local',
246
- },
247
- custom_provider: {
248
- api: 'openai-responses',
249
- base_url: 'http://127.0.0.1:11434/v1',
250
- secret_header_names: ['authorization'],
251
- models: [{id: 'llama-3.1', label: 'Llama 3.1'}],
252
- requires_api_key: true,
253
- },
254
- });
255
- });
256
-
257
166
  test('returns 404 when the lease token is no longer the active job lease', async () => {
258
167
  const {run, job, step} = await createRunningAgentStep();
259
168
  await insertRunningJobLease({
@@ -381,10 +290,10 @@ describe('GET /runs/jobs/current/agent-runtime-config', () => {
381
290
 
382
291
  test('returns 409 and reports when workspace credentials cannot be decrypted', async () => {
383
292
  const {job, step} = await createRunningAgentStep();
384
- vi.mocked(resolveRuntimeCredentials).mockRejectedValueOnce(
293
+ resolveRuntimeCredentials.mockRejectedValueOnce(
385
294
  createInterModuleKnownError(
386
- secretsInterModuleContract.methods.getSecretsByNamespace,
387
- 'secret-decryption-failed',
295
+ agentInterModuleContract.methods.resolveRuntimeCredentials,
296
+ 'model-provider-credentials-invalid',
388
297
  {},
389
298
  ),
390
299
  );
@@ -430,15 +339,14 @@ function runtimeConfigUrl(stepId: string, attempt: number): string {
430
339
  return `${URL}?${search.toString()}`;
431
340
  }
432
341
 
433
- async function saveWorkspaceCredential(workspaceId: string, apiKey: string) {
434
- return await testAndSaveModelProviderConfig(
435
- {
436
- workspaceId,
437
- providerId: 'anthropic',
438
- credentials: {api_key: apiKey},
439
- },
440
- {probe: async () => undefined, secrets},
441
- );
342
+ function saveWorkspaceCredential(workspaceId: string, apiKey: string): void {
343
+ runtimeConfigs.set(workspaceId, {
344
+ harness: 'pi',
345
+ provider_id: 'anthropic',
346
+ model: 'claude-opus-4-8',
347
+ thinking: 'xhigh',
348
+ credentials: {api_key: apiKey},
349
+ });
442
350
  }
443
351
 
444
352
  type TestStepInput =
@@ -1,5 +1,5 @@
1
1
  import {buildUserContext, setUserContext} from '@shipfox/api-auth-context';
2
- import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
2
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
3
3
  import {ClientError} from '@shipfox/node-fastify';
4
4
  import type {FastifyInstance} from 'fastify';
5
5
  import Fastify from 'fastify';
@@ -1,4 +1,4 @@
1
- import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
1
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
2
2
  import {workflowRunDtoSchema} from '@shipfox/api-workflows-dto';
3
3
  import {ClientError, defineRoute} from '@shipfox/node-fastify';
4
4
  import {z} from 'zod';
@@ -1,13 +1,12 @@
1
- import {createLeaseTokenAuthMethod} from '@shipfox/api-auth';
2
- import {integrationsInterModuleContract} from '@shipfox/api-integration-core-dto';
3
- import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
1
+ import {integrationsInterModuleContract} from '@shipfox/api-integration-core-dto/inter-module';
2
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
4
3
  import {createInterModuleKnownError} from '@shipfox/inter-module';
5
4
  import {closeApp, createApp, type FastifyInstance} from '@shipfox/node-fastify';
6
5
  import {createCapturingLogger} from '@shipfox/node-log/test';
7
6
  import {jobFactory} from '#test/factories/job.js';
8
7
  import {projectFactory} from '#test/factories/project.js';
9
8
  import {mintActiveLeaseToken} from '#test/fixtures/active-lease-token.js';
10
- import {mintLeaseToken} from '#test/fixtures/lease-token.js';
9
+ import {fakeLeaseTokenAuthMethod, mintLeaseToken} from '#test/fixtures/lease-token.js';
11
10
  import {runnersTestClient} from '#test/fixtures/runners-inter-module.js';
12
11
  import {createLeaseTokenRouteGroup} from './index.js';
13
12
 
@@ -29,7 +28,7 @@ describe('POST /runs/jobs/current/checkout-token', () => {
29
28
 
30
29
  beforeAll(async () => {
31
30
  app = await createApp({
32
- auth: [createLeaseTokenAuthMethod()],
31
+ auth: [fakeLeaseTokenAuthMethod],
33
32
  routes: [
34
33
  createLeaseTokenRouteGroup({
35
34
  agent: {} as never,
@@ -64,38 +63,6 @@ describe('POST /runs/jobs/current/checkout-token', () => {
64
63
  expect(res.statusCode).toBe(401);
65
64
  expect(res.json().code).toBe('unauthorized');
66
65
  });
67
-
68
- test('rejects an expired token', async () => {
69
- const token = await mintLeaseToken({
70
- jobId: crypto.randomUUID(),
71
- jobExecutionId: crypto.randomUUID(),
72
- expiresIn: '-1s',
73
- });
74
-
75
- const res = await app.inject({
76
- method: 'POST',
77
- url: URL,
78
- headers: {authorization: `Bearer ${token}`},
79
- });
80
-
81
- expect(res.statusCode).toBe(401);
82
- });
83
-
84
- test('rejects a token with the wrong audience', async () => {
85
- const token = await mintLeaseToken({
86
- jobId: crypto.randomUUID(),
87
- jobExecutionId: crypto.randomUUID(),
88
- audience: 'user-session',
89
- });
90
-
91
- const res = await app.inject({
92
- method: 'POST',
93
- url: URL,
94
- headers: {authorization: `Bearer ${token}`},
95
- });
96
-
97
- expect(res.statusCode).toBe(401);
98
- });
99
66
  });
100
67
 
101
68
  test('returns basic auth for a GitHub-style spec with credentials', async () => {
@@ -2,8 +2,8 @@ import {requireLeasedJobContext} from '@shipfox/api-auth-context';
2
2
  import {
3
3
  type IntegrationsModuleClient,
4
4
  integrationsInterModuleContract,
5
- } from '@shipfox/api-integration-core-dto';
6
- import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
5
+ } from '@shipfox/api-integration-core-dto/inter-module';
6
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
7
7
  import type {RunnersInterModuleClient} from '@shipfox/api-runners-dto/inter-module';
8
8
  import {checkoutTokenResponseSchema} from '@shipfox/api-workflows-dto';
9
9
  import {isInterModuleKnownError} from '@shipfox/inter-module';
@@ -1,5 +1,5 @@
1
1
  import {buildUserContext, setUserContext} from '@shipfox/api-auth-context';
2
- import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
2
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
3
3
  import type {FastifyInstance} from 'fastify';
4
4
  import Fastify from 'fastify';
5
5
  import {serializerCompiler, validatorCompiler} from 'fastify-type-provider-zod';
@@ -1,4 +1,4 @@
1
- import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
1
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
2
2
  import {
3
3
  workflowRunAggregatesQuerySchema,
4
4
  workflowRunAggregatesResponseSchema,
@@ -1,5 +1,5 @@
1
1
  import {buildUserContext, setUserContext} from '@shipfox/api-auth-context';
2
- import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
2
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
3
3
  import {ClientError} from '@shipfox/node-fastify';
4
4
  import {eq} from 'drizzle-orm';
5
5
  import type {FastifyInstance} from 'fastify';
@@ -1,4 +1,4 @@
1
- import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
1
+ import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
2
2
  import {workflowRunDetailResponseSchema} from '@shipfox/api-workflows-dto';
3
3
  import {ClientError, defineRoute} from '@shipfox/node-fastify';
4
4
  import {z} from 'zod';