#!/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])]), ); }