@tomflow/proflow-task-orchestration 0.1.13 → 0.1.15
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 +12 -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/model.d.ts +6 -0
- package/dist/src/model.js +1 -0
- package/dist/src/phase4-continuation-services.d.ts +8 -0
- package/dist/src/phase4-continuation-services.js +15 -0
- package/dist/src/phase4-contracts.js +14 -3
- package/dist/src/phase4-services.d.ts +8 -0
- package/dist/src/phase4-services.js +36 -6
- package/dist/src/services.d.ts +14 -0
- package/dist/src/services.js +69 -26
- package/package.json +3 -3
- package/proflow.module.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @tomflow/proflow-task-orchestration
|
|
2
2
|
|
|
3
|
+
## 0.1.15
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- fix(product): bind phase1 materialization to current authorization
|
|
8
|
+
|
|
9
|
+
## 0.1.14
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- fix(task): bind terminal evidence to current document revision
|
|
14
|
+
|
|
3
15
|
## 0.1.13
|
|
4
16
|
|
|
5
17
|
### 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.15";
|
|
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.15";
|
|
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.15";
|
|
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.15";
|
|
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.15",
|
|
7
7
|
kind: "library",
|
|
8
8
|
templateVersion: "1.0.0",
|
|
9
9
|
platformCompatibility: ">=1.0.0 <2.0.0",
|
package/dist/src/model.d.ts
CHANGED
|
@@ -78,10 +78,16 @@ export interface NodeExecutionHistory {
|
|
|
78
78
|
startedAt: string | null;
|
|
79
79
|
endedAt: string;
|
|
80
80
|
}
|
|
81
|
+
export declare const taskDocumentRef: (taskId: string, documentType: string) => string;
|
|
81
82
|
export interface TaskDocument {
|
|
83
|
+
documentRef: string;
|
|
82
84
|
taskId: string;
|
|
83
85
|
documentType: string;
|
|
84
86
|
sourceNodeId: string | null;
|
|
87
|
+
sourceNodeRunNo: number | null;
|
|
88
|
+
revision: number;
|
|
89
|
+
sourceDocumentRef: string | null;
|
|
90
|
+
sourceDocumentRevision: number | null;
|
|
85
91
|
filePath: string;
|
|
86
92
|
contentHash: string;
|
|
87
93
|
updatedByRef: string;
|
package/dist/src/model.js
CHANGED
|
@@ -106,10 +106,18 @@ export declare function createTaskServices(options: ServiceOptions): {
|
|
|
106
106
|
task: Pick<Task, "taskId" | "title" | "objective" | "status" | "version">;
|
|
107
107
|
node: Pick<import("./model.ts").TaskNode, "nodeId" | "title" | "objective" | "status" | "version" | "runNo" | "requiredAgentPackageRef" | "workerRef" | "inputDocuments" | "outputDocuments">;
|
|
108
108
|
documents: Array<{
|
|
109
|
+
documentRef: string;
|
|
109
110
|
documentType: string;
|
|
110
111
|
path: string;
|
|
111
112
|
contentHash: string;
|
|
112
113
|
sizeBytes: number;
|
|
114
|
+
revision: number;
|
|
115
|
+
sourceNodeId: string | null;
|
|
116
|
+
sourceNodeRunNo: number | null;
|
|
117
|
+
sourceDocumentRef: string | null;
|
|
118
|
+
sourceDocumentRevision: number | null;
|
|
119
|
+
updatedByRef: string;
|
|
120
|
+
updatedAt: string;
|
|
113
121
|
}>;
|
|
114
122
|
}>;
|
|
115
123
|
listPendingMessages: (raw: unknown) => TaskResult<{
|
|
@@ -126,8 +126,23 @@ export function createTaskServices(options) {
|
|
|
126
126
|
throw new ProjectionError("PRODUCT_CAMPAIGN_NOT_FOUND");
|
|
127
127
|
if (campaign.status !== "OPEN")
|
|
128
128
|
throw new ProjectionError("PRODUCT_CAMPAIGN_CLOSED");
|
|
129
|
+
if (campaign.version !== input.expectedCampaignVersion)
|
|
130
|
+
throw new ProjectionError("PRODUCT_CAMPAIGN_VERSION_CONFLICT", true);
|
|
129
131
|
if (campaign.discussionRef !== input.discussionRef)
|
|
130
132
|
throw new ProjectionError("PRODUCT_DISCUSSION_MISMATCH");
|
|
133
|
+
const authorization = tx.campaignAuthorizations.get(input.authorizationRef);
|
|
134
|
+
if (!authorization)
|
|
135
|
+
throw new ProjectionError("CAMPAIGN_AUTHORIZATION_NOT_FOUND");
|
|
136
|
+
if (authorization.version !== input.expectedAuthorizationVersion)
|
|
137
|
+
throw new ProjectionError("CAMPAIGN_AUTHORIZATION_VERSION_CONFLICT", true);
|
|
138
|
+
if (authorization.revokedAt !== null)
|
|
139
|
+
throw new ProjectionError("CAMPAIGN_AUTHORIZATION_REVOKED");
|
|
140
|
+
if (authorization.campaignRef !== campaign.campaignRef)
|
|
141
|
+
throw new ProjectionError("CAMPAIGN_AUTHORIZATION_CAMPAIGN_MISMATCH");
|
|
142
|
+
if (authorization.discussionRef !== input.discussionRef)
|
|
143
|
+
throw new ProjectionError("PRODUCT_DISCUSSION_MISMATCH");
|
|
144
|
+
if (authorization.goalRevision !== campaign.goalRevision)
|
|
145
|
+
throw new ProjectionError("CAMPAIGN_GOAL_REVISION_MISMATCH");
|
|
131
146
|
const current = tx.productDocuments.get(input.documentRef);
|
|
132
147
|
if (input.expectedRevision === 0) {
|
|
133
148
|
if (current)
|
|
@@ -58,6 +58,8 @@ const submitProductTaskIntentSchema = z
|
|
|
58
58
|
.object({
|
|
59
59
|
intentRef: id,
|
|
60
60
|
campaignRef: id.optional(),
|
|
61
|
+
authorizationRef: id.optional(),
|
|
62
|
+
expectedAuthorizationVersion: version.optional(),
|
|
61
63
|
discussionRef: id,
|
|
62
64
|
goalRevision: version,
|
|
63
65
|
intentRevision: version,
|
|
@@ -74,11 +76,17 @@ const submitProductTaskIntentSchema = z
|
|
|
74
76
|
})
|
|
75
77
|
.strict()
|
|
76
78
|
.superRefine((input, context) => {
|
|
77
|
-
|
|
79
|
+
const campaignFields = [
|
|
80
|
+
input.campaignRef,
|
|
81
|
+
input.authorizationRef,
|
|
82
|
+
input.expectedAuthorizationVersion,
|
|
83
|
+
input.expectedCampaignVersion,
|
|
84
|
+
];
|
|
85
|
+
if (campaignFields.some((value) => value !== undefined) && campaignFields.some((value) => value === undefined))
|
|
78
86
|
context.addIssue({
|
|
79
87
|
code: "custom",
|
|
80
|
-
message: "campaignRef and expectedCampaignVersion must be supplied together",
|
|
81
|
-
path: ["
|
|
88
|
+
message: "campaignRef, authorizationRef, expectedAuthorizationVersion, and expectedCampaignVersion must be supplied together",
|
|
89
|
+
path: ["authorizationRef"],
|
|
82
90
|
});
|
|
83
91
|
const packages = new Set(input.roleBindings.map((binding) => binding.agentPackageRef));
|
|
84
92
|
if (packages.size !== requiredTaskAgentPackageRefs.length ||
|
|
@@ -128,6 +136,9 @@ const phase4Schemas = {
|
|
|
128
136
|
documentRef: id,
|
|
129
137
|
campaignRef: id,
|
|
130
138
|
discussionRef: id,
|
|
139
|
+
authorizationRef: id,
|
|
140
|
+
expectedAuthorizationVersion: version,
|
|
141
|
+
expectedCampaignVersion: version,
|
|
131
142
|
documentType: id,
|
|
132
143
|
content: z.string().min(1),
|
|
133
144
|
expectedRevision: revision,
|
|
@@ -80,10 +80,18 @@ export declare function createTaskServices(options: ServiceOptions): {
|
|
|
80
80
|
task: Pick<Task, "taskId" | "title" | "objective" | "status" | "version">;
|
|
81
81
|
node: Pick<import("./model.ts").TaskNode, "nodeId" | "title" | "objective" | "status" | "version" | "runNo" | "requiredAgentPackageRef" | "workerRef" | "inputDocuments" | "outputDocuments">;
|
|
82
82
|
documents: Array<{
|
|
83
|
+
documentRef: string;
|
|
83
84
|
documentType: string;
|
|
84
85
|
path: string;
|
|
85
86
|
contentHash: string;
|
|
86
87
|
sizeBytes: number;
|
|
88
|
+
revision: number;
|
|
89
|
+
sourceNodeId: string | null;
|
|
90
|
+
sourceNodeRunNo: number | null;
|
|
91
|
+
sourceDocumentRef: string | null;
|
|
92
|
+
sourceDocumentRevision: number | null;
|
|
93
|
+
updatedByRef: string;
|
|
94
|
+
updatedAt: string;
|
|
87
95
|
}>;
|
|
88
96
|
}>;
|
|
89
97
|
listPendingMessages: (raw: unknown) => TaskResult<{
|
|
@@ -4,7 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { productDocumentAbsolute } from "./product-document-path.js";
|
|
5
5
|
import { ZodError } from "zod";
|
|
6
6
|
import { validatePublicInput } from "./phase4-contracts.js";
|
|
7
|
-
import { requiredTaskAgentPackageRefs } from "./model.js";
|
|
7
|
+
import { requiredTaskAgentPackageRefs, taskDocumentRef } from "./model.js";
|
|
8
8
|
import { createTaskServices as createLegacyTaskServices, } from "./services.js";
|
|
9
9
|
class Phase4DomainError extends Error {
|
|
10
10
|
code;
|
|
@@ -144,6 +144,16 @@ function readinessBlockedReason(tx, task, workspaceRoot) {
|
|
|
144
144
|
return "TASK_REQUIREMENT_REQUIRED";
|
|
145
145
|
if (contentHash(readFileSync(absolute, "utf8")) !== requirement.contentHash)
|
|
146
146
|
return "TASK_DOCUMENT_INDEX_MISMATCH";
|
|
147
|
+
if (requirement.sourceDocumentRef !== null) {
|
|
148
|
+
const source = tx.productDocuments.get(requirement.sourceDocumentRef);
|
|
149
|
+
if (!source ||
|
|
150
|
+
requirement.sourceDocumentRevision === null ||
|
|
151
|
+
source.revision !== requirement.sourceDocumentRevision ||
|
|
152
|
+
source.contentHash !== requirement.contentHash ||
|
|
153
|
+
source.filePath !== requirement.filePath ||
|
|
154
|
+
source.updatedByRef !== requirement.updatedByRef)
|
|
155
|
+
return "TASK_DOCUMENT_INDEX_MISMATCH";
|
|
156
|
+
}
|
|
147
157
|
const bindings = tx.roleBindings.listByTask(task.taskId);
|
|
148
158
|
if (bindings.length !== requiredTaskAgentPackageRefs.length)
|
|
149
159
|
return "TASK_ROLE_BINDING_REQUIRED";
|
|
@@ -325,12 +335,21 @@ export function createTaskServices(options) {
|
|
|
325
335
|
if (campaign.status !== "OPEN")
|
|
326
336
|
throw new Phase4DomainError("PRODUCT_CAMPAIGN_CLOSED");
|
|
327
337
|
checkVersion(campaign.version, input.expectedCampaignVersion, "PRODUCT_CAMPAIGN_VERSION_CONFLICT");
|
|
328
|
-
if (campaign.discussionRef !== input.discussionRef ||
|
|
329
|
-
campaign.goalRevision !== input.goalRevision)
|
|
338
|
+
if (campaign.discussionRef !== input.discussionRef || campaign.goalRevision !== input.goalRevision)
|
|
330
339
|
throw new Phase4DomainError("CAMPAIGN_GOAL_REVISION_MISMATCH");
|
|
331
|
-
|
|
340
|
+
const authorization = requireAuthorization(tx, input.authorizationRef);
|
|
341
|
+
checkVersion(authorization.version, input.expectedAuthorizationVersion, "CAMPAIGN_AUTHORIZATION_VERSION_CONFLICT");
|
|
342
|
+
if (authorization.revokedAt !== null)
|
|
343
|
+
throw new Phase4DomainError("CAMPAIGN_AUTHORIZATION_REVOKED");
|
|
344
|
+
if (authorization.campaignRef !== campaign.campaignRef)
|
|
345
|
+
throw new Phase4DomainError("CAMPAIGN_AUTHORIZATION_CAMPAIGN_MISMATCH");
|
|
346
|
+
if (authorization.discussionRef !== input.discussionRef)
|
|
347
|
+
throw new Phase4DomainError("PRODUCT_DISCUSSION_MISMATCH");
|
|
348
|
+
if (authorization.goalRevision !== campaign.goalRevision)
|
|
349
|
+
throw new Phase4DomainError("CAMPAIGN_GOAL_REVISION_MISMATCH");
|
|
350
|
+
if (!subsetOf(input.scope, campaign.scope) || !subsetOf(input.scope, authorization.scope))
|
|
332
351
|
throw new Phase4DomainError("PRODUCT_INTENT_SCOPE_VIOLATION");
|
|
333
|
-
if (!same(input.constraints, campaign.constraints))
|
|
352
|
+
if (!same(input.constraints, campaign.constraints) || !same(input.constraints, authorization.constraints))
|
|
334
353
|
throw new Phase4DomainError("CAMPAIGN_CONSTRAINT_VIOLATION");
|
|
335
354
|
}
|
|
336
355
|
if (input.productDocuments.length > 0 && input.campaignRef === undefined)
|
|
@@ -537,13 +556,21 @@ export function createTaskServices(options) {
|
|
|
537
556
|
if (!existsSync(absolute) ||
|
|
538
557
|
contentHash(readFileSync(absolute, "utf8")) !== requirement.contentHash)
|
|
539
558
|
throw new Phase4DomainError("PRODUCT_DOCUMENT_INDEX_MISMATCH");
|
|
559
|
+
const currentRequirement = tx.documents.get(current.taskId, "REQUIREMENT");
|
|
560
|
+
const taskDocumentRevision = (currentRequirement?.revision ?? 0) + 1;
|
|
561
|
+
const transferredDocumentRef = taskDocumentRef(current.taskId, "REQUIREMENT");
|
|
540
562
|
tx.documents.upsert({
|
|
563
|
+
documentRef: transferredDocumentRef,
|
|
541
564
|
taskId: current.taskId,
|
|
542
565
|
documentType: "REQUIREMENT",
|
|
543
566
|
sourceNodeId: null,
|
|
567
|
+
sourceNodeRunNo: null,
|
|
568
|
+
revision: taskDocumentRevision,
|
|
569
|
+
sourceDocumentRef: requirement.documentRef,
|
|
570
|
+
sourceDocumentRevision: requirement.revision,
|
|
544
571
|
filePath: requirement.filePath,
|
|
545
572
|
contentHash: requirement.contentHash,
|
|
546
|
-
updatedByRef:
|
|
573
|
+
updatedByRef: requirement.updatedByRef,
|
|
547
574
|
updatedAt: timestamp,
|
|
548
575
|
});
|
|
549
576
|
const advancedIntent = {
|
|
@@ -571,6 +598,9 @@ export function createTaskServices(options) {
|
|
|
571
598
|
payload: {
|
|
572
599
|
intentRef: intent.intentRef,
|
|
573
600
|
productDocumentRef: requirement.documentRef,
|
|
601
|
+
productDocumentRevision: requirement.revision,
|
|
602
|
+
taskDocumentRef: transferredDocumentRef,
|
|
603
|
+
taskDocumentRevision,
|
|
574
604
|
},
|
|
575
605
|
createdAt: timestamp,
|
|
576
606
|
});
|
package/dist/src/services.d.ts
CHANGED
|
@@ -77,6 +77,7 @@ export interface CompleteNodeResult {
|
|
|
77
77
|
nextNodeId: string | null;
|
|
78
78
|
}
|
|
79
79
|
export interface DocumentResult {
|
|
80
|
+
documentRef: string;
|
|
80
81
|
taskId: string;
|
|
81
82
|
nodeId: string | null;
|
|
82
83
|
documentType: string;
|
|
@@ -84,6 +85,11 @@ export interface DocumentResult {
|
|
|
84
85
|
contentHash: string;
|
|
85
86
|
sizeBytes: number;
|
|
86
87
|
taskVersion: number;
|
|
88
|
+
revision: number;
|
|
89
|
+
sourceNodeRunNo: number | null;
|
|
90
|
+
sourceDocumentRef: string | null;
|
|
91
|
+
sourceDocumentRevision: number | null;
|
|
92
|
+
updatedByRef: string;
|
|
87
93
|
updatedAt: string;
|
|
88
94
|
content?: string;
|
|
89
95
|
}
|
|
@@ -150,10 +156,18 @@ export declare function createTaskServices(options: {
|
|
|
150
156
|
task: Pick<Task, "taskId" | "title" | "objective" | "status" | "version">;
|
|
151
157
|
node: Pick<TaskNode, "nodeId" | "title" | "objective" | "status" | "version" | "runNo" | "requiredAgentPackageRef" | "workerRef" | "inputDocuments" | "outputDocuments">;
|
|
152
158
|
documents: Array<{
|
|
159
|
+
documentRef: string;
|
|
153
160
|
documentType: string;
|
|
154
161
|
path: string;
|
|
155
162
|
contentHash: string;
|
|
156
163
|
sizeBytes: number;
|
|
164
|
+
revision: number;
|
|
165
|
+
sourceNodeId: string | null;
|
|
166
|
+
sourceNodeRunNo: number | null;
|
|
167
|
+
sourceDocumentRef: string | null;
|
|
168
|
+
sourceDocumentRevision: number | null;
|
|
169
|
+
updatedByRef: string;
|
|
170
|
+
updatedAt: string;
|
|
157
171
|
}>;
|
|
158
172
|
}>;
|
|
159
173
|
listPendingMessages: (raw: unknown) => TaskResult<{
|
package/dist/src/services.js
CHANGED
|
@@ -3,7 +3,7 @@ import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readdirSync, rea
|
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { ZodError } from "zod";
|
|
5
5
|
import { validatePublicInput } from "./contracts.js";
|
|
6
|
-
import { requiredTaskAgentPackageRefs } from "./model.js";
|
|
6
|
+
import { requiredTaskAgentPackageRefs, taskDocumentRef } from "./model.js";
|
|
7
7
|
const allowedDocumentTypes = [
|
|
8
8
|
"REQUIREMENT",
|
|
9
9
|
"PRD",
|
|
@@ -590,9 +590,14 @@ export function createTaskServices(options) {
|
|
|
590
590
|
for (const item of validated.initialDocuments) {
|
|
591
591
|
const path = relativeDocumentPath(taskId, item.documentType);
|
|
592
592
|
tx.documents.upsert({
|
|
593
|
+
documentRef: taskDocumentRef(taskId, item.documentType),
|
|
593
594
|
taskId,
|
|
594
595
|
documentType: item.documentType,
|
|
595
596
|
sourceNodeId: null,
|
|
597
|
+
sourceNodeRunNo: null,
|
|
598
|
+
revision: 1,
|
|
599
|
+
sourceDocumentRef: null,
|
|
600
|
+
sourceDocumentRevision: null,
|
|
596
601
|
filePath: path,
|
|
597
602
|
contentHash: contentHash(item.content),
|
|
598
603
|
updatedByRef: validated.actorRef,
|
|
@@ -848,6 +853,12 @@ export function createTaskServices(options) {
|
|
|
848
853
|
const doc = tx.documents.get(task.taskId, type);
|
|
849
854
|
if (!doc || !existsSync(join(workspaceRoot, doc.filePath)))
|
|
850
855
|
throw new DomainError("NODE_OUTPUT_MISSING", "A required output document is missing.");
|
|
856
|
+
if (doc.documentRef !== taskDocumentRef(task.taskId, type) ||
|
|
857
|
+
doc.sourceNodeId !== node.nodeId ||
|
|
858
|
+
doc.sourceNodeRunNo !== node.runNo ||
|
|
859
|
+
doc.updatedByRef !== input.actorRef ||
|
|
860
|
+
doc.revision < 1)
|
|
861
|
+
throw new DomainError("NODE_OUTPUT_EVIDENCE_INVALID", "A required output document is not evidence from the current Node run.");
|
|
851
862
|
if (contentHash(readFileSync(join(workspaceRoot, doc.filePath), "utf8")) !== doc.contentHash)
|
|
852
863
|
throw new DomainError("DOCUMENT_INDEX_MISMATCH", "A required output document does not match its index.");
|
|
853
864
|
}
|
|
@@ -1199,10 +1210,18 @@ export function createTaskServices(options) {
|
|
|
1199
1210
|
.map((type) => tx.documents.get(task.taskId, type))
|
|
1200
1211
|
.filter((item) => item !== undefined)
|
|
1201
1212
|
.map((document) => ({
|
|
1213
|
+
documentRef: document.documentRef,
|
|
1202
1214
|
documentType: document.documentType,
|
|
1203
1215
|
path: document.filePath,
|
|
1204
1216
|
contentHash: document.contentHash,
|
|
1205
1217
|
sizeBytes: statSync(join(workspaceRoot, document.filePath)).size,
|
|
1218
|
+
revision: document.revision,
|
|
1219
|
+
sourceNodeId: document.sourceNodeId,
|
|
1220
|
+
sourceNodeRunNo: document.sourceNodeRunNo,
|
|
1221
|
+
sourceDocumentRef: document.sourceDocumentRef,
|
|
1222
|
+
sourceDocumentRevision: document.sourceDocumentRevision,
|
|
1223
|
+
updatedByRef: document.updatedByRef,
|
|
1224
|
+
updatedAt: document.updatedAt,
|
|
1206
1225
|
}));
|
|
1207
1226
|
return {
|
|
1208
1227
|
task: {
|
|
@@ -1247,6 +1266,7 @@ export function createTaskServices(options) {
|
|
|
1247
1266
|
const content = readFileSync(join(workspaceRoot, document.filePath), "utf8");
|
|
1248
1267
|
const task = requireTask(tx, input.taskId);
|
|
1249
1268
|
return {
|
|
1269
|
+
documentRef: document.documentRef,
|
|
1250
1270
|
taskId: input.taskId,
|
|
1251
1271
|
nodeId: document.sourceNodeId,
|
|
1252
1272
|
documentType: type,
|
|
@@ -1254,6 +1274,11 @@ export function createTaskServices(options) {
|
|
|
1254
1274
|
contentHash: document.contentHash,
|
|
1255
1275
|
sizeBytes: statSync(join(workspaceRoot, document.filePath)).size,
|
|
1256
1276
|
taskVersion: task.version,
|
|
1277
|
+
revision: document.revision,
|
|
1278
|
+
sourceNodeRunNo: document.sourceNodeRunNo,
|
|
1279
|
+
sourceDocumentRef: document.sourceDocumentRef,
|
|
1280
|
+
sourceDocumentRevision: document.sourceDocumentRevision,
|
|
1281
|
+
updatedByRef: document.updatedByRef,
|
|
1257
1282
|
updatedAt: document.updatedAt,
|
|
1258
1283
|
content,
|
|
1259
1284
|
};
|
|
@@ -1301,9 +1326,23 @@ export function createTaskServices(options) {
|
|
|
1301
1326
|
const result = command("putTaskDocument", raw, (tx, validated, timestamp) => {
|
|
1302
1327
|
const task = requireTask(tx, validated.taskId);
|
|
1303
1328
|
checkVersion(task.version, validated.expectedTaskVersion, "TASK_VERSION_CONFLICT");
|
|
1304
|
-
if (validated.nodeId !== null)
|
|
1305
|
-
requireNode(tx, task.taskId, validated.nodeId);
|
|
1306
1329
|
const validatedType = ensureType(validated.documentType);
|
|
1330
|
+
let sourceNodeRunNo = null;
|
|
1331
|
+
if (validated.nodeId !== null) {
|
|
1332
|
+
const producer = requireNode(tx, task.taskId, validated.nodeId);
|
|
1333
|
+
if (task.status !== "ACTIVE" ||
|
|
1334
|
+
task.currentNodeId !== producer.nodeId ||
|
|
1335
|
+
producer.status !== "IN_PROGRESS")
|
|
1336
|
+
throw new DomainError("NODE_NOT_CURRENT", "Only the current IN_PROGRESS Node may produce Task evidence.");
|
|
1337
|
+
if (!matchesWorker(validated.actorRef, producer.workerRef))
|
|
1338
|
+
throw new DomainError("WORKER_MISMATCH", "Only the bound current Worker may produce Task evidence.");
|
|
1339
|
+
if (!producer.outputDocuments.includes(validatedType))
|
|
1340
|
+
throw new DomainError("NODE_OUTPUT_DOCUMENT_NOT_DECLARED", "Document type is not declared as an output of the current Node.");
|
|
1341
|
+
sourceNodeRunNo = producer.runNo;
|
|
1342
|
+
}
|
|
1343
|
+
else if (task.status !== "PENDING") {
|
|
1344
|
+
throw new DomainError("TASK_INVALID_STATE", "Node-less Task documents can only be written while the Task is PENDING.");
|
|
1345
|
+
}
|
|
1307
1346
|
const path = relativeDocumentPath(task.taskId, validatedType);
|
|
1308
1347
|
const staged = `${documentUpdateRecoveryPath(task.taskId, validatedType, validated.idempotencyKey)}/next.md`;
|
|
1309
1348
|
// BEGIN IMMEDIATE + optimistic version validation happens before the
|
|
@@ -1317,10 +1356,18 @@ export function createTaskServices(options) {
|
|
|
1317
1356
|
updatedAt: timestamp,
|
|
1318
1357
|
};
|
|
1319
1358
|
tx.tasks.update(updated);
|
|
1359
|
+
const currentDocument = tx.documents.get(task.taskId, validatedType);
|
|
1360
|
+
const documentRevision = (currentDocument?.revision ?? 0) + 1;
|
|
1361
|
+
const documentRef = taskDocumentRef(task.taskId, validatedType);
|
|
1320
1362
|
tx.documents.upsert({
|
|
1363
|
+
documentRef,
|
|
1321
1364
|
taskId: task.taskId,
|
|
1322
1365
|
documentType: validatedType,
|
|
1323
1366
|
sourceNodeId: validated.nodeId,
|
|
1367
|
+
sourceNodeRunNo,
|
|
1368
|
+
revision: documentRevision,
|
|
1369
|
+
sourceDocumentRef: null,
|
|
1370
|
+
sourceDocumentRevision: null,
|
|
1324
1371
|
filePath: path,
|
|
1325
1372
|
contentHash: contentHash(validated.content),
|
|
1326
1373
|
updatedByRef: validated.actorRef,
|
|
@@ -1336,9 +1383,15 @@ export function createTaskServices(options) {
|
|
|
1336
1383
|
actorRef: validated.actorRef,
|
|
1337
1384
|
taskVersion: readied.version,
|
|
1338
1385
|
nodeVersion: null,
|
|
1339
|
-
payload: {
|
|
1386
|
+
payload: {
|
|
1387
|
+
documentType: validatedType,
|
|
1388
|
+
documentRef,
|
|
1389
|
+
revision: documentRevision,
|
|
1390
|
+
sourceNodeRunNo,
|
|
1391
|
+
},
|
|
1340
1392
|
}, timestamp);
|
|
1341
1393
|
return {
|
|
1394
|
+
documentRef,
|
|
1342
1395
|
taskId: task.taskId,
|
|
1343
1396
|
nodeId: validated.nodeId,
|
|
1344
1397
|
documentType: validatedType,
|
|
@@ -1346,6 +1399,11 @@ export function createTaskServices(options) {
|
|
|
1346
1399
|
contentHash: contentHash(validated.content),
|
|
1347
1400
|
sizeBytes: Buffer.byteLength(validated.content),
|
|
1348
1401
|
taskVersion: readied.version,
|
|
1402
|
+
revision: documentRevision,
|
|
1403
|
+
sourceNodeRunNo,
|
|
1404
|
+
sourceDocumentRef: null,
|
|
1405
|
+
sourceDocumentRevision: null,
|
|
1406
|
+
updatedByRef: validated.actorRef,
|
|
1349
1407
|
updatedAt: timestamp,
|
|
1350
1408
|
};
|
|
1351
1409
|
});
|
|
@@ -1373,31 +1431,16 @@ export function createTaskServices(options) {
|
|
|
1373
1431
|
promoteDocument(staged, relativeDocumentPath(input.taskId, type));
|
|
1374
1432
|
}
|
|
1375
1433
|
cleanupCreateStage(input.taskId);
|
|
1376
|
-
const result = store.
|
|
1434
|
+
const result = store.read((tx) => {
|
|
1377
1435
|
requireTask(tx, input.taskId);
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
const path = relativeDocumentPath(input.taskId, type);
|
|
1381
|
-
const absolute = join(workspaceRoot, path);
|
|
1436
|
+
for (const document of tx.documents.listByTask(input.taskId)) {
|
|
1437
|
+
const absolute = join(workspaceRoot, document.filePath);
|
|
1382
1438
|
if (!existsSync(absolute))
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
const digest = contentHash(content);
|
|
1387
|
-
if (!current || current.contentHash !== digest) {
|
|
1388
|
-
tx.documents.upsert({
|
|
1389
|
-
taskId: input.taskId,
|
|
1390
|
-
documentType: type,
|
|
1391
|
-
sourceNodeId: current?.sourceNodeId ?? null,
|
|
1392
|
-
filePath: path,
|
|
1393
|
-
contentHash: digest,
|
|
1394
|
-
updatedByRef: input.actorRef,
|
|
1395
|
-
updatedAt: now(),
|
|
1396
|
-
});
|
|
1397
|
-
reconciled++;
|
|
1398
|
-
}
|
|
1439
|
+
throw new DomainError("DOCUMENT_INDEX_MISMATCH", "Canonical Task document is missing.");
|
|
1440
|
+
if (contentHash(readFileSync(absolute, "utf8")) !== document.contentHash)
|
|
1441
|
+
throw new DomainError("DOCUMENT_INDEX_MISMATCH", "Canonical Task document does not match durable evidence metadata.");
|
|
1399
1442
|
}
|
|
1400
|
-
return { reconciled };
|
|
1443
|
+
return { reconciled: 0 };
|
|
1401
1444
|
});
|
|
1402
1445
|
return success(result);
|
|
1403
1446
|
}
|
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.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@tomflow/proflow-deployment-conformance": "^0.1.13",
|
|
27
|
-
"@tomflow/proflow-task-
|
|
28
|
-
"@tomflow/proflow-task-
|
|
27
|
+
"@tomflow/proflow-task-migration-runner": "^0.1.12",
|
|
28
|
+
"@tomflow/proflow-task-store-sqlite": "^0.1.14"
|
|
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.15",
|
|
7
7
|
"kind": "library",
|
|
8
8
|
"templateVersion": "1.0.0",
|
|
9
9
|
"platformCompatibility": ">=1.0.0 <2.0.0",
|