@taskforcehq/taskforce 0.3.311 → 0.3.313

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 (454) hide show
  1. package/README.md +40 -2
  2. package/dist/TaskforceCore.js +1 -1
  3. package/dist/components/features/InitiativesModule.js +1 -1
  4. package/dist/components/features/TaskSettings.js +1 -1
  5. package/dist/components/views/StandaloneLayout.js +3 -6
  6. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +1 -2
  7. package/dist/components/views/standalone/modals/AccountHubModal.js +2 -3
  8. package/dist/components/views/standalone/modals/HelpModal.js +15 -2
  9. package/dist/hooks/useFilterSort.js +1 -1
  10. package/dist/hooks/useTaskforce.js +2 -2
  11. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +5 -2
  12. package/dist/ui/assets/{AgentsModule-D8u9P5dZ.js → AgentsModule-BULqIo8e.js} +1 -1
  13. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-CsUauFA2.js → AnnotatedAttachmentWorkspace-DHr4rHK9.js} +1 -1
  14. package/dist/ui/assets/{ContextAttachmentManager-DbQvDHFz.js → ContextAttachmentManager-DoMe2OpY.js} +1 -1
  15. package/dist/ui/assets/{DocumentWorkspace-BmTkN1A8.js → DocumentWorkspace-DumBWhvb.js} +1 -1
  16. package/dist/ui/assets/{EntityActivityTimeline-BXHTM-Nh.js → EntityActivityTimeline-sHk8Nzbq.js} +1 -1
  17. package/dist/ui/assets/{InitiativesModule-D33RyXx0.js → InitiativesModule-3X9UNzHh.js} +1 -1
  18. package/dist/ui/assets/{PlansPage-D0RXlT9_.js → PlansPage-C3A3EYaV.js} +1 -1
  19. package/dist/ui/assets/{TaskContextUpload-Vsg67lpR.js → TaskContextUpload-C8G7pWkX.js} +1 -1
  20. package/dist/ui/assets/{TaskSettings-DshLw7_d.js → TaskSettings-DM1o9Wla.js} +2 -2
  21. package/dist/ui/assets/{WorkflowsModule-CLM-1BnF.js → WorkflowsModule-CW0crzvD.js} +1 -1
  22. package/dist/ui/assets/documentReferences-BZB3nMRb.js +1 -0
  23. package/dist/ui/assets/index-BWFQ9mGn.js +5 -0
  24. package/dist/ui/assets/{index-DOR9PRqt.css → index-DcK_8tuE.css} +1 -1
  25. package/dist/ui/index.html +2 -2
  26. package/package.json +12 -4
  27. package/scripts/postinstall.js +4 -3
  28. package/dist/TaskforceCore.CategoryFallback.test.d.ts +0 -1
  29. package/dist/TaskforceCore.CategoryFallback.test.js +0 -101
  30. package/dist/TaskforceCore.filter-persistence.test.d.ts +0 -1
  31. package/dist/TaskforceCore.filter-persistence.test.js +0 -402
  32. package/dist/TaskforceCore.routing.test.d.ts +0 -1
  33. package/dist/TaskforceCore.routing.test.js +0 -311
  34. package/dist/TaskforceCore.test.d.ts +0 -1
  35. package/dist/TaskforceCore.test.js +0 -7910
  36. package/dist/TaskforceWidget.test.d.ts +0 -1
  37. package/dist/TaskforceWidget.test.js +0 -92
  38. package/dist/UnsavedChangesRelaxed.test.d.ts +0 -1
  39. package/dist/UnsavedChangesRelaxed.test.js +0 -16
  40. package/dist/__tests__/localizationCatalogs.test.d.ts +0 -1
  41. package/dist/__tests__/localizationCatalogs.test.js +0 -54
  42. package/dist/__tests__/workflowSimulation.test.d.ts +0 -1
  43. package/dist/__tests__/workflowSimulation.test.js +0 -113
  44. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.test.d.ts +0 -1
  45. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.test.js +0 -278
  46. package/dist/annotatedAttachments/payload.test.d.ts +0 -1
  47. package/dist/annotatedAttachments/payload.test.js +0 -67
  48. package/dist/compat/workspaceSyncCompat.test.d.ts +0 -1
  49. package/dist/compat/workspaceSyncCompat.test.js +0 -124
  50. package/dist/components/features/AgentsModule.test.d.ts +0 -1
  51. package/dist/components/features/AgentsModule.test.js +0 -601
  52. package/dist/components/features/AnnotatedAttachmentWorkspace.test.d.ts +0 -1
  53. package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +0 -2883
  54. package/dist/components/features/DocumentWorkspace.test.d.ts +0 -1
  55. package/dist/components/features/DocumentWorkspace.test.js +0 -1439
  56. package/dist/components/features/TaskSettings.test.d.ts +0 -1
  57. package/dist/components/features/TaskSettings.test.js +0 -1674
  58. package/dist/components/features/settings/TaxonomyLibraryManager.test.d.ts +0 -1
  59. package/dist/components/features/settings/TaxonomyLibraryManager.test.js +0 -141
  60. package/dist/components/task/TaskActionHeader.test.d.ts +0 -1
  61. package/dist/components/task/TaskActionHeader.test.js +0 -91
  62. package/dist/components/task/TaskCard.test.d.ts +0 -1
  63. package/dist/components/task/TaskCard.test.js +0 -513
  64. package/dist/components/task/TaskContextUpload.test.d.ts +0 -1
  65. package/dist/components/task/TaskContextUpload.test.js +0 -506
  66. package/dist/components/task/TaskForm.test.d.ts +0 -1
  67. package/dist/components/task/TaskForm.test.js +0 -946
  68. package/dist/components/task/TaskKanban.empty-column-drop.test.d.ts +0 -1
  69. package/dist/components/task/TaskKanban.empty-column-drop.test.js +0 -66
  70. package/dist/components/task/TaskKanban.planning-drop.test.d.ts +0 -1
  71. package/dist/components/task/TaskKanban.planning-drop.test.js +0 -93
  72. package/dist/components/task/TaskKanban.test.d.ts +0 -1
  73. package/dist/components/task/TaskKanban.test.js +0 -99
  74. package/dist/components/task/TaskList.test.d.ts +0 -1
  75. package/dist/components/task/TaskList.test.js +0 -473
  76. package/dist/components/task/TaskStatusActions.test.d.ts +0 -1
  77. package/dist/components/task/TaskStatusActions.test.js +0 -54
  78. package/dist/components/task/taskKanbanDropRules.test.d.ts +0 -1
  79. package/dist/components/task/taskKanbanDropRules.test.js +0 -114
  80. package/dist/components/ui/LevelSelector.test.d.ts +0 -1
  81. package/dist/components/ui/LevelSelector.test.js +0 -55
  82. package/dist/components/ui/Markdown.test.d.ts +0 -1
  83. package/dist/components/ui/Markdown.test.js +0 -38
  84. package/dist/components/ui/MultiSelectFilter.test.d.ts +0 -1
  85. package/dist/components/ui/MultiSelectFilter.test.js +0 -74
  86. package/dist/components/ui/TaxonomyDropdown.test.d.ts +0 -1
  87. package/dist/components/ui/TaxonomyDropdown.test.js +0 -130
  88. package/dist/components/ui/TopNoticeLayer.test.d.ts +0 -1
  89. package/dist/components/ui/TopNoticeLayer.test.js +0 -25
  90. package/dist/components/views/AppShellHeader.test.d.ts +0 -1
  91. package/dist/components/views/AppShellHeader.test.js +0 -42
  92. package/dist/components/views/PlanComparisonPage.test.d.ts +0 -1
  93. package/dist/components/views/PlanComparisonPage.test.js +0 -758
  94. package/dist/components/views/PlansPage.test.d.ts +0 -1
  95. package/dist/components/views/PlansPage.test.js +0 -710
  96. package/dist/components/views/chrome/WorkspaceChromeSections.test.d.ts +0 -1
  97. package/dist/components/views/chrome/WorkspaceChromeSections.test.js +0 -54
  98. package/dist/components/views/chrome/workspaceChrome.test.d.ts +0 -1
  99. package/dist/components/views/chrome/workspaceChrome.test.js +0 -52
  100. package/dist/components/views/panels/DocumentFilterToolbar.test.d.ts +0 -1
  101. package/dist/components/views/panels/DocumentFilterToolbar.test.js +0 -19
  102. package/dist/components/views/panels/FilterToolbar.test.d.ts +0 -1
  103. package/dist/components/views/panels/FilterToolbar.test.js +0 -47
  104. package/dist/components/views/panels/PlanningDrawer.test.d.ts +0 -1
  105. package/dist/components/views/panels/PlanningDrawer.test.js +0 -529
  106. package/dist/components/views/planningScope.test.d.ts +0 -1
  107. package/dist/components/views/planningScope.test.js +0 -60
  108. package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +0 -1
  109. package/dist/components/views/standalone/modals/AccountHubModal.test.js +0 -72
  110. package/dist/components/views/standalone/modals/HelpModal.test.d.ts +0 -1
  111. package/dist/components/views/standalone/modals/HelpModal.test.js +0 -23
  112. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +0 -1
  113. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +0 -24
  114. package/dist/components/views/teamManagementAccess.test.d.ts +0 -1
  115. package/dist/components/views/teamManagementAccess.test.js +0 -44
  116. package/dist/config/deployment.test.d.ts +0 -1
  117. package/dist/config/deployment.test.js +0 -90
  118. package/dist/config/envSchema.test.d.ts +0 -1
  119. package/dist/config/envSchema.test.js +0 -125
  120. package/dist/core/AiProfileService.test.d.ts +0 -1
  121. package/dist/core/AiProfileService.test.js +0 -184
  122. package/dist/core/AiProfiles.test.d.ts +0 -1
  123. package/dist/core/AiProfiles.test.js +0 -1873
  124. package/dist/core/AuthTokenService.test.d.ts +0 -1
  125. package/dist/core/AuthTokenService.test.js +0 -78
  126. package/dist/core/BulkUpdateFieldsPreview.test.d.ts +0 -1
  127. package/dist/core/BulkUpdateFieldsPreview.test.js +0 -62
  128. package/dist/core/CompletedAt.test.d.ts +0 -1
  129. package/dist/core/CompletedAt.test.js +0 -44
  130. package/dist/core/CreatedBy.test.d.ts +0 -1
  131. package/dist/core/CreatedBy.test.js +0 -188
  132. package/dist/core/DeletedTaskTrash.test.d.ts +0 -1
  133. package/dist/core/DeletedTaskTrash.test.js +0 -104
  134. package/dist/core/EntitlementsPolicy.test.d.ts +0 -1
  135. package/dist/core/EntitlementsPolicy.test.js +0 -280
  136. package/dist/core/GlobalSettingsService.test.d.ts +0 -1
  137. package/dist/core/GlobalSettingsService.test.js +0 -45
  138. package/dist/core/JsonBackupRuntime.test.d.ts +0 -1
  139. package/dist/core/JsonBackupRuntime.test.js +0 -155
  140. package/dist/core/PlanFeatureCatalog.test.d.ts +0 -1
  141. package/dist/core/PlanFeatureCatalog.test.js +0 -139
  142. package/dist/core/PlanVersionPolicy.test.d.ts +0 -1
  143. package/dist/core/PlanVersionPolicy.test.js +0 -236
  144. package/dist/core/PlanningEntities.test.d.ts +0 -1
  145. package/dist/core/PlanningEntities.test.js +0 -509
  146. package/dist/core/ScheduleFields.test.d.ts +0 -1
  147. package/dist/core/ScheduleFields.test.js +0 -193
  148. package/dist/core/SetupState.test.d.ts +0 -1
  149. package/dist/core/SetupState.test.js +0 -113
  150. package/dist/core/SignupMonetizationSettings.test.d.ts +0 -1
  151. package/dist/core/SignupMonetizationSettings.test.js +0 -515
  152. package/dist/core/Stats.test.d.ts +0 -1
  153. package/dist/core/Stats.test.js +0 -36
  154. package/dist/core/SystemAdmin.test.d.ts +0 -1
  155. package/dist/core/SystemAdmin.test.js +0 -1106
  156. package/dist/core/TaskActivitySync.test.d.ts +0 -1
  157. package/dist/core/TaskActivitySync.test.js +0 -90
  158. package/dist/core/TaskAttachmentsCanonical.test.d.ts +0 -1
  159. package/dist/core/TaskAttachmentsCanonical.test.js +0 -356
  160. package/dist/core/TaskAuditTimeline.test.d.ts +0 -1
  161. package/dist/core/TaskAuditTimeline.test.js +0 -319
  162. package/dist/core/TaskTaxonomyValidation.test.d.ts +0 -1
  163. package/dist/core/TaskTaxonomyValidation.test.js +0 -440
  164. package/dist/core/Taskforce.ids.test.d.ts +0 -1
  165. package/dist/core/Taskforce.ids.test.js +0 -611
  166. package/dist/core/Taskforce.listTasksSlim.test.d.ts +0 -1
  167. package/dist/core/Taskforce.listTasksSlim.test.js +0 -91
  168. package/dist/core/Taskforce.mcpAuth.test.d.ts +0 -1
  169. package/dist/core/Taskforce.mcpAuth.test.js +0 -450
  170. package/dist/core/Taskforce.realtimeMutation.test.d.ts +0 -1
  171. package/dist/core/Taskforce.realtimeMutation.test.js +0 -131
  172. package/dist/core/UserProfileAvatarDrafts.test.d.ts +0 -1
  173. package/dist/core/UserProfileAvatarDrafts.test.js +0 -138
  174. package/dist/core/WorkspaceDelete.test.d.ts +0 -1
  175. package/dist/core/WorkspaceDelete.test.js +0 -159
  176. package/dist/core/WorkspacePermissions.test.d.ts +0 -1
  177. package/dist/core/WorkspacePermissions.test.js +0 -270
  178. package/dist/core/WorkspacePlanMode.test.d.ts +0 -1
  179. package/dist/core/WorkspacePlanMode.test.js +0 -383
  180. package/dist/core/WorkspaceSyncSettings.test.d.ts +0 -1
  181. package/dist/core/WorkspaceSyncSettings.test.js +0 -65
  182. package/dist/core/__tests__/TaskAttachmentsCanonical.test.d.ts +0 -1
  183. package/dist/core/__tests__/TaskAttachmentsCanonical.test.js +0 -212
  184. package/dist/documentReviews/DocumentReviewSessionStore.test.d.ts +0 -1
  185. package/dist/documentReviews/DocumentReviewSessionStore.test.js +0 -127
  186. package/dist/hooks/authGuardPolicy.test.d.ts +0 -1
  187. package/dist/hooks/authGuardPolicy.test.js +0 -76
  188. package/dist/hooks/bootstrapTimeouts.test.d.ts +0 -1
  189. package/dist/hooks/bootstrapTimeouts.test.js +0 -12
  190. package/dist/hooks/sync/controlPlane.test.d.ts +0 -1
  191. package/dist/hooks/sync/controlPlane.test.js +0 -121
  192. package/dist/hooks/sync/lifecyclePolicy.test.d.ts +0 -1
  193. package/dist/hooks/sync/lifecyclePolicy.test.js +0 -124
  194. package/dist/hooks/sync/orchestratorShared.test.d.ts +0 -1
  195. package/dist/hooks/sync/orchestratorShared.test.js +0 -98
  196. package/dist/hooks/sync/pullLifecycle.test.d.ts +0 -1
  197. package/dist/hooks/sync/pullLifecycle.test.js +0 -80
  198. package/dist/hooks/sync/recovery.test.d.ts +0 -1
  199. package/dist/hooks/sync/recovery.test.js +0 -292
  200. package/dist/hooks/sync/transfers.test.d.ts +0 -1
  201. package/dist/hooks/sync/transfers.test.js +0 -928
  202. package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +0 -1
  203. package/dist/hooks/sync/useRecentSyncEvents.test.js +0 -124
  204. package/dist/hooks/sync/useSyncStatusActions.test.d.ts +0 -1
  205. package/dist/hooks/sync/useSyncStatusActions.test.js +0 -133
  206. package/dist/hooks/sync/useSyncStatusControls.test.d.ts +0 -1
  207. package/dist/hooks/sync/useSyncStatusControls.test.js +0 -88
  208. package/dist/hooks/useFilterSort.test.d.ts +0 -1
  209. package/dist/hooks/useFilterSort.test.js +0 -419
  210. package/dist/hooks/useRealtimeSync.test.d.ts +0 -1
  211. package/dist/hooks/useRealtimeSync.test.js +0 -311
  212. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.d.ts +0 -1
  213. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +0 -54
  214. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +0 -1
  215. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +0 -228
  216. package/dist/hooks/useSyncOrchestrator.retry-closure.test.d.ts +0 -1
  217. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +0 -2027
  218. package/dist/hooks/useTaskData.test.d.ts +0 -1
  219. package/dist/hooks/useTaskData.test.js +0 -291
  220. package/dist/hooks/useTaskMutations.test.d.ts +0 -1
  221. package/dist/hooks/useTaskMutations.test.js +0 -256
  222. package/dist/hooks/useTaskforce.contract.test.d.ts +0 -1
  223. package/dist/hooks/useTaskforce.contract.test.js +0 -7
  224. package/dist/hooks/useTaskforce.priority-filters.test.d.ts +0 -1
  225. package/dist/hooks/useTaskforce.priority-filters.test.js +0 -28
  226. package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +0 -1
  227. package/dist/hooks/useTaskforce.runtime-routing.test.js +0 -246
  228. package/dist/hooks/useTaskforce.sync-behavior.test.d.ts +0 -1
  229. package/dist/hooks/useTaskforce.sync-behavior.test.js +0 -3644
  230. package/dist/hooks/useWorkspaceSyncController.test.d.ts +0 -1
  231. package/dist/hooks/useWorkspaceSyncController.test.js +0 -192
  232. package/dist/hooks/workspace/useTaskforceApiRouting.test.d.ts +0 -1
  233. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +0 -77
  234. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +0 -1
  235. package/dist/hooks/workspace/workspaceLocalState.test.js +0 -59
  236. package/dist/localization/localizationCatalogs.test.d.ts +0 -1
  237. package/dist/localization/localizationCatalogs.test.js +0 -37
  238. package/dist/mcp/aiProfileBootstrap.test.d.ts +0 -1
  239. package/dist/mcp/aiProfileBootstrap.test.js +0 -68
  240. package/dist/mcp/canonicalAssetHelpers.test.d.ts +0 -1
  241. package/dist/mcp/canonicalAssetHelpers.test.js +0 -67
  242. package/dist/mcp/clientIntegrations.test.d.ts +0 -1
  243. package/dist/mcp/clientIntegrations.test.js +0 -219
  244. package/dist/mcp/optionResolution.test.d.ts +0 -1
  245. package/dist/mcp/optionResolution.test.js +0 -43
  246. package/dist/mcp/runtime.test.d.ts +0 -1
  247. package/dist/mcp/runtime.test.js +0 -5211
  248. package/dist/mcp/structuredCommentValidation.test.d.ts +0 -1
  249. package/dist/mcp/structuredCommentValidation.test.js +0 -31
  250. package/dist/mcp/structuredDocValidation.test.d.ts +0 -1
  251. package/dist/mcp/structuredDocValidation.test.js +0 -35
  252. package/dist/mcp/toolRegistry.test.d.ts +0 -1
  253. package/dist/mcp/toolRegistry.test.js +0 -34
  254. package/dist/migrations/billingSchemaParity.test.d.ts +0 -1
  255. package/dist/migrations/billingSchemaParity.test.js +0 -160
  256. package/dist/migrations/taskSchemaMigrations.test.d.ts +0 -1
  257. package/dist/migrations/taskSchemaMigrations.test.js +0 -225
  258. package/dist/qaOpen.test.d.ts +0 -1
  259. package/dist/qaOpen.test.js +0 -56
  260. package/dist/releaseVersion.test.d.ts +0 -1
  261. package/dist/releaseVersion.test.js +0 -32
  262. package/dist/runtime/appGates.test.d.ts +0 -1
  263. package/dist/runtime/appGates.test.js +0 -42
  264. package/dist/server/aiProfileAvatarAssets.test.d.ts +0 -1
  265. package/dist/server/aiProfileAvatarAssets.test.js +0 -213
  266. package/dist/server/annotatedAttachmentsRoutes.test.d.ts +0 -1
  267. package/dist/server/annotatedAttachmentsRoutes.test.js +0 -532
  268. package/dist/server/auth.test.d.ts +0 -1
  269. package/dist/server/auth.test.js +0 -131
  270. package/dist/server/buildInfo.test.d.ts +0 -1
  271. package/dist/server/buildInfo.test.js +0 -29
  272. package/dist/server/cors.test.d.ts +0 -1
  273. package/dist/server/cors.test.js +0 -77
  274. package/dist/server/documentReviewRoutes.test.d.ts +0 -1
  275. package/dist/server/documentReviewRoutes.test.js +0 -190
  276. package/dist/server/email.test.d.ts +0 -1
  277. package/dist/server/email.test.js +0 -113
  278. package/dist/server/index.cookieProxy.test.d.ts +0 -1
  279. package/dist/server/index.cookieProxy.test.js +0 -67
  280. package/dist/server/index.rateLimit.test.d.ts +0 -1
  281. package/dist/server/index.rateLimit.test.js +0 -22
  282. package/dist/server/index.test.d.ts +0 -1
  283. package/dist/server/index.test.js +0 -283
  284. package/dist/server/rateLimit.test.d.ts +0 -1
  285. package/dist/server/rateLimit.test.js +0 -107
  286. package/dist/server/realtimeSync.auth.test.d.ts +0 -1
  287. package/dist/server/realtimeSync.auth.test.js +0 -62
  288. package/dist/server/realtimeSync.replay.test.d.ts +0 -1
  289. package/dist/server/realtimeSync.replay.test.js +0 -192
  290. package/dist/server/realtimeSync.workspaceIsolation.test.d.ts +0 -1
  291. package/dist/server/realtimeSync.workspaceIsolation.test.js +0 -101
  292. package/dist/server/routes/billing.test.d.ts +0 -1
  293. package/dist/server/routes/billing.test.js +0 -3515
  294. package/dist/server/routes/shared.test.d.ts +0 -1
  295. package/dist/server/routes/shared.test.js +0 -32
  296. package/dist/server/routes/sync.integration.test.d.ts +0 -1
  297. package/dist/server/routes/sync.integration.test.js +0 -2932
  298. package/dist/server/routes/syncAuxRoutes.test.d.ts +0 -1
  299. package/dist/server/routes/syncAuxRoutes.test.js +0 -158
  300. package/dist/server/routes/syncRouteGuards.test.d.ts +0 -1
  301. package/dist/server/routes/syncRouteGuards.test.js +0 -94
  302. package/dist/server/routes.test.d.ts +0 -1
  303. package/dist/server/routes.test.js +0 -15612
  304. package/dist/server/runtimeMode.test.d.ts +0 -1
  305. package/dist/server/runtimeMode.test.js +0 -26
  306. package/dist/server/securityHeaders.test.d.ts +0 -1
  307. package/dist/server/securityHeaders.test.js +0 -70
  308. package/dist/services/templateEngine.test.d.ts +0 -1
  309. package/dist/services/templateEngine.test.js +0 -150
  310. package/dist/services/templates.test.d.ts +0 -1
  311. package/dist/services/templates.test.js +0 -45
  312. package/dist/services/workflowDocumentFixtures.test.d.ts +0 -1
  313. package/dist/services/workflowDocumentFixtures.test.js +0 -78
  314. package/dist/services/workflowExportMatrix.test.d.ts +0 -1
  315. package/dist/services/workflowExportMatrix.test.js +0 -113
  316. package/dist/services/workflowExportSnapshots.test.d.ts +0 -1
  317. package/dist/services/workflowExportSnapshots.test.js +0 -30
  318. package/dist/services/workflowGeneration.test.d.ts +0 -1
  319. package/dist/services/workflowGeneration.test.js +0 -12
  320. package/dist/services/workflowSimulation.test.d.ts +0 -1
  321. package/dist/services/workflowSimulation.test.js +0 -113
  322. package/dist/services/workflowV2Policy.test.d.ts +0 -1
  323. package/dist/services/workflowV2Policy.test.js +0 -80
  324. package/dist/shared/customTaxonomyLibrary.test.d.ts +0 -1
  325. package/dist/shared/customTaxonomyLibrary.test.js +0 -20
  326. package/dist/shared/passwordPolicy.test.d.ts +0 -1
  327. package/dist/shared/passwordPolicy.test.js +0 -34
  328. package/dist/shared/runtimeContract.test.d.ts +0 -1
  329. package/dist/shared/runtimeContract.test.js +0 -23
  330. package/dist/shared/taxonomyLibrary.test.d.ts +0 -1
  331. package/dist/shared/taxonomyLibrary.test.js +0 -26
  332. package/dist/shared/taxonomyState.test.d.ts +0 -1
  333. package/dist/shared/taxonomyState.test.js +0 -71
  334. package/dist/soak/preflight.cli.d.ts +0 -1
  335. package/dist/soak/preflight.cli.js +0 -28
  336. package/dist/soak/preflight.d.ts +0 -26
  337. package/dist/soak/preflight.js +0 -113
  338. package/dist/soak/preflight.test.d.ts +0 -1
  339. package/dist/soak/preflight.test.js +0 -39
  340. package/dist/storage/objectStorageClient.test.d.ts +0 -1
  341. package/dist/storage/objectStorageClient.test.js +0 -82
  342. package/dist/storage/objectStorageConfig.test.d.ts +0 -1
  343. package/dist/storage/objectStorageConfig.test.js +0 -50
  344. package/dist/storage/postgresAdapter.test.d.ts +0 -1
  345. package/dist/storage/postgresAdapter.test.js +0 -29
  346. package/dist/storage/workspaceAssetStore.test.d.ts +0 -1
  347. package/dist/storage/workspaceAssetStore.test.js +0 -588
  348. package/dist/sync/cloudSyncApi.test.d.ts +0 -1
  349. package/dist/sync/cloudSyncApi.test.js +0 -111
  350. package/dist/sync/collaborationSyncPayload.test.d.ts +0 -1
  351. package/dist/sync/collaborationSyncPayload.test.js +0 -125
  352. package/dist/sync/contentSyncPayload.test.d.ts +0 -1
  353. package/dist/sync/contentSyncPayload.test.js +0 -143
  354. package/dist/sync/encryptionService.test.d.ts +0 -1
  355. package/dist/sync/encryptionService.test.js +0 -52
  356. package/dist/sync/planningSyncPayload.test.d.ts +0 -1
  357. package/dist/sync/planningSyncPayload.test.js +0 -92
  358. package/dist/sync/realtimeFeature.test.d.ts +0 -1
  359. package/dist/sync/realtimeFeature.test.js +0 -30
  360. package/dist/sync/realtimeReconcile.test.d.ts +0 -1
  361. package/dist/sync/realtimeReconcile.test.js +0 -43
  362. package/dist/sync/syncApplyHandlers.test.d.ts +0 -1
  363. package/dist/sync/syncApplyHandlers.test.js +0 -626
  364. package/dist/sync/syncService.test.d.ts +0 -1
  365. package/dist/sync/syncService.test.js +0 -903
  366. package/dist/sync/taskSyncPayload.test.d.ts +0 -1
  367. package/dist/sync/taskSyncPayload.test.js +0 -54
  368. package/dist/sync/workspacePullFeed.test.d.ts +0 -1
  369. package/dist/sync/workspacePullFeed.test.js +0 -438
  370. package/dist/sync/workspaceSyncModel.test.d.ts +0 -1
  371. package/dist/sync/workspaceSyncModel.test.js +0 -1442
  372. package/dist/test/setup.d.ts +0 -1
  373. package/dist/test/setup.js +0 -15
  374. package/dist/ui/assets/documentReferences-BPWp6ce0.js +0 -1
  375. package/dist/ui/assets/index-zvbo2Jse.js +0 -5
  376. package/dist/utils/accountProfileSummaryCache.test.d.ts +0 -1
  377. package/dist/utils/accountProfileSummaryCache.test.js +0 -63
  378. package/dist/utils/annotatedAttachments.test.d.ts +0 -1
  379. package/dist/utils/annotatedAttachments.test.js +0 -108
  380. package/dist/utils/assignees.test.d.ts +0 -1
  381. package/dist/utils/assignees.test.js +0 -25
  382. package/dist/utils/avatarUpload.test.d.ts +0 -1
  383. package/dist/utils/avatarUpload.test.js +0 -40
  384. package/dist/utils/canonicalAssetKind.test.d.ts +0 -1
  385. package/dist/utils/canonicalAssetKind.test.js +0 -24
  386. package/dist/utils/coalescedRefresh.test.d.ts +0 -1
  387. package/dist/utils/coalescedRefresh.test.js +0 -30
  388. package/dist/utils/constants.icons.test.d.ts +0 -1
  389. package/dist/utils/constants.icons.test.js +0 -41
  390. package/dist/utils/planParser.test.d.ts +0 -1
  391. package/dist/utils/planParser.test.js +0 -183
  392. package/dist/utils/planningReferences.test.d.ts +0 -1
  393. package/dist/utils/planningReferences.test.js +0 -17
  394. package/dist/utils/supportContact.test.d.ts +0 -1
  395. package/dist/utils/supportContact.test.js +0 -19
  396. package/dist/utils/syncEventDetails.test.d.ts +0 -1
  397. package/dist/utils/syncEventDetails.test.js +0 -23
  398. package/dist/utils/syncEventPresentation.test.d.ts +0 -1
  399. package/dist/utils/syncEventPresentation.test.js +0 -64
  400. package/dist/utils/syncStatusPresentation.test.d.ts +0 -1
  401. package/dist/utils/syncStatusPresentation.test.js +0 -164
  402. package/dist/utils/taskActivity.test.d.ts +0 -1
  403. package/dist/utils/taskActivity.test.js +0 -218
  404. package/dist/utils/taskDraftDefaults.test.d.ts +0 -1
  405. package/dist/utils/taskDraftDefaults.test.js +0 -34
  406. package/dist/utils/taskDragZones.test.d.ts +0 -1
  407. package/dist/utils/taskDragZones.test.js +0 -14
  408. package/dist/utils/taskNormalization.test.d.ts +0 -1
  409. package/dist/utils/taskNormalization.test.js +0 -158
  410. package/dist/utils/taskSearch.test.d.ts +0 -1
  411. package/dist/utils/taskSearch.test.js +0 -69
  412. package/dist/utils/taskUtils.test.d.ts +0 -1
  413. package/dist/utils/taskUtils.test.js +0 -19
  414. package/dist/utils/taskforceApiClient.test.d.ts +0 -1
  415. package/dist/utils/taskforceApiClient.test.js +0 -44
  416. package/dist/utils/taxonomyFormDefaults.test.d.ts +0 -1
  417. package/dist/utils/taxonomyFormDefaults.test.js +0 -63
  418. package/dist/utils/uiNotice.test.d.ts +0 -1
  419. package/dist/utils/uiNotice.test.js +0 -18
  420. package/dist/utils/workspaceIdentity.test.d.ts +0 -1
  421. package/dist/utils/workspaceIdentity.test.js +0 -61
  422. package/dist/utils/workspaceLifecycle.test.d.ts +0 -1
  423. package/dist/utils/workspaceLifecycle.test.js +0 -64
  424. package/dist/utils/workspaceSyncPresentation.test.d.ts +0 -1
  425. package/dist/utils/workspaceSyncPresentation.test.js +0 -71
  426. package/scripts/bootstrap-playwright-auth.mjs +0 -155
  427. package/scripts/check-cloud-mcp.mjs +0 -83
  428. package/scripts/check-compat-boundaries.mjs +0 -41
  429. package/scripts/check-env.ts +0 -31
  430. package/scripts/check-localization-strings.mjs +0 -49
  431. package/scripts/check-runtime-boundaries.mjs +0 -44
  432. package/scripts/debug-global.mjs +0 -34
  433. package/scripts/debug-login.mjs +0 -32
  434. package/scripts/debug-server.mjs +0 -36
  435. package/scripts/deploy-prod.sh +0 -41
  436. package/scripts/deploy-staging.sh +0 -23
  437. package/scripts/export-sqlite-to-postgres.mjs +0 -123
  438. package/scripts/generate-env-example.ts +0 -8
  439. package/scripts/mcp-stdio-adapter.mjs +0 -160
  440. package/scripts/migrate-task-data-assets.mjs +0 -408
  441. package/scripts/playwright-auth.sh +0 -19
  442. package/scripts/qa-open-lib.mjs +0 -175
  443. package/scripts/qa-open-lib.mjs.d.ts +0 -49
  444. package/scripts/qa-open.mjs +0 -352
  445. package/scripts/release-version-lib.mjs +0 -88
  446. package/scripts/release-version.mjs +0 -72
  447. package/scripts/run-billing-performance-smoke.sh +0 -24
  448. package/scripts/run-dev-proxy.sh +0 -51
  449. package/scripts/run-e2e-realtime.sh +0 -30
  450. package/scripts/run-smoke.sh +0 -35
  451. package/scripts/run-soak.sh +0 -41
  452. package/scripts/test-global.js +0 -143
  453. package/scripts/update-homebrew-formula.sh +0 -108
  454. package/scripts/with-esbuild.sh +0 -11
@@ -1,1106 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import fs from 'fs';
3
- import os from 'os';
4
- import path from 'path';
5
- import { TaskforceCore } from './Taskforce';
6
- function makeProjectRoot() {
7
- return fs.mkdtempSync(path.join(os.tmpdir(), 'taskforce-system-admin-'));
8
- }
9
- function insertFreePriceMapping(core, planVersionId) {
10
- const db = core.db;
11
- const now = new Date().toISOString();
12
- db.prepare(`
13
- INSERT OR IGNORE INTO billing_price_mappings (
14
- tenant_id, plan_version_id, billing_interval, stripe_price_id, pricing_type, active, unit_amount, currency, created_at, updated_at
15
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
16
- `).run(core.getTenantId(), planVersionId, 'month', `free:${planVersionId}:month`, 'free', 1, 0, null, now, now);
17
- }
18
- function seedPlanCatalog(core) {
19
- core.upsertPlanCatalog({ planId: 'free', displayName: 'Free', status: 'active' });
20
- core.upsertPlanCatalog({ planId: 'basic', displayName: 'Basic', status: 'active' });
21
- core.upsertPlanCatalog({ planId: 'team-5', displayName: 'Team 5', status: 'active' });
22
- core.updatePlanVersion({
23
- planId: 'basic',
24
- versionNumber: 1,
25
- seatLimit: null
26
- });
27
- core.updatePlanVersion({
28
- planId: 'team-5',
29
- versionNumber: 1,
30
- seatLimit: 5
31
- });
32
- core.updatePlanVersionFeatures({
33
- planVersionId: 'free-v1',
34
- features: [
35
- { featureKey: 'workspace.workspaces', access: 'enabled' }
36
- ]
37
- });
38
- core.updatePlanVersionFeatures({
39
- planVersionId: 'basic-v1',
40
- features: [
41
- { featureKey: 'context.uploads', access: 'enabled' },
42
- { featureKey: 'workspace.workspaces', access: 'enabled' }
43
- ]
44
- });
45
- core.updatePlanVersionFeatures({
46
- planVersionId: 'team-5-v1',
47
- features: [
48
- { featureKey: 'collaboration.invites', access: 'enabled' },
49
- { featureKey: 'collaboration.team_management', access: 'enabled' },
50
- { featureKey: 'context.uploads', access: 'enabled' },
51
- { featureKey: 'workspace.workspaces', access: 'enabled' }
52
- ]
53
- });
54
- insertFreePriceMapping(core, 'free-v1');
55
- }
56
- function seedPlans(core) {
57
- seedPlanCatalog(core);
58
- core.updateGlobalSettings({
59
- billing: {
60
- onboardingPolicy: {
61
- mode: 'auto_grant_free',
62
- planId: 'free'
63
- }
64
- }
65
- });
66
- }
67
- function openCore(projectRoot, options = {}) {
68
- const core = new TaskforceCore({
69
- projectRoot,
70
- storagePath: path.join(projectRoot, '.taskforce')
71
- });
72
- if (options.seedDefaultPlans === true) {
73
- seedPlans(core);
74
- }
75
- return core;
76
- }
77
- describe('TaskforceCore system admin model', () => {
78
- it('creates billing row for registration without workspace', () => {
79
- const projectRoot = makeProjectRoot();
80
- const core = openCore(projectRoot, { seedDefaultPlans: true });
81
- try {
82
- const created = core.registerPasswordUser({
83
- email: 'billing-noworkspace@example.com',
84
- password: 'ValidPassphrase123!'
85
- });
86
- const db = core.db;
87
- const row = db.prepare(`
88
- SELECT user_id, tier, entitlement_state
89
- FROM user_billing
90
- WHERE tenant_id = ? AND user_id = ?
91
- LIMIT 1
92
- `).get('default', created.userId);
93
- expect(row?.user_id).toBe(created.userId);
94
- expect(row?.tier).toBe('free');
95
- expect(row?.entitlement_state).toBe('active');
96
- }
97
- finally {
98
- core.close();
99
- fs.rmSync(projectRoot, { recursive: true, force: true });
100
- }
101
- });
102
- it('creates billing row for registration with workspace', () => {
103
- const projectRoot = makeProjectRoot();
104
- const core = openCore(projectRoot, { seedDefaultPlans: true });
105
- try {
106
- core.createWorkspace({ workspaceId: 'workspace-1', name: 'Workspace 1' });
107
- const created = core.registerPasswordUser({
108
- email: 'billing-workspace@example.com',
109
- password: 'ValidPassphrase123!',
110
- workspaceId: 'workspace-1',
111
- role: 'member'
112
- });
113
- const db = core.db;
114
- const row = db.prepare(`
115
- SELECT user_id, tier, entitlement_state
116
- FROM user_billing
117
- WHERE tenant_id = ? AND user_id = ?
118
- LIMIT 1
119
- `).get('default', created.userId);
120
- expect(row?.user_id).toBe(created.userId);
121
- expect(row?.tier).toBe('free');
122
- expect(row?.entitlement_state).toBe('active');
123
- }
124
- finally {
125
- core.close();
126
- fs.rmSync(projectRoot, { recursive: true, force: true });
127
- }
128
- });
129
- it('starts a configured onboarding trial when the onboarding policy is trial-based', () => {
130
- const projectRoot = makeProjectRoot();
131
- const core = openCore(projectRoot, { seedDefaultPlans: true });
132
- try {
133
- core.createPlanVersion({
134
- planId: 'team-5',
135
- versionNumber: 2,
136
- isDefault: true,
137
- seatLimit: 10,
138
- trialDays: 14
139
- });
140
- core.updatePlanVersionFeatures({
141
- planVersionId: 'team-5-v2',
142
- features: [
143
- { featureKey: 'collaboration.invites', access: 'enabled' },
144
- { featureKey: 'collaboration.team_management', access: 'enabled' },
145
- { featureKey: 'context.uploads', access: 'enabled' },
146
- { featureKey: 'workspace.workspaces', access: 'enabled' }
147
- ]
148
- });
149
- core.createPlanVersion({
150
- planId: 'free',
151
- versionNumber: 2,
152
- isDefault: false,
153
- seatLimit: null
154
- });
155
- core.updateGlobalSettings({
156
- billing: {
157
- onboardingPolicy: {
158
- mode: 'auto_start_trial',
159
- planId: 'team-5'
160
- }
161
- }
162
- });
163
- const created = core.registerPasswordUser({
164
- email: 'configured-signup@example.com',
165
- password: 'ValidPassphrase123!'
166
- });
167
- const entitlement = core.getAccountEntitlement(created.userId);
168
- const db = core.db;
169
- const billing = db.prepare(`
170
- SELECT tier, entitlement_state
171
- FROM user_billing
172
- WHERE tenant_id = ? AND user_id = ?
173
- LIMIT 1
174
- `).get(core.getTenantId(), created.userId);
175
- expect(entitlement.planVersionId).toBe('team-5-v2');
176
- expect(entitlement.planId).toBe('team-5');
177
- expect(entitlement.state).toBe('trialing');
178
- expect(billing?.tier).toBe('team-5');
179
- expect(billing?.entitlement_state).toBe('trialing');
180
- }
181
- finally {
182
- core.close();
183
- fs.rmSync(projectRoot, { recursive: true, force: true });
184
- }
185
- });
186
- it('fails registration when the onboarding policy is stale or invalid', () => {
187
- const projectRoot = makeProjectRoot();
188
- const core = openCore(projectRoot, { seedDefaultPlans: true });
189
- try {
190
- core.updateGlobalSettings({
191
- billing: {
192
- onboardingPolicy: {
193
- mode: 'auto_grant_free',
194
- planId: 'missing'
195
- }
196
- }
197
- });
198
- expect(() => core.registerPasswordUser({
199
- email: 'fallback-signup@example.com',
200
- password: 'ValidPassphrase123!'
201
- })).toThrowError(/was not found/i);
202
- }
203
- finally {
204
- core.close();
205
- fs.rmSync(projectRoot, { recursive: true, force: true });
206
- }
207
- });
208
- it('registers without an entitlement when plan selection is required', () => {
209
- const projectRoot = makeProjectRoot();
210
- const core = openCore(projectRoot, { seedDefaultPlans: true });
211
- try {
212
- core.updateGlobalSettings({
213
- billing: {
214
- onboardingPolicy: {
215
- mode: 'require_plan_selection',
216
- planId: null
217
- }
218
- }
219
- });
220
- const created = core.registerPasswordUser({
221
- email: 'pending-plan@example.com',
222
- password: 'ValidPassphrase123!'
223
- });
224
- expect(created.planSelectionRequired).toBe(true);
225
- expect(core.hasAccountEntitlement(created.userId)).toBe(true);
226
- const db = core.db;
227
- const billing = db.prepare(`
228
- SELECT entitlement_state, tier
229
- FROM user_billing
230
- WHERE tenant_id = ? AND user_id = ?
231
- LIMIT 1
232
- `).get('default', created.userId);
233
- expect(billing?.entitlement_state).toBe('pending_plan_selection');
234
- expect(String(billing?.tier || '')).toBe('');
235
- expect(core.getAccountAccessStatus(created.userId)).toMatchObject({
236
- gate: 'plan_selection_required',
237
- hasEntitlement: true,
238
- canAccessApp: false,
239
- canAccessPlans: true
240
- });
241
- }
242
- finally {
243
- core.close();
244
- fs.rmSync(projectRoot, { recursive: true, force: true });
245
- }
246
- });
247
- it('removes legacy documents.workspace_access plan feature rows during startup normalization', () => {
248
- const projectRoot = makeProjectRoot();
249
- let core = openCore(projectRoot, { seedDefaultPlans: true });
250
- try {
251
- const db = core.db;
252
- const now = new Date().toISOString();
253
- db.prepare(`
254
- INSERT INTO plan_feature_matrix (tenant_id, plan_version_id, feature_key, access, config_json, created_at, updated_at)
255
- VALUES (?, ?, ?, ?, ?, ?, ?)
256
- ON CONFLICT (tenant_id, plan_version_id, feature_key) DO UPDATE SET
257
- access = excluded.access,
258
- config_json = excluded.config_json,
259
- updated_at = excluded.updated_at
260
- `).run('default', 'team-5-v1', 'documents.workspace_access', 'enabled', '{}', now, now);
261
- }
262
- finally {
263
- core.close();
264
- }
265
- core = openCore(projectRoot);
266
- try {
267
- const db = core.db;
268
- const row = db.prepare(`
269
- SELECT feature_key
270
- FROM plan_feature_matrix
271
- WHERE tenant_id = ? AND plan_version_id = ? AND feature_key = ?
272
- LIMIT 1
273
- `).get('default', 'team-5-v1', 'documents.workspace_access');
274
- expect(row).toBeUndefined();
275
- const snapshot = core.listPlanVersions('team-5').find((version) => version.planVersionId === 'team-5-v1');
276
- expect(snapshot).toBeDefined();
277
- expect(snapshot.features['documents.workspace_access']).toBeUndefined();
278
- }
279
- finally {
280
- core.close();
281
- fs.rmSync(projectRoot, { recursive: true, force: true });
282
- }
283
- });
284
- it('keeps configured auto-grant onboarding plan when registration includes a selected plan version', () => {
285
- const projectRoot = makeProjectRoot();
286
- const core = openCore(projectRoot, { seedDefaultPlans: true });
287
- try {
288
- core.updatePlanVersion({
289
- planId: 'basic',
290
- versionNumber: 1,
291
- seatLimit: null
292
- });
293
- core.updateGlobalSettings({
294
- billing: {
295
- onboardingPolicy: {
296
- mode: 'auto_grant_free',
297
- planId: 'free'
298
- }
299
- }
300
- });
301
- const created = core.registerPasswordUser({
302
- email: 'selected-plan@example.com',
303
- password: 'ValidPassphrase123!',
304
- planVersionId: 'basic-v1'
305
- });
306
- expect(created.planSelectionRequired).toBe(false);
307
- expect(core.getAccountEntitlement(created.userId)).toMatchObject({
308
- planVersionId: 'free-v1',
309
- planId: 'free',
310
- state: 'active'
311
- });
312
- }
313
- finally {
314
- core.close();
315
- fs.rmSync(projectRoot, { recursive: true, force: true });
316
- }
317
- });
318
- it('uses the explicitly selected plan version when onboarding requires plan selection', () => {
319
- const projectRoot = makeProjectRoot();
320
- const core = openCore(projectRoot, { seedDefaultPlans: true });
321
- try {
322
- core.updatePlanVersion({
323
- planId: 'basic',
324
- versionNumber: 1,
325
- seatLimit: null
326
- });
327
- core.updateGlobalSettings({
328
- billing: {
329
- onboardingPolicy: {
330
- mode: 'require_plan_selection',
331
- planId: null
332
- }
333
- }
334
- });
335
- const created = core.registerPasswordUser({
336
- email: 'selected-plan-required@example.com',
337
- password: 'ValidPassphrase123!',
338
- planVersionId: 'basic-v1'
339
- });
340
- expect(created.planSelectionRequired).toBe(false);
341
- expect(core.getAccountEntitlement(created.userId)).toMatchObject({
342
- planVersionId: 'basic-v1',
343
- planId: 'basic',
344
- state: 'checkout_pending'
345
- });
346
- }
347
- finally {
348
- core.close();
349
- fs.rmSync(projectRoot, { recursive: true, force: true });
350
- }
351
- });
352
- it('auto-heals stranded legacy personal free entitlements onto the active default free version', () => {
353
- const projectRoot = makeProjectRoot();
354
- const core = openCore(projectRoot, { seedDefaultPlans: true });
355
- try {
356
- core.upsertPlanCatalog({ planId: 'free', displayName: 'Free', status: 'active' });
357
- insertFreePriceMapping(core, 'free-v1');
358
- core.createPlanVersion({
359
- planId: 'free',
360
- versionNumber: 2,
361
- isDefault: true,
362
- seatLimit: 1
363
- });
364
- insertFreePriceMapping(core, 'free-v2');
365
- core.updateGlobalSettings({
366
- billing: {
367
- onboardingPolicy: {
368
- mode: 'require_plan_selection',
369
- planId: null
370
- }
371
- }
372
- });
373
- const created = core.registerPasswordUser({
374
- email: 'legacy-free@example.com',
375
- password: 'ValidPassphrase123!',
376
- planVersionId: 'free-v1'
377
- });
378
- core.updateAccountEntitlement({
379
- accountId: created.userId,
380
- planVersionId: 'free-v1',
381
- entitlementState: 'canceled',
382
- actorUserId: created.userId,
383
- actorRole: 'user',
384
- reason: 'legacy-free-v1-canceled'
385
- });
386
- expect(core.getAccountAccessStatus(created.userId)).toMatchObject({
387
- gate: 'ok',
388
- hasEntitlement: true,
389
- canAccessApp: true,
390
- entitlement: {
391
- planVersionId: 'free-v2',
392
- planId: 'free',
393
- state: 'active'
394
- }
395
- });
396
- expect(core.getAccountEntitlement(created.userId)).toMatchObject({
397
- planVersionId: 'free-v2',
398
- planId: 'free',
399
- state: 'active'
400
- });
401
- }
402
- finally {
403
- core.close();
404
- fs.rmSync(projectRoot, { recursive: true, force: true });
405
- }
406
- });
407
- it('does not auto-heal canceled accounts already on the current default free version', () => {
408
- const projectRoot = makeProjectRoot();
409
- const core = openCore(projectRoot, { seedDefaultPlans: true });
410
- try {
411
- core.upsertPlanCatalog({ planId: 'free', displayName: 'Free', status: 'active' });
412
- insertFreePriceMapping(core, 'free-v1');
413
- core.createPlanVersion({
414
- planId: 'free',
415
- versionNumber: 2,
416
- isDefault: true,
417
- seatLimit: 1
418
- });
419
- insertFreePriceMapping(core, 'free-v2');
420
- core.updateGlobalSettings({
421
- billing: {
422
- onboardingPolicy: {
423
- mode: 'require_plan_selection',
424
- planId: null
425
- }
426
- }
427
- });
428
- const created = core.registerPasswordUser({
429
- email: 'current-default-free@example.com',
430
- password: 'ValidPassphrase123!',
431
- planVersionId: 'free-v2'
432
- });
433
- core.updateAccountEntitlement({
434
- accountId: created.userId,
435
- planVersionId: 'free-v2',
436
- entitlementState: 'canceled',
437
- actorUserId: created.userId,
438
- actorRole: 'user',
439
- reason: 'intentional-free-v2-cancel'
440
- });
441
- expect(core.getAccountAccessStatus(created.userId)).toMatchObject({
442
- gate: 'missing_entitlement',
443
- hasEntitlement: true,
444
- canAccessApp: false,
445
- entitlement: {
446
- planVersionId: 'free-v2',
447
- planId: 'free',
448
- state: 'canceled'
449
- }
450
- });
451
- expect(core.getAccountEntitlement(created.userId)).toMatchObject({
452
- planVersionId: 'free-v2',
453
- planId: 'free',
454
- state: 'canceled'
455
- });
456
- }
457
- finally {
458
- core.close();
459
- fs.rmSync(projectRoot, { recursive: true, force: true });
460
- }
461
- });
462
- it('treats accounts without an entitlement or pending-selection marker as missing entitlement', () => {
463
- const projectRoot = makeProjectRoot();
464
- const core = openCore(projectRoot, { seedDefaultPlans: true });
465
- try {
466
- core.updateGlobalSettings({
467
- billing: {
468
- onboardingPolicy: {
469
- mode: 'require_plan_selection',
470
- planId: null
471
- }
472
- }
473
- });
474
- const now = new Date().toISOString();
475
- const db = core.db;
476
- db.prepare(`
477
- INSERT INTO users (id, tenant_id, email, display_name, system_role, beta_access, is_disabled, created_at, updated_at)
478
- VALUES (?, ?, ?, ?, 'user', 1, 0, ?, ?)
479
- `).run('user-missing-entitlement', 'default', 'broken@example.com', 'Broken', now, now);
480
- expect(core.getAccountAccessStatus('user-missing-entitlement')).toMatchObject({
481
- gate: 'missing_entitlement',
482
- hasEntitlement: false,
483
- canAccessApp: false,
484
- planSelectionRequired: false
485
- });
486
- }
487
- finally {
488
- core.close();
489
- fs.rmSync(projectRoot, { recursive: true, force: true });
490
- }
491
- });
492
- it('repairs missing default version during startup for existing plans', () => {
493
- const projectRoot = makeProjectRoot();
494
- const initial = openCore(projectRoot, { seedDefaultPlans: true });
495
- try {
496
- const db = initial.db;
497
- db.prepare(`
498
- UPDATE plan_versions
499
- SET is_default = 0
500
- WHERE tenant_id = ? AND plan_id = ?
501
- `).run('default', 'free');
502
- }
503
- finally {
504
- initial.close();
505
- }
506
- const reopened = openCore(projectRoot, { seedDefaultPlans: false });
507
- try {
508
- const db = reopened.db;
509
- const row = db.prepare(`
510
- SELECT plan_version_id, is_default
511
- FROM plan_versions
512
- WHERE tenant_id = ? AND plan_id = ? AND plan_version_id = ?
513
- LIMIT 1
514
- `).get('default', 'free', 'free-v1');
515
- expect(row?.plan_version_id).toBe('free-v1');
516
- expect(row?.is_default).toBe(1);
517
- expect(reopened.getRequiredDefaultPlanVersionId('free')).toBe('free-v1');
518
- }
519
- finally {
520
- reopened.close();
521
- fs.rmSync(projectRoot, { recursive: true, force: true });
522
- }
523
- });
524
- it('preserves require_plan_selection onboarding policy across restart', () => {
525
- const projectRoot = makeProjectRoot();
526
- const initial = openCore(projectRoot, { seedDefaultPlans: true });
527
- try {
528
- initial.updateGlobalSettings({
529
- billing: {
530
- onboardingPolicy: {
531
- mode: 'require_plan_selection',
532
- planId: null
533
- }
534
- }
535
- });
536
- }
537
- finally {
538
- initial.close();
539
- }
540
- const reopened = openCore(projectRoot, { seedDefaultPlans: false });
541
- try {
542
- expect(reopened.getOnboardingPolicy()).toEqual({
543
- mode: 'require_plan_selection',
544
- planId: null
545
- });
546
- }
547
- finally {
548
- reopened.close();
549
- fs.rmSync(projectRoot, { recursive: true, force: true });
550
- }
551
- });
552
- it('migrates legacy seat-limit feature config to version seatLimit during startup', () => {
553
- const projectRoot = makeProjectRoot();
554
- const initial = openCore(projectRoot, { seedDefaultPlans: true });
555
- try {
556
- const db = initial.db;
557
- const now = new Date().toISOString();
558
- db.prepare(`
559
- UPDATE plan_versions
560
- SET seat_limit = NULL, updated_at = ?
561
- WHERE tenant_id = ? AND plan_version_id = ?
562
- `).run(now, 'default', 'team-5-v1');
563
- db.prepare(`
564
- INSERT INTO plan_feature_matrix (tenant_id, plan_version_id, feature_key, access, config_json, created_at, updated_at)
565
- VALUES (?, ?, 'collaboration.seat_limit', 'limited', ?, ?, ?)
566
- ON CONFLICT (tenant_id, plan_version_id, feature_key) DO UPDATE SET
567
- access = excluded.access,
568
- config_json = excluded.config_json,
569
- updated_at = excluded.updated_at
570
- `).run('default', 'team-5-v1', JSON.stringify({ seat_limit: 7 }), now, now);
571
- }
572
- finally {
573
- initial.close();
574
- }
575
- const reopened = openCore(projectRoot, { seedDefaultPlans: false });
576
- try {
577
- const version = reopened.getPlanVersionFeatures('team-5', 'team-5-v1');
578
- expect(version.seatLimit).toBe(7);
579
- expect(version.features['collaboration.seat_limit']?.config || {}).toEqual({});
580
- }
581
- finally {
582
- reopened.close();
583
- fs.rmSync(projectRoot, { recursive: true, force: true });
584
- }
585
- });
586
- it('normalizes legacy billing mirror rows during startup', () => {
587
- const projectRoot = makeProjectRoot();
588
- const initial = openCore(projectRoot, { seedDefaultPlans: true });
589
- let legacyUserId = '';
590
- try {
591
- const created = initial.registerPasswordUser({
592
- email: 'legacy-billing@example.com',
593
- password: 'ValidPassphrase123!'
594
- });
595
- legacyUserId = created.userId;
596
- const db = initial.db;
597
- db.prepare(`
598
- UPDATE user_billing
599
- SET tier = 'free',
600
- entitlement_state = 'free'
601
- WHERE tenant_id = ? AND user_id = ?
602
- `).run('default', legacyUserId);
603
- }
604
- finally {
605
- initial.close();
606
- }
607
- const reopened = openCore(projectRoot, { seedDefaultPlans: false });
608
- try {
609
- const db = reopened.db;
610
- const normalizedPersonalVersionId = reopened.getDefaultPlanVersionIdByMode('personal');
611
- const normalizedBillingMirror = reopened.getBillingMirrorProfileForPlanVersion(normalizedPersonalVersionId);
612
- const row = db.prepare(`
613
- SELECT tier, entitlement_state
614
- FROM user_billing
615
- WHERE tenant_id = ? AND user_id = ?
616
- LIMIT 1
617
- `).get('default', legacyUserId);
618
- expect(row?.tier).toBe(normalizedBillingMirror.planId);
619
- expect(row?.entitlement_state).toBe('active');
620
- }
621
- finally {
622
- reopened.close();
623
- fs.rmSync(projectRoot, { recursive: true, force: true });
624
- }
625
- });
626
- it('does not seed business plans into an empty catalog on startup', () => {
627
- const projectRoot = makeProjectRoot();
628
- const core = openCore(projectRoot, { seedDefaultPlans: false });
629
- try {
630
- expect(core.listPlanCatalog()).toEqual([]);
631
- }
632
- finally {
633
- core.close();
634
- fs.rmSync(projectRoot, { recursive: true, force: true });
635
- }
636
- });
637
- it('keeps a custom catalog untouched on startup', () => {
638
- const projectRoot = makeProjectRoot();
639
- const initial = openCore(projectRoot, { seedDefaultPlans: false });
640
- try {
641
- const db = initial.db;
642
- const now = new Date().toISOString();
643
- db.prepare(`DELETE FROM plan_feature_matrix WHERE tenant_id = ?`).run('default');
644
- db.prepare(`DELETE FROM plan_versions WHERE tenant_id = ?`).run('default');
645
- db.prepare(`DELETE FROM plan_catalog WHERE tenant_id = ?`).run('default');
646
- db.prepare(`
647
- INSERT INTO plan_catalog (plan_id, tenant_id, display_name, status, created_at, updated_at)
648
- VALUES (?, ?, ?, 'active', ?, ?)
649
- `).run('agency', 'default', 'Agency', now, now);
650
- db.prepare(`
651
- INSERT INTO plan_versions (plan_version_id, tenant_id, plan_id, version_number, is_default, seat_limit, metadata_json, created_at, updated_at)
652
- VALUES (?, ?, ?, 1, 1, 25, '{}', ?, ?)
653
- `).run('agency-v1', 'default', 'agency', now, now);
654
- }
655
- finally {
656
- initial.close();
657
- }
658
- const reopened = openCore(projectRoot, { seedDefaultPlans: false });
659
- try {
660
- const plans = reopened.listPlanCatalog();
661
- expect(plans.map((plan) => plan.planId)).toEqual(['agency']);
662
- expect(reopened.getDefaultPlanVersionIdByMode('personal')).toBe('agency-v1');
663
- }
664
- finally {
665
- reopened.close();
666
- fs.rmSync(projectRoot, { recursive: true, force: true });
667
- }
668
- });
669
- it('assigns first registered user as system_admin and later users as user', () => {
670
- const projectRoot = makeProjectRoot();
671
- const core = openCore(projectRoot, { seedDefaultPlans: true });
672
- try {
673
- const first = core.registerPasswordUser({
674
- email: 'first@example.com',
675
- password: 'ValidPassphrase123!'
676
- });
677
- const second = core.registerPasswordUser({
678
- email: 'second@example.com',
679
- password: 'ValidPassphrase123!'
680
- });
681
- expect(core.getSystemRole(first.userId)).toBe('system_admin');
682
- expect(core.getSystemRole(second.userId)).toBe('user');
683
- expect(core.countSystemAdmins()).toBe(1);
684
- }
685
- finally {
686
- core.close();
687
- fs.rmSync(projectRoot, { recursive: true, force: true });
688
- }
689
- });
690
- it('applies registration betaAccess at user level', () => {
691
- const projectRoot = makeProjectRoot();
692
- const core = openCore(projectRoot, { seedDefaultPlans: true });
693
- try {
694
- core.createWorkspace({ workspaceId: 'workspace-1', name: 'Workspace 1' });
695
- core.registerPasswordUser({
696
- email: 'bootstrap-admin@example.com',
697
- password: 'ValidPassphrase123!'
698
- });
699
- const user = core.registerPasswordUser({
700
- email: 'beta-off@example.com',
701
- password: 'ValidPassphrase123!',
702
- workspaceId: 'workspace-1',
703
- role: 'owner',
704
- betaAccess: false
705
- });
706
- expect(core.getUserBetaAccess(user.userId)).toBe(false);
707
- expect(core.setUserBetaAccessGlobal(user.userId, true)).toBe(true);
708
- expect(core.getUserBetaAccess(user.userId)).toBe(true);
709
- }
710
- finally {
711
- core.close();
712
- fs.rmSync(projectRoot, { recursive: true, force: true });
713
- }
714
- });
715
- it('enforces last-system-admin guard on demote and disable', () => {
716
- const projectRoot = makeProjectRoot();
717
- const core = openCore(projectRoot, { seedDefaultPlans: true });
718
- try {
719
- const first = core.registerPasswordUser({
720
- email: 'first@example.com',
721
- password: 'ValidPassphrase123!'
722
- });
723
- expect(() => core.setSystemRole(first.userId, 'user')).toThrowError(/last system admin/i);
724
- expect(() => core.setUserDisabledGlobal(first.userId, true)).toThrowError(/last system admin/i);
725
- }
726
- finally {
727
- core.close();
728
- fs.rmSync(projectRoot, { recursive: true, force: true });
729
- }
730
- });
731
- it('does not auto-promote system role from workspace role changes post-migration', () => {
732
- const projectRoot = makeProjectRoot();
733
- const core = openCore(projectRoot, { seedDefaultPlans: true });
734
- try {
735
- const first = core.registerPasswordUser({
736
- email: 'first@example.com',
737
- password: 'ValidPassphrase123!'
738
- });
739
- const second = core.registerPasswordUser({
740
- email: 'second@example.com',
741
- password: 'ValidPassphrase123!'
742
- });
743
- core.createWorkspace({ workspaceId: 'workspace-1', name: 'Workspace 1' });
744
- core.ensureBootstrapUserAccess({
745
- userId: second.userId,
746
- workspaceId: 'workspace-1',
747
- role: 'member',
748
- email: 'second@example.com'
749
- });
750
- expect(core.updateWorkspaceUserRole(second.userId, 'workspace-1', 'admin')).toBe(true);
751
- expect(core.getSystemRole(second.userId)).toBe('user');
752
- expect(core.getSystemRole(first.userId)).toBe('system_admin');
753
- }
754
- finally {
755
- core.close();
756
- fs.rmSync(projectRoot, { recursive: true, force: true });
757
- }
758
- });
759
- it('lists system users without ambiguous updated_at ordering', () => {
760
- const projectRoot = makeProjectRoot();
761
- const core = openCore(projectRoot, { seedDefaultPlans: true });
762
- try {
763
- const admin = core.registerPasswordUser({
764
- email: 'admin@example.com',
765
- password: 'ValidPassphrase123!'
766
- });
767
- const member = core.registerPasswordUser({
768
- email: 'member@example.com',
769
- password: 'ValidPassphrase123!'
770
- });
771
- core.createPlanVersion({
772
- planId: 'free',
773
- versionNumber: 2,
774
- isDefault: true
775
- });
776
- core.updateAccountEntitlement({
777
- accountId: member.userId,
778
- planVersionId: 'free-v2',
779
- entitlementState: 'active',
780
- actorUserId: admin.userId,
781
- actorRole: 'system_admin'
782
- });
783
- const users = core.listSystemUsers();
784
- expect(users.map((user) => user.email)).toContain('admin@example.com');
785
- expect(users.map((user) => user.email)).toContain('member@example.com');
786
- }
787
- finally {
788
- core.close();
789
- fs.rmSync(projectRoot, { recursive: true, force: true });
790
- }
791
- });
792
- it('purges a non-production test user and clears active ownership references', () => {
793
- const projectRoot = makeProjectRoot();
794
- const core = openCore(projectRoot, { seedDefaultPlans: true });
795
- try {
796
- const admin = core.registerPasswordUser({
797
- email: 'admin@example.com',
798
- password: 'ValidPassphrase123!'
799
- });
800
- const member = core.registerPasswordUser({
801
- email: 'member@example.com',
802
- password: 'ValidPassphrase123!'
803
- });
804
- const workspaceId = member.workspaceId || 'default';
805
- const db = core.db;
806
- db.prepare(`
807
- UPDATE workspace_memberships
808
- SET role = 'member', updated_at = ?
809
- WHERE tenant_id = ? AND user_id = ?
810
- `).run(new Date().toISOString(), 'default', member.userId);
811
- const task = core.createTask({
812
- title: 'Signup test task',
813
- assignee: member.userId
814
- }, { workspaceId });
815
- const initiative = core.createInitiative({
816
- title: 'Signup initiative',
817
- ownerId: member.userId
818
- }, workspaceId);
819
- const workstream = core.createWorkstream({
820
- title: 'Signup workstream',
821
- ownerId: member.userId
822
- }, workspaceId);
823
- const now = new Date().toISOString();
824
- db.prepare(`
825
- INSERT INTO ui_state (tenant_id, workspace_id, user_id, state_key, payload, updated_at)
826
- VALUES (?, ?, ?, ?, ?, ?)
827
- `).run('default', workspaceId, member.userId, 'admin.users', '{"open":true}', now);
828
- db.prepare(`
829
- INSERT INTO user_preferences (tenant_id, user_id, prefs_key, payload, updated_at)
830
- VALUES (?, ?, ?, ?, ?)
831
- `).run('default', member.userId, 'profile', '{"theme":"light"}', now);
832
- const summary = core.purgeSystemUser({
833
- targetUserId: member.userId,
834
- actorUserId: admin.userId,
835
- emailConfirmation: 'member@example.com'
836
- });
837
- expect(summary).toMatchObject({
838
- targetUserId: member.userId,
839
- email: 'member@example.com',
840
- unassignedTaskCount: 1,
841
- clearedInitiativeOwnerCount: 1,
842
- clearedWorkstreamOwnerCount: 1,
843
- deletedUiStateCount: 1,
844
- deletedUserPreferenceCount: 1,
845
- deletedUserCount: 1
846
- });
847
- expect(db.prepare(`SELECT assignee FROM tasks WHERE id = ?`).get(task.id)).toMatchObject({
848
- assignee: 'unassigned'
849
- });
850
- expect(db.prepare(`SELECT owner_id FROM initiatives WHERE id = ?`).get(initiative.id)).toMatchObject({
851
- owner_id: null
852
- });
853
- expect(db.prepare(`SELECT owner_id FROM workstreams WHERE id = ?`).get(workstream.id)).toMatchObject({
854
- owner_id: null
855
- });
856
- expect(db.prepare(`SELECT COUNT(*) AS count FROM users WHERE tenant_id = ? AND id = ?`).get('default', member.userId)).toMatchObject({
857
- count: 0
858
- });
859
- expect(db.prepare(`SELECT COUNT(*) AS count FROM account_entitlements WHERE tenant_id = ? AND account_id = ?`).get('default', member.userId)).toMatchObject({
860
- count: 0
861
- });
862
- expect(db.prepare(`SELECT COUNT(*) AS count FROM ui_state WHERE tenant_id = ? AND user_id = ?`).get('default', member.userId)).toMatchObject({
863
- count: 0
864
- });
865
- expect(db.prepare(`SELECT COUNT(*) AS count FROM user_preferences WHERE tenant_id = ? AND user_id = ?`).get('default', member.userId)).toMatchObject({
866
- count: 0
867
- });
868
- }
869
- finally {
870
- core.close();
871
- fs.rmSync(projectRoot, { recursive: true, force: true });
872
- }
873
- });
874
- it('blocks purging a user with active commercial billing state', () => {
875
- const projectRoot = makeProjectRoot();
876
- const core = openCore(projectRoot, { seedDefaultPlans: true });
877
- try {
878
- const admin = core.registerPasswordUser({
879
- email: 'admin@example.com',
880
- password: 'ValidPassphrase123!'
881
- });
882
- const member = core.registerPasswordUser({
883
- email: 'member@example.com',
884
- password: 'ValidPassphrase123!'
885
- });
886
- const db = core.db;
887
- const now = new Date().toISOString();
888
- db.prepare(`
889
- UPDATE workspace_memberships
890
- SET role = 'member', updated_at = ?
891
- WHERE tenant_id = ? AND user_id = ?
892
- `).run(now, 'default', member.userId);
893
- db.prepare(`
894
- INSERT INTO user_billing (
895
- user_id, tenant_id, stripe_customer_id, entitlement_state, tier, created_at, updated_at
896
- ) VALUES (?, ?, ?, ?, ?, ?, ?)
897
- ON CONFLICT (user_id) DO UPDATE SET
898
- stripe_customer_id = excluded.stripe_customer_id,
899
- updated_at = excluded.updated_at
900
- `).run(member.userId, 'default', 'cus_test_123', 'active', 'basic', now, now);
901
- expect(() => core.purgeSystemUser({
902
- targetUserId: member.userId,
903
- actorUserId: admin.userId,
904
- emailConfirmation: 'member@example.com'
905
- })).toThrowError(/active commercial billing state/i);
906
- }
907
- finally {
908
- core.close();
909
- fs.rmSync(projectRoot, { recursive: true, force: true });
910
- }
911
- });
912
- it('creates v1 automatically when a new plan is created', () => {
913
- const projectRoot = makeProjectRoot();
914
- const core = openCore(projectRoot, { seedDefaultPlans: true });
915
- try {
916
- const plan = core.upsertPlanCatalog({
917
- planId: 'agency',
918
- displayName: 'Agency',
919
- status: 'active'
920
- });
921
- expect(plan.planId).toBe('agency');
922
- const versions = core.listPlanVersions('agency');
923
- expect(versions).toHaveLength(1);
924
- expect(versions[0]).toMatchObject({
925
- planVersionId: 'agency-v1',
926
- planId: 'agency',
927
- versionNumber: 1,
928
- isDefault: true,
929
- seatLimit: null
930
- });
931
- }
932
- finally {
933
- core.close();
934
- fs.rmSync(projectRoot, { recursive: true, force: true });
935
- }
936
- });
937
- it('does not recreate v1 when an existing plan is updated', () => {
938
- const projectRoot = makeProjectRoot();
939
- const core = openCore(projectRoot, { seedDefaultPlans: true });
940
- try {
941
- core.upsertPlanCatalog({
942
- planId: 'agency',
943
- displayName: 'Agency',
944
- status: 'active'
945
- });
946
- core.createPlanVersion({
947
- planId: 'agency',
948
- versionNumber: 2,
949
- isDefault: true,
950
- seatLimit: 25
951
- });
952
- core.upsertPlanCatalog({
953
- planId: 'agency',
954
- displayName: 'Agency Plus',
955
- status: 'archived'
956
- });
957
- const versions = core.listPlanVersions('agency');
958
- expect(versions.map((version) => version.planVersionId)).toEqual(['agency-v2', 'agency-v1']);
959
- expect(versions.find((version) => version.planVersionId === 'agency-v1')).toMatchObject({
960
- isDefault: false
961
- });
962
- }
963
- finally {
964
- core.close();
965
- fs.rmSync(projectRoot, { recursive: true, force: true });
966
- }
967
- });
968
- it('backfills owner/admin memberships once and does not rerun after completion', () => {
969
- const projectRoot = makeProjectRoot();
970
- const initial = openCore(projectRoot);
971
- try {
972
- initial.createWorkspace({ workspaceId: 'workspace-1', name: 'Workspace 1' });
973
- initial.ensureBootstrapUserAccess({
974
- userId: 'owner-1',
975
- workspaceId: 'workspace-1',
976
- role: 'owner',
977
- email: 'owner@example.com'
978
- });
979
- initial.ensureBootstrapUserAccess({
980
- userId: 'admin-1',
981
- workspaceId: 'workspace-1',
982
- role: 'admin',
983
- email: 'admin@example.com'
984
- });
985
- initial.ensureBootstrapUserAccess({
986
- userId: 'member-1',
987
- workspaceId: 'workspace-1',
988
- role: 'member',
989
- email: 'member@example.com'
990
- });
991
- const db = initial.db;
992
- db.prepare(`UPDATE users SET system_role = 'user' WHERE tenant_id = ?`).run('default');
993
- db.prepare(`DELETE FROM system_settings WHERE tenant_id = ? AND settings_key = ?`).run('default', 'migration.system_role.backfill.v1');
994
- }
995
- finally {
996
- initial.close();
997
- }
998
- const reopened = openCore(projectRoot);
999
- try {
1000
- expect(reopened.getSystemRole('owner-1')).toBe('system_admin');
1001
- expect(reopened.getSystemRole('admin-1')).toBe('system_admin');
1002
- expect(reopened.getSystemRole('member-1')).toBe('user');
1003
- expect(reopened.countSystemAdmins()).toBe(2);
1004
- expect(reopened.setSystemRole('owner-1', 'user')).toBe(true);
1005
- }
1006
- finally {
1007
- reopened.close();
1008
- }
1009
- const reopenedAgain = openCore(projectRoot);
1010
- try {
1011
- expect(reopenedAgain.getSystemRole('owner-1')).toBe('user');
1012
- expect(reopenedAgain.getSystemRole('admin-1')).toBe('system_admin');
1013
- expect(reopenedAgain.countSystemAdmins()).toBe(1);
1014
- }
1015
- finally {
1016
- reopenedAgain.close();
1017
- fs.rmSync(projectRoot, { recursive: true, force: true });
1018
- }
1019
- });
1020
- it('backfills user beta access from membership beta flags once', () => {
1021
- const projectRoot = makeProjectRoot();
1022
- const initial = openCore(projectRoot);
1023
- try {
1024
- initial.createWorkspace({ workspaceId: 'workspace-1', name: 'Workspace 1' });
1025
- initial.ensureBootstrapUserAccess({
1026
- userId: 'beta-on',
1027
- workspaceId: 'workspace-1',
1028
- role: 'member',
1029
- email: 'beta-on@example.com'
1030
- });
1031
- initial.ensureBootstrapUserAccess({
1032
- userId: 'beta-off',
1033
- workspaceId: 'workspace-1',
1034
- role: 'member',
1035
- email: 'beta-off@example.com'
1036
- });
1037
- initial.setWorkspaceUserBetaAccess('beta-off', 'workspace-1', false);
1038
- const db = initial.db;
1039
- db.prepare(`UPDATE users SET beta_access = 1 WHERE tenant_id = ?`).run('default');
1040
- db.prepare(`DELETE FROM system_settings WHERE tenant_id = ? AND settings_key = ?`).run('default', 'migration.user_beta_access.backfill.v1');
1041
- }
1042
- finally {
1043
- initial.close();
1044
- }
1045
- const reopened = openCore(projectRoot);
1046
- try {
1047
- expect(reopened.getUserBetaAccess('beta-on')).toBe(true);
1048
- expect(reopened.getUserBetaAccess('beta-off')).toBe(false);
1049
- expect(reopened.setUserBetaAccessGlobal('beta-off', true)).toBe(true);
1050
- }
1051
- finally {
1052
- reopened.close();
1053
- }
1054
- const reopenedAgain = openCore(projectRoot);
1055
- try {
1056
- expect(reopenedAgain.getUserBetaAccess('beta-off')).toBe(true);
1057
- }
1058
- finally {
1059
- reopenedAgain.close();
1060
- fs.rmSync(projectRoot, { recursive: true, force: true });
1061
- }
1062
- });
1063
- it('resolvePreferredUserWorkspaceId prefers explicit and persisted ids before fallback', () => {
1064
- const projectRoot = makeProjectRoot();
1065
- const core = openCore(projectRoot);
1066
- try {
1067
- core.createWorkspace({ workspaceId: 'workspace-a', name: 'Workspace A' });
1068
- core.createWorkspace({ workspaceId: 'workspace-b', name: 'Workspace B' });
1069
- core.ensureBootstrapUserAccess({
1070
- userId: 'member-1',
1071
- workspaceId: 'workspace-a',
1072
- role: 'member',
1073
- email: 'member-1@example.com'
1074
- });
1075
- core.ensureBootstrapUserAccess({
1076
- userId: 'member-1',
1077
- workspaceId: 'workspace-b',
1078
- role: 'member',
1079
- email: 'member-1@example.com'
1080
- });
1081
- expect(core.resolvePreferredUserWorkspaceId('member-1', { preferredWorkspaceId: 'workspace-b' })).toBe('workspace-b');
1082
- expect(core.resolvePreferredUserWorkspaceId('member-1', { persistedWorkspaceId: 'workspace-a' })).toBe('workspace-a');
1083
- expect(core.resolvePreferredUserWorkspaceId('member-1', { preferredWorkspaceId: 'workspace-missing', persistedWorkspaceId: 'workspace-b' })).toBe('workspace-b');
1084
- expect(core.resolvePreferredUserWorkspaceId('member-1')).toBeTruthy();
1085
- }
1086
- finally {
1087
- core.close();
1088
- fs.rmSync(projectRoot, { recursive: true, force: true });
1089
- }
1090
- });
1091
- it('resolvePreferredUserWorkspaceId returns null for users with no active memberships', () => {
1092
- const projectRoot = makeProjectRoot();
1093
- const core = openCore(projectRoot, { seedDefaultPlans: true });
1094
- try {
1095
- const created = core.registerPasswordUser({
1096
- email: 'nomembership@example.com',
1097
- password: 'ValidPassphrase123!'
1098
- });
1099
- expect(core.resolvePreferredUserWorkspaceId(created.userId)).toBeNull();
1100
- }
1101
- finally {
1102
- core.close();
1103
- fs.rmSync(projectRoot, { recursive: true, force: true });
1104
- }
1105
- });
1106
- });