Skip to content

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.

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:

@hellmai:registry=<registry-url>

The private consumer configuration uses a licence key:

@hellmai:registry=<registry-url>
//<registry-host>/:username=license
//<registry-host>/:_password=<base64 licence key supplied with your grant>

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:

npm view @hellmai/lumenflow-cli@latest version --registry <registry-url>

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.

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:

allowBuilds:
  better-sqlite3: true

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:

pnpm add -D @hellmai/lumenflow-cli
npx lumenflow init

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.

From the project’s main checkout:

pnpm lumenflow:upgrade --latest --dry-run
pnpm lumenflow:upgrade --latest
pnpm lumenflow:doctor
pnpm gates

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.

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:

pnpm lumenflow:doctor            # from 1.0.10: "LumenFlow Package Cohort" lists each version
pnpm why -r @hellmai/lumenflow-host   # any release: more than one version means a split
pnpm lumenflow:upgrade --version 1.0.10   # pins every workspace package, then refuses any split
pnpm install --frozen-lockfile

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.

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.

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.

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:done completion lock on this host, waiting at most 2 seconds for each. A claim that is completing refuses the upgrade with WU-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, hardened or pre-binding) with the commands the claim’s holder runs in order, with its own session, then wu:done from the main checkout as usual:

    cd <claim worktree>
    node <main checkout>/node_modules/@hellmai/lumenflow-cli/dist/wu-rebase.js --id WU-X
    pnpm -C <claim worktree> install --frozen-lockfile
    pnpm -C <claim worktree> wu:brief --id WU-X
    pnpm -C <claim worktree> wu:prep --id WU-X
  • A bound (hardened) claim needs no policy refresh. The digest ignores pack version and integrity, 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’s wu:rebase run 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.md it rewrites, or a policy key in workspace.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’s wu:repair --claim --id WU-X --integration-target --refresh-policy, followed by the same rebase, install, wu:brief and wu:prep. The refresh holds and restores the claim’s own generated audit append and checkpoint rows itself (--check previews them); other changes in the claim worktree refuse before publication, naming the path. A wu:rebase run 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-policy once the policy changed. When such a claim’s worktree still runs its own 1.0.8 install, its rebase line is the same cd <claim worktree> plus the new release’s wu-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 the agent:session --resume route from wu: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:repair on 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-wu is 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:

pnpm --package=@hellmai/lumenflow-cli dlx lumenflow-upgrade --latest

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:

pnpm --package=@hellmai/lumenflow-cli dlx config-unset --key security.network_default
pnpm git:sync-main

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.

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.

SymptomAction
Wrong or missing @hellmai:registry mappingSet the project routing line above and retry.
Metadata or download deniedConfirm the configured licence or token, its expiry/suspension status and its package entitlement with the licence issuer.
Registry unavailableCheck connectivity and the licence registry; retry after service is restored.
Dry-run cannot resolve a versionCorrect the reported access problem before running a real upgrade.
Old imports still failReplace 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 codeMeaningAction
LICENSE_EXPIREDThe licence behind the credential ended.Ask for a renewed grant and replace the .npmrc lines for it.
LICENSE_SUSPENDEDThe licence was suspended.Ask for the licence to be reinstated or a new grant issued.
ACCESS_DENIEDThe 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:

cd worktrees/<lane>-wu-<id>
pnpm lumenflow:upgrade --adopt-claimed-wu WU-<id> --version <exact x.y.z>
  • 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 .gitignore block (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 .gitignore has 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-run is refused (the command would otherwise install and commit; preview with a normal lumenflow:upgrade --dry-run from 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.json and every workspace package.json that declares @hellmai/lumenflow-* at another version must already be in the WU’s effective code_paths (including pending wu:edit changes) before any write; otherwise the command refuses before writing. The workspace selection is the plain upgrade’s: pnpm-workspace.yaml globs, otherwise the root workspaces field, 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> to minimumReleaseAgeExclude in pnpm-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’s code_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/* and workspace:* declarations are verified unchanged; a selected package declared with workspace: is refused before any change.
  • Exact original-slot semantics: the installer is told to save exact versions (--save-exact, or --exact for yarn and bun, chosen by the logical package manager so a Windows plan that resolves the executable to an absolute .cmd path 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 across dependencies, devDependencies, optionalDependencies and peerDependencies: 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 or workspace: 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 as dependencies.left-pad can 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:done publish 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.yaml entries 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.