UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

321 lines (320 loc) • 16 kB
import { t as hasErrnoCode } from "./errno-FR4CRyya.mjs"; import { a as executeSqliteQueryTakeFirstSync, i as executeSqliteQuerySync, o as getNodeSqliteKysely } from "./kysely-sync-DUWzQvCU.mjs"; import { r as openNodeSqliteDatabase } from "./node-sqlite-DyeJqxPf.mjs"; import { d as sqlitePrimaryResultCode } from "./sqlite-error-diagnostics-CWRyv4lz.mjs"; import { o as OPENCLAW_DATABASE_SCHEMA_DOCS_URL } from "./openclaw-state-db-contract-BYv0nUQr.mjs"; import { a as runSqliteImmediateTransactionSync } from "./sqlite-transaction-DBRLvCDM.mjs"; import { t as VERSION } from "./version-D0DaWqMn.mjs"; import { l as resolveQuarantineStorePath } from "./openclaw-state-db.paths-CmKkMYdo.mjs"; import { _ as applyPrivateModeSync } from "./gateway-state-owner-eDtEELE9.mjs"; import { t as openSqliteReadOnlyDatabase } from "./sqlite-snapshot-source-ChTpAJ4b.mjs"; import { T as OpenClawQuarantineReadCleanupError } from "./openclaw-state-worker-error-Bi-BnNrV.mjs"; import { a as serializeSqliteFileGeneration, i as sameSqliteFileGeneration, r as readStableSqliteFileGeneration, t as parseSqliteFileGeneration } from "./sqlite-file-generation-HEkrmgCv.mjs"; import { a as readOpenClawAgentDatabaseIdentity } from "./openclaw-agent-db-identity-BlmER9-X.mjs"; import { f as hasRevokedOpenClawAgentDatabaseValidation, h as invalidateOpenClawAgentDatabaseValidation } from "./openclaw-agent-db-validation-cache-CHrtutWN.mjs"; import { existsSync, mkdirSync, realpathSync, statSync } from "node:fs"; import path from "node:path"; //#region src/state/openclaw-quarantine-store.ts const OPENCLAW_QUARANTINE_SCHEMA_VERSION = 2; const OPENCLAW_QUARANTINE_BUSY_TIMEOUT_MS = 5e3; const OPENCLAW_QUARANTINE_DIR_MODE = 448; const OPENCLAW_QUARANTINE_FILE_MODE = 384; function resolveAgentIntegrityPath(pathname) { try { return realpathSync.native(pathname); } catch (error) { if (!hasErrnoCode(error, "ENOENT")) throw error; return path.resolve(pathname); } } function resolveAgentDatabaseIntegrityGateReason(database, proof) { const { verification, validation, integrityRevoked, reuseIntegrity } = proof; if (integrityRevoked) return "stale-lease-full"; if (hasRevokedOpenClawAgentDatabaseValidation(database.path, validation)) return "revoked"; if (!reuseIntegrity) return "lease-class"; return verification?.clean_close === 0 && verification.app_version === VERSION && `${verification.dev}:${verification.ino}` === readOpenClawAgentDatabaseIdentity(database).identity ? "dirty-receipt" : "no-proof"; } /** The lease owner consumes this receipt under the shared writer admission. */ function readOpenClawAgentIntegrityVerification(pathname, env = process.env, consume = false) { const read = (database) => { const query = getNodeSqliteKysely(database); const row = executeSqliteQueryTakeFirstSync(database, query.selectFrom("agent_integrity_verifications").selectAll().where("path", "=", resolveAgentIntegrityPath(pathname))); if (consume) { const current = statSync(pathname, { bigint: true, throwIfNoEntry: false }); executeSqliteQuerySync(database, query.updateTable("agent_integrity_verifications").set({ clean_close: 0 }).where((eb) => eb.or([eb("path", "=", resolveAgentIntegrityPath(pathname)), ...current ? [eb.and([eb("dev", "=", String(current.dev)), eb("ino", "=", String(current.ino))])] : []]))); } return row; }; if (consume) return withQuarantineWriter(env, (database) => runSqliteImmediateTransactionSync(database, () => read(database), { databaseLabel: resolveQuarantineStorePath(env), operationLabel: "quarantine.integrity.consume" })); const storePath = resolveQuarantineStorePath(env); if (!existsSync(storePath)) return; let database; try { database = openSqliteReadOnlyDatabase(storePath); return read(database); } catch { return; } finally { database?.close(); } } function canReuseOpenClawAgentIntegrityVerification(pathname, record, migrationPending) { if (migrationPending || !record || record.clean_close !== 1 || record.app_version !== VERSION || record.path !== resolveAgentIntegrityPath(pathname)) return false; const current = statSync(pathname, { bigint: true, throwIfNoEntry: false }); return current !== void 0 && record.dev === String(current.dev) && record.ino === String(current.ino); } /** Record the lease owner's full check without certifying a clean close. */ function recordOpenClawAgentIntegrityVerification(pathname, env, identity) { const current = statSync(pathname, { bigint: true, throwIfNoEntry: false }); if (!current || identity !== `${current.dev}:${current.ino}`) return false; const dev = String(current.dev); const ino = String(current.ino); withQuarantineWriter(env, (database) => { const query = getNodeSqliteKysely(database); executeSqliteQuerySync(database, query.insertInto("agent_integrity_verifications").values({ path: resolveAgentIntegrityPath(pathname), dev, ino, app_version: VERSION, verified_at: Date.now(), clean_close: 0 }).onConflict((conflict) => conflict.column("path").doUpdateSet({ dev, ino, app_version: VERSION, verified_at: Date.now(), clean_close: 0 }))); }); return true; } /** Unclean disposal removes the proof that any surviving last closer could certify. */ function clearOpenClawAgentIntegrityVerification(pathname, env = process.env, runtimeProof = "revoke") { if (runtimeProof === "revoke") invalidateOpenClawAgentDatabaseValidation(pathname); withQuarantineWriter(env, (database) => runSqliteImmediateTransactionSync(database, () => invalidateAgentIntegrityVerification(database, pathname, runtimeProof), { databaseLabel: resolveQuarantineStorePath(env), operationLabel: "quarantine.integrity.invalidate" })); } function invalidateAgentIntegrityVerification(database, pathname, runtimeProof = "revoke") { const query = getNodeSqliteKysely(database); const stored = executeSqliteQueryTakeFirstSync(database, query.selectFrom("agent_integrity_verifications").select(["dev", "ino"]).where("path", "=", resolveAgentIntegrityPath(pathname))); const current = statSync(pathname, { bigint: true, throwIfNoEntry: false }); if (runtimeProof === "revoke") { for (const file of [stored, current]) if (file) invalidateOpenClawAgentDatabaseValidation(pathname, `${file.dev}:${file.ino}`); } const matchesFile = (eb) => eb.or([eb("path", "=", resolveAgentIntegrityPath(pathname)), ...[stored, current].flatMap((file) => file ? [eb.and([eb("dev", "=", String(file.dev)), eb("ino", "=", String(file.ino))])] : [])]); if (runtimeProof === "retain") executeSqliteQuerySync(database, query.updateTable("agent_integrity_verifications").set({ clean_close: 0 }).where(matchesFile)); else executeSqliteQuerySync(database, query.deleteFrom("agent_integrity_verifications").where(matchesFile)); } /** Only the last graceful lease release may publish cleanliness. */ function markOpenClawAgentIntegrityClean(pathname, env, identity) { const current = statSync(pathname, { bigint: true, throwIfNoEntry: false }); if (!current || identity !== `${current.dev}:${current.ino}`) return "file-changed"; return withQuarantineWriter(env, (database) => { const query = getNodeSqliteKysely(database); return executeSqliteQuerySync(database, query.updateTable("agent_integrity_verifications").set({ clean_close: 1 }).where("path", "=", resolveAgentIntegrityPath(pathname)).where("dev", "=", String(current.dev)).where("ino", "=", String(current.ino)).where("app_version", "=", VERSION)).numAffectedRows === 1n ? "written" : "verification-missing"; }); } function configureQuarantineWriter(database, storePath) { database.exec(` PRAGMA journal_mode = DELETE; PRAGMA synchronous = FULL; `); const userVersion = readQuarantineSchemaVersion(database, storePath); if (userVersion > OPENCLAW_QUARANTINE_SCHEMA_VERSION) throw new Error(`OpenClaw quarantine store ${storePath} uses newer schema version ${userVersion}.`); if (userVersion === OPENCLAW_QUARANTINE_SCHEMA_VERSION) return; if (userVersion === 1) { database.exec(` BEGIN IMMEDIATE; ALTER TABLE quarantined_databases ADD COLUMN verified_generation TEXT; PRAGMA user_version = ${OPENCLAW_QUARANTINE_SCHEMA_VERSION}; COMMIT; `); return; } database.exec(` BEGIN IMMEDIATE; CREATE TABLE IF NOT EXISTS quarantined_databases ( path TEXT NOT NULL PRIMARY KEY, kind TEXT NOT NULL, reason TEXT NOT NULL, quarantined_at INTEGER NOT NULL, writer_app_version TEXT, verified_generation TEXT ) STRICT; PRAGMA user_version = ${OPENCLAW_QUARANTINE_SCHEMA_VERSION}; COMMIT; `); } function readQuarantineSchemaVersion(database, storePath) { return parseQuarantineSchemaVersion(database.prepare("PRAGMA user_version").get()?.user_version, storePath); } function parseQuarantineSchemaVersion(userVersion, storePath) { if (typeof userVersion !== "number" || !Number.isInteger(userVersion)) throw new Error(`OpenClaw quarantine store ${storePath} has an invalid schema version.`); return userVersion; } function withQuarantineWriter(env, operation) { const storePath = resolveQuarantineStorePath(env); const existed = existsSync(storePath); const dir = path.dirname(storePath); mkdirSync(dir, { recursive: true, mode: OPENCLAW_QUARANTINE_DIR_MODE }); applyPrivateModeSync(dir, OPENCLAW_QUARANTINE_DIR_MODE); const database = openNodeSqliteDatabase(storePath, { timeout: OPENCLAW_QUARANTINE_BUSY_TIMEOUT_MS }); let completed = false; try { if (!existed) applyPrivateModeSync(storePath, OPENCLAW_QUARANTINE_FILE_MODE); configureQuarantineWriter(database, storePath); database.exec(`CREATE TABLE IF NOT EXISTS agent_integrity_verifications ( path TEXT NOT NULL PRIMARY KEY, dev TEXT NOT NULL, ino TEXT NOT NULL, app_version TEXT NOT NULL, verified_at INTEGER NOT NULL, clean_close INTEGER NOT NULL CHECK (clean_close IN (0, 1)) ) STRICT;`); const result = operation(database); completed = true; return result; } finally { if (database.isOpen) database.close(); if (completed || !existed) applyPrivateModeSync(storePath, OPENCLAW_QUARANTINE_FILE_MODE); } } /** Read one authoritative quarantine decision without creating the store. */ function readOpenClawDatabaseQuarantine(pathname, options = {}) { const storePath = resolveQuarantineStorePath(options.env ?? process.env); if (!existsSync(storePath)) return; const database = openSqliteReadOnlyDatabase(storePath, { timeout: OPENCLAW_QUARANTINE_BUSY_TIMEOUT_MS }); let outcome; try { outcome = { value: readQuarantineDecision(database, pathname, storePath) }; } catch (error) { outcome = { error }; } try { database.close(); } catch (closeError) { throw new OpenClawQuarantineReadCleanupError("error" in outcome ? [outcome.error, closeError] : [closeError], "value" in outcome ? outcome.value : void 0); } if ("error" in outcome) throw outcome.error; return outcome.value; } function readQuarantineDecision(database, pathname, storePath) { let row; let queryFailure; try { row = database.prepare(`SELECT q.*, q.path AS quarantine_path, v.user_version FROM pragma_user_version AS v LEFT JOIN quarantined_databases AS q ON q.path = ? LIMIT 1`).get(path.resolve(pathname)); } catch (error) { if (sqlitePrimaryResultCode(error) !== 1) throw error; row = { user_version: readQuarantineSchemaVersion(database, storePath) }; queryFailure = { error }; } const userVersion = parseQuarantineSchemaVersion(row?.user_version, storePath); if (userVersion === 0) return; if (userVersion > OPENCLAW_QUARANTINE_SCHEMA_VERSION) throw new Error(`OpenClaw quarantine store ${storePath} uses newer schema version ${userVersion}.`); if (queryFailure) throw queryFailure.error; if (!row || row.quarantine_path === null) return; const verifiedGenerationJson = userVersion >= 2 ? row.verified_generation : void 0; if (row.kind !== "agent" && row.kind !== "state" || typeof row.reason !== "string" || typeof row.quarantined_at !== "number" || !Number.isInteger(row.quarantined_at) || verifiedGenerationJson !== void 0 && verifiedGenerationJson !== null && typeof verifiedGenerationJson !== "string") throw new Error(`OpenClaw quarantine store ${storePath} contains an invalid row.`); if (typeof verifiedGenerationJson === "string") { let verifiedGeneration; try { verifiedGeneration = parseSqliteFileGeneration(verifiedGenerationJson); } catch { throw new Error(`OpenClaw quarantine store ${storePath} contains an invalid row.`); } try { const currentGeneration = readStableSqliteFileGeneration(path.resolve(pathname)); if (!sameSqliteFileGeneration(verifiedGeneration, currentGeneration)) return; } catch { return; } } return { kind: row.kind, quarantinedAt: row.quarantined_at, reason: row.reason }; } /** Runtime opens refuse recorded damage while tolerating a broken quarantine index. */ function readOpenClawDatabaseQuarantineFailure(kind, pathname, options = {}) { let quarantine; let cleanupFailure; try { quarantine = readOpenClawDatabaseQuarantine(pathname, options); } catch (error) { if (!(error instanceof OpenClawQuarantineReadCleanupError)) return; if (!error.quarantine) throw error; quarantine = error.quarantine; cleanupFailure = error; } if (!quarantine) return; const failure = /* @__PURE__ */ new Error(`OpenClaw ${kind} database ${pathname} is quarantined after integrity verification failed: ${quarantine.reason ?? "unknown integrity error"}. Restore the database from a backup or repair it, then run openclaw doctor --fix to clear the quarantine. See ${OPENCLAW_DATABASE_SCHEMA_DOCS_URL}.`); failure.name = "SqliteIntegrityError"; if (cleanupFailure) failure.cause = cleanupFailure; return failure; } function recordOpenClawDatabaseQuarantine(options) { const serializedGeneration = options.generation ? serializeSqliteFileGeneration(options.generation) : null; try { return withQuarantineWriter(options.env ?? process.env, (database) => runSqliteImmediateTransactionSync(database, () => { database.prepare(` INSERT INTO quarantined_databases ( path, kind, reason, quarantined_at, writer_app_version, verified_generation ) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(path) DO UPDATE SET kind = excluded.kind, reason = excluded.reason, quarantined_at = excluded.quarantined_at, writer_app_version = excluded.writer_app_version, verified_generation = excluded.verified_generation `).run(path.resolve(options.path), options.kind, options.reason, Date.now(), VERSION, serializedGeneration); if (options.kind === "agent") invalidateAgentIntegrityVerification(database, options.path); return true; }, { databaseLabel: resolveQuarantineStorePath(options.env ?? process.env), operationLabel: "quarantine.record" })); } catch { return false; } } function clearOpenClawDatabaseQuarantine(pathname, options = {}) { const env = options.env ?? process.env; if (!existsSync(resolveQuarantineStorePath(env))) return true; try { return withQuarantineWriter(env, (database) => runSqliteImmediateTransactionSync(database, () => { database.prepare("DELETE FROM quarantined_databases WHERE path = ?").run(path.resolve(pathname)); invalidateAgentIntegrityVerification(database, pathname); return true; }, { databaseLabel: resolveQuarantineStorePath(env), operationLabel: "quarantine.clear" })); } catch { return false; } } //#endregion export { readOpenClawAgentIntegrityVerification as a, recordOpenClawDatabaseQuarantine as c, markOpenClawAgentIntegrityClean as i, resolveAgentDatabaseIntegrityGateReason as l, clearOpenClawAgentIntegrityVerification as n, readOpenClawDatabaseQuarantineFailure as o, clearOpenClawDatabaseQuarantine as r, recordOpenClawAgentIntegrityVerification as s, canReuseOpenClawAgentIntegrityVerification as t };