diff --git a/CLAUDE.md b/CLAUDE.md index 3ff3aa0..f404e8f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -949,6 +949,8 @@ Admin's own database is separate and holds `accounts` · `admin_instances` · `l `plans` is keyed on `(deployment, tier)` — six rows, two deployments times three tiers — and holds base allowances only. **Every Paddle price ID lives in `catalogue`**, one row per priceable component (`base`, `limit`, `feature`), because a metered plan is priced by several prices and one map on a plan row cannot express that. `entitlements` holds one row per instance with `desired` beside `granted`: the checkout is built from `desired`, a licence is only ever signed from `granted`, and an abandoned checkout therefore leaves a `desired` that reached nothing. The two Free plans have **no catalogue rows at all**, which is what keeps Free outside Paddle. +**No tier bundles a feature.** `console`, `oidc`, `vuln_scanning` and `status_pages` are each a per-customer priceable add-on: every plan row carries an empty `base_features`, and the grant comes from a `catalogue` row the customer buys. Adding a fifth feature therefore means one more `KindFeature` row per paid plan in `SeedCatalogue` and one entry in `adminsite/lib/features.ts` — that map is what the customer's grant list, the staff configurator and the purchase form all enumerate, so a feature missing from it exists in the licence and is invisible in the portal. `SeedCatalogue` upserts on `(kind, deployment, tier, feature_key)`, so a new row reaches an existing database on the next admin boot with no migration; `SeedPlans` is `$setOnInsert` on the whole document and would not, which is the other reason bundling into a tier is the harder path. + ### Migrations `services.RunMigrations()` runs at boot, recording markers in `migrations`: diff --git a/admin/internal/models/catalogue.go b/admin/internal/models/catalogue.go index 6affabd..366fd09 100644 --- a/admin/internal/models/catalogue.go +++ b/admin/internal/models/catalogue.go @@ -67,8 +67,10 @@ func (r CatalogueRow) Priced(env string) bool { return false } -// SeedCatalogue inserts the twenty rows the four PAID plans need: a base, a -// server limit, and one row per feature key. +// SeedCatalogue inserts the twenty-four rows the four PAID plans need: a base, a +// server limit, and one row per feature key. The count is deliberate — it moves +// whenever shared/license gains a feature, and this comment is how the next +// person knows the number was chosen rather than drifted. // // The two Free plans get no rows at all, and that absence is what keeps Free // outside Paddle: with nothing to price, no checkout can be built for it. Do not @@ -86,6 +88,7 @@ func SeedCatalogue(ctx context.Context) error { {Kind: KindFeature, Deployment: deployment, Tier: tier, FeatureKey: license.FeatureConsole}, {Kind: KindFeature, Deployment: deployment, Tier: tier, FeatureKey: license.FeatureOIDC}, {Kind: KindFeature, Deployment: deployment, Tier: tier, FeatureKey: license.FeatureVulnScanning}, + {Kind: KindFeature, Deployment: deployment, Tier: tier, FeatureKey: license.FeatureStatusPages}, } for _, r := range rows { filter := bson.M{ diff --git a/adminsite/lib/features.ts b/adminsite/lib/features.ts index 7ee06a2..31b5b8d 100644 --- a/adminsite/lib/features.ts +++ b/adminsite/lib/features.ts @@ -10,12 +10,14 @@ export const FEATURE_LABEL: Record = { console: "Browser console", oidc: "Single sign-on", vuln_scanning: "Vulnerability scanning", + status_pages: "Status pages", }; export const FEATURE_DESC: Record = { console: "In-browser SSH, RDP and VNC sessions", oidc: "OIDC sign-in for your whole team", vuln_scanning: "Package inventory matched against distribution security advisories", + status_pages: "Public status pages for your customers, built from your monitors", }; export function featureLabel(key: string): string { diff --git a/docsite/docs/hq/licensing-and-entitlements.md b/docsite/docs/hq/licensing-and-entitlements.md index 2b5006f..bc5d40c 100644 --- a/docsite/docs/hq/licensing-and-entitlements.md +++ b/docsite/docs/hq/licensing-and-entitlements.md @@ -28,13 +28,14 @@ entitlement. ## Features -Three features are enabled per instance rather than bundled into a tier: +Four features are enabled per instance rather than bundled into a tier: | Feature | What it enables | | ---------------------- | ------------------------------------------------------------------------------- | | Browser console | The [browser console](../vantage/browser-console.md) | | Single sign-on | [Sign-in through your identity provider](../vantage/settings.md#single-sign-on) | | Vulnerability scanning | [Package vulnerability scanning](../vantage/vulnerabilities.md) | +| Status pages | [Public status pages](../vantage/status-pages.md) | No tier includes them by default; you enable them on the instances that need them.