fix(licensing): honor activated licenses for dev-grant-excluded features in dev mode

In dev/demo builds HasFeature returned the implicit dev grant verdict
unconditionally, so features excluded from that grant (white_label,
multi_user, unlimited, env-gated multi_tenant) were denied even when an
explicitly activated, signature-verified license carried them - while
/api/license/status reported them active from the same claims. That
inconsistency made branded report rendering impossible to exercise in
dev builds. Excluded features now fall through to real entitlement
evaluation, so an activated license behaves the same in dev as in
release builds; the no-license dev posture is unchanged.
This commit is contained in:
rcourtman 2026-06-10 17:09:00 +01:00
parent 168863adec
commit 3badd0a840
2 changed files with 39 additions and 1 deletions

View file

@ -535,7 +535,13 @@ func (s *Service) IsValid() bool {
func (s *Service) HasFeature(feature string) bool {
// In demo mode or dev mode, grant all Pro features
if isDemoMode() || isDevMode() {
return devModeFeatureEnabled(feature)
if devModeFeatureEnabled(feature) {
return true
}
// Features excluded from the implicit dev grant (white_label,
// multi_user, unlimited, env-gated multi_tenant) fall through to
// real entitlement evaluation, so an explicitly activated license
// behaves the same in dev builds as in release builds.
}
s.mu.Lock() // Need write lock since we may update grace period

View file

@ -567,6 +567,38 @@ func TestServiceStatus_DevModeKeepsCustomerFacingStatusCommunityWithoutLicense(t
}
}
func TestServiceHasFeature_DevModeHonorsActivatedLicenseForExcludedFeatures(t *testing.T) {
t.Setenv("PULSE_DEV", "true")
t.Setenv("PULSE_MOCK_MODE", "true")
t.Setenv("PULSE_LICENSE_DEV_MODE", "true")
t.Setenv("PULSE_MULTI_TENANT_ENABLED", "")
SetPublicKey(nil)
t.Cleanup(func() { SetPublicKey(nil) })
svc := NewService()
if svc.HasFeature(FeatureWhiteLabel) {
t.Fatalf("HasFeature(%q)=true without a license, want false in dev mode", FeatureWhiteLabel)
}
licenseKey, err := GenerateLicenseForTesting("dev-white-label@example.com", TierEnterprise, 24*time.Hour)
if err != nil {
t.Fatalf("GenerateLicenseForTesting: %v", err)
}
if _, err := svc.Activate(licenseKey); err != nil {
t.Fatalf("Activate in dev mode: %v", err)
}
// The implicit dev grant excludes white_label, but an explicitly
// activated license that carries it must behave like a release build.
if !svc.HasFeature(FeatureWhiteLabel) {
t.Fatalf("HasFeature(%q)=false with an activated enterprise license, want true", FeatureWhiteLabel)
}
// Features the dev grant already enables stay enabled.
if !svc.HasFeature(FeatureAdvancedReporting) {
t.Fatalf("HasFeature(%q)=false, want true for dev-mode backend gate bypass", FeatureAdvancedReporting)
}
}
func TestServiceStatus_DevModeMultiTenantBypassDoesNotChangeCustomerFacingStatus(t *testing.T) {
t.Setenv("PULSE_DEV", "true")
t.Setenv("PULSE_MULTI_TENANT_ENABLED", "true")