Installing and upgrading with a LumenFlow licence
LumenFlow packages use a private licence registry. Your licence grant states its URL
(shown below as <registry-url>). Licences are issued free or paid. Consumers need no GitHub account, repository access or GitHub seat. GitHub stores the
project’s source; it is not the package registry or release runner.
This page retains its former migration URL so existing links keep working.
Configure package access
Section titled “Configure package access”Your licence grant supplies the registry URL and the credential for a private consumer .npmrc. Keep the credentials in your
user npm configuration or an external private file selected with NPM_CONFIG_USERCONFIG.
Do not commit licence keys, Base64 values or API tokens. Base64 is encoding, not encryption.
The project .npmrc may contain this non-secret routing line:
The private consumer configuration uses a licence key:
A token with package read access, issued with your grant, can instead use
//<registry-host>/:_authToken=<token> in the private configuration. Use the authentication
method supplied with your grant; do not combine both methods.
Only @hellmai uses this registry. Other dependencies keep their existing registry.
The CLI lets npm resolve project, user and environment configuration on Windows, macOS and Linux.
For isolated installs it temporarily copies project configuration with private file permissions,
retains user/environment configuration, and restores the isolated file before committing.
Verify that your licence can read actual package metadata:
The result is the newest release authorized for your licence and may be a prerelease. If the licence issuer
supplies an exact release version, use that version when installing. npm whoami is not an access
check for this service.
Install in a new project
Section titled “Install in a new project”After configuring access, allow the CLI’s native SQLite dependency to build. With pnpm 10.26
or newer, merge this into the project’s root pnpm-workspace.yaml, keeping existing settings:
On pnpm 10.0–10.25, add better-sqlite3 to the existing onlyBuiltDependencies list instead.
Do not configure both forms; when migrating to allowBuilds, retain existing approvals as
true entries. See pnpm’s build settings.
Then install the CLI and initialize the project:
lumenflow init writes this approval for you when the project’s package.json has no
packageManager field and no npm, yarn or bun lockfile (such a project installs with pnpm), merging
into any existing allowBuilds entry. npx lumenflow init <options> accepts the same options as
npx lumenflow <options>.
If an earlier install skipped the SQLite build, configure it above and run
pnpm rebuild better-sqlite3 before initialization.
Packages contain distribution artifacts. Access controls keep the source repository private, but installed JavaScript can still be inspected. Revoking a licence prevents future downloads; it does not erase an existing installation or prevent someone copying files already downloaded.
Upgrade an existing project
Section titled “Upgrade an existing project”From the project’s main checkout:
Default and --latest upgrades resolve a concrete version from entitled package metadata before
mutation. Missing routing, denied access or unavailable metadata stops the real upgrade with
remediation. The command does not try another registry. A dry run may show an unresolved preview
and an access warning; it does not bootstrap, install, rewrite or migrate state.
Preview managed documentation before upgrading
Section titled “Preview managed documentation before upgrading”When the executing CLI matches the target release, --dry-run lists each managed
file the real docs sync would write, with new file or template change as its
reason. Unchanged and stamp-only files are omitted. The list includes core docs,
layout-dependent architecture references, scaffold-like onboarding and reserved
canonical skills. Existing LUMENFLOW.local.md additions and project-owned tool
adapters stay untouched. A first-upgrade backup into an absent local additions
file is labeled separately as user-content preservation.
Reserved skills are always planned under .lumenflow/skills, whatever a legacy
skillsDir setting says. When that setting names another path, such as a
tool-specific skills directory, the preview prints a migration note instead of a
write: the files there are project-owned snapshots, their bytes are preserved,
and nothing is created or changed in that directory. Set skillsDir to
.lumenflow/skills or remove it to clear the note.
The output also prints whether those managed-document writes change the trusted
claim policy content digest, its before/after values, and the responsible files.
It uses the same digest function as claims. Only workspace.yaml, LUMENFLOW.md
and LUMENFLOW.local.md enter that digest; a constraints or skill rewrite alone
has no digest effect. This document preview holds the current workspace policy
fixed. The final trusted claim policy digest is also computed when the shared
workspace planners establish no effective policy rewrite, including an existing
builtin pack whose version/integrity alone changes. Unprojected workspace effects
instead make the final verdict explicitly unavailable. Adding a pack or
changing its source from local to builtin can change the digest with no document
changes; these cases are reported as unavailable. A same-version upgrade that short-circuits reports no document writes and
an unchanged final digest; --force previews the sync instead. The real upgrade
still reads trusted origin policy before and after publication for its live-claim
notice.
A dry run never installs or bootstraps a target CLI. If that target’s templates
are unavailable because the executing release differs, the output explicitly
reports the file prediction and digest verdict as unavailable. Invoke the exact
target CLI’s upgrade entry with --dry-run to preview its templates. Unresolved
target versions and unreadable policy inputs never produce an unchanged verdict.
Legacy @lumenflow/<name> dependencies migrate to @hellmai/lumenflow-<name> in their existing
dependency sections. A partial migration is completed on the default/latest path; an existing
published declaration takes precedence over its duplicate legacy key. Other packages are preserved.
The command updates the lockfile, bundled pack pins, scripts and managed documentation through its
normal governed transaction. Review project-owned imports separately: it does not rewrite source.
A bundled pack pin left stale by a plain npm install (never running this command) does not block
jobs either: the loader trusts the CLI’s own bundled pack content over a stale
workspace.yaml pin and reports the drift as an advisory; see
Pack refuses every job after a CLI upgrade
in Troubleshooting.
The upgrade consumes the marker created by its own transaction before committing. If a completed upgrade left a pending marker, rerun the same-version upgrade from a clean main checkout to let the command verify and repair that marker through its normal micro-worktree transaction. This recovery checks origin and registered-worktree authority, the committed marker bytes and upgrade commit, the target version, and the installed package cohort. An unrelated, edited or ambiguous marker is refused; do not edit the marker manually.
Historical runtime files under .lumenflow/state that are both tracked and covered by the
maintained runtime-write and generated ignore policies are retained as local ignored files after
the governed fast-forward. Lifecycle ledgers, migration sentinels, upgrade markers and authored
state stay tracked. If a runtime file changes or disappears concurrently while the upgrade is
running, restoration stops and preserves both copies for reconciliation.
If this project has lifecycle enforcement enabled, the upgrade command also regenerates the
integrity manifest, the same governed step pnpm lumenflow:enable performs; never edit that
manifest by hand.
Workspace consumers
Section titled “Workspace consumers”From 1.0.10 a project that is itself a workspace (a pnpm-workspace.yaml packages: list, or the
npm, yarn or bun workspaces field) is upgraded as one cohort. Every workspace package that
declares an @hellmai/lumenflow-* package, such as apps/web or packages/shared, gets the exact
target version in its own dependency section, in the same upgrade commit as the root.
Declarations that do not come from the registry (workspace:, link:, file:, catalog: and
npm: aliases) are left as written. After the install the upgrade reads the lockfile, and it
refuses before committing anything if any @hellmai/lumenflow-* package still resolves at more
than one version, printing each version and the workspace packages that reach it.
pnpm lumenflow:doctor reports the same split as an error (exit code 2) at any time.
1.0.9 upgraded only the root package.json. A workspace project upgraded with 1.0.9 can hold two
@hellmai/lumenflow-host versions side by side, and every kernel route then refuses with
Compiled builtin PID1 entry differs from this original trusted host. To detect and repair it:
On 1.0.9 itself, set every @hellmai/lumenflow-* declaration in every workspace package.json
to the root’s exact version, run pnpm install, and confirm that pnpm why -r @hellmai/lumenflow-host shows one version. From 1.0.10 the PID1 refusal also names both entry
paths and both host versions.
workspace.yaml is edited key by key
Section titled “workspace.yaml is edited key by key”lumenflow:upgrade edits only the workspace.yaml keys it owns: the built-in pack pin under packs, the obsolete 5.x software_delivery.state block, and a generated test_incremental command. Comments, quoting, long wrapped strings and key order elsewhere stay byte-identical, and a second upgrade leaves the file unchanged.
One version pin point for pnpm workspaces
Section titled “One version pin point for pnpm workspaces”When pnpm-workspace.yaml defines a catalog entry for an @hellmai/lumenflow-* package (the
default catalog: or a named catalogs: map), that entry is the one pin point. The upgrade rewrites
the entry to the target version, leaves every catalog: reference in the root and the members
alone, and refreshes the lockfile, so the commit changes the catalog entry and the lockfile only.
pnpm update -w pkg@x is not run for a package reached through a catalog entry: pnpm would replace
the catalog: reference with the literal version and leave the entry stale. Entries whose value is
not a registry version (workspace:, file:, link:) are not touched.
Hard-coded engine pins left in the root and workspace members are still re-pinned in place, and the
upgrade lists each one with its file path plus the one command that converts them:
pnpm lumenflow:upgrade --version <x.y.z> --use-catalog --force. Nothing is converted without
--use-catalog; with it, hard-coded pins become catalog: references and one default catalog entry
is created, so later upgrades edit only that entry.
The release-age exemption stays exact and is managed for you, and it is an approval, not a default.
Where the installed pnpm parses pkg@version entries in minimumReleaseAgeExclude (pnpm 10.19.0 and
later, checked with pnpm --version in the project), an explicit --version X is the same human
approval pnpm asks for interactively: before the install the upgrade adds X for the engine packages
the project uses, so a strict minimumReleaseAge policy does not stop it, and the running engine must
be X. On the default and --latest paths the registry chooses the version, so nothing is
pre-approved: a strict policy makes pnpm refuse and the error prints the approving command, pnpm lumenflow:upgrade --version followed by that version. After every install the entries the upgrade owns are pruned to the versions each directory’s
lockfile still resolves. Entries for other packages, ranges, name patterns and unknown names are never
changed. A scope pattern is deliberately not written: it would let any freshly published package in the
scope skip the release-age delay. Older pnpm keeps today’s behaviour and nothing is written.
--adopt-claimed-wu is unchanged by this section.
Upgrading on main while claims are live
Section titled “Upgrading on main while claims are live”From 1.0.10, pnpm lumenflow:upgrade from the main checkout runs while WUs are claimed when it is
a same-scope upgrade. Such an upgrade writes only package.json, the lockfile, workspace.yaml
pack pins, managed docs, hooks and the upgrade marker, through its micro-worktree. It never touches
a claim, a lane branch, a claim worktree or a WU record, and its commit contains no path under the
WU directory and no ledger change. An upgrade that must migrate WU records, the state namespace,
the workspace, legacy CI or source, or bridge the package scope (the 6.x to 1.0 path), or whose
plan cannot be classified, keeps the claim refusal described in the next paragraphs.
-
Before it publishes, the upgrade reads every live claim from one origin snapshot and holds each claim’s
wu:donecompletion lock on this host, waiting at most 2 seconds for each. A claim that is completing refuses the upgrade withWU-X is completing ... nothing was published; retry once it finishes. Claim data it cannot read becomes a notice line, not a refusal, and an unreachable origin still fails at the push. -
After the install it prints one block per live claim (WU, worktree,
hardenedorpre-binding) with the commands the claim’s holder runs in order, with its own session, thenwu:donefrom the main checkout as usual: -
A bound (
hardened) claim needs no policy refresh. The digest ignores packversionandintegrity, and a pin taken by an earlier release verifies once across the digest protocol boundary, but only the main checkout’s new release can verify it. The claim worktree’s older release reads the live policy with the pack identity the upgrade just moved, and a refresh would write a pin that release cannot read. So the first step is the new release’swu:rebaserun from the claim worktree; the frozen install then moves the worktree onto the new release. -
An upgrade that changes the pinned policy itself (the managed
LUMENFLOW.mdit rewrites, or a policy key inworkspace.yaml) stales every live pin, bound ones included. The upgrade reads the trusted policy digest on origin before and after its transaction; when it moved, or cannot be read, every claim is listed under “Needs a policy refresh” and its commands start with the main checkout’swu:repair --claim --id WU-X --integration-target --refresh-policy, followed by the same rebase, install,wu:briefandwu:prep. The refresh holds and restores the claim’s own generated audit append and checkpoint rows itself (--checkpreviews them); other changes in the claim worktree refuse before publication, naming the path. Awu:rebaserun first refuses naming the refresh and leaves those rows untouched. -
A claim made under 1.0.8 (no binding yet) keeps one repair from the main checkout first:
pnpm -C <main checkout> wu:repair --claim --id WU-X --integration-target --refresh-policy --reason "lumenflow:upgrade to <version>" --worktree "<claim worktree>". Its binding upgrade also requires--refresh-policyonce the policy changed. When such a claim’s worktree still runs its own 1.0.8 install, its rebase line is the samecd <claim worktree>plus the new release’swu-rebase.js: no lifecycle step runs under the old release, whose rebase neither publishes the lane nor republishes the claim source. A holder without its session gets theagent:session --resumeroute fromwu:repair. -
One engine runs the whole route. The engine never compares a claim’s engine version with the running one, and the repair’s main-side push and its worktree-side steps both run under the main checkout’s new release, so a claim made under an earlier 1.0.x needs no old-release root. A harness that selects an installed engine per claim and asserts that root’s version against the claim’s pinned version (for example a wrapper-level exact-version guard, or a per-checkout lifecycle-root variable of its own) manufactures the two-root conflict: point every step of the route above at the main checkout’s upgraded install, and remove the per-claim version assertion. The engine reads no lifecycle-root variable. Do not retry
wu:repairon a claim whose retained publication journal is unknown until the fix for it is installed. -
Cost: a bound claim pays rebase, install, brief and prep; a pre-binding claim, or any claim across an upgrade that changed the pinned policy, pays one refresh more.
-
A completion that published before the upgrade landed is not failed by it: the post-landing readback no longer requires the policy at the WU’s landing to equal the live policy.
-
--adopt-claimed-wuis not needed for ordinary upgrades.
For a real default upgrade that keeps the claim refusal, the target CLI checks main and claim authority before configuration migration and no-op planning, then checks again before mutation. An active historical claim is refused while its old configuration is still intact. Dry-run and claim-scoped adoption follow their separate routes described on this page.
The upgrade preflight distinguishes current claims from retained legacy lane branches. It reads
origin main, lane refs and modern claim markers in one bounded atomic fetch. A modern claim
marker blocks the upgrade, and so does a registered lane worktree unless its WU is proven released (below). A legacy lane ref blocks only when the
matching WU record on origin main is still in_progress or claimed and includes its lane, claim
mode and valid claim timestamp. Historical waiting, blocked, ready and terminal records,
plus lane refs with no matching WU record, do not block by themselves. If origin authority or an
active WU projection is unreadable or ambiguous, the command stops and explains what could not be
verified.
Before it reads claim authority, a real upgrade removes the leftover claim marker of every WU that origin main records as done (with the marker’s claim generation) and whose lane branch is gone, and prints the WU ids it removed; it is the same cleanup as pnpm state:cleanup --claim-markers. A finished WU’s leftover is therefore never mistaken for a live claim, and an in-progress claim is never touched.
On an upgrade that keeps the claim refusal, a registered lane worktree is a hint, not a claim. The upgrade admits it only when, at the one fresh origin snapshot it already reads, no modern claim ref exists for that WU, exactly one canonical origin record is ready with every structured claim field cleared (the wu:release contract), and the last ownership event for that WU on origin main is release. This is the state wu:release --keep-branch leaves, so a retained registration, branch and dirty index no longer block the upgrade and are never touched. The same proof also clears a stale local in_progress or claimed record of exactly those released WUs; git:sync-main is not a prerequisite. A missing, duplicated, malformed, unreadable, bound or still-claimed record, a missing or trailing-claim event history, an unreadable origin, or any active peer worktree refuses the whole upgrade.
The local workspace configuration and namespace migration sentinel must also match that same origin snapshot before any WU inventory is read and again before admission. Only CRLF-to-LF line-ending differences are allowed; other byte changes, missing or nonregular selector inputs, and selector changes during the check refuse. The last decisive ownership row must satisfy the native release contract, including a valid timestamp and nonempty reason. Superseded historical release shapes do not override a later valid release; a later claim or unknown ownership event still refuses.
Ordinary git:sync-main can also fast-forward with unstaged changes to tracked, maintained-policy ignored runtime files when every affected regular file has the same committed blob and mode at local HEAD and the single freshly fetched configured target. It rechecks the index, tracked status, paths and configured target before merging that captured SHA, preserving concurrent appends and file identity without untracking or rewriting runtime history. Arbitrary project or global ignore rules do not qualify. Staged or mixed ordinary changes, other user files, unignored runtime state, protected lifecycle or enforcement evidence, symlinks, changed or deleted target files, and divergence refuse. Dirty --reconcile and internal targetCommit synchronization do not use this exception.
When that runtime dirt accompanies only heartbeat appends to the tracked WU event ledger, normal sync may relocate those heartbeats to the maintained heartbeat journal. The canonical ledger may have advanced on the freshly fetched target, provided both committed ledger entries remain regular files with the same mode. Every eligible companion runtime file still needs an identical committed blob and mode at local HEAD and that target. The index, HEAD, paths, target and heartbeat proof are checked before relocation; an unrelated runtime target change refuses without writing the heartbeat journal. Runtime companions stay physically untouched. Sync preserves existing and concurrent journal history, including durably relocated heartbeat records if Git later refuses the merge; other lifecycle edits, staged changes, divergence, dirty --reconcile and internal targetCommit still refuse.
If the installed CLI predates this bridge, run the currently licensed CLI from a temporary package execution environment after configuring access:
If the older CLI rejects a retired workspace setting before the upgrade, use the
public config-unset from a licensed release that supports removing that key in
a temporary package environment. Complete the old CLI’s existing WU lifecycle
before cleanup: historical releases such as 1.0.0-rc.4 require keys that newer
releases reject. Removing them before the old lifecycle finishes prevents its
remaining commands from loading the workspace.
For example, to remove the retired security default with a compatible release:
Run cleanup from main and synchronize each native config change before removing
the next reported retired key. The same cleanup entry handles retired
control_plane.policy_mode, memory_namespace and event_namespace settings.
Keep the compatible installed CLI for synchronization during that intermediate
configuration. Confirm each change reached the configured origin before the
next removal. A refusal about unpublished local commits requires reconciling
that work through the native lifecycle; cleanup does not authorize discarding it.
After cleanup, proceed to the normal upgrade before starting further old-CLI
lifecycle operations. The temporary cleanup does not upgrade the project’s declared
packages or lockfile; run the normal upgrade afterwards. Existing security
scopes and unrelated settings are preserved. Do not edit workspace.yaml by
hand or replace its complete security configuration to remove a retired key.
The temporary execution syntax is documented by pnpm.
The cloud-shaped upgrade leg’s consumer is a pnpm workspace (the root plus an apps/* and a
packages/* package on the cohort): after its upgrade every member must declare the candidate and
the lockfile must resolve one @hellmai cohort before its post-upgrade WU lifecycle runs. The leg
then repeats the upgrade on two further fresh fixtures with a claim made by the previous published
release: one carries the claim across a plain upgrade (--version <candidate>, claim live) and lands
it with the printed commands, the other adopts it with --adopt-claimed-wu and lands it with the
previous release’s root. --latest on this fixture bridges legacy declarations, a claim-bearing
route that keeps its refusal.
Release rehearsals run all four packed-consumer smoke legs. Their small
current-version lifecycle fixture uses the public configuration setting
software_delivery.gates.execution_lock.concurrency: 1 so its full gate plan
can share host capacity. Every gate, admission timeout and fail-closed rule
still applies. This fixture setting does not alter an upgraded project’s
configuration.
An explicit --version 6.1.6 in a legacy-only project keeps its existing @lumenflow/* names and
registry. It does not bootstrap the private-scope CLI. Historical availability remains a property of
that original registry; this command does not publish, deprecate or remove historical packages.
Package name map
Section titled “Package name map”| Before (npm) | After (private registry) |
|---|---|
@lumenflow/cli | @hellmai/lumenflow-cli |
@lumenflow/kernel | @hellmai/lumenflow-kernel |
@lumenflow/host | @hellmai/lumenflow-host |
@lumenflow/runtime | @hellmai/lumenflow-runtime |
@lumenflow/memory | @hellmai/lumenflow-memory |
@lumenflow/agent | @hellmai/lumenflow-agent |
@lumenflow/initiatives | @hellmai/lumenflow-initiatives |
@lumenflow/metrics | @hellmai/lumenflow-metrics |
@lumenflow/mcp | @hellmai/lumenflow-mcp |
@lumenflow/shims | @hellmai/lumenflow-shims |
@lumenflow/surfaces | @hellmai/lumenflow-surfaces |
@lumenflow/conductor-sdk | @hellmai/lumenflow-conductor-sdk |
@lumenflow/control-plane-sdk | @hellmai/lumenflow-control-plane-sdk |
@lumenflow/packs-software-delivery | @hellmai/lumenflow-packs-software-delivery |
@lumenflow/packs-agent-runtime | @hellmai/lumenflow-packs-agent-runtime |
@lumenflow/packs-campus | @hellmai/lumenflow-packs-campus |
@lumenflow/packs-protocol-adapters | @hellmai/lumenflow-packs-protocol-adapters |
@lumenflow/packs-reliability-operations | @hellmai/lumenflow-packs-reliability-operations |
@lumenflow/packs-sidekick | @hellmai/lumenflow-packs-sidekick |
Subpath imports retain their suffix: @lumenflow/kernel/primitives/error-handler becomes
@hellmai/lumenflow-kernel/primitives/error-handler.
Access and troubleshooting
Section titled “Access and troubleshooting”| Symptom | Action |
|---|---|
Wrong or missing @hellmai:registry mapping | Set the project routing line above and retry. |
| Metadata or download denied | Confirm the configured licence or token, its expiry/suspension status and its package entitlement with the licence issuer. |
| Registry unavailable | Check connectivity and the licence registry; retry after service is restored. |
| Dry-run cannot resolve a version | Correct the reported access problem before running a real upgrade. |
| Old imports still fail | Replace project-owned @lumenflow/* import specifiers using the package map. |
Diagnose and renew an expired registry credential
Section titled “Diagnose and renew an expired registry credential”The registry credential in your .npmrc (username=license plus _password, or _authToken) is
separate from the cached runtime licence file, so one can be valid while the other has expired. When
the registry refuses the credential, lumenflow:upgrade prints the registry’s own code and detail
instead of a generic metadata error:
| Registry code | Meaning | Action |
|---|---|---|
LICENSE_EXPIRED | The licence behind the credential ended. | Ask for a renewed grant and replace the .npmrc lines for it. |
LICENSE_SUSPENDED | The licence was suspended. | Ask for the licence to be reinstated or a new grant issued. |
ACCESS_DENIED | The grant does not cover package access. | Ask for the grant to be confirmed or a new one issued. |
Any HTTP 403 from the licensing registry is a licence entitlement refusal: the grant is expired,
version-restricted, suspended or scope-mismatched for the package. lumenflow:upgrade says so, names
the status, and tells you to ask for the licence to be renewed or extend the grant. It prints this even
when the registry returns no error body. It never prints credential material (no .npmrc values, no
Authorization header, no token), and a command that exits non-zero always prints a reason naming
the error class or code. Registry text echoed back to you has the key and its base64 form redacted.
lumenflow:doctor checks the same credential npm resolves for //<registry-host>/ (project
.npmrc, then user .npmrc, then npm_config_*) against the registry. Its Licence section prints
the expiry date when known, warns inside 14 days of expiry (louder inside 3), and fails once the
credential is expired or suspended. After renewing, run lumenflow:doctor to confirm.
Free and paid grants use the same installation mechanism. Selecting a paid grant is not itself a payment or an automatic billing integration.
Claim-scoped adoption inside a claimed WU worktree
Section titled “Claim-scoped adoption inside a claimed WU worktree”From 1.0.10 the ordinary same-scope lumenflow:upgrade runs from the main checkout while claims are live, so this route is not needed for ordinary upgrades. An upgrade that still refuses while a claim is active (it migrates claim-bearing state or bridges the scope), or a claimed WU that must itself pin an exact private cohort, uses the one exception, run from that WU’s own registered worktree:
- Requires an exact concrete version (tags, ranges and —latest are refused), directly declared
@hellmai/lumenflow-*packages, a clean worktree and no pending marker. - Genuinely local lifecycle runtime is not “dirty”. The managed
.gitignoreblock (rendered from the canonical runtime-write registry) now ignores the anti-replay receipt sequences under.lumenflow/state/packs/software-delivery/lifecycle-enforcement/sequences/and the revocations under.../lifecycle-enforcement/revoked/, both as local, per-machine state that must never be committed or replayed from another checkout. Claimed adoption classifies those paths as local runtime from the same registry even before the consumer’s.gitignorehas been refreshed, so retained spent receipt sequences and the own heartbeat no longer block the clean-worktree check. Their bytes are left unchanged and uncommitted. The tracked integrity manifest stays tracked, and every other dirty or untracked file is still refused. - Invocation is validated before anything else runs.
--dry-runis refused (the command would otherwise install and commit; preview with a normallumenflow:upgrade --dry-runfrom the main checkout). A missing, empty or flag-shaped WU value (for example--adopt-claimed-wu --version 1.0.8) is refused. No file is changed in any of these cases. - Authority: the existing claim binding (owner, session, unique registered worktree) is attested before and after, the own WU must be present in freshly fetched origin claim refs (unreadable origin fails closed; peers’ claims neither block nor grant), the canonical tracking ref must equal the observed origin main, and the latest claim event identity and HEAD must not change.
package.json, the lockfile,.lumenflow/state/lumenflow-upgrade-marker.jsonand every workspacepackage.jsonthat declares@hellmai/lumenflow-*at another version must already be in the WU’s effectivecode_paths(including pendingwu:editchanges) before any write; otherwise the command refuses before writing. The workspace selection is the plain upgrade’s: pnpm-workspace.yaml globs, otherwise the rootworkspacesfield, each member pinned exactly in its original section. After the install, a lockfile that resolves more than one@hellmai/lumenflow-*version is refused.- pnpm’s own install-time write is part of the operation. While it resolves the install, pnpm appends entries such as
|| <version>tominimumReleaseAgeExcludeinpnpm-workspace.yaml, at the root and in any nested workspace or spike. A change confined to that key is committed with the adoption (the file must be in the WU’scode_paths, and the plain upgrade commits the same files); any other change to those files is still a foreign write and refuses. - Only the selected private cohort is pinned. Legacy
@lumenflow/*andworkspace:*declarations are verified unchanged; a selected package declared withworkspace:is refused before any change. - Exact original-slot semantics: the installer is told to save exact versions (
--save-exact, or--exactfor yarn and bun, chosen by the logical package manager so a Windows plan that resolves the executable to an absolute.cmdpath still gets the flag). Before the install the transaction records each selected declaration key (section plus package name). Afterwards the key set must be identical acrossdependencies,devDependencies,optionalDependenciesandpeerDependencies: a selected package that was moved to another section, duplicated into a second section or removed is refused. Each selected spec must equal the requested concrete version exactly; a caret, tilde, range, tag orworkspace:or other protocol spec is refused. Every other manifest field is compared structurally: the complete normalized manifest minus only the original selected slots (section plus name) must be identical before and after, so a custom top-level key such asdependencies.left-padcan never mask an installer write. Any change is refused. On a refusal the edits are rolled back, no marker is written and nothing is committed. This applies only to--adopt-claimed-wu; the ordinary main-checkout upgrade is unchanged. - The marker is created and consumed only after the installed cohort verifies exactly, then one ordinary hooked commit of exactly those paths is made. Nothing is pushed;
wu:prep/wu:donepublish it. The unchanged pre-commit guard is the proof. - The installer inherits the consumer PATH and runtime (for example Node 24); the lifecycle entry may run a different Node. Both versions are printed. Neither is replaced.
- Limit: the marker is plain JSON validated by the unchanged guard for kind, status, created_at and version, so exclusivity is by this supported transaction and the claim binding, not by marker cryptography. On any refusal after the first write the operation rolls its own partial results back (manifests, lockfile,
pnpm-workspace.yamlentries and the marker are restored to HEAD), so the worktree is clean and the same command is retryable; peer edits are never reset or removed, and a HEAD that moved during the run is kept untouched.