@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.
- package/dist/Taskforce.module.css +295 -89
- package/dist/TaskforceCore.js +215 -43
- package/dist/TaskforceCore.test.js +433 -28
- 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 +46 -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/DocumentViewer.d.ts +5 -1
- package/dist/components/features/DocumentViewer.js +60 -3
- package/dist/components/features/DocumentWorkspace.js +1 -1
- package/dist/components/features/DocumentWorkspace.test.js +33 -0
- package/dist/components/features/documentWorkspaceModel.d.ts +1 -1
- 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.js +85 -90
- package/dist/components/task/TaskForm.test.js +110 -8
- package/dist/components/task/TaskKanban.d.ts +2 -1
- package/dist/components/task/TaskKanban.js +94 -13
- 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 -14
- package/dist/components/views/PlanComparisonPage.test.js +238 -0
- package/dist/components/views/PlansPage.js +127 -36
- package/dist/components/views/PlansPage.test.js +294 -12
- package/dist/components/views/StandaloneLayout.js +283 -533
- 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 +26 -1
- package/dist/components/views/standalone/modals/AccountHubModal.js +76 -2
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +20 -3
- 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/core/AiProfileService.d.ts +39 -2
- package/dist/core/AiProfileService.js +639 -73
- package/dist/core/AiProfileService.test.js +19 -1
- package/dist/core/AiProfiles.test.js +566 -7
- 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.js +3 -3
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +115 -6
- package/dist/core/PlanEntitlementService.d.ts +23 -2
- package/dist/core/PlanEntitlementService.js +343 -57
- package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
- package/dist/core/PlanFeatureCatalog.test.js +84 -0
- package/dist/core/PlanVersionPolicy.test.js +35 -2
- package/dist/core/PlanningEntities.test.js +52 -0
- package/dist/core/SignupMonetizationSettings.test.js +199 -27
- package/dist/core/SyncReconciliationService.js +2 -65
- package/dist/core/SystemAdmin.test.js +42 -42
- package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
- package/dist/core/Taskforce.d.ts +225 -14
- package/dist/core/Taskforce.js +765 -192
- 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 +117 -16
- package/dist/core/shared.d.ts +1 -1
- package/dist/core/shared.js +5 -1
- package/dist/core/types.d.ts +68 -2
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +26 -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 +15 -3
- package/dist/hooks/sync/transfers.js +179 -62
- 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 +9 -2
- package/dist/hooks/useSyncOrchestrator.js +359 -178
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +407 -0
- package/dist/hooks/useTaskData.js +8 -3
- package/dist/hooks/useTaskData.test.js +45 -0
- package/dist/hooks/useTaskforce.d.ts +60 -2
- package/dist/hooks/useTaskforce.js +549 -240
- 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 +2380 -212
- package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.js +6 -1
- 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/documentAssetRegistrar.js +3 -3
- package/dist/mcp/runtime.d.ts +2 -0
- package/dist/mcp/runtime.js +131 -64
- package/dist/mcp/runtime.test.js +291 -12
- 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 +3 -0
- package/dist/server/routes/admin.js +272 -19
- package/dist/server/routes/auth.d.ts +15 -2
- package/dist/server/routes/auth.js +994 -57
- package/dist/server/routes/authSupport.d.ts +1 -1
- package/dist/server/routes/authSupport.js +1 -1
- package/dist/server/routes/billing.js +361 -35
- package/dist/server/routes/billing.test.js +592 -12
- package/dist/server/routes/documents.d.ts +1 -0
- package/dist/server/routes/documents.js +172 -70
- package/dist/server/routes/shared.d.ts +28 -2
- package/dist/server/routes/shared.js +69 -4
- package/dist/server/routes/sync.integration.test.js +437 -39
- package/dist/server/routes/sync.js +61 -605
- 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.js +43 -1
- package/dist/server/routes/workspaces.js +36 -5
- package/dist/server/routes.js +60 -3
- package/dist/server/routes.test.js +1824 -171
- 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 +17 -70
- package/dist/sync/syncApplyHandlers.d.ts +7 -0
- package/dist/sync/syncApplyHandlers.js +28 -13
- package/dist/sync/syncApplyHandlers.test.js +70 -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 +5 -0
- package/dist/sync/syncService.js +52 -0
- package/dist/sync/syncService.test.js +57 -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 +45 -0
- package/dist/sync/workspaceSyncModel.js +183 -28
- package/dist/sync/workspaceSyncModel.test.js +291 -12
- package/dist/sync/workspaceSyncState.d.ts +17 -0
- package/dist/sync/workspaceSyncState.js +2 -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-BG6P_GVW.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-Dhm7M8e7.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-ln0aF7xc.js → TaskSettings-DZX4jk7e.js} +1 -1
- package/dist/ui/assets/{WorkflowsModule-BcS4afRn.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-BSUaRwF8.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.js +3 -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 +3 -2
- package/scripts/export-sqlite-to-postgres.mjs +7 -9
- 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-N2MnQBZk.js +0 -1
- package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +0 -1
- package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +0 -252
- package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
- package/dist/ui/assets/PlansPage-Cq0zXj3I.js +0 -1
- package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
- package/dist/ui/assets/index-C2-OXZV7.css +0 -1
- package/dist/ui/assets/index-DcSI5F86.js +0 -6
- 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
|
-
|
|
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,13 +148,13 @@ 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
|
}
|
|
116
|
-
if (
|
|
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
|
-
|
|
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 (
|
|
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',
|
|
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]
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
709
|
-
? (typeof input.
|
|
710
|
-
: current.
|
|
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 (!
|
|
833
|
+
if (!planId) {
|
|
715
834
|
return {
|
|
716
835
|
valid: false,
|
|
717
|
-
code: '
|
|
718
|
-
message: 'An onboarding plan
|
|
836
|
+
code: 'ONBOARDING_PLAN_REQUIRED',
|
|
837
|
+
message: 'An onboarding plan is required for the selected onboarding mode.'
|
|
719
838
|
};
|
|
720
839
|
}
|
|
721
|
-
const
|
|
722
|
-
|
|
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: '
|
|
732
|
-
message: `Configured onboarding plan
|
|
733
|
-
details: {
|
|
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 (
|
|
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
|
|
853
|
+
message: `Configured onboarding plan "${planId}" is disabled.`,
|
|
741
854
|
details: {
|
|
742
|
-
|
|
743
|
-
|
|
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
|
|
864
|
+
message: `Configured onboarding plan "${planId}" does not have active free pricing on its default version.`,
|
|
752
865
|
details: {
|
|
753
|
-
|
|
754
|
-
|
|
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
|
|
875
|
+
message: `Configured onboarding plan "${planId}" is not trial eligible on its default version.`,
|
|
763
876
|
details: {
|
|
764
|
-
|
|
765
|
-
|
|
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.
|
|
801
|
-
? this.
|
|
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
|
|
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)
|
|
900
|
-
|
|
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.
|
|
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
|
|
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 =
|
|
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 {};
|