@taskforcehq/taskforce 0.3.305 → 0.3.306

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 (337) hide show
  1. package/dist/Taskforce.module.css +295 -89
  2. package/dist/TaskforceCore.js +215 -43
  3. package/dist/TaskforceCore.test.js +433 -28
  4. package/dist/assets/fonts/CourierPrime-Bold.woff2 +0 -0
  5. package/dist/assets/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  6. package/dist/assets/fonts/CourierPrime-Italic.woff2 +0 -0
  7. package/dist/assets/fonts/CourierPrime-Regular.woff2 +0 -0
  8. package/dist/assets/fonts/Noir_medium.woff2 +0 -0
  9. package/dist/assets/fonts/Noir_regular.woff2 +0 -0
  10. package/dist/assets/fonts/RussoOne-Regular.woff2 +0 -0
  11. package/dist/assets/fonts/SpecialElite-Regular.woff2 +0 -0
  12. package/dist/compat/workspaceSyncCompat.js +46 -2
  13. package/dist/compat/workspaceSyncCompat.test.js +38 -1
  14. package/dist/components/context/ContextAttachmentManager.d.ts +14 -0
  15. package/dist/components/context/ContextAttachmentManager.js +549 -0
  16. package/dist/components/features/AgentsModule.d.ts +10 -1
  17. package/dist/components/features/AgentsModule.js +260 -15
  18. package/dist/components/features/AgentsModule.test.js +255 -5
  19. package/dist/components/features/DocumentViewer.d.ts +5 -1
  20. package/dist/components/features/DocumentViewer.js +60 -3
  21. package/dist/components/features/DocumentWorkspace.js +1 -1
  22. package/dist/components/features/DocumentWorkspace.test.js +33 -0
  23. package/dist/components/features/documentWorkspaceModel.d.ts +1 -1
  24. package/dist/components/task/TaskCard.js +19 -28
  25. package/dist/components/task/TaskCard.test.js +38 -3
  26. package/dist/components/task/TaskContextUpload.js +4 -526
  27. package/dist/components/task/TaskForm.js +85 -90
  28. package/dist/components/task/TaskForm.test.js +110 -8
  29. package/dist/components/task/TaskKanban.d.ts +2 -1
  30. package/dist/components/task/TaskKanban.js +94 -13
  31. package/dist/components/task/TaskKanban.test.js +36 -0
  32. package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
  33. package/dist/components/ui/EditableAvatarButton.js +18 -0
  34. package/dist/components/views/PlanComparisonPage.d.ts +9 -1
  35. package/dist/components/views/PlanComparisonPage.js +70 -14
  36. package/dist/components/views/PlanComparisonPage.test.js +238 -0
  37. package/dist/components/views/PlansPage.js +127 -36
  38. package/dist/components/views/PlansPage.test.js +294 -12
  39. package/dist/components/views/StandaloneLayout.js +283 -533
  40. package/dist/components/views/panels/PlanningDrawer.d.ts +8 -1
  41. package/dist/components/views/panels/PlanningDrawer.js +18 -11
  42. package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
  43. package/dist/components/views/panels/PlanningDrawer.test.js +141 -0
  44. package/dist/components/views/planningScope.d.ts +20 -0
  45. package/dist/components/views/planningScope.js +25 -0
  46. package/dist/components/views/planningScope.test.d.ts +1 -0
  47. package/dist/components/views/planningScope.test.js +60 -0
  48. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +26 -1
  49. package/dist/components/views/standalone/modals/AccountHubModal.js +76 -2
  50. package/dist/components/views/standalone/modals/AccountHubModal.test.js +20 -3
  51. package/dist/components/views/standalone/modals/AvatarImageManagerModal.d.ts +20 -0
  52. package/dist/components/views/standalone/modals/AvatarImageManagerModal.js +127 -0
  53. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +2 -9
  54. package/dist/components/views/standalone/modals/EditProfileModal.js +3 -2
  55. package/dist/components/views/standalone/modals/SyncEnableWarningModal.d.ts +9 -0
  56. package/dist/components/views/standalone/modals/SyncEnableWarningModal.js +6 -0
  57. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +1 -0
  58. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +24 -0
  59. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +2 -1
  60. package/dist/components/views/standalone/modals/SyncStatusModal.js +2 -2
  61. package/dist/core/AiProfileService.d.ts +39 -2
  62. package/dist/core/AiProfileService.js +639 -73
  63. package/dist/core/AiProfileService.test.js +19 -1
  64. package/dist/core/AiProfiles.test.js +566 -7
  65. package/dist/core/AttachmentLinkService.js +57 -15
  66. package/dist/core/AuthIdentityService.d.ts +107 -0
  67. package/dist/core/AuthIdentityService.js +284 -0
  68. package/dist/core/AuthTokenService.d.ts +4 -0
  69. package/dist/core/AuthTokenService.js +34 -6
  70. package/dist/core/AuthTokenService.test.js +3 -3
  71. package/dist/core/EntitlementsPolicy.test.js +2 -2
  72. package/dist/core/GlobalSettingsService.js +115 -6
  73. package/dist/core/PlanEntitlementService.d.ts +23 -2
  74. package/dist/core/PlanEntitlementService.js +343 -57
  75. package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
  76. package/dist/core/PlanFeatureCatalog.test.js +84 -0
  77. package/dist/core/PlanVersionPolicy.test.js +35 -2
  78. package/dist/core/PlanningEntities.test.js +52 -0
  79. package/dist/core/SignupMonetizationSettings.test.js +199 -27
  80. package/dist/core/SyncReconciliationService.js +2 -65
  81. package/dist/core/SystemAdmin.test.js +42 -42
  82. package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
  83. package/dist/core/Taskforce.d.ts +225 -14
  84. package/dist/core/Taskforce.js +765 -192
  85. package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
  86. package/dist/core/Taskforce.listTasksSlim.test.js +91 -0
  87. package/dist/core/UserProfileAvatarDrafts.test.js +30 -3
  88. package/dist/core/WorkspaceLifecycleService.js +0 -5
  89. package/dist/core/WorkspacePermissions.test.js +25 -1
  90. package/dist/core/WorkspacePlanMode.test.js +117 -16
  91. package/dist/core/shared.d.ts +1 -1
  92. package/dist/core/shared.js +5 -1
  93. package/dist/core/types.d.ts +68 -2
  94. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +26 -1
  95. package/dist/hooks/sync/bootstrap.js +2 -0
  96. package/dist/hooks/sync/controlPlane.d.ts +37 -0
  97. package/dist/hooks/sync/controlPlane.js +78 -0
  98. package/dist/hooks/sync/controlPlane.test.d.ts +1 -0
  99. package/dist/hooks/sync/controlPlane.test.js +121 -0
  100. package/dist/hooks/sync/lifecyclePolicy.d.ts +45 -0
  101. package/dist/hooks/sync/lifecyclePolicy.js +93 -0
  102. package/dist/hooks/sync/lifecyclePolicy.test.d.ts +1 -0
  103. package/dist/hooks/sync/lifecyclePolicy.test.js +124 -0
  104. package/dist/hooks/sync/orchestratorShared.d.ts +10 -1
  105. package/dist/hooks/sync/orchestratorShared.js +68 -47
  106. package/dist/hooks/sync/orchestratorShared.test.js +50 -1
  107. package/dist/hooks/sync/pullLifecycle.d.ts +31 -0
  108. package/dist/hooks/sync/pullLifecycle.js +24 -0
  109. package/dist/hooks/sync/pullLifecycle.test.d.ts +1 -0
  110. package/dist/hooks/sync/pullLifecycle.test.js +80 -0
  111. package/dist/hooks/sync/recovery.d.ts +16 -2
  112. package/dist/hooks/sync/recovery.js +171 -53
  113. package/dist/hooks/sync/recovery.test.d.ts +1 -0
  114. package/dist/hooks/sync/recovery.test.js +292 -0
  115. package/dist/hooks/sync/transfers.d.ts +15 -3
  116. package/dist/hooks/sync/transfers.js +179 -62
  117. package/dist/hooks/sync/transfers.test.d.ts +1 -0
  118. package/dist/hooks/sync/transfers.test.js +396 -0
  119. package/dist/hooks/sync/useRecentSyncEvents.d.ts +21 -0
  120. package/dist/hooks/sync/useRecentSyncEvents.js +92 -0
  121. package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +1 -0
  122. package/dist/hooks/sync/useRecentSyncEvents.test.js +124 -0
  123. package/dist/hooks/sync/useSyncStatusActions.d.ts +30 -0
  124. package/dist/hooks/sync/useSyncStatusActions.js +88 -0
  125. package/dist/hooks/sync/useSyncStatusActions.test.d.ts +1 -0
  126. package/dist/hooks/sync/useSyncStatusActions.test.js +133 -0
  127. package/dist/hooks/sync/useSyncStatusControls.d.ts +25 -0
  128. package/dist/hooks/sync/useSyncStatusControls.js +60 -0
  129. package/dist/hooks/sync/useSyncStatusControls.test.d.ts +1 -0
  130. package/dist/hooks/sync/useSyncStatusControls.test.js +88 -0
  131. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +12 -0
  132. package/dist/hooks/useSyncOrchestrator.d.ts +9 -2
  133. package/dist/hooks/useSyncOrchestrator.js +359 -178
  134. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +407 -0
  135. package/dist/hooks/useTaskData.js +8 -3
  136. package/dist/hooks/useTaskData.test.js +45 -0
  137. package/dist/hooks/useTaskforce.d.ts +60 -2
  138. package/dist/hooks/useTaskforce.js +549 -240
  139. package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
  140. package/dist/hooks/useTaskforce.runtime-routing.test.js +152 -0
  141. package/dist/hooks/useTaskforce.sync-behavior.test.js +2380 -212
  142. package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
  143. package/dist/hooks/useWorkspaceSyncController.js +6 -1
  144. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
  145. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +316 -35
  146. package/dist/localization/locales/en-US.d.ts +0 -1
  147. package/dist/localization/locales/en-US.js +0 -1
  148. package/dist/localization/locales/es-419.js +0 -1
  149. package/dist/localization/locales/pt-BR.js +0 -1
  150. package/dist/mcp/documentAssetRegistrar.js +3 -3
  151. package/dist/mcp/runtime.d.ts +2 -0
  152. package/dist/mcp/runtime.js +131 -64
  153. package/dist/mcp/runtime.test.js +291 -12
  154. package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
  155. package/dist/migrations/taskSchemaMigrations.js +171 -61
  156. package/dist/migrations/taskSchemaMigrations.test.js +15 -0
  157. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  158. package/dist/resources/templates/workflows/collaborate.yaml +1 -1
  159. package/dist/resources/templates/workflows/evaluate.yaml +2 -2
  160. package/dist/resources/templates/workflows/plan.yaml +1 -1
  161. package/dist/resources/templates/workflows/review.yaml +2 -2
  162. package/dist/server/annotatedAttachmentsRoutes.test.js +3 -3
  163. package/dist/server/auth/providers/apple.d.ts +33 -0
  164. package/dist/server/auth/providers/apple.js +82 -0
  165. package/dist/server/auth/providers/appleClientSecret.d.ts +37 -0
  166. package/dist/server/auth/providers/appleClientSecret.js +65 -0
  167. package/dist/server/auth/providers/github.d.ts +26 -0
  168. package/dist/server/auth/providers/github.js +86 -0
  169. package/dist/server/auth/providers/google.d.ts +21 -0
  170. package/dist/server/auth/providers/google.js +56 -0
  171. package/dist/server/auth/providers/types.d.ts +21 -0
  172. package/dist/server/auth/providers/types.js +1 -0
  173. package/dist/server/auth.d.ts +2 -0
  174. package/dist/server/auth.js +6 -3
  175. package/dist/server/documentReviewRoutes.test.js +36 -3
  176. package/dist/server/index.cookieProxy.test.js +1 -0
  177. package/dist/server/index.d.ts +12 -0
  178. package/dist/server/index.js +120 -9
  179. package/dist/server/index.rateLimit.test.js +3 -0
  180. package/dist/server/index.test.js +106 -3
  181. package/dist/server/routes/admin.d.ts +3 -0
  182. package/dist/server/routes/admin.js +272 -19
  183. package/dist/server/routes/auth.d.ts +15 -2
  184. package/dist/server/routes/auth.js +994 -57
  185. package/dist/server/routes/authSupport.d.ts +1 -1
  186. package/dist/server/routes/authSupport.js +1 -1
  187. package/dist/server/routes/billing.js +361 -35
  188. package/dist/server/routes/billing.test.js +592 -12
  189. package/dist/server/routes/documents.d.ts +1 -0
  190. package/dist/server/routes/documents.js +172 -70
  191. package/dist/server/routes/shared.d.ts +28 -2
  192. package/dist/server/routes/shared.js +69 -4
  193. package/dist/server/routes/sync.integration.test.js +437 -39
  194. package/dist/server/routes/sync.js +61 -605
  195. package/dist/server/routes/syncAuxRoutes.d.ts +15 -0
  196. package/dist/server/routes/syncAuxRoutes.js +91 -0
  197. package/dist/server/routes/syncAuxRoutes.test.d.ts +1 -0
  198. package/dist/server/routes/syncAuxRoutes.test.js +158 -0
  199. package/dist/server/routes/syncPullApplyRoutes.d.ts +36 -0
  200. package/dist/server/routes/syncPullApplyRoutes.js +204 -0
  201. package/dist/server/routes/syncPushRoutes.d.ts +24 -0
  202. package/dist/server/routes/syncPushRoutes.js +212 -0
  203. package/dist/server/routes/syncRouteGuards.d.ts +36 -0
  204. package/dist/server/routes/syncRouteGuards.js +67 -0
  205. package/dist/server/routes/syncRouteGuards.test.d.ts +1 -0
  206. package/dist/server/routes/syncRouteGuards.test.js +94 -0
  207. package/dist/server/routes/syncRouteTypes.d.ts +13 -0
  208. package/dist/server/routes/syncRouteTypes.js +1 -0
  209. package/dist/server/routes/syncSnapshotRoutes.d.ts +66 -0
  210. package/dist/server/routes/syncSnapshotRoutes.js +180 -0
  211. package/dist/server/routes/tasks.js +43 -1
  212. package/dist/server/routes/workspaces.js +36 -5
  213. package/dist/server/routes.js +60 -3
  214. package/dist/server/routes.test.js +1824 -171
  215. package/dist/services/workflowExportSnapshots.test.js +2 -0
  216. package/dist/shared/aiProfileSeatScope.d.ts +5 -0
  217. package/dist/shared/aiProfileSeatScope.js +21 -0
  218. package/dist/shared/passwordPolicy.d.ts +13 -0
  219. package/dist/shared/passwordPolicy.js +84 -0
  220. package/dist/shared/passwordPolicy.test.d.ts +1 -0
  221. package/dist/shared/passwordPolicy.test.js +34 -0
  222. package/dist/storage/workspaceAssetStore.d.ts +15 -3
  223. package/dist/storage/workspaceAssetStore.js +132 -44
  224. package/dist/storage/workspaceAssetStore.test.js +93 -6
  225. package/dist/styles/fonts.css +70 -0
  226. package/dist/sync/collaborationSyncPayload.d.ts +6 -1
  227. package/dist/sync/collaborationSyncPayload.js +17 -11
  228. package/dist/sync/collaborationSyncPayload.test.js +13 -1
  229. package/dist/sync/contentSyncState.d.ts +4 -8
  230. package/dist/sync/contentSyncState.js +17 -70
  231. package/dist/sync/syncApplyHandlers.d.ts +7 -0
  232. package/dist/sync/syncApplyHandlers.js +28 -13
  233. package/dist/sync/syncApplyHandlers.test.js +70 -3
  234. package/dist/sync/syncFieldSemantics.d.ts +12 -0
  235. package/dist/sync/syncFieldSemantics.js +55 -0
  236. package/dist/sync/syncResourceAdapters.d.ts +44 -0
  237. package/dist/sync/syncResourceAdapters.js +77 -0
  238. package/dist/sync/syncService.d.ts +5 -0
  239. package/dist/sync/syncService.js +52 -0
  240. package/dist/sync/syncService.test.js +57 -3
  241. package/dist/sync/taskSyncChangeKey.d.ts +2 -0
  242. package/dist/sync/taskSyncChangeKey.js +143 -0
  243. package/dist/sync/taskSyncPayload.js +3 -8
  244. package/dist/sync/workspacePullFeed.js +4 -3
  245. package/dist/sync/workspacePullFeed.test.js +43 -0
  246. package/dist/sync/workspaceRepair.js +5 -2
  247. package/dist/sync/workspaceSyncModel.d.ts +45 -0
  248. package/dist/sync/workspaceSyncModel.js +183 -28
  249. package/dist/sync/workspaceSyncModel.test.js +291 -12
  250. package/dist/sync/workspaceSyncState.d.ts +17 -0
  251. package/dist/sync/workspaceSyncState.js +2 -1
  252. package/dist/sync/workspaceSyncSurface.js +3 -8
  253. package/dist/types.d.ts +17 -0
  254. package/dist/ui/assets/AgentsModule-DrLawwNl.js +1 -0
  255. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BG6P_GVW.js → AnnotatedAttachmentWorkspace-Z9_whf7F.js} +1 -1
  256. package/dist/ui/assets/CourierPrime-Bold-BuLj_dpw.woff2 +0 -0
  257. package/dist/ui/assets/CourierPrime-BoldItalic-BrK2pUkZ.woff2 +0 -0
  258. package/dist/ui/assets/CourierPrime-Italic-DFUZK5Ey.woff2 +0 -0
  259. package/dist/ui/assets/CourierPrime-Regular-BsKphzVf.woff2 +0 -0
  260. package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +252 -0
  261. package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +1 -0
  262. package/dist/ui/assets/{InitiativesModule-Dhm7M8e7.js → InitiativesModule-9E6ParrY.js} +1 -1
  263. package/dist/ui/assets/Noir_medium-bxQwKGzB.woff2 +0 -0
  264. package/dist/ui/assets/Noir_regular-ojf2kxlG.woff2 +0 -0
  265. package/dist/ui/assets/PlansPage-CvfnMiWq.css +1 -0
  266. package/dist/ui/assets/PlansPage-DRXscYxH.js +1 -0
  267. package/dist/ui/assets/RussoOne-Regular-Cu_qq_qC.woff2 +0 -0
  268. package/dist/ui/assets/SpecialElite-Regular-Di6gSvAS.woff2 +0 -0
  269. package/dist/ui/assets/{TaskSettings-ln0aF7xc.js → TaskSettings-DZX4jk7e.js} +1 -1
  270. package/dist/ui/assets/{WorkflowsModule-BcS4afRn.js → WorkflowsModule-BA7jcEpH.js} +1 -1
  271. package/dist/ui/assets/index-9eT8e0Lu.css +1 -0
  272. package/dist/ui/assets/index-C6k75jr8.js +6 -0
  273. package/dist/ui/assets/{vendor-icons-BSUaRwF8.js → vendor-icons-DuEd_65c.js} +1 -1
  274. package/dist/ui/fonts/CourierPrime-Bold.ttf +0 -0
  275. package/dist/ui/fonts/CourierPrime-Bold.woff2 +0 -0
  276. package/dist/ui/fonts/CourierPrime-BoldItalic.ttf +0 -0
  277. package/dist/ui/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  278. package/dist/ui/fonts/CourierPrime-Italic.ttf +0 -0
  279. package/dist/ui/fonts/CourierPrime-Italic.woff2 +0 -0
  280. package/dist/ui/fonts/CourierPrime-Regular.ttf +0 -0
  281. package/dist/ui/fonts/CourierPrime-Regular.woff2 +0 -0
  282. package/dist/ui/fonts/Noir_medium.otf +0 -0
  283. package/dist/ui/fonts/Noir_medium.woff2 +0 -0
  284. package/dist/ui/fonts/Noir_regular.otf +0 -0
  285. package/dist/ui/fonts/Noir_regular.woff2 +0 -0
  286. package/dist/ui/fonts/RussoOne-Regular.woff2 +0 -0
  287. package/dist/ui/fonts/SpecialElite-Regular.ttf +0 -0
  288. package/dist/ui/fonts/SpecialElite-Regular.woff2 +0 -0
  289. package/dist/ui/index.html +3 -3
  290. package/dist/utils/accountProfileSummaryCache.d.ts +5 -0
  291. package/dist/utils/assignees.d.ts +2 -0
  292. package/dist/utils/assignees.js +2 -0
  293. package/dist/utils/avatarUpload.d.ts +27 -0
  294. package/dist/utils/avatarUpload.js +132 -0
  295. package/dist/utils/avatarUpload.test.d.ts +1 -0
  296. package/dist/utils/avatarUpload.test.js +40 -0
  297. package/dist/utils/bootstrapTrace.d.ts +7 -0
  298. package/dist/utils/bootstrapTrace.js +29 -0
  299. package/dist/utils/commercialLifecyclePresentation.js +3 -0
  300. package/dist/utils/contextAssetEvents.d.ts +2 -0
  301. package/dist/utils/syncEventDetails.d.ts +3 -0
  302. package/dist/utils/syncEventDetails.js +64 -0
  303. package/dist/utils/syncEventDetails.test.d.ts +1 -0
  304. package/dist/utils/syncEventDetails.test.js +23 -0
  305. package/dist/utils/syncEventPresentation.d.ts +15 -0
  306. package/dist/utils/syncEventPresentation.js +129 -0
  307. package/dist/utils/syncEventPresentation.test.d.ts +1 -0
  308. package/dist/utils/syncEventPresentation.test.js +64 -0
  309. package/dist/utils/syncStatusPresentation.d.ts +72 -0
  310. package/dist/utils/syncStatusPresentation.js +217 -0
  311. package/dist/utils/syncStatusPresentation.test.d.ts +1 -0
  312. package/dist/utils/syncStatusPresentation.test.js +164 -0
  313. package/dist/utils/taskActivity.d.ts +2 -0
  314. package/dist/utils/taskActivity.js +228 -22
  315. package/dist/utils/taskActivity.test.js +119 -8
  316. package/dist/utils/workspaceSyncPresentation.d.ts +4 -0
  317. package/dist/utils/workspaceSyncPresentation.js +68 -2
  318. package/dist/utils/workspaceSyncPresentation.test.js +36 -1
  319. package/package.json +3 -2
  320. package/scripts/export-sqlite-to-postgres.mjs +7 -9
  321. package/scripts/run-e2e-realtime.sh +17 -3
  322. package/scripts/run-smoke.sh +17 -5
  323. package/scripts/run-soak.sh +17 -5
  324. package/src/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  325. package/src/resources/templates/workflows/collaborate.yaml +1 -1
  326. package/src/resources/templates/workflows/evaluate.yaml +2 -2
  327. package/src/resources/templates/workflows/plan.yaml +1 -1
  328. package/src/resources/templates/workflows/review.yaml +2 -2
  329. package/dist/ui/assets/AgentsModule-N2MnQBZk.js +0 -1
  330. package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +0 -1
  331. package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +0 -252
  332. package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
  333. package/dist/ui/assets/PlansPage-Cq0zXj3I.js +0 -1
  334. package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
  335. package/dist/ui/assets/index-C2-OXZV7.css +0 -1
  336. package/dist/ui/assets/index-DcSI5F86.js +0 -6
  337. package/scripts/migrate-planning-model.ts +0 -233
@@ -3,6 +3,7 @@ import { DOCUMENT_CLOUD_STORAGE_FEATURE_KEY, WORKSPACES_FEATURE_KEY, AI_PROFILES
3
3
  const LEGACY_REMOVED_PLAN_FEATURE_KEYS = Object.freeze([
4
4
  'documents.workspace_access'
5
5
  ]);
6
+ const LEGACY_WORKSPACES_FEATURE_KEY = 'workspace.switching';
6
7
  /**
7
8
  * Handles all plan catalog, plan version, and account entitlement logic.
8
9
  * Extracted from Taskforce.ts to reduce monolith size.
@@ -32,6 +33,45 @@ export class PlanEntitlementService {
32
33
  return {};
33
34
  }
34
35
  }
36
+ normalizeOptionalCatalogMarketingCopy(raw) {
37
+ const value = String(raw ?? '').trim();
38
+ return value || null;
39
+ }
40
+ canonicalizePlanFeatureKey(raw) {
41
+ const featureKey = String(raw || '').trim();
42
+ return featureKey === LEGACY_WORKSPACES_FEATURE_KEY ? WORKSPACES_FEATURE_KEY : featureKey;
43
+ }
44
+ normalizeOptionalCatalogDisplayOrder(raw) {
45
+ if (raw === null || raw === undefined || raw === '')
46
+ return null;
47
+ const numeric = Number(raw);
48
+ if (!Number.isFinite(numeric))
49
+ return null;
50
+ return Math.max(0, Math.floor(numeric));
51
+ }
52
+ listPlanFeatureCatalogOverrideMap() {
53
+ const rows = this.db.prepare(`
54
+ SELECT feature_key, public_label, public_description, public_display_order
55
+ FROM plan_feature_catalog_overrides
56
+ WHERE tenant_id = ?
57
+ `).all(this.tenantId);
58
+ const overrides = new Map();
59
+ for (const row of rows) {
60
+ const originalFeatureKey = String(row.feature_key || '').trim();
61
+ const featureKey = this.canonicalizePlanFeatureKey(originalFeatureKey);
62
+ if (!featureKey)
63
+ continue;
64
+ if (featureKey === WORKSPACES_FEATURE_KEY && originalFeatureKey === LEGACY_WORKSPACES_FEATURE_KEY && overrides.has(featureKey)) {
65
+ continue;
66
+ }
67
+ overrides.set(featureKey, {
68
+ publicLabel: this.normalizeOptionalCatalogMarketingCopy(row.public_label),
69
+ publicDescription: this.normalizeOptionalCatalogMarketingCopy(row.public_description),
70
+ publicDisplayOrder: this.normalizeOptionalCatalogDisplayOrder(row.public_display_order)
71
+ });
72
+ }
73
+ return overrides;
74
+ }
35
75
  // ─── Normalizers ─────────────────────────────────────────────────────────
36
76
  normalizeEntitlementState(value) {
37
77
  const normalized = String(value || '').trim().toLowerCase();
@@ -91,9 +131,10 @@ export class PlanEntitlementService {
91
131
  return nextMetadata;
92
132
  }
93
133
  sanitizePlanFeatureConfig(featureKey, access, config) {
94
- if (featureKey === 'collaboration.seat_limit')
134
+ const normalizedFeatureKey = this.canonicalizePlanFeatureKey(featureKey);
135
+ if (normalizedFeatureKey === 'collaboration.seat_limit')
95
136
  return {};
96
- if (featureKey === DOCUMENT_CLOUD_STORAGE_FEATURE_KEY) {
137
+ if (normalizedFeatureKey === DOCUMENT_CLOUD_STORAGE_FEATURE_KEY) {
97
138
  const limit = this.readPositiveIntegerConfig(config[DOCUMENT_CLOUD_STORAGE_LIMIT_CONFIG_KEY], 1);
98
139
  if (access === 'limited') {
99
140
  return {
@@ -107,13 +148,13 @@ export class PlanEntitlementService {
107
148
  }
108
149
  return {};
109
150
  }
110
- if (featureKey === WORKSPACES_FEATURE_KEY) {
151
+ if (normalizedFeatureKey === WORKSPACES_FEATURE_KEY) {
111
152
  const limit = this.readPositiveIntegerConfig(config[WORKSPACES_LIMIT_CONFIG_KEY], 1);
112
153
  return {
113
154
  [WORKSPACES_LIMIT_CONFIG_KEY]: limit ?? 1
114
155
  };
115
156
  }
116
- if (featureKey === AI_PROFILES_FEATURE_KEY) {
157
+ if (normalizedFeatureKey === AI_PROFILES_FEATURE_KEY) {
117
158
  if (access === 'limited') {
118
159
  const limit = this.readPositiveIntegerConfig(config[AI_PROFILES_LIMIT_CONFIG_KEY], 1);
119
160
  return {
@@ -144,21 +185,25 @@ export class PlanEntitlementService {
144
185
  LIMIT 1
145
186
  `).get(this.tenantId, accountId);
146
187
  }
147
- normalizeExpiredCheckoutPendingRow(accountId) {
188
+ normalizeExpiredLifecycleRow(accountId) {
148
189
  const row = this.readAccountEntitlementRow(accountId);
149
190
  if (!row?.account_id || !row.plan_version_id) {
150
191
  return row;
151
192
  }
152
193
  const currentState = this.normalizeEntitlementState(row.entitlement_state);
153
- if (currentState !== 'checkout_pending' || !this.isIsoTimestampExpired(row.effective_until || null)) {
194
+ if (!this.isIsoTimestampExpired(row.effective_until || null)) {
154
195
  return row;
155
196
  }
197
+ if (currentState !== 'checkout_pending' && currentState !== 'trialing') {
198
+ return row;
199
+ }
200
+ const updatedBy = currentState === 'trialing' ? 'trial-expired' : 'checkout-expired';
156
201
  const now = new Date().toISOString();
157
202
  this.db.prepare(`
158
203
  UPDATE account_entitlements
159
204
  SET entitlement_state = ?, effective_until = NULL, updated_by = ?, updated_at = ?
160
205
  WHERE tenant_id = ? AND account_id = ?
161
- `).run('pending_plan_selection', 'checkout-expired', now, this.tenantId, accountId);
206
+ `).run('pending_plan_selection', updatedBy, now, this.tenantId, accountId);
162
207
  this.createUserBillingRecord(accountId, {
163
208
  entitlementState: 'pending_plan_selection',
164
209
  planId: null,
@@ -255,8 +300,48 @@ export class PlanEntitlementService {
255
300
  normalizeLegacyPlanDataIfNeeded() {
256
301
  this.migrateLegacySeatLimitFeatureConfigToVersionSeatLimit();
257
302
  this.normalizeLegacyBillingMirrorRows();
303
+ this.renameLegacyWorkspacesFeatureKey();
258
304
  this.removeLegacyPlanFeatureRows();
259
305
  }
306
+ renameLegacyWorkspacesFeatureKey() {
307
+ const now = new Date().toISOString();
308
+ const tx = this.db.transaction(() => {
309
+ this.db.prepare(`
310
+ DELETE FROM plan_feature_matrix
311
+ WHERE tenant_id = ?
312
+ AND feature_key = ?
313
+ AND EXISTS (
314
+ SELECT 1
315
+ FROM plan_feature_matrix canonical
316
+ WHERE canonical.tenant_id = plan_feature_matrix.tenant_id
317
+ AND canonical.plan_version_id = plan_feature_matrix.plan_version_id
318
+ AND canonical.feature_key = ?
319
+ )
320
+ `).run(this.tenantId, LEGACY_WORKSPACES_FEATURE_KEY, WORKSPACES_FEATURE_KEY);
321
+ this.db.prepare(`
322
+ UPDATE plan_feature_matrix
323
+ SET feature_key = ?, updated_at = ?
324
+ WHERE tenant_id = ? AND feature_key = ?
325
+ `).run(WORKSPACES_FEATURE_KEY, now, this.tenantId, LEGACY_WORKSPACES_FEATURE_KEY);
326
+ this.db.prepare(`
327
+ DELETE FROM plan_feature_catalog_overrides
328
+ WHERE tenant_id = ?
329
+ AND feature_key = ?
330
+ AND EXISTS (
331
+ SELECT 1
332
+ FROM plan_feature_catalog_overrides canonical
333
+ WHERE canonical.tenant_id = plan_feature_catalog_overrides.tenant_id
334
+ AND canonical.feature_key = ?
335
+ )
336
+ `).run(this.tenantId, LEGACY_WORKSPACES_FEATURE_KEY, WORKSPACES_FEATURE_KEY);
337
+ this.db.prepare(`
338
+ UPDATE plan_feature_catalog_overrides
339
+ SET feature_key = ?, updated_at = ?
340
+ WHERE tenant_id = ? AND feature_key = ?
341
+ `).run(WORKSPACES_FEATURE_KEY, now, this.tenantId, LEGACY_WORKSPACES_FEATURE_KEY);
342
+ });
343
+ tx();
344
+ }
260
345
  removeLegacyPlanFeatureRows() {
261
346
  const featureKeys = [...LEGACY_REMOVED_PLAN_FEATURE_KEYS];
262
347
  if (featureKeys.length < 1)
@@ -547,7 +632,9 @@ export class PlanEntitlementService {
547
632
  return { limit: 1, feature: null };
548
633
  }
549
634
  const entitlement = this.getAccountEntitlement(normalizedUserId);
550
- const feature = entitlement.version.features[WORKSPACES_FEATURE_KEY] || null;
635
+ const feature = entitlement.version.features[WORKSPACES_FEATURE_KEY]
636
+ || entitlement.version.features[LEGACY_WORKSPACES_FEATURE_KEY]
637
+ || null;
551
638
  if (!feature || feature.access === 'disabled') {
552
639
  return { limit: 1, feature };
553
640
  }
@@ -577,11 +664,17 @@ export class PlanEntitlementService {
577
664
  throw error;
578
665
  }
579
666
  const feature = entitlement.version.features[AI_PROFILES_FEATURE_KEY] || null;
580
- if (!feature || feature.access !== 'limited') {
667
+ if (!feature || feature.access === 'disabled') {
581
668
  return { limit: null, feature };
582
669
  }
583
670
  const configuredLimit = this.readPositiveIntegerConfig(feature.config?.[AI_PROFILES_LIMIT_CONFIG_KEY], 1);
584
- return { limit: configuredLimit ?? 1, feature };
671
+ if (configuredLimit !== null) {
672
+ return { limit: configuredLimit, feature };
673
+ }
674
+ if (feature.access === 'limited') {
675
+ return { limit: 1, feature };
676
+ }
677
+ return { limit: null, feature };
585
678
  }
586
679
  planHasAnyActivePricing(planIdRaw) {
587
680
  const planId = String(planIdRaw || '').trim().toLowerCase();
@@ -678,6 +771,16 @@ export class PlanEntitlementService {
678
771
  throw error;
679
772
  }
680
773
  }
774
+ getOnboardingPlanSnapshot(planIdRaw) {
775
+ const planId = String(planIdRaw || '').trim().toLowerCase();
776
+ if (!planId)
777
+ return null;
778
+ const plan = this.listPlanCatalog().find((entry) => entry.planId === planId) || null;
779
+ if (!plan)
780
+ return null;
781
+ const defaultVersion = this.listPlanVersions(planId).find((version) => version.isDefault) || null;
782
+ return { plan, defaultVersion };
783
+ }
681
784
  getOnboardingPolicy() {
682
785
  const billing = this.deps.readGlobalConfig().billing || {};
683
786
  const policy = billing.onboardingPolicy && typeof billing.onboardingPolicy === 'object'
@@ -686,16 +789,32 @@ export class PlanEntitlementService {
686
789
  if (policy?.mode === 'require_plan_selection'
687
790
  || policy?.mode === 'auto_grant_free'
688
791
  || policy?.mode === 'auto_start_trial') {
792
+ const rawPolicy = policy;
793
+ const explicitPlanId = typeof rawPolicy.planId === 'string'
794
+ ? (rawPolicy.planId.trim() || null)
795
+ : null;
796
+ const legacyPlanVersionId = typeof rawPolicy.planVersionId === 'string'
797
+ ? (rawPolicy.planVersionId.trim() || null)
798
+ : null;
799
+ const resolvedLegacyPlanId = !explicitPlanId && legacyPlanVersionId
800
+ ? (() => {
801
+ const row = this.db.prepare(`
802
+ SELECT plan_id
803
+ FROM plan_versions
804
+ WHERE tenant_id = ? AND plan_version_id = ?
805
+ LIMIT 1
806
+ `).get(this.tenantId, legacyPlanVersionId);
807
+ return typeof row?.plan_id === 'string' ? (row.plan_id.trim() || null) : null;
808
+ })()
809
+ : null;
689
810
  return {
690
811
  mode: policy.mode,
691
- planVersionId: typeof policy.planVersionId === 'string'
692
- ? (policy.planVersionId.trim() || null)
693
- : null
812
+ planId: explicitPlanId || resolvedLegacyPlanId
694
813
  };
695
814
  }
696
815
  return {
697
816
  mode: 'require_plan_selection',
698
- planVersionId: null
817
+ planId: null
699
818
  };
700
819
  }
701
820
  validateOnboardingPolicy(input) {
@@ -705,64 +824,58 @@ export class PlanEntitlementService {
705
824
  : input?.mode === 'require_plan_selection'
706
825
  ? 'require_plan_selection'
707
826
  : current.mode;
708
- const planVersionId = input?.planVersionId !== undefined
709
- ? (typeof input.planVersionId === 'string' ? input.planVersionId.trim() || null : null)
710
- : current.planVersionId;
827
+ const planId = input?.planId !== undefined
828
+ ? (typeof input.planId === 'string' ? input.planId.trim().toLowerCase() || null : null)
829
+ : current.planId;
711
830
  if (mode === 'require_plan_selection') {
712
831
  return { valid: true };
713
832
  }
714
- if (!planVersionId) {
833
+ if (!planId) {
715
834
  return {
716
835
  valid: false,
717
- code: 'ONBOARDING_PLAN_VERSION_REQUIRED',
718
- message: 'An onboarding plan version is required for the selected onboarding mode.'
836
+ code: 'ONBOARDING_PLAN_REQUIRED',
837
+ message: 'An onboarding plan is required for the selected onboarding mode.'
719
838
  };
720
839
  }
721
- const row = this.db.prepare(`
722
- SELECT v.plan_version_id, v.plan_id, COALESCE(p.status, 'active') AS plan_status
723
- FROM plan_versions v
724
- JOIN plan_catalog p ON p.tenant_id = v.tenant_id AND p.plan_id = v.plan_id
725
- WHERE v.tenant_id = ? AND v.plan_version_id = ?
726
- LIMIT 1
727
- `).get(this.tenantId, planVersionId);
728
- if (!row?.plan_version_id || !row.plan_id) {
840
+ const onboardingPlan = this.getOnboardingPlanSnapshot(planId);
841
+ if (!onboardingPlan?.plan || !onboardingPlan.defaultVersion) {
729
842
  return {
730
843
  valid: false,
731
- code: 'ONBOARDING_PLAN_VERSION_NOT_FOUND',
732
- message: `Configured onboarding plan version "${planVersionId}" was not found.`,
733
- details: { planVersionId }
844
+ code: 'ONBOARDING_PLAN_NOT_FOUND',
845
+ message: `Configured onboarding plan "${planId}" was not found or has no default version.`,
846
+ details: { planId }
734
847
  };
735
848
  }
736
- if (String(row.plan_status || '').trim().toLowerCase() !== 'active') {
849
+ if (onboardingPlan.plan.status !== 'active') {
737
850
  return {
738
851
  valid: false,
739
852
  code: 'ONBOARDING_PLAN_NOT_ENABLED',
740
- message: `Configured onboarding plan version "${planVersionId}" belongs to a disabled plan.`,
853
+ message: `Configured onboarding plan "${planId}" is disabled.`,
741
854
  details: {
742
- planVersionId,
743
- planId: String(row.plan_id)
855
+ planId: onboardingPlan.plan.planId,
856
+ planVersionId: onboardingPlan.defaultVersion.planVersionId
744
857
  }
745
858
  };
746
859
  }
747
- if (mode === 'auto_grant_free' && !this.planVersionHasActiveFreePricing(planVersionId)) {
860
+ if (mode === 'auto_grant_free' && !this.planVersionHasActiveFreePricing(onboardingPlan.defaultVersion.planVersionId)) {
748
861
  return {
749
862
  valid: false,
750
863
  code: 'ONBOARDING_PLAN_MUST_BE_FREE',
751
- message: `Configured onboarding plan version "${planVersionId}" does not have active free pricing.`,
864
+ message: `Configured onboarding plan "${planId}" does not have active free pricing on its default version.`,
752
865
  details: {
753
- planVersionId,
754
- planId: String(row.plan_id)
866
+ planId: onboardingPlan.plan.planId,
867
+ planVersionId: onboardingPlan.defaultVersion.planVersionId
755
868
  }
756
869
  };
757
870
  }
758
- if (mode === 'auto_start_trial' && !this.planVersionIsTrialEligible(planVersionId)) {
871
+ if (mode === 'auto_start_trial' && !this.planVersionIsTrialEligible(onboardingPlan.defaultVersion.planVersionId)) {
759
872
  return {
760
873
  valid: false,
761
874
  code: 'ONBOARDING_PLAN_TRIAL_REQUIRED',
762
- message: `Configured onboarding plan version "${planVersionId}" is not trial eligible.`,
875
+ message: `Configured onboarding plan "${planId}" is not trial eligible on its default version.`,
763
876
  details: {
764
- planVersionId,
765
- planId: String(row.plan_id)
877
+ planId: onboardingPlan.plan.planId,
878
+ planVersionId: onboardingPlan.defaultVersion.planVersionId
766
879
  }
767
880
  };
768
881
  }
@@ -797,11 +910,11 @@ export class PlanEntitlementService {
797
910
  planId: null
798
911
  };
799
912
  }
800
- const configuredVersion = onboardingPolicy.planVersionId
801
- ? this.getPlanVersionSnapshot(onboardingPolicy.planVersionId)
913
+ const configuredVersion = onboardingPolicy.planId
914
+ ? this.getOnboardingPlanSnapshot(onboardingPolicy.planId)?.defaultVersion || null
802
915
  : null;
803
916
  if (!configuredVersion) {
804
- throw new TaskforceRuleError('Configured onboarding plan version is required.', 500, 'ONBOARDING_PLAN_VERSION_REQUIRED');
917
+ throw new TaskforceRuleError('Configured onboarding plan is required.', 500, 'ONBOARDING_PLAN_REQUIRED');
805
918
  }
806
919
  return {
807
920
  requiresPlanSelection: false,
@@ -811,6 +924,26 @@ export class PlanEntitlementService {
811
924
  planId: configuredVersion.planId
812
925
  };
813
926
  }
927
+ listOnboardingPlanOptions() {
928
+ return this.listPlanCatalog()
929
+ .filter((plan) => plan.status === 'active')
930
+ .map((plan) => {
931
+ const defaultVersion = this.listPlanVersions(plan.planId).find((version) => version.isDefault) || null;
932
+ if (!defaultVersion)
933
+ return null;
934
+ return {
935
+ planId: plan.planId,
936
+ planDisplayName: plan.displayName || plan.planId,
937
+ defaultPlanVersionId: defaultVersion.planVersionId,
938
+ defaultVersionNumber: defaultVersion.versionNumber,
939
+ planStatus: plan.status,
940
+ supportsAutoGrantFree: this.planVersionHasActiveFreePricing(defaultVersion.planVersionId),
941
+ supportsAutoStartTrial: this.planVersionIsTrialEligible(defaultVersion.planVersionId)
942
+ };
943
+ })
944
+ .filter((option) => Boolean(option))
945
+ .sort((a, b) => a.planDisplayName.localeCompare(b.planDisplayName));
946
+ }
814
947
  // ─── Public billing methods ───────────────────────────────────────────────
815
948
  createUserBillingRecord(userId, options) {
816
949
  const now = new Date().toISOString();
@@ -887,6 +1020,80 @@ export class PlanEntitlementService {
887
1020
  createdAt: entitlement.effectiveAt
888
1021
  });
889
1022
  }
1023
+ tryAutoHealCommercialEntitlementFromBillingMirror(accountId, entitlement) {
1024
+ const currentState = entitlement ? this.normalizeEntitlementState(entitlement.state) : null;
1025
+ if (currentState && this.isEntitlementStateAllowed(currentState))
1026
+ return null;
1027
+ if (currentState && currentState !== 'pending_plan_selection')
1028
+ return null;
1029
+ const billingRow = this.db.prepare(`
1030
+ SELECT stripe_customer_id, stripe_subscription_id, stripe_price_id, stripe_status, entitlement_state, tier, effective_until
1031
+ FROM user_billing
1032
+ WHERE tenant_id = ? AND user_id = ?
1033
+ LIMIT 1
1034
+ `).get(this.tenantId, accountId);
1035
+ const hasStripeLink = Boolean(String(billingRow?.stripe_customer_id || '').trim()
1036
+ || String(billingRow?.stripe_subscription_id || '').trim());
1037
+ if (!hasStripeLink)
1038
+ return null;
1039
+ if (this.isIsoTimestampExpired(billingRow?.effective_until || null))
1040
+ return null;
1041
+ const billingStatus = String(billingRow?.stripe_status || '').trim().toLowerCase();
1042
+ const billingStateFromMirror = this.normalizeEntitlementState(billingRow?.entitlement_state);
1043
+ const derivedBillingState = this.isEntitlementStateAllowed(billingStateFromMirror)
1044
+ ? billingStateFromMirror
1045
+ : (billingStatus === 'trialing' ? 'trialing'
1046
+ : billingStatus === 'active' ? 'active'
1047
+ : billingStatus === 'past_due' ? 'grace'
1048
+ : null);
1049
+ if (!derivedBillingState || !this.isEntitlementStateAllowed(derivedBillingState))
1050
+ return null;
1051
+ const stripePriceId = String(billingRow?.stripe_price_id || '').trim();
1052
+ let healedPlanVersionId = '';
1053
+ if (stripePriceId) {
1054
+ const mappingRow = this.db.prepare(`
1055
+ SELECT plan_version_id
1056
+ FROM billing_price_mappings
1057
+ WHERE tenant_id = ? AND stripe_price_id = ? AND active = 1
1058
+ ORDER BY updated_at DESC, created_at DESC
1059
+ LIMIT 1
1060
+ `).get(this.tenantId, stripePriceId);
1061
+ healedPlanVersionId = String(mappingRow?.plan_version_id || '').trim();
1062
+ }
1063
+ if (!healedPlanVersionId) {
1064
+ const billingPlanId = String(billingRow?.tier || '').trim().toLowerCase();
1065
+ if (!billingPlanId)
1066
+ return null;
1067
+ try {
1068
+ healedPlanVersionId = this.getRequiredDefaultPlanVersion(billingPlanId).planVersionId;
1069
+ }
1070
+ catch {
1071
+ return null;
1072
+ }
1073
+ }
1074
+ let healedVersion;
1075
+ try {
1076
+ healedVersion = this.getPlanVersionSnapshot(healedPlanVersionId);
1077
+ }
1078
+ catch {
1079
+ return null;
1080
+ }
1081
+ const effectiveAt = entitlement?.effectiveAt || new Date().toISOString();
1082
+ console.info(`[Taskforce Billing] Auto-healing canonical entitlement from billing mirror for account=${accountId} `
1083
+ + `previousState=${currentState || '(none)'} mirrorState=${billingStateFromMirror || '(none)'} stripeStatus=${billingStatus || '(none)'} `
1084
+ + `planVersionId=${healedVersion.planVersionId} planId=${healedVersion.planId} stripePriceId=${stripePriceId || '(none)'}`);
1085
+ return this.transitionCanonicalCommercialState({
1086
+ accountId,
1087
+ planVersionId: healedVersion.planVersionId,
1088
+ state: derivedBillingState,
1089
+ effectiveAt,
1090
+ effectiveUntil: billingRow?.effective_until || null,
1091
+ billingMirrorPlanId: healedVersion.planId,
1092
+ source: 'backfill',
1093
+ updatedBy: 'billing-mirror-auto-heal',
1094
+ createdAt: effectiveAt
1095
+ });
1096
+ }
890
1097
  getAccountAccessStatus(accountId) {
891
1098
  const normalizedAccountId = String(accountId || '').trim();
892
1099
  if (!normalizedAccountId) {
@@ -896,8 +1103,10 @@ export class PlanEntitlementService {
896
1103
  const validation = this.validateOnboardingPolicy(onboardingPolicy);
897
1104
  const currentEntitlement = this.tryGetAccountEntitlement(normalizedAccountId);
898
1105
  const entitlement = currentEntitlement
899
- ? (this.tryAutoHealLegacyPersonalFreeEntitlement(normalizedAccountId, currentEntitlement) || currentEntitlement)
900
- : null;
1106
+ ? (this.tryAutoHealLegacyPersonalFreeEntitlement(normalizedAccountId, currentEntitlement)
1107
+ || this.tryAutoHealCommercialEntitlementFromBillingMirror(normalizedAccountId, currentEntitlement)
1108
+ || currentEntitlement)
1109
+ : this.tryAutoHealCommercialEntitlementFromBillingMirror(normalizedAccountId, null);
901
1110
  if (!entitlement) {
902
1111
  return !validation.valid
903
1112
  ? this.buildBlockedAccessStatus({
@@ -995,7 +1204,7 @@ export class PlanEntitlementService {
995
1204
  if (!normalizedAccountId) {
996
1205
  throw new TaskforceRuleError('accountId is required', 400, 'ACCOUNT_ID_REQUIRED');
997
1206
  }
998
- const row = this.normalizeExpiredCheckoutPendingRow(normalizedAccountId);
1207
+ const row = this.normalizeExpiredLifecycleRow(normalizedAccountId);
999
1208
  if (!row?.account_id || !row.plan_version_id) {
1000
1209
  throw new TaskforceRuleError('Entitlement not found', 404, 'ENTITLEMENT_NOT_FOUND');
1001
1210
  }
@@ -1045,6 +1254,8 @@ export class PlanEntitlementService {
1045
1254
  FROM ai_profiles
1046
1255
  WHERE tenant_id = ?
1047
1256
  AND workspace_id = ?
1257
+ AND archived_at IS NULL
1258
+ AND seat_scope = 'cloud_metered'
1048
1259
  `).get(this.tenantId, normalizedWorkspaceId);
1049
1260
  const used = Math.max(0, Number(row?.c || 0));
1050
1261
  const { limit } = this.resolveAiProfileLimitForWorkspace(normalizedWorkspaceId);
@@ -1142,6 +1353,20 @@ export class PlanEntitlementService {
1142
1353
  entitlementState: 'active'
1143
1354
  };
1144
1355
  }
1356
+ resolveInviteAcceptanceDefaultEntitlement() {
1357
+ const defaultPersonalVersion = this.tryGetDefaultPlanVersionByMode('personal', { allowFallback: true });
1358
+ if (!defaultPersonalVersion)
1359
+ return null;
1360
+ if (this.isTeamPlanVersion(defaultPersonalVersion))
1361
+ return null;
1362
+ if (!this.planVersionHasActiveFreePricing(defaultPersonalVersion.planVersionId))
1363
+ return null;
1364
+ return {
1365
+ planVersionId: defaultPersonalVersion.planVersionId,
1366
+ planId: defaultPersonalVersion.planId,
1367
+ commercialState: 'active'
1368
+ };
1369
+ }
1145
1370
  // ─── Plan catalog admin ───────────────────────────────────────────────────
1146
1371
  isValidEntitlementTransition(fromState, toState) {
1147
1372
  if (fromState === toState)
@@ -1169,6 +1394,7 @@ export class PlanEntitlementService {
1169
1394
  upsertPlanCatalog(input) {
1170
1395
  const planId = String(input.planId || '').trim().toLowerCase();
1171
1396
  const displayName = String(input.displayName || '').trim();
1397
+ const description = String(input.description || '').trim() || null;
1172
1398
  const initialMode = String(input.initialMode || '').trim().toLowerCase() === 'team'
1173
1399
  ? 'team'
1174
1400
  : 'personal';
@@ -1194,7 +1420,7 @@ export class PlanEntitlementService {
1194
1420
  });
1195
1421
  const now = new Date().toISOString();
1196
1422
  const existing = this.db.prepare(`
1197
- SELECT plan_id, status
1423
+ SELECT plan_id, status, description
1198
1424
  FROM plan_catalog
1199
1425
  WHERE tenant_id = ? AND plan_id = ?
1200
1426
  LIMIT 1
@@ -1211,13 +1437,14 @@ export class PlanEntitlementService {
1211
1437
  : 'archived';
1212
1438
  const tx = this.db.transaction(() => {
1213
1439
  this.db.prepare(`
1214
- INSERT INTO plan_catalog (plan_id, tenant_id, display_name, status, created_at, updated_at)
1215
- VALUES (?, ?, ?, ?, ?, ?)
1440
+ INSERT INTO plan_catalog (plan_id, tenant_id, display_name, description, status, created_at, updated_at)
1441
+ VALUES (?, ?, ?, ?, ?, ?, ?)
1216
1442
  ON CONFLICT (plan_id) DO UPDATE SET
1217
1443
  display_name = excluded.display_name,
1444
+ description = excluded.description,
1218
1445
  status = excluded.status,
1219
1446
  updated_at = excluded.updated_at
1220
- `).run(planId, this.tenantId, displayName, status, now, now);
1447
+ `).run(planId, this.tenantId, displayName, description, status, now, now);
1221
1448
  if (isNewPlan) {
1222
1449
  this.db.prepare(`
1223
1450
  INSERT INTO plan_versions (plan_version_id, tenant_id, plan_id, version_number, is_default, seat_limit, metadata_json, created_at, updated_at)
@@ -1243,11 +1470,11 @@ export class PlanEntitlementService {
1243
1470
  });
1244
1471
  tx();
1245
1472
  this.repairPlanDefaultVersionIfNeeded(planId);
1246
- return { planId, displayName, status };
1473
+ return { planId, displayName, description, status };
1247
1474
  }
1248
1475
  listPlanCatalog() {
1249
1476
  const rows = this.db.prepare(`
1250
- SELECT plan_id, display_name, status, created_at, updated_at
1477
+ SELECT plan_id, display_name, description, status, created_at, updated_at
1251
1478
  FROM plan_catalog
1252
1479
  WHERE tenant_id = ?
1253
1480
  ORDER BY updated_at DESC, created_at DESC
@@ -1255,21 +1482,76 @@ export class PlanEntitlementService {
1255
1482
  return rows.map((row) => ({
1256
1483
  planId: String(row.plan_id || ''),
1257
1484
  displayName: String(row.display_name || ''),
1485
+ description: typeof row.description === 'string' && row.description.trim().length > 0 ? row.description.trim() : null,
1258
1486
  status: String(row.status || '').trim().toLowerCase() === 'archived' ? 'archived' : 'active',
1259
1487
  createdAt: String(row.created_at || ''),
1260
1488
  updatedAt: String(row.updated_at || '')
1261
1489
  }));
1262
1490
  }
1263
1491
  listPlanFeatureCatalog() {
1492
+ const overrides = this.listPlanFeatureCatalogOverrideMap();
1264
1493
  return DEFAULT_PLAN_FEATURE_CATALOG.map((feature) => ({
1265
1494
  featureKey: feature.featureKey,
1266
1495
  label: feature.label,
1267
1496
  description: feature.description,
1497
+ publicLabel: overrides.get(feature.featureKey)?.publicLabel ?? feature.publicLabel ?? null,
1498
+ publicDescription: overrides.get(feature.featureKey)?.publicDescription ?? feature.publicDescription ?? null,
1499
+ publicDisplayOrder: overrides.get(feature.featureKey)?.publicDisplayOrder ?? feature.publicDisplayOrder ?? null,
1268
1500
  allowedAccessModes: [...feature.allowedAccessModes],
1269
1501
  configTemplate: { ...feature.configTemplate },
1270
1502
  ...(feature.dependsOn?.length ? { dependsOn: [...feature.dependsOn] } : {})
1271
1503
  }));
1272
1504
  }
1505
+ updatePlanFeatureCatalogMarketingCopy(input) {
1506
+ const featureKey = this.canonicalizePlanFeatureKey(input.featureKey);
1507
+ if (!featureKey)
1508
+ throw new TaskforceRuleError('featureKey is required', 400, 'FEATURE_KEY_REQUIRED');
1509
+ const catalogFeature = DEFAULT_PLAN_FEATURE_CATALOG.find((feature) => feature.featureKey === featureKey);
1510
+ if (!catalogFeature)
1511
+ throw new TaskforceRuleError('Unknown plan feature key', 404, 'PLAN_FEATURE_NOT_FOUND');
1512
+ const publicLabel = this.normalizeOptionalCatalogMarketingCopy(input.publicLabel);
1513
+ const publicDescription = this.normalizeOptionalCatalogMarketingCopy(input.publicDescription);
1514
+ const publicDisplayOrder = this.normalizeOptionalCatalogDisplayOrder(input.publicDisplayOrder);
1515
+ const now = new Date().toISOString();
1516
+ const tx = this.db.transaction(() => {
1517
+ if (!publicLabel && !publicDescription && publicDisplayOrder === null) {
1518
+ this.db.prepare(`
1519
+ DELETE FROM plan_feature_catalog_overrides
1520
+ WHERE tenant_id = ? AND feature_key = ?
1521
+ `).run(this.tenantId, featureKey);
1522
+ return;
1523
+ }
1524
+ this.db.prepare(`
1525
+ INSERT INTO plan_feature_catalog_overrides (
1526
+ tenant_id,
1527
+ feature_key,
1528
+ public_label,
1529
+ public_description,
1530
+ public_display_order,
1531
+ created_at,
1532
+ updated_at
1533
+ )
1534
+ VALUES (?, ?, ?, ?, ?, ?, ?)
1535
+ ON CONFLICT (tenant_id, feature_key) DO UPDATE SET
1536
+ public_label = excluded.public_label,
1537
+ public_description = excluded.public_description,
1538
+ public_display_order = excluded.public_display_order,
1539
+ updated_at = excluded.updated_at
1540
+ `).run(this.tenantId, featureKey, publicLabel, publicDescription, publicDisplayOrder, now, now);
1541
+ });
1542
+ tx();
1543
+ return this.listPlanFeatureCatalog().find((feature) => feature.featureKey === featureKey) || {
1544
+ featureKey: catalogFeature.featureKey,
1545
+ label: catalogFeature.label,
1546
+ description: catalogFeature.description,
1547
+ publicLabel,
1548
+ publicDescription,
1549
+ publicDisplayOrder,
1550
+ allowedAccessModes: [...catalogFeature.allowedAccessModes],
1551
+ configTemplate: { ...catalogFeature.configTemplate },
1552
+ ...(catalogFeature.dependsOn?.length ? { dependsOn: [...catalogFeature.dependsOn] } : {})
1553
+ };
1554
+ }
1273
1555
  listPlanVersions(planId) {
1274
1556
  const normalizedPlanId = String(planId || '').trim().toLowerCase();
1275
1557
  if (!normalizedPlanId)
@@ -1460,9 +1742,13 @@ export class PlanEntitlementService {
1460
1742
  `).all(this.tenantId, planVersionId);
1461
1743
  const features = {};
1462
1744
  for (const featureRow of featuresRows) {
1463
- const key = String(featureRow.feature_key || '').trim();
1745
+ const originalKey = String(featureRow.feature_key || '').trim();
1746
+ const key = this.canonicalizePlanFeatureKey(originalKey);
1464
1747
  if (!key)
1465
1748
  continue;
1749
+ if (key === WORKSPACES_FEATURE_KEY && originalKey === LEGACY_WORKSPACES_FEATURE_KEY && features[key]) {
1750
+ continue;
1751
+ }
1466
1752
  features[key] = {
1467
1753
  featureKey: key,
1468
1754
  access: this.normalizeFeatureAccess(featureRow.access),
@@ -1492,7 +1778,7 @@ export class PlanEntitlementService {
1492
1778
  ...currentVersion.features
1493
1779
  };
1494
1780
  for (const feature of features) {
1495
- const featureKey = String(feature.featureKey || '').trim();
1781
+ const featureKey = this.canonicalizePlanFeatureKey(feature.featureKey);
1496
1782
  if (!featureKey)
1497
1783
  continue;
1498
1784
  const access = this.normalizeFeatureAccess(feature.access);
@@ -0,0 +1 @@
1
+ export {};