A licence is a lease: a signed statement the platform issues for one installation and one runnable copy. The machine that runs automations verifies the lease with a public key and nothing else. It holds no private key and no shared secret and makes no network call. There is nothing on the customer's machine that could mint a lease.
#What a lease says
A lease is JSON with three parts: the claims, the algorithm (ed25519) and the signature over the canonical form of the claims. The claims are:
licenceId, the entitlement, stable across renewals.installationId, one per customer installation.instanceKey, one per runnable copy. A fleet of ten robots is one installation and ten instances.leaseNonce, distinct per issued lease, so a renewal is a new artifact and a replay is detectable.tierandcapabilities.issuedAt,renewBy,expiresAtandgraceUntil.
The claims are serialised with their keys sorted recursively, so the same claims always produce the same bytes and a re-serialised lease still verifies. The verifier checks the signature over the canonical bytes, never over whatever JSON it happened to receive.
#How a lease is verified
The check order is part of the contract: signature, then binding, then time, then capability. A lease that is both forged and expired reports forgery first.
- Shape: a malformed lease is a fact about the file, not the licence.
- Schema: an unknown
schemaVersionis refused before its claims are read. - Signature: the
keyIdmust match the public key the robot holds. The signature must verify. - Binding: the lease must name this installation and this instance.
- Time: past
graceUntilthe right is gone. BetweenexpiresAtandgraceUntilthe robot runs in grace and says so. - Capability: when a capability is required, the lease must carry it.
Each failure has its own code: LICENCE_MALFORMED, LICENCE_SCHEMA_UNSUPPORTED, LICENCE_SIGNATURE_INVALID, LICENCE_WRONG_INSTALLATION, LICENCE_WRONG_INSTANCE, LICENCE_EXPIRED, LICENCE_CAPABILITY_MISSING and LICENCE_MISSING.
#Where enforcement happens
LicenseManager.canExecute('execution') is the enforcement point. It is asked once before an automation starts and never consulted again during the run. Two rules follow from that.
- Fail closed on new work. A deployment that declares itself licensed (
TS_LICENCE_ENFORCE=1) and cannot prove it refuses to start. A deployment that says nothing runs unenforced and the decision says so. - Fail open on in-flight work. A lease that expires mid-run cannot abort a production line. The next start is what refuses.
An unlicensed start is a pre-run refusal, not a retryable exception. LicenceRefusal is deliberately neither a SystemException (which the runner retries) nor a BusinessException (which means your own rule failed). A configured lease that does not verify always refuses, so configuring a broken lease is not a way to run unlicensed.
The licence is configured through the environment, with the same convention as the model adapter:
TS_LICENCE_ENFORCE 1 or true to require a usable lease
TS_LICENCE_LEASE path to the signed lease
TS_LICENCE_PUBLIC_KEY path to the issuer's public key
TS_INSTALLATION_ID this installation
TS_INSTANCE_KEY this runnable copy
There is no CLI verb for licensing. It is read from the environment when the Robot service starts.
#Tiers and capabilities
Capabilities are authoring, execution, desktop, cloud and managed-oncall. The default tiers are solo, team and managed: solo carries authoring and execution. team adds desktop. managed adds cloud and managed-oncall. The tier names, their capability sets and the lease and grace durations are operator-owned values, not fixed decisions.
The issuing side, runtime/licence-service.ts, holds the private key and is the platform's own administrative edge. A customer's Robot imports the verifier, which needs the public key and nothing else. Renewal is the customer's own explicit act through their tooling. Because verification is offline, a deactivated instance is not stopped instantly: it stops when its lease stops being renewed, which is why leases are short and renewBy exists.