Skip to content

feat(schemas,console): add self-hosted license payload guard and OSS default quota - #9599

Merged
wangsijie merged 1 commit into
masterfrom
wangsijie-log-14234-schemas-license-payload-guard-licensekey-system-entries-and
Sep 15, 2026
Merged

wangsijie merged 1 commit into
masterfrom
wangsijie-log-14234-schemas-license-payload-guard-licensekey-system-entries-and

Conversation

@wangsijie

Copy link
Copy Markdown
Contributor

Summary

First issue of M1 in the Self-hosted plans entitlement license project. Implements LOG-14234: the shared license contract, so Core and Console read one shape rather than each defining their own.

Types and constants only — nothing reads them yet. The consumers land in the following M1 issues (the Core license reader, SubscriptionLibrary, and SubscriptionDataProvider).

ReservedPlanId

Adds SelfHostedPro = 'self-hosted-pro' and SelfHostedEnterprise = 'self-hosted-enterprise'. Unlike the existing members, neither is ever returned by the Cloud subscription API: both are carried in a signed license payload and resolved locally.

licensePayloadGuard

New packages/schemas/src/consts/license.ts, alongside ReservedPlanId and shaped after consts/message-rate-limit.ts (hand-written type, zod guard, frozen default, partial override guard in one file).

The payload is the claim set of the Ed25519-signed JWT an operator installs: plan, env, customerId, licenseId, iat, exp, and the entitlements. Unknown claims are stripped rather than rejected, so the license service can add one without invalidating keys on an older Logto.

Two decisions worth a reviewer's eye:

  • Entitlements are nested under a single quota field rather than flattened into the claims. That keeps the standard iat / exp out of the same namespace as product entitlements, and gives the downstream consumers one object to merge instead of picking seven claims apart. If you prefer flat, it is a one-line change.
  • quota is partial. The issue calls these "quota overrides", and keeping them optional means a key signed before a new entitlement existed still verifies on a newer Logto — the missing entitlement stays locked instead of the whole license failing to parse.

The names follow the license vocabulary (bringYourUi, not the Cloud bringYourUiEnabled). These values never travel through the Cloud subscription API or its usage reporting, so borrowing the Cloud suffixes would imply a compatibility that does not exist. samlApplicationsLimit is the one name shared with Cloud and keeps the same meaning there — null is unlimited.

ossDefaultQuota and resolveLicenseQuota

ossDefaultQuota is today's OSS: every feature locked, SAML applications capped at 3.

resolveLicenseQuota applies a payload's overrides on top of it. This is one line past the issue's scope list, added because both the Core and the Console issue describe the same merge and would otherwise implement it twice. It also removes a real footgun: samlApplicationsLimit is nullable and null means unlimited, so a spread or ?? merge silently turns an unlimited license back into the OSS cap of 3. There is a test for exactly that.

LicenseKey.License

New system key holding { jwt, installedAt } — the installed key verbatim, so it can be re-verified on every read and re-signed on refresh. Wired into all five SystemKey / SystemType / SystemGuard / systemKeys / systemGuards summaries; omitting the systemKeys spread compiles cleanly but breaks the CLI at runtime, so there is a test for that too. No migration: the systems table stores key / value and rows are inserted lazily. M2 adds the deployment ID and refresh state as further keys in this group.

Console

SkuName's phrase map ends in satisfies Record<ReservedPlanId, …>, so the two new plan IDs break its typecheck until they are listed. Both map to phrases that already exist, and self-hosted-enterprise maps to the same 'enterprise' the existing fallback already returns, so rendered output is unchanged. The satisfies clause is what caught this and is kept.

Testing

Unit tests

🤖 Generated with Claude Code

https://claude.ai/code/session_01UNfQWWRxGBra4UB5RHAzUy


Generated by Claude Code

…default quota

Add the shared license contract so Core and Console read one shape: the
`self-hosted-pro` and `self-hosted-enterprise` reserved plan IDs, the signed
license payload guard with its entitlements, the `license` system key holding an
installed key, and the `ossDefaultQuota` fallback for an instance without one.

Extend the Console SKU name map for the two new reserved plan IDs to keep its
exhaustiveness check satisfied.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UNfQWWRxGBra4UB5RHAzUy
Copilot AI balanced review requested due to automatic review settings September 14, 2026 10:06
@github-actions github-actions Bot added feature Cool stuff size/l labels Sep 14, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The installed-license guard does not enforce its documented ISO timestamp contract.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds the shared self-hosted licensing contract and OSS entitlement defaults for future Core and Console consumers.

Changes:

  • Defines self-hosted plan IDs, license payload guards, quotas, and resolution logic.
  • Registers installed licenses as system entries with tests.
  • Adds Console display names for self-hosted plans.
File summaries
File Description
packages/schemas/src/types/system.ts Defines and registers installed license storage.
packages/schemas/src/types/system.test.ts Tests license system registration and parsing.
packages/schemas/src/consts/subscriptions.ts Adds self-hosted reserved plan IDs.
packages/schemas/src/consts/license.ts Defines license contracts and quota resolution.
packages/schemas/src/consts/license.test.ts Tests payload validation and quota behavior.
packages/schemas/src/consts/index.ts Exports licensing constants.
packages/console/src/components/SkuName/index.tsx Maps self-hosted plans to existing phrases.
Review details
  • Files reviewed: 7/7 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/schemas/src/types/system.ts
@wangsijie
wangsijie merged commit b7170a6 into master Sep 15, 2026
35 checks passed
@wangsijie
wangsijie deleted the wangsijie-log-14234-schemas-license-payload-guard-licensekey-system-entries-and branch September 15, 2026 06:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

3 participants