From f3518d9b9bb1fcfb4eccbbc9f7dd5f91c2f2d169 Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Thu, 24 Sep 2026 04:52:30 +0100 Subject: [PATCH] fix(provider-msp): qualify installer purchase guidance Keep first-run evaluation and summary guidance conditional on actual Plan availability and confirmed active limits. Remove the ordinary manual-key purchase prompt while retaining the separate custom-license binding path, and update the installability contract and focused assertions in the same commit. Change-source: pulse-maintainer --- deploy/provider-msp/setup.sh | 25 +++++++++---------- .../subsystems/deployment-installability.md | 7 ++++++ .../installtests/provider_msp_deploy_test.go | 18 ++++++------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/deploy/provider-msp/setup.sh b/deploy/provider-msp/setup.sh index 9630fd014..ef91f0ad4 100755 --- a/deploy/provider-msp/setup.sh +++ b/deploy/provider-msp/setup.sh @@ -438,7 +438,8 @@ ensure_eval_license() { local expires expires="$(printf '%s' "${response}" | jq -r '.expires_at // empty' 2>/dev/null || true)" log "evaluation license installed: 2 client workspaces${expires:+, expires ${expires}}" - log " when you need a third client, buy a plan from Plan in your provider portal" + log " Plan in your provider portal shows whether a paid upgrade is available" + log " keep within two clients until Plan confirms a higher active limit" } ensure_generated_secrets() { @@ -681,14 +682,12 @@ validate_env_file() { # of the first screen, and an isolation guarantee is the one claim a provider # cannot evaluate from a screenshot. # - # Unlicensed runs on msp_eval (2 client workspaces). Set the licence file - # when you buy; the paid caps come from the licence, never from here. + # Unlicensed runs on msp_eval (2 client workspaces). Paid caps come from a + # valid licence, never from local configuration. local license_file license_file="$(env_value CP_PROVIDER_MSP_LICENSE_FILE "${env_path}")" if [[ -z "${license_file}" ]]; then log "no CP_PROVIDER_MSP_LICENSE_FILE set: evaluation mode, 2 client workspaces" - log "to buy, request a licence bound to this lease signing public key:" - log " $(derive_lease_signing_public_key)" return 0 fi if [[ "${license_file}" != /* ]]; then @@ -696,11 +695,10 @@ validate_env_file() { fi if [[ ! -f "${license_file}" ]]; then die "CP_PROVIDER_MSP_LICENSE_FILE is set but does not exist: ${license_file} -Leave it blank to run in evaluation mode (2 client workspaces), or request your -provider MSP license with this lease signing public key -(./setup.sh --print-lease-signing-public-key): - $(derive_lease_signing_public_key) -The license must bind this key or the control plane will refuse to start." +Leave it blank to run in evaluation mode (2 client workspaces), or place an +already issued license at that path. For a custom license, print the platform's +lease signing public key with ./setup.sh --print-lease-signing-public-key; +the license must bind that key or the control plane will refuse to start." fi } @@ -805,9 +803,10 @@ Prove the platform before the first real client: Portal (after bootstrap): https://${domain}/portal -Plan: the evaluation covers two clients. When you need a third, open Plan in -the portal and buy one; checkout is by Stripe and the new limit applies within -seconds. Change plan or cancel renewal from Manage billing in the same place. +Plan: the evaluation covers two clients. Open Plan in the portal to see whether +a paid upgrade is available. Keep within two clients until Plan confirms a +higher active limit. A paid plan exposes Manage billing for changes or +cancellation. Day 2: portal sessions last 7 days. Re-run the bootstrap command above any time to print a fresh owner sign-in link, or use diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index 2f86bf0b4..3d86edf2b 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -894,6 +894,13 @@ artifact-selection behaviour. sign-in page agree. `provider-msp portal-link` is part of the packaged day-2 surface and mints links only for existing account members or pending invitees. + Setup's evaluation and closing guidance must point to Plan for actual paid + upgrade availability, not promise live Stripe checkout or a time-bound cap + increase. Operators must remain within the two-client evaluation limit + until Plan confirms a higher active limit. The ordinary empty-license path + must not present a lease signing key as a manual purchase step; the explicit + `--print-lease-signing-public-key` path remains available for separately + issued custom licenses, which must bind that key. Provider-hosted MSP installability must also pass provider-default report branding through the packaged tenant environment rather than requiring report-specific operator provisioning. The deployable control-plane config diff --git a/scripts/installtests/provider_msp_deploy_test.go b/scripts/installtests/provider_msp_deploy_test.go index d05e90ba5..bd8c3ef4e 100644 --- a/scripts/installtests/provider_msp_deploy_test.go +++ b/scripts/installtests/provider_msp_deploy_test.go @@ -403,11 +403,12 @@ func TestProviderMSPSetupScriptSupportsUnlicensedEvaluation(t *testing.T) { "PULSE_PROVIDER_MSP_EVAL_EMAIL", "PULSE_PROVIDER_MSP_SIGNUP_SOURCE", `setup_stage: "images_ready"`, - "buy a plan from Plan in your provider portal", + "Plan in your provider portal shows whether a paid upgrade is available", + "keep within two clients until Plan confirms a higher active limit", ) - // Buying happens in the portal. The old hint sent providers to a request - // form that waited on a human reply with a checkout link. - assertNotContainsAny(t, script, "request an upgrade", "eval_license_id=") + // A purchase route is shown only when available; setup must not claim that + // checkout is live or that the paid limit is already active. + assertNotContainsAny(t, script, "request an upgrade", "eval_license_id=", "to buy, request a licence bound", "checkout is by Stripe and the new limit applies within") if strings.LastIndex(script, "pull_provider_images\n") > strings.LastIndex(script, "ensure_eval_license\n") { t.Fatal("evaluation must be issued only after pinned provider images are reachable") } @@ -620,13 +621,12 @@ func TestProviderMSPSetupLeavesPlatformRunning(t *testing.T) { ) assertNotContainsAny(t, script, "Pulse Provider MSP setup prepared.") - // The closing summary says where buying happens and no longer asks the - // provider to carry a lease signing key to anyone: the portal purchase - // binds it without a copy step. + // The closing summary points at Plan without claiming checkout is live, + // and no longer asks the provider to carry a lease signing key to anyone. summary := script[strings.Index(script, "Pulse Provider MSP is running."):] summary = summary[:strings.Index(summary, "EOF")] - assertContainsAll(t, summary, "open Plan in\nthe portal", "Manage billing") - assertNotContainsAny(t, summary, "must bind this key", "derive_lease_signing_public_key") + assertContainsAll(t, summary, "Open Plan in the portal to see whether", "Keep within two clients until Plan confirms a", "Manage billing") + assertNotContainsAny(t, summary, "must bind this key", "derive_lease_signing_public_key", "checkout is by Stripe", "applies within\nseconds") sequence := " ensure_eval_license\n validate_compose_config\n start_provider_services\n run_install_proof_if_requested\n print_summary\n" if !strings.Contains(script, sequence) {