From 4cdc78d54537e46cc7c16b355b3d25af2d2a677e Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 23 Jul 2026 07:48:17 +0300 Subject: [PATCH] feat(deploy): add Gelios profile authority transition --- ...ine-provider-security-catalog-artifact.mjs | 8 +- infra/deploy-runner/nodedc-deploy | 77 ++++++++++++++----- .../test_engine_provider_security_catalog.py | 12 ++- 3 files changed, 71 insertions(+), 26 deletions(-) diff --git a/infra/deploy-runner/build-engine-provider-security-catalog-artifact.mjs b/infra/deploy-runner/build-engine-provider-security-catalog-artifact.mjs index 68c6c44..abe3070 100644 --- a/infra/deploy-runner/build-engine-provider-security-catalog-artifact.mjs +++ b/infra/deploy-runner/build-engine-provider-security-catalog-artifact.mjs @@ -14,7 +14,7 @@ const engineRoot = resolve( const artifactRoot = resolve( process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(here, "../deploy-artifacts"), ); -const [transitionId = "20260722-020", ...extra] = process.argv.slice(2); +const [transitionId = "20260723-025", ...extra] = process.argv.slice(2); if (extra.length || !/^\d{8}-[0-9]{3}$/.test(transitionId)) { throw new Error("usage: build-engine-provider-security-catalog-artifact.mjs [YYYYMMDD-NNN]"); } @@ -22,7 +22,7 @@ if (extra.length || !/^\d{8}-[0-9]{3}$/.test(transitionId)) { const id = `engine-provider-security-catalog-${transitionId}`; const target = join(artifactRoot, `nodedc-${id}.tgz`); const file = "nodedc-source/server/assets/provider-packages/v1/catalog.json"; -const expectedSha256 = "dd67d8b01091072a4e7be3820b9639c12f2f897ccbbd79208123564d10c1cd81"; +const expectedSha256 = "773335bb616a5c03eb2108c4f53ef092c02e75ee75f014511201bc12e2956b27"; const source = join(engineRoot, file); const catalogBytes = await readFile(source); @@ -44,10 +44,10 @@ try { artifact: target, artifactSha256: sha(await readFile(target)), services: ["nodedc-backend"], - providerPackage: "gelios.provider.v7", + providerPackage: "gelios.provider.v8", providerCredential: "ndcProviderRotatingAccessApi", endpoint: "https://api.geliospro.com/api/v1/units?incltrip=true&inclcntrs=true&inclsnsrs=true&incllsv=true", - dataProductId: "fleet.positions.current.v5", + dataProductId: "fleet.units.profile.current.v1", preserved: ["n8n", "L1 graph", "Engine UI", "databases", "provider credential values"], files: [file], }, null, 2)); diff --git a/infra/deploy-runner/nodedc-deploy b/infra/deploy-runner/nodedc-deploy index 29a8cae..9739184 100755 --- a/infra/deploy-runner/nodedc-deploy +++ b/infra/deploy-runner/nodedc-deploy @@ -242,10 +242,10 @@ ENGINE_PROVIDER_SECURITY_CATALOG_ARTIFACT_ENTRIES = ( ENGINE_PROVIDER_SECURITY_CATALOG_REL, ) ENGINE_PROVIDER_SECURITY_CATALOG_PREDECESSOR_SHA256 = ( - "1aac712a05e55137d69eedaf363969460ffe866288e8e18e35711967ab232f39" + "dd67d8b01091072a4e7be3820b9639c12f2f897ccbbd79208123564d10c1cd81" ) ENGINE_PROVIDER_SECURITY_CATALOG_TARGET_SHA256 = ( - "dd67d8b01091072a4e7be3820b9639c12f2f897ccbbd79208123564d10c1cd81" + "773335bb616a5c03eb2108c4f53ef092c02e75ee75f014511201bc12e2956b27" ) ENGINE_CONTROL_PLANE_STATE_REL = "nodedc-control-plane" ENGINE_PUBLISH_GRANT_STATE_REL = f"{ENGINE_CONTROL_PLANE_STATE_REL}/publish-grants" @@ -3892,6 +3892,13 @@ def validate_engine_provider_security_catalog_payload(payload_dir, entries): package_ids = [item.get("id") for item in packages if isinstance(item, dict)] \ if isinstance(packages, list) else None provider = next( + ( + item for item in packages + if isinstance(item, dict) and item.get("id") == "gelios.provider.v8" + ), + None, + ) if isinstance(packages, list) else None + previous_provider = next( ( item for item in packages if isinstance(item, dict) and item.get("id") == "gelios.provider.v7" @@ -3911,9 +3918,10 @@ def validate_engine_provider_security_catalog_payload(payload_dir, entries): "gelios.provider.v5", "moscow-department-of-transport.pmd-slow-zones.v1", "gelios.provider.v7", + "gelios.provider.v8", ] or not isinstance(provider, dict) - or provider.get("version") != "7.0.0" + or provider.get("version") != "8.0.0" or provider.get("providerId") != "gelios" or credential != { "authModeId": "gelios.rest-rotating-bearer.v3", @@ -3930,13 +3938,41 @@ def validate_engine_provider_security_catalog_payload(payload_dir, entries): "&inclsnsrs=true&incllsv=true" ), } - or capability.get("dataProductIds") != ["fleet.positions.current.v5"] + or capability.get("dataProductIds") != ["fleet.units.profile.current.v1"] or publisher != { "nodeType": "n8n-nodes-ndc.ndcDataProductPublish", "credentialType": "ndcDataProductWriterApi", } + or previous_provider != { + "id": "gelios.provider.v7", + "version": "7.0.0", + "providerId": "gelios", + "providerCredential": { + "authModeId": "gelios.rest-rotating-bearer.v3", + "credentialType": "ndcProviderRotatingAccessApi", + }, + "capabilities": [ + { + "id": "gelios.units.current.read", + "classification": "read", + "status": "implemented", + "request": { + "method": "GET", + "url": ( + "https://api.geliospro.com/api/v1/units?incltrip=true" + "&inclcntrs=true&inclsnsrs=true&incllsv=true" + ), + }, + "dataProductIds": ["fleet.positions.current.v5"], + }, + ], + "publisher": { + "nodeType": "n8n-nodes-ndc.ndcDataProductPublish", + "credentialType": "ndcDataProductWriterApi", + }, + } ): - die("Engine provider security catalog v7 telemetry projection mismatch") + die("Engine provider security catalog v7/v8 projection mismatch") return catalog @@ -3976,28 +4012,31 @@ def accept_engine_provider_security_catalog_runtime(): const module=await import('file:///app/server/dataProductPublishGrant/providerCatalog.js'); const catalog=await module.loadProviderSecurityCatalog(); const validation=module.validateProviderSecurityCatalog(catalog); -const provider=catalog.packages?.find((item)=>item.id==='gelios.provider.v7'); +const provider=catalog.packages?.find((item)=>item.id==='gelios.provider.v8'); const capability=provider?.capabilities?.[0]; const slot='ndcProviderRotatingAccessApi'; const credential={id:'native-provider',name:'provider',nodeDcCredentialId:'provider-ref'}; const policy={kind:'httpRequest',allowedHosts:['api.geliospro.com'],requireHttps:true,disableRedirects:true}; const queryParameters={parameters:[{name:'incltrip',value:'true'},{name:'inclcntrs',value:'true'},{name:'inclsnsrs',value:'true'},{name:'incllsv',value:'true'}]}; const source={id:'units',data:{n8n:{id:'units',name:'units',type:'n8n-nodes-base.httpRequest',parameters:{method:'GET',url:'https://api.geliospro.com/api/v1/units',sendQuery:true,specifyQuery:'keypair',queryParameters},credentials:{[slot]:credential}},nodedcAgentCredentialPolicies:{[slot]:policy}}}; -const publish={id:'publish',data:{n8n:{id:'publish',name:'publish',type:'n8n-nodes-ndc.ndcDataProductPublish',parameters:{dataProductId:'fleet.positions.current.v5'},credentials:{}}}}; -const graph={nodes:[source,publish],edges:[{source:'units',target:'publish'}]}; -const resolved=module.resolveProviderConnectionFromGraph({graph,targetNodeId:'publish',catalog}); -const drift=structuredClone(graph);drift.nodes[0].data.n8n.parameters.queryParameters.parameters.pop(); -const rejected=module.resolveProviderConnectionFromGraph({graph:drift,targetNodeId:'publish',catalog}); -if(!validation.ok||provider?.providerCredential?.credentialType!==slot||capability?.request?.url!=='https://api.geliospro.com/api/v1/units?incltrip=true&inclcntrs=true&inclsnsrs=true&incllsv=true'||capability?.dataProductIds?.[0]!=='fleet.positions.current.v5'||!resolved.ok||resolved.descriptor?.packageId!=='gelios.provider.v7'||resolved.descriptor?.providerCredentialRef!=='provider-ref'||rejected.blockers?.join(',')!=='publish_grant_provider_request_not_exact')process.exit(2); -process.stdout.write('engine-provider-catalog:gelios.provider.v7:ndcProviderRotatingAccessApi:fleet.positions.current.v5'); +const profilePublish={id:'profile-publish',data:{n8n:{id:'profile-publish',name:'profile-publish',type:'n8n-nodes-ndc.ndcDataProductPublish',parameters:{dataProductId:'fleet.units.profile.current.v1'},credentials:{}}}}; +const positionsPublish={id:'positions-publish',data:{n8n:{id:'positions-publish',name:'positions-publish',type:'n8n-nodes-ndc.ndcDataProductPublish',parameters:{dataProductId:'fleet.positions.current.v5'},credentials:{}}}}; +const profileGraph={nodes:[source,profilePublish],edges:[{source:'units',target:'profile-publish'}]}; +const positionsGraph={nodes:[source,positionsPublish],edges:[{source:'units',target:'positions-publish'}]}; +const profileResolved=module.resolveProviderConnectionFromGraph({graph:profileGraph,targetNodeId:'profile-publish',catalog}); +const positionsResolved=module.resolveProviderConnectionFromGraph({graph:positionsGraph,targetNodeId:'positions-publish',catalog}); +const drift=structuredClone(profileGraph);drift.nodes[0].data.n8n.parameters.queryParameters.parameters.pop(); +const rejected=module.resolveProviderConnectionFromGraph({graph:drift,targetNodeId:'profile-publish',catalog}); +if(!validation.ok||provider?.providerCredential?.credentialType!==slot||capability?.request?.url!=='https://api.geliospro.com/api/v1/units?incltrip=true&inclcntrs=true&inclsnsrs=true&incllsv=true'||capability?.dataProductIds?.[0]!=='fleet.units.profile.current.v1'||!profileResolved.ok||profileResolved.descriptor?.packageId!=='gelios.provider.v8'||profileResolved.descriptor?.providerCredentialRef!=='provider-ref'||!positionsResolved.ok||positionsResolved.descriptor?.packageId!=='gelios.provider.v7'||positionsResolved.descriptor?.dataProductId!=='fleet.positions.current.v5'||rejected.blockers?.join(',')!=='publish_grant_provider_request_not_exact')process.exit(2); +process.stdout.write('engine-provider-catalog:gelios.provider.v7:fleet.positions.current.v5|gelios.provider.v8:fleet.units.profile.current.v1'); """.strip(), ), - "Engine provider security catalog v7 telemetry", + "Engine provider security catalog v8 unit profile", container_id=engine_backend_container_id(), ) expected = ( - "engine-provider-catalog:gelios.provider.v7:" - "ndcProviderRotatingAccessApi:fleet.positions.current.v5" + "engine-provider-catalog:gelios.provider.v7:fleet.positions.current.v5|" + "gelios.provider.v8:fleet.units.profile.current.v1" ) if live != expected: die("Engine provider security catalog live acceptance mismatch") @@ -7981,14 +8020,14 @@ def plan_artifact(artifact): print("node_intelligence_image=preserved") print("n8n_l1=untouched") if provider_catalog_preflight: - print("engine_provider_catalog_transition=gelios-telemetry-authority-v7") - print("engine_provider_package=gelios.provider.v7") + print("engine_provider_catalog_transition=gelios-unit-profile-authority-v8") + print("engine_provider_package=gelios.provider.v8") print("engine_provider_credential=ndcProviderRotatingAccessApi") print( "engine_provider_endpoint=https://api.geliospro.com/api/v1/units?" "incltrip=true&inclcntrs=true&inclsnsrs=true&incllsv=true" ) - print("engine_data_product=fleet.positions.current.v5") + print("engine_data_product=fleet.units.profile.current.v1") print( "predecessor_catalog_sha256=" f"{provider_catalog_preflight['catalog_sha256']}" diff --git a/infra/deploy-runner/test_engine_provider_security_catalog.py b/infra/deploy-runner/test_engine_provider_security_catalog.py index 003c302..4c644e6 100644 --- a/infra/deploy-runner/test_engine_provider_security_catalog.py +++ b/infra/deploy-runner/test_engine_provider_security_catalog.py @@ -29,7 +29,7 @@ RUNNER = load_runner() class EngineProviderSecurityCatalogTest(unittest.TestCase): - def test_builder_emits_exact_backend_only_telemetry_authority_slice(self): + def test_builder_emits_exact_backend_only_unit_profile_authority_slice(self): with tempfile.TemporaryDirectory(prefix="nodedc-engine-provider-catalog-") as directory: artifact_dir = Path(directory) / "artifacts" env = os.environ.copy() @@ -58,12 +58,18 @@ class EngineProviderSecurityCatalogTest(unittest.TestCase): ) catalog = RUNNER.validate_engine_provider_security_catalog_payload(payload, entries) provider = next( + item for item in catalog["packages"] if item["id"] == "gelios.provider.v8" + ) + previous_provider = next( item for item in catalog["packages"] if item["id"] == "gelios.provider.v7" ) - self.assertEqual(result["providerPackage"], "gelios.provider.v7") + self.assertEqual(result["providerPackage"], "gelios.provider.v8") self.assertEqual(result["providerCredential"], "ndcProviderRotatingAccessApi") - self.assertEqual(result["dataProductId"], "fleet.positions.current.v5") + self.assertEqual(result["dataProductId"], "fleet.units.profile.current.v1") self.assertEqual(provider["capabilities"][0]["dataProductIds"], [ + "fleet.units.profile.current.v1" + ]) + self.assertEqual(previous_provider["capabilities"][0]["dataProductIds"], [ "fleet.positions.current.v5" ])