mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
Tighten MSP workspace tier limits
This commit is contained in:
parent
e450c6a4da
commit
914465e67f
8 changed files with 36 additions and 12 deletions
|
|
@ -7433,6 +7433,21 @@
|
|||
"L4",
|
||||
"L17"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "msp-provider-tier-limit-lock",
|
||||
"summary": "MSP provider workspace limits are intentionally tight: Starter includes up to 5 client workspaces, Growth up to 15, Scale up to 40, and larger providers move to Enterprise/custom terms so support, onboarding, reporting, and deployment load are sized before rollout.",
|
||||
"kind": "pricing",
|
||||
"decided_at": "2026-06-01",
|
||||
"subsystem_ids": [
|
||||
"cloud-paid"
|
||||
],
|
||||
"lane_ids": [
|
||||
"L2",
|
||||
"L3",
|
||||
"L4",
|
||||
"L17"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -239,7 +239,9 @@ or other self-hosted uncapped continuity plans.
|
|||
(`CP_MSP_STARTER_PRICE_ID`, `CP_MSP_GROWTH_PRICE_ID`,
|
||||
`CP_MSP_SCALE_PRICE_ID`) is still validated in `internal/cloudcp/config.go`
|
||||
against the canonical `msp_starter`, `msp_growth`, and `msp_scale` plan
|
||||
versions, and the MSP signup routes stay gated behind the same
|
||||
versions. The canonical provider workspace limits are 5 client workspaces
|
||||
for Starter, 15 for Growth, and 40 for Scale; larger providers belong to
|
||||
Enterprise/custom terms. The MSP signup routes stay gated behind the same
|
||||
`PublicCloudSignupEnabled` flag as the individual cloud signup front door.
|
||||
Checkout metadata produced by the MSP front door must mark
|
||||
`account_kind=msp`, `signup_source=public_msp_signup`, and
|
||||
|
|
|
|||
|
|
@ -147,8 +147,8 @@ var publicMSPSignupPageTemplate = template.Must(template.New("public-msp-signup-
|
|||
<form method="POST" action="{{.FormAction}}">
|
||||
<input type="hidden" name="tier" value="{{.Tier}}">
|
||||
<div class="tier-group">
|
||||
<div class="tier-option"><strong>Starter</strong> — up to 10 client workspaces, $149/mo</div>
|
||||
<div class="tier-option"><strong>Growth / Scale</strong> — request access for 25+ client workspaces, custom onboarding, or assisted rollout.</div>
|
||||
<div class="tier-option"><strong>Starter</strong> — up to 5 client workspaces, $149/mo</div>
|
||||
<div class="tier-option"><strong>Growth / Scale</strong> — request access for 15+ client workspaces, custom onboarding, or assisted rollout.</div>
|
||||
</div>
|
||||
|
||||
<label for="email">Work Email</label>
|
||||
|
|
|
|||
|
|
@ -146,6 +146,12 @@ func TestMSPSignupPageKeepsStarterSelfServeWhenMultipleTiersConfigured(t *testin
|
|||
if !strings.Contains(body, "Growth / Scale") || !strings.Contains(body, "request access") {
|
||||
t.Fatal("expected assisted Growth/Scale copy")
|
||||
}
|
||||
if !strings.Contains(body, "up to 5 client workspaces") {
|
||||
t.Fatal("expected canonical MSP Starter workspace limit copy")
|
||||
}
|
||||
if !strings.Contains(body, "15+ client workspaces") {
|
||||
t.Fatal("expected assisted MSP workspace threshold copy")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMSPSignupPostValidRedirectsToStripeWithMSPMetadata(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -186,9 +186,9 @@ var CloudPlanWorkspaceLimits = map[string]int{
|
|||
"cloud_founding": 1,
|
||||
|
||||
// MSP tiers — client caps from pricing spec
|
||||
"msp_starter": 10, // MSP Starter: up to 10 clients
|
||||
"msp_growth": 25, // MSP Growth: up to 25 clients
|
||||
"msp_scale": 50, // MSP Scale: up to 50 clients
|
||||
"msp_starter": 5, // MSP Starter: up to 5 clients
|
||||
"msp_growth": 15, // MSP Growth: up to 15 clients
|
||||
"msp_scale": 40, // MSP Scale: up to 40 clients
|
||||
}
|
||||
|
||||
// UnknownPlanDefaultWorkspaceLimit is the safe-default workspace limit applied
|
||||
|
|
|
|||
|
|
@ -584,10 +584,10 @@ func TestWorkspaceLimitForPlan_KnownPlans(t *testing.T) {
|
|||
{"cloud_power", 1},
|
||||
{"cloud_max", 1},
|
||||
{"cloud_founding", 1},
|
||||
{"msp_starter", 10},
|
||||
{"msp_hosted_v1", 10},
|
||||
{"msp_growth", 25},
|
||||
{"msp_scale", 50},
|
||||
{"msp_starter", 5},
|
||||
{"msp_hosted_v1", 5},
|
||||
{"msp_growth", 15},
|
||||
{"msp_scale", 40},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
|
|||
|
|
@ -43,8 +43,8 @@ func TestMSPPlanAliasCanonicalizationContract(t *testing.T) {
|
|||
if got := CanonicalizePlanVersion("msp_hosted_v1"); got != "msp_starter" {
|
||||
t.Fatalf("CanonicalizePlanVersion(msp_hosted_v1) = %q, want %q", got, "msp_starter")
|
||||
}
|
||||
if limit, known := WorkspaceLimitForPlan("msp_hosted_v1"); !known || limit != 10 {
|
||||
t.Fatalf("WorkspaceLimitForPlan(msp_hosted_v1) = (%d, %v), want (10, true)", limit, known)
|
||||
if limit, known := WorkspaceLimitForPlan("msp_hosted_v1"); !known || limit != 5 {
|
||||
t.Fatalf("WorkspaceLimitForPlan(msp_hosted_v1) = (%d, %v), want (5, true)", limit, known)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3800,6 +3800,7 @@ class SubsystemLookupTest(unittest.TestCase):
|
|||
"cloud-msp-stripe-prices",
|
||||
"legacy-grandfathering-eligibility-cutoff",
|
||||
"msp-buying-motion-lock",
|
||||
"msp-provider-tier-limit-lock",
|
||||
"self-hosted-plans-surface-entitlement-first",
|
||||
"self-hosted-paid-surface-classification",
|
||||
"self-hosted-core-monitoring-free",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue