URD: Licensing
| Module | CORE-15 | Version | v0.1 |
|---|---|---|---|
| Status | Built | Date | 2026-06-05 |
Business documentation. This URD is Licensing's feature list - each feature below is one Functional Area (
<AREA>). The same<AREA>keys the feature's PRDs (PRD-<AREA>-NNN) and tests (TC-<AREA>-NNN), and each feature is listed in the Delivery feature catalog. See the Feature Spine convention.
1. Purpose
Define the user-facing requirements for the Licensing module - reusable plans, license issuance and lifecycle, device activation, and runtime validation - so platform operators can control exactly what each merchant is entitled to run, merchants can self-serve a free trial, and every other service can enforce features and seat quotas offline against a trustworthy signed certificate.
2. Scope
| Included | Excluded |
|---|---|
| Reusable plans: trial / subscription / perpetual | Billing, invoicing, dunning |
| Typed feature flags and seat quotas per plan | Payment / subscription-charge integration |
| Public plan catalog | Usage metering / heartbeat tracking |
| License issuance + lifecycle (renew / suspend / reinstate / revoke) | Per-feature runtime enforcement (each consumer's own job) |
| Self-service free trial | License management UI (frontend concern) |
| Device activation (fingerprint binding, seat limit) | Certificate verification (every other service, offline) |
| Runtime validation returning a signed certificate | Eager expiry sweeping (expiry is lazy) |
| Per-license override of plan defaults | |
| Append-only event audit log |
3. Definitions
| Term | Definition |
|---|---|
| Plan | A reusable template a license is issued from; carries type, duration, grace period, seat limit, and feature flags |
| Feature flag | A typed key/value on a plan (boolean / number / text / json) that switches a feature on/off or sets a quota |
| License | An issued entitlement bound to a merchant or user, with a unique key and a lifecycle status |
| Activation | A binding of a license to one device, identified by a fingerprint, counted against the seat limit |
| Seat limit | The maximum number of unique devices that may activate against a single license |
| Grace period | A window after expiry during which the license is still honoured, flagged as in-grace |
| Certificate | A signed snapshot of a license's status and resolved features, trusted offline by other services |
| Override | Per-license adjustments to a plan's seat limit or feature values |
4. Conceptual Model
Conceptual only - the full schema lives in the developer domain model.
5. Feature Catalog
The feature list of this module. Each row is one feature (a Functional Area). Detail in §6. Mirrored in the Delivery feature catalog.
| Feature ID | Feature | Phase | Status | Priority |
|---|---|---|---|---|
PLN | Plans & Feature Flags | P1 | Built | High |
LIC | License Lifecycle | P1 | Built | High |
ACT | Device Activation | P2 | Built | High |
VAL | Runtime Validation & Entitlements | P1 | Built | High |
Status: live from Plane where mapped, otherwise registry-declared. Vocabulary mirrors Plane (state-group / phase).
6. Features
One sub-section per feature, in catalog order. Each feature keeps its description, requirements, and acceptance together. Priority = MoSCoW (Must / Should / Could / Won't).
PLN - Plans & Feature Flags Built
Feature ID: licensing/PLN · Phase: P1 · PRDs: PRD-PLN-001 · Dev: @nx/licensing
What it does for users: platform operators define reusable plans - trial, subscription, or perpetual - each carrying a duration, optional grace period, a device seat limit, and a bag of typed feature flags that licenses issued from the plan will grant.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-PLN-001 | M | Create a plan of type trial, subscription, or perpetual |
| URD-PLN-002 | M | A plan defines a duration (or null for perpetual) and an optional grace period |
| URD-PLN-003 | M | A plan optionally caps the number of devices (seat limit); null = unlimited |
| URD-PLN-004 | M | Attach typed feature flags to a plan: boolean, number, text, or json |
| URD-PLN-005 | M | A feature flag can be deactivated, resolving to its type's empty/off default without deletion |
| URD-PLN-006 | M | The feature-flag code is unique within a plan |
| URD-PLN-007 | S | Plans carry a localized display name and description and a display order |
| URD-PLN-008 | M | A public catalog lists active plans with their active features, in display order |
| URD-PLN-009 | S | Plans are soft-deleted (deactivated/archived), never hard-deleted from the app |
Acceptance
AC-PLN-01: Plan with features
| Given | When | Then |
|---|---|---|
| An operator creating a subscription plan | They set duration, grace period, seat limit, and several feature flags | The plan is saved; the feature-flag codes are unique within the plan |
| A feature flag set to deactivated | The plan's features are resolved | That flag returns its type's empty/off default rather than its configured value |
AC-PLN-02: Public catalog
| Given | When | Then |
|---|---|---|
| Several active plans and one archived plan | The catalog is requested | Only active plans appear, ordered by display order, each with its active features inlined |
LIC - License Lifecycle Built
Feature ID: licensing/LIC · Phase: P1 · PRDs: PRD-PLN-001 · Dev: @nx/licensing
What it does for users: a license is issued from a plan and bound to a merchant or user with a unique key, then moves through a tracked lifecycle - renew, suspend, reinstate, revoke - with every change audited and the certificate re-signed.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-LIC-001 | M | Issue a license from a plan, bound to a merchant or user, with a unique generated key |
| URD-LIC-002 | M | An issued license computes its expiry from the plan's duration and grace period (perpetual = no expiry) |
| URD-LIC-003 | M | A license has a status: activated, suspended, expired, or revoked |
| URD-LIC-004 | M | Suspend an active license, then reinstate it back to active |
| URD-LIC-005 | M | Renew a license to extend its validity; renewing an expired license restores it to active |
| URD-LIC-006 | M | Renew is rejected for a perpetual license (no duration to extend) |
| URD-LIC-007 | M | Revoke a license permanently; revoked is terminal and cannot be renewed or reinstated |
| URD-LIC-008 | M | A merchant self-serves a single free-trial license; an existing trial is returned rather than duplicated |
| URD-LIC-009 | M | Every lifecycle action is recorded in an append-only event log |
| URD-LIC-010 | M | Each lifecycle change re-signs and re-publishes the license certificate |
| URD-LIC-011 | S | A license may override its plan's seat limit and feature values |
Acceptance
AC-LIC-01: Issue & lifecycle
| Given | When | Then |
|---|---|---|
| A subscription plan | A license is issued for a merchant | A unique key is generated; expiry = start + duration; status activated; a created event is logged |
| An active license | It is suspended then reinstated | Status goes activated → suspended → activated; both actions are audited |
| A perpetual license | Renew is attempted | Rejected - a perpetual license has no duration to extend |
| An expired license | It is renewed | Status returns to activated with a fresh validity period starting now |
| Any license | It is revoked | Status → revoked (terminal); further renew/reinstate are rejected |
AC-LIC-02: Free trial
| Given | When | Then |
|---|---|---|
| A merchant with no trial | They request a free trial | A trial license is issued for them |
| A merchant who already has a trial | They request a free trial again | The existing trial is returned; no duplicate is created |
ACT - Device Activation Built
Feature ID: licensing/ACT · Phase: P2 · PRDs: PRD-ACT-001 · Dev: @nx/licensing
What it does for users: a license is bound to specific devices by fingerprint; each unique device consumes one seat, the plan's seat limit is enforced, and a device can be deactivated to free its seat.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-ACT-001 | M | Activate a license on a device, identified by a fingerprint, with optional label, platform, hostname |
| URD-ACT-002 | M | Re-activating the same device on the same license reuses its existing seat (idempotent) |
| URD-ACT-003 | M | The number of unique device seats cannot exceed the plan's (or override's) seat limit |
| URD-ACT-004 | M | A null seat limit means unlimited device activations |
| URD-ACT-005 | M | Deactivate a device to free its seat |
| URD-ACT-006 | S | Activation and deactivation are recorded in the event log |
| URD-ACT-007 | M | Activation is refused when the license is not in an active status (suspended / expired / revoked / not yet started) |
Acceptance
AC-ACT-01: Seat enforcement
| Given | When | Then |
|---|---|---|
| A license with seat limit 2 and 2 devices active | A third unique device activates | Rejected - seat limit reached |
| The same device that is already active | It activates again | The existing seat is reused; no new seat is consumed |
| A licensed device | It is deactivated | The seat is freed and may be taken by another device |
AC-ACT-02: Activation guarded by license status
| Given | When | Then |
|---|---|---|
| A suspended, expired, or revoked license | A device tries to activate against it | Rejected - the license is not in an active status |
| An active license with a free seat | A device activates against it | A seat is consumed and the activation succeeds |
Built per PRD-ACT-001.
VAL - Runtime Validation & Entitlements Built
Feature ID: licensing/VAL · Phase: P1 · PRDs: PRD-PLN-001 · Dev: @nx/licensing
What it does for users: a service validates a license key and receives the license status, its resolved feature set, the device-seat usage, and a signed certificate that other services trust offline to gate features and seats.
Requirements
| ID | P | Requirement |
|---|---|---|
| URD-VAL-001 | M | Validate a license key and return whether it is valid plus a result code |
| URD-VAL-002 | M | An inactive license (suspended / revoked / expired) validates as invalid with a status-specific code |
| URD-VAL-003 | M | A not-yet-started license validates as invalid (not started) |
| URD-VAL-004 | M | A license past expiry but within grace validates as valid with a grace-period code |
| URD-VAL-005 | M | A license past grace is lazily flipped to expired on validation and returns invalid |
| URD-VAL-006 | M | Validation returns the resolved feature set, with per-license overrides applied |
| URD-VAL-007 | M | When a device fingerprint is supplied, validation handles the seat (reuse or create under the limit) |
| URD-VAL-008 | M | A successful validation returns a signed certificate other services can verify offline |
| URD-VAL-009 | S | Validation records the last-validated time on the license (best-effort) |
Acceptance
AC-VAL-01: Validation outcomes
| Given | When | Then |
|---|---|---|
| An active, in-date license | It is validated | Valid; resolved features and a signed certificate are returned |
| A license within its grace period | It is validated | Valid with a grace-period code |
| A license past grace | It is validated | It is flipped to expired and returns invalid (expired) |
| A suspended or revoked license | It is validated | Invalid with the matching status code |
AC-VAL-02: Validation with a device
| Given | When | Then |
|---|---|---|
| A valid license with a free seat and a new fingerprint | It is validated with that fingerprint | A seat is consumed and the validation succeeds |
| A valid license at its seat limit and a new fingerprint | It is validated with that fingerprint | Validation fails - seat limit reached |
7. Constraints & Non-Goals
Constraints
| ID | Constraint |
|---|---|
| C-01 | A license is always bound to exactly one principal (merchant or user) |
| C-02 | A license key is unique among live licenses |
| C-03 | A feature-flag code is unique within a plan |
| C-04 | One seat per unique (license, device fingerprint) pair |
| C-05 | Active device seats never exceed the effective seat limit |
| C-06 | Revoked is terminal - no renew or reinstate |
| C-07 | The event log is append-only and survives license deletion |
| C-08 | Expiry is detected lazily on validation; there is no background sweeper |
| C-09 | Other services only verify the certificate offline; they never call licensing at runtime to authorize |
Non-Goals
- Billing, invoicing, and dunning
- Payment / subscription-charge integration
- Usage metering and heartbeat tracking
- Eager / scheduled expiry sweeping
- Per-feature runtime enforcement (each consuming module does its own gating)
- License management UI (frontend concern)
8. Version History
| Date | Author | Description | Ver |
|---|---|---|---|
| 2026-06-05 | Claude (AI pair) | Initial URD authored from the @nx/licensing package and developer docs (Feature Spine) | v0.1 |