NODEDC_PLATFORM/infra/deploy-runner/migrate-engine-gelios-v12-u...

209 lines
6.6 KiB
JavaScript

#!/usr/bin/env node
import { createHash } from "node:crypto";
import { readFile, writeFile } from "node:fs/promises";
import { resolve } from "node:path";
import { validateL2ExecutionPlan } from "../../packages/external-provider-contract/src/index.mjs";
import { geliosProviderPackageV12 } from "../../packages/external-provider-contract/providers/gelios/v12/index.mjs";
const PACKAGE_ID = "gelios.provider.v12";
const PREDECESSOR_VERSION = "12.0.0";
const TARGET_VERSION = "12.0.1";
const UNITS_CAPABILITY_ID = "gelios.units.current.read";
const engineRoot = resolve(
process.env.NODEDC_ENGINE_SOURCE_ROOT || "../NODEDC_ENGINE_INFRA",
);
const executionCatalogPath = resolve(
engineRoot,
"nodedc-source/server/assets/execution-plans/v1/catalog.json",
);
const securityCatalogPath = resolve(
engineRoot,
"nodedc-source/server/assets/provider-packages/v1/catalog.json",
);
if (
geliosProviderPackageV12.id !== PACKAGE_ID
|| geliosProviderPackageV12.version !== TARGET_VERSION
) {
throw new Error("gelios_v12_units_items_source_version_mismatch");
}
const sourceCapabilities = new Map(
geliosProviderPackageV12.capabilities.map((capability) => [
capability.id,
capability,
]),
);
const unitsCapability = sourceCapabilities.get(UNITS_CAPABILITY_ID);
if (
!unitsCapability
|| unitsCapability.request.response.collectionPaths[0] !== "items"
) {
throw new Error("gelios_v12_units_items_source_contract_invalid");
}
const executionCatalog = JSON.parse(
await readFile(executionCatalogPath, "utf8"),
);
const executionPackage = requirePackage(executionCatalog, "execution");
assertMigratableVersion(executionPackage.version, "execution");
executionPackage.version = TARGET_VERSION;
executionPackage.contractDigest = canonicalDigest(geliosProviderPackageV12);
executionPackage.capabilities = geliosProviderPackageV12.capabilities.map(
(capability) => ({
id: capability.id,
contractDigest: canonicalDigest(capability),
requestDigest: canonicalDigest(capability.request),
method: capability.request.method,
url: requestUrl(capability.request),
}),
);
for (const profile of executionPackage.profiles) {
const plan = profile.executionPlanTemplate;
if (!plan) throw new Error(`gelios_v12_registered_plan_missing:${profile.id}`);
if (plan.compilerVersion !== "1.4.0") {
throw new Error(`gelios_v12_registered_plan_compiler_drift:${profile.id}`);
}
plan.package.version = TARGET_VERSION;
plan.artifacts.packageDigest = canonicalDigest(geliosProviderPackageV12);
for (const step of plan.steps) {
const capabilityId = step.config?.capabilityId;
if (!capabilityId) continue;
const capability = sourceCapabilities.get(capabilityId);
if (!capability) {
throw new Error(
`gelios_v12_registered_plan_capability_missing:${profile.id}:${capabilityId}`,
);
}
step.config.capabilityDigest = canonicalDigest(capability);
if (step.kind === "provider_request") {
step.config.request = structuredClone(capability.request);
} else if (step.kind === "extract_items") {
step.config.response = structuredClone(capability.request.response);
}
}
const stepsById = new Map(plan.steps.map((step) => [step.id, step]));
for (const node of plan.graphBlueprint.nodes) {
const step = stepsById.get(node.stepId);
if (!step) {
throw new Error(
`gelios_v12_registered_plan_blueprint_step_missing:${profile.id}:${node.stepId}`,
);
}
node.configDigest = canonicalDigest({
stepConfig: step.config,
...(node.adapterConfig ? { adapterConfig: node.adapterConfig } : {}),
});
}
plan.graphBlueprint.blueprintDigest = digestWithout(
plan.graphBlueprint,
"blueprintDigest",
);
plan.executionPlanDigest = digestWithout(plan, "executionPlanDigest");
const validation = validateL2ExecutionPlan(plan);
if (!validation.ok) {
throw new Error(
`gelios_v12_registered_plan_invalid:${profile.id}:`
+ validation.errors.join(","),
);
}
}
const technicalProfile = executionPackage.profiles.find(
(profile) => profile.id === "gelios.units.profile.cold.v1",
);
const technicalExtract = technicalProfile?.executionPlanTemplate?.steps.find(
(step) => (
step.kind === "extract_items"
&& step.config?.capabilityId === UNITS_CAPABILITY_ID
),
);
if (technicalExtract?.config?.response?.collectionPaths?.[0] !== "items") {
throw new Error("gelios_v12_technical_profile_items_envelope_missing");
}
const securityCatalog = JSON.parse(
await readFile(securityCatalogPath, "utf8"),
);
const securityPackage = requirePackage(securityCatalog, "security");
assertMigratableVersion(securityPackage.version, "security");
securityPackage.version = TARGET_VERSION;
await writeFile(
executionCatalogPath,
`${JSON.stringify(executionCatalog, null, 2)}\n`,
);
await writeFile(
securityCatalogPath,
`${JSON.stringify(securityCatalog, null, 2)}\n`,
);
console.log(JSON.stringify({
ok: true,
packageId: PACKAGE_ID,
predecessorVersion: PREDECESSOR_VERSION,
targetVersion: TARGET_VERSION,
executionCatalogPath,
securityCatalogPath,
registeredProfiles: executionPackage.profiles.length,
technicalProfile: technicalProfile.id,
collectionPaths: technicalExtract.config.response.collectionPaths,
compilerVersion: technicalProfile.executionPlanTemplate.compilerVersion,
}, null, 2));
function requirePackage(catalog, kind) {
const matches = catalog.packages.filter(
(providerPackage) => providerPackage.id === PACKAGE_ID,
);
if (matches.length !== 1) {
throw new Error(`gelios_v12_${kind}_package_cardinality_invalid`);
}
return matches[0];
}
function assertMigratableVersion(version, kind) {
if (![PREDECESSOR_VERSION, TARGET_VERSION].includes(version)) {
throw new Error(
`gelios_v12_${kind}_package_version_drift:${String(version)}`,
);
}
}
function requestUrl(request) {
const url = new URL(request.path, request.baseUrl);
for (const [name, value] of Object.entries(request.query || {})) {
url.searchParams.append(name, String(value));
}
return url.toString();
}
function digestWithout(value, key) {
const descriptor = structuredClone(value);
delete descriptor[key];
return canonicalDigest(descriptor);
}
function canonicalDigest(value) {
return `sha256:${createHash("sha256").update(
JSON.stringify(stableValue(value)),
"utf8",
).digest("hex")}`;
}
function stableValue(value) {
if (Array.isArray(value)) return value.map(stableValue);
if (!value || typeof value !== "object") return value;
return Object.fromEntries(
Object.keys(value)
.filter((key) => value[key] !== undefined)
.sort()
.map((key) => [key, stableValue(value[key])]),
);
}