Files
playbook/antigravity-awesome-skills/tools/lib/aas-v1/adapters/index.js
T
2026-07-18 00:02:59 +00:00

344 lines
15 KiB
JavaScript

"use strict";
const crypto = require("node:crypto");
const fsp = require("node:fs/promises");
const path = require("node:path");
const { canonicalJson } = require("../canonical-json");
const { buildClaudeText, inspectClaudeBytes } = require("./claude");
const { buildCodexText, inspectCodexBytes } = require("./codex");
const { HostConfigError, hostConfigError } = require("./errors");
const {
assertExplicitAbsolutePath,
assertOwned,
assertSafeDirectory,
assertWindowsPrivatePath,
copyWindowsAcl,
currentUid,
digest,
fsyncDirectory,
hardenWindowsPrivatePath,
inspectRegularFile,
sameIdentity,
writeExclusiveSynced,
} = require("./safety");
const { normalizeServer } = require("./values");
const PRIVATE = new WeakMap();
const HOSTS = new Set(["codex", "claude"]);
const SCOPES = new Set(["project", "user"]);
function validateHostScope(host, scope) {
if (!HOSTS.has(host)) throw hostConfigError("AAS_ADAPTER_HOST_UNSUPPORTED", "invalidInput", { host });
if (!SCOPES.has(scope)) throw hostConfigError("AAS_ADAPTER_SCOPE_INVALID", "invalidInput", { scope });
}
function parentIdentity(parent) {
const stat = parent.stat;
return { dev: stat.dev, ino: stat.ino, uid: stat.uid, gid: stat.gid, mode: stat.mode & 0o7777 };
}
function publicInspection(host, scope, snapshot, inspection) {
return Object.freeze({
schemaVersion: 1,
host,
scope,
configPath: snapshot.path,
exists: snapshot.exists,
digest: snapshot.digest,
mode: snapshot.identity?.mode ?? null,
sectionPresent: inspection.sectionPresent,
configured: inspection.configured,
unknownKeys: Object.freeze([...inspection.unknownKeys]),
});
}
async function inspectHostConfig({ host, scope, configPath }) {
validateHostScope(host, scope);
const snapshot = await inspectRegularFile(configPath);
const inspection = host === "codex" ? inspectCodexBytes(snapshot.bytes) : inspectClaudeBytes(snapshot.bytes);
return publicInspection(host, scope, snapshot, inspection);
}
async function buildPatch({ host, scope, configPath, server }) {
validateHostScope(host, scope);
const desired = normalizeServer(host, server);
const snapshot = await inspectRegularFile(configPath);
const built = host === "codex" ? buildCodexText(snapshot.bytes, desired) : buildClaudeText(snapshot.bytes, desired);
const nextBytes = Buffer.from(built.text, "utf8");
const nextDigest = digest(nextBytes);
const changed = snapshot.digest !== nextDigest;
const patch = {
schemaVersion: 1,
host,
scope,
configPath: snapshot.path,
status: changed ? "changesProposed" : "alreadyConfigured",
changed,
exists: snapshot.exists,
currentDigest: snapshot.digest,
nextDigest,
redactedDiff: Object.freeze({
schemaVersion: 1,
changedPaths: Object.freeze([...built.changedPaths].sort()),
valuesRedacted: true,
envValuesRedacted: true,
}),
inspection: publicInspection(host, scope, snapshot, built.inspection),
};
Object.freeze(patch);
PRIVATE.set(patch, {
currentBytes: snapshot.bytes,
nextBytes,
identity: snapshot.identity,
parentIdentity: parentIdentity(snapshot.parent),
});
return patch;
}
function previewPatch(patch) {
if (!PRIVATE.has(patch)) throw hostConfigError("AAS_ADAPTER_PATCH_INVALID", "invalidInput");
return patch;
}
async function acquireLock(lockPath, payload) {
try {
const handle = await fsp.open(lockPath, "wx", 0o600);
try {
hardenWindowsPrivatePath(lockPath, false);
await handle.writeFile(`${canonicalJson(payload)}\n`);
await handle.chmod(0o600);
await handle.sync();
} finally {
await handle.close();
}
} catch (error) {
if (error.code === "EEXIST") throw hostConfigError("AAS_ADAPTER_LOCKED", "conflict");
throw error;
}
}
async function releaseLock(lockPath) {
try { await fsp.unlink(lockPath); } catch (error) { if (error.code !== "ENOENT") throw error; }
await fsyncDirectory(path.dirname(lockPath));
}
async function ensureBackupDirectory(backupDirectory) {
const absolute = assertExplicitAbsolutePath(backupDirectory, "AAS_ADAPTER_BACKUP_PATH_INVALID");
const existing = assertSafeDirectory(absolute, { allowMissing: true });
if (existing.exists) {
if (process.platform !== "win32" && (existing.stat.mode & 0o077) !== 0) throw hostConfigError("AAS_ADAPTER_BACKUP_DIRECTORY_PERMISSIONS", "filesystem");
if (process.platform === "win32") assertWindowsPrivatePath(existing.path);
return absolute;
}
assertSafeDirectory(path.dirname(absolute));
try {
await fsp.mkdir(absolute, { mode: 0o700 });
} catch (error) {
if (error.code !== "EEXIST") throw error;
}
const created = assertSafeDirectory(absolute);
await fsp.chmod(created.path, 0o700);
if (process.platform === "win32") hardenWindowsPrivatePath(created.path, true);
await fsyncDirectory(path.dirname(created.path));
return created.path;
}
function safeTimestamp(value) {
const date = value instanceof Date ? value : new Date(value ?? Date.now());
if (!Number.isFinite(date.getTime())) throw hostConfigError("AAS_ADAPTER_TIMESTAMP_INVALID", "invalidInput");
return date.toISOString().replace(/[:.]/g, "-");
}
function configKey(configPath) {
return digest(Buffer.from(configPath)).slice("sha256-".length, "sha256-".length + 24);
}
async function createBackup({ patch, snapshot, backupDirectory, retention, now }) {
if (!Number.isInteger(retention) || retention < 1 || retention > 100) throw hostConfigError("AAS_ADAPTER_RETENTION_INVALID", "invalidInput");
const directory = await ensureBackupDirectory(backupDirectory);
const key = configKey(patch.configPath);
const stem = `${key}-${safeTimestamp(now)}-${snapshot.digest.slice(-12)}`;
const backupFile = `${stem}.bak`;
const metadataFile = `${stem}.json`;
const backupPath = path.join(directory, backupFile);
const metadataPath = path.join(directory, metadataFile);
try {
await writeExclusiveSynced(backupPath, snapshot.bytes, 0o600);
const metadata = {
schemaVersion: 1,
host: patch.host,
scope: patch.scope,
configKey: key,
configBasename: path.basename(patch.configPath),
backupFile,
sourceDigest: snapshot.digest,
sourceMode: snapshot.identity.mode,
sourceUid: snapshot.identity.uid,
sourceGid: snapshot.identity.gid,
createdAt: (now instanceof Date ? now : new Date(now ?? Date.now())).toISOString(),
retention: { maxEntries: retention, enforcement: "explicit-cleanup" },
};
await writeExclusiveSynced(metadataPath, Buffer.from(`${canonicalJson(metadata)}\n`), 0o600);
await fsyncDirectory(directory);
return Object.freeze({ backupPath, metadataPath, sourceDigest: snapshot.digest });
} catch (error) {
await Promise.allSettled([fsp.unlink(backupPath), fsp.unlink(metadataPath)]);
throw error;
}
}
async function assertSnapshotUnchanged(patch, internal) {
const current = await inspectRegularFile(patch.configPath);
if (current.exists !== patch.exists || current.digest !== patch.currentDigest || !sameIdentity(current.identity, internal.identity)) {
throw hostConfigError("AAS_ADAPTER_CONFIG_CHANGED", "conflict");
}
if (!sameIdentity(parentIdentity(current.parent), internal.parentIdentity)) {
throw hostConfigError("AAS_ADAPTER_DIRECTORY_CHANGED", "conflict");
}
return current;
}
async function applyHostConfigPatch({ patch, approved = false, backupDirectory, retention = 5, now } = {}) {
const internal = PRIVATE.get(patch);
if (!internal) throw hostConfigError("AAS_ADAPTER_PATCH_INVALID", "invalidInput");
if (!approved) throw hostConfigError("AAS_ADAPTER_APPROVAL_REQUIRED", "policy");
if (!patch.changed) return Object.freeze({ status: "alreadyConfigured", configDigest: patch.currentDigest, backup: null });
if (patch.exists && typeof backupDirectory !== "string") throw hostConfigError("AAS_ADAPTER_BACKUP_REQUIRED", "policy");
const directory = path.dirname(patch.configPath);
const lockPath = path.join(directory, `.${path.basename(patch.configPath)}.aas.lock`);
const lockPayload = { schemaVersion: 1, pid: process.pid, createdAt: new Date().toISOString(), configKey: configKey(patch.configPath) };
await acquireLock(lockPath, lockPayload);
let stagePath;
try {
let current = await assertSnapshotUnchanged(patch, internal);
const backup = current.exists ? await createBackup({ patch, snapshot: current, backupDirectory, retention, now }) : null;
stagePath = path.join(directory, `.${path.basename(patch.configPath)}.aas-stage-${process.pid}-${crypto.randomBytes(12).toString("hex")}`);
const targetMode = current.exists ? current.identity.mode : 0o600;
const handle = await fsp.open(stagePath, "wx", targetMode);
try {
// A newly created Windows file inherits its parent DACL regardless of
// the POSIX mode. Install the approved target ACL while the stage is
// still empty, before writing configuration bytes.
if (process.platform === "win32") {
if (current.exists) copyWindowsAcl(patch.configPath, stagePath);
else hardenWindowsPrivatePath(stagePath, false);
}
await handle.writeFile(internal.nextBytes);
await handle.chmod(targetMode);
if (current.exists && currentUid() !== null) await handle.chown(current.identity.uid, current.identity.gid);
await handle.sync();
} finally {
await handle.close();
}
current = await assertSnapshotUnchanged(patch, internal);
await fsp.rename(stagePath, patch.configPath);
stagePath = null;
await fsyncDirectory(directory);
const written = await inspectRegularFile(patch.configPath, { allowMissing: false });
if (written.digest !== patch.nextDigest || (process.platform !== "win32" && written.identity.mode !== targetMode)) {
throw hostConfigError("AAS_ADAPTER_WRITE_VERIFICATION_FAILED", "execution");
}
return Object.freeze({ status: "applied", configDigest: written.digest, backup });
} finally {
if (stagePath) await fsp.unlink(stagePath).catch((error) => { if (error.code !== "ENOENT") throw error; });
await releaseLock(lockPath);
}
}
async function readBackupRecords({ backupDirectory, configPath, keep }) {
if (!Number.isInteger(keep) || keep < 0 || keep > 100) throw hostConfigError("AAS_ADAPTER_RETENTION_INVALID", "invalidInput");
const backup = assertSafeDirectory(backupDirectory);
if (process.platform !== "win32" && (backup.stat.mode & 0o077) !== 0) throw hostConfigError("AAS_ADAPTER_BACKUP_DIRECTORY_PERMISSIONS", "filesystem");
const directory = backup.path;
const absoluteConfig = assertExplicitAbsolutePath(configPath);
const key = configKey(absoluteConfig);
const names = (await fsp.readdir(directory)).filter((name) => name.startsWith(`${key}-`) && name.endsWith(".json")).sort().reverse();
const records = [];
for (const name of names) {
const metadataPath = path.join(directory, name);
const stat = await fsp.lstat(metadataPath);
if (stat.isSymbolicLink() || !stat.isFile() || (process.platform !== "win32" && (stat.mode & 0o777) !== 0o600)) throw hostConfigError("AAS_ADAPTER_BACKUP_UNSAFE", "filesystem");
assertOwned(stat, undefined, metadataPath);
if (process.platform === "win32") assertWindowsPrivatePath(metadataPath);
const bytes = await fsp.readFile(metadataPath);
if (bytes.length > 32 * 1024) throw hostConfigError("AAS_ADAPTER_BACKUP_UNSAFE", "filesystem");
let metadata;
try { metadata = JSON.parse(bytes.toString("utf8")); } catch { throw hostConfigError("AAS_ADAPTER_BACKUP_UNSAFE", "filesystem"); }
if (metadata.configKey !== key || typeof metadata.backupFile !== "string" || path.basename(metadata.backupFile) !== metadata.backupFile) {
throw hostConfigError("AAS_ADAPTER_BACKUP_UNSAFE", "filesystem");
}
const backupPath = path.join(directory, metadata.backupFile);
const backupStat = await fsp.lstat(backupPath);
if (backupStat.isSymbolicLink() || !backupStat.isFile() || (process.platform !== "win32" && (backupStat.mode & 0o777) !== 0o600)) throw hostConfigError("AAS_ADAPTER_BACKUP_UNSAFE", "filesystem");
assertOwned(backupStat, undefined, backupPath);
if (process.platform === "win32") assertWindowsPrivatePath(backupPath);
const backupBytes = await fsp.readFile(backupPath);
if (digest(backupBytes) !== metadata.sourceDigest) throw hostConfigError("AAS_ADAPTER_BACKUP_UNSAFE", "filesystem");
records.push({
metadata,
metadataPath,
backupPath,
approvalRecord: {
metadataNameDigest: digest(Buffer.from(name)),
backupNameDigest: digest(Buffer.from(metadata.backupFile)),
metadataDigest: digest(bytes),
backupDigest: digest(backupBytes),
},
});
}
const approvalPayload = {
schemaVersion: 1,
action: "mcp.backups.cleanup",
backupDirectoryDigest: digest(Buffer.from(directory)),
configPathDigest: digest(Buffer.from(absoluteConfig)),
keep,
records: records.map((record) => record.approvalRecord),
};
return { directory, key, records, approvalDigest: digest(Buffer.from(canonicalJson(approvalPayload))) };
}
async function previewBackupCleanup({ backupDirectory, configPath, keep } = {}) {
const inspected = await readBackupRecords({ backupDirectory, configPath, keep });
return Object.freeze({
schemaVersion: 1,
status: inspected.records.length > keep ? "changesProposed" : "nothingToClean",
approvalDigest: inspected.approvalDigest,
retained: Math.min(keep, inspected.records.length),
removeCount: Math.max(0, inspected.records.length - keep),
});
}
async function cleanupBackups({ backupDirectory, configPath, keep, approved = false, approvalDigest } = {}) {
if (!approved && !approvalDigest) throw hostConfigError("AAS_ADAPTER_APPROVAL_REQUIRED", "policy");
const preview = await readBackupRecords({ backupDirectory, configPath, keep });
if (approvalDigest && approvalDigest !== preview.approvalDigest) throw hostConfigError("AAS_ADAPTER_APPROVAL_MISMATCH", "policy");
const { directory, key } = preview;
const lockPath = path.join(directory, `.cleanup-${key}.lock`);
await acquireLock(lockPath, { schemaVersion: 1, pid: process.pid, configKey: key, createdAt: new Date().toISOString() });
try {
const locked = await readBackupRecords({ backupDirectory, configPath, keep });
if (locked.approvalDigest !== preview.approvalDigest) throw hostConfigError("AAS_ADAPTER_BACKUP_CHANGED", "conflict");
const removed = [];
for (const record of locked.records.slice(keep)) {
await fsp.unlink(record.backupPath);
await fsp.unlink(record.metadataPath);
removed.push(path.basename(record.backupPath));
}
await fsyncDirectory(directory);
return Object.freeze({ status: "cleaned", retained: Math.min(keep, locked.records.length), removed: Object.freeze(removed) });
} finally {
await releaseLock(lockPath);
}
}
module.exports = {
HostConfigError,
applyHostConfigPatch,
buildPatch,
cleanupBackups,
inspectHostConfig,
previewBackupCleanup,
previewPatch,
};