Files
playbook/antigravity-awesome-skills/tools/lib/aas-v1/stack/plan.js
T
2026-07-20 00:03:02 +00:00

347 lines
15 KiB
JavaScript

"use strict";
const versions = require("../versions");
const { canonicalJson, canonicalize, sha256 } = require("../canonical-json");
const { DIGEST_PATTERN, ID_PATTERN, validateManifest } = require("./manifest");
const { validateInstance } = require("../schema-validator");
const VERSION_KEYS = Object.freeze([
"protocolVersion",
"coreVersion",
"catalogSchemaVersion",
]);
const OPERATION_KINDS = new Set(["install", "replaceManaged", "removeManaged"]);
const OVERRIDE_KINDS = new Set(["managedDrift"]);
const OPERATION_ORDER = Object.freeze({ removeManaged: 0, replaceManaged: 1, install: 2 });
function compareStrings(left, right) {
return left < right ? -1 : (left > right ? 1 : 0);
}
function planError(code, category, details = {}) {
const error = new Error(code);
error.code = code;
error.category = category;
error.details = details;
return error;
}
function assertPlainObject(value, name) {
const prototype = value && typeof value === "object" ? Object.getPrototypeOf(value) : null;
if (!value || Array.isArray(value) || (prototype !== Object.prototype && prototype !== null)) {
throw planError("AAS_PLAN_INPUT_INVALID", "invalidInput", { field: name });
}
}
function assertExactKeys(value, allowed, field) {
const unknown = Object.keys(value).filter((key) => !allowed.has(key)).sort();
if (unknown.length) throw planError("AAS_PLAN_FIELD_UNKNOWN", "invalidInput", { field, keys: unknown });
}
function assertDigest(value, field) {
if (typeof value !== "string" || !DIGEST_PATTERN.test(value)) {
throw planError("AAS_PLAN_DIGEST_INVALID", "invalidInput", { field });
}
}
function assertNonEmptyString(value, field, maximum = 256) {
if (typeof value !== "string" || value.length < 1 || value.length > maximum) {
throw planError("AAS_PLAN_STRING_INVALID", "invalidInput", { field });
}
}
function assertVersionHandshake(handshake) {
assertPlainObject(handshake, "handshake");
assertExactKeys(handshake, new Set(VERSION_KEYS), "handshake");
const incompatible = VERSION_KEYS.filter((key) => handshake[key] !== versions[key]);
if (incompatible.length) {
throw planError("AAS_PLAN_VERSION_INCOMPATIBLE", "incompatibleVersion", {
fields: incompatible,
expected: Object.fromEntries(incompatible.map((key) => [key, versions[key]])),
received: Object.fromEntries(incompatible.map((key) => [key, handshake[key] ?? null])),
});
}
return Object.fromEntries(VERSION_KEYS.map((key) => [key, versions[key]]));
}
function normalizeCatalog(catalog, manifestCatalog) {
assertPlainObject(catalog, "catalog");
assertExactKeys(catalog, new Set(["package", "version", "integrity"]), "catalog");
for (const key of ["package", "version", "integrity"]) {
if (catalog[key] !== manifestCatalog[key]) {
throw planError("AAS_PLAN_CATALOG_MISMATCH", "integrity", { field: key });
}
}
assertDigest(catalog.integrity, "catalog.integrity");
return { package: catalog.package, version: catalog.version, integrity: catalog.integrity };
}
function normalizeRuntime(runtime) {
assertPlainObject(runtime, "runtime");
assertExactKeys(runtime, new Set(["package", "version", "integrity", "closureDigest"]), "runtime");
assertNonEmptyString(runtime.package, "runtime.package", 214);
assertNonEmptyString(runtime.version, "runtime.version", 64);
assertNonEmptyString(runtime.integrity, "runtime.integrity", 512);
assertDigest(runtime.closureDigest, "runtime.closureDigest");
return { ...runtime };
}
function normalizeTarget(target, manifest) {
assertPlainObject(target, "target");
assertExactKeys(target, new Set(["host", "scope", "adapterVersion", "identityDigest"]), "target");
assertNonEmptyString(target.adapterVersion, "target.adapterVersion", 64);
assertDigest(target.identityDigest, "target.identityDigest");
if (!manifest.targets.some((candidate) => candidate.host === target.host && candidate.scope === target.scope)) {
throw planError("AAS_PLAN_TARGET_NOT_IN_MANIFEST", "invalidInput", { host: target.host, scope: target.scope });
}
return { ...target };
}
function normalizeInstalledState(installedState) {
assertPlainObject(installedState, "installedState");
assertExactKeys(installedState, new Set(["digest", "entries"]), "installedState");
assertDigest(installedState.digest, "installedState.digest");
if (!Array.isArray(installedState.entries) || installedState.entries.length > 128) {
throw planError("AAS_PLAN_INSTALLED_STATE_INVALID", "invalidInput", {});
}
const seen = new Set();
const entries = installedState.entries.map((entry, index) => {
assertPlainObject(entry, `installedState.entries[${index}]`);
assertExactKeys(entry, new Set(["skillId", "treeDigest", "catalogIntegrity"]), `installedState.entries[${index}]`);
if (typeof entry.skillId !== "string" || !ID_PATTERN.test(entry.skillId) || seen.has(entry.skillId)) {
throw planError("AAS_PLAN_INSTALLED_ENTRY_INVALID", "invalidInput", { index });
}
seen.add(entry.skillId);
assertDigest(entry.treeDigest, `installedState.entries[${index}].treeDigest`);
assertDigest(entry.catalogIntegrity, `installedState.entries[${index}].catalogIntegrity`);
return { ...entry };
});
entries.sort((left, right) => compareStrings(left.skillId, right.skillId));
const computedDigest = sha256(canonicalJson({ schemaVersion: 1, entries }));
if (installedState.digest !== computedDigest) {
throw planError("AAS_PLAN_INSTALLED_STATE_DIGEST_MISMATCH", "integrity", {});
}
return { digest: installedState.digest, entries };
}
function normalizeOperation(operation, index, desiredSkillIds) {
assertPlainObject(operation, `operations[${index}]`);
assertExactKeys(
operation,
new Set(["kind", "skillId", "sourceTreeDigest", "expectedTreeDigest", "resultTreeDigest", "backupRequired"]),
`operations[${index}]`,
);
if (!OPERATION_KINDS.has(operation.kind) || typeof operation.skillId !== "string" || !ID_PATTERN.test(operation.skillId)) {
throw planError("AAS_PLAN_OPERATION_INVALID", "invalidInput", { index });
}
if (operation.kind !== "removeManaged" && !desiredSkillIds.has(operation.skillId)) {
throw planError("AAS_PLAN_OPERATION_SKILL_NOT_DESIRED", "invalidInput", { index, skillId: operation.skillId });
}
if (operation.kind === "removeManaged" && desiredSkillIds.has(operation.skillId)) {
throw planError("AAS_PLAN_REMOVE_SKILL_STILL_DESIRED", "invalidInput", { index, skillId: operation.skillId });
}
if (operation.kind === "install") {
if (operation.expectedTreeDigest !== null || operation.backupRequired !== false) {
throw planError("AAS_PLAN_OPERATION_PRECONDITION_INVALID", "invalidInput", { index });
}
assertDigest(operation.sourceTreeDigest, `operations[${index}].sourceTreeDigest`);
assertDigest(operation.resultTreeDigest, `operations[${index}].resultTreeDigest`);
if (operation.sourceTreeDigest !== operation.resultTreeDigest) {
throw planError("AAS_PLAN_OPERATION_RESULT_MISMATCH", "integrity", { index });
}
} else if (operation.kind === "replaceManaged") {
assertDigest(operation.sourceTreeDigest, `operations[${index}].sourceTreeDigest`);
assertDigest(operation.expectedTreeDigest, `operations[${index}].expectedTreeDigest`);
assertDigest(operation.resultTreeDigest, `operations[${index}].resultTreeDigest`);
if (operation.sourceTreeDigest !== operation.resultTreeDigest) {
throw planError("AAS_PLAN_OPERATION_RESULT_MISMATCH", "integrity", { index });
}
if (operation.backupRequired !== true) {
throw planError("AAS_PLAN_OPERATION_BACKUP_REQUIRED", "invalidInput", { index });
}
} else {
if (operation.sourceTreeDigest !== null || operation.resultTreeDigest !== null || operation.backupRequired !== true) {
throw planError("AAS_PLAN_OPERATION_PRECONDITION_INVALID", "invalidInput", { index });
}
assertDigest(operation.expectedTreeDigest, `operations[${index}].expectedTreeDigest`);
}
return { ...operation };
}
function normalizeOperations(operations, manifest) {
if (!Array.isArray(operations) || operations.length > 256) {
throw planError("AAS_PLAN_OPERATIONS_INVALID", "invalidInput", {});
}
const desiredSkillIds = new Set(manifest.skills.map((skill) => skill.id));
const normalized = operations.map((operation, index) => normalizeOperation(operation, index, desiredSkillIds));
const seen = new Set();
for (const operation of normalized) {
if (seen.has(operation.skillId)) {
throw planError("AAS_PLAN_OPERATION_DUPLICATE", "invalidInput", { skillId: operation.skillId });
}
seen.add(operation.skillId);
}
return normalized.sort((left, right) => (
OPERATION_ORDER[left.kind] - OPERATION_ORDER[right.kind]
|| compareStrings(left.skillId, right.skillId)
));
}
function normalizeOverrides(overrides, operationIds) {
if (!Array.isArray(overrides) || overrides.length > 128) {
throw planError("AAS_PLAN_OVERRIDES_INVALID", "invalidInput", {});
}
const seen = new Set();
const normalized = overrides.map((override, index) => {
assertPlainObject(override, `overrides[${index}]`);
assertExactKeys(override, new Set(["kind", "skillId", "reasonCodes"]), `overrides[${index}]`);
if (!OVERRIDE_KINDS.has(override.kind) || typeof override.skillId !== "string" || !operationIds.has(override.skillId)) {
throw planError("AAS_PLAN_OVERRIDE_INVALID", "invalidInput", { index });
}
if (!Array.isArray(override.reasonCodes) || override.reasonCodes.length < 1 || override.reasonCodes.some((value) => typeof value !== "string")) {
throw planError("AAS_PLAN_OVERRIDE_REASON_REQUIRED", "invalidInput", { index });
}
const key = `${override.kind}:${override.skillId}`;
if (seen.has(key)) throw planError("AAS_PLAN_OVERRIDE_DUPLICATE", "invalidInput", { index });
seen.add(key);
return {
kind: override.kind,
skillId: override.skillId,
reasonCodes: [...new Set(override.reasonCodes)].sort(),
};
});
return normalized.sort((left, right) => compareStrings(`${left.kind}:${left.skillId}`, `${right.kind}:${right.skillId}`));
}
function normalizeStateCommit(stateCommit, installedStateDigest) {
assertPlainObject(stateCommit, "stateCommit");
assertExactKeys(stateCommit, new Set(["previousDigest", "nextDigest", "position"]), "stateCommit");
if (stateCommit.previousDigest !== installedStateDigest) {
throw planError("AAS_PLAN_STATE_COMMIT_MISMATCH", "drift", {});
}
assertDigest(stateCommit.nextDigest, "stateCommit.nextDigest");
if (stateCommit.position !== "final") throw planError("AAS_PLAN_STATE_COMMIT_NOT_FINAL", "invalidInput", {});
return { ...stateCommit };
}
function expectedNextStateDigest(installedState, operations, catalogIntegrity) {
const entries = new Map(installedState.entries.map((entry) => [entry.skillId, entry]));
for (const operation of operations) {
if (operation.kind === "removeManaged") entries.delete(operation.skillId);
else entries.set(operation.skillId, {
skillId: operation.skillId,
treeDigest: operation.resultTreeDigest,
catalogIntegrity,
});
}
const normalized = [...entries.values()].sort((left, right) => compareStrings(left.skillId, right.skillId));
return sha256(canonicalJson({ schemaVersion: 1, entries: normalized }));
}
function buildPlanEnvelope(input) {
assertPlainObject(input, "input");
assertExactKeys(
input,
new Set(["manifest", "handshake", "catalog", "runtime", "target", "installedState", "operations", "overrides", "stateCommit"]),
"input",
);
const validation = validateManifest(input.manifest);
if (!validation.ok) throw planError("AAS_PLAN_MANIFEST_INVALID", "invalidInput", validation.details);
const boundVersions = assertVersionHandshake(input.handshake);
const catalog = normalizeCatalog(input.catalog, input.manifest.catalog);
const runtime = normalizeRuntime(input.runtime);
const target = normalizeTarget(input.target, input.manifest);
const installedState = normalizeInstalledState(input.installedState);
const operations = normalizeOperations(input.operations, input.manifest);
const operationIds = new Set(operations.map((operation) => operation.skillId));
const overrides = normalizeOverrides(input.overrides ?? [], operationIds);
const stateCommit = normalizeStateCommit(input.stateCommit, installedState.digest);
if (runtime.package !== catalog.package || runtime.version !== catalog.version) {
throw planError("AAS_PLAN_RUNTIME_CATALOG_MISMATCH", "integrity", {});
}
if (stateCommit.nextDigest !== expectedNextStateDigest(installedState, operations, catalog.integrity)) {
throw planError("AAS_PLAN_NEXT_STATE_MISMATCH", "integrity", {});
}
const payload = canonicalize({
schemaVersion: 2,
kind: "aas.stack-plan.payload",
versions: boundVersions,
manifestDigest: validation.manifestDigest,
catalog,
runtime,
target,
installedState,
desiredSkills: input.manifest.skills.map((skill) => skill.id).sort(),
profile: input.manifest.profile,
operations,
overrides,
stateCommit,
});
const payloadJson = canonicalJson(payload);
return validateInstance("plan.schema.json", {
schemaVersion: 2,
kind: "aas.stack-plan",
digest: sha256(payloadJson),
payload,
}, "AAS_PLAN_SCHEMA_INVALID", "invalidInput");
}
function validatePlanEnvelope(plan) {
validateInstance("plan.schema.json", plan, "AAS_TRANSACTION_PLAN_SCHEMA_INVALID");
assertPlainObject(plan, "plan");
assertExactKeys(plan, new Set(["schemaVersion", "kind", "digest", "payload"]), "plan");
if (plan.schemaVersion !== 2 || plan.kind !== "aas.stack-plan") {
throw planError("AAS_TRANSACTION_PLAN_INVALID", "integrity", {});
}
assertDigest(plan.digest, "plan.digest");
assertPlainObject(plan.payload, "plan.payload");
assertExactKeys(plan.payload, new Set([
"schemaVersion", "kind", "versions", "manifestDigest", "catalog", "runtime", "target",
"installedState", "desiredSkills", "profile", "operations", "overrides", "stateCommit",
]), "plan.payload");
if (plan.payload.schemaVersion !== 2 || plan.payload.kind !== "aas.stack-plan.payload"
|| plan.digest !== sha256(canonicalJson(plan.payload))) {
throw planError("AAS_TRANSACTION_PLAN_INVALID", "integrity", {});
}
assertDigest(plan.payload.manifestDigest, "manifestDigest");
if (!Array.isArray(plan.payload.desiredSkills) || plan.payload.desiredSkills.length > 128
|| plan.payload.desiredSkills.some((id) => typeof id !== "string" || !ID_PATTERN.test(id))) {
throw planError("AAS_PLAN_DESIRED_SKILLS_INVALID", "invalidInput", {});
}
const syntheticManifest = {
schemaVersion: 2,
name: "validated-plan",
catalog: plan.payload.catalog,
targets: [{ host: plan.payload.target?.host, scope: plan.payload.target?.scope }],
profile: plan.payload.profile,
skills: plan.payload.desiredSkills.map((id) => ({ id })),
};
const rebuilt = buildPlanEnvelope({
manifest: syntheticManifest,
handshake: plan.payload.versions,
catalog: plan.payload.catalog,
runtime: plan.payload.runtime,
target: plan.payload.target,
installedState: plan.payload.installedState,
operations: plan.payload.operations,
overrides: plan.payload.overrides,
stateCommit: plan.payload.stateCommit,
});
const expectedPayload = { ...rebuilt.payload, manifestDigest: plan.payload.manifestDigest };
if (canonicalJson(expectedPayload) !== canonicalJson(plan.payload)) {
throw planError("AAS_TRANSACTION_PLAN_INVALID", "integrity", {});
}
return plan;
}
module.exports = {
OPERATION_KINDS,
OVERRIDE_KINDS,
VERSION_KEYS,
assertVersionHandshake,
buildPlanEnvelope,
validatePlanEnvelope,
};