Runtime licence check
Downloading LumenFlow requires a licence grant for the private licence registry (see Private distribution). That controls who can get the product at all — it does not, by itself, stop an already installed copy from running after the grant is revoked. The runtime licence check closes that gap: at start, before any gated command runs, the CLI verifies a cached, cryptographically signed licence file before trusting anything in it.
Obtaining the licence automatically
Section titled “Obtaining the licence automatically”Nobody hand-delivers a licence.lic file. The first gated command with no
cached licence file obtains one itself, from the same registry sign-in you
already have for the private npm registry — read directly from your npm
user configuration, never by spawning npm. Two sign-in forms work:
- A licence key (issued with your licence): the CLI resolves the licence id and checks out a fresh signed file directly.
- A user token (issued with your licence): the CLI first lists your own licences with that token, picks the active one for LumenFlow, then checks out the same way.
The obtained file is verified — the exact same signature-then-product check below — before it is ever written to the cache; a bad response is never written. If no sign-in is found, the refusal names the remedy: sign in to the private registry as the install guide describes.
Accepting the installed licence terms
Section titled “Accepting the installed licence terms”Before checking the signed licence file, a gated command reads the complete
LICENSE shipped with the installed CLI package. It resolves that file from
the running module’s package location on Windows, macOS, and Linux; your
working directory, project LICENSE, and licence configuration cannot
supply replacement terms.
The CLI presents the notice and exact installed terms path before accepting.
Type accept (or y/yes) at the prompt, or use the existing
LUMENFLOW_EULA_ACCEPTED=1 / true environment flow for CI and other
non-interactive hosts. The local eula-acceptance.json, beside your licence
cache, keeps the acceptance timestamp and SHA-256 hash of every byte of
those full terms. Matching records pass silently; a terms-only change or an
old short-notice hash requires acceptance again.
Missing or unreadable installed terms refuse before a prompt or record
write, even when environment acceptance is set or a prior record exists.
Reinstall the CLI with its full LICENSE to repair the installation.
Help and lumenflow:doctor remain available: doctor reports accepted,
not accepted, or terms unavailable against the same shipped full-terms hash.
This changes no licence restriction. The local record and installed files remain editable by their recipient; the hash does not authenticate legal identity or make local acceptance tamper-resistant.
How verification works
Section titled “How verification works”- Read the cached signed licence file from the per-OS location below. Missing → attempt automatic activation (above); still missing after that → refused, with the exact path and the sign-in remedy.
- Parse the signed licence file envelope. A malformed file (missing markers, bad base64/JSON, a missing field) is refused.
- Verify the Ed25519 signature against the configured public key. No field of the file is read or trusted before this succeeds — an unsigned, wrongly signed, or tampered file is refused at this step, before expiry or entitlement are ever inspected.
- Check the licence’s product, expiry, and revocation status.
A valid, fresh file passes with no network call. Only once the cache becomes stale does the CLI check out a fresh signed file (using the licence key already inside the cached file — never re-reading your npm configuration) to renew it — never on every start, and never once the offline grace window has already lapsed (that state is already a final refusal either way).
A network response can only DENY, never GRANT. That call only ever runs from a state the offline check has already allowed; it can mark that state revoked, or supply a fresh file that must independently pass the same signature-then-product verification every cached file does — it cannot grant access beyond what a signature-verified licence file already establishes. No licence key, password, user token, or certificate content ever appears in any output.
Offline grace window
Section titled “Offline grace window”A licence does not fail the instant its file goes stale. Two dates come from
the signed file itself: the licence’s own expiry, and the cached file’s
server-issued freshness window. Once past the earlier of the two, the
licence is expiring and later expired-but-still-allowed for a bounded
grace period (14 days by default, build-embedded) — long enough to tolerate a
machine that is briefly offline. Only after the grace period elapses does
the command refuse outright.
Both the grace deadline and the dates it is computed from live inside the signed file, so editing the file cannot push the deadline later — doing so breaks the signature and the file is refused at step 3 above. A small local record of the latest observed time, kept next to the licence cache, also detects a clock moved backwards between runs. Local checks cannot fully protect against a recipient who controls their own machine; see below.
Failure messages
Section titled “Failure messages”Every refusal states what is wrong and how to fix it — for example, an expired licence past its grace window names where to place a refreshed licence file and to contact the licence issuer. No refusal leaks internals or key material. A broken local licence configuration file, a missing licence, a bad signature, and a lapsed grace window are all reported this way; none of them crash the CLI.
Which commands are gated
Section titled “Which commands are gated”The check runs once, from the CLI’s single shared entry wrapper, so it applies uniformly rather than command-by-command.
| Command | Behaviour without a valid licence |
|---|---|
--help / -h | Always shown, for every command — unaffected by licence state. |
lumenflow:doctor | Always runs (exiting 1, advisory, when no licence is cached). Reports the licence state and cache location so an unlicensed or expired user can self-diagnose without being locked out. |
| Every other command | Refused with the reason and the remedy; exits non-zero. |
Every installed copy of the CLI is gated, including copies in project dependencies and global installs.
Licence state in lumenflow:doctor
Section titled “Licence state in lumenflow:doctor”The section also validates the registry credential the package manager sends for
the licensing registry against the
registry itself, because the cached licence can be valid while that credential has expired. It
prints the expiry date when known, warns within 14 days (urgently within 3), and fails when the
registry reports LICENSE_EXPIRED, LICENSE_SUSPENDED or ACCESS_DENIED. See
diagnose and renew an expired registry credential.
Reported states: valid, expiring, expired, revoked, missing, and
invalid (a structurally bad, tampered, or wrong-product file). This
section is advisory in doctor — the runtime gate above is the actual
enforcement point.
Cache location (per platform)
Section titled “Cache location (per platform)”| Platform | Location |
|---|---|
| Windows | %APPDATA%\lumenflow\licence.lic |
| macOS | ~/Library/Application Support/lumenflow/licence.lic |
| Linux | $XDG_CONFIG_HOME/lumenflow/licence.lic (falls back to ~/.config/lumenflow/licence.lic) |
The cache location, the product id, and the pre-expiry warning window are
configurable through a JSON config file named by
LUMENFLOW_LICENCE_CONFIG_FILE, or a direct override of the licence file
path via LUMENFLOW_LICENCE_FILE. The verification public key, the licence service endpoint, and the offline grace window are not configurable. They are fixed, build-embedded values.
Keep the licence file outside your own project’s repository. Every
default location above already is. If you override it (via the config file
or LUMENFLOW_LICENCE_FILE) to a path inside your repository’s working
tree, the licence gate’s own stale-cache refresh rewrites that file in
place on later runs — leaving your repository dirty and able to fail any
clean-tree check your own workflow runs (a CI step, a release gate). lumenflow:doctor
warns when the resolved licence file path is inside the current
repository’s working tree.
What this does not protect against
Section titled “What this does not protect against”- Reading the shipped JavaScript. Unchanged from Private distribution: the licence gate and the EULA are the controls, not the artifact’s readability.
- Erasing an already-installed copy. Revocation stops a copy from passing this check on its next stale-cache refresh or once its grace window lapses; it does not reach into a running process or delete files.
- Local state on the recipient’s own machine. A recipient who controls that machine can alter any local record, as with any other local file.
There is no telemetry and no phone-home on every start — the only network calls this check ever makes are the bounded checkout calls described above (first-run activation when no cache exists, or renewal once the cache is already stale).