fix(deploy): attest Engine telemetry gateway transition
This commit is contained in:
parent
f97a9d924d
commit
6f9da1b677
|
|
@ -14,6 +14,22 @@ const engineRoot = resolve(
|
|||
const artifactRoot = resolve(
|
||||
process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(here, "../deploy-artifacts"),
|
||||
);
|
||||
const baselineArtifact = resolve(
|
||||
here,
|
||||
"../deploy-artifacts/nodedc-engine-l2-closed-loop-20260723-031.tgz",
|
||||
);
|
||||
const baselineArtifactSha256 =
|
||||
"33103bcf8a5f020855f3a095306b94158d9a452344ac7313120b1624a745e47e";
|
||||
const nodeIntelligenceDescriptorPath =
|
||||
"nodedc-source/services/node-intelligence/activation.json";
|
||||
const predecessorNodeIntelligenceDescriptorSha256 =
|
||||
"63e7619c6971d02102583bb5d80d33ece293b952f113200fa0b76f0e88c2dd32";
|
||||
const targetNodeIntelligenceDescriptorSha256 =
|
||||
"25ed3efd858aaf82c242dba501f0acc0c6c3dc91e8845707a4d3325750eab59f";
|
||||
const predecessorGatewaySha256 =
|
||||
"4f600a2781be9118bec891fa2a6f20d7f55e0892ef2f06af24059193cebd28f4";
|
||||
const targetGatewaySha256 =
|
||||
"69bfc91e913a3fad04e13aca86efb9d62f73c0c7d1f8f7200907494b29fa9e8d";
|
||||
const [patchId = "", ...extra] = process.argv.slice(2);
|
||||
if (
|
||||
extra.length
|
||||
|
|
@ -31,21 +47,33 @@ const targetSha256 = Object.freeze({
|
|||
"nodedc-source/server/routes/n8n.js":
|
||||
"903245ae363e9b9ac161498f17988a38876a0e0ac8d80f3fa1b0112ceb7fe906",
|
||||
"nodedc-source/server/routes/engineAgentGateway.js":
|
||||
"69bfc91e913a3fad04e13aca86efb9d62f73c0c7d1f8f7200907494b29fa9e8d",
|
||||
targetGatewaySha256,
|
||||
[descriptorPath]:
|
||||
"b25ab8b6e6ad8ac24614c4630cc3635abe453464466d5a72238b05e48a24d882",
|
||||
[nodeIntelligenceDescriptorPath]:
|
||||
targetNodeIntelligenceDescriptorSha256,
|
||||
});
|
||||
const entries = Object.freeze(Object.keys(targetSha256));
|
||||
const artifact = join(artifactRoot, `nodedc-${patchId}.tgz`);
|
||||
|
||||
await assertFresh(artifact);
|
||||
await assertExactSources();
|
||||
const targetNodeIntelligenceDescriptor =
|
||||
await buildTargetNodeIntelligenceDescriptor();
|
||||
const stage = await mkdtemp(join(tmpdir(), "nodedc-engine-mcp-telemetry-catalog-"));
|
||||
try {
|
||||
const payload = join(stage, "payload");
|
||||
for (const relativePath of entries) {
|
||||
const destination = join(payload, relativePath);
|
||||
await mkdir(dirname(destination), { recursive: true });
|
||||
if (relativePath === nodeIntelligenceDescriptorPath) {
|
||||
await writeFile(destination, targetNodeIntelligenceDescriptor, {
|
||||
encoding: "utf8",
|
||||
flag: "wx",
|
||||
mode: 0o644,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
await copyFile(join(engineRoot, relativePath), destination);
|
||||
}
|
||||
await writeFile(
|
||||
|
|
@ -74,6 +102,11 @@ try {
|
|||
mcpTool: "engine_get_telemetry_reading_catalog",
|
||||
readingValuesIncluded: false,
|
||||
rawExecutionDataIncluded: false,
|
||||
nodeIntelligenceRelease: "2.33.2-974a9fb3492f",
|
||||
predecessorGatewaySha256,
|
||||
targetGatewaySha256,
|
||||
predecessorNodeIntelligenceDescriptorSha256,
|
||||
targetNodeIntelligenceDescriptorSha256,
|
||||
untouched: [
|
||||
"L2 graph",
|
||||
"n8n",
|
||||
|
|
@ -92,6 +125,7 @@ try {
|
|||
|
||||
async function assertExactSources() {
|
||||
for (const [relativePath, expected] of Object.entries(targetSha256)) {
|
||||
if (relativePath === nodeIntelligenceDescriptorPath) continue;
|
||||
const sourcePath = join(engineRoot, relativePath);
|
||||
const info = await lstat(sourcePath);
|
||||
if (!info.isFile() || info.isSymbolicLink()) {
|
||||
|
|
@ -147,6 +181,49 @@ async function assertExactSources() {
|
|||
}
|
||||
}
|
||||
|
||||
async function buildTargetNodeIntelligenceDescriptor() {
|
||||
const baselineBytes = await readFile(baselineArtifact);
|
||||
if (digest(baselineBytes) !== baselineArtifactSha256) {
|
||||
throw new Error("engine_mcp_telemetry_catalog_baseline_artifact_mismatch");
|
||||
}
|
||||
const baseline = run("tar", [
|
||||
"-xOf",
|
||||
baselineArtifact,
|
||||
`payload/${nodeIntelligenceDescriptorPath}`,
|
||||
]).stdout;
|
||||
if (
|
||||
digest(Buffer.from(baseline, "utf8"))
|
||||
!== predecessorNodeIntelligenceDescriptorSha256
|
||||
) {
|
||||
throw new Error(
|
||||
"engine_mcp_telemetry_catalog_baseline_descriptor_mismatch",
|
||||
);
|
||||
}
|
||||
const descriptor = JSON.parse(baseline);
|
||||
if (
|
||||
descriptor?.schemaVersion
|
||||
!== "nodedc.engine-node-intelligence-transition/v1"
|
||||
|| descriptor?.action !== "activate"
|
||||
|| descriptor?.releaseId !== "2.33.2-974a9fb3492f"
|
||||
|| descriptor?.source?.gatewaySha256 !== predecessorGatewaySha256
|
||||
) {
|
||||
throw new Error(
|
||||
"engine_mcp_telemetry_catalog_baseline_descriptor_contract_mismatch",
|
||||
);
|
||||
}
|
||||
descriptor.source.gatewaySha256 = targetGatewaySha256;
|
||||
const rendered = `${JSON.stringify(descriptor, null, 2)}\n`;
|
||||
if (
|
||||
digest(Buffer.from(rendered, "utf8"))
|
||||
!== targetNodeIntelligenceDescriptorSha256
|
||||
) {
|
||||
throw new Error(
|
||||
"engine_mcp_telemetry_catalog_target_descriptor_mismatch",
|
||||
);
|
||||
}
|
||||
return rendered;
|
||||
}
|
||||
|
||||
async function assertFresh(path) {
|
||||
try {
|
||||
await lstat(path);
|
||||
|
|
@ -186,4 +263,5 @@ function run(command, args) {
|
|||
if (result.status !== 0) {
|
||||
throw new Error(`${command}_failed:${result.stderr || result.stdout}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -554,10 +554,12 @@ ENGINE_MCP_TELEMETRY_CATALOG_ARTIFACT_ENTRIES = (
|
|||
"nodedc-source/server/routes/n8n.js",
|
||||
"nodedc-source/server/routes/engineAgentGateway.js",
|
||||
ENGINE_MCP_TELEMETRY_CATALOG_DESCRIPTOR_REL,
|
||||
ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL,
|
||||
)
|
||||
ENGINE_MCP_TELEMETRY_CATALOG_PREDECESSOR_SHA256 = {
|
||||
"nodedc-source/server/routes/n8n.js": "1c2427c1d5830c40b1e8ae05f3d683fc39d07d7e0fe2431b0f6efbbb1d3fcb88",
|
||||
"nodedc-source/server/routes/engineAgentGateway.js": "4f600a2781be9118bec891fa2a6f20d7f55e0892ef2f06af24059193cebd28f4",
|
||||
ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL: "63e7619c6971d02102583bb5d80d33ece293b952f113200fa0b76f0e88c2dd32",
|
||||
}
|
||||
ENGINE_MCP_TELEMETRY_CATALOG_FOUNDATION_SHA256 = {
|
||||
ENGINE_MCP_EXECUTION_PROFILE_DECODER_DESCRIPTOR_REL: "93e431902e9bcd3b828a82ed6b42b48d939051f21bf8824dafcf2addac8a711c",
|
||||
|
|
@ -566,6 +568,7 @@ ENGINE_MCP_TELEMETRY_CATALOG_TARGET_SHA256 = {
|
|||
"nodedc-source/server/routes/n8n.js": "903245ae363e9b9ac161498f17988a38876a0e0ac8d80f3fa1b0112ceb7fe906",
|
||||
"nodedc-source/server/routes/engineAgentGateway.js": "69bfc91e913a3fad04e13aca86efb9d62f73c0c7d1f8f7200907494b29fa9e8d",
|
||||
ENGINE_MCP_TELEMETRY_CATALOG_DESCRIPTOR_REL: "b25ab8b6e6ad8ac24614c4630cc3635abe453464466d5a72238b05e48a24d882",
|
||||
ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL: "25ed3efd858aaf82c242dba501f0acc0c6c3dc91e8845707a4d3325750eab59f",
|
||||
}
|
||||
ENGINE_MCP_TELEMETRY_CATALOG_NEW_PATHS = (
|
||||
ENGINE_MCP_TELEMETRY_CATALOG_DESCRIPTOR_REL,
|
||||
|
|
@ -5156,6 +5159,23 @@ def validate_engine_mcp_telemetry_catalog_slice(payload_dir, entries):
|
|||
)
|
||||
):
|
||||
die("Engine MCP telemetry catalog gateway contract mismatch")
|
||||
node_intelligence_descriptor = read_engine_node_intelligence_descriptor(
|
||||
payload_dir / ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL,
|
||||
"Engine MCP telemetry catalog node-intelligence descriptor",
|
||||
)
|
||||
if (
|
||||
node_intelligence_descriptor.get("action") != "activate"
|
||||
or node_intelligence_descriptor.get("releaseId")
|
||||
!= ENGINE_NODE_INTELLIGENCE_RELEASE_ID
|
||||
or node_intelligence_descriptor.get("source", {}).get("gatewaySha256")
|
||||
!= ENGINE_MCP_TELEMETRY_CATALOG_TARGET_SHA256[
|
||||
ENGINE_NODE_INTELLIGENCE_GATEWAY_REL
|
||||
]
|
||||
):
|
||||
die(
|
||||
"Engine MCP telemetry catalog node-intelligence attestation "
|
||||
"contract mismatch"
|
||||
)
|
||||
|
||||
|
||||
def validate_engine_agent_full_grant_migration_slice(payload_dir, entries):
|
||||
|
|
@ -5577,6 +5597,10 @@ def load_artifact(artifact, work_dir):
|
|||
and not is_engine_mcp_ontology_sdk_slice(manifest["component"], entries)
|
||||
and not is_engine_mcp_autonomy_provider_v5_slice(manifest["component"], entries)
|
||||
and not is_engine_l2_closed_loop_slice(manifest["component"], entries)
|
||||
and not is_engine_mcp_telemetry_catalog_slice(
|
||||
manifest["component"],
|
||||
entries,
|
||||
)
|
||||
and not is_engine_provider_authority_diagnostics_slice(
|
||||
manifest["component"],
|
||||
entries,
|
||||
|
|
@ -9113,6 +9137,9 @@ def plan_artifact(artifact):
|
|||
print("engine_mcp_tool=engine_get_telemetry_reading_catalog")
|
||||
print("engine_mcp_reading_values_included=no")
|
||||
print("engine_mcp_raw_execution_data_included=no")
|
||||
print("node_intelligence_descriptor=attested-gateway-successor")
|
||||
print("node_intelligence_release=2.33.2-974a9fb3492f")
|
||||
print("node_intelligence_image=preserved")
|
||||
for foundation_path, foundation_sha256 in (
|
||||
mcp_telemetry_catalog_preflight["foundation_sha256"].items()
|
||||
):
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ class EngineMcpTelemetryCatalogTest(unittest.TestCase):
|
|||
for relative_path, expected in (
|
||||
RUNNER.ENGINE_MCP_TELEMETRY_CATALOG_TARGET_SHA256.items()
|
||||
):
|
||||
if relative_path == RUNNER.ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL:
|
||||
continue
|
||||
path = ENGINE_ROOT / relative_path
|
||||
if (
|
||||
not path.is_file()
|
||||
|
|
@ -114,6 +116,27 @@ class EngineMcpTelemetryCatalogTest(unittest.TestCase):
|
|||
("http://127.0.0.1:3001/health",),
|
||||
)
|
||||
RUNNER.validate_engine_mcp_telemetry_catalog_slice(payload, entries)
|
||||
node_intelligence_descriptor = json.loads(
|
||||
(
|
||||
payload / RUNNER.ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL
|
||||
).read_text(encoding="utf-8")
|
||||
)
|
||||
self.assertEqual(
|
||||
node_intelligence_descriptor["source"]["gatewaySha256"],
|
||||
RUNNER.ENGINE_MCP_TELEMETRY_CATALOG_TARGET_SHA256[
|
||||
RUNNER.ENGINE_NODE_INTELLIGENCE_GATEWAY_REL
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
hashlib.sha256(
|
||||
(
|
||||
payload / RUNNER.ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL
|
||||
).read_bytes()
|
||||
).hexdigest(),
|
||||
RUNNER.ENGINE_MCP_TELEMETRY_CATALOG_TARGET_SHA256[
|
||||
RUNNER.ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL
|
||||
],
|
||||
)
|
||||
with tempfile.TemporaryDirectory(
|
||||
prefix="nodedc-engine-mcp-telemetry-load-"
|
||||
) as load_directory:
|
||||
|
|
@ -238,6 +261,11 @@ class EngineMcpTelemetryCatalogTest(unittest.TestCase):
|
|||
plan,
|
||||
)
|
||||
self.assertIn("engine_mcp_reading_values_included=no", plan)
|
||||
self.assertIn(
|
||||
"node_intelligence_descriptor=attested-gateway-successor",
|
||||
plan,
|
||||
)
|
||||
self.assertIn("node_intelligence_image=preserved", plan)
|
||||
self.assertIn("mcp_nginx=untouched", plan)
|
||||
self.assertIn("embedded_ai_workspace=untouched", plan)
|
||||
self.assertIn(
|
||||
|
|
@ -298,6 +326,10 @@ class EngineMcpTelemetryCatalogTest(unittest.TestCase):
|
|||
)
|
||||
with (
|
||||
mock.patch.object(RUNNER, "component_root", return_value=ENGINE_ROOT),
|
||||
mock.patch.object(
|
||||
RUNNER,
|
||||
"validate_engine_mcp_telemetry_catalog_slice",
|
||||
),
|
||||
mock.patch.object(
|
||||
RUNNER,
|
||||
"engine_backend_container_id",
|
||||
|
|
|
|||
Loading…
Reference in New Issue