openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
218 lines (217 loc) • 11.4 kB
JavaScript
import { i as executeSqliteQuerySync, o as getNodeSqliteKysely, p as sql } from "./kysely-sync-DUWzQvCU.mjs";
import { a as resolveExistingSqliteFileUri, r as openNodeSqliteDatabase } from "./node-sqlite-DyeJqxPf.mjs";
import { a as runSqliteImmediateTransactionSync } from "./sqlite-transaction-DBRLvCDM.mjs";
import { p as syncDirectorySync, s as requireDirectorySync } from "./directory-durability-r17SFK6C.mjs";
import { n as withExistingSqliteRollbackDatabase } from "./sqlite-existing-database-D6EBvtXU.mjs";
import { c as resolvePackageActivationAnchor, d as resolvePackageActivationJournalPath, l as resolvePackageActivationControl, n as assertPackageActivationLayout } from "./package-update-activation-paths-BEqYuIQ6.mjs";
import { n as ImmutableInstallRecordSchema, r as ImmutablePreparedGenerationSchema, t as ImmutableInstallDescriptorSchema } from "./update-immutable-install-schema-DOyR4KUG.mjs";
import fs from "node:fs";
import path from "node:path";
import { isDeepStrictEqual } from "node:util";
//#region src/infra/package-update-activation-immutable.ts
const MAX_RECORD_BYTES = 1048576;
const queries = (db) => getNodeSqliteKysely(db);
function rootOwnedIdentity(file, directory) {
const stat = fs.lstatSync(file, { bigint: true });
if (stat.uid !== 0n || stat.ino === 0n || (directory ? !stat.isDirectory() : !stat.isFile() || stat.nlink !== 1n) || (stat.mode & 18n) !== 0n || fs.realpathSync(file) !== file) throw new Error(`Immutable installation control has unsafe ownership or permissions: ${file}`);
return `${stat.dev}:${stat.ino}`;
}
function validateRecord(record) {
const parsed = ImmutableInstallRecordSchema.parse(record);
for (const generation of [
parsed.descriptor.current,
parsed.prepared,
parsed.activation?.previous,
parsed.activation?.operation?.previous,
parsed.activation?.operation?.candidate
]) if (generation && generation.path !== path.join(parsed.descriptor.root, "releases", generation.sha)) throw new Error("Immutable generation is outside its recorded release root.");
if (Buffer.byteLength(JSON.stringify(parsed)) > MAX_RECORD_BYTES) throw new Error("Immutable installation record exceeds 1 MiB.");
if (parsed.activation?.operation && (!parsed.descriptor.activationEnabled || parsed.activation.operation.authority.installKey !== parsed.descriptor.root)) throw new Error("Immutable activation does not match its enabled installation owner.");
const recovery = parsed.activation?.operation?.recovery;
if (recovery) {
const control = resolvePackageActivationControl(resolvePackageActivationAnchor(parsed.descriptor.root));
if (recovery.root !== parsed.descriptor.root || recovery.path !== path.join(control, `recovery-${recovery.sha}`) || recovery.helperPath !== path.join(control, "recovery.mjs")) throw new Error("Immutable recovery artifact is outside its installation control.");
}
return parsed;
}
function readRecord(db, root, rootIdentity) {
const rows = executeSqliteQuerySync(db, queries(db).selectFrom("immutable_installation").selectAll().where((eb) => eb.fn("length", [eb.cast("descriptor_json", "blob")]), "<=", MAX_RECORD_BYTES).where((eb) => eb.fn("length", [eb.cast("prepared_json", "blob")]), "<=", MAX_RECORD_BYTES).limit(2)).rows;
const row = rows[0];
if (rows.length !== 1 || !row || row.slot !== 1) throw new Error("Immutable installation control must contain one bounded record.");
if (row.activation_json && Buffer.byteLength(row.activation_json) > MAX_RECORD_BYTES) throw new Error("Immutable activation record exceeds 1 MiB.");
const record = validateRecord({
revision: row.revision,
descriptor: JSON.parse(row.descriptor_json),
prepared: JSON.parse(row.prepared_json),
...row.activation_json && row.activation_json !== "null" ? { activation: JSON.parse(row.activation_json) } : {}
});
if (record.descriptor.root !== root || record.descriptor.rootIdentity !== rootIdentity) throw new Error("Immutable installation control does not match the installation.");
return record;
}
function captureImmutableControl(root) {
const anchor = resolvePackageActivationAnchor(root);
assertPackageActivationLayout(anchor);
const control = resolvePackageActivationControl(anchor);
const journal = resolvePackageActivationJournalPath(anchor);
const rootIdentity = rootOwnedIdentity(root, true);
const identities = [
root,
path.dirname(root),
control,
journal
].map((file, index) => rootOwnedIdentity(file, index !== 3));
const assertIdentity = () => {
if ([
root,
path.dirname(root),
control,
journal
].some((file, index) => rootOwnedIdentity(file, index !== 3) !== identities[index])) throw new Error("Immutable installation control identity changed.");
};
const validate = (db) => {
if (db.prepare("SELECT name FROM sqlite_schema WHERE type = 'table' AND name = 'package_activation'").get()) throw new Error("An existing package activation journal remains with its recovery owner.");
};
const read = (db) => readRecord(db, root, rootIdentity);
return {
control,
journal,
assertIdentity,
assertFileSafe: (file) => {
rootOwnedIdentity(file, false);
},
validate,
read
};
}
function withImmutableControl(root, write, operation) {
const { journal, assertIdentity, validate, read } = captureImmutableControl(root);
return withExistingSqliteRollbackDatabase(journal, {
write,
busyTimeoutMs: 0,
assertIdentity,
validate
}, (db, transact) => {
if (write) db.exec("PRAGMA synchronous = EXTRA");
const fencedTransaction = (work, options) => transact(work, {
...options,
withCommit(commit) {
assertIdentity();
if (options?.withCommit) options.withCommit(commit);
else commit();
}
});
return operation(db, fencedTransaction, () => read(db));
});
}
/** CLI adoption only; the caller retains live installation/executor authority. */
function createImmutableInstallRecord(descriptor, assertCurrent) {
const record = validateRecord({
revision: 0,
descriptor: ImmutableInstallDescriptorSchema.parse(descriptor),
prepared: null
});
const root = record.descriptor.root;
const anchor = resolvePackageActivationAnchor(root);
const control = resolvePackageActivationControl(anchor);
const parent = path.dirname(root);
const parentIdentity = rootOwnedIdentity(parent, true);
const assertAdoption = () => {
assertCurrent();
assertPackageActivationLayout(anchor);
if (rootOwnedIdentity(root, true) !== record.descriptor.rootIdentity || rootOwnedIdentity(parent, true) !== parentIdentity || fs.lstatSync(control, { throwIfNoEntry: false })) throw new Error("Immutable adoption requires an unchanged installation and no existing control.");
};
assertAdoption();
const stagedControl = fs.mkdtempSync(`${control}.prepare-`);
const stagedIdentity = rootOwnedIdentity(stagedControl, true);
const journal = path.join(stagedControl, "operation.sqlite");
try {
fs.chmodSync(stagedControl, 493);
const fd = fs.openSync(journal, "wx", 420);
try {
fs.fchmodSync(fd, 420);
const journalIdentity = rootOwnedIdentity(journal, false);
const assertCreated = () => {
assertAdoption();
if (rootOwnedIdentity(stagedControl, true) !== stagedIdentity || rootOwnedIdentity(journal, false) !== journalIdentity) throw new Error("Immutable adoption staging identity changed.");
};
const db = openNodeSqliteDatabase(resolveExistingSqliteFileUri(journal));
try {
db.exec("PRAGMA synchronous = EXTRA");
runSqliteImmediateTransactionSync(db, () => {
assertCreated();
executeSqliteQuerySync(db, queries(db).schema.createTable("immutable_installation").addColumn("slot", "integer", (column) => column.primaryKey().notNull().check(sql`slot = 1`)).addColumn("revision", "integer", (column) => column.notNull()).addColumn("descriptor_json", "text", (column) => column.notNull()).addColumn("prepared_json", "text", (column) => column.notNull()).addColumn("activation_json", "text", (column) => column.notNull().defaultTo("null")).modifyEnd(sql`STRICT`));
executeSqliteQuerySync(db, queries(db).insertInto("immutable_installation").values({
slot: 1,
revision: 0,
descriptor_json: JSON.stringify(record.descriptor),
prepared_json: "null"
}));
assertCreated();
}, { withCommit(commit) {
assertCreated();
commit();
} });
} finally {
db.close();
}
fs.fsyncSync(fd);
assertCreated();
} finally {
fs.closeSync(fd);
}
requireDirectorySync(syncDirectorySync(stagedControl), "Immutable adoption control");
assertAdoption();
fs.renameSync(stagedControl, control);
requireDirectorySync(syncDirectorySync(parent), "Immutable adoption publication");
return record;
} finally {
if (fs.lstatSync(stagedControl, { throwIfNoEntry: false })) {
if (rootOwnedIdentity(stagedControl, true) === stagedIdentity) fs.rmSync(stagedControl, { recursive: true });
}
}
}
/** Revalidate the durable revision immediately before a synchronous filesystem effect. */
function assertImmutableInstallRecordCurrent(expected, assertCurrent) {
assertCurrent();
withImmutableControl(expected.descriptor.root, false, (_db, _transact, read) => {
if (!isDeepStrictEqual(read(), expected)) throw new Error("Immutable activation record is no longer current.");
});
assertCurrent();
}
/** One control owner publishes adoption, preparation, pointer effects, and recovery. */
function updateImmutableInstallRecord(expected, changes, assertCurrent) {
const next = validateRecord({
...changes,
revision: expected.revision + 1
});
if (next.descriptor.root !== expected.descriptor.root || next.descriptor.rootIdentity !== expected.descriptor.rootIdentity) throw new Error("Immutable update cannot transfer installation ownership.");
assertCurrent();
return withImmutableControl(expected.descriptor.root, true, (db, transact, read) => {
const columns = db.prepare("PRAGMA table_info(immutable_installation)").all();
return transact(() => {
assertCurrent();
if (!isDeepStrictEqual(read(), expected)) throw new Error("Immutable preparation record is no longer current.");
if (!columns.some((column) => column.name === "activation_json")) executeSqliteQuerySync(db, queries(db).schema.alterTable("immutable_installation").addColumn("activation_json", "text", (column) => column.notNull().defaultTo("null")));
executeSqliteQuerySync(db, queries(db).updateTable("immutable_installation").set({
revision: next.revision,
descriptor_json: JSON.stringify(next.descriptor),
prepared_json: JSON.stringify(next.prepared),
activation_json: JSON.stringify(next.activation ?? null)
}).where("slot", "=", 1).where("revision", "=", expected.revision));
return read();
}, { withCommit(commit) {
assertCurrent();
commit();
} });
});
}
/** Recording preparation never grants pointer publication or service authority. */
function recordImmutablePreparedGeneration(expected, prepared, assertCurrent) {
if (expected.activation?.operation) throw new Error("Immutable activation recovery is pending; run openclaw update recover.");
return updateImmutableInstallRecord(expected, {
...expected,
prepared: ImmutablePreparedGenerationSchema.parse(prepared)
}, assertCurrent);
}
//#endregion
export { updateImmutableInstallRecord as a, recordImmutablePreparedGeneration as i, captureImmutableControl as n, createImmutableInstallRecord as r, assertImmutableInstallRecordCurrent as t };