From 8cc917508e3fb3fd6e57db44a967e1ac37ccd48c Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 24 Jul 2026 09:32:02 +0300 Subject: [PATCH] feat(deploy): package classified subject aspect transition --- ...engine-mcp-classified-aspects-artifact.mjs | 186 ++++++++++++++++++ ...rnal-data-plane-unit-contacts-artifact.mjs | 129 ++++++++++++ ...le-foundry-classified-aspects-artifact.mjs | 170 ++++++++++++++++ 3 files changed, 485 insertions(+) create mode 100644 infra/deploy-runner/build-engine-mcp-classified-aspects-artifact.mjs create mode 100644 infra/deploy-runner/build-external-data-plane-unit-contacts-artifact.mjs create mode 100644 infra/deploy-runner/build-module-foundry-classified-aspects-artifact.mjs diff --git a/infra/deploy-runner/build-engine-mcp-classified-aspects-artifact.mjs b/infra/deploy-runner/build-engine-mcp-classified-aspects-artifact.mjs new file mode 100644 index 0000000..27b3b3a --- /dev/null +++ b/infra/deploy-runner/build-engine-mcp-classified-aspects-artifact.mjs @@ -0,0 +1,186 @@ +#!/usr/bin/env node + +import { createHash } from "node:crypto"; +import { spawnSync } from "node:child_process"; +import { copyFile, lstat, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const workspaceRoot = resolve(scriptDir, "../../.."); +const engineRoot = resolve( + process.env.NODEDC_ENGINE_SOURCE_ROOT + || join(workspaceRoot, "NODEDC_ENGINE_INFRA"), +); +const artifactDir = resolve( + process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts"), +); +const [patchId = "engine-mcp-classified-aspects-20260724-039", ...extra] = + process.argv.slice(2); + +if (extra.length || !/^engine-mcp-classified-aspects-\d{8}-\d{3}$/.test(patchId)) { + throw new Error( + "usage: build-engine-mcp-classified-aspects-artifact.mjs " + + "[engine-mcp-classified-aspects-YYYYMMDD-NNN]", + ); +} + +const files = Object.freeze([ + "nodedc-source/server/l2ExecutionPlan/compiler.js", + "nodedc-source/server/assets/execution-plans/v1/catalog.json", + "nodedc-source/server/assets/provider-packages/v1/catalog.json", +]); +const expectedSha256 = Object.freeze({ + "nodedc-source/server/l2ExecutionPlan/compiler.js": + "4854a62fb44cb3dd715f2cb8728740575453a666f5c31abe9d41bc2562be5e95", + "nodedc-source/server/assets/execution-plans/v1/catalog.json": + "2e70b607b4a347592ce2f4732f6da0781ac94ec4829ac336021cb501fdafe9aa", + "nodedc-source/server/assets/provider-packages/v1/catalog.json": + "2fce1c623a8acf9c01a43463e6d38eda71ad28379a2d91b2ca40f31f8dccf3c6", +}); +const artifact = join(artifactDir, `nodedc-${patchId}.tgz`); +const checksum = `${artifact}.sha256`; +const stage = await mkdtemp(join(tmpdir(), "nodedc-engine-classified-aspects-")); + +await assertFresh(artifact); +await assertEngineBoundary(); + +try { + for (const relativePath of files) { + const source = join(engineRoot, relativePath); + const info = await lstat(source); + if (!info.isFile() || info.isSymbolicLink()) { + throw new Error(`source_file_rejected:${relativePath}`); + } + const destination = join(stage, "payload", relativePath); + await mkdir(dirname(destination), { recursive: true }); + await copyFile(source, destination); + } + await writeFile( + join(stage, "manifest.env"), + `id=${patchId}\ncomponent=engine\ntype=app-overlay\n`, + "utf8", + ); + await writeFile(join(stage, "files.txt"), `${files.join("\n")}\n`, "utf8"); + await mkdir(artifactDir, { recursive: true }); + run("python3", ["-c", canonicalTarScript(), artifact, stage]); + const sha256 = digest(await readFile(artifact)); + await writeFile(checksum, `${sha256} ${artifact.split("/").at(-1)}\n`, "utf8"); + console.log(JSON.stringify({ + ok: true, + patchId, + artifact, + checksum, + sha256, + services: ["nodedc-backend", "app"], + engineCorePolicy: "provider-neutral-scalar-visibility-guards", + providerPackage: "gelios.provider.v10", + files, + expectedSha256, + untouched: [ + "live L2 graphs", + "n8n L1", + "Engine UI source and dist", + "node-intelligence", + "databases", + "credentials", + "embedded Codex", + ], + }, null, 2)); +} finally { + await rm(stage, { recursive: true, force: true }); +} + +async function assertEngineBoundary() { + for (const relativePath of files) { + const actual = digest(await readFile(join(engineRoot, relativePath))); + if (actual !== expectedSha256[relativePath]) { + throw new Error( + `classified_aspect_target_mismatch:${relativePath}:` + + `expected=${expectedSha256[relativePath]}:actual=${actual}`, + ); + } + } + + const compiler = await readFile(join(engineRoot, files[0]), "utf8"); + if ( + !compiler.includes("guardMatches") + || !compiler.includes("'1.3.0'") + || /gelios|fleet\.units\.contacts/i.test(compiler) + ) { + throw new Error("classified_aspect_compiler_boundary_invalid"); + } + + const executionCatalog = JSON.parse( + await readFile(join(engineRoot, files[1]), "utf8"), + ); + const providerPackage = executionCatalog?.packages?.find( + (entry) => entry?.id === "gelios.provider.v10", + ); + if ( + providerPackage?.version !== "10.0.0" + || !executionCatalog?.runtime?.compilerVersions?.includes("1.3.0") + || !providerPackage?.profiles?.some( + (profile) => + profile?.dataProductId === "fleet.units.contacts.current.v1", + ) + ) { + throw new Error("classified_aspect_execution_catalog_invalid"); + } + + const securityCatalog = JSON.parse( + await readFile(join(engineRoot, files[2]), "utf8"), + ); + const securityPackage = securityCatalog?.packages?.find( + (entry) => entry?.id === "gelios.provider.v10", + ); + if ( + securityPackage?.version !== "10.0.0" + || securityPackage?.capabilities?.length !== 1 + || JSON.stringify(securityPackage.capabilities[0]?.dataProductIds) + !== JSON.stringify(["fleet.units.contacts.current.v1"]) + ) { + throw new Error("classified_aspect_security_catalog_invalid"); + } +} + +async function assertFresh(path) { + try { + await lstat(path); + } catch (error) { + if (error?.code === "ENOENT") return; + throw error; + } + throw new Error("artifact_already_exists"); +} + +function canonicalTarScript() { + return [ + "import gzip,io,pathlib,sys,tarfile", + "root=pathlib.Path(sys.argv[2])", + "with open(sys.argv[1],'wb') as out:", + " with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:", + " with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:", + " for top in ('manifest.env','files.txt','payload'):", + " p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])", + " for x in paths:", + " info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix())", + " info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644", + " with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)", + ].join("\n"); +} + +function digest(value) { + return createHash("sha256").update(value).digest("hex"); +} + +function run(command, args) { + const result = spawnSync(command, args, { + encoding: "utf8", + maxBuffer: 64 * 1024 * 1024, + }); + if (result.status !== 0) { + throw new Error(`${command}_failed:${result.stderr || result.stdout}`); + } +} diff --git a/infra/deploy-runner/build-external-data-plane-unit-contacts-artifact.mjs b/infra/deploy-runner/build-external-data-plane-unit-contacts-artifact.mjs new file mode 100644 index 0000000..e8ef363 --- /dev/null +++ b/infra/deploy-runner/build-external-data-plane-unit-contacts-artifact.mjs @@ -0,0 +1,129 @@ +#!/usr/bin/env node + +import { createHash } from "node:crypto"; +import { spawnSync } from "node:child_process"; +import { copyFile, lstat, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const platformRoot = resolve(scriptDir, "../.."); +const artifactDir = resolve( + process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts"), +); +const [patchId = "external-data-plane-unit-contacts-20260724-014", ...extra] = + process.argv.slice(2); + +if (extra.length || !/^external-data-plane-unit-contacts-\d{8}-\d{3}$/.test(patchId)) { + throw new Error( + "usage: build-external-data-plane-unit-contacts-artifact.mjs " + + "[external-data-plane-unit-contacts-YYYYMMDD-NNN]", + ); +} + +const sourceRelative = + "services/external-data-plane/definitions/fleet.units.contacts.current.v1.json"; +const destinationRelative = `platform/${sourceRelative}`; +const artifact = join(artifactDir, `nodedc-${patchId}.tgz`); +const checksum = `${artifact}.sha256`; +const stage = await mkdtemp(join(tmpdir(), "nodedc-edp-unit-contacts-artifact-")); + +await assertFresh(artifact); +await assertDefinition(); + +try { + const payloadFile = join(stage, "payload", destinationRelative); + await mkdir(dirname(payloadFile), { recursive: true }); + await copyFile(join(platformRoot, sourceRelative), payloadFile); + await writeFile( + join(stage, "manifest.env"), + `id=${patchId}\ncomponent=platform\ntype=app-overlay\n`, + "utf8", + ); + await writeFile(join(stage, "files.txt"), `${destinationRelative}\n`, "utf8"); + await mkdir(artifactDir, { recursive: true }); + run("python3", ["-c", canonicalTarScript(), artifact, stage]); + const sha256 = digest(await readFile(artifact)); + await writeFile(checksum, `${sha256} ${artifact.split("/").at(-1)}\n`, "utf8"); + console.log(JSON.stringify({ + ok: true, + patchId, + artifact, + checksum, + sha256, + services: ["external-data-plane"], + files: [destinationRelative], + }, null, 2)); +} finally { + await rm(stage, { recursive: true, force: true }); +} + +async function assertDefinition() { + const definition = JSON.parse( + await readFile(join(platformRoot, sourceRelative), "utf8"), + ); + if ( + definition?.id !== "fleet.units.contacts.current.v1" + || definition?.version !== "1.0.0" + || definition?.ontologyRevision !== "ontology.map.moving_object.v3" + || JSON.stringify(definition?.semanticTypes) + !== JSON.stringify(["map.moving_object"]) + || JSON.stringify(definition?.fields) !== JSON.stringify([ + "device_imei", + "device_phone_primary", + "device_phone_secondary", + "display_name", + "provider_creator_login", + ]) + ) { + throw new Error("unit_contacts_definition_contract_invalid"); + } + if ( + /(token|secret|password|authorization|decrypt|raw_provider_payload)/i.test( + JSON.stringify(definition), + ) + ) { + throw new Error("unit_contacts_definition_secret_boundary_violation"); + } +} + +async function assertFresh(path) { + try { + await lstat(path); + } catch (error) { + if (error?.code === "ENOENT") return; + throw error; + } + throw new Error("artifact_already_exists"); +} + +function canonicalTarScript() { + return [ + "import gzip,io,pathlib,sys,tarfile", + "root=pathlib.Path(sys.argv[2])", + "with open(sys.argv[1],'wb') as out:", + " with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:", + " with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:", + " for top in ('manifest.env','files.txt','payload'):", + " p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])", + " for x in paths:", + " info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix())", + " info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644", + " with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)", + ].join("\n"); +} + +function digest(value) { + return createHash("sha256").update(value).digest("hex"); +} + +function run(command, args) { + const result = spawnSync(command, args, { + encoding: "utf8", + maxBuffer: 64 * 1024 * 1024, + }); + if (result.status !== 0) { + throw new Error(`${command}_failed:${result.stderr || result.stdout}`); + } +} diff --git a/infra/deploy-runner/build-module-foundry-classified-aspects-artifact.mjs b/infra/deploy-runner/build-module-foundry-classified-aspects-artifact.mjs new file mode 100644 index 0000000..b8e82d6 --- /dev/null +++ b/infra/deploy-runner/build-module-foundry-classified-aspects-artifact.mjs @@ -0,0 +1,170 @@ +#!/usr/bin/env node + +import { createHash } from "node:crypto"; +import { spawnSync } from "node:child_process"; +import { copyFile, lstat, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const workspaceRoot = resolve(scriptDir, "../../.."); +const foundryRoot = resolve(workspaceRoot, "NODEDC_DESIGN_GUIDELINE"); +const artifactDir = resolve( + process.env.NODEDC_DEPLOY_ARTIFACT_DIR || resolve(scriptDir, "../deploy-artifacts"), +); +const [patchId = "module-foundry-classified-aspects-20260724-010", ...extra] = + process.argv.slice(2); + +if (extra.length || !/^module-foundry-classified-aspects-\d{8}-\d{3}$/.test(patchId)) { + throw new Error( + "usage: build-module-foundry-classified-aspects-artifact.mjs " + + "[module-foundry-classified-aspects-YYYYMMDD-NNN]", + ); +} + +const files = Object.freeze([ + "apps/catalog/src/MapFixturePreview.tsx", + "apps/catalog/src/mapSubjectCard.mjs", + "apps/catalog/src/useMapDataProductRuntime.ts", + "registry/data-product-consumer-policies.json", + "registry/schemas/application-manifest-v0.1.schema.json", + "server/catalog-server.mjs", + "server/foundry-data-product-consumer.mjs", + "server/foundry-mcp.mjs", + "server/map-subject-detail-profile.mjs", +]); +const artifact = join(artifactDir, `nodedc-${patchId}.tgz`); +const checksum = `${artifact}.sha256`; +const stage = await mkdtemp(join(tmpdir(), "nodedc-foundry-classified-aspects-")); + +await assertFresh(artifact); +await assertClassifiedAspectBoundary(); + +try { + for (const relativePath of files) { + const source = join(foundryRoot, relativePath); + const info = await lstat(source); + if (!info.isFile() || info.isSymbolicLink()) { + throw new Error(`source_file_rejected:${relativePath}`); + } + const destination = join(stage, "payload", relativePath); + await mkdir(dirname(destination), { recursive: true }); + await copyFile(source, destination); + } + await writeFile( + join(stage, "manifest.env"), + `id=${patchId}\ncomponent=module-foundry\ntype=app-overlay\n`, + "utf8", + ); + await writeFile(join(stage, "files.txt"), `${files.join("\n")}\n`, "utf8"); + await mkdir(artifactDir, { recursive: true }); + run("python3", ["-c", canonicalTarScript(), artifact, stage]); + const sha256 = digest(await readFile(artifact)); + await writeFile(checksum, `${sha256} ${artifact.split("/").at(-1)}\n`, "utf8"); + console.log(JSON.stringify({ + ok: true, + patchId, + artifact, + checksum, + sha256, + services: ["nodedc-module-foundry"], + policy: "provider-neutral-data-class", + files, + }, null, 2)); +} finally { + await rm(stage, { recursive: true, force: true }); +} + +async function assertClassifiedAspectBoundary() { + const profile = await readFile( + join(foundryRoot, "server/map-subject-detail-profile.mjs"), + "utf8", + ); + const consumer = await readFile( + join(foundryRoot, "server/foundry-data-product-consumer.mjs"), + "utf8", + ); + const card = await readFile( + join(foundryRoot, "apps/catalog/src/mapSubjectCard.mjs"), + "utf8", + ); + const gateway = await readFile(join(foundryRoot, "server/foundry-mcp.mjs"), "utf8"); + for (const [name, source] of [ + ["profile", profile], + ["consumer", consumer], + ["card", card], + ["gateway", gateway], + ]) { + if (!source.includes("dataClass")) { + throw new Error(`classified_aspect_marker_missing:${name}:dataClass`); + } + } + if ( + /gelios\.provider|fleet\.units\.contacts/i.test(profile) + || /gelios\.provider|fleet\.units\.contacts/i.test(card) + || /gelios\.provider|fleet\.units\.contacts/i.test(gateway) + ) { + throw new Error("classified_aspect_ui_or_gateway_provider_hardcode"); + } + + const policies = JSON.parse( + await readFile( + join(foundryRoot, "registry/data-product-consumer-policies.json"), + "utf8", + ), + ); + const matches = policies?.policies?.filter( + (policy) => + policy?.dataProductId === "fleet.units.contacts.current.v1" + && policy?.productVersion === "1.0.0", + ) || []; + if ( + matches.length !== 1 + || matches[0]?.dataClass !== "restricted" + || matches[0]?.consumerContract?.ontologyRevision + !== "ontology.map.moving_object.v3" + ) { + throw new Error("classified_aspect_consumer_policy_invalid"); + } +} + +async function assertFresh(path) { + try { + await lstat(path); + } catch (error) { + if (error?.code === "ENOENT") return; + throw error; + } + throw new Error("artifact_already_exists"); +} + +function canonicalTarScript() { + return [ + "import gzip,io,pathlib,sys,tarfile", + "root=pathlib.Path(sys.argv[2])", + "with open(sys.argv[1],'wb') as out:", + " with gzip.GzipFile(filename='',mode='wb',fileobj=out,compresslevel=9,mtime=0) as gz:", + " with tarfile.open(fileobj=gz,mode='w',format=tarfile.PAX_FORMAT) as tar:", + " for top in ('manifest.env','files.txt','payload'):", + " p=root/top; paths=[p]+(sorted(p.rglob('*')) if p.is_dir() else [])", + " for x in paths:", + " info=tar.gettarinfo(str(x),arcname=x.relative_to(root).as_posix())", + " info.uid=info.gid=0; info.uname=info.gname='root'; info.mtime=0; info.mode=0o755 if info.isdir() else 0o644", + " with (open(x,'rb') if info.isfile() else io.BytesIO()) as src: tar.addfile(info,src if info.isfile() else None)", + ].join("\n"); +} + +function digest(value) { + return createHash("sha256").update(value).digest("hex"); +} + +function run(command, args) { + const result = spawnSync(command, args, { + encoding: "utf8", + maxBuffer: 64 * 1024 * 1024, + }); + if (result.status !== 0) { + throw new Error(`${command}_failed:${result.stderr || result.stdout}`); + } +}