From 2b1d82d96552d5210d6feee0efc538be3689bb0e Mon Sep 17 00:00:00 2001 From: rcourtman Date: Tue, 28 Apr 2026 17:39:09 +0100 Subject: [PATCH] Retire self-hosted trial posture prompts --- .../v6/internal/subsystems/agent-lifecycle.md | 4 +- .../v6/internal/subsystems/api-contracts.md | 11 +- .../v6/internal/subsystems/cloud-paid.md | 15 +- .../internal/subsystems/storage-recovery.md | 10 +- .../components/Settings/ProLicensePanel.tsx | 1 - .../Settings/ProLicensePlanSection.tsx | 17 -- .../__tests__/ProLicensePanel.test.tsx | 16 +- .../Settings/useProLicensePanelState.ts | 7 - .../frontendResourceTypeBoundaries.test.ts | 4 +- .../__tests__/licensePresentation.test.ts | 7 - .../utils/__tests__/pricingHandoff.test.ts | 6 + .../src/utils/licensePresentation.ts | 12 +- frontend-modern/src/utils/pricingHandoff.ts | 1 - internal/api/contract_test.go | 2 +- internal/api/entitlement_handlers_test.go | 22 +-- internal/api/license_handlers_test.go | 4 +- internal/api/licensing_bridge.go | 11 -- .../licensing_handlers_auto_migrate_test.go | 4 +- internal/api/subscription_entitlements.go | 32 +--- pkg/licensing/entitlement_payload.go | 12 +- pkg/licensing/entitlement_payload_test.go | 6 + pkg/licensing/trial_start.go | 59 ------- pkg/licensing/trial_start_test.go | 163 ------------------ .../tests/12-v5-commercial-migration.spec.ts | 9 +- ...58-self-hosted-trial-rate-limit-ui.spec.ts | 8 +- ...f-hosted-manual-activation-success.spec.ts | 4 +- 26 files changed, 84 insertions(+), 363 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md index a779a7c16..b04474884 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -1629,7 +1629,9 @@ fleet surfaces must not expose direct trial CTAs or depend on `POST /api/license/trial/start`, and the normal router must fail that path as `404` without mutating entitlements. The retired `/auth/trial-activate` self-hosted callback must also stay absent from lifecycle retry and backoff -behavior. +behavior. Lifecycle-adjacent setup and install surfaces must also treat +`trial_eligible` and `trial_eligibility_reason` as retired compatibility +fields, not as prompt state or setup transport state. That same shared `internal/api/` dependency also assumes session-carried OIDC refresh tokens stay fail-closed at rest: `session_store.go` may only persist or recover those tokens through encrypted-at-rest session payloads, and any diff --git a/docs/release-control/v6/internal/subsystems/api-contracts.md b/docs/release-control/v6/internal/subsystems/api-contracts.md index 8cb42ec4e..3bb75b4a3 100644 --- a/docs/release-control/v6/internal/subsystems/api-contracts.md +++ b/docs/release-control/v6/internal/subsystems/api-contracts.md @@ -676,7 +676,11 @@ the canonical monitored-system blocked payload. `/auth/trial-activate` self-hosted return path must also stay absent from the ordinary router and settings UI; signed hosted entitlement leases may refresh cached hosted/cloud entitlement state, but they must not create a - local Pro trial acquisition callback. + local Pro trial acquisition callback. Entitlement payloads may retain + `trial_eligible` and `trial_eligibility_reason` as compatibility fields, + but ordinary self-hosted responses leave eligibility false and the reason + empty; active trial state is represented by `subscription_state`, + `trial_expires_at`, and `trial_days_remaining` only. 29. Keep `/api/security/dev/reset-first-run` transport-backed and genuinely unauthenticated: when the dev reset route clears first-run auth it must also clear any env-backed auth state that feeds `/api/security/status`, so @@ -869,7 +873,7 @@ for upgrade links, trial CTAs, plan upsells, and paid-only navigation rather than as a billing entitlement change. That same contract split also makes the licensing boundary explicit: `/api/license/runtime-capabilities` is the public runtime feature contract, -`/api/license/commercial-posture` is the non-billing upgrade/trial posture +`/api/license/commercial-posture` is the non-billing upgrade posture contract for real customer workspaces, and `/api/license/entitlements` remains billing-only. New callers must extend one of those owned shapes instead of reviving a combined entitlement payload for mixed runtime, @@ -2947,7 +2951,8 @@ out of the router inventory and must not return the old hosted-signup or trial-rate-limit acquisition payloads from an ordinary self-hosted runtime; `frontend-modern/src/api/license.ts`, demo mode, and feature gates must not expose a start-trial client method or in-app CTA in the same slice as any -handler change. +handler change. Commercial migration state must travel through +`commercial_migration`, not through trial-denial reason strings. That same shared commercial API boundary also owns hosted self-serve failure transport semantics. Hosted trial request and verification failures may render owned HTML pages, but they must preserve the originating Pulse instance and diff --git a/docs/release-control/v6/internal/subsystems/cloud-paid.md b/docs/release-control/v6/internal/subsystems/cloud-paid.md index 1e2a62cd9..455fcc50e 100644 --- a/docs/release-control/v6/internal/subsystems/cloud-paid.md +++ b/docs/release-control/v6/internal/subsystems/cloud-paid.md @@ -241,7 +241,11 @@ Community limit enforcement. 13. Add or change hosted billing-admin presentation through `frontend-modern/src/components/Settings/BillingAdminPanel.tsx`, `frontend-modern/src/components/Settings/BillingAdminOrganizationsTable.tsx`, and `frontend-modern/src/components/Settings/useBillingAdminPanelState.ts` 14. Add or change shared commercial plan/usage presentation through `frontend-modern/src/components/Settings/CommercialBillingSections.tsx` and `frontend-modern/src/utils/commercialBillingModel.ts` 15. Add or change organization billing and usage presentation through `frontend-modern/src/components/Settings/OrganizationBillingPanel.tsx`, `frontend-modern/src/components/Settings/OrganizationBillingLoadingState.tsx`, and `frontend-modern/src/components/Settings/useOrganizationBillingPanelState.ts` -16. Add or change self-hosted Pro plan, trial, recovery, and entitlement actions through `frontend-modern/src/components/Settings/ProLicensePanel.tsx`, `frontend-modern/src/components/Settings/ProLicensePlanSection.tsx`, `frontend-modern/src/components/Settings/SelfHostedCommercialRecoverySection.tsx`, and `frontend-modern/src/components/Settings/useProLicensePanelState.ts` +16. Add or change self-hosted Pro plan, recovery, and entitlement actions through `frontend-modern/src/components/Settings/ProLicensePanel.tsx`, `frontend-modern/src/components/Settings/ProLicensePlanSection.tsx`, `frontend-modern/src/components/Settings/SelfHostedCommercialRecoverySection.tsx`, and `frontend-modern/src/components/Settings/useProLicensePanelState.ts` + Ordinary self-hosted trial acquisition is retired: these plan surfaces may + show active historical `subscription_state=trial` entitlement state, but + they must not turn `trial_eligible`, `trial_eligibility_reason`, or an + expired trial marker into a default Pro CTA or banner. 17. Add or change monitored-system ledger, disclosure, or admission-preview presentation through `frontend-modern/src/components/Settings/MonitoredSystemLedgerPanel.tsx`, `frontend-modern/src/components/Settings/MonitoredSystemAdmissionPreview.tsx`, `frontend-modern/src/components/Commercial/MonitoredSystemDefinitionDisclosure.tsx`, and `frontend-modern/src/utils/monitoredSystemPresentation.ts` 18. Add or change paid relay settings and pairing presentation through `frontend-modern/src/components/Settings/RelaySettingsPanel.tsx`, `frontend-modern/src/components/Settings/RelayPairingSection.tsx`, and `frontend-modern/src/components/Settings/useRelaySettingsPanelState.ts`. The Dashboard shell must not carry a Relay onboarding card or equivalent blanket upsell — relay discovery stays inside its owning settings surface. Public demo and other read-only presentation policy states must suppress @@ -504,11 +508,14 @@ Community limit enforcement. `frontend-modern/src/utils/pricingHandoff.ts` now routes pro-feature paywall keys (`ai_alerts`, `ai_autofix`, `relay`, `rbac`, `audit_logging`, -`advanced_sso`, `agent_profiles`, `long_term_metrics`, `trial_expired`) to the +`advanced_sso`, `agent_profiles`, `long_term_metrics`) to the self-hosted billing plan page instead of the Pulse Account purchase-start handoff. The purchase-start handoff requires a `PublicURL` and fails on local instances; routing these keys to the in-product billing plan keeps upgrades accessible from self-hosted environments. +Retired trial-acquisition intents such as `trial_expired` must not be owned +pricing handoff destinations; no ordinary self-hosted runtime path should emit +them as an upgrade reason or plan-page CTA. The monitored-system app-shell warning CTA now follows that same commercial boundary by rendering only in hosted mode. Ordinary self-hosted installs must not see finite monitored-system pressure in the global app shell; when hosted @@ -567,7 +574,7 @@ endpoints are fully hidden (`404`) and which remain available only as a non-commercial public contract. `/api/license/runtime-capabilities` is the canonical public exception for feature truth and history retention. `/api/license/commercial-posture` is the canonical non-billing commercial -contract for upgrade/trial posture in real customer workspaces, while +contract for upgrade posture in real customer workspaces, while `/api/license/entitlements` remains billing-only. In public demo mode both commercial routes, plus `/auth/license-purchase-start`, must stay hidden and public browsers must not see licensed identity, plan labels, upgrade reasons, @@ -623,7 +630,7 @@ owner for the first `/api/license/commercial-posture` read, while other non-billing feature hooks may consume the resolved posture store but must not each trigger their own mount-time posture fetch. Billing-owned surfaces such as `frontend-modern/src/components/Settings/useProLicensePanelState.ts` -may still force refresh through the same shared store when plan, trial, or +may still force refresh through the same shared store when plan, activation, or recovery actions mutate commercial truth. Non-billing browser journeys must also stay off `/api/license/entitlements` entirely. Dashboard, infrastructure, alerts, diff --git a/docs/release-control/v6/internal/subsystems/storage-recovery.md b/docs/release-control/v6/internal/subsystems/storage-recovery.md index 04a891428..7bb817dbb 100644 --- a/docs/release-control/v6/internal/subsystems/storage-recovery.md +++ b/docs/release-control/v6/internal/subsystems/storage-recovery.md @@ -756,9 +756,9 @@ storage and recovery may inherit demo-safe `/api/license/runtime-capabilities` reads for capability and history-retention truth, but `/api/license/commercial-posture`, `/api/license/entitlements`, and `/auth/license-purchase-start` stay hidden and those surfaces must not expect -licensed identity, upgrade prompts, trial urgency, checkout handoff state, or -observed usage counts to remain present once the public-demo contract is -applied. +licensed identity, upgrade prompts, trial urgency or eligibility reasons, +checkout handoff state, or observed usage counts to remain present once the +public-demo contract is applied. That same runtime-safe exception now also redacts monitored-system capacity posture. Storage/recovery surfaces may keep demo-safe capability and retention truth from `/api/license/runtime-capabilities`, but they must not expect @@ -1887,7 +1887,9 @@ storage- or recovery-adjacent flows must not invoke or advertise the retired `POST /api/license/trial/start` route, and the normal self-hosted router must return `404` for that path without mutating entitlements. The retired `/auth/trial-activate` self-hosted callback must also stay absent from -storage/recovery-local retry or backoff behavior. +storage/recovery-local retry or backoff behavior. Retired +`trial_eligible`/`trial_eligibility_reason` payload fields are compatibility +only and must not become storage/recovery prompt, identity, or restore state. That same shared `internal/api/` dependency now also assumes adjacent commercial helper surfaces speak in monitored-system terms: recovery- or storage-adjacent API wiring may consume the canonical monitored-system ledger diff --git a/frontend-modern/src/components/Settings/ProLicensePanel.tsx b/frontend-modern/src/components/Settings/ProLicensePanel.tsx index a887ab212..1c41d235f 100644 --- a/frontend-modern/src/components/Settings/ProLicensePanel.tsx +++ b/frontend-modern/src/components/Settings/ProLicensePanel.tsx @@ -114,7 +114,6 @@ const ProLicensePanelContent: Component = () => { purchaseActivationAction={state.purchaseActivationAction()} onPurchaseActivationActionClick={state.handlePurchaseActivationActionClick} purchaseActivationNotice={state.purchaseActivationNotice()} - trialEnded={state.trialEnded()} /> void; - trialEnded: boolean; } const formatDate = (value?: string | null) => { @@ -99,8 +96,6 @@ const formatDate = (value?: string | null) => { }; export const ProLicensePlanSection: Component = (props) => { - const trialEndedNotice = props.trialEnded ? getTrialEndedProLicenseNotice() : null; - return ( <> @@ -321,18 +316,6 @@ export const ProLicensePlanSection: Component = (pro )} - -
-

{trialEndedNotice?.title}

-

{trialEndedNotice?.body}

- - {trialEndedNotice?.actionLabel} - -
-

Could not load license status

diff --git a/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx b/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx index 33cf2fb59..396b2e4ae 100644 --- a/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/ProLicensePanel.test.tsx @@ -118,7 +118,7 @@ describe('ProLicensePanel', () => { subscription_state: 'expired', upgrade_reasons: [], tier: 'free', - trial_eligible: true, + trial_eligible: false, }; loadRuntimeLicenseStatusMock.mockReset(); @@ -252,7 +252,7 @@ describe('ProLicensePanel', () => { ); }); - it('hides start trial action and shows trial-ended banner when trial was already used', async () => { + it('does not surface a trial-ended banner for retired self-hosted trial state', async () => { mockEntitlements = { capabilities: [], limits: [], @@ -268,7 +268,7 @@ describe('ProLicensePanel', () => { expect( screen.queryByRole('button', { name: /start 14-day pro trial/i }), ).not.toBeInTheDocument(); - expect(screen.getByText('Your Pro trial has ended')).toBeInTheDocument(); + expect(screen.queryByText('Your Pro trial has ended')).not.toBeInTheDocument(); }); it('renders trial countdown from entitlements payload', async () => { @@ -1014,7 +1014,6 @@ describe('ProLicensePanel', () => { upgrade_reasons: [], tier: 'free', trial_eligible: false, - trial_eligibility_reason: 'commercial_migration_pending', commercial_migration: { source: 'v5_license', state: 'pending', @@ -1040,7 +1039,6 @@ describe('ProLicensePanel', () => { upgrade_reasons: [], tier: 'free', trial_eligible: false, - trial_eligibility_reason: 'commercial_migration_pending', commercial_migration: { source: 'v5_license', state: 'pending', @@ -1064,7 +1062,6 @@ describe('ProLicensePanel', () => { upgrade_reasons: [], tier: 'free', trial_eligible: false, - trial_eligibility_reason: 'commercial_migration_failed', commercial_migration: { source: 'v5_license', state: 'failed', @@ -1117,7 +1114,7 @@ describe('ProLicensePanel', () => { expect(proLicensePanelStateSource).toContain("'A license or activation key is required'"); expect(proLicensePlanSectionSource).toContain('getLicenseStatusLoadingState'); expect(proLicensePlanSectionSource).toContain('getNoActiveProLicenseState'); - expect(proLicensePlanSectionSource).toContain('getTrialEndedProLicenseNotice'); + expect(proLicensePlanSectionSource).not.toContain('getTrialEndedProLicenseNotice'); expect(proLicensePlanSectionSource).toContain('currentPlanSummary.title'); expect(proLicensePlanSectionSource).toContain('currentPlanSummary.supplementalBadges'); expect(proLicensePlanSectionSource).toContain('props.activationSuccessSummary'); @@ -1132,9 +1129,8 @@ describe('ProLicensePanel', () => { expect(proLicensePlanSectionSource).not.toContain('MonitoredSystemDefinitionDisclosure'); expect(proLicensePlanSectionSource).not.toContain('trialStartTitle'); expect(proLicensePlanSectionSource).not.toContain('trialStartIdleActionLabel'); - expect(proLicensePlanSectionSource).toContain( - 'const trialEndedNotice = props.trialEnded ? getTrialEndedProLicenseNotice() : null;', - ); + expect(proLicensePlanSectionSource).not.toContain('trialEndedNotice'); + expect(proLicensePlanSectionSource).not.toContain('props.trialEnded'); expect(proLicensePlanSectionSource).toContain('planSelectionPrompt'); expect(proLicensePlanSectionSource).not.toContain( "resolveSelfHostedPurchaseStartDestination('self_hosted_plan')", diff --git a/frontend-modern/src/components/Settings/useProLicensePanelState.ts b/frontend-modern/src/components/Settings/useProLicensePanelState.ts index eb133ca30..514cd4d80 100644 --- a/frontend-modern/src/components/Settings/useProLicensePanelState.ts +++ b/frontend-modern/src/components/Settings/useProLicensePanelState.ts @@ -427,12 +427,6 @@ export function useProLicensePanelState() { : undefined; }); - const trialEnded = createMemo( - () => - subscriptionState() === 'expired' && - entitlements()?.trial_eligibility_reason === 'already_used', - ); - const purchaseActivationNotice = createMemo(() => { if (purchaseActivationResult().trim().toLowerCase() === 'activated') { return null; @@ -668,6 +662,5 @@ export function useProLicensePanelState() { showCountingRulesByDefault, showRecoveryByDefault, statusPresentation, - trialEnded, }; } diff --git a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts index b5359d45f..cf9854fd3 100644 --- a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts +++ b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts @@ -2505,7 +2505,7 @@ describe('frontend resource type boundaries', () => { expect(proLicensePanelStateSource).toContain('getCommercialMigrationNotice'); expect(proLicensePlanSectionSource).toContain('getLicenseStatusLoadingState'); expect(proLicensePlanSectionSource).toContain('getNoActiveProLicenseState'); - expect(proLicensePlanSectionSource).toContain('getTrialEndedProLicenseNotice'); + expect(proLicensePlanSectionSource).not.toContain('getTrialEndedProLicenseNotice'); expect(proLicensePlanSectionSource).not.toContain('getInactiveProUpsellNotice'); expect(proLicensePanelSource).not.toContain('Loading license status...'); expect(proLicensePanelSource).not.toContain('No Pro license is active.'); @@ -2519,7 +2519,7 @@ describe('frontend resource type boundaries', () => { ); expect(licensePresentationSource).toContain('export const getLicenseStatusLoadingState'); expect(licensePresentationSource).toContain('export const getNoActiveProLicenseState'); - expect(licensePresentationSource).toContain('export const getTrialEndedProLicenseNotice'); + expect(licensePresentationSource).not.toContain('export const getTrialEndedProLicenseNotice'); expect(licensePresentationSource).toContain('export const getInactiveProUpsellNotice'); expect(licensePresentationSource).toContain( 'export const getOrganizationBillingLicenseStatusLabel', diff --git a/frontend-modern/src/utils/__tests__/licensePresentation.test.ts b/frontend-modern/src/utils/__tests__/licensePresentation.test.ts index 3c076543d..c259371d7 100644 --- a/frontend-modern/src/utils/__tests__/licensePresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/licensePresentation.test.ts @@ -23,7 +23,6 @@ import { getOrganizationBillingLicenseStatusLabel, getInactiveProUpsellNotice, getPurchaseActivationNotice, - getTrialEndedProLicenseNotice, isDisplayableLicenseFeature, isGrandfatheredRecurringV5PlanVersion, isUncappedGrandfatheredPlanVersion, @@ -50,12 +49,6 @@ describe('licensePresentation', () => { expect(getNoActiveProLicenseState()).toEqual({ text: 'No Pro license is active.', }); - expect(getTrialEndedProLicenseNotice()).toEqual({ - tone: 'border-red-200 dark:border-red-900 bg-red-50 dark:bg-red-900 text-red-900 dark:text-red-100', - title: 'Your Pro trial has ended', - body: 'Choose a paid plan to keep root-cause analysis, safe remediation workflows, and 90-day history on this instance.', - actionLabel: 'View Pro plans', - }); expect(getInactiveProUpsellNotice()).toEqual({ tone: 'border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900 text-amber-800 dark:text-amber-200', title: 'Pro options available', diff --git a/frontend-modern/src/utils/__tests__/pricingHandoff.test.ts b/frontend-modern/src/utils/__tests__/pricingHandoff.test.ts index 3ee2650e7..67286f40d 100644 --- a/frontend-modern/src/utils/__tests__/pricingHandoff.test.ts +++ b/frontend-modern/src/utils/__tests__/pricingHandoff.test.ts @@ -46,6 +46,12 @@ describe('pricingHandoff', () => { expect(getUpgradeFallbackDestination('rbac')).toBe(SELF_HOSTED_PRO_BILLING_PLAN_HREF); }); + it('does not keep retired trial-expired as an owned in-product billing intent', () => { + expect(getPricingRouteDestination('?feature=trial_expired')).toBe( + getSelfHostedPurchaseStartUrl('trial_expired', new URLSearchParams('feature=trial_expired')), + ); + }); + it('routes unknown feature upgrades to Pulse Account purchase start', () => { expect(getUpgradeFallbackDestination('unknown_pro_feature')).toBe( getSelfHostedPurchaseStartUrl('unknown_pro_feature'), diff --git a/frontend-modern/src/utils/licensePresentation.ts b/frontend-modern/src/utils/licensePresentation.ts index de25fdc4d..199ec58dc 100644 --- a/frontend-modern/src/utils/licensePresentation.ts +++ b/frontend-modern/src/utils/licensePresentation.ts @@ -630,7 +630,6 @@ export const getCommercialMigrationNotice = ( if (!migration?.state) return null; const actionText = getCommercialMigrationActionText(migration.recommended_action); - const blockedText = 'A new Pro trial stays blocked until this is resolved.'; if (migration.state === 'pending') { let body = @@ -651,7 +650,7 @@ export const getCommercialMigrationNotice = ( return { tone: 'border-amber-200 dark:border-amber-900 bg-amber-50 dark:bg-amber-900 text-amber-900 dark:text-amber-100', title: 'v5 license migration pending', - body: `${body} ${actionText} ${blockedText}`, + body: `${body} ${actionText}`, }; } @@ -680,7 +679,7 @@ export const getCommercialMigrationNotice = ( return { tone: 'border-red-200 dark:border-red-900 bg-red-50 dark:bg-red-900 text-red-900 dark:text-red-100', title: 'v5 license migration needs attention', - body: `${body} ${actionText} ${blockedText}`, + body: `${body} ${actionText}`, }; }; @@ -780,13 +779,6 @@ export const getNoActiveProLicenseState = (): LicenseLoadingStateCopy => ({ text: 'No Pro license is active.', }); -export const getTrialEndedProLicenseNotice = (): LicenseActionNotice => ({ - tone: 'border-red-200 dark:border-red-900 bg-red-50 dark:bg-red-900 text-red-900 dark:text-red-100', - title: 'Your Pro trial has ended', - body: 'Choose a paid plan to keep root-cause analysis, safe remediation workflows, and 90-day history on this instance.', - actionLabel: 'View Pro plans', -}); - export const getInactiveProUpsellNotice = (): LicenseActionNotice => ({ tone: 'border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900 text-amber-800 dark:text-amber-200', title: 'Pro options available', diff --git a/frontend-modern/src/utils/pricingHandoff.ts b/frontend-modern/src/utils/pricingHandoff.ts index 11407900c..64365c539 100644 --- a/frontend-modern/src/utils/pricingHandoff.ts +++ b/frontend-modern/src/utils/pricingHandoff.ts @@ -63,7 +63,6 @@ const IN_PRODUCT_PRICING_DESTINATIONS: Record = { advanced_sso: SELF_HOSTED_PRO_BILLING_PLAN_HREF, agent_profiles: SELF_HOSTED_PRO_BILLING_PLAN_HREF, long_term_metrics: SELF_HOSTED_PRO_BILLING_PLAN_HREF, - trial_expired: SELF_HOSTED_PRO_BILLING_PLAN_HREF, }; const INTERNAL_HREF_BASE = 'https://pulse.invalid'; diff --git a/internal/api/contract_test.go b/internal/api/contract_test.go index a28dd119c..0ce9c8f89 100644 --- a/internal/api/contract_test.go +++ b/internal/api/contract_test.go @@ -5327,7 +5327,7 @@ func TestContract_SelfHostedCommunityEntitlementsJSONSnapshot(t *testing.T) { "valid":false, "is_lifetime":false, "days_remaining":0, - "trial_eligible":true, + "trial_eligible":false, "max_history_days":7, "overflow_days_remaining":14, "legacy_connections":{"proxmox_nodes":0,"docker_hosts":0,"kubernetes_clusters":0}, diff --git a/internal/api/entitlement_handlers_test.go b/internal/api/entitlement_handlers_test.go index 983c47806..6dbcc0b6a 100644 --- a/internal/api/entitlement_handlers_test.go +++ b/internal/api/entitlement_handlers_test.go @@ -548,7 +548,7 @@ func TestHandleRuntimeCapabilities_HostedCommunityEvaluatorStateStripsLegacyComm } } -func TestEntitlementHandler_TrialEligibility_FreshOrgAllowed(t *testing.T) { +func TestEntitlementHandler_SelfHostedTrialEligibilityRetiredForFreshOrg(t *testing.T) { baseDir := t.TempDir() mtp := config.NewMultiTenantPersistence(baseDir) h := NewLicenseHandlers(mtp, false) @@ -569,8 +569,8 @@ func TestEntitlementHandler_TrialEligibility_FreshOrgAllowed(t *testing.T) { if payload.SubscriptionState != string(license.SubStateActive) { t.Fatalf("subscription_state=%q, want %q", payload.SubscriptionState, license.SubStateActive) } - if !payload.TrialEligible { - t.Fatalf("trial_eligible=%v, want true", payload.TrialEligible) + if payload.TrialEligible { + t.Fatalf("trial_eligible=%v, want false", payload.TrialEligible) } if payload.TrialEligibilityReason != "" { t.Fatalf("trial_eligibility_reason=%q, want empty", payload.TrialEligibilityReason) @@ -622,8 +622,8 @@ func TestEntitlementHandler_OverflowOnlyBillingStateReportsActiveCommunity(t *te if payload.SubscriptionState != string(license.SubStateActive) { t.Fatalf("subscription_state=%q, want %q", payload.SubscriptionState, license.SubStateActive) } - if !payload.TrialEligible { - t.Fatalf("trial_eligible=%v, want true", payload.TrialEligible) + if payload.TrialEligible { + t.Fatalf("trial_eligible=%v, want false", payload.TrialEligible) } if payload.MonitoredSystemCapacity == nil || payload.MonitoredSystemCapacity.Limit != 0 || payload.MonitoredSystemCapacity.BlocksNewSystems { t.Fatalf("expected uncapped monitored_system_capacity, got %+v", payload.MonitoredSystemCapacity) @@ -709,7 +709,7 @@ func TestEntitlementHandler_DevModeIncludesMultiTenantWhenRuntimeEnabled(t *test } } -func TestEntitlementHandler_TrialEligibility_AlreadyUsedDenied(t *testing.T) { +func TestEntitlementHandler_ExpiredTrialStateDoesNotExposeStartReason(t *testing.T) { baseDir := t.TempDir() mtp := config.NewMultiTenantPersistence(baseDir) orgID := "default" @@ -746,12 +746,12 @@ func TestEntitlementHandler_TrialEligibility_AlreadyUsedDenied(t *testing.T) { if payload.TrialEligible { t.Fatalf("trial_eligible=%v, want false", payload.TrialEligible) } - if payload.TrialEligibilityReason != "already_used" { - t.Fatalf("trial_eligibility_reason=%q, want %q", payload.TrialEligibilityReason, "already_used") + if payload.TrialEligibilityReason != "" { + t.Fatalf("trial_eligibility_reason=%q, want empty", payload.TrialEligibilityReason) } } -func TestEntitlementHandler_CommercialMigrationBlocksTrialEligibility(t *testing.T) { +func TestEntitlementHandler_CommercialMigrationDoesNotExposeTrialStartReason(t *testing.T) { baseDir := t.TempDir() mtp := config.NewMultiTenantPersistence(baseDir) orgID := "default" @@ -795,8 +795,8 @@ func TestEntitlementHandler_CommercialMigrationBlocksTrialEligibility(t *testing if payload.TrialEligible { t.Fatalf("trial_eligible=%v, want false", payload.TrialEligible) } - if payload.TrialEligibilityReason != "commercial_migration_pending" { - t.Fatalf("trial_eligibility_reason=%q, want %q", payload.TrialEligibilityReason, "commercial_migration_pending") + if payload.TrialEligibilityReason != "" { + t.Fatalf("trial_eligibility_reason=%q, want empty", payload.TrialEligibilityReason) } } diff --git a/internal/api/license_handlers_test.go b/internal/api/license_handlers_test.go index b541db2f6..fc66371c9 100644 --- a/internal/api/license_handlers_test.go +++ b/internal/api/license_handlers_test.go @@ -1556,8 +1556,8 @@ func TestHandleClearLicense_ClearsActiveTrialButPreservesTrialUsedMarker(t *test if payload.TrialEligible { t.Fatalf("payload.trial_eligible=%v, want false", payload.TrialEligible) } - if payload.TrialEligibilityReason != "already_used" { - t.Fatalf("payload.trial_eligibility_reason=%q, want %q", payload.TrialEligibilityReason, "already_used") + if payload.TrialEligibilityReason != "" { + t.Fatalf("payload.trial_eligibility_reason=%q, want empty", payload.TrialEligibilityReason) } } diff --git a/internal/api/licensing_bridge.go b/internal/api/licensing_bridge.go index 541ef4f1d..ae34bd463 100644 --- a/internal/api/licensing_bridge.go +++ b/internal/api/licensing_bridge.go @@ -35,8 +35,6 @@ type licenseFeaturesResponse = pkglicensing.LicenseFeaturesResponse type activateLicenseRequestModel = pkglicensing.ActivateLicenseRequest type activateLicenseResponseModel = pkglicensing.ActivateLicenseResponse type activationStateModel = pkglicensing.ActivationState -type trialStartDecisionModel = pkglicensing.TrialStartDecision -type trialStartDenialReasonModel = pkglicensing.TrialStartDenialReason type billingStoreModel = pkglicensing.BillingStore type billingState = pkglicensing.BillingState type subscriptionState = pkglicensing.SubscriptionState @@ -92,7 +90,6 @@ const ( activationKeyPrefixValue = pkglicensing.ActivationKeyPrefix // Conversion event type constants for backend-emitted events. - conversionEventTrialStarted = pkglicensing.EventTrialStarted conversionEventLicenseActivated = pkglicensing.EventLicenseActivated conversionEventLicenseActivationFailed = pkglicensing.EventLicenseActivationFailed conversionEventCheckoutStarted = pkglicensing.EventCheckoutStarted @@ -152,14 +149,6 @@ func buildTrialBillingStateWithPlanFromLicensing(now time.Time, capabilities []s return pkglicensing.BuildTrialBillingStateWithPlan(now, capabilities, planVersion, duration) } -func evaluateTrialStartEligibilityFromLicensing(hasActiveLicense bool, existing *billingState) trialStartDecisionModel { - return pkglicensing.EvaluateTrialStartEligibility(hasActiveLicense, existing) -} - -func trialStartErrorFromLicensing(reason trialStartDenialReasonModel) (code, message string, includeOrgID bool) { - return pkglicensing.TrialStartError(reason) -} - func newLicensePersistenceFromLicensing(configDir string) (*licensePersistence, error) { return pkglicensing.NewPersistence(configDir) } diff --git a/internal/api/licensing_handlers_auto_migrate_test.go b/internal/api/licensing_handlers_auto_migrate_test.go index d69bf9fcd..75d74c4bc 100644 --- a/internal/api/licensing_handlers_auto_migrate_test.go +++ b/internal/api/licensing_handlers_auto_migrate_test.go @@ -315,8 +315,8 @@ func TestGetTenantComponents_PersistsCommercialMigrationState_WhenAutoExchangeFa if payload.TrialEligible { t.Fatalf("trial_eligible=%v, want false", payload.TrialEligible) } - if payload.TrialEligibilityReason != "commercial_migration_pending" { - t.Fatalf("trial_eligibility_reason=%q, want %q", payload.TrialEligibilityReason, "commercial_migration_pending") + if payload.TrialEligibilityReason != "" { + t.Fatalf("trial_eligibility_reason=%q, want empty", payload.TrialEligibilityReason) } handlers.StopAllBackgroundLoops() diff --git a/internal/api/subscription_entitlements.go b/internal/api/subscription_entitlements.go index 587c9d34e..9c7b4de5b 100644 --- a/internal/api/subscription_entitlements.go +++ b/internal/api/subscription_entitlements.go @@ -19,7 +19,7 @@ type EntitlementPayload = entitlementPayloadModel type RuntimeCapabilitiesPayload = runtimeCapabilitiesPayloadModel // CommercialPosturePayload is the canonical non-billing commercial response -// for upgrade/trial posture and monitored-system migration guidance. +// for upgrade posture and monitored-system migration guidance. type CommercialPosturePayload = commercialPosturePayloadModel // LimitStatus represents a quantitative limit with current usage state. @@ -72,13 +72,12 @@ func (h *LicenseHandlers) buildCommercialEntitlementPayload( if existing != nil { payload.CommercialMigration = cloneCommercialMigrationStatusFromLicensing(existing.CommercialMigration) } - payload.TrialEligible, payload.TrialEligibilityReason = h.trialStartEligibility(ctx, svc, existing) payload.HostedMode = h != nil && h.hostedMode return payload, nil } // HandleEntitlements returns the normalized entitlement payload for the current tenant. -// This is the commercial entitlement endpoint for billing, trial, and upgrade presentation. +// This is the commercial entitlement endpoint for billing, activation, and upgrade presentation. func (h *LicenseHandlers) HandleEntitlements(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeErrorResponse(w, http.StatusMethodNotAllowed, "method_not_allowed", "Method not allowed", nil) @@ -417,30 +416,3 @@ func (h *LicenseHandlers) billingStateForContext(ctx context.Context) *billingSt } return existing } - -func (h *LicenseHandlers) trialStartEligibility(ctx context.Context, svc *licenseService, existing *billingState) (eligible bool, reason string) { - if h == nil || h.mtPersistence == nil { - return false, "unavailable" - } - - orgID := GetOrgID(ctx) - if orgID == "" { - orgID = "default" - } - - if existing == nil { - billingStore := config.NewFileBillingStore(h.mtPersistence.BaseDataDir()) - loaded, err := billingStore.GetBillingState(orgID) - if err != nil { - return false, "unavailable" - } - existing = loaded - } - - hasActiveLicense := svc != nil && svc.Current() != nil && svc.IsValid() - decision := evaluateTrialStartEligibilityFromLicensing(hasActiveLicense, existing) - if decision.Allowed { - return true, "" - } - return false, string(decision.Reason) -} diff --git a/pkg/licensing/entitlement_payload.go b/pkg/licensing/entitlement_payload.go index af2dce207..fc7868618 100644 --- a/pkg/licensing/entitlement_payload.go +++ b/pkg/licensing/entitlement_payload.go @@ -57,10 +57,12 @@ type EntitlementPayload struct { // GracePeriodEnd is the RFC3339 grace period end timestamp when available. GracePeriodEnd *string `json:"grace_period_end,omitempty"` - // TrialEligible indicates whether this org can start a self-serve trial right now. + // TrialEligible is retained for compatibility with retired self-hosted trial clients. + // New self-hosted payloads leave it false; active trial state is exposed through + // SubscriptionState, TrialExpiresAt, and TrialDaysRemaining. TrialEligible bool `json:"trial_eligible"` - // TrialEligibilityReason is set when trial start is denied. + // TrialEligibilityReason is retained for compatibility with retired self-hosted trial clients. TrialEligibilityReason string `json:"trial_eligibility_reason,omitempty"` // MaxHistoryDays is the maximum metrics history retention in days for the current tier. @@ -95,7 +97,7 @@ type EntitlementPayload struct { } // CommercialPosturePayload is the canonical non-billing commercial contract -// for upgrade messaging, trial posture, and monitored-system migration copy. +// for upgrade messaging and monitored-system migration copy. // It intentionally excludes billing identity, grandfathered plan terms, and // other full-entitlement details that belong only to billing surfaces. type CommercialPosturePayload struct { @@ -114,10 +116,10 @@ type CommercialPosturePayload struct { // TrialDaysRemaining is the number of whole or partial days remaining in trial. TrialDaysRemaining *int `json:"trial_days_remaining,omitempty"` - // TrialEligible indicates whether this org can start a self-serve trial right now. + // TrialEligible is retained for compatibility with retired self-hosted trial clients. TrialEligible bool `json:"trial_eligible"` - // TrialEligibilityReason is set when trial start is denied. + // TrialEligibilityReason is retained for compatibility with retired self-hosted trial clients. TrialEligibilityReason string `json:"trial_eligibility_reason,omitempty"` // OverflowDaysRemaining is set when the onboarding overflow (+1 host) is active. diff --git a/pkg/licensing/entitlement_payload_test.go b/pkg/licensing/entitlement_payload_test.go index 5882e11af..aa7453959 100644 --- a/pkg/licensing/entitlement_payload_test.go +++ b/pkg/licensing/entitlement_payload_test.go @@ -86,6 +86,12 @@ func TestBuildEntitlementPayload_FreeTier(t *testing.T) { if len(payload.UpgradeReasons) == 0 { t.Fatalf("expected upgrade reasons for free tier") } + if payload.TrialEligible { + t.Fatal("expected retired self-hosted trial eligibility to stay false") + } + if payload.TrialEligibilityReason != "" { + t.Fatalf("TrialEligibilityReason=%q, want empty", payload.TrialEligibilityReason) + } for _, reason := range payload.UpgradeReasons { if reason.ActionURL == "" { t.Fatalf("expected action_url for reason %q", reason.Key) diff --git a/pkg/licensing/trial_start.go b/pkg/licensing/trial_start.go index 98985fa54..fcdda27d7 100644 --- a/pkg/licensing/trial_start.go +++ b/pkg/licensing/trial_start.go @@ -7,65 +7,6 @@ import ( const DefaultTrialDuration = 14 * 24 * time.Hour -type TrialStartDenialReason string - -const ( - TrialStartAllowed TrialStartDenialReason = "" - TrialStartDeniedLicense TrialStartDenialReason = "license_active" - TrialStartDeniedAlreadyUsed TrialStartDenialReason = "already_used" - TrialStartDeniedSubscription TrialStartDenialReason = "subscription_active" - TrialStartDeniedMigrationPending TrialStartDenialReason = "commercial_migration_pending" - TrialStartDeniedMigrationFailed TrialStartDenialReason = "commercial_migration_failed" -) - -type TrialStartDecision struct { - Allowed bool - Reason TrialStartDenialReason -} - -func EvaluateTrialStartEligibility(hasActiveLicense bool, existing *BillingState) TrialStartDecision { - if hasActiveLicense { - return TrialStartDecision{Allowed: false, Reason: TrialStartDeniedLicense} - } - if existing == nil { - return TrialStartDecision{Allowed: true, Reason: TrialStartAllowed} - } - if existing.CommercialMigration != nil { - switch existing.CommercialMigration.State { - case CommercialMigrationStatePending: - return TrialStartDecision{Allowed: false, Reason: TrialStartDeniedMigrationPending} - case CommercialMigrationStateFailed: - return TrialStartDecision{Allowed: false, Reason: TrialStartDeniedMigrationFailed} - } - } - if existing.TrialStartedAt != nil { - return TrialStartDecision{Allowed: false, Reason: TrialStartDeniedAlreadyUsed} - } - switch existing.SubscriptionState { - case SubStateActive, SubStateGrace, SubStateSuspended: - return TrialStartDecision{Allowed: false, Reason: TrialStartDeniedSubscription} - default: - return TrialStartDecision{Allowed: true, Reason: TrialStartAllowed} - } -} - -func TrialStartError(reason TrialStartDenialReason) (code, message string, includeOrgID bool) { - switch reason { - case TrialStartDeniedLicense: - return "trial_not_available", "Trial cannot be started while a license is active", false - case TrialStartDeniedAlreadyUsed: - return "trial_already_used", "Trial has already been used for this organization", true - case TrialStartDeniedSubscription: - return "trial_not_available", "Trial cannot be started while a subscription is active", true - case TrialStartDeniedMigrationPending: - return "trial_not_available", "Trial cannot be started while a paid v5 license migration is pending", false - case TrialStartDeniedMigrationFailed: - return "trial_not_available", "Trial cannot be started until the paid v5 license migration is resolved", false - default: - return "", "", false - } -} - func TrialWindow(now time.Time, duration time.Duration) (startedAt, endsAt int64) { if duration <= 0 { duration = DefaultTrialDuration diff --git a/pkg/licensing/trial_start_test.go b/pkg/licensing/trial_start_test.go index 841b30b21..6af099c0a 100644 --- a/pkg/licensing/trial_start_test.go +++ b/pkg/licensing/trial_start_test.go @@ -5,169 +5,6 @@ import ( "time" ) -func TestEvaluateTrialStartEligibility(t *testing.T) { - now := time.Unix(1700000000, 0) - started := now.Add(-24 * time.Hour).Unix() - - tests := []struct { - name string - hasActiveLicense bool - existing *BillingState - wantAllowed bool - wantReason TrialStartDenialReason - }{ - { - name: "denied when license active", - hasActiveLicense: true, - existing: nil, - wantAllowed: false, - wantReason: TrialStartDeniedLicense, - }, - { - name: "allowed with no state", - hasActiveLicense: false, - existing: nil, - wantAllowed: true, - wantReason: TrialStartAllowed, - }, - { - name: "denied when trial already used", - hasActiveLicense: false, - existing: &BillingState{TrialStartedAt: &started}, - wantAllowed: false, - wantReason: TrialStartDeniedAlreadyUsed, - }, - { - name: "denied on active subscription", - hasActiveLicense: false, - existing: &BillingState{SubscriptionState: SubStateActive}, - wantAllowed: false, - wantReason: TrialStartDeniedSubscription, - }, - { - name: "denied on grace subscription", - hasActiveLicense: false, - existing: &BillingState{SubscriptionState: SubStateGrace}, - wantAllowed: false, - wantReason: TrialStartDeniedSubscription, - }, - { - name: "denied on suspended subscription", - hasActiveLicense: false, - existing: &BillingState{SubscriptionState: SubStateSuspended}, - wantAllowed: false, - wantReason: TrialStartDeniedSubscription, - }, - { - name: "denied when v5 migration is pending", - hasActiveLicense: false, - existing: &BillingState{CommercialMigration: &CommercialMigrationStatus{ - Source: CommercialMigrationSourceV5License, - State: CommercialMigrationStatePending, - }}, - wantAllowed: false, - wantReason: TrialStartDeniedMigrationPending, - }, - { - name: "denied when v5 migration failed", - hasActiveLicense: false, - existing: &BillingState{CommercialMigration: &CommercialMigrationStatus{ - Source: CommercialMigrationSourceV5License, - State: CommercialMigrationStateFailed, - }}, - wantAllowed: false, - wantReason: TrialStartDeniedMigrationFailed, - }, - { - name: "allowed on expired subscription", - hasActiveLicense: false, - existing: &BillingState{SubscriptionState: SubStateExpired}, - wantAllowed: true, - wantReason: TrialStartAllowed, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := EvaluateTrialStartEligibility(tt.hasActiveLicense, tt.existing) - if got.Allowed != tt.wantAllowed { - t.Fatalf("allowed=%t, want %t", got.Allowed, tt.wantAllowed) - } - if got.Reason != tt.wantReason { - t.Fatalf("reason=%q, want %q", got.Reason, tt.wantReason) - } - }) - } -} - -func TestTrialStartError(t *testing.T) { - tests := []struct { - name string - reason TrialStartDenialReason - wantCode string - wantMessage string - wantIncludeOrgID bool - }{ - { - name: "license active", - reason: TrialStartDeniedLicense, - wantCode: "trial_not_available", - wantMessage: "Trial cannot be started while a license is active", - wantIncludeOrgID: false, - }, - { - name: "already used", - reason: TrialStartDeniedAlreadyUsed, - wantCode: "trial_already_used", - wantMessage: "Trial has already been used for this organization", - wantIncludeOrgID: true, - }, - { - name: "subscription active", - reason: TrialStartDeniedSubscription, - wantCode: "trial_not_available", - wantMessage: "Trial cannot be started while a subscription is active", - wantIncludeOrgID: true, - }, - { - name: "migration pending", - reason: TrialStartDeniedMigrationPending, - wantCode: "trial_not_available", - wantMessage: "Trial cannot be started while a paid v5 license migration is pending", - wantIncludeOrgID: false, - }, - { - name: "migration failed", - reason: TrialStartDeniedMigrationFailed, - wantCode: "trial_not_available", - wantMessage: "Trial cannot be started until the paid v5 license migration is resolved", - wantIncludeOrgID: false, - }, - { - name: "unknown", - reason: TrialStartAllowed, - wantCode: "", - wantMessage: "", - wantIncludeOrgID: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - code, message, includeOrgID := TrialStartError(tt.reason) - if code != tt.wantCode { - t.Fatalf("code=%q, want %q", code, tt.wantCode) - } - if message != tt.wantMessage { - t.Fatalf("message=%q, want %q", message, tt.wantMessage) - } - if includeOrgID != tt.wantIncludeOrgID { - t.Fatalf("includeOrgID=%t, want %t", includeOrgID, tt.wantIncludeOrgID) - } - }) - } -} - func TestTrialWindow(t *testing.T) { now := time.Unix(1700000000, 0) diff --git a/tests/integration/tests/12-v5-commercial-migration.spec.ts b/tests/integration/tests/12-v5-commercial-migration.spec.ts index fb383d3db..32f239055 100644 --- a/tests/integration/tests/12-v5-commercial-migration.spec.ts +++ b/tests/integration/tests/12-v5-commercial-migration.spec.ts @@ -21,7 +21,6 @@ type EntitlementPayload = { type ExpectedCopy = { title: RegExp; bodyFragments: RegExp[]; - trialReason: string; }; const expectedState = process.env.PULSE_E2E_EXPECT_COMMERCIAL_MIGRATION_STATE || ''; @@ -98,8 +97,7 @@ function expectedCopyFor(state: string, reason: string, action: string): Expecte return { title: /v5 license migration pending/i, - bodyFragments: [reasonFragment, actionFragment, /new pro trial stays blocked/i], - trialReason: 'commercial_migration_pending', + bodyFragments: [reasonFragment, actionFragment], }; } @@ -118,8 +116,7 @@ function expectedCopyFor(state: string, reason: string, action: string): Expecte return { title: /v5 license migration needs attention/i, - bodyFragments: [reasonFragment, actionFragment, /new pro trial stays blocked/i], - trialReason: 'commercial_migration_failed', + bodyFragments: [reasonFragment, actionFragment], }; } @@ -173,7 +170,7 @@ test.describe.serial('v5 commercial migration notice', () => { expect(entitlements.commercial_migration?.reason).toBe(expectedReason); expect(entitlements.commercial_migration?.recommended_action).toBe(expectedAction); expect(entitlements.trial_eligible).toBe(false); - expect(entitlements.trial_eligibility_reason).toBe(expectedCopy.trialReason); + expect(entitlements.trial_eligibility_reason || '').toBe(''); }); test('Pro settings renders the expected migration state', async ({ page }) => { diff --git a/tests/integration/tests/58-self-hosted-trial-rate-limit-ui.spec.ts b/tests/integration/tests/58-self-hosted-trial-rate-limit-ui.spec.ts index 62316dd6c..7cfef2f81 100644 --- a/tests/integration/tests/58-self-hosted-trial-rate-limit-ui.spec.ts +++ b/tests/integration/tests/58-self-hosted-trial-rate-limit-ui.spec.ts @@ -1,13 +1,13 @@ import { expect, test } from '@playwright/test'; import { ensureAuthenticated } from './helpers'; -const FREE_TRIAL_ELIGIBLE_ENTITLEMENTS = { +const FREE_SELF_HOSTED_ENTITLEMENTS = { capabilities: [], limits: [], subscription_state: 'active', upgrade_reasons: [], tier: 'free', - trial_eligible: true, + trial_eligible: false, }; const SELF_HOSTED_SECURITY_STATUS = { @@ -72,7 +72,7 @@ test.describe.serial('Self-hosted paid prompt visibility', () => { await route.fulfill({ status: 200, contentType: 'application/json', - body: JSON.stringify(FREE_TRIAL_ELIGIBLE_ENTITLEMENTS), + body: JSON.stringify(FREE_SELF_HOSTED_ENTITLEMENTS), }); }); @@ -80,7 +80,7 @@ test.describe.serial('Self-hosted paid prompt visibility', () => { await route.fulfill({ status: 200, contentType: 'application/json', - body: JSON.stringify(FREE_TRIAL_ELIGIBLE_ENTITLEMENTS), + body: JSON.stringify(FREE_SELF_HOSTED_ENTITLEMENTS), }); }); diff --git a/tests/integration/tests/70-self-hosted-manual-activation-success.spec.ts b/tests/integration/tests/70-self-hosted-manual-activation-success.spec.ts index cb5398c37..d03550bec 100644 --- a/tests/integration/tests/70-self-hosted-manual-activation-success.spec.ts +++ b/tests/integration/tests/70-self-hosted-manual-activation-success.spec.ts @@ -8,7 +8,7 @@ const INACTIVE_ENTITLEMENTS = { subscription_state: 'expired', upgrade_reasons: [], tier: 'free', - trial_eligible: true, + trial_eligible: false, hosted_mode: false, valid: false, }; @@ -56,7 +56,7 @@ const INACTIVE_COMMERCIAL_POSTURE = { subscription_state: 'expired', upgrade_reasons: [], tier: 'free', - trial_eligible: true, + trial_eligible: false, monitored_system_capacity: null, monitored_system_continuity: null, has_migration_gap: false,