#!/usr/bin/env node import { createHash } from "node:crypto"; import { readFile, writeFile } from "node:fs/promises"; import { resolve } from "node:path"; import { compileL2ExecutionPlan, instantiateL2Connection, } from "../../packages/external-provider-contract/src/index.mjs"; import { geliosProviderPackageV12, geliosTelemetryFieldRegistryV5, } from "../../packages/external-provider-contract/providers/gelios/v12/index.mjs"; const PUBLIC_PROFILE_METADATA = Object.freeze({ "gelios.positions.current.realtime.v7": { cadence: "hot", dataClass: "operational", }, "gelios.positions.current.manual.v7": { cadence: "on_demand", dataClass: "operational", }, "gelios.geozones.current.realtime.v1": { cadence: "cold", dataClass: "operational", }, "gelios.units.profile.cold.v1": { cadence: "cold", dataClass: "operational", }, "gelios.units.contacts.cold.v1": { cadence: "cold", dataClass: "restricted", }, "gelios.units.identity.warm.v1": { cadence: "warm", dataClass: "restricted", }, }); 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", ); const executionCatalog = JSON.parse( await readFile(executionCatalogPath, "utf8"), ); executionCatalog.packages = executionCatalog.packages.filter( (providerPackage) => providerPackage.id !== geliosProviderPackageV12.id, ); executionCatalog.packages.push(executionCatalogEntry()); await writeFile(executionCatalogPath, `${JSON.stringify(executionCatalog, null, 2)}\n`); const securityCatalog = JSON.parse( await readFile(securityCatalogPath, "utf8"), ); securityCatalog.packages = securityCatalog.packages.filter( (providerPackage) => ![ "gelios.provider.v11", geliosProviderPackageV12.id, ].includes(providerPackage.id), ); securityCatalog.packages.push(securityCatalogEntry()); await writeFile(securityCatalogPath, `${JSON.stringify(securityCatalog, null, 2)}\n`); console.log(JSON.stringify({ ok: true, providerPackage: geliosProviderPackageV12.id, executionCatalogPath, securityCatalogPath, historicalExecutionPackagePreserved: executionCatalog.packages.some( (providerPackage) => providerPackage.id === "gelios.provider.v11", ), activeIdentityAuthority: geliosProviderPackageV12.id, }, null, 2)); function executionCatalogEntry() { const profiles = geliosProviderPackageV12.collectionProfiles.map((profile) => { const connection = instantiateL2Connection(geliosProviderPackageV12, { tenantId: "tenant-catalog-build", connectionId: `catalog-${profile.id.replaceAll(".", "-")}`, collectionProfileId: profile.id, providerCredentialRef: "ndc-credref:catalog-build-provider-v12", }); const plan = compileL2ExecutionPlan( geliosProviderPackageV12, connection, profile.dataProductId === "fleet.positions.current.v5" ? { telemetryFieldRegistry: geliosTelemetryFieldRegistryV5 } : {}, ); const { packageDigest: _packageDigest, ...artifacts } = plan.artifacts; const publicMetadata = PUBLIC_PROFILE_METADATA[profile.id]; if (!publicMetadata) { throw new Error(`gelios_v12_public_profile_metadata_missing:${profile.id}`); } return { id: profile.id, dataProductId: profile.dataProductId, ...publicMetadata, capabilityIds: [...profile.capabilityIds], stepSignatures: plan.steps.map((step) => ({ id: step.id, kind: step.kind, ...(step.config.capabilityId ? { capabilityId: step.config.capabilityId } : {}), ...(step.config.mappingContractId ? { mappingContractId: step.config.mappingContractId, } : {}), ...(step.config.dataProductId ? { dataProductId: step.config.dataProductId } : {}), ...(step.config.nodeType ? { nodeType: step.config.nodeType } : {}), })), artifacts, executionPlanTemplate: plan, }; }); return { id: geliosProviderPackageV12.id, providerId: geliosProviderPackageV12.providerId, version: geliosProviderPackageV12.version, contractDigest: canonicalDigest(geliosProviderPackageV12), providerCredential: { authModeId: "gelios.rest-rotating-bearer.v3", credentialType: "ndcProviderRotatingAccessApi", }, publisher: { nodeType: "n8n-nodes-ndc.ndcDataProductPublish", credentialType: "ndcDataProductWriterApi", }, capabilities: geliosProviderPackageV12.capabilities.map((capability) => ({ id: capability.id, contractDigest: canonicalDigest(capability), requestDigest: canonicalDigest(capability.request), method: capability.request.method, url: requestUrl(capability.request), })), profiles, }; } function securityCatalogEntry() { const productsByCapability = new Map( geliosProviderPackageV12.capabilities.map((capability) => [capability.id, new Set()]), ); for (const profile of geliosProviderPackageV12.collectionProfiles) { if (profile.dataProductId !== "fleet.units.identity.current.v1") continue; for (const capabilityId of profile.capabilityIds) { productsByCapability.get(capabilityId)?.add(profile.dataProductId); } } return { id: geliosProviderPackageV12.id, version: geliosProviderPackageV12.version, providerId: geliosProviderPackageV12.providerId, providerCredential: { authModeId: "gelios.rest-rotating-bearer.v3", credentialType: "ndcProviderRotatingAccessApi", }, capabilities: geliosProviderPackageV12.capabilities .filter((capability) => productsByCapability.get(capability.id)?.size) .map((capability) => ({ id: capability.id, classification: capability.classification, status: capability.status, request: { method: capability.request.method, url: requestUrl(capability.request), }, dataProductIds: [...productsByCapability.get(capability.id)].sort(), })), publisher: { nodeType: "n8n-nodes-ndc.ndcDataProductPublish", credentialType: "ndcDataProductWriterApi", }, }; } 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 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).sort().map((key) => [key, stableValue(value[key])]), ); }