mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 17:53:39 +00:00
## Problem A pnpm 11 global update from OpenClaw 2026.7.1-2 to 2026.8.1 fails verification and rolls back. The 2026.8.1 package puts `openclaw-install-guard` inside `dist/`, but the old updater treats every uninventoried `dist/` file as invalid. ## Root cause Package lifecycle state was stored inside the closed `dist/` inventory. That made a valid pending lifecycle marker look like package corruption to an older updater. ## Fix - Store pending lifecycle state at package root as `.openclaw-lifecycle-pending`. - Let postinstall remove the marker only after all lifecycle work succeeds. - Use one lifecycle completion owner from the updater, `dist/index.js`, and `openclaw.mjs`. - Keep the lifecycle lock valid beyond the full preinstall and postinstall timeout budget. - Keep temporary recovery support for the 2026.8.1 `dist/openclaw-install-guard` path. - Keep source package preparation and worker package generation aligned with the new marker contract. ## Product proof - Red: a published 2026.7.1-2 pnpm 11 install rejected the published 2026.8.1 package with `unexpected packaged dist file dist/openclaw-install-guard`, exited nonzero, and remained on 2026.7.1-2. - Green: the built candidate passes the old-updater upgrade path, the pnpm 11 lifecycle-repair path, a forced postinstall failure and retry, and native npm controls. - Anti-cheat: the proof checks the installed CLI version before and after the update. ## Validation - `node scripts/run-vitest.mjs src/infra/package-lifecycle.test.ts src/infra/package-update-steps.pnpm11-guard.test.ts src/index.entrypoint.test.ts` - Focused lifecycle, updater, tarball, postinstall, inventory, and entrypoint suites: 176 tests passed. - Exact-head lifecycle lock suite: 4 tests passed, including the old 20-minute expiry boundary. - Remote core and scripts checks passed. - `git diff --check` - GitHub CI is the full release and platform gate. ## Scope - Production and release-tooling delta: +370/-162, net +208. - Test and CI support delta: +370/-84, net +286. - The production growth adds the shared lifecycle owner, crash-safe retry marker, and concurrent-launch lock. It removes the updater-only lifecycle sequence and keeps the closed `dist/` verifier unchanged. - Sibling coverage: updater, package launcher, legacy package entrypoint, installers, tarball validator, worker bootstrap package, and Docker package preparation. ## ClawSweeper - No actionable code findings. - Rank-up skip: the package-upgrade trace came from an internal isolated runner and is not suitable for a public log attachment. Exact-head GitHub CI and the focused regression commands above remain the public proof. Co-authored-by: Ayaan Zaidi <hi@obviy.us>
34 lines
1.2 KiB
JavaScript
Executable file
34 lines
1.2 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
|
|
import { rm } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import {
|
|
LEGACY_PACKAGE_INSTALL_GUARD_RELATIVE_PATH,
|
|
PACKAGE_LIFECYCLE_PENDING_RELATIVE_PATH,
|
|
} from "./lib/package-lifecycle-marker.mjs";
|
|
// Restores every source artifact temporarily rewritten for npm packaging.
|
|
import { restorePackageChangelog } from "./package-changelog.mjs";
|
|
import { restorePackageDocsMap } from "./package-docs-map.mjs";
|
|
import { restorePackageManifest } from "./package-manifest.mjs";
|
|
|
|
export async function restorePrepackArtifacts(cwd = process.cwd()) {
|
|
await restorePackageChangelog(cwd);
|
|
await restorePackageManifest(cwd);
|
|
// Release the lifecycle receipt only after every other source mutation settles.
|
|
await restorePackageDocsMap(cwd);
|
|
await Promise.all(
|
|
[PACKAGE_LIFECYCLE_PENDING_RELATIVE_PATH, LEGACY_PACKAGE_INSTALL_GUARD_RELATIVE_PATH].map(
|
|
(relativePath) => rm(path.join(cwd, relativePath), { force: true }),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
try {
|
|
await restorePrepackArtifacts();
|
|
} catch (error) {
|
|
console.error(error instanceof Error ? error.message : String(error));
|
|
process.exitCode = 1;
|
|
}
|
|
}
|