@taskforcehq/taskforce 0.3.330 → 0.3.332

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.
Files changed (93) hide show
  1. package/README.md +7 -2
  2. package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.d.ts +2 -2
  3. package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.js +2 -2
  4. package/dist/components/features/planning/PlanningModule.js +15 -3
  5. package/dist/components/features/planning/planningMetadata.d.ts +10 -0
  6. package/dist/components/features/planning/planningMetadata.js +8 -0
  7. package/dist/components/views/StandaloneLayout.js +35 -1
  8. package/dist/components/views/panels/PlanningDrawer.d.ts +2 -1
  9. package/dist/components/views/panels/PlanningDrawer.js +12 -8
  10. package/dist/config/envSchema.js +7 -0
  11. package/dist/core/McpTokenService.d.ts +2 -0
  12. package/dist/core/McpTokenService.js +13 -5
  13. package/dist/core/Taskforce.d.ts +11 -3
  14. package/dist/core/Taskforce.js +33 -3
  15. package/dist/core/types.d.ts +1 -0
  16. package/dist/documentReviews/DocumentReviewCommentStore.d.ts +2 -0
  17. package/dist/documentReviews/DocumentReviewCommentStore.js +28 -9
  18. package/dist/mcp/httpProtocolRouting.d.ts +1 -0
  19. package/dist/mcp/httpProtocolRouting.js +8 -5
  20. package/dist/mcp/runtime.js +24 -5
  21. package/dist/mcp/taskPlanningRegistrar.js +2 -2
  22. package/dist/mcp/toolProfiles.js +1 -0
  23. package/dist/mcp/toolRegistry.js +8 -8
  24. package/dist/server/buildInfo.d.ts +1 -0
  25. package/dist/server/buildInfo.js +5 -1
  26. package/dist/server/cloudMcpCapacityTelemetry.d.ts +68 -0
  27. package/dist/server/cloudMcpCapacityTelemetry.js +221 -0
  28. package/dist/server/localWorkspaceSyncServerRuntime.js +13 -5
  29. package/dist/server/routes/admin.js +14 -2
  30. package/dist/server/routes/shared.d.ts +3 -0
  31. package/dist/server/routes/shared.js +280 -240
  32. package/dist/service/localServiceCli.d.ts +2 -2
  33. package/dist/service/localServiceCli.js +4 -3
  34. package/dist/services/executorPhaseAIssuerCore.d.ts +13 -0
  35. package/dist/services/executorPhaseAIssuerCore.js +107 -0
  36. package/dist/services/taskforceAgentMcpBridge.d.ts +1 -1
  37. package/dist/services/taskforceAgentMcpBridge.js +35 -5
  38. package/dist/shared/executorPhaseASchemaValidation.js +1 -1
  39. package/dist/storage/postgresAdapter.js +3 -1
  40. package/dist/storage/postgresRequestDiagnostics.d.ts +9 -1
  41. package/dist/storage/postgresRequestDiagnostics.js +15 -2
  42. package/dist/storage/postgresWorker.js +13 -3
  43. package/dist/storage/postgresWorkerProtocol.d.ts +6 -0
  44. package/dist/storage/workflowStore.d.ts +2 -0
  45. package/dist/storage/workflowStore.js +39 -2
  46. package/dist/sync/cloudSyncApi.js +4 -2
  47. package/dist/sync/collaborationSyncState.js +5 -0
  48. package/dist/sync/coordinator/localWorkspaceSyncRunner.d.ts +3 -1
  49. package/dist/sync/coordinator/localWorkspaceSyncRunner.js +18 -0
  50. package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.js +2 -0
  51. package/dist/sync/syncApplyHandlers.d.ts +1 -0
  52. package/dist/sync/syncApplyHandlers.js +72 -15
  53. package/dist/sync/v3/durableWorkflowRuntimeMutationRouter.d.ts +7 -7
  54. package/dist/sync/v3/durableWorkflowRuntimeMutationRouter.js +50 -13
  55. package/dist/sync/v3/localOutboxRecoveryPreparationService.js +8 -2
  56. package/dist/sync/v3/localTaskCreateOutboxService.d.ts +3 -1
  57. package/dist/sync/v3/localTaskCreateOutboxService.js +6 -1
  58. package/dist/sync/v3/localTaskDeleteOutboxService.js +1 -0
  59. package/dist/sync/v3/localTaskLifecycleOutboxService.d.ts +2 -1
  60. package/dist/sync/v3/localTaskLifecycleOutboxService.js +5 -1
  61. package/dist/sync/v3/localTaskMetadataOutboxService.d.ts +2 -1
  62. package/dist/sync/v3/localTaskMetadataOutboxService.js +6 -2
  63. package/dist/sync/v3/localTaskStatusOutboxService.d.ts +2 -1
  64. package/dist/sync/v3/localTaskStatusOutboxService.js +20 -5
  65. package/dist/sync/v3/syncDurabilityStore.d.ts +2 -1
  66. package/dist/sync/v3/syncDurabilityStore.js +34 -6
  67. package/dist/sync/v3/workflowRuntimeMutationService.d.ts +5 -0
  68. package/dist/sync/v3/workflowRuntimeMutationService.js +124 -71
  69. package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +25 -3
  70. package/dist/sync/v3/workspaceSyncV3Feed.d.ts +1 -0
  71. package/dist/sync/v3/workspaceSyncV3Feed.js +6 -1
  72. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.d.ts +2 -0
  73. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.js +49 -18
  74. package/dist/sync/v3/workspaceSyncV3WorkflowRuntimeApply.d.ts +1 -0
  75. package/dist/sync/v3/workspaceSyncV3WorkflowRuntimeApply.js +50 -5
  76. package/dist/ui/assets/{AiIdentityRosterCard-OcSz1PkK.js → AiIdentityRosterCard-CDMPtnbC.js} +1 -1
  77. package/dist/ui/assets/{AiProfilesModule-ByhYW5Xi.js → AiProfilesModule-Cww85C4E.js} +1 -1
  78. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BTM6nIg_.js → AnnotatedAttachmentWorkspace-Dt9TKYSH.js} +1 -1
  79. package/dist/ui/assets/{AssetTraySortControl-DabEG8L4.js → AssetTraySortControl-CKgapnxu.js} +1 -1
  80. package/dist/ui/assets/{ContextAttachmentManager-Coa8yVGM.js → ContextAttachmentManager-T-cEVcOZ.js} +2 -2
  81. package/dist/ui/assets/{DocumentWorkspace-BB0qhdix.js → DocumentWorkspace-HD4MUjAZ.js} +1 -1
  82. package/dist/ui/assets/{EntityActivityTimeline-CZ8x4UJE.js → EntityActivityTimeline-_V0zfMuh.js} +1 -1
  83. package/dist/ui/assets/PlanningModule-mTkBygsB.js +1 -0
  84. package/dist/ui/assets/{PlansPage-D86pZSSl.js → PlansPage-Btiq06VO.js} +1 -1
  85. package/dist/ui/assets/{TaskContextUpload-BVIkU9yT.js → TaskContextUpload-C00NVUpn.js} +1 -1
  86. package/dist/ui/assets/{TaskSettings-Cv1mNhv_.js → TaskSettings-Cp-L-4xh.js} +1 -1
  87. package/dist/ui/assets/{TaskforceAgentsModule-gmH-PD2q.js → TaskforceAgentsModule-Coq9-PUp.js} +2 -2
  88. package/dist/ui/assets/{WorkflowManagerModule-DbfnHJ-f.js → WorkflowManagerModule-CuqRzMmM.js} +1 -1
  89. package/dist/ui/assets/index-DSQINwt9.js +7 -0
  90. package/dist/ui/index.html +1 -1
  91. package/package.json +3 -1
  92. package/dist/ui/assets/PlanningModule-pRPjR7v5.js +0 -1
  93. package/dist/ui/assets/index-CTsDBaef.js +0 -7
@@ -6,11 +6,42 @@ import { buildTaskActivityJournalEntry, serializeTaskActivityForSync, } from './
6
6
  import { fingerprintWorkspaceSyncPayload, WORKSPACE_SYNC_CONTRACT_VERSION, } from './syncDurabilityStore.js';
7
7
  import { buildWorkflowRuntimeEventId, buildWorkflowRuntimeTaskActivityId, } from './workflowRuntimeOperationIdentity.js';
8
8
  import { fingerprintWorkflowRuntimePayload, serializeWorkflowExecutionEventSyncPayload, serializeWorkflowExecutionRootSyncPayload, validateWorkflowRuntimeAuthoritativeResult, validateWorkflowRuntimeMutationCommand, } from './workflowRuntimeSync.js';
9
+ export class WorkflowRuntimeSemanticReplayError extends Error {
10
+ code = 'WORKFLOW_RUNTIME_SEMANTIC_REPLAY_REQUIRED';
11
+ statusCode = 409;
12
+ constructor() {
13
+ super('This workflow command was already completed by another synchronized runtime.');
14
+ this.name = 'WorkflowRuntimeSemanticReplayError';
15
+ }
16
+ }
17
+ function hasEquivalentCompletedStep(input) {
18
+ if (input.command.kind !== 'complete-step')
19
+ return false;
20
+ const events = input.workflowStore.listExecutionEvents(input.workspaceId, input.command.executionId);
21
+ const completed = events.find((event) => (event.idempotencyKey === `${input.command.idempotencyKey}:step-completed`));
22
+ const terminal = events.find((event) => (event.idempotencyKey === `${input.command.idempotencyKey}:step-started`
23
+ || event.idempotencyKey === `${input.command.idempotencyKey}:execution-completed`));
24
+ if (!completed || !terminal)
25
+ return false;
26
+ const evidence = completed.details.evidence;
27
+ const evidenceCommentId = evidence
28
+ && typeof evidence === 'object'
29
+ && !Array.isArray(evidence)
30
+ && evidence.type === 'comment'
31
+ ? String(evidence.commentId || '')
32
+ : '';
33
+ return completed.actorId === input.command.actorId
34
+ && terminal.actorId === input.command.actorId
35
+ && completed.stepId === input.command.expectedStepId
36
+ && evidenceCommentId === (input.command.evidenceCommentId || '');
37
+ }
9
38
  export function prepareWorkflowRuntimeMutation(input) {
10
39
  const before = input.workflowStore.getExecutionStateSnapshot(input.workspaceId, input.command.executionId);
11
40
  if (!before)
12
41
  throw new Error('Workflow execution not found.');
13
42
  if (before.execution.status !== input.command.expectedStatus) {
43
+ if (hasEquivalentCompletedStep(input))
44
+ throw new WorkflowRuntimeSemanticReplayError();
14
45
  throw new Error('Workflow execution has advanced beyond the expected status.');
15
46
  }
16
47
  if (input.command.kind === 'complete-step' && input.command.taskTarget.taskId !== before.execution.taskId) {
@@ -191,13 +222,25 @@ export function runDurableWorkflowRuntimeMutation(input) {
191
222
  response: JSON.parse(reservation.responseJson),
192
223
  };
193
224
  }
194
- const prepared = prepareWorkflowRuntimeMutation({
195
- workflowStore,
196
- workspaceId: input.workspaceId,
197
- clientId: input.clientId,
198
- operationId: input.operationId,
199
- command,
200
- });
225
+ let prepared;
226
+ try {
227
+ prepared = prepareWorkflowRuntimeMutation({
228
+ workflowStore,
229
+ workspaceId: input.workspaceId,
230
+ clientId: input.clientId,
231
+ operationId: input.operationId,
232
+ command,
233
+ });
234
+ }
235
+ catch (error) {
236
+ input.store.releaseFailedOperationReservation({
237
+ identity,
238
+ requestFingerprint,
239
+ leaseOwner: reservation.leaseOwner,
240
+ now: input.now,
241
+ });
242
+ throw error;
243
+ }
201
244
  const { before, willReassignTask, taskActivityId, eventIds } = prepared;
202
245
  const readTaskActivity = (result) => {
203
246
  const activity = result.taskActivity || input.core.getTaskEventForSync({
@@ -262,73 +305,83 @@ export function runDurableWorkflowRuntimeMutation(input) {
262
305
  },
263
306
  })),
264
307
  ];
265
- const accepted = input.store.acceptReservedOperationBatch({
266
- identity,
267
- requestFingerprint,
268
- leaseOwner: reservation.leaseOwner,
269
- now: input.now,
270
- entries,
271
- applyMutation: () => applyPreparedWorkflowRuntimeMutation({
272
- core: input.core,
273
- workflowStore,
274
- workspaceId: input.workspaceId,
275
- command,
276
- prepared,
277
- now: input.now,
278
- }),
279
- buildResponse: (result, revisions) => {
280
- const executionPayload = serializeWorkflowExecutionRootSyncPayload(result.executionResult.execution);
281
- const eventPayloads = result.executionResult.events.map(serializeWorkflowExecutionEventSyncPayload);
282
- const projectionRevisions = [
283
- {
284
- projection: 'workflow-execution/root-v1',
285
- ...revisionFor(revisions, 'workflow-execution', command.executionId),
286
- canonicalFingerprint: fingerprintWorkflowRuntimePayload(executionPayload),
287
- },
288
- ...result.newEvents.map((event) => {
289
- const payload = serializeWorkflowExecutionEventSyncPayload(event);
308
+ const accepted = (() => {
309
+ try {
310
+ return input.store.acceptReservedOperationBatch({
311
+ identity,
312
+ requestFingerprint,
313
+ leaseOwner: reservation.leaseOwner,
314
+ now: input.now,
315
+ entries,
316
+ applyMutation: () => applyPreparedWorkflowRuntimeMutation({
317
+ core: input.core,
318
+ workflowStore,
319
+ workspaceId: input.workspaceId,
320
+ command,
321
+ prepared,
322
+ now: input.now,
323
+ }),
324
+ buildResponse: (result, revisions) => {
325
+ const executionPayload = serializeWorkflowExecutionRootSyncPayload(result.executionResult.execution);
326
+ const eventPayloads = result.executionResult.events.map(serializeWorkflowExecutionEventSyncPayload);
327
+ const projectionRevisions = [
328
+ {
329
+ projection: 'workflow-execution/root-v1',
330
+ ...revisionFor(revisions, 'workflow-execution', command.executionId),
331
+ canonicalFingerprint: fingerprintWorkflowRuntimePayload(executionPayload),
332
+ },
333
+ ...result.newEvents.map((event) => {
334
+ const payload = serializeWorkflowExecutionEventSyncPayload(event);
335
+ return {
336
+ projection: 'workflow-execution-event/full-v1',
337
+ ...revisionFor(revisions, 'workflow-execution-event', event.id),
338
+ canonicalFingerprint: fingerprintWorkflowRuntimePayload(payload),
339
+ };
340
+ }),
341
+ ...(result.task ? [{
342
+ projection: 'task/root-v1',
343
+ ...revisionFor(revisions, 'task', result.task.id),
344
+ canonicalFingerprint: fingerprintWorkspaceSyncPayload(serializeTaskForSync(result.task)),
345
+ }, {
346
+ projection: 'entity-event/full-v1',
347
+ ...revisionFor(revisions, 'entity-event', readTaskActivity(result).id),
348
+ canonicalFingerprint: fingerprintWorkspaceSyncPayload(serializeTaskActivityForSync(readTaskActivity(result))),
349
+ }] : []),
350
+ ].sort((left, right) => BigInt(left.sequence) < BigInt(right.sequence) ? -1 : 1);
351
+ const authoritative = validateWorkflowRuntimeAuthoritativeResult({
352
+ execution: executionPayload,
353
+ events: eventPayloads,
354
+ acceptedEventIds: result.newEvents.map((event) => event.id),
355
+ ...(result.task ? {
356
+ task: {
357
+ payload: serializeTaskForSync(result.task),
358
+ activity: serializeTaskActivityForSync(readTaskActivity(result)),
359
+ },
360
+ } : {}),
361
+ revisions: projectionRevisions,
362
+ replayed: result.executionResult.replayed,
363
+ });
364
+ const executionRevision = revisionFor(revisions, 'workflow-execution', command.executionId);
290
365
  return {
291
- projection: 'workflow-execution-event/full-v1',
292
- ...revisionFor(revisions, 'workflow-execution-event', event.id),
293
- canonicalFingerprint: fingerprintWorkflowRuntimePayload(payload),
366
+ success: true,
367
+ contractVersion: WORKSPACE_SYNC_CONTRACT_VERSION,
368
+ workspaceId: input.workspaceId,
369
+ clientId: input.clientId,
370
+ operationId: input.operationId,
371
+ acceptedSequence: executionRevision.sequence,
372
+ operationHighWatermark: revisions.at(-1)?.sequence || executionRevision.sequence,
373
+ authoritative,
294
374
  };
295
- }),
296
- ...(result.task ? [{
297
- projection: 'task/root-v1',
298
- ...revisionFor(revisions, 'task', result.task.id),
299
- canonicalFingerprint: fingerprintWorkspaceSyncPayload(serializeTaskForSync(result.task)),
300
- }, {
301
- projection: 'entity-event/full-v1',
302
- ...revisionFor(revisions, 'entity-event', readTaskActivity(result).id),
303
- canonicalFingerprint: fingerprintWorkspaceSyncPayload(serializeTaskActivityForSync(readTaskActivity(result))),
304
- }] : []),
305
- ].sort((left, right) => BigInt(left.sequence) < BigInt(right.sequence) ? -1 : 1);
306
- const authoritative = validateWorkflowRuntimeAuthoritativeResult({
307
- execution: executionPayload,
308
- events: eventPayloads,
309
- acceptedEventIds: result.newEvents.map((event) => event.id),
310
- ...(result.task ? {
311
- task: {
312
- payload: serializeTaskForSync(result.task),
313
- activity: serializeTaskActivityForSync(readTaskActivity(result)),
314
- },
315
- } : {}),
316
- revisions: projectionRevisions,
317
- replayed: result.executionResult.replayed,
375
+ },
318
376
  });
319
- const executionRevision = revisionFor(revisions, 'workflow-execution', command.executionId);
320
- return {
321
- success: true,
322
- contractVersion: WORKSPACE_SYNC_CONTRACT_VERSION,
323
- workspaceId: input.workspaceId,
324
- clientId: input.clientId,
325
- operationId: input.operationId,
326
- acceptedSequence: executionRevision.sequence,
327
- operationHighWatermark: revisions.at(-1)?.sequence || executionRevision.sequence,
328
- authoritative,
329
- };
330
- },
331
- });
377
+ }
378
+ catch (error) {
379
+ if (hasEquivalentCompletedStep({ workflowStore, workspaceId: input.workspaceId, command })) {
380
+ throw new WorkflowRuntimeSemanticReplayError();
381
+ }
382
+ throw error;
383
+ }
384
+ })();
332
385
  const notify = (callback) => {
333
386
  if (!callback)
334
387
  return;
@@ -13,7 +13,7 @@ import { enqueueLocalTaskCommentReconciliation } from './localTaskCommentOutboxS
13
13
  import { buildWorkspaceSyncV3TaskCommentReconcileProvenance, indexWorkspaceSyncV3TaskCommentProvenanceEvidence, } from './taskCommentReconcileProvenance.js';
14
14
  import { serializeTaskRootForSync, } from './workspaceSyncV3TaskRootProjection.js';
15
15
  import { prepareWorkspaceSyncV3WorkflowRuntimeApply } from './workspaceSyncV3WorkflowRuntimeApply.js';
16
- import { serializeWorkflowExecutionEventSyncPayload, serializeWorkflowExecutionRootSyncPayload, serializeWorkflowPublishedDefinitionSyncPayload, } from './workflowRuntimeSync.js';
16
+ import { serializeWorkflowExecutionEventSyncPayload, serializeWorkflowExecutionRootSyncPayload, serializeWorkflowPublishedDefinitionSyncPayload, validateWorkflowExecutionEventSyncPayload, } from './workflowRuntimeSync.js';
17
17
  import { serializeWorkstreamTaskOrderForSync } from './workstreamTaskOrderSync.js';
18
18
  import { validateWorkspaceSyncV3WorkstreamTaskOrderFeedEntry } from './workspaceSyncV3WorkstreamTaskOrderProjection.js';
19
19
  function combinedRepairBaselineMarker(page) {
@@ -198,6 +198,9 @@ function applyEntries(input) {
198
198
  store: new WorkflowStore(input.core.getDatabaseAdapter(), input.tenantId),
199
199
  workspaceId: input.workspaceId,
200
200
  entries: input.entries,
201
+ replaceAuthoritativeHistoryExecutionIds: input.allowAuthoritativeRevisionReset
202
+ ? input.authoritativeWorkflowRecoveryExecutionIds
203
+ : undefined,
201
204
  });
202
205
  return store.runWorkspaceDomainCoexistenceTransaction({
203
206
  tenantId: input.tenantId,
@@ -1011,7 +1014,7 @@ export function applyWorkspaceSyncV3CombinedRepairPage(input) {
1011
1014
  : [];
1012
1015
  const selectRecoveryEntries = (entries) => {
1013
1016
  const byTarget = new Map(entries.map((entry) => [`${entry.domain}\0${entry.entityId}`, entry]));
1014
- return recoveryTargets.map((target) => {
1017
+ const selected = recoveryTargets.map((target) => {
1015
1018
  const entry = byTarget.get(`${target.domain}\0${target.entityId}`);
1016
1019
  if (!entry) {
1017
1020
  if (!target.required)
@@ -1023,12 +1026,28 @@ export function applyWorkspaceSyncV3CombinedRepairPage(input) {
1023
1026
  || (target.domain === 'task-attachment-links'
1024
1027
  && (entry.projection === 'task-attachment-links/full-v1'
1025
1028
  || entry.projection === 'task-attachment-links/full-v2'))
1026
- || (target.domain === 'entity-event' && entry.projection === 'entity-event/full-v1'));
1029
+ || (target.domain === 'entity-event' && entry.projection === 'entity-event/full-v1')
1030
+ || (target.domain === 'workflow-execution'
1031
+ && entry.projection === 'workflow-execution/root-v1'));
1027
1032
  if (!supportedTarget) {
1028
1033
  throw new Error('Targeted workspace sync recovery does not support this projection.');
1029
1034
  }
1030
1035
  return entry;
1031
1036
  }).filter((entry) => entry !== null);
1037
+ const workflowExecutionIds = new Set(selected
1038
+ .filter((entry) => entry.projection === 'workflow-execution/root-v1')
1039
+ .map((entry) => entry.entityId));
1040
+ if (workflowExecutionIds.size > 0) {
1041
+ for (const entry of entries) {
1042
+ if (entry.projection !== 'workflow-execution-event/full-v1')
1043
+ continue;
1044
+ const payload = validateWorkflowExecutionEventSyncPayload(entry.payload);
1045
+ if (!workflowExecutionIds.has(payload.event.workflowExecutionId))
1046
+ continue;
1047
+ selected.push(entry);
1048
+ }
1049
+ }
1050
+ return selected;
1032
1051
  };
1033
1052
  const projections = coveredProjections(input.page);
1034
1053
  let notices = notifications();
@@ -1056,6 +1075,9 @@ export function applyWorkspaceSyncV3CombinedRepairPage(input) {
1056
1075
  authoritativeEntityEventRecoveryIds: new Set(recoveryTargets
1057
1076
  .filter((target) => target.domain === 'entity-event')
1058
1077
  .map((target) => target.entityId)),
1078
+ authoritativeWorkflowRecoveryExecutionIds: new Set(recoveryTargets
1079
+ .filter((target) => target.domain === 'workflow-execution')
1080
+ .map((target) => target.entityId)),
1059
1081
  } : {}),
1060
1082
  });
1061
1083
  },
@@ -34,6 +34,7 @@ export interface WorkspaceSyncV3FeedProjectionInput {
34
34
  rows: readonly WorkspaceSyncJournalRow[];
35
35
  cycleHighWatermark: string;
36
36
  followingRow?: WorkspaceSyncJournalRow | null;
37
+ workspaceId: string;
37
38
  }
38
39
  export type WorkspaceSyncV3FeedOperationProjector = (input: WorkspaceSyncV3FeedProjectionInput) => WorkspaceSyncV3FeedEntry[];
39
40
  export interface WorkspaceSyncV3FeedCoveragePolicy {
@@ -207,7 +207,12 @@ export function buildWorkspaceSyncV3Feed(input) {
207
207
  throw new WorkspaceSyncV3FeedOperationTooLargeError(`Workspace sync operation ${clientId}/${operationId} exceeds ${MAX_OPERATION_ROWS} journal rows.`);
208
208
  }
209
209
  const groupRows = rows.slice(offset, end);
210
- const groupEntries = projectOperation({ rows: groupRows, cycleHighWatermark: String(high), followingRow: rows[end] || null });
210
+ const groupEntries = projectOperation({
211
+ rows: groupRows,
212
+ cycleHighWatermark: String(high),
213
+ followingRow: rows[end] || null,
214
+ workspaceId: input.workspaceId,
215
+ });
211
216
  if (entries.length + groupEntries.length > pageSize) {
212
217
  if (!entries.length)
213
218
  throw new WorkspaceSyncV3FeedOperationTooLargeError(`Covered operation ${clientId}/${operationId} exceeds the feed entry limit.`);
@@ -49,9 +49,11 @@ export declare function projectWorkspaceSyncV3NestedTaskOperation(input: {
49
49
  rows: readonly WorkspaceSyncJournalRow[];
50
50
  cycleHighWatermark: string;
51
51
  followingRow?: WorkspaceSyncJournalRow | null;
52
+ workspaceId: string;
52
53
  }): WorkspaceSyncV3FeedEntry[] | null;
53
54
  export declare function projectWorkspaceSyncV3NestedTaskV2Operation(input: {
54
55
  rows: readonly WorkspaceSyncJournalRow[];
55
56
  cycleHighWatermark: string;
56
57
  followingRow?: WorkspaceSyncJournalRow | null;
58
+ workspaceId: string;
57
59
  }): WorkspaceSyncV3FeedEntry[] | null;
@@ -225,6 +225,34 @@ function projectNestedDependentRows(rows, attachmentProjection) {
225
225
  return [projected];
226
226
  });
227
227
  }
228
+ function assertOperationEventWorkspace(events, workspaceId) {
229
+ if (events.some((event) => event.workspaceId !== workspaceId)) {
230
+ throw new Error('Nested task operation activity belongs to another workspace.');
231
+ }
232
+ }
233
+ function assertTaskOperationActivities(events, taskId) {
234
+ let membershipStarted = false;
235
+ let taskActivityCount = 0;
236
+ for (const event of events) {
237
+ if (event.entityType === 'task') {
238
+ if (membershipStarted) {
239
+ throw new Error('Task operation activity cannot follow workstream membership activity.');
240
+ }
241
+ if (event.entityId !== taskId || event.taskId !== taskId) {
242
+ throw new Error('Nested task operation event identity does not match its parent root.');
243
+ }
244
+ taskActivityCount += 1;
245
+ continue;
246
+ }
247
+ membershipStarted = true;
248
+ if (event.entityType !== 'workstream' || event.details?.taskId !== taskId) {
249
+ throw new Error('Task operation secondary activity does not describe workstream membership.');
250
+ }
251
+ }
252
+ if (taskActivityCount < 1) {
253
+ throw new Error('Task operation must contain primary task activity before workstream membership.');
254
+ }
255
+ }
228
256
  function isSupportedNestedOnlyOperation(rows) {
229
257
  const first = rows[0];
230
258
  if (!first)
@@ -247,7 +275,7 @@ function isSupportedNestedOnlyOperation(rows) {
247
275
  * root. Apply still requires referenced parents to exist on the receiver; feed
248
276
  * only needs to project the nested rows.
249
277
  */
250
- function projectNestedOnlyOperation(rows, attachmentProjection) {
278
+ function projectNestedOnlyOperation(rows, attachmentProjection, workspaceId) {
251
279
  if (!isSupportedNestedOnlyOperation(rows)) {
252
280
  throw new Error('Nested-only task operation is not an allowed repair or reconciliation shape.');
253
281
  }
@@ -259,6 +287,10 @@ function projectNestedOnlyOperation(rows, attachmentProjection) {
259
287
  throw new Error('Nested task operation dependent identity does not match its task root.');
260
288
  }
261
289
  }
290
+ const rawEvents = rows
291
+ .filter((row) => row.domain === 'entity-event')
292
+ .map((row) => canonicalEntityEventPayload(parsePayload(row)));
293
+ assertOperationEventWorkspace(rawEvents, workspaceId);
262
294
  const projected = projectNestedDependentRows(rows, attachmentProjection);
263
295
  const events = projected
264
296
  .filter((entry) => entry.domain === 'entity-event')
@@ -268,13 +300,7 @@ function projectNestedOnlyOperation(rows, attachmentProjection) {
268
300
  }
269
301
  if (taskScoped.length > 0 && events.length > 0) {
270
302
  const taskId = taskScoped[0].entityId;
271
- const [primaryEvent, ...secondaryEvents] = events;
272
- if (primaryEvent && (primaryEvent.entityType !== 'task' || primaryEvent.entityId !== taskId)) {
273
- throw new Error('Nested task operation event identity does not match its parent root.');
274
- }
275
- if (secondaryEvents.some((event) => (event.entityType !== 'workstream' || event.details?.taskId !== taskId))) {
276
- throw new Error('Task operation secondary activity does not describe workstream membership.');
277
- }
303
+ assertTaskOperationActivities(events, taskId);
278
304
  }
279
305
  if (events.length > 0 && projected.at(-1)?.domain !== 'entity-event') {
280
306
  throw new Error('Nested task operation must end with entity activity.');
@@ -293,7 +319,7 @@ function projectNestedTaskOperation(input, attachmentProjection) {
293
319
  const [first, ...dependents] = input.rows;
294
320
  if (first && projectionByDomain.has(first.domain)
295
321
  && input.rows.every((row) => projectionByDomain.has(row.domain))) {
296
- return projectNestedOnlyOperation(input.rows, attachmentProjection);
322
+ return projectNestedOnlyOperation(input.rows, attachmentProjection, input.workspaceId);
297
323
  }
298
324
  if (!first || !['task', 'initiative', 'workstream'].includes(first.domain)) {
299
325
  throw new Error('Nested task operation must begin with its v3-owned parent root.');
@@ -302,10 +328,13 @@ function projectNestedTaskOperation(input, attachmentProjection) {
302
328
  throw new Error('Nested task operation contains an unsupported dependent row.');
303
329
  }
304
330
  assertNestedDependentOrder(dependents);
331
+ const rawEventRows = dependents.filter((row) => row.domain === 'entity-event');
332
+ const rawEvents = rawEventRows.map((row) => canonicalEntityEventPayload(parsePayload(row)));
333
+ assertOperationEventWorkspace(rawEvents, input.workspaceId);
305
334
  const projectedDependents = projectNestedDependentRows(dependents, attachmentProjection);
306
335
  const eventRows = projectedDependents.filter((entry) => entry.domain === 'entity-event');
307
- const rawEventRows = dependents.filter((row) => row.domain === 'entity-event');
308
- const sentinelOnly = eventRows.length === 0 && rawEventRows.length > 0 && rawEventRows.every((row) => (canonicalEntityEventPayload(parsePayload(row)).action === 'sync-v3-repair-baseline'));
336
+ const sentinelOnly = eventRows.length === 0 && rawEvents.length > 0
337
+ && rawEvents.every((event) => event.action === 'sync-v3-repair-baseline');
309
338
  const nestedBodiesOnly = eventRows.length === 0
310
339
  && projectedDependents.length > 0
311
340
  && projectedDependents.every((entry) => entry.domain !== 'entity-event');
@@ -324,17 +353,19 @@ function projectNestedTaskOperation(input, attachmentProjection) {
324
353
  }
325
354
  const events = eventRows.map((entry) => validateWorkspaceSyncV3EntityEventPayload(entry.payload));
326
355
  const [primaryEvent, ...secondaryEvents] = events;
327
- if (primaryEvent && (primaryEvent.entityType !== first.domain || primaryEvent.entityId !== first.entityId)) {
328
- throw new Error('Nested task operation event identity does not match its parent root.');
329
- }
330
356
  if (new Set(events.map((event) => event.id)).size !== events.length) {
331
357
  throw new Error('Nested task operation contains duplicate entity-event identities.');
332
358
  }
333
- if (first.domain !== 'task' && secondaryEvents.length > 0) {
334
- throw new Error('Planning-root operations cannot contain secondary entity activity.');
359
+ if (first.domain === 'task' && events.length > 0) {
360
+ assertTaskOperationActivities(events, first.entityId);
335
361
  }
336
- if (first.domain === 'task' && secondaryEvents.some((event) => (event.entityType !== 'workstream' || event.details?.taskId !== first.entityId))) {
337
- throw new Error('Task operation secondary activity does not describe workstream membership.');
362
+ else if (first.domain !== 'task') {
363
+ if (primaryEvent && (primaryEvent.entityType !== first.domain || primaryEvent.entityId !== first.entityId)) {
364
+ throw new Error('Nested task operation event identity does not match its parent root.');
365
+ }
366
+ if (secondaryEvents.length > 0) {
367
+ throw new Error('Planning-root operations cannot contain secondary entity activity.');
368
+ }
338
369
  }
339
370
  let parent;
340
371
  if (first.domain === 'task') {
@@ -14,4 +14,5 @@ export declare function prepareWorkspaceSyncV3WorkflowRuntimeApply(input: {
14
14
  store: WorkflowStore;
15
15
  workspaceId: string;
16
16
  entries: WorkspaceSyncV3FeedEntry[];
17
+ replaceAuthoritativeHistoryExecutionIds?: ReadonlySet<string>;
17
18
  }): WorkspaceSyncV3WorkflowRuntimeApplyPlan;
@@ -1,5 +1,5 @@
1
1
  import { canonicalWorkspaceSyncJson } from './canonicalWorkspaceSyncJson.js';
2
- import { validateWorkflowExecutionEventSyncPayload, validateWorkflowExecutionRootSyncPayload, validateWorkflowPublishedDefinitionSyncPayload, validateWorkflowRuntimeEventHistory, } from './workflowRuntimeSync.js';
2
+ import { serializeWorkflowPublishedDefinitionSyncPayload, validateWorkflowExecutionEventSyncPayload, validateWorkflowExecutionRootSyncPayload, validateWorkflowPublishedDefinitionSyncPayload, validateWorkflowRuntimeEventHistory, } from './workflowRuntimeSync.js';
3
3
  import { validateWorkspaceSyncV3WorkflowRuntimeFeedEntry } from './workspaceSyncV3WorkflowRuntimeProjection.js';
4
4
  const WORKFLOW_PROJECTIONS = new Set([
5
5
  'workflow-published-definition/full-v2',
@@ -9,6 +9,19 @@ const WORKFLOW_PROJECTIONS = new Set([
9
9
  function compareEvents(left, right) {
10
10
  return canonicalWorkspaceSyncJson(left) === canonicalWorkspaceSyncJson(right);
11
11
  }
12
+ function executionIdentity(root) {
13
+ const execution = root.execution;
14
+ return canonicalWorkspaceSyncJson({
15
+ id: execution.id,
16
+ tenantId: execution.tenantId,
17
+ workspaceId: execution.workspaceId,
18
+ taskId: execution.taskId,
19
+ workflowTemplateId: execution.workflowTemplateId,
20
+ workflowVersionId: execution.workflowVersionId,
21
+ createdByActorId: execution.createdByActorId || null,
22
+ createdAt: execution.createdAt,
23
+ });
24
+ }
12
25
  function mergeEventHistory(input) {
13
26
  const local = [...input.local].sort((left, right) => left.sequenceNumber - right.sequenceNumber);
14
27
  const incoming = [...input.incoming].sort((left, right) => left.sequenceNumber - right.sequenceNumber);
@@ -47,6 +60,19 @@ function derivedTemplate(definition, existing) {
47
60
  deletedAt: null,
48
61
  };
49
62
  }
63
+ function installedPublishedDefinition(store, workspaceId, versionId) {
64
+ const version = store.getVersion(workspaceId, versionId);
65
+ if (!version || version.status !== 'published')
66
+ return null;
67
+ const template = store.getTemplate(workspaceId, version.workflowTemplateId);
68
+ if (!template)
69
+ return null;
70
+ return serializeWorkflowPublishedDefinitionSyncPayload({
71
+ template,
72
+ version,
73
+ steps: store.listSteps(workspaceId, version.id),
74
+ });
75
+ }
50
76
  /**
51
77
  * Builds complete workflow aggregates before any revision is accepted. A root
52
78
  * entry is the atomic install trigger; definition and event entries only carry
@@ -56,6 +82,7 @@ export function prepareWorkspaceSyncV3WorkflowRuntimeApply(input) {
56
82
  const workflowEntries = new Set(input.entries.filter((entry) => WORKFLOW_PROJECTIONS.has(entry.projection)));
57
83
  const definitions = new Map();
58
84
  const roots = new Map();
85
+ const rootEntries = new Map();
59
86
  const eventsByExecution = new Map();
60
87
  for (const entry of workflowEntries) {
61
88
  validateWorkspaceSyncV3WorkflowRuntimeFeedEntry(entry);
@@ -68,10 +95,23 @@ export function prepareWorkspaceSyncV3WorkflowRuntimeApply(input) {
68
95
  }
69
96
  else if (entry.projection === 'workflow-execution/root-v1') {
70
97
  const root = validateWorkflowExecutionRootSyncPayload(entry.payload);
71
- if (roots.has(root.execution.id)) {
72
- throw new Error(`Workflow execution root is duplicated in one apply batch: ${root.execution.id}.`);
98
+ const existing = roots.get(root.execution.id);
99
+ const existingEntry = rootEntries.get(root.execution.id);
100
+ if (existing && existingEntry) {
101
+ if (executionIdentity(existing) !== executionIdentity(root)) {
102
+ throw new Error(`Workflow execution root identity changes in one apply batch: ${root.execution.id}.`);
103
+ }
104
+ const revision = BigInt(entry.entityRevision);
105
+ const existingRevision = BigInt(existingEntry.entityRevision);
106
+ if (revision === existingRevision
107
+ && canonicalWorkspaceSyncJson(existing) !== canonicalWorkspaceSyncJson(root)) {
108
+ throw new Error(`Workflow execution root revision conflicts in one apply batch: ${root.execution.id}.`);
109
+ }
110
+ if (revision <= existingRevision)
111
+ continue;
73
112
  }
74
113
  roots.set(root.execution.id, root);
114
+ rootEntries.set(root.execution.id, entry);
75
115
  }
76
116
  else {
77
117
  const event = validateWorkflowExecutionEventSyncPayload(entry.payload).event;
@@ -87,6 +127,7 @@ export function prepareWorkspaceSyncV3WorkflowRuntimeApply(input) {
87
127
  }
88
128
  const assignments = new Map();
89
129
  for (const [executionId, root] of roots) {
130
+ const replaceAuthoritativeHistory = Boolean(input.replaceAuthoritativeHistoryExecutionIds?.has(executionId));
90
131
  const local = input.store.getAssignmentForExecutionSync(input.workspaceId, executionId);
91
132
  const definition = definitions.get(root.execution.workflowVersionId)
92
133
  || (local ? {
@@ -97,12 +138,13 @@ export function prepareWorkspaceSyncV3WorkflowRuntimeApply(input) {
97
138
  },
98
139
  version: local.version,
99
140
  steps: local.steps,
100
- } : null);
141
+ } : null)
142
+ || installedPublishedDefinition(input.store, input.workspaceId, root.execution.workflowVersionId);
101
143
  if (!definition) {
102
144
  throw new Error(`Workflow execution ${executionId} is missing its published definition.`);
103
145
  }
104
146
  const merged = mergeEventHistory({
105
- local: local?.events || [],
147
+ local: replaceAuthoritativeHistory ? [] : local?.events || [],
106
148
  incoming: eventsByExecution.get(executionId) || [],
107
149
  });
108
150
  const execution = merged.localExtension && local
@@ -131,12 +173,15 @@ export function prepareWorkspaceSyncV3WorkflowRuntimeApply(input) {
131
173
  return;
132
174
  if (entry.projection !== 'workflow-execution/root-v1')
133
175
  return;
176
+ if (rootEntries.get(entry.entityId) !== entry)
177
+ return;
134
178
  const assignment = assignments.get(entry.entityId);
135
179
  if (!assignment)
136
180
  throw new Error(`Workflow execution apply plan is missing ${entry.entityId}.`);
137
181
  input.store.installAssignmentForSync(input.workspaceId, assignment, {
138
182
  authority: 'cloud-execution',
139
183
  expectedTaskId: assignment.execution.taskId,
184
+ replaceAuthoritativeHistory: input.replaceAuthoritativeHistoryExecutionIds?.has(entry.entityId),
140
185
  });
141
186
  },
142
187
  };
@@ -1 +1 @@
1
- import{j as i}from"./vendor-react-CKJs5o3c.js";import{t as r}from"./index-CTsDBaef.js";const t=[{name:"Blue",value:"#4269d0"},{name:"Orange",value:"#efb118"},{name:"Red",value:"#ff725c"},{name:"Cyan",value:"#6cc5b0"},{name:"Green",value:"#3ca951"},{name:"Pink",value:"#ff8ab7"},{name:"Purple",value:"#a463f2"},{name:"Light blue",value:"#97bbf5"},{name:"Brown",value:"#9c6b4e"},{name:"Gray",value:"#9498a0"}];function N(e){const s=String(e||"ai").trim().toLowerCase();let a=2166136261;for(let l=0;l<s.length;l+=1)a^=s.charCodeAt(l),a=Math.imul(a,16777619);return t[(a>>>0)%t.length].value}function b(e){const[s,a=""]=e.split("#"),n=s.includes("?")?"&":"?";return`${s}${n}taskforceCloud=1${a?`#${a}`:""}`}function $(e){return/^https?:\/\//i.test(e)?e:b(e)}function j({name:e,username:s,subtitle:a,avatar:n,selected:l,disabled:o=!1,onSelect:u,ariaLabel:c,avatarVariant:f="portrait",avatarClassName:m="",avatarStyle:d,className:p="",topUtility:h,bottomUtility:v,footer:P}){return i.jsxs("button",{type:"button",className:`${r.aiProfileGroup} ${l?r.aiProfileGroupSelected:""} ${p}`.trim(),onClick:u,disabled:o,"aria-label":c,"aria-pressed":l,children:[h,v,i.jsxs("span",{className:r.aiProfileGroupHeader,children:[i.jsx("span",{className:`${r.aiProfileGroupAvatar} ${f==="logo"?r.aiProfileGroupAvatarLogo:""} ${m}`.trim(),style:d,"aria-hidden":"true",children:n}),i.jsxs("span",{className:r.aiProfileGroupIdentity,children:[i.jsx("span",{className:r.aiProfileName,children:e}),s?i.jsxs("span",{className:r.aiProfileHandle,children:["@",s]}):null,i.jsx("span",{className:r.aiProfileRole,children:a}),P]})]})]})}export{j as A,N as a,t as b,$ as r,b as w};
1
+ import{j as i}from"./vendor-react-CKJs5o3c.js";import{t as r}from"./index-DSQINwt9.js";const t=[{name:"Blue",value:"#4269d0"},{name:"Orange",value:"#efb118"},{name:"Red",value:"#ff725c"},{name:"Cyan",value:"#6cc5b0"},{name:"Green",value:"#3ca951"},{name:"Pink",value:"#ff8ab7"},{name:"Purple",value:"#a463f2"},{name:"Light blue",value:"#97bbf5"},{name:"Brown",value:"#9c6b4e"},{name:"Gray",value:"#9498a0"}];function N(e){const s=String(e||"ai").trim().toLowerCase();let a=2166136261;for(let l=0;l<s.length;l+=1)a^=s.charCodeAt(l),a=Math.imul(a,16777619);return t[(a>>>0)%t.length].value}function b(e){const[s,a=""]=e.split("#"),n=s.includes("?")?"&":"?";return`${s}${n}taskforceCloud=1${a?`#${a}`:""}`}function $(e){return/^https?:\/\//i.test(e)?e:b(e)}function j({name:e,username:s,subtitle:a,avatar:n,selected:l,disabled:o=!1,onSelect:u,ariaLabel:c,avatarVariant:f="portrait",avatarClassName:m="",avatarStyle:d,className:p="",topUtility:h,bottomUtility:v,footer:P}){return i.jsxs("button",{type:"button",className:`${r.aiProfileGroup} ${l?r.aiProfileGroupSelected:""} ${p}`.trim(),onClick:u,disabled:o,"aria-label":c,"aria-pressed":l,children:[h,v,i.jsxs("span",{className:r.aiProfileGroupHeader,children:[i.jsx("span",{className:`${r.aiProfileGroupAvatar} ${f==="logo"?r.aiProfileGroupAvatarLogo:""} ${m}`.trim(),style:d,"aria-hidden":"true",children:n}),i.jsxs("span",{className:r.aiProfileGroupIdentity,children:[i.jsx("span",{className:r.aiProfileName,children:e}),s?i.jsxs("span",{className:r.aiProfileHandle,children:["@",s]}):null,i.jsx("span",{className:r.aiProfileRole,children:a}),P]})]})]})}export{j as A,N as a,t as b,$ as r,b as w};