Skip to content

Domain Model

All five tables live in the licensing PostgreSQL schema and are defined in @nx/core at packages/core/src/models/schemas/licensing/. @nx/licensing re-exports the repositories only.

1. Full ERD

2. Entities

Policy

PropertyValue
Tablelicensing.Policy
Sourcepackages/core/src/models/schemas/licensing/policy/schema.ts
Soft-deleteyes (generateCommonColumnDefs)
Owner ID column— (system-level template)

Fields:

FieldTypeRequiredDefaultDescription
idtextSnowflakePrimary key
namejsonb (i18n)Display name { default, en, vi }
descriptionjsonb (i18n)Optional
producttextProduct identifier (indexed)
typetextPolicyTypes (see enum)
statustextACTIVATEDPolicyStatuses
sequenceinteger0Display order
durationjsonb (IDuration){ unit, value }; null = perpetual
activationjsonb (IActivationConfig){ limit }; null = unlimited devices
gracePeriodjsonb (IDuration)Extends validation window after expiry

type enum (PolicyTypes):

ValueDescription
000_TRIALTrial plan (the seeded free trial uses this)
100_SUBSCRIPTIONTime-bounded subscription
200_PERPETUALNo expiry (set duration: null)

status enum (PolicyStatuses): ACTIVATED · DEACTIVATED · ARCHIVED.

Indexes: IDX on product, type, status.

PolicyFeature

PropertyValue
Tablelicensing.PolicyFeature
Sourcepackages/core/src/models/schemas/licensing/policy-feature/schema.ts
Soft-deleteno (generateTzColumnDefs({ deleted: { enable: false } }))
Owner ID columnpolicyId

Fields:

FieldTypeRequiredDefaultDescription
idtextSnowflakePrimary key
policyIdtextFK → Policy.id (cascade delete)
codetextFeature key (e.g. MAX_ALLOCATION_LAYOUTS)
dataTypetextboolean / number / text / json (from generateDataTypeColumnDefs)
boValuebooleanValue when dataType=boolean
nValuenumericValue when dataType=number
tValuetextValue when dataType=text
jValuejsonbValue when dataType=json
namejsonb (i18n)Display name
descriptionjsonb (i18n)Optional
sequenceinteger0Display order
statustextACTIVATEDPolicyFeatureStatuses (ACTIVATED/DEACTIVATED)

Polymorphic value: LicensingBaseService.DATA_TYPE_RESOLVERS reads the column matching dataType. A DEACTIVATED feature resolves to the per-type default (false/0/''/null).

Indexes & constraints: UQ on (policyId, code); IDX on policyId, status; FK cascade on delete.

License

PropertyValue
Tablelicensing.License
Sourcepackages/core/src/models/schemas/licensing/license/schema.ts
Soft-deleteyes
Owner ID columnentityType + entityId (polymorphic principal)

Fields:

FieldTypeRequiredDefaultDescription
idtextSnowflakePrimary key
policyIdtextFK → Policy.id
keytextLicense key (PREFIX-XXXX-XXXX-XXXX-XXXX)
namejsonb (i18n)Display name
statustextACTIVATEDLicenseStatuses (see enum)
entityTypetextLicensePrincipalTypes: Merchant / User
entityIdtextPrincipal id
certificatetextLatest signed cert envelope (base64)
overridejsonb{ activation, features } — overrides Policy defaults
issuedAttimestamptznow()
startsAttimestamptznow()Validity start
expiresAttimestamptznull = perpetual
graceExpiresAttimestamptzEnd of post-expiry grace window
lastValidatedAttimestamptzUpdated (fire-and-forget) on each successful validate

status enum (LicenseStatuses):

ValueDescription
ACTIVATEDActive and usable
SUSPENDEDTemporarily disabled (reinstatable)
EXPIREDPast expiresAt + grace (set lazily on validate)
REVOKEDPermanently terminated

isActive = ACTIVATED only; isInactive = {SUSPENDED, REVOKED, EXPIRED}.

Indexes & constraints: partial-unique UQ on key (where deletedAt IS NULL); IDX on policyId, status, (entityType, entityId), entityId, expiresAt; FK → Policy.id.

Activation

PropertyValue
Tablelicensing.Activation
Sourcepackages/core/src/models/schemas/licensing/activation/schema.ts
Soft-deleteyes
Owner ID columnlicenseId

Fields:

FieldTypeRequiredDefaultDescription
idtextSnowflakePrimary key
licenseIdtextFK → License.id (cascade delete)
fingerprinttextDevice fingerprint
labeltextFriendly device name
platformtextOS / platform
hostnametextDevice hostname
iptextLast-seen IP

Indexes & constraints: partial-unique UQ on (licenseId, fingerprint) (where deletedAt IS NULL) — one activation per device per license; IDX on licenseId; FK cascade.

LicenseEvent

PropertyValue
Tablelicensing.LicenseEvent
Sourcepackages/core/src/models/schemas/licensing/license-event/schema.ts
Soft-deleteno (append-only audit)
Owner ID columnlicenseId (nullable)

Fields:

FieldTypeRequiredDefaultDescription
idtextSnowflakePrimary key
licenseIdtextFK → License.id (ON DELETE SET NULL)
eventtextLicenseEventTypes (created/activated/deactivated/suspended/reinstated/renewed/expired/revoked)
iptext
userAgenttext
datajsonb{}Event-specific payload
metadatajsonb

Indexes: IDX on licenseId, event; FK ON DELETE SET NULL (events survive license deletion).

3. Cross-entity Invariants

InvariantEnforcement
Activation count ≤ activation.limitActivationService.activate / ValidationService.tryCreateActivation: SELECT License FOR UPDATE → re-COUNT inside tx → rollback if >= limit
One activation per (licenseId, fingerprint)Partial-unique index + find-before-create fast path
License key unique among live rowsPartial-unique index on key WHERE deletedAt IS NULL
Feature value matches dataTypeDATA_TYPE_RESOLVERS reads the typed column; DEACTIVATED → per-type default
renew requires a Policy durationRENEW_PERPETUAL error if duration is null
Certificate reflects current license stateRe-published after every lifecycle mutation (issue/suspend/reinstate/renew/revoke) and on lazy expiry

4. Soft-delete Behavior

EntitySoft-deleteHard-deleteNotes
Policyyes (deletedAt)via repoPolicyController deletes by status (no hard delete in app)
Licenseyesvia repoRead default deletedAt IS NULL
Activationyescascade from License (FK)Deactivate uses deleteById
PolicyFeaturenocascade from Policy (FK)Deleted column disabled
LicenseEventnoFK SET NULL on license deleteAppend-only audit

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.