From df3ab06174ddb77ab6f04a2bb6da8a0ad7e0f0fe Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 29 Apr 2026 17:37:38 +0100 Subject: [PATCH] Prove v6 paid feature claims --- docs/PULSE_PRO.md | 2 +- docs/architecture/v6-pricing-and-tiering.md | 19 ++++----- ...d-feature-claim-proof-matrix-2026-04-29.md | 1 + .../v6/internal/subsystems/cloud-paid.md | 8 +++- .../__tests__/ProLicensePanel.test.tsx | 2 +- .../src/utils/__tests__/cloudPlans.test.ts | 2 +- .../__tests__/licensePresentation.test.ts | 2 +- .../utils/__tests__/selfHostedPlans.test.ts | 12 +++++- frontend-modern/src/utils/cloudPlans.ts | 2 +- frontend-modern/src/utils/selfHostedPlans.ts | 10 ++--- .../internal/paid_feature_claims_proof.py | 40 ++++++++++++++++++- 11 files changed, 76 insertions(+), 24 deletions(-) diff --git a/docs/PULSE_PRO.md b/docs/PULSE_PRO.md index 32cf1ad0a..422848e83 100644 --- a/docs/PULSE_PRO.md +++ b/docs/PULSE_PRO.md @@ -161,7 +161,7 @@ Patrol and the Assistant support tiered autonomy: - Everything in Community, plus: - 14-day history. - Remote access via Relay. -- Mobile app access and push notifications. +- Pulse Mobile pairing for handoff and push notifications. ### Pro - Everything in Relay, plus: diff --git a/docs/architecture/v6-pricing-and-tiering.md b/docs/architecture/v6-pricing-and-tiering.md index 56f324525..dc0d196e0 100644 --- a/docs/architecture/v6-pricing-and-tiering.md +++ b/docs/architecture/v6-pricing-and-tiering.md @@ -10,7 +10,8 @@ This document is the single source of truth for Pulse v6 pricing, tiering, feature allocation, and conversion mechanics. All code, UI, marketing, and documentation must -align with this document. If there is a conflict, this document wins. +align with this document. If there is a conflict with release-control state, the +release-control source wins and this file must be corrected. --- @@ -110,7 +111,7 @@ than sold by monitored-system volume. | Alert-triggered root-cause analysis | No | | Relay | No | | Push notifications | No | -| Custom URL | No | +| Customer-specific Relay URL | No | | RBAC | No | | Audit logging | No | | SAML SSO | No | @@ -133,9 +134,9 @@ remediation actions through Pulse. | Monitoring scope | **Core self-hosted monitoring included** | | Everything in Free | Yes | | Relay remote access | **Yes** | -| Mobile app access | **Yes** | +| Pulse Mobile pairing | **Yes** (handoff and push notifications) | | Push notifications | **Yes** | -| Custom URL | **yourlab.pulserelay.pro** | +| Customer-specific Relay URL | No; Relay uses the standard outbound relay service for v6 GA | | Metrics history | **14 days** | | Safe remediation workflows | No | | Alert-triggered root-cause analysis | No | @@ -143,7 +144,7 @@ remediation actions through Pulse. | Reporting | No | **Positioning:** The convenience tier. It should feel cheap enough to buy on the spot when -someone wants secure remote access, mobile checks, push notifications, and longer history +someone wants secure remote access, mobile pairing, push notifications, and longer history without changing their self-hosted monitoring scope. ### Pro — $8.99/month or $79/year @@ -160,7 +161,7 @@ without changing their self-hosted monitoring scope. | SAML SSO | **Yes** | | Agent profiles | **Yes** | | PDF/CSV reporting | **Yes** | -| Trial | **14-day, no credit card** | +| Self-hosted trial acquisition | No; local trial CTAs are retired for v6 GA | **Positioning:** For serious self-hosted operators who want Pulse to move from monitoring into operations. The marketing pitch focuses on three things: @@ -345,7 +346,7 @@ want to self-host. ## Self-Hosted Cap Migration -- There is no v6 self-hosted monitored-system cap migration for Community, Relay, Pro, or Pro+ +- There is no v6 self-hosted monitored-system capacity migration for Community, Relay, Pro, or Pro+ - Existing self-hosted users keep their monitored coverage through the v6 rollout - Hosted Cloud and MSP capacity limits remain plan-specific license claims, not self-hosted static tier defaults - The UI may still explain monitored-system identity, but it must not frame self-hosted growth as a capacity upsell @@ -479,7 +480,7 @@ explain monitored-system identity: ### Frontend -- [x] Remove self-hosted monitored-system cap pressure from billing and pricing surfaces +- [x] Remove self-hosted monitored-system capacity pressure from billing and pricing surfaces - [x] Present the public self-hosted ladder as Community / Relay / Pro - [ ] Keep ledger and inventory language focused on what Pulse monitors, not paid capacity pressure - [ ] Keep paid prompts out of ordinary self-hosted runtime surfaces; commercial copy belongs in explicit pricing, activation, recovery, hosted, or entitlement-aware paths @@ -521,7 +522,7 @@ explain monitored-system identity: | Date | Change | Author | |---|---|---| | 2026-04-29 | Replaced stale capacity-style monitoring phrasing with core-monitoring-included language across active v6 docs and upgrade-return copy so Community does not read like a former capacity upsell. | Codex | -| 2026-04-23 | Removed stale self-hosted monitored-system cap and Pro+ public-checkout language. Reaffirmed Community / Relay / Pro as current public self-hosted tiers, with Pro+ as continuity only and Pro value centered on operations, history, and admin controls. | Codex | +| 2026-04-23 | Removed stale self-hosted monitored-system capacity and Pro+ public-checkout language. Reaffirmed Community / Relay / Pro as current public self-hosted tiers, with Pro+ as continuity only and Pro value centered on operations, history, and admin controls. | Codex | | 2026-03-17 | Re-locked the self-hosted commercial model around monitored systems rather than installed agents. New self-hosted public pricing: Relay $4.99/$39, Pro $8.99/$79, Pro+ $14.99/$129. Added free-tier grace policy and marked the monitored-system counting migration as still required in code. | Codex + Richard | | 2026-02-25 | Initial v6 pricing structure finalized | Richard + Claude + Codex | | 2026-02-25 | Changed counting to agents-only model. Only installed Pulse Unified Agents count toward limits. PVE/PBS/PMG/Docker/K8s connections and discovered resources don't count. This makes limits much more generous in practice (5 agents can monitor an entire multi-node cluster). | Richard + Claude | diff --git a/docs/release-control/v6/internal/records/paid-feature-claim-proof-matrix-2026-04-29.md b/docs/release-control/v6/internal/records/paid-feature-claim-proof-matrix-2026-04-29.md index 390aaa011..0956a7a39 100644 --- a/docs/release-control/v6/internal/records/paid-feature-claim-proof-matrix-2026-04-29.md +++ b/docs/release-control/v6/internal/records/paid-feature-claim-proof-matrix-2026-04-29.md @@ -18,6 +18,7 @@ python3 scripts/release_control/paid_feature_claims_proof.py --json - Relay claims only remote web access, mobile pairing, push notifications, and 14-day metric history. - Pro preserves Relay capabilities and adds operator extras: root-cause analysis, safe remediation workflows, 90-day metric history, RBAC, audit logging, reporting, SAML SSO, and agent profiles. - Public app, docs, and site copy must not reintroduce old self-hosted monitoring-limit, higher-limit, default-trial, hosted-model-credit, or bundled-Patrol-credit claims. +- Relay must not be marketed as including a customer-specific `*.pulserelay.pro` URL until that product surface exists; v6 GA Relay is the standard outbound relay service. - History claims are enforced by the runtime metrics-history API, not only shown in copy. - Pro admin extras are backed by concrete core and enterprise runtime tests for RBAC, audit logging, reporting, SAML SSO, and agent profile behavior. - Public pricing, checkout, download, and relay-server entitlement behavior remain consistent with those claims. diff --git a/docs/release-control/v6/internal/subsystems/cloud-paid.md b/docs/release-control/v6/internal/subsystems/cloud-paid.md index c612ca994..4c04240e7 100644 --- a/docs/release-control/v6/internal/subsystems/cloud-paid.md +++ b/docs/release-control/v6/internal/subsystems/cloud-paid.md @@ -1542,7 +1542,13 @@ it as a current public Pulse Pro+ package. When direct plan-selection intent opens the explicit self-hosted comparison surface, the shared presentation helpers must show Pro's operations, admin, and reporting extras together while still framing Community, Relay, and Pro as core monitoring included in every -self-hosted tier rather than monitored-system capacity tiers. The same factual +self-hosted tier rather than monitored-system capacity tiers. Relay copy in +that shared owner must describe the current v6 GA product as standard Relay +remote access, supported Pulse Mobile pairing, push notifications, and 14-day +history, and it must not market a customer-specific Relay URL until that +routing surface exists as a backed product capability. Cloud common-inclusion +copy follows the same mobile-language rule: describe supported Pulse Mobile +pairing and push notifications rather than generic mobile app access. The same factual plan surface must keep inactive-state copy neutral: default Community installs must describe the instance as ready to use rather than as missing an activation key, and they must not foreground paid self-hosted activation, v5-era "No Pro diff --git a/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx b/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx index 7b21d2951..54cbd6ca8 100644 --- a/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx @@ -546,7 +546,7 @@ describe('ProLicensePanel', () => { expect( screen.getByText( - 'Relay is active on this instance. Remote web access, mobile pairing, push notifications, and longer history are available right now.', + 'Relay is active on this instance. Remote web access, Pulse Mobile pairing, push notifications, and longer history are available right now.', ), ).toBeInTheDocument(); expect(screen.queryByText('Optional extras')).not.toBeInTheDocument(); diff --git a/frontend-modern/src/utils/__tests__/cloudPlans.test.ts b/frontend-modern/src/utils/__tests__/cloudPlans.test.ts index 483b9ed10..eeaeecd9a 100644 --- a/frontend-modern/src/utils/__tests__/cloudPlans.test.ts +++ b/frontend-modern/src/utils/__tests__/cloudPlans.test.ts @@ -40,7 +40,7 @@ describe('cloudPlans', () => { 'Managed hosting', 'Daily backups', 'Secure agent connectivity via Relay', - 'Mobile app access and push notifications', + 'Pulse Mobile pairing and push notifications', 'Dedicated workspace URL', ], setupHeading: 'How it works', diff --git a/frontend-modern/src/utils/__tests__/licensePresentation.test.ts b/frontend-modern/src/utils/__tests__/licensePresentation.test.ts index ec1e310d2..4c23357ca 100644 --- a/frontend-modern/src/utils/__tests__/licensePresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/licensePresentation.test.ts @@ -426,7 +426,7 @@ describe('licensePresentation', () => { cards: [ { title: 'What Relay adds', - body: 'Reach your Pulse web UI securely from anywhere, pair the mobile app for handoff and push notifications, and keep 14 days of history.', + body: 'Reach your Pulse web UI securely from anywhere, pair supported Pulse Mobile clients for handoff and push notifications, and keep 14 days of history.', highlights: [ 'Pulse Relay (Remote Access)', 'Mobile App Pairing', diff --git a/frontend-modern/src/utils/__tests__/selfHostedPlans.test.ts b/frontend-modern/src/utils/__tests__/selfHostedPlans.test.ts index 8ca874b11..13db0ffc4 100644 --- a/frontend-modern/src/utils/__tests__/selfHostedPlans.test.ts +++ b/frontend-modern/src/utils/__tests__/selfHostedPlans.test.ts @@ -64,7 +64,7 @@ describe('selfHostedPlans', () => { expect(SELF_HOSTED_COMMERCIAL_PRESENTATION).toEqual({ pageTitle: 'Pricing', pageDescription: - 'Self-hosted Pulse includes core monitoring for free. Relay adds secure remote access to the Pulse web UI, mobile pairing, and push notifications, while Pro adds root-cause analysis, safe remediation workflows, 90-day history, and admin/reporting extras.', + 'Self-hosted Pulse includes core monitoring for free. Relay adds secure remote access to the Pulse web UI, Pulse Mobile pairing, and push notifications, while Pro adds root-cause analysis, safe remediation workflows, 90-day history, and admin/reporting extras.', mostPopularBadge: 'Most Popular', currentPlanLabel: 'Current Plan', includedLabel: 'Included', @@ -109,7 +109,7 @@ describe('selfHostedPlans', () => { 'Remote web access, mobile pairing, and push', ); expect(SELF_HOSTED_PLAN_BY_TIER.relay.entitlementSummary).toContain( - 'Remote web access, mobile pairing, push notifications, and longer history are available right now.', + 'Remote web access, Pulse Mobile pairing, push notifications, and longer history are available right now.', ); expect(SELF_HOSTED_PLAN_BY_TIER.relay.comparisonSummary).toContain( 'Reach your Pulse web UI securely from anywhere', @@ -121,6 +121,11 @@ describe('selfHostedPlans', () => { '14-day metric history', ]); expect(SELF_HOSTED_PLAN_BY_TIER.relay.includedExtras).toEqual([]); + expect(SELF_HOSTED_PLAN_BY_TIER.relay.highlights).toContain('Pulse Mobile pairing'); + expect(SELF_HOSTED_PLAN_BY_TIER.relay.highlights).toContain('No inbound ports required'); + expect(SELF_HOSTED_PLAN_BY_TIER.relay.highlights.join('\n')).not.toMatch( + /yourlab\.pulserelay\.pro|custom\s+(?:url|subdomain|domain)/i, + ); expect(SELF_HOSTED_PLAN_BY_TIER.pro.billingExtrasSummary).toBe( 'Analysis, remediation, and admin controls', ); @@ -245,6 +250,9 @@ describe('selfHostedPlans', () => { expect(paidClaimText).not.toMatch( /unlimited|monitoring capacity|guest capacity|hosted quickstart|trial/i, ); + expect(paidClaimText).not.toMatch( + /yourlab\.pulserelay\.pro|custom\s+(?:url|subdomain|domain)/i, + ); }); it('maps current billing tiers back to the shared self-hosted plan definitions', () => { diff --git a/frontend-modern/src/utils/cloudPlans.ts b/frontend-modern/src/utils/cloudPlans.ts index f99ae1a9c..77b3b496d 100644 --- a/frontend-modern/src/utils/cloudPlans.ts +++ b/frontend-modern/src/utils/cloudPlans.ts @@ -112,7 +112,7 @@ export const CLOUD_COMMERCIAL_PRESENTATION: CloudCommercialPresentation = { 'Managed hosting', 'Daily backups', 'Secure agent connectivity via Relay', - 'Mobile app access and push notifications', + 'Pulse Mobile pairing and push notifications', 'Dedicated workspace URL', ], setupHeading: 'How it works', diff --git a/frontend-modern/src/utils/selfHostedPlans.ts b/frontend-modern/src/utils/selfHostedPlans.ts index 5b4c34e2a..9e7b9a280 100644 --- a/frontend-modern/src/utils/selfHostedPlans.ts +++ b/frontend-modern/src/utils/selfHostedPlans.ts @@ -82,7 +82,7 @@ export function getSelfHostedPlanEntitlementSummary( case 'community': return `${planLabel} is active on this instance. It includes self-hosted monitoring, 7-day metric history, Pulse Patrol (BYOK), and update alerts.`; case 'relay': - return `${planLabel} is active on this instance. Remote web access, mobile pairing, push notifications, and longer history are available right now.`; + return `${planLabel} is active on this instance. Remote web access, Pulse Mobile pairing, push notifications, and longer history are available right now.`; case 'pro': return `${planLabel} is active on this instance. Root-cause analysis, safe remediation workflows, 90-day history, and admin/reporting extras are available right now.`; } @@ -135,13 +135,13 @@ export const SELF_HOSTED_PLAN_DEFINITIONS: readonly SelfHostedPlanDefinition[] = entitlementHighlights: getTierEntitlementHighlights('relay', 14), includedExtras: [], comparisonSummary: - 'Reach your Pulse web UI securely from anywhere, pair the mobile app for handoff and push notifications, and keep 14 days of history.', + 'Reach your Pulse web UI securely from anywhere, pair supported Pulse Mobile clients for handoff and push notifications, and keep 14 days of history.', highlights: [ 'Everything in Community', 'Remote web access via Relay', - 'Mobile app pairing', + 'Pulse Mobile pairing', 'Push notifications', - 'Custom URL (yourlab.pulserelay.pro)', + 'No inbound ports required', '14-day metric history', ], }, @@ -196,7 +196,7 @@ export function getSelfHostedPlanDefinitionForBillingTier( export const SELF_HOSTED_COMMERCIAL_PRESENTATION: SelfHostedCommercialPresentation = { pageTitle: 'Pricing', pageDescription: - 'Self-hosted Pulse includes core monitoring for free. Relay adds secure remote access to the Pulse web UI, mobile pairing, and push notifications, while Pro adds root-cause analysis, safe remediation workflows, 90-day history, and admin/reporting extras.', + 'Self-hosted Pulse includes core monitoring for free. Relay adds secure remote access to the Pulse web UI, Pulse Mobile pairing, and push notifications, while Pro adds root-cause analysis, safe remediation workflows, 90-day history, and admin/reporting extras.', mostPopularBadge: 'Most Popular', currentPlanLabel: 'Current Plan', includedLabel: 'Included', diff --git a/scripts/release_control/internal/paid_feature_claims_proof.py b/scripts/release_control/internal/paid_feature_claims_proof.py index 7c5e3440f..b4727ed50 100644 --- a/scripts/release_control/internal/paid_feature_claims_proof.py +++ b/scripts/release_control/internal/paid_feature_claims_proof.py @@ -48,6 +48,10 @@ FORBIDDEN_PUBLIC_COPY_PATTERNS: tuple[tuple[str, str], ...] = ( ("hosted model credits", r"\b(?:ai|model)\s+credits?\b"), ("hosted Patrol quickstart", r"\bhosted\s+(?:patrol\s+)?quickstart\b"), ("bundled Patrol run allowance", r"\b25\s+runs,\s+no\s+api\s+key\b"), + ( + "customer-specific Relay URL promise", + r"\byourlab\.pulserelay\.pro\b|\ba\s+custom\s+url\b|custom\s+url\s*\([^)]*pulserelay", + ), ) @@ -82,6 +86,17 @@ PUBLIC_COPY_AUDIT_FILES: tuple[CopyAuditFileSpec, ...] = ( r"does not expose a general in-app trial", ), ), + CopyAuditFileSpec( + repo="pulse", + relative_path="docs/architecture/v6-pricing-and-tiering.md", + required_patterns=( + r"monitored systems are not the paid\s+gate", + r"Customer-specific Relay URL.*standard outbound relay service", + r"Self-hosted trial acquisition.*No", + r"Metrics history.*14 days", + r"Metrics history.*90 days", + ), + ), CopyAuditFileSpec( repo="pulse", relative_path="frontend-modern/src/utils/selfHostedPlans.ts", @@ -93,12 +108,22 @@ PUBLIC_COPY_AUDIT_FILES: tuple[CopyAuditFileSpec, ...] = ( r"admin/reporting extras", ), ), + CopyAuditFileSpec( + repo="pulse-pro", + relative_path="MONETIZATION.md", + required_patterns=( + r"Relay Tier.*14-day history", + r"Pulse Relay.*secure remote access", + r"No inbound firewall ports", + r"Self-hosted Pro should be sold on root-cause analysis", + ), + ), CopyAuditFileSpec( repo="pulse-pro", relative_path="landing-page/index.html", required_patterns=( r"Community keeps core monitoring free", - r"mobile app pairing, push notifications, and 14-day history", + r"(?:Pulse Mobile|mobile app) pairing, push notifications, and 14-day history", r"root-cause analysis, safe remediation workflows, and 90-day history", r"Do Relay or Pro charge by server\?", r"Existing Pulse Pro and legacy Pro\+ holders keep their paid runtime access", @@ -109,11 +134,21 @@ PUBLIC_COPY_AUDIT_FILES: tuple[CopyAuditFileSpec, ...] = ( relative_path="license-server/public_pricing.go", required_patterns=( r"Community keeps core monitoring free", - r"mobile app pairing, push notifications, and 14-day history", + r"(?:Pulse Mobile|mobile app) pairing, push notifications, and 14-day history", r"root-cause analysis, safe remediation workflows, team controls, and 90-day history", r"not as the self-hosted paid gate", ), ), + CopyAuditFileSpec( + repo="pulse-pro", + relative_path="license-server/scripts/create-v6-stripe-prices.sh", + required_patterns=( + r"Pulse Relay", + r"Pulse Mobile pairing", + r"no inbound firewall ports", + r"14-day history", + ), + ), ) @@ -234,6 +269,7 @@ def build_command_specs(args: argparse.Namespace) -> list[CommandSpec]: "test", "--", "src/utils/__tests__/selfHostedPlans.test.ts", + "src/utils/__tests__/cloudPlans.test.ts", "src/utils/__tests__/commercialBillingModel.test.ts", "src/utils/__tests__/licensePresentation.test.ts", "src/stores/__tests__/license.test.ts",