@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 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.13";
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.13";
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.13";
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.13";
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.13",
6
+ moduleVersion: "0.1.15",
7
7
  kind: "library",
8
8
  templateVersion: "1.0.0",
9
9
  platformCompatibility: ">=1.0.0 <2.0.0",
@@ -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
@@ -8,3 +8,4 @@ export const taskWaitTypes = [
8
8
  "REQUIREMENT_BLOCKED",
9
9
  "EXTERNAL_BUSINESS_DEPENDENCY",
10
10
  ];
11
+ export const taskDocumentRef = (taskId, documentType) => "taskdoc:" + encodeURIComponent(taskId) + ":" + encodeURIComponent(documentType);
@@ -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
- if ((input.campaignRef === undefined) !== (input.expectedCampaignVersion === undefined))
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: ["expectedCampaignVersion"],
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
- if (!subsetOf(input.scope, campaign.scope))
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: input.actorRef,
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
  });
@@ -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<{
@@ -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: { documentType: validatedType },
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.transaction((tx) => {
1434
+ const result = store.read((tx) => {
1377
1435
  requireTask(tx, input.taskId);
1378
- let reconciled = 0;
1379
- for (const type of allowedDocumentTypes) {
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
- continue;
1384
- const content = readFileSync(absolute, "utf8");
1385
- const current = tx.documents.get(input.taskId, type);
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.13",
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-store-sqlite": "^0.1.12",
28
- "@tomflow/proflow-task-migration-runner": "^0.1.11"
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": [
@@ -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.13",
6
+ "moduleVersion": "0.1.15",
7
7
  "kind": "library",
8
8
  "templateVersion": "1.0.0",
9
9
  "platformCompatibility": ">=1.0.0 <2.0.0",