UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

132 lines (131 loc) • 6.71 kB
import { n as resolvePathViaExistingAncestorSync } from "./boundary-path-CAv_plX7.mjs"; import { r as resolveStateDir } from "./state-dir-CYkBUd_G.mjs"; import "./paths-BP0DYpE8.mjs"; import { t as formatErrorMessage } from "./errors-BvOkmQM5.mjs"; import { r as hasCommandProcessCleanupError } from "./exec-result-DXhDHhYI.mjs"; import { n as readUpdateRunDriver } from "./update-run-driver-k0LVIIq0.mjs"; import { t as DoctorMaintenanceRefusalError } from "./update-doctor-result-Buvixhp2.mjs"; import "./update-control-plane-sentinel-Do9UY9jx.mjs"; import { r as resolveUpdateCaptureRoot } from "./update-capture-paths-KB4ru3JI.mjs"; import { t as resolveDoctorUpdateAdmission } from "./doctor-maintenance-admission-B4Vl-794.mjs"; import path from "node:path"; import fs from "node:fs/promises"; import { randomUUID } from "node:crypto"; //#region src/commands/doctor-original-capture.ts const IMAGE_ACTIVATION_MARKER_ENV = ["OPENCLAW", "IMAGE_ACTIVATION_CAPTURE_MARKER"].join("_"); const STANDALONE_CAPTURE_RUN_ID = /^doctor-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u; /** A marker counts only while the sealed capture it names is still retained. */ async function readImageActivationCapture(marker, captureRoot) { try { const stat = await fs.lstat(marker); if (!stat.isFile() || process.getuid !== void 0 && stat.uid !== process.getuid()) return; const runId = (await fs.readFile(marker, "utf8")).trim(); if (!STANDALONE_CAPTURE_RUN_ID.test(runId)) return; return (await fs.lstat(path.join(captureRoot, runId, "manifest.json"))).isFile() ? runId : void 0; } catch { return; } } /** Preserve evidence before repair; neither a capture nor its lineage authorizes restoration. */ async function preserveDoctorOriginalState(params) { const original = params.writeAuthority?.originalRecoveryCapture; const runId = original?.runId ?? params.env["OPENCLAW_UPDATE_RUN_ID"]?.trim(); const updateInProgress = original !== void 0 || params.env.OPENCLAW_UPDATE_IN_PROGRESS === "1"; const assertCallerCurrent = () => { params.signal.throwIfAborted(); params.assertCurrent(); params.writeAuthority?.assertCurrent(); }; assertCallerCurrent(); if (updateInProgress && !runId) { params.runtime.log("Original update capture is unavailable. Doctor will repair current state without replacing the retained originals."); return; } let admission; const assertCurrent = () => { assertCallerCurrent(); admission?.assertCurrent(); }; try { admission = updateInProgress ? resolveDoctorUpdateAdmission(params.env, original?.runId) : void 0; if (admission && runId) { const { readUpdateRecoveryBaselineIdentity } = await import("./update-recovery-backup-reader-DowimwzK.mjs"); assertCurrent(); const retained = await readUpdateRecoveryBaselineIdentity({ runId, env: params.env, ...original ? { ref: original.ref, installRoot: original.installRoot } : {}, readContinuation: admission.readContinuation, assertCurrent }); assertCurrent(); params.runtime.log(retained ? `Original update capture retained at ${retained.ref.manifestPath}.` : "Original update capture is unavailable. Doctor will repair current state without replacing the retained originals."); return; } const { captureUpdateRecoveryBaseline, retireExpiredStandaloneDoctorCaptures } = await import("./update-recovery-baseline-capture-CoC_ljzr.mjs"); const stateDir = resolvePathViaExistingAncestorSync(resolveStateDir(params.env)); const retireExpiredCaptures = async (keepRunId) => { try { const retirement = await retireExpiredStandaloneDoctorCaptures({ stateDir, keepRunId, assertCurrent }); for (const directory of retirement.retired) params.runtime.log(`Retired standalone Doctor capture older than 30 days: ${directory}. Take a verified backup before an upgrade when you need a long-term recovery copy.`); for (const warning of retirement.warnings) params.runtime.log(warning); } catch (error) { params.runtime.log(`Standalone Doctor capture retirement unavailable: ${formatErrorMessage(error)}`); } }; const activationMarker = params.env[IMAGE_ACTIVATION_MARKER_ENV]?.trim() || void 0; const activationCapture = activationMarker ? await readImageActivationCapture(activationMarker, resolveUpdateCaptureRoot(stateDir)) : void 0; if (activationCapture) { assertCurrent(); params.runtime.log(`This container already retained a pre-repair capture (${activationCapture}); Doctor skipped a repeated full capture. Repair-specific backups still apply.`); await retireExpiredCaptures(activationCapture); return; } const installRoot = params.root; if (!installRoot) throw new Error("The installation could not be identified for the pre-repair capture."); const driver = readUpdateRunDriver(); if (!driver) throw new Error("The Doctor process could not be identified for the pre-repair capture."); assertCurrent(); const standaloneRunId = `doctor-${randomUUID()}`; const captured = await captureUpdateRecoveryBaseline({ runId: standaloneRunId, installRoot, env: params.env, drivers: [driver], assertCurrent, signal: params.signal, acquisition: { mode: "maintenance-owner" } }); assertCurrent(); params.runtime.log(`Pre-repair state retained for manual recovery at ${captured.ref.manifestPath}.`); if (activationMarker) try { await fs.rm(activationMarker, { force: true }); await fs.writeFile(activationMarker, standaloneRunId, { flag: "wx", mode: 384 }); } catch (error) { params.runtime.log(`Container activation marker unavailable: ${formatErrorMessage(error)}. The next container start takes another full capture.`); } await retireExpiredCaptures(standaloneRunId); for (const warning of captured.warnings) params.runtime.log(warning.message); for (const warning of captured.diagnostics.databaseWarnings) params.runtime.log(warning); } catch (error) { assertCurrent(); if (hasCommandProcessCleanupError(error) || error instanceof DoctorMaintenanceRefusalError && error.refusal.kind === "deferred") throw error; if (original?.ref) throw new DoctorMaintenanceRefusalError(`Doctor cannot verify its admitted original update capture: ${formatErrorMessage(error)}. State repair has not started. Inspect retained evidence with openclaw update status --json before retrying Doctor.`, { kind: "data-at-risk", reason: "unreadable-state" }, { cause: error }); params.runtime.log(`Original state capture was unavailable: ${formatErrorMessage(error)}. Doctor will continue; inspect retained evidence manually before restoring state.`); } } //#endregion export { preserveDoctorOriginalState };