diff --git a/community-resources/resources/META-INF/IdeaPlugin.xml b/community-resources/resources/META-INF/IdeaPlugin.xml
index a74b2a969abf..81f9dd58330a 100644
--- a/community-resources/resources/META-INF/IdeaPlugin.xml
+++ b/community-resources/resources/META-INF/IdeaPlugin.xml
@@ -7,6 +7,8 @@
+
+
diff --git a/platform/core-impl/src/com/intellij/ide/plugins/ModulesWithDependencies.kt b/platform/core-impl/src/com/intellij/ide/plugins/ModulesWithDependencies.kt
index 59ee824689b4..31bf5c46d85b 100644
--- a/platform/core-impl/src/com/intellij/ide/plugins/ModulesWithDependencies.kt
+++ b/platform/core-impl/src/com/intellij/ide/plugins/ModulesWithDependencies.kt
@@ -95,9 +95,6 @@ internal fun createModulesWithDependenciesAndAdditionalEdges(plugins: Collection
if (doesDependOnPluginAlias(module, ML_INLINE_ALIAS_ID)) {
moduleMap.get("intellij.ml.inline.completion")?.let { dependenciesCollector.add(it) }
}
- if (doesDependOnPluginAlias(module, PROVISIONER_ALIAS_ID)) {
- moduleMap.get("intellij.platform.ide.provisioner")?.let { dependenciesCollector.add(it) }
- }
if (doesDependOnPluginAlias(module, PluginId.getId("org.jetbrains.completion.full.line"))) {
moduleMap.get("intellij.fullLine.core")?.let { dependenciesCollector.add(it) }
moduleMap.get("intellij.fullLine.local")?.let { dependenciesCollector.add(it) }
diff --git a/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceConfiguration.kt b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceConfiguration.kt
new file mode 100644
index 000000000000..710d6ce7a723
--- /dev/null
+++ b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceConfiguration.kt
@@ -0,0 +1,37 @@
+package com.intellij.platform.ide.provisioner
+
+import com.intellij.platform.ide.provisioner.endpoint.ServiceEndpoint
+
+data class ProvisionedServiceConfiguration(
+ /** Generic Key-Value map of service-specific properties. */
+ private val properties: Map,
+
+ /** Endpoint descriptor in case the service involves a remote server. */
+ val endpoint: ServiceEndpoint?,
+) {
+ operator fun get(key: String): String? = properties[key]
+}
+
+sealed interface ProvisionedServiceConfigurationResult {
+ /**
+ * Represents the successfully loaded state.
+ */
+ sealed interface Success : ProvisionedServiceConfigurationResult {
+ data class ServiceProvisioned(val configuration: ProvisionedServiceConfiguration) : Success
+ data object ServiceNotProvisioned : Success
+ }
+
+ /**
+ * Denotes that the provisioner could not load the configuration of the service due to an error.
+ * Depending on the particular service, there may be different ways of treating this state.
+ * For example, the client may fall back to some predefined default configuration,
+ * or it may choose to prohibit the use of the corresponding IDE functionality altogether
+ * until a proper configuration becomes available.
+ */
+ sealed interface Failure : ProvisionedServiceConfigurationResult {
+ val message: String
+
+ data class LoginRequired(override val message: String) : Failure
+ data class GenericError(override val message: String, val cause: Throwable? = null) : Failure
+ }
+}
diff --git a/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceDescriptor.kt b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceDescriptor.kt
new file mode 100644
index 000000000000..f2fbc1917241
--- /dev/null
+++ b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceDescriptor.kt
@@ -0,0 +1,19 @@
+package com.intellij.platform.ide.provisioner
+
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Descriptor for a (potentially provisioned) piece of IDE functionality.
+ */
+interface ProvisionedServiceDescriptor {
+ /** Unique identifier for accessing the service endpoint using [ProvisionedServiceRegistry.getServiceById]. */
+ val id: String
+
+ /**
+ * The state of whether the service is provisioned (with its configuration in that case) or not.
+ *
+ * Note that it may take some time for the initial value to become available in the flow
+ * while the configuration is still loading.
+ */
+ val configurationFlow: Flow
+}
diff --git a/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceRegistry.kt b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceRegistry.kt
new file mode 100644
index 000000000000..ca028d6520c8
--- /dev/null
+++ b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionedServiceRegistry.kt
@@ -0,0 +1,23 @@
+package com.intellij.platform.ide.provisioner
+
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.components.service
+
+interface ProvisionedServiceRegistry {
+ /**
+ * Retrieves a [ProvisionedServiceDescriptor] by its [ProvisionedServiceDescriptor.id],
+ * or null if the provisioner doesn't recognize the ID.
+ * Note that a non-null result only means that the installed version of the provisioner plugin
+ * is aware and support the requested service; it doesn't mean that the service is available and/or enabled -
+ * this is what [ProvisionedServiceDescriptor.configurationFlow] is for.
+ */
+ fun getServiceById(id: String): ProvisionedServiceDescriptor?
+
+ companion object {
+ fun getInstance(): ProvisionedServiceRegistry = ApplicationManager.getApplication().service()
+ }
+}
+
+internal class DefaultProvisionedServiceRegistry : ProvisionedServiceRegistry {
+ override fun getServiceById(id: String): ProvisionedServiceDescriptor? = null
+}
diff --git a/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionerCompanyBrandingProvider.kt b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionerCompanyBrandingProvider.kt
new file mode 100644
index 000000000000..2efa61184f91
--- /dev/null
+++ b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/ProvisionerCompanyBrandingProvider.kt
@@ -0,0 +1,39 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.intellij.platform.ide.provisioner
+
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.components.service
+import com.intellij.openapi.util.NlsSafe
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import org.jetbrains.annotations.ApiStatus
+import javax.swing.Icon
+
+@ApiStatus.Internal
+interface ProvisionerCompanyBrandingProvider{
+ val companyBranding: Flow
+
+ @ApiStatus.Experimental
+ fun getCurrentEnterpriseState(): CompanyBranding
+
+ companion object {
+ fun getInstance(): ProvisionerCompanyBrandingProvider = ApplicationManager.getApplication().service()
+ }
+}
+
+internal class DefaultProvisionerCompanyBrandingProvider: ProvisionerCompanyBrandingProvider {
+ override val companyBranding = flowOf(CompanyBranding.NotProvisioned)
+ override fun getCurrentEnterpriseState(): CompanyBranding = CompanyBranding.NotProvisioned
+}
+
+sealed class CompanyBranding {
+ data object NotReady: CompanyBranding()
+ data class Provisioned(val info: EnterpriseInfo): CompanyBranding()
+ data object NotProvisioned: CompanyBranding()
+}
+
+data class EnterpriseInfo(
+ val logo: Icon,
+ val companyName: @NlsSafe String,
+ val browserUrl: String,
+)
diff --git a/platform/platform-impl/src/com/intellij/platform/ide/provisioner/endpoint/AuthToken.kt b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/endpoint/AuthToken.kt
new file mode 100644
index 000000000000..36bb5cfa927a
--- /dev/null
+++ b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/endpoint/AuthToken.kt
@@ -0,0 +1,31 @@
+package com.intellij.platform.ide.provisioner.endpoint
+
+data class AuthToken(
+ /**
+ * The map of HTTP request headers required for authenticating with the corresponding [ServiceEndpoint].
+ * Typically, it contains at least the `"Authorization"` credentials, but that's not guaranteed.
+ */
+ val requestHeaders: Map,
+) {
+ @Deprecated("For backward compatibility, until TBE plugin is updated")
+ @Suppress("unused")
+ constructor(
+ tokenValue: String,
+ tokenSchema: String,
+ additionalHeaders: Map,
+ ) : this(requestHeaders = mapOf("Authorization" to "${tokenSchema} ${tokenValue}") + additionalHeaders)
+}
+
+sealed interface AuthTokenResult {
+ data class Success(val token: AuthToken) : AuthTokenResult
+
+ sealed interface Failure : AuthTokenResult {
+ val message: String
+
+ data class Timeout(override val message: String) : Failure
+ data class NetworkError(override val message: String) : Failure
+ data class LoginRequired(override val message: String) : Failure
+ data class ValidationError(override val message: String) : Failure
+ data class GenericError(override val message: String, val cause: Throwable? = null) : Failure
+ }
+}
diff --git a/platform/platform-impl/src/com/intellij/platform/ide/provisioner/endpoint/ServiceEndpoint.kt b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/endpoint/ServiceEndpoint.kt
new file mode 100644
index 000000000000..9745ad6daab1
--- /dev/null
+++ b/platform/platform-impl/src/com/intellij/platform/ide/provisioner/endpoint/ServiceEndpoint.kt
@@ -0,0 +1,44 @@
+package com.intellij.platform.ide.provisioner.endpoint
+
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Describes a server endpoint for the provisioned service.
+ */
+interface ServiceEndpoint {
+ /** The URL of the service endpoint. */
+ val serverUrl: String
+
+ /**
+ * The current [token][AuthTokenResult] required to access the [server][serverUrl].
+ * The implementation is responsible for refreshing the token, so that the latest token value
+ * available in the flow is always usable (unless there's an [AuthTokenResult.Failure]).
+ *
+ * Note that an [AuthTokenResult.Success] doesn't guarantee that the token is going to stay
+ * valid up until its expiration time.
+ * The client code should still be able to handle an authorization error properly,
+ * and the [reportAuthFailure] method can be helpful in facilitating that.
+ */
+ val authTokenFlow: Flow
+
+ /**
+ * The client is advised to call this method as part of graceful error handling if a request
+ * to the endpoint fails because of an authentication failure ("401 Unauthorized").
+ *
+ * The token may become invalid due to an external change; for instance, the SSO provider
+ * may forcibly log the user out. An event like that may go unnoticed by the provisioner,
+ * and the new state may not be reflected in the [authTokenFlow] automatically.
+ *
+ * By calling this method, the client notifies the provisioner that the token is no more
+ * usable. The provisioner then makes the best effort to revalidate the token, which may
+ * hopefully result in a new token state (probably an [AuthTokenResult.Failure.LoginRequired])
+ * being pushed eventually through the [authTokenFlow].
+ *
+ * Note that the contract of this method should indeed be only treated as "best effort".
+ * The client must not rely on a new token state becoming available in the [authTokenFlow]
+ * right after calling this method, and should design the interaction with the user accordingly.
+ * In this sense, the default "noop" way of how the provisioner could implement this method
+ * is perfectly fine.
+ */
+ fun reportAuthFailure(authToken: AuthToken) {}
+}
diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml
index efb3e860a58f..d71ed1cedebe 100644
--- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml
+++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml
@@ -1847,6 +1847,11 @@
+
+
+
diff --git a/python/ide-common/resources/META-INF/PyCharmCorePlugin.xml b/python/ide-common/resources/META-INF/PyCharmCorePlugin.xml
index a6b97940e818..a0ce811b7d68 100644
--- a/python/ide-common/resources/META-INF/PyCharmCorePlugin.xml
+++ b/python/ide-common/resources/META-INF/PyCharmCorePlugin.xml
@@ -2,6 +2,9 @@
+
+
+