- 2026-07-24-licensing-core.md: 7 tasks. lk payload, offline verify, the trusted key slice, the noSign build tag, and lkctl for issuing by hand. - 2026-07-24-instance-licensing.md: 10 tasks. Licence on the instance document, cached runtime state, deny-by-default mutation gate, feature gates, service-layer limits, settings UI, and migration 0005 to grandfather existing cloud instances. Plan 2 opens by finishing the Org to Instance rename: 18 private identifiers survived plan 0b's sweep. Nothing functional, but the file that gains the licence cache is one of the two still carrying the old names. Spec index updated with plan links and shipped status.
Vantage Licensing Programme — Spec Index
Seven specs, designed 2026-07-24. Build in this order.
| # | Spec | Plan | Status |
|---|---|---|---|
| 0a | shared-module | plan | shipped |
| 0b | instance-rename | plan | shipped, migration verified on live |
| 1 | licensing-core | plan | planned |
| 2 | instance-licensing | plan | planned |
| 3 | admin-backend | — | blocks 4 and 5 |
| 4 | admin-site | — | needs 3 |
| 5 | paddle-billing | — | needs 3 |
Specs 1 and 2 together give working licensing with licences cut by hand with
lkctl — no admin service needed. 4 and 5 can run in parallel once 3 lands.
4 and 5 can run in parallel once 3 lands.
The shape
Account (admin only)
├── Instance 1 cloud vantage.hostxtra.co.uk/<slug> licence auto-injected
├── Instance 2 cloud licence auto-injected
└── Instance 3 self-hosted customer's own deployment licence pasted by hand
The control plane knows only Instance. Accounts exist solely in the admin service, because a self-hosted instance has no row in the cloud database at all.
Decisions that everything else follows from
Licences are offline-verified signed blobs. ed25519 via
github.com/hyperboloide/lk, public key compiled into the server, no phone-home
anywhere. This buys air-gapped self-hosting and means no Vantage instance ever
depends on the licensing service being up. It costs revocation: a licence is
valid until it expires whatever Paddle later says. Self Hosted is annual-only to
bound that window.
Every licence is bound to one instance UUID. Self-hosted customers link their UUID before the licence is signed, so there is no unbound licence and no claim protocol.
Expiry degrades, it does not break. Monitors keep executing, alerts keep firing, agents keep their keys, in-flight workflow runs finish. Mutations stop. Deletes and OS-update application stay open so a customer is never trapped over-limit or unpatched.
Tiers are data, not code. The server reads Limits and Features and never
branches on tier name. Tier contents live in the admin plans table and are
snapshotted into each issued licence, so editing a plan never rewrites history —
the same rule as workflow_runs.steps_snapshot.
| Free | Professional | Self Hosted | |
|---|---|---|---|
| deployment | cloud only | cloud | self-hosted |
| max servers | 3 | unlimited | unlimited |
| max secret groups | 1 | unlimited | unlimited |
| max channels | 1 | unlimited | unlimited |
| console | no | yes | yes |
| OIDC | no | yes | yes |
| term | monthly, £0 | monthly or annual | annual only |
Free is cloud-only by construction: it is only ever signed with
deployment: "cloud", and verification rejects a deployment mismatch. There is
no server-side flag to edit. One Free instance per account.
Existing cloud tenants are grandfathered to Professional, one year out, by
migration 0005.