feat(deploy): promote private node reconciliation
This commit is contained in:
parent
23919e384e
commit
93eb13219a
|
|
@ -23,8 +23,8 @@ if (extra.length || !/^engine-provider-authority-diagnostics-\d{8}-\d{3}$/.test(
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetSha256 = Object.freeze({
|
const targetSha256 = Object.freeze({
|
||||||
"nodedc-source/server/dataProductPublishGrant/providerCatalog.js":
|
"nodedc-source/server/nodeIntelligence/upstreamProjection.js":
|
||||||
"86e23683054860bb21b47e4e60a13db741088ee214a60704fd4fd24a49ea5852",
|
"2dfa6b4f37d9bfa8b92a8109d4060d02dd2634ceb8f7924504b83ccf3fdd1523",
|
||||||
});
|
});
|
||||||
const entries = Object.freeze(Object.keys(targetSha256));
|
const entries = Object.freeze(Object.keys(targetSha256));
|
||||||
const artifact = join(artifactRoot, `nodedc-${patchId}.tgz`);
|
const artifact = join(artifactRoot, `nodedc-${patchId}.tgz`);
|
||||||
|
|
@ -59,9 +59,9 @@ try {
|
||||||
entries,
|
entries,
|
||||||
targetSha256,
|
targetSha256,
|
||||||
services: ["nodedc-backend"],
|
services: ["nodedc-backend"],
|
||||||
transition: "publish-path-scoped-provider-request-authority",
|
transition: "private-node-name-reconciliation",
|
||||||
providerPackages: ["gelios.provider.v7", "gelios.provider.v8"],
|
validationBoundary: "engine-pinned-private-node-catalog",
|
||||||
dataProductIds: ["fleet.positions.current.v5", "fleet.units.profile.current.v1"],
|
privateNodeIdentity: "node-id-or-exact-display-name",
|
||||||
credentialValues: "preserved",
|
credentialValues: "preserved",
|
||||||
untouched: ["L2 graph", "n8n", "L1", "Engine UI", "databases"],
|
untouched: ["L2 graph", "n8n", "L1", "Engine UI", "databases"],
|
||||||
}, null, 2));
|
}, null, 2));
|
||||||
|
|
@ -85,13 +85,10 @@ async function assertExactSources() {
|
||||||
}
|
}
|
||||||
const source = await readFile(sourcePath, "utf8");
|
const source = await readFile(sourcePath, "utf8");
|
||||||
for (const marker of [
|
for (const marker of [
|
||||||
"publish_grant_provider_request_not_exact",
|
"isUnknownPrivateNodeIssue",
|
||||||
"publish_grant_provider_credential_binding_invalid",
|
"issue?.nodeName || issue?.node",
|
||||||
"publish_grant_provider_transport_policy_not_exact",
|
"privateResults",
|
||||||
"publish_grant_provider_request_path_unreachable",
|
"NDC_PRIVATE_NODE_VALIDATED_BY_ENGINE_CATALOG",
|
||||||
"publish_grant_provider_credential_identity_mismatch",
|
|
||||||
"exactRequestMatches",
|
|
||||||
"reachableRequestNodes",
|
|
||||||
]) {
|
]) {
|
||||||
if (!source.includes(marker)) {
|
if (!source.includes(marker)) {
|
||||||
throw new Error(`engine_provider_authority_diagnostics_marker_missing:${marker}`);
|
throw new Error(`engine_provider_authority_diagnostics_marker_missing:${marker}`);
|
||||||
|
|
|
||||||
|
|
@ -417,13 +417,13 @@ ENGINE_PROVIDER_ROTATING_SLOT_TARGET_SHA256 = {
|
||||||
"nodedc-source/server/dataProductPublishGrant/providerCatalog.js": "f6cf5f4de9e57f87fb904e2136a9f34d494e1ffc289aec3ed9ed788b5583a062",
|
"nodedc-source/server/dataProductPublishGrant/providerCatalog.js": "f6cf5f4de9e57f87fb904e2136a9f34d494e1ffc289aec3ed9ed788b5583a062",
|
||||||
}
|
}
|
||||||
ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_ARTIFACT_ENTRIES = (
|
ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_ARTIFACT_ENTRIES = (
|
||||||
"nodedc-source/server/dataProductPublishGrant/providerCatalog.js",
|
"nodedc-source/server/nodeIntelligence/upstreamProjection.js",
|
||||||
)
|
)
|
||||||
ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_PREDECESSOR_SHA256 = {
|
ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_PREDECESSOR_SHA256 = {
|
||||||
"nodedc-source/server/dataProductPublishGrant/providerCatalog.js": "e4c216b0affd89ddbd48abcec86febd48eb12a4c56507851daeb47f2ccd60c9a",
|
"nodedc-source/server/nodeIntelligence/upstreamProjection.js": "761a874b102a938bc6018159ddacdaac71ad6ae08e9f0f8d7f3b58a0165a5131",
|
||||||
}
|
}
|
||||||
ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_TARGET_SHA256 = {
|
ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_TARGET_SHA256 = {
|
||||||
"nodedc-source/server/dataProductPublishGrant/providerCatalog.js": "86e23683054860bb21b47e4e60a13db741088ee214a60704fd4fd24a49ea5852",
|
"nodedc-source/server/nodeIntelligence/upstreamProjection.js": "2dfa6b4f37d9bfa8b92a8109d4060d02dd2634ceb8f7924504b83ccf3fdd1523",
|
||||||
}
|
}
|
||||||
ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_ARTIFACT_ENTRIES = (
|
ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_ARTIFACT_ENTRIES = (
|
||||||
"nodedc-source/server/assets/provider-packages/v1/catalog.json",
|
"nodedc-source/server/assets/provider-packages/v1/catalog.json",
|
||||||
|
|
@ -4140,29 +4140,18 @@ def accept_engine_provider_authority_diagnostics_runtime():
|
||||||
"--input-type=module",
|
"--input-type=module",
|
||||||
"-e",
|
"-e",
|
||||||
"""
|
"""
|
||||||
const module=await import('file:///app/server/dataProductPublishGrant/providerCatalog.js');
|
const module=await import('file:///app/server/nodeIntelligence/upstreamProjection.js');
|
||||||
const catalog=await module.loadProviderSecurityCatalog();
|
const privateValidation={nodeId:'private-1',nodeName:'NDC Data Product Publish',valid:true,errors:[],warnings:[]};
|
||||||
const slot='ndcProviderRotatingAccessApi';
|
const privateOnly=module.reconcilePrivateNodeValidation({valid:false,errorCount:1,warningCount:0,summary:{errorCount:1,warningCount:0},errors:[{node:'NDC Data Product Publish',message:'Unknown node type: \"ndcDataProductPublish\".'}],warnings:[]},['private-1'],[privateValidation]);
|
||||||
const credential={id:'native-provider',name:'provider',nodeDcCredentialId:'provider-ref'};
|
const mixed=module.reconcilePrivateNodeValidation({valid:false,errorCount:2,warningCount:0,summary:{errorCount:2,warningCount:0},errors:[{node:'NDC Data Product Publish',message:'Unknown node type: \"ndcDataProductPublish\".'},{nodeId:'built-in-1',nodeName:'HTTP',message:'URL is required'}],warnings:[]},['private-1'],[privateValidation]);
|
||||||
const policy={kind:'httpRequest',allowedHosts:['api.geliospro.com'],requireHttps:true,disableRedirects:true};
|
if(!privateOnly.valid||privateOnly.errors?.length!==0||privateOnly.summary?.errorCount!==0||privateOnly.summary?.warningCount!==1||mixed.valid||mixed.errors?.length!==1||mixed.errors?.[0]?.nodeId!=='built-in-1')process.exit(2);
|
||||||
const query={sendQuery:true,specifyQuery:'keypair',queryParameters:{parameters:[{name:'incltrip',value:'true'},{name:'inclcntrs',value:'true'},{name:'inclsnsrs',value:'true'},{name:'incllsv',value:'true'}]}};
|
process.stdout.write('engine-private-node-reconciliation:exact-id-or-name:v1');
|
||||||
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(),
|
""".strip(),
|
||||||
),
|
),
|
||||||
"Engine provider publish-path authority",
|
"Engine private node validation reconciliation",
|
||||||
container_id=engine_backend_container_id(),
|
container_id=engine_backend_container_id(),
|
||||||
)
|
)
|
||||||
expected = "engine-provider-authority:publish-path-scoped:v2"
|
expected = "engine-private-node-reconciliation:exact-id-or-name:v1"
|
||||||
if live != expected:
|
if live != expected:
|
||||||
die("Engine provider authority diagnostics live acceptance mismatch")
|
die("Engine provider authority diagnostics live acceptance mismatch")
|
||||||
return {
|
return {
|
||||||
|
|
@ -4639,19 +4628,16 @@ def validate_engine_provider_authority_diagnostics_slice(payload_dir, entries):
|
||||||
):
|
):
|
||||||
die(f"Engine provider authority diagnostics target sha256 mismatch: {rel}")
|
die(f"Engine provider authority diagnostics target sha256 mismatch: {rel}")
|
||||||
source = (
|
source = (
|
||||||
payload_dir / "nodedc-source/server/dataProductPublishGrant/providerCatalog.js"
|
payload_dir / "nodedc-source/server/nodeIntelligence/upstreamProjection.js"
|
||||||
).read_text(encoding="utf-8")
|
).read_text(encoding="utf-8")
|
||||||
required_markers = (
|
required_markers = (
|
||||||
"publish_grant_provider_request_not_exact",
|
"isUnknownPrivateNodeIssue",
|
||||||
"publish_grant_provider_credential_binding_invalid",
|
"issue?.nodeName || issue?.node",
|
||||||
"publish_grant_provider_transport_policy_not_exact",
|
"privateResults",
|
||||||
"publish_grant_provider_request_path_unreachable",
|
"NDC_PRIVATE_NODE_VALIDATED_BY_ENGINE_CATALOG",
|
||||||
"publish_grant_provider_credential_identity_mismatch",
|
|
||||||
"exactRequestMatches",
|
|
||||||
"reachableRequestNodes",
|
|
||||||
)
|
)
|
||||||
if any(marker not in source for marker in required_markers):
|
if any(marker not in source for marker in required_markers):
|
||||||
die("Engine provider authority diagnostics reason-code contract mismatch")
|
die("Engine private node validation reconciliation contract mismatch")
|
||||||
|
|
||||||
|
|
||||||
def validate_engine_depttrans_zone_authority_v1_slice(payload_dir, entries):
|
def validate_engine_depttrans_zone_authority_v1_slice(payload_dir, entries):
|
||||||
|
|
@ -5496,7 +5482,7 @@ def preflight_engine_provider_authority_diagnostics_predecessor():
|
||||||
if backend["mode"] != "verified-derived-retry":
|
if backend["mode"] != "verified-derived-retry":
|
||||||
die("Engine provider authority diagnostics requires the active immutable backend")
|
die("Engine provider authority diagnostics requires the active immutable backend")
|
||||||
return {
|
return {
|
||||||
"mode": "publish-path-scoped-provider-request-authority",
|
"mode": "private-node-name-reconciliation",
|
||||||
"predecessor_sha256": actual,
|
"predecessor_sha256": actual,
|
||||||
"target_sha256": dict(ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_TARGET_SHA256),
|
"target_sha256": dict(ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_TARGET_SHA256),
|
||||||
"backend_mode": backend["mode"],
|
"backend_mode": backend["mode"],
|
||||||
|
|
@ -8093,9 +8079,9 @@ def plan_artifact(artifact):
|
||||||
print("engine_ui=untouched")
|
print("engine_ui=untouched")
|
||||||
print("engine_databases=untouched")
|
print("engine_databases=untouched")
|
||||||
if provider_authority_diagnostics_preflight:
|
if provider_authority_diagnostics_preflight:
|
||||||
print("engine_provider_authority_transition=publish-path-scoped-provider-request-authority")
|
print("engine_validation_transition=private-node-name-reconciliation")
|
||||||
print("engine_provider_packages=gelios.provider.v7,gelios.provider.v8")
|
print("engine_validation_boundary=engine-pinned-private-node-catalog")
|
||||||
print("engine_data_products=fleet.positions.current.v5,fleet.units.profile.current.v1")
|
print("private_node_identity=node-id-or-exact-display-name")
|
||||||
print("provider_credential_values=preserved")
|
print("provider_credential_values=preserved")
|
||||||
for changed_path in ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_ARTIFACT_ENTRIES:
|
for changed_path in ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_ARTIFACT_ENTRIES:
|
||||||
print(f"engine_provider_authority_changed_path={changed_path}")
|
print(f"engine_provider_authority_changed_path={changed_path}")
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ class EngineProviderAuthorityDiagnosticsTest(unittest.TestCase):
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
result = RUNNER.preflight_engine_provider_authority_diagnostics_predecessor()
|
result = RUNNER.preflight_engine_provider_authority_diagnostics_predecessor()
|
||||||
self.assertEqual(result["mode"], "publish-path-scoped-provider-request-authority")
|
self.assertEqual(result["mode"], "private-node-name-reconciliation")
|
||||||
|
|
||||||
with (
|
with (
|
||||||
mock.patch.object(RUNNER, "component_root", return_value=root),
|
mock.patch.object(RUNNER, "component_root", return_value=root),
|
||||||
|
|
@ -134,9 +134,9 @@ class EngineProviderAuthorityDiagnosticsTest(unittest.TestCase):
|
||||||
RUNNER.run_healthchecks("engine", entries, ("nodedc-backend",))
|
RUNNER.run_healthchecks("engine", entries, ("nodedc-backend",))
|
||||||
acceptance.assert_called_once_with()
|
acceptance.assert_called_once_with()
|
||||||
|
|
||||||
def test_live_acceptance_uses_publish_path_scoped_authority_contract(self):
|
def test_live_acceptance_uses_private_node_reconciliation_contract(self):
|
||||||
self.require_historical_target()
|
self.require_historical_target()
|
||||||
expected_live = "engine-provider-authority:publish-path-scoped:v2"
|
expected_live = "engine-private-node-reconciliation:exact-id-or-name:v1"
|
||||||
with (
|
with (
|
||||||
mock.patch.object(RUNNER, "component_root", return_value=ENGINE_ROOT),
|
mock.patch.object(RUNNER, "component_root", return_value=ENGINE_ROOT),
|
||||||
mock.patch.object(RUNNER, "engine_backend_container_id", return_value="backend"),
|
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()
|
result = RUNNER.accept_engine_provider_authority_diagnostics_runtime()
|
||||||
self.assertEqual(result["live"], expected_live)
|
self.assertEqual(result["live"], expected_live)
|
||||||
self.assertEqual(backend_probe.call_args.args[1], "Engine provider publish-path authority")
|
self.assertEqual(backend_probe.call_args.args[1], "Engine private node validation reconciliation")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue