Private package releases (Keygen registry)
LumenFlow 1.0 packages are distributed from an owner-operated, licence-gated
Keygen registry. pnpm release publishes from the owner’s machine through
the Keygen REST API: every tarball is verified byte-for-byte before upload,
uploaded to private storage, read back and compared, and only then published.
There is no npm publish, no hosted CI and no GitHub Packages; GitHub holds
source only.
Endpoints
Section titled “Endpoints”| Purpose | URL |
|---|---|
| Consumer npm registry | https://licensing.lumenflow.dev/v1/engines/npm/ |
| Keygen REST API (publisher) | https://licensing.lumenflow.dev/v1 |
| Private signed downloads | https://downloads.lumenflow.dev |
Every publishable @hellmai/lumenflow-* manifest declares
publishConfig.registry = the consumer registry and publishConfig.access =
restricted. Two manifests switch their registry declaration in their own
work units rather than here: @hellmai/lumenflow-cli (WU-4062, consumer
transport) and the surfaces package (WU-4058, source-leakage guard). Until
both land, those two manifests still name the previous registry; the
publisher never reads publishConfig.registry to choose a destination, so the
mismatch cannot redirect an upload.
Operator prerequisites (rc.1 rehearsal, WU-4066)
Section titled “Operator prerequisites (rc.1 rehearsal, WU-4066)”Before running pnpm release, confirm:
- A current consumer npmrc. Set
NPM_CONFIG_USERCONFIG(or pass--consumer-npmrc) to a licence npmrc minted from a current public-host free or paid grant. Older local-edge grants fail closed with401; mint a fresh grant rather than reusing a stale one — the release’s registry lineage probe reads through this credential. - The primary main checkout, not a worktree. The release CLI’s
ensureOnMainguard refuses any other branch or checkout by design. - A clean, freshly built
dist. Runturbo cleanthenbuild:distbefore relying on dist-derived output, or let the CLI’s own artifact-coherence check trigger the rebuild automatically. Stale compiled output blocks the run.
See the rc.1 release rehearsal verification report (WU-4066) for the full round-by-round history these prerequisites were discovered from.
Publish a release (owner)
Section titled “Publish a release (owner)”-
Point the release at the credential file (paths only; never export a token):
The file must be an owner-only regular file (mode
0600) whoseapiUrl,productIdandstorageOriginmatch the deployment. To point at another deployment, name a JSON config file withLUMENFLOW_KEYGEN_CONFIG_FILE(keys:apiUrl,storageOrigin,productId,credentialFile,journalDir,receiptDir).LUMENFLOW_KEYGEN_JOURNAL_DIRandLUMENFLOW_KEYGEN_RECEIPT_DIRoverride the two directories. -
Rehearse without uploading anything:
The full validation set runs in a disposable worktree. No Keygen upload, no pack-registry upload, and no credential is read. The version is checked against the Keygen channel model before anything else runs, so a version the registry would refuse (for example
1.0.0-preview.1) stops here with nothing modified. -
Publish:
1.0.0-rc.1goes to the rc channel;1.0.0goes to stable. The stable channel never receives a prerelease.--skip-publishbumps, validates and tags only: zero package uploads and zero pack-registry uploads, and no credential is read. -
Read the summary. For each package it prints an exact-archive receipt path (default
~/.lumenflow/keygen-receipts/publish-<artifactId>.json) holding the package, version, channel, Keygen ids,sha256,sha512andstoredBytesVerified: true.
The minify-and-mangle build stage (fail-closed)
Section titled “The minify-and-mangle build stage (fail-closed)”After every build:dist (on the dry-run and real paths alike, and even with
--skip-build) the release minifies every implementation .js/.mjs file
under each publishable package’s dist output and the CLI’s bundled packs,
before pack-integrity refresh, validation, consumer-install checks and
packing. There is no flag to skip it. The stage runs esbuild in process, per
file, with no bundling: format: 'esm', platform: 'node', target: 'node26', whitespace, syntax and identifier minification (keepNames: true
so the fn.name audit field two authorization paths read on survives),
charset: 'utf8', the shebang preserved verbatim, no source map,
legalComments: 'none'. esbuild is resolved from the workspace pnpm store
through vitest’s own dependency, so this stage adds no new root dependency.
An unresolvable esbuild or a per-file transform error aborts the release,
naming the file.
This replaces an earlier WU-4067 protected/bytecode build candidate that
ADR-122, Private Distribution Posture
(docs/09-architecture-decisions/ADR-122-private-distribution-posture.md)
rejected after its independent review found a confirmed defect on real
product modules and public decompilers for that artifact class. See that ADR
for the full decision record: the licence gate is the access control, not
the artifact; minification is readability friction, not a security boundary,
and is not represented as one anywhere in this guide.
No-upload guarantees
Section titled “No-upload guarantees”| Mode | Keygen package upload | Pack-registry upload | Credential read | Tag pushed |
|---|---|---|---|---|
--dry-run | none | none | no | no |
--skip-publish | none | none (read-only plan) | no | yes |
| default | verified archives | lagging packs only | owner-only file | yes |
Pushing the v* tag to GitHub cannot publish anything: the repository holds
source only, GitHub Actions is disabled for verification and publication, and
the former publish-on-tag workflow was removed by WU-4060. The only publication
route is the local pnpm release command on the owner’s machine.
What the publisher guarantees
Section titled “What the publisher guarantees”- The uploaded bytes are the packed bytes that were verified: no non-declaration
TypeScript, no
.mapfiles, no inlinedata:source maps, no__tests__entries, no links or unsafe paths, a complete gzip stream and tar end marker. - Uploads go only to a signed URL on the configured
httpsstorage host; redirects are never followed, and the API token is sent only to the API origin. - The stored object is downloaded and byte-compared before the release is
published. Keygen’s
UPLOADEDstatus alone is not trusted. - Re-running a release with identical bytes verifies the stored archive and creates no duplicate. Different bytes at an existing version are refused.
- An interrupted upload resumes the same draft from a protected local journal of signed upload links. Failed processing is reported for inspection, never deleted.
Grant access (owner)
Section titled “Grant access (owner)”Access is granted per recipient with the licensing service on the owner server:
Each grant writes a consumer.npmrc into its own protected directory. Share
only that recipient’s file, over your own secure channel. “Paid” is an
owner-assigned policy label: no signup, payment or billing automation exists.
Suspension blocks new downloads; it cannot remove copies that were already
installed or cached.
Install as a consumer
Section titled “Install as a consumer”Put the granted consumer.npmrc outside the repository and name it when
installing:
or export NPM_CONFIG_USERCONFIG=/secure/consumer.npmrc for the shell. The
release tooling’s own consumer checks (the packed-consumer smoke and the upgrade
matrix) use the same variable, or --consumer-npmrc <path>; they never read a
GitHub token. 401 or 403 means the credential is missing or wrong. A 404 for the
product package is accepted as “first release” only after the tooling has read
the packument of the published licensing fixture @hellmai/licensing-smoke-test
through the same registry URL and credential; if that fixture is unreachable
the run fails as a registry misconfiguration instead of silently skipping the
upgrade matrix.
Staging isolation during rehearsals
Section titled “Staging isolation during rehearsals”The release rehearsal publishes the workspace to an owned loopback registry
through the registry HTTP API after a real pnpm pack. It never spawns
pnpm publish, whose destination the manifest’s publishConfig.registry
controls, so the production registry cannot receive rehearsal traffic, and a
non-loopback staging registry is refused outright. “Loopback” is decided by
parsing the host as an IP address (127.0.0.0/8, ::1, localhost), so a
registrable name such as 127.attacker.example is refused. Prereleases are
staged under their channel dist-tag (rc, beta, …); only a stable version
is tagged latest, as on a real registry.
Troubleshooting
Section titled “Troubleshooting”| Message | Meaning and fix |
|---|---|
Registry authentication not found | No credential file configured; set LUMENFLOW_KEYGEN_CREDENTIAL_FILE or use --skip-publish. |
must be an owner-only regular file | chmod 0600 the credential file and make sure your user owns it. |
do not match the configured deployment | The file’s apiUrl/productId/storageOrigin differ from the resolved config; fix one side. |
Existing version has different archive bytes | The version is already published with other bytes; cut a new version, never replace. |
Unexpected private storage destination / redirect refused | Keygen offered a link off the configured storage host or the host redirected; inspect the deployment. |
Artifact processing failed; inspect the draft before retry | Look at the draft release in the portal; the publisher will not delete or replace it. |
Package processing timed out; retry resumes the same draft | Re-run; the journal resumes the same upload link while it is valid. |
the minify stage could not resolve esbuild from the workspace pnpm store | Run pnpm install and retry; esbuild ships as a vitest dependency, not a new root dependency. |
the minify stage failed to transform <file> | A per-file esbuild transform error; the release fails closed and names the file. |
cannot be published to the private Keygen registry | The version label is not rc/beta/alpha/dev.N or stable semver; nothing was modified. |
Registry misconfiguration: ... answered 404 for ... AND | The consumer-check registry URL or scope is wrong; the licensing fixture must be readable. |
Current limitations
Section titled “Current limitations”- Licence grants, renewals and revocations are manual owner actions.
- Distribution control is not code protection: a licensed recipient can read the minified JavaScript they install. ADR-122 accepts that boundary explicitly rather than claiming a stronger guarantee; obfuscation and compiled/bytecode routes were evaluated and rejected.
- The rc.1 licensed consumer proof (fresh free and paid installs, anonymous and revoked denial) is a separate verification against the live service; the publisher’s own tests use owned loopback fixtures.
- On Windows the credential-file mode check is skipped; keep the file private by other means.