@tomflow/proflow-task-orchestration 0.1.10 → 0.1.12
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 +14 -0
- package/dist/deployment/adapter.d.ts +3 -3
- package/dist/deployment/descriptor.d.ts +1 -1
- package/dist/deployment/descriptor.js +1 -1
- package/dist/src/contracts.js +10 -2
- package/dist/src/model.d.ts +2 -0
- package/dist/src/model.js +5 -0
- package/dist/src/services.d.ts +1 -0
- package/dist/src/services.js +57 -7
- package/dist/src/transitions.js +1 -1
- package/package.json +2 -2
- package/proflow.module.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @tomflow/proflow-task-orchestration
|
|
2
2
|
|
|
3
|
+
## 0.1.12
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Prune empty Task-level document recovery directories after recovery journals converge so durable recovery leaves no orphan filesystem residue.
|
|
8
|
+
|
|
9
|
+
- Harden Task-scoped Worker identity and generation semantics so reopened or invalidated executions cannot reuse stale Worker/conversation ownership or a previously consumed run generation.
|
|
10
|
+
|
|
11
|
+
## 0.1.11
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Release the verified Real-3 B1 task recovery, exact execution identity, durable observer resume, bounded gateway error, and matching Agent Action contract fixes.
|
|
16
|
+
|
|
3
17
|
## 0.1.10
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
|
@@ -4,7 +4,7 @@ declare const success: () => {
|
|
|
4
4
|
readonly ok: true;
|
|
5
5
|
readonly status: "SUCCEEDED";
|
|
6
6
|
readonly moduleRef: "task-orchestration";
|
|
7
|
-
readonly moduleVersion: "0.1.
|
|
7
|
+
readonly moduleVersion: "0.1.12";
|
|
8
8
|
};
|
|
9
9
|
observedEffects: string[];
|
|
10
10
|
};
|
|
@@ -17,7 +17,7 @@ export declare const behaviorAdapter: {
|
|
|
17
17
|
ok: true;
|
|
18
18
|
status: "SUCCEEDED";
|
|
19
19
|
moduleRef: "task-orchestration";
|
|
20
|
-
moduleVersion: "0.1.
|
|
20
|
+
moduleVersion: "0.1.12";
|
|
21
21
|
data: {
|
|
22
22
|
setupStatus: "READY";
|
|
23
23
|
runtimeStatus: "NOT_APPLICABLE";
|
|
@@ -32,7 +32,7 @@ export declare const behaviorAdapter: {
|
|
|
32
32
|
ok: true;
|
|
33
33
|
status: "SUCCEEDED";
|
|
34
34
|
moduleRef: "task-orchestration";
|
|
35
|
-
moduleVersion: "0.1.
|
|
35
|
+
moduleVersion: "0.1.12";
|
|
36
36
|
data: {
|
|
37
37
|
docs: string;
|
|
38
38
|
};
|
|
@@ -3,7 +3,7 @@ export declare const descriptor: {
|
|
|
3
3
|
readonly contractVersion: "1.0.0";
|
|
4
4
|
readonly moduleRef: "task-orchestration";
|
|
5
5
|
readonly packageName: "@tomflow/proflow-task-orchestration";
|
|
6
|
-
readonly moduleVersion: "0.1.
|
|
6
|
+
readonly moduleVersion: "0.1.12";
|
|
7
7
|
readonly kind: "library";
|
|
8
8
|
readonly templateVersion: "1.0.0";
|
|
9
9
|
readonly platformCompatibility: ">=1.0.0 <2.0.0";
|
|
@@ -3,7 +3,7 @@ export const descriptor = {
|
|
|
3
3
|
contractVersion: "1.0.0",
|
|
4
4
|
moduleRef: "task-orchestration",
|
|
5
5
|
packageName: "@tomflow/proflow-task-orchestration",
|
|
6
|
-
moduleVersion: "0.1.
|
|
6
|
+
moduleVersion: "0.1.12",
|
|
7
7
|
kind: "library",
|
|
8
8
|
templateVersion: "1.0.0",
|
|
9
9
|
platformCompatibility: ">=1.0.0 <2.0.0",
|
package/dist/src/contracts.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { requiredTaskAgentPackageRefs } from "./model.js";
|
|
2
|
+
import { requiredTaskAgentPackageRefs, taskWaitTypes } from "./model.js";
|
|
3
3
|
export const publicOperationNames = [
|
|
4
4
|
"createTaskGroup",
|
|
5
5
|
"getTaskGroup",
|
|
@@ -98,6 +98,14 @@ const schemas = {
|
|
|
98
98
|
path: ["sequenceNo"],
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
|
+
input.roleBindings.forEach((binding, index) => {
|
|
102
|
+
if (binding.workerRef !== null || binding.conversationLocator !== null)
|
|
103
|
+
context.addIssue({
|
|
104
|
+
code: "custom",
|
|
105
|
+
message: "createTask roleBindings must start with workerRef and conversationLocator unset",
|
|
106
|
+
path: ["roleBindings", index],
|
|
107
|
+
});
|
|
108
|
+
});
|
|
101
109
|
const declaredAgentPackageRefs = new Set(input.roleBindings.map((binding) => binding.agentPackageRef));
|
|
102
110
|
if (declaredAgentPackageRefs.size !== requiredTaskAgentPackageRefs.length ||
|
|
103
111
|
requiredTaskAgentPackageRefs.some((agentPackageRef) => !declaredAgentPackageRefs.has(agentPackageRef))) {
|
|
@@ -136,7 +144,7 @@ const schemas = {
|
|
|
136
144
|
waitNode: z
|
|
137
145
|
.object({
|
|
138
146
|
...nodeVersion,
|
|
139
|
-
waitType:
|
|
147
|
+
waitType: z.enum(taskWaitTypes),
|
|
140
148
|
reasonCode: id,
|
|
141
149
|
message: id,
|
|
142
150
|
relatedRef: id.optional(),
|
package/dist/src/model.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export declare const requiredTaskAgentPackageRefs: readonly ["@tomflow/proflow-agent-product", "@tomflow/proflow-agent-controller-dev", "@tomflow/proflow-agent-test-ops"];
|
|
2
|
+
export declare const taskWaitTypes: readonly ["BUSINESS_CONFIRMATION", "REQUIREMENT_BLOCKED", "EXTERNAL_BUSINESS_DEPENDENCY"];
|
|
3
|
+
export type TaskWaitType = (typeof taskWaitTypes)[number];
|
|
2
4
|
export type TaskGroupStatus = "READY" | "ACTIVE" | "SUCCEEDED";
|
|
3
5
|
export type TaskStatus = "PENDING" | "READY" | "ACTIVE" | "WAITING" | "FAILED" | "PAUSED" | "SUCCEEDED" | "TERMINATED";
|
|
4
6
|
export type NodeStatus = "PENDING" | "READY" | "IN_PROGRESS" | "WAITING" | "FAILED" | "SUCCEEDED" | "TERMINATED";
|
package/dist/src/model.js
CHANGED
package/dist/src/services.d.ts
CHANGED
|
@@ -144,6 +144,7 @@ export declare function createTaskServices(options: {
|
|
|
144
144
|
} | null;
|
|
145
145
|
canDrive: boolean;
|
|
146
146
|
blockedReason: string | null;
|
|
147
|
+
resumeSignalRef: string | null;
|
|
147
148
|
}>;
|
|
148
149
|
getNodeContext: (raw: unknown) => TaskResult<{
|
|
149
150
|
task: Pick<Task, "taskId" | "title" | "objective" | "status" | "version">;
|
package/dist/src/services.js
CHANGED
|
@@ -180,6 +180,17 @@ function recomputeReadiness(tx, task, timestamp, workspaceRoot) {
|
|
|
180
180
|
function matchesWorker(actorRef, workerRef) {
|
|
181
181
|
return workerRef !== null && actorRef === `worker:${workerRef}`;
|
|
182
182
|
}
|
|
183
|
+
function workerIdentityBoundToAnotherTask(tx, input) {
|
|
184
|
+
for (const task of tx.tasks.list()) {
|
|
185
|
+
if (task.taskId === input.taskId)
|
|
186
|
+
continue;
|
|
187
|
+
for (const binding of tx.roleBindings.listByTask(task.taskId))
|
|
188
|
+
if (binding.workerRef === input.workerRef ||
|
|
189
|
+
binding.conversationLocator === input.conversationLocator)
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
183
194
|
export function createTaskServices(options) {
|
|
184
195
|
const { store, workspaceRoot } = options;
|
|
185
196
|
const now = options.now ?? (() => new Date().toISOString());
|
|
@@ -321,13 +332,22 @@ export function createTaskServices(options) {
|
|
|
321
332
|
? taskId
|
|
322
333
|
: `id-${createHash("sha256").update(taskId).digest("hex")}`;
|
|
323
334
|
const stageDocumentPath = (taskId, type) => `.proflow/recovery/task-create/${safeTaskId(taskId)}/${type.toLowerCase().replaceAll("_", "-")}.md`;
|
|
324
|
-
const
|
|
335
|
+
const documentUpdateRecoveryTaskRoot = (taskId) => `.proflow/recovery/task-document/${safeTaskId(taskId)}`;
|
|
336
|
+
const documentUpdateRecoveryRoot = (taskId, type) => `${documentUpdateRecoveryTaskRoot(taskId)}/${type.toLowerCase().replaceAll("_", "-")}`;
|
|
325
337
|
const documentUpdateRecoveryPath = (taskId, type, idempotencyKey) => `${documentUpdateRecoveryRoot(taskId, type)}/${createHash("sha256").update(idempotencyKey).digest("hex")}`;
|
|
338
|
+
const cleanupDocumentRecoveryTaskRoot = (taskId) => {
|
|
339
|
+
const taskRootAbsolute = join(workspaceRoot, documentUpdateRecoveryTaskRoot(taskId));
|
|
340
|
+
if (existsSync(taskRootAbsolute) &&
|
|
341
|
+
readdirSync(taskRootAbsolute).length === 0)
|
|
342
|
+
rmSync(taskRootAbsolute, { recursive: true, force: true });
|
|
343
|
+
};
|
|
326
344
|
const recoverPendingDocumentUpdates = (taskId, type) => {
|
|
327
345
|
const recoveryRoot = documentUpdateRecoveryRoot(taskId, type);
|
|
328
346
|
const recoveryRootAbsolute = join(workspaceRoot, recoveryRoot);
|
|
329
|
-
if (!existsSync(recoveryRootAbsolute))
|
|
347
|
+
if (!existsSync(recoveryRootAbsolute)) {
|
|
348
|
+
cleanupDocumentRecoveryTaskRoot(taskId);
|
|
330
349
|
return;
|
|
350
|
+
}
|
|
331
351
|
const canonicalPath = relativeDocumentPath(taskId, type);
|
|
332
352
|
const canonicalAbsolute = join(workspaceRoot, canonicalPath);
|
|
333
353
|
const currentMetadata = store.read((tx) => tx.documents.get(taskId, type));
|
|
@@ -375,6 +395,7 @@ export function createTaskServices(options) {
|
|
|
375
395
|
}
|
|
376
396
|
if (readdirSync(recoveryRootAbsolute).length === 0)
|
|
377
397
|
rmSync(recoveryRootAbsolute, { recursive: true, force: true });
|
|
398
|
+
cleanupDocumentRecoveryTaskRoot(taskId);
|
|
378
399
|
};
|
|
379
400
|
const cleanupCreateStage = (taskId) => {
|
|
380
401
|
rmSync(join(workspaceRoot, `.proflow/recovery/task-create/${safeTaskId(taskId)}`), { recursive: true, force: true });
|
|
@@ -630,6 +651,8 @@ export function createTaskServices(options) {
|
|
|
630
651
|
throw new DomainError("AGENT_PACKAGE_NOT_ELIGIBLE", "Agent package is not declared by the Task.");
|
|
631
652
|
if (old.roleRef !== input.roleRef)
|
|
632
653
|
throw new DomainError("ROLE_BINDING_MISMATCH", "RoleRef does not match the declared binding.");
|
|
654
|
+
if (workerIdentityBoundToAnotherTask(tx, input))
|
|
655
|
+
throw new DomainError("TASK_ROLE_BINDING_CONFLICT", "Worker or Conversation identity is already bound to another Task.");
|
|
633
656
|
if (old.workerRef === input.workerRef &&
|
|
634
657
|
old.conversationLocator === input.conversationLocator)
|
|
635
658
|
return {
|
|
@@ -726,15 +749,24 @@ export function createTaskServices(options) {
|
|
|
726
749
|
const current = task.currentNodeId
|
|
727
750
|
? tx.nodes.get(task.currentNodeId)
|
|
728
751
|
: undefined;
|
|
752
|
+
if (task.status === "WAITING" && current?.status !== "WAITING")
|
|
753
|
+
throw new DomainError("TASK_INVALID_STATE", "A WAITING Task must have a current WAITING Node.");
|
|
729
754
|
if (current?.status === "FAILED")
|
|
730
755
|
throw new DomainError("TASK_INVALID_STATE", "A failed Node must be explicitly reopened before resume.");
|
|
731
|
-
if (current?.status === "WAITING")
|
|
756
|
+
if (current?.status === "WAITING") {
|
|
757
|
+
const unresolved = tx.messages
|
|
758
|
+
.listPending()
|
|
759
|
+
.some((message) => message.taskId === task.taskId &&
|
|
760
|
+
message.nodeId === current.nodeId);
|
|
761
|
+
if (unresolved)
|
|
762
|
+
throw new DomainError("TASK_BLOCKER_UNRESOLVED", "The current Node still has an unresolved business blocker.");
|
|
732
763
|
tx.nodes.update({
|
|
733
764
|
...current,
|
|
734
765
|
status: "IN_PROGRESS",
|
|
735
766
|
version: current.version + 1,
|
|
736
767
|
updatedAt: timestamp,
|
|
737
768
|
});
|
|
769
|
+
}
|
|
738
770
|
const updated = {
|
|
739
771
|
...task,
|
|
740
772
|
status: "ACTIVE",
|
|
@@ -910,6 +942,8 @@ export function createTaskServices(options) {
|
|
|
910
942
|
checkVersion(node.version, input.expectedNodeVersion, "NODE_VERSION_CONFLICT");
|
|
911
943
|
if (task.status !== "ACTIVE")
|
|
912
944
|
throw new DomainError("TASK_INVALID_STATE", "Task must be ACTIVE before a Node can wait.");
|
|
945
|
+
if (task.currentNodeId !== node.nodeId)
|
|
946
|
+
throw new DomainError("NODE_NOT_CURRENT", "Only the current Node can enter WAITING.");
|
|
913
947
|
if (node.status !== "IN_PROGRESS" ||
|
|
914
948
|
!matchesWorker(input.actorRef, node.workerRef))
|
|
915
949
|
throw new DomainError(!matchesWorker(input.actorRef, node.workerRef)
|
|
@@ -950,10 +984,14 @@ export function createTaskServices(options) {
|
|
|
950
984
|
const node = requireNode(tx, input.taskId, input.nodeId);
|
|
951
985
|
checkVersion(task.version, input.expectedTaskVersion, "TASK_VERSION_CONFLICT");
|
|
952
986
|
checkVersion(node.version, input.expectedNodeVersion, "NODE_VERSION_CONFLICT");
|
|
953
|
-
if (
|
|
954
|
-
throw new DomainError("TASK_INVALID_STATE", "Task
|
|
955
|
-
if (
|
|
956
|
-
throw new DomainError("
|
|
987
|
+
if (task.status !== "ACTIVE")
|
|
988
|
+
throw new DomainError("TASK_INVALID_STATE", "Task must be ACTIVE before the current run can fail.");
|
|
989
|
+
if (task.currentNodeId !== node.nodeId)
|
|
990
|
+
throw new DomainError("NODE_NOT_CURRENT", "Only the current Node can record a run failure.");
|
|
991
|
+
if (!matchesWorker(input.actorRef, node.workerRef))
|
|
992
|
+
throw new DomainError("WORKER_MISMATCH", "Only the bound running Worker may fail the current Node.");
|
|
993
|
+
if (node.status !== "IN_PROGRESS")
|
|
994
|
+
throw new DomainError("NODE_INVALID_STATE", "Only the current IN_PROGRESS run can fail.");
|
|
957
995
|
const failedNode = {
|
|
958
996
|
...node,
|
|
959
997
|
status: "FAILED",
|
|
@@ -1001,11 +1039,13 @@ export function createTaskServices(options) {
|
|
|
1001
1039
|
tx.nodes.update(reopened);
|
|
1002
1040
|
for (const later of tx.nodes.listByTask(task.taskId))
|
|
1003
1041
|
if (later.sequenceNo > target.sequenceNo) {
|
|
1042
|
+
const invalidatedStartedRun = later.startedAt !== null || later.workerRef !== null;
|
|
1004
1043
|
preserveRunHistory(tx, later, later.status, timestamp);
|
|
1005
1044
|
tx.nodes.update({
|
|
1006
1045
|
...later,
|
|
1007
1046
|
status: "PENDING",
|
|
1008
1047
|
version: later.version + 1,
|
|
1048
|
+
runNo: later.runNo + (invalidatedStartedRun ? 1 : 0),
|
|
1009
1049
|
workerRef: null,
|
|
1010
1050
|
resultSummary: null,
|
|
1011
1051
|
errorCode: null,
|
|
@@ -1116,6 +1156,13 @@ export function createTaskServices(options) {
|
|
|
1116
1156
|
else if (!terminal && !roleBinding?.workerRef)
|
|
1117
1157
|
blockedReason = "TASK_ROLE_BINDING_REQUIRED";
|
|
1118
1158
|
const canDrive = !terminal && blockedReason === null;
|
|
1159
|
+
let durableResumeEventId;
|
|
1160
|
+
if (task.status === "ACTIVE" && currentNode?.status === "IN_PROGRESS")
|
|
1161
|
+
for (const taskEvent of tx.events.listByTask(task.taskId))
|
|
1162
|
+
if (taskEvent.eventType === "TASK_RESUMED" &&
|
|
1163
|
+
taskEvent.taskVersion === task.version &&
|
|
1164
|
+
taskEvent.eventId !== undefined)
|
|
1165
|
+
durableResumeEventId = taskEvent.eventId;
|
|
1119
1166
|
return {
|
|
1120
1167
|
taskId: task.taskId,
|
|
1121
1168
|
taskStatus: task.status,
|
|
@@ -1140,6 +1187,9 @@ export function createTaskServices(options) {
|
|
|
1140
1187
|
: null,
|
|
1141
1188
|
canDrive,
|
|
1142
1189
|
blockedReason,
|
|
1190
|
+
resumeSignalRef: durableResumeEventId === undefined
|
|
1191
|
+
? null
|
|
1192
|
+
: `task-event:${durableResumeEventId}`,
|
|
1143
1193
|
};
|
|
1144
1194
|
}),
|
|
1145
1195
|
getNodeContext: (raw) => queryWithDocumentRecovery("getNodeContext", raw, (tx, input) => {
|
package/dist/src/transitions.js
CHANGED
|
@@ -7,7 +7,7 @@ const taskTransitions = {
|
|
|
7
7
|
READY: ["ACTIVE", "TERMINATED"],
|
|
8
8
|
ACTIVE: ["WAITING", "PAUSED", "FAILED", "SUCCEEDED", "TERMINATED"],
|
|
9
9
|
WAITING: ["ACTIVE", "PAUSED", "TERMINATED"],
|
|
10
|
-
FAILED: ["
|
|
10
|
+
FAILED: ["TERMINATED"],
|
|
11
11
|
PAUSED: ["ACTIVE", "TERMINATED"],
|
|
12
12
|
SUCCEEDED: [],
|
|
13
13
|
TERMINATED: [],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tomflow/proflow-task-orchestration",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@tomflow/proflow-deployment-conformance": "^0.1.13",
|
|
27
27
|
"@tomflow/proflow-task-migration-runner": "^0.1.11",
|
|
28
|
-
"@tomflow/proflow-task-store-sqlite": "^0.1.
|
|
28
|
+
"@tomflow/proflow-task-store-sqlite": "^0.1.12"
|
|
29
29
|
},
|
|
30
30
|
"description": "Owns the Task & Orchestration public contract, domain rules and application services.",
|
|
31
31
|
"keywords": [
|
package/proflow.module.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"contractVersion": "1.0.0",
|
|
4
4
|
"moduleRef": "task-orchestration",
|
|
5
5
|
"packageName": "@tomflow/proflow-task-orchestration",
|
|
6
|
-
"moduleVersion": "0.1.
|
|
6
|
+
"moduleVersion": "0.1.12",
|
|
7
7
|
"kind": "library",
|
|
8
8
|
"templateVersion": "1.0.0",
|
|
9
9
|
"platformCompatibility": ">=1.0.0 <2.0.0",
|