@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
@@ -4,7 +4,7 @@ import { assertWorkspaceSyncV3OperationBoundary } from './workspaceSyncV3Operati
4
4
  import { projectWorkspaceSyncV3TaskRootJournalRow } from './workspaceSyncV3TaskRootProjection.js';
5
5
  import { projectWorkspaceSyncV3InitiativeJournalRow } from './workspaceSyncV3Feed.js';
6
6
  import { projectWorkspaceSyncV3WorkstreamJournalRow } from './workspaceSyncV3WorkstreamProjection.js';
7
- import { serializeWorkspaceSyncV3EntityEventPayload, validateWorkspaceSyncV3EntityEventPayload, validateWorkspaceSyncV3TaskAttachmentLinksPayload, validateWorkspaceSyncV3TaskChecklistPayload, validateWorkspaceSyncV3TaskCommentAppendPayload, validateWorkspaceSyncV3TaskCommentsPayload, } from './workspaceSyncV3NestedTaskCodecs.js';
7
+ import { serializeWorkspaceSyncV3EntityEventPayload, serializeWorkspaceSyncV3TaskAttachmentLinksPayload, validateWorkspaceSyncV3EntityEventPayload, validateWorkspaceSyncV3TaskAttachmentLinksPayload, validateWorkspaceSyncV3TaskAttachmentLinksV2Payload, validateWorkspaceSyncV3TaskChecklistPayload, validateWorkspaceSyncV3TaskCommentAppendPayload, validateWorkspaceSyncV3TaskCommentsPayload, } from './workspaceSyncV3NestedTaskCodecs.js';
8
8
  import { isLegacyEntityEventAdoptionIdentity } from './entityEventLegacyAdoptionContract.js';
9
9
  export const WORKSPACE_SYNC_V3_NESTED_TASK_PROJECTIONS = Object.freeze([
10
10
  Object.freeze({
@@ -46,6 +46,15 @@ export const WORKSPACE_SYNC_V3_NESTED_TASK_PROJECTIONS = Object.freeze([
46
46
  operationAtomic: true,
47
47
  }),
48
48
  ]);
49
+ export const WORKSPACE_SYNC_V3_TASK_ATTACHMENT_LINKS_V2_PROJECTION = Object.freeze({
50
+ projectionId: 'task-attachment-links/full-v2',
51
+ domain: 'task-attachment-links',
52
+ schemaVersion: 2,
53
+ mutationKinds: Object.freeze(['replace']),
54
+ fields: Object.freeze(['taskId', 'links', 'cardCoverAssetId']),
55
+ dependencies: Object.freeze(['task/root-v1']),
56
+ operationAtomic: true,
57
+ });
49
58
  const projectionByDomain = new Map(WORKSPACE_SYNC_V3_NESTED_TASK_PROJECTIONS.map((projection) => [projection.domain, projection.projectionId]));
50
59
  function sequence(value, label) {
51
60
  const text = String(value ?? '').trim();
@@ -68,7 +77,7 @@ function parsePayload(row) {
68
77
  throw new Error(`Nested task journal payload is malformed at ${row.sequence}.`);
69
78
  }
70
79
  }
71
- function canonicalPayload(row) {
80
+ function canonicalPayload(row, attachmentProjection) {
72
81
  const raw = parsePayload(row);
73
82
  if (row.domain === 'task-comments') {
74
83
  if (row.mutationKind === 'append') {
@@ -91,7 +100,17 @@ function canonicalPayload(row) {
91
100
  return { payload, canonicalFingerprint: fingerprintWorkspaceSyncPayload(payload) };
92
101
  }
93
102
  if (row.domain === 'task-attachment-links' && row.mutationKind === 'replace') {
94
- const payload = validateWorkspaceSyncV3TaskAttachmentLinksPayload(raw);
103
+ const payload = attachmentProjection === 'task-attachment-links/full-v2'
104
+ ? validateWorkspaceSyncV3TaskAttachmentLinksV2Payload(raw)
105
+ : (() => {
106
+ try {
107
+ return validateWorkspaceSyncV3TaskAttachmentLinksPayload(raw);
108
+ }
109
+ catch {
110
+ const v2 = validateWorkspaceSyncV3TaskAttachmentLinksV2Payload(raw);
111
+ return serializeWorkspaceSyncV3TaskAttachmentLinksPayload(v2);
112
+ }
113
+ })();
95
114
  if (payload.taskId !== row.entityId)
96
115
  throw new Error(`Task attachment-link identity is invalid at ${row.sequence}.`);
97
116
  return { payload, canonicalFingerprint: fingerprintWorkspaceSyncPayload(payload) };
@@ -107,8 +126,10 @@ function canonicalPayload(row) {
107
126
  function canonicalEntityEventPayload(raw) {
108
127
  return validateWorkspaceSyncV3EntityEventPayload(serializeWorkspaceSyncV3EntityEventPayload(raw));
109
128
  }
110
- export function projectWorkspaceSyncV3NestedTaskJournalRow(row) {
111
- const projection = projectionByDomain.get(row.domain);
129
+ function projectNestedTaskJournalRow(row, attachmentProjection) {
130
+ const projection = row.domain === 'task-attachment-links'
131
+ ? attachmentProjection
132
+ : projectionByDomain.get(row.domain);
112
133
  if (!projection)
113
134
  return null;
114
135
  if (row.contractVersion !== WORKSPACE_SYNC_CONTRACT_VERSION) {
@@ -131,7 +152,7 @@ export function projectWorkspaceSyncV3NestedTaskJournalRow(row) {
131
152
  if (payload.action === 'sync-v3-repair-baseline')
132
153
  return null;
133
154
  }
134
- const { payload, canonicalFingerprint } = canonicalPayload(row);
155
+ const { payload, canonicalFingerprint } = canonicalPayload(row, attachmentProjection);
135
156
  return {
136
157
  sequence: journalSequence,
137
158
  projection,
@@ -146,8 +167,17 @@ export function projectWorkspaceSyncV3NestedTaskJournalRow(row) {
146
167
  occurredAt,
147
168
  };
148
169
  }
170
+ export function projectWorkspaceSyncV3NestedTaskJournalRow(row) {
171
+ return projectNestedTaskJournalRow(row, 'task-attachment-links/full-v1');
172
+ }
173
+ export function projectWorkspaceSyncV3NestedTaskV2JournalRow(row) {
174
+ return projectNestedTaskJournalRow(row, 'task-attachment-links/full-v2');
175
+ }
149
176
  export function validateWorkspaceSyncV3NestedTaskFeedEntry(entry) {
150
- const projected = projectWorkspaceSyncV3NestedTaskJournalRow({
177
+ const projector = entry.projection === 'task-attachment-links/full-v2'
178
+ ? projectWorkspaceSyncV3NestedTaskV2JournalRow
179
+ : projectWorkspaceSyncV3NestedTaskJournalRow;
180
+ const projected = projector({
151
181
  sequence: entry.sequence,
152
182
  contractVersion: WORKSPACE_SYNC_CONTRACT_VERSION,
153
183
  clientId: 'validation-client',
@@ -181,9 +211,9 @@ function assertNestedDependentOrder(rows) {
181
211
  throw new Error('Nested task operation dependents are duplicated or out of order.');
182
212
  }
183
213
  }
184
- function projectNestedDependentRows(rows) {
214
+ function projectNestedDependentRows(rows, attachmentProjection) {
185
215
  return rows.flatMap((row) => {
186
- const projected = projectWorkspaceSyncV3NestedTaskJournalRow(row);
216
+ const projected = projectNestedTaskJournalRow(row, attachmentProjection);
187
217
  if (!projected) {
188
218
  const payload = row.domain === 'entity-event'
189
219
  ? canonicalEntityEventPayload(parsePayload(row))
@@ -217,7 +247,7 @@ function isSupportedNestedOnlyOperation(rows) {
217
247
  * root. Apply still requires referenced parents to exist on the receiver; feed
218
248
  * only needs to project the nested rows.
219
249
  */
220
- function projectNestedOnlyOperation(rows) {
250
+ function projectNestedOnlyOperation(rows, attachmentProjection) {
221
251
  if (!isSupportedNestedOnlyOperation(rows)) {
222
252
  throw new Error('Nested-only task operation is not an allowed repair or reconciliation shape.');
223
253
  }
@@ -229,7 +259,7 @@ function projectNestedOnlyOperation(rows) {
229
259
  throw new Error('Nested task operation dependent identity does not match its task root.');
230
260
  }
231
261
  }
232
- const projected = projectNestedDependentRows(rows);
262
+ const projected = projectNestedDependentRows(rows, attachmentProjection);
233
263
  const events = projected
234
264
  .filter((entry) => entry.domain === 'entity-event')
235
265
  .map((entry) => validateWorkspaceSyncV3EntityEventPayload(entry.payload));
@@ -256,14 +286,14 @@ function projectNestedOnlyOperation(rows) {
256
286
  * parent-first operations; repair baselines and comment reconcile may emit
257
287
  * nested-only groups that still project when every row is a nested domain.
258
288
  */
259
- export function projectWorkspaceSyncV3NestedTaskOperation(input) {
289
+ function projectNestedTaskOperation(input, attachmentProjection) {
260
290
  if (!input.rows.some((row) => projectionByDomain.has(row.domain)))
261
291
  return null;
262
292
  assertWorkspaceSyncV3OperationBoundary(input);
263
293
  const [first, ...dependents] = input.rows;
264
294
  if (first && projectionByDomain.has(first.domain)
265
295
  && input.rows.every((row) => projectionByDomain.has(row.domain))) {
266
- return projectNestedOnlyOperation(input.rows);
296
+ return projectNestedOnlyOperation(input.rows, attachmentProjection);
267
297
  }
268
298
  if (!first || !['task', 'initiative', 'workstream'].includes(first.domain)) {
269
299
  throw new Error('Nested task operation must begin with its v3-owned parent root.');
@@ -272,7 +302,7 @@ export function projectWorkspaceSyncV3NestedTaskOperation(input) {
272
302
  throw new Error('Nested task operation contains an unsupported dependent row.');
273
303
  }
274
304
  assertNestedDependentOrder(dependents);
275
- const projectedDependents = projectNestedDependentRows(dependents);
305
+ const projectedDependents = projectNestedDependentRows(dependents, attachmentProjection);
276
306
  const eventRows = projectedDependents.filter((entry) => entry.domain === 'entity-event');
277
307
  const rawEventRows = dependents.filter((row) => row.domain === 'entity-event');
278
308
  const sentinelOnly = eventRows.length === 0 && rawEventRows.length > 0 && rawEventRows.every((row) => (canonicalEntityEventPayload(parsePayload(row)).action === 'sync-v3-repair-baseline'));
@@ -327,3 +357,9 @@ export function projectWorkspaceSyncV3NestedTaskOperation(input) {
327
357
  }
328
358
  return [parent, ...projectedDependents];
329
359
  }
360
+ export function projectWorkspaceSyncV3NestedTaskOperation(input) {
361
+ return projectNestedTaskOperation(input, 'task-attachment-links/full-v1');
362
+ }
363
+ export function projectWorkspaceSyncV3NestedTaskV2Operation(input) {
364
+ return projectNestedTaskOperation(input, 'task-attachment-links/full-v2');
365
+ }
@@ -2,7 +2,7 @@ import type { EntityEvent, TaskItem } from '../../types.js';
2
2
  import type { WorkspaceSyncV3ProjectionDescriptor } from './workspaceSyncV3FeedProfile.js';
3
3
  import { type WorkspaceSyncV3TaskAttachmentLink } from './workspaceSyncV3NestedTaskCodecs.js';
4
4
  export type WorkspaceSyncV3NestedTaskRepairCandidate = {
5
- projection: Extract<WorkspaceSyncV3ProjectionDescriptor, 'task-comments/full-v1' | 'task-checklist/full-v1' | 'task-attachment-links/full-v1' | 'entity-event/full-v1'>;
5
+ projection: Extract<WorkspaceSyncV3ProjectionDescriptor, 'task-comments/full-v1' | 'task-checklist/full-v1' | 'task-attachment-links/full-v1' | 'task-attachment-links/full-v2' | 'entity-event/full-v1'>;
6
6
  domain: 'task-comments' | 'task-checklist' | 'task-attachment-links' | 'entity-event';
7
7
  entityId: string;
8
8
  mutationKind: 'replace' | 'append';
@@ -16,4 +16,5 @@ export declare function buildWorkspaceSyncV3NestedTaskRepairCandidates(input: {
16
16
  tasks: readonly TaskItem[];
17
17
  events: readonly EntityEvent[];
18
18
  attachmentLinksByTaskId?: ReadonlyMap<string, readonly WorkspaceSyncV3TaskAttachmentLink[]>;
19
+ attachmentProjection?: 'task-attachment-links/full-v1' | 'task-attachment-links/full-v2';
19
20
  }): WorkspaceSyncV3NestedTaskRepairCandidate[];
@@ -1,8 +1,9 @@
1
- import { serializeWorkspaceSyncV3EntityEventPayload, serializeWorkspaceSyncV3TaskChecklistPayload, serializeWorkspaceSyncV3TaskCommentsPayload, validateWorkspaceSyncV3TaskAttachmentLinksPayload, } from './workspaceSyncV3NestedTaskCodecs.js';
1
+ import { serializeWorkspaceSyncV3EntityEventPayload, serializeWorkspaceSyncV3TaskChecklistPayload, serializeWorkspaceSyncV3TaskCommentsPayload, validateWorkspaceSyncV3TaskAttachmentLinksPayload, serializeWorkspaceSyncV3TaskAttachmentLinksV2Payload, } from './workspaceSyncV3NestedTaskCodecs.js';
2
2
  const rank = new Map([
3
3
  ['task-comments/full-v1', 0],
4
4
  ['task-checklist/full-v1', 1],
5
5
  ['task-attachment-links/full-v1', 2],
6
+ ['task-attachment-links/full-v2', 2],
6
7
  ['entity-event/full-v1', 3],
7
8
  ]);
8
9
  function compareCodeUnits(left, right) {
@@ -19,6 +20,7 @@ export function buildWorkspaceSyncV3NestedTaskRepairCandidates(input) {
19
20
  if (!links) {
20
21
  throw new Error(`Task attachment-link repair source is missing for ${task.id}.`);
21
22
  }
23
+ const attachmentProjection = input.attachmentProjection || 'task-attachment-links/full-v1';
22
24
  candidates.push({
23
25
  projection: 'task-comments/full-v1', domain: 'task-comments', entityId: task.id, mutationKind: 'replace',
24
26
  payload: serializeWorkspaceSyncV3TaskCommentsPayload({ taskId: task.id, comments: task.comments || [] }),
@@ -26,8 +28,14 @@ export function buildWorkspaceSyncV3NestedTaskRepairCandidates(input) {
26
28
  projection: 'task-checklist/full-v1', domain: 'task-checklist', entityId: task.id, mutationKind: 'replace',
27
29
  payload: serializeWorkspaceSyncV3TaskChecklistPayload({ taskId: task.id, items: task.checklistItems || [] }),
28
30
  }, {
29
- projection: 'task-attachment-links/full-v1', domain: 'task-attachment-links', entityId: task.id, mutationKind: 'replace',
30
- payload: validateWorkspaceSyncV3TaskAttachmentLinksPayload({ taskId: task.id, links: links || [] }),
31
+ projection: attachmentProjection, domain: 'task-attachment-links', entityId: task.id, mutationKind: 'replace',
32
+ payload: attachmentProjection === 'task-attachment-links/full-v2'
33
+ ? serializeWorkspaceSyncV3TaskAttachmentLinksV2Payload({
34
+ taskId: task.id,
35
+ links,
36
+ cardCoverAssetId: task.cardCoverAssetId ?? null,
37
+ })
38
+ : validateWorkspaceSyncV3TaskAttachmentLinksPayload({ taskId: task.id, links }),
31
39
  });
32
40
  }
33
41
  for (const event of input.events) {
@@ -30,6 +30,7 @@ type TaskOperationBase<TMutationKind extends string, TPayload> = {
30
30
  export type WorkspaceSyncV3TaskMetadataEnvelope = WorkspaceSyncV3OperationEnvelopeBase<TaskOperationBase<'metadata-update', {
31
31
  patch: Partial<TaskItem>;
32
32
  saveSource?: 'manual' | 'autosave';
33
+ expectedDetailRevision?: string;
33
34
  originActor?: WorkspaceSyncV3OriginActor;
34
35
  }>>;
35
36
  export type WorkspaceSyncV3TaskCreateEnvelope = WorkspaceSyncV3OperationEnvelopeBase<TaskOperationBase<'create', {
@@ -74,6 +75,7 @@ export type WorkspaceSyncV3TaskWorkflowEnvelope = WorkspaceSyncV3OperationEnvelo
74
75
  export type WorkspaceSyncV3TaskStatusEnvelope = WorkspaceSyncV3OperationEnvelopeBase<TaskOperationBase<'set-status', DurableTaskStatusMutationPayload & {
75
76
  workflowAssignment?: WorkflowAssignmentSyncRecord;
76
77
  originActor?: WorkspaceSyncV3OriginActor;
78
+ expectedDetailRevision?: string;
77
79
  }>>;
78
80
  export type WorkspaceSyncV3WorkflowRuntimeEnvelope = WorkspaceSyncV3OperationEnvelopeBase<{
79
81
  domain: 'workflow-execution';
@@ -131,11 +131,13 @@ export interface WorkspaceSyncV3OperatorDiagnosticsSnapshot {
131
131
  };
132
132
  attachmentApply: {
133
133
  openObligations: number;
134
+ terminalObligations: number;
134
135
  dueObligations: number;
135
136
  oldestOpenCreatedAt: string | null;
136
137
  nextAttemptAt: string | null;
137
138
  attempted: number;
138
139
  lastError: string | null;
140
+ terminalReason: string | null;
139
141
  };
140
142
  runner: {
141
143
  available: boolean;
@@ -216,12 +216,14 @@ export function buildWorkspaceSyncV3OperatorDiagnostics(input) {
216
216
  WHERE tenant_id = ? AND workspace_id = ? AND resolved_at IS NULL
217
217
  `).get(tenantId, workspaceId);
218
218
  attachmentApplyRow = input.db.prepare(`
219
- SELECT COUNT(*) AS count,
220
- SUM(CASE WHEN next_attempt_at <= ? THEN 1 ELSE 0 END) AS due,
221
- MIN(created_at) AS oldest_created_at,
222
- MIN(next_attempt_at) AS next_attempt_at,
223
- COALESCE(SUM(attempts), 0) AS attempts,
224
- MAX(last_error) AS last_error
219
+ SELECT SUM(CASE WHEN terminal_at IS NULL THEN 1 ELSE 0 END) AS count,
220
+ SUM(CASE WHEN terminal_at IS NOT NULL THEN 1 ELSE 0 END) AS terminal,
221
+ SUM(CASE WHEN terminal_at IS NULL AND next_attempt_at <= ? THEN 1 ELSE 0 END) AS due,
222
+ MIN(CASE WHEN terminal_at IS NULL THEN created_at END) AS oldest_created_at,
223
+ MIN(CASE WHEN terminal_at IS NULL THEN next_attempt_at END) AS next_attempt_at,
224
+ COALESCE(SUM(CASE WHEN terminal_at IS NULL THEN attempts ELSE 0 END), 0) AS attempts,
225
+ MAX(CASE WHEN terminal_at IS NULL THEN last_error END) AS last_error,
226
+ MAX(CASE WHEN terminal_at IS NOT NULL THEN terminal_reason END) AS terminal_reason
225
227
  FROM workspace_sync_v3_attachment_apply_obligations
226
228
  WHERE tenant_id = ? AND workspace_id = ? AND resolved_at IS NULL
227
229
  `).get(nowIso, tenantId, workspaceId);
@@ -255,7 +257,9 @@ export function buildWorkspaceSyncV3OperatorDiagnostics(input) {
255
257
  const oldestOpenRecoveryAt = String(recoveryRow?.oldest_created_at || '').trim() || null;
256
258
  const coordinatorGeneration = coordinatorState ? integer(coordinatorState.coordinator_generation) : null;
257
259
  const ownershipMode = coordinatorState ? String(coordinatorState.ownership_mode || '') || null : null;
258
- const localConverged = baseLocalConverged && integer(attachmentApplyRow?.count) === 0;
260
+ const localConverged = baseLocalConverged
261
+ && integer(attachmentApplyRow?.count) === 0
262
+ && integer(attachmentApplyRow?.terminal) === 0;
259
263
  const coordinatorFailures = [];
260
264
  if (input.runtimeMode === 'local') {
261
265
  if (!coordinatorState)
@@ -279,6 +283,9 @@ export function buildWorkspaceSyncV3OperatorDiagnostics(input) {
279
283
  if (integer(attachmentApplyRow?.count) > 0) {
280
284
  coordinatorFailures.push('open attachment apply obligations remain');
281
285
  }
286
+ if (integer(attachmentApplyRow?.terminal) > 0) {
287
+ coordinatorFailures.push('terminal unavailable attachment apply obligations require operator action');
288
+ }
282
289
  if (ownershipMode === 'server' && !runnerState) {
283
290
  coordinatorFailures.push('server ownership has no durable runner checkpoint');
284
291
  }
@@ -305,6 +312,9 @@ export function buildWorkspaceSyncV3OperatorDiagnostics(input) {
305
312
  failures.push('recent pull/apply window contains 409 responses');
306
313
  if (integer(attachmentApplyRow?.count) > 0)
307
314
  failures.push('attachment apply obligations remain open');
315
+ if (integer(attachmentApplyRow?.terminal) > 0) {
316
+ failures.push('terminal unavailable attachment apply obligations require operator action');
317
+ }
308
318
  if (cursorAtCycleHighWatermark === null)
309
319
  unknowns.push('no committed feed cycle coordinates are available');
310
320
  if (!events.length)
@@ -387,11 +397,13 @@ export function buildWorkspaceSyncV3OperatorDiagnostics(input) {
387
397
  },
388
398
  attachmentApply: {
389
399
  openObligations: integer(attachmentApplyRow?.count),
400
+ terminalObligations: integer(attachmentApplyRow?.terminal),
390
401
  dueObligations: integer(attachmentApplyRow?.due),
391
402
  oldestOpenCreatedAt: String(attachmentApplyRow?.oldest_created_at || '').trim() || null,
392
403
  nextAttemptAt: String(attachmentApplyRow?.next_attempt_at || '').trim() || null,
393
404
  attempted: integer(attachmentApplyRow?.attempts),
394
405
  lastError: String(attachmentApplyRow?.last_error || '').trim() || null,
406
+ terminalReason: String(attachmentApplyRow?.terminal_reason || '').trim() || null,
395
407
  },
396
408
  runner: {
397
409
  available: Boolean(runnerState),
@@ -36,12 +36,19 @@ export function buildWorkspaceSyncV3TaskCommentAuditPage(input) {
36
36
  const workspaceId = String(input.workspaceId || '').trim();
37
37
  const previousCursor = normalizeCursor(input.cursor);
38
38
  const limit = pageSize(input.limit);
39
+ // Task IDs are compared in JavaScript when a page is installed locally.
40
+ // Make the database boundary use the same bytewise ordering so Postgres
41
+ // locale collation cannot place mixed-case IDs in an order the local
42
+ // validator rejects (SQLite already defaults to binary ordering here).
43
+ const taskIdWithCollationSql = input.core.getDatabaseAdapter().provider === 'postgres'
44
+ ? 'id COLLATE "C"'
45
+ : 'id COLLATE BINARY';
39
46
  const rows = input.core.getDatabaseAdapter().prepare(`
40
47
  SELECT id
41
48
  FROM tasks
42
49
  WHERE tenant_id = ? AND workspace_id = ?
43
- AND (? = '' OR id > ?)
44
- ORDER BY id
50
+ AND (? = '' OR ${taskIdWithCollationSql} > ?)
51
+ ORDER BY ${taskIdWithCollationSql}
45
52
  LIMIT ?
46
53
  `).all(input.core.getTenantId(), workspaceId, previousCursor || '', previousCursor || '', limit + 1);
47
54
  const pageRows = rows.slice(0, limit);
@@ -0,0 +1,7 @@
1
+ import type { WorkspaceSyncV3FeedCoveragePolicy, WorkspaceSyncV3FeedEntry, WorkspaceSyncV3FeedOperationProjector } from './workspaceSyncV3Feed.js';
2
+ import type { WorkspaceSyncV3CoverageProfile } from './workspaceSyncV3FeedProfile.js';
3
+ /** Dark v7 candidate: v4 ownership plus explicit task card-cover selection. */
4
+ export declare const WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_PROFILE: WorkspaceSyncV3CoverageProfile;
5
+ export declare const projectWorkspaceSyncV3TaskCoverCoverageOperation: WorkspaceSyncV3FeedOperationProjector;
6
+ export declare const WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_POLICY: WorkspaceSyncV3FeedCoveragePolicy;
7
+ export declare function validateWorkspaceSyncV3TaskCoverCoverageFeedEntry(entry: WorkspaceSyncV3FeedEntry): void;
@@ -0,0 +1,37 @@
1
+ import { createHash } from 'crypto';
2
+ import { canonicalWorkspaceSyncJson } from './canonicalWorkspaceSyncJson.js';
3
+ import { projectWorkspaceSyncV3CombinedOperation, validateWorkspaceSyncV3CombinedFeedEntry, } from './workspaceSyncV3CombinedCoverage.js';
4
+ import { WORKSPACE_SYNC_V3_NESTED_COVERAGE_PROFILE } from './workspaceSyncV3NestedCoverage.js';
5
+ import { projectWorkspaceSyncV3NestedTaskV2Operation, validateWorkspaceSyncV3NestedTaskFeedEntry, WORKSPACE_SYNC_V3_TASK_ATTACHMENT_LINKS_V2_PROJECTION, } from './workspaceSyncV3NestedTaskProjection.js';
6
+ const projections = Object.freeze(WORKSPACE_SYNC_V3_NESTED_COVERAGE_PROFILE.projections.map((projection) => (projection.projectionId === 'task-attachment-links/full-v1'
7
+ ? WORKSPACE_SYNC_V3_TASK_ATTACHMENT_LINKS_V2_PROJECTION
8
+ : projection)));
9
+ const identity = Object.freeze({
10
+ coverageProfileId: 'task-planning-metadata',
11
+ coverageVersion: 7,
12
+ projections,
13
+ });
14
+ /** Dark v7 candidate: v4 ownership plus explicit task card-cover selection. */
15
+ export const WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_PROFILE = Object.freeze({
16
+ ...identity,
17
+ coverageDigest: createHash('sha256').update(canonicalWorkspaceSyncJson(identity)).digest('hex'),
18
+ });
19
+ const NESTED_DOMAINS = new Set(['task-comments', 'task-checklist', 'task-attachment-links', 'entity-event']);
20
+ export const projectWorkspaceSyncV3TaskCoverCoverageOperation = (input) => {
21
+ if (input.rows.some((row) => NESTED_DOMAINS.has(row.domain))) {
22
+ const nested = projectWorkspaceSyncV3NestedTaskV2Operation(input);
23
+ if (!nested)
24
+ throw new Error('Task-cover coverage operation is incomplete.');
25
+ return nested;
26
+ }
27
+ return projectWorkspaceSyncV3CombinedOperation(input);
28
+ };
29
+ export const WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_POLICY = Object.freeze({
30
+ profile: WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_PROFILE,
31
+ projectOperation: projectWorkspaceSyncV3TaskCoverCoverageOperation,
32
+ });
33
+ export function validateWorkspaceSyncV3TaskCoverCoverageFeedEntry(entry) {
34
+ if (NESTED_DOMAINS.has(entry.domain))
35
+ return validateWorkspaceSyncV3NestedTaskFeedEntry(entry);
36
+ return validateWorkspaceSyncV3CombinedFeedEntry(entry);
37
+ }
@@ -0,0 +1,6 @@
1
+ export declare const WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_SELECTOR: Readonly<{
2
+ coverageProfileId: "task-planning-metadata";
3
+ coverageVersion: 7;
4
+ coverageDigest: "84b0dee7ac0811234288c1b296357b2ddbcfe82d438ccf90b6bc6e8b5afea0b6";
5
+ }>;
6
+ export declare const WORKSPACE_SYNC_V3_TASK_COVER_EXCLUDED_V2_PROJECTIONS: readonly ("initiative/full-v1" | "taxonomy/full-v1" | "task/root-v1" | "task-relationship/full-v1" | "workstream/full-v1" | "task-comments/full-v1" | "task-checklist/full-v1" | "task-attachment-links/full-v2" | "entity-event/full-v1")[];
@@ -0,0 +1,9 @@
1
+ import { WORKSPACE_SYNC_V3_NESTED_EXCLUDED_V2_PROJECTIONS } from './workspaceSyncV3NestedWireContract.js';
2
+ export const WORKSPACE_SYNC_V3_TASK_COVER_COVERAGE_SELECTOR = Object.freeze({
3
+ coverageProfileId: 'task-planning-metadata',
4
+ coverageVersion: 7,
5
+ coverageDigest: '84b0dee7ac0811234288c1b296357b2ddbcfe82d438ccf90b6bc6e8b5afea0b6',
6
+ });
7
+ export const WORKSPACE_SYNC_V3_TASK_COVER_EXCLUDED_V2_PROJECTIONS = Object.freeze(WORKSPACE_SYNC_V3_NESTED_EXCLUDED_V2_PROJECTIONS.map((projection) => (projection === 'task-attachment-links/full-v1'
8
+ ? 'task-attachment-links/full-v2'
9
+ : projection)));
@@ -33,7 +33,8 @@ export async function buildWorkspacePullFeed(input) {
33
33
  const excludedProjections = new Set(input.excludedProjections || []);
34
34
  const commentsV3Owned = excludedProjections.has('task-comments/full-v1');
35
35
  const checklistV3Owned = excludedProjections.has('task-checklist/full-v1');
36
- const attachmentsV3Owned = excludedProjections.has('task-attachment-links/full-v1');
36
+ const attachmentsV3Owned = excludedProjections.has('task-attachment-links/full-v1')
37
+ || excludedProjections.has('task-attachment-links/full-v2');
37
38
  const suppressTaskCarriers = excludedProjections.has('task/root-v1')
38
39
  && commentsV3Owned && checklistV3Owned && attachmentsV3Owned;
39
40
  const syncCursor = cursor ? { watermark: cursor.u, id: cursor.id } : null;
@@ -22,6 +22,7 @@ export interface WorkspaceRepairManifestDocumentEntry {
22
22
  sha256: string;
23
23
  sizeBytes: number;
24
24
  existsLocally: boolean;
25
+ contentCheckReliable: boolean;
25
26
  source: 'metadata' | 'filesystem';
26
27
  publicationState: 'legacy' | 'versioned' | 'deleted' | 'invalid';
27
28
  publicationError: string | null;
@@ -36,6 +37,7 @@ export interface WorkspaceRepairManifestAssetEntry {
36
37
  sha256: string;
37
38
  sizeBytes: number;
38
39
  existsLocally: boolean;
40
+ contentCheckReliable: boolean;
39
41
  source: 'metadata' | 'filesystem';
40
42
  publicationState: 'legacy' | 'versioned' | 'deleted' | 'invalid';
41
43
  publicationError: string | null;
@@ -103,4 +105,5 @@ export declare function scanWorkspaceRepair(input: WorkspaceRepairInput): Worksp
103
105
  export declare function applyWorkspaceRepair(input: WorkspaceRepairInput & {
104
106
  upsertDocumentWatermark: (workspaceId: string, path: string, contentHash: string, updatedAt: string) => void;
105
107
  repairMissingAssetLinks?: boolean;
108
+ repairMissingContentFiles?: boolean;
106
109
  }): WorkspaceRepairApplyResult;
@@ -44,6 +44,14 @@ function collectFiles(dirPath, predicate) {
44
44
  visit(root);
45
45
  return output;
46
46
  }
47
+ function isStorageRootAccessible(basePath) {
48
+ try {
49
+ return fs.statSync(basePath).isDirectory();
50
+ }
51
+ catch {
52
+ return false;
53
+ }
54
+ }
47
55
  function readFileFingerprint(absolutePath) {
48
56
  try {
49
57
  const stat = fs.statSync(absolutePath);
@@ -145,6 +153,7 @@ function buildManifestEntriesFromMetadata(tenantId, workspaceId, store, basePath
145
153
  const assetDeletes = [];
146
154
  const protectedDocumentPaths = new Set();
147
155
  const protectedAssetPaths = new Set();
156
+ const storageRootAccessible = isStorageRootAccessible(basePath);
148
157
  const metadataEntries = store.listAllByWorkspace(workspaceId, { includeDeleted: true });
149
158
  for (const entry of metadataEntries) {
150
159
  const contentKind = entry.kind === 'document' ? 'document' : 'asset';
@@ -192,6 +201,7 @@ function buildManifestEntriesFromMetadata(tenantId, workspaceId, store, basePath
192
201
  ? resolveStorageKeyPathInsideRoot(basePath, published.physicalKey)
193
202
  : null;
194
203
  let existsLocally = false;
204
+ let contentCheckReliable = true;
195
205
  if (!supportsPublishedContent) {
196
206
  existsLocally = true;
197
207
  }
@@ -200,13 +210,23 @@ function buildManifestEntriesFromMetadata(tenantId, workspaceId, store, basePath
200
210
  existsLocally = true;
201
211
  }
202
212
  else if (physicalPath) {
203
- try {
204
- const stat = fs.statSync(physicalPath);
205
- existsLocally = stat.isFile()
206
- && (published.source !== 'versioned' || stat.size === published.sizeBytes);
207
- }
208
- catch {
213
+ if (!storageRootAccessible) {
214
+ // The storage root itself couldn't be verified (e.g. an unmounted volume or
215
+ // transient I/O failure) — every local file would look "missing" in that state,
216
+ // so don't let a whole-root outage masquerade as N individual deletions.
209
217
  existsLocally = false;
218
+ contentCheckReliable = false;
219
+ }
220
+ else {
221
+ try {
222
+ const stat = fs.statSync(physicalPath);
223
+ existsLocally = stat.isFile()
224
+ && (published.source !== 'versioned' || stat.size === published.sizeBytes);
225
+ }
226
+ catch (error) {
227
+ existsLocally = false;
228
+ contentCheckReliable = error?.code === 'ENOENT';
229
+ }
210
230
  }
211
231
  }
212
232
  }
@@ -231,6 +251,7 @@ function buildManifestEntriesFromMetadata(tenantId, workspaceId, store, basePath
231
251
  sha256: contentSha256,
232
252
  sizeBytes,
233
253
  existsLocally,
254
+ contentCheckReliable,
234
255
  source: 'metadata',
235
256
  publicationState,
236
257
  publicationError,
@@ -249,6 +270,7 @@ function buildManifestEntriesFromMetadata(tenantId, workspaceId, store, basePath
249
270
  sha256: contentSha256,
250
271
  sizeBytes,
251
272
  existsLocally,
273
+ contentCheckReliable,
252
274
  source: 'metadata',
253
275
  publicationState,
254
276
  publicationError,
@@ -307,6 +329,7 @@ export function buildWorkspaceRepairManifest(input) {
307
329
  sha256: fingerprint.sha256,
308
330
  sizeBytes: fingerprint.sizeBytes,
309
331
  existsLocally: true,
332
+ contentCheckReliable: true,
310
333
  source: 'filesystem',
311
334
  publicationState: 'legacy',
312
335
  publicationError: null,
@@ -337,6 +360,7 @@ export function buildWorkspaceRepairManifest(input) {
337
360
  sha256: fingerprint.sha256,
338
361
  sizeBytes: fingerprint.sizeBytes,
339
362
  existsLocally: true,
363
+ contentCheckReliable: true,
340
364
  source: 'filesystem',
341
365
  publicationState: 'legacy',
342
366
  publicationError: null,
@@ -466,6 +490,19 @@ export function scanWorkspaceRepair(input) {
466
490
  continue;
467
491
  }
468
492
  if (!document.existsLocally) {
493
+ if (!document.contentCheckReliable) {
494
+ issues.push({
495
+ code: 'invalid_published_document_content',
496
+ entityKind: 'document',
497
+ path: document.path,
498
+ assetId: document.assetId,
499
+ taskId: document.taskId,
500
+ repairable: false,
501
+ recommendedAction: 'inspect-published-content',
502
+ detail: 'Local document file could not be confirmed missing — the storage root was unreachable or the check was inconclusive. Skipped to avoid converting unavailable content into an authoritative deletion.'
503
+ });
504
+ continue;
505
+ }
469
506
  issues.push({
470
507
  code: 'missing_document_file',
471
508
  entityKind: 'document',
@@ -564,6 +601,19 @@ export function scanWorkspaceRepair(input) {
564
601
  if (!asset.existsLocally) {
565
602
  if (protectedAiAvatarAssetPaths.has(asset.path))
566
603
  continue;
604
+ if (!asset.contentCheckReliable) {
605
+ issues.push({
606
+ code: 'invalid_published_asset_content',
607
+ entityKind: 'asset',
608
+ path: asset.path,
609
+ assetId: asset.assetId,
610
+ taskId: asset.taskId,
611
+ repairable: false,
612
+ recommendedAction: 'inspect-published-content',
613
+ detail: 'Local asset file could not be confirmed missing — the storage root was unreachable or the check was inconclusive. Skipped to avoid converting unavailable content into an authoritative deletion.'
614
+ });
615
+ continue;
616
+ }
567
617
  issues.push({
568
618
  code: 'missing_asset_file',
569
619
  entityKind: 'asset',
@@ -791,6 +841,17 @@ export function applyWorkspaceRepair(input) {
791
841
  continue;
792
842
  }
793
843
  if ((issue.code === 'missing_document_file' || issue.code === 'missing_asset_file') && issue.assetId) {
844
+ if (input.repairMissingContentFiles !== true) {
845
+ actionsApplied.push({
846
+ code: issue.code,
847
+ path: issue.path,
848
+ assetId: issue.assetId,
849
+ action: issue.recommendedAction,
850
+ status: 'skipped',
851
+ detail: 'Missing content file repair requires explicit admin confirmation, since a single failed existence check can also mean the content is temporarily unavailable rather than deleted.'
852
+ });
853
+ continue;
854
+ }
794
855
  const updated = input.workspaceAssetStore.markDeleted(issue.assetId, workspaceId);
795
856
  actionsApplied.push({
796
857
  code: issue.code,