@taskforcehq/taskforce 0.3.328 → 0.3.329

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 (264) hide show
  1. package/dist/Taskforce.module.css +233 -34
  2. package/dist/TaskforceCore.js +237 -230
  3. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.js +1 -1
  4. package/dist/cli.js +34 -0
  5. package/dist/cliCloudTargetGuard.d.ts +5 -0
  6. package/dist/cliCloudTargetGuard.js +11 -0
  7. package/dist/components/features/AnnotatedAttachmentWorkspace.js +24 -8
  8. package/dist/components/features/TaskSettings.d.ts +12 -1
  9. package/dist/components/features/TaskSettings.js +147 -40
  10. package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.d.ts +1 -1
  11. package/dist/components/features/planning/PlanningModule.js +2 -5
  12. package/dist/components/features/planning/PlanningTaskOrderList.d.ts +1 -1
  13. package/dist/components/features/planning/PlanningTaskOrderList.js +17 -6
  14. package/dist/components/features/planning/planningTaskArrangement.d.ts +1 -0
  15. package/dist/components/features/planning/planningTaskArrangement.js +5 -5
  16. package/dist/components/features/taskforceAgentEditorModel.js +3 -1
  17. package/dist/components/task/TaskCard.d.ts +5 -0
  18. package/dist/components/task/TaskCard.js +65 -10
  19. package/dist/components/task/TaskImageReviews.js +14 -5
  20. package/dist/components/task/TaskKanban.d.ts +6 -1
  21. package/dist/components/task/TaskKanban.js +152 -6
  22. package/dist/components/task/TaskList.d.ts +5 -0
  23. package/dist/components/task/TaskList.js +3 -3
  24. package/dist/components/ui/PasswordInput.d.ts +6 -0
  25. package/dist/components/ui/PasswordInput.js +11 -0
  26. package/dist/components/ui/TopNoticeLayer.js +1 -1
  27. package/dist/components/views/AppShellHeader.js +32 -11
  28. package/dist/components/views/PlanComparisonPage.js +12 -0
  29. package/dist/components/views/StandaloneLayout.js +7 -31
  30. package/dist/components/views/WidgetView.js +3 -3
  31. package/dist/components/views/panels/PlanningDrawer.js +5 -12
  32. package/dist/components/views/standalone/modals/AccountSettingsModal.js +26 -17
  33. package/dist/config/envSchema.js +12 -0
  34. package/dist/config/localServiceRuntimeContract.d.ts +13 -0
  35. package/dist/config/localServiceRuntimeContract.js +135 -0
  36. package/dist/core/AiProfileService.js +46 -8
  37. package/dist/core/DeletedTaskLifecycleService.d.ts +1 -0
  38. package/dist/core/DeletedTaskLifecycleService.js +37 -6
  39. package/dist/core/TaskAttachmentLinksDurableMutationService.d.ts +3 -2
  40. package/dist/core/TaskAttachmentLinksDurableMutationService.js +33 -9
  41. package/dist/core/Taskforce.d.ts +62 -2
  42. package/dist/core/Taskforce.js +635 -32
  43. package/dist/core/types.d.ts +6 -0
  44. package/dist/hooks/sync/useDurableLocalOutboxDrain.d.ts +1 -1
  45. package/dist/hooks/sync/useDurableTaskCreateMutation.js +1 -1
  46. package/dist/hooks/sync/useDurableTaskMetadataMutation.d.ts +1 -0
  47. package/dist/hooks/sync/useDurableTaskMetadataMutation.js +1 -0
  48. package/dist/hooks/sync/useDurableTaskStatusMutation.d.ts +1 -0
  49. package/dist/hooks/tasks/taskHttpMutation.js +1 -0
  50. package/dist/hooks/tasks/useCloudTaskSearch.d.ts +12 -0
  51. package/dist/hooks/tasks/useCloudTaskSearch.js +71 -0
  52. package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.d.ts +16 -0
  53. package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.js +197 -0
  54. package/dist/hooks/tasks/useTaskEditorNavigation.d.ts +16 -3
  55. package/dist/hooks/tasks/useTaskEditorNavigation.js +72 -13
  56. package/dist/hooks/tasks/useTaskFormActions.d.ts +6 -0
  57. package/dist/hooks/tasks/useTaskFormActions.js +65 -12
  58. package/dist/hooks/ui/useSettingsModel.d.ts +24 -2
  59. package/dist/hooks/ui/useSettingsModel.js +13 -2
  60. package/dist/hooks/ui/useSettingsPersistence.d.ts +12 -1
  61. package/dist/hooks/ui/useSettingsPersistence.js +74 -47
  62. package/dist/hooks/useFilterSort.d.ts +7 -3
  63. package/dist/hooks/useFilterSort.js +15 -5
  64. package/dist/hooks/useTaskData.d.ts +10 -1
  65. package/dist/hooks/useTaskData.js +216 -7
  66. package/dist/hooks/useTaskMutations.d.ts +10 -2
  67. package/dist/hooks/useTaskMutations.js +223 -43
  68. package/dist/hooks/useTaskforce.d.ts +37 -9
  69. package/dist/hooks/useTaskforce.js +349 -39
  70. package/dist/hooks/useUiNotice.d.ts +4 -0
  71. package/dist/hooks/useUiNotice.js +4 -0
  72. package/dist/mcp/adminWorkspaceRegistrar.js +3 -3
  73. package/dist/mcp/documentAssetRegistrar.js +44 -35
  74. package/dist/mcp/documentHelpers.d.ts +1 -0
  75. package/dist/mcp/documentHelpers.js +7 -3
  76. package/dist/mcp/runtime.js +172 -42
  77. package/dist/mcp/taskPlanningRegistrar.js +41 -38
  78. package/dist/mcp/toolCallCompatibility.js +38 -1
  79. package/dist/mcp/toolCatalog.d.ts +1 -0
  80. package/dist/migrations/taskSchemaMigrations.js +12 -0
  81. package/dist/runtime/appGateDefinitions.d.ts +30 -6
  82. package/dist/runtime/appGateDefinitions.js +6 -6
  83. package/dist/runtime/appGates.d.ts +12 -13
  84. package/dist/runtime/appGates.js +29 -13
  85. package/dist/runtime/nodeAppGates.d.ts +8 -0
  86. package/dist/runtime/nodeAppGates.js +27 -0
  87. package/dist/server/cloudCollectionRead.d.ts +1 -1
  88. package/dist/server/cloudCollectionRead.js +2 -2
  89. package/dist/server/index.d.ts +3 -0
  90. package/dist/server/index.js +15 -3
  91. package/dist/server/localServiceLease.d.ts +3 -0
  92. package/dist/server/localServiceLease.js +9 -1
  93. package/dist/server/localWorkspaceSyncServerRuntime.js +10 -3
  94. package/dist/server/rateLimit.d.ts +1 -0
  95. package/dist/server/rateLimit.js +4 -0
  96. package/dist/server/realtimeSyncWs.js +36 -12
  97. package/dist/server/routes/admin.js +1 -0
  98. package/dist/server/routes/agentRoles.js +3 -2
  99. package/dist/server/routes/agentSkills.js +3 -2
  100. package/dist/server/routes/agents.js +118 -16
  101. package/dist/server/routes/annotatedAttachments.js +3 -2
  102. package/dist/server/routes/auth.js +3 -1
  103. package/dist/server/routes/durableTaskHttpRouters.d.ts +9 -2
  104. package/dist/server/routes/localService.js +2 -0
  105. package/dist/server/routes/primitives.d.ts +2 -0
  106. package/dist/server/routes/syncAuxRoutes.js +5 -2
  107. package/dist/server/routes/syncPullApplyRoutes.js +23 -4
  108. package/dist/server/routes/syncV3FeedRoutes.js +18 -16
  109. package/dist/server/routes/syncV3LocalCaptureRoutes.js +28 -2
  110. package/dist/server/routes/syncV3LocalFeedApplyRoutes.d.ts +1 -0
  111. package/dist/server/routes/syncV3LocalFeedApplyRoutes.js +26 -0
  112. package/dist/server/routes/syncV3OperationRoutes.js +10 -0
  113. package/dist/server/routes/tasks.js +103 -11
  114. package/dist/server/routes/workspaces.js +43 -1
  115. package/dist/server/workspaceDeletionAdmission.d.ts +23 -0
  116. package/dist/server/workspaceDeletionAdmission.js +50 -0
  117. package/dist/service/localServiceCli.js +16 -0
  118. package/dist/service/localServiceClientLifecycle.js +36 -10
  119. package/dist/services/codexAppServerModelGateway.d.ts +112 -0
  120. package/dist/services/codexAppServerModelGateway.js +438 -0
  121. package/dist/services/taskforceAgentMcpBridge.d.ts +7 -1
  122. package/dist/services/taskforceAgentMcpBridge.js +23 -3
  123. package/dist/shared/taskforceAgentDefinition.js +7 -1
  124. package/dist/shared/taskforceAgentModels.d.ts +1 -0
  125. package/dist/shared/taskforceAgentModels.js +30 -1
  126. package/dist/storage/postgresAdapter.d.ts +20 -3
  127. package/dist/storage/postgresAdapter.js +106 -14
  128. package/dist/storage/postgresWorker.js +13 -4
  129. package/dist/storage/postgresWorkerProtocol.d.ts +29 -0
  130. package/dist/storage/postgresWorkerProtocol.js +30 -0
  131. package/dist/sync/cloudSyncApi.d.ts +4 -1
  132. package/dist/sync/cloudSyncApi.js +7 -2
  133. package/dist/sync/contentSyncState.d.ts +1 -1
  134. package/dist/sync/coordinator/localSyncExecutionPermitStore.d.ts +1 -0
  135. package/dist/sync/coordinator/localSyncExecutionPermitStore.js +7 -1
  136. package/dist/sync/coordinator/localWorkspaceSyncRunner.js +6 -2
  137. package/dist/sync/engine/workspaceSyncEngineBrowserGatewayRemoteServices.js +20 -2
  138. package/dist/sync/engine/workspaceSyncEngineLocalServices.d.ts +4 -0
  139. package/dist/sync/engine/workspaceSyncEngineLocalServices.js +11 -0
  140. package/dist/sync/engine/workspaceSyncEngineRemoteServices.d.ts +11 -0
  141. package/dist/sync/engine/workspaceSyncEngineRemoteServices.js +2 -1
  142. package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +31 -4
  143. package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.d.ts +3 -1
  144. package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +25 -8
  145. package/dist/sync/engine/workspaceSyncEngineTransfers.d.ts +1 -1
  146. package/dist/sync/engine/workspaceSyncEngineTransfers.js +8 -3
  147. package/dist/sync/syncService.d.ts +2 -0
  148. package/dist/sync/syncService.js +4 -1
  149. package/dist/sync/v3/combinedFeedCapability.js +4 -0
  150. package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.d.ts +2 -0
  151. package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.js +11 -1
  152. package/dist/sync/v3/durableTaskChecklistMutationRouter.d.ts +2 -0
  153. package/dist/sync/v3/durableTaskChecklistMutationRouter.js +4 -0
  154. package/dist/sync/v3/durableTaskHttpBulkMutationRouter.d.ts +1 -0
  155. package/dist/sync/v3/durableTaskHttpBulkMutationRouter.js +30 -8
  156. package/dist/sync/v3/durableTaskHttpRootMutationRouter.d.ts +5 -3
  157. package/dist/sync/v3/durableTaskHttpRootMutationRouter.js +27 -2
  158. package/dist/sync/v3/localTaskAttachmentLinksOutboxService.d.ts +2 -0
  159. package/dist/sync/v3/localTaskAttachmentLinksOutboxService.js +47 -7
  160. package/dist/sync/v3/localTaskCreateOutboxService.js +15 -6
  161. package/dist/sync/v3/localTaskMetadataClient.d.ts +1 -0
  162. package/dist/sync/v3/localTaskMetadataClient.js +1 -0
  163. package/dist/sync/v3/localTaskMetadataOutboxHandler.js +5 -1
  164. package/dist/sync/v3/localTaskMetadataOutboxService.d.ts +2 -0
  165. package/dist/sync/v3/localTaskMetadataOutboxService.js +7 -0
  166. package/dist/sync/v3/localTaskRestoreOutboxService.js +14 -6
  167. package/dist/sync/v3/localTaskStatusClient.d.ts +1 -0
  168. package/dist/sync/v3/localTaskStatusClient.js +6 -1
  169. package/dist/sync/v3/localTaskStatusOutboxService.d.ts +2 -0
  170. package/dist/sync/v3/localTaskStatusOutboxService.js +54 -13
  171. package/dist/sync/v3/syncDurabilityStore.d.ts +7 -0
  172. package/dist/sync/v3/syncDurabilityStore.js +31 -11
  173. package/dist/sync/v3/taskAttachmentLinksMutationService.d.ts +6 -4
  174. package/dist/sync/v3/taskAttachmentLinksMutationService.js +19 -10
  175. package/dist/sync/v3/taskChecklistMutationService.d.ts +4 -2
  176. package/dist/sync/v3/taskChecklistMutationService.js +4 -0
  177. package/dist/sync/v3/taskCreateMutationService.d.ts +2 -2
  178. package/dist/sync/v3/taskCreateMutationService.js +7 -4
  179. package/dist/sync/v3/taskMetadataMutationService.d.ts +3 -0
  180. package/dist/sync/v3/taskMetadataMutationService.js +4 -0
  181. package/dist/sync/v3/taskRestoreMutationService.d.ts +2 -2
  182. package/dist/sync/v3/taskRestoreMutationService.js +7 -4
  183. package/dist/sync/v3/taskStatusMutationService.d.ts +2 -0
  184. package/dist/sync/v3/taskStatusMutationService.js +4 -0
  185. package/dist/sync/v3/workflowAssignmentSync.d.ts +1 -0
  186. package/dist/sync/v3/workflowAssignmentSync.js +3 -0
  187. package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.d.ts +1 -1
  188. package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.js +13 -3
  189. package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.d.ts +11 -0
  190. package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.js +27 -10
  191. package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.d.ts +2 -1
  192. package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.js +52 -10
  193. package/dist/sync/v3/workspaceSyncV3CloudOperationHandler.js +9 -1
  194. package/dist/sync/v3/workspaceSyncV3CombinedFeedOrchestrator.js +8 -3
  195. package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +51 -10
  196. package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.d.ts +7 -0
  197. package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.js +30 -18
  198. package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.d.ts +9 -4
  199. package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.js +98 -47
  200. package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.d.ts +10 -0
  201. package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.js +58 -21
  202. package/dist/sync/v3/workspaceSyncV3CombinedWireContract.d.ts +3 -1
  203. package/dist/sync/v3/workspaceSyncV3CombinedWireContract.js +18 -0
  204. package/dist/sync/v3/workspaceSyncV3CoverageRegistry.js +6 -0
  205. package/dist/sync/v3/workspaceSyncV3FeedProfile.d.ts +1 -1
  206. package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.d.ts +1 -0
  207. package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.js +23 -10
  208. package/dist/sync/v3/workspaceSyncV3NestedTaskApply.d.ts +3 -0
  209. package/dist/sync/v3/workspaceSyncV3NestedTaskApply.js +64 -13
  210. package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.d.ts +9 -0
  211. package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.js +26 -0
  212. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.d.ts +15 -5
  213. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.js +50 -14
  214. package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.d.ts +2 -1
  215. package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.js +11 -3
  216. package/dist/sync/v3/workspaceSyncV3OperationProtocol.d.ts +2 -0
  217. package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.d.ts +2 -0
  218. package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.js +19 -7
  219. package/dist/sync/v3/workspaceSyncV3TaskCommentAudit.js +9 -2
  220. package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.d.ts +7 -0
  221. package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.js +37 -0
  222. package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.d.ts +6 -0
  223. package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.js +9 -0
  224. package/dist/sync/workspacePullFeed.js +2 -1
  225. package/dist/sync/workspaceRepair.d.ts +3 -0
  226. package/dist/sync/workspaceRepair.js +67 -6
  227. package/dist/types.d.ts +47 -1
  228. package/dist/types.js +6 -0
  229. package/dist/ui/assets/{AiIdentityRosterCard-MZs7lVED.js → AiIdentityRosterCard-BZM4aQnM.js} +1 -1
  230. package/dist/ui/assets/AiProfilesModule-BbAEWtHH.js +1 -0
  231. package/dist/ui/assets/AnnotatedAttachmentWorkspace-B68Wg-RV.js +3 -0
  232. package/dist/ui/assets/AnnotatedAttachmentWorkspace-TzYu7pkG.css +1 -0
  233. package/dist/ui/assets/{AssetTraySortControl-Kf1pelSy.js → AssetTraySortControl-C8Rztyal.js} +1 -1
  234. package/dist/ui/assets/{ContextAttachmentManager-B8boOs6Q.js → ContextAttachmentManager-Cj3c7X9y.js} +2 -2
  235. package/dist/ui/assets/{DocumentWorkspace-DXH4XUsB.js → DocumentWorkspace-B5uvzems.js} +1 -1
  236. package/dist/ui/assets/{EntityActivityTimeline-BtSTq0ON.js → EntityActivityTimeline-BVFvCB5R.js} +1 -1
  237. package/dist/ui/assets/PlanningModule-B5DWQOGz.js +1 -0
  238. package/dist/ui/assets/PlansPage-AY4cGWco.js +1 -0
  239. package/dist/ui/assets/TaskContextUpload-DZxTVesW.js +1 -0
  240. package/dist/ui/assets/TaskSettings-BeWAuyZj.js +12 -0
  241. package/dist/ui/assets/{TaskforceAgentsModule-BazdUwxZ.js → TaskforceAgentsModule-BifCYtjR.js} +6 -6
  242. package/dist/ui/assets/{WorkflowManagerModule-DWFspC-o.js → WorkflowManagerModule-DEr5di_r.js} +1 -1
  243. package/dist/ui/assets/index-I5zhdtgt.js +7 -0
  244. package/dist/ui/assets/index-VRFsx3wz.css +1 -0
  245. package/dist/ui/assets/{vendor-icons-BkFLXavV.js → vendor-icons-D9Lpw-j4.js} +1 -1
  246. package/dist/ui/index.html +3 -3
  247. package/dist/utils/taskActivity.d.ts +3 -2
  248. package/dist/utils/taskActivity.js +19 -2
  249. package/dist/utils/taskEditorReconciliation.d.ts +40 -0
  250. package/dist/utils/taskEditorReconciliation.js +217 -0
  251. package/dist/utils/taskNormalization.d.ts +1 -0
  252. package/dist/utils/taskNormalization.js +39 -0
  253. package/dist/utils/taskSearch.js +2 -2
  254. package/dist/utils/uiNotice.d.ts +4 -0
  255. package/package.json +16 -4
  256. package/dist/ui/assets/AiProfilesModule-C3i0LOxe.js +0 -1
  257. package/dist/ui/assets/AnnotatedAttachmentWorkspace-CBRZ8rca.css +0 -1
  258. package/dist/ui/assets/AnnotatedAttachmentWorkspace-DVZk5Tln.js +0 -3
  259. package/dist/ui/assets/PlanningModule-BPVyEa-f.js +0 -1
  260. package/dist/ui/assets/PlansPage-ChqgxALc.js +0 -1
  261. package/dist/ui/assets/TaskContextUpload-CB3ol0sN.js +0 -1
  262. package/dist/ui/assets/TaskSettings-g4ECKNOz.js +0 -12
  263. package/dist/ui/assets/index-BUplSsv_.js +0 -7
  264. package/dist/ui/assets/index-gz2EXuoK.css +0 -1
@@ -6,6 +6,7 @@ import { runWorkspaceSyncV3CombinedProjectionCycle } from '../v3/workspaceSyncV3
6
6
  import { runWorkspaceSyncV3InitiativeProjectionCycle } from '../v3/workspaceSyncV3FeedOrchestrator.js';
7
7
  import { normalizeWorkspaceSyncV2ProjectionExclusions } from '../v3/workspaceSyncV2ProjectionExclusions.js';
8
8
  import { canonicalWorkspaceSyncJson } from '../v3/canonicalWorkspaceSyncJson.js';
9
+ import { authoritativeV2ApplyFailureMessage, } from './workspaceSyncEngineLocalServices.js';
9
10
  import { combinedAttachmentAssetHydrationId, createWorkspaceSyncV3TransferBindings, } from './workspaceSyncEngineTransfers.js';
10
11
  import { createWorkspaceSyncTransferEvidence, } from './workspaceSyncTransferEvidence.js';
11
12
  const NOOP_CLOUD_RESOLVER = (path) => path;
@@ -13,6 +14,7 @@ const RETRY_BASE_DELAY_MS = 1_000;
13
14
  const RETRY_MAX_DELAY_MS = 5 * 60_000;
14
15
  const COMBINED_FEED_COVERED_OUTBOX_BLOCK = 'WORKSPACE_SYNC_V3_COVERED_LOCAL_OPERATIONS';
15
16
  const REPAIR_OWNERSHIP_FINGERPRINT_VERSION = 2;
17
+ const NON_APPLYING_ACKNOWLEDGEMENT_CHECKPOINT_INTERVAL = 64;
16
18
  class WorkspaceSyncEngineServerRunnerOperationError extends Error {
17
19
  code;
18
20
  retryAfterMs;
@@ -513,7 +515,9 @@ export function createWorkspaceSyncEngineServerRunnerOperations(options) {
513
515
  ? 'repair'
514
516
  : 'push';
515
517
  activeRetryKind = pushRetryKind;
516
- persist({ ...checkpoint, controlState: 'pushing' });
518
+ if (checkpoint.controlState !== 'pushing') {
519
+ persist({ ...checkpoint, controlState: 'pushing' });
520
+ }
517
521
  let payload;
518
522
  let signature;
519
523
  if (checkpoint.pendingV2Push) {
@@ -551,11 +555,13 @@ export function createWorkspaceSyncEngineServerRunnerOperations(options) {
551
555
  persist({ ...checkpoint, pendingV2Push });
552
556
  }
553
557
  if (checkpoint.remoteHintObservedAt) {
554
- persist({
558
+ checkpoint = {
555
559
  ...checkpoint,
556
560
  consecutivePushesWhilePullDue: Math.min(Number.MAX_SAFE_INTEGER, checkpoint.consecutivePushesWhilePullDue + 1),
557
- });
561
+ };
558
562
  }
563
+ let persistedAcknowledgementCount = checkpoint.pendingV2Push
564
+ ?.acknowledgedNonApplyingBatchOperationIdentities.length || 0;
559
565
  const result = await runWorkspacePushSyncService({
560
566
  resolveCloudAuthUrl: NOOP_CLOUD_RESOLVER,
561
567
  workspaceId,
@@ -583,13 +589,14 @@ export function createWorkspaceSyncEngineServerRunnerOperations(options) {
583
589
  ],
584
590
  },
585
591
  });
592
+ persistedAcknowledgementCount = acknowledgedOperationIdentities.length;
586
593
  },
587
594
  onNonApplyingBatchAcknowledged: ({ acknowledgedOperationIdentities }) => {
588
595
  const pendingV2Push = checkpoint.pendingV2Push;
589
596
  if (!pendingV2Push) {
590
597
  throw new WorkspaceSyncEngineServerRunnerOperationError('The frozen V2 push disappeared while recording batch progress.', 'SYNC_RUNNER_PUSH_CHECKPOINT_INVALID');
591
598
  }
592
- persist({
599
+ checkpoint = {
593
600
  ...checkpoint,
594
601
  pendingV2Push: {
595
602
  ...pendingV2Push,
@@ -597,19 +604,25 @@ export function createWorkspaceSyncEngineServerRunnerOperations(options) {
597
604
  ...acknowledgedOperationIdentities,
598
605
  ],
599
606
  },
600
- });
607
+ };
608
+ if (acknowledgedOperationIdentities.length
609
+ - persistedAcknowledgementCount
610
+ >= NON_APPLYING_ACKNOWLEDGEMENT_CHECKPOINT_INTERVAL) {
611
+ persist(checkpoint);
612
+ persistedAcknowledgementCount = acknowledgedOperationIdentities.length;
613
+ }
601
614
  },
602
615
  });
603
616
  onResult?.(result);
604
617
  if (!result.success) {
605
618
  if (result.batchProgress && checkpoint.pendingV2Push) {
606
- persist({
619
+ checkpoint = {
607
620
  ...checkpoint,
608
621
  pendingV2Push: {
609
622
  ...checkpoint.pendingV2Push,
610
623
  lastBatchProgress: structuredClone(result.batchProgress),
611
624
  },
612
- });
625
+ };
613
626
  }
614
627
  const message = pushFailureMessage(result);
615
628
  if (isAuthFailure(result)) {
@@ -692,6 +705,8 @@ export function createWorkspaceSyncEngineServerRunnerOperations(options) {
692
705
  ? { durableRevision: entry.durableRevision }
693
706
  : {}),
694
707
  })), {
708
+ repairMode: attemptOptions.repairMode,
709
+ excludedProjections: result.effectiveV2ExcludedProjections,
695
710
  ...(completionGroup ? { completionGroup } : {}),
696
711
  commitServerCheckpoint: () => {
697
712
  const persisted = input.persistCheckpoint(nextCheckpoint, {
@@ -711,7 +726,9 @@ export function createWorkspaceSyncEngineServerRunnerOperations(options) {
711
726
  }
712
727
  if (!applyResult.ok || !commitState.checkpoint) {
713
728
  await abandonPushReceipts('WORKSPACE_SYNC_V2_PUSH_AUTHORITATIVE_APPLY_FAILED');
714
- throw new WorkspaceSyncEngineServerRunnerOperationError(`Authoritative V2 push echo apply failed (${applyResult.status || 0}).`, 'SYNC_RUNNER_PUSH_AUTHORITATIVE_APPLY_FAILED');
729
+ throw new WorkspaceSyncEngineServerRunnerOperationError(!applyResult.ok
730
+ ? authoritativeV2ApplyFailureMessage(applyResult)
731
+ : 'Authoritative V2 push echo did not commit the server checkpoint.', 'SYNC_RUNNER_PUSH_AUTHORITATIVE_APPLY_FAILED');
715
732
  }
716
733
  const committedCheckpoint = commitState.checkpoint;
717
734
  if (!committedCheckpoint) {
@@ -3,7 +3,7 @@ import type { WorkspaceSyncPayload } from '../workspaceSyncModel.js';
3
3
  import type { WorkspaceSyncPhase } from '../workspaceSyncState.js';
4
4
  import type { WorkspaceSyncV3CombinedCoverageSelector } from '../v3/workspaceSyncV3CombinedWireContract.js';
5
5
  import type { WorkspaceSyncEngineRuntime } from './workspaceSyncEngineRuntime.js';
6
- import type { WorkspaceSyncEngineLocalServices } from './workspaceSyncEngineLocalServices.js';
6
+ import { type WorkspaceSyncEngineLocalServices } from './workspaceSyncEngineLocalServices.js';
7
7
  import type { WorkspaceSyncEngineRemoteServices, WorkspaceSyncEngineRemoteTransferReceipt } from './workspaceSyncEngineRemoteServices.js';
8
8
  export type { PendingWorkspacePushReplay } from './workspaceSyncEngineTransferTypes.js';
9
9
  type StateSetter<T> = (value: T | ((current: T) => T)) => void;
@@ -4,6 +4,7 @@ import { resolveWorkspacePullRequestPlan, resolveWorkspacePullSuccessPlan } from
4
4
  import { runWorkspaceSyncV3InitiativeProjectionCycle } from '../v3/workspaceSyncV3FeedOrchestrator.js';
5
5
  import { runWorkspaceSyncV3CombinedProjectionCycle } from '../v3/workspaceSyncV3CombinedFeedOrchestrator.js';
6
6
  import { hydrateWorkspaceSyncV3AttachmentAsset } from '../v3/workspaceSyncV3AttachmentAssetHydration.js';
7
+ import { authoritativeV2ApplyFailureMessage, } from './workspaceSyncEngineLocalServices.js';
7
8
  import { WORKSPACE_SYNC_V3_APPLY_NOT_COMMITTED } from './workspaceSyncEngineRemoteServices.js';
8
9
  export function combinedAttachmentAssetHydrationId(input) {
9
10
  if (input.causeCode === 'TASK_ATTACHMENT_ASSET_NOT_READY' && input.causeAssetId) {
@@ -391,6 +392,10 @@ export function createWorkspaceTransferCoordinator({ cloudAuthConfigured, runtim
391
392
  }
392
393
  let applyResult;
393
394
  try {
395
+ const applyOptions = {
396
+ repairMode: replayPayload?.repairMode ?? repairMode,
397
+ excludedProjections: pushResult.effectiveV2ExcludedProjections,
398
+ };
394
399
  const authoritativeChanges = appliedTaskUpserts.map((entry) => ({
395
400
  op: 'upsert',
396
401
  archived: entry.archived === true,
@@ -398,8 +403,8 @@ export function createWorkspaceTransferCoordinator({ cloudAuthConfigured, runtim
398
403
  ...(entry.durableRevision ? { durableRevision: entry.durableRevision } : {}),
399
404
  }));
400
405
  applyResult = completionGroup
401
- ? await localServices.applyV2Batch(currentWorkspaceId, authoritativeChanges, { completionGroup })
402
- : await localServices.applyV2Batch(currentWorkspaceId, authoritativeChanges);
406
+ ? await localServices.applyV2Batch(currentWorkspaceId, authoritativeChanges, { ...applyOptions, completionGroup })
407
+ : await localServices.applyV2Batch(currentWorkspaceId, authoritativeChanges, applyOptions);
403
408
  }
404
409
  catch (error) {
405
410
  if (pushTransferReceipts.length > 0) {
@@ -419,7 +424,7 @@ export function createWorkspaceTransferCoordinator({ cloudAuthConfigured, runtim
419
424
  errorCode: 'WORKSPACE_SYNC_V2_PUSH_AUTHORITATIVE_APPLY_FAILED',
420
425
  });
421
426
  }
422
- throw new Error(`Authoritative V2 push echo apply failed (${applyResult.status || 0}).`);
427
+ throw new Error(authoritativeV2ApplyFailureMessage(applyResult));
423
428
  }
424
429
  for (const entry of appliedTaskUpserts) {
425
430
  const taskId = String(entry.task.id || '').trim();
@@ -77,6 +77,8 @@ export interface WorkspacePushSyncResult {
77
77
  effectiveDeltaDiagnostics?: WorkspaceSyncDeltaDiagnostics & {
78
78
  combinedV3SuppressedChangeCount?: number;
79
79
  };
80
+ /** Exact V3-owned projections excluded while preparing this V2 attempt. */
81
+ effectiveV2ExcludedProjections?: readonly WorkspaceSyncV2ProjectionExclusion[];
80
82
  deleteCount: number;
81
83
  currentTaskIds: Set<string>;
82
84
  currentTaskWatermarks?: Map<string, string>;
@@ -5,7 +5,7 @@ import { createWorkspaceSyncEngineV2PushBoundary, parseWorkspaceSyncEngineApplie
5
5
  import { encryptDocument } from './encryptionService.js';
6
6
  import { decryptWorkspaceSyncContentChangeInPlace } from './workspaceSyncContentDecryption.js';
7
7
  import { decideWorkspaceContentFetch, fingerprintWorkspaceContentBody, } from './workspaceContentManifest.js';
8
- import { workspaceSyncV2ProjectionExclusionsEqual, } from './v3/workspaceSyncV2ProjectionExclusions.js';
8
+ import { normalizeWorkspaceSyncV2ProjectionExclusions, workspaceSyncV2ProjectionExclusionsEqual, } from './v3/workspaceSyncV2ProjectionExclusions.js';
9
9
  import { WORKSPACE_PUSH_PREPARED_PLAN_VERSION, } from './workspacePushPreparedPlan.js';
10
10
  import { canonicalWorkspaceSyncJson } from './v3/canonicalWorkspaceSyncJson.js';
11
11
  function asObjectRecord(value) {
@@ -464,6 +464,7 @@ export async function runWorkspacePushSyncService(input) {
464
464
  }
465
465
  const changeCount = preparedChanges.length;
466
466
  const effectiveDeltaDiagnostics = buildEffectiveWorkspacePushDiagnostics(input.payload.deltaDiagnostics, preparedChanges, preparedOwnership);
467
+ const effectiveV2ExcludedProjections = normalizeWorkspaceSyncV2ProjectionExclusions(preparedOwnership.combinedExcludedProjections || []);
467
468
  const deleteCount = input.payload.deleteTaskIds.size;
468
469
  let requestMs = 0;
469
470
  const canElideSteadyStateRequest = changeCount === 0
@@ -478,6 +479,7 @@ export async function runWorkspacePushSyncService(input) {
478
479
  requestMs: 0,
479
480
  changeCount: 0,
480
481
  effectiveDeltaDiagnostics,
482
+ effectiveV2ExcludedProjections,
481
483
  deleteCount,
482
484
  currentTaskIds: input.payload.currentTaskIds,
483
485
  currentTaskWatermarks: input.payload.currentTaskWatermarks ?? new Map(),
@@ -841,6 +843,7 @@ export async function runWorkspacePushSyncService(input) {
841
843
  failureDomain: null,
842
844
  },
843
845
  effectiveDeltaDiagnostics,
846
+ effectiveV2ExcludedProjections,
844
847
  deleteCount,
845
848
  currentTaskIds: input.payload.currentTaskIds,
846
849
  currentTaskWatermarks: input.payload.currentTaskWatermarks ?? new Map(),
@@ -3,6 +3,7 @@ import { WORKSPACE_SYNC_V3_COMBINED_CAPABILITY_VERSION, WORKSPACE_SYNC_V3_COMBIN
3
3
  import { WORKSPACE_SYNC_V3_NESTED_COVERAGE_SELECTOR } from './workspaceSyncV3NestedWireContract.js';
4
4
  import { WORKSPACE_SYNC_V3_WORKFLOW_COVERAGE_SELECTOR } from './workspaceSyncV3WorkflowWireContract.js';
5
5
  import { WORKSPACE_SYNC_V3_PLANNING_ORDER_COVERAGE_SELECTOR } from './workspaceSyncV3PlanningOrderWireContract.js';
6
+ import { WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_SELECTOR } from './workspaceSyncV3TaskCoverWireContract.js';
6
7
  export { WORKSPACE_SYNC_V3_COMBINED_CAPABILITY_VERSION } from './workspaceSyncV3CombinedWireContract.js';
7
8
  export function resolveRequestedCombinedFeedCoverage(env = process.env) {
8
9
  const requestedVersion = String(env.TASKFORCE_SYNC_V3_COMBINED_FEED_COVERAGE_VERSION || '').trim();
@@ -18,6 +19,9 @@ export function resolveRequestedCombinedFeedCoverage(env = process.env) {
18
19
  if (requestedVersion === String(WORKSPACE_SYNC_V3_PLANNING_ORDER_COVERAGE_SELECTOR.coverageVersion)) {
19
20
  return WORKSPACE_SYNC_V3_PLANNING_ORDER_COVERAGE_SELECTOR;
20
21
  }
22
+ if (requestedVersion === String(WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_SELECTOR.coverageVersion)) {
23
+ return WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_SELECTOR;
24
+ }
21
25
  throw new Error(`Unsupported TASKFORCE_SYNC_V3_COMBINED_FEED_COVERAGE_VERSION: ${requestedVersion}.`);
22
26
  }
23
27
  export function resolveCombinedFeedCapability(input) {
@@ -12,6 +12,7 @@ export declare function resolveTaskAttachmentLinksMutationCapability(input: {
12
12
  export type DurableTaskAttachmentLinksMutationRouterCore = DurableTaskAttachmentLinksServiceCore & LocalTaskAttachmentLinksOutboxCore & {
13
13
  getDatabaseAdapter(): DatabaseAdapter;
14
14
  getTask(id: string, workspaceId?: string): TaskItem | null;
15
+ assertTaskDetailRevisionForUpdate(id: string, expected: string, workspaceId?: string): void;
15
16
  };
16
17
  export declare class DurableTaskAttachmentLinksRouterDisabledError extends Error {
17
18
  readonly code = "WORKSPACE_SYNC_V3_TASK_ATTACHMENT_LINKS_ROUTER_DISABLED";
@@ -41,6 +42,7 @@ export declare function createDurableTaskAttachmentLinksMutationRouter(input: {
41
42
  taskId: string;
42
43
  links: readonly WorkspaceSyncV3TaskAttachmentLink[];
43
44
  cardCoverAssetId?: string | null;
45
+ expectedDetailRevision?: string;
44
46
  }): {
45
47
  state: "pending" | "committed";
46
48
  route: "local-outbox" | "cloud-acceptance";
@@ -140,7 +140,13 @@ export function createDurableTaskAttachmentLinksMutationRouter(input) {
140
140
  catch {
141
141
  throw new DurableTaskAttachmentLinksMutationError('Captured task attachment-link payload is invalid.', 'TASK_ATTACHMENT_LINKS_IDENTITY_CONFLICT', 409);
142
142
  }
143
- if (canonicalWorkspaceSyncJson(frozen) !== canonicalWorkspaceSyncJson(links)
143
+ const relationshipIntent = (entries) => entries.map((link) => ({
144
+ assetId: link.assetId,
145
+ role: link.role,
146
+ order: link.order,
147
+ }));
148
+ if (canonicalWorkspaceSyncJson(relationshipIntent(frozen))
149
+ !== canonicalWorkspaceSyncJson(relationshipIntent(links))
144
150
  || String(payload.cardCoverAssetId || '') !== String(cardCoverAssetId || '')) {
145
151
  throw new DurableTaskAttachmentLinksMutationError('Operation identity is assigned to different task attachment links.', 'TASK_ATTACHMENT_LINKS_IDENTITY_CONFLICT', 409);
146
152
  }
@@ -204,6 +210,10 @@ export function createDurableTaskAttachmentLinksMutationRouter(input) {
204
210
  baseEntityRevision: coordinates.entityRevision, baseLifecycleRevision: coordinates.lifecycleRevision,
205
211
  now: acceptedAtIso, leaseExpiresAt: new Date(acceptedAt.getTime() + OPERATION_LEASE_MS).toISOString(),
206
212
  actorRef, onPostCommitError: input.onPostCommitError,
213
+ expectedDetailRevision: args.expectedDetailRevision,
214
+ assertPrecondition: args.expectedDetailRevision
215
+ ? () => input.core.assertTaskDetailRevisionForUpdate(taskId, args.expectedDetailRevision, workspaceId)
216
+ : undefined,
207
217
  });
208
218
  if (result.state === 'in_progress')
209
219
  throw new DurableTaskAttachmentLinksRouterInProgressError(result.retryAt);
@@ -11,6 +11,7 @@ export declare function resolveTaskChecklistMutationCapability(input: {
11
11
  export type DurableTaskChecklistMutationRouterCore = DurableTaskChecklistServiceCore & LocalTaskChecklistOutboxCore & {
12
12
  getDatabaseAdapter(): DatabaseAdapter;
13
13
  getTask(id: string, workspaceId?: string): TaskItem | null;
14
+ assertTaskDetailRevisionForUpdate(id: string, expected: string, workspaceId?: string): void;
14
15
  };
15
16
  export declare class DurableTaskChecklistRouterDisabledError extends Error {
16
17
  readonly code = "WORKSPACE_SYNC_V3_TASK_CHECKLIST_ROUTER_DISABLED";
@@ -38,6 +39,7 @@ export declare function createDurableTaskChecklistMutationRouter(input: {
38
39
  replace(args: {
39
40
  taskId: string;
40
41
  items: readonly Partial<ChecklistItem>[];
42
+ expectedDetailRevision?: string;
41
43
  }): {
42
44
  state: "pending" | "committed";
43
45
  route: "local-outbox" | "cloud-acceptance";
@@ -170,6 +170,10 @@ export function createDurableTaskChecklistMutationRouter(input) {
170
170
  baseEntityRevision: coordinates.entityRevision, baseLifecycleRevision: coordinates.lifecycleRevision,
171
171
  now: acceptedAtIso, leaseExpiresAt: new Date(acceptedAt.getTime() + OPERATION_LEASE_MS).toISOString(),
172
172
  actorRef, onPostCommitError: input.onPostCommitError,
173
+ expectedDetailRevision: args.expectedDetailRevision,
174
+ assertPrecondition: args.expectedDetailRevision
175
+ ? () => input.core.assertTaskDetailRevisionForUpdate(taskId, args.expectedDetailRevision, workspaceId)
176
+ : undefined,
173
177
  });
174
178
  if (result.state === 'in_progress')
175
179
  throw new DurableTaskChecklistRouterInProgressError(result.retryAt);
@@ -51,6 +51,7 @@ export declare function createDurableTaskHttpBulkMutationRouter(input: {
51
51
  }>, options?: {
52
52
  saveSource?: "manual" | "autosave";
53
53
  atomicItemFailures?: boolean;
54
+ expectedDetailRevision?: string;
54
55
  }): {
55
56
  preview: import("../../core/Taskforce.js").BulkUpdateFieldsPreview;
56
57
  results: TaskItem[];
@@ -222,7 +222,12 @@ export function createDurableTaskHttpBulkMutationRouter(input) {
222
222
  }, { atomicRecovery: true });
223
223
  },
224
224
  updateFields(updates, options = {}) {
225
- return input.rootRouter.runBatch('bulk-update-fields', { updates }, (batch) => {
225
+ return input.rootRouter.runBatch('bulk-update-fields', {
226
+ updates,
227
+ ...(options.expectedDetailRevision
228
+ ? { expectedDetailRevision: options.expectedDetailRevision }
229
+ : {}),
230
+ }, (batch) => {
226
231
  const preview = input.core.bulkUpdateFieldsPreview(updates, input.workspaceId);
227
232
  const results = [];
228
233
  const errors = preview.skipped
@@ -273,14 +278,21 @@ export function createDurableTaskHttpBulkMutationRouter(input) {
273
278
  && !(durableAttachmentLinks && key === 'cardCoverAssetId'))))
274
279
  : item.fields;
275
280
  let updated = input.core.getTask(item.id, input.workspaceId);
281
+ let expectedDetailRevision = options.expectedDetailRevision;
276
282
  if (Object.keys(rootFields).length > 0) {
277
283
  const child = input.createChildRouter(input.deriveChildOperationId('bulk-update-fields-root', item.index, item.id), () => { });
278
284
  updated = child.enabled
279
- ? batch.runChild(() => child.update(item.id, rootFields, options.saveSource || 'manual'))
280
- : input.core.updateTask(item.id, rootFields, input.workspaceId, {
281
- actorRef: 'user',
282
- saveSource: options.saveSource || 'manual',
283
- });
285
+ ? batch.runChild(() => child.update(item.id, rootFields, options.saveSource || 'manual', undefined, expectedDetailRevision))
286
+ : (() => {
287
+ if (expectedDetailRevision) {
288
+ input.core.assertTaskDetailRevisionForUpdate(item.id, expectedDetailRevision, input.workspaceId);
289
+ }
290
+ return input.core.updateTask(item.id, rootFields, input.workspaceId, {
291
+ actorRef: 'user',
292
+ saveSource: options.saveSource || 'manual',
293
+ });
294
+ })();
295
+ expectedDetailRevision = undefined;
284
296
  }
285
297
  if (durableChecklist && checklist) {
286
298
  const currentTask = input.core.getTask(item.id, input.workspaceId) || updated;
@@ -301,7 +313,12 @@ export function createDurableTaskHttpBulkMutationRouter(input) {
301
313
  updatedAt: String(unchanged ? existing.updatedAt : existing ? now : source.updatedAt || createdAt),
302
314
  };
303
315
  });
304
- updated = batch.runChild(() => checklist.replace({ taskId: item.id, items: normalizedChecklist }).task);
316
+ updated = batch.runChild(() => checklist.replace({
317
+ taskId: item.id,
318
+ items: normalizedChecklist,
319
+ expectedDetailRevision,
320
+ }).task);
321
+ expectedDetailRevision = undefined;
305
322
  }
306
323
  if (durableAttachmentLinks && attachmentLinks) {
307
324
  updated = batch.runChild(() => attachmentLinks.replace({
@@ -312,7 +329,9 @@ export function createDurableTaskHttpBulkMutationRouter(input) {
312
329
  ? null
313
330
  : String(item.fields.cardCoverAssetId).trim(),
314
331
  } : {}),
332
+ expectedDetailRevision,
315
333
  }).task);
334
+ expectedDetailRevision = undefined;
316
335
  }
317
336
  if (updated)
318
337
  results.push(updated);
@@ -327,7 +346,10 @@ export function createDurableTaskHttpBulkMutationRouter(input) {
327
346
  }
328
347
  batch.deferPostCommitNotification(() => input.core.notifyDurableTaskBatchCommitted(input.workspaceId, results.map((task) => task.id)));
329
348
  return { preview, results, errors };
330
- }, { atomicRecovery: true, allowIndependentNestedOwnership: true });
349
+ }, {
350
+ atomicRecovery: true,
351
+ allowIndependentNestedOwnership: true,
352
+ });
331
353
  },
332
354
  };
333
355
  }
@@ -3,7 +3,8 @@ import type { TaskRestoreDestination } from '../../shared/taskRestoreDestination
3
3
  export declare class DurableTaskHttpRootMutationError extends Error {
4
4
  readonly code: string;
5
5
  readonly statusCode: number;
6
- constructor(message: string, code: string, statusCode: number);
6
+ readonly details?: Record<string, unknown> | undefined;
7
+ constructor(message: string, code: string, statusCode: number, details?: Record<string, unknown> | undefined);
7
8
  }
8
9
  export declare function resolveTaskHttpRootMutationCapability(input: {
9
10
  runtimeMode: 'local' | 'cloud';
@@ -22,8 +23,9 @@ export declare function createDurableTaskHttpRootMutationRouter(input: {
22
23
  deferPostCommitNotification?: (notify: () => void) => void;
23
24
  }): {
24
25
  enabled: boolean;
26
+ hasCommittedOperation(): boolean;
25
27
  create(candidate: unknown): TaskItem;
26
- update(taskIdInput: string, candidate: unknown, saveSource: "manual" | "autosave", activityAction?: "task-updated" | "task-schedule-changed"): TaskItem;
28
+ update(taskIdInput: string, candidate: unknown, saveSource: "manual" | "autosave", activityAction?: "task-updated" | "task-schedule-changed", expectedDetailRevision?: string): TaskItem;
27
29
  runBatch<T>(routeInput: string, request: unknown, apply: (context: {
28
30
  runChild<R>(mutation: () => R): R;
29
31
  deferPostCommitNotification(notify: () => void): void;
@@ -36,5 +38,5 @@ export declare function createDurableTaskHttpRootMutationRouter(input: {
36
38
  restore(deletedRecordIdInput: string, restoreDestination?: TaskRestoreDestination): TaskItem;
37
39
  delete(taskIdInput: string): DeletedTaskRecord;
38
40
  };
39
- type TaskforceCoreForHttpRootMutation = Pick<import('../../core/Taskforce.js').TaskforceCore, 'getDatabaseAdapter' | 'getTask' | 'listAssignableActors' | 'getDeletedTask' | 'updateTask' | 'hasTaskDeletionMarkerForSync' | 'createTask' | 'listTaskAttachmentLinksForDurableSync' | 'resolveTaskAttachmentLinksForDurableSync' | 'normalizeTaskAttachmentDescriptorsForDurableSync' | 'notifyDurableTaskCreateCommitted' | 'notifyDurableTaskBatchCommitted' | 'notifyDurableTaskMetadataCommitted' | 'notifyDurableTaskLifecycleCommitted' | 'notifyDurableTaskDeleteCommitted' | 'getTaskEventForSync' | 'getEntityEventForSync' | 'lockTaskLifecycleMutationDomain' | 'archiveTask' | 'unarchiveTask' | 'restoreDeletedTask' | 'addTaskStatusEventsForDurableSync' | 'lockTaskRelationshipMutationDomain' | 'listTaskRelationships' | 'getTaskRelationshipForSync' | 'deleteTaskForSync'>;
41
+ type TaskforceCoreForHttpRootMutation = Pick<import('../../core/Taskforce.js').TaskforceCore, 'getDatabaseAdapter' | 'getTask' | 'getTaskDetailRevision' | 'assertTaskDetailRevisionForUpdate' | 'listAssignableActors' | 'getDeletedTask' | 'updateTask' | 'hasTaskDeletionMarkerForSync' | 'createTask' | 'listTaskAttachmentLinksForDurableSync' | 'resolveTaskAttachmentLinksForDurableSync' | 'normalizeTaskAttachmentDescriptorsForDurableSync' | 'notifyDurableTaskCreateCommitted' | 'notifyDurableTaskBatchCommitted' | 'notifyDurableTaskMetadataCommitted' | 'notifyDurableTaskLifecycleCommitted' | 'notifyDurableTaskDeleteCommitted' | 'getTaskEventForSync' | 'getEntityEventForSync' | 'lockTaskLifecycleMutationDomain' | 'archiveTask' | 'unarchiveTask' | 'restoreDeletedTask' | 'addTaskStatusEventsForDurableSync' | 'lockTaskRelationshipMutationDomain' | 'listTaskRelationships' | 'getTaskRelationshipForSync' | 'deleteTaskForSync'>;
40
42
  export {};
@@ -28,10 +28,12 @@ function requiredText(value, label) {
28
28
  export class DurableTaskHttpRootMutationError extends Error {
29
29
  code;
30
30
  statusCode;
31
- constructor(message, code, statusCode) {
31
+ details;
32
+ constructor(message, code, statusCode, details) {
32
33
  super(message);
33
34
  this.code = code;
34
35
  this.statusCode = statusCode;
36
+ this.details = details;
35
37
  this.name = 'DurableTaskHttpRootMutationError';
36
38
  }
37
39
  }
@@ -120,6 +122,11 @@ export function createDurableTaskHttpRootMutationRouter(input) {
120
122
  rethrowDurableReplayError(error, 'Committed durable task HTTP response is invalid.');
121
123
  }
122
124
  };
125
+ const detailRevisionPrecondition = (taskId, expectedDetailRevision) => {
126
+ if (!expectedDetailRevision)
127
+ return undefined;
128
+ return () => input.core.assertTaskDetailRevisionForUpdate(taskId, expectedDetailRevision, input.workspaceId);
129
+ };
123
130
  const lifecycle = (taskIdInput, mutationKind, payload) => {
124
131
  const taskId = requiredText(taskIdInput, 'Task id');
125
132
  const operation = context();
@@ -144,6 +151,12 @@ export function createDurableTaskHttpRootMutationRouter(input) {
144
151
  };
145
152
  return {
146
153
  enabled,
154
+ hasCommittedOperation() {
155
+ const operationId = String(input.operationId || '').trim();
156
+ if (!operationId)
157
+ return false;
158
+ return Boolean(store.readCommittedOperationResponse(operationIdentity(operationId)));
159
+ },
147
160
  create(candidate) {
148
161
  const source = candidate && typeof candidate === 'object' && !Array.isArray(candidate)
149
162
  ? candidate
@@ -183,7 +196,7 @@ export function createDurableTaskHttpRootMutationRouter(input) {
183
196
  throw new DurableTaskHttpRootMutationError('The durable task mutation is already in progress.', 'WORKSPACE_SYNC_OPERATION_IN_PROGRESS', 409);
184
197
  return result.response.authoritative.payload;
185
198
  },
186
- update(taskIdInput, candidate, saveSource, activityAction) {
199
+ update(taskIdInput, candidate, saveSource, activityAction, expectedDetailRevision) {
187
200
  const taskId = requiredText(taskIdInput, 'Task id');
188
201
  if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
189
202
  throw new DurableTaskHttpRootMutationError('Task patch must be an object.', 'DURABLE_TASK_PATCH_INVALID', 400);
@@ -224,6 +237,8 @@ export function createDurableTaskHttpRootMutationRouter(input) {
224
237
  now: operation.acceptedAtIso,
225
238
  leaseExpiresAt: new Date(operation.acceptedAt.getTime() + OPERATION_LEASE_MS).toISOString(),
226
239
  actorRef: input.actorRef,
240
+ expectedDetailRevision,
241
+ assertPrecondition: detailRevisionPrecondition(taskId, expectedDetailRevision),
227
242
  onPostCommitError: input.onPostCommitError,
228
243
  });
229
244
  if (result.state === 'in_progress')
@@ -283,6 +298,8 @@ export function createDurableTaskHttpRootMutationRouter(input) {
283
298
  actorRef: input.actorRef,
284
299
  saveSource,
285
300
  activityAction,
301
+ expectedDetailRevision,
302
+ assertPrecondition: detailRevisionPrecondition(taskId, expectedDetailRevision),
286
303
  onPostCommitError: input.onPostCommitError,
287
304
  });
288
305
  if (result.state === 'in_progress')
@@ -341,6 +358,14 @@ export function createDurableTaskHttpRootMutationRouter(input) {
341
358
  if (options?.atomicRecovery) {
342
359
  const notifications = [];
343
360
  const result = input.core.getDatabaseAdapter().transaction(() => {
361
+ // Child durable mutations lock the workspace sequence before
362
+ // touching task rows. Take the same lock first when a batch
363
+ // may combine a legacy root write with durable child owners.
364
+ store.lockWorkspaceSequenceForMutation({
365
+ tenantId: input.tenantId,
366
+ workspaceId: input.workspaceId,
367
+ now: operation.acceptedAtIso,
368
+ });
344
369
  const value = apply({
345
370
  runChild: (mutation) => input.core.getDatabaseAdapter().transaction(mutation)(),
346
371
  deferPostCommitNotification: (notify) => notifications.push(notify),
@@ -1,8 +1,10 @@
1
1
  import type { TaskEvent, TaskItem } from '../../core/Taskforce.js';
2
+ import type { DatabaseAdapter } from '../../storage/databaseAdapter.js';
2
3
  import type { DurableTaskAttachmentLinksServiceCore } from './taskAttachmentLinksMutationService.js';
3
4
  import { WorkspaceSyncDurabilityStore, type WorkspaceSyncOperationIdentity } from './syncDurabilityStore.js';
4
5
  import { type WorkspaceSyncV3TaskAttachmentLink } from './workspaceSyncV3NestedTaskCodecs.js';
5
6
  export interface LocalTaskAttachmentLinksOutboxCore extends DurableTaskAttachmentLinksServiceCore {
7
+ getDatabaseAdapter(): DatabaseAdapter;
6
8
  getWorkspaceAssetDispatchReadiness(assetId: string, workspaceId: string): {
7
9
  ready: boolean;
8
10
  retryable: boolean;
@@ -5,7 +5,9 @@ import { buildTaskAttachmentLinksActivityEventId } from './taskAttachmentLinksAc
5
5
  import { canonicalizeEntityActivityDetailsForSync, serializeTaskActivityForSync, } from './taskActivityJournal.js';
6
6
  import { fingerprintWorkspaceSyncPayload, } from './syncDurabilityStore.js';
7
7
  import { serializeTaskRootForSync } from './workspaceSyncV3TaskRootProjection.js';
8
- import { serializeWorkspaceSyncV3TaskAttachmentLinksPayload, } from './workspaceSyncV3NestedTaskCodecs.js';
8
+ import { resolveWorkspaceSyncV3RegisteredCoverage } from './workspaceSyncV3CoverageRegistry.js';
9
+ import { WorkspaceSyncV3CombinedProjectionStateStore } from './workspaceSyncV3CombinedProjectionState.js';
10
+ import { serializeWorkspaceSyncV3TaskAttachmentLinksPayload, serializeWorkspaceSyncV3TaskAttachmentLinksV2Payload, validateWorkspaceSyncV3TaskAttachmentLinksPayload, validateWorkspaceSyncV3TaskAttachmentLinksV2Payload, } from './workspaceSyncV3NestedTaskCodecs.js';
9
11
  import { withOriginActorPayload } from './workspaceSyncV3OriginActor.js';
10
12
  export class LocalAuthoritativeTaskAttachmentLinksError extends Error {
11
13
  code = 'WORKSPACE_SYNC_AUTHORITATIVE_RESPONSE_INVALID';
@@ -90,6 +92,24 @@ function requireRevision(value, label) {
90
92
  throw new LocalAuthoritativeTaskAttachmentLinksError(`${label} is invalid.`);
91
93
  return text;
92
94
  }
95
+ function resolveOwnedAttachmentProjection(input) {
96
+ const state = new WorkspaceSyncV3CombinedProjectionStateStore(input.core.getDatabaseAdapter()).read({
97
+ tenantId: input.identity.tenantId,
98
+ workspaceId: input.identity.workspaceId,
99
+ });
100
+ if (state.owner !== 'v3')
101
+ return null;
102
+ const coverage = resolveWorkspaceSyncV3RegisteredCoverage(state)?.profile;
103
+ if (!coverage)
104
+ throw new LocalAuthoritativeTaskAttachmentLinksError('Owned task attachment-link coverage is not registered.');
105
+ if (coverage.projections.some(({ projectionId }) => projectionId === 'task-attachment-links/full-v2')) {
106
+ return 'task-attachment-links/full-v2';
107
+ }
108
+ if (coverage.projections.some(({ projectionId }) => projectionId === 'task-attachment-links/full-v1')) {
109
+ return 'task-attachment-links/full-v1';
110
+ }
111
+ return null;
112
+ }
93
113
  export function enqueueLocalTaskAttachmentLinksMutation(input) {
94
114
  const requestedLinks = canonicalLinks(input.taskId, input.links);
95
115
  let attachments;
@@ -206,9 +226,20 @@ export function applyAuthoritativeTaskAttachmentLinksAndAck(input) {
206
226
  throw new LocalAuthoritativeTaskAttachmentLinksError('Authoritative task attachment-link operation ordering is invalid.');
207
227
  }
208
228
  const rootPayload = serializeTaskRootForSync(root.payload);
209
- const attachmentLinksPayload = serializeWorkspaceSyncV3TaskAttachmentLinksPayload({
210
- taskId: attachmentLinks.entityId, links: attachmentLinks.payload.links,
211
- });
229
+ const rawAttachmentPayload = attachmentLinks.payload;
230
+ const hasAuthoritativeCardCover = Object.prototype.hasOwnProperty.call(rawAttachmentPayload, 'cardCoverAssetId');
231
+ const validatedAttachmentLinksPayload = hasAuthoritativeCardCover
232
+ ? validateWorkspaceSyncV3TaskAttachmentLinksV2Payload(rawAttachmentPayload)
233
+ : validateWorkspaceSyncV3TaskAttachmentLinksPayload(rawAttachmentPayload);
234
+ const ownedAttachmentProjection = resolveOwnedAttachmentProjection(input);
235
+ const attachmentLinksPayload = ownedAttachmentProjection === 'task-attachment-links/full-v1'
236
+ ? serializeWorkspaceSyncV3TaskAttachmentLinksPayload(validatedAttachmentLinksPayload)
237
+ : validatedAttachmentLinksPayload;
238
+ const authoritativeCardCoverAssetId = hasAuthoritativeCardCover
239
+ && ownedAttachmentProjection !== 'task-attachment-links/full-v1'
240
+ ? validatedAttachmentLinksPayload
241
+ .cardCoverAssetId
242
+ : undefined;
212
243
  const activityPayload = serializeTaskActivityForSync(activity);
213
244
  const expectedActivityId = buildTaskAttachmentLinksActivityEventId({
214
245
  workspaceId: input.identity.workspaceId, clientId: input.identity.clientId,
@@ -268,6 +299,9 @@ export function applyAuthoritativeTaskAttachmentLinksAndAck(input) {
268
299
  completedAt: rootPayload.completedAt,
269
300
  canceledReason: rootPayload.canceledReason,
270
301
  attachments,
302
+ ...(authoritativeCardCoverAssetId !== undefined
303
+ ? { cardCoverAssetId: authoritativeCardCoverAssetId }
304
+ : {}),
271
305
  };
272
306
  const updated = input.core.updateTask(root.entityId, rootPatch, input.identity.workspaceId, {
273
307
  persistBackup: false, emitRealtime: false, skipTaskEvents: true,
@@ -282,9 +316,15 @@ export function applyAuthoritativeTaskAttachmentLinksAndAck(input) {
282
316
  input.core.upsertEntityEventForSync(activity, { allowAuthoritativeReplacement: true });
283
317
  const installedLinks = input.core.listTaskAttachmentLinksForDurableSync(updated.id, input.identity.workspaceId);
284
318
  if (canonicalWorkspaceSyncJson(serializeTaskRootForSync(updated)) !== canonicalWorkspaceSyncJson(rootPayload)
285
- || canonicalWorkspaceSyncJson(serializeWorkspaceSyncV3TaskAttachmentLinksPayload({
286
- taskId: updated.id, links: installedLinks,
287
- })) !== canonicalWorkspaceSyncJson(attachmentLinksPayload)) {
319
+ || canonicalWorkspaceSyncJson(authoritativeCardCoverAssetId !== undefined
320
+ ? serializeWorkspaceSyncV3TaskAttachmentLinksV2Payload({
321
+ taskId: updated.id,
322
+ links: installedLinks,
323
+ cardCoverAssetId: updated.cardCoverAssetId ?? null,
324
+ })
325
+ : serializeWorkspaceSyncV3TaskAttachmentLinksPayload({
326
+ taskId: updated.id, links: installedLinks,
327
+ })) !== canonicalWorkspaceSyncJson(attachmentLinksPayload)) {
288
328
  throw new LocalAuthoritativeTaskAttachmentLinksError('Installed task attachment-link state does not match authority.');
289
329
  }
290
330
  return updated;