@taskforcehq/taskforce 0.3.330 → 0.3.333

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 (107) 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/localService.js +1 -0
  31. package/dist/server/routes/shared.d.ts +3 -0
  32. package/dist/server/routes/shared.js +280 -240
  33. package/dist/server/routes/syncPushRoutes.d.ts +3 -0
  34. package/dist/server/routes/syncPushRoutes.js +8 -2
  35. package/dist/service/localServiceCli.d.ts +2 -2
  36. package/dist/service/localServiceCli.js +4 -3
  37. package/dist/services/executorPhaseAIssuerCore.d.ts +13 -0
  38. package/dist/services/executorPhaseAIssuerCore.js +107 -0
  39. package/dist/services/taskforceAgentMcpBridge.d.ts +1 -1
  40. package/dist/services/taskforceAgentMcpBridge.js +35 -5
  41. package/dist/shared/executorPhaseASchemaValidation.js +1 -1
  42. package/dist/storage/postgresAdapter.js +3 -1
  43. package/dist/storage/postgresRequestDiagnostics.d.ts +9 -1
  44. package/dist/storage/postgresRequestDiagnostics.js +15 -2
  45. package/dist/storage/postgresWorker.js +13 -3
  46. package/dist/storage/postgresWorkerProtocol.d.ts +6 -0
  47. package/dist/storage/workflowStore.d.ts +2 -0
  48. package/dist/storage/workflowStore.js +39 -2
  49. package/dist/sync/cloudSyncApi.js +4 -2
  50. package/dist/sync/collaborationSyncState.js +5 -0
  51. package/dist/sync/coordinator/localWorkspaceSyncRunner.d.ts +3 -1
  52. package/dist/sync/coordinator/localWorkspaceSyncRunner.js +18 -0
  53. package/dist/sync/coordinator/localWorkspaceSyncRunnerState.d.ts +1 -0
  54. package/dist/sync/coordinator/localWorkspaceSyncRunnerState.js +7 -6
  55. package/dist/sync/engine/workspaceSyncEngineBrowserGatewayRemoteServices.js +2 -2
  56. package/dist/sync/engine/workspaceSyncEngineLocalApply.d.ts +1 -0
  57. package/dist/sync/engine/workspaceSyncEnginePushCompletion.d.ts +6 -0
  58. package/dist/sync/engine/workspaceSyncEnginePushCompletion.js +34 -1
  59. package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +2 -2
  60. package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +4 -1
  61. package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.d.ts +1 -0
  62. package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.js +55 -10
  63. package/dist/sync/syncApplyHandlers.d.ts +1 -0
  64. package/dist/sync/syncApplyHandlers.js +72 -15
  65. package/dist/sync/syncService.d.ts +1 -0
  66. package/dist/sync/syncService.js +5 -2
  67. package/dist/sync/v3/durableWorkflowRuntimeMutationRouter.d.ts +7 -7
  68. package/dist/sync/v3/durableWorkflowRuntimeMutationRouter.js +50 -13
  69. package/dist/sync/v3/localOutboxRecoveryPreparationService.js +8 -2
  70. package/dist/sync/v3/localTaskCreateOutboxService.d.ts +3 -1
  71. package/dist/sync/v3/localTaskCreateOutboxService.js +6 -1
  72. package/dist/sync/v3/localTaskDeleteOutboxService.js +1 -0
  73. package/dist/sync/v3/localTaskLifecycleOutboxService.d.ts +2 -1
  74. package/dist/sync/v3/localTaskLifecycleOutboxService.js +5 -1
  75. package/dist/sync/v3/localTaskMetadataOutboxService.d.ts +2 -1
  76. package/dist/sync/v3/localTaskMetadataOutboxService.js +6 -2
  77. package/dist/sync/v3/localTaskStatusOutboxService.d.ts +2 -1
  78. package/dist/sync/v3/localTaskStatusOutboxService.js +20 -5
  79. package/dist/sync/v3/syncDurabilityStore.d.ts +2 -1
  80. package/dist/sync/v3/syncDurabilityStore.js +34 -6
  81. package/dist/sync/v3/workflowRuntimeMutationService.d.ts +5 -0
  82. package/dist/sync/v3/workflowRuntimeMutationService.js +124 -71
  83. package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +25 -3
  84. package/dist/sync/v3/workspaceSyncV3Feed.d.ts +1 -0
  85. package/dist/sync/v3/workspaceSyncV3Feed.js +6 -1
  86. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.d.ts +2 -0
  87. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.js +49 -18
  88. package/dist/sync/v3/workspaceSyncV3WorkflowRuntimeApply.d.ts +1 -0
  89. package/dist/sync/v3/workspaceSyncV3WorkflowRuntimeApply.js +50 -5
  90. package/dist/ui/assets/{AiIdentityRosterCard-OcSz1PkK.js → AiIdentityRosterCard-Cfvb9Nuv.js} +1 -1
  91. package/dist/ui/assets/{AiProfilesModule-ByhYW5Xi.js → AiProfilesModule-0oR70eFG.js} +1 -1
  92. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BTM6nIg_.js → AnnotatedAttachmentWorkspace-C33iH_r_.js} +1 -1
  93. package/dist/ui/assets/{AssetTraySortControl-DabEG8L4.js → AssetTraySortControl-BAqxUtke.js} +1 -1
  94. package/dist/ui/assets/{ContextAttachmentManager-Coa8yVGM.js → ContextAttachmentManager-BudIbOR9.js} +2 -2
  95. package/dist/ui/assets/{DocumentWorkspace-BB0qhdix.js → DocumentWorkspace-A8dFQyId.js} +1 -1
  96. package/dist/ui/assets/{EntityActivityTimeline-CZ8x4UJE.js → EntityActivityTimeline-g7vtdsx1.js} +1 -1
  97. package/dist/ui/assets/PlanningModule-D-DeQs4F.js +1 -0
  98. package/dist/ui/assets/{PlansPage-D86pZSSl.js → PlansPage-BWmfGi0Z.js} +1 -1
  99. package/dist/ui/assets/{TaskContextUpload-BVIkU9yT.js → TaskContextUpload-BA_lQmS9.js} +1 -1
  100. package/dist/ui/assets/{TaskSettings-Cv1mNhv_.js → TaskSettings-DAlH4YIP.js} +1 -1
  101. package/dist/ui/assets/{TaskforceAgentsModule-gmH-PD2q.js → TaskforceAgentsModule-DmeCf6pJ.js} +2 -2
  102. package/dist/ui/assets/{WorkflowManagerModule-DbfnHJ-f.js → WorkflowManagerModule-p08eDOaa.js} +1 -1
  103. package/dist/ui/assets/index-GHJgcW3j.js +7 -0
  104. package/dist/ui/index.html +1 -1
  105. package/package.json +3 -1
  106. package/dist/ui/assets/PlanningModule-pRPjR7v5.js +0 -1
  107. package/dist/ui/assets/index-CTsDBaef.js +0 -7
@@ -16,7 +16,9 @@ export interface LocalTaskCreateOutboxCore {
16
16
  allowV3OwnedCreate?: boolean;
17
17
  preferExplicitAttachmentAssetId?: boolean;
18
18
  }): TaskItem;
19
- installDurableTaskCreateAuthoritativeForSync(task: TaskItem, workspaceId: string): TaskItem;
19
+ installDurableTaskCreateAuthoritativeForSync(task: TaskItem, workspaceId: string, options?: {
20
+ preserveDeletedSuccessor?: boolean;
21
+ }): TaskItem;
20
22
  upsertEntityEventForSync(event: EntityEvent, options?: {
21
23
  allowAuthoritativeReplacement?: boolean;
22
24
  }): unknown;
@@ -356,10 +356,15 @@ export function applyAuthoritativeTaskCreateAndAck(input) {
356
356
  canonicalFingerprint: fingerprintWorkspaceSyncPayload(canonicalDependentActivities[index]),
357
357
  }))],
358
358
  applyAuthoritative: () => {
359
+ const hasDeletedSuccessor = input.store.listActiveOutboxSuccessors(input.identity).some((successor) => (successor.domain === 'task'
360
+ && successor.entityId === task.id
361
+ && successor.mutationKind === 'delete'));
359
362
  const updated = input.core.installDurableTaskCreateAuthoritativeForSync({
360
363
  ...task,
361
364
  localReferenceNumber: null,
362
- }, input.identity.workspaceId);
365
+ }, input.identity.workspaceId, {
366
+ preserveDeletedSuccessor: hasDeletedSuccessor,
367
+ });
363
368
  const installedWithAttachments = canonicalAttachmentProjection
364
369
  ? input.core.updateTask(task.id, {
365
370
  attachments: input.core.resolveTaskAttachmentLinksForDurableSync(canonicalAttachmentProjection.links, input.identity.workspaceId, { adoptAuthoritativeReferenceNumber: true }),
@@ -99,6 +99,7 @@ export function enqueueLocalTaskDeleteMutation(input) {
99
99
  entityId: taskId,
100
100
  mutationKind: 'delete',
101
101
  revisionSource: 'current',
102
+ queuedTaskSuccessor: true,
102
103
  ownership: { taskMetadata: true, taskLifecycle: true },
103
104
  payload: { relationships },
104
105
  now: input.now,
@@ -1,4 +1,4 @@
1
- import type { TaskEvent, TaskItem } from '../../core/Taskforce.js';
1
+ import type { DeletedTaskRecord, TaskEvent, TaskItem } from '../../core/Taskforce.js';
2
2
  import { type DurableTaskLifecycleMutationCore, type DurableTaskLifecycleMutationKind } from '../../core/TaskDurableLifecycleMutationService.js';
3
3
  import { type DurableTaskWorkflowMutationCore, type DurableTaskWorkflowMutationKind } from '../../core/TaskDurableWorkflowMutationService.js';
4
4
  import { WorkspaceSyncDurabilityStore, type WorkspaceSyncOperationIdentity, type WorkspaceSyncOutboxRecord } from './syncDurabilityStore.js';
@@ -16,6 +16,7 @@ export declare function normalizeLocalTaskLifecyclePayload(mutationKind: LocalTa
16
16
  };
17
17
  };
18
18
  export interface LocalTaskLifecycleOutboxCore extends DurableTaskLifecycleMutationCore, DurableTaskWorkflowMutationCore {
19
+ getDeletedTask(id: string, workspaceId?: string): DeletedTaskRecord | null;
19
20
  installDurableTaskLifecycleAuthoritativeForSync(task: TaskItem, mutationKind: LocalTaskLifecycleMutationKind, workspaceId: string): TaskItem;
20
21
  upsertEntityEventForSync(event: TaskEvent, options?: {
21
22
  allowAuthoritativeReplacement?: boolean;
@@ -276,9 +276,13 @@ export function applyAuthoritativeTaskLifecycleAndAck(input) {
276
276
  canonicalFingerprint: fingerprintWorkspaceSyncPayload(serializeTaskActivityForSync(activity)),
277
277
  }] : [])],
278
278
  applyAuthoritative: () => {
279
- const hasSuccessor = input.store.hasActiveOutboxSuccessor(input.identity);
279
+ const successors = input.store.listActiveOutboxSuccessors(input.identity);
280
+ const hasSuccessor = successors.length > 0;
280
281
  const updated = hasSuccessor
281
282
  ? input.core.getTask(task.id, input.identity.workspaceId)
283
+ || (successors.some((successor) => successor.mutationKind === 'delete')
284
+ ? input.core.getDeletedTask(task.id, input.identity.workspaceId)?.taskSnapshot
285
+ : null)
282
286
  : input.core.installDurableTaskLifecycleAuthoritativeForSync(task, input.mutationKind, input.identity.workspaceId);
283
287
  if (!updated)
284
288
  throw new LocalAuthoritativeTaskLifecycleError('Local task lifecycle projection is missing.');
@@ -1,7 +1,8 @@
1
- import type { EntityEvent, TaskItem } from '../../core/Taskforce.js';
1
+ import type { DeletedTaskRecord, EntityEvent, TaskItem } from '../../core/Taskforce.js';
2
2
  import { WorkspaceSyncDurabilityStore, type WorkspaceSyncOperationIdentity } from './syncDurabilityStore.js';
3
3
  export interface LocalTaskMetadataOutboxCore {
4
4
  getTask(id: string, workspaceId?: string): TaskItem | null | undefined;
5
+ getDeletedTask(id: string, workspaceId?: string): DeletedTaskRecord | null;
5
6
  updateTask(id: string, updates: Partial<TaskItem>, workspaceId?: string, options?: {
6
7
  persistBackup?: boolean;
7
8
  emitRealtime?: boolean;
@@ -196,8 +196,12 @@ export function applyAuthoritativeTaskMetadataAndAck(input) {
196
196
  for (const dependentActivity of canonicalDependentActivities) {
197
197
  input.core.upsertEntityEventForSync(dependentActivity, { allowAuthoritativeReplacement: true });
198
198
  }
199
- if (input.store.hasActiveOutboxSuccessor(input.identity)) {
200
- const provisional = input.core.getTask(taskId, input.identity.workspaceId);
199
+ const successors = input.store.listActiveOutboxSuccessors(input.identity);
200
+ if (successors.length > 0) {
201
+ const provisional = input.core.getTask(taskId, input.identity.workspaceId)
202
+ || (successors.some((successor) => successor.mutationKind === 'delete')
203
+ ? input.core.getDeletedTask(taskId, input.identity.workspaceId)?.taskSnapshot
204
+ : null);
201
205
  if (!provisional)
202
206
  throw new DurableTaskMetadataMutationError('Task not found.', 'TASK_NOT_FOUND', 404);
203
207
  return provisional;
@@ -1,8 +1,9 @@
1
- import type { EntityEvent, TaskEvent, TaskItem } from '../../core/Taskforce.js';
1
+ import type { DeletedTaskRecord, EntityEvent, TaskEvent, TaskItem } from '../../core/Taskforce.js';
2
2
  import { type DurableTaskStatusMutationCore, type DurableTaskStatusMutationPayload } from '../../core/TaskDurableStatusMutationService.js';
3
3
  import { WorkspaceSyncDurabilityStore, type WorkspaceSyncOperationIdentity, type WorkspaceSyncOutboxRecord } from './syncDurabilityStore.js';
4
4
  import type { DatabaseAdapter } from '../../storage/databaseAdapter.js';
5
5
  export interface LocalTaskStatusOutboxCore extends DurableTaskStatusMutationCore {
6
+ getDeletedTask(id: string, workspaceId?: string): DeletedTaskRecord | null;
6
7
  assertTaskDetailRevisionForUpdate?(id: string, expectedDetailRevision: string, workspaceId?: string): void;
7
8
  getDatabaseAdapter(): DatabaseAdapter;
8
9
  getTenantId(): string;
@@ -347,7 +347,11 @@ export function applyAuthoritativeTaskStatusAndAck(input) {
347
347
  if (!validLifecycle || authoritative.metadataOwned !== expectsMetadataOwnership) {
348
348
  throw new LocalAuthoritativeTaskStatusError('Authoritative task status state does not match the persisted request.');
349
349
  }
350
- const localCanonical = input.core.getTask(task.id, input.identity.workspaceId);
350
+ const activeSuccessors = input.store.listActiveOutboxSuccessors(input.identity);
351
+ const localCanonical = input.core.getTask(task.id, input.identity.workspaceId)
352
+ || (activeSuccessors.some((successor) => successor.mutationKind === 'delete')
353
+ ? input.core.getDeletedTask(task.id, input.identity.workspaceId)?.taskSnapshot
354
+ : null);
351
355
  if (!localCanonical)
352
356
  throw new LocalAuthoritativeTaskStatusError('Local task status projection is missing.');
353
357
  let canonicalMetadataIntent = null;
@@ -581,14 +585,20 @@ export function applyAuthoritativeTaskStatusAndAck(input) {
581
585
  expectedTaskId: task.id,
582
586
  });
583
587
  }
584
- const successors = input.store.listActiveOutboxSuccessors(input.identity);
588
+ const successors = activeSuccessors;
585
589
  const hasSuccessor = successors.length > 0;
586
- let updated = hasSuccessor
590
+ const activeTask = hasSuccessor
587
591
  ? input.core.getTask(task.id, input.identity.workspaceId)
592
+ : null;
593
+ const deletedSnapshot = !activeTask && successors.some((successor) => successor.mutationKind === 'delete')
594
+ ? input.core.getDeletedTask(task.id, input.identity.workspaceId)?.taskSnapshot
595
+ : null;
596
+ let updated = hasSuccessor
597
+ ? activeTask || deletedSnapshot
588
598
  : input.core.installDurableTaskStatusAuthoritativeForSync(task, payload, installAssignee, input.identity.workspaceId);
589
599
  if (!updated)
590
600
  throw new LocalAuthoritativeTaskStatusError('Local task status projection is missing.');
591
- if (reviewHandoff && reviewCommentsPayload && reviewCommentActivity) {
601
+ if (reviewHandoff && reviewCommentsPayload && reviewCommentActivity && !deletedSnapshot) {
592
602
  const localComments = serializeWorkspaceSyncV3TaskCommentsPayload({
593
603
  taskId: updated.id,
594
604
  comments: updated.comments || [],
@@ -612,7 +622,12 @@ export function applyAuthoritativeTaskStatusAndAck(input) {
612
622
  input.core.upsertEntityEventForSync(reviewCommentActivity, { allowAuthoritativeReplacement: true });
613
623
  activityReconciled = true;
614
624
  }
615
- if (hasSuccessor
625
+ else if (reviewHandoff && reviewCommentActivity && deletedSnapshot) {
626
+ input.core.upsertEntityEventForSync(reviewCommentActivity, { allowAuthoritativeReplacement: true });
627
+ activityReconciled = true;
628
+ }
629
+ if (!deletedSnapshot
630
+ && hasSuccessor
616
631
  && installAssignee
617
632
  && !successors.some((successor) => successor.ownsTaskAssignee)
618
633
  && normalizeAssignee(updated.assignee) !== normalizeAssignee(task.assignee)) {
@@ -90,6 +90,7 @@ export type WorkspaceSyncOutboxRecord = WorkspaceSyncOperationIdentity & {
90
90
  initiativeMetadataRevisionRebaseCount?: number;
91
91
  leaseOwner: string | null;
92
92
  leaseExpiresAt: string | null;
93
+ lastErrorCode: string | null;
93
94
  };
94
95
  export type WorkspaceSyncOutboxOperationLookup = WorkspaceSyncOperationIdentity & {
95
96
  status: string;
@@ -397,7 +398,7 @@ export declare class WorkspaceSyncDurabilityStore {
397
398
  identity: WorkspaceSyncOperationIdentity;
398
399
  recoveryBatchId: string;
399
400
  now: string;
400
- replayPolicy?: 'task-root';
401
+ replayPolicy?: 'task-root' | 'authoritative-workflow-runtime';
401
402
  appendToExistingRecovery?: boolean;
402
403
  }): WorkspaceSyncOutboxSupersession;
403
404
  recordOutboxRecoveryBaseline(input: {
@@ -27,6 +27,7 @@ const TASK_AGGREGATE_SUCCESSOR_MUTATIONS = new Map([
27
27
  const TASK_ROOT_SUCCESSOR_MUTATIONS = new Set([
28
28
  'metadata-update', 'set-status', 'archive', 'unarchive', 'complete', 'reopen',
29
29
  ]);
30
+ const TASK_TERMINAL_SUCCESSOR_MUTATIONS = new Set(['delete']);
30
31
  const TASK_CREATE_RECOVERY_ERROR_CODES = new Set([
31
32
  'TASK_CREATE_ID_CONFLICT',
32
33
  'WORKSPACE_SYNC_REVISION_CONFLICT',
@@ -795,7 +796,8 @@ export class WorkspaceSyncDurabilityStore {
795
796
  payload_json, payload_fingerprint, canonical_intent_json, status, attempt_count,
796
797
  initial_not_found_attempt_count,
797
798
  CAST(enqueue_sequence AS TEXT) AS enqueue_sequence,
798
- rebase_before_first_attempt, lease_owner, lease_expires_at
799
+ rebase_before_first_attempt, lease_owner, lease_expires_at,
800
+ last_error_code
799
801
  FROM workspace_sync_outbox
800
802
  WHERE tenant_id = ? AND workspace_id = ? AND client_id = ? AND operation_id = ?
801
803
  `).get(...operationParams(identity));
@@ -1343,6 +1345,17 @@ export class WorkspaceSyncDurabilityStore {
1343
1345
  }
1344
1346
  }
1345
1347
  }
1348
+ else if (input.replayPolicy === 'authoritative-workflow-runtime') {
1349
+ if (selected.length !== 1) {
1350
+ throw new Error('Workflow runtime recovery has intersecting intent that requires semantic history reconciliation.');
1351
+ }
1352
+ const candidate = selected[0];
1353
+ if (candidate.domain !== 'workflow-execution'
1354
+ || candidate.mutation_kind !== 'complete-step'
1355
+ || candidate.last_error_code !== 'WORKFLOW_RUNTIME_SEMANTIC_REPLAY_REQUIRED') {
1356
+ throw new Error('Workflow runtime recovery requires an exact authoritative semantic replay conflict.');
1357
+ }
1358
+ }
1346
1359
  this.db.prepare(`
1347
1360
  INSERT INTO workspace_sync_outbox_recoveries (
1348
1361
  tenant_id, workspace_id, recovery_batch_id,
@@ -1611,6 +1624,11 @@ export class WorkspaceSyncDurabilityStore {
1611
1624
  }
1612
1625
  }
1613
1626
  const sources = recoverySources.filter((source) => {
1627
+ if (source.domain === 'workflow-execution'
1628
+ && source.mutation_kind === 'complete-step'
1629
+ && source.last_error_code === 'WORKFLOW_RUNTIME_SEMANTIC_REPLAY_REQUIRED') {
1630
+ return false;
1631
+ }
1614
1632
  if (source.domain === 'task-comments' && source.mutation_kind === 'append') {
1615
1633
  return !this.isTaskCommentAppendRecoveryIntentSatisfied(tenantId, workspaceId, source);
1616
1634
  }
@@ -2591,6 +2609,12 @@ export class WorkspaceSyncDurabilityStore {
2591
2609
  const queueableTaskRoot = domain === 'task'
2592
2610
  && TASK_ROOT_SUCCESSOR_MUTATIONS.has(mutationKind)
2593
2611
  && (additionalTargets.length === 0 || queueableTaskReviewHandoff);
2612
+ const queueableTaskDelete = domain === 'task'
2613
+ && TASK_TERMINAL_SUCCESSOR_MUTATIONS.has(mutationKind)
2614
+ && additionalTargets.length === 0
2615
+ && ownsTaskMetadata
2616
+ && ownsTaskLifecycle
2617
+ && !ownsTaskAssignee;
2594
2618
  const queueableTaskAggregate = Boolean(TASK_AGGREGATE_SUCCESSOR_MUTATIONS.get(domain)?.has(mutationKind))
2595
2619
  && ((domain === 'task-comments' && mutationKind === 'reconcile' && additionalTargets.length === 0)
2596
2620
  || (additionalTargets.length === 1
@@ -2601,7 +2625,8 @@ export class WorkspaceSyncDurabilityStore {
2601
2625
  && !taskAggregateRootTarget.ownsTaskLifecycle
2602
2626
  && !taskAggregateRootTarget.ownsTaskAssignee))
2603
2627
  && !ownsTaskMetadata && !ownsTaskLifecycle && !ownsTaskAssignee;
2604
- if (queuedTaskSuccessor && (input.revisionSource !== 'current' || (!queueableTaskRoot && !queueableTaskAggregate))) {
2628
+ if (queuedTaskSuccessor && (input.revisionSource !== 'current'
2629
+ || (!queueableTaskRoot && !queueableTaskDelete && !queueableTaskAggregate))) {
2605
2630
  throw new Error('Queued task successors require a recognized task root or aggregate operation.');
2606
2631
  }
2607
2632
  const additionalTargetKeys = new Set();
@@ -2813,11 +2838,13 @@ export class WorkspaceSyncDurabilityStore {
2813
2838
  const isTaskRoot = activeDomain === 'task'
2814
2839
  && row.entity_id === entityId
2815
2840
  && (TASK_ROOT_SUCCESSOR_MUTATIONS.has(activeMutationKind)
2816
- || (queueableTaskAggregate && activeMutationKind === 'create'))
2841
+ || (activeMutationKind === 'create'
2842
+ && Number(row.owns_task_metadata) === 0
2843
+ && Number(row.owns_task_lifecycle) === 0
2844
+ && Number(row.owns_task_assignee) === 0))
2817
2845
  && Number(row.target_count) === 1;
2818
- return isTaskRoot
2819
- || isCompatibleTaskAggregate(row)
2820
- || isCompatibleWorkflowRuntimeAggregate(row);
2846
+ return isTaskRoot || (!queueableTaskDelete && (isCompatibleTaskAggregate(row)
2847
+ || isCompatibleWorkflowRuntimeAggregate(row)));
2821
2848
  });
2822
2849
  const compatibleWorkflowRuntimeSuccessor = queuedWorkflowRuntimeSuccessor
2823
2850
  && active.every((row) => (row.domain === 'workflow-execution'
@@ -4914,6 +4941,7 @@ export class WorkspaceSyncDurabilityStore {
4914
4941
  initiativeMetadataRevisionRebaseCount: Number(row.initiative_metadata_revision_rebase_count || 0),
4915
4942
  leaseOwner: row.lease_owner == null ? null : String(row.lease_owner),
4916
4943
  leaseExpiresAt: row.lease_expires_at == null ? null : String(row.lease_expires_at),
4944
+ lastErrorCode: row.last_error_code == null ? null : String(row.last_error_code),
4917
4945
  };
4918
4946
  }
4919
4947
  }
@@ -53,6 +53,11 @@ export type PreparedWorkflowRuntimeMutation = {
53
53
  terminalEventId: string;
54
54
  taskActivityId: string;
55
55
  };
56
+ export declare class WorkflowRuntimeSemanticReplayError extends Error {
57
+ readonly code = "WORKFLOW_RUNTIME_SEMANTIC_REPLAY_REQUIRED";
58
+ readonly statusCode = 409;
59
+ constructor();
60
+ }
56
61
  export declare function prepareWorkflowRuntimeMutation(input: {
57
62
  workflowStore: WorkflowStore;
58
63
  workspaceId: string;
@@ -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;