{
  "schema": "leumas.docs.page/1",
  "id": "pkg:@leumas/licensing",
  "slug": "packages/licensing",
  "kind": "capabilities",
  "bucket": "package",
  "title": "@leumas/licensing",
  "name": "@leumas/licensing",
  "eyebrow": null,
  "chip": null,
  "summary": "Leumas licensing — Ed25519-signed OFFLINE entitlement tokens (LEV1.<payload>.<sig>), license-key mint/hash, the platform issuer service + routers, and the appliance client. The 'buy on leumas.tech →...",
  "keywords": [
    "licensing",
    "lev1",
    "ed25519-signed",
    "activate",
    "licensing runtime-paths",
    "licensing api",
    "leumas licensing",
    "issuer"
  ],
  "audience": "both",
  "funnel": {
    "product": null,
    "cta": null
  },
  "body": "# @leumas/licensing\n\nEd25519-signed **offline entitlement tokens** — the licensing seam between the Leumas-hosted\nplatform and self-hosted Studio appliances (the EXE). Lands the unit deferred in\n`ops/migration/imperium-hosting.json` (\"offline signed entitlement tokens\").\n\n## Built (M2 groundwork, verified)\n\n- `src/token.js` — `signEntitlementToken` / `verifyEntitlementToken` / `needsRefresh`.\n  Format `LEV1.<b64url payload>.<b64url sig>`; node:crypto only. Payload carries\n  `entitlement_policies`-shaped docs + a tenant `features` map + `iat/nbf/exp/refreshAfter`.\n- `src/keygen.js` — `LEUMAS-XXXX-XXXX-XXXX-XXXX` mint (Crockford base32), peppered sha256 storage\n  hash (apikeys discipline), `keyLast4`.\n- `scripts/generate-keypair.mjs <kid>` — public key → `keys/public-keys.json` (committed, ships in\n  the EXE); private key → `LEUMAS_LICENSE_SIGNING_KEY` on the platform ONLY.\n\n## To build (M2 remainder — see ops/migration/studio-desktop-licensing.json)\n\n- `src/service.js` — platform issuer: issue-on-membership (subscribe to auth's membership events),\n  `activate` (key → activation row + signed token), `refresh` (heartbeat + revocation check),\n  `deactivate`, seat/activation limits. Tables: `licenses` + `license_activations` in\n  `@leumas/schemas` (real Drizzle tables — money-adjacent).\n- `src/client.js` — appliance: persists `license.json` under `@leumas/runtime-paths` dataRoot,\n  daily refresh with offline grace (token stays valid until `exp`), degrades to a banner —\n  NEVER bricks local data; `lastSeenWallClock` clock-tamper basic; `onChange` →\n  `entitlements.refresh()`.\n- `routers/platform.router.js` (`/licensing/*`, platform role only) +\n  `routers/appliance.router.js` (replaces the `/api/license` stub in\n  `products/leumas-api/src/routes/activation.js` behind the SAME route shapes).\n\n## Desktop sign-in — the key-free door\n\n`POST /licensing/desktop/signin` (platform) is what lets the Studio EXE's first run ask for a\n**leumas.tech email and password** instead of a licence key.\n\n```\nEXE first run ──-> POST /api/license/signin (appliance router)\n └-> client.signIn()\n └-> POST /licensing/desktop/signin (platform router)\n                                ├ verifyCredentials()          ← injected usersService.login\n                                └ service.signInDesktop()       → {token, license, licenseKey}\n <-─ appliance: provisionFederatedUser() + startSession() → session cookie\n```\n\nThree rules hold it together:\n\n- **The password is checked by the platform, and never travels further.** `@leumas/licensing` has\n  no `@leumas/auth`; the host injects `verifyCredentials`. Every failure — wrong password, unknown\n  address, deactivated account — comes back as one indistinguishable 401, so the endpoint is not an\n  account-enumeration oracle. It sits behind the same 10-per-15-min limiter as key activation.\n- **One licence per DEVICE, key rotated on every sign-in.** The appliance needs a plaintext key to\n  run its heartbeat and the platform stores only a hash, so a device signing in again is handed a\n  NEW key rather than one nobody can recover. Rotation touches only that device's row; a shared row\n  with a rotating key would cut off every other install the customer owns.\n- **After the save, the state on disk is identical to a key activation.** Same `licenseKey`, same\n  `instanceId`, same token — so `refresh`, `deactivate`, revocation, offline grace and the relay have\n  exactly one code path, not two.\n\nSign-in is offered only when the appliance has a central server (`LEUMAS_LICENSE_SERVER`). Without\none there is no account to check, so `/api/license/signin` answers 501 and `GET /api/license/status`\nreports `signIn: false` — which is what the first-run wizard reads to choose its flow. It never\nguesses.\n\n## The trust split (do not change casually)\n\nThe **policy ceiling is signed by Leumas** (the appliance's entitlements engine loads policies\nfrom the token, never from its own DB); **membership assignment stays local** (the customer's\nadmin grants plans to their team via the existing offline `grant` path in @leumas/auth).\n\nDesktop sign-in does not bend that split. It proves WHO centrally and mirrors that identity into a\nlocal row (`provisionFederatedUser`, admin only if it is the first account); what that person may do\non the box is still local RBAC under a Leumas-signed ceiling.\n",
  "source": {
    "path": "shared/packages/licensing/README.md",
    "blobSha": "",
    "commit": "",
    "committedAt": "",
    "provenance": "no-git",
    "bytes": 4724,
    "hash": "0f49212fc29f9a65c48d48542e942b89ca33621a"
  },
  "urls": {
    "html": "/p/packages/licensing",
    "json": "/docs/packages/licensing.json",
    "md": "/docs/packages/licensing.md"
  },
  "links": {
    "composes": [
      "pkg:@leumas/db",
      "pkg:@leumas/schemas"
    ],
    "usedBy": [
      "pkg:@leumas/imperium-server"
    ],
    "product": [],
    "howTo": [
      "how-to:surveillance-enterprise"
    ],
    "skills": []
  },
  "exports": {
    "total": 26,
    "component": 2,
    "hook": 0,
    "helper": 24,
    "names": [
      {
        "n": "RELAY_COLLECTION",
        "k": "component"
      },
      {
        "n": "RELEASE_CHANNELS",
        "k": "component"
      },
      {
        "n": "createInstanceAuth",
        "k": "helper"
      },
      {
        "n": "createLicenseClient",
        "k": "helper"
      },
      {
        "n": "createLicenseStatusRouter",
        "k": "helper"
      },
      {
        "n": "createLicensingRouter",
        "k": "helper"
      },
      {
        "n": "createLicensingService",
        "k": "helper"
      },
      {
        "n": "createRelay",
        "k": "helper"
      },
      {
        "n": "createStudioLink",
        "k": "helper"
      },
      {
        "n": "createUpdatesRouter",
        "k": "helper"
      },
      {
        "n": "hashLicenseKey",
        "k": "helper"
      },
      {
        "n": "hashLicenseKey",
        "k": "helper"
      },
      {
        "n": "keyLast4",
        "k": "helper"
      },
      {
        "n": "keyLast4",
        "k": "helper"
      },
      {
        "n": "loadPublicKeys",
        "k": "helper"
      },
      {
        "n": "mintLicenseKey",
        "k": "helper"
      },
      {
        "n": "mintLicenseKey",
        "k": "helper"
      },
      {
        "n": "needsRefresh",
        "k": "helper"
      },
      {
        "n": "needsRefresh",
        "k": "helper"
      },
      {
        "n": "normalizeLicenseKey",
        "k": "helper"
      },
      {
        "n": "normalizeLicenseKey",
        "k": "helper"
      },
      {
        "n": "resolveInstancePublicKeys",
        "k": "helper"
      },
      {
        "n": "signEntitlementToken",
        "k": "helper"
      },
      {
        "n": "signEntitlementToken",
        "k": "helper"
      },
      {
        "n": "verifyEntitlementToken",
        "k": "helper"
      },
      {
        "n": "verifyEntitlementToken",
        "k": "helper"
      }
    ]
  }
}
