@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.
- package/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +80 -0
- package/dist/core/agent-defaults.d.ts +2 -1
- package/dist/core/agent-defaults.d.ts.map +1 -1
- package/dist/core/agent-defaults.js.map +1 -1
- package/dist/core/agent-tools.d.ts +5 -2
- package/dist/core/agent-tools.d.ts.map +1 -1
- package/dist/core/agent-tools.js.map +1 -1
- package/dist/core/checkout.d.ts +2 -2
- package/dist/core/checkout.d.ts.map +1 -1
- package/dist/core/checkout.js.map +1 -1
- package/dist/core/entities/step.d.ts +2 -1
- package/dist/core/entities/step.d.ts.map +1 -1
- package/dist/core/entities/step.js.map +1 -1
- package/dist/core/errors.d.ts +0 -4
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +0 -6
- package/dist/core/errors.js.map +1 -1
- package/dist/core/job-execution.d.ts +0 -2
- package/dist/core/job-execution.d.ts.map +1 -1
- package/dist/core/job-execution.js +3 -7
- package/dist/core/job-execution.js.map +1 -1
- package/dist/core/run-workflow.d.ts +2 -2
- package/dist/core/run-workflow.d.ts.map +1 -1
- package/dist/core/run-workflow.js.map +1 -1
- package/dist/core/step-config/agent.d.ts +1 -1
- package/dist/core/step-config/agent.d.ts.map +1 -1
- package/dist/core/step-config/agent.js.map +1 -1
- package/dist/db/job-listeners.d.ts +2 -2
- package/dist/db/job-listeners.d.ts.map +1 -1
- package/dist/db/job-listeners.js.map +1 -1
- package/dist/db/workflow-runs/job-executions.d.ts +0 -5
- package/dist/db/workflow-runs/job-executions.d.ts.map +1 -1
- package/dist/db/workflow-runs/job-executions.js +0 -7
- package/dist/db/workflow-runs/job-executions.js.map +1 -1
- package/dist/db/workflow-runs/run-create.d.ts +2 -2
- package/dist/db/workflow-runs/run-create.d.ts.map +1 -1
- package/dist/db/workflow-runs/run-create.js.map +1 -1
- package/dist/db/workflow-runs.d.ts +1 -1
- package/dist/db/workflow-runs.d.ts.map +1 -1
- package/dist/db/workflow-runs.js +1 -1
- package/dist/db/workflow-runs.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/presentation/inter-module.d.ts +2 -2
- package/dist/presentation/inter-module.d.ts.map +1 -1
- package/dist/presentation/inter-module.js +2 -1
- package/dist/presentation/inter-module.js.map +1 -1
- package/dist/presentation/routes/cancel-run.d.ts +1 -1
- package/dist/presentation/routes/cancel-run.d.ts.map +1 -1
- package/dist/presentation/routes/cancel-run.js.map +1 -1
- package/dist/presentation/routes/checkout-token.d.ts +2 -2
- package/dist/presentation/routes/checkout-token.d.ts.map +1 -1
- package/dist/presentation/routes/checkout-token.js +1 -1
- package/dist/presentation/routes/checkout-token.js.map +1 -1
- package/dist/presentation/routes/get-run-aggregates.d.ts +1 -1
- package/dist/presentation/routes/get-run-aggregates.d.ts.map +1 -1
- package/dist/presentation/routes/get-run-aggregates.js.map +1 -1
- package/dist/presentation/routes/get-run.d.ts +1 -1
- package/dist/presentation/routes/get-run.d.ts.map +1 -1
- package/dist/presentation/routes/get-run.js.map +1 -1
- package/dist/presentation/routes/index.d.ts +2 -2
- package/dist/presentation/routes/index.d.ts.map +1 -1
- package/dist/presentation/routes/index.js.map +1 -1
- package/dist/presentation/routes/list-run-attempts.d.ts +1 -1
- package/dist/presentation/routes/list-run-attempts.d.ts.map +1 -1
- package/dist/presentation/routes/list-run-attempts.js.map +1 -1
- package/dist/presentation/routes/list-runs.d.ts +1 -1
- package/dist/presentation/routes/list-runs.d.ts.map +1 -1
- package/dist/presentation/routes/list-runs.js.map +1 -1
- package/dist/presentation/routes/next-step.d.ts.map +1 -1
- package/dist/presentation/routes/next-step.js +11 -8
- package/dist/presentation/routes/next-step.js.map +1 -1
- package/dist/presentation/routes/project-access.d.ts +1 -1
- package/dist/presentation/routes/project-access.d.ts.map +1 -1
- package/dist/presentation/routes/project-access.js.map +1 -1
- package/dist/presentation/routes/require-accessible-run.d.ts +1 -1
- package/dist/presentation/routes/require-accessible-run.d.ts.map +1 -1
- package/dist/presentation/routes/require-accessible-run.js.map +1 -1
- package/dist/presentation/routes/rerun-run.d.ts +1 -1
- package/dist/presentation/routes/rerun-run.d.ts.map +1 -1
- package/dist/presentation/routes/rerun-run.js.map +1 -1
- package/dist/temporal/activities/index.d.ts +2 -2
- package/dist/temporal/activities/index.d.ts.map +1 -1
- package/dist/temporal/activities/index.js.map +1 -1
- package/dist/temporal/activities/orchestration-activities.d.ts +2 -2
- package/dist/temporal/activities/orchestration-activities.d.ts.map +1 -1
- package/dist/temporal/activities/orchestration-activities.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +26 -28
- package/src/core/agent-defaults.ts +2 -1
- package/src/core/agent-tool-capability-warning.test.ts +26 -56
- package/src/core/agent-tools.ts +9 -7
- package/src/core/checkout.test.ts +2 -2
- package/src/core/checkout.ts +2 -2
- package/src/core/entities/step.ts +1 -2
- package/src/core/errors.ts +0 -7
- package/src/core/job-execution.ts +3 -10
- package/src/core/run-workflow.test.ts +22 -55
- package/src/core/run-workflow.ts +2 -2
- package/src/core/step-config/agent.ts +1 -1
- package/src/core/step-config/materialize-job-execution-steps.test.ts +1 -2
- package/src/core/step-config/materialize-workflow-model.test.ts +10 -13
- package/src/db/job-listeners.ts +2 -2
- package/src/db/workflow-runs/job-executions.ts +0 -21
- package/src/db/workflow-runs/run-create.test.ts +30 -23
- package/src/db/workflow-runs/run-create.ts +2 -2
- package/src/db/workflow-runs.ts +0 -1
- package/src/index.ts +3 -3
- package/src/presentation/dto/checkout-token.test.ts +7 -1
- package/src/presentation/inter-module.ts +4 -7
- package/src/presentation/routes/agent-runtime-config.test.ts +40 -132
- package/src/presentation/routes/cancel-run.test.ts +1 -1
- package/src/presentation/routes/cancel-run.ts +1 -1
- package/src/presentation/routes/checkout-token.test.ts +4 -37
- package/src/presentation/routes/checkout-token.ts +2 -2
- package/src/presentation/routes/get-run-aggregates.test.ts +1 -1
- package/src/presentation/routes/get-run-aggregates.ts +1 -1
- package/src/presentation/routes/get-run.test.ts +1 -1
- package/src/presentation/routes/get-run.ts +1 -1
- package/src/presentation/routes/get-step-secrets.test.ts +2 -2
- package/src/presentation/routes/index.test.ts +3 -3
- package/src/presentation/routes/index.ts +2 -2
- package/src/presentation/routes/list-run-attempts.test.ts +1 -1
- package/src/presentation/routes/list-run-attempts.ts +1 -1
- package/src/presentation/routes/list-runs.test.ts +1 -1
- package/src/presentation/routes/list-runs.ts +1 -1
- package/src/presentation/routes/next-step.test.ts +27 -69
- package/src/presentation/routes/next-step.ts +9 -5
- package/src/presentation/routes/project-access.ts +1 -1
- package/src/presentation/routes/report-step.test.ts +2 -3
- package/src/presentation/routes/require-accessible-run.ts +1 -1
- package/src/presentation/routes/rerun-run.test.ts +1 -1
- package/src/presentation/routes/rerun-run.ts +1 -1
- package/src/temporal/activities/index.ts +2 -2
- package/src/temporal/activities/orchestration-activities.ts +2 -2
- package/test/factories/workflow-model.ts +3 -1
- package/test/fixtures/active-lease-token.ts +5 -32
- package/test/fixtures/agent-inter-module.ts +32 -1
- package/test/fixtures/auth-inter-module.ts +2 -13
- package/test/fixtures/lease-token.ts +43 -29
- package/test/fixtures/projects-inter-module.ts +1 -1
- package/test/fixtures/runners-inter-module.ts +40 -48
- package/test/globalSetup.ts +0 -38
- package/test/setup.ts +2 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/db/runner-lease-table.d.ts +0 -469
- package/dist/db/runner-lease-table.d.ts.map +0 -1
- package/dist/db/runner-lease-table.js +0 -43
- package/dist/db/runner-lease-table.js.map +0 -1
- 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: [
|
|
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,
|
|
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,
|
|
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,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
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
45
|
+
function setRunnerToolCapabilities(
|
|
40
46
|
runnerSessionId: string,
|
|
41
|
-
capabilities:
|
|
42
|
-
):
|
|
43
|
-
|
|
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: [
|
|
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 =
|
|
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 =
|
|
189
|
+
const lease = getLeaseTokenClaims(token);
|
|
232
190
|
if (!lease) throw new Error('Expected minted lease token to verify');
|
|
233
|
-
|
|
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 =
|
|
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 {
|
|
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
|
|
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: [
|
|
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
|
-
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
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 {
|
|
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 {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
+
};
|