diff --git a/frontend-modern/src/utils/__tests__/pricingHandoff.test.ts b/frontend-modern/src/utils/__tests__/pricingHandoff.test.ts index fac637a37..96922ae26 100644 --- a/frontend-modern/src/utils/__tests__/pricingHandoff.test.ts +++ b/frontend-modern/src/utils/__tests__/pricingHandoff.test.ts @@ -81,6 +81,7 @@ describe('pricingHandoff', () => { 'audit_logging', 'advanced_reporting', 'agent_profiles', + 'external_probe', ]); for (const key of paidCatalogFeatureKeys) { expect(getUpgradeFallbackDestination(key)).toBe( diff --git a/frontend-modern/src/utils/__tests__/selfHostedPlans.test.ts b/frontend-modern/src/utils/__tests__/selfHostedPlans.test.ts index 97556bf56..c30cac295 100644 --- a/frontend-modern/src/utils/__tests__/selfHostedPlans.test.ts +++ b/frontend-modern/src/utils/__tests__/selfHostedPlans.test.ts @@ -201,6 +201,7 @@ describe('selfHostedPlans', () => { 'audit_logging', 'advanced_reporting', 'agent_profiles', + 'external_probe', ]); expect( SELF_HOSTED_PLAN_DEFINITIONS.map((plan) => ({ diff --git a/frontend-modern/src/utils/pricingHandoff.ts b/frontend-modern/src/utils/pricingHandoff.ts index 96efd4e4d..dcf1a262f 100644 --- a/frontend-modern/src/utils/pricingHandoff.ts +++ b/frontend-modern/src/utils/pricingHandoff.ts @@ -67,6 +67,7 @@ const IN_PRODUCT_PRICING_DESTINATIONS: Record = { audit_logging: SELF_HOSTED_PRO_BILLING_PLAN_HREF, advanced_reporting: SELF_HOSTED_PRO_BILLING_PLAN_HREF, agent_profiles: SELF_HOSTED_PRO_BILLING_PLAN_HREF, + external_probe: SELF_HOSTED_PRO_BILLING_PLAN_HREF, long_term_metrics: SELF_HOSTED_PRO_BILLING_PLAN_HREF, }; diff --git a/frontend-modern/src/utils/selfHostedFeatureCatalog.generated.ts b/frontend-modern/src/utils/selfHostedFeatureCatalog.generated.ts index 0d46196fd..14ebec60e 100644 --- a/frontend-modern/src/utils/selfHostedFeatureCatalog.generated.ts +++ b/frontend-modern/src/utils/selfHostedFeatureCatalog.generated.ts @@ -261,6 +261,23 @@ export const SELF_HOSTED_FEATURE_CATALOG: readonly GeneratedSelfHostedFeatureCat pro: true, }, }, + { + key: 'external_probe', + displayName: 'External Probes', + comparisonName: 'External Probes', + showInComparisonTable: true, + displayableInSelfHostedPlan: true, + roles: { + community: 'hidden', + relay: 'hidden', + pro: 'included_extra', + }, + includedIn: { + community: false, + relay: false, + pro: true, + }, + }, { key: 'kubernetes_ai', displayName: 'Kubernetes AI Analysis (Compatibility)', diff --git a/internal/api/contract_test.go b/internal/api/contract_test.go index de828d0e4..187718eed 100644 --- a/internal/api/contract_test.go +++ b/internal/api/contract_test.go @@ -7639,7 +7639,7 @@ func TestContract_EntitlementPayloadMonitoredSystemUsageJSONSnapshot(t *testing. } const want = `{ - "capabilities":["update_alerts","sso","advanced_sso","ai_patrol","relay","mobile_app","push_notifications","long_term_metrics","ai_alerts","ai_autofix","kubernetes_ai","agent_profiles","rbac","audit_logging","advanced_reporting"], + "capabilities":["update_alerts","sso","advanced_sso","ai_patrol","relay","mobile_app","push_notifications","long_term_metrics","ai_alerts","ai_autofix","kubernetes_ai","agent_profiles","rbac","audit_logging","advanced_reporting","external_probe"], "limits":[], "subscription_state":"active", "upgrade_reasons":[], @@ -7694,7 +7694,7 @@ func TestContract_SelfHostedCommunityEntitlementsJSONSnapshot(t *testing.T) { {"key":"ai_alerts","reason":"Upgrade to Pro so Patrol can investigate issues instead of handing you a stack of symptoms.","action_url":"https://pulserelay.pro/pricing?utm_source=pulse\u0026utm_medium=app\u0026utm_campaign=upgrade\u0026feature=ai_alerts"}, {"key":"rbac","reason":"Upgrade to Pro when more than one operator needs safe access boundaries around infrastructure changes.","action_url":"https://pulserelay.pro/pricing?utm_source=pulse\u0026utm_medium=app\u0026utm_campaign=upgrade\u0026feature=rbac"}, {"key":"agent_profiles","reason":"Upgrade to Pro to standardize agent behavior across systems without reconfiguring every install by hand.","action_url":"https://pulserelay.pro/pricing?utm_source=pulse\u0026utm_medium=app\u0026utm_campaign=upgrade\u0026feature=agent_profiles"}, - {"key":"audit_logging","reason":"Upgrade to Pro to keep a trustworthy action trail for incident review, accountability, and compliance.","action_url":"https://pulserelay.pro/pricing?utm_source=pulse\u0026utm_medium=app\u0026utm_campaign=upgrade\u0026feature=audit_logging"}, + {"key":"external_probe","reason":"Upgrade to Pro to run availability checks from a probe outside the monitored site, with independent alerting.","action_url":"https://pulserelay.pro/pricing?utm_source=pulse\u0026utm_medium=app\u0026utm_campaign=upgrade\u0026feature=external_probe"},{"key":"audit_logging","reason":"Upgrade to Pro to keep a trustworthy action trail for incident review, accountability, and compliance.","action_url":"https://pulserelay.pro/pricing?utm_source=pulse\u0026utm_medium=app\u0026utm_campaign=upgrade\u0026feature=audit_logging"}, {"key":"advanced_reporting","reason":"Upgrade to Pro to turn live infrastructure state into shareable reports without manual screenshot work.","action_url":"https://pulserelay.pro/pricing?utm_source=pulse\u0026utm_medium=app\u0026utm_campaign=upgrade\u0026feature=advanced_reporting"} ], "tier":"free", @@ -7780,7 +7780,7 @@ func TestContract_EntitlementPayloadMonitoredSystemUsageUnavailableJSONSnapshot( } const want = `{ - "capabilities":["update_alerts","sso","advanced_sso","ai_patrol","relay","mobile_app","push_notifications","long_term_metrics","ai_alerts","ai_autofix","kubernetes_ai","agent_profiles","rbac","audit_logging","advanced_reporting"], + "capabilities":["update_alerts","sso","advanced_sso","ai_patrol","relay","mobile_app","push_notifications","long_term_metrics","ai_alerts","ai_autofix","kubernetes_ai","agent_profiles","rbac","audit_logging","advanced_reporting","external_probe"], "limits":[], "subscription_state":"active", "upgrade_reasons":[], @@ -7822,7 +7822,7 @@ func TestContract_EntitlementPayloadLifetimeJSONSnapshot(t *testing.T) { } const want = `{ - "capabilities":["update_alerts","sso","advanced_sso","ai_patrol","relay","mobile_app","push_notifications","long_term_metrics","ai_alerts","ai_autofix","kubernetes_ai","agent_profiles","rbac","audit_logging","advanced_reporting"], + "capabilities":["update_alerts","sso","advanced_sso","ai_patrol","relay","mobile_app","push_notifications","long_term_metrics","ai_alerts","ai_autofix","kubernetes_ai","agent_profiles","rbac","audit_logging","advanced_reporting","external_probe"], "limits":[], "subscription_state":"active", "upgrade_reasons":[], diff --git a/internal/api/licensing_bridge.go b/internal/api/licensing_bridge.go index cb4413cee..12d66b4b9 100644 --- a/internal/api/licensing_bridge.go +++ b/internal/api/licensing_bridge.go @@ -58,6 +58,7 @@ const ( featureAuditLoggingValue = pkglicensing.FeatureAuditLogging featureRBACValue = pkglicensing.FeatureRBAC featureAdvancedReportingValue = pkglicensing.FeatureAdvancedReporting + featureExternalProbeValue = pkglicensing.FeatureExternalProbe featureWhiteLabelValue = pkglicensing.FeatureWhiteLabel featureLongTermMetricsValue = pkglicensing.FeatureLongTermMetrics featureDemoFixturesValue = pkglicensing.FeatureDemoFixtures diff --git a/internal/license/features.go b/internal/license/features.go index 9d34aeb17..6f9f151bb 100644 --- a/internal/license/features.go +++ b/internal/license/features.go @@ -17,6 +17,7 @@ const ( FeatureSSO = licensing.FeatureSSO FeatureAdvancedSSO = licensing.FeatureAdvancedSSO FeatureAdvancedReporting = licensing.FeatureAdvancedReporting + FeatureExternalProbe = licensing.FeatureExternalProbe FeatureLongTermMetrics = licensing.FeatureLongTermMetrics FeatureRelay = licensing.FeatureRelay FeatureMobileApp = licensing.FeatureMobileApp diff --git a/pkg/licensing/feature_map.go b/pkg/licensing/feature_map.go index 87daa3b71..a7f45c8b9 100644 --- a/pkg/licensing/feature_map.go +++ b/pkg/licensing/feature_map.go @@ -17,6 +17,7 @@ var DefaultFeatureKeys = []string{ FeatureRBAC, FeatureAuditLogging, FeatureAdvancedReporting, + FeatureExternalProbe, FeatureMultiTenant, } diff --git a/pkg/licensing/features.go b/pkg/licensing/features.go index 0947d86cd..da474f0ed 100644 --- a/pkg/licensing/features.go +++ b/pkg/licensing/features.go @@ -33,6 +33,7 @@ const ( FeatureRBAC = "rbac" // Role-Based Access Control FeatureAuditLogging = "audit_logging" // Persistent audit logs with signing FeatureAdvancedReporting = "advanced_reporting" // PDF/CSV reporting engine + FeatureExternalProbe = "external_probe" // Availability checks run from outside the monitored site // MSP/Enterprise tier features FeatureMultiUser = "multi_user" // Multi-user (likely merged with RBAC) @@ -249,6 +250,7 @@ var proFeatures = appendFeatures(relayFeatures, FeatureRBAC, FeatureAuditLogging, FeatureAdvancedReporting, + FeatureExternalProbe, ) // mspFeatures adds multi-tenant policy on top of pro. diff --git a/pkg/licensing/self_hosted_feature_catalog.go b/pkg/licensing/self_hosted_feature_catalog.go index 7f1c10c0f..7bbe12c7a 100644 --- a/pkg/licensing/self_hosted_feature_catalog.go +++ b/pkg/licensing/self_hosted_feature_catalog.go @@ -44,6 +44,7 @@ var orderedFeatureMetadataKeys = []string{ FeatureAuditLogging, FeatureAdvancedReporting, FeatureAgentProfiles, + FeatureExternalProbe, FeatureKubernetesAI, FeatureMultiUser, FeatureWhiteLabel, @@ -253,6 +254,20 @@ var featureMetadataCatalog = map[string]FeatureMetadata{ UpgradeReason: "Upgrade to Pro to turn live infrastructure state into shareable reports without manual screenshot work.", UpgradePriority: 9, }, + FeatureExternalProbe: { + Key: FeatureExternalProbe, + DisplayName: "External Probes", + ComparisonName: "External Probes", + ShowInComparisonTable: true, + DisplayableInPlanUI: true, + SelfHostedRoles: SelfHostedFeatureRoles{ + Community: SelfHostedFeatureRoleHidden, + Relay: SelfHostedFeatureRoleHidden, + Pro: SelfHostedFeatureRoleIncludedExtra, + }, + UpgradeReason: "Upgrade to Pro to run availability checks from a probe outside the monitored site, with independent alerting.", + UpgradePriority: 7, + }, FeatureMultiUser: { Key: FeatureMultiUser, DisplayName: "Multi-User Mode", diff --git a/pkg/licensing/upgrade.go b/pkg/licensing/upgrade.go index 6faf609b2..6b1888957 100644 --- a/pkg/licensing/upgrade.go +++ b/pkg/licensing/upgrade.go @@ -93,6 +93,8 @@ func UpgradeURLForFeature(feature string) string { return DefaultUpgradeURL + "&feature=audit_logging" case FeatureAdvancedReporting: return DefaultUpgradeURL + "&feature=advanced_reporting" + case FeatureExternalProbe: + return DefaultUpgradeURL + "&feature=external_probe" default: return DefaultUpgradeURL } diff --git a/pkg/licensing/upgrade_test.go b/pkg/licensing/upgrade_test.go index c5a613e59..4adfb8cab 100644 --- a/pkg/licensing/upgrade_test.go +++ b/pkg/licensing/upgrade_test.go @@ -147,6 +147,7 @@ func TestUpgradeReasonMatrixDerivesFromCanonicalFeatureMetadata(t *testing.T) { FeatureAIAlerts, FeatureRBAC, FeatureAgentProfiles, + FeatureExternalProbe, FeatureAuditLogging, FeatureAdvancedReporting, }