feat(platform): add managed data product history plane
This commit is contained in:
@@ -54,6 +54,7 @@ ENGINE_CREDENTIAL_BACKEND_ROOTFS_FILE = ENGINE_CREDENTIAL_BACKEND_RUNTIME_DIR /
|
||||
ENGINE_CREDENTIAL_BACKEND_METADATA_FILE = ENGINE_CREDENTIAL_BACKEND_RUNTIME_DIR / "runtime-metadata.json"
|
||||
ENGINE_CREDENTIAL_BACKEND_ACTIVATION_FILE = ENGINE_CREDENTIAL_BACKEND_RUNTIME_DIR / "activation.ready"
|
||||
ENGINE_DATA_PRODUCT_PUBLISH_GRANT_OVERRIDE_REL = "nodedc-source/services/backend/data-product-publish-grant/docker-compose.immutable-runtime.yml"
|
||||
ENGINE_DATA_PRODUCT_READ_GRANT_OVERRIDE_REL = "nodedc-source/services/backend/data-product-read-grant/docker-compose.immutable-runtime.yml"
|
||||
ENGINE_AGENT_FULL_GRANT_MIGRATION_STORE_REL = "nodedc-source/server/engineAgents/store.js"
|
||||
ENGINE_AGENT_FULL_GRANT_MIGRATION_PREDECESSOR_SHA256 = "52daa43499d6d9a97fe7ffa891edb9212b7791e733f91dd3ca686d42739b7e9a"
|
||||
ENGINE_AGENT_FULL_GRANT_MIGRATION_TARGET_SHA256 = "2e62654c2dc12905efcc83a9dff45a818dd7b47924a10600160835c4416540e9"
|
||||
@@ -115,13 +116,74 @@ ENGINE_NODE_INTELLIGENCE_ROLLBACK_ENTRIES = (
|
||||
ENGINE_NODE_INTELLIGENCE_GATEWAY_REL,
|
||||
ENGINE_NODE_INTELLIGENCE_SERVICE_ROOT_REL,
|
||||
)
|
||||
ENGINE_MCP_CONTROL_PLANE_DESCRIPTOR_REL = ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL
|
||||
ENGINE_MCP_CONTROL_PLANE_ARTIFACT_ENTRIES = (
|
||||
"nodedc-source/server/assets/engine-agent-npm/bin/nodedc-engine-codex-agent.mjs",
|
||||
"nodedc-source/server/assets/engine-agent-npm/package.json",
|
||||
"nodedc-source/server/assets/nodedc-engine-codex-agent-0.1.4.tgz",
|
||||
"nodedc-source/server/dataProductPublishGrant/providerCatalog.js",
|
||||
"nodedc-source/server/dataProductPublishGrant/signedDataPlaneClient.js",
|
||||
"nodedc-source/server/dataProductReadGrant/acceptance.js",
|
||||
"nodedc-source/server/dataProductReadGrant/service.js",
|
||||
"nodedc-source/server/dataProductReadGrant/store.js",
|
||||
"nodedc-source/server/engineAgents/store.js",
|
||||
"nodedc-source/server/nodeIntelligence/upstreamProjection.js",
|
||||
ENGINE_NODE_INTELLIGENCE_GATEWAY_REL,
|
||||
"nodedc-source/server/routes/n8n.js",
|
||||
"nodedc-source/server/routes/ndcAgentMcp.js",
|
||||
ENGINE_DATA_PRODUCT_READ_GRANT_OVERRIDE_REL,
|
||||
"nodedc-source/server/dataProductPublishGrant/service.js",
|
||||
"nodedc-source/server/dataProductPublishGrant/store.js",
|
||||
ENGINE_MCP_CONTROL_PLANE_DESCRIPTOR_REL,
|
||||
)
|
||||
ENGINE_MCP_CONTROL_PLANE_PREDECESSOR_SHA256 = {
|
||||
ENGINE_NODE_INTELLIGENCE_GATEWAY_REL: "604a75ad3b9b463cea5e578760c8a23592e2e00b8acfc0099730a347e27bf4e8",
|
||||
"nodedc-source/server/routes/ndcAgentMcp.js": "534e2c85e1faecc72a00da7ad32d0584ee09b6bd89eeec2221c3b23d80e1e962",
|
||||
"nodedc-source/server/routes/n8n.js": "de6e3c76740af86e2eaeedede140b5ee54eb526f03db270324d7a4fe513f6817",
|
||||
"nodedc-source/server/dataProductPublishGrant/providerCatalog.js": "a96d3695fdb3ed7012b9041182f37b09e32d1dfa0dbb391d8db2d7d25a39d64d",
|
||||
"nodedc-source/server/dataProductPublishGrant/signedDataPlaneClient.js": "1a26728ac69c5ef3fdce1ce1154a6ce330f72286b6e07825796a246070d15bbf",
|
||||
"nodedc-source/server/engineAgents/store.js": "2e62654c2dc12905efcc83a9dff45a818dd7b47924a10600160835c4416540e9",
|
||||
"nodedc-source/server/nodeIntelligence/upstreamProjection.js": "946353ed8582c5d96e08361603425cc45eaf231f730ae46a75028d3bef352ee9",
|
||||
"nodedc-source/server/assets/engine-agent-npm/bin/nodedc-engine-codex-agent.mjs": "7d3d7a9bc48614e6cd238199caa384073096b7ec8273703833681151b464ef17",
|
||||
"nodedc-source/server/assets/engine-agent-npm/package.json": "9f299b20db4855b1bb4a9bd2e1095dfd059cd90ec82e936884a380aa41249e28",
|
||||
"nodedc-source/server/dataProductPublishGrant/service.js": "85ded82d707e0ef5b1ad739b5a9be24c1432e2dd9e5051108739f09daf20f47c",
|
||||
"nodedc-source/server/dataProductPublishGrant/store.js": "ef2a5e6bfad9f0b1710db006a0d30d01cd9bcaf06524b0e9f975dd771efe3952",
|
||||
}
|
||||
ENGINE_MCP_CONTROL_PLANE_TARGET_SHA256 = {
|
||||
ENGINE_NODE_INTELLIGENCE_GATEWAY_REL: "96c726dab5cf1341f74e6e1095d518058ca320e0dd5738e25bdbe75db1f4fc15",
|
||||
"nodedc-source/server/routes/ndcAgentMcp.js": "534e2c85e1faecc72a00da7ad32d0584ee09b6bd89eeec2221c3b23d80e1e962",
|
||||
"nodedc-source/server/routes/n8n.js": "f293a7794405badbabd2bf7ef088f96fe1167d9e249f05cbfc8af280a0d3e8f8",
|
||||
"nodedc-source/server/dataProductPublishGrant/providerCatalog.js": "901b8fad80018ce177b34ced804b39cb140a47e831414057f484296b373c651d",
|
||||
"nodedc-source/server/dataProductPublishGrant/signedDataPlaneClient.js": "c2a13d5eb49937fe70ec6451d0465cac54c19c7fae44448db099079473ec02fc",
|
||||
"nodedc-source/server/dataProductReadGrant/acceptance.js": "202dbe575b2f2e1c584aa7e6e99e38fa84f12496feb454e3dbd3f98e2d0396dd",
|
||||
"nodedc-source/server/dataProductReadGrant/service.js": "65b8cdd6b603333bac1feb303e9791feb1e9da39dc9b5bfc3df3961273b0052e",
|
||||
"nodedc-source/server/dataProductReadGrant/store.js": "c3fcdc59a794f57d92e3d2ac713ee28341347cebd25364c4060beaa3dc2151de",
|
||||
"nodedc-source/server/engineAgents/store.js": "debd351fe0b8c72b33b8c79909b8f339cbaf061b970576f3ae72df52ebaa211f",
|
||||
"nodedc-source/server/nodeIntelligence/upstreamProjection.js": "761a874b102a938bc6018159ddacdaac71ad6ae08e9f0f8d7f3b58a0165a5131",
|
||||
"nodedc-source/server/assets/engine-agent-npm/bin/nodedc-engine-codex-agent.mjs": "521098de69fe288a56bd4158c849c1055ba24be08e19f7a4111618d0e8138445",
|
||||
"nodedc-source/server/assets/engine-agent-npm/package.json": "cbe113e9b10bb84b9ccbffa3e261908e58a8430c11abe2cf4fd5304741b04597",
|
||||
"nodedc-source/server/assets/nodedc-engine-codex-agent-0.1.4.tgz": "e74c0136d346f904b42589d75cb11a952b4d2f21153f1b057fba28e9acf4f95f",
|
||||
ENGINE_DATA_PRODUCT_READ_GRANT_OVERRIDE_REL: "952df0cb2ac477e644f5f3a9d64b4872ce1da33356eeab0bec17dcb2931cf653",
|
||||
"nodedc-source/server/dataProductPublishGrant/service.js": "ded3832c9677345a988448ae8e69cef254fd9bf39d2de20cffa295c1feedcd7c",
|
||||
"nodedc-source/server/dataProductPublishGrant/store.js": "a92303b2732e21f68c1ac732fa26e4983cbadf3515741cee983b916a283d754c",
|
||||
}
|
||||
ENGINE_MCP_CONTROL_PLANE_NEW_PATHS = (
|
||||
"nodedc-source/server/assets/nodedc-engine-codex-agent-0.1.4.tgz",
|
||||
"nodedc-source/server/dataProductReadGrant/acceptance.js",
|
||||
"nodedc-source/server/dataProductReadGrant/service.js",
|
||||
"nodedc-source/server/dataProductReadGrant/store.js",
|
||||
ENGINE_DATA_PRODUCT_READ_GRANT_OVERRIDE_REL,
|
||||
)
|
||||
ENGINE_CONTROL_PLANE_STATE_REL = "nodedc-control-plane"
|
||||
ENGINE_PUBLISH_GRANT_STATE_REL = f"{ENGINE_CONTROL_PLANE_STATE_REL}/publish-grants"
|
||||
ENGINE_READ_GRANT_STATE_REL = f"{ENGINE_CONTROL_PLANE_STATE_REL}/read-grants"
|
||||
ENGINE_CONTROL_PLANE_STATE_PATH = Path("/volume2/nodedc-demo/nodedc-control-plane")
|
||||
ENGINE_PUBLISH_GRANT_STATE_PATH = ENGINE_CONTROL_PLANE_STATE_PATH / "publish-grants"
|
||||
ENGINE_READ_GRANT_STATE_PATH = ENGINE_CONTROL_PLANE_STATE_PATH / "read-grants"
|
||||
ENGINE_EDP_PRIVATE_KEY_CONTAINER_PATH = "/run/nodedc-secrets/engine-edp-managed-provisioner-private.pem"
|
||||
ENGINE_CONTROL_PLANE_CONTAINER_PATH = "/var/lib/nodedc-control-plane"
|
||||
ENGINE_PUBLISH_GRANT_CONTAINER_PATH = "/var/lib/nodedc-control-plane/publish-grants"
|
||||
ENGINE_READ_GRANT_CONTAINER_PATH = "/var/lib/nodedc-control-plane/read-grants"
|
||||
EXTERNAL_DATA_PLANE_MANAGED_TRUST_CONTAINER_PATH = "/run/nodedc-trust/engine-managed-provisioner"
|
||||
EXTERNAL_DATA_PLANE_INTERNAL_URL = "http://external-data-plane:18106"
|
||||
PLATFORM_EXTERNAL_DATA_PLANE_COMPOSE_REL = "platform/docker-compose.external-data-plane.yml"
|
||||
@@ -135,9 +197,18 @@ ENGINE_N8N_RUNTIME_PACKAGE_PATH = "/home/node/.n8n/nodes/node_modules/n8n-nodes-
|
||||
ENGINE_N8N_NODE_MODULES_PATH = "/usr/local/lib/node_modules/n8n/node_modules"
|
||||
ENGINE_N8N_ICON_SHA256 = "1c928fc996d8b82121e8f8e327d74b1dd21556c106de99c5e8d317d926c0ba17"
|
||||
ENGINE_N8N_DARK_ICON_SHA256 = "ef3b8551da4ce04527736405afa9a220a2c76d047bd18f6f7124b9adb4216b0c"
|
||||
ENGINE_N8N_ACTIVATION_NODES_CATALOG_JSON_SHA256 = "230f712230f7ee8debaa4b44a4358cc19c205bc43305d8ed89d5e3daac466506"
|
||||
ENGINE_N8N_ACTIVATION_CREDENTIALS_CATALOG_JSON_SHA256 = "a26824ffc0e15db857c792153de3417febc0dbba4880380d6c8cd544b3c80f4d"
|
||||
ENGINE_N8N_ACTIVATION_META_JSON_SHA256 = "caf7635420c61333e65f68350f5567217aed96fb51354b38764bcac2c24e7966"
|
||||
ENGINE_N8N_RELEASE_CATALOG_JSON_SHA256 = {
|
||||
"0.1.2-05e4b38b14b4a019": {
|
||||
"nodes": "230f712230f7ee8debaa4b44a4358cc19c205bc43305d8ed89d5e3daac466506",
|
||||
"credentials": "a26824ffc0e15db857c792153de3417febc0dbba4880380d6c8cd544b3c80f4d",
|
||||
"meta": "caf7635420c61333e65f68350f5567217aed96fb51354b38764bcac2c24e7966",
|
||||
},
|
||||
"0.1.3-3354149b5245e39a": {
|
||||
"nodes": "b0a5215699a6e691b8cfc505ab457d5632ef6d83adb3537520d0b60760ba16b2",
|
||||
"credentials": "a26824ffc0e15db857c792153de3417febc0dbba4880380d6c8cd544b3c80f4d",
|
||||
"meta": "b8aa60c0d919573626fa0694a1e4ae9ede015325a5d312e3e05d5ac293084aa7",
|
||||
},
|
||||
}
|
||||
ENGINE_N8N_INACTIVE_NODES_CATALOG_JSON_SHA256 = "b70d9d8130d498c55de46a5d0758c844f1242b70803457b2291ab3a9de8056f2"
|
||||
ENGINE_N8N_INACTIVE_CREDENTIALS_CATALOG_JSON_SHA256 = "680e9f52aac791efbd38e3bd99bd51ef5cded9756d867897c6c2755850e87b50"
|
||||
ENGINE_N8N_INACTIVE_META_JSON_SHA256 = "0ac555d7ab31cb0ca042db4f474e83cfefbf20b5bbb2e1509fead27ed21e8acb"
|
||||
@@ -215,10 +286,10 @@ ENGINE_DATA_PRODUCT_PUBLISH_GRANT_PREDECESSOR_SHA256 = {
|
||||
"nodedc-source/server/credentialSink/store.js": "c78dc285a973b6acd8a2330f0310935ad08720b2905454492f292d235abf12c0",
|
||||
"nodedc-source/server/credentialSink/vendor/engine-credential-sink.mjs": "b4800eead9bf94793ff1280d06e34aad6b37ef055a9d7f8d83d7fb5f9bd66d8b",
|
||||
"nodedc-source/server/index.js": "b2b790b02839570d967a2ca68b00e2724485a99389ac9b3a589a1b22302a36b8",
|
||||
"nodedc-source/server/routes/engineAgentGateway.js": "e3450a4e1d5318dbac37627b67804d62804e27e2032c9e90478a1e739cea6d3d",
|
||||
"nodedc-source/server/routes/engineAgentGateway.js": "604a75ad3b9b463cea5e578760c8a23592e2e00b8acfc0099730a347e27bf4e8",
|
||||
"nodedc-source/server/routes/engineCredentialSink.js": "9cbb69dbc8cbe6181cd5b0170fe9c4d717b0a173866ca98cba3e3766c0bab94e",
|
||||
"nodedc-source/server/routes/n8n.js": "783d822e2457d82e890f43bc00c7e33822077dc2841f0511a89ecb210fd36d48",
|
||||
"nodedc-source/server/routes/ndcAgentMcp.js": "fbb3342b1a617b956d5b3a6a40d5111aa107c1176b4ff89c37b104995bada081",
|
||||
"nodedc-source/server/routes/ndcAgentMcp.js": "534e2c85e1faecc72a00da7ad32d0584ee09b6bd89eeec2221c3b23d80e1e962",
|
||||
ENGINE_CREDENTIAL_BACKEND_OVERRIDE_TEMPLATE_REL: "944fa64b08255eb8207b93fd327aebb98ecd9400d37d25fcfa8e3a040ee44afe",
|
||||
}
|
||||
ENGINE_CREDENTIAL_SINK_CONTRACT_SHA256 = "b4800eead9bf94793ff1280d06e34aad6b37ef055a9d7f8d83d7fb5f9bd66d8b"
|
||||
@@ -1048,7 +1119,7 @@ def ensure_engine_edp_managed_provisioner_keypair():
|
||||
return "created"
|
||||
|
||||
|
||||
def ensure_engine_publish_grant_private_state():
|
||||
def ensure_engine_data_product_grant_private_state(include_reader=False):
|
||||
# Compose must never create this bind source on our behalf: Docker's normal
|
||||
# 0755 directory would either expose control-plane metadata or be rejected
|
||||
# by the Engine's fail-closed PublishGrantStore.
|
||||
@@ -1060,10 +1131,16 @@ def ensure_engine_publish_grant_private_state():
|
||||
if stat.S_ISLNK(engine_root_stat.st_mode) or not stat.S_ISDIR(engine_root_stat.st_mode):
|
||||
die("Engine root is unsafe before private state preparation")
|
||||
|
||||
for state_path, label in (
|
||||
state_paths = [
|
||||
(engine_root / ENGINE_CONTROL_PLANE_STATE_REL, "Engine control-plane state"),
|
||||
(engine_root / ENGINE_PUBLISH_GRANT_STATE_REL, "Engine Publish grant state"),
|
||||
):
|
||||
]
|
||||
if include_reader:
|
||||
state_paths.append((
|
||||
engine_root / ENGINE_READ_GRANT_STATE_REL,
|
||||
"Engine Read grant state",
|
||||
))
|
||||
for state_path, label in state_paths:
|
||||
try:
|
||||
state_stat = state_path.lstat()
|
||||
except FileNotFoundError:
|
||||
@@ -1079,6 +1156,11 @@ def ensure_engine_publish_grant_private_state():
|
||||
die(f"{label} ownership or mode is unsafe: {state_path}")
|
||||
|
||||
|
||||
def ensure_engine_publish_grant_private_state():
|
||||
# Frozen compatibility seam for the established Publish transition.
|
||||
return ensure_engine_data_product_grant_private_state(include_reader=False)
|
||||
|
||||
|
||||
def fsync_directory(path):
|
||||
descriptor = os.open(str(path), os.O_RDONLY | os.O_DIRECTORY)
|
||||
try:
|
||||
@@ -1565,6 +1647,8 @@ def allowed_payload_path(component, rel):
|
||||
return True
|
||||
if rel == ENGINE_DATA_PRODUCT_PUBLISH_GRANT_OVERRIDE_REL:
|
||||
return True
|
||||
if rel == ENGINE_DATA_PRODUCT_READ_GRANT_OVERRIDE_REL:
|
||||
return True
|
||||
if rel == ENGINE_NODE_INTELLIGENCE_SERVICE_ROOT_REL or rel.startswith(
|
||||
ENGINE_NODE_INTELLIGENCE_SERVICE_ROOT_REL + "/"
|
||||
):
|
||||
@@ -2158,14 +2242,21 @@ def read_engine_n8n_transition_descriptor(path, label="Engine n8n transition des
|
||||
"composeOverride": ENGINE_N8N_COMPOSE_OVERRIDE_REL,
|
||||
"runtimePackagePath": ENGINE_N8N_RUNTIME_PACKAGE_PATH,
|
||||
"topologyServices": ["n8n"],
|
||||
"rollbackBaseline": "verified_inactive",
|
||||
}
|
||||
for key, expected in exact_common.items():
|
||||
if descriptor.get(key) != expected:
|
||||
die(f"Engine n8n transition {key} mismatch")
|
||||
if action == "activate":
|
||||
if descriptor.get("expectedCurrent") != "verified_inactive":
|
||||
die("Engine n8n activation must start from the verified inactive baseline")
|
||||
expected_current = descriptor.get("expectedCurrent")
|
||||
if (
|
||||
expected_current != "verified_inactive"
|
||||
and expected_current not in ENGINE_N8N_RELEASE_CATALOG_JSON_SHA256
|
||||
):
|
||||
die("Engine n8n activation expected-current release is not registered")
|
||||
if expected_current == release_id:
|
||||
die("Engine n8n activation target already equals expected current")
|
||||
if descriptor.get("rollbackBaseline") != expected_current:
|
||||
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):
|
||||
@@ -2173,6 +2264,8 @@ def read_engine_n8n_transition_descriptor(path, label="Engine n8n transition des
|
||||
else:
|
||||
if descriptor.get("expectedCurrent") != release_id:
|
||||
die("Engine n8n rollback expected-current release mismatch")
|
||||
if descriptor.get("rollbackBaseline") != "verified_inactive":
|
||||
die("Engine n8n inactive rollback baseline mismatch")
|
||||
if descriptor.get("expectedNodeTypes") != [] or descriptor.get("expectedCredentialTypes") != []:
|
||||
die("Engine n8n rollback must select the inactive catalog")
|
||||
return descriptor
|
||||
@@ -2275,10 +2368,13 @@ def validate_engine_n8n_catalog_payload(payload_dir, descriptor):
|
||||
die("Engine n8n light icon sha256 mismatch")
|
||||
if sha256_file(payload_dir / ENGINE_N8N_DARK_ICON_REL) != ENGINE_N8N_DARK_ICON_SHA256:
|
||||
die("Engine n8n dark icon sha256 mismatch")
|
||||
release_hashes = ENGINE_N8N_RELEASE_CATALOG_JSON_SHA256.get(descriptor["releaseId"])
|
||||
if release_hashes is None:
|
||||
die("Engine n8n activation release catalog is not registered")
|
||||
expected_catalog_hashes = (
|
||||
(nodes, ENGINE_N8N_ACTIVATION_NODES_CATALOG_JSON_SHA256, "node"),
|
||||
(credentials, ENGINE_N8N_ACTIVATION_CREDENTIALS_CATALOG_JSON_SHA256, "credential"),
|
||||
(meta, ENGINE_N8N_ACTIVATION_META_JSON_SHA256, "metadata"),
|
||||
(nodes, release_hashes["nodes"], "node"),
|
||||
(credentials, release_hashes["credentials"], "credential"),
|
||||
(meta, release_hashes["meta"], "metadata"),
|
||||
)
|
||||
else:
|
||||
expected_catalog_hashes = (
|
||||
@@ -2683,10 +2779,18 @@ def validate_engine_node_intelligence_transition(payload_dir, entries):
|
||||
"engine_get_node_guidance",
|
||||
"engine_validate_node_configuration",
|
||||
"engine_validate_l2_deep",
|
||||
"const ENGINE_AGENT_MCP_VERSION = '0.3.0'",
|
||||
):
|
||||
if required not in gateway_text:
|
||||
die(f"Engine node-intelligence gateway contract missing: {required}")
|
||||
if not any(
|
||||
version in gateway_text
|
||||
for version in (
|
||||
"const ENGINE_AGENT_MCP_VERSION = '0.3.0'",
|
||||
"const ENGINE_AGENT_MCP_VERSION = '0.4.0'",
|
||||
"const ENGINE_AGENT_MCP_VERSION = '0.5.0'",
|
||||
)
|
||||
):
|
||||
die("Engine node-intelligence gateway MCP version is not registered")
|
||||
validate_engine_node_intelligence_image_archive(
|
||||
payload_dir / ENGINE_NODE_INTELLIGENCE_IMAGE_ARCHIVE_REL,
|
||||
descriptor,
|
||||
@@ -2705,6 +2809,245 @@ def validate_engine_node_intelligence_transition(payload_dir, entries):
|
||||
return descriptor
|
||||
|
||||
|
||||
def is_engine_mcp_control_plane_slice(component, entries):
|
||||
return (
|
||||
component == "engine"
|
||||
and entries is not None
|
||||
and tuple(entries) == ENGINE_MCP_CONTROL_PLANE_ARTIFACT_ENTRIES
|
||||
)
|
||||
|
||||
|
||||
def validate_engine_mcp_control_plane_payload(payload_dir, entries):
|
||||
if tuple(entries) != ENGINE_MCP_CONTROL_PLANE_ARTIFACT_ENTRIES:
|
||||
die("Engine MCP control-plane files.txt exact set/order mismatch")
|
||||
for rel, expected_sha256 in ENGINE_MCP_CONTROL_PLANE_TARGET_SHA256.items():
|
||||
path = payload_dir / rel
|
||||
if path.is_symlink() or not path.is_file() or sha256_file(path) != expected_sha256:
|
||||
die(f"Engine MCP control-plane target sha256 mismatch: {rel}")
|
||||
|
||||
gateway = (payload_dir / ENGINE_NODE_INTELLIGENCE_GATEWAY_REL).read_text(encoding="utf-8")
|
||||
graph_route = (payload_dir / "nodedc-source/server/routes/ndcAgentMcp.js").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
installer = (
|
||||
payload_dir
|
||||
/ "nodedc-source/server/assets/engine-agent-npm/bin/nodedc-engine-codex-agent.mjs"
|
||||
).read_text(encoding="utf-8")
|
||||
package = read_strict_json(
|
||||
payload_dir / "nodedc-source/server/assets/engine-agent-npm/package.json",
|
||||
"Engine MCP Codex installer package",
|
||||
)
|
||||
if package.get("name") != "@nodedc/engine-codex-agent" or package.get("version") != "0.1.4":
|
||||
die("Engine MCP Codex installer package identity mismatch")
|
||||
if any(
|
||||
marker not in gateway
|
||||
for marker in (
|
||||
"const ENGINE_AGENT_MCP_VERSION = '0.5.0'",
|
||||
"engine_open_l2_change_session",
|
||||
"engine_get_l2_change_session",
|
||||
"engine_close_l2_change_session",
|
||||
"never_infer_expiry",
|
||||
"three_identical_failures_without_new_evidence",
|
||||
"engine_plan_data_product_read_grant",
|
||||
"engine_apply_data_product_read_grant",
|
||||
"engine_accept_data_product_read_grant",
|
||||
"engine_rollback_data_product_read_grant",
|
||||
)
|
||||
):
|
||||
die("Engine MCP bounded-change-session contract is incomplete")
|
||||
if any(
|
||||
marker not in graph_route
|
||||
for marker in (
|
||||
"update_node_no_effect",
|
||||
"subworkflow_post_write_equality_failed",
|
||||
"verifiedWrite: true",
|
||||
"graphDigest: expectedDigest",
|
||||
)
|
||||
):
|
||||
die("Engine MCP graph post-write equality contract is incomplete")
|
||||
if any(
|
||||
marker not in installer
|
||||
for marker in (
|
||||
"Do not interrupt the user after ordinary recoverable errors",
|
||||
"Never infer that a provider or managed capability token expired",
|
||||
"Three identical failures with no new evidence",
|
||||
"engine_plan_data_product_*_grant",
|
||||
"durable until an explicit rollback or revoke",
|
||||
)
|
||||
):
|
||||
die("Engine MCP Codex bounded-autonomy policy is incomplete")
|
||||
combined = "\n".join((gateway, graph_route, installer)).lower()
|
||||
if "gelios" in combined or "robot2b" in combined:
|
||||
die("provider identity is forbidden in Engine MCP control-plane source")
|
||||
|
||||
archive_path = payload_dir / "nodedc-source/server/assets/nodedc-engine-codex-agent-0.1.4.tgz"
|
||||
expected_members = {
|
||||
"package/bin/nodedc-engine-codex-agent.mjs": (
|
||||
payload_dir
|
||||
/ "nodedc-source/server/assets/engine-agent-npm/bin/nodedc-engine-codex-agent.mjs"
|
||||
).read_bytes(),
|
||||
"package/package.json": (
|
||||
payload_dir / "nodedc-source/server/assets/engine-agent-npm/package.json"
|
||||
).read_bytes(),
|
||||
}
|
||||
observed_members = {}
|
||||
try:
|
||||
with tarfile.open(archive_path, "r:gz") as archive:
|
||||
for member in archive:
|
||||
if not (member.isfile() or member.isdir()) or member.name.startswith("/"):
|
||||
die("Engine MCP Codex installer archive member is unsafe")
|
||||
if member.isfile():
|
||||
source = archive.extractfile(member)
|
||||
if source is None:
|
||||
die("Engine MCP Codex installer archive member is unreadable")
|
||||
observed_members[member.name] = source.read(MAX_FILE_BYTES + 1)
|
||||
except (OSError, tarfile.TarError):
|
||||
die("Engine MCP Codex installer archive is invalid")
|
||||
if observed_members != expected_members:
|
||||
die("Engine MCP Codex installer archive/source equality mismatch")
|
||||
|
||||
n8n_route = (payload_dir / "nodedc-source/server/routes/n8n.js").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
if "engineDataProductReadGrantN8nAdapter" not in n8n_route:
|
||||
die("Engine MCP managed reader native adapter is missing")
|
||||
if "ndcDataProductReaderApi" not in n8n_route or "ndc_edprb_" not in n8n_route:
|
||||
die("Engine MCP managed reader credential contract is incomplete")
|
||||
if "read_grant_must_be_durable" not in n8n_route:
|
||||
die("Engine MCP managed reader durable lifecycle is missing")
|
||||
reader_service = (
|
||||
payload_dir / "nodedc-source/server/dataProductReadGrant/service.js"
|
||||
).read_text(encoding="utf-8")
|
||||
if (
|
||||
"readerGrantLifetime: 'explicit-revoke'" not in reader_service
|
||||
or "expiresAt: null" not in reader_service
|
||||
or "ENGINE_READ_GRANT_TTL_DAYS" in reader_service
|
||||
):
|
||||
die("Engine MCP managed reader explicit-revoke lifecycle is incomplete")
|
||||
publish_service = (
|
||||
payload_dir / "nodedc-source/server/dataProductPublishGrant/service.js"
|
||||
).read_text(encoding="utf-8")
|
||||
if (
|
||||
"writerGrantLifetime: 'explicit-revoke'" not in publish_service
|
||||
or "migrateCurrentGrantToDurable" not in publish_service
|
||||
or "expiresAt: null" not in publish_service
|
||||
or "ENGINE_PUBLISH_GRANT_TTL_DAYS" in publish_service
|
||||
):
|
||||
die("Engine MCP managed writer explicit-revoke lifecycle is incomplete")
|
||||
reader_override = payload_dir / ENGINE_DATA_PRODUCT_READ_GRANT_OVERRIDE_REL
|
||||
try:
|
||||
reader_override_text = reader_override.read_text(encoding="utf-8")
|
||||
except (OSError, UnicodeDecodeError):
|
||||
die("Engine MCP managed reader runtime override is unreadable")
|
||||
if reader_override_text != expected_engine_data_product_read_grant_override():
|
||||
die("Engine MCP managed reader runtime override mismatch")
|
||||
agent_store = (payload_dir / "nodedc-source/server/engineAgents/store.js").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
for scope in (
|
||||
"'engine:l2:data-product-read-grant:plan'",
|
||||
"'engine:l2:data-product-read-grant:write'",
|
||||
):
|
||||
if scope not in agent_store:
|
||||
die(f"Engine MCP managed reader scope is missing: {scope}")
|
||||
|
||||
descriptor = read_engine_node_intelligence_descriptor(
|
||||
payload_dir / ENGINE_MCP_CONTROL_PLANE_DESCRIPTOR_REL,
|
||||
"Engine MCP control-plane node-intelligence descriptor",
|
||||
)
|
||||
if (
|
||||
descriptor.get("action") != "activate"
|
||||
or descriptor["source"].get("gatewaySha256")
|
||||
!= ENGINE_MCP_CONTROL_PLANE_TARGET_SHA256[ENGINE_NODE_INTELLIGENCE_GATEWAY_REL]
|
||||
or descriptor["source"].get("upstreamProjectionSha256")
|
||||
!= ENGINE_MCP_CONTROL_PLANE_TARGET_SHA256[
|
||||
"nodedc-source/server/nodeIntelligence/upstreamProjection.js"
|
||||
]
|
||||
):
|
||||
die("Engine MCP control-plane descriptor target mismatch")
|
||||
return descriptor
|
||||
|
||||
|
||||
def preflight_engine_mcp_control_plane_predecessor(payload_dir):
|
||||
candidate = validate_engine_mcp_control_plane_payload(
|
||||
payload_dir,
|
||||
ENGINE_MCP_CONTROL_PLANE_ARTIFACT_ENTRIES,
|
||||
)
|
||||
installed = current_engine_node_intelligence_descriptor()
|
||||
validate_installed_engine_node_intelligence_source(installed)
|
||||
if installed is None or installed.get("action") != "activate":
|
||||
die("Engine MCP control-plane requires active node intelligence")
|
||||
expected_candidate = json.loads(json.dumps(installed))
|
||||
expected_candidate["source"]["gatewaySha256"] = (
|
||||
ENGINE_MCP_CONTROL_PLANE_TARGET_SHA256[ENGINE_NODE_INTELLIGENCE_GATEWAY_REL]
|
||||
)
|
||||
expected_candidate["source"]["upstreamProjectionSha256"] = (
|
||||
ENGINE_MCP_CONTROL_PLANE_TARGET_SHA256[
|
||||
"nodedc-source/server/nodeIntelligence/upstreamProjection.js"
|
||||
]
|
||||
)
|
||||
if candidate != expected_candidate:
|
||||
die("Engine MCP control-plane descriptor crosses node-intelligence source boundary")
|
||||
root = component_root("engine")
|
||||
for rel, expected_sha256 in ENGINE_MCP_CONTROL_PLANE_PREDECESSOR_SHA256.items():
|
||||
path = root / rel
|
||||
if path.is_symlink() or not path.is_file() or sha256_file(path) != expected_sha256:
|
||||
die(f"Engine MCP control-plane predecessor drift detected: {rel}")
|
||||
for rel in ENGINE_MCP_CONTROL_PLANE_NEW_PATHS:
|
||||
path = root / rel
|
||||
if path.exists() or path.is_symlink():
|
||||
die(f"Engine MCP control-plane new path already exists: {rel}")
|
||||
backend = preflight_engine_credential_backend_runtime()
|
||||
if backend["mode"] != "verified-derived-retry":
|
||||
die("Engine MCP control-plane requires the active immutable backend runtime")
|
||||
return {
|
||||
"descriptor": candidate,
|
||||
"predecessor_gateway_sha256": installed["source"]["gatewaySha256"],
|
||||
"target_gateway_sha256": candidate["source"]["gatewaySha256"],
|
||||
"backend_mode": backend["mode"],
|
||||
}
|
||||
|
||||
|
||||
def accept_engine_mcp_control_plane_runtime():
|
||||
root = component_root("engine")
|
||||
descriptor = validate_engine_mcp_control_plane_payload(
|
||||
root,
|
||||
ENGINE_MCP_CONTROL_PLANE_ARTIFACT_ENTRIES,
|
||||
)
|
||||
installed = current_engine_node_intelligence_descriptor()
|
||||
if installed != descriptor:
|
||||
die("Engine MCP control-plane installed descriptor equality failed")
|
||||
validate_installed_engine_node_intelligence_source(installed)
|
||||
backend = preflight_engine_credential_backend_runtime()
|
||||
if backend["mode"] != "verified-derived-retry":
|
||||
die("Engine MCP control-plane backend runtime acceptance failed")
|
||||
live = run_engine_backend_probe(
|
||||
(
|
||||
"node",
|
||||
"--input-type=module",
|
||||
"-e",
|
||||
"""
|
||||
const module=await import('file:///app/server/routes/engineAgentGateway.js');
|
||||
const store=await import('file:///app/server/engineAgents/store.js');
|
||||
const names=new Set(module.engineAgentTools.map((tool)=>tool.name));
|
||||
const required=['engine_plan_data_product_read_grant','engine_apply_data_product_read_grant','engine_accept_data_product_read_grant','engine_rollback_data_product_read_grant'];
|
||||
const scopes=['engine:l2:data-product-read-grant:plan','engine:l2:data-product-read-grant:write'];
|
||||
if(!required.every((name)=>names.has(name))||!scopes.every((scope)=>store.ENGINE_AGENT_SCOPES.includes(scope)))process.exit(2);
|
||||
process.stdout.write('engine-mcp-reader-grant:0.5.0:'+required.length+':'+scopes.length);
|
||||
""".strip(),
|
||||
),
|
||||
"Engine MCP managed reader capability",
|
||||
container_id=engine_backend_container_id(),
|
||||
)
|
||||
if live != "engine-mcp-reader-grant:0.5.0:4:2":
|
||||
die("Engine MCP managed reader live capability acceptance mismatch")
|
||||
return {
|
||||
"gateway_sha256": descriptor["source"]["gatewaySha256"],
|
||||
"backend_mode": backend["mode"],
|
||||
"reader_grant": live,
|
||||
}
|
||||
|
||||
|
||||
def validate_no_lifecycle_scripts(payload_dir, label):
|
||||
forbidden = ("preinstall", "install", "postinstall", "prepare", "prepack", "postpack")
|
||||
for package_path in payload_dir.rglob("package.json"):
|
||||
@@ -2800,6 +3143,22 @@ def expected_engine_data_product_publish_grant_override():
|
||||
))
|
||||
|
||||
|
||||
def expected_engine_data_product_read_grant_override():
|
||||
return "\n".join((
|
||||
"services:",
|
||||
" nodedc-backend:",
|
||||
" environment:",
|
||||
f" ENGINE_CONTROL_PLANE_READ_GRANT_ROOT: {ENGINE_READ_GRANT_CONTAINER_PATH}",
|
||||
" volumes:",
|
||||
" - type: bind",
|
||||
f" source: {ENGINE_READ_GRANT_STATE_PATH}",
|
||||
f" target: {ENGINE_READ_GRANT_CONTAINER_PATH}",
|
||||
" bind:",
|
||||
" create_host_path: false",
|
||||
"",
|
||||
))
|
||||
|
||||
|
||||
def validate_engine_credential_sink_slice(payload_dir, entries):
|
||||
if tuple(entries) != ENGINE_CREDENTIAL_SINK_ARTIFACT_ENTRIES:
|
||||
die("Engine credential sink files.txt exact set/order mismatch")
|
||||
@@ -3103,20 +3462,30 @@ def load_artifact(artifact, work_dir):
|
||||
or rel.startswith(ENGINE_NODE_INTELLIGENCE_SERVICE_ROOT_REL + "/")
|
||||
for rel in entries
|
||||
)
|
||||
if touches_node_intelligence and not is_engine_node_intelligence_transition(
|
||||
manifest["component"], entries
|
||||
if (
|
||||
touches_node_intelligence
|
||||
and not is_engine_node_intelligence_transition(manifest["component"], entries)
|
||||
and not is_engine_mcp_control_plane_slice(manifest["component"], entries)
|
||||
):
|
||||
die("Engine node-intelligence payload requires the canonical transition")
|
||||
if is_engine_node_intelligence_transition(manifest["component"], entries):
|
||||
validate_engine_node_intelligence_transition(payload_dir, entries)
|
||||
if is_engine_mcp_control_plane_slice(manifest["component"], entries):
|
||||
validate_engine_mcp_control_plane_payload(payload_dir, entries)
|
||||
if touches_engine_credential_sink(manifest["component"], entries):
|
||||
validate_engine_credential_sink_slice(payload_dir, entries)
|
||||
if (
|
||||
touches_engine_data_product_publish_grant(entries)
|
||||
or ENGINE_DATA_PRODUCT_PUBLISH_GRANT_OVERRIDE_REL in entries
|
||||
not is_engine_mcp_control_plane_slice(manifest["component"], entries)
|
||||
and (
|
||||
touches_engine_data_product_publish_grant(entries)
|
||||
or ENGINE_DATA_PRODUCT_PUBLISH_GRANT_OVERRIDE_REL in entries
|
||||
)
|
||||
):
|
||||
validate_engine_data_product_publish_grant_slice(payload_dir, entries)
|
||||
elif ENGINE_AGENT_FULL_GRANT_MIGRATION_STORE_REL in entries:
|
||||
elif (
|
||||
not is_engine_mcp_control_plane_slice(manifest["component"], entries)
|
||||
and ENGINE_AGENT_FULL_GRANT_MIGRATION_STORE_REL in entries
|
||||
):
|
||||
validate_engine_agent_full_grant_migration_slice(payload_dir, entries)
|
||||
return manifest, entries, payload_dir
|
||||
|
||||
@@ -3202,6 +3571,7 @@ def touches_external_data_plane_files(entries):
|
||||
"platform/packages/external-provider-contract/src/data-plane.mjs",
|
||||
"platform/packages/external-provider-contract/src/data-product.mjs",
|
||||
"platform/packages/external-provider-contract/src/intake-batch.mjs",
|
||||
"platform/packages/external-provider-contract/src/index.mjs",
|
||||
"platform/packages/external-provider-contract/src/sensitive-field-policy.mjs",
|
||||
}
|
||||
return any(
|
||||
@@ -3462,6 +3832,12 @@ def is_platform_provider_catalog_only(entries):
|
||||
|
||||
|
||||
def component_services(component, entries=None):
|
||||
if is_engine_mcp_control_plane_slice(component, entries):
|
||||
# This slice updates only the existing Engine backend control plane.
|
||||
# Node intelligence keeps the same immutable sidecar image and n8n/L1
|
||||
# retain their current generations.
|
||||
return ("nodedc-backend",)
|
||||
|
||||
if is_engine_node_intelligence_transition(component, entries):
|
||||
if tuple(entries) == ENGINE_NODE_INTELLIGENCE_ARTIFACT_ENTRIES:
|
||||
return (ENGINE_NODE_INTELLIGENCE_SERVICE, "nodedc-backend")
|
||||
@@ -3653,6 +4029,18 @@ def component_compose_files(component, allow_prepared_engine_backend=False):
|
||||
# Canonical order is immutable: base, active L2 extension, credential
|
||||
# sink, then the additive Publish grant overlay.
|
||||
files.append(publish_grant_override)
|
||||
read_grant_override = root / ENGINE_DATA_PRODUCT_READ_GRANT_OVERRIDE_REL
|
||||
if read_grant_override.exists() or read_grant_override.is_symlink():
|
||||
if read_grant_override.is_symlink() or not read_grant_override.is_file():
|
||||
die("installed Engine data product read grant override is unsafe")
|
||||
try:
|
||||
read_grant_override_text = read_grant_override.read_text(encoding="utf-8")
|
||||
except (OSError, UnicodeDecodeError):
|
||||
die("installed Engine data product read grant override cannot be read")
|
||||
if read_grant_override_text != expected_engine_data_product_read_grant_override():
|
||||
die("installed Engine data product read grant override drift detected")
|
||||
# Read authority is additive and ordered after the writer/private-key overlay.
|
||||
files.append(read_grant_override)
|
||||
node_intelligence_descriptor = current_engine_node_intelligence_descriptor()
|
||||
if (
|
||||
node_intelligence_descriptor is not None
|
||||
@@ -4164,6 +4552,22 @@ def validate_engine_backend_mounts_for_installed_runtime(
|
||||
str(ENGINE_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE),
|
||||
),
|
||||
}
|
||||
read_override = component_root("engine") / ENGINE_DATA_PRODUCT_READ_GRANT_OVERRIDE_REL
|
||||
read_installed = read_override.exists() or read_override.is_symlink()
|
||||
if read_installed:
|
||||
if read_override.is_symlink() or not read_override.is_file():
|
||||
die("installed Engine data product read grant override is unsafe")
|
||||
try:
|
||||
read_override_text = read_override.read_text(encoding="utf-8")
|
||||
except (OSError, UnicodeDecodeError):
|
||||
die("installed Engine data product read grant override cannot be read")
|
||||
if read_override_text != expected_engine_data_product_read_grant_override():
|
||||
die("installed Engine data product read grant override drift detected")
|
||||
expected_publish_mounts[ENGINE_READ_GRANT_CONTAINER_PATH] = (
|
||||
"bind",
|
||||
True,
|
||||
str(ENGINE_READ_GRANT_STATE_PATH),
|
||||
)
|
||||
mounts = container.get("Mounts")
|
||||
if not isinstance(mounts, list):
|
||||
die("Engine backend mount inventory is missing")
|
||||
@@ -5306,10 +5710,6 @@ def preflight_engine_n8n_transition(descriptor, enforce_expected_current):
|
||||
"Engine n8n transition stale current state: "
|
||||
f"expected={descriptor['expectedCurrent']} actual={current_state}"
|
||||
)
|
||||
if current_descriptor and current_state != "verified_inactive":
|
||||
if current_descriptor.get("packageSha256") != descriptor["packageSha256"]:
|
||||
if enforce_expected_current:
|
||||
die("Engine n8n current release package sha256 mismatch")
|
||||
return {
|
||||
"base_image_id": base_image["id"],
|
||||
"base_image_architecture": base_image["architecture"],
|
||||
@@ -5558,6 +5958,7 @@ def plan_artifact(artifact):
|
||||
sha = sha256_file(artifact)
|
||||
publish_grant_preflight = None
|
||||
node_intelligence_preflight = None
|
||||
mcp_control_plane_preflight = None
|
||||
with tempfile.TemporaryDirectory(prefix="plan-", dir=TMP_DIR) as tmp:
|
||||
manifest, entries, payload_dir = load_artifact(artifact, Path(tmp))
|
||||
transition_descriptor = None
|
||||
@@ -5578,6 +5979,10 @@ def plan_artifact(artifact):
|
||||
node_intelligence_preflight = preflight_engine_node_intelligence_predecessor(
|
||||
payload_dir
|
||||
)
|
||||
if is_engine_mcp_control_plane_slice(manifest["component"], entries):
|
||||
mcp_control_plane_preflight = preflight_engine_mcp_control_plane_predecessor(
|
||||
payload_dir
|
||||
)
|
||||
|
||||
component = manifest["component"]
|
||||
root = component_root(component)
|
||||
@@ -5644,6 +6049,22 @@ def plan_artifact(artifact):
|
||||
print("node_intelligence_host_ports=none")
|
||||
print(f"predecessor_gateway_sha256={node_intelligence_preflight['gateway_sha256']}")
|
||||
print(f"backend_current_barrier={node_intelligence_preflight['backend_mode']}")
|
||||
if mcp_control_plane_preflight:
|
||||
print("engine_mcp_transition=managed-reader-grant+validator-reconciliation")
|
||||
print("engine_mcp_version=0.5.0")
|
||||
print("engine_mcp_installer=0.1.4")
|
||||
print("engine_mcp_reader_grant=plan+apply+accept+rollback")
|
||||
print(
|
||||
"predecessor_gateway_sha256="
|
||||
f"{mcp_control_plane_preflight['predecessor_gateway_sha256']}"
|
||||
)
|
||||
print(
|
||||
"target_gateway_sha256="
|
||||
f"{mcp_control_plane_preflight['target_gateway_sha256']}"
|
||||
)
|
||||
print(f"backend_current_barrier={mcp_control_plane_preflight['backend_mode']}")
|
||||
print("node_intelligence_image=preserved")
|
||||
print("n8n_l1=untouched")
|
||||
if transition_descriptor:
|
||||
print(f"n8n_transition={transition_descriptor['action']}")
|
||||
print(f"n8n_version={transition_descriptor['n8nVersion']}")
|
||||
@@ -5665,7 +6086,7 @@ def plan_artifact(artifact):
|
||||
print("n8n_database=preserved:n8n-postgres")
|
||||
print(f"n8n_expected_node_types={','.join(transition_descriptor['expectedNodeTypes'])}")
|
||||
print(f"n8n_expected_credential_types={','.join(transition_descriptor['expectedCredentialTypes'])}")
|
||||
print("n8n_rollback_baseline=verified_inactive")
|
||||
print(f"n8n_rollback_baseline={transition_descriptor['rollbackBaseline']}")
|
||||
touches_map_gateway = component == "platform" and any(rel == "platform/services/map-gateway" or rel.startswith("platform/services/map-gateway/") for rel in entries)
|
||||
touches_external_data_plane = component == "platform" and touches_external_data_plane_files(entries)
|
||||
if component == "module-foundry" or touches_map_gateway:
|
||||
@@ -6387,6 +6808,10 @@ def prepare_component_runtime(component, entries=None):
|
||||
ensure_engine_edp_managed_provisioner_keypair()
|
||||
ensure_engine_publish_grant_private_state()
|
||||
|
||||
if is_engine_mcp_control_plane_slice(component, entries):
|
||||
ensure_engine_edp_managed_provisioner_keypair()
|
||||
ensure_engine_data_product_grant_private_state(include_reader=True)
|
||||
|
||||
if is_engine_n8n_transition(component, entries):
|
||||
descriptor = current_engine_n8n_transition_descriptor()
|
||||
if descriptor is None:
|
||||
@@ -6553,6 +6978,20 @@ def external_data_plane_healthcheck(require_managed=True):
|
||||
}
|
||||
if require_managed:
|
||||
expected_json["managedWriterBindingProvisioning"] = "enabled"
|
||||
server_source = (
|
||||
component_root("platform")
|
||||
/ "platform/services/external-data-plane/src/server.mjs"
|
||||
)
|
||||
try:
|
||||
server_text = server_source.read_text(encoding="utf-8")
|
||||
except (OSError, UnicodeDecodeError):
|
||||
server_text = ""
|
||||
if 'managedReaderBindingProvisioning: config.managedProvisionerApiEnabled' in server_text:
|
||||
expected_json["managedReaderBindingProvisioning"] = "enabled"
|
||||
if 'managedWriterBindingLifetime: config.managedProvisionerApiEnabled ? "explicit-revoke" : "disabled"' in server_text:
|
||||
expected_json["managedWriterBindingLifetime"] = "explicit-revoke"
|
||||
if 'managedReaderBindingLifetime: config.managedProvisionerApiEnabled ? "explicit-revoke" : "disabled"' in server_text:
|
||||
expected_json["managedReaderBindingLifetime"] = "explicit-revoke"
|
||||
return {
|
||||
"url": "http://127.0.0.1:18106/healthz",
|
||||
"expected_json": expected_json,
|
||||
@@ -6576,6 +7015,7 @@ def component_healthchecks(component, entries=None, services=None):
|
||||
(
|
||||
is_engine_data_product_publish_grant_slice(component, entries)
|
||||
or is_engine_agent_full_grant_migration_slice(component, entries)
|
||||
or is_engine_mcp_control_plane_slice(component, entries)
|
||||
)
|
||||
and services is not None
|
||||
):
|
||||
@@ -6888,10 +7328,12 @@ def run_healthchecks(component, entries=None, services=None):
|
||||
component,
|
||||
entries,
|
||||
)
|
||||
touches_mcp_control_plane = is_engine_mcp_control_plane_slice(component, entries)
|
||||
if (
|
||||
touches_engine_credential_sink(component, entries)
|
||||
or touches_publish_grant
|
||||
or touches_agent_grant_migration
|
||||
or touches_mcp_control_plane
|
||||
):
|
||||
# The HTTP endpoint can become ready before Docker publishes the first
|
||||
# successful health probe. Wait for the Compose health barrier before
|
||||
@@ -6903,12 +7345,15 @@ def run_healthchecks(component, entries=None, services=None):
|
||||
touches_engine_credential_sink(component, entries)
|
||||
or touches_publish_grant
|
||||
or touches_agent_grant_migration
|
||||
or touches_mcp_control_plane
|
||||
):
|
||||
runtime = preflight_engine_credential_backend_runtime()
|
||||
if runtime["mode"] != "verified-derived-retry":
|
||||
die("Engine backend immutable runtime activation acceptance failed")
|
||||
if touches_agent_grant_migration:
|
||||
accept_engine_agent_full_grant_migration_runtime()
|
||||
if touches_mcp_control_plane:
|
||||
accept_engine_mcp_control_plane_runtime()
|
||||
container_name = COMPONENTS[component].get("health_container")
|
||||
if container_name:
|
||||
healthcheck_container(container_name)
|
||||
@@ -6983,6 +7428,8 @@ def apply_artifact(artifact):
|
||||
payload_dir
|
||||
)
|
||||
node_intelligence_descriptor = node_intelligence_preflight["descriptor"]
|
||||
if is_engine_mcp_control_plane_slice(component, entries):
|
||||
preflight_engine_mcp_control_plane_predecessor(payload_dir)
|
||||
if not artifact_only and not compose_root.is_dir():
|
||||
if bootstrap_root and is_relative_to(compose_root.resolve(strict=False), root.resolve(strict=False)):
|
||||
compose_root.mkdir(parents=True, exist_ok=True)
|
||||
@@ -7159,6 +7606,7 @@ def apply_artifact(artifact):
|
||||
(
|
||||
is_engine_data_product_publish_grant_slice(component, entries)
|
||||
or is_engine_agent_full_grant_migration_slice(component, entries)
|
||||
or is_engine_mcp_control_plane_slice(component, entries)
|
||||
)
|
||||
and services is not None
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user