feat(schemas,console): add self-hosted license payload guard and OSS default quota - #9599
Merged
wangsijie merged 1 commit intoSep 15, 2026
Conversation
…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
wangsijie
requested review from
charIeszhao,
gao-sun and
xiaoyijun
as code owners
September 14, 2026 10:06
Contributor
There was a problem hiding this comment.
🟡 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.
charIeszhao
approved these changes
Sep 15, 2026
wangsijie
deleted the
wangsijie-log-14234-schemas-license-payload-guard-licensekey-system-entries-and
branch
September 15, 2026 06:11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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, andSubscriptionDataProvider).ReservedPlanIdAdds
SelfHostedPro = 'self-hosted-pro'andSelfHostedEnterprise = '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.licensePayloadGuardNew
packages/schemas/src/consts/license.ts, alongsideReservedPlanIdand shaped afterconsts/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:
quotafield rather than flattened into the claims. That keeps the standardiat/expout 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.quotais 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 CloudbringYourUiEnabled). 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.samlApplicationsLimitis the one name shared with Cloud and keeps the same meaning there —nullis unlimited.ossDefaultQuotaandresolveLicenseQuotaossDefaultQuotais today's OSS: every feature locked, SAML applications capped at 3.resolveLicenseQuotaapplies 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:samlApplicationsLimitis nullable andnullmeans 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.LicenseNew 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 fiveSystemKey/SystemType/SystemGuard/systemKeys/systemGuardssummaries; omitting thesystemKeysspread compiles cleanly but breaks the CLI at runtime, so there is a test for that too. No migration: thesystemstable storeskey/valueand rows are inserted lazily. M2 adds the deployment ID and refresh state as further keys in this group.Console
SkuName's phrase map ends insatisfies Record<ReservedPlanId, …>, so the two new plan IDs break its typecheck until they are listed. Both map to phrases that already exist, andself-hosted-enterprisemaps to the same'enterprise'the existing fallback already returns, so rendered output is unchanged. Thesatisfiesclause is what caught this and is kept.Testing
Unit tests
🤖 Generated with Claude Code
https://claude.ai/code/session_01UNfQWWRxGBra4UB5RHAzUy
Generated by Claude Code