bazis 0.130.0 → 0.132.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/cli/templates/process.ts +1 -1
- package/core/agent/AgentToolExecutor.ts +3 -0
- package/core/agent/Tool.contract.ts +1 -1
- package/core/agent/index.ts +1 -0
- package/core/agent/metadata.ts +9 -0
- package/core/agent/semantic.ts +8 -1
- package/core/infra/connectors/codex/CodexAppServerClient.ts +1 -1
- package/core/workflow/{runtime_module/config/Runtime.config.ts → contracts/Workflow.config.ts} +15 -6
- package/core/workflow/contracts/WorkflowExtensions.ts +40 -0
- package/core/workflow/contracts/WorkflowInputValidator.ts +6 -5
- package/core/workflow/contracts/types.ts +37 -1
- package/core/workflow/definitions_module/WorkflowDefinitions.module.ts +18 -3
- package/core/workflow/definitions_module/access/Definitions.permissions.ts +4 -3
- package/core/workflow/definitions_module/access/Folder.references.ts +37 -0
- package/core/workflow/definitions_module/access/Process.references.ts +1 -1
- package/core/workflow/definitions_module/access/ProcessFolder.lookup.ts +20 -0
- package/core/workflow/definitions_module/code/CatalogImport.ts +95 -0
- package/core/workflow/definitions_module/code/CodeProcessPublisher.ts +26 -16
- package/core/workflow/definitions_module/code/codeProcesses.ts +7 -3
- package/core/workflow/definitions_module/code/defineProcess.ts +10 -4
- package/core/workflow/definitions_module/events/Definitions.events.ts +16 -0
- package/core/workflow/definitions_module/http/Processes.controller.ts +72 -25
- package/core/workflow/definitions_module/model/Definitions.dbContext.ts +2 -2
- package/core/workflow/definitions_module/model/WfProcess.model.ts +19 -5
- package/core/workflow/definitions_module/model/WfVersion.model.ts +1 -1
- package/core/workflow/definitions_module/services/IWorkflowDefinitions.service.ts +49 -14
- package/core/workflow/definitions_module/services/WorkflowDefinitions.service.ts +128 -45
- package/core/workflow/generated/deps.ts +15 -5
- package/core/workflow/index.ts +17 -3
- package/core/workflow/internal/WorkflowEvents.ts +23 -0
- package/core/workflow/internal/WorkflowLanguages.ts +35 -0
- package/core/workflow/internal/canonicalJson.ts +8 -0
- package/core/workflow/internal/http.ts +53 -5
- package/core/workflow/runtime_module/WorkflowRuntime.module.ts +0 -2
- package/core/workflow/runtime_module/access/Runtime.permissions.ts +5 -3
- package/core/workflow/runtime_module/background/WorkflowRetention.ts +1 -1
- package/core/workflow/runtime_module/background/WorkflowWorker.ts +2 -2
- package/core/workflow/runtime_module/events/Runtime.events.ts +30 -3
- package/core/workflow/runtime_module/http/Events.controller.ts +2 -1
- package/core/workflow/runtime_module/http/Instances.controller.ts +32 -21
- package/core/workflow/runtime_module/internal/Schedules.ts +2 -2
- package/core/workflow/runtime_module/internal/ToolStep.ts +42 -10
- package/core/workflow/runtime_module/internal/WorkflowEngine.ts +83 -31
- package/core/workflow/runtime_module/internal/WorkflowJobs.ts +2 -4
- package/core/workflow/runtime_module/model/Runtime.dbContext.ts +2 -2
- package/core/workflow/runtime_module/model/WfHistory.model.ts +2 -2
- package/core/workflow/runtime_module/model/WfInstance.model.ts +28 -3
- package/core/workflow/runtime_module/model/WfJob.model.ts +17 -1
- package/core/workflow/runtime_module/model/WfSubscription.model.ts +1 -1
- package/core/workflow/runtime_module/model/WfToken.model.ts +11 -1
- package/core/workflow/runtime_module/services/IWorkflowRuntime.service.ts +19 -6
- package/core/workflow/runtime_module/services/WorkflowRuntime.service.ts +20 -17
- package/core/workflow/tasks_module/events/Tasks.events.ts +15 -0
- package/core/workflow/tasks_module/http/Tasks.controller.ts +21 -18
- package/core/workflow/tasks_module/model/Tasks.dbContext.ts +2 -2
- package/core/workflow/tasks_module/model/WfTask.model.ts +8 -4
- package/core/workflow/tasks_module/services/IWorkflowTasks.service.ts +4 -4
- package/core/workflow/tasks_module/services/WorkflowTasks.service.ts +11 -8
- package/library/openapi/codegen.ts +12 -0
- package/library/validation/types/ValidatorOptions.ts +12 -0
- package/library/workflow/ProcessDocument.ts +59 -23
- package/library/workflow/expressionSyntax.ts +243 -0
- package/library/workflow/expressions.ts +343 -0
- package/library/workflow/formFields.ts +120 -34
- package/library/workflow/index.ts +48 -9
- package/library/workflow/localized.ts +35 -0
- package/library/workflow/masking.ts +58 -0
- package/library/workflow/mermaid.ts +18 -8
- package/library/workflow/nodeKinds.ts +88 -17
- package/library/workflow/palette.ts +108 -0
- package/library/workflow/paths.ts +31 -6
- package/library/workflow/structure.ts +282 -0
- package/library/workflow/upgradeDocument.ts +84 -0
- package/library/workflow/validateProcess.ts +130 -48
- package/package.json +1 -1
- package/library/workflow/conditions.ts +0 -44
|
@@ -5,7 +5,7 @@ import { WorkflowClock } from "../../contracts/WorkflowClock";
|
|
|
5
5
|
import { IWorkflowDefinitions } from "../../definitions_module/services/IWorkflowDefinitions.service";
|
|
6
6
|
import { WORKFLOW_INSTANCES_DELETED } from "../events/Runtime.events";
|
|
7
7
|
import { RuntimeDbContext } from "../model/Runtime.dbContext";
|
|
8
|
-
import type { WorkflowSettings } from "
|
|
8
|
+
import type { WorkflowSettings } from "../../contracts/Workflow.config";
|
|
9
9
|
|
|
10
10
|
const DAY_MS = 86_400_000;
|
|
11
11
|
|
|
@@ -2,14 +2,14 @@ import { BackgroundService, delay } from "../../../background";
|
|
|
2
2
|
import type { ServiceProvider } from "../../../di";
|
|
3
3
|
import { WorkflowClock } from "../../contracts/WorkflowClock";
|
|
4
4
|
import type { ConfigView } from "../../../kernel";
|
|
5
|
-
import type { WorkflowSettings } from "
|
|
5
|
+
import type { WorkflowSettings } from "../../contracts/Workflow.config";
|
|
6
6
|
import { WorkflowJobs } from "../internal/WorkflowJobs";
|
|
7
7
|
|
|
8
8
|
/** How often the scheduled starts are matched against the published versions. */
|
|
9
9
|
const RECONCILE_MS = 60_000;
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Takes due jobs of `
|
|
12
|
+
* Takes due jobs of `bazis_wf2_run_jobs` every `worker.intervalMs`, up to
|
|
13
13
|
* `worker.batchSize` at a time ({@link WorkflowJobs}). A full batch is
|
|
14
14
|
* followed by the next one at once.
|
|
15
15
|
*/
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createEventToken } from "../../../kernel";
|
|
2
|
-
import type { FormSpec } from "../../../../library/workflow";
|
|
3
|
-
import type {
|
|
2
|
+
import type { FormSpec, LocalizedText } from "../../../../library/workflow";
|
|
3
|
+
import type { WorkflowActor, WorkflowRequest } from "../../contracts/types";
|
|
4
|
+
import type { InstanceStatus, StartedVia } from "../model/WfInstance.model";
|
|
4
5
|
|
|
5
6
|
/** A token reached a user task. Published after the step commits. */
|
|
6
7
|
export interface WorkflowTaskRequested {
|
|
@@ -9,7 +10,7 @@ export interface WorkflowTaskRequested {
|
|
|
9
10
|
readonly processKey: string;
|
|
10
11
|
readonly nodeId: string;
|
|
11
12
|
readonly partition: string | null;
|
|
12
|
-
readonly title:
|
|
13
|
+
readonly title: LocalizedText;
|
|
13
14
|
readonly assignee: { readonly role: string } | { readonly user: string };
|
|
14
15
|
readonly form: FormSpec | null;
|
|
15
16
|
/** When the task is due, from the node's `dueIn`. */
|
|
@@ -37,7 +38,33 @@ export interface WorkflowInstancesDeleted {
|
|
|
37
38
|
readonly instanceIds: readonly string[];
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
/** What the audit events of an instance share. Published after the commit. */
|
|
42
|
+
interface WorkflowInstanceAudit {
|
|
43
|
+
readonly instanceId: string;
|
|
44
|
+
readonly processKey: string;
|
|
45
|
+
readonly partition: string | null;
|
|
46
|
+
readonly actor: WorkflowActor;
|
|
47
|
+
readonly request: WorkflowRequest | null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** An instance started: by hand, from an event or on schedule. */
|
|
51
|
+
export interface WorkflowInstanceStarted extends WorkflowInstanceAudit {
|
|
52
|
+
readonly version: number;
|
|
53
|
+
readonly businessKey: string | null;
|
|
54
|
+
readonly startedVia: StartedVia;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface WorkflowInstanceCancelled extends WorkflowInstanceAudit {
|
|
58
|
+
readonly reason: string | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The failed steps of an instance run again. */
|
|
62
|
+
export type WorkflowInstanceRetried = WorkflowInstanceAudit;
|
|
63
|
+
|
|
40
64
|
export const WORKFLOW_TASK_REQUESTED = createEventToken<WorkflowTaskRequested>("workflow.task-requested");
|
|
41
65
|
export const WORKFLOW_TOKEN_CLOSED = createEventToken<WorkflowTokenClosed>("workflow.token-closed");
|
|
42
66
|
export const WORKFLOW_INSTANCE_FINISHED = createEventToken<WorkflowInstanceFinished>("workflow.instance-finished");
|
|
43
67
|
export const WORKFLOW_INSTANCES_DELETED = createEventToken<WorkflowInstancesDeleted>("workflow.instances-deleted");
|
|
68
|
+
export const WORKFLOW_INSTANCE_STARTED = createEventToken<WorkflowInstanceStarted>("workflow.instance-started");
|
|
69
|
+
export const WORKFLOW_INSTANCE_CANCELLED = createEventToken<WorkflowInstanceCancelled>("workflow.instance-cancelled");
|
|
70
|
+
export const WORKFLOW_INSTANCE_RETRIED = createEventToken<WorkflowInstanceRetried>("workflow.instance-retried");
|
|
@@ -15,11 +15,12 @@ export class EventsController {
|
|
|
15
15
|
const eventKey = param(ctx, "eventKey");
|
|
16
16
|
const request = await body(ctx);
|
|
17
17
|
const partition = optionalString(request.partition, "partition") ?? null;
|
|
18
|
-
const { actor } = await callerOf(ctx, "workflow.events.emit", { scope: partition, event: eventKey });
|
|
18
|
+
const { actor, request: from } = await callerOf(ctx, "workflow.events.emit", { scope: partition, event: eventKey });
|
|
19
19
|
const result = await this.runtime.emit(eventKey, request.payload ?? {}, {
|
|
20
20
|
correlation: optionalString(request.correlation, "correlation") ?? null,
|
|
21
21
|
partition,
|
|
22
22
|
actor,
|
|
23
|
+
request: from,
|
|
23
24
|
});
|
|
24
25
|
ctx.res.status(202);
|
|
25
26
|
return result;
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { contextOnlyController } from "../../../http/Routing/contextOnlyControllers";
|
|
2
2
|
import { can, type AccessObject } from "../../../access";
|
|
3
3
|
import { Authorize, Controller, Created, Get, Post, type HttpContext } from "../../../http";
|
|
4
|
-
import {
|
|
4
|
+
import { buildListDocument } from "../../../../library/jsonapi";
|
|
5
|
+
import { body, callerOf, filterOf, listQuery, optionalString, pagingOfList, param, requiredString } from "../../internal/http";
|
|
5
6
|
import type { InstanceStatus } from "../model/WfInstance.model";
|
|
7
|
+
import { IWorkflowDefinitions } from "../../definitions_module/services/IWorkflowDefinitions.service";
|
|
6
8
|
import { IWorkflowRuntime, type InstanceSummary } from "../services/IWorkflowRuntime.service";
|
|
7
9
|
|
|
8
10
|
/** Instances over HTTP (WORKFLOW-001 §4); rights are `workflow.instances.*` (WORKFLOW-002 §6.2). */
|
|
9
11
|
@Controller("workflow/instances")
|
|
10
12
|
export class InstancesController {
|
|
11
|
-
constructor(
|
|
13
|
+
constructor(
|
|
14
|
+
private readonly runtime: IWorkflowRuntime,
|
|
15
|
+
private readonly definitions: IWorkflowDefinitions,
|
|
16
|
+
) {}
|
|
12
17
|
|
|
13
18
|
@Post()
|
|
14
19
|
@Authorize(can("workflow.instances.start"))
|
|
@@ -16,12 +21,14 @@ export class InstancesController {
|
|
|
16
21
|
const request = await body(ctx);
|
|
17
22
|
const processKey = requiredString(request.processKey, "processKey", 64);
|
|
18
23
|
const partition = optionalString(request.partition, "partition") ?? null;
|
|
19
|
-
const { actor } = await callerOf(ctx, "workflow.instances.start", { scope: partition, process: processKey });
|
|
24
|
+
const { actor, request: from, language } = await callerOf(ctx, "workflow.instances.start", { scope: partition, process: processKey, folder: await this.definitions.folderOf(processKey) });
|
|
20
25
|
const instance = await this.runtime.start(processKey, {
|
|
21
26
|
input: request.input,
|
|
22
27
|
partition,
|
|
23
28
|
businessKey: optionalString(request.businessKey, "businessKey") ?? null,
|
|
24
29
|
actor,
|
|
30
|
+
request: from,
|
|
31
|
+
language,
|
|
25
32
|
});
|
|
26
33
|
return Created(`${ctx.path}/${instance.id}`, instance);
|
|
27
34
|
}
|
|
@@ -30,23 +37,27 @@ export class InstancesController {
|
|
|
30
37
|
@Authorize(can("workflow.instances.read"))
|
|
31
38
|
async list(ctx: HttpContext) {
|
|
32
39
|
const { access } = await callerOf(ctx);
|
|
40
|
+
const list = listQuery(ctx, {
|
|
41
|
+
filter: { partition: ["eq"], processKey: ["eq"], status: ["eq"], businessKey: ["eq"], startedById: ["eq"] },
|
|
42
|
+
sort: ["createdAt", "endedAt", "status", "processKey"],
|
|
43
|
+
});
|
|
33
44
|
const page = await this.runtime.instances({
|
|
34
|
-
...
|
|
45
|
+
...pagingOfList(list),
|
|
35
46
|
access: await access.filter("workflow.instances.read"),
|
|
36
|
-
partition:
|
|
37
|
-
processKey:
|
|
38
|
-
status:
|
|
39
|
-
businessKey:
|
|
40
|
-
startedById:
|
|
47
|
+
partition: filterOf(list, "partition"),
|
|
48
|
+
processKey: filterOf(list, "processKey"),
|
|
49
|
+
status: filterOf(list, "status") as InstanceStatus | undefined,
|
|
50
|
+
businessKey: filterOf(list, "businessKey"),
|
|
51
|
+
startedById: filterOf(list, "startedById"),
|
|
41
52
|
});
|
|
42
|
-
return
|
|
53
|
+
return buildListDocument(page.items, list, page.total, { basePath: ctx.path });
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
@Get(":id")
|
|
46
57
|
@Authorize(can("workflow.instances.read"))
|
|
47
58
|
async get(ctx: HttpContext) {
|
|
48
59
|
const instance = await this.runtime.instance(param(ctx, "id"));
|
|
49
|
-
await callerOf(ctx, "workflow.instances.read", objectOf(instance));
|
|
60
|
+
await callerOf(ctx, "workflow.instances.read", await this.objectOf(instance));
|
|
50
61
|
return instance;
|
|
51
62
|
}
|
|
52
63
|
|
|
@@ -54,7 +65,7 @@ export class InstancesController {
|
|
|
54
65
|
@Authorize(can("workflow.instances.read"))
|
|
55
66
|
async history(ctx: HttpContext) {
|
|
56
67
|
const instance = await this.runtime.instance(param(ctx, "id"));
|
|
57
|
-
await callerOf(ctx, "workflow.instances.read", objectOf(instance));
|
|
68
|
+
await callerOf(ctx, "workflow.instances.read", await this.objectOf(instance));
|
|
58
69
|
return { data: await this.runtime.history(instance.id) };
|
|
59
70
|
}
|
|
60
71
|
|
|
@@ -63,22 +74,22 @@ export class InstancesController {
|
|
|
63
74
|
async cancel(ctx: HttpContext) {
|
|
64
75
|
const instance = await this.runtime.instance(param(ctx, "id"));
|
|
65
76
|
const request = await body(ctx);
|
|
66
|
-
const { actor } = await callerOf(ctx, "workflow.instances.manage", objectOf(instance));
|
|
67
|
-
return await this.runtime.cancel(instance.id, { reason: optionalString(request.reason, "reason", 1000) ?? null, actor });
|
|
77
|
+
const { actor, request: from } = await callerOf(ctx, "workflow.instances.manage", await this.objectOf(instance));
|
|
78
|
+
return await this.runtime.cancel(instance.id, { reason: optionalString(request.reason, "reason", 1000) ?? null, actor, request: from });
|
|
68
79
|
}
|
|
69
80
|
|
|
70
81
|
@Post(":id/retry")
|
|
71
82
|
@Authorize(can("workflow.instances.manage"))
|
|
72
83
|
async retry(ctx: HttpContext) {
|
|
73
84
|
const instance = await this.runtime.instance(param(ctx, "id"));
|
|
74
|
-
const { actor } = await callerOf(ctx, "workflow.instances.manage", objectOf(instance));
|
|
75
|
-
return await this.runtime.retry(instance.id, { actor });
|
|
85
|
+
const { actor, request } = await callerOf(ctx, "workflow.instances.manage", await this.objectOf(instance));
|
|
86
|
+
return await this.runtime.retry(instance.id, { actor, request });
|
|
76
87
|
}
|
|
77
|
-
}
|
|
78
|
-
contextOnlyController(InstancesController);
|
|
79
88
|
|
|
80
|
-
/** The instance as the object of a permission. */
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
/** The instance as the object of a permission. */
|
|
90
|
+
private async objectOf(instance: InstanceSummary): Promise<AccessObject> {
|
|
91
|
+
return { scope: instance.partition, process: instance.processKey, folder: await this.definitions.folderOf(instance.processKey), startedById: instance.startedBy.id };
|
|
92
|
+
}
|
|
83
93
|
}
|
|
94
|
+
contextOnlyController(InstancesController);
|
|
84
95
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseCron } from "../../../../library/workflow";
|
|
1
|
+
import { inputSpecOf, parseCron, validateForm } from "../../../../library/workflow";
|
|
2
2
|
import type { IWorkflowDefinitions, VersionDetails } from "../../definitions_module/services/IWorkflowDefinitions.service";
|
|
3
3
|
import type { RuntimeDbContext } from "../model/Runtime.dbContext";
|
|
4
4
|
import { WfJob } from "../model/WfJob.model";
|
|
@@ -65,7 +65,7 @@ export class Schedules {
|
|
|
65
65
|
else await this.db.jobs.asNoTracking().where((j) => j.id.eq(job.id)).executeUpdate({ dueAt: next, lockedUntil: null, attempts: 0, lastError: null });
|
|
66
66
|
for (const [nodeId, cron] of scheduleNodes(latest)) {
|
|
67
67
|
if (!cron.matches(due)) continue;
|
|
68
|
-
await this.engine.begin(graph, nodeId, { input: {}, partition: null, businessKey: null, startedVia: "schedule", actor: SCHEDULE_ACTOR });
|
|
68
|
+
await this.engine.begin(graph, nodeId, { input: validateForm(inputSpecOf(latest.document), {}).value, partition: null, businessKey: null, startedVia: "schedule", actor: SCHEDULE_ACTOR });
|
|
69
69
|
}
|
|
70
70
|
});
|
|
71
71
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { AgentToolResult } from "../../../agent";
|
|
2
|
-
import { durationMs, resolveInput } from "../../../../library/workflow";
|
|
1
|
+
import type { AgentSchemaContract, AgentToolResult } from "../../../agent";
|
|
2
|
+
import { durationMs, maskByMapping, maskBySchema, masksPersonal, personalPaths, resolveInput, type ProcessDocument } from "../../../../library/workflow";
|
|
3
|
+
import type { WorkflowToolMetadata } from "../../contracts/types";
|
|
3
4
|
import type { WorkflowTools } from "../../internal/WorkflowTools";
|
|
4
5
|
import type { RuntimeDbContext } from "../model/Runtime.dbContext";
|
|
5
6
|
import type { WfJob } from "../model/WfJob.model";
|
|
@@ -12,6 +13,11 @@ import { dataOf, type WorkflowEngine } from "./WorkflowEngine";
|
|
|
12
13
|
* started the instance, with the token id as its idempotency key: after a crash
|
|
13
14
|
* between the call and the step the call may repeat (TOOLS-001 §7).
|
|
14
15
|
*/
|
|
16
|
+
/** How long a claimed job belongs to its worker; if it dies, another one takes the job after that. */
|
|
17
|
+
export const LEASE_MS = 5 * 60_000;
|
|
18
|
+
|
|
19
|
+
const TIMEOUT_CODES: ReadonlySet<string> = new Set(["TOOL_TIMEOUT", "TOOL_TIMEOUT_OUTCOME_UNKNOWN"]);
|
|
20
|
+
|
|
15
21
|
export class ToolStep {
|
|
16
22
|
constructor(
|
|
17
23
|
private readonly db: RuntimeDbContext,
|
|
@@ -29,21 +35,29 @@ export class ToolStep {
|
|
|
29
35
|
const graph = await this.engine.graphOf(instance.versionId);
|
|
30
36
|
const node = graph.document.nodes[token.nodeId];
|
|
31
37
|
const name = String(node?.config?.tool ?? "");
|
|
32
|
-
const input = resolveInput(node?.config?.input ?? {}, dataOf(instance));
|
|
38
|
+
const input = resolveInput(node?.config?.input ?? {}, dataOf(instance, this.engine.now()));
|
|
39
|
+
const timeoutMs = stepTimeoutMs(node?.config?.timeout, graph.document);
|
|
40
|
+
// A step may run longer than the lease: another worker must not take the job and call the Tool again meanwhile.
|
|
41
|
+
if (timeoutMs !== undefined && timeoutMs >= LEASE_MS) {
|
|
42
|
+
await this.db.jobs.asNoTracking().where((j) => j.id.eq(job.id)).executeUpdate({ lockedUntil: new Date(this.engine.now().getTime() + timeoutMs + LEASE_MS) });
|
|
43
|
+
}
|
|
33
44
|
const runner = this.tools.bound;
|
|
34
45
|
const result: AgentToolResult = runner === undefined
|
|
35
46
|
? { callId: token.id, name, status: "error", error: { code: "TOOL_NOT_FOUND", message: `Tool ${name} is not available to processes` }, metadata: {} } as AgentToolResult
|
|
36
47
|
: await runner.run(name, input, {
|
|
37
48
|
idempotencyKey: token.id,
|
|
38
49
|
actor: { kind: instance.startedByKind, id: instance.startedById },
|
|
39
|
-
metadata: { instanceId: instance.id,
|
|
50
|
+
metadata: { instanceId: instance.id, processKey: instance.processKey, nodeId: token.nodeId, partition: instance.partition, test: false } satisfies WorkflowToolMetadata,
|
|
40
51
|
signal,
|
|
52
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
41
53
|
});
|
|
42
54
|
|
|
43
|
-
const
|
|
55
|
+
const timedOut = TIMEOUT_CODES.has(result.error?.code ?? "");
|
|
56
|
+
const reason = timedOut ? "timeout" : reasonOf(result);
|
|
57
|
+
const payloads = graph.document.settings?.log?.toolPayloads === true ? this.payloads(graph.document, name, node?.config?.input, input, result) : {};
|
|
44
58
|
await this.engine.inStep(instance.id, async (step) => {
|
|
45
59
|
const current = step.tokens.find((t) => t.id === token.id);
|
|
46
|
-
const retryAt = current?.status === "waiting" && step.instance.status === "running" && result.status === "error" && reason === undefined
|
|
60
|
+
const retryAt = current?.status === "waiting" && step.instance.status === "running" && result.status === "error" && (reason === undefined || timedOut)
|
|
47
61
|
? nextAttempt(node?.config?.retry, job.attempts, this.engine.now())
|
|
48
62
|
: undefined;
|
|
49
63
|
if (retryAt !== undefined) {
|
|
@@ -58,6 +72,7 @@ export class ToolStep {
|
|
|
58
72
|
status: result.status,
|
|
59
73
|
attempt: job.attempts,
|
|
60
74
|
durationMs: result.durationMs ?? null,
|
|
75
|
+
...payloads,
|
|
61
76
|
...(result.error ? { error: { code: result.error.code ?? null, message: result.error.message, ...(reason === undefined ? {} : { reason }) } } : {}),
|
|
62
77
|
...(retryAt === undefined ? {} : { retryAt: retryAt.toISOString() }),
|
|
63
78
|
});
|
|
@@ -73,6 +88,15 @@ export class ToolStep {
|
|
|
73
88
|
}
|
|
74
89
|
});
|
|
75
90
|
}
|
|
91
|
+
|
|
92
|
+
/** The input and output for the history under `log.toolPayloads`; personal data is masked unless `log.maskPersonal` is `false`. */
|
|
93
|
+
private payloads(document: ProcessDocument, name: string, mapping: unknown, input: unknown, result: AgentToolResult): Record<string, unknown> {
|
|
94
|
+
if (!masksPersonal(document)) return { input, output: result.output ?? null };
|
|
95
|
+
const contract = this.tools.bound?.describe(name);
|
|
96
|
+
const schemaOf = (side: AgentSchemaContract | undefined) => (side?.kind === "json-schema" ? side.schema : undefined);
|
|
97
|
+
const masked = maskByMapping(mapping ?? {}, maskBySchema(input, schemaOf(contract?.input)), personalPaths(document));
|
|
98
|
+
return { input: masked, output: maskBySchema(result.output ?? null, schemaOf(contract?.output)) };
|
|
99
|
+
}
|
|
76
100
|
}
|
|
77
101
|
|
|
78
102
|
/** The business reason of a `ToolError`, if the Tool gave one. */
|
|
@@ -82,15 +106,23 @@ function reasonOf(result: AgentToolResult): string | undefined {
|
|
|
82
106
|
return typeof reason === "string" ? reason : undefined;
|
|
83
107
|
}
|
|
84
108
|
|
|
109
|
+
/** How long a step's Tool may run: its `timeout`, else `settings.stepTimeout`; `undefined` leaves the Tool's own limit. */
|
|
110
|
+
function stepTimeoutMs(timeout: unknown, document: ProcessDocument): number | undefined {
|
|
111
|
+
const value = typeof timeout === "string" ? timeout : document.settings?.stepTimeout;
|
|
112
|
+
return value === undefined ? undefined : durationMs(value);
|
|
113
|
+
}
|
|
114
|
+
|
|
85
115
|
/**
|
|
86
116
|
* When to try a failed call again under the node's `retry` policy, or
|
|
87
|
-
* `undefined` when the attempts are used up. The pause
|
|
88
|
-
*
|
|
117
|
+
* `undefined` when the attempts are used up. The pause starts at `backoff` (10
|
|
118
|
+
* seconds by default), grows by `factor` (2 by default) unless `growth` is
|
|
119
|
+
* `fixed`, and stops growing at a day.
|
|
89
120
|
*/
|
|
90
121
|
export function nextAttempt(policy: unknown, attempts: number, now: Date): Date | undefined {
|
|
91
122
|
if (typeof policy !== "object" || policy === null) return undefined;
|
|
92
|
-
const { maxAttempts, backoff } = policy as { maxAttempts?: unknown; backoff?: unknown };
|
|
123
|
+
const { maxAttempts, backoff, growth, factor } = policy as { maxAttempts?: unknown; backoff?: unknown; growth?: unknown; factor?: unknown };
|
|
93
124
|
if (typeof maxAttempts !== "number" || attempts >= maxAttempts) return undefined;
|
|
94
125
|
const base = typeof backoff === "string" ? durationMs(backoff) ?? 10_000 : 10_000;
|
|
95
|
-
|
|
126
|
+
const scale = growth === "fixed" ? 1 : (typeof factor === "number" ? factor : 2) ** Math.max(attempts - 1, 0);
|
|
127
|
+
return new Date(now.getTime() + Math.min(base * scale, 86_400_000));
|
|
96
128
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import type { WorkflowActor } from "../../contracts/types";
|
|
1
|
+
import type { EventToken } from "../../../kernel";
|
|
2
|
+
import { durationMs, evaluateExpression, initialVarsOf, isTruthy, masksPersonal, maskByMapping, nodeKindOf, outputOf, personalPaths, personalVariables, PERSONAL_MASK, readPath, type Expr, type FormSpec, type InstanceData, type ProcessFlow, type ProcessNode } from "../../../../library/workflow";
|
|
3
|
+
import type { WorkflowActor, WorkflowRequest } from "../../contracts/types";
|
|
5
4
|
import type { WorkflowClock } from "../../contracts/WorkflowClock";
|
|
6
5
|
import type { IWorkflowDefinitions, VersionDetails } from "../../definitions_module/services/IWorkflowDefinitions.service";
|
|
7
|
-
import {
|
|
6
|
+
import type { WorkflowEvents } from "../../internal/WorkflowEvents";
|
|
7
|
+
import { WORKFLOW_INSTANCE_CANCELLED, WORKFLOW_INSTANCE_FINISHED, WORKFLOW_INSTANCE_RETRIED, WORKFLOW_INSTANCE_STARTED, WORKFLOW_TASK_REQUESTED, WORKFLOW_TOKEN_CLOSED } from "../events/Runtime.events";
|
|
8
8
|
import type { RuntimeDbContext } from "../model/Runtime.dbContext";
|
|
9
9
|
import { WfHistory, type HistoryType } from "../model/WfHistory.model";
|
|
10
10
|
import { WfInstance, type StartedVia } from "../model/WfInstance.model";
|
|
@@ -36,6 +36,10 @@ export interface BeginOptions {
|
|
|
36
36
|
readonly businessKey: string | null;
|
|
37
37
|
readonly startedVia: StartedVia;
|
|
38
38
|
readonly actor: WorkflowActor;
|
|
39
|
+
/** Where the start came from; `null` for code, schedules and the worker. */
|
|
40
|
+
readonly request?: WorkflowRequest | null;
|
|
41
|
+
/** The event that starts it: `$event` and `$trigger.eventKey`. */
|
|
42
|
+
readonly event?: { readonly key: string; readonly payload: unknown };
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
const LIVE = new Set(["active", "waiting", "failed"]);
|
|
@@ -51,11 +55,8 @@ export class WorkflowEngine {
|
|
|
51
55
|
private readonly db: RuntimeDbContext,
|
|
52
56
|
private readonly definitions: IWorkflowDefinitions,
|
|
53
57
|
private readonly graphs: GraphCache,
|
|
54
|
-
private readonly events:
|
|
55
|
-
/** The scope of the engine: event handlers run in it. */
|
|
56
|
-
private readonly scope: ServiceProvider,
|
|
58
|
+
private readonly events: WorkflowEvents,
|
|
57
59
|
private readonly clock: WorkflowClock,
|
|
58
|
-
private readonly logger?: Logger,
|
|
59
60
|
) {}
|
|
60
61
|
|
|
61
62
|
/** The engine's time ({@link WorkflowClock}). */
|
|
@@ -87,6 +88,14 @@ export class WorkflowEngine {
|
|
|
87
88
|
version: graph.version,
|
|
88
89
|
partition: options.partition,
|
|
89
90
|
input: options.input,
|
|
91
|
+
vars: initialVarsOf(graph.document),
|
|
92
|
+
event: options.event?.payload ?? null,
|
|
93
|
+
trigger: {
|
|
94
|
+
via: options.startedVia,
|
|
95
|
+
at: this.now().toISOString(),
|
|
96
|
+
actor: options.actor,
|
|
97
|
+
...(options.event === undefined ? {} : { eventKey: options.event.key }),
|
|
98
|
+
},
|
|
90
99
|
businessKey: options.businessKey,
|
|
91
100
|
startedVia: options.startedVia,
|
|
92
101
|
startedByKind: options.actor.kind,
|
|
@@ -97,6 +106,10 @@ export class WorkflowEngine {
|
|
|
97
106
|
await this.db.saveChanges();
|
|
98
107
|
const step: Step = { instance, tokens: [], graph, events: [] };
|
|
99
108
|
this.history(step, null, null, "started", { via: options.startedVia }, options.actor);
|
|
109
|
+
step.events.push({
|
|
110
|
+
event: WORKFLOW_INSTANCE_STARTED as EventToken<unknown>,
|
|
111
|
+
payload: { instanceId: instance.id, processKey: graph.processKey, version: graph.version, partition: options.partition, businessKey: options.businessKey, startedVia: options.startedVia, actor: options.actor, request: options.request ?? null },
|
|
112
|
+
});
|
|
100
113
|
await this.advance(step, [this.spawn(step, startNodeId, null)]);
|
|
101
114
|
await this.db.saveChanges();
|
|
102
115
|
this.publishAfter(transaction, step);
|
|
@@ -147,13 +160,20 @@ export class WorkflowEngine {
|
|
|
147
160
|
/**
|
|
148
161
|
* A step's Tool failed for good. An error boundary attached to the step takes
|
|
149
162
|
* the token (first one whose `errorCode` equals the Tool's `reason` or error
|
|
150
|
-
* code, else one without `errorCode`); otherwise
|
|
163
|
+
* code, else one without `errorCode`); otherwise `onError: "skip"` moves on with
|
|
164
|
+
* `{ skipped: true, error }`; otherwise the instance fails.
|
|
151
165
|
*/
|
|
152
166
|
async failStep(step: Step, token: WfToken, error: { readonly code: string; readonly message: string; readonly reason?: string }): Promise<void> {
|
|
153
167
|
const boundaries = (step.graph.boundaries.get(token.nodeId) ?? []).filter(([, node]) => node.kind === "boundaryError");
|
|
154
168
|
const matching = boundaries.find(([, node]) => node.config?.errorCode !== undefined && (node.config.errorCode === error.reason || node.config.errorCode === error.code))
|
|
155
169
|
?? boundaries.find(([, node]) => node.config?.errorCode === undefined);
|
|
156
170
|
if (!matching) {
|
|
171
|
+
if (step.graph.document.nodes[token.nodeId]?.config?.onError === "skip") {
|
|
172
|
+
const skipped = { code: error.code, message: error.message, ...(error.reason === undefined ? {} : { reason: error.reason }) };
|
|
173
|
+
this.history(step, token, token.nodeId, "skipped", skipped);
|
|
174
|
+
await this.complete(step, token.id, { skipped: true, error: skipped });
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
157
177
|
this.fail(step, token, error.code, error.message);
|
|
158
178
|
return;
|
|
159
179
|
}
|
|
@@ -194,6 +214,7 @@ export class WorkflowEngine {
|
|
|
194
214
|
const token = step.tokens.find((t) => t.id === tokenId);
|
|
195
215
|
if (!token || token.status !== "waiting" || step.instance.status !== "running") return false;
|
|
196
216
|
const node = step.graph.document.nodes[token.nodeId];
|
|
217
|
+
if (node?.kind === "eventWait" || node?.kind === "boundaryEvent") step.instance.event = payload;
|
|
197
218
|
if (node?.kind === "eventWait") {
|
|
198
219
|
this.history(step, token, token.nodeId, "event", { event: eventKey });
|
|
199
220
|
return await this.complete(step, tokenId, payload);
|
|
@@ -231,8 +252,12 @@ export class WorkflowEngine {
|
|
|
231
252
|
}
|
|
232
253
|
|
|
233
254
|
/** Retries the failed steps: their tokens are back on their nodes and the instance runs again. */
|
|
234
|
-
async retry(step: Step, actor: WorkflowActor): Promise<void> {
|
|
255
|
+
async retry(step: Step, actor: WorkflowActor, request: WorkflowRequest | null = null): Promise<void> {
|
|
235
256
|
const failed = step.tokens.filter((t) => t.status === "failed");
|
|
257
|
+
step.events.push({
|
|
258
|
+
event: WORKFLOW_INSTANCE_RETRIED as EventToken<unknown>,
|
|
259
|
+
payload: { instanceId: step.instance.id, processKey: step.instance.processKey, partition: step.instance.partition, actor, request },
|
|
260
|
+
});
|
|
236
261
|
step.instance.status = "running";
|
|
237
262
|
step.instance.error = null;
|
|
238
263
|
for (const token of failed) {
|
|
@@ -243,8 +268,12 @@ export class WorkflowEngine {
|
|
|
243
268
|
}
|
|
244
269
|
|
|
245
270
|
/** Closes every live token, job and subscription; the application drops the tasks on `WORKFLOW_TOKEN_CLOSED`. */
|
|
246
|
-
async cancel(step: Step, reason: string | null, actor: WorkflowActor): Promise<void> {
|
|
271
|
+
async cancel(step: Step, reason: string | null, actor: WorkflowActor, request: WorkflowRequest | null = null): Promise<void> {
|
|
247
272
|
const open = step.tokens.filter((t) => LIVE.has(t.status));
|
|
273
|
+
step.events.push({
|
|
274
|
+
event: WORKFLOW_INSTANCE_CANCELLED as EventToken<unknown>,
|
|
275
|
+
payload: { instanceId: step.instance.id, processKey: step.instance.processKey, partition: step.instance.partition, reason, actor, request },
|
|
276
|
+
});
|
|
248
277
|
for (const token of open) {
|
|
249
278
|
token.status = "cancelled";
|
|
250
279
|
token.endedAt = this.now();
|
|
@@ -287,6 +316,7 @@ export class WorkflowEngine {
|
|
|
287
316
|
if (step.instance.status === "running" && !step.tokens.some((t) => LIVE.has(t.status))) {
|
|
288
317
|
step.instance.status = "completed";
|
|
289
318
|
step.instance.endedAt = this.now();
|
|
319
|
+
step.instance.output = outputOf(step.graph.document, step.instance.vars);
|
|
290
320
|
this.history(step, null, null, "completed", null);
|
|
291
321
|
this.finished(step, "completed");
|
|
292
322
|
}
|
|
@@ -308,6 +338,8 @@ export class WorkflowEngine {
|
|
|
308
338
|
// An error boundary that caught a step's failure: it only leads on.
|
|
309
339
|
if (role === "boundary") return this.leave(step, token, this.single(step, token.nodeId));
|
|
310
340
|
switch (node.kind) {
|
|
341
|
+
case "assign":
|
|
342
|
+
return this.assign(step, token, node);
|
|
311
343
|
case "serviceTask":
|
|
312
344
|
return this.arm(step, token, this.waitForTool(step, token));
|
|
313
345
|
case "userTask":
|
|
@@ -326,8 +358,28 @@ export class WorkflowEngine {
|
|
|
326
358
|
}
|
|
327
359
|
}
|
|
328
360
|
|
|
361
|
+
/**
|
|
362
|
+
* An `assign` step: each value sees the ones before it. The history names the
|
|
363
|
+
* variables; their values go there only under `log.variables`, personal ones masked.
|
|
364
|
+
*/
|
|
365
|
+
private assign(step: Step, token: WfToken, node: ProcessNode): WfToken[] {
|
|
366
|
+
const { document } = step.graph;
|
|
367
|
+
const assignments = (node.config?.assignments ?? []) as readonly { readonly var: string; readonly value: Expr }[];
|
|
368
|
+
const personal = masksPersonal(document) ? personalVariables(document) : new Set<string>();
|
|
369
|
+
const reads = masksPersonal(document) ? personalPaths(document) : new Set<string>();
|
|
370
|
+
const logged: Record<string, unknown> = {};
|
|
371
|
+
for (const { var: name, value } of assignments) {
|
|
372
|
+
const result = evaluateExpression(value, dataOf(step.instance, this.now())) ?? null;
|
|
373
|
+
step.instance.vars = { ...step.instance.vars, [name]: result };
|
|
374
|
+
logged[name] = personal.has(name) ? PERSONAL_MASK : maskByMapping(value, result, reads);
|
|
375
|
+
}
|
|
376
|
+
const log = document.settings?.log?.variables === true;
|
|
377
|
+
this.history(step, token, token.nodeId, "assigned", log ? { values: logged } : { vars: assignments.map((item) => item.var) });
|
|
378
|
+
return this.leave(step, token, this.single(step, token.nodeId));
|
|
379
|
+
}
|
|
380
|
+
|
|
329
381
|
private waitForTimer(step: Step, token: WfToken, node: ProcessNode): WfToken[] {
|
|
330
|
-
const dueAt = timerDue(node.config ?? {}, dataOf(step.instance), this.now());
|
|
382
|
+
const dueAt = timerDue(node.config ?? {}, dataOf(step.instance, this.now()), this.now());
|
|
331
383
|
if (dueAt === undefined) {
|
|
332
384
|
this.fail(step, token, "bad_timer", `the timer of ${token.nodeId} has no valid date`);
|
|
333
385
|
return [];
|
|
@@ -345,7 +397,7 @@ export class WorkflowEngine {
|
|
|
345
397
|
if (host.status !== "waiting") return next;
|
|
346
398
|
for (const [id, node] of step.graph.boundaries.get(host.nodeId) ?? []) {
|
|
347
399
|
if (node.kind === "boundaryEvent") {
|
|
348
|
-
const correlation = correlationOf(node.config ?? {}, dataOf(step.instance));
|
|
400
|
+
const correlation = correlationOf(node.config ?? {}, dataOf(step.instance, this.now()));
|
|
349
401
|
if (correlation === undefined) {
|
|
350
402
|
this.fail(step, host, "bad_correlation", `the correlation of ${id} has no value`);
|
|
351
403
|
return [];
|
|
@@ -356,7 +408,7 @@ export class WorkflowEngine {
|
|
|
356
408
|
continue;
|
|
357
409
|
}
|
|
358
410
|
if (node.kind !== "boundaryTimer") continue;
|
|
359
|
-
const dueAt = timerDue(node.config ?? {}, dataOf(step.instance), this.now());
|
|
411
|
+
const dueAt = timerDue(node.config ?? {}, dataOf(step.instance, this.now()), this.now());
|
|
360
412
|
if (dueAt === undefined) {
|
|
361
413
|
this.fail(step, host, "bad_timer", `the timer of ${id} has no valid date`);
|
|
362
414
|
return [];
|
|
@@ -378,7 +430,7 @@ export class WorkflowEngine {
|
|
|
378
430
|
}
|
|
379
431
|
|
|
380
432
|
private waitForEvent(step: Step, token: WfToken, node: ProcessNode): WfToken[] {
|
|
381
|
-
const correlation = correlationOf(node.config ?? {}, dataOf(step.instance));
|
|
433
|
+
const correlation = correlationOf(node.config ?? {}, dataOf(step.instance, this.now()));
|
|
382
434
|
if (correlation === undefined) {
|
|
383
435
|
this.fail(step, token, "bad_correlation", `the correlation of ${token.nodeId} has no value`);
|
|
384
436
|
return [];
|
|
@@ -430,8 +482,8 @@ export class WorkflowEngine {
|
|
|
430
482
|
private choose(step: Step, token: WfToken): WfToken[] {
|
|
431
483
|
const flows = step.graph.outgoing.get(token.nodeId) ?? [];
|
|
432
484
|
if (flows.length === 1) return this.leave(step, token, flows[0]!);
|
|
433
|
-
const data = dataOf(step.instance);
|
|
434
|
-
const chosen = flows.find(([, flow]) => flow.condition !== undefined &&
|
|
485
|
+
const data = dataOf(step.instance, this.now());
|
|
486
|
+
const chosen = flows.find(([, flow]) => flow.condition !== undefined && isTruthy(evaluateExpression(flow.condition, data)))
|
|
435
487
|
?? flows.find(([, flow]) => flow.isDefault === true);
|
|
436
488
|
if (!chosen) {
|
|
437
489
|
this.fail(step, token, "no_branch", "no condition matched and there is no default flow");
|
|
@@ -493,21 +545,12 @@ export class WorkflowEngine {
|
|
|
493
545
|
});
|
|
494
546
|
}
|
|
495
547
|
|
|
496
|
-
/**
|
|
497
|
-
* Events go out after the commit, so a handler never sees a step that rolled
|
|
498
|
-
* back. A failing handler is logged: the step is already saved.
|
|
499
|
-
*/
|
|
548
|
+
/** Events go out after the commit, so a handler never sees a step that rolled back. */
|
|
500
549
|
private publishAfter(transaction: { afterCommit(callback: () => void | Promise<void>): void }, step: Step): void {
|
|
501
550
|
if (step.events.length === 0) return;
|
|
502
551
|
const events = [...step.events];
|
|
503
552
|
transaction.afterCommit(async () => {
|
|
504
|
-
for (const { event, payload } of events)
|
|
505
|
-
try {
|
|
506
|
-
await this.events.publishScoped(this.scope, event, payload);
|
|
507
|
-
} catch (error) {
|
|
508
|
-
this.logger?.error("workflow event handler failed", { event: event.name, instance: step.instance.id, error: error instanceof Error ? error.message : String(error) });
|
|
509
|
-
}
|
|
510
|
-
}
|
|
553
|
+
for (const { event, payload } of events) await this.events.publish(event, payload);
|
|
511
554
|
});
|
|
512
555
|
}
|
|
513
556
|
}
|
|
@@ -537,6 +580,15 @@ export function correlationOf(config: Readonly<Record<string, unknown>>, data: I
|
|
|
537
580
|
return value === undefined || value === null || value === "" || typeof value === "object" ? undefined : String(value);
|
|
538
581
|
}
|
|
539
582
|
|
|
540
|
-
|
|
541
|
-
|
|
583
|
+
/** What expressions read in an instance at this moment of the engine's clock. */
|
|
584
|
+
export function dataOf(instance: WfInstance, now: Date): InstanceData {
|
|
585
|
+
return {
|
|
586
|
+
input: instance.input,
|
|
587
|
+
vars: instance.vars,
|
|
588
|
+
nodes: instance.nodes,
|
|
589
|
+
event: instance.event,
|
|
590
|
+
trigger: instance.trigger,
|
|
591
|
+
run: { id: instance.id, processKey: instance.processKey, version: instance.version, partition: instance.partition, test: instance.test },
|
|
592
|
+
now: now.toISOString(),
|
|
593
|
+
};
|
|
542
594
|
}
|
|
@@ -4,11 +4,9 @@ import { RuntimeDbContext } from "../model/Runtime.dbContext";
|
|
|
4
4
|
import type { WfJob } from "../model/WfJob.model";
|
|
5
5
|
import { Schedules } from "./Schedules";
|
|
6
6
|
import { TimerStep } from "./TimerStep";
|
|
7
|
-
import { ToolStep } from "./ToolStep";
|
|
7
|
+
import { LEASE_MS, ToolStep } from "./ToolStep";
|
|
8
8
|
import { WorkflowEngine } from "./WorkflowEngine";
|
|
9
9
|
|
|
10
|
-
/** How long a claimed job belongs to its worker; if it dies, another one takes the job after that. */
|
|
11
|
-
const LEASE_MS = 5 * 60_000;
|
|
12
10
|
/** A job that keeps failing outside its Tool (the database, a lost version) fails its step after this many tries. */
|
|
13
11
|
const MAX_JOB_ATTEMPTS = 5;
|
|
14
12
|
|
|
@@ -16,7 +14,7 @@ const MAX_JOB_ATTEMPTS = 5;
|
|
|
16
14
|
export type JobFailureReport = (message: string, error: unknown, fields?: Readonly<Record<string, unknown>>) => void;
|
|
17
15
|
|
|
18
16
|
/**
|
|
19
|
-
* Claiming and running jobs of `
|
|
17
|
+
* Claiming and running jobs of `bazis_wf2_run_jobs`: the work of the worker, and
|
|
20
18
|
* of `drain` in tests. Every job runs in its own DI scope.
|
|
21
19
|
*/
|
|
22
20
|
export class WorkflowJobs {
|
|
@@ -19,7 +19,7 @@ export const RUNTIME_ENTITIES = [WfInstance, WfToken, WfJob, WfSubscription, WfH
|
|
|
19
19
|
/** The tables of the Runtime part: bazis creates and checks them in the application database. */
|
|
20
20
|
export const runtimeStore = defineOrmOwnedStoreV1({
|
|
21
21
|
contract: "bazis.orm-owned-store/v1",
|
|
22
|
-
storeKey: "bazis.workflow.runtime",
|
|
22
|
+
storeKey: "bazis.workflow.runtime.v2",
|
|
23
23
|
formatVersion: 1,
|
|
24
|
-
ownedScope: { schema: "public", tablePrefix: "
|
|
24
|
+
ownedScope: { schema: "public", tablePrefix: "bazis_wf2_run_" },
|
|
25
25
|
});
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Column, Entity, ForeignKey, Index, UUID } from "../../../orm";
|
|
2
2
|
import { WfInstance } from "./WfInstance.model";
|
|
3
3
|
|
|
4
|
-
export type HistoryType = "started" | "entered" | "left" | "tool" | "timer" | "event" | "error" | "retried" | "cancelled" | "completed";
|
|
4
|
+
export type HistoryType = "started" | "entered" | "left" | "tool" | "assigned" | "skipped" | "timer" | "event" | "error" | "retried" | "cancelled" | "completed";
|
|
5
5
|
|
|
6
6
|
/** The journal of an instance, append only. Its v7 ids give the order. */
|
|
7
7
|
@ForeignKey(() => WfInstance, { properties: ["instanceId"], onDelete: "cascade" })
|
|
8
8
|
@Index(["instanceId", "id"])
|
|
9
|
-
@Entity({ table: "
|
|
9
|
+
@Entity({ table: "bazis_wf2_run_history" })
|
|
10
10
|
export class WfHistory {
|
|
11
11
|
@UUID({ version: "v7" })
|
|
12
12
|
id = "";
|