Licensing

Offline-verified EdDSA license keys + cloud-issued entitlement snapshots that feed plugin-billing; pricing decided server-side, never baked into a published version.

@voltro/plugin-licensing lets a cloud decide a customer's plan entitlements and pricing, and enforces them in a running app — without baking anything into an immutable published version. The license is a compact EdDSA (Ed25519) token, verified offline with a public key the app embeds. It's asymmetric on purpose: the signing secret never leaves the cloud, so a customer cannot forge a license.

The plugin does not enforce quotas itself — it feeds plugin-billing, which meters and enforces them via a new resolveEntitlementLimit seam.

Wiring

// app.config.ts
import { licensingPlugin, entitlementResolver } from '@voltro/plugin-licensing'
import { billingPlugin } from '@voltro/plugin-billing'

export default {
  type: 'api' as const,
  name: 'api',
  plugins: [
    licensingPlugin(),                          // reads VOLTRO_LICENSE_* env
    billingPlugin({
      plans: { free: { entitlements: { projects: 1 } } },  // static fallback
      resolveEntitlementLimit: entitlementResolver,        // license overrides per-tenant
    }),
  ],
}

With a valid pro license granting { projects: 3 }, requireEntitlement(ctx, 'projects', 1) allows three and then fails EntitlementExceeded. With no license, the static free plan (projects: 1) applies — the resolver returns null and billing falls back to its own registry.

Keys

Generate a signing keypair:

voltro secret keypair
# VOLTRO_LICENSE_SIGNING_KEY  (Ed25519 private, cloud-only — signs licenses, NEVER ship)
# VOLTRO_LICENSE_PUBLIC_KEY   (Ed25519 public — ship to apps for offline verify)

Keep the private key in the cloud; the public key is safe to embed. The app reads VOLTRO_LICENSE_PUBLIC_KEY + VOLTRO_LICENSE_KEY (or the publicKey / licenseKey options).

Options

Option Env fallback Meaning
publicKey VOLTRO_LICENSE_PUBLIC_KEY Ed25519 public key (SPKI/PEM).
licenseKey VOLTRO_LICENSE_KEY The signed license token, verified at boot.
snapshotUrl URL to periodically re-fetch a fresh license (entitlement sync).
refreshMs Refresh cadence for snapshotUrl (default 15 min, cluster-coordinated).
graceSeconds Seconds an expired license is still honored (default 0).

API

  • entitlementResolver(tenantId, key) — pass to billingPlugin({ resolveEntitlementLimit }). Returns the license's limit for the pair, or null to fall back to the static plan registry.
  • LicenseServiceyield* LicenseService in a handler to read limitFor / hasFeature / allowsPlugin / edition / snapshot directly.
  • licenseAllowsPlugin(name) / requireLicensedPlugin(name) — the plugin gate ("installed ≠ billable"). A license carries a plugins: string[] grant; requireLicensedPlugin fails PluginNotLicensed when a plugin's production use isn't licensed. Metered plugin pricing uses entitlement keys (plugin:<name>:<meter>); plan-gated plugins use the grant. Pricing is decided server-side and travels in the signed license — no plugin_definitions table needed in the app.
  • verifyLicense(token, publicKeyPem) — offline EdDSA verify → LicenseClaims.
  • @voltro/plugin-licensing/signsignLicense(input, privateKeyPem) — server-only signer for the cloud/CLI to mint licenses.

Notes

  • Server-only (imports jose) — never import from a browser-loaded descriptor.
  • Fail-closed: an invalid/expired token grants nothing; billing falls back to the static plan. The last-good snapshot is honored within graceSeconds.
  • Cloud-side issuance (a license endpoint + voltro cloud register) ships in a later milestone; today licenses are provided via env / config.