Add paid feature claim proof bundle

This commit is contained in:
rcourtman 2026-04-29 14:18:43 +01:00
parent 17ba5f3043
commit 48c8d26198
14 changed files with 719 additions and 37 deletions

View file

@ -0,0 +1,21 @@
# Paid Feature Claim Proof Matrix
Date: 2026-04-29
## Decision
Self-hosted v6 paid claims are release-gated by an executable proof bundle. The bundle verifies that Community, Relay, and Pro claims line up across the canonical licensing contract, history entitlement enforcement, frontend plan copy, public checkout/pricing, download authorization, and Relay runtime entitlement behavior.
## Proof Command
```bash
python3 scripts/release_control/paid_feature_claims_proof.py --json
```
## Required Coverage
- Community keeps core monitoring free and does not introduce monitored-system volume caps.
- 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.
- History claims are enforced by the runtime metrics-history API, not only shown in copy.
- Public pricing, checkout, download, and relay-server entitlement behavior remain consistent with those claims.

View file

@ -2512,6 +2512,12 @@
"path": "docs/release-control/v6/internal/records/cloud-msp-price-audit-2026-03-13.md",
"kind": "file"
},
{
"repo": "pulse",
"path": "docs/release-control/v6/internal/records/paid-feature-claim-proof-matrix-2026-04-29.md",
"kind": "file",
"evidence_tier": "test-proof"
},
{
"repo": "pulse",
"path": "docs/release-control/v6/internal/records/self-hosted-commercial-ga-coherence-2026-04-20.md",
@ -2527,6 +2533,12 @@
"path": "docs/release-control/v6/internal/subsystems/cloud-paid.md",
"kind": "file"
},
{
"repo": "pulse",
"path": "scripts/release_control/paid_feature_claims_proof.py",
"kind": "file",
"evidence_tier": "test-proof"
},
{
"repo": "pulse-pro",
"path": "V6_LAUNCH_CHECKLIST.md",
@ -4415,11 +4427,23 @@
"L16"
],
"evidence": [
{
"repo": "pulse",
"path": "docs/release-control/v6/internal/records/paid-feature-claim-proof-matrix-2026-04-29.md",
"kind": "file",
"evidence_tier": "test-proof"
},
{
"repo": "pulse",
"path": "docs/release-control/v6/internal/records/paid-feature-entitlement-gating-2026-03-12.md",
"kind": "file",
"evidence_tier": "managed-runtime-exercise"
},
{
"repo": "pulse",
"path": "scripts/release_control/paid_feature_claims_proof.py",
"kind": "file",
"evidence_tier": "test-proof"
}
]
},

View file

@ -178,19 +178,27 @@ The canonical operator preview for that cutover now lives in
`pulse-pro/scripts/grandfathered_recurring_cutover_preview.py`, and it must use
the same active-at-snapshot rule as the live server before anyone sets
`PULSE_LICENSE_GRANDFATHERED_RECURRING_SNAPSHOT_AT`.
That same commercial control boundary also owns the server-side checkout funnel
analytics, alerting, digest generation, and admin summary payloads in
`pulse-pro:license-server/checkout_funnel.go`. Alert evaluation there must use
rolling trailing windows for "last 24 hours" and "previous 7 days" semantics
rather than UTC calendar-day buckets, so midnight does not suppress active
checkout regressions or leave the admin summary without the alert state the
server already observed.
The retired server-side checkout funnel analytics, alerting, digest
generation, and admin summary payloads are no longer part of the active v6
proof floor. `pulse-pro:license-server/v6_checkout.go` may accept legacy
funnel-telemetry fields only as ignored compatibility input for older static
site deploys; active paid-feature proof must stay on checkout creation,
portal-handoff state, pricing/catalog truth, download delivery, and license or
relay entitlement behavior rather than requiring the removed funnel monitoring
files.
That same public-pricing boundary also owns the machine-readable v6 pricing
validator and launch checklist in `pulse-pro:scripts/validate_public_pricing_model.py`
and `pulse-pro:V6_LAUNCH_CHECKLIST.md`. Those operator surfaces must describe
self-hosted Pulse as core monitoring included by default, must not reintroduce
monitored-system upsell language, and must verify the no-cap release posture
rather than legacy Community limit enforcement.
Self-hosted paid-feature claims must also remain covered by the executable
cross-repo proof bundle in `scripts/release_control/paid_feature_claims_proof.py`.
When Community, Relay, or Pro claims change, that proof must continue to verify
the canonical licensing matrix, runtime history entitlement enforcement,
frontend plan presentation, public pricing/checkout/download behavior, and
Relay entitlement acceptance together rather than treating copy, checkout, and
runtime gating as separate unlinked claims.
## Extension Points

View file

@ -1834,7 +1834,6 @@
"internal/hosted/provisioner.go",
"pulse-pro:.github/workflows/deploy-v6-preview-demo.yml",
"pulse-pro:landing-page/thanks.html",
"pulse-pro:license-server/checkout_funnel.go",
"pulse-pro:license-server/grandfathering.go",
"pulse-pro:license-server/main.go",
"pulse-pro:license-server/self_hosted_feature_catalog.generated.go",
@ -2530,19 +2529,6 @@
"frontend-modern/src/pages/__tests__/RuntimeHome.test.tsx"
]
},
{
"id": "checkout-funnel-analytics",
"label": "checkout funnel analytics proof",
"match_prefixes": [],
"match_files": [
"pulse-pro:license-server/checkout_funnel.go"
],
"allow_same_subsystem_tests": false,
"test_prefixes": [],
"exact_files": [
"pulse-pro:license-server/checkout_funnel_test.go"
]
},
{
"id": "legacy-grandfathering-cutover",
"label": "legacy recurring grandfathering cutover proof",

View file

@ -28,6 +28,7 @@ import {
SELF_HOSTED_RECOVERY_PRESENTATION,
} from '@/utils/licensePresentation';
import { SELF_HOSTED_PRO_BILLING_PRESENTATION } from '@/components/Settings/selfHostedBillingPresentation';
import { getSelfHostedPlanEntitlementSummary } from '@/utils/selfHostedPlans';
describe('licensePresentation', () => {
it('returns canonical tier labels', () => {
@ -378,6 +379,38 @@ describe('licensePresentation', () => {
});
});
it('sources active self-hosted current-plan summaries from the shared plan contract', () => {
expect(
getSelfHostedCurrentPlanPresentation({
entitlements: {
tier: 'relay',
subscription_state: 'active',
capabilities: ['relay', 'mobile_app', 'push_notifications'],
limits: [],
upgrade_reasons: [],
},
displayableCapabilities: [
'Pulse Relay (Remote Access)',
'Mobile App Pairing',
'Push Notifications',
],
}).body,
).toBe(getSelfHostedPlanEntitlementSummary('relay', 'Relay'));
expect(
getSelfHostedCurrentPlanPresentation({
entitlements: {
tier: 'pro_plus',
subscription_state: 'active',
capabilities: ['relay', 'ai_autofix'],
limits: [],
upgrade_reasons: [],
},
displayableCapabilities: ['Pulse Relay (Remote Access)', 'Safe Remediation Workflows'],
}).body,
).toBe(getSelfHostedPlanEntitlementSummary('pro', 'Legacy Pulse Pro+'));
});
it('builds restrained higher-tier comparison cards for Community and Relay only', () => {
expect(
getSelfHostedPlanComparisonPresentation({

View file

@ -1,12 +1,24 @@
import { describe, expect, it } from 'vitest';
import {
getSelfHostedPlanEntitlementSummary,
getSelfHostedPlanDefinitionForBillingTier,
SELF_HOSTED_COMMERCIAL_PRESENTATION,
SELF_HOSTED_FEATURE_ROWS,
SELF_HOSTED_PLAN_BY_TIER,
SELF_HOSTED_PLAN_DEFINITIONS,
} from '../selfHostedPlans';
import {
getSelfHostedFeaturesForRole,
SELF_HOSTED_FEATURE_CATALOG,
} from '../selfHostedFeatureCatalog.generated';
const primaryPillarClaimLabels = (tier: 'relay' | 'pro', metricHistoryDays: number) =>
getSelfHostedFeaturesForRole(tier, 'primary_pillar').map((entry) =>
entry.key === 'long_term_metrics'
? `${metricHistoryDays}-day metric history`
: entry.comparisonName,
);
describe('selfHostedPlans', () => {
it('keeps core monitoring aligned as an included self-hosted baseline', () => {
@ -145,6 +157,96 @@ describe('selfHostedPlans', () => {
);
});
it('derives paid self-hosted feature claims from the generated catalog', () => {
expect(SELF_HOSTED_PLAN_BY_TIER.community.entitlementSummary).toBe(
getSelfHostedPlanEntitlementSummary('community'),
);
expect(SELF_HOSTED_PLAN_BY_TIER.relay.entitlementSummary).toBe(
getSelfHostedPlanEntitlementSummary('relay'),
);
expect(SELF_HOSTED_PLAN_BY_TIER.pro.entitlementSummary).toBe(
getSelfHostedPlanEntitlementSummary('pro'),
);
expect(getSelfHostedPlanEntitlementSummary('pro', 'Legacy Pulse Pro+')).toBe(
'Legacy Pulse Pro+ is active on this instance. Root-cause analysis, safe remediation workflows, 90-day history, and admin/reporting extras are available right now.',
);
expect(SELF_HOSTED_PLAN_BY_TIER.relay.entitlementHighlights).toEqual(
primaryPillarClaimLabels('relay', SELF_HOSTED_PLAN_BY_TIER.relay.metricHistoryDays),
);
expect(SELF_HOSTED_PLAN_BY_TIER.pro.entitlementHighlights).toEqual(
primaryPillarClaimLabels('pro', SELF_HOSTED_PLAN_BY_TIER.pro.metricHistoryDays),
);
expect(SELF_HOSTED_PLAN_BY_TIER.pro.includedExtras).toEqual(
getSelfHostedFeaturesForRole('pro', 'included_extra').map((entry) => entry.displayName),
);
const displayablePaidFeatures = SELF_HOSTED_FEATURE_CATALOG.filter(
(entry) =>
entry.displayableInSelfHostedPlan &&
!entry.includedIn.community &&
(entry.includedIn.relay || entry.includedIn.pro),
).map((entry) => entry.key);
expect(displayablePaidFeatures).toEqual([
'relay',
'mobile_app',
'push_notifications',
'ai_alerts',
'ai_autofix',
'long_term_metrics',
'advanced_sso',
'rbac',
'audit_logging',
'advanced_reporting',
'agent_profiles',
]);
expect(
SELF_HOSTED_PLAN_DEFINITIONS.map((plan) => ({
tier: plan.tier,
historyDays: plan.metricHistoryDays,
extras: plan.billingExtrasSummary,
})),
).toEqual([
{ tier: 'community', historyDays: 7, extras: 'Patrol, alerts, and OIDC' },
{
tier: 'relay',
historyDays: 14,
extras: 'Remote web access, mobile pairing, and push',
},
{ tier: 'pro', historyDays: 90, extras: 'Analysis, remediation, and admin controls' },
]);
expect(SELF_HOSTED_FEATURE_ROWS.find((row) => row.key === 'history')).toEqual({
key: 'history',
name: 'Metric History',
community: '7 days',
relay: '14 days',
pro: '90 days',
});
const paidClaimText = [
SELF_HOSTED_COMMERCIAL_PRESENTATION.pageDescription,
SELF_HOSTED_PLAN_BY_TIER.relay.entitlementSummary,
SELF_HOSTED_PLAN_BY_TIER.relay.comparisonSummary,
...SELF_HOSTED_PLAN_BY_TIER.relay.entitlementHighlights,
...SELF_HOSTED_PLAN_BY_TIER.relay.highlights,
SELF_HOSTED_PLAN_BY_TIER.pro.entitlementSummary,
SELF_HOSTED_PLAN_BY_TIER.pro.comparisonSummary,
...SELF_HOSTED_PLAN_BY_TIER.pro.entitlementHighlights,
...SELF_HOSTED_PLAN_BY_TIER.pro.includedExtras,
...SELF_HOSTED_PLAN_BY_TIER.pro.highlights,
].join('\n');
for (const hiddenEntry of SELF_HOSTED_FEATURE_CATALOG.filter(
(entry) => !entry.displayableInSelfHostedPlan,
)) {
expect(paidClaimText).not.toContain(hiddenEntry.displayName);
expect(paidClaimText).not.toContain(hiddenEntry.comparisonName);
}
expect(paidClaimText).not.toMatch(
/unlimited|monitoring capacity|guest capacity|hosted quickstart|trial/i,
);
});
it('maps current billing tiers back to the shared self-hosted plan definitions', () => {
expect(getSelfHostedPlanDefinitionForBillingTier('free')).toBe(
SELF_HOSTED_PLAN_BY_TIER.community,

View file

@ -14,7 +14,10 @@ import {
type MonitoredSystemCapacityStatus,
type MonitoredSystemLimitUsageStatus,
} from '@/utils/monitoredSystemPresentation';
import { getSelfHostedPlanDefinitionForBillingTier } from '@/utils/selfHostedPlans';
import {
getSelfHostedPlanDefinitionForBillingTier,
getSelfHostedPlanEntitlementSummary,
} from '@/utils/selfHostedPlans';
import {
getSelfHostedFeatureCatalogEntry,
isDisplayableSelfHostedFeatureKey,
@ -417,16 +420,10 @@ const getSelfHostedActivePlanSummary = (
planLabel: string,
planDefinition: ReturnType<typeof getSelfHostedPlanDefinitionForBillingTier>,
): string | null => {
switch (planDefinition?.tier) {
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.`;
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.`;
default:
return null;
if (!planDefinition) {
return null;
}
return getSelfHostedPlanEntitlementSummary(planDefinition.tier, planLabel);
};
export const getSelfHostedPlanComparisonPresentation = ({

View file

@ -68,6 +68,26 @@ const getTierEntitlementHighlights = (
const getTierIncludedExtras = (tier: SelfHostedTierKey): readonly string[] =>
getSelfHostedFeaturesForRole(tier, 'included_extra').map((entry) => entry.displayName);
const SELF_HOSTED_PLAN_DEFAULT_ENTITLEMENT_LABELS: Record<SelfHostedTierKey, string> = {
community: 'Community',
relay: 'Relay',
pro: 'Pulse Pro',
};
export function getSelfHostedPlanEntitlementSummary(
tier: SelfHostedTierKey,
planLabel = SELF_HOSTED_PLAN_DEFAULT_ENTITLEMENT_LABELS[tier],
): string {
switch (tier) {
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.`;
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.`;
}
}
const buildSelfHostedComparisonFeatureRows = (): readonly SelfHostedFeatureRow[] =>
SELF_HOSTED_FEATURE_CATALOG.filter((entry) => entry.showInComparisonTable).map((entry) => ({
key: entry.key,
@ -85,8 +105,7 @@ export const SELF_HOSTED_PLAN_DEFINITIONS: readonly SelfHostedPlanDefinition[] =
subline: 'Core monitoring included',
metricHistoryDays: 7,
billingExtrasSummary: 'Patrol, alerts, and OIDC',
entitlementSummary:
'Community is active on this instance. It includes self-hosted monitoring, 7-day metric history, Pulse Patrol (BYOK), and update alerts.',
entitlementSummary: getSelfHostedPlanEntitlementSummary('community'),
entitlementHighlights: [
'Real-time monitoring',
'7-day metric history',
@ -112,8 +131,7 @@ export const SELF_HOSTED_PLAN_DEFINITIONS: readonly SelfHostedPlanDefinition[] =
subline: 'or $4.99/month',
metricHistoryDays: 14,
billingExtrasSummary: 'Remote web access, mobile pairing, and push',
entitlementSummary:
'Relay is active on this instance. Remote web access, mobile pairing, push notifications, and longer history are available right now.',
entitlementSummary: getSelfHostedPlanEntitlementSummary('relay'),
entitlementHighlights: getTierEntitlementHighlights('relay', 14),
includedExtras: [],
comparisonSummary:
@ -134,8 +152,7 @@ export const SELF_HOSTED_PLAN_DEFINITIONS: readonly SelfHostedPlanDefinition[] =
subline: 'or $8.99/month',
metricHistoryDays: 90,
billingExtrasSummary: 'Analysis, remediation, and admin controls',
entitlementSummary:
'Pulse Pro is active on this instance. Root-cause analysis, safe remediation workflows, 90-day history, and admin/reporting extras are available right now.',
entitlementSummary: getSelfHostedPlanEntitlementSummary('pro'),
entitlementHighlights: getTierEntitlementHighlights('pro', 90),
includedExtras: getTierIncludedExtras('pro'),
comparisonSummary:

View file

@ -179,10 +179,13 @@ func TestHandleMetricsHistory_TierAwareHistoryRanges(t *testing.T) {
range_ string
want int
}{
{name: "community allows free seven day history", range_: "7d", want: http.StatusOK},
{name: "community blocks relay history", range_: "14d", want: http.StatusPaymentRequired},
{name: "relay allows 14 days", tier: pkglicensing.TierRelay, range_: "14d", want: http.StatusOK},
{name: "relay allows equivalent hour range", tier: pkglicensing.TierRelay, range_: "336h", want: http.StatusOK},
{name: "relay blocks longer day range", tier: pkglicensing.TierRelay, range_: "15d", want: http.StatusPaymentRequired},
{name: "pro allows 90 days", tier: pkglicensing.TierPro, range_: "90d", want: http.StatusOK},
{name: "pro blocks longer than 90 days", tier: pkglicensing.TierPro, range_: "91d", want: http.StatusPaymentRequired},
}
for _, tt := range tests {

View file

@ -127,6 +127,129 @@ func TestDeriveCapabilitiesFromTier_Sorted(t *testing.T) {
}
}
func TestSelfHostedPaidFeatureClaimMatrix(t *testing.T) {
tests := []struct {
name string
tier Tier
wantHistoryDays int
wantCoreMonitoringLimit int
wantIncludedCapabilities []string
wantExcludedCapabilities []string
}{
{
name: "community keeps core monitoring free and does not claim paid extras",
tier: TierFree,
wantHistoryDays: 7,
wantCoreMonitoringLimit: 0,
wantIncludedCapabilities: []string{
FeatureUpdateAlerts,
FeatureSSO,
FeatureAIPatrol,
},
wantExcludedCapabilities: []string{
FeatureRelay,
FeatureMobileApp,
FeaturePushNotifications,
FeatureLongTermMetrics,
FeatureAIAlerts,
FeatureAIAutoFix,
FeatureRBAC,
FeatureAdvancedReporting,
},
},
{
name: "relay sells remote access mobile handoff push and fourteen day history",
tier: TierRelay,
wantHistoryDays: 14,
wantCoreMonitoringLimit: 0,
wantIncludedCapabilities: []string{
FeatureUpdateAlerts,
FeatureSSO,
FeatureAIPatrol,
FeatureRelay,
FeatureMobileApp,
FeaturePushNotifications,
FeatureLongTermMetrics,
},
wantExcludedCapabilities: []string{
FeatureAIAlerts,
FeatureAIAutoFix,
FeatureRBAC,
FeatureAdvancedReporting,
FeatureAgentProfiles,
},
},
{
name: "pro sells operator extras and preserves relay capabilities",
tier: TierPro,
wantHistoryDays: 90,
wantCoreMonitoringLimit: 0,
wantIncludedCapabilities: []string{
FeatureRelay,
FeatureMobileApp,
FeaturePushNotifications,
FeatureLongTermMetrics,
FeatureAIAlerts,
FeatureAIAutoFix,
FeatureRBAC,
FeatureAuditLogging,
FeatureAdvancedReporting,
FeatureAgentProfiles,
},
wantExcludedCapabilities: []string{
FeatureMultiTenant,
FeatureUnlimited,
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
gotHistoryDays, ok := TierHistoryDays[tt.tier]
if !ok {
t.Fatalf("TierHistoryDays missing entry for tier %q", tt.tier)
}
if gotHistoryDays != tt.wantHistoryDays {
t.Fatalf(
"TierHistoryDays[%q] = %d, want %d",
tt.tier,
gotHistoryDays,
tt.wantHistoryDays,
)
}
gotMonitoringLimit, ok := TierMonitoredSystemLimits[tt.tier]
if !ok {
t.Fatalf("TierMonitoredSystemLimits missing entry for tier %q", tt.tier)
}
if gotMonitoringLimit != tt.wantCoreMonitoringLimit {
t.Fatalf(
"TierMonitoredSystemLimits[%q] = %d, want %d",
tt.tier,
gotMonitoringLimit,
tt.wantCoreMonitoringLimit,
)
}
capabilities := make(map[string]struct{})
for _, capability := range TierFeatures[tt.tier] {
capabilities[capability] = struct{}{}
}
for _, capability := range tt.wantIncludedCapabilities {
if _, ok := capabilities[capability]; !ok {
t.Fatalf("tier %q missing paid-feature claim capability %q", tt.tier, capability)
}
}
for _, capability := range tt.wantExcludedCapabilities {
if _, ok := capabilities[capability]; ok {
t.Fatalf("tier %q unexpectedly includes capability %q", tt.tier, capability)
}
}
})
}
}
func TestDeriveEntitlements(t *testing.T) {
caps, limits := DeriveEntitlements(TierPro, []string{"custom"}, 50, 100)

View file

@ -0,0 +1,214 @@
#!/usr/bin/env python3
"""Run the automated proof bundle for self-hosted paid feature claims."""
from __future__ import annotations
import argparse
import json
import subprocess
from dataclasses import asdict, dataclass
from pathlib import Path
@dataclass
class CommandSpec:
name: str
cwd: str
command: list[str]
@dataclass
class CommandResult:
name: str
cwd: str
command: list[str]
ok: bool
exit_code: int
detail: str
def default_pulse_dir() -> Path:
return Path(__file__).resolve().parents[3]
def default_pulse_pro_license_server_dir() -> Path:
return default_pulse_dir().parent / "pulse-pro" / "license-server"
def default_pulse_pro_relay_server_dir() -> Path:
return default_pulse_dir().parent / "pulse-pro" / "relay-server"
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Run the automated proof bundle for v6 paid feature claims."
)
parser.add_argument("--pulse-dir", default=str(default_pulse_dir()))
parser.add_argument("--frontend-dir", help="Optional override for the pulse frontend-modern directory")
parser.add_argument(
"--pulse-pro-license-server-dir",
default=str(default_pulse_pro_license_server_dir()),
)
parser.add_argument(
"--pulse-pro-relay-server-dir",
default=str(default_pulse_pro_relay_server_dir()),
)
parser.add_argument("--json", action="store_true", help="Emit JSON instead of human-readable output")
parser.add_argument("--report-out", help="Optional path to write a markdown report")
parser.add_argument(
"--report-title",
default="Paid Feature Claim Automated Proof",
help="Markdown title used when writing --report-out",
)
return parser.parse_args(argv)
def frontend_dir_from_args(args: argparse.Namespace) -> Path:
if args.frontend_dir:
return Path(args.frontend_dir).resolve()
return Path(args.pulse_dir).resolve() / "frontend-modern"
def build_command_specs(args: argparse.Namespace) -> list[CommandSpec]:
pulse_dir = Path(args.pulse_dir).resolve()
frontend_dir = frontend_dir_from_args(args)
pulse_pro_license_server_dir = Path(args.pulse_pro_license_server_dir).resolve()
pulse_pro_relay_server_dir = Path(args.pulse_pro_relay_server_dir).resolve()
return [
CommandSpec(
name="pulse-licensing-paid-feature-contract",
cwd=str(pulse_dir),
command=[
"go",
"test",
"./pkg/licensing",
"-run",
"^(TestSelfHostedPaidFeatureClaimMatrix|TestTierMonitoredSystemLimits|TestTierHistoryDays|TestTierFeatureInheritance|TestBuildEntitlementPayload_MaxHistoryDays)$",
"-count=1",
],
),
CommandSpec(
name="pulse-api-history-entitlement-enforcement",
cwd=str(pulse_dir),
command=[
"go",
"test",
"./internal/api",
"-run",
"^TestHandleMetricsHistory_TierAwareHistoryRanges$",
"-count=1",
],
),
CommandSpec(
name="pulse-frontend-paid-claim-contract",
cwd=str(frontend_dir),
command=[
"npm",
"test",
"--",
"src/utils/__tests__/selfHostedPlans.test.ts",
"src/utils/__tests__/commercialBillingModel.test.ts",
"src/utils/__tests__/licensePresentation.test.ts",
"src/stores/__tests__/license.test.ts",
"src/components/shared/__tests__/HistoryChart.test.tsx",
"src/components/Settings/__tests__/ProLicensePanel.test.tsx",
"src/components/Settings/__tests__/RelaySettingsPanel.runtime.test.tsx",
],
),
CommandSpec(
name="pulse-pro-public-pricing-and-checkout-contract",
cwd=str(pulse_pro_license_server_dir),
command=[
"go",
"test",
".",
"-run",
"^(TestHandlePublicPricingModelV6|TestCanonicalTierEntitlementContracts_DoNotMonetizeMonitoringVolume|TestV6PublicProPricingPreservesPaidCapabilityFloor|TestHandleCheckoutSessionCreate|TestResolvePublicCheckoutPlan_LockedSelfHostedTiers|TestHandlePulseProDownloadRequiresAuth|TestHandlePulseProDownloadActivationKeyReturnsSignedURLs|TestHandlePulseProDownloadRejectsFreeLicense)$",
"-count=1",
],
),
CommandSpec(
name="pulse-pro-relay-entitlement-runtime",
cwd=str(pulse_pro_relay_server_dir),
command=[
"go",
"test",
".",
"-run",
"^(TestValidateLicense_AllProTiers|TestValidateLicense_FreeTierRejected|TestHostedEntitlementValidator_RejectsMissingRelayCapability|TestBridge_V6GrantRegister|TestBridge_V6GrantRejected_CancelledState|TestBridge_V6Grant_FullDataRouting|TestBridge_PushNotificationHandled)$",
"-count=1",
],
),
]
def summarize_output(stdout: str, stderr: str) -> str:
stdout_lines = [line.strip() for line in stdout.splitlines() if line.strip()]
stderr_lines = [line.strip() for line in stderr.splitlines() if line.strip()]
for line in reversed(stdout_lines):
if line.startswith("ok ") or line.startswith("PASS ") or " passed " in line:
return line[:237] + "..." if len(line) > 240 else line
lines = stdout_lines or stderr_lines
if not lines:
return "pass"
summary = lines[-1]
if len(summary) > 240:
return summary[:237] + "..."
return summary
def run_command(spec: CommandSpec) -> CommandResult:
proc = subprocess.run(spec.command, cwd=spec.cwd, capture_output=True, text=True, check=False)
return CommandResult(
name=spec.name,
cwd=spec.cwd,
command=spec.command,
ok=proc.returncode == 0,
exit_code=proc.returncode,
detail=summarize_output(proc.stdout, proc.stderr),
)
def run_proof(args: argparse.Namespace) -> list[CommandResult]:
return [run_command(spec) for spec in build_command_specs(args)]
def render_markdown_report(title: str, results: list[CommandResult]) -> str:
lines = [
f"# {title}",
"",
"## Scope",
"",
"This proof checks that self-hosted paid claims map to concrete entitlement, API, UI, license-server, and relay-server behavior.",
"",
"## Results",
"",
]
for result in results:
status = "PASS" if result.ok else "FAIL"
lines.append(f"- `{status}` `{result.name}`")
lines.append(f" - cwd: `{result.cwd}`")
lines.append(f" - command: `{' '.join(result.command)}`")
lines.append(f" - detail: {result.detail}")
return "\n".join(lines) + "\n"
def main(argv: list[str] | None = None) -> int:
args = parse_args(argv)
results = run_proof(args)
if args.report_out:
Path(args.report_out).write_text(
render_markdown_report(args.report_title, results),
encoding="utf-8",
)
if args.json:
print(json.dumps([asdict(result) for result in results], indent=2))
else:
for result in results:
status = "PASS" if result.ok else "FAIL"
print(f"{status} {result.name}: {result.detail}")
return 0 if all(result.ok for result in results) else 1
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -0,0 +1,136 @@
from __future__ import annotations
import contextlib
import io
import json
import sys
import tempfile
import unittest
from pathlib import Path
INTERNAL_DIR = Path(__file__).resolve().parent
if str(INTERNAL_DIR) not in sys.path:
sys.path.insert(0, str(INTERNAL_DIR))
import paid_feature_claims_proof as proof
def run_main(argv: list[str]) -> tuple[int, str]:
buffer = io.StringIO()
with contextlib.redirect_stdout(buffer):
exit_code = proof.main(argv)
return exit_code, buffer.getvalue()
class PaidFeatureClaimsProofTest(unittest.TestCase):
def test_default_paths_resolve_from_internal_script_location(self) -> None:
self.assertEqual(proof.default_pulse_dir(), Path(proof.__file__).resolve().parents[3])
self.assertEqual(
proof.default_pulse_pro_license_server_dir(),
proof.default_pulse_dir().parent / "pulse-pro" / "license-server",
)
self.assertEqual(
proof.default_pulse_pro_relay_server_dir(),
proof.default_pulse_dir().parent / "pulse-pro" / "relay-server",
)
def test_build_command_specs_cover_paid_claim_layers(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
pulse_dir = Path(tmp) / "pulse"
pulse_dir.mkdir()
frontend_dir = pulse_dir / "frontend-modern"
frontend_dir.mkdir()
pulse_pro_license_server_dir = Path(tmp) / "pulse-pro" / "license-server"
pulse_pro_license_server_dir.mkdir(parents=True)
pulse_pro_relay_server_dir = Path(tmp) / "pulse-pro" / "relay-server"
pulse_pro_relay_server_dir.mkdir(parents=True)
args = proof.parse_args(
[
"--pulse-dir",
str(pulse_dir),
"--pulse-pro-license-server-dir",
str(pulse_pro_license_server_dir),
"--pulse-pro-relay-server-dir",
str(pulse_pro_relay_server_dir),
]
)
specs = proof.build_command_specs(args)
self.assertEqual(len(specs), 5)
self.assertEqual(specs[0].cwd, str(pulse_dir.resolve()))
self.assertEqual(specs[2].cwd, str(frontend_dir.resolve()))
self.assertEqual(specs[3].cwd, str(pulse_pro_license_server_dir.resolve()))
self.assertEqual(specs[4].cwd, str(pulse_pro_relay_server_dir.resolve()))
names = {spec.name for spec in specs}
self.assertIn("pulse-licensing-paid-feature-contract", names)
self.assertIn("pulse-api-history-entitlement-enforcement", names)
self.assertIn("pulse-frontend-paid-claim-contract", names)
self.assertIn("pulse-pro-public-pricing-and-checkout-contract", names)
self.assertIn("pulse-pro-relay-entitlement-runtime", names)
def test_run_command_success_and_failure(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
ok = proof.run_command(
proof.CommandSpec(
name="ok",
cwd=tmp,
command=["python3", "-c", "print('paid proof ok')"],
)
)
self.assertTrue(ok.ok)
self.assertEqual(ok.detail, "paid proof ok")
failed = proof.run_command(
proof.CommandSpec(
name="fail",
cwd=tmp,
command=["python3", "-c", "import sys; sys.stderr.write('boom\\n'); sys.exit(3)"],
)
)
self.assertFalse(failed.ok)
self.assertEqual(failed.exit_code, 3)
self.assertEqual(failed.detail, "boom")
def test_summarize_output_prefers_success_summary_over_expected_error_logs(self) -> None:
stdout = """
stderr | expected store fallback test
Error: API error
Test Files 7 passed (7)
Tests 116 passed (116)
"""
stderr = """
at runSuite (node_modules/@vitest/runner/dist/chunk-hooks.js:1729:8)
"""
self.assertEqual(proof.summarize_output(stdout, stderr), "Tests 116 passed (116)")
def test_main_json_and_report(self) -> None:
original_run_proof = proof.run_proof
try:
proof.run_proof = lambda _args: [ # type: ignore[assignment]
proof.CommandResult(
name="fake",
cwd="/tmp",
command=["echo", "fake"],
ok=True,
exit_code=0,
detail="pass",
)
]
with tempfile.TemporaryDirectory() as tmp:
report_path = Path(tmp) / "report.md"
exit_code, output = run_main(["--report-out", str(report_path), "--json"])
self.assertEqual(exit_code, 0)
self.assertEqual(json.loads(output)[0]["name"], "fake")
report = report_path.read_text(encoding="utf-8")
self.assertIn("Paid Feature Claim Automated Proof", report)
self.assertIn("self-hosted paid claims", report)
finally:
proof.run_proof = original_run_proof # type: ignore[assignment]
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,17 @@
#!/usr/bin/env python3
from __future__ import annotations
from pathlib import Path
import runpy
def main(argv: list[str] | None = None) -> int:
namespace = runpy.run_path(
str(Path(__file__).resolve().parent / "internal" / "paid_feature_claims_proof.py")
)
return namespace["main"](argv)
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -13,6 +13,7 @@ class ProofEntrypointsTest(unittest.TestCase):
entrypoints = [
"commercial_cancellation_reactivation_proof.py",
"commercial_cancellation_reactivation_rehearsal.py",
"paid_feature_claims_proof.py",
"mobile_relay_auth_approvals_proof.py",
"relay_registration_reconnect_drain_proof.py",
]