@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
@@ -1,4 +1,3 @@
1
- import {createLeaseTokenAuthMethod} from '@shipfox/api-auth';
2
1
  import {closeApp, createApp, type FastifyInstance} from '@shipfox/node-fastify';
3
2
  import {createCapturingLogger} from '@shipfox/node-log/test';
4
3
  import {eq} from 'drizzle-orm';
@@ -17,6 +16,7 @@ import {mintActiveLeaseToken} from '#test/fixtures/active-lease-token.js';
17
16
  import {agentTestClient} from '#test/fixtures/agent-inter-module.js';
18
17
  import {annotationsTestClient} from '#test/fixtures/annotations-inter-module.js';
19
18
  import {workflowsTestAuthClient} from '#test/fixtures/auth-inter-module.js';
19
+ import {fakeLeaseTokenAuthMethod} from '#test/fixtures/lease-token.js';
20
20
  import {projectsTestClient} from '#test/fixtures/projects-inter-module.js';
21
21
  import {runnersTestClient} from '#test/fixtures/runners-inter-module.js';
22
22
  import {createTestSecretsClient} from '#test/fixtures/secrets-inter-module.js';
@@ -31,7 +31,7 @@ describe('GET /runs/jobs/current/steps/:stepId/secrets', () => {
31
31
 
32
32
  beforeAll(async () => {
33
33
  app = await createApp({
34
- auth: [createLeaseTokenAuthMethod()],
34
+ auth: [fakeLeaseTokenAuthMethod],
35
35
  routes: [
36
36
  createLeaseTokenRouteGroup({
37
37
  agent: agentTestClient,
@@ -1,4 +1,3 @@
1
- import {createLeaseTokenAuthMethod} from '@shipfox/api-auth';
2
1
  import {
3
2
  AUTH_LEASED_JOB,
4
3
  AUTH_USER,
@@ -10,6 +9,7 @@ import type {FastifyRequest} from 'fastify';
10
9
  import {agentTestClient} from '#test/fixtures/agent-inter-module.js';
11
10
  import {annotationsTestClient} from '#test/fixtures/annotations-inter-module.js';
12
11
  import {workflowsTestAuthClient} from '#test/fixtures/auth-inter-module.js';
12
+ import {fakeLeaseTokenAuthMethod} from '#test/fixtures/lease-token.js';
13
13
  import {projectsTestClient} from '#test/fixtures/projects-inter-module.js';
14
14
  import {runnersTestClient} from '#test/fixtures/runners-inter-module.js';
15
15
  import {createTestSecretsClient} from '#test/fixtures/secrets-inter-module.js';
@@ -50,7 +50,7 @@ describe('workflow route auth', () => {
50
50
 
51
51
  test('rejects API-key-only requests', async () => {
52
52
  const app = await createApp({
53
- auth: [fakeUserAuth, createLeaseTokenAuthMethod()],
53
+ auth: [fakeUserAuth, fakeLeaseTokenAuthMethod],
54
54
  routes: workflowRoutes,
55
55
  swagger: false,
56
56
  });
@@ -71,7 +71,7 @@ describe('workflow route auth', () => {
71
71
 
72
72
  test('step routes reject requests without a lease token', async () => {
73
73
  const app = await createApp({
74
- auth: [fakeUserAuth, createLeaseTokenAuthMethod()],
74
+ auth: [fakeUserAuth, fakeLeaseTokenAuthMethod],
75
75
  routes: workflowRoutes,
76
76
  swagger: false,
77
77
  });
@@ -2,8 +2,8 @@ import type {AnnotationsInterModuleClient} from '@shipfox/annotations-dto/inter-
2
2
  import type {AgentInterModuleClient} from '@shipfox/api-agent-dto/inter-module';
3
3
  import {AUTH_LEASED_JOB, AUTH_USER} from '@shipfox/api-auth-context';
4
4
  import type {AuthInterModuleClient} from '@shipfox/api-auth-dto/inter-module';
5
- import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto';
6
- import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
5
+ import type {IntegrationsModuleClient} 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 type {SecretsInterModuleClient} from '@shipfox/api-secrets-dto/inter-module';
9
9
  import type {RouteGroup} from '@shipfox/node-fastify';
@@ -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 {workflowRunAttemptsResponseSchema} from '@shipfox/api-workflows-dto';
3
3
  import {defineRoute} from '@shipfox/node-fastify';
4
4
  import {z} from 'zod';
@@ -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 {encodeTimestampIdCursor} from '@shipfox/node-drizzle';
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 {
3
3
  workflowRunListQuerySchema,
4
4
  workflowRunListResponseSchema,
@@ -1,7 +1,6 @@
1
1
  import type {AnnotationsInterModuleClient} from '@shipfox/annotations-dto/inter-module';
2
- import {createLeaseTokenAuthMethod, verifyJobLeaseToken} from '@shipfox/api-auth';
3
2
  import {closeApp, createApp, type FastifyInstance} from '@shipfox/node-fastify';
4
- import {eq, sql} from 'drizzle-orm';
3
+ import {eq} from 'drizzle-orm';
5
4
  import {JobNotFoundError} from '#core/errors.js';
6
5
  import {recordStepResult as recordJobExecutionStepResult} from '#core/job-execution.js';
7
6
  import {db} from '#db/db.js';
@@ -16,9 +15,16 @@ import {insertRunningJobLease, mintActiveLeaseToken} from '#test/fixtures/active
16
15
  import {agentTestClient} from '#test/fixtures/agent-inter-module.js';
17
16
  import {workflowsTestAuthClient} from '#test/fixtures/auth-inter-module.js';
18
17
  import {arrangeJobWithSteps} from '#test/fixtures/job-with-steps.js';
19
- import {mintLeaseToken} from '#test/fixtures/lease-token.js';
18
+ import {
19
+ fakeLeaseTokenAuthMethod,
20
+ getLeaseTokenClaims,
21
+ mintLeaseToken,
22
+ } from '#test/fixtures/lease-token.js';
20
23
  import {projectsTestClient} from '#test/fixtures/projects-inter-module.js';
21
- import {runnersTestClient} from '#test/fixtures/runners-inter-module.js';
24
+ import {
25
+ runnersTestClient,
26
+ setRunnerToolCapabilities as setTestRunnerToolCapabilities,
27
+ } from '#test/fixtures/runners-inter-module.js';
22
28
  import {createTestSecretsClient} from '#test/fixtures/secrets-inter-module.js';
23
29
  import {createLeaseTokenRouteGroup} from './index.js';
24
30
 
@@ -36,16 +42,11 @@ async function recordStepResult(
36
42
  return recordJobExecutionStepResult({...rest, jobExecutionId: step.jobExecutionId});
37
43
  }
38
44
 
39
- async function setRunnerToolCapabilities(
45
+ function setRunnerToolCapabilities(
40
46
  runnerSessionId: string,
41
- capabilities: Record<string, unknown>,
42
- ): Promise<void> {
43
- await db().execute(sql`
44
- UPDATE runners_runner_sessions
45
- SET tool_capabilities = ${JSON.stringify(capabilities)}::jsonb,
46
- tool_capabilities_reported_at = now()
47
- WHERE id = ${runnerSessionId}
48
- `);
47
+ capabilities: {harnesses: {pi?: {tools: string[]}; claude?: {tools: string[]}}},
48
+ ): void {
49
+ setTestRunnerToolCapabilities(runnerSessionId, {capabilities, reportFresh: true});
49
50
  }
50
51
 
51
52
  const annotationWrites = vi.fn<AnnotationsInterModuleClient['replaceOrRemoveAnnotation']>();
@@ -58,7 +59,7 @@ describe('POST /runs/jobs/current/steps/next', () => {
58
59
 
59
60
  beforeAll(async () => {
60
61
  app = await createApp({
61
- auth: [createLeaseTokenAuthMethod()],
62
+ auth: [fakeLeaseTokenAuthMethod],
62
63
  routes: [
63
64
  createLeaseTokenRouteGroup({
64
65
  agent: agentTestClient,
@@ -108,62 +109,14 @@ describe('POST /runs/jobs/current/steps/next', () => {
108
109
  expect(res.statusCode).toBe(401);
109
110
  expect(res.json().code).toBe('unauthorized');
110
111
  });
111
-
112
- test('rejects an expired token', async () => {
113
- const token = await mintLeaseToken({
114
- jobId: crypto.randomUUID(),
115
- jobExecutionId: crypto.randomUUID(),
116
- expiresIn: '-1s',
117
- });
118
-
119
- const res = await app.inject({
120
- method: 'POST',
121
- url: URL,
122
- headers: {authorization: `Bearer ${token}`},
123
- });
124
-
125
- expect(res.statusCode).toBe(401);
126
- expect(res.json().code).toBe('unauthorized');
127
- });
128
-
129
- test('rejects a token signed with the wrong secret', async () => {
130
- const token = await mintLeaseToken({
131
- jobId: crypto.randomUUID(),
132
- jobExecutionId: crypto.randomUUID(),
133
- secret: 'wrong-secret',
134
- });
135
-
136
- const res = await app.inject({
137
- method: 'POST',
138
- url: URL,
139
- headers: {authorization: `Bearer ${token}`},
140
- });
141
-
142
- expect(res.statusCode).toBe(401);
143
- expect(res.json().code).toBe('unauthorized');
144
- });
145
-
146
- test('rejects a token with the wrong audience (e.g. a user JWT)', async () => {
147
- const token = await mintLeaseToken({
148
- jobId: crypto.randomUUID(),
149
- jobExecutionId: crypto.randomUUID(),
150
- audience: 'user-session',
151
- });
152
-
153
- const res = await app.inject({
154
- method: 'POST',
155
- url: URL,
156
- headers: {authorization: `Bearer ${token}`},
157
- });
158
-
159
- expect(res.statusCode).toBe(401);
160
- expect(res.json().code).toBe('unauthorized');
161
- });
162
112
  });
163
113
 
164
114
  test('returns the lowest-position pending step and marks it running', async () => {
165
115
  const {jobId, steps} = await arrangeJobWithSteps(3);
166
116
  const token = await mintActiveLeaseToken({jobId});
117
+ const lease = getLeaseTokenClaims(token);
118
+ if (!lease) throw new Error('Expected minted lease token to verify');
119
+ const getLeaseState = vi.spyOn(runnersTestClient, 'getLeaseState');
167
120
 
168
121
  const res = await app.inject({
169
122
  method: 'POST',
@@ -172,12 +125,17 @@ describe('POST /runs/jobs/current/steps/next', () => {
172
125
  });
173
126
 
174
127
  expect(res.statusCode).toBe(200);
128
+ expect(getLeaseState).toHaveBeenCalledWith({
129
+ jobId: lease.jobId,
130
+ jobExecutionId: lease.jobExecutionId,
131
+ runnerSessionId: lease.runnerSessionId,
132
+ });
175
133
  const body = res.json();
176
134
  expect(body.kind).toBe('step');
177
135
  expect(body.step.id).toBe(steps[0]?.id);
178
136
  expect(body.step.status).toBe('running');
179
137
  expect(body.lease_token).toEqual(expect.any(String));
180
- const scopedLease = await verifyJobLeaseToken(body.lease_token);
138
+ const scopedLease = getLeaseTokenClaims(body.lease_token);
181
139
  expect(scopedLease).toMatchObject({
182
140
  jobId,
183
141
  jobExecutionId: steps[0]?.jobExecutionId,
@@ -228,9 +186,9 @@ describe('POST /runs/jobs/current/steps/next', () => {
228
186
  })
229
187
  .where(eq(stepsTable.id, steps[0]?.id as string));
230
188
  const token = await mintActiveLeaseToken({jobId});
231
- const lease = await verifyJobLeaseToken(token);
189
+ const lease = getLeaseTokenClaims(token);
232
190
  if (!lease) throw new Error('Expected minted lease token to verify');
233
- await setRunnerToolCapabilities(lease.runnerSessionId, {harnesses: {pi: {tools: ['read']}}});
191
+ setRunnerToolCapabilities(lease.runnerSessionId, {harnesses: {pi: {tools: ['read']}}});
234
192
 
235
193
  const first = await app.inject({
236
194
  method: 'POST',
@@ -378,7 +336,7 @@ describe('POST /runs/jobs/current/steps/next', () => {
378
336
  expect(res.statusCode).toBe(200);
379
337
  const body = res.json();
380
338
  expect(body.attempt).toBe(2);
381
- const scopedLease = await verifyJobLeaseToken(body.lease_token);
339
+ const scopedLease = getLeaseTokenClaims(body.lease_token);
382
340
  expect(scopedLease?.currentStepId).toBe(steps[0]?.id);
383
341
  expect(scopedLease?.currentStepAttempt).toBe(body.attempt);
384
342
  });
@@ -6,7 +6,7 @@ import type {RunnersInterModuleClient} from '@shipfox/api-runners-dto/inter-modu
6
6
  import {nextStepResponseSchema} from '@shipfox/api-workflows-dto';
7
7
  import {ClientError, defineRoute} from '@shipfox/node-fastify';
8
8
  import {warnAgentToolCapabilityMismatchOnDispatch} from '#core/agent-tool-capability-warning.js';
9
- import {JobLeaseNotActiveError, JobNotFoundError} from '#core/errors.js';
9
+ import {JobNotFoundError} from '#core/errors.js';
10
10
  import {nextStepForLeasedJobExecution} from '#core/job-execution.js';
11
11
  import {toStepDto} from '#presentation/dto/step.js';
12
12
 
@@ -30,17 +30,21 @@ export function createNextStepRoute(params: {
30
30
  if (error instanceof JobNotFoundError) {
31
31
  throw new ClientError(error.message, 'job-not-found', {status: 404});
32
32
  }
33
- if (error instanceof JobLeaseNotActiveError) {
34
- throw new ClientError('Job lease is no longer active', 'lease-not-active', {status: 404});
35
- }
36
33
  throw error;
37
34
  },
38
35
  handler: async (request) => {
39
36
  const leasedJob = requireLeasedJobContext(request);
40
- const next = await nextStepForLeasedJobExecution({
37
+ const {active: leaseIsActive} = await params.runners.getLeaseState({
41
38
  jobId: leasedJob.jobId,
42
39
  jobExecutionId: leasedJob.jobExecutionId,
43
40
  runnerSessionId: leasedJob.runnerSessionId,
41
+ });
42
+ if (!leaseIsActive) {
43
+ throw new ClientError('Job lease is no longer active', 'lease-not-active', {status: 404});
44
+ }
45
+
46
+ const next = await nextStepForLeasedJobExecution({
47
+ jobExecutionId: leasedJob.jobExecutionId,
44
48
  agent: params.agent,
45
49
  });
46
50
 
@@ -1,5 +1,5 @@
1
1
  import {requireWorkspaceAccess} 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 {FastifyRequest} from 'fastify';
5
5
 
@@ -1,4 +1,3 @@
1
- import {createLeaseTokenAuthMethod} from '@shipfox/api-auth';
2
1
  import {closeApp, createApp, type FastifyInstance} from '@shipfox/node-fastify';
3
2
  import {nextStepForJob} from '#core/job-execution.js';
4
3
  import {
@@ -13,7 +12,7 @@ import {agentTestClient} from '#test/fixtures/agent-inter-module.js';
13
12
  import {annotationsTestClient} from '#test/fixtures/annotations-inter-module.js';
14
13
  import {workflowsTestAuthClient} from '#test/fixtures/auth-inter-module.js';
15
14
  import {arrangeJobWithSteps} from '#test/fixtures/job-with-steps.js';
16
- import {mintLeaseToken} from '#test/fixtures/lease-token.js';
15
+ import {fakeLeaseTokenAuthMethod, mintLeaseToken} from '#test/fixtures/lease-token.js';
17
16
  import {projectsTestClient} from '#test/fixtures/projects-inter-module.js';
18
17
  import {runnersTestClient} from '#test/fixtures/runners-inter-module.js';
19
18
  import {createTestSecretsClient} from '#test/fixtures/secrets-inter-module.js';
@@ -32,7 +31,7 @@ describe('POST /runs/jobs/current/steps/:stepId/report', () => {
32
31
 
33
32
  beforeAll(async () => {
34
33
  app = await createApp({
35
- auth: [createLeaseTokenAuthMethod()],
34
+ auth: [fakeLeaseTokenAuthMethod],
36
35
  routes: [
37
36
  createLeaseTokenRouteGroup({
38
37
  agent: agentTestClient,
@@ -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 {ClientError} from '@shipfox/node-fastify';
3
3
  import type {FastifyRequest} from 'fastify';
4
4
  import type {WorkflowRun} from '#core/entities/workflow-run.js';
@@ -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,5 +1,5 @@
1
1
  import {requireUserContext} 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 {rerunWorkflowRunBodySchema, workflowRunResponseSchema} from '@shipfox/api-workflows-dto';
4
4
  import {ClientError, defineRoute} from '@shipfox/node-fastify';
5
5
  import {z} from 'zod';
@@ -1,6 +1,6 @@
1
1
  import type {AgentInterModuleClient} from '@shipfox/api-agent-dto/inter-module';
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 {RunnersInterModuleClient} from '@shipfox/api-runners-dto/inter-module';
5
5
  import type {SecretsInterModuleClient} from '@shipfox/api-secrets-dto/inter-module';
6
6
  import {
@@ -1,6 +1,6 @@
1
1
  import type {AgentInterModuleClient} from '@shipfox/api-agent-dto/inter-module';
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 {
5
5
  type RunnersInterModuleClient,
6
6
  runnersInterModuleContract,
@@ -49,6 +49,7 @@ interface TestWorkflowJob {
49
49
  readonly success?: string | undefined;
50
50
  readonly outputs?: Readonly<Record<string, string>> | undefined;
51
51
  readonly env?: WorkflowModel['env'] | undefined;
52
+ readonly listening?: WorkflowModel['jobs'][number]['listening'] | undefined;
52
53
  readonly steps: readonly TestWorkflowStep[];
53
54
  }
54
55
 
@@ -70,7 +71,7 @@ export function workflowModel(input: TestWorkflowModelInput = {}): WorkflowModel
70
71
  return {
71
72
  id: jobId,
72
73
  key,
73
- mode: 'one_shot' as const,
74
+ mode: job.listening === undefined ? ('one_shot' as const) : ('listening' as const),
74
75
  runner: normalizeStringArray(job.runner ?? input.runner ?? DEFAULT_RUNNER_LABELS),
75
76
  ...(job.runnerTemplates === undefined
76
77
  ? {}
@@ -90,6 +91,7 @@ export function workflowModel(input: TestWorkflowModelInput = {}): WorkflowModel
90
91
  {kind: 'literal' as const, value: job.name},
91
92
  ],
92
93
  }),
94
+ ...(job.listening === undefined ? {} : {listening: job.listening}),
93
95
  ...optionalScopedEnv(job.env),
94
96
  dependencies: normalizeStringArray(job.needs).map(stableId),
95
97
  steps: job.steps.map((step, stepIndex) => normalizeStep(step, jobId, stepIndex)),
@@ -1,11 +1,10 @@
1
- import {db} from '#db/db.js';
2
- import {runnerSessions, runningJobExecutions} from '#db/runner-lease-table.js';
3
1
  import {
4
2
  getFirstJobExecutionByJobId,
5
3
  getJobById,
6
4
  getWorkflowRunByAttemptId,
7
5
  } from '#db/workflow-runs.js';
8
6
  import {mintLeaseToken} from './lease-token.js';
7
+ import {registerActiveRunnerLease} from './runners-inter-module.js';
9
8
 
10
9
  export interface MintActiveLeaseTokenParams {
11
10
  jobId: string;
@@ -26,13 +25,9 @@ export async function mintActiveLeaseToken(params: MintActiveLeaseTokenParams):
26
25
  if (!run) throw new Error('Expected workflow run to exist');
27
26
  const runnerSessionId = crypto.randomUUID();
28
27
 
29
- await insertRunningJobLease({
30
- workspaceId: run.workspaceId,
31
- workflowRunId: run.id,
32
- workflowRunAttemptId: job.workflowRunAttemptId,
28
+ registerActiveRunnerLease({
33
29
  jobId: params.jobId,
34
30
  jobExecutionId: jobExecution.id,
35
- projectId: run.projectId,
36
31
  runnerSessionId,
37
32
  });
38
33
 
@@ -57,29 +52,7 @@ export interface InsertRunningJobLeaseParams {
57
52
  runnerSessionId: string;
58
53
  }
59
54
 
60
- export async function insertRunningJobLease(params: InsertRunningJobLeaseParams): Promise<void> {
61
- await db()
62
- .insert(runnerSessions)
63
- .values({
64
- id: params.runnerSessionId,
65
- workspaceId: params.workspaceId,
66
- scope: 'workspace',
67
- registrationTokenId: crypto.randomUUID(),
68
- registrationTokenKind: 'manual',
69
- labels: ['linux'],
70
- });
71
-
72
- await db()
73
- .insert(runningJobExecutions)
74
- .values({
75
- workspaceId: params.workspaceId,
76
- workflowRunId: params.workflowRunId,
77
- workflowRunAttemptId: params.workflowRunAttemptId,
78
- jobId: params.jobId,
79
- jobExecutionId: params.jobExecutionId,
80
- projectId: params.projectId,
81
- runnerSessionId: params.runnerSessionId,
82
- requiredLabels: ['linux'],
83
- runnerLabels: ['linux'],
84
- });
55
+ export function insertRunningJobLease(params: InsertRunningJobLeaseParams): Promise<void> {
56
+ registerActiveRunnerLease(params);
57
+ return Promise.resolve();
85
58
  }
@@ -1,7 +1,38 @@
1
- import type {AgentInterModuleClient} from '@shipfox/api-agent-dto/inter-module';
1
+ import {
2
+ DEFAULT_HARNESS_TOOL_DEPLOYMENT_CONFIG,
3
+ listEnabledHarnessTools,
4
+ listHarnessDescriptors,
5
+ MODEL_PROVIDER_CATALOG_SEED,
6
+ MODEL_PROVIDER_IDS,
7
+ } from '@shipfox/api-agent-dto';
8
+ import type {
9
+ AgentInterModuleClient,
10
+ AgentValidationCatalog,
11
+ } from '@shipfox/api-agent-dto/inter-module';
2
12
  import type {AgentDefaultsResolver} from '#core/agent-defaults.js';
3
13
 
14
+ export const agentValidationCatalog: AgentValidationCatalog = {
15
+ version: 1,
16
+ providers: MODEL_PROVIDER_IDS.map((id) => ({
17
+ id,
18
+ support_status:
19
+ MODEL_PROVIDER_CATALOG_SEED.find((entry) => entry.id === id)?.support_status ?? 'unsupported',
20
+ })),
21
+ harnesses: listHarnessDescriptors().map((harness) => ({
22
+ id: harness.id,
23
+ supported_provider_ids: [...harness.supportedProviderIds],
24
+ thinking_levels: [...harness.thinkingLevels],
25
+ effective_tools: listEnabledHarnessTools(
26
+ harness.id,
27
+ DEFAULT_HARNESS_TOOL_DEPLOYMENT_CONFIG,
28
+ ).map((tool) => tool.name),
29
+ })),
30
+ };
31
+
4
32
  export const agentTestClient: AgentInterModuleClient = {
33
+ getValidationCatalog() {
34
+ return Promise.resolve(agentValidationCatalog);
35
+ },
5
36
  resolveAgentConfig({config}) {
6
37
  return Promise.resolve(resolveTestAgentDefaults(config));
7
38
  },
@@ -1,22 +1,11 @@
1
- import {JOB_LEASE_TOKEN_AUDIENCE} from '@shipfox/api-auth-dto';
2
1
  import type {AuthInterModuleClient} from '@shipfox/api-auth-dto/inter-module';
3
- import {jobLeaseTokenKey} from '@shipfox/node-auth-root-key';
4
- import {signHs256} from '@shipfox/node-jwt';
5
-
6
- const leaseSecret = jobLeaseTokenKey();
2
+ import {mintLeaseToken} from './lease-token.js';
7
3
 
8
4
  export const workflowsTestAuthClient: AuthInterModuleClient = {
9
5
  mintRunnerSessionToken() {
10
6
  throw new Error('Runner session token minting is not configured');
11
7
  },
12
8
  async mintJobLeaseToken(claims) {
13
- return {
14
- token: await signHs256({
15
- payload: claims,
16
- secret: leaseSecret,
17
- expiresIn: '90m',
18
- audience: JOB_LEASE_TOKEN_AUDIENCE,
19
- }),
20
- };
9
+ return {token: await mintLeaseToken(claims)};
21
10
  },
22
11
  };
@@ -1,45 +1,59 @@
1
- import {JOB_LEASE_TOKEN_AUDIENCE} from '@shipfox/api-auth-dto';
2
- import {jobLeaseTokenKey} from '@shipfox/node-auth-root-key';
3
- import {signHs256} from '@shipfox/node-jwt';
1
+ import {AUTH_LEASED_JOB, setLeasedJobContext} from '@shipfox/api-auth-context';
2
+ import {JOB_LEASE_TOKEN_AUDIENCE, type JobLeaseTokenClaims} from '@shipfox/api-auth-dto';
3
+ import {type AuthMethod, ClientError, extractBearerToken} from '@shipfox/node-fastify';
4
4
  import {getFirstJobExecutionByJobId} from '#db/workflow-runs.js';
5
5
 
6
- // Matches test/env.ts; the lease-token auth method reads this same value from config.
7
- const SECRET = jobLeaseTokenKey();
6
+ const leaseClaims = new Map<string, JobLeaseTokenClaims>();
8
7
 
9
8
  export interface MintLeaseTokenParams {
10
9
  jobId: string;
11
10
  jobExecutionId?: string;
12
- // Override informational claims to pair a job with chosen surrounding context.
13
11
  workflowRunId?: string;
14
12
  workflowRunAttemptId?: string;
15
13
  projectId?: string;
16
14
  workspaceId?: string;
17
15
  runnerSessionId?: string;
18
- secret?: string;
19
- expiresIn?: string;
20
- audience?: string;
16
+ currentStepId?: string | undefined;
17
+ currentStepAttempt?: number | undefined;
21
18
  }
22
19
 
23
20
  export async function mintLeaseToken(params: MintLeaseTokenParams): Promise<string> {
24
- let jobExecutionId = params.jobExecutionId;
25
- if (jobExecutionId === undefined) {
26
- const jobExecution = await getFirstJobExecutionByJobId(params.jobId);
27
- if (!jobExecution) throw new Error('Expected job execution to exist');
28
- jobExecutionId = jobExecution.id;
29
- }
30
-
31
- return signHs256({
32
- payload: {
33
- jobId: params.jobId,
34
- jobExecutionId,
35
- workflowRunId: params.workflowRunId ?? crypto.randomUUID(),
36
- workflowRunAttemptId: params.workflowRunAttemptId ?? crypto.randomUUID(),
37
- projectId: params.projectId ?? crypto.randomUUID(),
38
- workspaceId: params.workspaceId ?? crypto.randomUUID(),
39
- runnerSessionId: params.runnerSessionId ?? crypto.randomUUID(),
40
- },
41
- secret: params.secret ?? SECRET,
42
- expiresIn: params.expiresIn ?? '90m',
43
- audience: params.audience ?? JOB_LEASE_TOKEN_AUDIENCE,
21
+ const jobExecution =
22
+ params.jobExecutionId === undefined
23
+ ? await getFirstJobExecutionByJobId(params.jobId)
24
+ : undefined;
25
+ const jobExecutionId = params.jobExecutionId ?? jobExecution?.id;
26
+ if (!jobExecutionId) throw new Error('Expected job execution to exist');
27
+ const token = `test-auth-${crypto.randomUUID()}`;
28
+ leaseClaims.set(token, {
29
+ jobId: params.jobId,
30
+ jobExecutionId,
31
+ workflowRunId: params.workflowRunId ?? crypto.randomUUID(),
32
+ workflowRunAttemptId: params.workflowRunAttemptId ?? crypto.randomUUID(),
33
+ projectId: params.projectId ?? crypto.randomUUID(),
34
+ workspaceId: params.workspaceId ?? crypto.randomUUID(),
35
+ runnerSessionId: params.runnerSessionId ?? crypto.randomUUID(),
36
+ ...(params.currentStepId && params.currentStepAttempt !== undefined
37
+ ? {currentStepId: params.currentStepId, currentStepAttempt: params.currentStepAttempt}
38
+ : {}),
39
+ aud: JOB_LEASE_TOKEN_AUDIENCE,
40
+ iat: 0,
41
+ exp: Number.MAX_SAFE_INTEGER,
44
42
  });
43
+ return token;
44
+ }
45
+
46
+ export function getLeaseTokenClaims(token: string): JobLeaseTokenClaims | undefined {
47
+ return leaseClaims.get(token);
45
48
  }
49
+
50
+ export const fakeLeaseTokenAuthMethod: AuthMethod = {
51
+ name: AUTH_LEASED_JOB,
52
+ authenticate: (request) => {
53
+ const token = extractBearerToken(request.headers.authorization);
54
+ const claims = token ? leaseClaims.get(token) : undefined;
55
+ if (!claims) throw new ClientError('Authentication required', 'unauthorized', {status: 401});
56
+ setLeasedJobContext(request, claims);
57
+ return Promise.resolve();
58
+ },
59
+ };
@@ -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
 
3
3
  export const projectsTestClient = {
4
4
  getProjectById: async () => ({project: undefined}),