Skip to content

feat(entitlements): trim to MaxSSOUsers + MaxChecksPerMinute, enforce both - #49

Merged
fclairamb merged 1 commit into
mainfrom
fix/entitlements-shrink-to-sso-and-rate
May 10, 2026
Merged

fclairamb merged 1 commit into
mainfrom
fix/entitlements-shrink-to-sso-and-rate

Conversation

@fclairamb

Copy link
Copy Markdown
Owner

Summary

The v1 entitlements model had eleven limits and six feature flags but no enforcement — CanCreate and FeatureEnabled were stubs that always allowed. This PR replaces that surface with the two limits that actually matter today, and wires enforcement for both.

  • MaxSSOUsers (default 30 on self-hosted, nil on SaaS) — checked at every OAuth ensureMembership and inside autoJoinMatchingOrgs across all six provider services (google/github/gitlab/microsoft/discord/slack). The 31st new SSO membership for an org is refused with ENTITLEMENT_EXCEEDED. The very first member of a fresh org always passes (count 0 < cap).
  • MaxChecksPerMinute (default 6 on SaaS, nil on self-hosted) — gated at the worker dispatch via an in-memory token bucket per org. Drained buckets reschedule the job for next period without writing a result, and bump solidping_checks_rate_limited_total{organization}.

A new SP_DEPLOYMENT_MODE env (saas | self-hosted, default self-hosted) drives default selection at startup. Admins can still override either cap per-org via the existing PUT/PATCH /api/v1/orgs/$org/entitlements.

Cleanup

The entitlements payload schema shrinks. Dropped from the model: MaxChecks, MaxMembers, MaxStatusPages, MaxCheckGroups, MaxMaintenanceWindows, MaxConnections, MaxWorkers, MaxAPITokens, RetentionDaysRaw, RetentionDaysAggregated, MinCheckPeriodSeconds, AllowedCheckTypes, and the six features (SSO, MCP, CustomBranding, PrioritySupport, MultiRegion, AdvancedAlerts).

encoding/json silently drops unknown keys, so historical audit-log rows written under v1 still parse cleanly into the smaller struct. PATCH bodies, however, reject deleted keys loudly via DisallowUnknownFields — clients sending legacy maxChecks etc. get a clear VALIDATION_ERROR.

The four Count*ForOrg DB methods that fed the deleted "usage" panel are removed; a single CountSSOMembersForOrg joins organization_members with user_providers for the SSO cap.

Caveats

  • Token bucket is process-local. Multi-replica check-worker deployments will need a shared store (Redis / PG advisory lock). A TODO marks the spot in entitlements/service.go.
  • No DB migration. The payload JSONB column is unchanged; old keys silently dropped on read.

Test plan

  • make lint-back clean
  • go test ./... — 1886 tests pass across 122 packages
  • Boot self-hosted: GET /api/v1/orgs/default/entitlements returns {maxSsoUsers: 30} + source: "default", no maxChecksPerMinute
  • Boot SP_DEPLOYMENT_MODE=saas: same call returns {maxChecksPerMinute: 6}, no maxSsoUsers
  • PATCH with a deleted key ({"maxChecks": 100}) returns VALIDATION_ERROR
  • SSO cap blocks the 31st distinct user from joining an org under self-hosted defaults
  • Rate cap defers excess executions and increments solidping_checks_rate_limited_total

🤖 Generated with Claude Code

… both

Pre-1.0 cleanup. The v1 entitlements model had eleven limits and six
feature flags but no enforcement: CanCreate / FeatureEnabled were
stubs that always allowed. Replace that surface with the two limits
that actually matter today and wire enforcement for both:

- MaxSSOUsers (default 30 on self-hosted, nil on SaaS) — checked at
  every OAuth ensureMembership and autoJoinMatchingOrgs across the
  six provider services. The 31st new SSO membership is refused with
  ENTITLEMENT_EXCEEDED; the very first member of a fresh org always
  passes (count=0 < cap).
- MaxChecksPerMinute (default 6 on SaaS, nil on self-hosted) — gated
  at the worker dispatch via an in-memory token bucket per org.
  Drained buckets reschedule the job for next period without writing
  a result and bump solidping_checks_rate_limited_total{organization}.

A new SP_DEPLOYMENT_MODE env (saas | self-hosted, default
self-hosted) drives the default selection at startup; admins can
still override either cap per-org via the existing PUT/PATCH
/api/v1/orgs/$org/entitlements path. The entitlements payload schema
shrinks accordingly: MaxChecks, MaxMembers, MaxStatusPages,
MaxCheckGroups, MaxMaintenanceWindows, MaxConnections, MaxWorkers,
MaxAPITokens, RetentionDaysRaw, RetentionDaysAggregated,
MinCheckPeriodSeconds, AllowedCheckTypes, and the six Feature flags
are dropped. encoding/json silently ignores unknown keys, so audit
rows written under v1 still parse; PATCH bodies reject deleted keys
loudly via DisallowUnknownFields so clients get a clear
VALIDATION_ERROR.

The token bucket lives in process memory — multi-replica deployments
will need a shared store (TODO comment marks the spot).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@fclairamb
fclairamb enabled auto-merge (squash) May 10, 2026 15:51
@fclairamb
fclairamb merged commit d25cc5f into main May 10, 2026
11 checks passed
@fclairamb
fclairamb deleted the fix/entitlements-shrink-to-sso-and-rate branch May 10, 2026 15:57
@fclairamb fclairamb mentioned this pull request May 10, 2026
fclairamb added a commit that referenced this pull request Jun 23, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.1.0](v0.0.0...v0.1.0)
(2026-06-22)


### Features

* add check type registry, sample configs, notification senders, and
observability integrations
([eea049e](eea049e))
* batch — Warning/Degraded statuses, SSL/Docker check upgrades, MCP
OAuth 2.1, config-as-code
([#85](#85))
([936d830](936d830))
* batch spec implementations (dashboard UI, jobs, notifications, SaaS,
perf) ([#78](#78))
([fe003e4](fe003e4))
* bulk feature integration (discovery, web push, badges, webhooks, and
more) ([#73](#73))
([a976856](a976856))
* custom user-agent, refresh checks, vite plugin switch, CI updates
([#1](#1))
([cc00858](cc00858))
* documentation site served at /docs (co-located in web/docs)
([#88](#88))
([27d9ccd](27d9ccd))
* email check frontend (spec 03)
([#30](#30))
([aebd106](aebd106))
* email inbox foundation via JMAP (spec 01)
([#24](#24))
([0f3b0c1](0f3b0c1))
* email passive checks (spec 02)
([#27](#27))
([ba5536f](ba5536f))
* **entitlements:** trim to MaxSSOUsers + MaxChecksPerMinute + status
page history fixes
([#50](#50))
([a007de0](a007de0))
* **entitlements:** trim to MaxSSOUsers + MaxChecksPerMinute, enforce
both ([#49](#49))
([d25cc5f](d25cc5f))
* group incident correlation (spec 04 backend v1)
([#31](#31))
([1883986](1883986))
* **observability:** instrument hot path + add bench harness
([#60](#60))
([c6604cc](c6604cc))
* **oncall:** searchable timezone dropdown + sidebar logo polish
([#46](#46))
([4396898](4396898))
* per-IP HTTP rate limiting and concurrency limiting
([#59](#59))
([f9cc273](f9cc273))
* soften HTTP rate limits with bounded queues + request timeout
([#63](#63))
([025501b](025501b))
* SolidPing — distributed uptime monitoring platform
([eef4383](eef4383))
* **statuspages:** drag-and-drop resource reordering + dash conventions
([#41](#41))
([cf910d4](cf910d4))


### Bug Fixes

* **deps:** update dependency i18next to v26
([#26](#26))
([d5d1b7e](d5d1b7e))
* **deps:** update dependency lucide-react to v1
([#28](#28))
([cdc8e8c](cdc8e8c))
* **deps:** update dependency react-i18next to v17
([#29](#29))
([13135d8](13135d8))
* **deps:** update dependency recharts to v3.8.1
([#14](#14))
([5d95a3b](5d95a3b))
* **deps:** update go dependencies (non-major)
([#16](#16))
([2450f13](2450f13))
* **deps:** update go dependencies (non-major)
([#19](#19))
([f842310](f842310))
* **deps:** update go dependencies (non-major)
([#37](#37))
([c020eca](c020eca))
* **deps:** update go dependencies (non-major)
([#44](#44))
([ff0bcd0](ff0bcd0))
* **deps:** update go dependencies (non-major)
([#52](#52))
([2e3bc28](2e3bc28))
* **deps:** update go dependencies (non-major)
([#69](#69))
([a65ed4c](a65ed4c))
* **deps:** update go dependencies (non-major)
([#77](#77))
([a0920ff](a0920ff))
* **deps:** update go dependencies (non-major) to v1.4.2
([#86](#86))
([1eb7504](1eb7504))
* **deps:** update module github.com/aws/aws-sdk-go-v2/config to
v1.32.18 ([#72](#72))
([6406537](6406537))
* **deps:** update module github.com/aws/aws-sdk-go-v2/service/s3 to
v1.101.0 ([#39](#39))
([f9c9563](f9c9563))
* **deps:** update module github.com/go-webauthn/webauthn to v0.17.3
([#45](#45))
([593931a](593931a))
* **deps:** update module github.com/go-webauthn/webauthn to v0.17.4
([#70](#70))
([fca1c97](fca1c97))
* **deps:** update module github.com/ibm/sarama to v1.48.1
([#47](#47))
([93ccf3a](93ccf3a))
* **deps:** update module github.com/ibm/sarama to v1.48.2
([#54](#54))
([e84d868](e84d868))
* **deps:** update module github.com/ibm/sarama to v1.49.0
([#64](#64))
([fd61bd7](fd61bd7))
* **deps:** update module github.com/oapi-codegen/runtime to v1.4.1
([#65](#65))
([a3a3e9f](a3a3e9f))
* **deps:** update module github.com/prometheus/common to v0.67.5
([#61](#61))
([095dd6f](095dd6f))
* **deps:** update module github.com/slack-go/slack to v0.24.0
([#75](#75))
([6e86d5b](6e86d5b))
* **deps:** update module golang.org/x/sys to v0.44.0
([#42](#42))
([a245730](a245730))
* **deps:** update module golang.org/x/sys to v0.45.0
([#67](#67))
([49c3d90](49c3d90))
* **deps:** update module golang.org/x/term to v0.43.0
([#43](#43))
([4a934f1](4a934f1))
* **deps:** update module google.golang.org/grpc to v1.81.0
([#36](#36))
([1be183a](1be183a))
* **deps:** update module google.golang.org/grpc to v1.81.1
([#55](#55))
([6bfac2d](6bfac2d))
* status lifecycle improvements and created/running result handling
([#5](#5))
([fc64c7d](fc64c7d))
* **status0:** white page header and de-duplicated footer
([#48](#48))
([77506a7](77506a7))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
This was referenced Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant