diff --git a/app/MindWork AI Studio/Settings/ManagedConfiguration.cs b/app/MindWork AI Studio/Settings/ManagedConfiguration.cs index e92ac103..5d83121e 100644 --- a/app/MindWork AI Studio/Settings/ManagedConfiguration.cs +++ b/app/MindWork AI Studio/Settings/ManagedConfiguration.cs @@ -269,12 +269,13 @@ public static partial class ManagedConfiguration /// change it at all. /// /// The collection of available plugins to check against. - /// - /// The IDs of all configuration plugins which are deployed on this machine, including those which - /// could not be loaded. A deployed plugin was not removed, so its settings must stay untouched. + /// + /// The IDs of the configuration plugins which an organization deployed on this machine, including + /// those which could not be loaded. A deployed plugin was not removed, so its settings must stay + /// untouched. /// /// True when at least one setting was changed, otherwise false. - public static bool CleanupLeftOverManagedConfigurations(IReadOnlyCollection availablePlugins, IReadOnlySet deployedConfigPluginIds) + public static bool CleanupLeftOverManagedConfigurations(IReadOnlyCollection availablePlugins, IReadOnlySet deployedEnterpriseConfigPluginIds) { var wasChanged = false; var registeredSettingNames = new HashSet(StringComparer.Ordinal); @@ -293,7 +294,7 @@ public static partial class ManagedConfiguration configMeta.RestoreLockedConfiguration(); // Check the locked state: - if (configMeta.IsLocked && configMeta.LockedByConfigPluginId != Guid.Empty && !IsPluginPresent(configMeta.LockedByConfigPluginId, availablePlugins, deployedConfigPluginIds)) + if (configMeta.IsLocked && configMeta.LockedByConfigPluginId != Guid.Empty && !IsPluginPresent(configMeta.LockedByConfigPluginId, availablePlugins, deployedEnterpriseConfigPluginIds)) { Log.LogInformation($"Resetting the setting '{configMeta.SettingName}': it was locked by the configuration plugin '{configMeta.LockedByConfigPluginId}', which is not available anymore."); configMeta.ResetLockedConfiguration(); @@ -301,11 +302,11 @@ public static partial class ManagedConfiguration } // Check the editable default state: - if (CleanupEditableDefaultState(configMeta, availablePlugins, deployedConfigPluginIds)) + if (CleanupEditableDefaultState(configMeta, availablePlugins, deployedEnterpriseConfigPluginIds)) wasChanged = true; // Check the additive plugin contribution: - if (configMeta.HasPluginContribution && configMeta.PluginContributionByConfigPluginId != Guid.Empty && !IsPluginPresent(configMeta.PluginContributionByConfigPluginId, availablePlugins, deployedConfigPluginIds)) + if (configMeta.HasPluginContribution && configMeta.PluginContributionByConfigPluginId != Guid.Empty && !IsPluginPresent(configMeta.PluginContributionByConfigPluginId, availablePlugins, deployedEnterpriseConfigPluginIds)) { Log.LogInformation($"Clearing the plugin contribution for the setting '{configMeta.SettingName}': the configuration plugin '{configMeta.PluginContributionByConfigPluginId}' is not available anymore."); configMeta.ClearPluginContribution(); @@ -328,7 +329,7 @@ public static partial class ManagedConfiguration /// The latter matters for organizations: a broken configuration plugin is still in charge, so we /// must not treat its settings as left over. /// - private static bool IsPluginPresent(Guid configPluginId, IReadOnlyCollection availablePlugins, IReadOnlySet deployedConfigPluginIds) => deployedConfigPluginIds.Contains(configPluginId) || availablePlugins.Any(x => x.Id == configPluginId); + private static bool IsPluginPresent(Guid configPluginId, IReadOnlyCollection availablePlugins, IReadOnlySet deployedEnterpriseConfigPluginIds) => deployedEnterpriseConfigPluginIds.Contains(configPluginId) || availablePlugins.Any(x => x.Id == configPluginId); /// /// Removes persisted managed states which belong to settings that are not registered anymore. @@ -390,7 +391,7 @@ public static partial class ManagedConfiguration private static bool ClearEditableDefaultState(string settingName) => SettingsManagerAccess.ConfigurationData.ManagedEditableDefaults.Remove(settingName); - private static bool CleanupEditableDefaultState(ConfigMetaBase configMeta, IReadOnlyCollection availablePlugins, IReadOnlySet deployedConfigPluginIds) + private static bool CleanupEditableDefaultState(ConfigMetaBase configMeta, IReadOnlyCollection availablePlugins, IReadOnlySet deployedEnterpriseConfigPluginIds) { if (!TryGetEditableDefaultState(configMeta.SettingName, out var editableDefaultState)) { @@ -401,7 +402,7 @@ public static partial class ManagedConfiguration return true; } - if (IsPluginPresent(editableDefaultState.ConfigPluginId, availablePlugins, deployedConfigPluginIds)) + if (IsPluginPresent(editableDefaultState.ConfigPluginId, availablePlugins, deployedEnterpriseConfigPluginIds)) return false; Log.LogInformation($"Clearing the editable default of the setting '{configMeta.SettingName}': the configuration plugin '{editableDefaultState.ConfigPluginId}' is not available anymore."); diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs index b3b69d21..bd9cf2b9 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs @@ -255,10 +255,10 @@ public sealed record PluginConfigurationObject /// The type of configuration object to process. /// A selection expression to retrieve the configuration objects from the main configuration. /// A list of currently available plugins. - /// - /// The IDs of all configuration plugins which are deployed on this machine, including those which - /// could not be loaded. Objects of a deployed plugin are never removed, because the plugin was not - /// removed either. + /// + /// The IDs of the configuration plugins which an organization deployed on this machine, including + /// those which could not be loaded. Objects of a deployed plugin are never removed, because the + /// plugin was not removed either. /// /// A list of all existing configuration objects. /// An optional parameter specifying the type of secret store to use for deleting associated API keys from the OS keyring, if applicable. @@ -268,7 +268,7 @@ public sealed record PluginConfigurationObject PluginConfigurationObjectType configObjectType, Expression>> configObjectSelection, IList availablePlugins, - IReadOnlySet deployedConfigPluginIds, + IReadOnlySet deployedEnterpriseConfigPluginIds, IList configObjectList, SecretStoreType? secretStoreType = null, bool deleteSecret = false) where TClass : IConfigurationObject @@ -295,7 +295,7 @@ public sealed record PluginConfigurationObject // the organization's providers and data sources, including their secrets, although the // organization still manages this AI Studio instance: // - if(deployedConfigPluginIds.Contains(configObjectSourcePluginId) && availablePlugins.All(plugin => plugin.Id != configObjectSourcePluginId)) + if(deployedEnterpriseConfigPluginIds.Contains(configObjectSourcePluginId) && availablePlugins.All(plugin => plugin.Id != configObjectSourcePluginId)) continue; // Is the source plugin still available? If not, we can be pretty sure that this configuration object is left diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Download.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Download.cs index 648eced1..87229419 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Download.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Download.cs @@ -45,7 +45,7 @@ public static partial class PluginFactory LOG.LogInformation($"Try to download configuration plugin with ID='{configPlugId}' from server='{configServerUrl}' (GET {downloadUrl})"); var tempDownloadFile = Path.GetTempFileName(); - var stagedDirectory = Path.Join(CONFIGURATION_PLUGINS_ROOT, $"{configPlugId}.staging-{Guid.NewGuid():N}"); + var stagedDirectory = Path.Join(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT, $"{configPlugId}.staging-{Guid.NewGuid():N}"); string? backupDirectory = null; var wasSuccessful = false; try @@ -66,10 +66,10 @@ public static partial class PluginFactory ExtractConfigPluginArchive(tempDownloadFile, stagedDirectory); - var configDirectory = Path.Join(CONFIGURATION_PLUGINS_ROOT, configPlugId.ToString()); + var configDirectory = Path.Join(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT, configPlugId.ToString()); if (Directory.Exists(configDirectory)) { - backupDirectory = Path.Join(CONFIGURATION_PLUGINS_ROOT, $"{configPlugId}.backup-{Guid.NewGuid():N}"); + backupDirectory = Path.Join(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT, $"{configPlugId}.backup-{Guid.NewGuid():N}"); Directory.Move(configDirectory, backupDirectory); } @@ -84,7 +84,7 @@ public static partial class PluginFactory { LOG.LogError(e, "An error occurred while downloading or extracting the enterprise configuration plugin."); - var configDirectory = Path.Join(CONFIGURATION_PLUGINS_ROOT, configPlugId.ToString()); + var configDirectory = Path.Join(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT, configPlugId.ToString()); if (!string.IsNullOrWhiteSpace(backupDirectory) && Directory.Exists(backupDirectory) && !Directory.Exists(configDirectory)) { try diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs index 95844959..bc2fb497 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -140,7 +140,7 @@ public static partial class PluginFactory else if (isConfigurationPluginInConfigDirectory) { isManagedByConfigServer = true; - LOG.LogWarning($"The configuration plugin '{plugin.Id}' does not define 'DEPLOYED_USING_CONFIG_SERVER'. Falling back to the plugin path and treating it as managed because it is stored under '{CONFIGURATION_PLUGINS_ROOT}'."); + LOG.LogWarning($"The configuration plugin '{plugin.Id}' does not define 'DEPLOYED_USING_CONFIG_SERVER'. Falling back to the plugin path and treating it as managed because it is stored under '{ENTERPRISE_CONFIGURATION_PLUGINS_ROOT}'."); } } else if (plugin is PluginAssistants assistantPlugin) @@ -192,40 +192,40 @@ public static partial class PluginFactory // // - // Configuration plugins which are deployed but could not be loaded count as present: they - // were not removed, so everything they manage must stay as it is. Otherwise, one broken - // configuration plugin would wipe the entire organization configuration: + // Enterprise configuration plugins which are deployed but could not be loaded count as + // present: they were not removed, so everything they manage must stay as it is. Otherwise, + // one broken configuration plugin would wipe the entire organization configuration: // - var deployedConfigPluginIds = GetDeployedConfigPluginIds(); - var unloadedConfigPluginIds = deployedConfigPluginIds.Where(x => AVAILABLE_PLUGINS.All(plugin => plugin.Id != x)).ToList(); - foreach (var unloadedConfigPluginId in unloadedConfigPluginIds) - LOG.LogWarning($"The configuration plugin '{unloadedConfigPluginId}' is deployed, but was not loaded. Everything it manages stays unchanged, because the plugin was not removed. Please check the errors above and fix the plugin."); + var deployedEnterpriseConfigPluginIds = GetDeployedEnterpriseConfigPluginIds(); + var unloadedEnterpriseConfigPluginIds = deployedEnterpriseConfigPluginIds.Where(x => AVAILABLE_PLUGINS.All(plugin => plugin.Id != x)).ToList(); + foreach (var unloadedEnterpriseConfigPluginId in unloadedEnterpriseConfigPluginIds) + LOG.LogWarning($"The configuration plugin '{unloadedEnterpriseConfigPluginId}' is deployed, but was not loaded. Everything it manages stays unchanged, because the plugin was not removed. Please check the errors above and fix the plugin."); // Check LLM providers: - var wasConfigurationChanged = await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, AVAILABLE_PLUGINS, deployedConfigPluginIds, configObjectList, SecretStoreType.LLM_PROVIDER); + var wasConfigurationChanged = await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, AVAILABLE_PLUGINS, deployedEnterpriseConfigPluginIds, configObjectList, SecretStoreType.LLM_PROVIDER); // Check transcription providers: - if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.TRANSCRIPTION_PROVIDER, x => x.TranscriptionProviders, AVAILABLE_PLUGINS, deployedConfigPluginIds, configObjectList, SecretStoreType.TRANSCRIPTION_PROVIDER)) + if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.TRANSCRIPTION_PROVIDER, x => x.TranscriptionProviders, AVAILABLE_PLUGINS, deployedEnterpriseConfigPluginIds, configObjectList, SecretStoreType.TRANSCRIPTION_PROVIDER)) wasConfigurationChanged = true; // Check embedding providers: - if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.EMBEDDING_PROVIDER, x => x.EmbeddingProviders, AVAILABLE_PLUGINS, deployedConfigPluginIds, configObjectList, SecretStoreType.EMBEDDING_PROVIDER)) + if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.EMBEDDING_PROVIDER, x => x.EmbeddingProviders, AVAILABLE_PLUGINS, deployedEnterpriseConfigPluginIds, configObjectList, SecretStoreType.EMBEDDING_PROVIDER)) wasConfigurationChanged = true; // Check data sources: - if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.DATA_SOURCE, x => x.DataSources, AVAILABLE_PLUGINS, deployedConfigPluginIds, configObjectList, SecretStoreType.DATA_SOURCE, deleteSecret: true)) + if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.DATA_SOURCE, x => x.DataSources, AVAILABLE_PLUGINS, deployedEnterpriseConfigPluginIds, configObjectList, SecretStoreType.DATA_SOURCE, deleteSecret: true)) wasConfigurationChanged = true; // Check chat templates: - if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, AVAILABLE_PLUGINS, deployedConfigPluginIds, configObjectList)) + if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, AVAILABLE_PLUGINS, deployedEnterpriseConfigPluginIds, configObjectList)) wasConfigurationChanged = true; // Check profiles: - if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.PROFILE, x => x.Profiles, AVAILABLE_PLUGINS, deployedConfigPluginIds, configObjectList)) + if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.PROFILE, x => x.Profiles, AVAILABLE_PLUGINS, deployedEnterpriseConfigPluginIds, configObjectList)) wasConfigurationChanged = true; // Check document analysis policies: - if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.DOCUMENT_ANALYSIS_POLICY, x => x.DocumentAnalysis.Policies, AVAILABLE_PLUGINS, deployedConfigPluginIds, configObjectList)) + if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.DOCUMENT_ANALYSIS_POLICY, x => x.DocumentAnalysis.Policies, AVAILABLE_PLUGINS, deployedEnterpriseConfigPluginIds, configObjectList)) wasConfigurationChanged = true; // Check left-over mandatory info acceptances: @@ -234,11 +234,11 @@ public static partial class PluginFactory // Check all managed settings, i.e. settings which a configuration plugin can lock, // provide as an editable default, or contribute to: - if(ManagedConfiguration.CleanupLeftOverManagedConfigurations(AVAILABLE_PLUGINS, deployedConfigPluginIds)) + if(ManagedConfiguration.CleanupLeftOverManagedConfigurations(AVAILABLE_PLUGINS, deployedEnterpriseConfigPluginIds)) wasConfigurationChanged = true; // Compatibility shim, see documentation/compatibility-shims/2026-08-orphaned-config-locks.md (remove after 2027-08-06): - if (RepairLegacyConfigOnlySettings(unloadedConfigPluginIds.Count > 0)) + if (RepairLegacyConfigOnlySettings(unloadedEnterpriseConfigPluginIds.Count > 0)) wasConfigurationChanged = true; if (wasConfigurationChanged) @@ -249,9 +249,11 @@ public static partial class PluginFactory } /// - /// Determines the IDs of all configuration plugins which are deployed on this machine. + /// Determines the IDs of all configuration plugins which an organization deployed on this machine. /// /// + /// Local configuration plugins are not part of this: they belong to the user, not to an + /// organization, and they can live in any directory below the plugins root.

/// We read these IDs from the file system instead of taking them from the loaded plugins. A /// configuration plugin might be present but not loadable, e.g. due to invalid Lua code, a /// missing `plugin.lua`, or an incomplete download. Such a plugin still manages this AI Studio @@ -259,13 +261,13 @@ public static partial class PluginFactory /// configuration server live in a directory named after their ID, which is the only information /// left when the plugin itself cannot be read. ///
- private static HashSet GetDeployedConfigPluginIds() + private static HashSet GetDeployedEnterpriseConfigPluginIds() { - var deployedConfigPluginIds = new HashSet(); - if (!Directory.Exists(CONFIGURATION_PLUGINS_ROOT)) - return deployedConfigPluginIds; + var deployedEnterpriseConfigPluginIds = new HashSet(); + if (!Directory.Exists(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT)) + return deployedEnterpriseConfigPluginIds; - foreach (var configPluginDirectory in Directory.EnumerateDirectories(CONFIGURATION_PLUGINS_ROOT)) + foreach (var configPluginDirectory in Directory.EnumerateDirectories(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT)) { if (!Guid.TryParse(Path.GetFileName(configPluginDirectory), out var configPluginId) || configPluginId == Guid.Empty) continue; @@ -274,10 +276,10 @@ public static partial class PluginFactory if (!Directory.EnumerateFileSystemEntries(configPluginDirectory).Any()) continue; - deployedConfigPluginIds.Add(configPluginId); + deployedEnterpriseConfigPluginIds.Add(configPluginId); } - return deployedConfigPluginIds; + return deployedEnterpriseConfigPluginIds; } /// The directory the plugin is located in, or null when the code has no directory yet. diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Remove.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Remove.cs index 0a7b4a12..65dcba8e 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Remove.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Remove.cs @@ -22,9 +22,9 @@ public static partial class PluginFactory // Case 2: Startup cleanup before the initial plugin load. // In this case, we inspect the .config directories directly. - if (Directory.Exists(CONFIGURATION_PLUGINS_ROOT)) + if (Directory.Exists(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT)) { - foreach (var pluginDirectory in Directory.EnumerateDirectories(CONFIGURATION_PLUGINS_ROOT)) + foreach (var pluginDirectory in Directory.EnumerateDirectories(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT)) { var directoryName = Path.GetFileName(pluginDirectory); if (!Guid.TryParse(directoryName, out var pluginId)) @@ -36,7 +36,7 @@ public static partial class PluginFactory var deployFlag = ReadDeployFlagFromPluginFile(pluginDirectory); var isManagedByConfigServer = deployFlag ?? true; if (!deployFlag.HasValue) - LOG.LogWarning($"Configuration plugin '{pluginId}' does not define 'DEPLOYED_USING_CONFIG_SERVER'. Falling back to the plugin path and treating it as managed because it is stored under '{CONFIGURATION_PLUGINS_ROOT}'."); + LOG.LogWarning($"Configuration plugin '{pluginId}' does not define 'DEPLOYED_USING_CONFIG_SERVER'. Falling back to the plugin path and treating it as managed because it is stored under '{ENTERPRISE_CONFIGURATION_PLUGINS_ROOT}'."); if (isManagedByConfigServer) pluginIdsToRemove.Add(pluginId); @@ -106,7 +106,7 @@ public static partial class PluginFactory private static void DeleteConfigurationPluginDirectory(Guid pluginId) { - var pluginDirectory = Path.Join(CONFIGURATION_PLUGINS_ROOT, pluginId.ToString()); + var pluginDirectory = Path.Join(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT, pluginId.ToString()); if (!Directory.Exists(pluginDirectory)) { LOG.LogWarning($"Plugin directory '{pluginDirectory}' does not exist."); diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs index 505e379b..9e9584aa 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs @@ -11,7 +11,16 @@ public static partial class PluginFactory private static string DATA_DIR = string.Empty; private static string PLUGINS_ROOT = string.Empty; private static string INTERNAL_PLUGINS_ROOT = string.Empty; - private static string CONFIGURATION_PLUGINS_ROOT = string.Empty; + + /// + /// The directory the config server downloads the configuration plugins of an organization into. + /// + /// + /// This is not the home of configuration plugins in general: a local configuration plugin can + /// live in any directory below the plugins root. Only the IT department of an organization + /// deploys plugins here, each in a directory named after its configuration ID. + /// + private static string ENTERPRISE_CONFIGURATION_PLUGINS_ROOT = string.Empty; private static string HOT_RELOAD_LOCK_FILE = string.Empty; private static FileSystemWatcher HOT_RELOAD_WATCHER = null!; @@ -65,7 +74,7 @@ public static partial class PluginFactory PLUGINS_ROOT = Path.Join(DATA_DIR, "plugins"); HOT_RELOAD_LOCK_FILE = Path.Join(PLUGINS_ROOT, ".lock"); INTERNAL_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".internal"); - CONFIGURATION_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".config"); + ENTERPRISE_CONFIGURATION_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".config"); if (!Directory.Exists(PLUGINS_ROOT)) Directory.CreateDirectory(PLUGINS_ROOT); @@ -89,12 +98,12 @@ public static partial class PluginFactory /// True when the directory is nested in the enterprise configuration directory. private static bool IsEnterpriseConfigurationPath(string? pluginPath) { - if (string.IsNullOrWhiteSpace(pluginPath) || string.IsNullOrWhiteSpace(CONFIGURATION_PLUGINS_ROOT)) + if (string.IsNullOrWhiteSpace(pluginPath) || string.IsNullOrWhiteSpace(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT)) return false; try { - var configurationRoot = Path.GetFullPath(CONFIGURATION_PLUGINS_ROOT).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar; + var configurationRoot = Path.GetFullPath(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar; var pluginDirectory = Path.GetFullPath(pluginPath).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar; return pluginDirectory.StartsWith(configurationRoot, StringComparison.OrdinalIgnoreCase); }