diff --git a/infra/deploy-runner/build-engine-provider-authority-diagnostics-artifact.mjs b/infra/deploy-runner/build-engine-provider-authority-diagnostics-artifact.mjs index eac127f..e66f229 100644 --- a/infra/deploy-runner/build-engine-provider-authority-diagnostics-artifact.mjs +++ b/infra/deploy-runner/build-engine-provider-authority-diagnostics-artifact.mjs @@ -24,7 +24,7 @@ if (extra.length || !/^engine-provider-authority-diagnostics-\d{8}-\d{3}$/.test( const targetSha256 = Object.freeze({ "nodedc-source/server/dataProductPublishGrant/providerCatalog.js": - "1a14299167ebe17efd677fa3c59b84c80e12d6846bac6f40f9bcae0729ab22c6", + "86e23683054860bb21b47e4e60a13db741088ee214a60704fd4fd24a49ea5852", }); const entries = Object.freeze(Object.keys(targetSha256)); const artifact = join(artifactRoot, `nodedc-${patchId}.tgz`); @@ -59,9 +59,9 @@ try { entries, targetSha256, services: ["nodedc-backend"], - transition: "exact-safe-provider-authority-reason-codes", - providerPackage: "gelios.provider.v4", - dataProductId: "fleet.positions.current.v3", + transition: "publish-path-scoped-provider-request-authority", + providerPackages: ["gelios.provider.v7", "gelios.provider.v8"], + dataProductIds: ["fleet.positions.current.v5", "fleet.units.profile.current.v1"], credentialValues: "preserved", untouched: ["L2 graph", "n8n", "L1", "Engine UI", "databases"], }, null, 2)); @@ -90,6 +90,8 @@ async function assertExactSources() { "publish_grant_provider_transport_policy_not_exact", "publish_grant_provider_request_path_unreachable", "publish_grant_provider_credential_identity_mismatch", + "exactRequestMatches", + "reachableRequestNodes", ]) { if (!source.includes(marker)) { throw new Error(`engine_provider_authority_diagnostics_marker_missing:${marker}`); diff --git a/infra/deploy-runner/nodedc-deploy b/infra/deploy-runner/nodedc-deploy index 9739184..5bbbf46 100755 --- a/infra/deploy-runner/nodedc-deploy +++ b/infra/deploy-runner/nodedc-deploy @@ -420,10 +420,10 @@ ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_ARTIFACT_ENTRIES = ( "nodedc-source/server/dataProductPublishGrant/providerCatalog.js", ) ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_PREDECESSOR_SHA256 = { - "nodedc-source/server/dataProductPublishGrant/providerCatalog.js": "f6cf5f4de9e57f87fb904e2136a9f34d494e1ffc289aec3ed9ed788b5583a062", + "nodedc-source/server/dataProductPublishGrant/providerCatalog.js": "e4c216b0affd89ddbd48abcec86febd48eb12a4c56507851daeb47f2ccd60c9a", } ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_TARGET_SHA256 = { - "nodedc-source/server/dataProductPublishGrant/providerCatalog.js": "1a14299167ebe17efd677fa3c59b84c80e12d6846bac6f40f9bcae0729ab22c6", + "nodedc-source/server/dataProductPublishGrant/providerCatalog.js": "86e23683054860bb21b47e4e60a13db741088ee214a60704fd4fd24a49ea5852", } ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_ARTIFACT_ENTRIES = ( "nodedc-source/server/assets/provider-packages/v1/catalog.json", @@ -4145,22 +4145,24 @@ const catalog=await module.loadProviderSecurityCatalog(); 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 read=(id,url,parameters={})=>({id,data:{n8n:{id,name:id,type:'n8n-nodes-base.httpRequest',parameters:{method:'GET',url,...parameters},credentials:{[slot]:credential}},nodedcAgentCredentialPolicies:{[slot]:structuredClone(policy)}}}); -const query={sendQuery:true,specifyQuery:'keypair',queryParameters:{parameters:[{name:'incltrip',value:'true'}]}}; -const graph={nodes:[read('monitoring','https://api.geliospro.com/api/v1/users/me/monitoring-config'),read('units','https://api.geliospro.com/api/v1/units',query),{id:'publish',data:{n8n:{id:'publish',name:'publish',type:'n8n-nodes-ndc.ndcDataProductPublish',parameters:{dataProductId:'fleet.positions.current.v3'},credentials:{}}}}],edges:[{source:'monitoring',target:'publish'},{source:'units',target:'publish'}]}; -const valid=module.resolveProviderConnectionFromGraph({graph,targetNodeId:'publish',catalog}); -const policyDrift=structuredClone(graph);policyDrift.nodes[0].data.nodedcAgentCredentialPolicies[slot].allowedHosts=['*.geliospro.com']; -const policyResult=module.resolveProviderConnectionFromGraph({graph:policyDrift,targetNodeId:'publish',catalog}); -const bindingDrift=structuredClone(graph);delete bindingDrift.nodes[0].data.n8n.credentials[slot].id; -const bindingResult=module.resolveProviderConnectionFromGraph({graph:bindingDrift,targetNodeId:'publish',catalog}); -if(!valid.ok||policyResult.blockers?.join(',')!=='publish_grant_provider_transport_policy_not_exact'||bindingResult.blockers?.join(',')!=='publish_grant_provider_credential_binding_invalid')process.exit(2); -process.stdout.write('engine-provider-authority-diagnostics:exact-reason-codes:v1'); +const query={sendQuery:true,specifyQuery:'keypair',queryParameters:{parameters:[{name:'incltrip',value:'true'},{name:'inclcntrs',value:'true'},{name:'inclsnsrs',value:'true'},{name:'incllsv',value:'true'}]}}; +const read=(id)=>({id,data:{n8n:{id,name:id,type:'n8n-nodes-base.httpRequest',parameters:{method:'GET',url:'https://api.geliospro.com/api/v1/units',...structuredClone(query)},credentials:{[slot]:credential}},nodedcAgentCredentialPolicies:{[slot]:structuredClone(policy)}}}); +const publish=(id,dataProductId)=>({id,data:{n8n:{id,name:id,type:'n8n-nodes-ndc.ndcDataProductPublish',parameters:{dataProductId},credentials:{}}}}); +const graph={nodes:[read('hot-read'),read('profile-read'),publish('hot-publish','fleet.positions.current.v5'),publish('profile-publish','fleet.units.profile.current.v1')],edges:[{source:'hot-read',target:'hot-publish'},{source:'profile-read',target:'profile-publish'}]}; +const hot=module.resolveProviderConnectionFromGraph({graph,targetNodeId:'hot-publish',catalog}); +const profile=module.resolveProviderConnectionFromGraph({graph,targetNodeId:'profile-publish',catalog}); +const crossed=structuredClone(graph);crossed.edges.push({source:'hot-read',target:'profile-publish'}); +const ambiguous=module.resolveProviderConnectionFromGraph({graph:crossed,targetNodeId:'profile-publish',catalog}); +const drift=structuredClone(graph);drift.nodes[1].data.n8n.parameters.queryParameters.parameters.pop(); +const rejected=module.resolveProviderConnectionFromGraph({graph:drift,targetNodeId:'profile-publish',catalog}); +if(!hot.ok||hot.descriptor?.packageId!=='gelios.provider.v7'||hot.descriptor?.providerRequestNodeId!=='hot-read'||!profile.ok||profile.descriptor?.packageId!=='gelios.provider.v8'||profile.descriptor?.providerRequestNodeId!=='profile-read'||ambiguous.blockers?.join(',')!=='publish_grant_provider_request_not_exact'||rejected.blockers?.join(',')!=='publish_grant_provider_request_not_exact')process.exit(2); +process.stdout.write('engine-provider-authority:publish-path-scoped:v2'); """.strip(), ), - "Engine provider authority diagnostics", + "Engine provider publish-path authority", container_id=engine_backend_container_id(), ) - expected = "engine-provider-authority-diagnostics:exact-reason-codes:v1" + expected = "engine-provider-authority:publish-path-scoped:v2" if live != expected: die("Engine provider authority diagnostics live acceptance mismatch") return { @@ -4645,6 +4647,8 @@ def validate_engine_provider_authority_diagnostics_slice(payload_dir, entries): "publish_grant_provider_transport_policy_not_exact", "publish_grant_provider_request_path_unreachable", "publish_grant_provider_credential_identity_mismatch", + "exactRequestMatches", + "reachableRequestNodes", ) if any(marker not in source for marker in required_markers): die("Engine provider authority diagnostics reason-code contract mismatch") @@ -5492,7 +5496,7 @@ def preflight_engine_provider_authority_diagnostics_predecessor(): if backend["mode"] != "verified-derived-retry": die("Engine provider authority diagnostics requires the active immutable backend") return { - "mode": "exact-provider-authority-reason-codes", + "mode": "publish-path-scoped-provider-request-authority", "predecessor_sha256": actual, "target_sha256": dict(ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_TARGET_SHA256), "backend_mode": backend["mode"], @@ -8089,9 +8093,9 @@ def plan_artifact(artifact): print("engine_ui=untouched") print("engine_databases=untouched") if provider_authority_diagnostics_preflight: - print("engine_provider_authority_transition=exact-safe-reason-codes") - print("engine_provider_package=gelios.provider.v4") - print("engine_data_product=fleet.positions.current.v3") + print("engine_provider_authority_transition=publish-path-scoped-provider-request-authority") + print("engine_provider_packages=gelios.provider.v7,gelios.provider.v8") + print("engine_data_products=fleet.positions.current.v5,fleet.units.profile.current.v1") print("provider_credential_values=preserved") for changed_path in ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_ARTIFACT_ENTRIES: print(f"engine_provider_authority_changed_path={changed_path}") diff --git a/infra/deploy-runner/test_engine_provider_authority_diagnostics.py b/infra/deploy-runner/test_engine_provider_authority_diagnostics.py index 013f7cc..ca3cf26 100644 --- a/infra/deploy-runner/test_engine_provider_authority_diagnostics.py +++ b/infra/deploy-runner/test_engine_provider_authority_diagnostics.py @@ -101,7 +101,7 @@ class EngineProviderAuthorityDiagnosticsTest(unittest.TestCase): ), ): result = RUNNER.preflight_engine_provider_authority_diagnostics_predecessor() - self.assertEqual(result["mode"], "exact-provider-authority-reason-codes") + self.assertEqual(result["mode"], "publish-path-scoped-provider-request-authority") with ( mock.patch.object(RUNNER, "component_root", return_value=root), @@ -134,9 +134,9 @@ class EngineProviderAuthorityDiagnosticsTest(unittest.TestCase): RUNNER.run_healthchecks("engine", entries, ("nodedc-backend",)) acceptance.assert_called_once_with() - def test_live_acceptance_uses_exact_reason_code_contract(self): + def test_live_acceptance_uses_publish_path_scoped_authority_contract(self): self.require_historical_target() - expected_live = "engine-provider-authority-diagnostics:exact-reason-codes:v1" + expected_live = "engine-provider-authority:publish-path-scoped:v2" with ( mock.patch.object(RUNNER, "component_root", return_value=ENGINE_ROOT), mock.patch.object(RUNNER, "engine_backend_container_id", return_value="backend"), @@ -148,7 +148,7 @@ class EngineProviderAuthorityDiagnosticsTest(unittest.TestCase): ): result = RUNNER.accept_engine_provider_authority_diagnostics_runtime() self.assertEqual(result["live"], expected_live) - self.assertEqual(backend_probe.call_args.args[1], "Engine provider authority diagnostics") + self.assertEqual(backend_probe.call_args.args[1], "Engine provider publish-path authority") if __name__ == "__main__":