UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

80 lines (79 loc) • 4.09 kB
import { t as deferSqlitePostCommitPublication } from "./sqlite-post-commit-PDRfkj0T.mjs"; import { s as resolveOpenClawStateSqlitePath } from "./openclaw-state-db.paths-BvBEgHyD.mjs"; import { l as runOpenClawStateWriteTransaction } from "./openclaw-state-db-VZFEEleX.mjs"; import { a as loadCronRows, h as upsertCronJobRow, n as deleteCronJobRowInDatabase, p as rowToCronJob } from "./row-codec-CXnIpQeG.mjs"; import { t as inspectCronRowsForDoctor } from "./doctor-inventory-Cl1RqRf6.mjs"; import { t as createVerifiedSqliteSnapshot } from "./sqlite-snapshot-n92svotn.mjs"; import { t as inspectCronJobsReadOnly } from "./read-only-BKtsVdF7.mjs"; import { o as noteCronJobsStoreCommit } from "./store-abzXKIps.mjs"; import { n as sanitizeOpenClawStateLeaseRows } from "./openclaw-state-snapshot-sanitizer-DbP8gU53.mjs"; import { isDeepStrictEqual } from "node:util"; import { randomUUID } from "node:crypto"; //#region src/cron/store/doctor.ts async function inspectCronJobsForDoctor(scope) { return { jobs: await inspectCronJobsReadOnly(scope.env) }; } function definitionEvidence(jobs) { return jobs.map(({ storeKey, id, sortOrder, definitionJson }) => ({ storeKey, id, sortOrder, definitionJson })); } /** One host-owned repair transaction; plugins choose rows, never database paths or SQL. */ async function repairCronJobsForDoctor(scope, authority, inspected, requested) { authority.assertCurrent(); const inventory = structuredClone(inspected); const changes = structuredClone(requested).filter((change) => change.definition === null || !isDeepStrictEqual(change.job.definition, change.definition)); if (changes.length === 0) return { changed: 0 }; const seen = /* @__PURE__ */ new Set(); for (const change of changes) { const key = JSON.stringify([change.job.storeKey, change.job.id]); if (seen.has(key) || !inventory.jobs.some((job) => isDeepStrictEqual(job, change.job))) throw new Error("Cron Doctor repair requires distinct inspected rows."); if (change.definition && change.definition.id !== change.job.id) throw new Error("Cron Doctor repair must preserve job IDs."); seen.add(key); } const expected = definitionEvidence(inventory.jobs); const assertRowsUnchanged = (db) => { if (!isDeepStrictEqual(definitionEvidence(inspectCronRowsForDoctor(db)), expected)) throw new Error("Cron definitions changed during Doctor repair; inspect again before retrying."); }; const sourcePath = resolveOpenClawStateSqlitePath(scope.env); const backupPath = `${sourcePath}.doctor-cron-${Date.now()}-${randomUUID()}.bak`; await createVerifiedSqliteSnapshot({ sourcePath, targetPath: backupPath, preserveRowIds: true, transform: sanitizeOpenClawStateLeaseRows, requireNonEmptySource: true, validate: assertRowsUnchanged, beforePublish: () => authority.assertCurrent(), afterPublish: (guard) => guard.assertTargetUnchanged(() => authority.assertCurrent()) }); try { authority.assertCurrent(); runOpenClawStateWriteTransaction(({ db }) => { authority.assertOwnedInTransaction(db); assertRowsUnchanged(db); for (const { job, definition } of changes) { if (!definition) { deleteCronJobRowInDatabase(db, job.storeKey, job.id); continue; } const row = loadCronRows(db, job.storeKey, /* @__PURE__ */ new Set([job.id]))[0]; const replacement = row && rowToCronJob(row, definition); if (!replacement) throw new Error(`Cron Doctor repair cannot persist job ${job.id}; backup retained at ${backupPath}.`); upsertCronJobRow(db, job.storeKey, replacement, job.sortOrder, { preserveRuntimeState: true }); } for (const storeKey of new Set(changes.map(({ job }) => job.storeKey))) deferSqlitePostCommitPublication(db, () => noteCronJobsStoreCommit(storeKey)); }, { env: scope.env }, { operationLabel: "cron.doctor-repair" }); } catch (error) { throw new Error(`Cron Doctor repair failed; verified backup retained at ${backupPath}: ${String(error)}`, { cause: error }); } return { changed: changes.length, backupPath }; } //#endregion export { inspectCronJobsForDoctor, repairCronJobsForDoctor };