@taskforcehq/taskforce 0.3.309 → 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 (470) hide show
  1. package/README.md +89 -127
  2. package/dist/TaskforceCore.js +1 -1
  3. package/dist/components/context/ContextAttachmentManager.js +4 -3
  4. package/dist/components/features/InitiativesModule.js +1 -1
  5. package/dist/components/features/TaskSettings.js +1 -1
  6. package/dist/components/ui/DisclosureSectionHeader.d.ts +12 -0
  7. package/dist/components/ui/DisclosureSectionHeader.js +8 -0
  8. package/dist/components/views/PlanComparisonPage.js +1 -1
  9. package/dist/components/views/StandaloneLayout.js +18 -4
  10. package/dist/components/views/panels/PlanningDrawer.js +2 -1
  11. package/dist/components/views/standalone/modals/AccountHubModal.js +1 -1
  12. package/dist/components/views/standalone/modals/HelpModal.d.ts +3 -1
  13. package/dist/components/views/standalone/modals/HelpModal.js +17 -2
  14. package/dist/core/PlanEntitlementService.d.ts +2 -0
  15. package/dist/core/PlanEntitlementService.js +57 -4
  16. package/dist/hooks/useFilterSort.js +1 -1
  17. package/dist/hooks/useTaskforce.js +2 -2
  18. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +5 -2
  19. package/dist/localization/locales/en-US.js +1 -1
  20. package/dist/localization/locales/es-419.js +1 -1
  21. package/dist/localization/locales/pt-BR.js +1 -1
  22. package/dist/mcp/runtime.js +49 -14
  23. package/dist/server/routes/billing.js +0 -1
  24. package/dist/ui/assets/AgentsModule-BULqIo8e.js +1 -0
  25. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-CCckzWYZ.js → AnnotatedAttachmentWorkspace-DHr4rHK9.js} +1 -1
  26. package/dist/ui/assets/ContextAttachmentManager-DoMe2OpY.js +3 -0
  27. package/dist/ui/assets/{DocumentWorkspace-BVaWPA25.js → DocumentWorkspace-DumBWhvb.js} +1 -1
  28. package/dist/ui/assets/{EntityActivityTimeline-DSj0ThaK.js → EntityActivityTimeline-sHk8Nzbq.js} +1 -1
  29. package/dist/ui/assets/{InitiativesModule-AxMkWtxH.js → InitiativesModule-3X9UNzHh.js} +1 -1
  30. package/dist/ui/assets/PlansPage-C3A3EYaV.js +1 -0
  31. package/dist/ui/assets/PlansPage-DeAzWYTb.css +1 -0
  32. package/dist/ui/assets/{TaskContextUpload-Dw4rTF4i.js → TaskContextUpload-C8G7pWkX.js} +1 -1
  33. package/dist/ui/assets/{TaskSettings-f2ga42_V.js → TaskSettings-DM1o9Wla.js} +2 -2
  34. package/dist/ui/assets/{WorkflowsModule-E4UL3mov.js → WorkflowsModule-CW0crzvD.js} +1 -1
  35. package/dist/ui/assets/documentReferences-BZB3nMRb.js +1 -0
  36. package/dist/ui/assets/index-BWFQ9mGn.js +5 -0
  37. package/dist/ui/assets/{index-BvAbqx5Q.css → index-DcK_8tuE.css} +1 -1
  38. package/dist/ui/assets/{vendor-icons-B32hGuF2.js → vendor-icons-CLnehDTw.js} +1 -1
  39. package/dist/ui/index.html +3 -3
  40. package/dist/utils/supportContact.d.ts +9 -0
  41. package/dist/utils/supportContact.js +32 -0
  42. package/package.json +12 -4
  43. package/scripts/postinstall.js +4 -3
  44. package/dist/TaskforceCore.CategoryFallback.test.d.ts +0 -1
  45. package/dist/TaskforceCore.CategoryFallback.test.js +0 -101
  46. package/dist/TaskforceCore.filter-persistence.test.d.ts +0 -1
  47. package/dist/TaskforceCore.filter-persistence.test.js +0 -402
  48. package/dist/TaskforceCore.routing.test.d.ts +0 -1
  49. package/dist/TaskforceCore.routing.test.js +0 -311
  50. package/dist/TaskforceCore.test.d.ts +0 -1
  51. package/dist/TaskforceCore.test.js +0 -7910
  52. package/dist/TaskforceWidget.test.d.ts +0 -1
  53. package/dist/TaskforceWidget.test.js +0 -92
  54. package/dist/UnsavedChangesRelaxed.test.d.ts +0 -1
  55. package/dist/UnsavedChangesRelaxed.test.js +0 -16
  56. package/dist/__tests__/localizationCatalogs.test.d.ts +0 -1
  57. package/dist/__tests__/localizationCatalogs.test.js +0 -54
  58. package/dist/__tests__/workflowSimulation.test.d.ts +0 -1
  59. package/dist/__tests__/workflowSimulation.test.js +0 -113
  60. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.test.d.ts +0 -1
  61. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.test.js +0 -278
  62. package/dist/annotatedAttachments/payload.test.d.ts +0 -1
  63. package/dist/annotatedAttachments/payload.test.js +0 -67
  64. package/dist/compat/workspaceSyncCompat.test.d.ts +0 -1
  65. package/dist/compat/workspaceSyncCompat.test.js +0 -124
  66. package/dist/components/features/AgentsModule.test.d.ts +0 -1
  67. package/dist/components/features/AgentsModule.test.js +0 -601
  68. package/dist/components/features/AnnotatedAttachmentWorkspace.test.d.ts +0 -1
  69. package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +0 -2883
  70. package/dist/components/features/DocumentWorkspace.test.d.ts +0 -1
  71. package/dist/components/features/DocumentWorkspace.test.js +0 -1439
  72. package/dist/components/features/TaskSettings.test.d.ts +0 -1
  73. package/dist/components/features/TaskSettings.test.js +0 -1674
  74. package/dist/components/features/settings/TaxonomyLibraryManager.test.d.ts +0 -1
  75. package/dist/components/features/settings/TaxonomyLibraryManager.test.js +0 -141
  76. package/dist/components/task/TaskActionHeader.test.d.ts +0 -1
  77. package/dist/components/task/TaskActionHeader.test.js +0 -91
  78. package/dist/components/task/TaskCard.test.d.ts +0 -1
  79. package/dist/components/task/TaskCard.test.js +0 -513
  80. package/dist/components/task/TaskContextUpload.test.d.ts +0 -1
  81. package/dist/components/task/TaskContextUpload.test.js +0 -506
  82. package/dist/components/task/TaskForm.test.d.ts +0 -1
  83. package/dist/components/task/TaskForm.test.js +0 -946
  84. package/dist/components/task/TaskKanban.empty-column-drop.test.d.ts +0 -1
  85. package/dist/components/task/TaskKanban.empty-column-drop.test.js +0 -66
  86. package/dist/components/task/TaskKanban.planning-drop.test.d.ts +0 -1
  87. package/dist/components/task/TaskKanban.planning-drop.test.js +0 -93
  88. package/dist/components/task/TaskKanban.test.d.ts +0 -1
  89. package/dist/components/task/TaskKanban.test.js +0 -99
  90. package/dist/components/task/TaskList.test.d.ts +0 -1
  91. package/dist/components/task/TaskList.test.js +0 -473
  92. package/dist/components/task/TaskStatusActions.test.d.ts +0 -1
  93. package/dist/components/task/TaskStatusActions.test.js +0 -54
  94. package/dist/components/task/taskKanbanDropRules.test.d.ts +0 -1
  95. package/dist/components/task/taskKanbanDropRules.test.js +0 -114
  96. package/dist/components/ui/LevelSelector.test.d.ts +0 -1
  97. package/dist/components/ui/LevelSelector.test.js +0 -55
  98. package/dist/components/ui/Markdown.test.d.ts +0 -1
  99. package/dist/components/ui/Markdown.test.js +0 -38
  100. package/dist/components/ui/MultiSelectFilter.test.d.ts +0 -1
  101. package/dist/components/ui/MultiSelectFilter.test.js +0 -74
  102. package/dist/components/ui/TaxonomyDropdown.test.d.ts +0 -1
  103. package/dist/components/ui/TaxonomyDropdown.test.js +0 -130
  104. package/dist/components/ui/TopNoticeLayer.test.d.ts +0 -1
  105. package/dist/components/ui/TopNoticeLayer.test.js +0 -25
  106. package/dist/components/views/AppShellHeader.test.d.ts +0 -1
  107. package/dist/components/views/AppShellHeader.test.js +0 -42
  108. package/dist/components/views/PlanComparisonPage.test.d.ts +0 -1
  109. package/dist/components/views/PlanComparisonPage.test.js +0 -757
  110. package/dist/components/views/PlansPage.test.d.ts +0 -1
  111. package/dist/components/views/PlansPage.test.js +0 -710
  112. package/dist/components/views/chrome/WorkspaceChromeSections.test.d.ts +0 -1
  113. package/dist/components/views/chrome/WorkspaceChromeSections.test.js +0 -54
  114. package/dist/components/views/chrome/workspaceChrome.test.d.ts +0 -1
  115. package/dist/components/views/chrome/workspaceChrome.test.js +0 -52
  116. package/dist/components/views/panels/DocumentFilterToolbar.test.d.ts +0 -1
  117. package/dist/components/views/panels/DocumentFilterToolbar.test.js +0 -19
  118. package/dist/components/views/panels/FilterToolbar.test.d.ts +0 -1
  119. package/dist/components/views/panels/FilterToolbar.test.js +0 -47
  120. package/dist/components/views/panels/PlanningDrawer.test.d.ts +0 -1
  121. package/dist/components/views/panels/PlanningDrawer.test.js +0 -529
  122. package/dist/components/views/planningScope.test.d.ts +0 -1
  123. package/dist/components/views/planningScope.test.js +0 -60
  124. package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +0 -1
  125. package/dist/components/views/standalone/modals/AccountHubModal.test.js +0 -66
  126. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +0 -1
  127. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +0 -24
  128. package/dist/components/views/teamManagementAccess.test.d.ts +0 -1
  129. package/dist/components/views/teamManagementAccess.test.js +0 -44
  130. package/dist/config/deployment.test.d.ts +0 -1
  131. package/dist/config/deployment.test.js +0 -90
  132. package/dist/config/envSchema.test.d.ts +0 -1
  133. package/dist/config/envSchema.test.js +0 -125
  134. package/dist/core/AiProfileService.test.d.ts +0 -1
  135. package/dist/core/AiProfileService.test.js +0 -184
  136. package/dist/core/AiProfiles.test.d.ts +0 -1
  137. package/dist/core/AiProfiles.test.js +0 -1873
  138. package/dist/core/AuthTokenService.test.d.ts +0 -1
  139. package/dist/core/AuthTokenService.test.js +0 -78
  140. package/dist/core/BulkUpdateFieldsPreview.test.d.ts +0 -1
  141. package/dist/core/BulkUpdateFieldsPreview.test.js +0 -62
  142. package/dist/core/CompletedAt.test.d.ts +0 -1
  143. package/dist/core/CompletedAt.test.js +0 -44
  144. package/dist/core/CreatedBy.test.d.ts +0 -1
  145. package/dist/core/CreatedBy.test.js +0 -188
  146. package/dist/core/DeletedTaskTrash.test.d.ts +0 -1
  147. package/dist/core/DeletedTaskTrash.test.js +0 -104
  148. package/dist/core/EntitlementsPolicy.test.d.ts +0 -1
  149. package/dist/core/EntitlementsPolicy.test.js +0 -280
  150. package/dist/core/GlobalSettingsService.test.d.ts +0 -1
  151. package/dist/core/GlobalSettingsService.test.js +0 -45
  152. package/dist/core/JsonBackupRuntime.test.d.ts +0 -1
  153. package/dist/core/JsonBackupRuntime.test.js +0 -155
  154. package/dist/core/PlanFeatureCatalog.test.d.ts +0 -1
  155. package/dist/core/PlanFeatureCatalog.test.js +0 -139
  156. package/dist/core/PlanVersionPolicy.test.d.ts +0 -1
  157. package/dist/core/PlanVersionPolicy.test.js +0 -204
  158. package/dist/core/PlanningEntities.test.d.ts +0 -1
  159. package/dist/core/PlanningEntities.test.js +0 -509
  160. package/dist/core/ScheduleFields.test.d.ts +0 -1
  161. package/dist/core/ScheduleFields.test.js +0 -193
  162. package/dist/core/SetupState.test.d.ts +0 -1
  163. package/dist/core/SetupState.test.js +0 -113
  164. package/dist/core/SignupMonetizationSettings.test.d.ts +0 -1
  165. package/dist/core/SignupMonetizationSettings.test.js +0 -515
  166. package/dist/core/Stats.test.d.ts +0 -1
  167. package/dist/core/Stats.test.js +0 -36
  168. package/dist/core/SystemAdmin.test.d.ts +0 -1
  169. package/dist/core/SystemAdmin.test.js +0 -1106
  170. package/dist/core/TaskActivitySync.test.d.ts +0 -1
  171. package/dist/core/TaskActivitySync.test.js +0 -90
  172. package/dist/core/TaskAttachmentsCanonical.test.d.ts +0 -1
  173. package/dist/core/TaskAttachmentsCanonical.test.js +0 -356
  174. package/dist/core/TaskAuditTimeline.test.d.ts +0 -1
  175. package/dist/core/TaskAuditTimeline.test.js +0 -319
  176. package/dist/core/TaskTaxonomyValidation.test.d.ts +0 -1
  177. package/dist/core/TaskTaxonomyValidation.test.js +0 -440
  178. package/dist/core/Taskforce.ids.test.d.ts +0 -1
  179. package/dist/core/Taskforce.ids.test.js +0 -611
  180. package/dist/core/Taskforce.listTasksSlim.test.d.ts +0 -1
  181. package/dist/core/Taskforce.listTasksSlim.test.js +0 -91
  182. package/dist/core/Taskforce.mcpAuth.test.d.ts +0 -1
  183. package/dist/core/Taskforce.mcpAuth.test.js +0 -450
  184. package/dist/core/Taskforce.realtimeMutation.test.d.ts +0 -1
  185. package/dist/core/Taskforce.realtimeMutation.test.js +0 -131
  186. package/dist/core/UserProfileAvatarDrafts.test.d.ts +0 -1
  187. package/dist/core/UserProfileAvatarDrafts.test.js +0 -138
  188. package/dist/core/WorkspaceDelete.test.d.ts +0 -1
  189. package/dist/core/WorkspaceDelete.test.js +0 -159
  190. package/dist/core/WorkspacePermissions.test.d.ts +0 -1
  191. package/dist/core/WorkspacePermissions.test.js +0 -270
  192. package/dist/core/WorkspacePlanMode.test.d.ts +0 -1
  193. package/dist/core/WorkspacePlanMode.test.js +0 -383
  194. package/dist/core/WorkspaceSyncSettings.test.d.ts +0 -1
  195. package/dist/core/WorkspaceSyncSettings.test.js +0 -65
  196. package/dist/core/__tests__/TaskAttachmentsCanonical.test.d.ts +0 -1
  197. package/dist/core/__tests__/TaskAttachmentsCanonical.test.js +0 -212
  198. package/dist/documentReviews/DocumentReviewSessionStore.test.d.ts +0 -1
  199. package/dist/documentReviews/DocumentReviewSessionStore.test.js +0 -127
  200. package/dist/hooks/authGuardPolicy.test.d.ts +0 -1
  201. package/dist/hooks/authGuardPolicy.test.js +0 -76
  202. package/dist/hooks/bootstrapTimeouts.test.d.ts +0 -1
  203. package/dist/hooks/bootstrapTimeouts.test.js +0 -12
  204. package/dist/hooks/sync/controlPlane.test.d.ts +0 -1
  205. package/dist/hooks/sync/controlPlane.test.js +0 -121
  206. package/dist/hooks/sync/lifecyclePolicy.test.d.ts +0 -1
  207. package/dist/hooks/sync/lifecyclePolicy.test.js +0 -124
  208. package/dist/hooks/sync/orchestratorShared.test.d.ts +0 -1
  209. package/dist/hooks/sync/orchestratorShared.test.js +0 -98
  210. package/dist/hooks/sync/pullLifecycle.test.d.ts +0 -1
  211. package/dist/hooks/sync/pullLifecycle.test.js +0 -80
  212. package/dist/hooks/sync/recovery.test.d.ts +0 -1
  213. package/dist/hooks/sync/recovery.test.js +0 -292
  214. package/dist/hooks/sync/transfers.test.d.ts +0 -1
  215. package/dist/hooks/sync/transfers.test.js +0 -928
  216. package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +0 -1
  217. package/dist/hooks/sync/useRecentSyncEvents.test.js +0 -124
  218. package/dist/hooks/sync/useSyncStatusActions.test.d.ts +0 -1
  219. package/dist/hooks/sync/useSyncStatusActions.test.js +0 -133
  220. package/dist/hooks/sync/useSyncStatusControls.test.d.ts +0 -1
  221. package/dist/hooks/sync/useSyncStatusControls.test.js +0 -88
  222. package/dist/hooks/useFilterSort.test.d.ts +0 -1
  223. package/dist/hooks/useFilterSort.test.js +0 -419
  224. package/dist/hooks/useRealtimeSync.test.d.ts +0 -1
  225. package/dist/hooks/useRealtimeSync.test.js +0 -311
  226. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.d.ts +0 -1
  227. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +0 -54
  228. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +0 -1
  229. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +0 -228
  230. package/dist/hooks/useSyncOrchestrator.retry-closure.test.d.ts +0 -1
  231. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +0 -2027
  232. package/dist/hooks/useTaskData.test.d.ts +0 -1
  233. package/dist/hooks/useTaskData.test.js +0 -291
  234. package/dist/hooks/useTaskMutations.test.d.ts +0 -1
  235. package/dist/hooks/useTaskMutations.test.js +0 -256
  236. package/dist/hooks/useTaskforce.contract.test.d.ts +0 -1
  237. package/dist/hooks/useTaskforce.contract.test.js +0 -7
  238. package/dist/hooks/useTaskforce.priority-filters.test.d.ts +0 -1
  239. package/dist/hooks/useTaskforce.priority-filters.test.js +0 -28
  240. package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +0 -1
  241. package/dist/hooks/useTaskforce.runtime-routing.test.js +0 -246
  242. package/dist/hooks/useTaskforce.sync-behavior.test.d.ts +0 -1
  243. package/dist/hooks/useTaskforce.sync-behavior.test.js +0 -3644
  244. package/dist/hooks/useWorkspaceSyncController.test.d.ts +0 -1
  245. package/dist/hooks/useWorkspaceSyncController.test.js +0 -192
  246. package/dist/hooks/workspace/useTaskforceApiRouting.test.d.ts +0 -1
  247. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +0 -77
  248. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +0 -1
  249. package/dist/hooks/workspace/workspaceLocalState.test.js +0 -59
  250. package/dist/localization/localizationCatalogs.test.d.ts +0 -1
  251. package/dist/localization/localizationCatalogs.test.js +0 -37
  252. package/dist/mcp/aiProfileBootstrap.test.d.ts +0 -1
  253. package/dist/mcp/aiProfileBootstrap.test.js +0 -68
  254. package/dist/mcp/canonicalAssetHelpers.test.d.ts +0 -1
  255. package/dist/mcp/canonicalAssetHelpers.test.js +0 -67
  256. package/dist/mcp/clientIntegrations.test.d.ts +0 -1
  257. package/dist/mcp/clientIntegrations.test.js +0 -219
  258. package/dist/mcp/optionResolution.test.d.ts +0 -1
  259. package/dist/mcp/optionResolution.test.js +0 -43
  260. package/dist/mcp/runtime.test.d.ts +0 -1
  261. package/dist/mcp/runtime.test.js +0 -5120
  262. package/dist/mcp/structuredCommentValidation.test.d.ts +0 -1
  263. package/dist/mcp/structuredCommentValidation.test.js +0 -31
  264. package/dist/mcp/structuredDocValidation.test.d.ts +0 -1
  265. package/dist/mcp/structuredDocValidation.test.js +0 -35
  266. package/dist/mcp/toolRegistry.test.d.ts +0 -1
  267. package/dist/mcp/toolRegistry.test.js +0 -34
  268. package/dist/migrations/billingSchemaParity.test.d.ts +0 -1
  269. package/dist/migrations/billingSchemaParity.test.js +0 -160
  270. package/dist/migrations/taskSchemaMigrations.test.d.ts +0 -1
  271. package/dist/migrations/taskSchemaMigrations.test.js +0 -225
  272. package/dist/qaOpen.test.d.ts +0 -1
  273. package/dist/qaOpen.test.js +0 -56
  274. package/dist/releaseVersion.test.d.ts +0 -1
  275. package/dist/releaseVersion.test.js +0 -32
  276. package/dist/runtime/appGates.test.d.ts +0 -1
  277. package/dist/runtime/appGates.test.js +0 -42
  278. package/dist/server/aiProfileAvatarAssets.test.d.ts +0 -1
  279. package/dist/server/aiProfileAvatarAssets.test.js +0 -213
  280. package/dist/server/annotatedAttachmentsRoutes.test.d.ts +0 -1
  281. package/dist/server/annotatedAttachmentsRoutes.test.js +0 -532
  282. package/dist/server/auth.test.d.ts +0 -1
  283. package/dist/server/auth.test.js +0 -131
  284. package/dist/server/buildInfo.test.d.ts +0 -1
  285. package/dist/server/buildInfo.test.js +0 -29
  286. package/dist/server/cors.test.d.ts +0 -1
  287. package/dist/server/cors.test.js +0 -77
  288. package/dist/server/documentReviewRoutes.test.d.ts +0 -1
  289. package/dist/server/documentReviewRoutes.test.js +0 -190
  290. package/dist/server/email.test.d.ts +0 -1
  291. package/dist/server/email.test.js +0 -113
  292. package/dist/server/index.cookieProxy.test.d.ts +0 -1
  293. package/dist/server/index.cookieProxy.test.js +0 -67
  294. package/dist/server/index.rateLimit.test.d.ts +0 -1
  295. package/dist/server/index.rateLimit.test.js +0 -22
  296. package/dist/server/index.test.d.ts +0 -1
  297. package/dist/server/index.test.js +0 -283
  298. package/dist/server/rateLimit.test.d.ts +0 -1
  299. package/dist/server/rateLimit.test.js +0 -107
  300. package/dist/server/realtimeSync.auth.test.d.ts +0 -1
  301. package/dist/server/realtimeSync.auth.test.js +0 -62
  302. package/dist/server/realtimeSync.replay.test.d.ts +0 -1
  303. package/dist/server/realtimeSync.replay.test.js +0 -192
  304. package/dist/server/realtimeSync.workspaceIsolation.test.d.ts +0 -1
  305. package/dist/server/realtimeSync.workspaceIsolation.test.js +0 -101
  306. package/dist/server/routes/billing.test.d.ts +0 -1
  307. package/dist/server/routes/billing.test.js +0 -3455
  308. package/dist/server/routes/shared.test.d.ts +0 -1
  309. package/dist/server/routes/shared.test.js +0 -32
  310. package/dist/server/routes/sync.integration.test.d.ts +0 -1
  311. package/dist/server/routes/sync.integration.test.js +0 -2932
  312. package/dist/server/routes/syncAuxRoutes.test.d.ts +0 -1
  313. package/dist/server/routes/syncAuxRoutes.test.js +0 -158
  314. package/dist/server/routes/syncRouteGuards.test.d.ts +0 -1
  315. package/dist/server/routes/syncRouteGuards.test.js +0 -94
  316. package/dist/server/routes.test.d.ts +0 -1
  317. package/dist/server/routes.test.js +0 -15612
  318. package/dist/server/runtimeMode.test.d.ts +0 -1
  319. package/dist/server/runtimeMode.test.js +0 -26
  320. package/dist/server/securityHeaders.test.d.ts +0 -1
  321. package/dist/server/securityHeaders.test.js +0 -70
  322. package/dist/services/templateEngine.test.d.ts +0 -1
  323. package/dist/services/templateEngine.test.js +0 -150
  324. package/dist/services/templates.test.d.ts +0 -1
  325. package/dist/services/templates.test.js +0 -45
  326. package/dist/services/workflowDocumentFixtures.test.d.ts +0 -1
  327. package/dist/services/workflowDocumentFixtures.test.js +0 -78
  328. package/dist/services/workflowExportMatrix.test.d.ts +0 -1
  329. package/dist/services/workflowExportMatrix.test.js +0 -113
  330. package/dist/services/workflowExportSnapshots.test.d.ts +0 -1
  331. package/dist/services/workflowExportSnapshots.test.js +0 -30
  332. package/dist/services/workflowGeneration.test.d.ts +0 -1
  333. package/dist/services/workflowGeneration.test.js +0 -12
  334. package/dist/services/workflowSimulation.test.d.ts +0 -1
  335. package/dist/services/workflowSimulation.test.js +0 -113
  336. package/dist/services/workflowV2Policy.test.d.ts +0 -1
  337. package/dist/services/workflowV2Policy.test.js +0 -80
  338. package/dist/shared/customTaxonomyLibrary.test.d.ts +0 -1
  339. package/dist/shared/customTaxonomyLibrary.test.js +0 -20
  340. package/dist/shared/passwordPolicy.test.d.ts +0 -1
  341. package/dist/shared/passwordPolicy.test.js +0 -34
  342. package/dist/shared/runtimeContract.test.d.ts +0 -1
  343. package/dist/shared/runtimeContract.test.js +0 -23
  344. package/dist/shared/taxonomyLibrary.test.d.ts +0 -1
  345. package/dist/shared/taxonomyLibrary.test.js +0 -26
  346. package/dist/shared/taxonomyState.test.d.ts +0 -1
  347. package/dist/shared/taxonomyState.test.js +0 -71
  348. package/dist/soak/preflight.cli.d.ts +0 -1
  349. package/dist/soak/preflight.cli.js +0 -28
  350. package/dist/soak/preflight.d.ts +0 -26
  351. package/dist/soak/preflight.js +0 -113
  352. package/dist/soak/preflight.test.d.ts +0 -1
  353. package/dist/soak/preflight.test.js +0 -39
  354. package/dist/storage/objectStorageClient.test.d.ts +0 -1
  355. package/dist/storage/objectStorageClient.test.js +0 -82
  356. package/dist/storage/objectStorageConfig.test.d.ts +0 -1
  357. package/dist/storage/objectStorageConfig.test.js +0 -50
  358. package/dist/storage/postgresAdapter.test.d.ts +0 -1
  359. package/dist/storage/postgresAdapter.test.js +0 -29
  360. package/dist/storage/workspaceAssetStore.test.d.ts +0 -1
  361. package/dist/storage/workspaceAssetStore.test.js +0 -588
  362. package/dist/sync/cloudSyncApi.test.d.ts +0 -1
  363. package/dist/sync/cloudSyncApi.test.js +0 -111
  364. package/dist/sync/collaborationSyncPayload.test.d.ts +0 -1
  365. package/dist/sync/collaborationSyncPayload.test.js +0 -125
  366. package/dist/sync/contentSyncPayload.test.d.ts +0 -1
  367. package/dist/sync/contentSyncPayload.test.js +0 -143
  368. package/dist/sync/encryptionService.test.d.ts +0 -1
  369. package/dist/sync/encryptionService.test.js +0 -52
  370. package/dist/sync/planningSyncPayload.test.d.ts +0 -1
  371. package/dist/sync/planningSyncPayload.test.js +0 -92
  372. package/dist/sync/realtimeFeature.test.d.ts +0 -1
  373. package/dist/sync/realtimeFeature.test.js +0 -30
  374. package/dist/sync/realtimeReconcile.test.d.ts +0 -1
  375. package/dist/sync/realtimeReconcile.test.js +0 -43
  376. package/dist/sync/syncApplyHandlers.test.d.ts +0 -1
  377. package/dist/sync/syncApplyHandlers.test.js +0 -626
  378. package/dist/sync/syncService.test.d.ts +0 -1
  379. package/dist/sync/syncService.test.js +0 -903
  380. package/dist/sync/taskSyncPayload.test.d.ts +0 -1
  381. package/dist/sync/taskSyncPayload.test.js +0 -54
  382. package/dist/sync/workspacePullFeed.test.d.ts +0 -1
  383. package/dist/sync/workspacePullFeed.test.js +0 -438
  384. package/dist/sync/workspaceSyncModel.test.d.ts +0 -1
  385. package/dist/sync/workspaceSyncModel.test.js +0 -1442
  386. package/dist/test/setup.d.ts +0 -1
  387. package/dist/test/setup.js +0 -15
  388. package/dist/ui/assets/AgentsModule-BS4Pi_nC.js +0 -1
  389. package/dist/ui/assets/ContextAttachmentManager-CfG_ER7C.js +0 -3
  390. package/dist/ui/assets/PlansPage-B_z-D6Nh.css +0 -1
  391. package/dist/ui/assets/PlansPage-Ndt7mYfo.js +0 -1
  392. package/dist/ui/assets/documentReferences-BOUcJm6-.js +0 -1
  393. package/dist/ui/assets/index-MAHKvFqp.js +0 -4
  394. package/dist/utils/accountProfileSummaryCache.test.d.ts +0 -1
  395. package/dist/utils/accountProfileSummaryCache.test.js +0 -63
  396. package/dist/utils/annotatedAttachments.test.d.ts +0 -1
  397. package/dist/utils/annotatedAttachments.test.js +0 -108
  398. package/dist/utils/assignees.test.d.ts +0 -1
  399. package/dist/utils/assignees.test.js +0 -25
  400. package/dist/utils/avatarUpload.test.d.ts +0 -1
  401. package/dist/utils/avatarUpload.test.js +0 -40
  402. package/dist/utils/canonicalAssetKind.test.d.ts +0 -1
  403. package/dist/utils/canonicalAssetKind.test.js +0 -24
  404. package/dist/utils/coalescedRefresh.test.d.ts +0 -1
  405. package/dist/utils/coalescedRefresh.test.js +0 -30
  406. package/dist/utils/constants.icons.test.d.ts +0 -1
  407. package/dist/utils/constants.icons.test.js +0 -41
  408. package/dist/utils/planParser.test.d.ts +0 -1
  409. package/dist/utils/planParser.test.js +0 -183
  410. package/dist/utils/planningReferences.test.d.ts +0 -1
  411. package/dist/utils/planningReferences.test.js +0 -17
  412. package/dist/utils/syncEventDetails.test.d.ts +0 -1
  413. package/dist/utils/syncEventDetails.test.js +0 -23
  414. package/dist/utils/syncEventPresentation.test.d.ts +0 -1
  415. package/dist/utils/syncEventPresentation.test.js +0 -64
  416. package/dist/utils/syncStatusPresentation.test.d.ts +0 -1
  417. package/dist/utils/syncStatusPresentation.test.js +0 -164
  418. package/dist/utils/taskActivity.test.d.ts +0 -1
  419. package/dist/utils/taskActivity.test.js +0 -218
  420. package/dist/utils/taskDraftDefaults.test.d.ts +0 -1
  421. package/dist/utils/taskDraftDefaults.test.js +0 -34
  422. package/dist/utils/taskDragZones.test.d.ts +0 -1
  423. package/dist/utils/taskDragZones.test.js +0 -14
  424. package/dist/utils/taskNormalization.test.d.ts +0 -1
  425. package/dist/utils/taskNormalization.test.js +0 -158
  426. package/dist/utils/taskSearch.test.d.ts +0 -1
  427. package/dist/utils/taskSearch.test.js +0 -69
  428. package/dist/utils/taskUtils.test.d.ts +0 -1
  429. package/dist/utils/taskUtils.test.js +0 -19
  430. package/dist/utils/taskforceApiClient.test.d.ts +0 -1
  431. package/dist/utils/taskforceApiClient.test.js +0 -44
  432. package/dist/utils/taxonomyFormDefaults.test.d.ts +0 -1
  433. package/dist/utils/taxonomyFormDefaults.test.js +0 -63
  434. package/dist/utils/uiNotice.test.d.ts +0 -1
  435. package/dist/utils/uiNotice.test.js +0 -18
  436. package/dist/utils/workspaceIdentity.test.d.ts +0 -1
  437. package/dist/utils/workspaceIdentity.test.js +0 -61
  438. package/dist/utils/workspaceLifecycle.test.d.ts +0 -1
  439. package/dist/utils/workspaceLifecycle.test.js +0 -64
  440. package/dist/utils/workspaceSyncPresentation.test.d.ts +0 -1
  441. package/dist/utils/workspaceSyncPresentation.test.js +0 -71
  442. package/scripts/bootstrap-playwright-auth.mjs +0 -155
  443. package/scripts/check-cloud-mcp.mjs +0 -83
  444. package/scripts/check-compat-boundaries.mjs +0 -41
  445. package/scripts/check-env.ts +0 -31
  446. package/scripts/check-localization-strings.mjs +0 -49
  447. package/scripts/check-runtime-boundaries.mjs +0 -44
  448. package/scripts/debug-global.mjs +0 -34
  449. package/scripts/debug-login.mjs +0 -32
  450. package/scripts/debug-server.mjs +0 -36
  451. package/scripts/deploy-prod.sh +0 -41
  452. package/scripts/deploy-staging.sh +0 -23
  453. package/scripts/export-sqlite-to-postgres.mjs +0 -123
  454. package/scripts/generate-env-example.ts +0 -8
  455. package/scripts/mcp-stdio-adapter.mjs +0 -160
  456. package/scripts/migrate-task-data-assets.mjs +0 -408
  457. package/scripts/playwright-auth.sh +0 -19
  458. package/scripts/qa-open-lib.mjs +0 -175
  459. package/scripts/qa-open-lib.mjs.d.ts +0 -49
  460. package/scripts/qa-open.mjs +0 -352
  461. package/scripts/release-version-lib.mjs +0 -88
  462. package/scripts/release-version.mjs +0 -72
  463. package/scripts/run-billing-performance-smoke.sh +0 -24
  464. package/scripts/run-dev-proxy.sh +0 -51
  465. package/scripts/run-e2e-realtime.sh +0 -30
  466. package/scripts/run-smoke.sh +0 -35
  467. package/scripts/run-soak.sh +0 -41
  468. package/scripts/test-global.js +0 -143
  469. package/scripts/update-homebrew-formula.sh +0 -108
  470. 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
- });