209 lines
6.6 KiB
JavaScript
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])]),
|
|
);
|
|
}
|