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

99 lines
3.5 KiB
JavaScript

"use strict";
const fs = require("node:fs");
const path = require("node:path");
const { fsyncDirectorySync } = require("../durability");
const { canonicalJson, sha256 } = require("../canonical-json");
const { transactionError } = require("./errors");
const { validateInstance } = require("../schema-validator");
function compareStrings(left, right) {
return left < right ? -1 : (left > right ? 1 : 0);
}
function publicEntries(entries) {
return entries.map(({ skillId, treeDigest, catalogIntegrity }) => ({ skillId, treeDigest, catalogIntegrity }))
.sort((left, right) => compareStrings(left.skillId, right.skillId));
}
function digestManagedEntries(entries) {
return sha256(canonicalJson({ schemaVersion: 1, entries: publicEntries(entries) }));
}
function buildManagedState({ target, catalog, entries, completedPlanDigests }) {
const normalizedEntries = entries.map((entry) => ({ ...entry }))
.sort((left, right) => compareStrings(left.skillId, right.skillId));
const state = {
schemaVersion: 1,
target: { ...target },
catalog: { ...catalog },
entries: normalizedEntries,
completedPlanDigests: [...new Set(completedPlanDigests)].sort(compareStrings).slice(-256),
stateDigest: digestManagedEntries(normalizedEntries),
};
return state;
}
function readManagedState(stateFile) {
if (!fs.existsSync(stateFile)) return null;
const stat = fs.lstatSync(stateFile);
if (stat.isSymbolicLink() || !stat.isFile() || stat.nlink !== 1 || stat.size > 1024 * 1024) {
throw transactionError("AAS_TRANSACTION_STATE_UNSAFE", "filesystem", {});
}
let state;
try {
state = JSON.parse(fs.readFileSync(stateFile, "utf8"));
} catch (cause) {
throw transactionError("AAS_TRANSACTION_STATE_CORRUPT", "integrity", {}, cause);
}
if (!state || state.schemaVersion !== 1 || !Array.isArray(state.entries) || !Array.isArray(state.completedPlanDigests)) {
throw transactionError("AAS_TRANSACTION_STATE_CORRUPT", "integrity", {});
}
validateInstance("managed-state.schema.json", state, "AAS_TRANSACTION_STATE_SCHEMA_INVALID");
const ids = new Set();
for (const entry of state.entries) {
if (!entry || typeof entry.skillId !== "string" || ids.has(entry.skillId)
|| typeof entry.treeDigest !== "string" || typeof entry.catalogIntegrity !== "string"
|| typeof entry.installedByPlanDigest !== "string") {
throw transactionError("AAS_TRANSACTION_STATE_CORRUPT", "integrity", {});
}
ids.add(entry.skillId);
}
if (state.stateDigest !== digestManagedEntries(state.entries)) {
throw transactionError("AAS_TRANSACTION_STATE_DIGEST_MISMATCH", "integrity", {});
}
return state;
}
function fsyncDirectory(directory) {
fsyncDirectorySync(directory);
}
function writeFileDurable(filePath, bytes, mode = 0o600) {
const descriptor = fs.openSync(filePath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY, mode);
try {
fs.writeFileSync(descriptor, bytes);
fs.fsyncSync(descriptor);
} finally {
fs.closeSync(descriptor);
}
}
function atomicWriteManagedState(stateFile, state, transactionRoot) {
const temporary = path.join(transactionRoot, "managed-state.next.json");
if (fs.existsSync(temporary)) fs.rmSync(temporary, { force: true });
writeFileDurable(temporary, `${canonicalJson(state)}\n`);
fs.renameSync(temporary, stateFile);
fsyncDirectory(path.dirname(stateFile));
}
module.exports = {
atomicWriteManagedState,
buildManagedState,
digestManagedEntries,
fsyncDirectory,
publicEntries,
readManagedState,
writeFileDurable,
};