@tomflow/proflow-task-orchestration 0.1.12 → 0.1.14
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/index.d.ts +3 -2
- package/dist/src/index.js +3 -2
- package/dist/src/model.d.ts +6 -0
- package/dist/src/model.js +1 -0
- package/dist/src/phase4-continuation-services.d.ts +135 -0
- package/dist/src/phase4-continuation-services.js +296 -0
- package/dist/src/phase4-contracts.d.ts +5 -0
- package/dist/src/phase4-contracts.js +193 -0
- package/dist/src/phase4-model.d.ts +122 -0
- package/dist/src/phase4-model.js +1 -0
- package/dist/src/phase4-services.d.ts +126 -0
- package/dist/src/phase4-services.js +830 -0
- package/dist/src/product-document-path.d.ts +2 -0
- package/dist/src/product-document-path.js +24 -0
- 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
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { lstatSync, realpathSync } from "node:fs";
|
|
2
|
+
import { resolve, sep } from "node:path";
|
|
3
|
+
/** Only owner-generated, flat Markdown files under the workspace are accepted. */
|
|
4
|
+
export function productDocumentAbsolute(workspaceRoot, filePath) {
|
|
5
|
+
if (!/^product-requirements\/[a-f0-9]{24}\.md$/.test(filePath))
|
|
6
|
+
throw new Error("PRODUCT_DOCUMENT_PATH_INVALID");
|
|
7
|
+
const workspace = realpathSync(workspaceRoot);
|
|
8
|
+
const directory = resolve(workspace, "product-requirements");
|
|
9
|
+
const absolute = resolve(workspace, filePath);
|
|
10
|
+
for (const path of [directory, absolute]) {
|
|
11
|
+
try {
|
|
12
|
+
const stat = lstatSync(path);
|
|
13
|
+
if (stat.isSymbolicLink() || (path === directory ? !stat.isDirectory() : !stat.isFile()))
|
|
14
|
+
throw new Error("PRODUCT_DOCUMENT_PATH_INVALID");
|
|
15
|
+
if (!realpathSync(path).startsWith(`${workspace}${sep}`))
|
|
16
|
+
throw new Error("PRODUCT_DOCUMENT_PATH_INVALID");
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return absolute;
|
|
24
|
+
}
|
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.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"@tomflow/proflow-module-contract": "^0.1.13"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@tomflow/proflow-
|
|
26
|
+
"@tomflow/proflow-task-store-sqlite": "^0.1.13",
|
|
27
27
|
"@tomflow/proflow-task-migration-runner": "^0.1.11",
|
|
28
|
-
"@tomflow/proflow-
|
|
28
|
+
"@tomflow/proflow-deployment-conformance": "^0.1.13"
|
|
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.14",
|
|
7
7
|
"kind": "library",
|
|
8
8
|
"templateVersion": "1.0.0",
|
|
9
9
|
"platformCompatibility": ">=1.0.0 <2.0.0",
|