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
This commit is contained in:
pulse-triage[bot] 2026-09-24 04:52:30 +01:00
parent 2752b1040e
commit f3518d9b9b
3 changed files with 28 additions and 22 deletions

View file

@ -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

View file

@ -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

View file

@ -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) {