UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,135 lines • 56.1 kB
import { t as containsAsciiControlCharacter } from "./string-normalization-CECOqf64.mjs";
import { a as canonicalPathFromExistingAncestor$1, c as isPathInside$1, o as ensureAbsoluteDirectory$1, w as root } from "./fs-safe-BUXBSN-4.mjs";
import { r as resolveStateDir } from "./state-dir-BHJORNly.mjs";
import "./paths-DGIwWbpu.mjs";
import { t as openNodeSqliteDatabase } from "./node-sqlite-XIcwmOrl.mjs";
import { d as createPrivateSqliteDirectory, f as createPrivateSqliteTempDirectory } from "./sqlite-snapshot-staging-BIgaAYlp.mjs";
import { a as readSqliteUserVersion } from "./sqlite-user-version-ZJ4dPMsi.mjs";
import { t as applyPrivateModeSync } from "./private-mode-B6dWGRb2.mjs";
import { a as resolveSqliteDatabaseFilePaths, t as SQLITE_SIDECAR_SUFFIXES } from "./sqlite-files-BEC3zbaw.mjs";
import { b as assertOpenClawStateDatabaseForMaintenance } from "./openclaw-state-db-fast-path-CPAamWzU.mjs";
import { n as runExec } from "./exec-CMAKGVCq.mjs";
import { c as sha256File, f as syncDirectoryIfSupported, i as pinDirectory, l as syncDirectory, n as getPublishFileExclusiveFailureDetails, o as publishFileNoClobber, s as requireDirectorySync, t as ensureDurableDirectory } from "./directory-durability-CqSvP1tc.mjs";
import { i as sameFileMutationFingerprint, t as copyFileHandle } from "./file-descriptor-B4yBUiAP.mjs";
import { n as assertSqliteIntegrity } from "./sqlite-integrity-GtqcmwG3.mjs";
import { n as normalizeAgentId, t as isValidAgentId } from "./agent-id-D1-Q6vR4.mjs";
import "./session-key-C09ow4h0.mjs";
import "./openclaw-state-db-D1jWDzK3.mjs";
import { t as resolveSystemBin } from "./resolve-system-bin-DzBc-jWt.mjs";
import { t as assertOpenClawAgentDatabaseForMaintenance } from "./openclaw-agent-db-maintenance-CYe8pVkd.mjs";
import { t as assertNotUpdateCapturePath } from "./update-capture-paths-BuDS3zdN.mjs";
import { t as loadSqliteVecExtension } from "./sqlite-vec-BcbJNyja.mjs";
import { r as publishVerifiedSqliteFile, t as createVerifiedSqliteSnapshot } from "./sqlite-snapshot-c6hBDowh.mjs";
import { t as embedSessionColdArchivesInSnapshot } from "./session-cold-storage-backup-BfxM5Vhp.mjs";
import { n as sanitizeOpenClawStateLeaseRows, t as sanitizeOpenClawGlobalStateSnapshot } from "./openclaw-state-snapshot-sanitizer-B8rMlqrV.mjs";
import fs from "node:fs";
import path from "node:path";
import fs$1 from "node:fs/promises";
import os from "node:os";
import { isDeepStrictEqual } from "node:util";
import { randomUUID } from "node:crypto";
import { assertDirectoryIdentitySync, sameFileIdentity } from "@openclaw/fs-safe/advanced";
import { hasNodeErrorCode } from "@openclaw/fs-safe/path";
import { readOwnerAndDaclBatch } from "@openclaw/fs-safe/permissions";
//#region src/snapshot/snapshot-provider.ts
const SNAPSHOT_MANIFEST_FILENAME = "manifest.json";
const SNAPSHOT_SQLITE_FILENAME = "database.sqlite";
//#endregion
//#region src/snapshot/manifest.ts
const MAX_MANIFEST_BYTES = 1048576;
const MAX_SQLITE_USER_VERSION = 2147483647;
const MIN_SQLITE_USER_VERSION = -2147483648;
const SNAPSHOT_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,254}$/;
const SHA256_PATTERN = /^[a-f0-9]{64}$/;
async function hashSnapshotArtifact(snapshotDir) {
	const opened = await (await root(snapshotDir)).open(SNAPSHOT_SQLITE_FILENAME, {
		hardlinks: "reject",
		symlinks: "reject"
	});
	try {
		return {
			...await hashFileHandle(opened.handle),
			stat: opened.stat
		};
	} finally {
		await opened.handle.close();
	}
}
async function copySnapshotArtifact(snapshotDir, targetPath) {
	const source = await (await root(snapshotDir)).open(SNAPSHOT_SQLITE_FILENAME, {
		hardlinks: "reject",
		symlinks: "reject"
	});
	let target;
	let targetIdentity;
	try {
		target = await fs$1.open(targetPath, "wx+", 384);
		targetIdentity = await target.stat();
		const digest = await hashFileHandle(source.handle, target);
		await target.sync();
		const finalIdentity = await target.stat();
		const currentIdentity = await fs$1.lstat(targetPath);
		if (!sameFileIdentity(targetIdentity, finalIdentity) || !sameFileIdentity(targetIdentity, currentIdentity)) throw new Error(`Snapshot restore staging file changed during copy: ${targetPath}`);
		return {
			...digest,
			stat: finalIdentity
		};
	} catch (error) {
		await target?.close().catch(() => void 0);
		target = void 0;
		if (targetIdentity) {
			const currentIdentity = await fs$1.lstat(targetPath).catch(() => void 0);
			if (currentIdentity && sameFileIdentity(targetIdentity, currentIdentity)) await fs$1.unlink(targetPath).catch(() => void 0);
		}
		throw error;
	} finally {
		await target?.close().catch(() => void 0);
		await source.handle.close().catch(() => void 0);
	}
}
async function hashFileHandle(source, target) {
	const initialStat = await source.stat({ bigint: true });
	let sizeBytes = 0;
	if (target) sizeBytes = await copyFileHandle(source, target);
	const hashed = await sha256File(target ?? source);
	const finalStat = await source.stat({ bigint: true });
	if (!sameFileMutationFingerprint(initialStat, finalStat) || target && sizeBytes !== hashed.bytes) throw new Error("Snapshot artifact changed while being read.");
	return {
		sha256: hashed.digest,
		sizeBytes: hashed.bytes
	};
}
async function writeSnapshotManifest(snapshotDir, manifest) {
	const manifestPath = path.join(snapshotDir, SNAPSHOT_MANIFEST_FILENAME);
	const handle = await fs$1.open(manifestPath, "wx+", 384);
	try {
		await handle.writeFile(`${JSON.stringify(manifest, null, 2)}\n`, "utf8");
		await handle.sync();
	} finally {
		await handle.close();
	}
}
async function readSnapshotManifest(snapshotDir, expectedSnapshotId = path.basename(snapshotDir)) {
	const snapshotRoot = await root(snapshotDir);
	const manifestPath = path.join(snapshotDir, SNAPSHOT_MANIFEST_FILENAME);
	const result = await snapshotRoot.read(SNAPSHOT_MANIFEST_FILENAME, {
		hardlinks: "reject",
		maxBytes: MAX_MANIFEST_BYTES,
		symlinks: "reject"
	});
	let parsed;
	try {
		parsed = JSON.parse(result.buffer.toString("utf8"));
	} catch (error) {
		throw new Error(`Snapshot manifest is not valid JSON: ${manifestPath}`, { cause: error });
	}
	return parseSnapshotManifest(parsed, manifestPath, expectedSnapshotId);
}
function parseSnapshotManifest(value, manifestPath, expectedSnapshotId) {
	const record = requireRecord(value, "manifest", manifestPath);
	requireExactKeys(record, [
		"schemaVersion",
		"snapshotId",
		"createdAt",
		"database",
		"artifact"
	]);
	if (record.schemaVersion !== 1) throw new Error(`Unsupported snapshot manifest schemaVersion ${String(record.schemaVersion)}: ${manifestPath}`);
	const snapshotId = requireSnapshotId(record.snapshotId, manifestPath);
	if (snapshotId !== expectedSnapshotId) throw new Error(`Snapshot manifest id ${snapshotId} does not match directory ${expectedSnapshotId}: ${manifestPath}`);
	const createdAt = requireCanonicalTimestamp(record.createdAt, manifestPath);
	const database = parseSnapshotDatabase(record.database, manifestPath);
	const artifactRecord = requireRecord(record.artifact, "artifact", manifestPath);
	requireExactKeys(artifactRecord, [
		"path",
		"sha256",
		"sizeBytes"
	]);
	if (artifactRecord.path !== "database.sqlite") throw new Error(`Snapshot manifest artifact.path must be ${SNAPSHOT_SQLITE_FILENAME}: ${manifestPath}`);
	if (typeof artifactRecord.sha256 !== "string" || !SHA256_PATTERN.test(artifactRecord.sha256)) throw new Error(`Snapshot manifest artifact.sha256 is invalid: ${manifestPath}`);
	if (!Number.isSafeInteger(artifactRecord.sizeBytes) || Number(artifactRecord.sizeBytes) <= 0) throw new Error(`Snapshot manifest artifact.sizeBytes is invalid: ${manifestPath}`);
	return {
		schemaVersion: 1,
		snapshotId,
		createdAt,
		database,
		artifact: {
			path: SNAPSHOT_SQLITE_FILENAME,
			sha256: artifactRecord.sha256,
			sizeBytes: Number(artifactRecord.sizeBytes)
		}
	};
}
function parseSnapshotDatabase(value, manifestPath) {
	const database = requireRecord(value, "database", manifestPath);
	const role = database.role;
	const basename = requireSafeText(database.basename, "database.basename", manifestPath, 255);
	if (path.basename(basename) !== basename || basename === "." || basename === "..") throw new Error(`Snapshot manifest database.basename is invalid: ${manifestPath}`);
	const userVersion = requireSqliteUserVersion(database.userVersion, manifestPath);
	if (role === "global") {
		requireExactKeys(database, [
			"role",
			"basename",
			"userVersion"
		]);
		return {
			role,
			basename,
			userVersion
		};
	}
	if (role === "agent") {
		requireExactKeys(database, [
			"role",
			"agentId",
			"basename",
			"userVersion"
		]);
		const agentId = requireSafeText(database.agentId, "database.agentId", manifestPath, 64);
		if (!isValidAgentId(agentId) || normalizeAgentId(agentId) !== agentId) throw new Error(`Snapshot manifest database.agentId is invalid: ${manifestPath}`);
		return {
			role,
			agentId,
			basename,
			userVersion
		};
	}
	if (role === "generic") {
		requireExactKeys(database, [
			"role",
			"id",
			"basename",
			"userVersion"
		]);
		return {
			role,
			id: requireSafeText(database.id, "database.id", manifestPath, 256),
			basename,
			userVersion
		};
	}
	throw new Error(`Snapshot manifest database.role is invalid: ${manifestPath}`);
}
function requireRecord(value, field, manifestPath) {
	if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`Snapshot manifest ${field} must be an object: ${manifestPath}`);
	return value;
}
function requireExactKeys(record, expectedKeys) {
	const actual = Object.keys(record).toSorted();
	const expected = [...expectedKeys].toSorted();
	if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) throw new Error(`Snapshot manifest fields must be exactly ${expectedKeys.join(", ")}; got ${actual.join(", ")}`);
}
function requireSnapshotId(value, manifestPath) {
	if (typeof value !== "string" || !SNAPSHOT_ID_PATTERN.test(value)) throw new Error(`Snapshot manifest snapshotId is invalid: ${manifestPath}`);
	return value;
}
function requireCanonicalTimestamp(value, manifestPath) {
	if (typeof value !== "string") throw new Error(`Snapshot manifest createdAt is invalid: ${manifestPath}`);
	const parsed = new Date(value);
	if (!Number.isFinite(parsed.getTime()) || parsed.toISOString() !== value) throw new Error(`Snapshot manifest createdAt is not canonical ISO 8601: ${manifestPath}`);
	return value;
}
function requireSafeText(value, field, manifestPath, maxLength) {
	if (typeof value !== "string" || value.length === 0 || value.length > maxLength || value.trim() !== value || containsAsciiControlCharacter(value)) throw new Error(`Snapshot manifest ${field} is invalid: ${manifestPath}`);
	return value;
}
function requireSqliteUserVersion(value, manifestPath) {
	if (!Number.isSafeInteger(value) || Number(value) < MIN_SQLITE_USER_VERSION || Number(value) > MAX_SQLITE_USER_VERSION) throw new Error(`Snapshot manifest database.userVersion is invalid: ${manifestPath}`);
	return Number(value);
}
//#endregion
//#region src/snapshot/openclaw-snapshot-copy.ts
function normalizeSnapshotIdentity(identity) {
	if (identity.role === "global") return identity;
	if (identity.role === "agent") {
		const agentId = normalizeAgentId(identity.agentId);
		if (!isValidAgentId(identity.agentId) || agentId !== identity.agentId) throw new Error(`SQLite snapshot agent id must be canonical: ${identity.agentId}`);
		return {
			role: "agent",
			agentId
		};
	}
	const id = identity.id.trim();
	if (!id || id !== identity.id || id.length > 256 || containsAsciiControlCharacter(id)) throw new Error("SQLite snapshot generic database id is invalid.");
	return {
		role: "generic",
		id
	};
}
function buildSnapshotValidator(identity) {
	if (identity.role === "global") return (database, pathname) => assertOpenClawStateDatabaseForMaintenance(database, { pathname });
	if (identity.role === "agent") return (database, pathname) => assertOpenClawAgentDatabaseForMaintenance(database, {
		agentId: identity.agentId,
		pathname
	});
	return () => void 0;
}
/** Produce the canonical sanitized, compact, verified copy used by every snapshot provider. */
async function createOpenClawSnapshotCopy(params) {
	assertNotUpdateCapturePath(params.database.path, resolveStateDir());
	const identity = normalizeSnapshotIdentity(params.database.identity);
	return {
		identity,
		...await createVerifiedSqliteSnapshot({
			sourcePath: params.database.path,
			targetPath: params.targetPath,
			requireNonEmptySource: identity.role !== "generic",
			transform: async (database) => {
				if (identity.role === "global") sanitizeOpenClawGlobalStateSnapshot(database);
				else if (identity.role === "agent") sanitizeOpenClawStateLeaseRows(database);
				await embedSessionColdArchivesInSnapshot({
					database,
					sourceStorePath: params.database.path
				});
			},
			validate: buildSnapshotValidator(identity)
		})
	};
}
//#endregion
//#region src/snapshot/restore-paths.ts
async function assertFreshRestoreTarget(databasePath) {
	for (const candidate of resolveSqliteDatabaseFilePaths(databasePath)) {
		try {
			await fs$1.lstat(candidate);
		} catch (error) {
			if (hasNodeErrorCode(error, "ENOENT")) continue;
			throw error;
		}
		throw new Error(`Fresh SQLite restore path already exists: ${candidate}`);
	}
}
function assertNoSqliteSidecarsSync(databasePath, errorPrefix) {
	for (const suffix of SQLITE_SIDECAR_SUFFIXES) {
		const sidecarPath = `${databasePath}${suffix}`;
		try {
			fs.lstatSync(sidecarPath);
		} catch (error) {
			if (hasNodeErrorCode(error, "ENOENT")) continue;
			throw error;
		}
		throw new Error(`${errorPrefix}: ${sidecarPath}`);
	}
}
//#endregion
//#region src/snapshot/local-repository-directory-policy.ts
const MACOS_REPLACEMENT_ACL_PERMISSIONS = /* @__PURE__ */ new Set([
	"add_file",
	"add_subdirectory",
	"chown",
	"delete",
	"delete_child",
	"writesecurity"
]);
const WINDOWS_STAGING_REPLACEMENT_RIGHTS_MASK = 302841920;
const WINDOWS_TRUSTED_OWNER_SIDS = /* @__PURE__ */ new Set([
	"S-1-5-18",
	"S-1-5-32-544",
	"S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464"
]);
const WINDOWS_TRUSTED_ACCESS_SIDS = /* @__PURE__ */ new Set([...WINDOWS_TRUSTED_OWNER_SIDS, "S-1-3-0"]);
const WINDOWS_FILE_RIGHTS = [
	[1, "RD"],
	[2, "WD"],
	[4, "AD"],
	[8, "REA"],
	[16, "WEA"],
	[32, "X"],
	[64, "DC"],
	[128, "RA"],
	[256, "WA"],
	[65536, "D"],
	[131072, "RC"],
	[262144, "WDAC"],
	[524288, "WO"],
	[1048576, "S"],
	[33554432, "MA"],
	[268435456, "GA"],
	[536870912, "GE"],
	[1073741824, "GW"],
	[2147483648, "GR"]
];
const WINDOWS_KNOWN_FILE_RIGHTS_MASK = WINDOWS_FILE_RIGHTS.reduce((mask, [right]) => mask | right, 0);
let macosTrustedAclPrincipalsPromise;
function assertDirectory(stat, pathname, label) {
	if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error(`${label} must be a real directory: ${pathname}`);
}
async function assertDirectoryIdentity(directoryPath, expectedIdentity) {
	const currentIdentity = await fs$1.lstat(directoryPath, { bigint: typeof expectedIdentity.dev === "bigint" });
	assertDirectory(currentIdentity, directoryPath, "SQLite staging directory");
	if (!sameFileIdentity(currentIdentity, expectedIdentity)) throw new Error(`SQLite staging directory changed during operation: ${directoryPath}`);
}
function assertDirectoryIdentitySync$1(directoryPath, expectedIdentity) {
	const currentIdentity = fs.lstatSync(directoryPath);
	assertDirectory(currentIdentity, directoryPath, "SQLite staging directory");
	if (!sameFileIdentity(currentIdentity, expectedIdentity)) throw new Error(`SQLite staging directory changed during operation: ${directoryPath}`);
}
async function assertTrustedStagingRoot(expectedIdentity, rootPath, options = {}) {
	const resolvedRootPath = path.resolve(rootPath);
	const trustedRootPath = await fs$1.realpath(resolvedRootPath);
	const rootIdentity = await fs$1.lstat(trustedRootPath, { bigint: typeof expectedIdentity.dev === "bigint" });
	assertDirectory(rootIdentity, trustedRootPath, "Private SQLite staging root");
	if (!sameFileIdentity(rootIdentity, expectedIdentity)) throw new Error(`Private SQLite staging root changed during operation: ${resolvedRootPath}`);
	if (process.platform === "win32") {
		await assertTrustedWindowsStagingPath(trustedRootPath);
		return trustedRootPath;
	}
	const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
	const unsafeMode = (Number(rootIdentity.mode) & 18) !== 0;
	if (uid === void 0 || Number(rootIdentity.uid) !== uid || unsafeMode && options.allowModeRepair !== true) throw new Error(`Private SQLite staging root must be owned by the current user and not writable by other users: ${resolvedRootPath}`);
	if (process.platform === "darwin") await assertTrustedMacosAcl(trustedRootPath, options.allowModeRepair !== true);
	await assertTrustedPosixStagingAncestors(trustedRootPath, rootIdentity, uid);
	return trustedRootPath;
}
async function assertPrivateStagingDirectory(expectedIdentity, directoryPath) {
	const currentIdentity = await fs$1.lstat(directoryPath);
	assertDirectory(currentIdentity, directoryPath, "Private SQLite staging directory");
	if (!sameFileIdentity(currentIdentity, expectedIdentity)) throw new Error(`Private SQLite staging directory changed during operation: ${directoryPath}`);
	if (process.platform === "win32") return;
	const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
	if (uid === void 0 || currentIdentity.uid !== uid || (currentIdentity.mode & 63) !== 0) throw new Error(`Private SQLite staging directory permissions are unsafe: ${directoryPath}`);
	if (process.platform === "darwin") await assertTrustedMacosAcl(directoryPath, true);
}
async function assertTrustedPosixStagingAncestors(rootPath, rootIdentity, uid) {
	let childIdentity = rootIdentity;
	let currentPath = path.dirname(rootPath);
	while (currentPath !== rootPath) {
		const currentIdentity = await fs$1.lstat(currentPath);
		assertDirectory(currentIdentity, currentPath, "SQLite staging ancestor");
		const writableByOtherUsers = (currentIdentity.mode & 18) !== 0;
		const ownerCanReplaceChild = currentIdentity.uid !== uid && currentIdentity.uid !== 0;
		const stickyOwnerIsTrusted = currentIdentity.uid === uid || currentIdentity.uid === 0;
		const stickyProtectsChild = (currentIdentity.mode & 512) !== 0 && stickyOwnerIsTrusted && Number(childIdentity.uid) === uid;
		if (ownerCanReplaceChild || writableByOtherUsers && !stickyProtectsChild) throw new Error(`SQLite staging ancestor must not allow another user to replace its child: ${currentPath}`);
		if (process.platform === "darwin") await assertTrustedMacosAcl(currentPath, false);
		const parentPath = path.dirname(currentPath);
		if (parentPath === currentPath) return;
		childIdentity = currentIdentity;
		currentPath = parentPath;
	}
}
function parseMacosAclEntries(output, pathname) {
	const lines = output.split(/\r?\n/u);
	const header = lines.shift();
	if (!header) throw new Error(`Unable to inspect macOS ACL for SQLite staging: ${pathname}`);
	const entries = [];
	for (const line of lines) {
		if (!/^\s*\d+:\s/u.test(line)) continue;
		const match = line.match(/^\s*\d+:\s+(.+?)\s+(?:inherited\s+)?(allow|deny)\s+([a-z_,]+)\s*$/u);
		if (!match) throw new Error(`Unable to parse macOS ACL for SQLite staging: ${pathname}`);
		const [, principal, effect, permissions] = match;
		if (!principal || !permissions || effect !== "allow" && effect !== "deny") throw new Error(`Unable to parse macOS ACL for SQLite staging: ${pathname}`);
		entries.push({
			principal: normalizeAclPrincipal(principal),
			effect,
			permissions: new Set(permissions.split(","))
		});
	}
	if (/^[^\s]{10}\+/u.test(header) && entries.length === 0) throw new Error(`Unable to parse macOS ACL for SQLite staging: ${pathname}`);
	return entries;
}
function normalizeAclPrincipal(principal) {
	return principal.trim().toLowerCase();
}
async function resolveTrustedMacosAclPrincipals() {
	macosTrustedAclPrincipalsPromise ??= (async () => {
		const dsmemberutil = resolveSystemBin("dsmemberutil");
		if (!dsmemberutil) throw new Error("Unable to resolve dsmemberutil for macOS ACL verification.");
		const currentUsername = os.userInfo().username;
		const usernames = /* @__PURE__ */ new Set([currentUsername, "root"]);
		const trusted = /* @__PURE__ */ new Set();
		for (const username of usernames) {
			const { stdout } = await runExec(dsmemberutil, [
				"getuuid",
				"-U",
				username
			], {
				timeoutMs: 5e3,
				maxBuffer: 65536
			});
			const uuid = stdout.trim();
			if (!/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/iu.test(uuid)) throw new Error(`Unable to resolve trusted macOS ACL principal for ${username}.`);
			trusted.add(normalizeAclPrincipal(uuid));
			trusted.add(normalizeAclPrincipal(username));
			trusted.add(normalizeAclPrincipal(`user:${username}`));
		}
		return trusted;
	})();
	return await macosTrustedAclPrincipalsPromise;
}
async function assertTrustedMacosAcl(pathname, requirePrivate) {
	const ls = resolveSystemBin("ls");
	if (!ls) throw new Error(`Unable to verify macOS ACL for SQLite staging: ${pathname}`);
	let entries;
	try {
		const [result, trustedPrincipals] = await Promise.all([runExec(ls, [
			"-lden",
			"--",
			pathname
		], {
			timeoutMs: 5e3,
			maxBuffer: 1048576
		}), resolveTrustedMacosAclPrincipals()]);
		entries = parseMacosAclEntries(result.stdout, pathname).filter((entry) => !trustedPrincipals.has(entry.principal));
	} catch (error) {
		throw new Error(`Unable to verify macOS ACL for SQLite staging: ${pathname}`, { cause: error });
	}
	if (entries.find((entry) => entry.effect === "allow" && (requirePrivate || [...entry.permissions].some((permission) => MACOS_REPLACEMENT_ACL_PERMISSIONS.has(permission))))) throw new Error(`macOS ACL permits untrusted SQLite staging access: ${pathname}`);
}
async function assertTrustedWindowsStagingPath(rootPath) {
	const paths = [rootPath];
	let currentPath = path.dirname(rootPath);
	while (currentPath !== rootPath) {
		paths.push(currentPath);
		const parentPath = path.dirname(currentPath);
		if (parentPath === currentPath) break;
		currentPath = parentPath;
	}
	let security;
	try {
		security = await readOwnerAndDaclBatch(paths);
	} catch (error) {
		throw new Error(`Unable to verify private Windows ACL for SQLite staging: ${rootPath}`, { cause: error });
	}
	for (const [index, pathname] of paths.entries()) {
		const pathSecurity = security[index];
		if (pathSecurity.status !== "supported" || !pathSecurity.isLocal || !pathSecurity.daclPresent || !pathSecurity.complete) throw new Error(`Unable to verify private Windows ACL for SQLite staging: ${pathname}`);
		assertTrustedWindowsAcl(pathname, index === 0, pathSecurity);
	}
}
function assertTrustedWindowsAcl(pathname, requirePrivate, security) {
	const pathRole = requirePrivate ? "repository root" : "ancestor";
	const currentUserSid = security.currentUserSid.toUpperCase();
	const ownerSid = security.ownerSid.toUpperCase();
	if (ownerSid !== currentUserSid && !WINDOWS_TRUSTED_OWNER_SIDS.has(ownerSid)) throw new Error(`Windows SQLite staging ${pathRole} is owned by an untrusted principal: path=${pathname} principal=${ownerSid}. Choose a local directory owned only by the current user or a trusted OS principal.`);
	const allowedEntries = security.aces.filter((entry) => entry.aceType === "allow");
	if (allowedEntries.length === 0) throw new Error(`Unable to verify private Windows ACL for SQLite staging: ${pathname}`);
	const unsafeMask = requirePrivate ? -1048577 : WINDOWS_STAGING_REPLACEMENT_RIGHTS_MASK | ~WINDOWS_KNOWN_FILE_RIGHTS_MASK;
	const unsafeEntry = allowedEntries.find((entry) => {
		const sid = entry.sid.toUpperCase();
		return sid !== currentUserSid && !WINDOWS_TRUSTED_ACCESS_SIDS.has(sid) && (requirePrivate || !entry.flags.inheritOnly) && (entry.mask & unsafeMask) !== 0;
	});
	if (unsafeEntry) throw new Error(`Windows ACL permits untrusted SQLite staging access on ${pathRole}: path=${pathname} principal=${unsafeEntry.sid.toUpperCase()} rights=${formatWindowsSecurityRights(unsafeEntry)}. Remove the untrusted grant or choose a private local directory; do not use a shared or synced root.`);
}
function formatWindowsSecurityRights(entry) {
	const rights = WINDOWS_FILE_RIGHTS.filter(([right]) => (entry.mask & right) !== 0).map(([, name]) => name);
	if ((entry.mask & ~WINDOWS_KNOWN_FILE_RIGHTS_MASK) !== 0) rights.push("UNKNOWN");
	return `${[
		entry.flags.objectInherit ? "(OI)" : "",
		entry.flags.containerInherit ? "(CI)" : "",
		entry.flags.noPropagateInherit ? "(NP)" : "",
		entry.flags.inheritOnly ? "(IO)" : ""
	].join("")}(${rights.join(",")})`;
}
//#endregion
//#region src/snapshot/local-repository.ts
const SNAPSHOT_DIRECTORY_MODE = 448;
const SNAPSHOT_FILE_MODE = 384;
const SNAPSHOT_PENDING_FILENAME = ".pending";
const SNAPSHOT_ARTIFACT_ENTRIES = /* @__PURE__ */ new Set([
	SNAPSHOT_MANIFEST_FILENAME,
	SNAPSHOT_PENDING_FILENAME,
	SNAPSHOT_SQLITE_FILENAME
]);
const RESTORE_STAGING_ENTRIES = /* @__PURE__ */ new Set([SNAPSHOT_SQLITE_FILENAME]);
const VALIDATION_STAGING_ENTRIES = /* @__PURE__ */ new Set([SNAPSHOT_SQLITE_FILENAME, ...SQLITE_SIDECAR_SUFFIXES.map((suffix) => `${SNAPSHOT_SQLITE_FILENAME}${suffix}`)]);
function createLocalSqliteSnapshotProvider(options) {
	return new LocalSqliteSnapshotProvider(options);
}
var LocalSqliteSnapshotProvider = class {
	#allowedDatabaseRoles;
	#repositoryPath;
	#validationRootPath;
	#now;
	constructor(options) {
		this.#allowedDatabaseRoles = options.allowedDatabaseRoles;
		this.#repositoryPath = path.resolve(options.repositoryPath);
		this.#validationRootPath = path.resolve(options.validationRootPath ?? path.dirname(this.#repositoryPath));
		this.#now = options.now ?? (() => /* @__PURE__ */ new Date());
	}
	async create(database) {
		const repositoryReceipt = await ensurePrivateDirectory(this.#repositoryPath, "SQLite snapshot repository");
		const repositoryIdentity = repositoryReceipt.identity;
		const trustedRepositoryPath = await assertTrustedStagingRoot(repositoryIdentity, this.#repositoryPath);
		const sourcePath = path.resolve(database.path);
		const identity = normalizeSnapshotIdentity(database.identity);
		const now = this.#now();
		if (!Number.isFinite(now.getTime())) throw new Error("SQLite snapshot timestamp is invalid.");
		const snapshotId = buildSnapshotId(now);
		const snapshotRefPath = path.join(this.#repositoryPath, snapshotId);
		const snapshotDir = path.join(trustedRepositoryPath, snapshotId);
		const stagingDir = path.join(trustedRepositoryPath, `.tmp-${randomUUID()}`);
		const artifactPath = path.join(stagingDir, SNAPSHOT_SQLITE_FILENAME);
		await assertDirectoryIdentity(trustedRepositoryPath, repositoryIdentity);
		await createPrivateSqliteDirectory(stagingDir);
		let stagingIdentity;
		let publishedDirectory;
		let publishedIdentity;
		const publishedEntries = /* @__PURE__ */ new Map();
		let snapshotDirectoryCreated = false;
		try {
			await assertDirectoryIdentity(trustedRepositoryPath, repositoryIdentity);
			stagingIdentity = await fs$1.lstat(stagingDir);
			applyPrivateModeSync(stagingDir, SNAPSHOT_DIRECTORY_MODE);
			await assertPrivateStagingDirectory(stagingIdentity, stagingDir);
			await assertDirectoryIdentity(trustedRepositoryPath, repositoryIdentity);
			const result = await createOpenClawSnapshotCopy({
				database: {
					path: sourcePath,
					identity
				},
				targetPath: artifactPath
			});
			applyPrivateModeSync(artifactPath, SNAPSHOT_FILE_MODE);
			const artifact = await hashSnapshotArtifact(stagingDir);
			const manifest = {
				schemaVersion: 1,
				snapshotId,
				createdAt: now.toISOString(),
				database: buildDatabaseManifest(identity, sourcePath, result.userVersion),
				artifact: {
					path: SNAPSHOT_SQLITE_FILENAME,
					sha256: artifact.sha256,
					sizeBytes: artifact.sizeBytes
				}
			};
			await writeSnapshotManifest(stagingDir, manifest);
			applyPrivateModeSync(path.join(stagingDir, SNAPSHOT_MANIFEST_FILENAME), SNAPSHOT_FILE_MODE);
			await readSnapshotManifest(stagingDir, snapshotId);
			await syncDirectoryIfSupported(stagingDir);
			await assertDirectoryIdentity(trustedRepositoryPath, repositoryIdentity);
			try {
				await createPrivateSqliteDirectory(snapshotDir);
				snapshotDirectoryCreated = true;
			} catch (error) {
				if (error.code === "EEXIST") throw new Error(`SQLite snapshot directory already exists: ${snapshotDir}`, { cause: error });
				throw error;
			}
			await assertDirectoryIdentity(trustedRepositoryPath, repositoryIdentity);
			publishedDirectory = await pinDirectory(snapshotDir, { label: "SQLite snapshot directory" });
			publishedIdentity = publishedDirectory.receipt.identity;
			applyPrivateModeSync(snapshotDir, SNAPSHOT_DIRECTORY_MODE);
			await assertPrivateStagingDirectory(publishedIdentity, snapshotDir);
			await publishedDirectory.assertCurrent();
			const pendingPath = path.join(snapshotDir, SNAPSHOT_PENDING_FILENAME);
			const pendingHandle = await fs$1.open(pendingPath, "wx+", SNAPSHOT_FILE_MODE);
			let pendingIdentity;
			try {
				pendingIdentity = await pendingHandle.stat();
				publishedEntries.set(SNAPSHOT_PENDING_FILENAME, pendingIdentity);
				await pendingHandle.sync();
			} finally {
				await pendingHandle.close();
			}
			await publishedDirectory.assertCurrent();
			requireDirectorySync(await publishedDirectory.sync(), "SQLite snapshot directory");
			await assertDirectoryIdentity(trustedRepositoryPath, repositoryIdentity);
			requireDirectorySync(await syncDirectory(repositoryReceipt), "SQLite snapshot repository");
			await publishedDirectory.assertCurrent();
			await publishSnapshotEntryNoOverwrite(path.join(stagingDir, SNAPSHOT_SQLITE_FILENAME), path.join(snapshotDir, SNAPSHOT_SQLITE_FILENAME), SNAPSHOT_SQLITE_FILENAME, publishedEntries);
			await publishedDirectory.assertCurrent();
			await publishSnapshotEntryNoOverwrite(path.join(stagingDir, SNAPSHOT_MANIFEST_FILENAME), path.join(snapshotDir, SNAPSHOT_MANIFEST_FILENAME), SNAPSHOT_MANIFEST_FILENAME, publishedEntries);
			await publishedDirectory.assertCurrent();
			requireDirectorySync(await publishedDirectory.sync(), "SQLite snapshot directory");
			await commitPendingSnapshot({
				allowedDatabaseRoles: void 0,
				repositoryIdentity,
				repositoryPath: trustedRepositoryPath,
				snapshotPath: snapshotDir,
				snapshotDirectory: publishedDirectory,
				validationRootPath: trustedRepositoryPath,
				expected: {
					manifest,
					pendingIdentity
				}
			});
			publishedEntries.delete(SNAPSHOT_PENDING_FILENAME);
			await publishedDirectory.close();
			publishedDirectory = void 0;
			return {
				ref: { path: snapshotRefPath },
				manifest
			};
		} catch (error) {
			await publishedDirectory?.close().catch(() => void 0);
			publishedDirectory = void 0;
			if (snapshotDirectoryCreated) publishedIdentity ??= await fs$1.lstat(snapshotDir).catch(() => void 0);
			if (publishedIdentity) {
				if (await removePublishedSnapshotDirectoryIfOwned(snapshotDir, publishedIdentity, publishedEntries)) await syncDirectoryIfSupported(trustedRepositoryPath);
			}
			throw error;
		} finally {
			if (stagingIdentity ? await removePrivateDirectoryIfOwned(stagingDir, stagingIdentity, SNAPSHOT_ARTIFACT_ENTRIES).catch(() => false) : await fs$1.rmdir(stagingDir).then(() => true).catch(() => false)) await syncDirectoryIfSupported(trustedRepositoryPath).catch(() => void 0);
		}
	}
	async verify(snapshot) {
		const snapshotDir = await this.#resolveSnapshotDirectory(snapshot);
		const manifest = await readVerifiedSnapshotManifest(snapshotDir);
		assertAllowedDatabaseRole(manifest, this.#allowedDatabaseRoles);
		const artifact = await hashSnapshotArtifact(snapshotDir);
		const artifactPath = path.join(snapshotDir, SNAPSHOT_SQLITE_FILENAME);
		assertArtifactMatchesManifest(artifactPath, artifact, manifest);
		await verifySnapshotDatabaseFile(artifactPath, artifact.stat, manifest, this.#validationRootPath);
		await assertSnapshotContents(snapshotDir);
		return {
			ok: true,
			manifest
		};
	}
	async restoreFresh(snapshot, targetPath) {
		const snapshotDir = await this.#resolveSnapshotDirectory(snapshot);
		const manifest = await readVerifiedSnapshotManifest(snapshotDir);
		assertAllowedDatabaseRole(manifest, this.#allowedDatabaseRoles);
		const resolvedTargetPath = path.resolve(targetPath);
		await assertFreshRestoreTarget(resolvedTargetPath);
		const canonicalRepositoryPath = await fs$1.realpath(this.#repositoryPath);
		const canonicalRestoreParentPath = await canonicalPathFromExistingAncestor$1(path.dirname(resolvedTargetPath));
		const canonicalTargetPath = path.join(canonicalRestoreParentPath, path.basename(resolvedTargetPath));
		if (isPathInside$1(canonicalRepositoryPath, canonicalTargetPath)) throw new Error(`SQLite restore target must be outside snapshot repository ${this.#repositoryPath}: ${resolvedTargetPath}`);
		const restoreParentPath = path.dirname(canonicalTargetPath);
		const restoreParentReceipt = await ensureRestoreParentDirectory(restoreParentPath);
		const trustedRestoreParentPath = await fs$1.realpath(restoreParentPath);
		const trustedTargetPath = path.join(trustedRestoreParentPath, path.basename(resolvedTargetPath));
		if (!isPathInside$1(canonicalTargetPath, trustedTargetPath) || !isPathInside$1(trustedTargetPath, canonicalTargetPath)) throw new Error(`SQLite restore target changed while creating its parent: ${resolvedTargetPath}`);
		if (isPathInside$1(canonicalRepositoryPath, trustedTargetPath)) throw new Error(`SQLite restore target must be outside snapshot repository ${this.#repositoryPath}: ${resolvedTargetPath}`);
		const restoreParentIdentity = await fs$1.lstat(trustedRestoreParentPath);
		if (!sameFileIdentity(restoreParentReceipt.identity, restoreParentIdentity)) throw new Error(`SQLite restore parent changed after durable creation: ${trustedRestoreParentPath}`);
		await assertFreshRestoreTarget(trustedTargetPath);
		return await withPrivateSqliteStagingDirectory({
			rootReceipt: restoreParentReceipt,
			prefix: ".tmp-restore-",
			allowedEntries: RESTORE_STAGING_ENTRIES,
			operation: async (stagingDir, stagingIdentity) => {
				const stagedSourcePath = path.join(stagingDir, SNAPSHOT_SQLITE_FILENAME);
				const stagedArtifact = await copySnapshotArtifact(snapshotDir, stagedSourcePath);
				await assertDirectoryIdentity(stagingDir, stagingIdentity);
				assertArtifactMatchesManifest(stagedSourcePath, stagedArtifact, manifest);
				await assertSnapshotContents(snapshotDir);
				await verifySnapshotDatabaseFile(stagedSourcePath, stagedArtifact.stat, manifest, trustedRestoreParentPath);
				await publishVerifiedSqliteFile({
					sourceIdentity: stagedArtifact.stat,
					sourcePath: stagedSourcePath,
					targetPath: trustedTargetPath,
					expectedContent: manifest.artifact,
					requireAtomicPublication: true,
					beforePublish: async () => {
						await assertDirectoryIdentity(trustedRestoreParentPath, restoreParentIdentity);
						await assertFreshRestoreTarget(trustedTargetPath);
					},
					afterPublish: (guard) => {
						guard.assertTargetMatchesExpectedContent(() => {
							assertDirectoryIdentitySync$1(trustedRestoreParentPath, restoreParentIdentity);
							assertNoSqliteSidecarsSync(trustedTargetPath, "Restored SQLite database has unexpected sidecar");
						});
					}
				});
				return {
					ok: true,
					manifest
				};
			}
		});
	}
	async list() {
		const repositoryStat = await lstatIfExists(this.#repositoryPath);
		if (!repositoryStat) return [];
		assertDirectory(repositoryStat, this.#repositoryPath, "SQLite snapshot repository");
		const entries = await fs$1.readdir(this.#repositoryPath, { withFileTypes: true });
		const snapshots = [];
		for (const entry of entries) {
			if (entry.name.startsWith(".tmp-")) {
				if (entry.isSymbolicLink() || !entry.isDirectory()) throw new Error(`SQLite snapshot repository contains unsafe staging entry: ${path.join(this.#repositoryPath, entry.name)}`);
				continue;
			}
			if (entry.isSymbolicLink() || !entry.isDirectory()) throw new Error(`SQLite snapshot repository contains unexpected entry: ${path.join(this.#repositoryPath, entry.name)}`);
			const snapshotPath = path.join(this.#repositoryPath, entry.name);
			const snapshotState = await classifySnapshotDirectory(snapshotPath);
			if (snapshotState === "incomplete") continue;
			const manifest = snapshotState === "complete-pending" ? await recoverCompletePendingSnapshot({
				allowedDatabaseRoles: this.#allowedDatabaseRoles,
				repositoryIdentity: repositoryStat,
				repositoryPath: this.#repositoryPath,
				snapshotPath,
				validationRootPath: this.#validationRootPath
			}) : await readVerifiedSnapshotManifest(snapshotPath);
			assertAllowedDatabaseRole(manifest, this.#allowedDatabaseRoles);
			snapshots.push({
				ref: { path: snapshotPath },
				manifest
			});
		}
		return snapshots.toSorted((left, right) => right.manifest.createdAt.localeCompare(left.manifest.createdAt) || right.manifest.snapshotId.localeCompare(left.manifest.snapshotId));
	}
	async #resolveSnapshotDirectory(snapshot) {
		const snapshotDir = path.resolve(snapshot.path);
		if (path.dirname(snapshotDir) !== this.#repositoryPath) throw new Error(`SQLite snapshot must be an immediate child of repository ${this.#repositoryPath}: ${snapshotDir}`);
		const repositoryStat = await lstatIfExists(this.#repositoryPath);
		if (!repositoryStat) throw new Error(`SQLite snapshot repository does not exist: ${this.#repositoryPath}. Check the snapshot path or create a snapshot with \`openclaw backup sqlite create\`.`);
		assertDirectory(repositoryStat, this.#repositoryPath, "SQLite snapshot repository");
		const snapshotStat = await lstatIfExists(snapshotDir);
		if (!snapshotStat) throw new Error(`SQLite snapshot does not exist: ${snapshotDir}. Run \`openclaw backup sqlite list --repository ${this.#repositoryPath}\` to inspect available snapshots.`);
		assertDirectory(snapshotStat, snapshotDir, "SQLite snapshot");
		if (await lstatIfExists(path.join(snapshotDir, SNAPSHOT_PENDING_FILENAME))) {
			if (await classifySnapshotDirectory(snapshotDir) === "complete-pending") await recoverCompletePendingSnapshot({
				allowedDatabaseRoles: this.#allowedDatabaseRoles,
				repositoryIdentity: repositoryStat,
				repositoryPath: this.#repositoryPath,
				snapshotPath: snapshotDir,
				validationRootPath: this.#validationRootPath
			});
		}
		return snapshotDir;
	}
};
async function readVerifiedSnapshotManifest(snapshotDir) {
	await assertSnapshotContents(snapshotDir);
	return await readSnapshotManifest(snapshotDir);
}
function assertArtifactMatchesManifest(artifactPath, artifact, manifest) {
	if (artifact.sizeBytes !== manifest.artifact.sizeBytes) throw new Error(`Snapshot artifact size mismatch for ${artifactPath}: expected ${manifest.artifact.sizeBytes}, got ${artifact.sizeBytes}`);
	if (artifact.sha256 !== manifest.artifact.sha256) throw new Error(`Snapshot artifact hash mismatch for ${artifactPath}: expected ${manifest.artifact.sha256}, got ${artifact.sha256}`);
}
function assertAllowedDatabaseRole(manifest, allowedRoles) {
	if (!allowedRoles || allowedRoles.includes(manifest.database.role)) return;
	throw new Error(`SQLite snapshot database role ${manifest.database.role} is not allowed for this operation.`);
}
async function verifySnapshotDatabaseFile(artifactPath, expectedIdentity, manifest, validationRootPath) {
	const beforeOpen = await fs$1.lstat(artifactPath);
	if (beforeOpen.isSymbolicLink() || !beforeOpen.isFile() || beforeOpen.nlink > 1 || !sameFileIdentity(expectedIdentity, beforeOpen)) throw new Error(`Snapshot artifact changed before SQLite verification: ${artifactPath}`);
	const validationRootIdentity = await lstatIfExists(validationRootPath, { bigint: true });
	if (!validationRootIdentity) throw new Error(`SQLite validation root does not exist: ${validationRootPath}. Create a private directory there or pass an existing directory with \`--scratch\`.`);
	assertDirectory(validationRootIdentity, validationRootPath, "SQLite validation root");
	const canonicalValidationRootPath = await fs$1.realpath(validationRootPath);
	await withPrivateSqliteStagingDirectory({
		rootReceipt: {
			path: canonicalValidationRootPath,
			realPath: canonicalValidationRootPath,
			identity: validationRootIdentity
		},
		prefix: ".tmp-verify-",
		allowedEntries: VALIDATION_STAGING_ENTRIES,
		operation: async (validationDir) => {
			const validationPath = path.join(validationDir, SNAPSHOT_SQLITE_FILENAME);
			const validationArtifact = await copySnapshotArtifact(path.dirname(artifactPath), validationPath);
			assertArtifactMatchesManifest(validationPath, validationArtifact, manifest);
			const database = openNodeSqliteDatabase(validationPath, {
				allowExtension: true,
				readOnly: true
			});
			try {
				database.exec("PRAGMA busy_timeout = 30000; PRAGMA trusted_schema = OFF;");
				await loadSqliteVecExtension({ db: database });
				assertSqliteIntegrity(database, artifactPath);
				buildManifestDatabaseValidator(manifest.database)(database, artifactPath);
			} finally {
				database.close();
			}
			const validatedArtifact = await hashSnapshotArtifact(validationDir);
			if (!sameFileIdentity(validationArtifact.stat, validatedArtifact.stat)) throw new Error(`Snapshot validation copy changed: ${validationPath}`);
			assertArtifactMatchesManifest(validationPath, validatedArtifact, manifest);
		}
	});
	const afterOpen = await fs$1.lstat(artifactPath);
	if (afterOpen.isSymbolicLink() || !afterOpen.isFile() || afterOpen.nlink > 1 || !sameFileIdentity(expectedIdentity, afterOpen)) throw new Error(`Snapshot artifact changed during SQLite verification: ${artifactPath}`);
	const verifiedArtifact = await hashSnapshotArtifact(path.dirname(artifactPath));
	if (!sameFileIdentity(expectedIdentity, verifiedArtifact.stat)) throw new Error(`Snapshot artifact changed after SQLite verification: ${artifactPath}`);
	assertArtifactMatchesManifest(artifactPath, verifiedArtifact, manifest);
}
function buildDatabaseManifest(identity, sourcePath, userVersion) {
	const basename = path.basename(sourcePath);
	if (identity.role === "global") return {
		role: "global",
		basename,
		userVersion
	};
	if (identity.role === "agent") return {
		role: "agent",
		agentId: identity.agentId,
		basename,
		userVersion
	};
	return {
		role: "generic",
		id: identity.id,
		basename,
		userVersion
	};
}
function buildManifestDatabaseValidator(manifest) {
	const validateOwner = buildSnapshotValidator(manifest);
	return (database, pathname) => {
		validateOwner(database, pathname);
		const userVersion = readSqliteUserVersion(database);
		if (userVersion !== manifest.userVersion) throw new Error(`Snapshot database user_version mismatch for ${pathname}: expected ${manifest.userVersion}, got ${userVersion}`);
	};
}
function buildSnapshotId(now) {
	return `${now.toISOString().replaceAll(/[:.]/g, "-")}-${randomUUID()}`;
}
async function ensurePrivateDirectory(directoryPath, scopeLabel) {
	let expectedExistingIdentity;
	if (process.platform !== "win32") {
		expectedExistingIdentity = await lstatIfExists(directoryPath, { bigint: true });
		if (expectedExistingIdentity) {
			assertDirectory(expectedExistingIdentity, directoryPath, scopeLabel);
			const realPath = await assertTrustedStagingRoot(expectedExistingIdentity, directoryPath, { allowModeRepair: true });
			assertDirectoryIdentitySync(directoryPath, {
				dev: expectedExistingIdentity.dev,
				ino: expectedExistingIdentity.ino,
				realPath
			});
			applyPrivateModeSync(directoryPath, SNAPSHOT_DIRECTORY_MODE);
		}
	}
	const receipt = await ensureDurableDirectory({
		directoryPath,
		label: scopeLabel,
		expectedExistingIdentity,
		create: async (targetPath) => {
			if (process.platform === "win32") {
				const parentResult = await ensureAbsoluteDirectory$1(path.dirname(targetPath), {
					mode: SNAPSHOT_DIRECTORY_MODE,
					scopeLabel
				});
				if (!parentResult.ok) throw parentResult.error;
				try {
					await createPrivateSqliteDirectory(targetPath);
					return;
				} catch (error) {
					if (error.code !== "EEXIST") throw error;
				}
			}
			const result = await ensureAbsoluteDirectory$1(targetPath, {
				mode: SNAPSHOT_DIRECTORY_MODE,
				scopeLabel
			});
			if (!result.ok) throw result.error;
			applyPrivateModeSync(result.path, SNAPSHOT_DIRECTORY_MODE);
		}
	});
	requireDirectorySync(receipt.parentSync, scopeLabel);
	return receipt;
}
async function ensureRestoreParentDirectory(directoryPath) {
	const receipt = await ensureDurableDirectory({
		directoryPath,
		label: "SQLite restore target",
		create: async (targetPath) => {
			const result = await ensureAbsoluteDirectory$1(targetPath, {
				mode: SNAPSHOT_DIRECTORY_MODE,
				scopeLabel: "SQLite restore target"
			});
			if (!result.ok) throw result.error;
		}
	});
	requireDirectorySync(receipt.parentSync, "SQLite restore target");
	return receipt;
}
async function publishSnapshotEntryNoOverwrite(sourcePath, targetPath, entryName, publishedEntries) {
	let publication;
	try {
		publication = await publishFileNoClobber(sourcePath, targetPath, {
			strategy: "link-or-copy",
			moveSource: true,
			durability: "fail-closed"
		});
	} catch (error) {
		const details = getPublishFileExclusiveFailureDetails(error);
		if (details?.targetCreated && details.cleanup !== "removed") {
			const [currentSource, currentTarget] = await Promise.all([fs$1.lstat(sourcePath).catch(() => void 0), fs$1.lstat(targetPath).catch(() => void 0)]);
			const matchesReceipt = details.targetIdentity && currentTarget && sameFileIdentity(details.targetIdentity, currentTarget);
			const matchesSource = currentSource && currentTarget && sameFileIdentity(currentSource, currentTarget);
			if (currentTarget && (matchesReceipt || matchesSource)) publishedEntries.set(entryName, currentTarget);
		}
		throw error;
	}
	const expectedTargetIdentity = publication.identity;
	publishedEntries.set(entryName, expectedTargetIdentity);
	const initialTargetIdentity = await fs$1.lstat(targetPath);
	if (!sameFileIdentity(expectedTargetIdentity, initialTargetIdentity)) throw new Error(`SQLite snapshot entry changed during publication: ${targetPath}`);
	const finalTargetIdentity = await fs$1.lstat(targetPath);
	if (!sameFileIdentity(initialTargetIdentity, finalTargetIdentity)) throw new Error(`SQLite snapshot entry changed after publication: ${targetPath}`);
	publishedEntries.set(entryName, finalTargetIdentity);
}
async function assertSnapshotContents(snapshotDir, allowPending = false) {
	const expected = /* @__PURE__ */ new Set([SNAPSHOT_MANIFEST_FILENAME, SNAPSHOT_SQLITE_FILENAME]);
	const entries = await fs$1.readdir(snapshotDir, { withFileTypes: true });
	for (const entry of entries) {
		if (allowPending && entry.name === SNAPSHOT_PENDING_FILENAME) {
			readPendingSnapshotIdentity(path.join(snapshotDir, entry.name));
			continue;
		}
		if (!expected.delete(entry.name)) throw new Error(`SQLite snapshot contains unexpected entry: ${path.join(snapshotDir, entry.name)}`);
		if (entry.isSymbolicLink() || !entry.isFile()) throw new Error(`SQLite snapshot entry must be a regular file: ${path.join(snapshotDir, entry.name)}`);
		if ((await fs$1.lstat(path.join(snapshotDir, entry.name))).nlink > 1) throw new Error(`SQLite snapshot entry must not be hardlinked: ${path.join(snapshotDir, entry.name)}`);
	}
	if (expected.size > 0) throw new Error(`SQLite snapshot is missing ${[...expected].join(", ")}: ${snapshotDir}`);
}
async function classifySnapshotDirectory(snapshotDir) {
	const entries = await fs$1.readdir(snapshotDir, { withFileTypes: true });
	const knownEntries = /* @__PURE__ */ new Set([
		SNAPSHOT_MANIFEST_FILENAME,
		SNAPSHOT_PENDING_FILENAME,
		SNAPSHOT_SQLITE_FILENAME
	]);
	for (const entry of entries) if (!knownEntries.has(entry.name) || entry.isSymbolicLink() || !entry.isFile()) throw new Error(`SQLite snapshot contains unexpected incomplete entry: ${path.join(snapshotDir, entry.name)}`);
	const names = new Set(entries.map((entry) => entry.name));
	if (names.size === 0) return "incomplete";
	if (!names.has(SNAPSHOT_PENDING_FILENAME)) return "committed";
	return names.has("manifest.json") && names.has("database.sqlite") ? "complete-pending" : "incomplete";
}
async function recoverCompletePendingSnapshot(params) {
	const trustedRepositoryPath = await assertTrustedStagingRoot(params.repositoryIdentity, params.repositoryPath);
	await assertDirectoryIdentity(trustedRepositoryPath, params.repositoryIdentity);
	const snapshotDirectory = await pinDirectory(params.snapshotPath, { label: "SQLite pending snapshot directory" });
	try {
		await assertPrivateStagingDirectory(snapshotDirectory.receipt.identity, params.snapshotPath);
		return await commitPendingSnapshot({
			...params,
			repositoryPath: trustedRepositoryPath,
			snapshotDirectory
		});
	} finally {
		await snapshotDirectory.close().catch(() => void 0);
	}
}
async function commitPendingSnapshot(params) {
	const { snapshotPath, snapshotDirectory, expected } = params;
	const scopeLabel = expected ? "SQLite snapshot directory" : "SQLite pending snapshot directory";
	await snapshotDirectory.assertCurrent();
	await assertSnapshotContents(snapshotPath, true);
	const manifest = await readSnapshotManifest(snapshotPath);
	if (expected && !isDeepStrictEqual(manifest, expected.manifest)) throw new Error(`SQLite snapshot manifest changed during publication: ${snapshotPath}`);
	assertAllowedDatabaseRole(manifest, params.allowedDatabaseRoles);
	const artifact = await hashSnapshotArtifact(snapshotPath);
	const artifactPath = path.join(snapshotPath, SNAPSHOT_SQLITE_FILENAME);
	assertArtifactMatchesManifest(artifactPath, artifact, manifest);
	await verifySnapshotDatabaseFile(artifactPath, artifact.stat, manifest, params.validationRootPath);
	if (!expected) requireDirectorySync(await snapshotDirectory.sync(), scopeLabel);
	const pendingPath = path.join(snapshotPath, SNAPSHOT_PENDING_FILENAME);
	const pendingIdentity = readPendingSnapshotIdentity(pendingPath);
	if (pendingIdentity) {
		if (expected && !sameFileIdentity(expected.pendingIdentity, pendingIdentity)) throw new Error(`SQLite snapshot pending marker changed: ${pendingPath}`);
		await snapshotDirectory.assertCurrent();
		const currentPendingIdentity = readPendingSnapshotIdentity(pendingPath);
		if (currentPendingIdentity) {
			if (!sameFileIdentity(pendingIdentity, currentPendingIdentity)) throw new Error(`SQLite snapshot pending marker changed: ${pendingPath}`);
			try {
				fs.unlinkSync(pendingPath);
			} catch (error) {
				if (error.code !== "ENOENT") throw error;
			}
		}
	}
	requireDirectorySync(await snapshotDirectory.sync(), scopeLabel);
	await snapshotDirectory.assertCurrent();
	const committedManifest = await readSnapshotManifest(snapshotPath);
	if (!isDeepStrictEqual(committedManifest, manifest)) throw new Error(`SQLite snapshot manifest changed ${expected ? "after commit" : "during recovery"}: ${snapshotPath}`);
	assertArtifactMatchesManifest(artifactPath, await hashSnapshotArtifact(snapshotPath), committedManifest);
	await assertSnapshotContents(snapshotPath);
	await snapshotDirectory.assertCurrent();
	await assertDirectoryIdentity(params.repositoryPath, params.repositoryIdentity);
	return committedManifest;
}
function readPendingSnapshotIdentity(pendingPath) {
	const identity = lstatIfExistsSync(pendingPath);
	if (identity && (identity.isSymbolicLink() || !identity.isFile() || identity.nlink > 1)) throw new Error(`SQLite snapshot pending marker is unsafe: ${pendingPath}`);
	return identity;
}
async function lstatIfExists(pathname, options) {
	try {
		return await fs$1.lstat(pathname, options);
	} catch (error) {
		if (error.code === "ENOENT") return;
		throw error;
	}
}
function lstatIfExistsSync(pathname) {
	try {
		return fs.lstatSync(pathname);
	} catch (error) {
		if (error.code === "ENOENT") return;
		throw error;
	}
}
async function removePrivateDirectoryIfOwned(directoryPath, expectedIdentity, allowedEntries) {
	const currentIdentity = await lstatIfExists(directoryPath);
	if (!currentIdentity) return false;
	if (currentIdentity.isSymbolicLink() || !currentIdentity.isDirectory() || !sameFileIdentity(currentIdentity, expectedIdentity)) throw new Error(`Private SQLite staging directory changed before cleanup: ${directoryPath}`);
	const entries = await fs$1.readdir(directoryPath, { withFileTypes: true });
	const verifiedPaths = [];
	for (const entry of entries) {
		const entryPath = path.join(directoryPath, entry.name);
		if (!allowedEntries.has(entry.name) || entry.isSymbolicLink() || !entry.isFile()) throw new Error(`Private SQLite staging directory has unexpected entry: ${entryPath}`);
		if ((await fs$1.lstat(entryPath)).nlink > 1) throw new Error(`Private SQLite staging file must not be hardlinked: ${entryPath}`);
		verifiedPaths.push(entryPath);
	}
	await Promise.all(verifiedPaths.map(async (entryPath) => await fs$1.unlink(entryPath)));
	await fs$1.rmdir(directoryPath);
	return true;
}
async function withPrivateSqliteStagingDirectory(options) {
	const trustedRootPath = await assertTrustedStagingRoot(options.rootReceipt.identity, options.rootReceipt.path);
	await assertDirectoryIdentity(trustedRootPath, options.rootReceipt.identity);
	const directoryPath = await createPrivateSqliteTempDirectory(trustedRootPath, options.prefix);
	const directoryIdentity = await fs$1.lstat(directoryPath);
	let outcome;
	try {
		applyPrivateModeSync(directoryPath, SNAPSHOT_DIRECTORY_MODE);
		await assertPrivateStagingDirectory(directoryIdentity, directoryPath);
		await assertDirectoryIdentity(trustedRootPath, options.rootReceipt.identity);
		outcome = {
			ok: true,
			value: await options.operation(directoryPath, directoryIdentity)
		};
	} catch (error) {
		outcome = {
			ok: false,
			error
		};
	}
	let cleanupOutcome;
	try {
		if (!await removePrivateDirectoryIfOwned(directoryPath, directoryIdentity, options.allowedEntries)) throw new Error(`Private SQLite staging directory disappeared: ${directoryPath}`);
		cleanupOutcome = { ok: true };
	} catch (error) {
		cleanupOutcome = {
			ok: false,
			error
		};
	}
	if (!cleanupOutcome.ok) {
		if (!outcome.ok) throw new AggregateError([outcome.error, cleanupOutcome.error], `SQLite staging operation and cleanup both failed: ${directoryPath}`);
		throw new Error(`Failed to clean private SQLite staging directory: ${directoryPath}`, { cause: cleanupOutcome.error });
	}
	requireDirectorySync(await syncDirectory(options.rootReceipt), "Private SQLite staging root");
	if (!outcome.ok) throw outcome.error;
	return outcome.value;
}
/** Create or strictly admit a Git repository through the local snapshot root trust policy. */
async function ensurePrivateSnapshotRepositoryRoot(rootPath) {
	try {
		return await assertTrustedStagingRoot(await fs$1.lstat(rootPath), rootPath);
	} catch (error) {
		if (error.code !== "ENOENT") throw error;
	}
	return await assertTrustedStagingRoot((await ensurePrivateDirectory(rootPath, "Git backup repository")).identity, rootPath);
}
async function removePublishedSnapshotDirectoryIfOwned(directoryPath, expectedIdentity, publishedEntries) {
	const currentIdentity = await lstatIfExists(directoryPath);
	if (!currentIdentity || currentIdentity.isSymbolicLink() || !currentIdentity.isDirectory() || !sameFileIdentity(currentIdentity, expectedIdentity)) return false;
	const entries = await fs$1.readdir(directoryPath, { withFileTypes: true });
	for (const entry of entries) {
		const expectedEntryIdentity = publishedEntries.get(entry.name);
		if (!expectedEntryIdentity || entry.isSymbolicLink() || !entry.isFile()) continue;
		const entryPath = path.join(directoryPath, entry.name);
		const currentEntryIdentity = await fs$1.lstat(entryPath);
		if (sameFileIdentity(currentEntryIdentity, expectedEntryIdentity)) await fs$1.unlink(entryPath);
	}
	if ((await fs$1.readdir(directoryPath)).length > 0) return false;
	await fs$1.rmdir(directoryPath);
	return true;
}
//#endregion
export { buildSnapshotValidator as a, SNAPSHOT_SQLITE_FILENAME as c, assertNoSqliteSidecarsSync as i, ensurePrivateSnapshotRepositoryRoot as n, createOpenClawSnapshotCopy as o, assertFreshRestoreTarget as r, hashSnapshotArtifact as s, createLocalSqliteSnapshotProvider as t };