Pulse/pkg/licensing/self_hosted_feature_catalog.go
rcourtman ee8a24e14a backend and governance: MCP contract, agent capabilities, API, and release-control
Manifest-backed MCP tools, prompts, and resources with surface affordance contracts; agent capability manifest and governance projection; API contract tests and capability route projection; operations-loop and intelligence-funnel telemetry; release-control subsystem documentation, registry, and tooling; licensing and configuration.
2026-06-23 17:26:15 +01:00

371 lines
12 KiB
Go

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",
ComparisonName: "Patrol Investigates Issues",
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 Handles Safe Fixes",
ComparisonName: "Patrol Handles Safe Fixes",
ShowInComparisonTable: true,
DisplayableInPlanUI: true,
SelfHostedRoles: SelfHostedFeatureRoles{
Community: SelfHostedFeatureRoleHidden,
Relay: SelfHostedFeatureRoleHidden,
Pro: SelfHostedFeatureRolePrimaryPillar,
},
UpgradeReason: "Upgrade to Pro so Patrol can investigate issues, handle safe fixes within Patrol mode, and verify the outcome.",
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
}