@shipfox/api-workflows 8.0.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 (55) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +65 -0
  3. package/dist/core/agent-tools.d.ts +3 -1
  4. package/dist/core/agent-tools.d.ts.map +1 -1
  5. package/dist/core/agent-tools.js.map +1 -1
  6. package/dist/core/errors.d.ts +0 -4
  7. package/dist/core/errors.d.ts.map +1 -1
  8. package/dist/core/errors.js +0 -6
  9. package/dist/core/errors.js.map +1 -1
  10. package/dist/core/job-execution.d.ts +0 -2
  11. package/dist/core/job-execution.d.ts.map +1 -1
  12. package/dist/core/job-execution.js +3 -7
  13. package/dist/core/job-execution.js.map +1 -1
  14. package/dist/db/workflow-runs/job-executions.d.ts +0 -5
  15. package/dist/db/workflow-runs/job-executions.d.ts.map +1 -1
  16. package/dist/db/workflow-runs/job-executions.js +0 -7
  17. package/dist/db/workflow-runs/job-executions.js.map +1 -1
  18. package/dist/db/workflow-runs.d.ts +1 -1
  19. package/dist/db/workflow-runs.d.ts.map +1 -1
  20. package/dist/db/workflow-runs.js +1 -1
  21. package/dist/db/workflow-runs.js.map +1 -1
  22. package/dist/index.js +2 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/presentation/routes/next-step.d.ts.map +1 -1
  25. package/dist/presentation/routes/next-step.js +11 -8
  26. package/dist/presentation/routes/next-step.js.map +1 -1
  27. package/dist/tsconfig.test.tsbuildinfo +1 -1
  28. package/package.json +26 -28
  29. package/src/core/agent-tool-capability-warning.test.ts +25 -55
  30. package/src/core/agent-tools.ts +7 -5
  31. package/src/core/errors.ts +0 -7
  32. package/src/core/job-execution.ts +3 -10
  33. package/src/core/run-workflow.test.ts +20 -55
  34. package/src/core/step-config/materialize-job-execution-steps.test.ts +1 -2
  35. package/src/core/step-config/materialize-workflow-model.test.ts +11 -23
  36. package/src/db/workflow-runs/job-executions.ts +0 -21
  37. package/src/db/workflow-runs/run-create.test.ts +31 -33
  38. package/src/db/workflow-runs.ts +0 -1
  39. package/src/index.ts +1 -1
  40. package/src/presentation/dto/checkout-token.test.ts +7 -1
  41. package/src/presentation/routes/agent-runtime-config.test.ts +38 -97
  42. package/src/presentation/routes/next-step.test.ts +18 -12
  43. package/src/presentation/routes/next-step.ts +9 -5
  44. package/test/factories/workflow-model.ts +3 -1
  45. package/test/fixtures/active-lease-token.ts +5 -32
  46. package/test/fixtures/agent-inter-module.ts +3 -2
  47. package/test/fixtures/runners-inter-module.ts +40 -48
  48. package/test/globalSetup.ts +0 -38
  49. package/test/setup.ts +2 -0
  50. package/tsconfig.build.tsbuildinfo +1 -1
  51. package/dist/db/runner-lease-table.d.ts +0 -469
  52. package/dist/db/runner-lease-table.d.ts.map +0 -1
  53. package/dist/db/runner-lease-table.js +0 -43
  54. package/dist/db/runner-lease-table.js.map +0 -1
  55. package/src/db/runner-lease-table.ts +0 -39
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipfox/api-workflows",
3
3
  "license": "MIT",
4
- "version": "8.0.0",
4
+ "version": "9.0.1",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/ShipfoxHQ/shipfox.git",
@@ -11,10 +11,6 @@
11
11
  "type": "module",
12
12
  "main": "dist/index.js",
13
13
  "types": "dist/index.d.ts",
14
- "imports": {
15
- "#test/*": "./test/*",
16
- "#*": "./dist/*"
17
- },
18
14
  "exports": {
19
15
  ".": {
20
16
  "types": "./dist/index.d.ts",
@@ -26,29 +22,31 @@
26
22
  "@temporalio/workflow": "1.18.1",
27
23
  "drizzle-orm": "^0.45.2",
28
24
  "zod": "^4.4.3",
29
- "@shipfox/api-auth-context": "7.1.0",
30
- "@shipfox/api-auth-dto": "7.1.0",
31
- "@shipfox/api-agent-dto": "8.0.0",
32
- "@shipfox/annotations-dto": "6.0.0",
33
- "@shipfox/api-definitions-dto": "6.0.0",
34
- "@shipfox/api-projects-dto": "8.0.0",
35
- "@shipfox/api-integration-core-dto": "8.0.0",
36
- "@shipfox/api-runners": "8.0.0",
37
- "@shipfox/api-runners-dto": "7.0.1",
38
- "@shipfox/api-secrets-dto": "6.0.0",
39
- "@shipfox/api-workflows-dto": "8.0.0",
40
- "@shipfox/inter-module": "0.2.0",
41
- "@shipfox/expression": "1.1.3",
42
- "@shipfox/node-drizzle": "0.3.2",
43
- "@shipfox/node-error-monitoring": "0.2.0",
44
- "@shipfox/node-fastify": "0.3.0",
45
- "@shipfox/node-module": "0.5.0",
46
- "@shipfox/node-opentelemetry": "0.6.0",
47
- "@shipfox/node-outbox": "0.2.4",
48
- "@shipfox/node-postgres": "0.4.2",
49
- "@shipfox/node-temporal": "0.4.0",
50
- "@shipfox/runner-labels": "0.1.1",
51
- "@shipfox/workflow-document": "2.1.1"
25
+ "@shipfox/api-auth-dto": "9.0.1",
26
+ "@shipfox/api-agent-dto": "9.0.1",
27
+ "@shipfox/annotations-dto": "9.0.1",
28
+ "@shipfox/api-auth-context": "9.0.1",
29
+ "@shipfox/api-definitions-dto": "9.0.1",
30
+ "@shipfox/api-projects-dto": "9.0.1",
31
+ "@shipfox/api-secrets-dto": "9.0.1",
32
+ "@shipfox/api-workflows-dto": "9.0.1",
33
+ "@shipfox/api-integration-core-dto": "9.0.1",
34
+ "@shipfox/inter-module": "0.2.1",
35
+ "@shipfox/api-runners-dto": "9.0.1",
36
+ "@shipfox/expression": "1.1.4",
37
+ "@shipfox/node-error-monitoring": "0.2.1",
38
+ "@shipfox/node-fastify": "0.3.1",
39
+ "@shipfox/node-opentelemetry": "0.6.1",
40
+ "@shipfox/node-outbox": "0.2.5",
41
+ "@shipfox/node-postgres": "0.4.3",
42
+ "@shipfox/node-module": "1.0.0",
43
+ "@shipfox/node-temporal": "0.4.1",
44
+ "@shipfox/runner-labels": "0.1.2",
45
+ "@shipfox/node-drizzle": "0.3.3",
46
+ "@shipfox/workflow-document": "2.1.2"
47
+ },
48
+ "imports": {
49
+ "#*": "./dist/*"
52
50
  },
53
51
  "scripts": {
54
52
  "build": "shipfox-swc && shipfox-temporal-bundle dist/temporal/workflows/index.js",
@@ -1,8 +1,7 @@
1
1
  import type {AnnotationsInterModuleClient} from '@shipfox/annotations-dto/inter-module';
2
2
  import type {JobLeaseTokenClaims} from '@shipfox/api-auth-dto';
3
- import {sql} from 'drizzle-orm';
4
- import {db} from '#db/db.js';
5
- import {runnersTestClient} from '#test/fixtures/runners-inter-module.js';
3
+ import type {RunnersInterModuleClient} from '@shipfox/api-runners-dto/inter-module';
4
+ import {runnersTestClient, setRunnerToolCapabilities} from '#test/fixtures/runners-inter-module.js';
6
5
  import {warnAgentToolCapabilityMismatchOnDispatch as warnAgentToolCapabilityMismatchOnDispatchImpl} from './agent-tool-capability-warning.js';
7
6
  import type {Step} from './entities/step.js';
8
7
 
@@ -86,45 +85,20 @@ function agentStep(params: Partial<Step> = {}): Step {
86
85
  };
87
86
  }
88
87
 
89
- async function insertRunnerSession(params: {
88
+ function setRunnerSessionCapabilities(params: {
90
89
  runnerSessionId: string;
91
90
  workspaceId: string;
92
- toolCapabilities: Record<string, unknown> | null;
91
+ toolCapabilities:
92
+ | Awaited<
93
+ ReturnType<RunnersInterModuleClient['getEffectiveRunnerToolCapabilities']>
94
+ >['capabilities']
95
+ | null;
93
96
  reportedAt?: 'fresh' | 'stale' | 'missing';
94
- }): Promise<void> {
95
- const reportedAt =
96
- params.reportedAt === 'missing'
97
- ? sql`NULL`
98
- : params.reportedAt === 'stale'
99
- ? sql`now() - interval '1 hour'`
100
- : sql`now()`;
101
- const toolCapabilities =
102
- params.toolCapabilities === null
103
- ? sql`NULL`
104
- : sql`${JSON.stringify(params.toolCapabilities)}::jsonb`;
105
-
106
- await db().execute(sql`
107
- INSERT INTO runners_runner_sessions (
108
- id,
109
- workspace_id,
110
- scope,
111
- registration_token_id,
112
- registration_token_kind,
113
- labels,
114
- tool_capabilities,
115
- tool_capabilities_reported_at
116
- )
117
- VALUES (
118
- ${params.runnerSessionId},
119
- ${params.workspaceId},
120
- 'workspace',
121
- ${crypto.randomUUID()},
122
- 'manual',
123
- ARRAY['linux'],
124
- ${toolCapabilities},
125
- ${reportedAt}
126
- )
127
- `);
97
+ }): void {
98
+ setRunnerToolCapabilities(params.runnerSessionId, {
99
+ capabilities: params.toolCapabilities === null ? {harnesses: {}} : params.toolCapabilities,
100
+ reportFresh: params.reportedAt !== 'missing' && params.reportedAt !== 'stale',
101
+ });
128
102
  }
129
103
 
130
104
  function annotationsFor(jobExecutionId: string): Array<{context: string; body: string}> {
@@ -138,7 +112,7 @@ describe('warnAgentToolCapabilityMismatchOnDispatch', () => {
138
112
  it('writes a warning when the matched runner advertised a set without requested tools', async () => {
139
113
  const identity = lease();
140
114
  const step = agentStep({jobExecutionId: identity.jobExecutionId});
141
- await insertRunnerSession({
115
+ setRunnerSessionCapabilities({
142
116
  runnerSessionId: identity.runnerSessionId,
143
117
  workspaceId: identity.workspaceId,
144
118
  toolCapabilities: {harnesses: {pi: {tools: ['read']}}},
@@ -156,7 +130,7 @@ describe('warnAgentToolCapabilityMismatchOnDispatch', () => {
156
130
  it('writes unknown wording when capabilities are missing', async () => {
157
131
  const identity = lease();
158
132
  const step = agentStep({jobExecutionId: identity.jobExecutionId});
159
- await insertRunnerSession({
133
+ setRunnerSessionCapabilities({
160
134
  runnerSessionId: identity.runnerSessionId,
161
135
  workspaceId: identity.workspaceId,
162
136
  toolCapabilities: null,
@@ -173,7 +147,7 @@ describe('warnAgentToolCapabilityMismatchOnDispatch', () => {
173
147
  it('writes missing-harness wording when fresh capabilities omit the harness', async () => {
174
148
  const identity = lease();
175
149
  const step = agentStep({jobExecutionId: identity.jobExecutionId});
176
- await insertRunnerSession({
150
+ setRunnerSessionCapabilities({
177
151
  runnerSessionId: identity.runnerSessionId,
178
152
  workspaceId: identity.workspaceId,
179
153
  toolCapabilities: {harnesses: {claude: {tools: ['read', 'web_search']}}},
@@ -199,7 +173,7 @@ describe('warnAgentToolCapabilityMismatchOnDispatch', () => {
199
173
  prompt: 'Fix it.',
200
174
  },
201
175
  });
202
- await insertRunnerSession({
176
+ setRunnerSessionCapabilities({
203
177
  runnerSessionId: identity.runnerSessionId,
204
178
  workspaceId: identity.workspaceId,
205
179
  toolCapabilities: {harnesses: {pi: {tools: ['read']}}},
@@ -214,20 +188,16 @@ describe('warnAgentToolCapabilityMismatchOnDispatch', () => {
214
188
  it('removes an existing warning when a later dispatch has all requested tools', async () => {
215
189
  const identity = lease();
216
190
  const step = agentStep({jobExecutionId: identity.jobExecutionId});
217
- await insertRunnerSession({
191
+ setRunnerSessionCapabilities({
218
192
  runnerSessionId: identity.runnerSessionId,
219
193
  workspaceId: identity.workspaceId,
220
194
  toolCapabilities: {harnesses: {pi: {tools: ['read']}}},
221
195
  });
222
196
  await warnAgentToolCapabilityMismatchOnDispatch({leaseIdentity: identity, step});
223
- await db().execute(sql`
224
- UPDATE runners_runner_sessions
225
- SET tool_capabilities = ${JSON.stringify({
226
- harnesses: {pi: {tools: ['read', 'web_search']}},
227
- })}::jsonb,
228
- tool_capabilities_reported_at = now()
229
- WHERE id = ${identity.runnerSessionId}
230
- `);
197
+ setRunnerToolCapabilities(identity.runnerSessionId, {
198
+ capabilities: {harnesses: {pi: {tools: ['read', 'web_search']}}},
199
+ reportFresh: true,
200
+ });
231
201
 
232
202
  await warnAgentToolCapabilityMismatchOnDispatch({leaseIdentity: identity, step});
233
203
 
@@ -247,7 +217,7 @@ describe('warnAgentToolCapabilityMismatchOnDispatch', () => {
247
217
  prompt: 'Fix it.',
248
218
  },
249
219
  });
250
- await insertRunnerSession({
220
+ setRunnerSessionCapabilities({
251
221
  runnerSessionId: identity.runnerSessionId,
252
222
  workspaceId: identity.workspaceId,
253
223
  toolCapabilities: null,
@@ -265,7 +235,7 @@ describe('warnAgentToolCapabilityMismatchOnDispatch', () => {
265
235
  type: 'run',
266
236
  config: {run: 'echo ok'},
267
237
  });
268
- await insertRunnerSession({
238
+ setRunnerSessionCapabilities({
269
239
  runnerSessionId: identity.runnerSessionId,
270
240
  workspaceId: identity.workspaceId,
271
241
  toolCapabilities: null,
@@ -290,7 +260,7 @@ describe('warnAgentToolCapabilityMismatchOnDispatch', () => {
290
260
  prompt: 'Fix it.',
291
261
  },
292
262
  });
293
- await insertRunnerSession({
263
+ setRunnerSessionCapabilities({
294
264
  runnerSessionId: identity.runnerSessionId,
295
265
  workspaceId: identity.workspaceId,
296
266
  toolCapabilities: {harnesses: {pi: {tools: []}}},
@@ -3,11 +3,6 @@ import type {
3
3
  MaterializedAgentIntegrationToolConfigDto,
4
4
  } from '@shipfox/api-agent-dto';
5
5
  import type {WorkflowModel} from '@shipfox/api-definitions-dto';
6
- import type {
7
- AgentToolCatalogEntry,
8
- AgentToolCatalogMethod,
9
- IntegrationProviderKind,
10
- } from '@shipfox/api-integration-core-dto';
11
6
  import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto/inter-module';
12
7
  import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
13
8
  import {AgentIntegrationMaterializationError} from './errors.js';
@@ -15,6 +10,13 @@ import {AgentIntegrationMaterializationError} from './errors.js';
15
10
  type WorkflowModelJob = WorkflowModel['jobs'][number];
16
11
  type WorkflowModelAgentStep = Extract<WorkflowModelJob['steps'][number], {kind: 'agent'}>;
17
12
  type WorkflowModelStepIntegration = NonNullable<WorkflowModelAgentStep['integrations']>[number];
13
+ type IntegrationsAgentToolsContext = Awaited<
14
+ ReturnType<IntegrationsModuleClient['getAgentToolsContext']>
15
+ >;
16
+ export type AgentToolCatalogEntry =
17
+ IntegrationsAgentToolsContext['catalogs'][number]['tools'][number];
18
+ type AgentToolCatalogMethod = NonNullable<AgentToolCatalogEntry['methods']>[number];
19
+ type IntegrationProviderKind = IntegrationsAgentToolsContext['catalogs'][number]['provider'];
18
20
  type AgentToolCatalogs = ReadonlyMap<IntegrationProviderKind, readonly AgentToolCatalogEntry[]>;
19
21
  type WorkspaceConnectionSnapshot = ReadonlyMap<
20
22
  string,
@@ -104,13 +104,6 @@ export class JobNotFoundError extends Error {
104
104
  }
105
105
  }
106
106
 
107
- export class JobLeaseNotActiveError extends Error {
108
- constructor(readonly jobExecutionId: string) {
109
- super(`Job lease is no longer active: ${jobExecutionId}`);
110
- this.name = 'JobLeaseNotActiveError';
111
- }
112
- }
113
-
114
107
  export class InvalidJobRunnerLabelsError extends Error {
115
108
  constructor(readonly labels: readonly string[]) {
116
109
  super(`Job runner labels are invalid: ${labels.join(', ')}`);
@@ -16,7 +16,6 @@ import {
16
16
  getStepAttemptsByJobExecutionId,
17
17
  getStepsByJobExecutionIdForUpdate,
18
18
  insertRunningStepAttempt,
19
- lockActiveJobExecutionLeaseForUpdate,
20
19
  markStepRunning,
21
20
  markStepSkipped,
22
21
  settleJobFailed,
@@ -33,7 +32,6 @@ import type {PersistedEvaluationTraceEntry, Step, StepStatusReason} from './enti
33
32
  import {
34
33
  AgentConfigUnresolvableError,
35
34
  InterpolationUnresolvableError,
36
- JobLeaseNotActiveError,
37
35
  JobNotFoundError,
38
36
  StepAttemptAheadError,
39
37
  StepNotFoundError,
@@ -336,21 +334,16 @@ function dispatchConfigError(error: DispatchConfigError): Record<string, unknown
336
334
  }
337
335
 
338
336
  export interface NextStepForLeasedJobExecutionParams {
339
- jobId: string;
340
337
  jobExecutionId: string;
341
- runnerSessionId: string;
342
338
  agent?: AgentInterModuleClient | undefined;
343
339
  }
344
340
 
345
341
  export function nextStepForLeasedJobExecution(
346
342
  params: NextStepForLeasedJobExecutionParams,
347
343
  ): Promise<NextStep> {
348
- return withTransaction(async (tx) => {
349
- const leaseIsActive = await lockActiveJobExecutionLeaseForUpdate(params, tx);
350
- if (!leaseIsActive) throw new JobLeaseNotActiveError(params.jobExecutionId);
351
-
352
- return nextStepForJobExecutionInTransaction(params.jobExecutionId, tx, params.agent);
353
- });
344
+ return withTransaction((tx) =>
345
+ nextStepForJobExecutionInTransaction(params.jobExecutionId, tx, params.agent),
346
+ );
354
347
  }
355
348
 
356
349
  export function nextStepForJob(
@@ -1,6 +1,5 @@
1
1
  import type {AgentInterModuleClient} from '@shipfox/api-agent-dto/inter-module';
2
- import type {WorkflowDefinition} from '@shipfox/api-definitions';
3
- import {createWorkflowModelSnapshot} from '@shipfox/api-definitions-dto';
2
+ import {createWorkflowModelSnapshot, type WorkflowModel} from '@shipfox/api-definitions-dto';
4
3
  import type {DefinitionsInterModuleClient} from '@shipfox/api-definitions-dto/inter-module';
5
4
  import {agentValidationCatalog} from '#test/fixtures/agent-inter-module.js';
6
5
  import {workflowModel} from '#test/index.js';
@@ -17,61 +16,26 @@ const mockResolveAgentConfig = vi.hoisted(() =>
17
16
  }),
18
17
  );
19
18
 
20
- vi.mock('@shipfox/api-definitions', () => ({
21
- DEFAULT_JOB_CHECKOUT: {
22
- permissions: {contents: 'read'},
23
- persistCredentials: true,
24
- },
25
- getDefinitionById: vi.fn(),
26
- }));
19
+ type DefinitionForWorkflowRun = NonNullable<
20
+ Awaited<ReturnType<DefinitionsInterModuleClient['getDefinitionForWorkflowRun']>>['definition']
21
+ >;
27
22
 
28
- import {getDefinitionById} from '@shipfox/api-definitions';
29
-
30
- const mockGetDefinitionById = vi.mocked(getDefinitionById);
23
+ let definitionResponse: DefinitionForWorkflowRun | null = null;
31
24
  const definitions: DefinitionsInterModuleClient = {
32
- getDefinitionForWorkflowRun: async ({definitionId}) => {
33
- const definition = await mockGetDefinitionById(definitionId);
34
- return {
35
- definition:
36
- definition === undefined
37
- ? null
38
- : {
39
- id: definition.id,
40
- projectId: definition.projectId,
41
- name: definition.name,
42
- model: createWorkflowModelSnapshot(definition.model),
43
- sourceSnapshot: definition.sourceSnapshot,
44
- },
45
- };
46
- },
25
+ getDefinitionForWorkflowRun: async () => ({definition: definitionResponse}),
47
26
  };
48
27
 
49
- function buildDefinition(overrides?: Partial<WorkflowDefinition>): WorkflowDefinition {
50
- const model = workflowModel();
51
- const document = {
52
- name: model.name,
53
- jobs: {
54
- build: {steps: [{run: 'echo hello'}]},
55
- },
56
- };
28
+ function buildDefinition(
29
+ overrides: Partial<Omit<DefinitionForWorkflowRun, 'model'>> & {model?: WorkflowModel} = {},
30
+ ): DefinitionForWorkflowRun {
31
+ const {model = workflowModel(), ...definitionOverrides} = overrides;
57
32
  return {
58
33
  id: crypto.randomUUID(),
59
34
  projectId: crypto.randomUUID(),
60
- configPath: '.shipfox/workflows/test.yml',
61
- source: 'manual',
62
- sha: null,
63
- ref: null,
64
35
  name: 'Test Workflow',
65
- definition: document,
66
- document,
67
- model,
36
+ model: createWorkflowModelSnapshot(model),
68
37
  sourceSnapshot: null,
69
- contentHash: null,
70
- fetchedAt: new Date(),
71
- createdAt: new Date(),
72
- updatedAt: new Date(),
73
- deletedAt: null,
74
- ...overrides,
38
+ ...definitionOverrides,
75
39
  };
76
40
  }
77
41
 
@@ -97,11 +61,12 @@ describe('runWorkflow', () => {
97
61
  workspaceId = crypto.randomUUID();
98
62
  projectId = crypto.randomUUID();
99
63
  mockResolveAgentConfig.mockClear();
64
+ definitionResponse = null;
100
65
  });
101
66
 
102
67
  test('creates a run from a valid definition with the provided manual trigger payload', async () => {
103
68
  const definition = buildDefinition({projectId});
104
- mockGetDefinitionById.mockResolvedValue(definition);
69
+ definitionResponse = definition;
105
70
  const triggerPayload = manualPayload();
106
71
 
107
72
  const run = await runWorkflow(definitions, {
@@ -131,7 +96,7 @@ describe('runWorkflow', () => {
131
96
  },
132
97
  });
133
98
  const definition = buildDefinition({projectId, model});
134
- mockGetDefinitionById.mockResolvedValue(definition);
99
+ definitionResponse = definition;
135
100
 
136
101
  const run = await runWorkflow(definitions, {
137
102
  workspaceId,
@@ -147,7 +112,7 @@ describe('runWorkflow', () => {
147
112
 
148
113
  test('persists an integration trigger payload intact', async () => {
149
114
  const definition = buildDefinition({projectId});
150
- mockGetDefinitionById.mockResolvedValue(definition);
115
+ definitionResponse = definition;
151
116
  const triggerPayload: TriggerPayload = {
152
117
  provider: 'github',
153
118
  source: 'github_acme',
@@ -173,7 +138,7 @@ describe('runWorkflow', () => {
173
138
  test('creates the run with the definition source snapshot', async () => {
174
139
  const sourceSnapshot = {content: 'name: Test Workflow\njobs: {}\n', format: 'yaml'} as const;
175
140
  const definition = buildDefinition({projectId, sourceSnapshot});
176
- mockGetDefinitionById.mockResolvedValue(definition);
141
+ definitionResponse = definition;
177
142
 
178
143
  const run = await runWorkflow(definitions, {
179
144
  workspaceId,
@@ -187,7 +152,7 @@ describe('runWorkflow', () => {
187
152
  });
188
153
 
189
154
  test('throws DefinitionNotFoundError for unknown definition', async () => {
190
- mockGetDefinitionById.mockResolvedValue(undefined);
155
+ definitionResponse = null;
191
156
 
192
157
  const unknownId = crypto.randomUUID();
193
158
 
@@ -205,7 +170,7 @@ describe('runWorkflow', () => {
205
170
  test('throws ProjectMismatchError when definition.projectId does not match', async () => {
206
171
  const otherProjectId = crypto.randomUUID();
207
172
  const definition = buildDefinition({projectId: otherProjectId});
208
- mockGetDefinitionById.mockResolvedValue(definition);
173
+ definitionResponse = definition;
209
174
 
210
175
  await expect(
211
176
  runWorkflow(definitions, {
@@ -220,7 +185,7 @@ describe('runWorkflow', () => {
220
185
 
221
186
  test('passes inputs through to the run', async () => {
222
187
  const definition = buildDefinition({projectId});
223
- mockGetDefinitionById.mockResolvedValue(definition);
188
+ definitionResponse = definition;
224
189
 
225
190
  const run = await runWorkflow(definitions, {
226
191
  workspaceId,
@@ -5,10 +5,9 @@ import {
5
5
  AGENT_INTEGRATION_MCP_TRANSPORT,
6
6
  } from '@shipfox/api-agent-dto';
7
7
  import {agentInterModuleContract} from '@shipfox/api-agent-dto/inter-module';
8
- import type {AgentToolCatalogEntry} from '@shipfox/api-integration-core';
9
8
  import {createInterModuleKnownError} from '@shipfox/inter-module';
10
9
  import type {AgentDefaultsResolver} from '#core/agent-defaults.js';
11
- import type {AgentToolMaterializationContext} from '#core/agent-tools.js';
10
+ import type {AgentToolCatalogEntry, AgentToolMaterializationContext} from '#core/agent-tools.js';
12
11
  import {AgentConfigUnresolvableError, InterpolationUnresolvableError} from '#core/errors.js';
13
12
  import {resolveTestAgentDefaults} from '#test/fixtures/agent-inter-module.js';
14
13
  import {workflowModel} from '#test/index.js';
@@ -1,16 +1,9 @@
1
1
  import {agentInterModuleContract} from '@shipfox/api-agent-dto/inter-module';
2
- import {
3
- DEFAULT_JOB_CHECKOUT,
4
- normalizeWorkflowDocument as normalizeWorkflowDocumentImpl,
5
- type WorkflowModel,
6
- } from '@shipfox/api-definitions';
2
+ import {DEFAULT_JOB_CHECKOUT, type WorkflowModel} from '@shipfox/api-definitions-dto';
7
3
  import {createInterModuleKnownError} from '@shipfox/inter-module';
8
4
  import type {AgentDefaultsResolver} from '#core/agent-defaults.js';
9
5
  import {AgentConfigUnresolvableError, InterpolationUnresolvableError} from '#core/errors.js';
10
- import {
11
- agentValidationCatalog,
12
- resolveTestAgentDefaults,
13
- } from '#test/fixtures/agent-inter-module.js';
6
+ import {resolveTestAgentDefaults} from '#test/fixtures/agent-inter-module.js';
14
7
  import {workflowModel} from '#test/index.js';
15
8
  import {
16
9
  materializeJobRunner,
@@ -29,12 +22,6 @@ function materializeWorkflowModel(
29
22
  return materializeWorkflowModelImpl({resolveAgentDefaults: resolveTestAgentDefaults, ...params});
30
23
  }
31
24
 
32
- function normalizeWorkflowDocument(
33
- document: Parameters<typeof normalizeWorkflowDocumentImpl>[0],
34
- ): ReturnType<typeof normalizeWorkflowDocumentImpl> {
35
- return normalizeWorkflowDocumentImpl(document, {agentValidationCatalog});
36
- }
37
-
38
25
  function expression(source: string): TestWorkflowExpression {
39
26
  return {language: 'cel', check: 'typed', source: source as TestWorkflowExpression['source']};
40
27
  }
@@ -548,7 +535,7 @@ describe('materializeWorkflowModel', () => {
548
535
  });
549
536
 
550
537
  it('freezes vars from the run-creation context into step config', async () => {
551
- const model = normalizeWorkflowDocument({
538
+ const model = workflowModel({
552
539
  name: 'vars workflow',
553
540
  runner: 'ubuntu-latest',
554
541
  jobs: {
@@ -1008,14 +995,15 @@ describe('materializeWorkflowModel', () => {
1008
995
 
1009
996
  it('skips listening job steps at workflow-run creation', async () => {
1010
997
  const stepNameSource = `Review ${template('execution.index')}`;
1011
- const model = normalizeWorkflowDocument({
998
+ const model = workflowModel({
1012
999
  name: 'Listening workflow',
1013
1000
  runner: 'ubuntu-latest',
1014
1001
  jobs: {
1015
1002
  review: {
1016
1003
  listening: {
1017
1004
  on: [{source: 'github', event: 'pull_request_review'}],
1018
- max_executions: 3,
1005
+ maxExecutions: 3,
1006
+ onResolve: 'finish',
1019
1007
  },
1020
1008
  steps: [{name: stepNameSource, prompt: 'Summarize the review.'}],
1021
1009
  },
@@ -1036,14 +1024,15 @@ describe('materializeWorkflowModel', () => {
1036
1024
  });
1037
1025
 
1038
1026
  it('materializes one-shot siblings while skipping listening steps', async () => {
1039
- const model = normalizeWorkflowDocument({
1027
+ const model = workflowModel({
1040
1028
  name: 'Mixed workflow',
1041
1029
  runner: 'ubuntu-latest',
1042
1030
  jobs: {
1043
1031
  review: {
1044
1032
  listening: {
1045
1033
  on: [{source: 'github', event: 'pull_request_review'}],
1046
- max_executions: 3,
1034
+ maxExecutions: 3,
1035
+ onResolve: 'finish',
1047
1036
  },
1048
1037
  steps: [{name: `Review ${template('execution.index')}`, prompt: 'Summarize it.'}],
1049
1038
  },
@@ -1104,12 +1093,12 @@ describe('materializeWorkflowModel', () => {
1104
1093
  });
1105
1094
 
1106
1095
  it('materializes step if as server-owned condition outside runner config', async () => {
1107
- const model = normalizeWorkflowDocument({
1096
+ const model = workflowModel({
1108
1097
  name: 'Conditional workflow',
1109
1098
  runner: 'ubuntu-latest',
1110
1099
  jobs: {
1111
1100
  build: {
1112
- steps: [{if: template('false'), run: 'npm test'}],
1101
+ steps: [{if: expression('false'), run: 'npm test'}],
1113
1102
  },
1114
1103
  },
1115
1104
  });
@@ -1121,7 +1110,6 @@ describe('materializeWorkflowModel', () => {
1121
1110
  language: 'cel',
1122
1111
  source: 'false',
1123
1112
  check: 'typed',
1124
- resultType: 'bool',
1125
1113
  });
1126
1114
  expect(step?.config).toEqual({run: 'npm test'});
1127
1115
  });
@@ -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()