Loading…
Loading…
Explains how the platform validates and enforces license terms from signed JWTs, including capacity limits, bootstrap licenses, and feature entitlements.
The License Validator is a system component responsible for parsing, validating, and enforcing the terms of the product license. It ensures that the platform operates within the capacity limits and feature entitlements granted by a signed JSON Web Token (JWT).
The validator's primary input is a license JWT, which is either uploaded by a platform operator or read from the database. It verifies the token's authenticity, integrity, and claims against the specific installation's identity. As a fallback for new installations, it uses a time-limited, embedded "bootstrap" license.
The key output is a LicenseState object, which represents the effective entitlements and capacity limits. This state is cached for performance and consulted by various system components to enforce license terms, such as gating the creation of new services or enabling specific features. The validator runs periodically and upon specific events (like application startup or a new license upload) to ensure the license state remains current.
The system determines the current license state by following a strict precedence order: a valid, active license JWT is always preferred. If one is not available or fails re-validation, it attempts to fall back to a temporary bootstrap license. If neither is available, the system enters a disabled state with zero capacity.
The LicenseValidator resolves the effective license state by checking for an active license in the database, re-validating it, and falling back to the bootstrap license if necessary. This process is executed on application startup, on a recurring hourly schedule, and whenever the cached state expires (after 30 seconds) and a new check is triggered.
The following diagram illustrates the license resolution logic:
flowchart TD
subgraph "License Resolution Process"
direction TD
A(Start: Resolve License) --> B{Active license present?}
B -- Yes --> C[Re-verify JWT signature and claims]
C --> D{Verification successful?}
D -- Yes --> E{Expired beyond 24hr grace period?}
E -- No --> F[Apply thresholds from license meta claim]
F --> G(State: Licensed)
E -- Yes --> H[Deactivate expired license in DB]
H --> I{Bootstrap license available?}
D -- No --> J[Log warning and use fallback]
J --> I
B -- No --> I
subgraph "Bootstrap and Disabled States"
I -- Yes --> K{Bootstrap conditions met?<br>(Install < 45 days old AND not HARD_STOP)}
K -- Yes --> L(State: Bootstrap)
K -- No --> M{System in HARD_STOP state?}
M -- Yes --> N(State: Disabled)
M -- No --> O[Attempt last-resort bootstrap load]
O --> P{Bootstrap now available?}
P -- Yes --> L
P -- No --> N
end
endEvery active license JWT is re-validated to ensure its integrity and applicability. The validation process, performed by the verifyV3 method, is strict and enforces multiple conditions. A token that fails any of these checks is considered invalid.
Signature Verification: The JWT signature is verified using the RS256 algorithm. The public key is resolved using the kid (Key ID) from the JWT header.
kid = bootstrap-placeholder-v1), a bundled public key is used.SpecariaJwksProvider.Header Validation:
alg (Algorithm) must be RS256.typ (Type) must be JWT.Claim Validation:
iss (Issuer): Must be https://license.specaria.io.exp (Expiration Time): The token must not be expired, accounting for a 300-second clock skew tolerance. An active license that is expired beyond a 24-hour grace period (EXPIRY_GRACE) is deactivated.license_format_version: Must be 3. No other versions are supported.aud (Audience): Must match spc:<product_slug>:<installation_id> (e.g., spc:s2r:uuid-goes-here) to bind the license to the specific installation. For the bootstrap license, the literal spc:s2r:bootstrap is also accepted.installation_id: The UUID in this claim must match the installation's own ID. This serves as a defense-in-depth check.To optimize performance, the resolved LicenseState is held in a 30-second in-memory cache (CACHE_TTL).
effective() or currentState() will return the cached state if it is still valid.invalidateCache() after a new license is successfully uploaded to ensure the new terms take effect immediately.For new installations, a temporary bootstrap license is used. Its activation is gated by two conditions:
bootstrap_activated_at timestamp.HARD_STOP state as determined by the SpecariaGraceStateMachine.If these conditions are not met, the bootstrap license is not used, and the system will proceed to a disabled state if no other valid license is found.
A new license JWT can be provided to the system by a platform operator. The uploadAndActivate method handles this process.
Procedure:
uploadAndActivate(jwtRaw, uploadedBy).ERR_BAD_REQUEST).verifyV3 to perform the full suite of JWT validation checks. The allowBootstrap flag is set to false, meaning an operator cannot upload a bootstrap token.invalidateCache().LicenseState, which is resolved by an immediate call to effective().Expected Result: A successful upload replaces the current license. The system immediately begins operating under the terms of the new license.
Failure Conditions:
If the JWT fails validation for any reason (e.g., bad signature, incorrect aud claim, unsupported format), the method throws a LicenseException with a specific error code, and the current license state remains unchanged.
The validator parses specific claims from the JWT to determine license entitlements. The token must be a v3 format license.
| Claim | Type | Required | Description |
|---|---|---|---|
iss | String | Yes | Issuer. Must be https://license.specaria.io. |
sub | String | Yes | Subject. Used as the customer identifier. |
aud | String or Array of String | Yes | Audience. Must contain spc:<product_slug>:<installation_id> or spc:s2r:bootstrap. |
exp | Number | Yes | Expiration time (Unix timestamp). |
iat | Number | Yes | Issued at time (Unix timestamp). |
jti | String | Yes | JWT ID. A unique identifier for the token. |
These claims define the specific terms and features of the license.
| Claim | Type | Description |
|---|---|---|
license_format_version | Number | Required. The format version of the license. Must be 3. |
installation_id | String | Required. The UUID of the installation this license is bound to. May also be nested under an installation object. |
issued_to_email | String | The email address of the license recipient. |
limits | Object | A map of capacity limits. See Limits Object below. May be nested under product_specific. |
features | Array of String | A list of enabled feature names. |
deployment_type | String | The deployment environment type (e.g., CUSTOMER_VPC). |
update_channel | String | The selected software update channel (e.g., release, early-access). Defaults to release if absent or unknown. |
update_channels_allowed | Array of String | The list of update channels the operator is permitted to select. |
registry_token | String | A 64-character hex token for authenticating to a channel-gated update registry. |
meta | Object | A map containing metadata and consent flags. See Meta Object below. |
commerce | Object | A map containing commercial contract details. See Commerce Object below. |
enforcement | Object | A map defining the license enforcement policy. See Enforcement Object below. |
limits ObjectThe limits object defines the capacity caps for the installation. Any key in this object with an integer value is treated as an enforceable capacity dimension.
| Key | Type | Default | Description |
|---|---|---|---|
max_converted_services | Integer | 50 | The maximum number of services that can be onboarded. If the limits object is present but omits this key, it defaults to 0. |
max_discovered_services | Integer | 500 | The maximum number of services that can be discovered. If the limits object is present but omits this key, it defaults to 0. |
| custom dimensions | Integer | 0 | Any other integer-valued key is treated as a capacity limit, enforced by its key name. |
meta ObjectThe meta object contains operator-tunable settings and consent flags.
| Key | Type | Default | Description |
|---|---|---|---|
heartbeat_consent | Boolean | false | If true, enables sending telemetry heartbeat data. |
debug_capture_consent | Boolean | false | If true, grants consent for platform operators to capture debug data. |
platform_operators | Array of String | A list of email addresses for platform operator staff who are granted special read-only access. | |
read_only_after_days | Integer | (System default) | Overrides the number of days the system can be offline before entering READ_ONLY mode. Must be between 1 and 365. |
hard_stop_after_days | Integer | (System default) | Overrides the number of days the system can be offline before entering HARD_STOP mode. Must be between 1 and 365 and greater than read_only_after_days. |
latency_injection_ms | Integer | (System default) | Overrides the amount of artificial latency (in ms) injected when the system is in a degraded mode. Must be between 0 and 60000. |
telemetry_consent | Object | (All enabled) | A map of string:boolean pairs to selectively disable telemetry categories. An absent key or map implies consent for that category. |
heartbeat_redacted_fields | Array of String | A list of metric keys to be masked in telemetry data. |
commerce ObjectThis object contains commercial details for reference. These fields are not enforced by the validator.
| Key | Type | Description |
|---|---|---|
tier | String | The commercial tier of the license. |
billing_ref | String | A reference identifier for billing. |
contract_start | String | The start date of the contract (ISO 8601 format). |
contract_end | String | The end date of the contract (ISO 8601 format). |
enforcement ObjectThis object defines how license expiry is handled. If the object is absent, the policy defaults to updates-only.
| Key | Type | Default | Description |
|---|---|---|---|
mode | String | updates-only | The enforcement mode. Resolved via EnforcementMode.fromWire(). |
grace_days | Integer | 0 | The number of grace days after expiration. Bounded 0..3650. |
read_only_after_days | Integer | 14 | The number of days after expiration before entering read-only mode. Bounded 0..3650. |
hard_stop_after_days | Integer | 30 | The number of days after expiration before entering a hard stop. Bounded 0..3650. |
The LicenseValidator exposes several public methods for other components to query the license state and perform enforcement checks.
| Method | Description |
|---|---|
effective() | Returns the effective LicenseState, using a 30-second in-memory cache. This is the primary method for checking the current license. |
currentState() | An alias for effective(). |
uploadAndActivate(String jwtRaw, String uploadedBy) | Validates and activates a new license JWT provided by an operator. Throws LicenseException on failure. |
canOnboardAdditionalService() | Returns true if the number of active services is less than the max_converted_services cap. |
rawLicenseJwt() | Returns the raw, unparsed JWT string of the active license. Used by the telemetry client. Returns null if no active license is present. |
debugCaptureConsent() | Returns true if the meta.debug_capture_consent claim in the active license is true. Fails closed (returns false) on any error. |
metaThresholds() | Parses and returns the tunable grace period and latency thresholds from the meta claim of the active license. Fails closed (returns empty thresholds) on any error. |
enforcementPolicy() | Parses and returns the enforcement policy from the enforcement claim. Fails soft (returns updatesOnly policy) on any error. |
platformOperatorEmails() | Returns a set of normalized email addresses from the meta.platform_operators claim. Fails closed (returns an empty set) on any error. |
cachedSaasEnabled() | Returns the saasEnabled flag from the last resolved license state without triggering a re-resolution. This is safe to call from contexts where database access is restricted. |
invalidateCache() | Clears the in-memory license cache, forcing a full re-resolution on the next call to effective(). |
When license validation fails, the validator throws a LicenseException containing one of the following error codes.
| Error Code | Meaning | Trigger |
|---|---|---|
S2R-LIC-0001 | Bad Signature | The JWT signature is invalid, the key could not be located via kid, or the token is malformed. |
S2R-LIC-0002 | Expired | The exp claim is in the past, beyond the allowed clock skew. |
S2R-LIC-0017 | Bad Algorithm | The JWT header alg is not RS256. This prevents algorithm substitution attacks. |
S2R-LIC-0018 | Bad Type | The JWT header typ is not JWT. |
S2R-LIC-0019 | Bad Issuer | The iss claim does not match the required issuer (https://license.specaria.io). |
S2R-LIC-0015 | Installation Mismatch | The installation_id claim in the JWT does not match the ID of the local installation. |
S2R-LIC-0012 | Bad Audience | The aud claim does not match the expected value for this installation (spc:s2r:<installation_id>). |
S2R-LIC-0016 | Unsupported Format | The license_format_version claim is not 3. |
S2R-LIC-0007 | Bad Request | An operator attempted to upload a license that was null, empty, or blank. |
S2R-LIC-0005 | Conversion Limit Reached | The system has reached the max_converted_services capacity defined in the license. This code is used by consuming services, not thrown by the validator itself. |
SpecariaJwksProvider) that fetches and caches public keys from a re<customer>e JWKS endpoint for verifying license JWT signatures.SpecariaGraceStateMachine) that manages the system's operational state (e.g., READ_ONLY, HARD_STOP) during periods of license expiry or offline operation.SpecariaHeartbeatClient) that sends telemetry data, which includes the raw license JWT obtained from this validator.