@shipfox/api-workflows 16.0.0 → 17.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +47 -0
- package/dist/core/agent-tools.js +8 -3
- package/dist/core/agent-tools.js.map +1 -1
- package/dist/core/entities/step.d.ts +1 -1
- package/dist/core/entities/step.d.ts.map +1 -1
- package/dist/core/entities/step.js +2 -1
- package/dist/core/entities/step.js.map +1 -1
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/step-config/materialize-job-execution-steps.js +2 -0
- package/dist/core/step-config/materialize-job-execution-steps.js.map +1 -1
- package/dist/core/step-config/resolve-step-config.js +3 -1
- package/dist/core/step-config/resolve-step-config.js.map +1 -1
- package/dist/db/db.d.ts +8 -8
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/schema/steps.d.ts +2 -2
- package/dist/db/schema/workflow-run-attempts.d.ts +2 -2
- package/dist/db/workflow-runs/outbox.d.ts +1 -0
- package/dist/db/workflow-runs/outbox.d.ts.map +1 -1
- package/dist/db/workflow-runs/outbox.js +2 -1
- package/dist/db/workflow-runs/outbox.js.map +1 -1
- package/dist/db/workflow-runs/run-create.d.ts.map +1 -1
- package/dist/db/workflow-runs/run-create.js +49 -1
- package/dist/db/workflow-runs/run-create.js.map +1 -1
- package/dist/db/workflow-runs/steps.d.ts +6 -0
- package/dist/db/workflow-runs/steps.d.ts.map +1 -1
- package/dist/db/workflow-runs/steps.js +14 -0
- package/dist/db/workflow-runs/steps.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/presentation/inter-module.d.ts.map +1 -1
- package/dist/presentation/inter-module.js +85 -19
- package/dist/presentation/inter-module.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +11 -11
- package/src/core/agent-tools.test.ts +85 -0
- package/src/core/agent-tools.ts +11 -3
- package/src/core/entities/step.ts +1 -1
- package/src/core/errors.ts +2 -0
- package/src/core/job-execution.test.ts +1 -0
- package/src/core/run-workflow.test.ts +4 -0
- package/src/core/step-config/materialize-job-execution-steps.ts +2 -0
- package/src/core/step-config/resolve-step-config.ts +4 -1
- package/src/db/index.ts +1 -0
- package/src/db/workflow-runs/outbox.ts +2 -0
- package/src/db/workflow-runs/run-create.test.ts +41 -0
- package/src/db/workflow-runs/run-create.ts +81 -3
- package/src/db/workflow-runs/steps.ts +19 -0
- package/src/db/workflow-runs.ts +1 -0
- package/src/presentation/inter-module.test.ts +223 -0
- package/src/presentation/inter-module.ts +106 -24
- package/src/presentation/routes/agent-runtime-config.test.ts +2 -0
- package/test/factories/workflow-model.ts +74 -2
- package/test/fixtures/agent-inter-module.ts +2 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/api-workflows",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "17.0.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -23,30 +23,30 @@
|
|
|
23
23
|
"drizzle-orm": "^0.45.2",
|
|
24
24
|
"js-yaml": "^4.1.0",
|
|
25
25
|
"zod": "^4.4.3",
|
|
26
|
-
"@shipfox/api-auth-context": "
|
|
27
|
-
"@shipfox/api-auth-dto": "
|
|
28
|
-
"@shipfox/api-
|
|
29
|
-
"@shipfox/annotations-dto": "12.3.0",
|
|
30
|
-
"@shipfox/api-definitions-dto": "16.0.0",
|
|
26
|
+
"@shipfox/api-auth-context": "17.0.0",
|
|
27
|
+
"@shipfox/api-auth-dto": "17.0.0",
|
|
28
|
+
"@shipfox/api-definitions-dto": "17.0.0",
|
|
31
29
|
"@shipfox/api-projects-dto": "15.0.0",
|
|
32
30
|
"@shipfox/api-integration-core-dto": "16.0.0",
|
|
31
|
+
"@shipfox/api-agent-dto": "17.0.0",
|
|
33
32
|
"@shipfox/api-runners-dto": "16.0.0",
|
|
34
33
|
"@shipfox/api-secrets-dto": "12.0.0",
|
|
35
|
-
"@shipfox/
|
|
34
|
+
"@shipfox/annotations-dto": "12.3.0",
|
|
35
|
+
"@shipfox/api-workflows-dto": "17.0.0",
|
|
36
36
|
"@shipfox/api-workspaces-dto": "15.0.0",
|
|
37
37
|
"@shipfox/config": "1.2.4",
|
|
38
38
|
"@shipfox/inter-module": "0.2.3",
|
|
39
|
-
"@shipfox/expression": "2.4.
|
|
40
|
-
"@shipfox/node-drizzle": "0.3.5",
|
|
39
|
+
"@shipfox/expression": "2.4.1",
|
|
41
40
|
"@shipfox/node-error-monitoring": "0.3.0",
|
|
42
41
|
"@shipfox/node-fastify": "0.4.3",
|
|
43
42
|
"@shipfox/node-module": "1.0.7",
|
|
44
43
|
"@shipfox/node-opentelemetry": "0.6.5",
|
|
45
44
|
"@shipfox/node-outbox": "0.2.6",
|
|
46
|
-
"@shipfox/node-
|
|
45
|
+
"@shipfox/node-drizzle": "0.3.5",
|
|
46
|
+
"@shipfox/node-postgres": "0.5.1",
|
|
47
47
|
"@shipfox/node-temporal": "0.4.6",
|
|
48
48
|
"@shipfox/runner-labels": "0.2.0",
|
|
49
|
-
"@shipfox/workflow-document": "3.3.
|
|
49
|
+
"@shipfox/workflow-document": "3.3.1"
|
|
50
50
|
},
|
|
51
51
|
"imports": {
|
|
52
52
|
"#*": "./dist/*"
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {workflowModel} from '#test/index.js';
|
|
2
|
+
import {loadAgentToolMaterializationContext} from './agent-tools.js';
|
|
3
|
+
import {AgentIntegrationMaterializationError} from './errors.js';
|
|
4
|
+
|
|
5
|
+
describe('loadAgentToolMaterializationContext', () => {
|
|
6
|
+
const workspaceId = crypto.randomUUID();
|
|
7
|
+
const projectId = crypto.randomUUID();
|
|
8
|
+
|
|
9
|
+
test('returns undefined for a model with only run steps', async () => {
|
|
10
|
+
const model = workflowModel({
|
|
11
|
+
name: 'Plain',
|
|
12
|
+
runner: 'ubuntu-latest',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
await expect(
|
|
16
|
+
loadAgentToolMaterializationContext({model, workspaceId, projectId}),
|
|
17
|
+
).resolves.toBeUndefined();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('returns undefined for a model with only checkout steps', async () => {
|
|
21
|
+
const model = workflowModel({
|
|
22
|
+
name: 'Checkout',
|
|
23
|
+
runner: 'ubuntu-latest',
|
|
24
|
+
jobs: {
|
|
25
|
+
build: {
|
|
26
|
+
steps: [
|
|
27
|
+
{
|
|
28
|
+
checkout: {
|
|
29
|
+
repository: 'acme/platform',
|
|
30
|
+
fetchDepth: 1,
|
|
31
|
+
permissions: {contents: 'read'},
|
|
32
|
+
persistCredentials: true,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
await expect(
|
|
41
|
+
loadAgentToolMaterializationContext({model, workspaceId, projectId}),
|
|
42
|
+
).resolves.toBeUndefined();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('returns undefined for agent steps without integrations', async () => {
|
|
46
|
+
const model = workflowModel({
|
|
47
|
+
name: 'Agent',
|
|
48
|
+
runner: 'ubuntu-latest',
|
|
49
|
+
jobs: {
|
|
50
|
+
build: {
|
|
51
|
+
steps: [{prompt: 'Do the thing'}],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await expect(
|
|
57
|
+
loadAgentToolMaterializationContext({model, workspaceId, projectId}),
|
|
58
|
+
).resolves.toBeUndefined();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('does not short-circuit for a tool-step-only model', async () => {
|
|
62
|
+
const model = workflowModel({
|
|
63
|
+
name: 'Tools',
|
|
64
|
+
runner: 'ubuntu-latest',
|
|
65
|
+
jobs: {
|
|
66
|
+
build: {
|
|
67
|
+
steps: [
|
|
68
|
+
{
|
|
69
|
+
tool: 'get_issue',
|
|
70
|
+
connection: 'linear-main',
|
|
71
|
+
with: {id: 'ENG-1'},
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// A tool step is an integration tool reference, so the loader must not
|
|
79
|
+
// return early: with project access missing it reports the materialization
|
|
80
|
+
// setup failure instead of silently skipping tool-step runs.
|
|
81
|
+
await expect(
|
|
82
|
+
loadAgentToolMaterializationContext({model, workspaceId, projectId}),
|
|
83
|
+
).rejects.toThrow(AgentIntegrationMaterializationError);
|
|
84
|
+
});
|
|
85
|
+
});
|
package/src/core/agent-tools.ts
CHANGED
|
@@ -61,7 +61,7 @@ export async function loadAgentToolMaterializationContext(params: {
|
|
|
61
61
|
readonly projects?: ProjectsModuleClient | undefined;
|
|
62
62
|
readonly jobs?: readonly WorkflowModelJob[] | undefined;
|
|
63
63
|
}): Promise<AgentToolMaterializationContext | undefined> {
|
|
64
|
-
if (!
|
|
64
|
+
if (!hasIntegrationToolReferences(params.model, params.jobs)) return undefined;
|
|
65
65
|
|
|
66
66
|
if (params.projects === undefined) {
|
|
67
67
|
throw new AgentIntegrationMaterializationError('Project access is not configured');
|
|
@@ -410,12 +410,20 @@ function isPermissionScope(
|
|
|
410
410
|
);
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
-
|
|
413
|
+
/**
|
|
414
|
+
* Model-level twin of `hasIntegrationToolReferences` in
|
|
415
|
+
* `libs/api/definitions/src/core/has-integration-tool-references.ts`, which
|
|
416
|
+
* runs the same criterion over the authored `WorkflowDocument` at sync time.
|
|
417
|
+
* Keep both in sync.
|
|
418
|
+
*/
|
|
419
|
+
function hasIntegrationToolReferences(
|
|
414
420
|
model: WorkflowModel | null,
|
|
415
421
|
jobs: readonly WorkflowModelJob[] | undefined,
|
|
416
422
|
): boolean {
|
|
417
423
|
if (model === null) return false;
|
|
418
424
|
return (jobs ?? model.jobs).some((job) =>
|
|
419
|
-
job.steps.some(
|
|
425
|
+
job.steps.some(
|
|
426
|
+
(step) => step.kind === 'tool' || (step.kind === 'agent' && step.integrations !== undefined),
|
|
427
|
+
),
|
|
420
428
|
);
|
|
421
429
|
}
|
|
@@ -13,7 +13,7 @@ import type {InterpolationUnresolvableField} from '../errors.js';
|
|
|
13
13
|
|
|
14
14
|
export type StepStatus = 'pending' | 'running' | 'succeeded' | 'failed' | 'cancelled' | 'skipped';
|
|
15
15
|
|
|
16
|
-
export const STEP_TYPES = ['setup', 'run', 'agent', 'checkout'] as const;
|
|
16
|
+
export const STEP_TYPES = ['setup', 'run', 'agent', 'checkout', 'tool'] as const;
|
|
17
17
|
|
|
18
18
|
export type StepType = (typeof STEP_TYPES)[number];
|
|
19
19
|
|
package/src/core/errors.ts
CHANGED
|
@@ -62,6 +62,8 @@ describe('runWorkflow', () => {
|
|
|
62
62
|
getValidationCatalog: vi.fn().mockResolvedValue(agentValidationCatalog),
|
|
63
63
|
resolveAgentConfig: mockResolveAgentConfig,
|
|
64
64
|
resolveRuntimeCredentials: vi.fn(),
|
|
65
|
+
claimSession: vi.fn(),
|
|
66
|
+
carryOverSessions: vi.fn(),
|
|
65
67
|
};
|
|
66
68
|
|
|
67
69
|
beforeEach(() => {
|
|
@@ -256,6 +258,8 @@ describe('runDevWorkflow', () => {
|
|
|
256
258
|
getValidationCatalog: vi.fn().mockResolvedValue(agentValidationCatalog),
|
|
257
259
|
resolveAgentConfig: mockResolveAgentConfig,
|
|
258
260
|
resolveRuntimeCredentials: vi.fn(),
|
|
261
|
+
claimSession: vi.fn(),
|
|
262
|
+
carryOverSessions: vi.fn(),
|
|
259
263
|
};
|
|
260
264
|
|
|
261
265
|
beforeEach(() => {
|
|
@@ -168,6 +168,8 @@ function stepDisplayName(step: WorkflowModelStep): string {
|
|
|
168
168
|
: `${step.model} · ${firstLine(step.prompt)}`;
|
|
169
169
|
case 'checkout':
|
|
170
170
|
return 'Checkout';
|
|
171
|
+
case 'tool':
|
|
172
|
+
return step.tool.method === undefined ? step.tool.id : `${step.tool.id}.${step.tool.method}`;
|
|
171
173
|
default:
|
|
172
174
|
return assertNever(step);
|
|
173
175
|
}
|
|
@@ -154,7 +154,10 @@ function resolveWorkingDirectoryConfig(
|
|
|
154
154
|
params: Omit<BuildStepConfigParams, 'context'> & {readonly context: WorkflowEvaluationContext},
|
|
155
155
|
): WorkingDirectoryConfig {
|
|
156
156
|
const value = params.step.workingDirectory;
|
|
157
|
-
|
|
157
|
+
// Tool steps have no working tree, so they never carry a working-directory
|
|
158
|
+
// template; the union narrows the remaining kinds to those that can.
|
|
159
|
+
const template =
|
|
160
|
+
params.step.kind === 'tool' ? undefined : params.step.templates?.workingDirectory;
|
|
158
161
|
if (value === undefined) {
|
|
159
162
|
return {
|
|
160
163
|
config: {},
|
package/src/db/index.ts
CHANGED
|
@@ -99,6 +99,7 @@ export async function writeJobStepsSettledOutbox(
|
|
|
99
99
|
export async function writeStepAttemptTerminatedOutbox(
|
|
100
100
|
tx: Tx,
|
|
101
101
|
params: {
|
|
102
|
+
stepAttemptId: string;
|
|
102
103
|
stepId: string;
|
|
103
104
|
attempt: number;
|
|
104
105
|
status: 'succeeded' | 'failed' | 'cancelled';
|
|
@@ -119,6 +120,7 @@ export async function writeStepAttemptTerminatedOutbox(
|
|
|
119
120
|
attempt: params.attempt,
|
|
120
121
|
status: params.status,
|
|
121
122
|
logOutcome: params.logOutcome,
|
|
123
|
+
stepAttemptId: params.stepAttemptId,
|
|
122
124
|
},
|
|
123
125
|
});
|
|
124
126
|
}
|
|
@@ -1191,6 +1191,47 @@ describe('workflow run queries', () => {
|
|
|
1191
1191
|
}),
|
|
1192
1192
|
expected: {field: 'step.working_directory', source: 'vars.REQUIRED'},
|
|
1193
1193
|
},
|
|
1194
|
+
{
|
|
1195
|
+
field: 'tool.with',
|
|
1196
|
+
model: () =>
|
|
1197
|
+
workflowModel({
|
|
1198
|
+
name: 'Missing tool with var',
|
|
1199
|
+
runner: 'ubuntu-latest',
|
|
1200
|
+
jobs: {
|
|
1201
|
+
build: {
|
|
1202
|
+
steps: [
|
|
1203
|
+
{
|
|
1204
|
+
tool: 'get_issue',
|
|
1205
|
+
connection: 'linear-main',
|
|
1206
|
+
with: {id: template('vars.REQUIRED')},
|
|
1207
|
+
},
|
|
1208
|
+
],
|
|
1209
|
+
},
|
|
1210
|
+
},
|
|
1211
|
+
}),
|
|
1212
|
+
expected: {field: 'tool.with', source: 'vars.REQUIRED'},
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
field: 'tool.outputs',
|
|
1216
|
+
model: () =>
|
|
1217
|
+
workflowModel({
|
|
1218
|
+
name: 'Missing tool output var',
|
|
1219
|
+
runner: 'ubuntu-latest',
|
|
1220
|
+
jobs: {
|
|
1221
|
+
build: {
|
|
1222
|
+
steps: [
|
|
1223
|
+
{
|
|
1224
|
+
tool: 'get_issue',
|
|
1225
|
+
connection: 'linear-main',
|
|
1226
|
+
with: {id: 'ENG-1'},
|
|
1227
|
+
outputs: {sev: template('vars.REQUIRED')},
|
|
1228
|
+
},
|
|
1229
|
+
],
|
|
1230
|
+
},
|
|
1231
|
+
},
|
|
1232
|
+
}),
|
|
1233
|
+
expected: {field: 'tool.outputs', envKey: 'sev', source: 'vars.REQUIRED'},
|
|
1234
|
+
},
|
|
1194
1235
|
{
|
|
1195
1236
|
field: 'checkout.repository',
|
|
1196
1237
|
model: () =>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createWorkflowModelSnapshot,
|
|
3
3
|
WORKFLOW_MODEL_CHECKOUT_TARGET_FIELDS,
|
|
4
|
+
type WorkflowJsonTemplateTree,
|
|
4
5
|
type WorkflowModel,
|
|
5
6
|
} from '@shipfox/api-definitions-dto';
|
|
6
7
|
import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto/inter-module';
|
|
@@ -455,9 +456,13 @@ function referencedVariables(
|
|
|
455
456
|
|
|
456
457
|
for (const step of job.steps) {
|
|
457
458
|
collectFieldVariableReferences(step.templates?.name, references, {field: 'step.name'});
|
|
458
|
-
collectFieldVariableReferences(
|
|
459
|
-
|
|
460
|
-
|
|
459
|
+
collectFieldVariableReferences(
|
|
460
|
+
step.kind === 'tool' ? undefined : step.templates?.workingDirectory,
|
|
461
|
+
references,
|
|
462
|
+
{
|
|
463
|
+
field: 'step.working_directory',
|
|
464
|
+
},
|
|
465
|
+
);
|
|
461
466
|
if (step.kind === 'run') {
|
|
462
467
|
collectFieldVariableReferences(step.templates?.command, references, {field: 'run'});
|
|
463
468
|
collectTemplateVariableReferences(step.templates?.env, references);
|
|
@@ -467,6 +472,16 @@ function referencedVariables(
|
|
|
467
472
|
collectFieldVariableReferences(step.templates?.provider, references, {
|
|
468
473
|
field: 'agent.provider',
|
|
469
474
|
});
|
|
475
|
+
} else if (step.kind === 'tool') {
|
|
476
|
+
collectTemplateTreeVariableReferences(step.templates?.with, references, {
|
|
477
|
+
field: 'tool.with',
|
|
478
|
+
});
|
|
479
|
+
for (const [key, expression] of Object.entries(step.outputMappings ?? {})) {
|
|
480
|
+
collectExpressionVariableReferences(expression, references, {
|
|
481
|
+
field: 'tool.outputs',
|
|
482
|
+
envKey: key,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
470
485
|
} else if (step.kind === 'checkout') {
|
|
471
486
|
for (const [key, field] of WORKFLOW_MODEL_CHECKOUT_TARGET_FIELDS) {
|
|
472
487
|
collectFieldVariableReferences(step.checkout.templates?.[key], references, {field});
|
|
@@ -534,6 +549,69 @@ function collectFieldVariableReferences(
|
|
|
534
549
|
}
|
|
535
550
|
}
|
|
536
551
|
|
|
552
|
+
/**
|
|
553
|
+
* Collect `vars.*` references from a tool step's `with` template tree: a
|
|
554
|
+
* `WorkflowJsonTemplateTree` mirrors the authored `with` payload with every
|
|
555
|
+
* interpolated string leaf replaced by its parsed template, so walk it like
|
|
556
|
+
* the authored structure and collect from each leaf template.
|
|
557
|
+
*/
|
|
558
|
+
function collectTemplateTreeVariableReferences(
|
|
559
|
+
tree: WorkflowJsonTemplateTree | undefined,
|
|
560
|
+
references: ReferencedVariable[],
|
|
561
|
+
source: {
|
|
562
|
+
readonly field: InterpolationUnresolvableError['field'];
|
|
563
|
+
readonly envKey?: string | undefined;
|
|
564
|
+
},
|
|
565
|
+
): void {
|
|
566
|
+
if (tree === undefined) return;
|
|
567
|
+
|
|
568
|
+
if (Array.isArray(tree)) {
|
|
569
|
+
// A field template is itself an array of segments; a `with` list is an
|
|
570
|
+
// array of child trees. Segments carry a `kind`, so distinguish the two.
|
|
571
|
+
if (tree.every((element) => isFieldTemplateSegment(element))) {
|
|
572
|
+
collectFieldVariableReferences(tree, references, source);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
for (const child of tree) collectTemplateTreeVariableReferences(child, references, source);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (typeof tree === 'object') {
|
|
580
|
+
for (const child of Object.values(tree)) {
|
|
581
|
+
collectTemplateTreeVariableReferences(child, references, source);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function isFieldTemplateSegment(value: unknown): value is ResolvedFieldSegment {
|
|
587
|
+
return (
|
|
588
|
+
typeof value === 'object' &&
|
|
589
|
+
value !== null &&
|
|
590
|
+
((value as {kind?: unknown}).kind === 'literal' ||
|
|
591
|
+
(value as {kind?: unknown}).kind === 'deferred')
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function collectExpressionVariableReferences(
|
|
596
|
+
expression: WorkflowExpression,
|
|
597
|
+
references: ReferencedVariable[],
|
|
598
|
+
source: {
|
|
599
|
+
readonly field: InterpolationUnresolvableError['field'];
|
|
600
|
+
readonly envKey?: string | undefined;
|
|
601
|
+
},
|
|
602
|
+
): void {
|
|
603
|
+
const keyAccess = analyzeContextKeyAccess(expression);
|
|
604
|
+
for (const reference of keyAccess.references) {
|
|
605
|
+
if (reference.root !== 'vars') continue;
|
|
606
|
+
references.push({
|
|
607
|
+
key: reference.key,
|
|
608
|
+
field: source.field,
|
|
609
|
+
source: expression.source,
|
|
610
|
+
envKey: source.envKey,
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
537
615
|
function logTemplateDiagnostics(params: {
|
|
538
616
|
readonly workflowRunId: string;
|
|
539
617
|
readonly diagnostics: readonly (WorkflowStepTemplateDiagnostic & {
|
|
@@ -197,6 +197,7 @@ export async function bulkUpdateStepStatuses(
|
|
|
197
197
|
),
|
|
198
198
|
)
|
|
199
199
|
.returning({
|
|
200
|
+
id: stepAttempts.id,
|
|
200
201
|
stepId: stepAttempts.stepId,
|
|
201
202
|
attempt: stepAttempts.attempt,
|
|
202
203
|
logOutcome: stepAttempts.logOutcome,
|
|
@@ -205,6 +206,7 @@ export async function bulkUpdateStepStatuses(
|
|
|
205
206
|
if (finalizedAttempts.length > 0) {
|
|
206
207
|
for (const attempt of finalizedAttempts) {
|
|
207
208
|
await writeStepAttemptTerminatedOutbox(tx, {
|
|
209
|
+
stepAttemptId: attempt.id,
|
|
208
210
|
stepId: attempt.stepId,
|
|
209
211
|
attempt: attempt.attempt,
|
|
210
212
|
status: params.status,
|
|
@@ -459,6 +461,7 @@ export async function finishStepAttempt(params: FinishStepAttemptParams, tx: Tx)
|
|
|
459
461
|
),
|
|
460
462
|
)
|
|
461
463
|
.returning({
|
|
464
|
+
id: stepAttempts.id,
|
|
462
465
|
stepId: stepAttempts.stepId,
|
|
463
466
|
attempt: stepAttempts.attempt,
|
|
464
467
|
logOutcome: stepAttempts.logOutcome,
|
|
@@ -468,6 +471,7 @@ export async function finishStepAttempt(params: FinishStepAttemptParams, tx: Tx)
|
|
|
468
471
|
if (!row) return;
|
|
469
472
|
|
|
470
473
|
await writeStepAttemptTerminatedOutbox(tx, {
|
|
474
|
+
stepAttemptId: row.id,
|
|
471
475
|
stepId: row.stepId,
|
|
472
476
|
attempt: row.attempt,
|
|
473
477
|
status: params.status,
|
|
@@ -531,6 +535,21 @@ export async function getStepAttempts(jobId: string): Promise<StepAttempt[]> {
|
|
|
531
535
|
return rows.map((row) => toStepAttempt(row.stepAttempt));
|
|
532
536
|
}
|
|
533
537
|
|
|
538
|
+
/**
|
|
539
|
+
* Lists only the step attempt ids of a job, without materializing the full
|
|
540
|
+
* attempt entities. The session release sweep only needs the ids to release
|
|
541
|
+
* claims, so this keeps the per-job-terminated query off a full-row projection.
|
|
542
|
+
*/
|
|
543
|
+
export async function listStepAttemptIdsByJobId(jobId: string): Promise<string[]> {
|
|
544
|
+
const rows = await db()
|
|
545
|
+
.select({id: stepAttempts.id})
|
|
546
|
+
.from(stepAttempts)
|
|
547
|
+
.innerJoin(steps, eq(stepAttempts.stepId, steps.id))
|
|
548
|
+
.innerJoin(jobExecutions, eq(steps.jobExecutionId, jobExecutions.id))
|
|
549
|
+
.where(eq(jobExecutions.jobId, jobId));
|
|
550
|
+
return rows.map((row) => row.id);
|
|
551
|
+
}
|
|
552
|
+
|
|
534
553
|
export async function getStepAttemptsByJobIds(jobIds: string[]): Promise<StepAttempt[]> {
|
|
535
554
|
if (jobIds.length === 0) return [];
|
|
536
555
|
const rows = await db()
|