@taskforcehq/taskforce 0.3.169 → 0.3.300

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 (696) hide show
  1. package/README.md +52 -21
  2. package/dist/Taskforce.module.css +3394 -5981
  3. package/dist/TaskforceCore.CategoryFallback.test.js +5 -2
  4. package/dist/TaskforceCore.filter-persistence.test.js +402 -0
  5. package/dist/TaskforceCore.js +388 -71
  6. package/dist/TaskforceCore.routing.test.js +311 -0
  7. package/dist/TaskforceCore.test.js +3344 -615
  8. package/dist/TaskforceWidget.js +3 -2
  9. package/dist/__tests__/localizationCatalogs.test.js +48 -31
  10. package/dist/__tests__/workflowSimulation.test.js +2 -2
  11. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.d.ts +60 -0
  12. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.js +324 -0
  13. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.test.js +278 -0
  14. package/dist/annotatedAttachments/payload.d.ts +2 -0
  15. package/dist/annotatedAttachments/payload.js +54 -0
  16. package/dist/annotatedAttachments/payload.test.js +67 -0
  17. package/dist/annotatedAttachments/service.d.ts +49 -0
  18. package/dist/annotatedAttachments/service.js +121 -0
  19. package/dist/annotatedAttachments/types.d.ts +86 -0
  20. package/dist/cli.js +74 -25
  21. package/dist/compat/deploymentCompat.d.ts +12 -0
  22. package/dist/compat/deploymentCompat.js +43 -0
  23. package/dist/compat/workspaceSyncCompat.d.ts +6 -0
  24. package/dist/compat/workspaceSyncCompat.js +79 -0
  25. package/dist/compat/workspaceSyncCompat.test.js +87 -0
  26. package/dist/components/features/AgentsModule.d.ts +5 -0
  27. package/dist/components/features/AgentsModule.js +131 -0
  28. package/dist/components/features/AgentsModule.test.js +102 -0
  29. package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +24 -0
  30. package/dist/components/features/AnnotatedAttachmentWorkspace.js +1942 -0
  31. package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +2831 -0
  32. package/dist/components/features/DocumentGenerateModal.js +8 -16
  33. package/dist/components/features/DocumentIndex.d.ts +3 -4
  34. package/dist/components/features/DocumentIndex.js +22 -10
  35. package/dist/components/features/DocumentViewer.d.ts +6 -2
  36. package/dist/components/features/DocumentViewer.js +1000 -18
  37. package/dist/components/features/DocumentWorkspace.d.ts +40 -2
  38. package/dist/components/features/DocumentWorkspace.js +96 -16
  39. package/dist/components/features/DocumentWorkspace.test.js +1202 -0
  40. package/dist/components/features/InitiativesModule.d.ts +22 -0
  41. package/dist/components/features/InitiativesModule.js +33 -0
  42. package/dist/components/features/TaskSettings.d.ts +41 -32
  43. package/dist/components/features/TaskSettings.js +798 -313
  44. package/dist/components/features/TaskSettings.test.js +1221 -47
  45. package/dist/components/features/WorkflowsModule.d.ts +27 -0
  46. package/dist/components/features/WorkflowsModule.js +21 -0
  47. package/dist/components/features/documentWorkspaceFilters.d.ts +10 -0
  48. package/dist/components/features/documentWorkspaceFilters.js +11 -0
  49. package/dist/components/features/settings/CategoryManager.d.ts +2 -0
  50. package/dist/components/features/settings/CategoryManager.js +15 -8
  51. package/dist/components/features/settings/PriorityManager.d.ts +18 -0
  52. package/dist/components/features/settings/PriorityManager.js +51 -0
  53. package/dist/components/features/settings/TaxonomyEditor.d.ts +2 -1
  54. package/dist/components/features/settings/TaxonomyEditor.js +41 -18
  55. package/dist/components/features/settings/TaxonomyLibraryManager.d.ts +26 -0
  56. package/dist/components/features/settings/TaxonomyLibraryManager.js +301 -0
  57. package/dist/components/features/settings/TaxonomyLibraryManager.test.js +141 -0
  58. package/dist/components/features/settings/TaxonomyManager.d.ts +35 -12
  59. package/dist/components/features/settings/TaxonomyManager.js +59 -27
  60. package/dist/components/features/settings/TypeManager.d.ts +11 -0
  61. package/dist/components/features/settings/TypeManager.js +48 -11
  62. package/dist/components/features/settings/{WorkflowEditorPrototype.d.ts → WorkflowEditor.d.ts} +2 -2
  63. package/dist/components/features/settings/{WorkflowEditorPrototype.js → WorkflowEditor.js} +3 -3
  64. package/dist/components/task/TaskActionHeader.d.ts +12 -2
  65. package/dist/components/task/TaskActionHeader.js +64 -8
  66. package/dist/components/task/TaskActionHeader.test.js +73 -0
  67. package/dist/components/task/TaskCard.d.ts +14 -16
  68. package/dist/components/task/TaskCard.js +144 -174
  69. package/dist/components/task/TaskCard.test.js +416 -64
  70. package/dist/components/task/TaskContextUpload.d.ts +6 -5
  71. package/dist/components/task/TaskContextUpload.js +199 -64
  72. package/dist/components/task/TaskContextUpload.test.js +331 -14
  73. package/dist/components/task/TaskForm.d.ts +23 -29
  74. package/dist/components/task/TaskForm.js +348 -169
  75. package/dist/components/task/TaskForm.test.js +419 -23
  76. package/dist/components/task/TaskKanban.d.ts +20 -21
  77. package/dist/components/task/TaskKanban.js +176 -316
  78. package/dist/components/task/TaskKanban.planning-drop.test.js +93 -0
  79. package/dist/components/task/TaskList.d.ts +22 -11
  80. package/dist/components/task/TaskList.js +98 -12
  81. package/dist/components/task/TaskList.test.js +206 -7
  82. package/dist/components/task/TaskReferenceBadge.d.ts +12 -0
  83. package/dist/components/task/TaskReferenceBadge.js +5 -0
  84. package/dist/components/task/TaskStatusActions.d.ts +12 -0
  85. package/dist/components/task/TaskStatusActions.js +18 -0
  86. package/dist/components/task/TaskStatusActions.test.js +45 -0
  87. package/dist/components/ui/ImageReferenceBadge.d.ts +12 -0
  88. package/dist/components/ui/ImageReferenceBadge.js +5 -0
  89. package/dist/components/ui/LevelSelector.d.ts +2 -1
  90. package/dist/components/ui/LevelSelector.js +2 -2
  91. package/dist/components/ui/Markdown.d.ts +1 -2
  92. package/dist/components/ui/Markdown.js +2 -13
  93. package/dist/components/ui/Modal.d.ts +3 -2
  94. package/dist/components/ui/Modal.js +4 -3
  95. package/dist/components/ui/MultiSelectFilter.d.ts +3 -1
  96. package/dist/components/ui/MultiSelectFilter.js +2 -2
  97. package/dist/components/ui/ReferenceBadgeButton.d.ts +13 -0
  98. package/dist/components/ui/ReferenceBadgeButton.js +6 -0
  99. package/dist/components/ui/TaxonomyDropdown.d.ts +7 -1
  100. package/dist/components/ui/TaxonomyDropdown.js +58 -23
  101. package/dist/components/ui/TopNoticeLayer.d.ts +7 -0
  102. package/dist/components/ui/TopNoticeLayer.js +14 -0
  103. package/dist/components/ui/TopNoticeLayer.test.js +25 -0
  104. package/dist/components/views/AppShellHeader.d.ts +13 -0
  105. package/dist/components/views/AppShellHeader.js +19 -0
  106. package/dist/components/views/AppShellHeader.test.js +21 -0
  107. package/dist/components/views/PlanComparisonPage.d.ts +73 -0
  108. package/dist/components/views/PlanComparisonPage.js +196 -0
  109. package/dist/components/views/PlanComparisonPage.test.js +251 -0
  110. package/dist/components/views/PlansPage.d.ts +14 -0
  111. package/dist/components/views/PlansPage.js +355 -0
  112. package/dist/components/views/StandaloneLayout.d.ts +2 -2
  113. package/dist/components/views/StandaloneLayout.js +2212 -1194
  114. package/dist/components/views/WidgetView.d.ts +2 -2
  115. package/dist/components/views/WidgetView.js +149 -43
  116. package/dist/components/views/chrome/WorkspaceChromeSections.d.ts +57 -0
  117. package/dist/components/views/chrome/WorkspaceChromeSections.js +38 -0
  118. package/dist/components/views/chrome/WorkspaceChromeSections.test.js +54 -0
  119. package/dist/components/views/chrome/workspaceChrome.d.ts +31 -0
  120. package/dist/components/views/chrome/workspaceChrome.js +108 -0
  121. package/dist/components/views/chrome/workspaceChrome.test.js +52 -0
  122. package/dist/components/views/panels/DocumentFilterToolbar.d.ts +10 -0
  123. package/dist/components/views/panels/DocumentFilterToolbar.js +7 -0
  124. package/dist/components/views/panels/DocumentFilterToolbar.test.js +19 -0
  125. package/dist/components/views/panels/FilterToolbar.d.ts +66 -0
  126. package/dist/components/views/panels/FilterToolbar.js +21 -0
  127. package/dist/components/views/panels/FilterToolbar.test.d.ts +1 -0
  128. package/dist/components/views/panels/FilterToolbar.test.js +45 -0
  129. package/dist/components/views/panels/FilterToolbarControls.d.ts +46 -0
  130. package/dist/components/views/panels/FilterToolbarControls.js +61 -0
  131. package/dist/components/views/panels/PlanningDrawer.d.ts +115 -0
  132. package/dist/components/views/panels/PlanningDrawer.js +220 -0
  133. package/dist/components/views/panels/ScheduleSidebar.d.ts +52 -0
  134. package/dist/components/views/panels/ScheduleSidebar.js +127 -0
  135. package/dist/components/views/panels/scheduleUtils.d.ts +14 -0
  136. package/dist/components/views/panels/scheduleUtils.js +79 -0
  137. package/dist/components/views/teamManagementAccess.d.ts +6 -0
  138. package/dist/components/views/teamManagementAccess.js +18 -0
  139. package/dist/components/views/teamManagementAccess.test.d.ts +1 -0
  140. package/dist/components/views/teamManagementAccess.test.js +44 -0
  141. package/dist/config/clientDeployment.d.ts +2 -0
  142. package/dist/config/clientDeployment.js +13 -0
  143. package/dist/config/cloudEnvironment.d.ts +15 -0
  144. package/dist/config/cloudEnvironment.js +60 -0
  145. package/dist/config/deployment.d.ts +20 -0
  146. package/dist/config/deployment.js +67 -0
  147. package/dist/config/deployment.test.d.ts +1 -0
  148. package/dist/config/deployment.test.js +88 -0
  149. package/dist/config/envSchema.js +36 -8
  150. package/dist/config/envSchema.test.js +28 -0
  151. package/dist/core/AiProfileService.d.ts +66 -0
  152. package/dist/core/AiProfileService.js +320 -0
  153. package/dist/core/AiProfileService.test.d.ts +1 -0
  154. package/dist/core/AiProfileService.test.js +164 -0
  155. package/dist/core/AiProfiles.test.d.ts +1 -0
  156. package/dist/core/AiProfiles.test.js +449 -0
  157. package/dist/core/AttachmentLinkService.d.ts +15 -0
  158. package/dist/core/AttachmentLinkService.js +157 -0
  159. package/dist/core/AuthTokenService.d.ts +44 -0
  160. package/dist/core/AuthTokenService.js +207 -0
  161. package/dist/core/CreatedBy.test.js +134 -6
  162. package/dist/core/DeletedTaskLifecycleService.d.ts +33 -0
  163. package/dist/core/DeletedTaskLifecycleService.js +158 -0
  164. package/dist/core/DeletedTaskTrash.test.d.ts +1 -0
  165. package/dist/core/DeletedTaskTrash.test.js +104 -0
  166. package/dist/core/EntitlementsPolicy.test.js +78 -5
  167. package/dist/core/GlobalSettingsService.d.ts +16 -0
  168. package/dist/core/GlobalSettingsService.js +370 -0
  169. package/dist/core/JsonBackupRuntime.test.d.ts +1 -0
  170. package/dist/core/JsonBackupRuntime.test.js +155 -0
  171. package/dist/core/McpTokenService.d.ts +118 -0
  172. package/dist/core/McpTokenService.js +921 -0
  173. package/dist/core/PlanEntitlementService.d.ts +201 -0
  174. package/dist/core/PlanEntitlementService.js +1442 -0
  175. package/dist/core/PlanVersionPolicy.test.d.ts +1 -0
  176. package/dist/core/PlanVersionPolicy.test.js +178 -0
  177. package/dist/core/PlanningEntities.test.d.ts +1 -0
  178. package/dist/core/PlanningEntities.test.js +95 -0
  179. package/dist/core/ScheduleFields.test.js +15 -19
  180. package/dist/core/SignupMonetizationSettings.test.d.ts +1 -0
  181. package/dist/core/SignupMonetizationSettings.test.js +159 -0
  182. package/dist/core/Stats.test.d.ts +1 -0
  183. package/dist/core/Stats.test.js +36 -0
  184. package/dist/core/SyncReconciliationService.d.ts +93 -0
  185. package/dist/core/SyncReconciliationService.js +406 -0
  186. package/dist/core/SystemAdmin.test.js +578 -13
  187. package/dist/core/TaskActivityService.d.ts +21 -0
  188. package/dist/core/TaskActivityService.js +130 -0
  189. package/dist/core/TaskAttachmentsCanonical.test.d.ts +1 -0
  190. package/dist/core/TaskAttachmentsCanonical.test.js +306 -0
  191. package/dist/core/TaskAuditTimeline.test.d.ts +1 -0
  192. package/dist/core/TaskAuditTimeline.test.js +132 -0
  193. package/dist/core/TaskTaxonomyValidation.test.js +338 -15
  194. package/dist/core/Taskforce.d.ts +930 -183
  195. package/dist/core/Taskforce.ids.test.js +252 -0
  196. package/dist/core/Taskforce.js +3848 -2532
  197. package/dist/core/Taskforce.mcpAuth.test.d.ts +1 -0
  198. package/dist/core/Taskforce.mcpAuth.test.js +343 -0
  199. package/dist/core/Taskforce.realtimeMutation.test.js +57 -2
  200. package/dist/core/TaxonomySettingsService.d.ts +18 -0
  201. package/dist/core/TaxonomySettingsService.js +162 -0
  202. package/dist/core/WorkspaceDelete.test.js +32 -13
  203. package/dist/core/WorkspaceLifecycleService.d.ts +11 -0
  204. package/dist/core/WorkspaceLifecycleService.js +88 -0
  205. package/dist/core/WorkspacePermissions.test.js +95 -0
  206. package/dist/core/WorkspacePlanMode.test.js +114 -1
  207. package/dist/core/WorkspacePreferenceService.d.ts +41 -0
  208. package/dist/core/WorkspacePreferenceService.js +246 -0
  209. package/dist/core/WorkspaceSyncSettings.test.d.ts +1 -0
  210. package/dist/core/WorkspaceSyncSettings.test.js +65 -0
  211. package/dist/core/__tests__/TaskAttachmentsCanonical.test.d.ts +1 -0
  212. package/dist/core/__tests__/TaskAttachmentsCanonical.test.js +212 -0
  213. package/dist/core/shared.d.ts +36 -0
  214. package/dist/core/shared.js +128 -0
  215. package/dist/core/types.d.ts +614 -0
  216. package/dist/core/types.js +27 -0
  217. package/dist/documentReviews/DocumentReviewCommentStore.d.ts +56 -0
  218. package/dist/documentReviews/DocumentReviewCommentStore.js +230 -0
  219. package/dist/documentReviews/DocumentReviewSessionStore.d.ts +52 -0
  220. package/dist/documentReviews/DocumentReviewSessionStore.js +193 -0
  221. package/dist/documentReviews/DocumentReviewSessionStore.test.d.ts +1 -0
  222. package/dist/documentReviews/DocumentReviewSessionStore.test.js +127 -0
  223. package/dist/documentReviews/service.d.ts +68 -0
  224. package/dist/documentReviews/service.js +210 -0
  225. package/dist/documentReviews/types.d.ts +58 -0
  226. package/dist/documentReviews/types.js +1 -0
  227. package/dist/hooks/auth/useAuthGuardPolicy.d.ts +2 -0
  228. package/dist/hooks/auth/useAuthGuardPolicy.js +1 -0
  229. package/dist/hooks/tasks/useCurrentTaskRelations.d.ts +15 -0
  230. package/dist/hooks/tasks/useCurrentTaskRelations.js +37 -0
  231. package/dist/hooks/tasks/useTaskActions.d.ts +3 -0
  232. package/dist/hooks/tasks/useTaskActions.js +2 -0
  233. package/dist/hooks/tasks/useTaskEditorNavigation.d.ts +65 -0
  234. package/dist/hooks/tasks/useTaskEditorNavigation.js +235 -0
  235. package/dist/hooks/tasks/useTaskFilters.d.ts +2 -0
  236. package/dist/hooks/tasks/useTaskFilters.js +2 -0
  237. package/dist/hooks/tasks/useTaskFormActions.d.ts +58 -0
  238. package/dist/hooks/tasks/useTaskFormActions.js +365 -0
  239. package/dist/hooks/tasks/useTaskRelationships.d.ts +16 -0
  240. package/dist/hooks/tasks/useTaskRelationships.js +64 -0
  241. package/dist/hooks/ui/useResourceExport.d.ts +19 -0
  242. package/dist/hooks/ui/useResourceExport.js +84 -0
  243. package/dist/hooks/ui/useSettingsModel.d.ts +203 -0
  244. package/dist/hooks/ui/useSettingsModel.js +125 -0
  245. package/dist/hooks/ui/useSettingsPersistence.d.ts +36 -0
  246. package/dist/hooks/ui/useSettingsPersistence.js +254 -0
  247. package/dist/hooks/ui/useSettingsTaxonomyManagement.d.ts +119 -0
  248. package/dist/hooks/ui/useSettingsTaxonomyManagement.js +536 -0
  249. package/dist/hooks/ui/useUiNotice.d.ts +1 -0
  250. package/dist/hooks/ui/useUiNotice.js +1 -0
  251. package/dist/hooks/ui/useWorkflowTemplates.d.ts +37 -0
  252. package/dist/hooks/ui/useWorkflowTemplates.js +111 -0
  253. package/dist/hooks/ui/useZenMode.d.ts +5 -0
  254. package/dist/hooks/ui/useZenMode.js +32 -0
  255. package/dist/hooks/useFilterSort.d.ts +60 -0
  256. package/dist/hooks/useFilterSort.js +362 -0
  257. package/dist/hooks/useFilterSort.test.d.ts +1 -0
  258. package/dist/hooks/useFilterSort.test.js +333 -0
  259. package/dist/hooks/useRealtimeSync.d.ts +1 -0
  260. package/dist/hooks/useRealtimeSync.js +24 -9
  261. package/dist/hooks/useRealtimeSync.test.js +28 -1
  262. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.d.ts +1 -0
  263. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +34 -0
  264. package/dist/hooks/useSyncOrchestrator.d.ts +65 -103
  265. package/dist/hooks/useSyncOrchestrator.js +1538 -670
  266. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +700 -174
  267. package/dist/hooks/useTaskData.d.ts +35 -0
  268. package/dist/hooks/useTaskData.js +239 -0
  269. package/dist/hooks/useTaskData.test.d.ts +1 -0
  270. package/dist/hooks/useTaskData.test.js +119 -0
  271. package/dist/hooks/useTaskMutations.d.ts +46 -0
  272. package/dist/hooks/useTaskMutations.js +472 -0
  273. package/dist/hooks/useTaskMutations.test.d.ts +1 -0
  274. package/dist/hooks/useTaskMutations.test.js +80 -0
  275. package/dist/hooks/useTaskforce.contract.test.d.ts +1 -0
  276. package/dist/hooks/useTaskforce.contract.test.js +7 -0
  277. package/dist/hooks/useTaskforce.d.ts +260 -295
  278. package/dist/hooks/useTaskforce.js +1954 -2623
  279. package/dist/hooks/useTaskforce.priority-filters.test.js +6 -11
  280. package/dist/hooks/useTaskforce.sync-behavior.test.d.ts +1 -0
  281. package/dist/hooks/useTaskforce.sync-behavior.test.js +1120 -0
  282. package/dist/hooks/useUiNotice.d.ts +16 -0
  283. package/dist/hooks/useUiNotice.js +49 -0
  284. package/dist/hooks/useWorkspaceSyncController.d.ts +23 -46
  285. package/dist/hooks/useWorkspaceSyncController.js +280 -461
  286. package/dist/hooks/useWorkspaceSyncController.test.js +106 -199
  287. package/dist/hooks/workspace/useTaskforceApiRouting.d.ts +8 -0
  288. package/dist/hooks/workspace/useTaskforceApiRouting.js +91 -0
  289. package/dist/hooks/workspace/useTaskforceApiRouting.test.d.ts +1 -0
  290. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +60 -0
  291. package/dist/hooks/workspace/useWorkspaceSyncController.d.ts +1 -0
  292. package/dist/hooks/workspace/useWorkspaceSyncController.js +1 -0
  293. package/dist/hooks/workspace/workspaceLocalState.d.ts +30 -0
  294. package/dist/hooks/workspace/workspaceLocalState.js +101 -0
  295. package/dist/localization/locales/en-US.d.ts +1 -8
  296. package/dist/localization/locales/en-US.js +4 -11
  297. package/dist/localization/locales/es-419.js +2 -9
  298. package/dist/localization/locales/pt-BR.js +2 -9
  299. package/dist/localization/localizationCatalogs.test.d.ts +1 -0
  300. package/dist/localization/localizationCatalogs.test.js +37 -0
  301. package/dist/main.d.ts +1 -0
  302. package/dist/main.js +9 -15
  303. package/dist/mcp/clientIntegrations.d.ts +33 -0
  304. package/dist/mcp/clientIntegrations.js +199 -0
  305. package/dist/mcp/clientIntegrations.test.d.ts +1 -0
  306. package/dist/mcp/clientIntegrations.test.js +110 -0
  307. package/dist/mcp/index.d.ts +2 -0
  308. package/dist/mcp/index.js +2 -0
  309. package/dist/mcp/optionResolution.test.js +6 -2
  310. package/dist/mcp/runtime.d.ts +120 -0
  311. package/dist/mcp/runtime.js +5668 -0
  312. package/dist/mcp/runtime.test.d.ts +1 -0
  313. package/dist/mcp/runtime.test.js +3293 -0
  314. package/dist/mcp/server.js +2 -1793
  315. package/dist/mcp/structuredCommentValidation.test.d.ts +1 -0
  316. package/dist/{__tests__ → mcp}/structuredCommentValidation.test.js +1 -1
  317. package/dist/mcp/structuredDocValidation.js +1 -2
  318. package/dist/mcp/structuredDocValidation.test.d.ts +1 -0
  319. package/dist/{__tests__ → mcp}/structuredDocValidation.test.js +9 -4
  320. package/dist/mcp/toolRegistry.d.ts +570 -0
  321. package/dist/mcp/toolRegistry.js +90 -0
  322. package/dist/mcp/toolRegistry.test.d.ts +1 -0
  323. package/dist/mcp/toolRegistry.test.js +30 -0
  324. package/dist/migrations/billingSchemaParity.test.js +111 -0
  325. package/dist/migrations/taskSchemaMigrations.d.ts +5 -0
  326. package/dist/migrations/taskSchemaMigrations.js +681 -39
  327. package/dist/migrations/taskSchemaMigrations.test.d.ts +1 -0
  328. package/dist/migrations/taskSchemaMigrations.test.js +210 -0
  329. package/dist/plugins/vite.js +13 -7
  330. package/dist/public/apple-touch-icon.png +0 -0
  331. package/dist/public/archive/phoenix/apple-touch-icon.png +0 -0
  332. package/dist/public/archive/phoenix/favicon.ico +0 -0
  333. package/dist/public/archive/phoenix/site.webmanifest +1 -0
  334. package/dist/public/favicon-96x96.png +0 -0
  335. package/dist/public/favicon.ico +0 -0
  336. package/dist/public/favicon.svg +17 -0
  337. package/dist/public/site.webmanifest +21 -1
  338. package/dist/public/web-app-manifest-192x192.png +0 -0
  339. package/dist/public/web-app-manifest-512x512.png +0 -0
  340. package/dist/releaseVersion.test.d.ts +1 -0
  341. package/dist/{__tests__/releaseVersion.test.js → releaseVersion.test.js} +1 -1
  342. package/dist/resources/templates/WORKFLOW_V2_POLICY.md +1 -1
  343. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +62 -52
  344. package/dist/resources/templates/workflows/collaborate.yaml +11 -5
  345. package/dist/resources/templates/workflows/do.yaml +8 -2
  346. package/dist/resources/templates/workflows/evaluate.yaml +13 -7
  347. package/dist/resources/templates/workflows/execute.yaml +40 -23
  348. package/dist/resources/templates/workflows/plan.yaml +15 -8
  349. package/dist/resources/templates/workflows/review.yaml +14 -8
  350. package/dist/runtime/appGateDefinitions.d.ts +32 -0
  351. package/dist/runtime/appGateDefinitions.js +32 -0
  352. package/dist/runtime/appGates.d.ts +29 -0
  353. package/dist/runtime/appGates.js +53 -0
  354. package/dist/runtime/appGates.test.d.ts +1 -0
  355. package/dist/runtime/appGates.test.js +42 -0
  356. package/dist/runtime/migrationFlags.d.ts +1 -0
  357. package/dist/runtime/migrationFlags.js +1 -0
  358. package/dist/server/annotatedAttachmentsRoutes.test.d.ts +1 -0
  359. package/dist/server/annotatedAttachmentsRoutes.test.js +473 -0
  360. package/dist/server/auth.js +14 -2
  361. package/dist/server/auth.test.js +21 -1
  362. package/dist/server/cors.js +11 -5
  363. package/dist/server/cors.test.js +6 -0
  364. package/dist/server/documentReviewRoutes.test.d.ts +1 -0
  365. package/dist/server/documentReviewRoutes.test.js +157 -0
  366. package/dist/server/index.cookieProxy.test.js +46 -1
  367. package/dist/server/index.d.ts +20 -0
  368. package/dist/server/index.js +168 -41
  369. package/dist/server/index.rateLimit.test.js +6 -0
  370. package/dist/server/index.test.js +133 -2
  371. package/dist/server/mockStripe.d.ts +72 -0
  372. package/dist/server/mockStripe.js +357 -0
  373. package/dist/server/realtimeSyncWs.js +4 -0
  374. package/dist/server/routes/admin.d.ts +114 -0
  375. package/dist/server/routes/admin.js +2226 -0
  376. package/dist/server/routes/annotatedAttachments.d.ts +24 -0
  377. package/dist/server/routes/annotatedAttachments.js +235 -0
  378. package/dist/server/routes/auth.d.ts +61 -0
  379. package/dist/server/routes/auth.js +1296 -0
  380. package/dist/server/{routes.billing.d.ts → routes/billing.d.ts} +3 -2
  381. package/dist/server/routes/billing.js +2001 -0
  382. package/dist/server/routes/billing.test.d.ts +1 -0
  383. package/dist/server/routes/billing.test.js +1631 -0
  384. package/dist/server/routes/documentReviews.d.ts +11 -0
  385. package/dist/server/routes/documentReviews.js +191 -0
  386. package/dist/server/routes/documents.d.ts +118 -0
  387. package/dist/server/routes/documents.js +1113 -0
  388. package/dist/server/routes/resources.d.ts +25 -0
  389. package/dist/server/routes/resources.js +225 -0
  390. package/dist/server/routes/shared.d.ts +35 -0
  391. package/dist/server/routes/shared.js +305 -0
  392. package/dist/server/routes/shared.test.d.ts +1 -0
  393. package/dist/server/routes/shared.test.js +32 -0
  394. package/dist/server/{routes.sync.d.ts → routes/sync.d.ts} +5 -5
  395. package/dist/server/routes/sync.integration.test.d.ts +1 -0
  396. package/dist/server/routes/sync.integration.test.js +2003 -0
  397. package/dist/server/routes/sync.js +3143 -0
  398. package/dist/server/routes/tasks.d.ts +72 -0
  399. package/dist/server/routes/tasks.js +1252 -0
  400. package/dist/server/routes/workspaces.d.ts +32 -0
  401. package/dist/server/routes/workspaces.js +307 -0
  402. package/dist/server/routes.d.ts +8 -9
  403. package/dist/server/routes.js +968 -4092
  404. package/dist/server/routes.test.js +9043 -2415
  405. package/dist/server/runtimeMode.d.ts +1 -1
  406. package/dist/server/runtimeMode.js +3 -18
  407. package/dist/server/runtimeMode.test.js +3 -3
  408. package/dist/server/securityHeaders.d.ts +1 -1
  409. package/dist/server/securityHeaders.js +65 -6
  410. package/dist/server/securityHeaders.test.js +40 -2
  411. package/dist/services/templateEngine.test.d.ts +1 -0
  412. package/dist/{__tests__ → services}/templateEngine.test.js +1 -1
  413. package/dist/services/templates.test.d.ts +1 -0
  414. package/dist/{__tests__ → services}/templates.test.js +1 -1
  415. package/dist/services/workflowDocumentFixtures.test.d.ts +1 -0
  416. package/dist/{__tests__ → services}/workflowDocumentFixtures.test.js +4 -4
  417. package/dist/services/workflowExportMatrix.test.d.ts +1 -0
  418. package/dist/{__tests__ → services}/workflowExportMatrix.test.js +1 -1
  419. package/dist/services/workflowExportSnapshots.test.d.ts +1 -0
  420. package/dist/services/workflowGeneration.test.d.ts +1 -0
  421. package/dist/services/workflowSimulation.test.d.ts +1 -0
  422. package/dist/services/workflowSimulation.test.js +113 -0
  423. package/dist/services/workflowV2Policy.test.d.ts +1 -0
  424. package/dist/{__tests__ → services}/workflowV2Policy.test.js +2 -2
  425. package/dist/shared/aiProfileSurfaceType.d.ts +9 -0
  426. package/dist/shared/aiProfileSurfaceType.js +49 -0
  427. package/dist/shared/customTaxonomyLibrary.d.ts +11 -0
  428. package/dist/shared/customTaxonomyLibrary.js +276 -0
  429. package/dist/shared/customTaxonomyLibrary.test.d.ts +1 -0
  430. package/dist/shared/customTaxonomyLibrary.test.js +20 -0
  431. package/dist/shared/taxonomyCatalog.d.ts +19 -0
  432. package/dist/shared/taxonomyCatalog.js +36 -0
  433. package/dist/shared/taxonomyDefaults.d.ts +29 -0
  434. package/dist/shared/taxonomyDefaults.js +26 -0
  435. package/dist/shared/taxonomyLibrary.d.ts +15 -0
  436. package/dist/shared/taxonomyLibrary.js +98 -0
  437. package/dist/shared/taxonomyLibrary.test.d.ts +1 -0
  438. package/dist/shared/taxonomyLibrary.test.js +26 -0
  439. package/dist/shared/taxonomyState.d.ts +36 -0
  440. package/dist/shared/taxonomyState.js +73 -0
  441. package/dist/shared/taxonomyState.test.d.ts +1 -0
  442. package/dist/shared/taxonomyState.test.js +71 -0
  443. package/dist/shared/workflowDefaults.d.ts +9 -0
  444. package/dist/shared/workflowDefaults.js +11 -0
  445. package/dist/storage/documentIntegrity.d.ts +2 -0
  446. package/dist/storage/documentIntegrity.js +34 -17
  447. package/dist/storage/documentPurge.d.ts +2 -1
  448. package/dist/storage/documentPurge.js +35 -8
  449. package/dist/storage/index.d.ts +4 -0
  450. package/dist/storage/index.js +2 -0
  451. package/dist/storage/objectStorage.d.ts +1 -1
  452. package/dist/storage/objectStorageClient.d.ts +6 -4
  453. package/dist/storage/objectStorageClient.js +77 -29
  454. package/dist/storage/objectStorageClient.test.d.ts +1 -0
  455. package/dist/storage/objectStorageClient.test.js +57 -0
  456. package/dist/storage/objectStorageConfig.js +3 -1
  457. package/dist/storage/objectStorageConfig.test.d.ts +1 -0
  458. package/dist/storage/objectStorageConfig.test.js +38 -0
  459. package/dist/storage/postgresAdapter.d.ts +5 -0
  460. package/dist/storage/postgresAdapter.js +2 -2
  461. package/dist/storage/postgresAdapter.test.d.ts +1 -0
  462. package/dist/storage/postgresAdapter.test.js +29 -0
  463. package/dist/storage/taskAssetStore.d.ts +7 -4
  464. package/dist/storage/taskAssetStore.js +65 -60
  465. package/dist/storage/workspaceAssetStore.d.ts +122 -0
  466. package/dist/storage/workspaceAssetStore.js +496 -0
  467. package/dist/storage/workspaceAssetStore.test.d.ts +1 -0
  468. package/dist/storage/workspaceAssetStore.test.js +501 -0
  469. package/dist/sync/cloudSyncApi.d.ts +33 -2
  470. package/dist/sync/cloudSyncApi.js +37 -15
  471. package/dist/sync/cloudSyncApi.test.js +60 -3
  472. package/dist/sync/index.d.ts +2 -0
  473. package/dist/sync/index.js +3 -0
  474. package/dist/sync/syncService.d.ts +25 -0
  475. package/dist/sync/syncService.js +184 -16
  476. package/dist/sync/syncService.test.js +289 -25
  477. package/dist/sync/workspaceRepair.d.ts +114 -0
  478. package/dist/sync/workspaceRepair.js +566 -0
  479. package/dist/sync/workspaceSyncModel.d.ts +230 -2
  480. package/dist/sync/workspaceSyncModel.js +456 -17
  481. package/dist/sync/workspaceSyncModel.test.js +642 -0
  482. package/dist/sync/workspaceSyncState.d.ts +12 -0
  483. package/dist/sync/workspaceSyncState.js +12 -0
  484. package/dist/sync/workspaceSyncSurface.d.ts +33 -0
  485. package/dist/sync/workspaceSyncSurface.js +74 -0
  486. package/dist/types.d.ts +143 -18
  487. package/dist/types.js +9 -24
  488. package/dist/ui/assets/AgentsModule-TVmCgizS.js +1 -0
  489. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +1 -0
  490. package/dist/ui/assets/AnnotatedAttachmentWorkspace-CAyhnPeR.js +3 -0
  491. package/dist/ui/assets/DocumentWorkspace-BOCw5e4D.js +250 -0
  492. package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +1 -0
  493. package/dist/ui/assets/InitiativesModule-CN0r02Oi.js +1 -0
  494. package/dist/ui/assets/PlansPage-BlVC_lRq.css +1 -0
  495. package/dist/ui/assets/PlansPage-FcQSNLZd.js +1 -0
  496. package/dist/ui/assets/TaskSettings-BwB9NtmJ.css +1 -0
  497. package/dist/ui/assets/TaskSettings-Cw5zYD1L.js +8 -0
  498. package/dist/ui/assets/WorkflowsModule-CY9R2zPB.js +5 -0
  499. package/dist/ui/assets/index-Bzqy5QGm.js +6 -0
  500. package/dist/ui/assets/index-DxRiI3tY.css +1 -0
  501. package/dist/ui/assets/vendor-dnd-DRzYolkg.js +5 -0
  502. package/dist/ui/assets/{vendor-icons-ZFG3SGwo.js → vendor-icons-QZyhEwgT.js} +1 -1
  503. package/dist/ui/assets/{vendor-markdown-CKYkQHfj.js → vendor-markdown-BUxTU7dS.js} +1 -1
  504. package/dist/ui/assets/vendor-react-CKJs5o3c.js +61 -0
  505. package/dist/ui/assets/vendor-router-BbWMxlnO.js +3 -0
  506. package/dist/ui/branding/logos/logo.png +0 -0
  507. package/dist/ui/branding/logos/logo_dark.svg +27 -0
  508. package/dist/ui/branding/logos/logo_favicon.svg +35 -0
  509. package/dist/ui/branding/logos/logo_light.svg +27 -0
  510. package/dist/ui/favicon/apple-touch-icon.png +0 -0
  511. package/dist/ui/favicon/archive/phoenix/apple-touch-icon.png +0 -0
  512. package/dist/ui/favicon/archive/phoenix/favicon.ico +0 -0
  513. package/dist/ui/favicon/archive/phoenix/site.webmanifest +1 -0
  514. package/dist/ui/favicon/favicon-96x96.png +0 -0
  515. package/dist/ui/favicon/favicon.ico +0 -0
  516. package/dist/ui/favicon/favicon.svg +17 -0
  517. package/dist/ui/favicon/site.webmanifest +21 -1
  518. package/dist/ui/favicon/web-app-manifest-192x192.png +0 -0
  519. package/dist/ui/favicon/web-app-manifest-512x512.png +0 -0
  520. package/dist/ui/index.html +10 -7
  521. package/dist/ui/product/screen_01.png +0 -0
  522. package/dist/ui/product/screen_02.png +0 -0
  523. package/dist/ui/product/screen_03.png +0 -0
  524. package/dist/utils/annotatedAttachments.d.ts +20 -0
  525. package/dist/utils/annotatedAttachments.js +74 -0
  526. package/dist/utils/annotatedAttachments.test.d.ts +1 -0
  527. package/dist/utils/annotatedAttachments.test.js +108 -0
  528. package/dist/utils/assignees.d.ts +26 -0
  529. package/dist/utils/assignees.js +132 -0
  530. package/dist/utils/canonicalAssetKind.d.ts +16 -0
  531. package/dist/utils/canonicalAssetKind.js +39 -0
  532. package/dist/utils/canonicalAssetKind.test.d.ts +1 -0
  533. package/dist/utils/canonicalAssetKind.test.js +24 -0
  534. package/dist/utils/coalescedRefresh.d.ts +5 -0
  535. package/dist/utils/coalescedRefresh.js +16 -0
  536. package/dist/utils/coalescedRefresh.test.d.ts +1 -0
  537. package/dist/utils/coalescedRefresh.test.js +30 -0
  538. package/dist/utils/constants.d.ts +16 -3
  539. package/dist/utils/constants.icons.test.js +2 -1
  540. package/dist/utils/constants.js +70 -13
  541. package/dist/utils/contextAssetEvents.d.ts +7 -0
  542. package/dist/utils/contextAssetEvents.js +6 -0
  543. package/dist/utils/contextFiles.d.ts +14 -3
  544. package/dist/utils/contextFiles.js +40 -8
  545. package/dist/utils/documentManagerApi.d.ts +6 -0
  546. package/dist/utils/documentManagerApi.js +19 -0
  547. package/dist/utils/documentNames.d.ts +24 -0
  548. package/dist/utils/documentNames.js +91 -0
  549. package/dist/utils/documentReferences.d.ts +9 -0
  550. package/dist/utils/documentReferences.js +11 -0
  551. package/dist/utils/entityReferences.d.ts +9 -0
  552. package/dist/utils/entityReferences.js +32 -0
  553. package/dist/utils/imageReferences.d.ts +9 -0
  554. package/dist/utils/imageReferences.js +11 -0
  555. package/dist/utils/planParser.d.ts +2 -2
  556. package/dist/utils/planParser.js +11 -11
  557. package/dist/utils/planParser.test.d.ts +1 -0
  558. package/dist/{__tests__ → utils}/planParser.test.js +22 -22
  559. package/dist/utils/planningReferences.d.ts +17 -0
  560. package/dist/utils/planningReferences.js +30 -0
  561. package/dist/utils/planningReferences.test.d.ts +1 -0
  562. package/dist/utils/planningReferences.test.js +17 -0
  563. package/dist/utils/taskActivity.d.ts +9 -0
  564. package/dist/utils/taskActivity.js +147 -0
  565. package/dist/utils/taskActivity.test.d.ts +1 -0
  566. package/dist/utils/taskActivity.test.js +34 -0
  567. package/dist/utils/taskDraftDefaults.d.ts +21 -0
  568. package/dist/utils/taskDraftDefaults.js +40 -0
  569. package/dist/utils/taskDraftDefaults.test.d.ts +1 -0
  570. package/dist/utils/taskDraftDefaults.test.js +34 -0
  571. package/dist/utils/taskNormalization.d.ts +25 -0
  572. package/dist/utils/taskNormalization.js +161 -0
  573. package/dist/utils/taskNormalization.test.d.ts +1 -0
  574. package/dist/utils/taskNormalization.test.js +158 -0
  575. package/dist/utils/taskReferences.d.ts +5 -0
  576. package/dist/utils/taskReferences.js +11 -0
  577. package/dist/utils/taskforceApiClient.d.ts +2 -0
  578. package/dist/utils/taskforceApiClient.js +101 -3
  579. package/dist/utils/taskforceApiClient.test.d.ts +1 -0
  580. package/dist/utils/taskforceApiClient.test.js +44 -0
  581. package/dist/utils/taxonomyFilterOptions.d.ts +2 -0
  582. package/dist/utils/taxonomyFilterOptions.js +17 -0
  583. package/dist/utils/taxonomyFormDefaults.d.ts +3 -0
  584. package/dist/utils/taxonomyFormDefaults.js +28 -0
  585. package/dist/utils/taxonomyFormDefaults.test.d.ts +1 -0
  586. package/dist/utils/taxonomyFormDefaults.test.js +63 -0
  587. package/dist/utils/taxonomyLifecycle.d.ts +3 -0
  588. package/dist/utils/taxonomyLifecycle.js +17 -0
  589. package/dist/utils/taxonomySorting.d.ts +7 -0
  590. package/dist/utils/taxonomySorting.js +74 -0
  591. package/dist/utils/theme.d.ts +15 -0
  592. package/dist/utils/theme.js +23 -0
  593. package/dist/utils/uiNotice.d.ts +7 -0
  594. package/dist/utils/uiNotice.js +6 -0
  595. package/dist/utils/uiNotice.test.d.ts +1 -0
  596. package/dist/utils/uiNotice.test.js +18 -0
  597. package/dist/utils/workspaceIdentity.d.ts +14 -0
  598. package/dist/utils/workspaceIdentity.js +21 -0
  599. package/dist/utils/workspaceIdentity.test.js +30 -1
  600. package/dist/utils/workspaceSyncPresentation.d.ts +20 -0
  601. package/dist/utils/workspaceSyncPresentation.js +21 -0
  602. package/dist/utils/workspaceSyncPresentation.test.d.ts +1 -0
  603. package/dist/utils/workspaceSyncPresentation.test.js +36 -0
  604. package/package.json +28 -23
  605. package/scripts/bootstrap-playwright-auth.mjs +155 -0
  606. package/scripts/check-compat-boundaries.mjs +41 -0
  607. package/scripts/check-env.ts +9 -2
  608. package/scripts/debug-login.mjs +32 -0
  609. package/scripts/migrate-planning-model.ts +233 -0
  610. package/scripts/migrate-task-data-assets.mjs +408 -0
  611. package/scripts/playwright-auth.sh +11 -0
  612. package/scripts/run-dev-proxy.sh +2 -0
  613. package/scripts/update-homebrew-formula.sh +20 -3
  614. package/scripts/with-esbuild.sh +11 -0
  615. package/src/resources/templates/WORKFLOW_V2_POLICY.md +1 -1
  616. package/src/resources/templates/workflow-sources/workflowDocs.mjs +62 -52
  617. package/src/resources/templates/workflows/collaborate.yaml +11 -5
  618. package/src/resources/templates/workflows/do.yaml +8 -2
  619. package/src/resources/templates/workflows/evaluate.yaml +13 -7
  620. package/src/resources/templates/workflows/execute.yaml +40 -23
  621. package/src/resources/templates/workflows/plan.yaml +15 -8
  622. package/src/resources/templates/workflows/review.yaml +14 -8
  623. package/dist/TaskforceScreenshot.d.ts +0 -11
  624. package/dist/TaskforceScreenshot.js +0 -113
  625. package/dist/compat/priorityCompat.d.ts +0 -2
  626. package/dist/compat/priorityCompat.js +0 -24
  627. package/dist/components/features/settings/TaxonomyManager 2.d.ts +0 -52
  628. package/dist/components/features/settings/TaxonomyManager 2.js +0 -75
  629. package/dist/components/task/TaskKanban 2.d.ts +0 -76
  630. package/dist/components/task/TaskKanban 2.js +0 -1004
  631. package/dist/components/task/TaskScreenshotSection.d.ts +0 -15
  632. package/dist/components/task/TaskScreenshotSection.js +0 -22
  633. package/dist/components/views/StandaloneLayout 2.d.ts +0 -8
  634. package/dist/components/views/StandaloneLayout 2.js +0 -1660
  635. package/dist/core/InitiativeTemplates.test.js +0 -50
  636. package/dist/core/ParentLifecycle.test.js +0 -98
  637. package/dist/migrations/clientMigrations.d.ts +0 -9
  638. package/dist/migrations/clientMigrations.js +0 -74
  639. package/dist/migrations/flags.d.ts +0 -6
  640. package/dist/migrations/flags.js +0 -8
  641. package/dist/migrations/taskMigrations.d.ts +0 -71
  642. package/dist/migrations/taskMigrations.js +0 -236
  643. package/dist/migrations/taskMigrations.test.js +0 -34
  644. package/dist/server/routes.billing.js +0 -991
  645. package/dist/server/routes.billing.test.js +0 -387
  646. package/dist/server/routes.sync.integration.test.js +0 -470
  647. package/dist/server/routes.sync.js +0 -1141
  648. package/dist/soak/syncSoakHarness.d.ts +0 -6
  649. package/dist/soak/syncSoakHarness.js +0 -10
  650. package/dist/ui/assets/index-0W5vyCpn.css +0 -1
  651. package/dist/ui/assets/index-MCZuNNfn.js +0 -23
  652. package/dist/ui/assets/logo_white_01-0Vx-iIIv.png +0 -0
  653. package/dist/ui/assets/vendor-dnd-BI1K77C1.js +0 -5
  654. package/dist/ui/assets/vendor-react-DdIDsh7f.js +0 -9
  655. package/dist/ui/assets/vendor-router-BtiNU7Dl.js +0 -3
  656. package/dist/utils/taskHierarchy.d.ts +0 -4
  657. package/dist/utils/taskHierarchy.js +0 -69
  658. package/dist/utils/taskHierarchy.test.js +0 -38
  659. package/dist/utils/taskParenting.d.ts +0 -3
  660. package/dist/utils/taskParenting.js +0 -24
  661. package/dist/utils/taskParenting.test.js +0 -21
  662. package/dist/utils/taskRelations.d.ts +0 -6
  663. package/dist/utils/taskRelations.js +0 -24
  664. package/dist/utils/taskRelations.test.js +0 -34
  665. package/scripts/check-compat-boundaries.js +0 -48
  666. /package/dist/{__tests__/planParser.test.d.ts → TaskforceCore.filter-persistence.test.d.ts} +0 -0
  667. /package/dist/{__tests__/releaseVersion.test.d.ts → TaskforceCore.routing.test.d.ts} +0 -0
  668. /package/dist/{__tests__/structuredCommentValidation.test.d.ts → annotatedAttachments/AnnotatedAttachmentSessionStore.test.d.ts} +0 -0
  669. /package/dist/{__tests__/structuredDocValidation.test.d.ts → annotatedAttachments/payload.test.d.ts} +0 -0
  670. /package/dist/{__tests__/templateEngine.test.d.ts → annotatedAttachments/types.js} +0 -0
  671. /package/dist/{__tests__/templates.test.d.ts → compat/workspaceSyncCompat.test.d.ts} +0 -0
  672. /package/dist/{__tests__/workflowDocumentFixtures.test.d.ts → components/features/AgentsModule.test.d.ts} +0 -0
  673. /package/dist/{__tests__/workflowExportMatrix.test.d.ts → components/features/AnnotatedAttachmentWorkspace.test.d.ts} +0 -0
  674. /package/dist/{__tests__/workflowExportSnapshots.test.d.ts → components/features/DocumentWorkspace.test.d.ts} +0 -0
  675. /package/dist/{__tests__/workflowGeneration.test.d.ts → components/features/settings/TaxonomyLibraryManager.test.d.ts} +0 -0
  676. /package/dist/{__tests__/workflowV2Policy.test.d.ts → components/task/TaskActionHeader.test.d.ts} +0 -0
  677. /package/dist/{core/InitiativeTemplates.test.d.ts → components/task/TaskKanban.planning-drop.test.d.ts} +0 -0
  678. /package/dist/{core/ParentLifecycle.test.d.ts → components/task/TaskStatusActions.test.d.ts} +0 -0
  679. /package/dist/{migrations/taskMigrations.test.d.ts → components/ui/TopNoticeLayer.test.d.ts} +0 -0
  680. /package/dist/{server/routes.billing.test.d.ts → components/views/AppShellHeader.test.d.ts} +0 -0
  681. /package/dist/{server/routes.sync.integration.test.d.ts → components/views/PlanComparisonPage.test.d.ts} +0 -0
  682. /package/dist/{utils/taskHierarchy.test.d.ts → components/views/chrome/WorkspaceChromeSections.test.d.ts} +0 -0
  683. /package/dist/{utils/taskParenting.test.d.ts → components/views/chrome/workspaceChrome.test.d.ts} +0 -0
  684. /package/dist/{utils/taskRelations.test.d.ts → components/views/panels/DocumentFilterToolbar.test.d.ts} +0 -0
  685. /package/dist/public/{android-chrome-192x192.png → archive/phoenix/android-chrome-192x192.png} +0 -0
  686. /package/dist/public/{android-chrome-512x512.png → archive/phoenix/android-chrome-512x512.png} +0 -0
  687. /package/dist/public/{favicon-16x16.png → archive/phoenix/favicon-16x16.png} +0 -0
  688. /package/dist/public/{favicon-32x32.png → archive/phoenix/favicon-32x32.png} +0 -0
  689. /package/dist/public/{favicon.png → archive/phoenix/favicon.png} +0 -0
  690. /package/dist/{__tests__ → services}/workflowExportSnapshots.test.js +0 -0
  691. /package/dist/{__tests__ → services}/workflowGeneration.test.js +0 -0
  692. /package/dist/ui/favicon/{android-chrome-192x192.png → archive/phoenix/android-chrome-192x192.png} +0 -0
  693. /package/dist/ui/favicon/{android-chrome-512x512.png → archive/phoenix/android-chrome-512x512.png} +0 -0
  694. /package/dist/ui/favicon/{favicon-16x16.png → archive/phoenix/favicon-16x16.png} +0 -0
  695. /package/dist/ui/favicon/{favicon-32x32.png → archive/phoenix/favicon-32x32.png} +0 -0
  696. /package/dist/ui/favicon/{favicon.png → archive/phoenix/favicon.png} +0 -0
@@ -0,0 +1,1442 @@
1
+ import { TaskforceRuleError } from './types.js';
2
+ import { DOCUMENT_CLOUD_STORAGE_FEATURE_KEY, WORKSPACES_FEATURE_KEY, DOCUMENT_CLOUD_STORAGE_LIMIT_CONFIG_KEY, WORKSPACES_LIMIT_CONFIG_KEY, DEFAULT_PLAN_FEATURE_CATALOG, PLAN_FEATURE_DEPENDENCIES, generateUuidV7, } from './shared.js';
3
+ const LEGACY_REMOVED_PLAN_FEATURE_KEYS = Object.freeze([
4
+ 'documents.workspace_access'
5
+ ]);
6
+ /**
7
+ * Handles all plan catalog, plan version, and account entitlement logic.
8
+ * Extracted from Taskforce.ts to reduce monolith size.
9
+ */
10
+ export class PlanEntitlementService {
11
+ db;
12
+ tenantId;
13
+ deps;
14
+ constructor(db, tenantId, deps) {
15
+ this.db = db;
16
+ this.tenantId = tenantId;
17
+ this.deps = deps;
18
+ }
19
+ generateEntityId(prefix) {
20
+ return `${prefix}-${generateUuidV7()}`;
21
+ }
22
+ parseJsonObject(raw) {
23
+ if (typeof raw !== 'string' || !raw.trim())
24
+ return {};
25
+ try {
26
+ const parsed = JSON.parse(raw);
27
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
28
+ ? parsed
29
+ : {};
30
+ }
31
+ catch {
32
+ return {};
33
+ }
34
+ }
35
+ // ─── Normalizers ─────────────────────────────────────────────────────────
36
+ normalizeEntitlementState(value) {
37
+ const normalized = String(value || '').trim().toLowerCase();
38
+ if (normalized === 'active' || normalized === 'trialing' || normalized === 'grace' || normalized === 'suspended' || normalized === 'canceled') {
39
+ return normalized;
40
+ }
41
+ return 'canceled';
42
+ }
43
+ normalizeFeatureAccess(value) {
44
+ const normalized = String(value || '').trim().toLowerCase();
45
+ if (normalized === 'enabled' || normalized === 'limited' || normalized === 'disabled') {
46
+ return normalized;
47
+ }
48
+ return 'disabled';
49
+ }
50
+ readPositiveIntegerConfig(value, minimum) {
51
+ const numeric = Number(value);
52
+ if (!Number.isFinite(numeric))
53
+ return null;
54
+ return Math.max(minimum, Math.floor(numeric));
55
+ }
56
+ normalizePlanVersionTrialDays(value) {
57
+ if (value === null || value === undefined || value === '')
58
+ return 0;
59
+ const numeric = Number(value);
60
+ if (!Number.isFinite(numeric) || !Number.isInteger(numeric)) {
61
+ throw new TaskforceRuleError('trialDays must be an integer between 0 and 99.', 400, 'PLAN_TRIAL_DAYS_INVALID');
62
+ }
63
+ const normalized = Math.floor(numeric);
64
+ if (normalized < 0 || normalized > 99) {
65
+ throw new TaskforceRuleError('trialDays must be an integer between 0 and 99.', 400, 'PLAN_TRIAL_DAYS_INVALID');
66
+ }
67
+ return normalized;
68
+ }
69
+ readPlanVersionTrialDays(metadata) {
70
+ const rawValue = metadata.trialDays;
71
+ if (rawValue === null || rawValue === undefined || rawValue === '')
72
+ return 0;
73
+ const numeric = Number(rawValue);
74
+ if (!Number.isFinite(numeric) || !Number.isInteger(numeric))
75
+ return 0;
76
+ const normalized = Math.floor(numeric);
77
+ return normalized >= 0 && normalized <= 99 ? normalized : 0;
78
+ }
79
+ applyPlanVersionTrialDaysMetadata(metadata, trialDays) {
80
+ const nextMetadata = metadata && typeof metadata === 'object' ? { ...metadata } : {};
81
+ if (trialDays === undefined) {
82
+ return nextMetadata;
83
+ }
84
+ nextMetadata.trialDays = trialDays;
85
+ return nextMetadata;
86
+ }
87
+ sanitizePlanFeatureConfig(featureKey, access, config) {
88
+ if (featureKey === 'collaboration.seat_limit')
89
+ return {};
90
+ if (featureKey === DOCUMENT_CLOUD_STORAGE_FEATURE_KEY) {
91
+ const limit = this.readPositiveIntegerConfig(config[DOCUMENT_CLOUD_STORAGE_LIMIT_CONFIG_KEY], 1);
92
+ if (access === 'limited') {
93
+ return {
94
+ [DOCUMENT_CLOUD_STORAGE_LIMIT_CONFIG_KEY]: limit ?? 1024
95
+ };
96
+ }
97
+ if (limit !== null) {
98
+ return {
99
+ [DOCUMENT_CLOUD_STORAGE_LIMIT_CONFIG_KEY]: limit
100
+ };
101
+ }
102
+ return {};
103
+ }
104
+ if (featureKey === WORKSPACES_FEATURE_KEY) {
105
+ const limit = this.readPositiveIntegerConfig(config[WORKSPACES_LIMIT_CONFIG_KEY], 1);
106
+ return {
107
+ [WORKSPACES_LIMIT_CONFIG_KEY]: limit ?? 1
108
+ };
109
+ }
110
+ return config && typeof config === 'object' ? { ...config } : {};
111
+ }
112
+ isEntitlementStateAllowed(state) {
113
+ return state === 'active' || state === 'trialing' || state === 'grace';
114
+ }
115
+ // ─── Plan defaults repair ─────────────────────────────────────────────────
116
+ repairPlanDefaultVersionIfNeeded(planIdRaw) {
117
+ const planId = String(planIdRaw || '').trim().toLowerCase();
118
+ if (!planId)
119
+ return;
120
+ const rows = this.db.prepare(`
121
+ SELECT plan_version_id, version_number, is_default
122
+ FROM plan_versions
123
+ WHERE tenant_id = ? AND plan_id = ?
124
+ ORDER BY version_number DESC, created_at DESC
125
+ `).all(this.tenantId, planId);
126
+ if (rows.length === 0)
127
+ return;
128
+ const defaultRows = rows.filter((row) => Boolean(row.is_default) && row.plan_version_id);
129
+ if (defaultRows.length === 1)
130
+ return;
131
+ const fallback = rows.find((row) => row.plan_version_id) || null;
132
+ if (!fallback?.plan_version_id)
133
+ return;
134
+ const now = new Date().toISOString();
135
+ this.db.prepare(`
136
+ UPDATE plan_versions
137
+ SET is_default = CASE WHEN plan_version_id = ? THEN 1 ELSE 0 END,
138
+ updated_at = ?
139
+ WHERE tenant_id = ? AND plan_id = ?
140
+ `).run(String(fallback.plan_version_id), now, this.tenantId, planId);
141
+ }
142
+ repairPlanCatalogDefaultsIfNeeded() {
143
+ const planRows = this.db.prepare(`
144
+ SELECT DISTINCT plan_id
145
+ FROM plan_versions
146
+ WHERE tenant_id = ?
147
+ `).all(this.tenantId);
148
+ for (const row of planRows) {
149
+ const planId = String(row.plan_id || '').trim().toLowerCase();
150
+ if (!planId)
151
+ continue;
152
+ this.repairPlanDefaultVersionIfNeeded(planId);
153
+ }
154
+ }
155
+ repairSignupMonetizationSettingsIfNeeded() {
156
+ const current = this.deps.readGlobalConfig();
157
+ const currentAuth = current.auth && typeof current.auth === 'object'
158
+ ? current.auth
159
+ : {};
160
+ const strategy = currentAuth.signupMonetizationStrategy === 'plan_selection_required'
161
+ ? 'plan_selection_required'
162
+ : currentAuth.signupMonetizationStrategy === 'auto_assign_default'
163
+ ? 'auto_assign_default'
164
+ : null;
165
+ const hasStrategy = strategy === 'auto_assign_default' || strategy === 'plan_selection_required';
166
+ const hasDefaultVersionId = typeof currentAuth.defaultSignupPlanVersionId === 'string'
167
+ && currentAuth.defaultSignupPlanVersionId.trim().length > 0;
168
+ if (strategy === 'plan_selection_required') {
169
+ return;
170
+ }
171
+ if (strategy === 'auto_assign_default' && hasDefaultVersionId) {
172
+ return;
173
+ }
174
+ const fallbackVersion = this.tryGetDefaultPlanVersionByMode('personal', { allowFallback: true });
175
+ if (!fallbackVersion) {
176
+ return;
177
+ }
178
+ this.deps.writeGlobalConfig({
179
+ ...current,
180
+ auth: {
181
+ ...currentAuth,
182
+ signupMonetizationStrategy: hasStrategy
183
+ ? strategy
184
+ : 'auto_assign_default',
185
+ defaultSignupPlanVersionId: strategy === 'auto_assign_default' && hasDefaultVersionId
186
+ ? currentAuth.defaultSignupPlanVersionId
187
+ : fallbackVersion.planVersionId
188
+ }
189
+ });
190
+ }
191
+ normalizeLegacyPlanDataIfNeeded() {
192
+ this.migrateLegacySeatLimitFeatureConfigToVersionSeatLimit();
193
+ this.normalizeLegacyBillingMirrorRows();
194
+ this.removeLegacyPlanFeatureRows();
195
+ }
196
+ removeLegacyPlanFeatureRows() {
197
+ const featureKeys = [...LEGACY_REMOVED_PLAN_FEATURE_KEYS];
198
+ if (featureKeys.length < 1)
199
+ return;
200
+ const placeholders = featureKeys.map(() => '?').join(', ');
201
+ this.db.prepare(`
202
+ DELETE FROM plan_feature_matrix
203
+ WHERE tenant_id = ?
204
+ AND feature_key IN (${placeholders})
205
+ `).run(this.tenantId, ...featureKeys);
206
+ }
207
+ migrateLegacySeatLimitFeatureConfigToVersionSeatLimit() {
208
+ const rows = this.db.prepare(`
209
+ SELECT pfm.plan_version_id, pfm.config_json, pv.seat_limit
210
+ FROM plan_feature_matrix pfm
211
+ JOIN plan_versions pv
212
+ ON pv.tenant_id = pfm.tenant_id
213
+ AND pv.plan_version_id = pfm.plan_version_id
214
+ WHERE pfm.tenant_id = ?
215
+ AND pfm.feature_key = 'collaboration.seat_limit'
216
+ `).all(this.tenantId);
217
+ if (rows.length < 1)
218
+ return;
219
+ const now = new Date().toISOString();
220
+ const tx = this.db.transaction(() => {
221
+ for (const row of rows) {
222
+ const planVersionId = String(row.plan_version_id || '').trim();
223
+ if (!planVersionId)
224
+ continue;
225
+ const config = this.parseJsonObject(row.config_json);
226
+ const legacySeatLimit = this.readPositiveIntegerConfig(config.seatLimit ?? config.seat_limit, 1);
227
+ if (legacySeatLimit !== null && row.seat_limit === null) {
228
+ this.db.prepare(`
229
+ UPDATE plan_versions
230
+ SET seat_limit = ?, updated_at = ?
231
+ WHERE tenant_id = ? AND plan_version_id = ?
232
+ `).run(legacySeatLimit, now, this.tenantId, planVersionId);
233
+ }
234
+ if (Object.keys(config).length > 0) {
235
+ this.db.prepare(`
236
+ UPDATE plan_feature_matrix
237
+ SET config_json = ?, updated_at = ?
238
+ WHERE tenant_id = ? AND plan_version_id = ? AND feature_key = 'collaboration.seat_limit'
239
+ `).run('{}', now, this.tenantId, planVersionId);
240
+ }
241
+ }
242
+ });
243
+ tx();
244
+ }
245
+ normalizeLegacyBillingMirrorRows() {
246
+ const defaultPersonalVersion = this.tryGetDefaultPlanVersionByMode('personal', { allowFallback: true });
247
+ if (!defaultPersonalVersion)
248
+ return;
249
+ const rows = this.db.prepare(`
250
+ SELECT user_id, tier, entitlement_state
251
+ FROM user_billing
252
+ WHERE tenant_id = ?
253
+ `).all(this.tenantId);
254
+ if (rows.length < 1)
255
+ return;
256
+ const now = new Date().toISOString();
257
+ const tx = this.db.transaction(() => {
258
+ for (const row of rows) {
259
+ const userId = String(row.user_id || '').trim();
260
+ if (!userId)
261
+ continue;
262
+ const tier = String(row.tier || '').trim().toLowerCase();
263
+ const state = String(row.entitlement_state || '').trim().toLowerCase();
264
+ if (state === 'pending_plan_selection')
265
+ continue;
266
+ if (tier === 'free' || state === 'free') {
267
+ this.db.prepare(`
268
+ UPDATE user_billing
269
+ SET tier = ?, entitlement_state = 'active', updated_at = ?
270
+ WHERE tenant_id = ? AND user_id = ?
271
+ `).run(defaultPersonalVersion.planId, now, this.tenantId, userId);
272
+ }
273
+ }
274
+ });
275
+ tx();
276
+ }
277
+ backfillAccountEntitlements() {
278
+ const defaultPersonalVersion = this.tryGetDefaultPlanVersionByMode('personal', { allowFallback: true });
279
+ if (!defaultPersonalVersion) {
280
+ return;
281
+ }
282
+ const users = this.db.prepare(`
283
+ SELECT
284
+ u.id AS user_id,
285
+ ub.tier,
286
+ ub.entitlement_state,
287
+ ub.created_at AS billing_created_at,
288
+ ub.updated_at AS billing_updated_at
289
+ FROM users u
290
+ LEFT JOIN user_billing ub
291
+ ON ub.tenant_id = u.tenant_id
292
+ AND ub.user_id = u.id
293
+ LEFT JOIN account_entitlements ae
294
+ ON ae.tenant_id = u.tenant_id
295
+ AND ae.account_id = u.id
296
+ WHERE u.tenant_id = ?
297
+ AND ae.account_id IS NULL
298
+ `).all(this.tenantId);
299
+ for (const row of users) {
300
+ const accountId = String(row.user_id || '').trim();
301
+ if (!accountId)
302
+ continue;
303
+ const rawBillingState = String(row.entitlement_state || '').trim().toLowerCase();
304
+ if (rawBillingState === 'pending_plan_selection')
305
+ continue;
306
+ const mirroredPlanId = String(row.tier || '').trim().toLowerCase();
307
+ const planVersion = this.tryGetDefaultPlanVersionByPlanId(mirroredPlanId)
308
+ || defaultPersonalVersion;
309
+ this.upsertCanonicalEntitlementRecord({
310
+ accountId,
311
+ planVersionId: planVersion.planVersionId,
312
+ state: this.resolveCanonicalEntitlementStateFromBilling(row.entitlement_state),
313
+ effectiveAt: row.billing_created_at ? String(row.billing_created_at) : undefined,
314
+ updatedBy: 'backfill',
315
+ createdAt: row.billing_created_at ? String(row.billing_created_at) : undefined,
316
+ updatedAt: row.billing_updated_at ? String(row.billing_updated_at) : undefined
317
+ });
318
+ }
319
+ }
320
+ // ─── Internal helpers ─────────────────────────────────────────────────────
321
+ getRequiredDefaultPlanVersion(planIdRaw) {
322
+ const planId = String(planIdRaw || '').trim().toLowerCase();
323
+ if (!planId) {
324
+ throw new TaskforceRuleError('planId is required', 400, 'PLAN_ID_REQUIRED');
325
+ }
326
+ const planRow = this.db.prepare(`
327
+ SELECT plan_id, status
328
+ FROM plan_catalog
329
+ WHERE tenant_id = ? AND plan_id = ?
330
+ LIMIT 1
331
+ `).get(this.tenantId, planId);
332
+ if (!planRow?.plan_id) {
333
+ throw new TaskforceRuleError(`Required plan "${planId}" is missing`, 500, 'PLAN_NOT_CONFIGURED');
334
+ }
335
+ if (String(planRow.status || '').trim().toLowerCase() !== 'active') {
336
+ throw new TaskforceRuleError(`Required plan "${planId}" is not active`, 500, 'PLAN_NOT_ACTIVE');
337
+ }
338
+ this.repairPlanDefaultVersionIfNeeded(planId);
339
+ const versionRow = this.db.prepare(`
340
+ SELECT plan_version_id
341
+ FROM plan_versions
342
+ WHERE tenant_id = ? AND plan_id = ? AND is_default = 1
343
+ ORDER BY version_number DESC
344
+ LIMIT 1
345
+ `).get(this.tenantId, planId);
346
+ if (!versionRow?.plan_version_id) {
347
+ throw new TaskforceRuleError(`Required default version for "${planId}" is missing`, 500, 'DEFAULT_PLAN_VERSION_NOT_CONFIGURED');
348
+ }
349
+ return this.getPlanVersionSnapshot(String(versionRow.plan_version_id));
350
+ }
351
+ resolveCanonicalEntitlementStateFromBilling(raw) {
352
+ return this.normalizeEntitlementState(raw);
353
+ }
354
+ upsertCanonicalEntitlementRecord(input) {
355
+ const now = new Date().toISOString();
356
+ const createdAt = input.createdAt || now;
357
+ const updatedAt = input.updatedAt || now;
358
+ this.db.prepare(`
359
+ INSERT INTO account_entitlements (account_id, tenant_id, plan_version_id, entitlement_state, effective_at, effective_until, updated_by, created_at, updated_at)
360
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
361
+ ON CONFLICT (account_id) DO UPDATE SET
362
+ plan_version_id = excluded.plan_version_id,
363
+ entitlement_state = excluded.entitlement_state,
364
+ effective_at = excluded.effective_at,
365
+ effective_until = excluded.effective_until,
366
+ updated_by = excluded.updated_by,
367
+ updated_at = excluded.updated_at
368
+ `).run(input.accountId, this.tenantId, input.planVersionId, input.state, input.effectiveAt || now, input.effectiveUntil || null, input.updatedBy, createdAt, updatedAt);
369
+ }
370
+ isFeatureEnabledForPlanMode(feature) {
371
+ return Boolean(feature && feature.access !== 'disabled');
372
+ }
373
+ syncManagedTeamFeatures(features) {
374
+ const normalized = {};
375
+ for (const [featureKey, feature] of Object.entries(features || {})) {
376
+ normalized[featureKey] = {
377
+ featureKey,
378
+ access: this.normalizeFeatureAccess(feature.access),
379
+ config: feature.config && typeof feature.config === 'object' ? { ...feature.config } : {}
380
+ };
381
+ }
382
+ const teamManagement = normalized['collaboration.team_management'];
383
+ normalized['collaboration.invites'] = {
384
+ featureKey: 'collaboration.invites',
385
+ access: this.isFeatureEnabledForPlanMode(teamManagement) ? 'enabled' : 'disabled',
386
+ config: {}
387
+ };
388
+ return normalized;
389
+ }
390
+ normalizePlanFeatureDependencies(features) {
391
+ const normalized = this.syncManagedTeamFeatures(features);
392
+ let changed = true;
393
+ while (changed) {
394
+ changed = false;
395
+ for (const [featureKey, dependencies] of Object.entries(PLAN_FEATURE_DEPENDENCIES)) {
396
+ const feature = normalized[featureKey];
397
+ if (!feature || feature.access === 'disabled')
398
+ continue;
399
+ const unmetDependency = dependencies.some((dependencyKey) => {
400
+ const dependencyFeature = normalized[dependencyKey];
401
+ return !dependencyFeature || dependencyFeature.access === 'disabled';
402
+ });
403
+ if (!unmetDependency)
404
+ continue;
405
+ normalized[featureKey] = {
406
+ ...feature,
407
+ access: 'disabled'
408
+ };
409
+ changed = true;
410
+ }
411
+ }
412
+ return normalized;
413
+ }
414
+ isTeamPlanVersion(version) {
415
+ const teamFeature = version?.features?.['collaboration.team_management'];
416
+ return this.isFeatureEnabledForPlanMode(teamFeature);
417
+ }
418
+ assertSeatLimitCompatibleWithMode(input) {
419
+ const modeLabel = String(input.modeLabel || 'plan version').trim() || 'plan version';
420
+ const isTeam = this.isTeamPlanVersion({ features: input.features || {} });
421
+ if (!isTeam)
422
+ return;
423
+ if (input.seatLimit === null || !Number.isFinite(input.seatLimit) || input.seatLimit <= 0) {
424
+ throw new TaskforceRuleError(`${modeLabel} requires a positive seat limit when team management is enabled.`, 409, 'PLAN_SEAT_LIMIT_REQUIRED');
425
+ }
426
+ }
427
+ buildPlanModeFeatures(mode) {
428
+ const access = mode === 'team' ? 'enabled' : 'disabled';
429
+ return [
430
+ { featureKey: 'collaboration.invites', access, config: {} },
431
+ { featureKey: 'collaboration.team_management', access, config: {} }
432
+ ];
433
+ }
434
+ resolveWorkspaceLimitForUser(userId) {
435
+ const normalizedUserId = String(userId || '').trim();
436
+ if (!normalizedUserId) {
437
+ return { limit: 1, feature: null };
438
+ }
439
+ const entitlement = this.getAccountEntitlement(normalizedUserId);
440
+ const feature = entitlement.version.features[WORKSPACES_FEATURE_KEY] || null;
441
+ if (!feature || feature.access === 'disabled') {
442
+ return { limit: 1, feature };
443
+ }
444
+ const configuredLimit = this.readPositiveIntegerConfig(feature.config?.[WORKSPACES_LIMIT_CONFIG_KEY], 1);
445
+ if (configuredLimit !== null) {
446
+ return { limit: configuredLimit, feature };
447
+ }
448
+ return { limit: null, feature };
449
+ }
450
+ planHasAnyActivePricing(planIdRaw) {
451
+ const planId = String(planIdRaw || '').trim().toLowerCase();
452
+ if (!planId)
453
+ return false;
454
+ const row = this.db.prepare(`
455
+ SELECT COUNT(*) AS count
456
+ FROM billing_price_mappings bpm
457
+ JOIN plan_versions pv
458
+ ON pv.tenant_id = bpm.tenant_id
459
+ AND pv.plan_version_id = bpm.plan_version_id
460
+ WHERE bpm.tenant_id = ?
461
+ AND pv.plan_id = ?
462
+ AND bpm.active = 1
463
+ `).get(this.tenantId, planId);
464
+ return Number(row?.count || 0) > 0;
465
+ }
466
+ tryGetDefaultPlanVersionByPlanId(planIdRaw) {
467
+ const planId = String(planIdRaw || '').trim().toLowerCase();
468
+ if (!planId)
469
+ return null;
470
+ try {
471
+ return this.getRequiredDefaultPlanVersion(planId);
472
+ }
473
+ catch (error) {
474
+ if (error instanceof TaskforceRuleError) {
475
+ if (error.code === 'PLAN_NOT_CONFIGURED'
476
+ || error.code === 'PLAN_NOT_ACTIVE'
477
+ || error.code === 'DEFAULT_PLAN_VERSION_NOT_CONFIGURED') {
478
+ return null;
479
+ }
480
+ }
481
+ throw error;
482
+ }
483
+ }
484
+ getActiveDefaultPlanVersions() {
485
+ const rows = this.db.prepare(`
486
+ SELECT v.plan_version_id
487
+ FROM plan_versions v
488
+ JOIN plan_catalog p
489
+ ON p.tenant_id = v.tenant_id
490
+ AND p.plan_id = v.plan_id
491
+ WHERE v.tenant_id = ?
492
+ AND v.is_default = 1
493
+ AND COALESCE(p.status, 'active') = 'active'
494
+ ORDER BY p.updated_at DESC, p.created_at DESC, v.version_number DESC, v.created_at DESC
495
+ `).all(this.tenantId);
496
+ return rows
497
+ .map((row) => String(row.plan_version_id || '').trim())
498
+ .filter(Boolean)
499
+ .map((planVersionId) => this.getPlanVersionSnapshot(planVersionId));
500
+ }
501
+ getDefaultPlanVersionByMode(mode, options = {}) {
502
+ const versions = this.getActiveDefaultPlanVersions();
503
+ const matching = versions.find((version) => this.isTeamPlanVersion(version) === (mode === 'team'));
504
+ if (matching) {
505
+ return matching;
506
+ }
507
+ if (options.allowFallback && versions.length > 0) {
508
+ return versions[0];
509
+ }
510
+ throw new TaskforceRuleError(`No active default ${mode} plan version is configured`, 500, 'DEFAULT_PLAN_VERSION_NOT_CONFIGURED');
511
+ }
512
+ tryGetDefaultPlanVersionByMode(mode, options = {}) {
513
+ try {
514
+ return this.getDefaultPlanVersionByMode(mode, options);
515
+ }
516
+ catch (error) {
517
+ if (error instanceof TaskforceRuleError) {
518
+ if (error.code === 'DEFAULT_PLAN_VERSION_NOT_CONFIGURED') {
519
+ return null;
520
+ }
521
+ }
522
+ throw error;
523
+ }
524
+ }
525
+ resolveRegistrationPlanAssignment(selectedPlanVersionIdRaw) {
526
+ const selectedPlanVersionId = String(selectedPlanVersionIdRaw || '').trim();
527
+ if (selectedPlanVersionId) {
528
+ const row = this.db.prepare(`
529
+ SELECT v.plan_version_id, v.plan_id, COALESCE(p.status, 'active') AS plan_status
530
+ FROM plan_versions v
531
+ JOIN plan_catalog p ON p.tenant_id = v.tenant_id AND p.plan_id = v.plan_id
532
+ WHERE v.tenant_id = ? AND v.plan_version_id = ?
533
+ LIMIT 1
534
+ `).get(this.tenantId, selectedPlanVersionId);
535
+ if (!row?.plan_version_id || !row.plan_id) {
536
+ throw new TaskforceRuleError(`Selected signup plan version "${selectedPlanVersionId}" was not found.`, 400, 'SIGNUP_PLAN_VERSION_NOT_FOUND', { planVersionId: selectedPlanVersionId });
537
+ }
538
+ if (String(row.plan_status || '').trim().toLowerCase() !== 'active') {
539
+ throw new TaskforceRuleError(`Selected signup plan version "${selectedPlanVersionId}" belongs to a disabled plan.`, 400, 'SIGNUP_PLAN_NOT_ENABLED', {
540
+ planVersionId: selectedPlanVersionId,
541
+ planId: String(row.plan_id)
542
+ });
543
+ }
544
+ return {
545
+ requiresPlanSelection: false,
546
+ planVersionId: String(row.plan_version_id),
547
+ planId: String(row.plan_id)
548
+ };
549
+ }
550
+ const signupSettings = this.getSignupMonetizationSettings();
551
+ if (signupSettings.strategy === 'plan_selection_required') {
552
+ return {
553
+ requiresPlanSelection: true,
554
+ planVersionId: null,
555
+ planId: null
556
+ };
557
+ }
558
+ const validation = this.validateSignupMonetizationSettings(signupSettings);
559
+ if (!validation.valid) {
560
+ throw new TaskforceRuleError(validation.message || 'Signup monetization is misconfigured.', 500, validation.code || 'SIGNUP_MONETIZATION_MISCONFIGURED', validation.details);
561
+ }
562
+ const configuredVersion = signupSettings.strategy === 'auto_assign_default' && signupSettings.defaultSignupPlanVersionId
563
+ ? this.getPlanVersionSnapshot(signupSettings.defaultSignupPlanVersionId)
564
+ : null;
565
+ if (!configuredVersion) {
566
+ throw new TaskforceRuleError('Default signup plan version is required when signup strategy is auto-assign default.', 500, 'SIGNUP_PLAN_VERSION_REQUIRED');
567
+ }
568
+ return {
569
+ requiresPlanSelection: false,
570
+ planVersionId: configuredVersion.planVersionId,
571
+ planId: configuredVersion.planId
572
+ };
573
+ }
574
+ // ─── Public billing methods ───────────────────────────────────────────────
575
+ createUserBillingRecord(userId, options) {
576
+ const now = new Date().toISOString();
577
+ const entitlementState = String(options?.entitlementState || 'active').trim().toLowerCase() || 'active';
578
+ const planId = typeof options?.planId === 'string'
579
+ ? options.planId.trim().toLowerCase()
580
+ : '';
581
+ this.db.prepare(`
582
+ INSERT INTO user_billing (user_id, tenant_id, entitlement_state, tier, created_at, updated_at)
583
+ VALUES (?, ?, ?, ?, ?, ?)
584
+ ON CONFLICT (user_id) DO UPDATE SET
585
+ entitlement_state = excluded.entitlement_state,
586
+ tier = excluded.tier,
587
+ updated_at = excluded.updated_at
588
+ `).run(userId, this.tenantId, entitlementState, planId, now, now);
589
+ }
590
+ getUserEntitlement(userId) {
591
+ const account = this.getAccountEntitlement(userId);
592
+ return {
593
+ entitlementState: account.state,
594
+ tier: account.planId
595
+ };
596
+ }
597
+ hasAccountEntitlement(accountId) {
598
+ const normalizedAccountId = String(accountId || '').trim();
599
+ if (!normalizedAccountId)
600
+ return false;
601
+ return this.tryGetAccountEntitlement(normalizedAccountId) !== null;
602
+ }
603
+ tryGetAccountEntitlement(accountId) {
604
+ try {
605
+ return this.getAccountEntitlement(accountId);
606
+ }
607
+ catch (error) {
608
+ if (error instanceof TaskforceRuleError && error.code === 'ENTITLEMENT_NOT_FOUND') {
609
+ return null;
610
+ }
611
+ throw error;
612
+ }
613
+ }
614
+ getAccountAccessStatus(accountId) {
615
+ const normalizedAccountId = String(accountId || '').trim();
616
+ if (!normalizedAccountId) {
617
+ throw new TaskforceRuleError('accountId is required', 400, 'ACCOUNT_ID_REQUIRED');
618
+ }
619
+ const signupSettings = this.getSignupMonetizationSettings();
620
+ const validation = this.validateSignupMonetizationSettings(signupSettings);
621
+ const entitlement = this.tryGetAccountEntitlement(normalizedAccountId);
622
+ if (entitlement && this.isEntitlementStateAllowed(entitlement.state)) {
623
+ return {
624
+ gate: 'ok',
625
+ hasEntitlement: true,
626
+ canAccessApp: true,
627
+ canAccessPlans: true,
628
+ signupMonetizationStrategy: signupSettings.strategy,
629
+ planSelectionRequired: false,
630
+ message: null,
631
+ entitlement
632
+ };
633
+ }
634
+ if (entitlement) {
635
+ return {
636
+ gate: 'missing_entitlement',
637
+ hasEntitlement: true,
638
+ canAccessApp: false,
639
+ canAccessPlans: true,
640
+ signupMonetizationStrategy: signupSettings.strategy,
641
+ planSelectionRequired: false,
642
+ message: entitlement.state === 'suspended'
643
+ ? 'Your subscription is suspended. Update billing to continue.'
644
+ : 'Your plan is inactive. Choose a plan to continue.',
645
+ entitlement
646
+ };
647
+ }
648
+ if (signupSettings.strategy === 'plan_selection_required') {
649
+ const billingRow = this.db.prepare(`
650
+ SELECT entitlement_state
651
+ FROM user_billing
652
+ WHERE tenant_id = ? AND user_id = ?
653
+ LIMIT 1
654
+ `).get(this.tenantId, normalizedAccountId);
655
+ const billingState = String(billingRow?.entitlement_state || '').trim().toLowerCase();
656
+ if (billingState === 'pending_plan_selection') {
657
+ return {
658
+ gate: 'plan_selection_required',
659
+ hasEntitlement: false,
660
+ canAccessApp: false,
661
+ canAccessPlans: true,
662
+ signupMonetizationStrategy: signupSettings.strategy,
663
+ planSelectionRequired: true,
664
+ message: 'Choose a plan to continue.',
665
+ entitlement: null
666
+ };
667
+ }
668
+ return {
669
+ gate: 'plan_selection_required',
670
+ hasEntitlement: false,
671
+ canAccessApp: false,
672
+ canAccessPlans: true,
673
+ signupMonetizationStrategy: signupSettings.strategy,
674
+ planSelectionRequired: true,
675
+ message: 'Choose a plan to continue.',
676
+ entitlement: null
677
+ };
678
+ }
679
+ if (!validation.valid) {
680
+ return {
681
+ gate: 'misconfigured',
682
+ hasEntitlement: false,
683
+ canAccessApp: false,
684
+ canAccessPlans: true,
685
+ signupMonetizationStrategy: signupSettings.strategy,
686
+ planSelectionRequired: false,
687
+ message: validation.message || 'Signup monetization is misconfigured.',
688
+ entitlement: null
689
+ };
690
+ }
691
+ return {
692
+ gate: 'missing_entitlement',
693
+ hasEntitlement: false,
694
+ canAccessApp: false,
695
+ canAccessPlans: true,
696
+ signupMonetizationStrategy: signupSettings.strategy,
697
+ planSelectionRequired: false,
698
+ message: 'No entitlement is linked to this account.',
699
+ entitlement: null
700
+ };
701
+ }
702
+ getEffectivePlanVersion(accountId) {
703
+ const normalizedAccountId = String(accountId || '').trim();
704
+ if (!normalizedAccountId) {
705
+ throw new TaskforceRuleError('accountId is required', 400, 'ACCOUNT_ID_REQUIRED');
706
+ }
707
+ const row = this.db.prepare(`
708
+ SELECT v.plan_version_id, v.plan_id, v.version_number, v.is_default, v.seat_limit, v.metadata_json
709
+ FROM account_entitlements e
710
+ JOIN plan_versions v ON v.plan_version_id = e.plan_version_id
711
+ WHERE e.tenant_id = ? AND e.account_id = ?
712
+ LIMIT 1
713
+ `).get(this.tenantId, normalizedAccountId);
714
+ if (!row?.plan_version_id || !row.plan_id) {
715
+ throw new TaskforceRuleError('Plan version not found for account', 404, 'PLAN_VERSION_NOT_FOUND');
716
+ }
717
+ const featuresRows = this.db.prepare(`
718
+ SELECT feature_key, access, config_json
719
+ FROM plan_feature_matrix
720
+ WHERE tenant_id = ? AND plan_version_id = ?
721
+ `).all(this.tenantId, row.plan_version_id);
722
+ const features = {};
723
+ for (const featureRow of featuresRows) {
724
+ const featureKey = String(featureRow.feature_key || '').trim();
725
+ if (!featureKey)
726
+ continue;
727
+ features[featureKey] = {
728
+ featureKey,
729
+ access: this.normalizeFeatureAccess(featureRow.access),
730
+ config: this.parseJsonObject(featureRow.config_json || '{}')
731
+ };
732
+ }
733
+ const metadata = this.parseJsonObject(row.metadata_json || '{}');
734
+ return {
735
+ planVersionId: String(row.plan_version_id),
736
+ planId: String(row.plan_id),
737
+ versionNumber: Number(row.version_number || 1),
738
+ isDefault: Boolean(row.is_default),
739
+ seatLimit: row.seat_limit === null || row.seat_limit === undefined
740
+ ? null
741
+ : Math.max(0, Number(row.seat_limit)),
742
+ trialDays: this.readPlanVersionTrialDays(metadata),
743
+ metadata,
744
+ features
745
+ };
746
+ }
747
+ getAccountEntitlement(accountId) {
748
+ const normalizedAccountId = String(accountId || '').trim();
749
+ if (!normalizedAccountId) {
750
+ throw new TaskforceRuleError('accountId is required', 400, 'ACCOUNT_ID_REQUIRED');
751
+ }
752
+ const row = this.db.prepare(`
753
+ SELECT account_id, plan_version_id, entitlement_state, effective_at, effective_until
754
+ FROM account_entitlements
755
+ WHERE tenant_id = ? AND account_id = ?
756
+ LIMIT 1
757
+ `).get(this.tenantId, normalizedAccountId);
758
+ if (!row?.account_id || !row.plan_version_id) {
759
+ throw new TaskforceRuleError('Entitlement not found', 404, 'ENTITLEMENT_NOT_FOUND');
760
+ }
761
+ const version = this.getEffectivePlanVersion(normalizedAccountId);
762
+ return {
763
+ accountId: String(row.account_id),
764
+ planVersionId: String(row.plan_version_id),
765
+ planId: version.planId,
766
+ state: this.normalizeEntitlementState(row.entitlement_state),
767
+ effectiveAt: row.effective_at ? String(row.effective_at) : null,
768
+ effectiveUntil: row.effective_until ? String(row.effective_until) : null,
769
+ version
770
+ };
771
+ }
772
+ countWorkspaceSeatUsage(workspaceId, options = {}) {
773
+ const normalizedWorkspaceId = String(workspaceId || '').trim();
774
+ if (!normalizedWorkspaceId) {
775
+ throw new TaskforceRuleError('workspaceId is required', 400, 'WORKSPACE_ID_REQUIRED');
776
+ }
777
+ const accountId = this.deps.getWorkspaceOwnerUserId(normalizedWorkspaceId);
778
+ if (!accountId) {
779
+ return { used: 0, limit: null, remaining: null };
780
+ }
781
+ const entitlement = this.getAccountEntitlement(accountId);
782
+ const includeInvited = options.includeInvited !== false;
783
+ const statuses = includeInvited ? ['active', 'invited', 'disabled'] : ['active', 'disabled'];
784
+ const placeholders = statuses.map(() => '?').join(', ');
785
+ const row = this.db.prepare(`
786
+ SELECT COUNT(*) AS c
787
+ FROM workspace_memberships
788
+ WHERE tenant_id = ?
789
+ AND workspace_id = ?
790
+ AND COALESCE(status, 'active') IN (${placeholders})
791
+ `).get(this.tenantId, normalizedWorkspaceId, ...statuses);
792
+ const used = Math.max(0, Number(row?.c || 0));
793
+ const limit = entitlement.version.seatLimit;
794
+ const remaining = limit === null ? null : Math.max(0, limit - used);
795
+ return { used, limit, remaining };
796
+ }
797
+ assertSeatAvailable(workspaceId) {
798
+ const seatUsage = this.countWorkspaceSeatUsage(workspaceId, { includeInvited: true });
799
+ if (seatUsage.limit !== null && seatUsage.used >= seatUsage.limit) {
800
+ throw new TaskforceRuleError('Seat limit reached for this workspace', 403, 'SEAT_LIMIT_REACHED', seatUsage);
801
+ }
802
+ }
803
+ evaluateFeatureAccess(input) {
804
+ const workspaceId = String(input.workspaceId || '').trim();
805
+ const featureKey = String(input.featureKey || '').trim();
806
+ if (!workspaceId) {
807
+ return { allowed: false, reasonCode: 'WORKSPACE_ID_REQUIRED', state: 'canceled' };
808
+ }
809
+ if (!featureKey) {
810
+ return { allowed: false, reasonCode: 'FEATURE_KEY_REQUIRED', state: 'canceled' };
811
+ }
812
+ const accountId = this.deps.getWorkspaceOwnerUserId(workspaceId);
813
+ if (!accountId) {
814
+ return { allowed: false, reasonCode: 'ENTITLEMENT_NOT_FOUND', state: 'canceled' };
815
+ }
816
+ const entitlement = this.getAccountEntitlement(accountId);
817
+ if (!this.isEntitlementStateAllowed(entitlement.state)) {
818
+ return { allowed: false, reasonCode: 'ENTITLEMENT_STATE_BLOCKED', state: entitlement.state };
819
+ }
820
+ const feature = entitlement.version.features[featureKey];
821
+ if (!feature) {
822
+ return { allowed: false, reasonCode: 'FEATURE_DISABLED', state: entitlement.state };
823
+ }
824
+ if (feature.access === 'disabled') {
825
+ return { allowed: false, reasonCode: 'FEATURE_DISABLED', state: entitlement.state, feature };
826
+ }
827
+ if (featureKey === 'collaboration.seat_limit') {
828
+ const seatUsage = this.countWorkspaceSeatUsage(workspaceId, { includeInvited: true });
829
+ if (seatUsage.limit !== null && seatUsage.used >= seatUsage.limit) {
830
+ return { allowed: false, reasonCode: 'SEAT_LIMIT_REACHED', state: entitlement.state, seatUsage, feature };
831
+ }
832
+ return { allowed: true, reasonCode: 'OK', state: entitlement.state, seatUsage, feature };
833
+ }
834
+ return { allowed: true, reasonCode: 'OK', state: entitlement.state, feature };
835
+ }
836
+ getUserAccountPlanMode(userId) {
837
+ const normalizedUserId = String(userId || '').trim();
838
+ if (!normalizedUserId)
839
+ return 'personal';
840
+ const entitlement = this.getAccountEntitlement(normalizedUserId);
841
+ return this.isTeamPlanVersion(entitlement.version) ? 'team' : 'personal';
842
+ }
843
+ setUserAccountPlanMode(userId, mode) {
844
+ const normalizedUserId = String(userId || '').trim();
845
+ if (!normalizedUserId) {
846
+ throw new TaskforceRuleError('userId is required', 400, 'USER_ID_REQUIRED');
847
+ }
848
+ const normalizedMode = String(mode || '').trim().toLowerCase() === 'team' ? 'team' : 'personal';
849
+ const targetVersion = this.getDefaultPlanVersionByMode(normalizedMode);
850
+ const now = new Date().toISOString();
851
+ this.db.prepare(`
852
+ INSERT INTO user_billing (user_id, tenant_id, entitlement_state, tier, created_at, updated_at)
853
+ VALUES (?, ?, ?, ?, ?, ?)
854
+ ON CONFLICT (user_id) DO UPDATE SET
855
+ entitlement_state = excluded.entitlement_state,
856
+ tier = excluded.tier,
857
+ updated_at = excluded.updated_at
858
+ `).run(normalizedUserId, this.tenantId, 'active', targetVersion.planId, now, now);
859
+ const planVersionId = targetVersion.planVersionId;
860
+ const entitlementState = 'active';
861
+ this.upsertCanonicalEntitlementRecord({
862
+ accountId: normalizedUserId,
863
+ planVersionId,
864
+ state: entitlementState,
865
+ effectiveAt: now,
866
+ updatedBy: 'legacy-plan-mode',
867
+ createdAt: now,
868
+ updatedAt: now
869
+ });
870
+ return normalizedMode;
871
+ }
872
+ getRequiredDefaultPlanVersionId(planId) {
873
+ return this.getRequiredDefaultPlanVersion(planId).planVersionId;
874
+ }
875
+ getDefaultPlanVersionIdByMode(mode, options = {}) {
876
+ return this.getDefaultPlanVersionByMode(mode, options).planVersionId;
877
+ }
878
+ getBillingMirrorProfileForPlanVersion(planVersionId) {
879
+ const version = this.getPlanVersionSnapshot(planVersionId);
880
+ return {
881
+ planId: version.planId,
882
+ entitlementState: 'active'
883
+ };
884
+ }
885
+ // ─── Signup monetization settings ─────────────────────────────────────────
886
+ getSignupMonetizationSettings() {
887
+ const auth = this.deps.readGlobalConfig().auth || {};
888
+ return {
889
+ strategy: auth.signupMonetizationStrategy === 'plan_selection_required'
890
+ ? 'plan_selection_required'
891
+ : 'auto_assign_default',
892
+ defaultSignupPlanVersionId: typeof auth.defaultSignupPlanVersionId === 'string'
893
+ ? (auth.defaultSignupPlanVersionId.trim() || null)
894
+ : null
895
+ };
896
+ }
897
+ validateSignupMonetizationSettings(input) {
898
+ const current = this.getSignupMonetizationSettings();
899
+ const strategy = input?.strategy === 'plan_selection_required'
900
+ ? 'plan_selection_required'
901
+ : input?.strategy === 'auto_assign_default'
902
+ ? 'auto_assign_default'
903
+ : current.strategy;
904
+ const defaultSignupPlanVersionId = input?.defaultSignupPlanVersionId !== undefined
905
+ ? (typeof input.defaultSignupPlanVersionId === 'string' ? input.defaultSignupPlanVersionId.trim() || null : null)
906
+ : current.defaultSignupPlanVersionId;
907
+ if (strategy === 'plan_selection_required') {
908
+ return { valid: true };
909
+ }
910
+ if (!defaultSignupPlanVersionId) {
911
+ return {
912
+ valid: false,
913
+ code: 'SIGNUP_PLAN_VERSION_REQUIRED',
914
+ message: 'Default signup plan version is required when signup strategy is auto-assign default.'
915
+ };
916
+ }
917
+ const row = this.db.prepare(`
918
+ SELECT v.plan_version_id, v.plan_id, COALESCE(p.status, 'active') AS plan_status
919
+ FROM plan_versions v
920
+ JOIN plan_catalog p ON p.tenant_id = v.tenant_id AND p.plan_id = v.plan_id
921
+ WHERE v.tenant_id = ? AND v.plan_version_id = ?
922
+ LIMIT 1
923
+ `).get(this.tenantId, defaultSignupPlanVersionId);
924
+ if (!row?.plan_version_id || !row.plan_id) {
925
+ return {
926
+ valid: false,
927
+ code: 'SIGNUP_PLAN_VERSION_NOT_FOUND',
928
+ message: `Configured default signup plan version "${defaultSignupPlanVersionId}" was not found.`,
929
+ details: { defaultSignupPlanVersionId }
930
+ };
931
+ }
932
+ if (String(row.plan_status || '').trim().toLowerCase() !== 'active') {
933
+ return {
934
+ valid: false,
935
+ code: 'SIGNUP_PLAN_NOT_ENABLED',
936
+ message: `Configured default signup plan version "${defaultSignupPlanVersionId}" belongs to a disabled plan.`,
937
+ details: {
938
+ defaultSignupPlanVersionId,
939
+ planId: String(row.plan_id)
940
+ }
941
+ };
942
+ }
943
+ return { valid: true };
944
+ }
945
+ // ─── Plan catalog admin ───────────────────────────────────────────────────
946
+ isValidEntitlementTransition(fromState, toState) {
947
+ if (fromState === toState)
948
+ return true;
949
+ const allowed = {
950
+ trialing: ['active', 'grace', 'canceled'],
951
+ active: ['grace', 'suspended', 'canceled'],
952
+ grace: ['active', 'suspended', 'canceled'],
953
+ suspended: ['active', 'canceled'],
954
+ canceled: ['trialing', 'active']
955
+ };
956
+ return allowed[fromState].includes(toState);
957
+ }
958
+ addEntitlementAuditLog(input) {
959
+ const now = new Date().toISOString();
960
+ const id = this.generateEntityId('ent-audit');
961
+ this.db.prepare(`
962
+ INSERT INTO entitlement_audit_log (
963
+ id, tenant_id, actor_user_id, actor_role, account_id, action, reason, before_json, after_json, request_id, created_at
964
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
965
+ `).run(id, this.tenantId, String(input.actorUserId || 'anonymous').trim() || 'anonymous', String(input.actorRole || 'system_admin').trim() || 'system_admin', String(input.accountId || '').trim(), String(input.action || '').trim(), input.reason ? String(input.reason) : null, JSON.stringify(input.before && typeof input.before === 'object' ? input.before : {}), JSON.stringify(input.after && typeof input.after === 'object' ? input.after : {}), input.requestId ? String(input.requestId) : null, now);
966
+ }
967
+ upsertPlanCatalog(input) {
968
+ const planId = String(input.planId || '').trim().toLowerCase();
969
+ const displayName = String(input.displayName || '').trim();
970
+ const initialMode = String(input.initialMode || '').trim().toLowerCase() === 'team'
971
+ ? 'team'
972
+ : 'personal';
973
+ const initialSeatLimit = input.initialSeatLimit === null || input.initialSeatLimit === undefined
974
+ ? null
975
+ : Math.max(0, Math.floor(Number(input.initialSeatLimit)));
976
+ const initialTrialDays = this.normalizePlanVersionTrialDays(input.initialTrialDays);
977
+ if (!planId)
978
+ throw new TaskforceRuleError('planId is required', 400, 'PLAN_ID_REQUIRED');
979
+ if (!displayName)
980
+ throw new TaskforceRuleError('displayName is required', 400, 'PLAN_DISPLAY_NAME_REQUIRED');
981
+ this.assertSeatLimitCompatibleWithMode({
982
+ seatLimit: initialSeatLimit,
983
+ features: this.buildPlanModeFeatures(initialMode).reduce((acc, feature) => {
984
+ acc[feature.featureKey] = {
985
+ featureKey: feature.featureKey,
986
+ access: feature.access,
987
+ config: feature.config
988
+ };
989
+ return acc;
990
+ }, {}),
991
+ modeLabel: 'New team plan'
992
+ });
993
+ const now = new Date().toISOString();
994
+ const existing = this.db.prepare(`
995
+ SELECT plan_id, status
996
+ FROM plan_catalog
997
+ WHERE tenant_id = ? AND plan_id = ?
998
+ LIMIT 1
999
+ `).get(this.tenantId, planId);
1000
+ const isNewPlan = !existing?.plan_id;
1001
+ const status = input.status === 'active'
1002
+ ? 'active'
1003
+ : input.status === 'archived'
1004
+ ? 'archived'
1005
+ : isNewPlan
1006
+ ? 'archived'
1007
+ : String(existing?.status || '').trim().toLowerCase() === 'active'
1008
+ ? 'active'
1009
+ : 'archived';
1010
+ const tx = this.db.transaction(() => {
1011
+ this.db.prepare(`
1012
+ INSERT INTO plan_catalog (plan_id, tenant_id, display_name, status, created_at, updated_at)
1013
+ VALUES (?, ?, ?, ?, ?, ?)
1014
+ ON CONFLICT (plan_id) DO UPDATE SET
1015
+ display_name = excluded.display_name,
1016
+ status = excluded.status,
1017
+ updated_at = excluded.updated_at
1018
+ `).run(planId, this.tenantId, displayName, status, now, now);
1019
+ if (isNewPlan) {
1020
+ this.db.prepare(`
1021
+ INSERT INTO plan_versions (plan_version_id, tenant_id, plan_id, version_number, is_default, seat_limit, metadata_json, created_at, updated_at)
1022
+ VALUES (?, ?, ?, 1, 1, NULL, ?, ?, ?)
1023
+ ON CONFLICT (plan_version_id) DO NOTHING
1024
+ `).run(`${planId}-v1`, this.tenantId, planId, JSON.stringify(this.applyPlanVersionTrialDaysMetadata({}, initialTrialDays)), now, now);
1025
+ this.db.prepare(`
1026
+ UPDATE plan_versions
1027
+ SET seat_limit = ?, updated_at = ?
1028
+ WHERE tenant_id = ? AND plan_version_id = ?
1029
+ `).run(initialSeatLimit, now, this.tenantId, `${planId}-v1`);
1030
+ for (const feature of this.buildPlanModeFeatures(initialMode)) {
1031
+ this.db.prepare(`
1032
+ INSERT INTO plan_feature_matrix (tenant_id, plan_version_id, feature_key, access, config_json, created_at, updated_at)
1033
+ VALUES (?, ?, ?, ?, ?, ?, ?)
1034
+ ON CONFLICT (tenant_id, plan_version_id, feature_key) DO UPDATE SET
1035
+ access = excluded.access,
1036
+ config_json = excluded.config_json,
1037
+ updated_at = excluded.updated_at
1038
+ `).run(this.tenantId, `${planId}-v1`, feature.featureKey, feature.access, JSON.stringify(feature.config), now, now);
1039
+ }
1040
+ }
1041
+ });
1042
+ tx();
1043
+ this.repairPlanDefaultVersionIfNeeded(planId);
1044
+ return { planId, displayName, status };
1045
+ }
1046
+ listPlanCatalog() {
1047
+ const rows = this.db.prepare(`
1048
+ SELECT plan_id, display_name, status, created_at, updated_at
1049
+ FROM plan_catalog
1050
+ WHERE tenant_id = ?
1051
+ ORDER BY updated_at DESC, created_at DESC
1052
+ `).all(this.tenantId);
1053
+ return rows.map((row) => ({
1054
+ planId: String(row.plan_id || ''),
1055
+ displayName: String(row.display_name || ''),
1056
+ status: String(row.status || '').trim().toLowerCase() === 'archived' ? 'archived' : 'active',
1057
+ createdAt: String(row.created_at || ''),
1058
+ updatedAt: String(row.updated_at || '')
1059
+ }));
1060
+ }
1061
+ listPlanFeatureCatalog() {
1062
+ return DEFAULT_PLAN_FEATURE_CATALOG.map((feature) => ({
1063
+ featureKey: feature.featureKey,
1064
+ label: feature.label,
1065
+ description: feature.description,
1066
+ allowedAccessModes: [...feature.allowedAccessModes],
1067
+ configTemplate: { ...feature.configTemplate },
1068
+ ...(feature.dependsOn?.length ? { dependsOn: [...feature.dependsOn] } : {})
1069
+ }));
1070
+ }
1071
+ listPlanVersions(planId) {
1072
+ const normalizedPlanId = String(planId || '').trim().toLowerCase();
1073
+ if (!normalizedPlanId)
1074
+ throw new TaskforceRuleError('planId is required', 400, 'PLAN_ID_REQUIRED');
1075
+ this.repairPlanDefaultVersionIfNeeded(normalizedPlanId);
1076
+ const rows = this.db.prepare(`
1077
+ SELECT plan_version_id
1078
+ FROM plan_versions
1079
+ WHERE tenant_id = ? AND plan_id = ?
1080
+ ORDER BY version_number DESC
1081
+ `).all(this.tenantId, normalizedPlanId);
1082
+ return rows
1083
+ .map((row) => String(row.plan_version_id || '').trim())
1084
+ .filter(Boolean)
1085
+ .map((planVersionId) => this.getPlanVersionSnapshot(planVersionId));
1086
+ }
1087
+ getPlanVersionFeatures(planId, versionId) {
1088
+ const normalizedPlanId = String(planId || '').trim().toLowerCase();
1089
+ const normalizedVersionId = String(versionId || '').trim();
1090
+ if (!normalizedPlanId)
1091
+ throw new TaskforceRuleError('planId is required', 400, 'PLAN_ID_REQUIRED');
1092
+ if (!normalizedVersionId)
1093
+ throw new TaskforceRuleError('versionId is required', 400, 'PLAN_VERSION_ID_REQUIRED');
1094
+ const version = this.getPlanVersionSnapshot(normalizedVersionId);
1095
+ if (version.planId !== normalizedPlanId) {
1096
+ throw new TaskforceRuleError('Plan version does not belong to plan', 404, 'PLAN_VERSION_NOT_FOUND');
1097
+ }
1098
+ return version;
1099
+ }
1100
+ searchAccountsByEmail(query, limit = 20) {
1101
+ const q = String(query || '').trim().toLowerCase();
1102
+ if (!q)
1103
+ return [];
1104
+ const safeLimit = Math.max(1, Math.min(100, Math.floor(Number(limit) || 20)));
1105
+ const rows = this.db.prepare(`
1106
+ SELECT id, email, display_name, is_disabled
1107
+ FROM users
1108
+ WHERE tenant_id = ?
1109
+ AND (LOWER(email) LIKE ? OR LOWER(COALESCE(display_name, '')) LIKE ? OR LOWER(id) LIKE ?)
1110
+ ORDER BY updated_at DESC, created_at DESC
1111
+ LIMIT ?
1112
+ `).all(this.tenantId, `%${q}%`, `%${q}%`, `%${q}%`, safeLimit);
1113
+ return rows.map((row) => ({
1114
+ accountId: String(row.id || ''),
1115
+ email: String(row.email || ''),
1116
+ displayName: row.display_name ? String(row.display_name) : null,
1117
+ disabled: Boolean(row.is_disabled)
1118
+ }));
1119
+ }
1120
+ createPlanVersion(input) {
1121
+ const planId = String(input.planId || '').trim().toLowerCase();
1122
+ const versionNumber = Math.max(1, Math.floor(Number(input.versionNumber) || 1));
1123
+ if (!planId)
1124
+ throw new TaskforceRuleError('planId is required', 400, 'PLAN_ID_REQUIRED');
1125
+ const catalog = this.db.prepare(`
1126
+ SELECT plan_id FROM plan_catalog WHERE tenant_id = ? AND plan_id = ? LIMIT 1
1127
+ `).get(this.tenantId, planId);
1128
+ if (!catalog?.plan_id) {
1129
+ throw new TaskforceRuleError('Plan not found', 404, 'PLAN_NOT_FOUND');
1130
+ }
1131
+ const planVersionId = `${planId}-v${versionNumber}`;
1132
+ const existingVersion = this.db.prepare(`
1133
+ SELECT plan_version_id
1134
+ FROM plan_versions
1135
+ WHERE tenant_id = ? AND plan_version_id = ?
1136
+ LIMIT 1
1137
+ `).get(this.tenantId, planVersionId);
1138
+ if (existingVersion?.plan_version_id) {
1139
+ throw new TaskforceRuleError('Plan version already exists', 409, 'PLAN_VERSION_EXISTS');
1140
+ }
1141
+ const isDefault = input.isDefault === true;
1142
+ const seatLimit = input.seatLimit === null || input.seatLimit === undefined
1143
+ ? null
1144
+ : Math.max(0, Math.floor(Number(input.seatLimit)));
1145
+ const trialDays = this.normalizePlanVersionTrialDays(input.trialDays);
1146
+ const baseMetadata = input.metadata && typeof input.metadata === 'object' ? input.metadata : {};
1147
+ const metadata = this.applyPlanVersionTrialDaysMetadata(baseMetadata, trialDays);
1148
+ this.assertSeatLimitCompatibleWithMode({
1149
+ seatLimit,
1150
+ features: {},
1151
+ modeLabel: `Plan version ${planVersionId}`
1152
+ });
1153
+ const now = new Date().toISOString();
1154
+ const tx = this.db.transaction(() => {
1155
+ if (isDefault) {
1156
+ this.db.prepare(`
1157
+ UPDATE plan_versions
1158
+ SET is_default = 0, updated_at = ?
1159
+ WHERE tenant_id = ? AND plan_id = ?
1160
+ `).run(now, this.tenantId, planId);
1161
+ }
1162
+ this.db.prepare(`
1163
+ INSERT INTO plan_versions (plan_version_id, tenant_id, plan_id, version_number, is_default, seat_limit, metadata_json, created_at, updated_at)
1164
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
1165
+ `).run(planVersionId, this.tenantId, planId, versionNumber, isDefault ? 1 : 0, seatLimit, JSON.stringify(metadata), now, now);
1166
+ });
1167
+ tx();
1168
+ this.repairPlanDefaultVersionIfNeeded(planId);
1169
+ return this.getPlanVersionSnapshot(planVersionId);
1170
+ }
1171
+ updatePlanVersion(input) {
1172
+ const planId = String(input.planId || '').trim().toLowerCase();
1173
+ const versionNumber = Math.max(1, Math.floor(Number(input.versionNumber) || 1));
1174
+ if (!planId)
1175
+ throw new TaskforceRuleError('planId is required', 400, 'PLAN_ID_REQUIRED');
1176
+ const planVersionId = `${planId}-v${versionNumber}`;
1177
+ const existing = this.db.prepare(`
1178
+ SELECT plan_version_id, is_default, metadata_json, seat_limit
1179
+ FROM plan_versions
1180
+ WHERE tenant_id = ? AND plan_version_id = ? AND plan_id = ?
1181
+ LIMIT 1
1182
+ `).get(this.tenantId, planVersionId, planId);
1183
+ if (!existing?.plan_version_id) {
1184
+ throw new TaskforceRuleError('Plan version not found', 404, 'PLAN_VERSION_NOT_FOUND');
1185
+ }
1186
+ const existingDefault = Boolean(existing.is_default);
1187
+ const nextDefault = input.isDefault === undefined ? existingDefault : input.isDefault === true;
1188
+ if (existingDefault && !nextDefault) {
1189
+ const alternate = this.db.prepare(`
1190
+ SELECT plan_version_id
1191
+ FROM plan_versions
1192
+ WHERE tenant_id = ? AND plan_id = ? AND plan_version_id != ? AND is_default = 1
1193
+ LIMIT 1
1194
+ `).get(this.tenantId, planId, planVersionId);
1195
+ if (!alternate?.plan_version_id) {
1196
+ throw new TaskforceRuleError('A plan must have exactly one default version', 409, 'PLAN_DEFAULT_VERSION_REQUIRED');
1197
+ }
1198
+ }
1199
+ const seatLimit = input.seatLimit === undefined
1200
+ ? (existing.seat_limit === null || existing.seat_limit === undefined ? null : Math.max(0, Number(existing.seat_limit)))
1201
+ : input.seatLimit === null
1202
+ ? null
1203
+ : Math.max(0, Math.floor(Number(input.seatLimit)));
1204
+ const baseMetadata = input.metadata === undefined
1205
+ ? this.parseJsonObject(existing.metadata_json || '{}')
1206
+ : (input.metadata && typeof input.metadata === 'object' ? input.metadata : {});
1207
+ const trialDays = input.trialDays === undefined
1208
+ ? undefined
1209
+ : this.normalizePlanVersionTrialDays(input.trialDays);
1210
+ const metadata = this.applyPlanVersionTrialDaysMetadata(baseMetadata, trialDays);
1211
+ const currentFeatures = this.getPlanVersionSnapshot(planVersionId).features;
1212
+ this.assertSeatLimitCompatibleWithMode({
1213
+ seatLimit,
1214
+ features: currentFeatures,
1215
+ modeLabel: `Plan version ${planVersionId}`
1216
+ });
1217
+ const now = new Date().toISOString();
1218
+ const tx = this.db.transaction(() => {
1219
+ if (nextDefault) {
1220
+ this.db.prepare(`
1221
+ UPDATE plan_versions
1222
+ SET is_default = CASE WHEN plan_version_id = ? THEN 1 ELSE 0 END,
1223
+ updated_at = ?
1224
+ WHERE tenant_id = ? AND plan_id = ?
1225
+ `).run(planVersionId, now, this.tenantId, planId);
1226
+ }
1227
+ else {
1228
+ this.db.prepare(`
1229
+ UPDATE plan_versions
1230
+ SET updated_at = ?
1231
+ WHERE tenant_id = ? AND plan_id = ?
1232
+ `).run(now, this.tenantId, planId);
1233
+ }
1234
+ this.db.prepare(`
1235
+ UPDATE plan_versions
1236
+ SET seat_limit = ?, metadata_json = ?, updated_at = ?
1237
+ WHERE tenant_id = ? AND plan_version_id = ?
1238
+ `).run(seatLimit, JSON.stringify(metadata), now, this.tenantId, planVersionId);
1239
+ });
1240
+ tx();
1241
+ this.repairPlanDefaultVersionIfNeeded(planId);
1242
+ return this.getPlanVersionSnapshot(planVersionId);
1243
+ }
1244
+ getPlanVersionSnapshot(planVersionId) {
1245
+ const row = this.db.prepare(`
1246
+ SELECT plan_version_id, plan_id, version_number, is_default, seat_limit, metadata_json
1247
+ FROM plan_versions
1248
+ WHERE tenant_id = ? AND plan_version_id = ?
1249
+ LIMIT 1
1250
+ `).get(this.tenantId, planVersionId);
1251
+ if (!row?.plan_version_id || !row.plan_id) {
1252
+ throw new TaskforceRuleError('Plan version not found', 404, 'PLAN_VERSION_NOT_FOUND');
1253
+ }
1254
+ const featuresRows = this.db.prepare(`
1255
+ SELECT feature_key, access, config_json
1256
+ FROM plan_feature_matrix
1257
+ WHERE tenant_id = ? AND plan_version_id = ?
1258
+ `).all(this.tenantId, planVersionId);
1259
+ const features = {};
1260
+ for (const featureRow of featuresRows) {
1261
+ const key = String(featureRow.feature_key || '').trim();
1262
+ if (!key)
1263
+ continue;
1264
+ features[key] = {
1265
+ featureKey: key,
1266
+ access: this.normalizeFeatureAccess(featureRow.access),
1267
+ config: this.parseJsonObject(featureRow.config_json || '{}')
1268
+ };
1269
+ }
1270
+ const metadata = this.parseJsonObject(row.metadata_json || '{}');
1271
+ return {
1272
+ planVersionId: String(row.plan_version_id),
1273
+ planId: String(row.plan_id),
1274
+ versionNumber: Number(row.version_number || 1),
1275
+ isDefault: Boolean(row.is_default),
1276
+ seatLimit: row.seat_limit === null || row.seat_limit === undefined ? null : Number(row.seat_limit),
1277
+ trialDays: this.readPlanVersionTrialDays(metadata),
1278
+ metadata,
1279
+ features: this.normalizePlanFeatureDependencies(features)
1280
+ };
1281
+ }
1282
+ updatePlanVersionFeatures(input) {
1283
+ const planVersionId = String(input.planVersionId || '').trim();
1284
+ if (!planVersionId)
1285
+ throw new TaskforceRuleError('planVersionId is required', 400, 'PLAN_VERSION_ID_REQUIRED');
1286
+ const now = new Date().toISOString();
1287
+ const features = Array.isArray(input.features) ? input.features : [];
1288
+ const currentVersion = this.getPlanVersionSnapshot(planVersionId);
1289
+ const nextFeatures = {
1290
+ ...currentVersion.features
1291
+ };
1292
+ for (const feature of features) {
1293
+ const featureKey = String(feature.featureKey || '').trim();
1294
+ if (!featureKey)
1295
+ continue;
1296
+ const access = this.normalizeFeatureAccess(feature.access);
1297
+ const rawConfig = feature.config && typeof feature.config === 'object' ? feature.config : {};
1298
+ nextFeatures[featureKey] = {
1299
+ featureKey,
1300
+ access,
1301
+ config: this.sanitizePlanFeatureConfig(featureKey, access, rawConfig)
1302
+ };
1303
+ }
1304
+ const normalizedNextFeatures = this.normalizePlanFeatureDependencies(nextFeatures);
1305
+ this.assertSeatLimitCompatibleWithMode({
1306
+ seatLimit: currentVersion.seatLimit,
1307
+ features: normalizedNextFeatures,
1308
+ modeLabel: `Plan version ${planVersionId}`
1309
+ });
1310
+ const tx = this.db.transaction(() => {
1311
+ for (const [featureKey, feature] of Object.entries(normalizedNextFeatures)) {
1312
+ const access = this.normalizeFeatureAccess(feature.access);
1313
+ const rawConfig = feature.config && typeof feature.config === 'object' ? feature.config : {};
1314
+ const config = this.sanitizePlanFeatureConfig(featureKey, access, rawConfig);
1315
+ this.db.prepare(`
1316
+ INSERT INTO plan_feature_matrix (tenant_id, plan_version_id, feature_key, access, config_json, created_at, updated_at)
1317
+ VALUES (?, ?, ?, ?, ?, ?, ?)
1318
+ ON CONFLICT (tenant_id, plan_version_id, feature_key) DO UPDATE SET
1319
+ access = excluded.access,
1320
+ config_json = excluded.config_json,
1321
+ updated_at = excluded.updated_at
1322
+ `).run(this.tenantId, planVersionId, featureKey, access, JSON.stringify(config), now, now);
1323
+ }
1324
+ });
1325
+ tx();
1326
+ const snapshot = this.getPlanVersionSnapshot(planVersionId);
1327
+ this.assertSeatLimitCompatibleWithMode({
1328
+ seatLimit: snapshot.seatLimit,
1329
+ features: this.normalizePlanFeatureDependencies(snapshot.features),
1330
+ modeLabel: `Plan version ${planVersionId}`
1331
+ });
1332
+ return snapshot;
1333
+ }
1334
+ updateAccountEntitlement(input) {
1335
+ const accountId = String(input.accountId || '').trim();
1336
+ if (!accountId)
1337
+ throw new TaskforceRuleError('accountId is required', 400, 'ACCOUNT_ID_REQUIRED');
1338
+ const existing = this.db.prepare(`
1339
+ SELECT account_id, plan_version_id, entitlement_state, effective_at, effective_until
1340
+ FROM account_entitlements
1341
+ WHERE tenant_id = ? AND account_id = ?
1342
+ LIMIT 1
1343
+ `).get(this.tenantId, accountId);
1344
+ const before = existing?.account_id ? this.getAccountEntitlement(accountId) : null;
1345
+ const nextPlanVersionId = String(input.planVersionId || before?.planVersionId || '').trim();
1346
+ if (!nextPlanVersionId) {
1347
+ throw new TaskforceRuleError('planVersionId is required', 400, 'PLAN_VERSION_ID_REQUIRED');
1348
+ }
1349
+ const currentState = before?.state || 'active';
1350
+ const nextState = this.normalizeEntitlementState(input.entitlementState || currentState);
1351
+ if (before && !this.isValidEntitlementTransition(before.state, nextState)) {
1352
+ throw new TaskforceRuleError('Invalid entitlement state transition', 409, 'INVALID_STATE_TRANSITION');
1353
+ }
1354
+ const normalizedReason = typeof input.reason === 'string' ? input.reason.trim() : '';
1355
+ if ((nextState === 'suspended' || nextState === 'canceled') && !normalizedReason) {
1356
+ throw new TaskforceRuleError('Reason is required when setting state to suspended/canceled.', 400, 'ENTITLEMENT_REASON_REQUIRED');
1357
+ }
1358
+ const effectiveAt = input.effectiveAt === undefined ? (before?.effectiveAt || null) : (input.effectiveAt ? String(input.effectiveAt) : null);
1359
+ const effectiveUntil = input.effectiveUntil === undefined ? (before?.effectiveUntil || null) : (input.effectiveUntil ? String(input.effectiveUntil) : null);
1360
+ const now = new Date().toISOString();
1361
+ this.upsertCanonicalEntitlementRecord({
1362
+ accountId,
1363
+ planVersionId: nextPlanVersionId,
1364
+ state: nextState,
1365
+ effectiveAt,
1366
+ effectiveUntil,
1367
+ updatedBy: String(input.actorUserId || 'system').trim() || 'system',
1368
+ createdAt: before?.effectiveAt || now,
1369
+ updatedAt: now
1370
+ });
1371
+ const after = this.getAccountEntitlement(accountId);
1372
+ this.addEntitlementAuditLog({
1373
+ actorUserId: input.actorUserId,
1374
+ actorRole: input.actorRole,
1375
+ accountId,
1376
+ action: 'account-entitlement-update',
1377
+ reason: normalizedReason || undefined,
1378
+ before: before || {
1379
+ accountId,
1380
+ missing: true
1381
+ },
1382
+ after,
1383
+ requestId: input.requestId
1384
+ });
1385
+ return after;
1386
+ }
1387
+ listEntitlementAuditLog(accountId, limit = 100, offset = 0) {
1388
+ const normalizedAccountId = String(accountId || '').trim();
1389
+ const safeLimit = Math.max(1, Math.min(500, Math.floor(Number(limit) || 100)));
1390
+ const safeOffset = Math.max(0, Math.floor(Number(offset) || 0));
1391
+ const rows = this.db.prepare(`
1392
+ SELECT id, account_id, action, actor_user_id, actor_role, reason, before_json, after_json, created_at
1393
+ FROM entitlement_audit_log
1394
+ WHERE tenant_id = ? AND account_id = ?
1395
+ ORDER BY created_at DESC
1396
+ LIMIT ?
1397
+ OFFSET ?
1398
+ `).all(this.tenantId, normalizedAccountId, safeLimit, safeOffset);
1399
+ return rows.map((row) => ({
1400
+ id: String(row.id || ''),
1401
+ accountId: String(row.account_id || ''),
1402
+ action: String(row.action || ''),
1403
+ actorUserId: String(row.actor_user_id || ''),
1404
+ actorRole: String(row.actor_role || ''),
1405
+ reason: row.reason ? String(row.reason) : null,
1406
+ before: this.parseJsonObject(row.before_json || '{}'),
1407
+ after: this.parseJsonObject(row.after_json || '{}'),
1408
+ createdAt: String(row.created_at || '')
1409
+ }));
1410
+ }
1411
+ getAccountEntitlementsPayload(input) {
1412
+ const workspaceId = String(input.workspaceId || '').trim();
1413
+ const userId = String(input.userId || '').trim();
1414
+ const accountId = this.deps.getWorkspaceOwnerUserId(workspaceId) || userId;
1415
+ const entitlement = this.getAccountEntitlement(accountId);
1416
+ const seatUsage = this.countWorkspaceSeatUsage(workspaceId, { includeInvited: true });
1417
+ const features = Object.values(entitlement.version.features).reduce((acc, feature) => {
1418
+ const evaluation = this.evaluateFeatureAccess({
1419
+ userId,
1420
+ workspaceId,
1421
+ featureKey: feature.featureKey
1422
+ });
1423
+ acc[feature.featureKey] = {
1424
+ allowed: evaluation.allowed,
1425
+ access: feature.access,
1426
+ config: feature.config
1427
+ };
1428
+ return acc;
1429
+ }, {});
1430
+ return {
1431
+ accountId,
1432
+ workspaceId,
1433
+ planId: entitlement.planId,
1434
+ planVersionId: entitlement.planVersionId,
1435
+ state: entitlement.state,
1436
+ effectiveAt: entitlement.effectiveAt,
1437
+ effectiveUntil: entitlement.effectiveUntil,
1438
+ seatUsage,
1439
+ features
1440
+ };
1441
+ }
1442
+ }