@voltro/workflow 0.85.0 → 0.87.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/CHANGELOG.md +5987 -0
- package/THIRD-PARTY-NOTICES.md +1122 -31
- package/dist/admission.js +140 -0
- package/dist/admissionDrainer.js +705 -0
- package/dist/admissionGate.js +115 -0
- package/dist/admissionStore.js +347 -0
- package/dist/admissionSubmission.js +297 -0
- package/dist/admissionTables.js +137 -0
- package/dist/admissionTransaction.js +21 -0
- package/dist/awaitEvent.js +82 -0
- package/dist/budgetHold.js +136 -0
- package/dist/cancelOnPlan.js +75 -0
- package/dist/cancelOnSweep.js +188 -0
- package/dist/cancelPendingStart.js +50 -0
- package/dist/cluster.d.ts +111 -27
- package/dist/cluster.js +12 -2
- package/dist/clusterCron.js +19 -0
- package/dist/clusterDiagnostics.js +20 -0
- package/dist/clusterDiagnosticsSql.js +145 -0
- package/dist/clusterLayer.js +145 -0
- package/dist/clusterMigrationGate.js +61 -0
- package/dist/clusterRunnerLayer.js +31 -0
- package/dist/clusterShardGroups.js +33 -0
- package/dist/clusterTestSuite.d.ts +23 -0
- package/dist/clusterTestSuite.js +209 -179
- package/dist/controlMonitor.js +13 -0
- package/dist/controlRequest.js +367 -0
- package/dist/executionRefusal.js +162 -0
- package/dist/failureHandoff.js +183 -0
- package/dist/failureMessage.js +12 -0
- package/dist/failureRecovery.js +50 -0
- package/dist/failureReportStore.js +60 -0
- package/dist/flowControl.js +142 -0
- package/dist/forwardStartReceipts.js +41 -0
- package/dist/inMemoryLayer.js +7 -0
- package/dist/inMemoryRecorder.js +56 -0
- package/dist/index.d.ts +129 -30
- package/dist/index.js +50 -5
- package/dist/inspectWorkflow.js +101 -0
- package/dist/{journalEncryption-DTyhUzaS.js → journalEncryption.js} +30 -37
- package/dist/journalRoutingGuard.js +50 -0
- package/dist/journalRoutingLayout.js +47 -0
- package/dist/journalRoutingLock.js +39 -0
- package/dist/journalRoutingMaintenance.js +24 -0
- package/dist/journalRoutingMessageStorage.js +32 -0
- package/dist/journalRoutingNamespace.js +23 -0
- package/dist/journalRoutingRuntime.js +33 -0
- package/dist/journalRoutingSharding.js +28 -0
- package/dist/journalRoutingState.js +93 -0
- package/dist/journalRoutingTable.js +11 -0
- package/dist/journalRoutingTransaction.js +16 -0
- package/dist/messages.js +92 -0
- package/dist/nondeterminism.js +51 -0
- package/dist/parentClosure.js +141 -0
- package/dist/parentClosureRecovery.js +54 -0
- package/dist/pendingSubmission.js +85 -0
- package/dist/prepareSubmissionDispatch.js +17 -0
- package/dist/preparedExecution.js +14 -0
- package/dist/primitives.js +225 -2
- package/dist/queuedCancellationRequest.js +364 -0
- package/dist/queuedControl.js +21 -0
- package/dist/recorder.js +11 -0
- package/dist/residentExecutionEvidence.js +64 -0
- package/dist/retryPolicy.js +57 -0
- package/dist/runEventsTable.js +17 -0
- package/dist/runGeneration.js +345 -0
- package/dist/runRecording.js +615 -0
- package/dist/runStepsTable.js +28 -0
- package/dist/runnerStorageGuard.js +29 -0
- package/dist/runsTable.js +42 -0
- package/dist/scheduleDeclaration.js +44 -0
- package/dist/signalHint.js +23 -0
- package/dist/signals.js +70 -0
- package/dist/signalsSuspending.js +57 -0
- package/dist/sqliteWorkflowOwnership.js +26 -0
- package/dist/stalenessSweep.js +191 -0
- package/dist/startContextsTable.js +20 -0
- package/dist/startReceipt.js +143 -0
- package/dist/submissionRecovery.js +122 -0
- package/dist/submissionRejection.js +10 -0
- package/dist/submissionRetry.js +71 -0
- package/dist/suspensionUnavailable.js +9 -0
- package/dist/updates.js +87 -0
- package/dist/watermarkTable.js +9 -0
- package/dist/webCrypto.js +17 -0
- package/dist/workflowDelivery.js +24 -0
- package/dist/workflowExecutorRouting.js +33 -0
- package/dist/workflowJournal.js +124 -0
- package/dist/workflowJournalReset.js +111 -0
- package/dist/workflowReadSelections.js +12 -0
- package/dist/workflowRestartDispatch.js +35 -0
- package/dist/workflowRestartObservation.js +60 -0
- package/dist/workflowRestartPreparation.js +109 -0
- package/dist/workflowRestartReservation.js +427 -0
- package/dist/workflowResumeDispatch.js +30 -0
- package/dist/workflowRuntimeReadiness.js +12 -0
- package/dist/workflowShardRelocation.js +58 -0
- package/dist/workflowShardRelocationSql.js +72 -0
- package/package.json +4 -4
- package/dist/cluster-F_bhqVP7.js +0 -679
- package/dist/primitives-DX04WTWh.js +0 -507
- package/dist/src-C2NEyUM8.js +0 -3925
- package/dist/workflowReadSelections-CZAUjP7f.js +0 -2096
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import "./workflowDelivery.js";
|
|
2
|
+
import { activityPrimaryKey as e, classifyRunReply as t, runEntityAddress as n } from "./workflowJournal.js";
|
|
3
|
+
import { Effect as r, Option as i, Schema as a } from "effect";
|
|
4
|
+
import { SqlClient as o } from "effect/sql";
|
|
5
|
+
import { MessageStorage as s, Sharding as c } from "effect/cluster";
|
|
6
|
+
//#region src/workflowRestartObservation.ts
|
|
7
|
+
var l = a.fromJsonString(a.Record(a.String, a.String)), u = (u) => r.gen(function* () {
|
|
8
|
+
if (!/^[1-9]\d*$/.test(u.delivery.requestId) || u.executionId.length === 0 || u.activities.some((e) => e.name.length === 0 || !Number.isSafeInteger(e.attempt) || e.attempt < 1)) return yield* r.die(/* @__PURE__ */ Error("Restart observation requires an exact delivery and valid activity attempts"));
|
|
9
|
+
let d = (yield* o.SqlClient).withoutTransforms(), f = yield* s.MessageStorage, p = yield* c.Sharding, m = n(u.workflow, u.executionId, p), h = (e, t) => r.gen(function* () {
|
|
10
|
+
let [n] = yield* d`SELECT m.entity_type, m.entity_id, m.tag, m.headers, r.id AS reply_id, r.payload
|
|
11
|
+
FROM cluster_messages m
|
|
12
|
+
LEFT JOIN cluster_replies r ON r.id = m.last_reply_id AND r.request_id = m.id AND r.kind = 0
|
|
13
|
+
WHERE m.id = ${e} AND m.kind = 0`;
|
|
14
|
+
if (n !== void 0) return n.entity_type !== `Workflow/${u.workflow._tag}` || n.entity_id !== u.executionId || n.tag !== t ? yield* r.die(/* @__PURE__ */ Error("Restart observation belongs to another execution or procedure")) : n;
|
|
15
|
+
}), g = yield* h(u.delivery.requestId, "run");
|
|
16
|
+
if (g === void 0) return {
|
|
17
|
+
kind: "unavailable",
|
|
18
|
+
reason: "no-journal"
|
|
19
|
+
};
|
|
20
|
+
if (((yield* r.sync(() => a.decodeUnknownSync(l)(g.headers)))["voltro-workflow-restart"] ?? null) !== u.delivery.restartToken) return {
|
|
21
|
+
kind: "unavailable",
|
|
22
|
+
reason: "delivery-changed"
|
|
23
|
+
};
|
|
24
|
+
if (g.reply_id === null || g.payload === null) return {
|
|
25
|
+
kind: "unavailable",
|
|
26
|
+
reason: "no-reply"
|
|
27
|
+
};
|
|
28
|
+
let _ = t(yield* r.sync(() => JSON.parse(g.payload)));
|
|
29
|
+
if (_ !== "failed") return {
|
|
30
|
+
kind: "unavailable",
|
|
31
|
+
reason: _
|
|
32
|
+
};
|
|
33
|
+
let v = /* @__PURE__ */ new Map([[u.delivery.requestId, String(g.reply_id)]]);
|
|
34
|
+
for (let t of u.activities) {
|
|
35
|
+
let n = yield* f.requestIdForPrimaryKey({
|
|
36
|
+
address: m,
|
|
37
|
+
tag: "activity",
|
|
38
|
+
id: e(t.name, t.attempt)
|
|
39
|
+
});
|
|
40
|
+
if (i.isNone(n)) return {
|
|
41
|
+
kind: "unavailable",
|
|
42
|
+
reason: "activity-not-found"
|
|
43
|
+
};
|
|
44
|
+
let r = String(n.value), a = yield* h(r, "activity");
|
|
45
|
+
if (a?.reply_id == null) return {
|
|
46
|
+
kind: "unavailable",
|
|
47
|
+
reason: "activity-not-found"
|
|
48
|
+
};
|
|
49
|
+
v.set(r, String(a.reply_id));
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
kind: "observed",
|
|
53
|
+
replies: [...v].map(([e, t]) => ({
|
|
54
|
+
requestId: e,
|
|
55
|
+
replyId: t
|
|
56
|
+
}))
|
|
57
|
+
};
|
|
58
|
+
}).pipe(r.provideService(o.SafeIntegers, !0));
|
|
59
|
+
//#endregion
|
|
60
|
+
export { u as observeWorkflowRestartReplies };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { readWorkflowRestartCommandInTransaction as e, readWorkflowRestartReservationInTransaction as t, reserveWorkflowRestartInTransaction as n } from "./workflowRestartReservation.js";
|
|
2
|
+
import { Schema as r } from "effect";
|
|
3
|
+
import { eq as i } from "@voltro/database";
|
|
4
|
+
//#region src/workflowRestartPreparation.ts
|
|
5
|
+
var a = async (t) => {
|
|
6
|
+
if (t.workflow._tag !== t.command.workflowName || t.control.workflowName !== t.command.workflowName) throw Error("Restart preparation requires the matching workflow");
|
|
7
|
+
let n = await t.transaction(async (n) => {
|
|
8
|
+
let a = await e(n, t.command);
|
|
9
|
+
if (a !== void 0) return {
|
|
10
|
+
kind: "accepted",
|
|
11
|
+
request: a
|
|
12
|
+
};
|
|
13
|
+
let [o] = await n.query({
|
|
14
|
+
table: "_voltro_workflow_runs",
|
|
15
|
+
predicate: i("id", t.command.runId),
|
|
16
|
+
lock: "update",
|
|
17
|
+
order: [],
|
|
18
|
+
take: 1,
|
|
19
|
+
skip: void 0,
|
|
20
|
+
projection: ["runnerGeneration"]
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
kind: "captured",
|
|
24
|
+
generation: r.decodeUnknownSync(r.NonEmptyString)(o?.runnerGeneration)
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
return n.kind === "accepted" ? {
|
|
28
|
+
kind: "reserved",
|
|
29
|
+
request: n.request,
|
|
30
|
+
reused: !0
|
|
31
|
+
} : s({
|
|
32
|
+
...t,
|
|
33
|
+
command: {
|
|
34
|
+
...t.command,
|
|
35
|
+
generation: n.generation
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}, o = r.Struct({
|
|
39
|
+
requestId: r.NonEmptyString,
|
|
40
|
+
restartToken: r.NullOr(r.NonEmptyString)
|
|
41
|
+
}), s = async (e) => {
|
|
42
|
+
let { command: a } = e;
|
|
43
|
+
if (e.workflow._tag !== a.workflowName || e.control.workflowName !== a.workflowName) throw Error("Restart preparation requires the matching workflow");
|
|
44
|
+
let s = await e.transaction(async (n) => {
|
|
45
|
+
let s = await t(n, a);
|
|
46
|
+
if (s !== void 0) return {
|
|
47
|
+
kind: "accepted",
|
|
48
|
+
request: s
|
|
49
|
+
};
|
|
50
|
+
let [c] = await n.query({
|
|
51
|
+
table: "_voltro_workflow_runs",
|
|
52
|
+
predicate: i("id", a.runId),
|
|
53
|
+
lock: "update",
|
|
54
|
+
order: [],
|
|
55
|
+
take: 1,
|
|
56
|
+
skip: void 0,
|
|
57
|
+
projection: void 0
|
|
58
|
+
});
|
|
59
|
+
if (c === void 0 || c.tag !== a.workflowName || c.runnerGeneration !== a.generation || c.status !== "failed" || c.discardedAt != null || typeof c.executionId != "string" || c.executionId.length === 0) throw Error("Restart preparation requires its failed resident generation");
|
|
60
|
+
if ((r.decodeUnknownSync(r.NullOr(r.Struct({ tenantId: r.NullOr(r.NonEmptyString) })))(c.subject ?? null)?.tenantId ?? null) !== a.tenantId) throw Error("Restart preparation belongs to another tenant");
|
|
61
|
+
return {
|
|
62
|
+
kind: "observe",
|
|
63
|
+
executionId: c.executionId,
|
|
64
|
+
delivery: r.decodeUnknownSync(o)(c.engineDelivery),
|
|
65
|
+
activities: await e.readActivities(n)
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
if (s.kind === "accepted") return {
|
|
69
|
+
kind: "reserved",
|
|
70
|
+
request: s.request,
|
|
71
|
+
reused: !0
|
|
72
|
+
};
|
|
73
|
+
let c = await e.observe({
|
|
74
|
+
workflow: e.workflow,
|
|
75
|
+
executionId: s.executionId,
|
|
76
|
+
delivery: s.delivery,
|
|
77
|
+
activities: s.activities
|
|
78
|
+
});
|
|
79
|
+
return e.transaction(async (r) => {
|
|
80
|
+
let o = await t(r, a);
|
|
81
|
+
if (o !== void 0) return {
|
|
82
|
+
kind: "reserved",
|
|
83
|
+
request: o,
|
|
84
|
+
reused: !0
|
|
85
|
+
};
|
|
86
|
+
if (c.kind === "unavailable") return c;
|
|
87
|
+
let [l] = await r.query({
|
|
88
|
+
table: "_voltro_workflow_runs",
|
|
89
|
+
predicate: i("id", a.runId),
|
|
90
|
+
lock: "update",
|
|
91
|
+
order: [],
|
|
92
|
+
take: 1,
|
|
93
|
+
skip: void 0,
|
|
94
|
+
projection: ["executionId"]
|
|
95
|
+
});
|
|
96
|
+
if (l?.executionId !== s.executionId) throw Error("Restart execution changed during observation");
|
|
97
|
+
return n(r, {
|
|
98
|
+
...a,
|
|
99
|
+
token: e.createToken(),
|
|
100
|
+
delivery: s.delivery,
|
|
101
|
+
replies: c.replies,
|
|
102
|
+
control: e.control,
|
|
103
|
+
paused: await e.readPaused(r),
|
|
104
|
+
now: e.now
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
//#endregion
|
|
109
|
+
export { s as prepareWorkflowRestart, a as prepareWorkflowRestartCommand };
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { resolveAdmissionKeys as e } from "./admission.js";
|
|
2
|
+
import { decodePendingPayload as t, evaluateAdmission as n, recordAdmission as r } from "./admissionStore.js";
|
|
3
|
+
import { ownedJournalRoutingPromise as i } from "./journalRoutingGuard.js";
|
|
4
|
+
import { resetExpectedWorkflowReplies as a } from "./workflowJournalReset.js";
|
|
5
|
+
import { Effect as o, Schema as s } from "effect";
|
|
6
|
+
import { WorkflowRestartObservationSchema as c, WorkflowRestartSelectionSchema as l, WorkflowRestartTargetSchema as u } from "@voltro/protocol";
|
|
7
|
+
import { and as d, derivedRowId as f, eq as p, id as m, integer as h, json as g, or as _, table as v, text as y, timestamp as b } from "@voltro/database";
|
|
8
|
+
//#region src/workflowRestartReservation.ts
|
|
9
|
+
var x = v("_voltro_workflow_restart_requests", {
|
|
10
|
+
id: m({ prefix: "wfrs" }),
|
|
11
|
+
runId: y(),
|
|
12
|
+
workflowName: y(),
|
|
13
|
+
executionId: y(),
|
|
14
|
+
generation: y(),
|
|
15
|
+
token: y(),
|
|
16
|
+
ledgerId: y(),
|
|
17
|
+
tenantId: y().nullable(),
|
|
18
|
+
delivery: g(),
|
|
19
|
+
replies: g(),
|
|
20
|
+
selection: g().nullable(),
|
|
21
|
+
eviction: g().nullable(),
|
|
22
|
+
requestedAt: b(),
|
|
23
|
+
dispatchAttempts: h().default(0),
|
|
24
|
+
enteredGeneration: y().nullable()
|
|
25
|
+
}).unique(["runId", "generation"]).unique(["runId", "token"]).nonReactive(), S = s.NonEmptyString, C = s.Struct({
|
|
26
|
+
requestId: S,
|
|
27
|
+
restartToken: s.NullOr(S)
|
|
28
|
+
}), w = s.Struct({
|
|
29
|
+
requestId: S,
|
|
30
|
+
replyId: S
|
|
31
|
+
}), T = l, E = v("_voltro_workflow_restart_withdrawals", {
|
|
32
|
+
id: m({ prefix: "wfrw" }),
|
|
33
|
+
runId: y(),
|
|
34
|
+
workflowName: y(),
|
|
35
|
+
tenantId: y().nullable(),
|
|
36
|
+
selection: g(),
|
|
37
|
+
withdrawnAt: b()
|
|
38
|
+
}).nonReactive(), D = s.Struct({
|
|
39
|
+
id: s.NonEmptyString,
|
|
40
|
+
runId: s.NonEmptyString,
|
|
41
|
+
workflowName: s.NonEmptyString,
|
|
42
|
+
tenantId: s.NullOr(s.NonEmptyString),
|
|
43
|
+
selection: T,
|
|
44
|
+
withdrawnAt: s.Date
|
|
45
|
+
}), O = async (e, t) => {
|
|
46
|
+
let n = s.decodeUnknownSync(u)(t), [r] = await e.query({
|
|
47
|
+
table: E.tableName,
|
|
48
|
+
predicate: p("id", n.requestId),
|
|
49
|
+
lock: "update",
|
|
50
|
+
order: [],
|
|
51
|
+
take: 1,
|
|
52
|
+
skip: void 0,
|
|
53
|
+
projection: void 0
|
|
54
|
+
});
|
|
55
|
+
if (r === void 0) return;
|
|
56
|
+
let i = s.decodeUnknownSync(D)(r);
|
|
57
|
+
if (i.runId !== n.runId) throw Error("Restart identity belongs to a different command");
|
|
58
|
+
if (!Number.isFinite(i.withdrawnAt.getTime())) throw Error("Invalid restart withdrawal timestamp");
|
|
59
|
+
return i;
|
|
60
|
+
}, k = async (e, t) => {
|
|
61
|
+
if (await O(e, t) !== void 0) throw Error("Restart request was permanently withdrawn");
|
|
62
|
+
}, A = s.Struct({
|
|
63
|
+
id: S,
|
|
64
|
+
runId: S,
|
|
65
|
+
workflowName: S,
|
|
66
|
+
executionId: S,
|
|
67
|
+
generation: S,
|
|
68
|
+
token: S,
|
|
69
|
+
ledgerId: S,
|
|
70
|
+
tenantId: s.NullOr(S),
|
|
71
|
+
delivery: C,
|
|
72
|
+
replies: s.Array(w),
|
|
73
|
+
selection: T,
|
|
74
|
+
eviction: s.NullOr(s.Struct({
|
|
75
|
+
executionId: S,
|
|
76
|
+
runId: s.String
|
|
77
|
+
})),
|
|
78
|
+
requestedAt: s.Date,
|
|
79
|
+
dispatchAttempts: s.Number.check(s.isInt(), s.isGreaterThanOrEqualTo(0)),
|
|
80
|
+
enteredGeneration: s.NullOr(S)
|
|
81
|
+
}), j = (e, t) => e.kind === t.kind && (e.kind === "failed-steps" || t.kind === "from-step" && e.stepName === t.stepName), M = async (e, t) => {
|
|
82
|
+
let n = s.decodeUnknownSync(u)(t), [r] = await e.query({
|
|
83
|
+
table: x.tableName,
|
|
84
|
+
predicate: p("id", n.requestId),
|
|
85
|
+
lock: "update",
|
|
86
|
+
order: [],
|
|
87
|
+
take: 1,
|
|
88
|
+
skip: void 0,
|
|
89
|
+
projection: void 0
|
|
90
|
+
});
|
|
91
|
+
if (r === void 0) return;
|
|
92
|
+
let i = s.decodeUnknownSync(A)(r);
|
|
93
|
+
if (i.runId !== n.runId) throw Error("Restart identity belongs to a different command");
|
|
94
|
+
return i;
|
|
95
|
+
}, N = async (e, t) => {
|
|
96
|
+
let n = await O(e, t);
|
|
97
|
+
if (n !== void 0) return s.decodeUnknownSync(c)({
|
|
98
|
+
...t,
|
|
99
|
+
kind: "withdrawn",
|
|
100
|
+
workflowName: n.workflowName,
|
|
101
|
+
selection: n.selection,
|
|
102
|
+
withdrawnAt: n.withdrawnAt.getTime()
|
|
103
|
+
});
|
|
104
|
+
let r = await M(e, t);
|
|
105
|
+
if (r === void 0) return s.decodeUnknownSync(c)({
|
|
106
|
+
...t,
|
|
107
|
+
kind: "not-found"
|
|
108
|
+
});
|
|
109
|
+
let i = {
|
|
110
|
+
requestId: r.id,
|
|
111
|
+
runId: r.runId,
|
|
112
|
+
workflowName: r.workflowName,
|
|
113
|
+
executionId: r.executionId,
|
|
114
|
+
originalGeneration: r.generation,
|
|
115
|
+
selection: r.selection,
|
|
116
|
+
requestedAt: r.requestedAt.getTime(),
|
|
117
|
+
dispatchAttempts: r.dispatchAttempts
|
|
118
|
+
};
|
|
119
|
+
return s.decodeUnknownSync(c)(r.enteredGeneration === null ? {
|
|
120
|
+
...i,
|
|
121
|
+
kind: "pending"
|
|
122
|
+
} : {
|
|
123
|
+
...i,
|
|
124
|
+
kind: "entered",
|
|
125
|
+
enteredGeneration: r.enteredGeneration
|
|
126
|
+
});
|
|
127
|
+
}, P = async (e, t) => {
|
|
128
|
+
for (let e of [
|
|
129
|
+
t.id,
|
|
130
|
+
t.runId,
|
|
131
|
+
t.workflowName
|
|
132
|
+
]) s.decodeUnknownSync(S)(e);
|
|
133
|
+
if (t.id.length > 64) throw Error("Invalid restart request identity");
|
|
134
|
+
s.decodeUnknownSync(s.NullOr(S))(t.tenantId);
|
|
135
|
+
let n = s.decodeUnknownSync(T)(t.selection);
|
|
136
|
+
await k(e, {
|
|
137
|
+
requestId: t.id,
|
|
138
|
+
runId: t.runId
|
|
139
|
+
});
|
|
140
|
+
let r = await M(e, {
|
|
141
|
+
requestId: t.id,
|
|
142
|
+
runId: t.runId
|
|
143
|
+
});
|
|
144
|
+
if (r !== void 0) {
|
|
145
|
+
if (r.runId !== t.runId || r.workflowName !== t.workflowName || r.tenantId !== t.tenantId || !j(r.selection, n)) throw Error("Restart identity belongs to a different command");
|
|
146
|
+
return r;
|
|
147
|
+
}
|
|
148
|
+
}, F = async (e, t) => {
|
|
149
|
+
let n = s.decodeUnknownSync(D)({
|
|
150
|
+
...t,
|
|
151
|
+
withdrawnAt: new Date(t.now)
|
|
152
|
+
});
|
|
153
|
+
if (s.decodeUnknownSync(u)({
|
|
154
|
+
requestId: n.id,
|
|
155
|
+
runId: n.runId
|
|
156
|
+
}), !Number.isFinite(n.withdrawnAt.getTime())) throw Error("Invalid restart withdrawal timestamp");
|
|
157
|
+
let r = await O(e, {
|
|
158
|
+
requestId: n.id,
|
|
159
|
+
runId: n.runId
|
|
160
|
+
});
|
|
161
|
+
if (r !== void 0) {
|
|
162
|
+
if (r.workflowName !== n.workflowName || r.tenantId !== n.tenantId || !j(r.selection, n.selection)) throw Error("Restart identity belongs to a different command");
|
|
163
|
+
return {
|
|
164
|
+
kind: "withdrawn",
|
|
165
|
+
withdrawal: r,
|
|
166
|
+
reused: !0
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
let i = await P(e, n);
|
|
170
|
+
if (i !== void 0) return {
|
|
171
|
+
kind: "reserved",
|
|
172
|
+
request: i
|
|
173
|
+
};
|
|
174
|
+
let [a] = await e.query({
|
|
175
|
+
table: "_voltro_workflow_runs",
|
|
176
|
+
predicate: p("id", n.runId),
|
|
177
|
+
lock: "update",
|
|
178
|
+
order: [],
|
|
179
|
+
take: 1,
|
|
180
|
+
skip: void 0,
|
|
181
|
+
projection: void 0
|
|
182
|
+
});
|
|
183
|
+
if (a === void 0 || a.tag !== n.workflowName) throw Error("Restart withdrawal requires its resident workflow");
|
|
184
|
+
if ((s.decodeUnknownSync(s.NullOr(s.Struct({ tenantId: s.NullOr(S) })))(a.subject ?? null)?.tenantId ?? null) !== n.tenantId) throw Error("Restart withdrawal belongs to another resident tenant");
|
|
185
|
+
return await e.insert(E.tableName, n), {
|
|
186
|
+
kind: "withdrawn",
|
|
187
|
+
withdrawal: n,
|
|
188
|
+
reused: !1
|
|
189
|
+
};
|
|
190
|
+
}, I = async (e, t) => {
|
|
191
|
+
s.decodeUnknownSync(S)(t.generation);
|
|
192
|
+
let n = await P(e, t);
|
|
193
|
+
if (n !== void 0 && n.generation !== t.generation) throw Error("Restart identity belongs to a different command");
|
|
194
|
+
return n;
|
|
195
|
+
}, L = async (i, a) => {
|
|
196
|
+
for (let e of [
|
|
197
|
+
a.id,
|
|
198
|
+
a.runId,
|
|
199
|
+
a.generation,
|
|
200
|
+
a.token
|
|
201
|
+
]) s.decodeUnknownSync(S)(e);
|
|
202
|
+
if (a.id.length > 64 || !Number.isFinite(new Date(a.now).getTime())) throw Error("Invalid restart request identity or timestamp");
|
|
203
|
+
await k(i, {
|
|
204
|
+
requestId: a.id,
|
|
205
|
+
runId: a.runId
|
|
206
|
+
});
|
|
207
|
+
let o = s.decodeUnknownSync(C)(a.delivery), c = s.decodeUnknownSync(T)(a.selection);
|
|
208
|
+
if (o.restartToken === a.token) throw Error("Restart requires a new delivery token");
|
|
209
|
+
let l = s.decodeUnknownSync(s.Array(w))(a.replies), u = /* @__PURE__ */ new Map();
|
|
210
|
+
for (let e of l) {
|
|
211
|
+
if (!/^[1-9]\d*$/.test(e.requestId) || !/^[1-9]\d*$/.test(e.replyId) || u.has(e.requestId)) throw Error("Restart requires distinct, exact journal reply identities");
|
|
212
|
+
u.set(e.requestId, e.replyId);
|
|
213
|
+
}
|
|
214
|
+
if (!u.has(o.requestId)) throw Error("Restart selection must include its run reply");
|
|
215
|
+
let [f] = await i.query({
|
|
216
|
+
table: x.tableName,
|
|
217
|
+
predicate: p("id", a.id),
|
|
218
|
+
lock: "update",
|
|
219
|
+
order: [],
|
|
220
|
+
take: 1,
|
|
221
|
+
skip: void 0,
|
|
222
|
+
projection: void 0
|
|
223
|
+
});
|
|
224
|
+
if (f !== void 0) {
|
|
225
|
+
let e = s.decodeUnknownSync(A)(f);
|
|
226
|
+
if (e.runId !== a.runId || e.generation !== a.generation || e.token !== a.token || e.workflowName !== a.control.workflowName || e.tenantId !== a.tenantId || e.delivery.requestId !== o.requestId || e.delivery.restartToken !== o.restartToken || !j(e.selection, c) || e.replies.length !== u.size || e.replies.some((e) => u.get(e.requestId) !== e.replyId)) throw Error("Restart identity belongs to a different request");
|
|
227
|
+
return {
|
|
228
|
+
kind: "reserved",
|
|
229
|
+
request: e,
|
|
230
|
+
reused: !0
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
let [m] = await i.query({
|
|
234
|
+
table: "_voltro_workflow_runs",
|
|
235
|
+
predicate: p("id", a.runId),
|
|
236
|
+
lock: "update",
|
|
237
|
+
order: [],
|
|
238
|
+
take: 1,
|
|
239
|
+
skip: void 0,
|
|
240
|
+
projection: void 0
|
|
241
|
+
});
|
|
242
|
+
if (m === void 0 || m.tag !== a.control.workflowName || m.runnerGeneration !== a.generation || m.status !== "failed" || m.discardedAt != null || typeof m.executionId != "string" || m.executionId.length === 0) throw Error("Restart requires its undiscarded failed resident generation");
|
|
243
|
+
if ((s.decodeUnknownSync(s.NullOr(s.Struct({ tenantId: s.NullOr(S) })))(m.subject ?? null)?.tenantId ?? null) !== a.tenantId) throw Error("Restart belongs to another resident tenant");
|
|
244
|
+
let h = s.decodeUnknownSync(C)(m.engineDelivery);
|
|
245
|
+
if (h.requestId !== o.requestId || h.restartToken !== o.restartToken) throw Error("Restart journal delivery does not belong to its resident generation");
|
|
246
|
+
let [g] = await i.query({
|
|
247
|
+
table: x.tableName,
|
|
248
|
+
predicate: d(p("runId", a.runId), _(p("generation", a.generation), p("token", a.token))),
|
|
249
|
+
lock: "update",
|
|
250
|
+
order: [],
|
|
251
|
+
take: 1,
|
|
252
|
+
skip: void 0,
|
|
253
|
+
projection: ["id", "generation"]
|
|
254
|
+
});
|
|
255
|
+
if (g !== void 0) {
|
|
256
|
+
if (g.generation !== a.generation) throw Error("Restart delivery token was already used by this run");
|
|
257
|
+
return { kind: "already-requested" };
|
|
258
|
+
}
|
|
259
|
+
let v = t(i, a.control.workflowName, m.payload), y = e(a.control, v), b = await n({
|
|
260
|
+
store: i,
|
|
261
|
+
control: a.control,
|
|
262
|
+
keys: y,
|
|
263
|
+
paused: a.paused,
|
|
264
|
+
now: a.now,
|
|
265
|
+
reconsidering: !0
|
|
266
|
+
});
|
|
267
|
+
if (b.kind !== "admit") return {
|
|
268
|
+
kind: "not-admitted",
|
|
269
|
+
decision: b
|
|
270
|
+
};
|
|
271
|
+
let E = await r({
|
|
272
|
+
store: i,
|
|
273
|
+
control: a.control,
|
|
274
|
+
keys: y,
|
|
275
|
+
outcome: "admitted",
|
|
276
|
+
mode: "restart",
|
|
277
|
+
reason: b.reason,
|
|
278
|
+
collapsed: 1,
|
|
279
|
+
waitedMs: 0,
|
|
280
|
+
executionId: null,
|
|
281
|
+
runId: null,
|
|
282
|
+
tenantId: a.tenantId,
|
|
283
|
+
now: a.now
|
|
284
|
+
});
|
|
285
|
+
if (E === void 0) throw Error("Restart has no admission reservation");
|
|
286
|
+
let D = s.decodeUnknownSync(A)({
|
|
287
|
+
id: a.id,
|
|
288
|
+
runId: a.runId,
|
|
289
|
+
workflowName: a.control.workflowName,
|
|
290
|
+
executionId: m.executionId,
|
|
291
|
+
generation: a.generation,
|
|
292
|
+
token: a.token,
|
|
293
|
+
ledgerId: E,
|
|
294
|
+
tenantId: a.tenantId,
|
|
295
|
+
delivery: o,
|
|
296
|
+
replies: l,
|
|
297
|
+
selection: c,
|
|
298
|
+
eviction: b.evict ?? null,
|
|
299
|
+
requestedAt: new Date(a.now),
|
|
300
|
+
dispatchAttempts: 0,
|
|
301
|
+
enteredGeneration: null
|
|
302
|
+
});
|
|
303
|
+
return await i.insert(x.tableName, D), {
|
|
304
|
+
kind: "reserved",
|
|
305
|
+
request: D,
|
|
306
|
+
reused: !1
|
|
307
|
+
};
|
|
308
|
+
}, R = async (e, t) => {
|
|
309
|
+
let [n] = await e.query({
|
|
310
|
+
table: x.tableName,
|
|
311
|
+
predicate: p("id", t),
|
|
312
|
+
lock: "update",
|
|
313
|
+
order: [],
|
|
314
|
+
take: 1,
|
|
315
|
+
skip: void 0,
|
|
316
|
+
projection: void 0
|
|
317
|
+
});
|
|
318
|
+
if (n === void 0) throw Error("Restart dispatch has no resident request");
|
|
319
|
+
let r = s.decodeUnknownSync(A)(n);
|
|
320
|
+
if (r.enteredGeneration !== null) throw Error("Restart request has already entered its resident generation");
|
|
321
|
+
let [i] = await e.query({
|
|
322
|
+
table: "_voltro_workflow_admissions",
|
|
323
|
+
predicate: p("id", r.ledgerId),
|
|
324
|
+
lock: "update",
|
|
325
|
+
order: [],
|
|
326
|
+
take: 1,
|
|
327
|
+
skip: void 0,
|
|
328
|
+
projection: void 0
|
|
329
|
+
});
|
|
330
|
+
if (i === void 0 || i.outcome !== "admitted" || i.releasedAt !== null || i.workflowName !== r.workflowName || i.tenantId !== r.tenantId || i.executionId !== null || i.runId !== null) throw Error("Restart dispatch has no matching held resident reservation");
|
|
331
|
+
let [a] = await e.query({
|
|
332
|
+
table: "_voltro_workflow_runs",
|
|
333
|
+
predicate: p("id", r.runId),
|
|
334
|
+
lock: "update",
|
|
335
|
+
order: [],
|
|
336
|
+
take: 1,
|
|
337
|
+
skip: void 0,
|
|
338
|
+
projection: void 0
|
|
339
|
+
});
|
|
340
|
+
if (a === void 0 || a.tag !== r.workflowName || a.executionId !== r.executionId || a.runnerGeneration !== r.generation || a.status !== "failed" || a.discardedAt != null) throw Error("Restart dispatch no longer owns its failed resident generation");
|
|
341
|
+
let o = s.decodeUnknownSync(C)(a.engineDelivery);
|
|
342
|
+
if (o.requestId !== r.delivery.requestId || o.restartToken !== r.delivery.restartToken) throw Error("Restart dispatch no longer owns its journal delivery");
|
|
343
|
+
return {
|
|
344
|
+
request: r,
|
|
345
|
+
run: a
|
|
346
|
+
};
|
|
347
|
+
}, z = async (e, t) => {
|
|
348
|
+
let { request: n } = await R(e, t), r = n.dispatchAttempts + 1;
|
|
349
|
+
if (!Number.isSafeInteger(r)) throw Error("Restart dispatch attempt counter exhausted");
|
|
350
|
+
if (await e.update(x.tableName, t, { dispatchAttempts: r }) === null) throw Error("Restart request disappeared before dispatch");
|
|
351
|
+
return {
|
|
352
|
+
...n,
|
|
353
|
+
dispatchAttempts: r
|
|
354
|
+
};
|
|
355
|
+
}, B = (e) => o.gen(function* () {
|
|
356
|
+
let { request: t } = yield* i(() => e.transaction((t) => R(t, e.requestId)));
|
|
357
|
+
if (!(yield* i(() => e.acquireCapacity(t)))) return { kind: "waiting-capacity" };
|
|
358
|
+
let n = yield* i(() => e.transaction((e) => z(e, t.id)));
|
|
359
|
+
return yield* a(n.replies, {
|
|
360
|
+
requestId: n.delivery.requestId,
|
|
361
|
+
previousToken: n.delivery.restartToken,
|
|
362
|
+
token: n.token,
|
|
363
|
+
workflowName: n.workflowName,
|
|
364
|
+
executionId: n.executionId
|
|
365
|
+
});
|
|
366
|
+
}), V = async (e, t) => {
|
|
367
|
+
for (let e of [
|
|
368
|
+
t.runId,
|
|
369
|
+
t.workflowName,
|
|
370
|
+
t.executionId,
|
|
371
|
+
t.generation
|
|
372
|
+
]) s.decodeUnknownSync(S)(e);
|
|
373
|
+
if (!Number.isFinite(new Date(t.now).getTime())) throw Error("Restart entry requires a valid timestamp");
|
|
374
|
+
let n = s.decodeUnknownSync(C)(t.delivery);
|
|
375
|
+
if (n.restartToken === null) throw Error("Restart entry has no delivery token");
|
|
376
|
+
let [r] = await e.query({
|
|
377
|
+
table: x.tableName,
|
|
378
|
+
predicate: d(p("runId", t.runId), p("token", n.restartToken)),
|
|
379
|
+
lock: "update",
|
|
380
|
+
order: [],
|
|
381
|
+
take: 1,
|
|
382
|
+
skip: void 0,
|
|
383
|
+
projection: ["id"]
|
|
384
|
+
});
|
|
385
|
+
if (r === void 0) throw Error("Restart delivery has no matching resident request");
|
|
386
|
+
let { request: i, run: a } = await R(e, s.decodeUnknownSync(S)(r.id));
|
|
387
|
+
if (i.runId !== t.runId || i.workflowName !== t.workflowName || i.executionId !== t.executionId || i.generation === t.generation || i.delivery.requestId !== n.requestId || i.token !== n.restartToken) throw Error("Restart entry does not match its reserved delivery");
|
|
388
|
+
if (i.dispatchAttempts === 0) throw Error("Restart entry has no committed dispatch attempt");
|
|
389
|
+
if (await e.update("_voltro_workflow_admissions", i.ledgerId, {
|
|
390
|
+
executionId: i.executionId,
|
|
391
|
+
runId: i.runId
|
|
392
|
+
}) === null) throw Error("Restart reservation disappeared during entry");
|
|
393
|
+
if (await e.update(x.tableName, i.id, { enteredGeneration: t.generation }) === null) throw Error("Restart request disappeared during entry");
|
|
394
|
+
let o = await e.update("_voltro_workflow_runs", i.runId, {
|
|
395
|
+
runnerGeneration: t.generation,
|
|
396
|
+
engineDelivery: n,
|
|
397
|
+
runnerEnteredAt: new Date(t.now),
|
|
398
|
+
reclaimCount: 0,
|
|
399
|
+
status: "running",
|
|
400
|
+
errorTag: null,
|
|
401
|
+
errorMessage: null,
|
|
402
|
+
output: null,
|
|
403
|
+
completedAt: null,
|
|
404
|
+
durationMs: null
|
|
405
|
+
});
|
|
406
|
+
if (o === null) throw Error("Restart run disappeared during entry");
|
|
407
|
+
return {
|
|
408
|
+
row: o,
|
|
409
|
+
previous: a,
|
|
410
|
+
event: await e.insert("_voltro_workflow_run_events", {
|
|
411
|
+
id: f("wfev", JSON.stringify(["restart", i.id])),
|
|
412
|
+
runId: i.runId,
|
|
413
|
+
eventType: "run-redriven",
|
|
414
|
+
occurredAt: new Date(t.now),
|
|
415
|
+
stepName: null,
|
|
416
|
+
attempt: null,
|
|
417
|
+
payload: {
|
|
418
|
+
requestId: i.id,
|
|
419
|
+
executionId: i.executionId,
|
|
420
|
+
previousGeneration: i.generation,
|
|
421
|
+
generation: t.generation
|
|
422
|
+
}
|
|
423
|
+
})
|
|
424
|
+
};
|
|
425
|
+
};
|
|
426
|
+
//#endregion
|
|
427
|
+
export { z as beginWorkflowRestartDispatchInTransaction, V as consumeWorkflowRestartInTransaction, P as readWorkflowRestartCommandInTransaction, N as readWorkflowRestartObservationInTransaction, I as readWorkflowRestartReservationInTransaction, L as reserveWorkflowRestartInTransaction, B as resetReservedWorkflowJournal, F as withdrawWorkflowRestartInTransaction, x as workflowRestartRequestsTable, E as workflowRestartWithdrawalsTable };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { beginWorkflowControlDispatchInTransaction as e, bindWorkflowResumeJournalInTransaction as t } from "./controlRequest.js";
|
|
2
|
+
import { JournalRoutingGuard as n, ownedJournalRoutingPromise as r, protectJournalRoutingEffect as i } from "./journalRoutingGuard.js";
|
|
3
|
+
import { observeWorkflowSuspension as a, resetExpectedWorkflowSuspension as o } from "./workflowJournalReset.js";
|
|
4
|
+
import { Context as s, Effect as c, Option as l } from "effect";
|
|
5
|
+
import { SqlClient as u } from "effect/sql";
|
|
6
|
+
import { Sharding as d } from "effect/cluster";
|
|
7
|
+
//#region src/workflowResumeDispatch.ts
|
|
8
|
+
var f = (n) => c.gen(function* () {
|
|
9
|
+
let i = yield* c.context(), s = yield* r(() => n.transaction((e) => t(e, n.requestId, (e) => c.runPromiseWith(i)(a(e)))));
|
|
10
|
+
if (s.kind !== "bound") return s;
|
|
11
|
+
let l = yield* r(() => n.transaction((t) => e(t, n.requestId)));
|
|
12
|
+
if (l.kind !== "dispatch") return l;
|
|
13
|
+
let { request: u } = l;
|
|
14
|
+
if (u.operation !== "resume" || u.resumeJournal === null) return yield* c.die(/* @__PURE__ */ Error("Resume dispatch requires its committed suspension binding"));
|
|
15
|
+
let f = yield* o({
|
|
16
|
+
workflowName: u.workflowName,
|
|
17
|
+
executionId: u.executionId,
|
|
18
|
+
...u.resumeJournal
|
|
19
|
+
});
|
|
20
|
+
return yield* (yield* d.Sharding).pollStorage, {
|
|
21
|
+
kind: "attempted",
|
|
22
|
+
request: u,
|
|
23
|
+
journal: f
|
|
24
|
+
};
|
|
25
|
+
}), p = class extends s.Service()("@voltro/workflow/WorkflowResumeDispatcher") {}, m = c.gen(function* () {
|
|
26
|
+
let e = l.getOrUndefined(yield* c.serviceOption(n)), t = yield* u.SqlClient, r = yield* d.Sharding;
|
|
27
|
+
return p.of({ dispatch: (n) => i(e, f(n)).pipe(c.provideService(u.SqlClient, t), c.provideService(d.Sharding, r)) });
|
|
28
|
+
});
|
|
29
|
+
//#endregion
|
|
30
|
+
export { p as WorkflowResumeDispatcher, f as dispatchWorkflowResume, m as makeWorkflowResumeDispatcher };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { JournalRoutingGuard as e } from "./journalRoutingGuard.js";
|
|
2
|
+
import { Context as t, Effect as n, Option as r } from "effect";
|
|
3
|
+
import { Sharding as i } from "effect/cluster";
|
|
4
|
+
//#region src/workflowRuntimeReadiness.ts
|
|
5
|
+
var a = class extends t.Service()("@voltro/workflow/WorkflowRuntimeReadiness") {}, o = n.gen(function* () {
|
|
6
|
+
let t = yield* i.Sharding, a = r.getOrUndefined(yield* n.serviceOption(e)), o = !0;
|
|
7
|
+
return yield* n.addFinalizer(() => n.sync(() => {
|
|
8
|
+
o = !1;
|
|
9
|
+
})), { isReady: () => o && !a?.isClosed() && !n.runSync(t.isShutdown) };
|
|
10
|
+
}), s = n.map(n.serviceOption(a), (e) => r.isSome(e) ? e.value.isReady : () => !0);
|
|
11
|
+
//#endregion
|
|
12
|
+
export { a as WorkflowRuntimeReadiness, s as captureWorkflowRuntimeReadiness, o as makeWorkflowRuntimeReadiness };
|