feat(n8n): add rotating provider access credential

This commit is contained in:
Codex
2026-07-19 10:11:56 +03:00
parent 3c5d8f6cef
commit 0611a88971
10 changed files with 408 additions and 64 deletions
+38 -5
View File
@@ -208,6 +208,11 @@ ENGINE_N8N_RELEASE_CATALOG_JSON_SHA256 = {
"credentials": "a26824ffc0e15db857c792153de3417febc0dbba4880380d6c8cd544b3c80f4d",
"meta": "b8aa60c0d919573626fa0694a1e4ae9ede015325a5d312e3e05d5ac293084aa7",
},
"0.1.4-59dc9f7882721d6a": {
"nodes": "b0a5215699a6e691b8cfc505ab457d5632ef6d83adb3537520d0b60760ba16b2",
"credentials": "af8ada839070a4c24b9b10981a751c2b2db651dced1392333d7a4ef13de4564e",
"meta": "8fadbc594c5d14b1d53ab69b677f00823949410b9a37f4e43ecc321e89d68983",
},
}
ENGINE_N8N_INACTIVE_NODES_CATALOG_JSON_SHA256 = "b70d9d8130d498c55de46a5d0758c844f1242b70803457b2291ab3a9de8056f2"
ENGINE_N8N_INACTIVE_CREDENTIALS_CATALOG_JSON_SHA256 = "680e9f52aac791efbd38e3bd99bd51ef5cded9756d867897c6c2755850e87b50"
@@ -236,6 +241,7 @@ N8N_PRIVATE_EXTENSION_CREDENTIALS = (
"dist/credentials/NdcDataProductWriterApi.credentials.js",
"dist/credentials/NdcDataProductReaderApi.credentials.js",
"dist/credentials/NdcFoundryBindingApi.credentials.js",
"dist/credentials/NdcProviderRotatingAccessApi.credentials.js",
)
ENGINE_N8N_NODE_TYPES = (
"n8n-nodes-ndc.ndcDataProductPublish",
@@ -246,7 +252,13 @@ ENGINE_N8N_CREDENTIAL_TYPES = (
"ndcDataProductWriterApi",
"ndcDataProductReaderApi",
"ndcFoundryBindingApi",
"ndcProviderRotatingAccessApi",
)
ENGINE_N8N_CREDENTIAL_TYPES_BY_RELEASE = {
"0.1.2-05e4b38b14b4a019": ENGINE_N8N_CREDENTIAL_TYPES[:3],
"0.1.3-3354149b5245e39a": ENGINE_N8N_CREDENTIAL_TYPES[:3],
"0.1.4-59dc9f7882721d6a": ENGINE_N8N_CREDENTIAL_TYPES,
}
ENGINE_CREDENTIAL_SINK_ARTIFACT_ENTRIES = (
"nodedc-source/server/credentialPolicies/ndcPrivateNode.js",
"nodedc-source/server/credentialSink",
@@ -2191,6 +2203,13 @@ def is_engine_n8n_transition(component, entries):
return component == "engine" and entries is not None and ENGINE_N8N_TRANSITION_DESCRIPTOR_REL in entries
def engine_n8n_credential_types_for_release(release_id):
expected = ENGINE_N8N_CREDENTIAL_TYPES_BY_RELEASE.get(release_id)
if expected is None:
die("Engine n8n credential catalog release is not registered")
return expected
def read_engine_n8n_transition_descriptor(path, label="Engine n8n transition descriptor"):
descriptor = read_strict_json(path, label)
require_exact_json_keys(
@@ -2259,7 +2278,9 @@ def read_engine_n8n_transition_descriptor(path, label="Engine n8n transition des
die("Engine n8n activation rollback baseline mismatch")
if descriptor.get("expectedNodeTypes") != list(ENGINE_N8N_NODE_TYPES):
die("Engine n8n activation node type set mismatch")
if descriptor.get("expectedCredentialTypes") != list(ENGINE_N8N_CREDENTIAL_TYPES):
if descriptor.get("expectedCredentialTypes") != list(
engine_n8n_credential_types_for_release(release_id)
):
die("Engine n8n activation credential type set mismatch")
else:
if descriptor.get("expectedCurrent") != release_id:
@@ -2332,7 +2353,11 @@ def validate_engine_n8n_catalog_payload(payload_dir, descriptor):
if isinstance(item, dict) and str(item.get("name") or "") in ENGINE_N8N_CREDENTIAL_TYPES
]
expected_node_types = list(ENGINE_N8N_NODE_TYPES) if action == "activate" else []
expected_credential_types = list(ENGINE_N8N_CREDENTIAL_TYPES) if action == "activate" else []
expected_credential_types = (
list(engine_n8n_credential_types_for_release(descriptor["releaseId"]))
if action == "activate"
else []
)
if [item.get("name") for item in private_nodes] != expected_node_types:
die("Engine n8n pinned node catalog exact set mismatch")
if [item.get("name") for item in private_credentials] != expected_credential_types:
@@ -2340,7 +2365,7 @@ def validate_engine_n8n_catalog_payload(payload_dir, descriptor):
if any("usableAsTool" in item for item in private_nodes):
die("Engine n8n pinned node catalog would generate tool variants")
if action == "activate":
if len(nodes) != 437 or len(credentials) != 388:
if len(nodes) != 437 or len(credentials) != 385 + len(expected_credential_types):
die("Engine n8n activation catalog count mismatch")
if any(not str(item.get("displayName") or "").startswith("NDC ") for item in private_nodes):
die("Engine n8n private node visible name mismatch")
@@ -5694,7 +5719,11 @@ def preflight_engine_n8n_transition(descriptor, enforce_expected_current):
current_types = current_catalog["node_types"]
current_credentials = current_catalog["credential_types"]
expected_current_types = list(ENGINE_N8N_NODE_TYPES) if current_state != "verified_inactive" else []
expected_current_credentials = list(ENGINE_N8N_CREDENTIAL_TYPES) if current_state != "verified_inactive" else []
expected_current_credentials = (
list(engine_n8n_credential_types_for_release(current_state))
if current_state != "verified_inactive"
else []
)
if current_types != expected_current_types:
die("Engine n8n current live private-node set does not match its transition state")
if current_credentials != expected_current_credentials:
@@ -5929,7 +5958,11 @@ def accept_engine_n8n_runtime(descriptor):
die("Engine n8n private extension loader log acceptance failed")
expected_types = list(ENGINE_N8N_NODE_TYPES) if descriptor["action"] == "activate" else []
expected_credentials = list(ENGINE_N8N_CREDENTIAL_TYPES) if descriptor["action"] == "activate" else []
expected_credentials = (
list(engine_n8n_credential_types_for_release(descriptor["releaseId"]))
if descriptor["action"] == "activate"
else []
)
loader_catalog = engine_n8n_private_loader_catalog(container_id)
if loader_catalog["node_types"] != expected_types:
die("Engine n8n live package-loader node catalog acceptance failed")