feat(deploy): add Engine ontology SDK transition

This commit is contained in:
Codex
2026-07-19 12:33:56 +03:00
parent f67c49bc4d
commit d85912b9d7
4 changed files with 598 additions and 3 deletions
+277 -1
View File
@@ -174,6 +174,36 @@ ENGINE_MCP_CONTROL_PLANE_NEW_PATHS = (
"nodedc-source/server/dataProductReadGrant/store.js",
ENGINE_DATA_PRODUCT_READ_GRANT_OVERRIDE_REL,
)
ENGINE_MCP_ONTOLOGY_SDK_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.5.tgz",
"nodedc-source/server/assets/provider-packages/v1/catalog.json",
"nodedc-source/server/dataProductPublishGrant/providerCatalog.js",
"nodedc-source/server/engineAgents/store.js",
ENGINE_NODE_INTELLIGENCE_GATEWAY_REL,
ENGINE_MCP_CONTROL_PLANE_DESCRIPTOR_REL,
)
ENGINE_MCP_ONTOLOGY_SDK_PREDECESSOR_SHA256 = {
"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/provider-packages/v1/catalog.json": "30f89052b6557a2444550bd6e7eed30747b2d9b7ff3a22d079d9905fe0a04702",
"nodedc-source/server/dataProductPublishGrant/providerCatalog.js": "901b8fad80018ce177b34ced804b39cb140a47e831414057f484296b373c651d",
"nodedc-source/server/engineAgents/store.js": "debd351fe0b8c72b33b8c79909b8f339cbaf061b970576f3ae72df52ebaa211f",
ENGINE_NODE_INTELLIGENCE_GATEWAY_REL: "96c726dab5cf1341f74e6e1095d518058ca320e0dd5738e25bdbe75db1f4fc15",
}
ENGINE_MCP_ONTOLOGY_SDK_TARGET_SHA256 = {
"nodedc-source/server/assets/engine-agent-npm/bin/nodedc-engine-codex-agent.mjs": "adff3e474c914680f7d36b204d1dc03e69dc8065fff1f80b6713526cfc970137",
"nodedc-source/server/assets/engine-agent-npm/package.json": "0741647e4f7f58f69f3021d367484609a8e1eb7b8727d6ad9639bd9906b7f455",
"nodedc-source/server/assets/nodedc-engine-codex-agent-0.1.5.tgz": "72a1b2d41a12a298eaae63ba3334c7c66b6ca53de6a3f03a48607d4ff6da42fb",
"nodedc-source/server/assets/provider-packages/v1/catalog.json": "4800e1a1c2af5e4893b1a403c04e91f55689383457caff833edc9e35e8e7e37a",
"nodedc-source/server/dataProductPublishGrant/providerCatalog.js": "d5511a8bd3b4238af88a89537661c9ca4c0126bca7b5ad225985e27ccdb2c64c",
"nodedc-source/server/engineAgents/store.js": "4cd4bdd5958cfafee184e98a04fe12aa0c1cbe884326beaec63c99f9fff61285",
ENGINE_NODE_INTELLIGENCE_GATEWAY_REL: "25fa013ddbfd7d0c7ece8c792daec5f345062757c85eb56cfbff45bf1e812152",
}
ENGINE_MCP_ONTOLOGY_SDK_NEW_PATHS = (
"nodedc-source/server/assets/nodedc-engine-codex-agent-0.1.5.tgz",
)
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"
@@ -2813,6 +2843,7 @@ def validate_engine_node_intelligence_transition(payload_dir, entries):
"const ENGINE_AGENT_MCP_VERSION = '0.3.0'",
"const ENGINE_AGENT_MCP_VERSION = '0.4.0'",
"const ENGINE_AGENT_MCP_VERSION = '0.5.0'",
"const ENGINE_AGENT_MCP_VERSION = '0.6.0'",
)
):
die("Engine node-intelligence gateway MCP version is not registered")
@@ -3073,6 +3104,211 @@ process.stdout.write('engine-mcp-reader-grant:0.5.0:'+required.length+':'+scopes
}
def is_engine_mcp_ontology_sdk_slice(component, entries):
return (
component == "engine"
and entries is not None
and tuple(entries) == ENGINE_MCP_ONTOLOGY_SDK_ARTIFACT_ENTRIES
)
def validate_engine_mcp_ontology_sdk_payload(payload_dir, entries):
if tuple(entries) != ENGINE_MCP_ONTOLOGY_SDK_ARTIFACT_ENTRIES:
die("Engine MCP Ontology/SDK files.txt exact set/order mismatch")
for rel, expected_sha256 in ENGINE_MCP_ONTOLOGY_SDK_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 Ontology/SDK target sha256 mismatch: {rel}")
gateway = (payload_dir / ENGINE_NODE_INTELLIGENCE_GATEWAY_REL).read_text(
encoding="utf-8"
)
installer_path = (
payload_dir
/ "nodedc-source/server/assets/engine-agent-npm/bin/nodedc-engine-codex-agent.mjs"
)
installer = installer_path.read_text(encoding="utf-8")
package_path = payload_dir / "nodedc-source/server/assets/engine-agent-npm/package.json"
package = read_strict_json(package_path, "Engine MCP Codex installer package")
if package.get("name") != "@nodedc/engine-codex-agent" or package.get("version") != "0.1.5":
die("Engine MCP Ontology/SDK installer package identity mismatch")
if any(
marker not in gateway
for marker in (
"const ENGINE_AGENT_MCP_VERSION = '0.6.0'",
"authenticateEngineAgentOntologyToken",
"apiRouter.post('/ontology-mcp'",
"process.env.NODEDC_INTERNAL_ACCESS_TOKEN",
"serverName: 'nodedc-engine-agent'",
"serverName: 'nodedc_ontology'",
)
):
die("Engine MCP separate Ontology gateway contract is incomplete")
if any(
marker not in installer
for marker in (
"const ENGINE_SERVER_NAME = 'nodedc-engine-agent'",
"const ONTOLOGY_SERVER_NAME = 'nodedc_ontology'",
"Ontology tools/catalog are never embedded or multiplexed into Engine MCP",
"version: '0.1.5'",
)
):
die("Engine MCP separate Ontology installer contract is incomplete")
archive_path = payload_dir / "nodedc-source/server/assets/nodedc-engine-codex-agent-0.1.5.tgz"
expected_members = {
"package/bin/nodedc-engine-codex-agent.mjs": installer_path.read_bytes(),
"package/package.json": package_path.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 Ontology/SDK installer archive member is unsafe")
if member.isfile():
source = archive.extractfile(member)
if source is None:
die("Engine MCP Ontology/SDK installer archive member is unreadable")
observed_members[member.name] = source.read(MAX_FILE_BYTES + 1)
except (OSError, tarfile.TarError):
die("Engine MCP Ontology/SDK installer archive is invalid")
if observed_members != expected_members:
die("Engine MCP Ontology/SDK installer archive/source equality mismatch")
catalog = read_strict_json(
payload_dir / "nodedc-source/server/assets/provider-packages/v1/catalog.json",
"Engine provider security catalog",
)
packages = catalog.get("packages") if isinstance(catalog, dict) else None
if not isinstance(packages, list) or len(packages) != 1:
die("Engine provider security catalog must contain one active package")
provider = packages[0]
capabilities = provider.get("capabilities") if isinstance(provider, dict) else None
capability = capabilities[0] if isinstance(capabilities, list) and len(capabilities) == 1 else None
request = capability.get("request") if isinstance(capability, dict) else None
if (
provider.get("id") != "gelios.provider.v2"
or provider.get("version") != "2.0.0"
or (provider.get("providerCredential") or {}).get("credentialType") != "httpQueryAuth"
or not isinstance(request, dict)
or request.get("method") != "GET"
or request.get("url") != "https://admin.geliospro.com/sdk/?svc=get_units&params=%7B%7D"
):
die("Engine Gelios SDK/query-auth security projection mismatch")
resolver = (
payload_dir / "nodedc-source/server/dataProductPublishGrant/providerCatalog.js"
).read_text(encoding="utf-8")
if "new Set(['httpBearerAuth', 'httpQueryAuth'])" not in resolver:
die("Engine provider credential type allowlist mismatch")
store = (payload_dir / "nodedc-source/server/engineAgents/store.js").read_text(
encoding="utf-8"
)
if any(
marker not in store
for marker in (
"const STORE_VERSION = 3",
"ontologyTokenHash",
"`ndc_eao_${",
"authenticateEngineAgentOntologyToken",
)
):
die("Engine separate Ontology token store migration is incomplete")
descriptor = read_engine_node_intelligence_descriptor(
payload_dir / ENGINE_MCP_CONTROL_PLANE_DESCRIPTOR_REL,
"Engine MCP Ontology/SDK node-intelligence descriptor",
)
if (
descriptor.get("action") != "activate"
or descriptor["source"].get("gatewaySha256")
!= ENGINE_MCP_ONTOLOGY_SDK_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 Ontology/SDK descriptor target mismatch")
return descriptor
def preflight_engine_mcp_ontology_sdk_predecessor(payload_dir):
candidate = validate_engine_mcp_ontology_sdk_payload(
payload_dir,
ENGINE_MCP_ONTOLOGY_SDK_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 Ontology/SDK requires active node intelligence")
expected_candidate = json.loads(json.dumps(installed))
expected_candidate["source"]["gatewaySha256"] = (
ENGINE_MCP_ONTOLOGY_SDK_TARGET_SHA256[ENGINE_NODE_INTELLIGENCE_GATEWAY_REL]
)
if candidate != expected_candidate:
die("Engine MCP Ontology/SDK descriptor crosses node-intelligence source boundary")
root = component_root("engine")
for rel, expected_sha256 in ENGINE_MCP_ONTOLOGY_SDK_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 Ontology/SDK predecessor drift detected: {rel}")
for rel in ENGINE_MCP_ONTOLOGY_SDK_NEW_PATHS:
path = root / rel
if path.exists() or path.is_symlink():
die(f"Engine MCP Ontology/SDK new path already exists: {rel}")
backend = preflight_engine_credential_backend_runtime()
if backend["mode"] != "verified-derived-retry":
die("Engine MCP Ontology/SDK 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_ontology_sdk_runtime():
root = component_root("engine")
descriptor = validate_engine_mcp_ontology_sdk_payload(
root,
ENGINE_MCP_ONTOLOGY_SDK_ARTIFACT_ENTRIES,
)
installed = current_engine_node_intelligence_descriptor()
if installed != descriptor:
die("Engine MCP Ontology/SDK 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 Ontology/SDK backend runtime acceptance failed")
live = run_engine_backend_probe(
(
"node",
"--input-type=module",
"-e",
"""
const gateway=await import('file:///app/server/routes/engineAgentGateway.js');
const store=await import('file:///app/server/engineAgents/store.js');
const catalog=JSON.parse(await (await import('node:fs/promises')).readFile('/app/server/assets/provider-packages/v1/catalog.json','utf8'));
const provider=catalog.packages?.[0];
if(gateway.ENGINE_AGENT_MCP_VERSION!=='0.6.0')process.exit(2);
if(typeof store.authenticateEngineAgentOntologyToken!=='function')process.exit(3);
if(provider?.id!=='gelios.provider.v2'||provider?.providerCredential?.credentialType!=='httpQueryAuth')process.exit(4);
process.stdout.write('engine-mcp-ontology-sdk:0.6.0:0.1.5:gelios.provider.v2:httpQueryAuth');
""".strip(),
),
"Engine MCP Ontology/SDK capability",
container_id=engine_backend_container_id(),
)
expected = "engine-mcp-ontology-sdk:0.6.0:0.1.5:gelios.provider.v2:httpQueryAuth"
if live != expected:
die("Engine MCP Ontology/SDK live capability acceptance mismatch")
return {
"gateway_sha256": descriptor["source"]["gatewaySha256"],
"backend_mode": backend["mode"],
"capability": 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"):
@@ -3491,16 +3727,20 @@ def load_artifact(artifact, work_dir):
touches_node_intelligence
and not is_engine_node_intelligence_transition(manifest["component"], entries)
and not is_engine_mcp_control_plane_slice(manifest["component"], entries)
and not is_engine_mcp_ontology_sdk_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 is_engine_mcp_ontology_sdk_slice(manifest["component"], entries):
validate_engine_mcp_ontology_sdk_payload(payload_dir, entries)
if touches_engine_credential_sink(manifest["component"], entries):
validate_engine_credential_sink_slice(payload_dir, entries)
if (
not is_engine_mcp_control_plane_slice(manifest["component"], entries)
and not is_engine_mcp_ontology_sdk_slice(manifest["component"], entries)
and (
touches_engine_data_product_publish_grant(entries)
or ENGINE_DATA_PRODUCT_PUBLISH_GRANT_OVERRIDE_REL in entries
@@ -3509,6 +3749,7 @@ def load_artifact(artifact, work_dir):
validate_engine_data_product_publish_grant_slice(payload_dir, entries)
elif (
not is_engine_mcp_control_plane_slice(manifest["component"], entries)
and not is_engine_mcp_ontology_sdk_slice(manifest["component"], entries)
and ENGINE_AGENT_FULL_GRANT_MIGRATION_STORE_REL in entries
):
validate_engine_agent_full_grant_migration_slice(payload_dir, entries)
@@ -3857,7 +4098,10 @@ def is_platform_provider_catalog_only(entries):
def component_services(component, entries=None):
if is_engine_mcp_control_plane_slice(component, entries):
if (
is_engine_mcp_control_plane_slice(component, entries)
or is_engine_mcp_ontology_sdk_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.
@@ -5992,6 +6236,7 @@ def plan_artifact(artifact):
publish_grant_preflight = None
node_intelligence_preflight = None
mcp_control_plane_preflight = None
mcp_ontology_sdk_preflight = None
with tempfile.TemporaryDirectory(prefix="plan-", dir=TMP_DIR) as tmp:
manifest, entries, payload_dir = load_artifact(artifact, Path(tmp))
transition_descriptor = None
@@ -6016,6 +6261,10 @@ def plan_artifact(artifact):
mcp_control_plane_preflight = preflight_engine_mcp_control_plane_predecessor(
payload_dir
)
if is_engine_mcp_ontology_sdk_slice(manifest["component"], entries):
mcp_ontology_sdk_preflight = preflight_engine_mcp_ontology_sdk_predecessor(
payload_dir
)
component = manifest["component"]
root = component_root(component)
@@ -6098,6 +6347,24 @@ def plan_artifact(artifact):
print(f"backend_current_barrier={mcp_control_plane_preflight['backend_mode']}")
print("node_intelligence_image=preserved")
print("n8n_l1=untouched")
if mcp_ontology_sdk_preflight:
print("engine_mcp_transition=separate-ontology+gelios-sdk-query-auth")
print("engine_mcp_version=0.6.0")
print("engine_mcp_installer=0.1.5")
print("engine_mcp_ontology=separate-read-only-proxy")
print("engine_provider_package=gelios.provider.v2")
print("engine_provider_credential=httpQueryAuth")
print(
"predecessor_gateway_sha256="
f"{mcp_ontology_sdk_preflight['predecessor_gateway_sha256']}"
)
print(
"target_gateway_sha256="
f"{mcp_ontology_sdk_preflight['target_gateway_sha256']}"
)
print(f"backend_current_barrier={mcp_ontology_sdk_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']}")
@@ -7049,6 +7316,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)
or is_engine_mcp_ontology_sdk_slice(component, entries)
)
and services is not None
):
@@ -7362,11 +7630,13 @@ def run_healthchecks(component, entries=None, services=None):
entries,
)
touches_mcp_control_plane = is_engine_mcp_control_plane_slice(component, entries)
touches_mcp_ontology_sdk = is_engine_mcp_ontology_sdk_slice(component, entries)
if (
touches_engine_credential_sink(component, entries)
or touches_publish_grant
or touches_agent_grant_migration
or touches_mcp_control_plane
or touches_mcp_ontology_sdk
):
# The HTTP endpoint can become ready before Docker publishes the first
# successful health probe. Wait for the Compose health barrier before
@@ -7379,6 +7649,7 @@ def run_healthchecks(component, entries=None, services=None):
or touches_publish_grant
or touches_agent_grant_migration
or touches_mcp_control_plane
or touches_mcp_ontology_sdk
):
runtime = preflight_engine_credential_backend_runtime()
if runtime["mode"] != "verified-derived-retry":
@@ -7387,6 +7658,8 @@ def run_healthchecks(component, entries=None, services=None):
accept_engine_agent_full_grant_migration_runtime()
if touches_mcp_control_plane:
accept_engine_mcp_control_plane_runtime()
if touches_mcp_ontology_sdk:
accept_engine_mcp_ontology_sdk_runtime()
container_name = COMPONENTS[component].get("health_container")
if container_name:
healthcheck_container(container_name)
@@ -7463,6 +7736,8 @@ def apply_artifact(artifact):
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 is_engine_mcp_ontology_sdk_slice(component, entries):
preflight_engine_mcp_ontology_sdk_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)
@@ -7640,6 +7915,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)
or is_engine_mcp_ontology_sdk_slice(component, entries)
)
and services is not None
):