@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
@@ -1,7 +1,7 @@
1
1
  import { applyTaskCreateForDurableSync, assertTaskCreateIdentityAvailable, validateDurableTaskCreateInput, } from '../../core/TaskDurableCreateMutationService.js';
2
2
  import { serializeTaskForSync } from '../taskSyncPayload.js';
3
3
  import { buildTaskCreateActivityEventId } from './taskCreateActivityIdentity.js';
4
- import { serializeWorkspaceSyncV3TaskAttachmentLinksPayload, serializeWorkspaceSyncV3TaskChecklistPayload, serializeWorkspaceSyncV3TaskCommentsPayload, } from './workspaceSyncV3NestedTaskCodecs.js';
4
+ import { serializeWorkspaceSyncV3TaskAttachmentLinksPayload, serializeWorkspaceSyncV3TaskAttachmentLinksV2Payload, serializeWorkspaceSyncV3TaskChecklistPayload, serializeWorkspaceSyncV3TaskCommentsPayload, } from './workspaceSyncV3NestedTaskCodecs.js';
5
5
  import { asWorkspaceSyncActivityRevision, buildEntityActivityJournalEntry, buildTaskActivityJournalEntry, serializeTaskActivityForSync, } from './taskActivityJournal.js';
6
6
  import { fingerprintWorkspaceSyncPayload, WORKSPACE_SYNC_CONTRACT_VERSION, } from './syncDurabilityStore.js';
7
7
  function requireZeroBaseRevision(value, label) {
@@ -151,13 +151,15 @@ export function runDurableTaskCreate(input) {
151
151
  domain: 'task-attachment-links', entityId: taskId, mutationKind: 'replace', revisionKind: 'metadata',
152
152
  baseEntityRevision: 0, baseLifecycleRevision: 0,
153
153
  include: () => attachmentLinks.length > 0,
154
- buildCanonicalFingerprint: (task) => fingerprintWorkspaceSyncPayload(serializeWorkspaceSyncV3TaskAttachmentLinksPayload({
154
+ buildCanonicalFingerprint: (task) => fingerprintWorkspaceSyncPayload(serializeWorkspaceSyncV3TaskAttachmentLinksV2Payload({
155
155
  taskId: task.id,
156
156
  links: input.core.listTaskAttachmentLinksForDurableSync(task.id, input.workspaceId),
157
+ cardCoverAssetId: task.cardCoverAssetId ?? null,
157
158
  })),
158
- buildAuthoritativePayload: (task) => serializeWorkspaceSyncV3TaskAttachmentLinksPayload({
159
+ buildAuthoritativePayload: (task) => serializeWorkspaceSyncV3TaskAttachmentLinksV2Payload({
159
160
  taskId: task.id,
160
161
  links: input.core.listTaskAttachmentLinksForDurableSync(task.id, input.workspaceId),
162
+ cardCoverAssetId: task.cardCoverAssetId ?? null,
161
163
  }),
162
164
  }, buildTaskActivityJournalEntry({ eventId: activityEventId, readEvent: readActivity }), {
163
165
  ...buildEntityActivityJournalEntry({
@@ -257,9 +259,10 @@ export function runDurableTaskCreate(input) {
257
259
  sequence: attachmentRevision.sequence,
258
260
  entityRevision: attachmentRevision.entityRevision,
259
261
  lifecycleRevision: attachmentRevision.lifecycleRevision,
260
- payload: serializeWorkspaceSyncV3TaskAttachmentLinksPayload({
262
+ payload: serializeWorkspaceSyncV3TaskAttachmentLinksV2Payload({
261
263
  taskId: task.id,
262
264
  links: input.core.listTaskAttachmentLinksForDurableSync(task.id, input.workspaceId),
265
+ cardCoverAssetId: task.cardCoverAssetId ?? null,
263
266
  }),
264
267
  } } : {}),
265
268
  activity: serializeTaskActivityForSync(activity),
@@ -45,6 +45,7 @@ export interface DurableTaskMetadataServiceCore extends DurableTaskMetadataMutat
45
45
  workspaceId: string;
46
46
  eventId: string;
47
47
  }): EntityEvent | null;
48
+ assertTaskDetailRevisionForUpdate?(id: string, expectedDetailRevision: string, workspaceId?: string): void;
48
49
  }
49
50
  export declare function runDurableTaskMetadataUpdate(input: {
50
51
  store: WorkspaceSyncDurabilityStore;
@@ -67,5 +68,7 @@ export declare function runDurableTaskMetadataUpdate(input: {
67
68
  originActorRef?: string;
68
69
  saveSource?: 'manual' | 'autosave';
69
70
  activityAction?: 'task-updated' | 'task-schedule-changed';
71
+ expectedDetailRevision?: string;
72
+ assertPrecondition?: () => void;
70
73
  onPostCommitError?: (error: unknown) => void;
71
74
  }): DurableTaskMetadataMutationResult;
@@ -35,6 +35,9 @@ export function runDurableTaskMetadataUpdate(input) {
35
35
  ...(input.activityAction && input.activityAction !== 'task-updated'
36
36
  ? { activityAction: input.activityAction }
37
37
  : {}),
38
+ ...(input.expectedDetailRevision
39
+ ? { expectedDetailRevision: input.expectedDetailRevision }
40
+ : {}),
38
41
  },
39
42
  });
40
43
  const activityEventId = buildTaskMetadataActivityEventId({
@@ -112,6 +115,7 @@ export function runDurableTaskMetadataUpdate(input) {
112
115
  })),
113
116
  }],
114
117
  applyMutation: () => {
118
+ input.assertPrecondition?.();
115
119
  const actorRef = resolveDeferredMutationActorRef(input);
116
120
  if (!carriers)
117
121
  return applyTaskMetadataUpdateForDurableSync({
@@ -2,7 +2,7 @@ import type { TaskEvent } from '../../core/Taskforce.js';
2
2
  import type { TaskRestoreDestination } from '../../shared/taskRestoreDestination.js';
3
3
  import { type DurableTaskRestoreCore } from '../../core/TaskDurableRestoreMutationService.js';
4
4
  import { serializeTaskForSync } from '../taskSyncPayload.js';
5
- import { serializeWorkspaceSyncV3TaskAttachmentLinksPayload, serializeWorkspaceSyncV3TaskChecklistPayload, serializeWorkspaceSyncV3TaskCommentsPayload, type WorkspaceSyncV3TaskAttachmentLink } from './workspaceSyncV3NestedTaskCodecs.js';
5
+ import { serializeWorkspaceSyncV3TaskAttachmentLinksV2Payload, serializeWorkspaceSyncV3TaskChecklistPayload, serializeWorkspaceSyncV3TaskCommentsPayload, type WorkspaceSyncV3TaskAttachmentLink } from './workspaceSyncV3NestedTaskCodecs.js';
6
6
  import { type WorkspaceSyncActivityRevision } from './taskActivityJournal.js';
7
7
  import { WorkspaceSyncDurabilityStore } from './syncDurabilityStore.js';
8
8
  export interface DurableTaskRestoreServiceCore extends DurableTaskRestoreCore {
@@ -57,7 +57,7 @@ export type DurableTaskRestoreResponse = {
57
57
  sequence: string;
58
58
  entityRevision: string;
59
59
  lifecycleRevision: string;
60
- payload: ReturnType<typeof serializeWorkspaceSyncV3TaskAttachmentLinksPayload>;
60
+ payload: ReturnType<typeof serializeWorkspaceSyncV3TaskAttachmentLinksV2Payload>;
61
61
  };
62
62
  activity: TaskEvent;
63
63
  activityRevision: WorkspaceSyncActivityRevision;
@@ -1,7 +1,7 @@
1
1
  import { applyTaskRestoreForDurableSync, } from '../../core/TaskDurableRestoreMutationService.js';
2
2
  import { serializeTaskForSync } from '../taskSyncPayload.js';
3
3
  import { buildTaskLifecycleActivityEventId } from './taskLifecycleActivityIdentity.js';
4
- import { serializeWorkspaceSyncV3TaskAttachmentLinksPayload, serializeWorkspaceSyncV3TaskChecklistPayload, serializeWorkspaceSyncV3TaskCommentsPayload, } from './workspaceSyncV3NestedTaskCodecs.js';
4
+ import { serializeWorkspaceSyncV3TaskAttachmentLinksV2Payload, serializeWorkspaceSyncV3TaskChecklistPayload, serializeWorkspaceSyncV3TaskCommentsPayload, } from './workspaceSyncV3NestedTaskCodecs.js';
5
5
  import { asWorkspaceSyncActivityRevision, buildTaskActivityJournalEntry, } from './taskActivityJournal.js';
6
6
  import { fingerprintWorkspaceSyncPayload, WORKSPACE_SYNC_CONTRACT_VERSION, } from './syncDurabilityStore.js';
7
7
  export function runDurableTaskRestore(input) {
@@ -90,13 +90,15 @@ export function runDurableTaskRestore(input) {
90
90
  domain: 'task-attachment-links', entityId: input.taskId, mutationKind: 'replace', revisionKind: 'metadata',
91
91
  baseEntityRevision: 0, baseLifecycleRevision: 0,
92
92
  baseRevisionPolicy: 'current',
93
- buildCanonicalFingerprint: (task) => fingerprintWorkspaceSyncPayload(serializeWorkspaceSyncV3TaskAttachmentLinksPayload({
93
+ buildCanonicalFingerprint: (task) => fingerprintWorkspaceSyncPayload(serializeWorkspaceSyncV3TaskAttachmentLinksV2Payload({
94
94
  taskId: task.id,
95
95
  links: input.core.listTaskAttachmentLinksForDurableSync(task.id, input.workspaceId),
96
+ cardCoverAssetId: task.cardCoverAssetId ?? null,
96
97
  })),
97
- buildAuthoritativePayload: (task) => serializeWorkspaceSyncV3TaskAttachmentLinksPayload({
98
+ buildAuthoritativePayload: (task) => serializeWorkspaceSyncV3TaskAttachmentLinksV2Payload({
98
99
  taskId: task.id,
99
100
  links: input.core.listTaskAttachmentLinksForDurableSync(task.id, input.workspaceId),
101
+ cardCoverAssetId: task.cardCoverAssetId ?? null,
100
102
  }),
101
103
  }, buildTaskActivityJournalEntry({ eventId: activityEventId, readEvent: readActivity })],
102
104
  applyMutation: () => applyTaskRestoreForDurableSync({
@@ -151,9 +153,10 @@ export function runDurableTaskRestore(input) {
151
153
  domain: 'task-attachment-links', entityId: task.id, mutationKind: 'replace',
152
154
  sequence: attachmentRevision.sequence, entityRevision: attachmentRevision.entityRevision,
153
155
  lifecycleRevision: attachmentRevision.lifecycleRevision,
154
- payload: serializeWorkspaceSyncV3TaskAttachmentLinksPayload({
156
+ payload: serializeWorkspaceSyncV3TaskAttachmentLinksV2Payload({
155
157
  taskId: task.id,
156
158
  links: input.core.listTaskAttachmentLinksForDurableSync(task.id, input.workspaceId),
159
+ cardCoverAssetId: task.cardCoverAssetId ?? null,
157
160
  }),
158
161
  },
159
162
  activity: readActivity(),
@@ -64,6 +64,8 @@ export declare function runDurableTaskStatusMutation(input: {
64
64
  resolveActorRef?: () => string;
65
65
  originActorRef?: string;
66
66
  includeWorkflowRuntimeJournal?: boolean;
67
+ expectedDetailRevision?: string;
68
+ assertPrecondition?: () => void;
67
69
  onPostCommitError?: (error: unknown) => void;
68
70
  }): {
69
71
  state: 'in_progress';
@@ -34,6 +34,9 @@ export function runDurableTaskStatusMutation(input) {
34
34
  baseLifecycleRevision: String(input.baseLifecycleRevision),
35
35
  payload: operationPayload,
36
36
  ...(input.originActorRef ? { originActorRef: input.originActorRef } : {}),
37
+ ...(input.expectedDetailRevision
38
+ ? { expectedDetailRevision: input.expectedDetailRevision }
39
+ : {}),
37
40
  },
38
41
  });
39
42
  const activityEventId = buildTaskLifecycleActivityEventId({
@@ -184,6 +187,7 @@ export function runDurableTaskStatusMutation(input) {
184
187
  })),
185
188
  }, ...workflowEntries],
186
189
  applyMutation: () => {
190
+ input.assertPrecondition?.();
187
191
  const currentTask = input.core.getTask(input.taskId, input.workspaceId);
188
192
  if (operationPayload.workflowAssignment) {
189
193
  if (payload.status !== 'in-progress'
@@ -2,6 +2,7 @@ import type { WorkflowAssignmentSyncRecord, WorkflowExecutionEventRecord, Workfl
2
2
  import { type DurableTaskStatusMutationPayload } from '../../core/TaskDurableStatusMutationService.js';
3
3
  export type TaskStatusOperationWithWorkflowAssignment = DurableTaskStatusMutationPayload & {
4
4
  workflowAssignment?: WorkflowAssignmentSyncRecord;
5
+ expectedDetailRevision?: string;
5
6
  };
6
7
  export declare class WorkflowAssignmentSyncError extends Error {
7
8
  readonly code = "WORKFLOW_ASSIGNMENT_SYNC_INVALID";
@@ -209,5 +209,8 @@ export function normalizeTaskStatusOperationWithWorkflowAssignment(raw) {
209
209
  ? {}
210
210
  : { workflowAssignment: normalizeWorkflowAssignmentSyncRecord(source.workflowAssignment) }),
211
211
  ...(originActor ? { originActor } : {}),
212
+ ...(typeof source.expectedDetailRevision === 'string' && source.expectedDetailRevision.trim()
213
+ ? { expectedDetailRevision: source.expectedDetailRevision.trim() }
214
+ : {}),
212
215
  };
213
216
  }
@@ -1,6 +1,6 @@
1
1
  import type { WorkspaceSyncIncomingTaskApplyModel } from '../syncApplyHandlers.js';
2
2
  import type { WorkspaceSyncChange } from '../workspaceSyncModel.js';
3
- export declare const WORKSPACE_SYNC_V2_EXCLUDABLE_PROJECTIONS: readonly ["task/root-v1", "task-relationship/full-v1", "taxonomy/full-v1", "initiative/full-v1", "workstream/full-v1", "task-comments/full-v1", "task-checklist/full-v1", "task-attachment-links/full-v1", "entity-event/full-v1", "workstream-task-order/full-v1"];
3
+ export declare const WORKSPACE_SYNC_V2_EXCLUDABLE_PROJECTIONS: readonly ["task/root-v1", "task-relationship/full-v1", "taxonomy/full-v1", "initiative/full-v1", "workstream/full-v1", "task-comments/full-v1", "task-checklist/full-v1", "task-attachment-links/full-v1", "task-attachment-links/full-v2", "entity-event/full-v1", "workstream-task-order/full-v1"];
4
4
  export type WorkspaceSyncV2ProjectionExclusion = typeof WORKSPACE_SYNC_V2_EXCLUDABLE_PROJECTIONS[number];
5
5
  export declare const WORKSPACE_SYNC_V3_COMBINED_V2_EXCLUSIONS: readonly WorkspaceSyncV2ProjectionExclusion[];
6
6
  export declare function normalizeWorkspaceSyncV2ProjectionExclusions(values: Iterable<unknown> | null | undefined): WorkspaceSyncV2ProjectionExclusion[];
@@ -8,6 +8,7 @@ export const WORKSPACE_SYNC_V2_EXCLUDABLE_PROJECTIONS = Object.freeze([
8
8
  'task-comments/full-v1',
9
9
  'task-checklist/full-v1',
10
10
  'task-attachment-links/full-v1',
11
+ 'task-attachment-links/full-v2',
11
12
  'entity-event/full-v1',
12
13
  'workstream-task-order/full-v1',
13
14
  ]);
@@ -49,7 +50,9 @@ export function prepareWorkspaceSyncV2TaskCarriers(taskMutations, excludedProjec
49
50
  return taskMutations;
50
51
  const commentsOwned = excludedProjections.has('task-comments/full-v1');
51
52
  const checklistOwned = excludedProjections.has('task-checklist/full-v1');
52
- const attachmentsOwned = excludedProjections.has('task-attachment-links/full-v1');
53
+ const cardCoverOwned = excludedProjections.has('task-attachment-links/full-v2');
54
+ const attachmentsOwned = excludedProjections.has('task-attachment-links/full-v1')
55
+ || cardCoverOwned;
53
56
  return {
54
57
  upserts: taskMutations.upserts.map(({ archived, task }) => {
55
58
  const existing = readExistingTask?.(String(task.id || '').trim());
@@ -63,6 +66,7 @@ export function prepareWorkspaceSyncV2TaskCarriers(taskMutations, excludedProjec
63
66
  ...(commentsOwned ? { comments: existing.comments || [] } : {}),
64
67
  ...(checklistOwned ? { checklistItems: existing.checklistItems || [] } : {}),
65
68
  ...(attachmentsOwned ? { attachments: existing.attachments || [] } : {}),
69
+ ...(cardCoverOwned ? { cardCoverAssetId: existing.cardCoverAssetId ?? null } : {}),
66
70
  },
67
71
  };
68
72
  }),
@@ -100,7 +104,10 @@ export function partitionWorkspaceSyncV2LegacyChanges(changes, excludedProjectio
100
104
  if (change.op === 'upsert') {
101
105
  const commentsOwned = excludedProjections.has('task-comments/full-v1');
102
106
  const checklistOwned = excludedProjections.has('task-checklist/full-v1');
103
- const attachmentsOwned = excludedProjections.has('task-attachment-links/full-v1');
107
+ const attachmentProjection = excludedProjections.has('task-attachment-links/full-v2')
108
+ ? 'task-attachment-links/full-v2'
109
+ : 'task-attachment-links/full-v1';
110
+ const attachmentsOwned = excludedProjections.has(attachmentProjection);
104
111
  if (excludedProjections.has('task/root-v1'))
105
112
  suppress('task/root-v1');
106
113
  if (commentsOwned || checklistOwned || attachmentsOwned) {
@@ -115,7 +122,10 @@ export function partitionWorkspaceSyncV2LegacyChanges(changes, excludedProjectio
115
122
  }
116
123
  if (attachmentsOwned) {
117
124
  delete task.attachments;
118
- suppress('task-attachment-links/full-v1');
125
+ if (attachmentProjection === 'task-attachment-links/full-v2') {
126
+ delete task.cardCoverAssetId;
127
+ }
128
+ suppress(attachmentProjection);
119
129
  }
120
130
  accepted.push({ ...change, task });
121
131
  continue;
@@ -15,10 +15,13 @@ export interface WorkspaceSyncV3AttachmentApplyObligation {
15
15
  createdAt: string;
16
16
  updatedAt: string;
17
17
  nextAttemptAt: string;
18
+ terminalAt: string | null;
19
+ terminalReason: string | null;
18
20
  resolvedAt: string | null;
19
21
  }
20
22
  export interface WorkspaceSyncV3AttachmentApplyObligationSummary {
21
23
  open: number;
24
+ terminal: number;
22
25
  due: number;
23
26
  oldestOpenCreatedAt: string | null;
24
27
  nextAttemptAt: string | null;
@@ -70,6 +73,14 @@ export declare class WorkspaceSyncV3AttachmentApplyObligationStore {
70
73
  taskId: string;
71
74
  now: string;
72
75
  }): void;
76
+ markTerminalUnavailable(input: {
77
+ tenantId: string;
78
+ workspaceId: string;
79
+ taskId: string;
80
+ canonicalFingerprint: string;
81
+ reason: string;
82
+ now: string;
83
+ }): void;
73
84
  recordAttemptFailure(input: {
74
85
  tenantId: string;
75
86
  workspaceId: string;
@@ -28,6 +28,8 @@ function mapRow(row) {
28
28
  createdAt: row.created_at,
29
29
  updatedAt: row.updated_at,
30
30
  nextAttemptAt: row.next_attempt_at,
31
+ terminalAt: row.terminal_at,
32
+ terminalReason: row.terminal_reason,
31
33
  resolvedAt: row.resolved_at,
32
34
  };
33
35
  }
@@ -52,8 +54,8 @@ export class WorkspaceSyncV3AttachmentApplyObligationStore {
52
54
  tenant_id, workspace_id, task_id, asset_ids_json, payload_json,
53
55
  canonical_fingerprint, source_sequence, attempts, last_error,
54
56
  entity_revision, lifecycle_revision, revision_fingerprint,
55
- created_at, updated_at, next_attempt_at, resolved_at
56
- ) VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?, NULL)
57
+ created_at, updated_at, next_attempt_at, terminal_at, terminal_reason, resolved_at
58
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, NULL)
57
59
  ON CONFLICT (tenant_id, workspace_id, task_id) DO UPDATE SET
58
60
  asset_ids_json = excluded.asset_ids_json,
59
61
  payload_json = excluded.payload_json,
@@ -66,6 +68,8 @@ export class WorkspaceSyncV3AttachmentApplyObligationStore {
66
68
  last_error = excluded.last_error,
67
69
  updated_at = excluded.updated_at,
68
70
  next_attempt_at = excluded.next_attempt_at,
71
+ terminal_at = NULL,
72
+ terminal_reason = NULL,
69
73
  resolved_at = NULL
70
74
  WHERE workspace_sync_v3_attachment_apply_obligations.resolved_at IS NOT NULL
71
75
  OR CAST(excluded.source_sequence AS ${this.db.provider === 'postgres' ? 'BIGINT' : 'INTEGER'})
@@ -77,7 +81,8 @@ export class WorkspaceSyncV3AttachmentApplyObligationStore {
77
81
  const rows = this.db.prepare(`
78
82
  SELECT *
79
83
  FROM workspace_sync_v3_attachment_apply_obligations
80
- WHERE tenant_id = ? AND workspace_id = ? AND resolved_at IS NULL AND next_attempt_at <= ?
84
+ WHERE tenant_id = ? AND workspace_id = ?
85
+ AND resolved_at IS NULL AND terminal_at IS NULL AND next_attempt_at <= ?
81
86
  ORDER BY next_attempt_at ASC, task_id ASC
82
87
  LIMIT ?
83
88
  `).all(input.tenantId, input.workspaceId, input.now, limit);
@@ -87,30 +92,34 @@ export class WorkspaceSyncV3AttachmentApplyObligationStore {
87
92
  const row = this.db.prepare(`
88
93
  SELECT COUNT(*) AS count
89
94
  FROM workspace_sync_v3_attachment_apply_obligations
90
- WHERE tenant_id = ? AND workspace_id = ? AND resolved_at IS NULL
95
+ WHERE tenant_id = ? AND workspace_id = ?
96
+ AND resolved_at IS NULL AND terminal_at IS NULL
91
97
  `).get(input.tenantId, input.workspaceId);
92
98
  return Number(row?.count || 0);
93
99
  }
94
100
  readSummary(input) {
95
101
  const aggregate = this.db.prepare(`
96
- SELECT COUNT(*) AS open,
97
- SUM(CASE WHEN next_attempt_at <= ? THEN 1 ELSE 0 END) AS due,
98
- MIN(created_at) AS oldest_open_created_at,
99
- MIN(next_attempt_at) AS next_attempt_at,
100
- SUM(attempts) AS total_attempts
102
+ SELECT SUM(CASE WHEN terminal_at IS NULL THEN 1 ELSE 0 END) AS open,
103
+ SUM(CASE WHEN terminal_at IS NOT NULL THEN 1 ELSE 0 END) AS terminal,
104
+ SUM(CASE WHEN terminal_at IS NULL AND next_attempt_at <= ? THEN 1 ELSE 0 END) AS due,
105
+ MIN(CASE WHEN terminal_at IS NULL THEN created_at END) AS oldest_open_created_at,
106
+ MIN(CASE WHEN terminal_at IS NULL THEN next_attempt_at END) AS next_attempt_at,
107
+ SUM(CASE WHEN terminal_at IS NULL THEN attempts ELSE 0 END) AS total_attempts
101
108
  FROM workspace_sync_v3_attachment_apply_obligations
102
109
  WHERE tenant_id = ? AND workspace_id = ? AND resolved_at IS NULL
103
110
  `).get(input.now, input.tenantId, input.workspaceId);
104
111
  const latest = this.db.prepare(`
105
112
  SELECT last_error
106
113
  FROM workspace_sync_v3_attachment_apply_obligations
107
- WHERE tenant_id = ? AND workspace_id = ? AND resolved_at IS NULL
114
+ WHERE tenant_id = ? AND workspace_id = ?
115
+ AND resolved_at IS NULL AND terminal_at IS NULL
108
116
  AND last_error IS NOT NULL
109
117
  ORDER BY updated_at DESC, task_id ASC
110
118
  LIMIT 1
111
119
  `).get(input.tenantId, input.workspaceId);
112
120
  return {
113
121
  open: Number(aggregate?.open || 0),
122
+ terminal: Number(aggregate?.terminal || 0),
114
123
  due: Number(aggregate?.due || 0),
115
124
  oldestOpenCreatedAt: aggregate?.oldest_open_created_at || null,
116
125
  nextAttemptAt: aggregate?.next_attempt_at || null,
@@ -142,6 +151,14 @@ export class WorkspaceSyncV3AttachmentApplyObligationStore {
142
151
  WHERE tenant_id = ? AND workspace_id = ? AND task_id = ? AND resolved_at IS NULL
143
152
  `).run(input.now, input.now, input.tenantId, input.workspaceId, input.taskId);
144
153
  }
154
+ markTerminalUnavailable(input) {
155
+ this.db.prepare(`
156
+ UPDATE workspace_sync_v3_attachment_apply_obligations
157
+ SET terminal_at = ?, terminal_reason = ?, last_error = ?, updated_at = ?
158
+ WHERE tenant_id = ? AND workspace_id = ? AND task_id = ?
159
+ AND canonical_fingerprint = ? AND resolved_at IS NULL AND terminal_at IS NULL
160
+ `).run(input.now, String(input.reason || '').slice(0, 500), String(input.reason || '').slice(0, 500), input.now, input.tenantId, input.workspaceId, input.taskId, input.canonicalFingerprint);
161
+ }
145
162
  recordAttemptFailure(input) {
146
163
  this.db.prepare(`
147
164
  UPDATE workspace_sync_v3_attachment_apply_obligations
@@ -7,10 +7,11 @@ export type WorkspaceSyncV3AttachmentAssetHydrationResult = {
7
7
  } | {
8
8
  success: false;
9
9
  error: string;
10
+ disposition?: 'retryable' | 'terminal-unavailable';
10
11
  };
11
12
  export declare function hydrateWorkspaceSyncV3AttachmentAsset(input: {
12
13
  workspaceId: string;
13
14
  assetId: string;
14
- remoteServices: Pick<WorkspaceSyncEngineRemoteServices, 'pullWorkspaceContent' | 'abandonTransferReceipts' | 'buildV2ApplyCompletionGroup'>;
15
+ remoteServices: Pick<WorkspaceSyncEngineRemoteServices, 'pullWorkspaceContent' | 'abandonTransferReceipts' | 'buildIndependentContentApplyCompletionGroup'>;
15
16
  localServices: Pick<WorkspaceSyncEngineLocalServices, 'applyV2Batch'>;
16
17
  }): Promise<WorkspaceSyncV3AttachmentAssetHydrationResult>;
@@ -3,20 +3,48 @@ import { fingerprintWorkspaceContentBody } from '../workspaceContentManifest.js'
3
3
  export async function hydrateWorkspaceSyncV3AttachmentAsset(input) {
4
4
  const remoteServices = input.remoteServices;
5
5
  const applyV2Batch = input.localServices.applyV2Batch;
6
+ const completionIdentity = `attachment-asset:${input.assetId}`;
6
7
  const response = await remoteServices.pullWorkspaceContent({
7
8
  workspaceId: input.workspaceId,
8
9
  requests: [{ kind: 'asset', assetId: input.assetId }],
9
10
  previousCursor: null,
11
+ independentCompletionIdentity: completionIdentity,
10
12
  });
11
13
  const receipt = response.transferReceipt;
12
14
  let transferCommitted = false;
15
+ let localApplyStarted = false;
13
16
  try {
14
17
  if (!response.ok) {
15
18
  return { success: false, error: `Workspace attachment asset hydration failed (${response.status || 0}).` };
16
19
  }
17
20
  const data = response.data || {};
18
- if ((data.missing?.length || 0) > 0 || (data.remaining?.length || 0) > 0) {
19
- return { success: false, error: `Workspace attachment asset is unavailable in cloud content: ${input.assetId}` };
21
+ if ((data.remaining?.length || 0) > 0) {
22
+ return {
23
+ success: false,
24
+ error: `Workspace attachment asset hydration is incomplete: ${input.assetId}`,
25
+ disposition: 'retryable',
26
+ };
27
+ }
28
+ const exactMissing = Array.isArray(data.missing) && data.missing.find((entry) => (entry?.kind === 'asset'
29
+ && 'assetId' in entry
30
+ && String(entry.assetId || '').trim() === input.assetId));
31
+ if (exactMissing) {
32
+ return {
33
+ success: false,
34
+ error: exactMissing.disposition === 'terminal-unavailable'
35
+ ? `Workspace attachment asset is authoritatively unavailable in cloud metadata: ${input.assetId}`
36
+ : `Workspace attachment asset body is temporarily unavailable in cloud content: ${input.assetId}`,
37
+ disposition: exactMissing.disposition === 'terminal-unavailable'
38
+ ? 'terminal-unavailable'
39
+ : 'retryable',
40
+ };
41
+ }
42
+ if ((data.missing?.length || 0) > 0) {
43
+ return {
44
+ success: false,
45
+ error: `Workspace attachment asset hydration returned an unexpected missing result: ${input.assetId}`,
46
+ disposition: 'retryable',
47
+ };
20
48
  }
21
49
  const rawChanges = Array.isArray(data.changes) ? data.changes : [];
22
50
  const changes = rawChanges.filter((change) => ((change.op === 'document-upsert' || change.op === 'asset-upsert')
@@ -50,10 +78,9 @@ export async function hydrateWorkspaceSyncV3AttachmentAsset(input) {
50
78
  return { success: false, error: `Workspace attachment asset hydration verification failed: ${input.assetId}` };
51
79
  }
52
80
  const completionGroup = receipt
53
- ? remoteServices.buildV2ApplyCompletionGroup({
81
+ ? remoteServices.buildIndependentContentApplyCompletionGroup({
54
82
  workspaceId: input.workspaceId,
55
- previousCursor: null,
56
- nextCursor: null,
83
+ identity: completionIdentity,
57
84
  receipts: [receipt],
58
85
  })
59
86
  : undefined;
@@ -63,6 +90,7 @@ export async function hydrateWorkspaceSyncV3AttachmentAsset(input) {
63
90
  error: 'Workspace attachment asset hydration could not bind its transfer to local apply.',
64
91
  };
65
92
  }
93
+ localApplyStarted = true;
66
94
  const applyResult = completionGroup
67
95
  ? await applyV2Batch(input.workspaceId, changes, { completionGroup })
68
96
  : await applyV2Batch(input.workspaceId, changes);
@@ -80,11 +108,25 @@ export async function hydrateWorkspaceSyncV3AttachmentAsset(input) {
80
108
  }
81
109
  finally {
82
110
  if (receipt && !transferCommitted) {
83
- await remoteServices.abandonTransferReceipts({
84
- workspaceId: input.workspaceId,
85
- receipts: [receipt],
86
- errorCode: 'WORKSPACE_SYNC_V3_ATTACHMENT_HYDRATION_NOT_COMMITTED',
87
- });
111
+ if (!localApplyStarted) {
112
+ const completionGroup = remoteServices.buildIndependentContentApplyCompletionGroup({
113
+ workspaceId: input.workspaceId,
114
+ identity: completionIdentity,
115
+ receipts: [receipt],
116
+ });
117
+ if (completionGroup) {
118
+ localApplyStarted = true;
119
+ const settlement = await applyV2Batch(input.workspaceId, [], { completionGroup });
120
+ transferCommitted = settlement.ok;
121
+ }
122
+ }
123
+ if (!transferCommitted) {
124
+ await remoteServices.abandonTransferReceipts({
125
+ workspaceId: input.workspaceId,
126
+ receipts: [receipt],
127
+ errorCode: 'WORKSPACE_SYNC_V3_ATTACHMENT_HYDRATION_NOT_COMMITTED',
128
+ });
129
+ }
88
130
  }
89
131
  }
90
132
  }
@@ -71,6 +71,10 @@ export function createWorkspaceSyncV3CloudOperationHandlers() {
71
71
  ...withDeferredOriginActor(context, operation.payload.originActor),
72
72
  taskId: operation.entityId, patch: operation.payload.patch,
73
73
  saveSource: operation.payload.saveSource,
74
+ expectedDetailRevision: operation.payload.expectedDetailRevision,
75
+ assertPrecondition: operation.payload.expectedDetailRevision && context.core.assertTaskDetailRevisionForUpdate
76
+ ? () => context.core.assertTaskDetailRevisionForUpdate(operation.entityId, operation.payload.expectedDetailRevision, context.workspaceId)
77
+ : undefined,
74
78
  baseEntityRevision: operation.baseEntityRevision, baseLifecycleRevision: operation.baseLifecycleRevision,
75
79
  });
76
80
  },
@@ -187,11 +191,15 @@ export function createWorkspaceSyncV3CloudOperationHandlers() {
187
191
  supports: (envelope) => envelope.operation.domain === 'task' && envelope.operation.mutationKind === 'set-status',
188
192
  apply: (envelope, context) => {
189
193
  const operation = envelope.operation;
190
- const { originActor, ...payload } = operation.payload;
194
+ const { originActor, expectedDetailRevision, ...payload } = operation.payload;
191
195
  return runDurableTaskStatusMutation({
192
196
  ...withDeferredOriginActor(context, originActor),
193
197
  taskId: operation.entityId,
194
198
  payload,
199
+ expectedDetailRevision,
200
+ assertPrecondition: expectedDetailRevision && context.core.assertTaskDetailRevisionForUpdate
201
+ ? () => context.core.assertTaskDetailRevisionForUpdate(operation.entityId, expectedDetailRevision, context.workspaceId)
202
+ : undefined,
195
203
  baseEntityRevision: operation.baseEntityRevision,
196
204
  baseLifecycleRevision: operation.baseLifecycleRevision,
197
205
  });
@@ -325,10 +325,14 @@ async function runWorkspaceSyncV3CombinedProjectionCycleInternal(input, staleRep
325
325
  let projection = initial;
326
326
  let recoveryRepairApplied = false;
327
327
  let restartStaleRepair = false;
328
+ let restartCoverageRepair = false;
328
329
  let restartedStaleRepair = staleRepairAlreadyRestarted;
329
330
  const persistedCoverage = projectionCoverage(projection);
330
331
  if (!persistedCoverage)
331
332
  return localFailure(projection, 'Persisted combined workspace sync coverage is unsupported.');
333
+ restartCoverageRepair = projection.owner === 'v3'
334
+ && projection.repair !== null
335
+ && !exactCoverage(persistedCoverage, coverage);
332
336
  if (projection.coverageRepairRequired
333
337
  || (projection.owner === 'v3' && !projection.repair && !exactCoverage(persistedCoverage, coverage))) {
334
338
  const feed = await dependencies.pullFeed(input.resolveCloudAuthUrl, {
@@ -413,12 +417,12 @@ async function runWorkspaceSyncV3CombinedProjectionCycleInternal(input, staleRep
413
417
  }
414
418
  let restartExpiredRepair = false;
415
419
  let restartedExpiredRepair = false;
416
- while (projection.owner === 'v2' || projection.repair || restartStaleRepair) {
417
- const previousCursor = restartExpiredRepair || restartStaleRepair
420
+ while (projection.owner === 'v2' || projection.repair || restartStaleRepair || restartCoverageRepair) {
421
+ const previousCursor = restartExpiredRepair || restartStaleRepair || restartCoverageRepair
418
422
  ? null : projection.repair?.cursor || null;
419
423
  const feed = await dependencies.pullFeed(input.resolveCloudAuthUrl, {
420
424
  workspaceId: input.workspaceId,
421
- ...(restartExpiredRepair || restartStaleRepair
425
+ ...(restartExpiredRepair || restartStaleRepair || restartCoverageRepair
422
426
  ? { forceRepair: true }
423
427
  : { repairCursor: previousCursor }),
424
428
  limit: 100,
@@ -449,6 +453,7 @@ async function runWorkspaceSyncV3CombinedProjectionCycleInternal(input, staleRep
449
453
  projection = updated;
450
454
  restartExpiredRepair = false;
451
455
  restartStaleRepair = false;
456
+ restartCoverageRepair = false;
452
457
  if (repairSnapshotWasInvalidated(applied)) {
453
458
  if (restartedStaleRepair) {
454
459
  return localFailure(projection, 'A refreshed combined repair snapshot still predates authoritative local state.');