@taskforcehq/taskforce 0.3.304 → 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.
- package/README.md +140 -247
- package/dist/Taskforce.module.css +295 -89
- package/dist/TaskforceCore.js +378 -111
- package/dist/TaskforceCore.test.js +1221 -107
- package/dist/assets/fonts/CourierPrime-Bold.woff2 +0 -0
- package/dist/assets/fonts/CourierPrime-BoldItalic.woff2 +0 -0
- package/dist/assets/fonts/CourierPrime-Italic.woff2 +0 -0
- package/dist/assets/fonts/CourierPrime-Regular.woff2 +0 -0
- package/dist/assets/fonts/Noir_medium.woff2 +0 -0
- package/dist/assets/fonts/Noir_regular.woff2 +0 -0
- package/dist/assets/fonts/RussoOne-Regular.woff2 +0 -0
- package/dist/assets/fonts/SpecialElite-Regular.woff2 +0 -0
- package/dist/compat/workspaceSyncCompat.js +52 -2
- package/dist/compat/workspaceSyncCompat.test.js +38 -1
- package/dist/components/context/ContextAttachmentManager.d.ts +14 -0
- package/dist/components/context/ContextAttachmentManager.js +549 -0
- package/dist/components/features/AgentsModule.d.ts +10 -1
- package/dist/components/features/AgentsModule.js +260 -15
- package/dist/components/features/AgentsModule.test.js +255 -5
- package/dist/components/features/DocumentIndex.d.ts +1 -1
- package/dist/components/features/DocumentIndex.js +1 -1
- package/dist/components/features/DocumentViewer.d.ts +6 -2
- package/dist/components/features/DocumentViewer.js +90 -10
- package/dist/components/features/DocumentWorkspace.d.ts +5 -22
- package/dist/components/features/DocumentWorkspace.js +12 -155
- package/dist/components/features/DocumentWorkspace.test.js +226 -6
- package/dist/components/features/TaskSettings.js +29 -3
- package/dist/components/features/TaskSettings.test.js +42 -1
- package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
- package/dist/components/features/documentWorkspaceModel.js +57 -0
- package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
- package/dist/components/features/useDocumentWorkspaceController.js +113 -0
- package/dist/components/task/TaskCard.js +19 -28
- package/dist/components/task/TaskCard.test.js +38 -3
- package/dist/components/task/TaskContextUpload.js +4 -526
- package/dist/components/task/TaskForm.d.ts +8 -8
- package/dist/components/task/TaskForm.js +362 -230
- package/dist/components/task/TaskForm.test.js +558 -381
- package/dist/components/task/TaskKanban.d.ts +2 -1
- package/dist/components/task/TaskKanban.js +96 -14
- package/dist/components/task/TaskKanban.test.js +36 -0
- package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
- package/dist/components/ui/EditableAvatarButton.js +18 -0
- package/dist/components/views/PlanComparisonPage.d.ts +9 -1
- package/dist/components/views/PlanComparisonPage.js +70 -17
- package/dist/components/views/PlanComparisonPage.test.js +289 -41
- package/dist/components/views/PlansPage.js +239 -106
- package/dist/components/views/PlansPage.test.js +593 -42
- package/dist/components/views/StandaloneLayout.js +310 -554
- package/dist/components/views/panels/FilterToolbar.test.js +6 -4
- package/dist/components/views/panels/PlanningDrawer.d.ts +8 -1
- package/dist/components/views/panels/PlanningDrawer.js +18 -11
- package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
- package/dist/components/views/panels/PlanningDrawer.test.js +141 -0
- package/dist/components/views/planningScope.d.ts +20 -0
- package/dist/components/views/planningScope.js +25 -0
- package/dist/components/views/planningScope.test.d.ts +1 -0
- package/dist/components/views/planningScope.test.js +60 -0
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +27 -14
- package/dist/components/views/standalone/modals/AccountHubModal.js +87 -15
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +42 -2
- package/dist/components/views/standalone/modals/AvatarImageManagerModal.d.ts +20 -0
- package/dist/components/views/standalone/modals/AvatarImageManagerModal.js +127 -0
- package/dist/components/views/standalone/modals/EditProfileModal.d.ts +2 -9
- package/dist/components/views/standalone/modals/EditProfileModal.js +3 -2
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.js +6 -0
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +24 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +2 -1
- package/dist/components/views/standalone/modals/SyncStatusModal.js +2 -2
- package/dist/config/envSchema.js +1 -1
- package/dist/core/AiProfileService.d.ts +45 -3
- package/dist/core/AiProfileService.js +774 -63
- package/dist/core/AiProfileService.test.js +22 -2
- package/dist/core/AiProfiles.test.js +761 -2
- package/dist/core/AttachmentLinkService.js +57 -15
- package/dist/core/AuthIdentityService.d.ts +107 -0
- package/dist/core/AuthIdentityService.js +284 -0
- package/dist/core/AuthTokenService.d.ts +4 -0
- package/dist/core/AuthTokenService.js +34 -6
- package/dist/core/AuthTokenService.test.d.ts +1 -0
- package/dist/core/AuthTokenService.test.js +78 -0
- package/dist/core/EntitlementsPolicy.test.js +77 -15
- package/dist/core/GlobalSettingsService.js +115 -6
- package/dist/core/PlanEntitlementService.d.ts +30 -3
- package/dist/core/PlanEntitlementService.js +454 -93
- package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
- package/dist/core/PlanFeatureCatalog.test.js +84 -0
- package/dist/core/PlanVersionPolicy.test.js +54 -28
- package/dist/core/PlanningEntities.test.js +52 -0
- package/dist/core/SignupMonetizationSettings.test.js +241 -44
- package/dist/core/SyncReconciliationService.js +2 -65
- package/dist/core/SystemAdmin.test.js +244 -88
- package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
- package/dist/core/TaskTaxonomyValidation.test.js +53 -0
- package/dist/core/Taskforce.d.ts +228 -14
- package/dist/core/Taskforce.js +814 -201
- package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
- package/dist/core/Taskforce.listTasksSlim.test.js +91 -0
- package/dist/core/UserProfileAvatarDrafts.test.js +30 -3
- package/dist/core/WorkspaceLifecycleService.js +0 -5
- package/dist/core/WorkspacePermissions.test.js +25 -1
- package/dist/core/WorkspacePlanMode.test.js +128 -27
- package/dist/core/shared.d.ts +3 -1
- package/dist/core/shared.js +16 -1
- package/dist/core/types.d.ts +68 -2
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +26 -1
- package/dist/hooks/sync/auth.js +3 -1
- package/dist/hooks/sync/bootstrap.js +2 -0
- package/dist/hooks/sync/controlPlane.d.ts +37 -0
- package/dist/hooks/sync/controlPlane.js +78 -0
- package/dist/hooks/sync/controlPlane.test.d.ts +1 -0
- package/dist/hooks/sync/controlPlane.test.js +121 -0
- package/dist/hooks/sync/lifecyclePolicy.d.ts +45 -0
- package/dist/hooks/sync/lifecyclePolicy.js +93 -0
- package/dist/hooks/sync/lifecyclePolicy.test.d.ts +1 -0
- package/dist/hooks/sync/lifecyclePolicy.test.js +124 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +10 -1
- package/dist/hooks/sync/orchestratorShared.js +68 -47
- package/dist/hooks/sync/orchestratorShared.test.js +50 -1
- package/dist/hooks/sync/pullLifecycle.d.ts +31 -0
- package/dist/hooks/sync/pullLifecycle.js +24 -0
- package/dist/hooks/sync/pullLifecycle.test.d.ts +1 -0
- package/dist/hooks/sync/pullLifecycle.test.js +80 -0
- package/dist/hooks/sync/recovery.d.ts +16 -2
- package/dist/hooks/sync/recovery.js +171 -53
- package/dist/hooks/sync/recovery.test.d.ts +1 -0
- package/dist/hooks/sync/recovery.test.js +292 -0
- package/dist/hooks/sync/transfers.d.ts +16 -3
- package/dist/hooks/sync/transfers.js +186 -65
- package/dist/hooks/sync/transfers.test.d.ts +1 -0
- package/dist/hooks/sync/transfers.test.js +396 -0
- package/dist/hooks/sync/useRecentSyncEvents.d.ts +21 -0
- package/dist/hooks/sync/useRecentSyncEvents.js +92 -0
- package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +1 -0
- package/dist/hooks/sync/useRecentSyncEvents.test.js +124 -0
- package/dist/hooks/sync/useSyncStatusActions.d.ts +30 -0
- package/dist/hooks/sync/useSyncStatusActions.js +88 -0
- package/dist/hooks/sync/useSyncStatusActions.test.d.ts +1 -0
- package/dist/hooks/sync/useSyncStatusActions.test.js +133 -0
- package/dist/hooks/sync/useSyncStatusControls.d.ts +25 -0
- package/dist/hooks/sync/useSyncStatusControls.js +60 -0
- package/dist/hooks/sync/useSyncStatusControls.test.d.ts +1 -0
- package/dist/hooks/sync/useSyncStatusControls.test.js +88 -0
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +12 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +16 -6
- package/dist/hooks/useSyncOrchestrator.js +424 -175
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +487 -10
- package/dist/hooks/useTaskData.js +8 -3
- package/dist/hooks/useTaskData.test.js +45 -0
- package/dist/hooks/useTaskMutations.d.ts +2 -1
- package/dist/hooks/useTaskMutations.js +12 -1
- package/dist/hooks/useTaskMutations.test.js +30 -1
- package/dist/hooks/useTaskforce.d.ts +65 -4
- package/dist/hooks/useTaskforce.js +564 -243
- package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
- package/dist/hooks/useTaskforce.runtime-routing.test.js +152 -0
- package/dist/hooks/useTaskforce.sync-behavior.test.js +2454 -208
- package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
- package/dist/hooks/useWorkspaceSyncController.js +17 -2
- package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +316 -35
- package/dist/localization/locales/en-US.d.ts +0 -1
- package/dist/localization/locales/en-US.js +0 -1
- package/dist/localization/locales/es-419.js +0 -1
- package/dist/localization/locales/pt-BR.js +0 -1
- package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
- package/dist/mcp/collaborationRegistrar.d.ts +2 -0
- package/dist/mcp/collaborationRegistrar.js +71 -0
- package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
- package/dist/mcp/documentAssetRegistrar.js +346 -0
- package/dist/mcp/runtime.d.ts +2 -0
- package/dist/mcp/runtime.js +2433 -3534
- package/dist/mcp/runtime.test.js +440 -2
- package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
- package/dist/mcp/taskPlanningRegistrar.js +418 -0
- package/dist/mcp/toolCatalog.d.ts +35 -0
- package/dist/mcp/toolCatalog.js +3 -0
- package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
- package/dist/migrations/taskSchemaMigrations.js +171 -61
- package/dist/migrations/taskSchemaMigrations.test.js +15 -0
- package/dist/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
- package/dist/resources/templates/workflows/collaborate.yaml +1 -1
- package/dist/resources/templates/workflows/evaluate.yaml +2 -2
- package/dist/resources/templates/workflows/plan.yaml +1 -1
- package/dist/resources/templates/workflows/review.yaml +2 -2
- package/dist/server/annotatedAttachmentsRoutes.test.js +3 -3
- package/dist/server/auth/providers/apple.d.ts +33 -0
- package/dist/server/auth/providers/apple.js +82 -0
- package/dist/server/auth/providers/appleClientSecret.d.ts +37 -0
- package/dist/server/auth/providers/appleClientSecret.js +65 -0
- package/dist/server/auth/providers/github.d.ts +26 -0
- package/dist/server/auth/providers/github.js +86 -0
- package/dist/server/auth/providers/google.d.ts +21 -0
- package/dist/server/auth/providers/google.js +56 -0
- package/dist/server/auth/providers/types.d.ts +21 -0
- package/dist/server/auth/providers/types.js +1 -0
- package/dist/server/auth.d.ts +2 -0
- package/dist/server/auth.js +6 -3
- package/dist/server/documentReviewRoutes.test.js +36 -3
- package/dist/server/index.cookieProxy.test.js +1 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.js +120 -9
- package/dist/server/index.rateLimit.test.js +3 -0
- package/dist/server/index.test.js +106 -3
- package/dist/server/routes/admin.d.ts +4 -5
- package/dist/server/routes/admin.js +410 -80
- package/dist/server/routes/annotatedAttachments.d.ts +1 -1
- package/dist/server/routes/annotatedAttachments.js +1 -1
- package/dist/server/routes/auth.d.ts +16 -6
- package/dist/server/routes/auth.js +1015 -118
- package/dist/server/routes/authSupport.d.ts +30 -0
- package/dist/server/routes/authSupport.js +81 -0
- package/dist/server/routes/billing.d.ts +1 -1
- package/dist/server/routes/billing.js +276 -34
- package/dist/server/routes/billing.test.js +760 -52
- package/dist/server/routes/documentReviews.d.ts +1 -1
- package/dist/server/routes/documentReviews.js +1 -1
- package/dist/server/routes/documents.d.ts +5 -2
- package/dist/server/routes/documents.js +242 -74
- package/dist/server/routes/primitives.d.ts +11 -0
- package/dist/server/routes/primitives.js +73 -0
- package/dist/server/routes/resources.d.ts +1 -1
- package/dist/server/routes/resources.js +1 -1
- package/dist/server/routes/shared.d.ts +29 -3
- package/dist/server/routes/shared.js +69 -4
- package/dist/server/routes/sync.d.ts +1 -1
- package/dist/server/routes/sync.integration.test.js +437 -39
- package/dist/server/routes/sync.js +62 -606
- package/dist/server/routes/syncAuxRoutes.d.ts +15 -0
- package/dist/server/routes/syncAuxRoutes.js +91 -0
- package/dist/server/routes/syncAuxRoutes.test.d.ts +1 -0
- package/dist/server/routes/syncAuxRoutes.test.js +158 -0
- package/dist/server/routes/syncPullApplyRoutes.d.ts +36 -0
- package/dist/server/routes/syncPullApplyRoutes.js +204 -0
- package/dist/server/routes/syncPushRoutes.d.ts +24 -0
- package/dist/server/routes/syncPushRoutes.js +212 -0
- package/dist/server/routes/syncRouteGuards.d.ts +36 -0
- package/dist/server/routes/syncRouteGuards.js +67 -0
- package/dist/server/routes/syncRouteGuards.test.d.ts +1 -0
- package/dist/server/routes/syncRouteGuards.test.js +94 -0
- package/dist/server/routes/syncRouteTypes.d.ts +13 -0
- package/dist/server/routes/syncRouteTypes.js +1 -0
- package/dist/server/routes/syncSnapshotRoutes.d.ts +66 -0
- package/dist/server/routes/syncSnapshotRoutes.js +180 -0
- package/dist/server/routes/tasks.d.ts +1 -1
- package/dist/server/routes/tasks.js +44 -2
- package/dist/server/routes/workspaces.d.ts +1 -1
- package/dist/server/routes/workspaces.js +37 -6
- package/dist/server/routes.d.ts +2 -9
- package/dist/server/routes.js +136 -165
- package/dist/server/routes.test.js +2457 -162
- package/dist/services/workflowExportSnapshots.test.js +2 -0
- package/dist/shared/aiProfileSeatScope.d.ts +5 -0
- package/dist/shared/aiProfileSeatScope.js +21 -0
- package/dist/shared/passwordPolicy.d.ts +13 -0
- package/dist/shared/passwordPolicy.js +84 -0
- package/dist/shared/passwordPolicy.test.d.ts +1 -0
- package/dist/shared/passwordPolicy.test.js +34 -0
- package/dist/storage/workspaceAssetStore.d.ts +15 -3
- package/dist/storage/workspaceAssetStore.js +132 -44
- package/dist/storage/workspaceAssetStore.test.js +93 -6
- package/dist/styles/fonts.css +70 -0
- package/dist/sync/collaborationSyncPayload.d.ts +6 -1
- package/dist/sync/collaborationSyncPayload.js +17 -11
- package/dist/sync/collaborationSyncPayload.test.js +13 -1
- package/dist/sync/contentSyncState.d.ts +4 -8
- package/dist/sync/contentSyncState.js +33 -77
- package/dist/sync/syncApplyHandlers.d.ts +7 -0
- package/dist/sync/syncApplyHandlers.js +28 -13
- package/dist/sync/syncApplyHandlers.test.js +72 -3
- package/dist/sync/syncFieldSemantics.d.ts +12 -0
- package/dist/sync/syncFieldSemantics.js +55 -0
- package/dist/sync/syncResourceAdapters.d.ts +44 -0
- package/dist/sync/syncResourceAdapters.js +77 -0
- package/dist/sync/syncService.d.ts +6 -0
- package/dist/sync/syncService.js +60 -0
- package/dist/sync/syncService.test.js +71 -3
- package/dist/sync/taskSyncChangeKey.d.ts +2 -0
- package/dist/sync/taskSyncChangeKey.js +143 -0
- package/dist/sync/taskSyncPayload.js +3 -8
- package/dist/sync/workspacePullFeed.js +4 -3
- package/dist/sync/workspacePullFeed.test.js +43 -0
- package/dist/sync/workspaceRepair.js +5 -2
- package/dist/sync/workspaceSyncModel.d.ts +48 -0
- package/dist/sync/workspaceSyncModel.js +195 -31
- package/dist/sync/workspaceSyncModel.test.js +325 -12
- package/dist/sync/workspaceSyncState.d.ts +19 -0
- package/dist/sync/workspaceSyncState.js +3 -1
- package/dist/sync/workspaceSyncSurface.js +3 -8
- package/dist/types.d.ts +17 -0
- package/dist/ui/assets/AgentsModule-DrLawwNl.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BlS-cqnl.js → AnnotatedAttachmentWorkspace-Z9_whf7F.js} +1 -1
- package/dist/ui/assets/CourierPrime-Bold-BuLj_dpw.woff2 +0 -0
- package/dist/ui/assets/CourierPrime-BoldItalic-BrK2pUkZ.woff2 +0 -0
- package/dist/ui/assets/CourierPrime-Italic-DFUZK5Ey.woff2 +0 -0
- package/dist/ui/assets/CourierPrime-Regular-BsKphzVf.woff2 +0 -0
- package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +252 -0
- package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +1 -0
- package/dist/ui/assets/{InitiativesModule-BjR6iBf9.js → InitiativesModule-9E6ParrY.js} +1 -1
- package/dist/ui/assets/Noir_medium-bxQwKGzB.woff2 +0 -0
- package/dist/ui/assets/Noir_regular-ojf2kxlG.woff2 +0 -0
- package/dist/ui/assets/PlansPage-CvfnMiWq.css +1 -0
- package/dist/ui/assets/PlansPage-DRXscYxH.js +1 -0
- package/dist/ui/assets/RussoOne-Regular-Cu_qq_qC.woff2 +0 -0
- package/dist/ui/assets/SpecialElite-Regular-Di6gSvAS.woff2 +0 -0
- package/dist/ui/assets/TaskSettings-DZX4jk7e.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-DnFqdUME.js → WorkflowsModule-BA7jcEpH.js} +1 -1
- package/dist/ui/assets/index-9eT8e0Lu.css +1 -0
- package/dist/ui/assets/index-C6k75jr8.js +6 -0
- package/dist/ui/assets/{vendor-icons-I7ZwG1z_.js → vendor-icons-DuEd_65c.js} +1 -1
- package/dist/ui/fonts/CourierPrime-Bold.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-Bold.woff2 +0 -0
- package/dist/ui/fonts/CourierPrime-BoldItalic.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-BoldItalic.woff2 +0 -0
- package/dist/ui/fonts/CourierPrime-Italic.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-Italic.woff2 +0 -0
- package/dist/ui/fonts/CourierPrime-Regular.ttf +0 -0
- package/dist/ui/fonts/CourierPrime-Regular.woff2 +0 -0
- package/dist/ui/fonts/Noir_medium.otf +0 -0
- package/dist/ui/fonts/Noir_medium.woff2 +0 -0
- package/dist/ui/fonts/Noir_regular.otf +0 -0
- package/dist/ui/fonts/Noir_regular.woff2 +0 -0
- package/dist/ui/fonts/RussoOne-Regular.woff2 +0 -0
- package/dist/ui/fonts/SpecialElite-Regular.ttf +0 -0
- package/dist/ui/fonts/SpecialElite-Regular.woff2 +0 -0
- package/dist/ui/index.html +3 -3
- package/dist/utils/accountProfileSummaryCache.d.ts +5 -0
- package/dist/utils/assignees.d.ts +2 -0
- package/dist/utils/assignees.js +2 -0
- package/dist/utils/avatarUpload.d.ts +27 -0
- package/dist/utils/avatarUpload.js +132 -0
- package/dist/utils/avatarUpload.test.d.ts +1 -0
- package/dist/utils/avatarUpload.test.js +40 -0
- package/dist/utils/bootstrapTrace.d.ts +7 -0
- package/dist/utils/bootstrapTrace.js +29 -0
- package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
- package/dist/utils/commercialLifecyclePresentation.js +199 -0
- package/dist/utils/contextAssetEvents.d.ts +2 -0
- package/dist/utils/syncEventDetails.d.ts +3 -0
- package/dist/utils/syncEventDetails.js +64 -0
- package/dist/utils/syncEventDetails.test.d.ts +1 -0
- package/dist/utils/syncEventDetails.test.js +23 -0
- package/dist/utils/syncEventPresentation.d.ts +15 -0
- package/dist/utils/syncEventPresentation.js +129 -0
- package/dist/utils/syncEventPresentation.test.d.ts +1 -0
- package/dist/utils/syncEventPresentation.test.js +64 -0
- package/dist/utils/syncStatusPresentation.d.ts +72 -0
- package/dist/utils/syncStatusPresentation.js +217 -0
- package/dist/utils/syncStatusPresentation.test.d.ts +1 -0
- package/dist/utils/syncStatusPresentation.test.js +164 -0
- package/dist/utils/taskActivity.d.ts +2 -0
- package/dist/utils/taskActivity.js +228 -22
- package/dist/utils/taskActivity.test.js +119 -8
- package/dist/utils/workspaceSyncPresentation.d.ts +4 -0
- package/dist/utils/workspaceSyncPresentation.js +68 -2
- package/dist/utils/workspaceSyncPresentation.test.js +36 -1
- package/package.json +10 -2
- package/scripts/check-cloud-mcp.mjs +83 -0
- package/scripts/export-sqlite-to-postgres.mjs +7 -9
- package/scripts/playwright-auth.sh +5 -1
- package/scripts/run-billing-performance-smoke.sh +24 -0
- package/scripts/run-e2e-realtime.sh +17 -3
- package/scripts/run-smoke.sh +17 -5
- package/scripts/run-soak.sh +17 -5
- package/src/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
- package/src/resources/templates/workflows/collaborate.yaml +1 -1
- package/src/resources/templates/workflows/evaluate.yaml +2 -2
- package/src/resources/templates/workflows/plan.yaml +1 -1
- package/src/resources/templates/workflows/review.yaml +2 -2
- package/dist/ui/assets/AgentsModule-BLWVbuKP.js +0 -1
- package/dist/ui/assets/DocumentWorkspace-BH_6DF6x.js +0 -250
- package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
- package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
- package/dist/ui/assets/PlansPage-CSDQ4sLa.js +0 -1
- package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
- package/dist/ui/assets/TaskSettings-CEDy34Jo.js +0 -9
- package/dist/ui/assets/index-DXUdtH1B.js +0 -6
- package/dist/ui/assets/index-Ii0B0rTO.css +0 -1
- package/scripts/migrate-planning-model.ts +0 -233
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { TaskforceRuleError } from './types.js';
|
|
2
|
-
import { DOCUMENT_CLOUD_STORAGE_FEATURE_KEY, WORKSPACES_FEATURE_KEY, DOCUMENT_CLOUD_STORAGE_LIMIT_CONFIG_KEY, WORKSPACES_LIMIT_CONFIG_KEY, DEFAULT_PLAN_FEATURE_CATALOG, PLAN_FEATURE_DEPENDENCIES, generateUuidV7, } from './shared.js';
|
|
2
|
+
import { DOCUMENT_CLOUD_STORAGE_FEATURE_KEY, WORKSPACES_FEATURE_KEY, AI_PROFILES_FEATURE_KEY, DOCUMENT_CLOUD_STORAGE_LIMIT_CONFIG_KEY, WORKSPACES_LIMIT_CONFIG_KEY, AI_PROFILES_LIMIT_CONFIG_KEY, DEFAULT_PLAN_FEATURE_CATALOG, PLAN_FEATURE_DEPENDENCIES, generateUuidV7, } from './shared.js';
|
|
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
|
-
|
|
134
|
+
const normalizedFeatureKey = this.canonicalizePlanFeatureKey(featureKey);
|
|
135
|
+
if (normalizedFeatureKey === 'collaboration.seat_limit')
|
|
95
136
|
return {};
|
|
96
|
-
if (
|
|
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,12 +148,21 @@ export class PlanEntitlementService {
|
|
|
107
148
|
}
|
|
108
149
|
return {};
|
|
109
150
|
}
|
|
110
|
-
if (
|
|
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
|
}
|
|
157
|
+
if (normalizedFeatureKey === AI_PROFILES_FEATURE_KEY) {
|
|
158
|
+
if (access === 'limited') {
|
|
159
|
+
const limit = this.readPositiveIntegerConfig(config[AI_PROFILES_LIMIT_CONFIG_KEY], 1);
|
|
160
|
+
return {
|
|
161
|
+
[AI_PROFILES_LIMIT_CONFIG_KEY]: limit ?? 1
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
116
166
|
return config && typeof config === 'object' ? { ...config } : {};
|
|
117
167
|
}
|
|
118
168
|
isEntitlementStateAllowed(state) {
|
|
@@ -135,21 +185,25 @@ export class PlanEntitlementService {
|
|
|
135
185
|
LIMIT 1
|
|
136
186
|
`).get(this.tenantId, accountId);
|
|
137
187
|
}
|
|
138
|
-
|
|
188
|
+
normalizeExpiredLifecycleRow(accountId) {
|
|
139
189
|
const row = this.readAccountEntitlementRow(accountId);
|
|
140
190
|
if (!row?.account_id || !row.plan_version_id) {
|
|
141
191
|
return row;
|
|
142
192
|
}
|
|
143
193
|
const currentState = this.normalizeEntitlementState(row.entitlement_state);
|
|
144
|
-
if (
|
|
194
|
+
if (!this.isIsoTimestampExpired(row.effective_until || null)) {
|
|
195
|
+
return row;
|
|
196
|
+
}
|
|
197
|
+
if (currentState !== 'checkout_pending' && currentState !== 'trialing') {
|
|
145
198
|
return row;
|
|
146
199
|
}
|
|
200
|
+
const updatedBy = currentState === 'trialing' ? 'trial-expired' : 'checkout-expired';
|
|
147
201
|
const now = new Date().toISOString();
|
|
148
202
|
this.db.prepare(`
|
|
149
203
|
UPDATE account_entitlements
|
|
150
204
|
SET entitlement_state = ?, effective_until = NULL, updated_by = ?, updated_at = ?
|
|
151
205
|
WHERE tenant_id = ? AND account_id = ?
|
|
152
|
-
`).run('pending_plan_selection',
|
|
206
|
+
`).run('pending_plan_selection', updatedBy, now, this.tenantId, accountId);
|
|
153
207
|
this.createUserBillingRecord(accountId, {
|
|
154
208
|
entitlementState: 'pending_plan_selection',
|
|
155
209
|
planId: null,
|
|
@@ -160,6 +214,20 @@ export class PlanEntitlementService {
|
|
|
160
214
|
isBillingConfirmedTransitionSource(source) {
|
|
161
215
|
return source === 'checkout-confirm' || source === 'stripe-webhook';
|
|
162
216
|
}
|
|
217
|
+
countWorkspaceStorageUsageBytes(workspaceIdRaw) {
|
|
218
|
+
const workspaceId = String(workspaceIdRaw || '').trim();
|
|
219
|
+
if (!workspaceId)
|
|
220
|
+
return 0;
|
|
221
|
+
const row = this.db.prepare(`
|
|
222
|
+
SELECT COALESCE(SUM(size_bytes), 0) AS total_bytes
|
|
223
|
+
FROM workspace_assets
|
|
224
|
+
WHERE tenant_id = ?
|
|
225
|
+
AND workspace_id = ?
|
|
226
|
+
AND deleted_at IS NULL
|
|
227
|
+
`).get(this.tenantId, workspaceId);
|
|
228
|
+
const total = Number(row?.total_bytes || 0);
|
|
229
|
+
return Number.isFinite(total) ? Math.max(0, Math.floor(total)) : 0;
|
|
230
|
+
}
|
|
163
231
|
resolveCanonicalTransitionSource(updatedBy) {
|
|
164
232
|
if (updatedBy === 'registration'
|
|
165
233
|
|| updatedBy === 'checkout-start'
|
|
@@ -232,8 +300,48 @@ export class PlanEntitlementService {
|
|
|
232
300
|
normalizeLegacyPlanDataIfNeeded() {
|
|
233
301
|
this.migrateLegacySeatLimitFeatureConfigToVersionSeatLimit();
|
|
234
302
|
this.normalizeLegacyBillingMirrorRows();
|
|
303
|
+
this.renameLegacyWorkspacesFeatureKey();
|
|
235
304
|
this.removeLegacyPlanFeatureRows();
|
|
236
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
|
+
}
|
|
237
345
|
removeLegacyPlanFeatureRows() {
|
|
238
346
|
const featureKeys = [...LEGACY_REMOVED_PLAN_FEATURE_KEYS];
|
|
239
347
|
if (featureKeys.length < 1)
|
|
@@ -524,7 +632,9 @@ export class PlanEntitlementService {
|
|
|
524
632
|
return { limit: 1, feature: null };
|
|
525
633
|
}
|
|
526
634
|
const entitlement = this.getAccountEntitlement(normalizedUserId);
|
|
527
|
-
const feature = entitlement.version.features[WORKSPACES_FEATURE_KEY]
|
|
635
|
+
const feature = entitlement.version.features[WORKSPACES_FEATURE_KEY]
|
|
636
|
+
|| entitlement.version.features[LEGACY_WORKSPACES_FEATURE_KEY]
|
|
637
|
+
|| null;
|
|
528
638
|
if (!feature || feature.access === 'disabled') {
|
|
529
639
|
return { limit: 1, feature };
|
|
530
640
|
}
|
|
@@ -534,6 +644,38 @@ export class PlanEntitlementService {
|
|
|
534
644
|
}
|
|
535
645
|
return { limit: null, feature };
|
|
536
646
|
}
|
|
647
|
+
resolveAiProfileLimitForWorkspace(workspaceId) {
|
|
648
|
+
const normalizedWorkspaceId = String(workspaceId || '').trim();
|
|
649
|
+
if (!normalizedWorkspaceId) {
|
|
650
|
+
return { limit: null, feature: null };
|
|
651
|
+
}
|
|
652
|
+
const accountId = this.deps.getWorkspaceOwnerUserId(normalizedWorkspaceId);
|
|
653
|
+
if (!accountId) {
|
|
654
|
+
return { limit: null, feature: null };
|
|
655
|
+
}
|
|
656
|
+
let entitlement;
|
|
657
|
+
try {
|
|
658
|
+
entitlement = this.getAccountEntitlement(accountId);
|
|
659
|
+
}
|
|
660
|
+
catch (error) {
|
|
661
|
+
if (error instanceof TaskforceRuleError && error.code === 'ENTITLEMENT_NOT_FOUND') {
|
|
662
|
+
return { limit: null, feature: null };
|
|
663
|
+
}
|
|
664
|
+
throw error;
|
|
665
|
+
}
|
|
666
|
+
const feature = entitlement.version.features[AI_PROFILES_FEATURE_KEY] || null;
|
|
667
|
+
if (!feature || feature.access === 'disabled') {
|
|
668
|
+
return { limit: null, feature };
|
|
669
|
+
}
|
|
670
|
+
const configuredLimit = this.readPositiveIntegerConfig(feature.config?.[AI_PROFILES_LIMIT_CONFIG_KEY], 1);
|
|
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 };
|
|
678
|
+
}
|
|
537
679
|
planHasAnyActivePricing(planIdRaw) {
|
|
538
680
|
const planId = String(planIdRaw || '').trim().toLowerCase();
|
|
539
681
|
if (!planId)
|
|
@@ -629,32 +771,15 @@ export class PlanEntitlementService {
|
|
|
629
771
|
throw error;
|
|
630
772
|
}
|
|
631
773
|
}
|
|
632
|
-
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
AND bpm.plan_version_id = v.plan_version_id
|
|
642
|
-
WHERE v.tenant_id = ?
|
|
643
|
-
AND COALESCE(p.status, 'active') = 'active'
|
|
644
|
-
AND bpm.active = 1
|
|
645
|
-
AND bpm.pricing_type = 'free'
|
|
646
|
-
ORDER BY v.is_default DESC, p.updated_at DESC, p.created_at DESC, v.version_number DESC, v.created_at DESC
|
|
647
|
-
`).all(this.tenantId);
|
|
648
|
-
for (const row of rows) {
|
|
649
|
-
const planVersionId = String(row.plan_version_id || '').trim();
|
|
650
|
-
if (!planVersionId)
|
|
651
|
-
continue;
|
|
652
|
-
const version = this.getPlanVersionSnapshot(planVersionId);
|
|
653
|
-
if (!this.isTeamPlanVersion(version)) {
|
|
654
|
-
return version;
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
return null;
|
|
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 };
|
|
658
783
|
}
|
|
659
784
|
getOnboardingPolicy() {
|
|
660
785
|
const billing = this.deps.readGlobalConfig().billing || {};
|
|
@@ -664,23 +789,32 @@ export class PlanEntitlementService {
|
|
|
664
789
|
if (policy?.mode === 'require_plan_selection'
|
|
665
790
|
|| policy?.mode === 'auto_grant_free'
|
|
666
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;
|
|
667
810
|
return {
|
|
668
811
|
mode: policy.mode,
|
|
669
|
-
|
|
670
|
-
? (policy.planVersionId.trim() || null)
|
|
671
|
-
: null
|
|
672
|
-
};
|
|
673
|
-
}
|
|
674
|
-
const implicitFreeVersion = this.tryGetImplicitFreeOnboardingPlanVersion();
|
|
675
|
-
if (implicitFreeVersion) {
|
|
676
|
-
return {
|
|
677
|
-
mode: 'auto_grant_free',
|
|
678
|
-
planVersionId: implicitFreeVersion.planVersionId
|
|
812
|
+
planId: explicitPlanId || resolvedLegacyPlanId
|
|
679
813
|
};
|
|
680
814
|
}
|
|
681
815
|
return {
|
|
682
816
|
mode: 'require_plan_selection',
|
|
683
|
-
|
|
817
|
+
planId: null
|
|
684
818
|
};
|
|
685
819
|
}
|
|
686
820
|
validateOnboardingPolicy(input) {
|
|
@@ -690,64 +824,58 @@ export class PlanEntitlementService {
|
|
|
690
824
|
: input?.mode === 'require_plan_selection'
|
|
691
825
|
? 'require_plan_selection'
|
|
692
826
|
: current.mode;
|
|
693
|
-
const
|
|
694
|
-
? (typeof input.
|
|
695
|
-
: current.
|
|
827
|
+
const planId = input?.planId !== undefined
|
|
828
|
+
? (typeof input.planId === 'string' ? input.planId.trim().toLowerCase() || null : null)
|
|
829
|
+
: current.planId;
|
|
696
830
|
if (mode === 'require_plan_selection') {
|
|
697
831
|
return { valid: true };
|
|
698
832
|
}
|
|
699
|
-
if (!
|
|
833
|
+
if (!planId) {
|
|
700
834
|
return {
|
|
701
835
|
valid: false,
|
|
702
|
-
code: '
|
|
703
|
-
message: 'An onboarding plan
|
|
836
|
+
code: 'ONBOARDING_PLAN_REQUIRED',
|
|
837
|
+
message: 'An onboarding plan is required for the selected onboarding mode.'
|
|
704
838
|
};
|
|
705
839
|
}
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
FROM plan_versions v
|
|
709
|
-
JOIN plan_catalog p ON p.tenant_id = v.tenant_id AND p.plan_id = v.plan_id
|
|
710
|
-
WHERE v.tenant_id = ? AND v.plan_version_id = ?
|
|
711
|
-
LIMIT 1
|
|
712
|
-
`).get(this.tenantId, planVersionId);
|
|
713
|
-
if (!row?.plan_version_id || !row.plan_id) {
|
|
840
|
+
const onboardingPlan = this.getOnboardingPlanSnapshot(planId);
|
|
841
|
+
if (!onboardingPlan?.plan || !onboardingPlan.defaultVersion) {
|
|
714
842
|
return {
|
|
715
843
|
valid: false,
|
|
716
|
-
code: '
|
|
717
|
-
message: `Configured onboarding plan
|
|
718
|
-
details: {
|
|
844
|
+
code: 'ONBOARDING_PLAN_NOT_FOUND',
|
|
845
|
+
message: `Configured onboarding plan "${planId}" was not found or has no default version.`,
|
|
846
|
+
details: { planId }
|
|
719
847
|
};
|
|
720
848
|
}
|
|
721
|
-
if (
|
|
849
|
+
if (onboardingPlan.plan.status !== 'active') {
|
|
722
850
|
return {
|
|
723
851
|
valid: false,
|
|
724
852
|
code: 'ONBOARDING_PLAN_NOT_ENABLED',
|
|
725
|
-
message: `Configured onboarding plan
|
|
853
|
+
message: `Configured onboarding plan "${planId}" is disabled.`,
|
|
726
854
|
details: {
|
|
727
|
-
|
|
728
|
-
|
|
855
|
+
planId: onboardingPlan.plan.planId,
|
|
856
|
+
planVersionId: onboardingPlan.defaultVersion.planVersionId
|
|
729
857
|
}
|
|
730
858
|
};
|
|
731
859
|
}
|
|
732
|
-
if (mode === 'auto_grant_free' && !this.planVersionHasActiveFreePricing(planVersionId)) {
|
|
860
|
+
if (mode === 'auto_grant_free' && !this.planVersionHasActiveFreePricing(onboardingPlan.defaultVersion.planVersionId)) {
|
|
733
861
|
return {
|
|
734
862
|
valid: false,
|
|
735
863
|
code: 'ONBOARDING_PLAN_MUST_BE_FREE',
|
|
736
|
-
message: `Configured onboarding plan
|
|
864
|
+
message: `Configured onboarding plan "${planId}" does not have active free pricing on its default version.`,
|
|
737
865
|
details: {
|
|
738
|
-
|
|
739
|
-
|
|
866
|
+
planId: onboardingPlan.plan.planId,
|
|
867
|
+
planVersionId: onboardingPlan.defaultVersion.planVersionId
|
|
740
868
|
}
|
|
741
869
|
};
|
|
742
870
|
}
|
|
743
|
-
if (mode === 'auto_start_trial' && !this.planVersionIsTrialEligible(planVersionId)) {
|
|
871
|
+
if (mode === 'auto_start_trial' && !this.planVersionIsTrialEligible(onboardingPlan.defaultVersion.planVersionId)) {
|
|
744
872
|
return {
|
|
745
873
|
valid: false,
|
|
746
874
|
code: 'ONBOARDING_PLAN_TRIAL_REQUIRED',
|
|
747
|
-
message: `Configured onboarding plan
|
|
875
|
+
message: `Configured onboarding plan "${planId}" is not trial eligible on its default version.`,
|
|
748
876
|
details: {
|
|
749
|
-
|
|
750
|
-
|
|
877
|
+
planId: onboardingPlan.plan.planId,
|
|
878
|
+
planVersionId: onboardingPlan.defaultVersion.planVersionId
|
|
751
879
|
}
|
|
752
880
|
};
|
|
753
881
|
}
|
|
@@ -755,7 +883,12 @@ export class PlanEntitlementService {
|
|
|
755
883
|
}
|
|
756
884
|
resolveRegistrationOnboardingOutcome(selectedPlanVersionIdRaw) {
|
|
757
885
|
const selectedPlanVersionId = String(selectedPlanVersionIdRaw || '').trim();
|
|
758
|
-
|
|
886
|
+
const onboardingPolicy = this.getOnboardingPolicy();
|
|
887
|
+
const validation = this.validateOnboardingPolicy(onboardingPolicy);
|
|
888
|
+
if (!validation.valid) {
|
|
889
|
+
throw new TaskforceRuleError(validation.message || 'Onboarding policy is misconfigured.', 500, validation.code || 'ONBOARDING_POLICY_INVALID', validation.details);
|
|
890
|
+
}
|
|
891
|
+
if (onboardingPolicy.mode === 'require_plan_selection' && selectedPlanVersionId) {
|
|
759
892
|
const planVersion = this.getPlanVersionSnapshot(selectedPlanVersionId);
|
|
760
893
|
const commercialState = this.planVersionHasActiveFreePricing(planVersion.planVersionId)
|
|
761
894
|
? 'active'
|
|
@@ -768,11 +901,6 @@ export class PlanEntitlementService {
|
|
|
768
901
|
planId: planVersion.planId
|
|
769
902
|
};
|
|
770
903
|
}
|
|
771
|
-
const onboardingPolicy = this.getOnboardingPolicy();
|
|
772
|
-
const validation = this.validateOnboardingPolicy(onboardingPolicy);
|
|
773
|
-
if (!validation.valid) {
|
|
774
|
-
throw new TaskforceRuleError(validation.message || 'Onboarding policy is misconfigured.', 500, validation.code || 'ONBOARDING_POLICY_INVALID', validation.details);
|
|
775
|
-
}
|
|
776
904
|
if (onboardingPolicy.mode === 'require_plan_selection') {
|
|
777
905
|
return {
|
|
778
906
|
requiresPlanSelection: true,
|
|
@@ -782,11 +910,11 @@ export class PlanEntitlementService {
|
|
|
782
910
|
planId: null
|
|
783
911
|
};
|
|
784
912
|
}
|
|
785
|
-
const configuredVersion = onboardingPolicy.
|
|
786
|
-
? this.
|
|
913
|
+
const configuredVersion = onboardingPolicy.planId
|
|
914
|
+
? this.getOnboardingPlanSnapshot(onboardingPolicy.planId)?.defaultVersion || null
|
|
787
915
|
: null;
|
|
788
916
|
if (!configuredVersion) {
|
|
789
|
-
throw new TaskforceRuleError('Configured onboarding plan
|
|
917
|
+
throw new TaskforceRuleError('Configured onboarding plan is required.', 500, 'ONBOARDING_PLAN_REQUIRED');
|
|
790
918
|
}
|
|
791
919
|
return {
|
|
792
920
|
requiresPlanSelection: false,
|
|
@@ -796,6 +924,26 @@ export class PlanEntitlementService {
|
|
|
796
924
|
planId: configuredVersion.planId
|
|
797
925
|
};
|
|
798
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
|
+
}
|
|
799
947
|
// ─── Public billing methods ───────────────────────────────────────────────
|
|
800
948
|
createUserBillingRecord(userId, options) {
|
|
801
949
|
const now = new Date().toISOString();
|
|
@@ -844,6 +992,108 @@ export class PlanEntitlementService {
|
|
|
844
992
|
throw error;
|
|
845
993
|
}
|
|
846
994
|
}
|
|
995
|
+
tryAutoHealLegacyPersonalFreeEntitlement(accountId, entitlement) {
|
|
996
|
+
if (entitlement.state !== 'canceled')
|
|
997
|
+
return null;
|
|
998
|
+
if (this.isTeamPlanVersion(entitlement.version))
|
|
999
|
+
return null;
|
|
1000
|
+
const defaultPersonalVersion = this.tryGetDefaultPlanVersionByMode('personal');
|
|
1001
|
+
if (!defaultPersonalVersion)
|
|
1002
|
+
return null;
|
|
1003
|
+
if (!this.planVersionHasActiveFreePricing(defaultPersonalVersion.planVersionId))
|
|
1004
|
+
return null;
|
|
1005
|
+
if (String(entitlement.planId || '').trim().toLowerCase() !== defaultPersonalVersion.planId)
|
|
1006
|
+
return null;
|
|
1007
|
+
if (entitlement.planVersionId === defaultPersonalVersion.planVersionId)
|
|
1008
|
+
return null;
|
|
1009
|
+
if (entitlement.version.versionNumber >= defaultPersonalVersion.versionNumber)
|
|
1010
|
+
return null;
|
|
1011
|
+
return this.transitionCanonicalCommercialState({
|
|
1012
|
+
accountId,
|
|
1013
|
+
planVersionId: defaultPersonalVersion.planVersionId,
|
|
1014
|
+
state: 'active',
|
|
1015
|
+
effectiveAt: entitlement.effectiveAt,
|
|
1016
|
+
effectiveUntil: null,
|
|
1017
|
+
billingMirrorPlanId: defaultPersonalVersion.planId,
|
|
1018
|
+
source: 'system',
|
|
1019
|
+
updatedBy: 'legacy-free-auto-heal',
|
|
1020
|
+
createdAt: entitlement.effectiveAt
|
|
1021
|
+
});
|
|
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
|
+
}
|
|
847
1097
|
getAccountAccessStatus(accountId) {
|
|
848
1098
|
const normalizedAccountId = String(accountId || '').trim();
|
|
849
1099
|
if (!normalizedAccountId) {
|
|
@@ -851,7 +1101,12 @@ export class PlanEntitlementService {
|
|
|
851
1101
|
}
|
|
852
1102
|
const onboardingPolicy = this.getOnboardingPolicy();
|
|
853
1103
|
const validation = this.validateOnboardingPolicy(onboardingPolicy);
|
|
854
|
-
const
|
|
1104
|
+
const currentEntitlement = this.tryGetAccountEntitlement(normalizedAccountId);
|
|
1105
|
+
const entitlement = currentEntitlement
|
|
1106
|
+
? (this.tryAutoHealLegacyPersonalFreeEntitlement(normalizedAccountId, currentEntitlement)
|
|
1107
|
+
|| this.tryAutoHealCommercialEntitlementFromBillingMirror(normalizedAccountId, currentEntitlement)
|
|
1108
|
+
|| currentEntitlement)
|
|
1109
|
+
: this.tryAutoHealCommercialEntitlementFromBillingMirror(normalizedAccountId, null);
|
|
855
1110
|
if (!entitlement) {
|
|
856
1111
|
return !validation.valid
|
|
857
1112
|
? this.buildBlockedAccessStatus({
|
|
@@ -949,7 +1204,7 @@ export class PlanEntitlementService {
|
|
|
949
1204
|
if (!normalizedAccountId) {
|
|
950
1205
|
throw new TaskforceRuleError('accountId is required', 400, 'ACCOUNT_ID_REQUIRED');
|
|
951
1206
|
}
|
|
952
|
-
const row = this.
|
|
1207
|
+
const row = this.normalizeExpiredLifecycleRow(normalizedAccountId);
|
|
953
1208
|
if (!row?.account_id || !row.plan_version_id) {
|
|
954
1209
|
throw new TaskforceRuleError('Entitlement not found', 404, 'ENTITLEMENT_NOT_FOUND');
|
|
955
1210
|
}
|
|
@@ -989,6 +1244,24 @@ export class PlanEntitlementService {
|
|
|
989
1244
|
const remaining = limit === null ? null : Math.max(0, limit - used);
|
|
990
1245
|
return { used, limit, remaining };
|
|
991
1246
|
}
|
|
1247
|
+
countAiProfileUsage(workspaceId) {
|
|
1248
|
+
const normalizedWorkspaceId = String(workspaceId || '').trim();
|
|
1249
|
+
if (!normalizedWorkspaceId) {
|
|
1250
|
+
throw new TaskforceRuleError('workspaceId is required', 400, 'WORKSPACE_ID_REQUIRED');
|
|
1251
|
+
}
|
|
1252
|
+
const row = this.db.prepare(`
|
|
1253
|
+
SELECT COUNT(*) AS c
|
|
1254
|
+
FROM ai_profiles
|
|
1255
|
+
WHERE tenant_id = ?
|
|
1256
|
+
AND workspace_id = ?
|
|
1257
|
+
AND archived_at IS NULL
|
|
1258
|
+
AND seat_scope = 'cloud_metered'
|
|
1259
|
+
`).get(this.tenantId, normalizedWorkspaceId);
|
|
1260
|
+
const used = Math.max(0, Number(row?.c || 0));
|
|
1261
|
+
const { limit } = this.resolveAiProfileLimitForWorkspace(normalizedWorkspaceId);
|
|
1262
|
+
const remaining = limit === null ? null : Math.max(0, limit - used);
|
|
1263
|
+
return { used, limit, remaining };
|
|
1264
|
+
}
|
|
992
1265
|
assertSeatAvailable(workspaceId) {
|
|
993
1266
|
const seatUsage = this.countWorkspaceSeatUsage(workspaceId, { includeInvited: true });
|
|
994
1267
|
if (seatUsage.limit !== null && seatUsage.used >= seatUsage.limit) {
|
|
@@ -1026,6 +1299,19 @@ export class PlanEntitlementService {
|
|
|
1026
1299
|
}
|
|
1027
1300
|
return { allowed: true, reasonCode: 'OK', state: entitlement.state, seatUsage, feature };
|
|
1028
1301
|
}
|
|
1302
|
+
if (featureKey === DOCUMENT_CLOUD_STORAGE_FEATURE_KEY && feature.access === 'limited') {
|
|
1303
|
+
const configuredLimitMb = this.readPositiveIntegerConfig(feature.config?.[DOCUMENT_CLOUD_STORAGE_LIMIT_CONFIG_KEY], 1) ?? 1;
|
|
1304
|
+
const storageLimitBytes = configuredLimitMb * 1024 * 1024;
|
|
1305
|
+
const rawRequestedBytes = input.context?.requestedBytes ?? input.context?.sizeBytes ?? input.context?.pendingSizeBytes;
|
|
1306
|
+
const requestedBytesNumber = Number(rawRequestedBytes);
|
|
1307
|
+
const requestedBytes = Number.isFinite(requestedBytesNumber)
|
|
1308
|
+
? Math.max(0, Math.floor(requestedBytesNumber))
|
|
1309
|
+
: 0;
|
|
1310
|
+
const usedBytes = this.countWorkspaceStorageUsageBytes(workspaceId);
|
|
1311
|
+
if ((usedBytes + requestedBytes) > storageLimitBytes) {
|
|
1312
|
+
return { allowed: false, reasonCode: 'UPLOAD_STORAGE_LIMIT_REACHED', state: entitlement.state, feature };
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1029
1315
|
return { allowed: true, reasonCode: 'OK', state: entitlement.state, feature };
|
|
1030
1316
|
}
|
|
1031
1317
|
getUserAccountPlanMode(userId) {
|
|
@@ -1067,6 +1353,20 @@ export class PlanEntitlementService {
|
|
|
1067
1353
|
entitlementState: 'active'
|
|
1068
1354
|
};
|
|
1069
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
|
+
}
|
|
1070
1370
|
// ─── Plan catalog admin ───────────────────────────────────────────────────
|
|
1071
1371
|
isValidEntitlementTransition(fromState, toState) {
|
|
1072
1372
|
if (fromState === toState)
|
|
@@ -1094,6 +1394,7 @@ export class PlanEntitlementService {
|
|
|
1094
1394
|
upsertPlanCatalog(input) {
|
|
1095
1395
|
const planId = String(input.planId || '').trim().toLowerCase();
|
|
1096
1396
|
const displayName = String(input.displayName || '').trim();
|
|
1397
|
+
const description = String(input.description || '').trim() || null;
|
|
1097
1398
|
const initialMode = String(input.initialMode || '').trim().toLowerCase() === 'team'
|
|
1098
1399
|
? 'team'
|
|
1099
1400
|
: 'personal';
|
|
@@ -1119,7 +1420,7 @@ export class PlanEntitlementService {
|
|
|
1119
1420
|
});
|
|
1120
1421
|
const now = new Date().toISOString();
|
|
1121
1422
|
const existing = this.db.prepare(`
|
|
1122
|
-
SELECT plan_id, status
|
|
1423
|
+
SELECT plan_id, status, description
|
|
1123
1424
|
FROM plan_catalog
|
|
1124
1425
|
WHERE tenant_id = ? AND plan_id = ?
|
|
1125
1426
|
LIMIT 1
|
|
@@ -1136,13 +1437,14 @@ export class PlanEntitlementService {
|
|
|
1136
1437
|
: 'archived';
|
|
1137
1438
|
const tx = this.db.transaction(() => {
|
|
1138
1439
|
this.db.prepare(`
|
|
1139
|
-
INSERT INTO plan_catalog (plan_id, tenant_id, display_name, status, created_at, updated_at)
|
|
1140
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
1440
|
+
INSERT INTO plan_catalog (plan_id, tenant_id, display_name, description, status, created_at, updated_at)
|
|
1441
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1141
1442
|
ON CONFLICT (plan_id) DO UPDATE SET
|
|
1142
1443
|
display_name = excluded.display_name,
|
|
1444
|
+
description = excluded.description,
|
|
1143
1445
|
status = excluded.status,
|
|
1144
1446
|
updated_at = excluded.updated_at
|
|
1145
|
-
`).run(planId, this.tenantId, displayName, status, now, now);
|
|
1447
|
+
`).run(planId, this.tenantId, displayName, description, status, now, now);
|
|
1146
1448
|
if (isNewPlan) {
|
|
1147
1449
|
this.db.prepare(`
|
|
1148
1450
|
INSERT INTO plan_versions (plan_version_id, tenant_id, plan_id, version_number, is_default, seat_limit, metadata_json, created_at, updated_at)
|
|
@@ -1168,11 +1470,11 @@ export class PlanEntitlementService {
|
|
|
1168
1470
|
});
|
|
1169
1471
|
tx();
|
|
1170
1472
|
this.repairPlanDefaultVersionIfNeeded(planId);
|
|
1171
|
-
return { planId, displayName, status };
|
|
1473
|
+
return { planId, displayName, description, status };
|
|
1172
1474
|
}
|
|
1173
1475
|
listPlanCatalog() {
|
|
1174
1476
|
const rows = this.db.prepare(`
|
|
1175
|
-
SELECT plan_id, display_name, status, created_at, updated_at
|
|
1477
|
+
SELECT plan_id, display_name, description, status, created_at, updated_at
|
|
1176
1478
|
FROM plan_catalog
|
|
1177
1479
|
WHERE tenant_id = ?
|
|
1178
1480
|
ORDER BY updated_at DESC, created_at DESC
|
|
@@ -1180,21 +1482,76 @@ export class PlanEntitlementService {
|
|
|
1180
1482
|
return rows.map((row) => ({
|
|
1181
1483
|
planId: String(row.plan_id || ''),
|
|
1182
1484
|
displayName: String(row.display_name || ''),
|
|
1485
|
+
description: typeof row.description === 'string' && row.description.trim().length > 0 ? row.description.trim() : null,
|
|
1183
1486
|
status: String(row.status || '').trim().toLowerCase() === 'archived' ? 'archived' : 'active',
|
|
1184
1487
|
createdAt: String(row.created_at || ''),
|
|
1185
1488
|
updatedAt: String(row.updated_at || '')
|
|
1186
1489
|
}));
|
|
1187
1490
|
}
|
|
1188
1491
|
listPlanFeatureCatalog() {
|
|
1492
|
+
const overrides = this.listPlanFeatureCatalogOverrideMap();
|
|
1189
1493
|
return DEFAULT_PLAN_FEATURE_CATALOG.map((feature) => ({
|
|
1190
1494
|
featureKey: feature.featureKey,
|
|
1191
1495
|
label: feature.label,
|
|
1192
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,
|
|
1193
1500
|
allowedAccessModes: [...feature.allowedAccessModes],
|
|
1194
1501
|
configTemplate: { ...feature.configTemplate },
|
|
1195
1502
|
...(feature.dependsOn?.length ? { dependsOn: [...feature.dependsOn] } : {})
|
|
1196
1503
|
}));
|
|
1197
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
|
+
}
|
|
1198
1555
|
listPlanVersions(planId) {
|
|
1199
1556
|
const normalizedPlanId = String(planId || '').trim().toLowerCase();
|
|
1200
1557
|
if (!normalizedPlanId)
|
|
@@ -1385,9 +1742,13 @@ export class PlanEntitlementService {
|
|
|
1385
1742
|
`).all(this.tenantId, planVersionId);
|
|
1386
1743
|
const features = {};
|
|
1387
1744
|
for (const featureRow of featuresRows) {
|
|
1388
|
-
const
|
|
1745
|
+
const originalKey = String(featureRow.feature_key || '').trim();
|
|
1746
|
+
const key = this.canonicalizePlanFeatureKey(originalKey);
|
|
1389
1747
|
if (!key)
|
|
1390
1748
|
continue;
|
|
1749
|
+
if (key === WORKSPACES_FEATURE_KEY && originalKey === LEGACY_WORKSPACES_FEATURE_KEY && features[key]) {
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1391
1752
|
features[key] = {
|
|
1392
1753
|
featureKey: key,
|
|
1393
1754
|
access: this.normalizeFeatureAccess(featureRow.access),
|
|
@@ -1417,7 +1778,7 @@ export class PlanEntitlementService {
|
|
|
1417
1778
|
...currentVersion.features
|
|
1418
1779
|
};
|
|
1419
1780
|
for (const feature of features) {
|
|
1420
|
-
const featureKey =
|
|
1781
|
+
const featureKey = this.canonicalizePlanFeatureKey(feature.featureKey);
|
|
1421
1782
|
if (!featureKey)
|
|
1422
1783
|
continue;
|
|
1423
1784
|
const access = this.normalizeFeatureAccess(feature.access);
|