package licensing import "sort" type SelfHostedFeatureRole string const ( SelfHostedFeatureRoleHidden SelfHostedFeatureRole = "hidden" SelfHostedFeatureRoleIncluded SelfHostedFeatureRole = "included" SelfHostedFeatureRolePrimaryPillar SelfHostedFeatureRole = "primary_pillar" SelfHostedFeatureRoleIncludedExtra SelfHostedFeatureRole = "included_extra" SelfHostedFeatureRoleCompatibilityOnly SelfHostedFeatureRole = "compatibility_only" ) type SelfHostedFeatureRoles struct { Community SelfHostedFeatureRole Relay SelfHostedFeatureRole Pro SelfHostedFeatureRole } type FeatureMetadata struct { Key string DisplayName string ComparisonName string ShowInComparisonTable bool DisplayableInPlanUI bool SelfHostedRoles SelfHostedFeatureRoles UpgradeReason string UpgradePriority int } var orderedFeatureMetadataKeys = []string{ FeatureUpdateAlerts, FeatureSSO, FeatureAIPatrol, FeatureRelay, FeatureMobileApp, FeaturePushNotifications, FeatureAIAlerts, FeatureAIAutoFix, FeatureLongTermMetrics, FeatureAdvancedSSO, FeatureRBAC, FeatureAuditLogging, FeatureAdvancedReporting, FeatureAgentProfiles, FeatureKubernetesAI, FeatureMultiUser, FeatureWhiteLabel, FeatureMultiTenant, FeatureUnlimited, FeatureDemoFixtures, } var featureMetadataCatalog = map[string]FeatureMetadata{ FeatureUpdateAlerts: { Key: FeatureUpdateAlerts, DisplayName: "Update Alerts (Container/Package Updates)", ComparisonName: "Update Alerts", ShowInComparisonTable: true, DisplayableInPlanUI: true, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleIncluded, Relay: SelfHostedFeatureRoleIncluded, Pro: SelfHostedFeatureRoleIncluded, }, }, FeatureSSO: { Key: FeatureSSO, DisplayName: "Core SSO (OIDC/SAML)", ComparisonName: "Core SSO (OIDC/SAML)", ShowInComparisonTable: true, DisplayableInPlanUI: true, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleIncluded, Relay: SelfHostedFeatureRoleIncluded, Pro: SelfHostedFeatureRoleIncluded, }, }, FeatureAIPatrol: { Key: FeatureAIPatrol, DisplayName: "Pulse Patrol (Background Health Checks)", ComparisonName: "Pulse Patrol", ShowInComparisonTable: true, DisplayableInPlanUI: true, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleIncluded, Relay: SelfHostedFeatureRoleIncluded, Pro: SelfHostedFeatureRoleIncluded, }, }, FeatureRelay: { Key: FeatureRelay, DisplayName: "Pulse Relay (Remote Access)", ComparisonName: "Pulse Relay (Remote Access)", ShowInComparisonTable: true, DisplayableInPlanUI: true, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleHidden, Relay: SelfHostedFeatureRolePrimaryPillar, Pro: SelfHostedFeatureRoleIncluded, }, UpgradeReason: "Get Relay so Pulse stays reachable securely from anywhere instead of only on the local dashboard.", UpgradePriority: 1, }, FeatureMobileApp: { Key: FeatureMobileApp, DisplayName: "Pulse Mobile Pairing", ComparisonName: "Pulse Mobile Pairing", ShowInComparisonTable: true, DisplayableInPlanUI: true, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleHidden, Relay: SelfHostedFeatureRolePrimaryPillar, Pro: SelfHostedFeatureRoleIncluded, }, UpgradeReason: "Get Relay so Pulse Mobile can pair with this instance for secure handoff and notifications.", UpgradePriority: 1, }, FeaturePushNotifications: { Key: FeaturePushNotifications, DisplayName: "Push Notifications", ComparisonName: "Push Notifications", ShowInComparisonTable: true, DisplayableInPlanUI: true, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleHidden, Relay: SelfHostedFeatureRolePrimaryPillar, Pro: SelfHostedFeatureRoleIncluded, }, UpgradeReason: "Get Relay so important alerts reach you immediately on mobile instead of waiting for you to reopen Pulse.", UpgradePriority: 1, }, FeatureLongTermMetrics: { Key: FeatureLongTermMetrics, DisplayName: "Extended Metric History", ComparisonName: "Extended Metric History", ShowInComparisonTable: false, DisplayableInPlanUI: true, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleHidden, Relay: SelfHostedFeatureRolePrimaryPillar, Pro: SelfHostedFeatureRolePrimaryPillar, }, UpgradeReason: "Get Relay for 14 days of history, or Pro for 90 days, so you can see what changed before and after an incident.", UpgradePriority: 2, }, FeatureAIAlerts: { Key: FeatureAIAlerts, DisplayName: "Patrol Investigates Issues and Explains the Root Cause", ComparisonName: "Patrol Investigates Issues and Explains the Root Cause", ShowInComparisonTable: true, DisplayableInPlanUI: true, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleHidden, Relay: SelfHostedFeatureRoleHidden, Pro: SelfHostedFeatureRolePrimaryPillar, }, UpgradeReason: "Upgrade to Pro so Patrol can investigate issues instead of handing you a stack of symptoms.", UpgradePriority: 4, }, FeatureAIAutoFix: { Key: FeatureAIAutoFix, DisplayName: "Patrol Applies Safe Fixes and Verifies the Result", ComparisonName: "Patrol Applies Safe Fixes and Verifies the Result", ShowInComparisonTable: true, DisplayableInPlanUI: true, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleHidden, Relay: SelfHostedFeatureRoleHidden, Pro: SelfHostedFeatureRolePrimaryPillar, }, UpgradeReason: "Upgrade to Pro so Patrol can investigate issues, apply safe fixes within Patrol mode, and verify the result.", UpgradePriority: 3, }, FeatureKubernetesAI: { Key: FeatureKubernetesAI, DisplayName: "Kubernetes AI Analysis (Compatibility)", ComparisonName: "Kubernetes AI Analysis (Compatibility)", ShowInComparisonTable: false, DisplayableInPlanUI: false, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleHidden, Relay: SelfHostedFeatureRoleHidden, Pro: SelfHostedFeatureRoleCompatibilityOnly, }, }, FeatureAgentProfiles: { Key: FeatureAgentProfiles, DisplayName: "Centralized Agent Profiles", ComparisonName: "Centralized Agent Profiles", ShowInComparisonTable: true, DisplayableInPlanUI: true, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleHidden, Relay: SelfHostedFeatureRoleHidden, Pro: SelfHostedFeatureRoleIncludedExtra, }, UpgradeReason: "Upgrade to Pro to standardize agent behavior across systems without reconfiguring every install by hand.", UpgradePriority: 6, }, FeatureRBAC: { Key: FeatureRBAC, DisplayName: "Role-Based Access Control (RBAC)", ComparisonName: "Role-Based Access Control (RBAC)", ShowInComparisonTable: true, DisplayableInPlanUI: true, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleHidden, Relay: SelfHostedFeatureRoleHidden, Pro: SelfHostedFeatureRoleIncludedExtra, }, UpgradeReason: "Upgrade to Pro when more than one operator needs safe access boundaries around infrastructure changes.", UpgradePriority: 5, }, FeatureAuditLogging: { Key: FeatureAuditLogging, DisplayName: "Audit Logging", ComparisonName: "Audit Logging", ShowInComparisonTable: true, DisplayableInPlanUI: true, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleHidden, Relay: SelfHostedFeatureRoleHidden, Pro: SelfHostedFeatureRoleIncludedExtra, }, UpgradeReason: "Upgrade to Pro to keep a trustworthy action trail for incident review, accountability, and compliance.", UpgradePriority: 8, }, FeatureAdvancedSSO: { Key: FeatureAdvancedSSO, DisplayName: "Multi-Provider SSO", ComparisonName: "Multi-Provider SSO", ShowInComparisonTable: false, DisplayableInPlanUI: false, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleIncluded, Relay: SelfHostedFeatureRoleIncluded, Pro: SelfHostedFeatureRoleIncluded, }, }, FeatureAdvancedReporting: { Key: FeatureAdvancedReporting, DisplayName: "PDF/CSV Reporting", ComparisonName: "PDF/CSV Reporting", ShowInComparisonTable: true, DisplayableInPlanUI: true, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleHidden, Relay: SelfHostedFeatureRoleHidden, Pro: SelfHostedFeatureRoleIncludedExtra, }, UpgradeReason: "Upgrade to Pro to turn live infrastructure state into shareable reports without manual screenshot work.", UpgradePriority: 9, }, FeatureMultiUser: { Key: FeatureMultiUser, DisplayName: "Multi-User Mode", ComparisonName: "Multi-User Mode", DisplayableInPlanUI: false, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleHidden, Relay: SelfHostedFeatureRoleHidden, Pro: SelfHostedFeatureRoleHidden, }, }, FeatureWhiteLabel: { Key: FeatureWhiteLabel, DisplayName: "White-Label Branding", ComparisonName: "White-Label Branding", DisplayableInPlanUI: false, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleHidden, Relay: SelfHostedFeatureRoleHidden, Pro: SelfHostedFeatureRoleHidden, }, }, FeatureMultiTenant: { Key: FeatureMultiTenant, DisplayName: "Multi-Tenant Mode", ComparisonName: "Multi-Tenant Mode", DisplayableInPlanUI: false, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleHidden, Relay: SelfHostedFeatureRoleHidden, Pro: SelfHostedFeatureRoleHidden, }, }, FeatureUnlimited: { Key: FeatureUnlimited, DisplayName: "Hosted Capacity Policy", ComparisonName: "Hosted Capacity Policy", DisplayableInPlanUI: false, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleHidden, Relay: SelfHostedFeatureRoleHidden, Pro: SelfHostedFeatureRoleHidden, }, }, FeatureDemoFixtures: { Key: FeatureDemoFixtures, DisplayName: "Demo Fixtures (Internal)", ComparisonName: "Demo Fixtures (Internal)", DisplayableInPlanUI: false, SelfHostedRoles: SelfHostedFeatureRoles{ Community: SelfHostedFeatureRoleHidden, Relay: SelfHostedFeatureRoleHidden, Pro: SelfHostedFeatureRoleHidden, }, }, } func AllFeatureMetadata() []FeatureMetadata { metadata := make([]FeatureMetadata, 0, len(orderedFeatureMetadataKeys)) for _, key := range orderedFeatureMetadataKeys { entry, ok := featureMetadataCatalog[key] if !ok { continue } metadata = append(metadata, entry) } return metadata } func GetFeatureMetadata(feature string) (FeatureMetadata, bool) { entry, ok := featureMetadataCatalog[feature] return entry, ok } func GetSelfHostedFeatureRole(feature string, tier Tier) SelfHostedFeatureRole { entry, ok := featureMetadataCatalog[feature] if !ok { return SelfHostedFeatureRoleHidden } switch tier { case TierFree: return entry.SelfHostedRoles.Community case TierRelay: return entry.SelfHostedRoles.Relay case TierPro, TierProPlus, TierProAnnual, TierLifetime: return entry.SelfHostedRoles.Pro default: return SelfHostedFeatureRoleHidden } } func IsCompatibilityOnlyFeature(feature string) bool { entry, ok := featureMetadataCatalog[feature] if !ok { return false } return entry.SelfHostedRoles.Community == SelfHostedFeatureRoleCompatibilityOnly || entry.SelfHostedRoles.Relay == SelfHostedFeatureRoleCompatibilityOnly || entry.SelfHostedRoles.Pro == SelfHostedFeatureRoleCompatibilityOnly } func GenericUpgradeFeatureMetadata() []FeatureMetadata { out := make([]FeatureMetadata, 0) for _, entry := range AllFeatureMetadata() { if entry.UpgradeReason != "" { out = append(out, entry) } } sort.SliceStable(out, func(i, j int) bool { if out[i].UpgradePriority == out[j].UpgradePriority { return out[i].Key < out[j].Key } return out[i].UpgradePriority < out[j].UpgradePriority }) return out }