Entitlement  how it actually works

There is no function that says stop this machine.

Licensing in KLYRN VM is a signed file read offline. It decides one thing: how many compute nodes may be enrolled. It is built so that nothing else can ever be decided from it. A lapsed commercial relationship between KLYRN and a hosting provider is not something the provider's customers are party to, so it does not reach their machines.

0
functions in internal/licence that can stop, pause or suspend a guest. The absence is the guarantee.
1
refusal in the entire feature: enrolling one more compute node. Nothing that exists is ever refused.
1 node
free allowance with no licence at all, running as many machines as its hardware allows.
14 days
default grace after expiry, during which the allowance and every operation are unchanged.

The rule is written at the top of the file that would have to break it.

The rule that outranks every other rule here: nothing in this package can stop a virtual machine. Not an expiry, not a revocation, not an unreachable licence service, not a document that fails to parse. A hosting provider's customers are not party to our commercial relationship with the provider, and a machine somebody is running their business on does not go dark because a certificate lapsed. The worst this package may ever do is refuse to create something new, and say why in a sentence a person can act on. There is deliberately no function here that returns "stop this machine", so no future caller can find one.

Package comment, vm/internal/licence/licence.go

That is not a promise about intent, it is a statement about the surface area. Everything the rest of the controller ever asks this package is: what is the current Status, is a named feature entitled, may one more node be enrolled, and please store this new document. A caller that wanted to power a guest off on an expiry would have to add the function first, and the diff that added it is the thing a reviewer is looking for.

Two tests hold it. TestNothingHereCanStopAMachine loads a licence that expired a year ago, past its grace, and asserts the free node is still granted and the operator is still told what is wrong. TestAnExpiredLicenceNeverTouchesARunningMachine goes through the HTTP API as a tenant and reads, starts, shuts down, reboots, lists tasks for and finally deletes a machine under that same expired licence: every one of them accepted.

Five states, and what each one actually does.

The allowance column is what may be enrolled. In every row without exception, every machine that already exists can be started, stopped, rebooted, consoled, backed up and deleted. The message column is the sentence the panel and klyrn-vm doctor show, verbatim.

  • none

    No licence file on disk. A fresh install, and a normal one.

    1 node

    “No licence. The free allowance is 1 compute node; every machine on it runs normally.” not flagged as a problem

  • active

    Verified against a key compiled into the binary, and unexpired.

    n nodes

    Nothing. There is no message, because there is nothing for anyone to do. no problem

  • grace

    Past expires_at, inside grace_days.

    n nodes

    “This licence expired and is inside its grace period. Everything works; renew before the grace period ends.” flagged, allowance unchanged

  • expired

    Past expiry and past grace.

    1 node

    “This licence has expired. Every existing machine keeps running and can be started, stopped, consoled and backed up as usual; new compute nodes beyond 1 are refused until it is renewed.” flagged

  • invalid

    Does not parse, does not verify, names another product, or names another installation.

    1 node

    The specific reason, not a code: for example “the licence signature does not verify against a key this build trusts”, or “this licence is for "klyrn-hosting", not for KLYRN VM”. flagged

Invalid and none behave identically. They are reported differently because one of them is somebody's afternoon to fix and the other is not, but neither costs an operator anything they were already running. An installation whose licence file was mangled by a config management tool keeps its free node and keeps every guest.

The only refusal, and the sentence it comes with.

CheckNewNode is called twice: before a bootstrap token is minted, and again before enrolment completes, because a token minted while there was room must not still be redeemable after the room is gone. It returns HTTP 403 with a hint attached that reads “every machine you already run is unaffected”. The message itself depends on why:

  • none or invalid

    “the free allowance is 1 compute node and 1 are already enrolled. Add a KLYRN VM licence to enrol more.”

  • expired

    “this licence has expired, so the free allowance of 1 compute node applies and 1 are already enrolled. Every existing machine keeps running; renew the licence to add nodes again.”

  • active or grace

    “this licence covers 4 compute nodes and 4 are already enrolled.”

The commercial unit is the node, and the document holds no VM count at all (not a large one, none), so that no later change can introduce one by accident. A licensed node runs as many machines as its hardware and the provider's own policy allow. klyrn-vm doctor warns before the wall rather than at it: when the last covered node is in use it says “the next compute node will be refused… because enrolment is when you find out”, and it warns at thirty days out because “an expired licence refuses only a new compute node, but that is the day you wanted to add one”.

The artefact itself.

A licence is one file at /etc/klyrn-vm/licence.json. It is a detached signature over an embedded JSON document, and it is read offline: verification needs no network, no callback and no licence server being up. Below is an example: the installation id, the customer and the signature are invented.

{
  "document": {"product":"klyrn-vm","install_id":"12e3edb44846ebb7ad2300aa94e7912a","plan":"pro","nodes":4,"features":["backups"],"issued_at":"2026-09-11T09:14:02.481293841Z","expires_at":"2027-09-11T09:14:02.481293841Z","grace_days":14,"customer":"Example Hosting"},
  "signature": "3iQ7bN… 88 base64 characters …vA=="
}
The document is stored compact on one line because that is the exact byte string the signature covers. install_id binds it to one installation, so a document that names no installation cannot be minted at all: it would verify everywhere.

Reformatting it is fine

The signature is over the canonical compact form, not the bytes as they sit on disk. Config management rewrites files, editors reindent on save, support processes pretty-print. None of that changes what a licence says, so none of it may invalidate one: a provider must not lose their entitlement to a stray text editor. Compaction never touches the inside of a string, so it removes formatting and nothing else.

Found the hard way. The round-trip test failed on its first run: MarshalIndent re-indents an embedded raw message, so every licence the first version of the CLI produced was unverifiable.

Changing it is not

That tolerance stops exactly one byte past whitespace. The test edits "nodes": 4 to "nodes": 40 in a real minted licence and asserts the result reads as invalid, and then asserts the thing that matters more: the tampered installation still has its free node, and CheckNewNode(0) still returns nil.

Tampering costs a would-be cheat their inflated allowance. It does not cost them the node they were entitled to anyway, and it does not cost their customers anything at all.

Two keys, because they sign two different kinds of claim.

Release signer software

Signs the release manifest. Anyone holding it can publish a binary that every controller and node in the fleet will install.

Entitlement signer commerce

Signs licence documents. Anyone holding it can mint an entitlement. It can publish nothing.

They must never be the same key, and that is checked by the release gate rather than remembered: signers_are_two_different_keys compares the two public halves and fails the release if they match. A single key would mean anyone who can mint a licence can also publish a binary. KLYRN VM's release key is in turn separate from KLYRN Hosting's, and a KLYRN Hosting licence never activates KLYRN VM: a document naming another product reads as invalid and the free allowance applies.

A build with no entitlement key verifies nothing. It logs “this build embeds no licence signer; the free allowance applies”, and then serves every machine on the installation normally. That is the direction this ought to fail in: a binary built without a key keeps a provider's customers running rather than locking out the provider.

The operator's side, in three steps.

Find the installation id

A licence is a claim about one installation. The controller prints its own id:

klyrn-vm admin status

It is also on the settings endpoint as install_id. Run as root on the controller.

Mint it, on the release machine

This runs where the entitlement seed lives, never on a customer server.

klyrn-vm release entitlement \
  --key ../secrets/vm-entitlement-dev.key \
  --install-id 12e3edb44846ebb7ad2300aa94e7912a \
  --plan pro --nodes 4 --days 365 --grace 14 \
  --customer "Example Hosting" --out licence.json

--key and --install-id are required; the rest default to pro, one node, 365 days, 14 days of grace and licence.json. --features takes a comma-separated list. It prints the plan, the node count, the expiry date, the grace window and the first eight characters of the signing key's public half.

Install it

Either through the API, as an admin:

POST /api/v1/licence     # the signed file as the body
GET  /api/v1/licence     # the current state, plan, allowance and nodes in use

Or drop the file at /etc/klyrn-vm/licence.json. The API path verifies the document before storing it, using a temporary file, so a licence that would not load never replaces one that does, and the install is written to the audit log with the plan, state and node count it produced.