Licence
The licence is an offline signed file verified on the control plane at boot and scheduler ticks. It makes no call to a licence server.
Reference · Updated 2026-10-17 · Describes release 0.1.164
Evidence reviewed 8 October 2026; operating baseline 0.1.130 (the 16 October 2026 handover build; see the release notes).
The licence is an offline signed file verified on the control plane at boot and scheduler ticks. It makes no call to a licence server. Establish whether this installation uses Mode A or Mode B before diagnosing blocked runs.
Mode A and Mode B
| Mode | Configuration | Result |
|---|---|---|
| A — no enforcement | No licence token, or no public verification key | unlicensed; Ops says no enforcement; runs continue with a boot warning |
| B — signed licence | Matching licence.publicKey and licence token supplied together |
Claims and signature are verified; expiry and invalid-state enforcement apply |
The published deployment procedure records no production signing key issued yet and Mode A for existing installations. Verification code and the deployment slot are delivered; production issuance and its approval job are not proof of a live Mode B installation. A development-key rehearsal is not a production licence.
Install or renew
For the managed pipeline, the secrets file remains the source of truth. The release/licence owner updates AIOPS_LICENSE there, updates the Azure DevOps group, and deploys with the current live image tag. The values file on the customer branch carries licence.publicKey. The pipeline passes the token through licence.value to the control plane.
The update command comes from deployment runbook §11. Set AIOPS_SLUG to the approved installation slug; ADO_PAT must already be supplied securely in the operator's environment and must not be printed.
AIOPS_SLUG=example-org
python3 scripts/customer-onboard.py ado "$AIOPS_SLUG" --update-group
That command writes every group value from the file; a value changed only in the Azure DevOps Library will be overwritten. Queue the normal approved deployment with the current image/chart. There is no upload or activation endpoint; renewal needs a rollout. Confirm the resulting licence card and licence.state_changed audit evidence. The scheduler picks up the state within a minute of startup.
For a chart using top-level existingSecret, put the licence in that Secret or licence.existingSecret; inline licence.value has nowhere to land and the chart refuses that combination. The licence reference defines the supported Secret keys.
Expiry and failure behaviour
| State | Operator sees | Effect |
|---|---|---|
valid |
Licence card | Normal runs |
grace |
Expired banner and grace days remaining | Warning only; runs continue. A token with grace_days: 0 has no grace |
expired |
Red banner | New agent spend and approved executions refuse to start |
invalid |
Red banner with reason | Same block as expired; check signature, claims, time and key/token match |
unlicensed |
No-enforcement card | Runs continue |
The default grace is 30 days unless the signed file overrides it. The verifier allows 300 seconds clock skew; keep system time correct rather than using clock changes as a renewal procedure.
| Surface after expiry/invalid state | Behaviour |
|---|---|
| Alert intake | Alerts still ingest/correlate; a blocked investigation fails and escalates the incident to a human |
| New chat, discovery and agent API | Refused before start; queued remote discovery can fail; a running chat stops at its next step |
| Approved change release and automatic execution | Refused (licence_blocked, API 409); rollback and ticket routes remain available |
| Investigation or scan already past its first step | Can finish; API-mode resumes are not rechecked, while embedded/database-mode resumes pass the start guard again |
| Login, portal, Ops, history, audit/RCA exports and reports | Continue to work; boot is never blocked |
Runner processes need no public key or licence token. The control plane publishes the verdict for database-mode runners and enforces it when handing jobs to API-mode runners. State changes land in the platform audit chain, not a workspace export.
Renewals and key changes to track
Keep expiry, grace, token id and renewal owner in the operating calendar. When the vendor changes the public signing key, install the new public key and a token signed by it in the same rollout. The verifier accepts one key; an old token paired with the new key is invalid. Keep licence renewal separate from tenant secret-store encryption-key rotation.
Sources: Offline licence reference, and Deployment runbook §11.
The file
A compact JWT. Header {"alg":"EdDSA","typ":"JWT"}; the Ed25519 signature is the raw 64 bytes, used directly as the JWS signature. Claims:
| Claim | Meaning |
|---|---|
iss |
a fixed issuer string: the ISSUER constant in app/aiops/licence.py, which scripts/issue-licence.py also writes |
sub |
the customer slug the file was issued to |
aud |
always aiops |
iat, nbf, exp |
issued / not-before / expiry, epoch seconds |
jti |
unique id of this file |
features |
list of enterprise feature keys; empty = all |
max_workspaces |
integer ceiling or null; informational, shown on the ops console, not enforced |
grace_days |
optional; overrides the platform default (AIOPS_LICENSE_GRACE_DAYS, 30) |
Verification requires iss, sub, aud, iat, exp, jti, checks issuer and audience, accepts 300 s of clock skew on exp/nbf/iat, and rejects an iat further in the future than that (a tampered clock). Only EdDSA is accepted — a token signed with any other algorithm is invalid.
Install
Helm (deploy/aiops-helm/values.yaml):
licence:
publicKey: |
-----BEGIN PUBLIC KEY-----
...the vendor PEM published with the release...
-----END PUBLIC KEY-----
# either inline (lands in the chart Secret as AIOPS_LICENSE) ...
value: "eyJhbGciOiJFZERTQSIs..."
# ... or a pre-made Secret (External Secrets, sealed-secrets), key AIOPS_LICENSE
existingSecret: ""
existingSecretKey: AIOPS_LICENSE
The pod receives AIOPS_LICENSE and AIOPS_LICENSE_PUBLIC_KEY; the app also accepts a PEM whose newlines arrive as literal \n. Any other deployment (compose, bare uvicorn) sets the same two environment variables. With the chart's top-level existingSecret in use, licence.value has nowhere to land and the chart refuses to render — put AIOPS_LICENSE in that Secret or use licence.existingSecret.
Only the control plane needs the two variables. Runner processes (aiops-agent-helm, compose agents profile) carry no key: the control plane publishes its verdict to PlatformSetting key licence at boot and on every state change, and a key-less process's run guard reads that row instead of its own (always unlicensed) cache. api-mode runners never see the guard at all — the control plane enforces it when it hands out the job.
Renewal is a new value (or a new Secret) and a rollout. There is no renewal ceremony, no activation, no upload endpoint; the scheduler tick picks the new state up within a minute of the pod starting.
What it does not do
- No phone-home. The control plane never contacts the vendor. Usage evidence for the contract is what
GET /admin/overviewand the audit export already show; nothing is transmitted. - No anti-tamper. The image is readable and the check lives in one Python module; anyone willing to patch it and rebuild can remove it, as with GitLab, Grafana Enterprise or Vault. The deterrents are the contract, revocable pull access to the image registry and the fact that the value is in updates, skills and support.
- No revocation before
exp. Verification is offline; the pre-expiry levers are the registry policy and the deploy pipeline. - No feature or seat enforcement.
featuresandmax_workspacesare surfaced on the ops console; the platform does not yet gate on them. - No licence server, heartbeat or usage report. Air-gap installs work with the same file for the same reason.