#!/usr/bin/env python3 import argparse import array import base64 import csv import fcntl import grp import hashlib import http.client import io import json import os import pwd import re import secrets import shutil import signal import socket import sqlite3 import stat import subprocess import sys import tarfile import tempfile import time import urllib.error import urllib.request from datetime import datetime, timezone from pathlib import Path, PurePosixPath EXPECTED_SELF = Path("/usr/local/sbin/nodedc-deploy") DEPLOY_ROOT = Path("/volume1/docker/nodedc-deploy") INBOX = DEPLOY_ROOT / "inbox" APPLIED_DIR = DEPLOY_ROOT / "applied" FAILED_DIR = DEPLOY_ROOT / "failed" BACKUPS_DIR = DEPLOY_ROOT / "backups" STATE_DIR = DEPLOY_ROOT / "state" TMP_DIR = DEPLOY_ROOT / ".tmp" RUNTIME_DIR = DEPLOY_ROOT / "runtime" STATE_FILE = STATE_DIR / "applied.jsonl" FAILED_STATE_FILE = STATE_DIR / "failed.jsonl" LOCK_DIR = STATE_DIR / "deploy.lock" DOCKER = Path("/usr/local/bin/docker") MAP_GATEWAY_SECRET_DIR = Path("/volume1/docker/nodedc-platform/secrets") MAP_GATEWAY_SECRET_FILE = MAP_GATEWAY_SECRET_DIR / "map-gateway-admin-secret" MAP_EGRESS_PROXY_SECRET_FILE = MAP_GATEWAY_SECRET_DIR / "map-egress-proxy-token" PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE = ( MAP_GATEWAY_SECRET_DIR / "device-core-internal-token" ) PLATFORM_DEVICE_CORE_HUB_TRUST_REL = ( "platform/deployment/device-core-hub-trust-v1.json" ) PLATFORM_DEVICE_CORE_HUB_TRUST_ENTRIES = ( "platform/docker-compose.platform-http.yml", PLATFORM_DEVICE_CORE_HUB_TRUST_REL, ) PLATFORM_DEVICE_MANAGER_PUBLIC_ROUTE_REL = ( "platform/deployment/device-manager-public-route-v1.json" ) PLATFORM_DEVICE_MANAGER_PUBLIC_ROUTE_ENTRIES = ( "platform/Caddyfile.http", PLATFORM_DEVICE_MANAGER_PUBLIC_ROUTE_REL, ) LAUNCHER_DEVICE_CORE_SESSION_ENTRIES = ( "server/control-plane-store.mjs", "server/dev-server.mjs", "server/device-core-session-access.mjs", "server/internal-request-auth.mjs", "src/shared/api/adminApi.ts", ) PROXY_CONTUR_ENV_FILE = Path("/volume1/docker/proxy-contur/.env") DC_AMD_PROXY_RUNTIME_DIR = Path("/volume1/docker/dc-amd-proxy/runtime") DEVICE_PLANE_ROOT = Path("/volume1/docker/nodedc-device-plane") DEVICE_PLANE_SECRET_DIR = DEVICE_PLANE_ROOT / "secrets" DEVICE_PLANE_MANAGER_DATA_DIR = DEVICE_PLANE_ROOT / "data" / "device-manager" DEVICE_PLANE_MANAGER_DATA_CONTAINER_DIR = "/var/lib/nodedc-device-manager" DEVICE_PLANE_MANAGER_PRESENTATION_PATH = ( f"{DEVICE_PLANE_MANAGER_DATA_CONTAINER_DIR}/device-manager-presentation.json" ) DEVICE_PLANE_MANAGER_MEDIA_ROOT = ( f"{DEVICE_PLANE_MANAGER_DATA_CONTAINER_DIR}/media" ) DEVICE_PLANE_POSTGRES_PASSWORD_FILE = DEVICE_PLANE_SECRET_DIR / "postgres-password" DEVICE_PLANE_GATEWAY_CORE_TOKEN_FILE = DEVICE_PLANE_SECRET_DIR / "gateway-core-token" DEVICE_PLANE_IDENTIFIER_PEPPER_FILE = DEVICE_PLANE_SECRET_DIR / "identifier-pepper" DEVICE_PLANE_MANAGEMENT_CORE_TOKEN_FILE = ( DEVICE_PLANE_SECRET_DIR / "management-core-token" ) DEVICE_PLANE_EDGE_CHANNEL_SECRET_DIR = ( DEVICE_PLANE_SECRET_DIR / "device-edge-channel" ) DEVICE_PLANE_EDGE_CHANNEL_CORE_PRIVATE_KEY_FILE = ( DEVICE_PLANE_EDGE_CHANNEL_SECRET_DIR / "core-private-key.pem" ) DEVICE_PLANE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE = ( DEVICE_PLANE_EDGE_CHANNEL_SECRET_DIR / "core-certificate.pem" ) DEVICE_PLANE_EDGE_CHANNEL_PEER_TRUST_DIR = ( DEVICE_PLANE_EDGE_CHANNEL_SECRET_DIR / "peers" ) DEVICE_PLANE_EDGE_CHANNEL_RECOVERY_DIR = ( DEVICE_PLANE_EDGE_CHANNEL_SECRET_DIR / "recovery" ) DEVICE_PLANE_EDGE_CHANNEL_EXPORT_DIR = ( DEVICE_PLANE_ROOT / "enrollment" / "device-edge-channel" ) DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_CERTIFICATE_FILE = ( DEVICE_PLANE_EDGE_CHANNEL_EXPORT_DIR / "core-certificate.pem" ) DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_FINGERPRINT_FILE = ( DEVICE_PLANE_EDGE_CHANNEL_EXPORT_DIR / "core-certificate.sha256" ) DEVICE_PLANE_CONTROL_CORE_IMAGE = "nodedc/device-control-core:local" DEVICE_PLANE_GATEWAY_IMAGE = "nodedc/device-gateway:local" DEVICE_PLANE_MANAGER_IMAGE = "nodedc/device-manager:local" DEVICE_PLANE_BACKHAUL_TARGET_IMAGE = "nodedc/device-backhaul-target:local" DEVICE_PLANE_BACKHAUL_FAILED_TARGET_REL = ( "deployment/device-plane-backhaul-target-v1.json" ) DEVICE_PLANE_BACKHAUL_TARGET_REL = ( "deployment/device-plane-backhaul-target-tailnet-serve-v1.json" ) DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL = ( "docker-compose.device-plane.backhaul-target.yml" ) DEVICE_PLANE_BACKHAUL_TARGET_ENTRIES = ( DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL, "services/device-backhaul-target", DEVICE_PLANE_BACKHAUL_TARGET_REL, ) DEVICE_PLANE_BACKHAUL_FAILED_TARGET_ENTRIES = ( DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL, "services/device-backhaul-target", DEVICE_PLANE_BACKHAUL_FAILED_TARGET_REL, ) DEVICE_PLANE_BACKHAUL_TARGET_PREDECESSOR_PATCH_ID = ( "device-plane-b2-discovery-loopback-20260803-006" ) DEVICE_PLANE_BACKHAUL_TARGET_PREDECESSOR_ARTIFACT_SHA256 = ( "25f9e9e55e283e9b7bb5e128ff14a244f848b1c063acca9724a23206131c9adf" ) DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_SHA256 = ( "e10807cb3bab4c1d7ae2ac8ce4af1936717036930bb0b1c6f8f2b89c3f82b7c0" ) DEVICE_PLANE_BACKHAUL_FAILED_PATCH_ID = ( "device-plane-backhaul-target-20260803-001" ) DEVICE_PLANE_BACKHAUL_FAILED_ARTIFACT_SHA256 = ( "ed0bda4110a756c32be68990e2e0f647409d5a77eec7e26c18502bafbdc1bb76" ) DEVICE_PLANE_BACKHAUL_FAILED_ARTIFACT = ( "nodedc-device-plane-device-plane-backhaul-target-20260803-001.tgz." "20260804-035519" ) DEVICE_PLANE_BACKHAUL_FAILED_BACKUP_ID = ( "device-plane-device-plane-backhaul-target-20260803-001-" "20260804-035519" ) DEVICE_PLANE_BACKHAUL_FAILED_BACKUP_SHA256 = { "manifest.env": ( "7b26cf56c52723d9ce559d9f9216757038e4d3aa812821b5f05046bfcf8c01eb" ), "files.txt": ( "761b9673fddb27558873a72f182d50ca46b2fa6808cbbeb03b40dba197a7586a" ), "source-before.tgz": ( "b008e5d971527a7e4fccda314d677b6a6583e1a0a5e293cdc937fa2b8f19e10e" ), "existing-files.txt": ( "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ), "missing-files.txt": ( "761b9673fddb27558873a72f182d50ca46b2fa6808cbbeb03b40dba197a7586a" ), "runtime-before.json": ( "d45c54ebb286af8850b478b07410dd4ece1135aa9717cd8f0d4aa9c94c56e3d7" ), } DEVICE_PLANE_BACKHAUL_FAILED_MESSAGE = ( "container healthcheck failed for " "2b6b1b324368421acee9b0945b18371bc500ece4ce99b2ff12da7da4ab157e49: " "unhealthy" ) DEVICE_PLANE_BACKHAUL_LOOPBACK_ADDRESS = "127.0.0.1" DEVICE_PLANE_BACKHAUL_LISTEN_PORT = 2222 DEVICE_PLANE_BACKHAUL_TAILNET_ADDRESS = "100.109.216.21" DEVICE_PLANE_BACKHAUL_TAILSCALE_SERVE_TARGET = "tcp://127.0.0.1:2222" DEVICE_PLANE_TAILSCALE = Path( "/var/packages/Tailscale/target/bin/tailscale" ) DEVICE_PLANE_TAILSCALE_PRIVILEGE = Path( "/var/packages/Tailscale/conf/privilege" ) DEVICE_PLANE_TAILSCALE_USER = "tailscale" DEVICE_PLANE_TAILSCALE_GROUP = "tailscale" DEVICE_PLANE_BACKHAUL_PERMITTED_TARGET = "127.0.0.1:9921" DEVICE_PLANE_BACKHAUL_ENROLLMENT_DIR = DEVICE_PLANE_ROOT / "enrollment" DEVICE_PLANE_BACKHAUL_ENROLLMENT_PUBLIC_KEY_FILE = ( DEVICE_PLANE_BACKHAUL_ENROLLMENT_DIR / "device-edge-backhaul.pub" ) DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_PUBLIC_KEY_FILE = ( DEVICE_PLANE_BACKHAUL_ENROLLMENT_DIR / "device-edge-vps-backhaul.pub" ) DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_REL = ( "deployment/device-plane-backhaul-vps-enrollment-v1.json" ) DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_ENTRIES = ( DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_REL, ) DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_PREDECESSOR_PATCH_ID = ( "device-plane-backhaul-target-tailnet-serve-20260804-002" ) DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_PREDECESSOR_ARTIFACT_SHA256 = ( "219408705dd4d80a962ed00eeb53a69df0b9ab6458443734d5c9cd1d1f795eba" ) DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_FINGERPRINT = ( "SHA256:HHTiDYiCRxSiKjBLCip6JMSzGfLGrDz5g8SIkosJcVw" ) DEVICE_PLANE_BACKHAUL_AUTHORIZED_KEYS_BACKUP = ( "device-plane-backhaul-authorized-keys-before" ) DEVICE_PLANE_BACKHAUL_SECRET_DIR = DEVICE_PLANE_SECRET_DIR / "backhaul-target" DEVICE_PLANE_BACKHAUL_HOST_KEY_FILE = ( DEVICE_PLANE_BACKHAUL_SECRET_DIR / "ssh_host_ed25519_key" ) DEVICE_PLANE_BACKHAUL_AUTHORIZED_KEYS_FILE = ( DEVICE_PLANE_BACKHAUL_SECRET_DIR / "authorized_keys" ) DEVICE_PLANE_BACKHAUL_TRUST_DIR = DEVICE_PLANE_ROOT / "trust" / "backhaul-target" DEVICE_PLANE_BACKHAUL_HOST_PUBLIC_KEY_FILE = ( DEVICE_PLANE_BACKHAUL_TRUST_DIR / "ssh_host_ed25519_key.pub" ) DEVICE_PLANE_POSTGRES_BOOTSTRAP_REL = ( "deployment/device-postgres-bootstrap-v1.json" ) DEVICE_PLANE_POSTGRES_BOOTSTRAP_ENTRIES = ( "docker-compose.device-plane.yml", DEVICE_PLANE_POSTGRES_BOOTSTRAP_REL, ) DEVICE_PLANE_POSTGRES_VOLUME = "nodedc-device-plane-postgres-data" DEVICE_PLANE_FOUNDATION_ENTRIES = ( ".dockerignore", "package.json", "package-lock.json", "docker-compose.device-plane.yml", "packages/device-protocol-contract", "packages/arusnavi-b2-adapter", "services/device-control-core", "services/device-gateway", ) DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_REL = ( "deployment/device-manager-control-plane-v1.json" ) DEVICE_PLANE_MANAGER_V2_CONTROL_PLANE_REL = ( "deployment/device-manager-control-plane-v2.json" ) DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL = ( "deployment/device-manager-release-v1.json" ) DEVICE_PLANE_MANAGER_RELEASE_V2_REL = ( "deployment/device-manager-release-v2.json" ) DEVICE_PLANE_MANAGER_RELEASE_V3_REL = ( "deployment/device-manager-release-v3.json" ) DEVICE_PLANE_MANAGER_RELEASE_V4_REL = ( "deployment/device-manager-release-v4.json" ) DEVICE_PLANE_MANAGER_RELEASE_V5_REL = ( "deployment/device-manager-release-v5.json" ) DEVICE_PLANE_MANAGER_RELEASE_V6_REL = ( "deployment/device-manager-release-v6.json" ) DEVICE_PLANE_MANAGER_RELEASE_V7_REL = ( "deployment/device-manager-release-v7.json" ) DEVICE_PLANE_MANAGER_RELEASE_V8_REL = ( "deployment/device-manager-release-v8.json" ) DEVICE_PLANE_MANAGER_RELEASE_V9_REL = ( "deployment/device-manager-release-v9.json" ) DEVICE_PLANE_MANAGER_RELEASE_V10_REL = ( "deployment/device-manager-release-v10.json" ) DEVICE_PLANE_MANAGER_RELEASE_V11_REL = ( "deployment/device-manager-release-v11.json" ) DEVICE_PLANE_MANAGER_RELEASE_V12_REL = ( "deployment/device-manager-release-v12.json" ) DEVICE_PLANE_MANAGER_RELEASE_V13_REL = ( "deployment/device-manager-release-v13.json" ) DEVICE_PLANE_MANAGER_COMPOSE_REL = "docker-compose.device-manager.yml" DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL = ( "deployment/device-edge-core-channel-bootstrap-v1.json" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL = ( "deployment/device-edge-core-channel-upgrade-v1.json" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL = ( "deployment/device-edge-core-channel-upgrade-v2.json" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_REL = ( "deployment/device-edge-core-channel-upgrade-v4.json" ) DEVICE_PLANE_CONTROL_CORE_RELEASE_REL = ( "deployment/device-control-core-release-v1.json" ) DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_REL = ( "deployment/device-control-core-release-v2.json" ) DEVICE_PLANE_CONTROL_CORE_RELEASE_V3_REL = ( "deployment/device-control-core-release-v3.json" ) DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_REL = ( "deployment/device-control-core-release-v4.json" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL = ( "docker-compose.device-edge-core-channel.yml" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_SHA256 = ( "cac1e06b21202d8d96f5694b1adb1e67c0a3cdd31fcfca56cb7e19839c8516d8" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_PREDECESSOR_BASE_COMPOSE_SHA256 = ( "eb1018cc0ffeaa0c019944d8810e2daa01eeca4c06289efff175785fb16457e7" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_BASE_COMPOSE_SHA256 = ( "6a4c08313cc97bbfd86c4e133c6b2e55ba18a499a3bb6f267c7059754f83e8cf" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_ENTRIES = ( ".dockerignore", "package.json", "package-lock.json", DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL, "packages/device-protocol-contract", "packages/device-edge-channel-contract", "services/device-control-core", DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL, ) DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_ENTRIES = ( ".dockerignore", "package.json", "package-lock.json", DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL, "packages/device-protocol-contract", "packages/device-edge-channel-contract", "services/device-control-core", DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL, ) DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_ENTRIES = ( ".dockerignore", "package.json", "package-lock.json", DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL, "packages/device-protocol-contract", "packages/device-edge-channel-contract", "services/device-control-core", DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL, ) DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_ENTRIES = ( ".dockerignore", "package.json", "package-lock.json", "docker-compose.device-plane.yml", "packages/device-protocol-contract", "packages/device-edge-channel-contract", "services/device-control-core", DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_REL, ) DEVICE_PLANE_CONTROL_CORE_RELEASE_ENTRIES = ( ".dockerignore", "package.json", "package-lock.json", "packages/device-protocol-contract", "packages/device-edge-channel-contract", "services/device-control-core", DEVICE_PLANE_CONTROL_CORE_RELEASE_REL, ) DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES = ( ".dockerignore", "package.json", "package-lock.json", "packages/device-protocol-contract", "packages/device-edge-channel-contract", "services/device-control-core", DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_REL, ) DEVICE_PLANE_CONTROL_CORE_RELEASE_V3_ENTRIES = ( ".dockerignore", "package.json", "package-lock.json", "packages/device-protocol-contract", "packages/device-edge-channel-contract", "packages/infrastructure-telemetry-contract", "services/device-control-core", DEVICE_PLANE_CONTROL_CORE_RELEASE_V3_REL, ) DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_ENTRIES = ( ".dockerignore", "package.json", "package-lock.json", "packages/device-protocol-contract", "packages/device-edge-channel-contract", "packages/infrastructure-telemetry-contract", "services/device-control-core", DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_REL, ) DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_REL = ( "deployment/device-control-core-release-v3-reconciliation-v1.json" ) DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_ENTRIES = ( DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_REL, ) DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_PATCH_ID = ( "device-control-core-release-v3-reconciliation-20260822-042" ) DEVICE_PLANE_CONTROL_CORE_V3_FAILED_PATCH_ID = ( "device-control-core-release-v3-20260822-040" ) DEVICE_PLANE_CONTROL_CORE_V3_FAILED_ARTIFACT = ( "nodedc-device-plane-device-control-core-release-v3-20260822-040.tgz." "20260822-184245" ) DEVICE_PLANE_CONTROL_CORE_V3_FAILED_ARTIFACT_SHA256 = ( "08448a56cdf391076f92c5242e368fd0033b420167874645eebfa1f22184ee92" ) DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_BACKUP_ID = ( "device-plane-device-control-core-release-v3-20260822-040-" "20260822-184245" ) DEVICE_PLANE_CONTROL_CORE_V3_PREAPPLY_IMAGE_ID = ( "sha256:31d35733ee46225b487c0f02a7b52d4ba2d13f5b99f6a717b7f5e6f5460b412a" ) DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_BACKUP_SHA256 = { "manifest.env": ( "eeb6ba544cde0d5d45a7bf16e921ca8969f0e5307872a53a5448d6a1279f76db" ), "files.txt": ( "be1bda7d2afff11cfaeacd607a63261f99c17d6c04c8c762424669585ad9a75f" ), "existing-files.txt": ( "a491b4a4ad7bc8dd8e8fc5b008f992663ef64e569b016efe7de1f4f14e5629ad" ), "missing-files.txt": ( "d04dd94a0f5a1fd6a7809219e953ade89a43ec5a2e29016cb666f684a77323c0" ), "source-before.tgz": ( "1c119875cccb52b761d1e2b42a6d666c9f9287782b424283b20180ea502237c4" ), "runtime-before.json": ( "b694a183345181e32a3335bb50d73c4b8df9ce45e54072f3a8dc06789684fa89" ), } DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_EXISTING = ( ".dockerignore", "package.json", "package-lock.json", "packages/device-protocol-contract", "packages/device-edge-channel-contract", "services/device-control-core", ) DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_MISSING = ( "packages/infrastructure-telemetry-contract", DEVICE_PLANE_CONTROL_CORE_RELEASE_V3_REL, ) DEVICE_PLANE_CONTROL_CORE_INCIDENT_AUDIT_REL = ( "deployment/device-control-core-incident-audit-v1.json" ) DEVICE_PLANE_CONTROL_CORE_INCIDENT_AUDIT_ENTRIES = ( DEVICE_PLANE_CONTROL_CORE_INCIDENT_AUDIT_REL, ) DEVICE_PLANE_CONTROL_CORE_INCIDENT_AUDIT_PATCH_ID = ( "device-control-core-incident-audit-20260822-043" ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_AUDIT_REL = ( "deployment/device-control-core-migration-replay-audit-v1.json" ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_AUDIT_ENTRIES = ( DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_AUDIT_REL, ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_AUDIT_PATCH_ID = ( "device-control-core-migration-replay-audit-20260822-045" ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_REL = ( "deployment/device-control-core-migration-replay-recovery-v1.json" ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_REL = ( "services/device-control-core/migrations/" "014_device_registry_profile_commands.sql" ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_ENTRIES = ( DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_REL, DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_REL, ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_PATCH_ID = ( "device-control-core-migration-replay-recovery-20260822-044" ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_ARTIFACT = ( "nodedc-device-plane-device-control-core-migration-replay-recovery-" "20260822-044.tgz" ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_ARTIFACT_SHA256 = ( "b893d8c90f98943797d32f486d6477d58a3be69eb1291e28c4a4bbd2e96774b7" ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_FAILED_ARTIFACT = ( "nodedc-device-plane-device-control-core-migration-replay-recovery-" "20260822-044.tgz.20260822-232936" ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_FAILED_AT = ( "2026-08-22T20:29:59+00:00" ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_REL = ( "deployment/" "device-control-core-migration-replay-checkpoint-recovery-v2.json" ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_ENTRIES = ( DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_REL, DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_REL, ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_PATCH_ID = ( "device-control-core-migration-replay-checkpoint-recovery-20260822-046" ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_ARTIFACT = ( "nodedc-device-plane-device-control-core-migration-replay-checkpoint-" "recovery-20260822-046.tgz" ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_ARTIFACT_SHA256 = ( "46000c76977fb583fc7c9cf74ecf624efd8b404f7b8d0322e0270e7b8ac6e450" ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_BACKUP_ID = ( "device-plane-device-control-core-migration-replay-checkpoint-recovery-" "20260822-046-20260823-002004" ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_BACKUP_SHA256 = { "manifest.env": ( "ca0d880ad8488a8e9d3b846f0f0b8e2d0a3e0df356b834ffdc1fedec65bb1acc" ), "files.txt": ( "34cf294a6f5cc68ee7470356bac847221aa8457f8c90312b2eda17ec59b30155" ), "existing-files.txt": ( "65452d59b095f05d550cafe9573ab8cad180d5c4799a353d67e6ac8e6366c7ad" ), "missing-files.txt": ( "b15be462f0b40badb4a3c8b5922bd23fd93cb896b80394252f217c2b1102f1f3" ), "source-before.tgz": ( "e27a9705c63a03158116530095466dcee8024ab1dee5ef26628e5691001d651e" ), "runtime-before.json": ( "f74bfdb74efb85c7b57cb5e4032d161985b6da8e0208276e218dd7013d1bea6f" ), } DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_PATCH_ID = ( "device-control-core-release-v4-20260823-047" ) DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_BASE_PATCH_ID = ( "device-control-core-release-v2-20260822-038" ) DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_BASE_ARTIFACT_SHA256 = ( "e2d062b82b022dba662522b5d6e192026ac964d78950d903295ca3cbbc95ab28" ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_PREDECESSOR_SHA256 = ( "751accf346b34d2774cc7b9572640d2c25fdb0b1db793ac32183b56f48e26508" ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_TARGET_SHA256 = ( "38bd86b42828d44c7101d5433ddc36018e92eedeee37b9de296432ad676edd46" ) DEVICE_PLANE_CONTROL_CORE_FINAL_COMMAND_KINDS = ( "owner_scope.ensure", "project.ensure", "collection.ensure", "project_grant.upsert", "adapter_package.ensure", "adapter_version.register", "model_profile.register", "edge.ensure", "route.ensure", "enrollment_intent.ensure", "device.claim", "device.update", "device.transfer", "discovery.reject", "discovery.expire", "device_credential_binding.upsert", "device_credential_binding.revoke", "device_binding.ensure", "device_binding.revoke", "device_configuration_revision.create", "device_configuration_desired.set", "asset.ensure", "asset_binding.ensure", "asset_binding.close", "infrastructure_host.ensure", "infrastructure_endpoint.ensure", "infrastructure_deployment.ensure", "infrastructure_service_instance.ensure", "health_observation.record", ) DEVICE_PLANE_CONTROL_CORE_REPLAY_005_COMMAND_KINDS = ( "owner_scope.ensure", "project.ensure", "collection.ensure", "project_grant.upsert", "adapter_package.ensure", "adapter_version.register", "model_profile.register", "edge.ensure", "route.ensure", "enrollment_intent.ensure", ) DEVICE_PLANE_CONTROL_CORE_REPLAY_007_COMMAND_KINDS = ( *DEVICE_PLANE_CONTROL_CORE_REPLAY_005_COMMAND_KINDS, "device.claim", "device.transfer", "discovery.reject", "discovery.expire", ) DEVICE_PLANE_CONTROL_CORE_REPLAY_009_COMMAND_KINDS = ( *DEVICE_PLANE_CONTROL_CORE_REPLAY_007_COMMAND_KINDS, "device_credential_binding.upsert", "device_credential_binding.revoke", ) DEVICE_PLANE_CONTROL_CORE_REPLAY_011_COMMAND_KINDS = ( "owner_scope.ensure", "project.ensure", "collection.ensure", "project_grant.upsert", "adapter_package.ensure", "adapter_version.register", "model_profile.register", "edge.ensure", "route.ensure", "enrollment_intent.ensure", "device.claim", "device.transfer", "discovery.reject", "discovery.expire", "device_credential_binding.upsert", "device_credential_binding.revoke", "device_binding.ensure", "device_binding.revoke", "device_configuration_revision.create", "device_configuration_desired.set", ) DEVICE_PLANE_CONTROL_CORE_REPLAY_CHECKPOINTS = ( ("replay-005", DEVICE_PLANE_CONTROL_CORE_REPLAY_005_COMMAND_KINDS), ("replay-007", DEVICE_PLANE_CONTROL_CORE_REPLAY_007_COMMAND_KINDS), ("replay-009", DEVICE_PLANE_CONTROL_CORE_REPLAY_009_COMMAND_KINDS), ("replay-011", DEVICE_PLANE_CONTROL_CORE_REPLAY_011_COMMAND_KINDS), ) DEVICE_PLANE_CONTROL_CORE_REPLAY_CHECKPOINT_PHASES = tuple( phase for phase, _kinds in DEVICE_PLANE_CONTROL_CORE_REPLAY_CHECKPOINTS ) DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_TRIGGER_KINDS = ( "asset.ensure", "asset_binding.ensure", "asset_binding.close", "infrastructure_host.ensure", "infrastructure_endpoint.ensure", "infrastructure_deployment.ensure", "infrastructure_service_instance.ensure", "health_observation.record", ) DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_FAILED_ARTIFACT = ( "nodedc-device-plane-device-control-core-release-v3-reconciliation-" "20260822-042.tgz.20260822-195448" ) DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_FAILED_ARTIFACT_SHA256 = ( "54ab243439bce724fa0a0872b76cc32e0052ea5127153214d92872f02ae831cf" ) DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_FAILED_BACKUP_ID = ( "device-plane-device-control-core-release-v3-reconciliation-" "20260822-042-20260822-195448" ) DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_FAILED_BACKUP_SHA256 = { "manifest.env": ( "37ed634d7ebde9d7da67990fe20cae7809a682c34652c37e163f9c2bc01c6a56" ), "files.txt": ( "865da0d154344b2c00725f80ba904f968dfbe8da775a2a7542edc0ae04820ce7" ), "existing-files.txt": ( "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ), "missing-files.txt": ( "865da0d154344b2c00725f80ba904f968dfbe8da775a2a7542edc0ae04820ce7" ), "source-before.tgz": ( "12be8d9b8ed064c1d17b67d4a126128a962da0dd82aa34c66532d2d55a927124" ), "runtime-before.json": ( "e65dee775511bc23f8b9238bb85bae7a1bbbc4a1d20d1329ae4b90ba60cef832" ), } DEVICE_PLANE_CONTROL_CORE_RELEASE_FIRST_PREDECESSOR_PATCH_ID = ( "device-edge-core-channel-upgrade-v4-20260812-023" ) DEVICE_PLANE_CONTROL_CORE_RELEASE_FIRST_PREDECESSOR_ARTIFACT_SHA256 = ( "c10d5b6b7d55ab239f85b6c8130e34ce9f84985e3b46e6e5534733156c7982fc" ) DEVICE_PLANE_MANAGER_RELEASE_V3_CONTROL_CORE_PREDECESSOR_PATCH_ID = ( "device-control-core-release-v2-20260821-030" ) DEVICE_PLANE_MANAGER_RELEASE_V3_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 = ( "8459521a662541a5a87cb0188991cdcfb51727427db8ec2a232ce4846bfc3454" ) DEVICE_PLANE_MANAGER_RELEASE_V3_EDGE_CHANNEL_PREDECESSOR_PATCH_ID = ( "device-edge-core-channel-upgrade-v4-20260812-023" ) DEVICE_PLANE_MANAGER_RELEASE_V3_EDGE_CHANNEL_PREDECESSOR_ARTIFACT_SHA256 = ( "c10d5b6b7d55ab239f85b6c8130e34ce9f84985e3b46e6e5534733156c7982fc" ) DEVICE_PLANE_MANAGER_RELEASE_V4_PREDECESSOR_PATCH_ID = ( "device-manager-release-v3-20260822-032" ) DEVICE_PLANE_MANAGER_RELEASE_V4_PREDECESSOR_ARTIFACT_SHA256 = ( "6e0eb3a0a6f19ceab92d46832b93bffbcea21247dbdc2ea50625a51ff460e4ca" ) DEVICE_PLANE_MANAGER_RELEASE_V5_PREDECESSOR_PATCH_ID = ( "device-manager-release-v4-20260822-033" ) DEVICE_PLANE_MANAGER_RELEASE_V5_PREDECESSOR_ARTIFACT_SHA256 = ( "52ba322042f1e4f595bbfea99f8bb35630b15984e0da648dc55348bc9e5b2066" ) DEVICE_PLANE_MANAGER_RELEASE_V6_PREDECESSOR_PATCH_ID = ( "device-manager-release-v5-20260822-034" ) DEVICE_PLANE_MANAGER_RELEASE_V6_PREDECESSOR_ARTIFACT_SHA256 = ( "acc1d2ae2cda66861054826928c25d01a2428e688cc8132a9c381831bf29ab5a" ) DEVICE_PLANE_MANAGER_RELEASE_V7_PREDECESSOR_PATCH_ID = ( "device-manager-release-v6-20260822-035" ) DEVICE_PLANE_MANAGER_RELEASE_V7_PREDECESSOR_ARTIFACT_SHA256 = ( "193faabe930e2b3f212f8eb45288e39f850ec714528b28881095be654baf9a80" ) DEVICE_PLANE_MANAGER_RELEASE_V7_CONTROL_CORE_PREDECESSOR_PATCH_ID = ( "device-control-core-release-v2-20260822-036" ) DEVICE_PLANE_MANAGER_RELEASE_V7_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 = ( "8708cc4b59fa0cd5e9c6e6a7b2654ba01ea60271549167aca2631f94000d3da3" ) DEVICE_PLANE_MANAGER_RELEASE_V8_PREDECESSOR_PATCH_ID = ( "device-manager-release-v6-20260822-035" ) DEVICE_PLANE_MANAGER_RELEASE_V8_PREDECESSOR_ARTIFACT_SHA256 = ( "193faabe930e2b3f212f8eb45288e39f850ec714528b28881095be654baf9a80" ) DEVICE_PLANE_MANAGER_RELEASE_V8_CONTROL_CORE_PREDECESSOR_PATCH_ID = ( "device-control-core-release-v2-20260822-038" ) DEVICE_PLANE_MANAGER_RELEASE_V8_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 = ( "e2d062b82b022dba662522b5d6e192026ac964d78950d903295ca3cbbc95ab28" ) DEVICE_PLANE_MANAGER_RELEASE_V9_PREDECESSOR_PATCH_ID = ( "device-manager-release-v8-20260822-039" ) DEVICE_PLANE_MANAGER_RELEASE_V9_PREDECESSOR_ARTIFACT_SHA256 = ( "30a83d4c6b5c029c96c4af19fa558e0ae75e4b60bc897881457304bd17e0e465" ) DEVICE_PLANE_MANAGER_RELEASE_V9_CONTROL_CORE_PREDECESSOR_PATCH_ID = ( "device-control-core-release-v3-20260822-040" ) DEVICE_PLANE_MANAGER_RELEASE_V9_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 = ( "08448a56cdf391076f92c5242e368fd0033b420167874645eebfa1f22184ee92" ) DEVICE_PLANE_MANAGER_RELEASE_V10_PREDECESSOR_PATCH_ID = ( "device-manager-release-v8-20260822-039" ) DEVICE_PLANE_MANAGER_RELEASE_V10_PREDECESSOR_ARTIFACT_SHA256 = ( "30a83d4c6b5c029c96c4af19fa558e0ae75e4b60bc897881457304bd17e0e465" ) DEVICE_PLANE_MANAGER_RELEASE_V10_CONTROL_CORE_PREDECESSOR_PATCH_ID = ( "device-control-core-release-v4-20260823-047" ) DEVICE_PLANE_MANAGER_RELEASE_V10_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 = ( "4aecceeb8d400fdd3dbec8fc86b151691f389d165e72677f396f8994823b6b5c" ) DEVICE_PLANE_MANAGER_RELEASE_V11_PREDECESSOR_PATCH_ID = ( "device-manager-release-v10-20260823-048" ) DEVICE_PLANE_MANAGER_RELEASE_V11_PREDECESSOR_ARTIFACT_SHA256 = ( "e6b983a314db4f8c27d89062dfedf5ed0523cc30421170799d181a19e2d85d4c" ) DEVICE_PLANE_MANAGER_RELEASE_V11_CONTROL_CORE_PREDECESSOR_PATCH_ID = ( "device-control-core-release-v4-20260823-047" ) DEVICE_PLANE_MANAGER_RELEASE_V11_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 = ( "4aecceeb8d400fdd3dbec8fc86b151691f389d165e72677f396f8994823b6b5c" ) DEVICE_PLANE_MANAGER_RELEASE_V12_PREDECESSOR_PATCH_ID = ( "device-manager-release-v11-20260823-049" ) DEVICE_PLANE_MANAGER_RELEASE_V12_PREDECESSOR_ARTIFACT_SHA256 = ( "c1e2056b50bfbb0d03d077461d0c27cc56cc52967c3f5620be14871c8a6d5cf0" ) DEVICE_PLANE_MANAGER_RELEASE_V12_CONTROL_CORE_PREDECESSOR_PATCH_ID = ( "device-control-core-release-v4-20260823-047" ) DEVICE_PLANE_MANAGER_RELEASE_V12_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 = ( "4aecceeb8d400fdd3dbec8fc86b151691f389d165e72677f396f8994823b6b5c" ) DEVICE_PLANE_MANAGER_RELEASE_V13_PREDECESSOR_PATCH_ID = ( "device-manager-release-v12-20260823-050" ) DEVICE_PLANE_MANAGER_RELEASE_V13_PREDECESSOR_ARTIFACT_SHA256 = ( "1a49839140e5f2e49763d78f24ee47d946e244bcfde15a9c38266e8bd14c0d49" ) DEVICE_PLANE_MANAGER_RELEASE_V13_CONTROL_CORE_PREDECESSOR_PATCH_ID = ( "device-control-core-release-v4-20260823-047" ) DEVICE_PLANE_MANAGER_RELEASE_V13_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 = ( "4aecceeb8d400fdd3dbec8fc86b151691f389d165e72677f396f8994823b6b5c" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_PATCH_ID = ( "device-edge-core-channel-bootstrap-20260812-018" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_ARTIFACT_SHA256 = ( "5598b7388b491fe524ab46038ce476482a93a6cf07d8ca5e00206c69ded02931" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_PREDECESSOR_PATCH_ID = ( "device-edge-core-channel-upgrade-20260812-019" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_PREDECESSOR_ARTIFACT_SHA256 = ( "8e9a220275959f378c1c4b00be5c7192e79afe2134eaab808a64e515870a8438" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_PREDECESSOR_PATCH_ID = ( "device-edge-core-channel-upgrade-v2-20260812-021" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_PREDECESSOR_ARTIFACT_SHA256 = ( "e40a6fd24edfecac09e42cd82635a77850541bcf047788db3e9c55d2b9e58867" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_FAILED_PATCH_ID = ( "device-edge-core-channel-upgrade-v3-20260812-022" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_FAILED_ARTIFACT_SHA256 = ( "9e2b409a4b2d19711db434e90d03ac8e3db77bd74949f83cace7949f33caf613" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_FAILED_ARTIFACT = ( "nodedc-device-plane-device-edge-core-channel-upgrade-v3-" "20260812-022.tgz.20260812-123620" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_FAILED_BACKUP_ID = ( "device-plane-device-edge-core-channel-upgrade-v3-" "20260812-022-20260812-123620" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_MANAGER_PREDECESSOR_PATCH_ID = ( "device-manager-release-20260811-010" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_MANAGER_PREDECESSOR_ARTIFACT_SHA256 = ( "d4132993216eb674967dc6fc65d9670cfc2a9efdf46186ca019030f259de2d0e" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_FAILED_PATCH_ID = ( "device-manager-release-20260811-016" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_FAILED_ARTIFACT_SHA256 = ( "590405821b95b54088f926e0d3b2cdf9c704b339f6749da500c2bb64fe0e952d" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_FAILED_ARTIFACT = ( "nodedc-device-plane-device-manager-release-20260811-016.tgz." "20260811-215941" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_FAILED_BACKUP_ID = ( "device-plane-device-manager-release-20260811-016-20260811-215941" ) DEVICE_PLANE_EDGE_CORE_CHANNEL_INVALID_CERTIFICATE_FINGERPRINT = ( "56:16:E0:3A:F4:03:85:FD:42:86:85:AF:2A:AF:1E:90:16:C8:F7:91:" "7C:AD:02:7D:B7:C2:ED:07:06:56:81:F6" ) DEVICE_PLANE_MANAGER_RELEASE_V1_COMPOSE_SHA256 = ( "4954120aaddc999798b64c304d8cf692b79714feb727d873117bd1f3434e865e" ) DEVICE_PLANE_MANAGER_RELEASE_V4_COMPOSE_SHA256 = ( "e7dff0f5873ad4586bd55946d3db2bb86092a5e149e886d120adc041e056c256" ) DEVICE_PLANE_MANAGER_RELEASE_V5_COMPOSE_SHA256 = ( DEVICE_PLANE_MANAGER_RELEASE_V4_COMPOSE_SHA256 ) DEVICE_PLANE_MANAGER_RELEASE_V6_COMPOSE_SHA256 = ( DEVICE_PLANE_MANAGER_RELEASE_V5_COMPOSE_SHA256 ) DEVICE_PLANE_MANAGER_RELEASE_V7_COMPOSE_SHA256 = ( DEVICE_PLANE_MANAGER_RELEASE_V6_COMPOSE_SHA256 ) DEVICE_PLANE_MANAGER_RELEASE_V8_COMPOSE_SHA256 = ( DEVICE_PLANE_MANAGER_RELEASE_V6_COMPOSE_SHA256 ) DEVICE_PLANE_MANAGER_RELEASE_V9_COMPOSE_SHA256 = ( DEVICE_PLANE_MANAGER_RELEASE_V8_COMPOSE_SHA256 ) DEVICE_PLANE_MANAGER_RELEASE_V10_COMPOSE_SHA256 = ( DEVICE_PLANE_MANAGER_RELEASE_V9_COMPOSE_SHA256 ) DEVICE_PLANE_MANAGER_RELEASE_V11_COMPOSE_SHA256 = ( DEVICE_PLANE_MANAGER_RELEASE_V10_COMPOSE_SHA256 ) DEVICE_PLANE_MANAGER_RELEASE_V12_COMPOSE_SHA256 = ( DEVICE_PLANE_MANAGER_RELEASE_V11_COMPOSE_SHA256 ) DEVICE_PLANE_MANAGER_RELEASE_V13_COMPOSE_SHA256 = ( DEVICE_PLANE_MANAGER_RELEASE_V12_COMPOSE_SHA256 ) DEVICE_PLANE_MANAGER_FAVICON_SHA256 = { "favicon.ico": ( "f8933114a85646335ea5c94944f56d3cd8c48a6032016719f7905ff244ec0aa2" ), "favicon/favicon.ico": ( "f8933114a85646335ea5c94944f56d3cd8c48a6032016719f7905ff244ec0aa2" ), "favicon/icon-adaptive.svg": ( "481984e83997d786bb0a72ad1ee80037db13aef3a0792ab3109df95c2199b38e" ), "favicon/apple-touch-icon.png": ( "afdccc28152a566e264e533ca218362f05d5bcec936c647f9a54f414c0bd4763" ), "favicon/icon-192.png": ( "5b10a24feb4754f15c69761cef42f91a01f885d04095156b1a12e254875fdd4d" ), "favicon/icon-512.png": ( "f98bac3dba59b7eefbe89f8bb8abc25567226a54ab7ed3b6b1a4caffbdd9ee15" ), "favicon/manifest.webmanifest.json": ( "2a8ecdc6e6c64833f812ae02bbc0c7bd9b435e0cfa75cc21d6edf054d41275fc" ), } DEVICE_PLANE_MANAGER_RELEASE_V2_COMPOSE_SHA256 = ( "369a2acf9c1a1030b9e1c6c366144b1eaf8900aef0ee59bb6bf23250b7b371b9" ) DEVICE_PLANE_MANAGER_COMPOSE_SHA256 = ( DEVICE_PLANE_MANAGER_RELEASE_V2_COMPOSE_SHA256 ) DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_ENTRIES = ( ".dockerignore", "package.json", "package-lock.json", DEVICE_PLANE_MANAGER_COMPOSE_REL, "packages/device-protocol-contract", "packages/arusnavi-b2-adapter", "services/device-control-core", "services/device-gateway/package.json", "services/device-edge-relay/package.json", "services/device-manager", DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_REL, ) DEVICE_PLANE_MANAGER_V2_CONTROL_PLANE_ENTRIES = ( ".dockerignore", "package.json", "package-lock.json", DEVICE_PLANE_MANAGER_COMPOSE_REL, "packages/device-protocol-contract", "packages/arusnavi-b2-adapter", "services/device-control-core", "services/device-gateway/package.json", "services/device-edge-relay/package.json", "services/device-manager", DEVICE_PLANE_MANAGER_V2_CONTROL_PLANE_REL, ) DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES = ( ".dockerignore", "package.json", "package-lock.json", DEVICE_PLANE_MANAGER_COMPOSE_REL, "packages/device-protocol-contract", "packages/arusnavi-b2-adapter", "services/device-control-core", "services/device-gateway/package.json", "services/device-edge-relay/package.json", "services/device-manager", DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL, ) DEVICE_PLANE_MANAGER_RELEASE_V1_SUCCESSOR_ENTRIES = ( ".dockerignore", "package.json", "package-lock.json", DEVICE_PLANE_MANAGER_COMPOSE_REL, "packages/device-protocol-contract", "packages/device-edge-channel-contract", "packages/arusnavi-b2-adapter", "services/device-control-core", "services/device-gateway/package.json", "services/device-edge-relay/package.json", "services/device-manager", DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL, ) DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES = ( ".dockerignore", "package.json", "package-lock.json", DEVICE_PLANE_MANAGER_COMPOSE_REL, "packages/device-protocol-contract", "packages/device-edge-channel-contract", "packages/arusnavi-b2-adapter", "services/device-control-core", "services/device-gateway/package.json", "services/device-edge-relay/package.json", "services/device-manager", DEVICE_PLANE_MANAGER_RELEASE_V2_REL, ) DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES = ( DEVICE_PLANE_MANAGER_COMPOSE_REL, "services/device-manager", DEVICE_PLANE_MANAGER_RELEASE_V3_REL, ) DEVICE_PLANE_MANAGER_RELEASE_V4_ENTRIES = ( DEVICE_PLANE_MANAGER_COMPOSE_REL, "services/device-manager", DEVICE_PLANE_MANAGER_RELEASE_V4_REL, ) DEVICE_PLANE_MANAGER_RELEASE_V5_ENTRIES = ( DEVICE_PLANE_MANAGER_COMPOSE_REL, "services/device-manager", DEVICE_PLANE_MANAGER_RELEASE_V5_REL, ) DEVICE_PLANE_MANAGER_RELEASE_V6_ENTRIES = ( DEVICE_PLANE_MANAGER_COMPOSE_REL, "services/device-manager", DEVICE_PLANE_MANAGER_RELEASE_V6_REL, ) DEVICE_PLANE_MANAGER_RELEASE_V7_ENTRIES = ( DEVICE_PLANE_MANAGER_COMPOSE_REL, "services/device-manager", DEVICE_PLANE_MANAGER_RELEASE_V7_REL, ) DEVICE_PLANE_MANAGER_RELEASE_V8_ENTRIES = ( DEVICE_PLANE_MANAGER_COMPOSE_REL, "services/device-manager", DEVICE_PLANE_MANAGER_RELEASE_V8_REL, ) DEVICE_PLANE_MANAGER_RELEASE_V9_ENTRIES = ( DEVICE_PLANE_MANAGER_COMPOSE_REL, "services/device-manager", DEVICE_PLANE_MANAGER_RELEASE_V9_REL, ) DEVICE_PLANE_MANAGER_RELEASE_V10_ENTRIES = ( DEVICE_PLANE_MANAGER_COMPOSE_REL, "services/device-manager", DEVICE_PLANE_MANAGER_RELEASE_V10_REL, ) DEVICE_PLANE_MANAGER_RELEASE_V11_ENTRIES = ( DEVICE_PLANE_MANAGER_COMPOSE_REL, "services/device-manager", DEVICE_PLANE_MANAGER_RELEASE_V11_REL, ) DEVICE_PLANE_MANAGER_RELEASE_V12_ENTRIES = ( DEVICE_PLANE_MANAGER_COMPOSE_REL, "services/device-manager", DEVICE_PLANE_MANAGER_RELEASE_V12_REL, ) DEVICE_PLANE_MANAGER_RELEASE_V13_ENTRIES = ( DEVICE_PLANE_MANAGER_COMPOSE_REL, "services/device-manager", DEVICE_PLANE_MANAGER_RELEASE_V13_REL, ) DEVICE_PLANE_MANAGER_RECONCILIATION_REL = ( "deployment/device-manager-control-plane-reconciliation-v1.json" ) DEVICE_PLANE_MANAGER_RECONCILIATION_ENTRIES = ( DEVICE_PLANE_MANAGER_RECONCILIATION_REL, ) DEVICE_PLANE_MANAGER_FAILED_PATCH_ID = ( "device-manager-control-plane-20260810-001" ) DEVICE_PLANE_MANAGER_FAILED_ARTIFACT = ( "nodedc-device-plane-device-manager-control-plane-20260810-001.tgz." "20260811-000321" ) DEVICE_PLANE_MANAGER_FAILED_ARTIFACT_SHA256 = ( "50e275c1085286bcb3bb2b273aefc8bbba70f446ca2c7bd464dc745710a291a6" ) DEVICE_PLANE_MANAGER_RECONCILIATION_PATCH_ID = ( "device-manager-control-plane-reconciliation-20260811-002" ) DEVICE_PLANE_MANAGER_RECONCILIATION_ARTIFACT = ( "nodedc-device-plane-device-manager-control-plane-reconciliation-" "20260811-002.tgz" ) DEVICE_PLANE_MANAGER_RECONCILIATION_ARTIFACT_SHA256 = ( "dd86dd58e4f649db0981db5089e003caf3961356179f2abb514662351487e1e6" ) DEVICE_PLANE_MANAGER_RECONCILIATION_APPLY_BACKUP_ID = ( "device-plane-device-manager-control-plane-reconciliation-" "20260811-002-20260811-004839" ) DEVICE_PLANE_MANAGER_RECONCILIATION_BACKUP_ID = ( "device-plane-device-manager-control-plane-20260810-001-" "20260811-000321" ) DEVICE_PLANE_MANAGER_RECONCILIATION_BACKUP_SHA256 = { "manifest.env": ( "70c8e6514f0a41cdd8892e29ca74c311c68e31bb74a7653d9b37e790db968526" ), "files.txt": ( "2101d48cfb832f50e6112e443b4b61813e9e6d94c8171ca66fb23ed7f51bc575" ), "existing-files.txt": ( "b605a86ce5ed6c0adb7b4cbeb5758795fe4219367df2c18db9411825f232e308" ), "missing-files.txt": ( "84c546b7ddb0cd8c80b3de29f583ec48678bc9bb47ad0460860dc0022a6b87e9" ), "source-before.tgz": ( "df6f29d173a56b0a067358d4b763c2de280450cf597957d1e79b8ae168e57735" ), "runtime-before.json": ( "590310c9ad923516a714c7d47ba26eee2d82113cb800a5a7bff29a7de94f165d" ), } DEVICE_PLANE_MANAGER_RECONCILIATION_EXISTING = ( ".dockerignore", "package.json", "package-lock.json", "packages/device-protocol-contract", "packages/arusnavi-b2-adapter", "services/device-control-core", "services/device-gateway/package.json", "services/device-edge-relay/package.json", ) DEVICE_PLANE_MANAGER_RECONCILIATION_MISSING = ( DEVICE_PLANE_MANAGER_COMPOSE_REL, "services/device-manager", DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_REL, ) DEVICE_PLANE_MANAGER_V2_RECONCILIATION_REL = ( "deployment/device-manager-control-plane-v2-reconciliation-v1.json" ) DEVICE_PLANE_MANAGER_V2_RECONCILIATION_ENTRIES = ( DEVICE_PLANE_MANAGER_V2_RECONCILIATION_REL, ) DEVICE_PLANE_MANAGER_V2_FAILED_PATCH_ID = ( "device-manager-control-plane-20260811-003" ) DEVICE_PLANE_MANAGER_V2_FAILED_ARTIFACT = ( "nodedc-device-plane-device-manager-control-plane-20260811-003.tgz." "20260811-012505" ) DEVICE_PLANE_MANAGER_V2_FAILED_ARTIFACT_SHA256 = ( "ba29618ffbfed55448768794f28b18dda439ddb39a1d2a4f1dece19de7f29990" ) DEVICE_PLANE_MANAGER_V2_RECONCILIATION_PATCH_ID = ( "device-manager-control-plane-v2-reconciliation-20260811-004" ) DEVICE_PLANE_MANAGER_V2_RECONCILIATION_BACKUP_ID = ( "device-plane-device-manager-control-plane-20260811-003-" "20260811-012505" ) DEVICE_PLANE_MANAGER_V2_RECONCILIATION_BACKUP_SHA256 = { "manifest.env": ( "4f7535e5eeee0bc3339173c2425787c153528d26f2c60f6cdd386ad3359e05cf" ), "files.txt": ( "191e81991258673881435083e4a6733062ec0e30afb9b80a875c1a6581dd18a2" ), "existing-files.txt": ( "b605a86ce5ed6c0adb7b4cbeb5758795fe4219367df2c18db9411825f232e308" ), "missing-files.txt": ( "c1ab9da97b257b467fd222eda0e34b5a7b29b628c71f10a5f4069b2991b3d39d" ), "source-before.tgz": ( "32d6d6341e410806bcff7007c5eacec34090bc337483222f396f19ca2edad9be" ), "runtime-before.json": ( "af8f2c71caeedcd61ebf02de98ff6c61d35c832fbd446a6b38a7e7c16150c2bd" ), } DEVICE_PLANE_MANAGER_V2_RECONCILIATION_EXISTING = ( ".dockerignore", "package.json", "package-lock.json", "packages/device-protocol-contract", "packages/arusnavi-b2-adapter", "services/device-control-core", "services/device-gateway/package.json", "services/device-edge-relay/package.json", ) DEVICE_PLANE_MANAGER_V2_RECONCILIATION_MISSING = ( DEVICE_PLANE_MANAGER_COMPOSE_REL, "services/device-manager", DEVICE_PLANE_MANAGER_V2_CONTROL_PLANE_REL, ) DEVICE_PLANE_FOUNDATION_RECOVERY_REL = ( "deployment/device-plane-foundation-recovery-v1.json" ) DEVICE_PLANE_FOUNDATION_RECOVERY_ENTRIES = ( *DEVICE_PLANE_FOUNDATION_ENTRIES, DEVICE_PLANE_FOUNDATION_RECOVERY_REL, ) DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_REL = ( "deployment/device-plane-foundation-network-publication-v1.json" ) DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_ENTRIES = ( *DEVICE_PLANE_FOUNDATION_ENTRIES, DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_REL, ) DEVICE_PLANE_B2_DISCOVERY_INGRESS_REL = ( "deployment/device-plane-b2-discovery-ingress-v1.json" ) DEVICE_PLANE_B2_DISCOVERY_INGRESS_ENTRIES = ( *DEVICE_PLANE_FOUNDATION_ENTRIES, "services/device-edge-relay/package.json", DEVICE_PLANE_B2_DISCOVERY_INGRESS_REL, ) DEVICE_PLANE_B2_DISCOVERY_INGRESS_PREDECESSOR_PATCH_ID = ( "device-plane-foundation-network-publication-20260725-003" ) DEVICE_PLANE_B2_DISCOVERY_INGRESS_PREDECESSOR_ARTIFACT_SHA256 = ( "6fdd5a12c310786db1753882fc1378184fe378d2cc533633a8c73c951521b7bf" ) DEVICE_PLANE_B2_DISCOVERY_INGRESS_COMPOSE_SHA256 = ( "eb1018cc0ffeaa0c019944d8810e2daa01eeca4c06289efff175785fb16457e7" ) DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_REL = ( "deployment/device-plane-b2-discovery-loopback-recovery-v1.json" ) DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_ENTRIES = ( DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_REL, ) DEVICE_PLANE_B2_DISCOVERY_FAILED_ENTRIES = ( *DEVICE_PLANE_FOUNDATION_ENTRIES, DEVICE_PLANE_B2_DISCOVERY_INGRESS_REL, ) DEVICE_PLANE_B2_DISCOVERY_FAILED_PATCH_ID = ( "device-plane-b2-discovery-loopback-20260801-003" ) DEVICE_PLANE_B2_DISCOVERY_FAILED_ARTIFACT = ( "nodedc-device-plane-device-plane-b2-discovery-loopback-" "20260801-003.tgz.20260802-154311" ) DEVICE_PLANE_B2_DISCOVERY_FAILED_ARTIFACT_SHA256 = ( "7273c5bf67fe6bc1f1da66ad726009240d39ee3aee58201b96c23d6f707a3d84" ) DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_BACKUP_ID = ( "device-plane-device-plane-b2-discovery-loopback-20260801-003-" "20260802-154311" ) DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_BACKUP_SHA256 = { "manifest.env": ( "2d93219283cb2f7ca65a8349c84a1d6ff0568bea33d564a9acb5286df0061c93" ), "files.txt": ( "596a364ea56b92789b4ba9f9e6d2628e5a7a56a1380abfc3124c08e08ff9d6ae" ), "existing-files.txt": ( "3b7dda5cc42ce460e4a74f6df986d4a4fa8aa28511e9d9b123deb445a0382083" ), "missing-files.txt": ( "fc252f30b3ea51aafcf8002d64de077b33dcfd20e77fa05cb88457876780b620" ), "runtime-before.json": ( "4171d00e5c8a8db9d26042241ff50c70a0aa96cfdbdd2eeb596f0e860349e6a2" ), "source-before.tgz": ( "1cbe3b31be02ce733afe17f3dd391708787f0b3ffcea038d0619316e831f7e34" ), } DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_PATCH_ID = ( "device-plane-b2-discovery-loopback-recovery-20260802-004" ) DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_ARTIFACT_SHA256 = ( "0888a6ff3404772179d74b46919fd3f193adacedc40944d20349a37d262974aa" ) DEVICE_PLANE_FOUNDATION_FAILED_PATCH_ID = ( "device-plane-foundation-20260725-001" ) DEVICE_PLANE_FOUNDATION_FAILED_ARTIFACT = ( "nodedc-device-plane-device-plane-foundation-20260725-001.tgz." "20260725-223441" ) DEVICE_PLANE_FOUNDATION_FAILED_ARTIFACT_SHA256 = ( "23d428de547854ad8b1a026671e2f850386ab0be98bde80f016f1e9db631ee24" ) DEVICE_PLANE_FOUNDATION_RECOVERY_BACKUP_ID = ( "device-plane-device-plane-foundation-20260725-001-20260725-223441" ) DEVICE_PLANE_FOUNDATION_RECOVERY_BACKUP_SHA256 = { "manifest.env": ( "21ff4250147f99fd1461191e1f05aee99e92da9cdedce4513749046c26327c77" ), "files.txt": ( "3b7dda5cc42ce460e4a74f6df986d4a4fa8aa28511e9d9b123deb445a0382083" ), "existing-files.txt": ( "f2e2c6e7501d3a383cd7e909a2e4e4cfbb3b0cda132f508f54e478bfb455585b" ), "missing-files.txt": ( "308d86656e0a0863d5fa16833ec67d1698c0cef7e009a7bd987e62a9e6469c63" ), "source-before.tgz": ( "df451cdf7a5798c332d03f15473358b1e7fdb7b96212037aaed2d8d00dfecf68" ), } DEVICE_PLANE_FOUNDATION_PREDECESSOR_COMPOSE_SHA256 = ( "e3afbb1ab252914e21dfb0acc1a2016fd8af471aa13bb14fb578f106f2cdae36" ) DEVICE_PLANE_POSTGRES_BOOTSTRAP_DESCRIPTOR_SHA256 = ( "9475f12d151aee386cba74a4c0f9e7485416d5616d3ad71dfd8963fe114abf61" ) DEVICE_PLANE_FOUNDATION_RECOVERY_IMAGE_IDS = { "device-control-core": ( "sha256:78a9fdfbe46f6e9531536abb997ae105fc7ea5c9a8176cb88ca8c0644900c254" ), "device-gateway": ( "sha256:9be91a3496d1ac627b6b621ade0c2ae77afa10e9ddb9b2071823769bc5a5a472" ), "device-postgres": ( "sha256:0001755f47e38bfd163788ccc543387b7f3da35a01b38a7c2ca1cd0277f06b5d" ), } DEVICE_PLANE_FOUNDATION_RECOVERY_FAILED_PATCH_ID = ( "device-plane-foundation-recovery-20260725-002" ) DEVICE_PLANE_FOUNDATION_RECOVERY_FAILED_ARTIFACT = ( "nodedc-device-plane-device-plane-foundation-recovery-20260725-002.tgz." "20260725-232447" ) DEVICE_PLANE_FOUNDATION_RECOVERY_FAILED_ARTIFACT_SHA256 = ( "9183cc385142584bfd12510bb0a3e6b833b2fd26607436f2486a564c628ea1bf" ) DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_BACKUP_ID = ( "device-plane-device-plane-foundation-recovery-20260725-002-" "20260725-232447" ) DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_BACKUP_SHA256 = { "manifest.env": ( "e1c31f28474d66c33f8b91e890c112e605ffe376ed3e7292d2f8504ea18fd9c1" ), "files.txt": ( "bb0e3deec5952811dd413afdf24d116e5bf3fd3e041f78bfe5cc5ca87166b8ff" ), "existing-files.txt": ( "f2e2c6e7501d3a383cd7e909a2e4e4cfbb3b0cda132f508f54e478bfb455585b" ), "missing-files.txt": ( "e57cda977037cabbe01de04ece1a1e9f15cb9eb3c0ad2996811d6ea3d4f4b3d2" ), "runtime-before.json": ( "f500964078c5814d2130fe57b5c67fb89dea000355591836e1b72088b37170ef" ), "source-before.tgz": ( "eed474b04608e206f09ebe68ca80da675a2318fa3efaf3be90f2d983429f9d9f" ), } DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_COMPOSE_SHA256 = ( "47d4d153d27bc6f418c1f787ea5ff0153b1ee082f96b92e28486805cb46b3f15" ) DEVICE_PLANE_PRIVATE_NETWORK = "nodedc-device-plane-private" DEVICE_PLANE_CONTROL_NETWORK = "nodedc-device-plane-control" DEVICE_PLANE_EGRESS_NETWORK = "nodedc-device-plane-egress" DEVICE_PLANE_FOUNDATION_PREDECESSOR_CONTAINER_IDS = { "device-control-core": ( "ccdaeee71472a557ccecb485e81b31f5a4ee37b1b3f3cb4d5edbcf12d02bb08b" ), "device-gateway": ( "d59258eb86ae8ec728ceaaed5b4976dd1359bdcecd6abc7950e73ffabf1d8eba" ), "device-postgres": ( "44702b995b4397131646ed8076144efd116cb38d00d9a3d641252b7ad635d7a1" ), } DEVICE_PLANE_RUNTIME_SERVICES = ( "device-control-core", "device-gateway", "device-postgres", ) DEVICE_PLANE_BACKHAUL_TARGET_SERVICE = "device-backhaul-target" EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_DIR = MAP_GATEWAY_SECRET_DIR / "external-data-plane-provisioner" EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_FILE = EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_DIR / "token" ENGINE_CREDENTIAL_PROVISIONER_PRIVATE_KEY_FILE = EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_DIR / "engine-credential-provisioner-ed25519.pem" ENGINE_CREDENTIAL_SINK_STATE_DIR = Path("/volume2/nodedc-demo/nodedc-data/engine-credential-sink") ENGINE_CREDENTIAL_SINK_PUBLIC_KEY_FILE = ENGINE_CREDENTIAL_SINK_STATE_DIR / "issuer-public-key.pem" ENGINE_CREDENTIAL_PROVISIONER_KEY_ID = "engine-credential-provisioner-20260716-001" ENGINE_CREDENTIAL_BACKEND_BASE_IMAGE = "node:20-alpine" ENGINE_CREDENTIAL_BACKEND_IMAGE = "nodedc/engine-backend:credential-sink-20260716-001" ENGINE_CREDENTIAL_BACKEND_PACKAGE_LOCK_SHA256 = "254f9086142eb469a0bcfcb393330a126e4d128a0df7455f0ac71d52b95e770b" ENGINE_CREDENTIAL_BACKEND_NODE_MODULES_DIR = Path("/volume2/nodedc-demo/nodedc-backend-node_modules") ENGINE_CREDENTIAL_BACKEND_OVERRIDE_TEMPLATE_REL = "nodedc-source/services/backend/credential-sink/docker-compose.immutable-runtime.yml" ENGINE_CREDENTIAL_BACKEND_RUNTIME_DIR = RUNTIME_DIR / "engine-credential-sink" ENGINE_CREDENTIAL_BACKEND_OVERRIDE_FILE = ENGINE_CREDENTIAL_BACKEND_RUNTIME_DIR / "docker-compose.immutable-runtime.yml" ENGINE_CREDENTIAL_BACKEND_ROOTFS_FILE = ENGINE_CREDENTIAL_BACKEND_RUNTIME_DIR / "canonical-rootfs.tar" 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" ENGINE_DATA_PRODUCT_PUBLISH_GRANT_PREDECESSOR_COMPOSE_SHA256 = "258cebb64ff1943c939655cc55bdce00fc5c4dced67ec291d84d6df066ace50e" ENGINE_EDP_MANAGED_PROVISIONER_SECRET_DIR = MAP_GATEWAY_SECRET_DIR / "engine-edp-managed-provisioner" ENGINE_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE = ENGINE_EDP_MANAGED_PROVISIONER_SECRET_DIR / "private-key.pem" ENGINE_EDP_MANAGED_PROVISIONER_TRUST_DIR = Path("/volume1/docker/nodedc-platform/trust/engine-managed-provisioner") ENGINE_EDP_MANAGED_PROVISIONER_PUBLIC_KEY_FILE = ENGINE_EDP_MANAGED_PROVISIONER_TRUST_DIR / "public-key.pem" FOUNDRY_EDP_MANAGED_PROVISIONER_SECRET_DIR = MAP_GATEWAY_SECRET_DIR / "foundry-edp-managed-provisioner" FOUNDRY_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE = FOUNDRY_EDP_MANAGED_PROVISIONER_SECRET_DIR / "private-key.pem" FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR = Path("/volume1/docker/nodedc-platform/trust/foundry-managed-provisioner") FOUNDRY_EDP_MANAGED_PROVISIONER_PUBLIC_KEY_FILE = FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR / "public-key.pem" EXTERNAL_DATA_PLANE_READER_GRANTS_DIR = MAP_GATEWAY_SECRET_DIR / "external-data-plane-reader-grants" FOUNDRY_BINDING_GRANTS_DIR = MAP_GATEWAY_SECRET_DIR / "foundry-binding-grants" N8N_PRIVATE_EXTENSION_RELEASES_ROOT = Path("/volume1/docker/nodedc-platform/n8n-private-extensions") ENGINE_N8N_SEALED_RELEASES_ROOT = Path("/volume2/nodedc-demo/n8n-private-extensions") ENGINE_N8N_TRANSITION_DESCRIPTOR_REL = "nodedc-source/services/n8n/private-extensions/ndc-activation.json" ENGINE_N8N_COMPOSE_OVERRIDE_REL = "nodedc-source/services/n8n/private-extensions/docker-compose.ndc-private-extension.yml" ENGINE_N8N_SCHEMA_ROOT_REL = "nodedc-source/server/assets/n8n/schema/v2.3.2" ENGINE_N8N_NODES_CATALOG_REL = f"{ENGINE_N8N_SCHEMA_ROOT_REL}/nodes.catalog.json" ENGINE_N8N_CREDENTIALS_CATALOG_REL = f"{ENGINE_N8N_SCHEMA_ROOT_REL}/credentials.catalog.json" ENGINE_N8N_SCHEMA_META_REL = f"{ENGINE_N8N_SCHEMA_ROOT_REL}/meta.json" ENGINE_N8N_ICON_REL = "nodedc-source/server/assets/n8n/icons/ndc.svg" ENGINE_N8N_DARK_ICON_REL = "nodedc-source/server/assets/n8n/icons/ndc.dark.svg" ENGINE_NODE_INTELLIGENCE_RELEASE_ID = "2.33.2-974a9fb3492f" ENGINE_NODE_INTELLIGENCE_UPSTREAM_COMMIT = "974a9fb3492fe2c4984ee0549085d531cdc6242a" ENGINE_NODE_INTELLIGENCE_IMAGE = ( f"nodedc/engine-node-intelligence:{ENGINE_NODE_INTELLIGENCE_RELEASE_ID}" ) ENGINE_NODE_INTELLIGENCE_SERVICE = "nodedc-node-intelligence" ENGINE_NODE_INTELLIGENCE_RUNTIME_UID = 11007 ENGINE_NODE_INTELLIGENCE_RUNTIME_GID = 11007 ENGINE_NODE_INTELLIGENCE_RUNTIME_DIR = RUNTIME_DIR / "engine-node-intelligence" ENGINE_NODE_INTELLIGENCE_SECRET_FILE = ENGINE_NODE_INTELLIGENCE_RUNTIME_DIR / "auth-token" ENGINE_NODE_INTELLIGENCE_RELEASES_DIR = ENGINE_NODE_INTELLIGENCE_RUNTIME_DIR / "releases" ENGINE_NODE_INTELLIGENCE_SECRET_CONTAINER_PATH = ( "/run/nodedc-secrets/engine-node-intelligence-auth-token" ) ENGINE_NODE_INTELLIGENCE_SOURCE_REL = "nodedc-source/server/nodeIntelligence" ENGINE_NODE_INTELLIGENCE_GATEWAY_REL = "nodedc-source/server/routes/engineAgentGateway.js" ENGINE_NODE_INTELLIGENCE_SERVICE_ROOT_REL = "nodedc-source/services/node-intelligence" ENGINE_NODE_INTELLIGENCE_OVERRIDE_REL = ( f"{ENGINE_NODE_INTELLIGENCE_SERVICE_ROOT_REL}/docker-compose.immutable-runtime.yml" ) ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL = ( f"{ENGINE_NODE_INTELLIGENCE_SERVICE_ROOT_REL}/activation.json" ) ENGINE_NODE_INTELLIGENCE_IMAGE_ARCHIVE_REL = ( f"{ENGINE_NODE_INTELLIGENCE_SERVICE_ROOT_REL}/image/engine-node-intelligence.tar" ) ENGINE_NODE_INTELLIGENCE_PREDECESSOR_GATEWAY_SHA256 = ( "6b8c80fa997ef7c438d199a6ee942c5e37aebc4057667af417897fa636131db4" ) ENGINE_NODE_INTELLIGENCE_PREDECESSOR_COMPOSE_SHA256 = ( "258cebb64ff1943c939655cc55bdce00fc5c4dced67ec291d84d6df066ace50e" ) ENGINE_NODE_INTELLIGENCE_ARTIFACT_ENTRIES = ( ENGINE_NODE_INTELLIGENCE_SOURCE_REL, ENGINE_NODE_INTELLIGENCE_GATEWAY_REL, ENGINE_NODE_INTELLIGENCE_SERVICE_ROOT_REL, ) 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_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_MCP_AUTONOMY_PROVIDER_V5_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.6.tgz", "nodedc-source/server/assets/provider-packages/v1/catalog.json", "nodedc-source/server/engineAgents/store.js", ENGINE_NODE_INTELLIGENCE_GATEWAY_REL, ENGINE_MCP_CONTROL_PLANE_DESCRIPTOR_REL, ) ENGINE_MCP_AUTONOMY_PROVIDER_V5_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": "17f3e368f3264cbbd708965c9e1fd735aa974f15a1383bf88cd6d14a43dbf32d", "nodedc-source/server/engineAgents/store.js": "debd351fe0b8c72b33b8c79909b8f339cbaf061b970576f3ae72df52ebaa211f", ENGINE_NODE_INTELLIGENCE_GATEWAY_REL: "96c726dab5cf1341f74e6e1095d518058ca320e0dd5738e25bdbe75db1f4fc15", } ENGINE_MCP_AUTONOMY_PROVIDER_V5_TARGET_SHA256 = { "nodedc-source/server/assets/engine-agent-npm/bin/nodedc-engine-codex-agent.mjs": "c15c9da4f90f44a4e9e12f3683127e906d614fb98e562faa0c939505c973e074", "nodedc-source/server/assets/engine-agent-npm/package.json": "2ca8dcab0fa04bb1b21add1f75a9be61d5aa97753443ee1917302b4e0da5780a", "nodedc-source/server/assets/nodedc-engine-codex-agent-0.1.6.tgz": "d007a81cb4e4af569b3c54d3869b0f60b5597e3b531bff232145fa1851d8572a", "nodedc-source/server/assets/provider-packages/v1/catalog.json": "63e0741646197f0b1b3c64a4095e1bc8fb3a95ee6caf20b0293f89d869c9e620", "nodedc-source/server/engineAgents/store.js": "4cd4bdd5958cfafee184e98a04fe12aa0c1cbe884326beaec63c99f9fff61285", ENGINE_NODE_INTELLIGENCE_GATEWAY_REL: "5331f6dc8dc306f641370a2968eb025ee3e278e27ae9a217e4cfc2901fec369c", } ENGINE_MCP_AUTONOMY_PROVIDER_V5_NEW_PATHS = ( "nodedc-source/server/assets/nodedc-engine-codex-agent-0.1.6.tgz", ) ENGINE_L2_CLOSED_LOOP_DESCRIPTOR_REL = ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL ENGINE_L2_CLOSED_LOOP_ARTIFACT_ENTRIES = ( "nodedc-source/server/index.js", "nodedc-source/server/l2/graphRepository.js", "nodedc-source/server/realtime/ws.js", ENGINE_NODE_INTELLIGENCE_GATEWAY_REL, "nodedc-source/server/routes/n8n.js", "nodedc-source/server/routes/ndcAgentMcp.js", "nodedc-source/src/App.tsx", "nodedc-source/src/n8n/N8nSubworkflowHost.tsx", "nodedc-source/src/realtime/useMultiplayer.ts", "nodedc-source/src/utils/n8nApi.ts", "nodedc-source/dist/index.html", "nodedc-source/dist/assets", ENGINE_L2_CLOSED_LOOP_DESCRIPTOR_REL, ) ENGINE_L2_CLOSED_LOOP_PARTIAL_SHA256 = { "nodedc-source/server/index.js": "1896e1cade61579863c50ff3f52f2e81ff27f2a511a9d362db81925ee21cefad", "nodedc-source/server/l2/graphRepository.js": "94ad08e1bb7e7be04854f1f911e06ee1acd6e09631f33f4c01e42e230665ac33", "nodedc-source/server/realtime/ws.js": "82cfa833e05c2fc6d6049dd4564af06164b5c784fdfb82c243ca67519f4509c2", ENGINE_NODE_INTELLIGENCE_GATEWAY_REL: "4f600a2781be9118bec891fa2a6f20d7f55e0892ef2f06af24059193cebd28f4", "nodedc-source/server/routes/n8n.js": "752cb1524160adc1c95163c34e139b615c063d2ce8980f2a63879bddbd0f1e08", "nodedc-source/server/routes/ndcAgentMcp.js": "353a10291ceb93c3641ece60ec6e809a394be86f5ceb97cb44755178940409dd", "nodedc-source/src/App.tsx": "b3e61a89330b774f309660208d05143d299ab582f18765c1c14e2122a62c4d5e", "nodedc-source/src/n8n/N8nSubworkflowHost.tsx": "683aa44ba92aeac4879889e2bdb5319282976d7680d620701578830ce9677087", "nodedc-source/src/realtime/useMultiplayer.ts": "a4d001d9914098e50a78d8abe0a66344d23680b7a19b3573aa7b6f48c8bb9c35", "nodedc-source/src/utils/n8nApi.ts": "6f16d4992c51ffcc1e71a7bd172fd9ceb440d6e1a74d69ef1db62e0ac4230b3c", "nodedc-source/dist/index.html": "90d72f8790dc8cf951066b1210447c84d640373117bae23f3a4cb3227fb96d6d", "nodedc-source/dist/assets/index-Bim2pv1P.css": "18322addd45c126a7b8396f36b005f3085fddba3e9b346dd2c910f6fa6987ebf", "nodedc-source/dist/assets/index-CqvJfRRS.js": "06e6c23b03ea2ba8a4890e1f1714fd08ebaf18d735834200af6ab430af360ca8", ENGINE_L2_CLOSED_LOOP_DESCRIPTOR_REL: "84b0e15a10cedf334ad04d6f31c908da2dc155503c9974f0eeb75b01e20fb884", } ENGINE_L2_CLOSED_LOOP_TARGET_SHA256 = { **ENGINE_L2_CLOSED_LOOP_PARTIAL_SHA256, ENGINE_L2_CLOSED_LOOP_DESCRIPTOR_REL: "63e7619c6971d02102583bb5d80d33ece293b952f113200fa0b76f0e88c2dd32", } ENGINE_L2_CLOSED_LOOP_STABLE_SHA256 = { "nodedc-source/server/index.js": "0ac408e0e9a7bc5c8e13a00afc957b982b065e2bc414919839e0b0a11aa05ba4", "nodedc-source/server/realtime/ws.js": "340437ee2c6cc06c41b1e47f8b1ada24b68e175bb6fc4c361398bef00dd89686", ENGINE_NODE_INTELLIGENCE_GATEWAY_REL: "5331f6dc8dc306f641370a2968eb025ee3e278e27ae9a217e4cfc2901fec369c", "nodedc-source/server/routes/n8n.js": "9bc3638e271102abec91bb80413329befb89d60c0e4dc0548f0dd11e93220d0a", "nodedc-source/server/routes/ndcAgentMcp.js": "534e2c85e1faecc72a00da7ad32d0584ee09b6bd89eeec2221c3b23d80e1e962", "nodedc-source/src/App.tsx": "d9bc0f23ceaaf4f5534ac9a9b1f97d84fd8eee4679c59b5999b5a75b8c77bb32", "nodedc-source/src/n8n/N8nSubworkflowHost.tsx": "306694f329670a64322651ad249b057a74b34dace82d68d4071a365636d91d71", "nodedc-source/src/realtime/useMultiplayer.ts": "3d8f150037d994dc8739fa9f6696acfe874c45128e567a6090cc4996b76f9e9d", "nodedc-source/src/utils/n8nApi.ts": "2d10acda54d8c10637008612af9e3ad7625bd7fe17e9cfa38aa45c340eb171eb", "nodedc-source/dist/index.html": "8894f62ad590168862e81266bc4602410279634b666af72cb14ad80aeb71ea74", "nodedc-source/dist/assets/index-4TenBzkg.js": "a48fcacf3348c20a432cd6da2627b89c0c3e54bc989c727b620125dd3412538a", "nodedc-source/dist/assets/index-BQ3RBHy9.js": "6e0f4c841afe3e0beb398943af915c8ee5d4a7df0b77b92e277e66faa3fa26a8", "nodedc-source/dist/assets/index-Bim2pv1P.css": "18322addd45c126a7b8396f36b005f3085fddba3e9b346dd2c910f6fa6987ebf", "nodedc-source/dist/assets/index-DJ1CMfu4.js": "a0aaf72d2ed390142ff2ea03dbcfdd534637e5faefd80d8aa7d705a804abe065", ENGINE_L2_CLOSED_LOOP_DESCRIPTOR_REL: "84b0e15a10cedf334ad04d6f31c908da2dc155503c9974f0eeb75b01e20fb884", } ENGINE_L2_CLOSED_LOOP_FAILED_PATCH_ID = "engine-l2-closed-loop-20260723-030" ENGINE_L2_CLOSED_LOOP_FAILED_ARTIFACT = ( "nodedc-engine-l2-closed-loop-20260723-030.tgz.20260723-123228" ) ENGINE_L2_CLOSED_LOOP_FAILED_ARTIFACT_SHA256 = ( "4c8401fcb1d318c52933cc3327015e9e4cbaaa5c38f1b45856acec2c72efdd2e" ) ENGINE_L2_CLOSED_LOOP_RECOVERY_BACKUP_ID = ( "engine-engine-l2-closed-loop-20260723-030-20260723-123228" ) ENGINE_L2_CLOSED_LOOP_RECOVERY_BACKUP_SHA256 = { "manifest.env": "7cf2a04c64f62547d408613307f7880bf350b9d305306af0e4eb8b81cfaa73bc", "files.txt": "c4d2e0395b8ff076b648bd348283e4113feecf50b056ba6d1f495bafe4d09f27", "source-before.tgz": "056d9400d52eeda7a51d225a803ab4d3465e06541d6646b4b728296a21c08ec5", "existing-files.txt": "4c5de3d062d96b398dcaf44a6dde20c168ca0efd263893d51ce1fc545c59b8f8", "missing-files.txt": "16d708945792cb35fcbe11d2c23a146e489fb183c4f498496ef40a97090c6008", } ENGINE_PROVIDER_SECURITY_CATALOG_REL = ( "nodedc-source/server/assets/provider-packages/v1/catalog.json" ) ENGINE_PROVIDER_SECURITY_CATALOG_ARTIFACT_ENTRIES = ( ENGINE_PROVIDER_SECURITY_CATALOG_REL, ) ENGINE_PROVIDER_SECURITY_CATALOG_PREDECESSOR_SHA256 = ( "dd67d8b01091072a4e7be3820b9639c12f2f897ccbbd79208123564d10c1cd81" ) ENGINE_PROVIDER_SECURITY_CATALOG_TARGET_SHA256 = ( "773335bb616a5c03eb2108c4f53ef092c02e75ee75f014511201bc12e2956b27" ) 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" EXTERNAL_DATA_PLANE_DATABASE_SERVICE = "external-data-plane-postgres" EXTERNAL_DATA_PLANE_SERVICE = "external-data-plane" EXTERNAL_DATA_PLANE_IMAGE = "nodedc/external-data-plane:local" ENGINE_N8N_VERSION = "2.3.2" ENGINE_N8N_BASE_IMAGE = "docker.n8n.io/n8nio/n8n:2.3.2" ENGINE_N8N_BASE_ARCHITECTURE = "amd64" ENGINE_N8N_RUNTIME_PACKAGE_PATH = "/home/node/.n8n/nodes/node_modules/n8n-nodes-ndc" 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_RELEASE_CATALOG_JSON_SHA256 = { "0.1.2-05e4b38b14b4a019": { "nodes": "230f712230f7ee8debaa4b44a4358cc19c205bc43305d8ed89d5e3daac466506", "credentials": "a26824ffc0e15db857c792153de3417febc0dbba4880380d6c8cd544b3c80f4d", "meta": "caf7635420c61333e65f68350f5567217aed96fb51354b38764bcac2c24e7966", }, "0.1.3-3354149b5245e39a": { "nodes": "b0a5215699a6e691b8cfc505ab457d5632ef6d83adb3537520d0b60760ba16b2", "credentials": "a26824ffc0e15db857c792153de3417febc0dbba4880380d6c8cd544b3c80f4d", "meta": "b8aa60c0d919573626fa0694a1e4ae9ede015325a5d312e3e05d5ac293084aa7", }, "0.1.4-59dc9f7882721d6a": { "nodes": "b0a5215699a6e691b8cfc505ab457d5632ef6d83adb3537520d0b60760ba16b2", "credentials": "af8ada839070a4c24b9b10981a751c2b2db651dced1392333d7a4ef13de4564e", "meta": "8fadbc594c5d14b1d53ab69b677f00823949410b9a37f4e43ecc321e89d68983", }, "0.1.5-3c8ae53f010d7c88": { "nodes": "fb2ef4d38b9c071fae117a27381714658a1a19334b82fc79d64d13f27555fe5a", "credentials": "af8ada839070a4c24b9b10981a751c2b2db651dced1392333d7a4ef13de4564e", "meta": "b324592c38d518af8320d8540c7061e4684056ae88a676cf5e50c6eef03c0dd7", }, "0.1.6-25cc2d7a52a0d0ee": { "nodes": "fb2ef4d38b9c071fae117a27381714658a1a19334b82fc79d64d13f27555fe5a", "credentials": "af8ada839070a4c24b9b10981a751c2b2db651dced1392333d7a4ef13de4564e", "meta": "58adcb182e5f231a4798636a99887a1f214e3c6e1eb4c0dc2d9b2f3aa80a99ef", }, } ENGINE_N8N_INACTIVE_NODES_CATALOG_JSON_SHA256 = "b70d9d8130d498c55de46a5d0758c844f1242b70803457b2291ab3a9de8056f2" ENGINE_N8N_INACTIVE_CREDENTIALS_CATALOG_JSON_SHA256 = "680e9f52aac791efbd38e3bd99bd51ef5cded9756d867897c6c2755850e87b50" ENGINE_N8N_INACTIVE_META_JSON_SHA256 = "0ac555d7ab31cb0ca042db4f474e83cfefbf20b5bbb2e1509fead27ed21e8acb" MAP_GATEWAY_RUNTIME_GID = 1000 EXTERNAL_DATA_PLANE_RUNTIME_UID = 11006 EXTERNAL_DATA_PLANE_RUNTIME_GID = 11006 MAX_ARTIFACT_BYTES = 512 * 1024 * 1024 MAX_MEMBER_COUNT = 20000 MAX_PAYLOAD_BYTES = 1024 * 1024 * 1024 MAX_FILE_BYTES = 256 * 1024 * 1024 PATCH_ID_RE = re.compile(r"^[A-Za-z0-9._-]{1,96}$") MANIFEST_KEYS = {"id", "component", "type"} MAP_GATEWAY_SECRET_RE = re.compile(r"^[A-Za-z0-9_-]{48,256}$") EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_RE = re.compile(r"^[A-Za-z0-9_-]{48,256}$") ENGINE_NODE_INTELLIGENCE_SECRET_RE = re.compile(r"^[A-Za-z0-9_-]{48,256}$") N8N_PRIVATE_EXTENSION_RELEASE_RE = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+-[a-f0-9]{16}$") N8N_PRIVATE_EXTENSION_NODES = ( "dist/nodes/NdcDataProductPublish/NdcDataProductPublish.node.js", "dist/nodes/NdcDataProductRead/NdcDataProductRead.node.js", "dist/nodes/NdcFoundryBinding/NdcFoundryBinding.node.js", ) 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", "n8n-nodes-ndc.ndcDataProductRead", "n8n-nodes-ndc.ndcFoundryBinding", ) 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, "0.1.5-3c8ae53f010d7c88": ENGINE_N8N_CREDENTIAL_TYPES, "0.1.6-25cc2d7a52a0d0ee": ENGINE_N8N_CREDENTIAL_TYPES, } ENGINE_CREDENTIAL_SINK_ARTIFACT_ENTRIES = ( "nodedc-source/server/credentialPolicies/ndcPrivateNode.js", "nodedc-source/server/credentialSink", "nodedc-source/server/index.js", "nodedc-source/server/routes/engineAgentGateway.js", "nodedc-source/server/routes/engineCredentialSink.js", "nodedc-source/server/routes/n8n.js", "nodedc-source/server/routes/ndcAgentMcp.js", ENGINE_CREDENTIAL_BACKEND_OVERRIDE_TEMPLATE_REL, ) ENGINE_DATA_PRODUCT_PUBLISH_GRANT_ARTIFACT_ENTRIES = ( "nodedc-source/server/assets/provider-packages/v1/catalog.json", "nodedc-source/server/dataProductPublishGrant", "nodedc-source/server/engineAgents/store.js", "nodedc-source/server/routes/engineAgentGateway.js", "nodedc-source/server/routes/n8n.js", ENGINE_DATA_PRODUCT_PUBLISH_GRANT_OVERRIDE_REL, ) ENGINE_DATA_PRODUCT_PUBLISH_GRANT_SOURCE_PREFIX = ( "nodedc-source/server/dataProductPublishGrant/" ) ENGINE_DATA_PRODUCT_PUBLISH_GRANT_SOURCE_FILES = ( "acceptance.js", "providerCatalog.js", "service.js", "signedDataPlaneClient.js", "store.js", ) ENGINE_PUBLISH_GRANT_COMPOSITE_PROVIDER_CATALOG_PREDECESSOR_SHA256 = ( "992159fc457ec76ce1f45aad337604c8a72b29252d44fbccda515f0fd6ea6428" ) ENGINE_PUBLISH_GRANT_COMPOSITE_PROVIDER_CATALOG_TARGET_SHA256 = ( "9c931f9abfcadb5b34a8a854c2efb8fd79913e000eecf0967d1b7c500bf9a56a" ) ENGINE_PUBLISH_GRANT_COMPOSITE_PROVIDER_CHANGED_PATHS = ( "nodedc-source/server/assets/provider-packages/v1/catalog.json", "nodedc-source/server/dataProductPublishGrant/providerCatalog.js", "nodedc-source/server/dataProductPublishGrant/service.js", "nodedc-source/server/dataProductPublishGrant/store.js", ) ENGINE_COMPOSITE_PROVIDER_V4_ARTIFACT_ENTRIES = ( "nodedc-source/server/assets/provider-packages/v1/catalog.json", "nodedc-source/server/dataProductPublishGrant/providerCatalog.js", "nodedc-source/server/dataProductPublishGrant/service.js", "nodedc-source/server/dataProductPublishGrant/store.js", ) ENGINE_COMPOSITE_PROVIDER_V4_PREDECESSOR_SHA256 = { "nodedc-source/server/assets/provider-packages/v1/catalog.json": "992159fc457ec76ce1f45aad337604c8a72b29252d44fbccda515f0fd6ea6428", "nodedc-source/server/dataProductPublishGrant/providerCatalog.js": "901b8fad80018ce177b34ced804b39cb140a47e831414057f484296b373c651d", # Publish Grant 20260718-004 was applied after MCP Control Plane # 20260718-003 and is therefore the authoritative installed predecessor. "nodedc-source/server/dataProductPublishGrant/service.js": "6a417b25b080c05b40c771df4e2dca163491af2c9083ff73dc7e54ad3b360241", "nodedc-source/server/dataProductPublishGrant/store.js": "a92303b2732e21f68c1ac732fa26e4983cbadf3515741cee983b916a283d754c", } ENGINE_COMPOSITE_PROVIDER_V4_TARGET_SHA256 = { "nodedc-source/server/assets/provider-packages/v1/catalog.json": "9c931f9abfcadb5b34a8a854c2efb8fd79913e000eecf0967d1b7c500bf9a56a", "nodedc-source/server/dataProductPublishGrant/providerCatalog.js": "689c6fbf695e582d983159973a21142787d1b19bb1d343da4c62c03092ac291f", "nodedc-source/server/dataProductPublishGrant/service.js": "83f045f4e0f332644310172ed51bb652d808bf04155b11c02e46bdffc95f7220", "nodedc-source/server/dataProductPublishGrant/store.js": "98662da6acd0489a9cae4b726eb61ce89d9b2c788b2ea95433e9c4f02930c095", } ENGINE_PROVIDER_ROTATING_SLOT_ARTIFACT_ENTRIES = ( "nodedc-source/server/assets/provider-packages/v1/catalog.json", "nodedc-source/server/dataProductPublishGrant/providerCatalog.js", ) ENGINE_PROVIDER_ROTATING_SLOT_PREDECESSOR_SHA256 = { "nodedc-source/server/assets/provider-packages/v1/catalog.json": "9c931f9abfcadb5b34a8a854c2efb8fd79913e000eecf0967d1b7c500bf9a56a", "nodedc-source/server/dataProductPublishGrant/providerCatalog.js": "689c6fbf695e582d983159973a21142787d1b19bb1d343da4c62c03092ac291f", } ENGINE_PROVIDER_ROTATING_SLOT_TARGET_SHA256 = { "nodedc-source/server/assets/provider-packages/v1/catalog.json": "17f3e368f3264cbbd708965c9e1fd735aa974f15a1383bf88cd6d14a43dbf32d", "nodedc-source/server/dataProductPublishGrant/providerCatalog.js": "f6cf5f4de9e57f87fb904e2136a9f34d494e1ffc289aec3ed9ed788b5583a062", } ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_ARTIFACT_ENTRIES = ( "nodedc-source/server/nodeIntelligence/upstreamProjection.js", ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL, ) ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_PREDECESSOR_SHA256 = { "nodedc-source/server/nodeIntelligence/upstreamProjection.js": "761a874b102a938bc6018159ddacdaac71ad6ae08e9f0f8d7f3b58a0165a5131", ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL: "8d65991fbc23578fd3e4830844d52827212c78aa94a9cb3d13bac7594b888497", } ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_TARGET_SHA256 = { "nodedc-source/server/nodeIntelligence/upstreamProjection.js": "2dfa6b4f37d9bfa8b92a8109d4060d02dd2634ceb8f7924504b83ccf3fdd1523", ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL: "84b0e15a10cedf334ad04d6f31c908da2dc155503c9974f0eeb75b01e20fb884", } ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_ARTIFACT_ENTRIES = ( "nodedc-source/server/assets/provider-packages/v1/catalog.json", "nodedc-source/server/assets/provider-packages/v1/depttrans-zone-authority-v1.json", "nodedc-source/server/dataProductPublishGrant/providerCatalog.js", "nodedc-source/server/dataProductPublishGrant/service.js", "nodedc-source/server/dataProductPublishGrant/store.js", ) ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_PREDECESSOR_SHA256 = { "nodedc-source/server/assets/provider-packages/v1/catalog.json": "63e0741646197f0b1b3c64a4095e1bc8fb3a95ee6caf20b0293f89d869c9e620", "nodedc-source/server/dataProductPublishGrant/providerCatalog.js": "1a14299167ebe17efd677fa3c59b84c80e12d6846bac6f40f9bcae0729ab22c6", "nodedc-source/server/dataProductPublishGrant/service.js": "83f045f4e0f332644310172ed51bb652d808bf04155b11c02e46bdffc95f7220", "nodedc-source/server/dataProductPublishGrant/store.js": "98662da6acd0489a9cae4b726eb61ce89d9b2c788b2ea95433e9c4f02930c095", } ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_TARGET_SHA256 = { "nodedc-source/server/assets/provider-packages/v1/catalog.json": "1aac712a05e55137d69eedaf363969460ffe866288e8e18e35711967ab232f39", "nodedc-source/server/assets/provider-packages/v1/depttrans-zone-authority-v1.json": "1482032178b4816e599df570face9b1dfa8ae1fa2943ac8f941c561e8cb9aa9d", "nodedc-source/server/dataProductPublishGrant/providerCatalog.js": "e4c216b0affd89ddbd48abcec86febd48eb12a4c56507851daeb47f2ccd60c9a", "nodedc-source/server/dataProductPublishGrant/service.js": "408836564e9a422eb5e648cc24d764f4bfdf7f83d93306742e8615fa8186a642", "nodedc-source/server/dataProductPublishGrant/store.js": "ace5971d0772e9a9499f0849e6b754e3677cc2ca3080e573a12e26acf5a6c0c0", } ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_NEW_PATHS = ( "nodedc-source/server/assets/provider-packages/v1/depttrans-zone-authority-v1.json", ) ENGINE_PROVIDER_TARGET_HOST_POLICY_ARTIFACT_ENTRIES = ( "nodedc-source/server/routes/n8n.js", ) ENGINE_PROVIDER_TARGET_HOST_POLICY_PREDECESSOR_SHA256 = { "nodedc-source/server/routes/n8n.js": "f293a7794405badbabd2bf7ef088f96fe1167d9e249f05cbfc8af280a0d3e8f8", } ENGINE_PROVIDER_TARGET_HOST_POLICY_TARGET_SHA256 = { "nodedc-source/server/routes/n8n.js": "9bc3638e271102abec91bb80413329befb89d60c0e4dc0548f0dd11e93220d0a", } ENGINE_MCP_EXECUTION_PROFILE_DECODER_DESCRIPTOR_REL = ( "nodedc-source/server/deployTransitions/executionProfileDecoderV1.json" ) ENGINE_MCP_EXECUTION_PROFILE_DECODER_ARTIFACT_ENTRIES = ( "nodedc-source/server/routes/n8n.js", ENGINE_MCP_EXECUTION_PROFILE_DECODER_DESCRIPTOR_REL, ) ENGINE_MCP_EXECUTION_PROFILE_DECODER_PREDECESSOR_SHA256 = { "nodedc-source/server/routes/n8n.js": "752cb1524160adc1c95163c34e139b615c063d2ce8980f2a63879bddbd0f1e08", } ENGINE_MCP_EXECUTION_PROFILE_DECODER_TARGET_SHA256 = { "nodedc-source/server/routes/n8n.js": "1c2427c1d5830c40b1e8ae05f3d683fc39d07d7e0fe2431b0f6efbbb1d3fcb88", ENGINE_MCP_EXECUTION_PROFILE_DECODER_DESCRIPTOR_REL: "93e431902e9bcd3b828a82ed6b42b48d939051f21bf8824dafcf2addac8a711c", } ENGINE_MCP_EXECUTION_PROFILE_DECODER_NEW_PATHS = ( ENGINE_MCP_EXECUTION_PROFILE_DECODER_DESCRIPTOR_REL, ) ENGINE_MCP_TELEMETRY_CATALOG_DESCRIPTOR_REL = ( "nodedc-source/server/deployTransitions/telemetryReadingCatalogV1.json" ) 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", } 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, ) ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_DESCRIPTOR_REL = ( "nodedc-source/server/deployTransitions/executionPlanMaterializationV1.json" ) ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_ARTIFACT_ENTRIES = ( "nodedc-source/server/routes/n8n.js", "nodedc-source/server/routes/engineAgentGateway.js", "nodedc-source/server/l2ExecutionPlan/catalog.js", "nodedc-source/server/l2ExecutionPlan/compiler.js", "nodedc-source/server/l2ExecutionPlan/materializer.js", "nodedc-source/server/assets/execution-plans/v1/catalog.json", ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_DESCRIPTOR_REL, ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL, ) ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_PREDECESSOR_SHA256 = { "nodedc-source/server/routes/n8n.js": "903245ae363e9b9ac161498f17988a38876a0e0ac8d80f3fa1b0112ceb7fe906", "nodedc-source/server/routes/engineAgentGateway.js": "69bfc91e913a3fad04e13aca86efb9d62f73c0c7d1f8f7200907494b29fa9e8d", ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL: "25ed3efd858aaf82c242dba501f0acc0c6c3dc91e8845707a4d3325750eab59f", } ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_FOUNDATION_SHA256 = { ENGINE_MCP_EXECUTION_PROFILE_DECODER_DESCRIPTOR_REL: "93e431902e9bcd3b828a82ed6b42b48d939051f21bf8824dafcf2addac8a711c", ENGINE_MCP_TELEMETRY_CATALOG_DESCRIPTOR_REL: "b25ab8b6e6ad8ac24614c4630cc3635abe453464466d5a72238b05e48a24d882", } ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_TARGET_SHA256 = { "nodedc-source/server/routes/n8n.js": "391fc81228fdacd6efc6c0868991a3485f71869708e49ac15819db6ccce1bfce", "nodedc-source/server/routes/engineAgentGateway.js": "9020cf49a3558c0497fed4ecfd81367cbc11b5b249881804c29fe437900c71f8", "nodedc-source/server/l2ExecutionPlan/catalog.js": "c35b4c7ad9319aabbf1366a11ff52a6e99d961893bafdfcb4e84fc5f24fc04be", "nodedc-source/server/l2ExecutionPlan/compiler.js": "beb3f664073f9a372432643936a04d0cb0028cd695f759c68bd053e9cd892fe4", "nodedc-source/server/l2ExecutionPlan/materializer.js": "ee3bcfd06b3a5fa46800df974a2dedfdd55eeaf662329f837486c011a9bd713e", "nodedc-source/server/assets/execution-plans/v1/catalog.json": "a153e040e0a592bad4375b98d9a1d83d148923aa0f0fd47d225b92eda281c4e9", ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_DESCRIPTOR_REL: "52c0152cff49c251be5581a9209d2e63ba710c16e815bdd6ece24f7c9dd7e480", ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL: "03b2ba120c3929e9cf99940ddc927082de376ceff762895a84103144202aef42", } ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_NEW_PATHS = ( "nodedc-source/server/l2ExecutionPlan/catalog.js", "nodedc-source/server/l2ExecutionPlan/compiler.js", "nodedc-source/server/l2ExecutionPlan/materializer.js", "nodedc-source/server/assets/execution-plans/v1/catalog.json", ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_DESCRIPTOR_REL, ) ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_DESCRIPTOR_REL = ( "nodedc-source/server/deployTransitions/executionPlanTelemetryRuntimeV2.json" ) ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_ARTIFACT_ENTRIES = ( "nodedc-source/server/l2ExecutionPlan/compiler.js", "nodedc-source/server/assets/execution-plans/v1/catalog.json", ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_DESCRIPTOR_REL, ) ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_PREDECESSOR_SHA256 = { "nodedc-source/server/l2ExecutionPlan/compiler.js": "beb3f664073f9a372432643936a04d0cb0028cd695f759c68bd053e9cd892fe4", "nodedc-source/server/assets/execution-plans/v1/catalog.json": "a153e040e0a592bad4375b98d9a1d83d148923aa0f0fd47d225b92eda281c4e9", } ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_FOUNDATION_SHA256 = { "nodedc-source/server/routes/n8n.js": "391fc81228fdacd6efc6c0868991a3485f71869708e49ac15819db6ccce1bfce", "nodedc-source/server/routes/engineAgentGateway.js": "9020cf49a3558c0497fed4ecfd81367cbc11b5b249881804c29fe437900c71f8", "nodedc-source/server/l2ExecutionPlan/catalog.js": "c35b4c7ad9319aabbf1366a11ff52a6e99d961893bafdfcb4e84fc5f24fc04be", "nodedc-source/server/l2ExecutionPlan/materializer.js": "ee3bcfd06b3a5fa46800df974a2dedfdd55eeaf662329f837486c011a9bd713e", ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_DESCRIPTOR_REL: "52c0152cff49c251be5581a9209d2e63ba710c16e815bdd6ece24f7c9dd7e480", ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL: "03b2ba120c3929e9cf99940ddc927082de376ceff762895a84103144202aef42", } ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_TARGET_SHA256 = { "nodedc-source/server/l2ExecutionPlan/compiler.js": "6b783ad15c26dc7de0645082c8a426002d943138c70bf31a240247b16a33b6a0", "nodedc-source/server/assets/execution-plans/v1/catalog.json": "5bdfc92284a7c836ff326e3a89b559110e376b32efd34b5f23f2bec272745781", ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_DESCRIPTOR_REL: "68b275efb6303284336d8b637c966c24d891a4bd0b3fec4fb83247359929ef79", } ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_NEW_PATHS = ( ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_DESCRIPTOR_REL, ) ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_DESCRIPTOR_REL = ( "nodedc-source/server/deployTransitions/executionPlanModuleOwnershipV3.json" ) ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_ARTIFACT_ENTRIES = ( "nodedc-source/server/l2ExecutionPlan/materializer.js", "nodedc-source/server/routes/engineAgentGateway.js", "nodedc-source/server/assets/execution-plans/v1/catalog.json", ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_DESCRIPTOR_REL, ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL, ) ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_PREDECESSOR_SHA256 = { "nodedc-source/server/l2ExecutionPlan/materializer.js": "ee3bcfd06b3a5fa46800df974a2dedfdd55eeaf662329f837486c011a9bd713e", "nodedc-source/server/routes/engineAgentGateway.js": "9020cf49a3558c0497fed4ecfd81367cbc11b5b249881804c29fe437900c71f8", "nodedc-source/server/assets/execution-plans/v1/catalog.json": "5bdfc92284a7c836ff326e3a89b559110e376b32efd34b5f23f2bec272745781", ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL: "03b2ba120c3929e9cf99940ddc927082de376ceff762895a84103144202aef42", } ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_FOUNDATION_SHA256 = { "nodedc-source/server/routes/n8n.js": "391fc81228fdacd6efc6c0868991a3485f71869708e49ac15819db6ccce1bfce", "nodedc-source/server/l2ExecutionPlan/catalog.js": "c35b4c7ad9319aabbf1366a11ff52a6e99d961893bafdfcb4e84fc5f24fc04be", "nodedc-source/server/l2ExecutionPlan/compiler.js": "6b783ad15c26dc7de0645082c8a426002d943138c70bf31a240247b16a33b6a0", ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_DESCRIPTOR_REL: "52c0152cff49c251be5581a9209d2e63ba710c16e815bdd6ece24f7c9dd7e480", ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_DESCRIPTOR_REL: "68b275efb6303284336d8b637c966c24d891a4bd0b3fec4fb83247359929ef79", } ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_TARGET_SHA256 = { "nodedc-source/server/l2ExecutionPlan/materializer.js": "dabf0073520049d7a04d1962b29e591ae092b87b287a50534ad2d98b03ae683c", "nodedc-source/server/routes/engineAgentGateway.js": "17c5f502b3ceacc45278eef7418d08e1e55a8b3e46e00942e559d25566fdba41", "nodedc-source/server/assets/execution-plans/v1/catalog.json": "7e34b93aa30b06cd52853b48013baef2970cc27e4fddc333a1011dc73ea8c08a", ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_DESCRIPTOR_REL: "d187d539a219fec453e06c55c3f5486ef66059371b4ae9b145266307b2af9889", ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL: "4cdeb85ebb2e43f088f095f75b7fc31aabd8ab81c8ac96ded4aafd7dbd8e30bd", } ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_NEW_PATHS = ( ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_DESCRIPTOR_REL, ) ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_DESCRIPTOR_REL = ( "nodedc-source/server/deployTransitions/normalizedIdentitySearchV1.json" ) ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_ARTIFACT_ENTRIES = ( "nodedc-source/server/routes/n8n.js", "nodedc-source/server/routes/engineAgentGateway.js", "nodedc-source/server/l2ExecutionPlan/compiler.js", "nodedc-source/server/assets/execution-plans/v1/catalog.json", "nodedc-source/server/assets/provider-packages/v1/catalog.json", ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_DESCRIPTOR_REL, ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL, ) ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_PREDECESSOR_SHA256 = { "nodedc-source/server/routes/n8n.js": "391fc81228fdacd6efc6c0868991a3485f71869708e49ac15819db6ccce1bfce", "nodedc-source/server/routes/engineAgentGateway.js": "17c5f502b3ceacc45278eef7418d08e1e55a8b3e46e00942e559d25566fdba41", "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", ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL: "4cdeb85ebb2e43f088f095f75b7fc31aabd8ab81c8ac96ded4aafd7dbd8e30bd", } ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_FOUNDATION_SHA256 = { "nodedc-source/server/l2ExecutionPlan/catalog.js": "c35b4c7ad9319aabbf1366a11ff52a6e99d961893bafdfcb4e84fc5f24fc04be", "nodedc-source/server/l2ExecutionPlan/materializer.js": "dabf0073520049d7a04d1962b29e591ae092b87b287a50534ad2d98b03ae683c", ENGINE_MCP_EXECUTION_PROFILE_DECODER_DESCRIPTOR_REL: "93e431902e9bcd3b828a82ed6b42b48d939051f21bf8824dafcf2addac8a711c", ENGINE_MCP_TELEMETRY_CATALOG_DESCRIPTOR_REL: "b25ab8b6e6ad8ac24614c4630cc3635abe453464466d5a72238b05e48a24d882", ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_DESCRIPTOR_REL: "52c0152cff49c251be5581a9209d2e63ba710c16e815bdd6ece24f7c9dd7e480", ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_DESCRIPTOR_REL: "68b275efb6303284336d8b637c966c24d891a4bd0b3fec4fb83247359929ef79", ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_DESCRIPTOR_REL: "d187d539a219fec453e06c55c3f5486ef66059371b4ae9b145266307b2af9889", } ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_TARGET_SHA256 = { "nodedc-source/server/routes/n8n.js": "a2bc69c72f68e57ed27120d0c88f4ffe6310bbf0c4360c8b0dba0953ccaaf522", "nodedc-source/server/routes/engineAgentGateway.js": "4a9524fd954320277042c783b7b19cbb2172f075f27652c0eebfd743ffc47872", "nodedc-source/server/l2ExecutionPlan/compiler.js": "01958d541c778002d33e3aead0cfe02df2084eb7222ce941cc7149d73a10f135", "nodedc-source/server/assets/execution-plans/v1/catalog.json": "2b8e5ee3d73d16f3cd6e34d1f7394946a6270a17b0e9e6511f12921ba2561fd3", "nodedc-source/server/assets/provider-packages/v1/catalog.json": "fe5256fd2ba295819daecc8d2acae34687e807978a6730dfa20703cb3bab0c1b", ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_DESCRIPTOR_REL: "41738185fe103642912b0aa1c29c51860970c38f9f916cc3725e79e258b9ea7e", ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL: "3e7aeb1d28eb291461f79cd656ece6488bc6d372124088e92f53bf89c3373f61", } ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_NEW_PATHS = ( ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_DESCRIPTOR_REL, ) ENGINE_MCP_L1_CREDENTIAL_REUSE_DESCRIPTOR_REL = ( "nodedc-source/server/deployTransitions/l1CredentialReuseV1.json" ) ENGINE_MCP_L1_CREDENTIAL_REUSE_ARTIFACT_ENTRIES = ( "nodedc-source/server/routes/n8n.js", ENGINE_MCP_L1_CREDENTIAL_REUSE_DESCRIPTOR_REL, ) ENGINE_MCP_L1_CREDENTIAL_REUSE_PREDECESSOR_SHA256 = { "nodedc-source/server/routes/n8n.js": "a2bc69c72f68e57ed27120d0c88f4ffe6310bbf0c4360c8b0dba0953ccaaf522", } ENGINE_MCP_L1_CREDENTIAL_REUSE_FOUNDATION_SHA256 = { "nodedc-source/server/routes/engineAgentGateway.js": "4a9524fd954320277042c783b7b19cbb2172f075f27652c0eebfd743ffc47872", ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_DESCRIPTOR_REL: "41738185fe103642912b0aa1c29c51860970c38f9f916cc3725e79e258b9ea7e", ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL: "3e7aeb1d28eb291461f79cd656ece6488bc6d372124088e92f53bf89c3373f61", } ENGINE_MCP_L1_CREDENTIAL_REUSE_TARGET_SHA256 = { "nodedc-source/server/routes/n8n.js": "6620e4bdd573e9f6b636a4b059eafef76d59da2fdb2183038fa5ec95357d8478", ENGINE_MCP_L1_CREDENTIAL_REUSE_DESCRIPTOR_REL: "2ada49ef8bb2f146a9ea8d3c9ab5ee55f6a4e1b17e0f4b62a16f27368bd0eb05", } ENGINE_MCP_L1_CREDENTIAL_REUSE_NEW_PATHS = ( ENGINE_MCP_L1_CREDENTIAL_REUSE_DESCRIPTOR_REL, ) ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_DESCRIPTOR_REL = ( "nodedc-source/server/deployTransitions/l1CredentialProvenanceV2.json" ) ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_ARTIFACT_ENTRIES = ( "nodedc-source/server/routes/n8n.js", ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_DESCRIPTOR_REL, ) ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_PREDECESSOR_SHA256 = { "nodedc-source/server/routes/n8n.js": "6620e4bdd573e9f6b636a4b059eafef76d59da2fdb2183038fa5ec95357d8478", } ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_FOUNDATION_SHA256 = { "nodedc-source/server/routes/engineAgentGateway.js": "4a9524fd954320277042c783b7b19cbb2172f075f27652c0eebfd743ffc47872", ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_DESCRIPTOR_REL: "41738185fe103642912b0aa1c29c51860970c38f9f916cc3725e79e258b9ea7e", ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL: "3e7aeb1d28eb291461f79cd656ece6488bc6d372124088e92f53bf89c3373f61", ENGINE_MCP_L1_CREDENTIAL_REUSE_DESCRIPTOR_REL: "2ada49ef8bb2f146a9ea8d3c9ab5ee55f6a4e1b17e0f4b62a16f27368bd0eb05", } ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_TARGET_SHA256 = { "nodedc-source/server/routes/n8n.js": "af07edcf784c420855134ab9178f020d259a1cac703cd643418ab7b4eb94dbd3", ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_DESCRIPTOR_REL: "5887da6e5cb611450e03a110be9786acbe47ae1b00217b8bf09e0967f71f6a3d", } ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_NEW_PATHS = ( ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_DESCRIPTOR_REL, ) ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_DESCRIPTOR_REL = ( "nodedc-source/server/deployTransitions/executionPlanSandboxRuntimeV4.json" ) ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_ARTIFACT_ENTRIES = ( "nodedc-source/server/l2ExecutionPlan/compiler.js", ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_DESCRIPTOR_REL, ) ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_PREDECESSOR_SHA256 = { "nodedc-source/server/l2ExecutionPlan/compiler.js": "01958d541c778002d33e3aead0cfe02df2084eb7222ce941cc7149d73a10f135", } ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_FOUNDATION_SHA256 = { "nodedc-source/server/l2ExecutionPlan/materializer.js": "dabf0073520049d7a04d1962b29e591ae092b87b287a50534ad2d98b03ae683c", "nodedc-source/server/routes/engineAgentGateway.js": "4a9524fd954320277042c783b7b19cbb2172f075f27652c0eebfd743ffc47872", "nodedc-source/server/assets/execution-plans/v1/catalog.json": "2b8e5ee3d73d16f3cd6e34d1f7394946a6270a17b0e9e6511f12921ba2561fd3", "nodedc-source/server/assets/provider-packages/v1/catalog.json": "fe5256fd2ba295819daecc8d2acae34687e807978a6730dfa20703cb3bab0c1b", ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_DESCRIPTOR_REL: "41738185fe103642912b0aa1c29c51860970c38f9f916cc3725e79e258b9ea7e", ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_DESCRIPTOR_REL: "5887da6e5cb611450e03a110be9786acbe47ae1b00217b8bf09e0967f71f6a3d", } ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_TARGET_SHA256 = { "nodedc-source/server/l2ExecutionPlan/compiler.js": "64f5196a83018505c6dac77a0f8674c27941257a874eed9b024c1c94f169be2b", ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_DESCRIPTOR_REL: "a4692643afb86dfeeee6ca24aefcc181913e715eff38fa158667d2f10b901847", } ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_NEW_PATHS = ( ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_DESCRIPTOR_REL, ) ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_DESCRIPTOR_REL = ( "nodedc-source/server/deployTransitions/geliosItemsEnvelopeV12.json" ) ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_ARTIFACT_ENTRIES = ( "nodedc-source/server/assets/execution-plans/v1/catalog.json", "nodedc-source/server/assets/provider-packages/v1/catalog.json", ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_DESCRIPTOR_REL, ) ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_PREDECESSOR_SHA256 = { "nodedc-source/server/assets/execution-plans/v1/catalog.json": "2b8e5ee3d73d16f3cd6e34d1f7394946a6270a17b0e9e6511f12921ba2561fd3", "nodedc-source/server/assets/provider-packages/v1/catalog.json": "fe5256fd2ba295819daecc8d2acae34687e807978a6730dfa20703cb3bab0c1b", } ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_FOUNDATION_SHA256 = { "nodedc-source/server/l2ExecutionPlan/compiler.js": "64f5196a83018505c6dac77a0f8674c27941257a874eed9b024c1c94f169be2b", "nodedc-source/server/l2ExecutionPlan/materializer.js": "dabf0073520049d7a04d1962b29e591ae092b87b287a50534ad2d98b03ae683c", "nodedc-source/server/routes/engineAgentGateway.js": "4a9524fd954320277042c783b7b19cbb2172f075f27652c0eebfd743ffc47872", ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_DESCRIPTOR_REL: "a4692643afb86dfeeee6ca24aefcc181913e715eff38fa158667d2f10b901847", } ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_TARGET_SHA256 = { "nodedc-source/server/assets/execution-plans/v1/catalog.json": "eb0c32fa0e8017b23e2d225fcb2d5805aa6dd1bf674e8aa248afc6a4079a7403", "nodedc-source/server/assets/provider-packages/v1/catalog.json": "42104d1267ca7840446c0c02edd3f9ecb29f8da8eb3e8f384cbd6dca0f676c4c", ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_DESCRIPTOR_REL: "16e39f54ebae776de9acfdf0078c79291310eeca5a1f720eb8b82496f4d419a3", } ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_NEW_PATHS = ( ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_DESCRIPTOR_REL, ) ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_DESCRIPTOR_REL = ( "nodedc-source/server/deployTransitions/registeredExecutionProfilesV2.json" ) ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_ARTIFACT_ENTRIES = ( "nodedc-source/server/assets/execution-plans/v1/catalog.json", "nodedc-source/server/l2ExecutionPlan/catalog.js", "nodedc-source/server/l2ExecutionPlan/registeredProfiles.js", "nodedc-source/server/routes/engineAgentGateway.js", ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL, ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_DESCRIPTOR_REL, ) ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_PREDECESSOR_SHA256 = { "nodedc-source/server/assets/execution-plans/v1/catalog.json": "eb0c32fa0e8017b23e2d225fcb2d5805aa6dd1bf674e8aa248afc6a4079a7403", "nodedc-source/server/l2ExecutionPlan/catalog.js": "c35b4c7ad9319aabbf1366a11ff52a6e99d961893bafdfcb4e84fc5f24fc04be", "nodedc-source/server/routes/engineAgentGateway.js": "4a9524fd954320277042c783b7b19cbb2172f075f27652c0eebfd743ffc47872", ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL: "3e7aeb1d28eb291461f79cd656ece6488bc6d372124088e92f53bf89c3373f61", } ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_FOUNDATION_SHA256 = { "nodedc-source/server/l2ExecutionPlan/compiler.js": "64f5196a83018505c6dac77a0f8674c27941257a874eed9b024c1c94f169be2b", "nodedc-source/server/l2ExecutionPlan/materializer.js": "dabf0073520049d7a04d1962b29e591ae092b87b287a50534ad2d98b03ae683c", ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_DESCRIPTOR_REL: "16e39f54ebae776de9acfdf0078c79291310eeca5a1f720eb8b82496f4d419a3", } ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_TARGET_SHA256 = { "nodedc-source/server/assets/execution-plans/v1/catalog.json": "940510499a9260dbff718b7b1f96f72f8e9ac593b460805af3007731578a7668", "nodedc-source/server/l2ExecutionPlan/catalog.js": "fec56b154ae483ad21bbaaeb4c55707bffce223c27874ac135ce58a9203259a0", "nodedc-source/server/l2ExecutionPlan/registeredProfiles.js": "3c521c1652c61c9d757197c76e053fd3ec602e13ef7f6a4856835ecc1a8a61d1", "nodedc-source/server/routes/engineAgentGateway.js": "8f04edc11251de86b825351c92338be9537207887cf802474b6b6d8c3cce4077", ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL: "3d72709e40b79c01a62a6af4bde911fca4f609d5ac487d284cf97fd8ebd73686", ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_DESCRIPTOR_REL: "1db523f035a47c6b00b41b26efe1390ad2d24acece9d30dd039eff56626e934d", } ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_NEW_PATHS = ( "nodedc-source/server/l2ExecutionPlan/registeredProfiles.js", ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_DESCRIPTOR_REL, ) ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_FAILED_PATHS = ( "nodedc-source/server/deployTransitions/registeredExecutionProfilesV1.json", ) ENGINE_MCP_GELIOS_UNITS_ITEMS_DESCRIPTOR_REL = ( "nodedc-source/server/deployTransitions/" "geliosUnitsItemsEnvelopeV12Patch1.json" ) ENGINE_MCP_GELIOS_UNITS_ITEMS_ARTIFACT_ENTRIES = ( "nodedc-source/server/assets/execution-plans/v1/catalog.json", "nodedc-source/server/assets/provider-packages/v1/catalog.json", ENGINE_MCP_GELIOS_UNITS_ITEMS_DESCRIPTOR_REL, ) ENGINE_MCP_GELIOS_UNITS_ITEMS_PREDECESSOR_SHA256 = { "nodedc-source/server/assets/execution-plans/v1/catalog.json": "940510499a9260dbff718b7b1f96f72f8e9ac593b460805af3007731578a7668", "nodedc-source/server/assets/provider-packages/v1/catalog.json": "42104d1267ca7840446c0c02edd3f9ecb29f8da8eb3e8f384cbd6dca0f676c4c", } ENGINE_MCP_GELIOS_UNITS_ITEMS_FOUNDATION_SHA256 = { "nodedc-source/server/l2ExecutionPlan/catalog.js": "fec56b154ae483ad21bbaaeb4c55707bffce223c27874ac135ce58a9203259a0", "nodedc-source/server/l2ExecutionPlan/compiler.js": "64f5196a83018505c6dac77a0f8674c27941257a874eed9b024c1c94f169be2b", "nodedc-source/server/l2ExecutionPlan/materializer.js": "dabf0073520049d7a04d1962b29e591ae092b87b287a50534ad2d98b03ae683c", "nodedc-source/server/l2ExecutionPlan/registeredProfiles.js": "3c521c1652c61c9d757197c76e053fd3ec602e13ef7f6a4856835ecc1a8a61d1", "nodedc-source/server/routes/engineAgentGateway.js": "8f04edc11251de86b825351c92338be9537207887cf802474b6b6d8c3cce4077", ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL: "3d72709e40b79c01a62a6af4bde911fca4f609d5ac487d284cf97fd8ebd73686", ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_DESCRIPTOR_REL: "1db523f035a47c6b00b41b26efe1390ad2d24acece9d30dd039eff56626e934d", } ENGINE_MCP_GELIOS_UNITS_ITEMS_TARGET_SHA256 = { "nodedc-source/server/assets/execution-plans/v1/catalog.json": "d0cc95fa5e55bda315947fdb13bd8a0d8931b9484677e4c4cc0dcb0e9614e215", "nodedc-source/server/assets/provider-packages/v1/catalog.json": "1f1b866b6f837bffc6d529dfffd8bfa9c87495bfcd9ae3515a79765143d94b1d", ENGINE_MCP_GELIOS_UNITS_ITEMS_DESCRIPTOR_REL: "7a1594573e98342d3a3edcc0dc4f663f06b30ca24cc20adb5f36d50591066d96", } ENGINE_MCP_GELIOS_UNITS_ITEMS_NEW_PATHS = ( ENGINE_MCP_GELIOS_UNITS_ITEMS_DESCRIPTOR_REL, ) ENGINE_AGENT_FULL_GRANT_MIGRATION_ARTIFACT_ENTRIES = ( ENGINE_AGENT_FULL_GRANT_MIGRATION_STORE_REL, ) ENGINE_DATA_PRODUCT_PUBLISH_GRANT_PREDECESSOR_SHA256 = { "docker-compose.yml": ENGINE_DATA_PRODUCT_PUBLISH_GRANT_PREDECESSOR_COMPOSE_SHA256, "nodedc-source/server/credentialPolicies/ndcPrivateNode.js": "723874a02dc7b8a68b22ff2304431cfe64f28933cedf5f1e6cda79b2e1cf704a", "nodedc-source/server/credentialSink/core.js": "9f0facc41fd398fcd955cffdd486abb126cdcd756c87ffde667fcbe00e2c41d3", "nodedc-source/server/credentialSink/requestAuth.js": "f8c9237c3e6f4219dee0d4f956d6e97f76f5bd7ba8a6fd0b4fb21aceffa38138", "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": "604a75ad3b9b463cea5e578760c8a23592e2e00b8acfc0099730a347e27bf4e8", "nodedc-source/server/routes/engineCredentialSink.js": "9cbb69dbc8cbe6181cd5b0170fe9c4d717b0a173866ca98cba3e3766c0bab94e", "nodedc-source/server/routes/n8n.js": "783d822e2457d82e890f43bc00c7e33822077dc2841f0511a89ecb210fd36d48", "nodedc-source/server/routes/ndcAgentMcp.js": "534e2c85e1faecc72a00da7ad32d0584ee09b6bd89eeec2221c3b23d80e1e962", ENGINE_CREDENTIAL_BACKEND_OVERRIDE_TEMPLATE_REL: "944fa64b08255eb8207b93fd327aebb98ecd9400d37d25fcfa8e3a040ee44afe", } ENGINE_DATA_PRODUCT_PUBLISH_GRANT_INSTALLED_FOUNDATION_SHA256 = { **ENGINE_DATA_PRODUCT_PUBLISH_GRANT_PREDECESSOR_SHA256, # This exact successor was installed by the later restart-safe-auth # transition; initial Publish-grant installation still uses the original # predecessor map above. "nodedc-source/server/index.js": "0ac408e0e9a7bc5c8e13a00afc957b982b065e2bc414919839e0b0a11aa05ba4", } ENGINE_CREDENTIAL_SINK_CONTRACT_SHA256 = "b4800eead9bf94793ff1280d06e34aad6b37ef055a9d7f8d83d7fb5f9bd66d8b" ENGINE_BASE_COMPOSE_SERVICES = ( "postgresql", "authentik-server", "authentik-worker", "n8n-postgres", "ops-postgres", "n8n", "nodedc-backend", "nodedc-frontend-build", "app", ) GITEA_ROOT = Path("/volume1/docker/nodedc-gitea") GITEA_COMPOSE_PROJECT = "nodedc-gitea" GITEA_SERVICE = "gitea" GITEA_COMPOSE_REL = "docker-compose.gitea.yml" GITEA_FRESH_INSTALL_DESCRIPTOR_REL = ( "deployment/gitea-fresh-install-v1.json" ) GITEA_FRESH_INSTALL_ENTRIES = ( GITEA_COMPOSE_REL, GITEA_FRESH_INSTALL_DESCRIPTOR_REL, ) GITEA_IMAGE = ( "docker.gitea.com/gitea:1.27.1-rootless@" "sha256:89dc3c214b3992e5bb01e05ad21139d7a8b302d3ea3d8942d3f7e904e92af148" ) GITEA_IMAGE_ID = ( "sha256:89dc3c214b3992e5bb01e05ad21139d7a8b302d3ea3d8942d3f7e904e92af148" ) GITEA_REPO_DIGEST = ( "docker.gitea.com/gitea@" "sha256:89dc3c214b3992e5bb01e05ad21139d7a8b302d3ea3d8942d3f7e904e92af148" ) GITEA_COMPOSE_SHA256 = ( "25868a40996c405543b4627d06499b68f43556e607d839e969a400b0bc0ddadb" ) GITEA_MINIMUM_COMPOSE_VERSION = (2, 20, 1) GITEA_REQUIRED_DOCKER_VERSION = "24.0.2" GITEA_RUNTIME_UID = 1000 GITEA_RUNTIME_GID = 1000 GITEA_NGINX_WORKER_UID = 1023 GITEA_NGINX_GID = 1023 GITEA_DATA_DIR = GITEA_ROOT / "data" GITEA_CONFIG_DIR = GITEA_ROOT / "config" GITEA_SOCKET_DIR = GITEA_ROOT / "socket" GITEA_SOCKET_FILE = GITEA_SOCKET_DIR / "gitea.sock" GITEA_SECRET_DIR = GITEA_ROOT / "secrets" GITEA_SECRET_KEY_FILE = GITEA_SECRET_DIR / "secret-key" GITEA_INTERNAL_TOKEN_FILE = GITEA_SECRET_DIR / "internal-token" GITEA_HTTP_PORT = 3000 GITEA_DISABLED_SSH_HOST_PORT = 4022 GITEA_FORBIDDEN_LEGACY_NETWORK = "nodedc-gitea_internal" GITEA_LEGACY_CONTAINER = "gitea" GITEA_LEGACY_REVERSE_PROXY_IP = "172.22.0.222" GITEA_REVERSE_PROXY_CONFIG = Path("/usr/syno/etc/www/ReverseProxy.json") GITEA_REVERSE_PROXY_UUID = "5bc46027-0307-4261-af7e-4f94a3c508c9" GITEA_REVERSE_PROXY_DESCRIPTION = "Gittea" GITEA_REVERSE_PROXY_GENERATED_CONFIG = Path( "/usr/local/etc/nginx/sites-available/" "82fc9da8-f8a2-48d1-8ea5-6ba8dc8299d8.w3conf" ) GITEA_NGINX = Path("/usr/bin/nginx") GITEA_NGINX_VERSION = "nginx version: nginx/1.23.1" GITEA_NGINX_MAIN_CONFIG = Path("/etc/nginx/nginx.conf.run") GITEA_NGINX_BRIDGE_CONFIG = Path( "/usr/local/etc/nginx/conf.d/http.nodedc-gitea-uds.conf" ) GITEA_NGINX_BRIDGE_CONTENT = ( "map $uri $nodedc_gitea_login_limit_key {\n" " default \"\";\n" " /user/login $http_x_real_ip;\n" "}\n" "\n" "limit_req_zone $nodedc_gitea_login_limit_key " "zone=nodedc_gitea_login:10m rate=10r/m;\n" "limit_conn_zone $http_x_real_ip zone=nodedc_gitea_conn:10m;\n" "\n" "server {\n" " listen 127.0.0.1:3000;\n" " server_name nodedc-gitea-uds.internal;\n" "\n" " location / {\n" " limit_req zone=nodedc_gitea_login burst=10 nodelay;\n" " limit_conn nodedc_gitea_conn 40;\n" " proxy_http_version 1.1;\n" " proxy_set_header Host $http_host;\n" " proxy_set_header X-Real-IP $http_x_real_ip;\n" " proxy_set_header X-Forwarded-For $http_x_forwarded_for;\n" " proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;\n" " proxy_pass http://unix:/volume1/docker/nodedc-gitea/socket/gitea.sock:;\n" " }\n" "}\n" ) GITEA_NGINX_BRIDGE_SHA256 = ( "164f37a12a4f91e656cf20bd5b109978d16d723bdfde236653722aaf820780c9" ) GITEA_IPTABLES = Path("/usr/bin/iptables") GITEA_SECRET_RE = re.compile(r"^[A-Za-z0-9_-]{64,256}$") GITEA_EXPECTED_ENVIRONMENT = { "GITEA_WORK_DIR": "/var/lib/gitea", "GITEA__database__DB_TYPE": "sqlite3", "GITEA__database__PATH": "/var/lib/gitea/data/gitea.db", "GITEA__server__DOMAIN": "git.dcserve.ru", "GITEA__server__ROOT_URL": "https://git.dcserve.ru/", "GITEA__server__PROTOCOL": "http+unix", "GITEA__server__HTTP_ADDR": "/run/gitea/gitea.sock", "GITEA__server__UNIX_SOCKET_PERMISSION": "0666", "GITEA__server__LOCAL_ROOT_URL": "http://unix/", "GITEA__server__DISABLE_SSH": "true", "GITEA__server__START_SSH_SERVER": "false", "GITEA__server__SSH_CREATE_AUTHORIZED_KEYS_FILE": "false", "GITEA__server__LFS_START_SERVER": "false", "GITEA__server__LFS_ALLOW_PURE_SSH": "false", "GITEA__server__OFFLINE_MODE": "true", "GITEA__server__LANDING_PAGE": "login", "GITEA__security__INSTALL_LOCK": "true", "GITEA__security__SECRET_KEY_URI": "file:/run/secrets/gitea_secret_key", "GITEA__security__INTERNAL_TOKEN_URI": ( "file:/run/secrets/gitea_internal_token" ), "GITEA__security__REVERSE_PROXY_LIMIT": "1", "GITEA__security__REVERSE_PROXY_TRUSTED_PROXIES": "127.0.0.0/8,::1/128", "GITEA__security__DISABLE_GIT_HOOKS": "true", "GITEA__security__DISABLE_WEBHOOKS": "true", "GITEA__security__IMPORT_LOCAL_PATHS": "false", "GITEA__security__ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET": "true", "GITEA__security__PASSWORD_HASH_ALGO": "argon2", "GITEA__security__MIN_PASSWORD_LENGTH": "16", "GITEA__security__PASSWORD_COMPLEXITY": "lower,upper,digit,spec", "GITEA__security__TWO_FACTOR_AUTH": "enforced", "GITEA__security__DISABLE_QUERY_AUTH_TOKEN": "true", "GITEA__security__ALLOWED_HOST_LIST": "loopback", "GITEA__service__DISABLE_REGISTRATION": "true", "GITEA__service__REQUIRE_SIGNIN_VIEW": "true", "GITEA__service__SHOW_REGISTRATION_BUTTON": "false", "GITEA__service__DEFAULT_KEEP_EMAIL_PRIVATE": "true", "GITEA__service__DEFAULT_ALLOW_CREATE_ORGANIZATION": "false", "GITEA__service__DEFAULT_USER_IS_RESTRICTED": "true", "GITEA__service__DEFAULT_USER_VISIBILITY": "private", "GITEA__service__ALLOWED_USER_VISIBILITY_MODES": "private", "GITEA__service__DEFAULT_ORG_VISIBILITY": "private", "GITEA__service__ENABLE_REVERSE_PROXY_AUTHENTICATION": "false", "GITEA__service__ENABLE_REVERSE_PROXY_AUTHENTICATION_API": "false", "GITEA__service__ENABLE_REVERSE_PROXY_AUTO_REGISTRATION": "false", "GITEA__service__ENABLE_NOTIFY_MAIL": "false", "GITEA__service__ENABLE_BASIC_AUTHENTICATION": "false", "GITEA__admin__DISABLE_REGULAR_ORG_CREATION": "true", "GITEA__admin__USER_DISABLED_FEATURES": ( "deletion,manage_ssh_keys,manage_gpg_keys,change_username" ), "GITEA__repository__FORCE_PRIVATE": "true", "GITEA__repository__DEFAULT_PRIVATE": "private", "GITEA__repository__USER_MAX_CREATION_LIMIT": "0", "GITEA__repository__ORG_MAX_CREATION_LIMIT": "0", "GITEA__repository__ENABLE_PUSH_CREATE_USER": "false", "GITEA__repository__ENABLE_PUSH_CREATE_ORG": "false", "GITEA__repository__DISABLE_MIGRATIONS": "true", "GITEA__repository__ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES": "false", "GITEA__repository__ALLOW_DELETION_OF_UNADOPTED_REPOSITORIES": "false", "GITEA__repository__DISABLE_HTTP_GIT": "false", "GITEA__repository.upload__ENABLED": "false", "GITEA__attachment__ENABLED": "false", "GITEA__actions__ENABLED": "false", "GITEA__packages__ENABLED": "false", "GITEA__oauth2__ENABLED": "false", "GITEA__oauth2_client__ENABLE_AUTO_REGISTRATION": "false", "GITEA__openid__ENABLE_OPENID_SIGNIN": "false", "GITEA__openid__ENABLE_OPENID_SIGNUP": "false", "GITEA__federation__ENABLED": "false", "GITEA__mailer__ENABLED": "false", "GITEA__session__COOKIE_SECURE": "true", "GITEA__session__SAME_SITE": "strict", "GITEA__api__ENABLE_SWAGGER": "false", "GITEA__migrations__ALLOW_LOCALNETWORKS": "false", "GITEA__migrations__SKIP_TLS_VERIFY": "false", "GITEA__cors__ENABLED": "false", "GITEA__metrics__ENABLED": "false", "GITEA__cron.update_checker__ENABLED": "false", "GITEA__log__MODE": "console", "GITEA__log__LEVEL": "Info", } # Incident salvage is deliberately additive to the fresh-install component. # It never imports the compromised SQLite database, config, home, credentials, # hooks or arbitrary repository trees. Only the exact v2 decision rows and # validated Git object/ref material may cross the read-only snapshot boundary. GITEA_SALVAGE_DESCRIPTOR_REL = ( "deployment/gitea-incident-salvage-v3.json" ) GITEA_SALVAGE_DECISION_PREFIX = "deployment/gitea-incident-salvage" GITEA_SALVAGE_DISPOSITION_REL = ( f"{GITEA_SALVAGE_DECISION_PREFIX}/confirmed-disposition-v1.json" ) GITEA_SALVAGE_CLOSURE_DISPOSITION_REL = ( f"{GITEA_SALVAGE_DECISION_PREFIX}/confirmed-closure-disposition-v1.json" ) GITEA_SALVAGE_DECISION_MANIFEST_REL = ( f"{GITEA_SALVAGE_DECISION_PREFIX}/confirmed-decision.json" ) GITEA_SALVAGE_USERS_REL = ( f"{GITEA_SALVAGE_DECISION_PREFIX}/users.decisions.csv" ) GITEA_SALVAGE_REPOSITORIES_REL = ( f"{GITEA_SALVAGE_DECISION_PREFIX}/repositories.decisions.csv" ) GITEA_SALVAGE_ENTRIES = ( GITEA_COMPOSE_REL, GITEA_SALVAGE_DESCRIPTOR_REL, GITEA_SALVAGE_DISPOSITION_REL, GITEA_SALVAGE_CLOSURE_DISPOSITION_REL, GITEA_SALVAGE_DECISION_MANIFEST_REL, GITEA_SALVAGE_USERS_REL, GITEA_SALVAGE_REPOSITORIES_REL, ) GITEA_SALVAGE_IMAGE = ( "docker.gitea.com/gitea:1.27.2-rootless@" "sha256:7de5f49ada687b8c8d2938f547cdb7634839764ba51f297457bae35cee3abd2c" ) GITEA_SALVAGE_IMAGE_ID = ( "sha256:272085a806e6d182352cdb011c0ebab1d2efc7ec45247de84de5659c7bc5c4c6" ) GITEA_SALVAGE_REPO_DIGEST = ( "docker.gitea.com/gitea@" "sha256:7de5f49ada687b8c8d2938f547cdb7634839764ba51f297457bae35cee3abd2c" ) GITEA_SALVAGE_COMPOSE_SHA256 = ( "2f031d5bfff4f42c73cabd8c94487ec3e4f1e1b0a96d3b7eec958904f735908a" ) GITEA_SALVAGE_DESCRIPTOR_SHA256 = ( "9b98eb1a1640fd5569cf051a621837379b167eff4527313a43a0a851e7cc181a" ) GITEA_SALVAGE_DISPOSITION_SHA256 = ( "0a066724bcf6e4933133db6cab6cc273393e3c262dd00dda0bbf9ceebd84f78c" ) GITEA_SALVAGE_CLOSURE_DISPOSITION_SHA256 = ( "7ed66d9848268431a703fe24b22c41afbaa7c5ff48949604d6fc448d93e0d243" ) GITEA_SALVAGE_DECISION_MANIFEST_SHA256 = ( "dc9528462624158eb44218d37cc7054d551ca2d7ded562592982aa3f34c9fc2a" ) GITEA_SALVAGE_USERS_SHA256 = ( "e3b82f1073a86eea9e567edff062dd1689d21ec0edcf3ed92e844da9351ee8b6" ) GITEA_SALVAGE_REPOSITORIES_SHA256 = ( "76b4bae2ab5cec490330c19bfc5ae9429abf7636705ae028c1c64fd54a6a0493" ) GITEA_SALVAGE_SNAPSHOT_ROOT = Path( "/volume1/.nodedc-security-snapshots/docker-gitea-incident-20260814" ) GITEA_SALVAGE_SNAPSHOT_UUID = "f5a3fe3a-93ea-bb4d-847f-6221a6bcbc9f" GITEA_SALVAGE_SNAPSHOT_DATABASE = ( GITEA_SALVAGE_SNAPSHOT_ROOT / "gitea/gitea/gitea.db" ) GITEA_SALVAGE_SNAPSHOT_DATABASE_BYTES = 182681600 GITEA_SALVAGE_SNAPSHOT_DATABASE_SHA256 = ( "8db9e74a5641662a808d8252c5d6c9de43fe9efd8687634bfbff2d4361a13052" ) GITEA_SALVAGE_SNAPSHOT_REPOSITORIES = ( GITEA_SALVAGE_SNAPSHOT_ROOT / "gitea/git/repositories" ) GITEA_SALVAGE_BTRFS = Path("/usr/sbin/btrfs") GITEA_SALVAGE_LEGACY_ROOT = Path("/volume1/docker/gitea") GITEA_SALVAGE_MAINTENANCE_CONTAINER = "nodedc-gitea-salvage-maintenance" GITEA_SALVAGE_REPOSITORY_QUARANTINE = GITEA_ROOT / "quarantine/repositories" GITEA_SALVAGE_BOOTSTRAP_DIR = GITEA_ROOT / "bootstrap" GITEA_SALVAGE_AUDIT_DIR = GITEA_ROOT / "audit" GITEA_SALVAGE_IDENTITY_MAP = GITEA_SALVAGE_AUDIT_DIR / "identity-map.json" GITEA_SALVAGE_DCTOUCH_PASSWORD_FILE = ( GITEA_SALVAGE_BOOTSTRAP_DIR / "dctouch.password" ) GITEA_SALVAGE_SILVER_PASSWORD_FILE = ( GITEA_SALVAGE_BOOTSTRAP_DIR / "silver.password" ) GITEA_SALVAGE_APP_INI = GITEA_CONFIG_DIR / "app.ini" GITEA_SALVAGE_DB = GITEA_DATA_DIR / "gitea/gitea.db" GITEA_SALVAGE_REPOSITORY_ROOT = GITEA_DATA_DIR / "git/repositories" GITEA_SALVAGE_USER_COUNTS = { "KEEP_ACTIVE": 2, "KEEP_LOCKED": 8, "DELETE": 962, } GITEA_SALVAGE_REPOSITORY_COUNTS = {"KEEP": 45, "DELETE": 2013} GITEA_SALVAGE_ACTIVE_USERS = {1: "dctouch", 16: "SILVER"} GITEA_SALVAGE_LOCKED_USERS = { 2: "KOPYLOV", 4: "kalininUN", 6: "arturHITECA", 7: "uePATSUKEVICH", 11: "sadenov-a", 15: "KKK", 17: "ayoauo", 18: "ayoayuoo", } # Exact stopped-container identity, reviewed from root-owned Docker inspect. # The mutable `latest` text is provenance only; the immutable image ID and full # stopped/mount topology below are the execution authority. GITEA_SALVAGE_EXPECTED_LEGACY_IMAGE = "gitea/gitea:latest" GITEA_SALVAGE_EXPECTED_LEGACY_IMAGE_ID = ( "sha256:bf95d9a45ce4fe38b027d051cdc4a4bc531513489fa6244af4074efbb1c376d6" ) GITEA_SALVAGE_EXPECTED_REF_MANIFEST_SHA256 = None GITEA_SALVAGE_DISPOSITION_REFERENCE_MANIFEST_SHA256 = ( "9cddaf0e4d4cf22dd264a6ae589ccc50d29e07f85c55e9d34b14627cecb8a311" ) GITEA_SALVAGE_EXPECTED_UNSUPPORTED_REPORT_SHA256 = ( "4b2cecf88c62fc5c4a43419885e88a01c9f9aac03133afb19dae0a7caef106ac" ) GITEA_SALVAGE_DISPOSITION_UNSUPPORTED_SCHEMA_SHA256 = ( "b5e3b6776926c4f1627fafd882362ed0ef986bfc86fc6ac6507a43976531b6db" ) GITEA_SALVAGE_DISPOSITION_TOPICS_SHA256 = ( "df6e3612186234bfcf3c172ef4e0fff933baaa691a510f9780ebf9e21c8d4d05" ) GITEA_SALVAGE_CLOSURE_PREDECESSOR_ARTIFACT_SHA256 = ( "d6870b5583a2f329eadb4e6cda65fdf4d271532df5ffbf8bfb1403968a434672" ) GITEA_SALVAGE_CLOSURE_PREDECESSOR_DISPOSITION_SHA256 = ( "0a066724bcf6e4933133db6cab6cc273393e3c262dd00dda0bbf9ceebd84f78c" ) GITEA_SALVAGE_CLOSURE_MAX_ROWS_PER_RELATION = 2_000_000 GITEA_SALVAGE_CLOSURE_MAX_TEXT_BYTES_PER_FIELD = 64 * 1024 * 1024 GITEA_SALVAGE_CLOSURE_ACTOR_RELATIONS = ( ("access_cache", "access", "DROP_CACHE_RECOMPUTE"), ("collaborations", "collaboration", "RECREATE_KEPT_ACTOR_AFTER_ID_MAP"), ) GITEA_SALVAGE_CLOSURE_ISSUE_DEPENDENTS = ( ( "comments", "comment", "poster_id", ("content", "patch", "old_title", "new_title", "old_ref", "new_ref"), ), ("issue_assignees", "issue_assignees", "assignee_id", ()), ( "issue_content_histories", "issue_content_history", "poster_id", ("content_text",), ), ("issue_labels", "issue_label", None, ()), ("issue_users", "issue_user", "uid", ()), ("issue_watches", "issue_watch", "user_id", ()), ("reviews", "review", "reviewer_id", ("content",)), ("stopwatches", "stopwatch", "user_id", ()), ("tracked_times", "tracked_time", "user_id", ()), ) GITEA_SALVAGE_CLOSURE_METRICS = tuple( sorted( { "action_artifacts", "action_run_indexes", "action_run_jobs", "action_runners", "action_runs", "action_schedules", "action_secrets", "action_tasks", "action_variables", "comments", "issue_assignees", "issue_content_histories", "issue_dependencies", "issue_labels", "issue_users", "issue_watches", "issues_ordinary", "labels", "milestones", "notifications", "package_blobs", "package_files", "package_properties", "package_versions", "packages", "project_boards", "project_issue_links", "projects", "pull_auto_merges", "pull_request_wrappers", "reactions", "releases", "repo_units", "review_states", "reviews", "stopwatches", "tracked_times", } ) ) GITEA_SALVAGE_CLOSURE_TEXT_COLUMNS = { "comments": ("content", "new_ref", "new_title", "old_ref", "old_title", "patch"), "issue_content_histories": ("content_text",), "issues_ordinary": ("content", "name"), "labels": ("color", "description", "name"), "milestones": ("content", "name"), "project_boards": ("color", "title"), "projects": ("description", "title"), "pull_auto_merges": ("merge_style", "message"), "pull_request_wrappers": ("content", "name"), "releases": ("note", "tag_name", "target", "title"), "repo_units": ("config",), "review_states": ("updated_files",), "reviews": ("content",), } GITEA_SALVAGE_CLOSURE_NUMERIC_COLUMNS = { "action_artifacts": ("file_compressed_size", "file_size"), "action_tasks": ("log_length", "log_size"), } GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_COUNTS = ( "comment_assignee", "comment_assignee_team", "comment_cross_reference", "comment_cross_reference_comment", "comment_current_milestone", "comment_current_project", "comment_dependent_issue", "comment_label", "comment_old_milestone", "comment_old_project", "comment_resolve_doer", "comment_review", "comment_tracked_time", "content_history_comment", "content_history_issue", "pull_merger", "review_reviewer_team", ) GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_ACTORS = ( "comment_assignee", "comment_resolve_doer", "pull_merger", ) GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_REPOSITORIES = ( "comment_cross_reference", "comment_current_milestone", "comment_current_project", "comment_dependent_issue", "comment_label", "comment_old_milestone", "comment_old_project", ) GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_NULLABLE_COLUMNS = ( "comment.assignee_id", "comment.dependent_issue_id", "comment.label_id", "comment.milestone_id", "comment.old_milestone_id", "comment.old_project_id", "comment.original_author_id", "comment.project_id", "comment.ref_action", "comment.ref_comment_id", "comment.ref_is_pull", "comment.ref_issue_id", "comment.ref_repo_id", "comment.resolve_doer_id", "comment.review_id", "comment.time_id", "issue_content_history.comment_id", "pull_request.merger_id", "review.original_author_id", ) GITEA_SALVAGE_CLOSURE_EXTERNAL_AUTHOR_SOURCES = ("comment", "review") GITEA_SALVAGE_DISPOSITION_REMAINING_BLOCKERS = ( "attachment-physical-verifier-pending", "candidate-root-activation-hard-frozen", "collaboration-kept-user-mapping-verifier-pending", "forensic-ref-archive-verifier-pending", "issue-pr-metadata-sanitized-archive-verifier-pending", "lfs-reachable-pointer-physical-verifier-pending", "package-action-physical-closure-verifier-pending", "reference-manifest-fsck-reachability-verifier-pending", "repository-object-reconstruction-verifier-pending", "target-unit-policy-acceptance-pending", "unsupported-schema-catalog-verifier-pending", ) GITEA_SALVAGE_CLOSURE_REMAINING_BLOCKERS = ( "attachment-physical-verifier-pending", "candidate-root-activation-hard-frozen", "closure-report-review-pin-pending", "collaboration-kept-user-mapping-verifier-pending", "forensic-ref-archive-verifier-pending", "issue-pr-metadata-sanitized-archive-verifier-pending", "lfs-reachable-pointer-physical-verifier-pending", "package-action-physical-closure-verifier-pending", "reference-manifest-fsck-reachability-verifier-pending", "repository-object-reconstruction-verifier-pending", "target-unit-policy-acceptance-pending", "unsupported-schema-catalog-verifier-pending", ) GITEA_SALVAGE_FS_IOC_GETFLAGS = 0x80086601 GITEA_SALVAGE_FS_NOCOW_FL = 0x00800000 GITEA_SALVAGE_EXCLUSIVE_ALLOCATION_BUDGET_BYTES = 512 * 1024 * 1024 GITEA_SALVAGE_DERIVED_INFO_PACKS_MAX_BYTES = 1024 * 1024 GITEA_SALVAGE_DERIVED_PACK_BITMAP_MAX_BYTES = 2 * 1024 * 1024 GITEA_SALVAGE_DERIVED_COMMIT_GRAPH_MAX_BYTES = 1024 * 1024 GITEA_SALVAGE_EVIDENCE_MAX_BYTES = 8 * 1024 * 1024 GITEA_SALVAGE_UNSUPPORTED_SIZE_PER_ROW_MAX_BYTES = 16 * 1024**4 GITEA_SALVAGE_UNSUPPORTED_SIZE_TOTAL_MAX_BYTES = (1 << 63) - 1 GITEA_SALVAGE_EXPECTED_UNSUPPORTED_SCHEMA_SHA256 = None GITEA_SALVAGE_UNSUPPORTED_REPOSITORY_TABLES = ( ("issues", "issue", "repo_id"), ("pull_requests_base", "pull_request", "base_repo_id"), ("pull_requests_head", "pull_request", "head_repo_id"), ("releases", "release", "repo_id"), ("attachments", "attachment", "repo_id"), ("collaborators", "collaboration", "repo_id"), ("deploy_keys", "deploy_key", "repo_id"), ("webhooks", "webhook", "repo_id"), ("protected_branches", "protected_branch", "repo_id"), ("milestones", "milestone", "repo_id"), ("labels", "label", "repo_id"), ("stars", "star", "repo_id"), ("watches", "watch", "repo_id"), ("access_grants", "access", "repo_id"), ("topics", "repo_topic", "repo_id"), ("mirrors", "mirror", "repo_id"), ("push_mirrors", "push_mirror", "repo_id"), ("lfs_objects", "lfs_meta_object", "repository_id"), ("lfs_locks", "lfs_lock", "repo_id"), ("packages", "package", "repo_id"), ("action_runs", "action_run", "repo_id"), ("action_schedules", "action_schedule", "repo_id"), ("action_runners", "action_runner", "repo_id"), ("action_variables", "action_variable", "repo_id"), ("action_secrets", "secret", "repo_id"), ) GITEA_SALVAGE_UNSUPPORTED_REPOSITORY_TEXT_METADATA = ( "description", "website", "original_url", "topics", "avatar", ) GITEA_SALVAGE_UNSUPPORTED_REPOSITORY_NUMERIC_HINTS = ( "num_watches", "num_stars", "num_issues", "num_pulls", "num_milestones", "num_projects", "num_action_runs", "lfs_size", ) # This is a code-owned, schema-only review surface. Table and column names from # the compromised database never become SQL identifiers. Direct repository # relations above are counted now; the tables below expose the exact schema # needed to design a later, reviewed issue/PR/package/Actions dependency closure. GITEA_SALVAGE_UNSUPPORTED_SCHEMA_TABLES = tuple( sorted( { "repository", "repo_unit", *(table for _label, table, _column in GITEA_SALVAGE_UNSUPPORTED_REPOSITORY_TABLES), "action_artifact", "action_run_index", "action_run_job", "action_task", "attachment", "comment", "issue_assignees", "issue_content_history", "issue_dependency", "issue_label", "issue_user", "issue_watch", "notification", "package_blob", "package_file", "package_property", "package_version", "project", "project_board", "project_issue", "pull_auto_merge", "reaction", "review", "review_state", "stopwatch", "tracked_time", } ) ) COMPONENTS = { "mission-core-map-access": { "payload_root": Path("/volume1/docker/nodedc-platform/mission-core-map-access"), "bootstrap_root": True, "artifact_only": True, "services": (), }, "engine": { "payload_root": Path("/volume2/nodedc-demo"), "compose_root": Path("/volume2/nodedc-demo"), "services": ("nodedc-backend", "app"), "compose_no_deps": True, "publish_dist": True, "healthchecks": ( "http://127.0.0.1:8080/", "http://127.0.0.1:3001/health", ), }, "launcher": { "payload_root": Path("/volume1/docker/nodedc-platform/launcher/source"), "build_root": Path("/volume1/docker/nodedc-platform/launcher/source"), "build": ("build", "--no-cache", "-t", "nodedc/launcher:local", "."), "compose_root": Path("/volume1/docker/nodedc-platform/platform"), "compose_env_file": Path("/volume1/docker/nodedc-platform/platform/.env.synology"), "compose_files": ( Path("/volume1/docker/nodedc-platform/platform/docker-compose.platform-http.yml"), ), "compose_no_deps": True, "services": ("launcher",), "healthchecks": ( { "url": "http://127.0.0.1:18080/healthz", "headers": {"Host": "hub.nodedc.ru"}, }, ), }, "platform": { "payload_root": Path("/volume1/docker/nodedc-platform"), "compose_root": Path("/volume1/docker/nodedc-platform/platform"), "compose_env_file": Path("/volume1/docker/nodedc-platform/platform/.env.synology"), "compose_files": ( Path("/volume1/docker/nodedc-platform/platform/docker-compose.platform-http.yml"), Path("/volume1/docker/nodedc-platform/platform/docker-compose.external-data-plane.yml"), ), "compose_no_deps": True, "services": ("notification-postgres", "notification-core", "ai-workspace-hub", "launcher", "reverse-proxy", "external-data-plane-postgres", "external-data-plane", "map-gateway"), "healthchecks": ( "http://127.0.0.1:5185/healthz", "http://127.0.0.1:18081/healthz", { "url": "http://127.0.0.1:18080/healthz", "headers": {"Host": "hub.nodedc.ru"}, }, { "url": "http://127.0.0.1:18080/", "headers": {"Host": "id.nodedc.ru"}, }, ), }, "tasker": { "payload_root": Path("/volume1/docker/nodedc-platform/tasker"), "compose_root": Path("/volume1/docker/nodedc-platform/tasker/plane-app"), "compose_project": "nodedc-tasker", "compose_env_file": Path("/volume1/docker/nodedc-platform/tasker/plane-app/.env.synology"), "compose_files": ( Path("/volume1/docker/nodedc-platform/tasker/plane-app/docker-compose.yaml"), Path("/volume1/docker/nodedc-platform/tasker/plane-app/docker-compose.synology.override.yml"), ), "services": ("api", "worker", "beat-worker", "web"), "healthchecks": ( { "url": "http://127.0.0.1:18090/", "headers": {"Host": "ops.nodedc.ru"}, }, ), }, "ops-agents": { "payload_root": Path("/volume1/docker/nodedc-platform/ops-agents"), "compose_root": Path("/volume1/docker/nodedc-platform/ops-agents"), "compose_env_file": Path("/volume1/docker/nodedc-platform/ops-agents/.env"), "compose_files": ( Path("/volume1/docker/nodedc-platform/ops-agents/docker-compose.synology.yml"), ), "compose_build": True, "compose_no_deps": True, "services": ("agent-gateway",), "healthchecks": ( "http://172.22.0.222:18190/readyz", ), }, "bim-viewer": { "payload_root": Path("/volume1/docker/nodedc-platform/bim-viewer/source"), "build_root": Path("/volume1/docker/nodedc-platform/bim-viewer/source/converter"), "build": ("build", "--no-cache", "-t", "nodedc/bim-converter:local", "."), "compose_root": Path("/volume1/docker/nodedc-platform/bim-viewer/source"), "compose_env_file": Path("/volume1/docker/nodedc-platform/bim-viewer/source/.env"), "compose_files": ( Path("/volume1/docker/nodedc-platform/bim-viewer/source/docker-compose.beam.yml"), ), "compose_no_deps": True, "services": ("ndc-beam-viewer", "nodedc-bim-converter"), "healthchecks": ( "http://127.0.0.1:18100/api/auth/session", ), }, "n8n-private-extension": { # This component only stages a verified, immutable offline release in # Platform-owned storage. It deliberately has no Compose file, service, # container mutation or Engine activation side effect. "payload_root": N8N_PRIVATE_EXTENSION_RELEASES_ROOT, "bootstrap_root": True, "artifact_only": True, "immutable_payload": True, "services": (), }, "module-foundry": { "payload_root": Path("/volume1/docker/nodedc-platform/module-foundry/source"), "compose_root": Path("/volume1/docker/nodedc-platform/module-foundry/source/infra"), "compose_project": "nodedc-module-foundry", "compose_env_file": Path("/volume1/docker/nodedc-platform/module-foundry/source/.env"), "compose_files": ( Path("/volume1/docker/nodedc-platform/module-foundry/source/infra/docker-compose.module-foundry.yml"), ), "bootstrap_root": True, "compose_build": True, "compose_no_deps": True, "services": ("nodedc-module-foundry",), "healthchecks": ( "http://172.22.0.222:9920/healthz", ), }, "device-plane": { "payload_root": DEVICE_PLANE_ROOT, "compose_root": DEVICE_PLANE_ROOT, "compose_project": "nodedc-device-plane", "compose_files": ( DEVICE_PLANE_ROOT / "docker-compose.device-plane.yml", ), "bootstrap_root": True, "compose_no_deps": True, # PostgreSQL is durable state infrastructure. The default legacy # selection remains Core + Gateway; the exact Device Manager slice # separately registers the third stateless service. "services": ("device-control-core", "device-gateway"), }, "gitea": { "payload_root": GITEA_ROOT, "compose_root": GITEA_ROOT, "compose_project": GITEA_COMPOSE_PROJECT, "compose_files": (GITEA_ROOT / GITEA_COMPOSE_REL,), "bootstrap_root": True, "compose_no_deps": True, "services": (GITEA_SERVICE,), }, "proxy-contur": { "payload_root": Path("/volume1/docker/proxy-contur"), "compose_root": Path("/volume1/docker/proxy-contur"), "compose_project": "proxy-contur", "compose_env_file": Path("/volume1/docker/proxy-contur/.env"), "compose_files": ( Path("/volume1/docker/proxy-contur/docker-compose.yml"), ), "compose_build": True, "compose_no_deps": True, "services": ("proxy-contur",), "health_container": "proxy-contur", }, "dc-amd-proxy": { "payload_root": Path("/volume1/docker/dc-amd-proxy"), "compose_root": Path("/volume1/docker/dc-amd-proxy"), "compose_project": "dc-amd-proxy", "bootstrap_root": True, "compose_build": True, "compose_no_deps": True, "services": ("dc-amd-proxy",), "health_container": "dc-amd-proxy", }, "dc-cms": { "payload_root": Path("/volume1/docker/dc-cms/source"), "compose_root": Path("/volume1/docker/dc-cms/source/infra"), "compose_env_file": Path("/volume1/docker/dc-cms/source/infra/.env.synology"), "compose_files": ( Path("/volume1/docker/dc-cms/source/infra/docker-compose.yml"), ), "bootstrap_root": True, "compose_build": True, "services": ("postgresql-authentik", "authentik-server", "authentik-worker", "authentik-bootstrap", "cms-app", "reverse-proxy"), "healthchecks": ( { "url": "http://172.22.0.222:9918/auth/login?returnTo=%2F", "headers": {"Host": "cms.dcserve.ru"}, }, { "url": "http://172.22.0.222:9919/", "headers": {"Host": "auth.dcserve.ru"}, }, ), }, "dc-cms-site-nodedc": { "payload_root": Path("/volume1/docker/dc-cms/sites/nodedc"), "compose_root": Path("/volume1/docker/dc-cms/source/infra"), "compose_env_file": Path("/volume1/docker/dc-cms/source/infra/.env.synology"), "compose_files": ( Path("/volume1/docker/dc-cms/source/infra/docker-compose.yml"), ), "compose_no_deps": True, "services": ("cms-app", "reverse-proxy"), "healthchecks": ( { "url": "http://172.22.0.222:9918/auth/login?returnTo=%2F", "headers": {"Host": "cms.dcserve.ru"}, }, ), }, } class NoRedirectHandler(urllib.request.HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): return None NO_REDIRECT_OPENER = urllib.request.build_opener(NoRedirectHandler) class DeployError(Exception): pass class ReconciliationRequired(DeployError): pass def die(message): raise DeployError(message) def utc_now(): return datetime.now(timezone.utc).replace(microsecond=0).isoformat() def stamp(): return datetime.now().strftime("%Y%m%d-%H%M%S") def sha256_file(path): import hashlib h = hashlib.sha256() with path.open("rb") as f: for chunk in iter(lambda: f.read(1024 * 1024), b""): h.update(chunk) return h.hexdigest() def sha256_json_value(value): import hashlib canonical = json.dumps( value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), ).encode("utf-8") return hashlib.sha256(canonical).hexdigest() def safe_name(value): return re.sub(r"[^A-Za-z0-9._-]", "_", value) def is_relative_to(child, parent): try: child.relative_to(parent) return True except ValueError: return False def ensure_layout(): for path in (INBOX, APPLIED_DIR, FAILED_DIR, BACKUPS_DIR, STATE_DIR, TMP_DIR, RUNTIME_DIR): path.mkdir(parents=True, exist_ok=True) for path in (STATE_DIR, BACKUPS_DIR, TMP_DIR, RUNTIME_DIR): os.chown(path, 0, 0) path.chmod(0o700) for path in (APPLIED_DIR, FAILED_DIR): os.chown(path, 0, 0) path.chmod(0o755) def ensure_platform_runtime_secret(secret_file, secret_re, label): # This is Platform infrastructure state, not a product setting. The # root-owned runner creates it once and only explicitly bound service mounts # can read it. It must never be copied into an artifact or a shared .env. secret_dir = secret_file.parent try: directory_stat = secret_dir.lstat() except FileNotFoundError: secret_dir.mkdir(parents=True, exist_ok=False) directory_stat = secret_dir.lstat() if stat.S_ISLNK(directory_stat.st_mode) or not stat.S_ISDIR(directory_stat.st_mode): die(f"{label} secret directory is unsafe: {secret_dir}") os.chown(secret_dir, 0, MAP_GATEWAY_RUNTIME_GID) secret_dir.chmod(0o710) try: secret_stat = secret_file.lstat() except FileNotFoundError: secret_value = secrets.token_urlsafe(48) temporary = secret_dir / f".{secret_file.name}.{os.getpid()}.{time.time_ns()}.tmp" descriptor = None try: descriptor = os.open(str(temporary), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o640) os.write(descriptor, f"{secret_value}\n".encode("ascii")) os.fsync(descriptor) os.fchown(descriptor, 0, MAP_GATEWAY_RUNTIME_GID) os.fchmod(descriptor, 0o640) os.close(descriptor) descriptor = None os.replace(temporary, secret_file) fsync_directory(secret_dir) finally: if descriptor is not None: os.close(descriptor) if temporary.exists(): temporary.unlink() return "created" if stat.S_ISLNK(secret_stat.st_mode) or not stat.S_ISREG(secret_stat.st_mode): die(f"{label} secret file is unsafe: {secret_file}") if secret_stat.st_uid != 0 or secret_stat.st_mode & (stat.S_IWGRP | stat.S_IWOTH): die(f"{label} secret file has unsafe ownership or permissions: {secret_file}") if secret_stat.st_size > 512: die(f"{label} secret file is too large: {secret_file}") try: secret_value = secret_file.read_text(encoding="ascii").strip() except UnicodeDecodeError: die(f"{label} secret file is not ascii: {secret_file}") if not secret_re.fullmatch(secret_value): die(f"{label} secret file has invalid format: {secret_file}") os.chown(secret_file, 0, MAP_GATEWAY_RUNTIME_GID) secret_file.chmod(0o640) return "reused" def ensure_map_gateway_admin_secret(): return ensure_platform_runtime_secret( MAP_GATEWAY_SECRET_FILE, MAP_GATEWAY_SECRET_RE, "map gateway", ) def read_proxy_contur_token(): try: source_stat = PROXY_CONTUR_ENV_FILE.lstat() except FileNotFoundError: die("proxy-contur env file is required for Map egress") if stat.S_ISLNK(source_stat.st_mode) or not stat.S_ISREG(source_stat.st_mode): die("proxy-contur env file is unsafe") if source_stat.st_size > 64 * 1024 or source_stat.st_mode & (stat.S_IWGRP | stat.S_IWOTH): die("proxy-contur env file has unsafe permissions") try: lines = PROXY_CONTUR_ENV_FILE.read_text(encoding="utf-8").splitlines() except UnicodeDecodeError: die("proxy-contur env file is not utf-8") value = None for raw in lines: line = raw.strip() if not line or line.startswith("#"): continue if line.startswith("export "): line = line[7:].lstrip() if not line.startswith("PROXY_TOKEN="): continue value = line.partition("=")[2].strip() if len(value) >= 2 and value[0] in ("'", '"') and value[-1] == value[0]: value = value[1:-1] break if not value or len(value) > 4096 or any(ord(char) < 33 or ord(char) == 127 for char in value): die("proxy-contur PROXY_TOKEN is missing or invalid") return value def sync_map_egress_proxy_secret(): # The Map Gateway gets a read-only file copy of the existing proxy token. # The value is never printed, placed in an artifact, or exposed to Foundry. token = read_proxy_contur_token() try: parent_stat = MAP_GATEWAY_SECRET_DIR.lstat() except FileNotFoundError: MAP_GATEWAY_SECRET_DIR.mkdir(parents=True, exist_ok=False) parent_stat = MAP_GATEWAY_SECRET_DIR.lstat() if stat.S_ISLNK(parent_stat.st_mode) or not stat.S_ISDIR(parent_stat.st_mode): die(f"map egress parent secret directory is unsafe: {MAP_GATEWAY_SECRET_DIR}") os.chown(MAP_GATEWAY_SECRET_DIR, 0, MAP_GATEWAY_RUNTIME_GID) MAP_GATEWAY_SECRET_DIR.chmod(0o710) if MAP_EGRESS_PROXY_SECRET_FILE.exists() or MAP_EGRESS_PROXY_SECRET_FILE.is_symlink(): target_stat = MAP_EGRESS_PROXY_SECRET_FILE.lstat() if stat.S_ISLNK(target_stat.st_mode) or not stat.S_ISREG(target_stat.st_mode): die("map egress proxy secret file is unsafe") temporary = MAP_GATEWAY_SECRET_DIR / f".{MAP_EGRESS_PROXY_SECRET_FILE.name}.{os.getpid()}.{time.time_ns()}.tmp" descriptor = None try: descriptor = os.open(str(temporary), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o640) os.write(descriptor, f"{token}\n".encode("utf-8")) os.fsync(descriptor) os.fchown(descriptor, 0, MAP_GATEWAY_RUNTIME_GID) os.fchmod(descriptor, 0o640) os.close(descriptor) descriptor = None os.replace(temporary, MAP_EGRESS_PROXY_SECRET_FILE) fsync_directory(MAP_GATEWAY_SECRET_DIR) finally: if descriptor is not None: os.close(descriptor) if temporary.exists(): temporary.unlink() return "synced" def ensure_root_owned_grant_directory(path, label): try: parent_stat = MAP_GATEWAY_SECRET_DIR.lstat() except FileNotFoundError: MAP_GATEWAY_SECRET_DIR.mkdir(parents=True, exist_ok=False) parent_stat = MAP_GATEWAY_SECRET_DIR.lstat() if stat.S_ISLNK(parent_stat.st_mode) or not stat.S_ISDIR(parent_stat.st_mode): die(f"{label} parent directory is unsafe: {MAP_GATEWAY_SECRET_DIR}") os.chown(MAP_GATEWAY_SECRET_DIR, 0, MAP_GATEWAY_RUNTIME_GID) MAP_GATEWAY_SECRET_DIR.chmod(0o710) try: directory_stat = path.lstat() except FileNotFoundError: path.mkdir(parents=False, exist_ok=False) directory_stat = path.lstat() if stat.S_ISLNK(directory_stat.st_mode) or not stat.S_ISDIR(directory_stat.st_mode): die(f"{label} directory is unsafe: {path}") os.chown(path, 0, 0) path.chmod(0o500) for entry in path.iterdir(): entry_stat = entry.lstat() if (not re.fullmatch(r"[a-f0-9]{64}", entry.name) or stat.S_ISLNK(entry_stat.st_mode) or not stat.S_ISREG(entry_stat.st_mode) or entry_stat.st_uid != 0 or entry_stat.st_gid != 0 or stat.S_IMODE(entry_stat.st_mode) != 0o400 or entry_stat.st_size < 2 or entry_stat.st_size > 128 * 1024): die(f"{label} record is unsafe: {entry}") def ensure_external_data_plane_provisioner_secret(): # Unlike the Map Gateway key, this capability can mint scoped writers. # Keep it in an isolated child directory readable only by the dedicated # EDP/provisioner uid, never by the shared gid 1000. try: parent_stat = MAP_GATEWAY_SECRET_DIR.lstat() except FileNotFoundError: MAP_GATEWAY_SECRET_DIR.mkdir(parents=True, exist_ok=False) parent_stat = MAP_GATEWAY_SECRET_DIR.lstat() if stat.S_ISLNK(parent_stat.st_mode) or not stat.S_ISDIR(parent_stat.st_mode): die(f"external data plane parent secret directory is unsafe: {MAP_GATEWAY_SECRET_DIR}") os.chown(MAP_GATEWAY_SECRET_DIR, 0, MAP_GATEWAY_RUNTIME_GID) MAP_GATEWAY_SECRET_DIR.chmod(0o710) try: directory_stat = EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_DIR.lstat() except FileNotFoundError: EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_DIR.mkdir(parents=False, exist_ok=False) directory_stat = EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_DIR.lstat() if stat.S_ISLNK(directory_stat.st_mode) or not stat.S_ISDIR(directory_stat.st_mode): die(f"external data plane provisioner secret directory is unsafe: {EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_DIR}") os.chown(EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_DIR, EXTERNAL_DATA_PLANE_RUNTIME_UID, EXTERNAL_DATA_PLANE_RUNTIME_GID) EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_DIR.chmod(0o500) try: secret_stat = EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_FILE.lstat() except FileNotFoundError: secret_value = secrets.token_urlsafe(48) temporary = EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_DIR / f".{EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_FILE.name}.{os.getpid()}.{time.time_ns()}.tmp" descriptor = None try: descriptor = os.open(str(temporary), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o400) os.write(descriptor, f"{secret_value}\n".encode("ascii")) os.fsync(descriptor) os.fchown(descriptor, EXTERNAL_DATA_PLANE_RUNTIME_UID, EXTERNAL_DATA_PLANE_RUNTIME_GID) os.fchmod(descriptor, 0o400) os.close(descriptor) descriptor = None os.replace(temporary, EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_FILE) fsync_directory(EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_DIR) finally: if descriptor is not None: os.close(descriptor) if temporary.exists(): temporary.unlink() return "created" if stat.S_ISLNK(secret_stat.st_mode) or not stat.S_ISREG(secret_stat.st_mode): die(f"external data plane provisioner secret file is unsafe: {EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_FILE}") if (secret_stat.st_uid != EXTERNAL_DATA_PLANE_RUNTIME_UID or secret_stat.st_gid != EXTERNAL_DATA_PLANE_RUNTIME_GID or stat.S_IMODE(secret_stat.st_mode) != 0o400): die(f"external data plane provisioner secret file has unsafe ownership or permissions: {EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_FILE}") if secret_stat.st_size > 512: die(f"external data plane provisioner secret file is too large: {EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_FILE}") try: secret_value = EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_FILE.read_text(encoding="ascii").strip() except UnicodeDecodeError: die(f"external data plane provisioner secret file is not ascii: {EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_FILE}") if not EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_RE.fullmatch(secret_value): die(f"external data plane provisioner secret file has invalid format: {EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_FILE}") return "reused" def resolve_openssl_binary(): candidate = shutil.which("openssl") if not candidate: die("openssl is required to provision runtime identities") path = Path(candidate).resolve() try: path_stat = path.lstat() except FileNotFoundError: die("resolved openssl binary is missing") if (not stat.S_ISREG(path_stat.st_mode) or path_stat.st_uid != 0 or path_stat.st_mode & (stat.S_IWGRP | stat.S_IWOTH)): die(f"openssl binary is unsafe: {path}") return path def run_openssl(arguments, label): result = subprocess.run( [str(resolve_openssl_binary()), *arguments], check=False, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, timeout=30, ) if result.returncode != 0: detail = re.sub( r"[^A-Za-z0-9 ._:/()\[\],+-]", "?", result.stderr.decode("utf-8", errors="replace").strip(), )[:320] die( f"openssl {label} failed" + (f": {detail}" if detail else "") ) def capture_openssl(arguments, label): result = subprocess.run( [str(resolve_openssl_binary()), *arguments], check=False, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=30, ) if result.returncode != 0: detail = re.sub( r"[^A-Za-z0-9 ._:/()\[\],+-]", "?", result.stderr.decode("utf-8", errors="replace").strip(), )[:320] die( f"openssl {label} failed" + (f": {detail}" if detail else "") ) return result.stdout def ensure_safe_runtime_directory(path, mode, label): try: path_stat = path.lstat() except FileNotFoundError: path.mkdir(parents=True, exist_ok=False) path_stat = path.lstat() if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISDIR(path_stat.st_mode): die(f"{label} directory is unsafe: {path}") os.chown(path, 0, MAP_GATEWAY_RUNTIME_GID) path.chmod(mode) def device_edge_channel_certificate_fingerprint(path): raw = capture_openssl( ["x509", "-in", str(path), "-noout", "-fingerprint", "-sha256"], "Device Edge channel certificate fingerprint", ).decode("ascii", errors="strict").strip() match = re.fullmatch( r"SHA256 Fingerprint=((?:[A-F0-9]{2}:){31}[A-F0-9]{2})", raw, flags=re.IGNORECASE, ) if not match: die("Device Edge channel certificate fingerprint is invalid") return match.group(1).upper() def install_public_runtime_export(source, destination, mode, label): source_bytes = source.read_bytes() if b"PRIVATE KEY" in source_bytes: die(f"{label} contains private key material") try: destination_stat = destination.lstat() except FileNotFoundError: destination_stat = None if destination_stat is not None and ( stat.S_ISLNK(destination_stat.st_mode) or not stat.S_ISREG(destination_stat.st_mode) ): die(f"{label} destination is unsafe: {destination}") temporary = destination.parent / ( f".{destination.name}.{os.getpid()}.{time.time_ns()}.tmp" ) descriptor = None try: descriptor = os.open( str(temporary), os.O_WRONLY | os.O_CREAT | os.O_EXCL, mode, ) os.write(descriptor, source_bytes) os.fsync(descriptor) os.fchown(descriptor, 0, MAP_GATEWAY_RUNTIME_GID) os.fchmod(descriptor, mode) os.close(descriptor) descriptor = None os.replace(temporary, destination) fsync_directory(destination.parent) finally: if descriptor is not None: os.close(descriptor) if temporary.exists(): temporary.unlink() def generate_device_edge_channel_core_identity(private_key, certificate): # Never inherit Synology's global /etc/ssl/openssl.cnf. DSM currently # points req.x509_extensions at v3_ca, which otherwise adds CA:TRUE before # our workload certificate extensions and produces a conflicting cert. config = private_key.parent / ( f".device-edge-core-openssl.{os.getpid()}.{time.time_ns()}.cnf" ) config_text = """[ req ] prompt = no distinguished_name = device_edge_core_dn x509_extensions = device_edge_core_client [ device_edge_core_dn ] CN = nodedc-device-control-core [ device_edge_core_client ] basicConstraints = critical,CA:FALSE keyUsage = critical,digitalSignature extendedKeyUsage = clientAuth subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer """ try: descriptor = os.open( str(config), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600, ) try: os.write(descriptor, config_text.encode("ascii")) os.fsync(descriptor) finally: os.close(descriptor) run_openssl([ "req", "-x509", "-newkey", "ed25519", "-nodes", "-days", "3650", "-config", str(config), "-extensions", "device_edge_core_client", "-keyout", str(private_key), "-out", str(certificate), ], "Device Edge channel Core identity generation") finally: if config.exists(): config.unlink() def device_edge_channel_certificate_text(path): return capture_openssl( ["x509", "-in", str(path), "-noout", "-text"], "Device Edge channel certificate extension inspection", ).decode("utf-8", errors="strict") def validate_device_edge_channel_certificate_extensions(path): text = device_edge_channel_certificate_text(path) required = ( ("X509v3 Basic Constraints: critical", "CA:FALSE"), ("X509v3 Key Usage: critical", "Digital Signature"), ("X509v3 Extended Key Usage:", "TLS Web Client Authentication"), ) for heading, value in required: if text.count(heading) != 1 or text.count(value) != 1: die("Device Edge channel Core certificate extension mismatch") if "CA:TRUE" in text or "Certificate Sign" in text: die("Device Edge channel Core certificate CA capability is forbidden") return "exact-clientAuth" def device_edge_channel_invalid_identity_is_exact_recoverable(): private_key = DEVICE_PLANE_EDGE_CHANNEL_CORE_PRIVATE_KEY_FILE certificate = DEVICE_PLANE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE if not private_key.is_file() or not certificate.is_file(): return False if ( device_edge_channel_certificate_fingerprint(certificate) != DEVICE_PLANE_EDGE_CORE_CHANNEL_INVALID_CERTIFICATE_FINGERPRINT ): return False text = device_edge_channel_certificate_text(certificate) if ( text.count("X509v3 Basic Constraints:") != 2 or text.count("CA:TRUE") != 1 or text.count("CA:FALSE") != 1 ): return False if ( DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_CERTIFICATE_FILE.exists() or DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_CERTIFICATE_FILE.is_symlink() or DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_FINGERPRINT_FILE.exists() or DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_FINGERPRINT_FILE.is_symlink() ): return False if any(DEVICE_PLANE_EDGE_CHANNEL_PEER_TRUST_DIR.iterdir()): return False certificate_public = capture_openssl( ["x509", "-in", str(certificate), "-pubkey", "-noout"], "invalid Device Edge certificate public key", ) private_public = capture_openssl( ["pkey", "-in", str(private_key), "-pubout"], "invalid Device Edge private key public derivation", ) return certificate_public == private_public def recover_invalid_device_edge_channel_core_identity(): if not device_edge_channel_invalid_identity_is_exact_recoverable(): die( "Device Edge channel Core identity is invalid but does not match " "the exact unexported failed-016 recovery boundary" ) recovery = ( DEVICE_PLANE_EDGE_CHANNEL_RECOVERY_DIR / DEVICE_PLANE_EDGE_CORE_CHANNEL_FAILED_PATCH_ID ) if recovery.exists() or recovery.is_symlink(): die("Device Edge channel Core identity recovery destination exists") DEVICE_PLANE_EDGE_CHANNEL_RECOVERY_DIR.mkdir(mode=0o700, exist_ok=False) os.chown(DEVICE_PLANE_EDGE_CHANNEL_RECOVERY_DIR, 0, 0) recovery.mkdir(mode=0o700) os.chown(recovery, 0, 0) private_destination = recovery / "core-private-key.invalid.pem" certificate_destination = recovery / "core-certificate.invalid.pem" os.replace( DEVICE_PLANE_EDGE_CHANNEL_CORE_PRIVATE_KEY_FILE, private_destination, ) os.replace( DEVICE_PLANE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE, certificate_destination, ) os.chown(private_destination, 0, 0) private_destination.chmod(0o600) os.chown(certificate_destination, 0, 0) certificate_destination.chmod(0o600) fsync_directory(recovery) fsync_directory(DEVICE_PLANE_EDGE_CHANNEL_RECOVERY_DIR) fsync_directory(DEVICE_PLANE_EDGE_CHANNEL_SECRET_DIR) return "failed-016-invalid-unexported-quarantined" def ensure_device_edge_channel_core_identity( *, allow_invalid_unexported_recovery=False, ): # The Core private key is born on Synology and never enters a deployment # artifact, Compose environment, Ops, or runner output. Only the matching # public certificate and fingerprint are exported for the explicit VPS # trust handoff. ensure_safe_runtime_directory( DEVICE_PLANE_EDGE_CHANNEL_SECRET_DIR, 0o710, "Device Edge channel identity", ) ensure_safe_runtime_directory( DEVICE_PLANE_EDGE_CHANNEL_PEER_TRUST_DIR, 0o710, "Device Edge channel peer trust", ) private_key = DEVICE_PLANE_EDGE_CHANNEL_CORE_PRIVATE_KEY_FILE certificate = DEVICE_PLANE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE private_exists = private_key.exists() or private_key.is_symlink() certificate_exists = certificate.exists() or certificate.is_symlink() if private_exists != certificate_exists: die("Device Edge channel Core identity is incomplete") recovered = False if private_exists and allow_invalid_unexported_recovery: if device_edge_channel_invalid_identity_is_exact_recoverable(): recover_invalid_device_edge_channel_core_identity() private_exists = False certificate_exists = False recovered = True created = False if not private_exists: private_tmp = private_key.with_name( f".{private_key.name}.{os.getpid()}.{time.time_ns()}.tmp" ) certificate_tmp = certificate.with_name( f".{certificate.name}.{os.getpid()}.{time.time_ns()}.tmp" ) try: generate_device_edge_channel_core_identity( private_tmp, certificate_tmp, ) os.chown(private_tmp, 0, MAP_GATEWAY_RUNTIME_GID) private_tmp.chmod(0o640) os.chown(certificate_tmp, 0, MAP_GATEWAY_RUNTIME_GID) certificate_tmp.chmod(0o640) fsync_file(private_tmp) fsync_file(certificate_tmp) os.replace(private_tmp, private_key) os.replace(certificate_tmp, certificate) fsync_directory(DEVICE_PLANE_EDGE_CHANNEL_SECRET_DIR) created = True finally: if private_tmp.exists(): private_tmp.unlink() if certificate_tmp.exists(): certificate_tmp.unlink() for path, label in ( (private_key, "private key"), (certificate, "certificate"), ): path_stat = path.lstat() if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or path_stat.st_uid != 0 or path_stat.st_gid != MAP_GATEWAY_RUNTIME_GID or stat.S_IMODE(path_stat.st_mode) != 0o640 or path_stat.st_size < 64 or path_stat.st_size > 32 * 1024 ): die(f"Device Edge channel Core {label} boundary mismatch") private_text = private_key.read_text(encoding="ascii") certificate_text = certificate.read_text(encoding="ascii") if "PRIVATE KEY" not in private_text or "PRIVATE KEY" in certificate_text: die("Device Edge channel Core private/public boundary mismatch") if certificate_text.count("-----BEGIN CERTIFICATE-----") != 1: die("Device Edge channel Core certificate cardinality mismatch") run_openssl(["pkey", "-in", str(private_key), "-check", "-noout"], "Device Edge channel Core private key validation") run_openssl(["x509", "-in", str(certificate), "-noout", "-checkend", "604800"], "Device Edge channel Core certificate lifetime validation") validate_device_edge_channel_certificate_extensions(certificate) run_openssl([ "verify", "-purpose", "sslclient", "-CAfile", str(certificate), str(certificate), ], "Device Edge channel Core certificate purpose validation") certificate_public = capture_openssl( ["x509", "-in", str(certificate), "-pubkey", "-noout"], "Device Edge channel Core certificate public key", ) private_public = capture_openssl( ["pkey", "-in", str(private_key), "-pubout"], "Device Edge channel Core private key public derivation", ) if certificate_public != private_public: die("Device Edge channel Core certificate/private key mismatch") ensure_safe_runtime_directory( DEVICE_PLANE_EDGE_CHANNEL_EXPORT_DIR, 0o755, "Device Edge channel public export", ) install_public_runtime_export( certificate, DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_CERTIFICATE_FILE, 0o444, "Device Edge channel Core certificate export", ) fingerprint = device_edge_channel_certificate_fingerprint(certificate) fingerprint_tmp = DEVICE_PLANE_EDGE_CHANNEL_EXPORT_DIR / ( f".core-certificate.sha256.{os.getpid()}.{time.time_ns()}.tmp" ) try: fingerprint_tmp.write_text(f"SHA256={fingerprint}\n", encoding="ascii") os.chown(fingerprint_tmp, 0, MAP_GATEWAY_RUNTIME_GID) fingerprint_tmp.chmod(0o444) fsync_file(fingerprint_tmp) os.replace( fingerprint_tmp, DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_FINGERPRINT_FILE, ) fsync_directory(DEVICE_PLANE_EDGE_CHANNEL_EXPORT_DIR) finally: if fingerprint_tmp.exists(): fingerprint_tmp.unlink() if recovered: return "recovered+created" return "created" if created else "reused" def validate_device_edge_channel_public_export(): certificate = DEVICE_PLANE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE exported_certificate = ( DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_CERTIFICATE_FILE ) exported_fingerprint = ( DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_FINGERPRINT_FILE ) for path, label in ( (exported_certificate, "certificate"), (exported_fingerprint, "fingerprint"), ): try: path_stat = path.lstat() except FileNotFoundError: die(f"Device Edge channel public {label} export is missing") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or path_stat.st_uid != 0 or path_stat.st_gid != MAP_GATEWAY_RUNTIME_GID or stat.S_IMODE(path_stat.st_mode) != 0o444 or path_stat.st_size < 32 or path_stat.st_size > 32 * 1024 ): die(f"Device Edge channel public {label} export is unsafe") certificate_bytes = certificate.read_bytes() exported_bytes = exported_certificate.read_bytes() if b"PRIVATE KEY" in exported_bytes or exported_bytes != certificate_bytes: die("Device Edge channel public certificate export mismatch") fingerprint = device_edge_channel_certificate_fingerprint(certificate) try: fingerprint_text = exported_fingerprint.read_text(encoding="ascii") except (OSError, UnicodeDecodeError): die("Device Edge channel public fingerprint export is unreadable") if fingerprint_text != f"SHA256={fingerprint}\n": die("Device Edge channel public fingerprint export mismatch") return "exact-public-only" def validate_ed25519_public_key_file(path): try: value = path.read_text(encoding="ascii") except (OSError, UnicodeDecodeError): die(f"Engine credential issuer public key is unreadable: {path}") match = re.fullmatch( r"-----BEGIN PUBLIC KEY-----\n([A-Za-z0-9+/=\n]+)-----END PUBLIC KEY-----\n?", value, ) if not match: die("Engine credential issuer public key PEM format mismatch") try: der = base64.b64decode(match.group(1).replace("\n", ""), validate=True) except ValueError: die("Engine credential issuer public key base64 is invalid") # SubjectPublicKeyInfo for Ed25519 is the fixed RFC 8410 algorithm header # plus one 32-byte public key. This rejects accidental RSA/EC key drift # without printing either private or public key material. if len(der) != 44 or not der.startswith(bytes.fromhex("302a300506032b6570032100")): die("Engine credential issuer key is not Ed25519") def fsync_file(path): descriptor = os.open(str(path), os.O_RDONLY) try: os.fsync(descriptor) finally: os.close(descriptor) def ensure_engine_credential_issuer_keypair(): # The private issuer never enters an artifact, Compose environment or # runner output. The Engine receives only its matching public key through # the already persistent nodedc-data mount. ensure_external_data_plane_provisioner_secret() state_dir = ENGINE_CREDENTIAL_SINK_STATE_DIR try: state_stat = state_dir.lstat() except FileNotFoundError: state_dir.mkdir(parents=True, exist_ok=False) state_stat = state_dir.lstat() if stat.S_ISLNK(state_stat.st_mode) or not stat.S_ISDIR(state_stat.st_mode): die(f"Engine credential sink state directory is unsafe: {state_dir}") os.chown(state_dir, 0, 0) state_dir.chmod(0o700) private_key = ENGINE_CREDENTIAL_PROVISIONER_PRIVATE_KEY_FILE public_key = ENGINE_CREDENTIAL_SINK_PUBLIC_KEY_FILE private_exists = private_key.exists() or private_key.is_symlink() public_exists = public_key.exists() or public_key.is_symlink() if not private_exists and public_exists: die("Engine credential issuer public key exists without its private key") if private_exists: private_stat = private_key.lstat() if (stat.S_ISLNK(private_stat.st_mode) or not stat.S_ISREG(private_stat.st_mode) or private_stat.st_uid != EXTERNAL_DATA_PLANE_RUNTIME_UID or private_stat.st_gid != EXTERNAL_DATA_PLANE_RUNTIME_GID or stat.S_IMODE(private_stat.st_mode) != 0o400 or private_stat.st_size < 64 or private_stat.st_size > 1024): die(f"Engine credential issuer private key is unsafe: {private_key}") run_openssl(["pkey", "-in", str(private_key), "-noout"], "private key validation") else: private_tmp = private_key.with_name(f".{private_key.name}.{os.getpid()}.{time.time_ns()}.tmp") descriptor = None try: descriptor = os.open(str(private_tmp), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) os.close(descriptor) descriptor = None run_openssl(["genpkey", "-algorithm", "ED25519", "-out", str(private_tmp)], "Ed25519 key generation") os.chown(private_tmp, EXTERNAL_DATA_PLANE_RUNTIME_UID, EXTERNAL_DATA_PLANE_RUNTIME_GID) private_tmp.chmod(0o400) fsync_file(private_tmp) os.replace(private_tmp, private_key) fsync_directory(private_key.parent) finally: if descriptor is not None: os.close(descriptor) if private_tmp.exists(): private_tmp.unlink() derived_public = state_dir / f".{public_key.name}.{os.getpid()}.{time.time_ns()}.tmp" descriptor = None try: descriptor = os.open(str(derived_public), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) os.close(descriptor) descriptor = None run_openssl(["pkey", "-in", str(private_key), "-pubout", "-out", str(derived_public)], "public key derivation") validate_ed25519_public_key_file(derived_public) if public_exists: public_stat = public_key.lstat() if (stat.S_ISLNK(public_stat.st_mode) or not stat.S_ISREG(public_stat.st_mode) or public_stat.st_uid != 0 or public_stat.st_gid != 0 or stat.S_IMODE(public_stat.st_mode) != 0o444 or public_stat.st_size < 64 or public_stat.st_size > 1024): die(f"Engine credential issuer public key is unsafe: {public_key}") validate_ed25519_public_key_file(public_key) if derived_public.read_bytes() != public_key.read_bytes(): die("Engine credential issuer keypair mismatch") else: os.chown(derived_public, 0, 0) derived_public.chmod(0o444) fsync_file(derived_public) os.replace(derived_public, public_key) fsync_directory(state_dir) finally: if descriptor is not None: os.close(descriptor) if derived_public.exists(): derived_public.unlink() return "reused" if private_exists else "created" def ensure_engine_edp_managed_provisioner_keypair(): # Engine is the sole holder of the signing key. EDP receives only the # matching public key from a separate trust directory. Neither file is # copied into an artifact, shared .env, n8n container, or L2 graph. openssl = shutil.which("openssl") if not openssl: die("openssl is required to manage the Engine EDP signing key") try: secret_parent_stat = MAP_GATEWAY_SECRET_DIR.lstat() except FileNotFoundError: MAP_GATEWAY_SECRET_DIR.mkdir(parents=True, exist_ok=False) secret_parent_stat = MAP_GATEWAY_SECRET_DIR.lstat() if stat.S_ISLNK(secret_parent_stat.st_mode) or not stat.S_ISDIR(secret_parent_stat.st_mode): die("Engine EDP signing key parent directory is unsafe") os.chown(MAP_GATEWAY_SECRET_DIR, 0, MAP_GATEWAY_RUNTIME_GID) MAP_GATEWAY_SECRET_DIR.chmod(0o710) try: private_dir_stat = ENGINE_EDP_MANAGED_PROVISIONER_SECRET_DIR.lstat() except FileNotFoundError: ENGINE_EDP_MANAGED_PROVISIONER_SECRET_DIR.mkdir(parents=False, exist_ok=False) private_dir_stat = ENGINE_EDP_MANAGED_PROVISIONER_SECRET_DIR.lstat() if stat.S_ISLNK(private_dir_stat.st_mode) or not stat.S_ISDIR(private_dir_stat.st_mode): die("Engine EDP signing key directory is unsafe") os.chown(ENGINE_EDP_MANAGED_PROVISIONER_SECRET_DIR, 0, 0) ENGINE_EDP_MANAGED_PROVISIONER_SECRET_DIR.chmod(0o700) trust_parent = ENGINE_EDP_MANAGED_PROVISIONER_TRUST_DIR.parent try: trust_parent_stat = trust_parent.lstat() except FileNotFoundError: trust_parent.mkdir(parents=True, exist_ok=False) trust_parent_stat = trust_parent.lstat() if stat.S_ISLNK(trust_parent_stat.st_mode) or not stat.S_ISDIR(trust_parent_stat.st_mode): die("Engine EDP trust parent directory is unsafe") os.chown(trust_parent, 0, EXTERNAL_DATA_PLANE_RUNTIME_GID) trust_parent.chmod(0o710) try: trust_dir_stat = ENGINE_EDP_MANAGED_PROVISIONER_TRUST_DIR.lstat() except FileNotFoundError: ENGINE_EDP_MANAGED_PROVISIONER_TRUST_DIR.mkdir(parents=False, exist_ok=False) trust_dir_stat = ENGINE_EDP_MANAGED_PROVISIONER_TRUST_DIR.lstat() if stat.S_ISLNK(trust_dir_stat.st_mode) or not stat.S_ISDIR(trust_dir_stat.st_mode): die("Engine EDP trust directory is unsafe") os.chown(ENGINE_EDP_MANAGED_PROVISIONER_TRUST_DIR, 0, EXTERNAL_DATA_PLANE_RUNTIME_GID) ENGINE_EDP_MANAGED_PROVISIONER_TRUST_DIR.chmod(0o550) try: private_stat = ENGINE_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE.lstat() except FileNotFoundError: private_stat = None try: public_stat = ENGINE_EDP_MANAGED_PROVISIONER_PUBLIC_KEY_FILE.lstat() except FileNotFoundError: public_stat = None # A public-only state is ambiguous: replacing its missing private half # would silently rotate trust. Fail before creating anything so recovery is # an explicit operator decision. Private-only is recoverable by derivation. if private_stat is None and public_stat is not None: die("Engine EDP public key exists without its private key") private_created = False if private_stat is None: temporary_private = ( ENGINE_EDP_MANAGED_PROVISIONER_SECRET_DIR / f".private-key.{os.getpid()}.{time.time_ns()}.tmp" ) try: subprocess.run( [openssl, "genpkey", "-algorithm", "ED25519", "-out", str(temporary_private)], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True, ) temporary_stat = temporary_private.lstat() if (stat.S_ISLNK(temporary_stat.st_mode) or not stat.S_ISREG(temporary_stat.st_mode) or temporary_stat.st_size < 80 or temporary_stat.st_size > 8192): die("generated Engine EDP private key is invalid") os.chown(temporary_private, 0, 0) temporary_private.chmod(0o400) with temporary_private.open("rb") as handle: os.fsync(handle.fileno()) os.replace(temporary_private, ENGINE_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE) fsync_directory(ENGINE_EDP_MANAGED_PROVISIONER_SECRET_DIR) private_created = True except (OSError, subprocess.CalledProcessError) as error: die(f"failed to generate Engine EDP signing key: {type(error).__name__}") finally: if temporary_private.exists(): temporary_private.unlink() private_stat = ENGINE_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE.lstat() if (stat.S_ISLNK(private_stat.st_mode) or not stat.S_ISREG(private_stat.st_mode) or private_stat.st_uid != 0 or private_stat.st_gid != 0 or stat.S_IMODE(private_stat.st_mode) != 0o400 or private_stat.st_size < 80 or private_stat.st_size > 8192): die("Engine EDP private key has unsafe ownership, mode, or size") try: derived = subprocess.run( [openssl, "pkey", "-in", str(ENGINE_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE), "-pubout"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ).stdout except (OSError, subprocess.CalledProcessError) as error: die(f"Engine EDP private key validation failed: {type(error).__name__}") if (not derived.startswith(b"-----BEGIN PUBLIC KEY-----\n") or not derived.rstrip().endswith(b"-----END PUBLIC KEY-----") or len(derived) > 8192): die("derived Engine EDP public key is invalid") try: derived_der = subprocess.run( [ openssl, "pkey", "-in", str(ENGINE_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE), "-pubout", "-outform", "DER", ], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ).stdout except (OSError, subprocess.CalledProcessError) as error: die(f"Engine EDP private key type validation failed: {type(error).__name__}") # RFC 8410 Ed25519 SubjectPublicKeyInfo is exactly 44 bytes and carries the # OID 1.3.101.112. Reject RSA/EC keys before either runtime can mount them. if (len(derived_der) != 44 or not derived_der.startswith(bytes.fromhex("302a300506032b6570032100"))): die("Engine EDP signing key must be Ed25519") if public_stat is not None: if (stat.S_ISLNK(public_stat.st_mode) or not stat.S_ISREG(public_stat.st_mode) or public_stat.st_uid != 0 or public_stat.st_gid != EXTERNAL_DATA_PLANE_RUNTIME_GID or stat.S_IMODE(public_stat.st_mode) != 0o440 or public_stat.st_size < 80 or public_stat.st_size > 8192): die("Engine EDP public key has unsafe ownership, mode, or size") installed = ENGINE_EDP_MANAGED_PROVISIONER_PUBLIC_KEY_FILE.read_bytes() if installed != derived: die("Engine EDP public key does not match the installed private key") return "created" if private_created else "reused" temporary_public = ( ENGINE_EDP_MANAGED_PROVISIONER_TRUST_DIR / f".public-key.{os.getpid()}.{time.time_ns()}.tmp" ) descriptor = None try: descriptor = os.open(str(temporary_public), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o440) os.write(descriptor, derived) os.fsync(descriptor) os.fchown(descriptor, 0, EXTERNAL_DATA_PLANE_RUNTIME_GID) os.fchmod(descriptor, 0o440) os.close(descriptor) descriptor = None os.replace(temporary_public, ENGINE_EDP_MANAGED_PROVISIONER_PUBLIC_KEY_FILE) fsync_directory(ENGINE_EDP_MANAGED_PROVISIONER_TRUST_DIR) finally: if descriptor is not None: os.close(descriptor) if temporary_public.exists(): temporary_public.unlink() return "created" return "created" def ensure_foundry_edp_managed_provisioner_keypair(): # Foundry receives its own signing identity. It must never reuse or read # the Engine private key; EDP trusts the matching public key separately. openssl = shutil.which("openssl") if not openssl: die("openssl is required to manage the Foundry EDP signing key") try: secret_parent_stat = MAP_GATEWAY_SECRET_DIR.lstat() except FileNotFoundError: MAP_GATEWAY_SECRET_DIR.mkdir(parents=True, exist_ok=False) secret_parent_stat = MAP_GATEWAY_SECRET_DIR.lstat() if stat.S_ISLNK(secret_parent_stat.st_mode) or not stat.S_ISDIR(secret_parent_stat.st_mode): die("Foundry EDP signing key parent directory is unsafe") os.chown(MAP_GATEWAY_SECRET_DIR, 0, MAP_GATEWAY_RUNTIME_GID) MAP_GATEWAY_SECRET_DIR.chmod(0o710) try: private_dir_stat = FOUNDRY_EDP_MANAGED_PROVISIONER_SECRET_DIR.lstat() except FileNotFoundError: FOUNDRY_EDP_MANAGED_PROVISIONER_SECRET_DIR.mkdir(parents=False, exist_ok=False) private_dir_stat = FOUNDRY_EDP_MANAGED_PROVISIONER_SECRET_DIR.lstat() if stat.S_ISLNK(private_dir_stat.st_mode) or not stat.S_ISDIR(private_dir_stat.st_mode): die("Foundry EDP signing key directory is unsafe") os.chown(FOUNDRY_EDP_MANAGED_PROVISIONER_SECRET_DIR, 0, 0) FOUNDRY_EDP_MANAGED_PROVISIONER_SECRET_DIR.chmod(0o700) trust_parent = FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR.parent try: trust_parent_stat = trust_parent.lstat() except FileNotFoundError: trust_parent.mkdir(parents=True, exist_ok=False) trust_parent_stat = trust_parent.lstat() if stat.S_ISLNK(trust_parent_stat.st_mode) or not stat.S_ISDIR(trust_parent_stat.st_mode): die("Foundry EDP trust parent directory is unsafe") os.chown(trust_parent, 0, EXTERNAL_DATA_PLANE_RUNTIME_GID) trust_parent.chmod(0o710) try: trust_dir_stat = FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR.lstat() except FileNotFoundError: FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR.mkdir(parents=False, exist_ok=False) trust_dir_stat = FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR.lstat() if stat.S_ISLNK(trust_dir_stat.st_mode) or not stat.S_ISDIR(trust_dir_stat.st_mode): die("Foundry EDP trust directory is unsafe") os.chown(FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR, 0, EXTERNAL_DATA_PLANE_RUNTIME_GID) FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR.chmod(0o550) try: private_stat = FOUNDRY_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE.lstat() except FileNotFoundError: private_stat = None try: public_stat = FOUNDRY_EDP_MANAGED_PROVISIONER_PUBLIC_KEY_FILE.lstat() except FileNotFoundError: public_stat = None if private_stat is None and public_stat is not None: die("Foundry EDP public key exists without its private key") private_created = False if private_stat is None: temporary_private = ( FOUNDRY_EDP_MANAGED_PROVISIONER_SECRET_DIR / f".private-key.{os.getpid()}.{time.time_ns()}.tmp" ) try: subprocess.run( [openssl, "genpkey", "-algorithm", "ED25519", "-out", str(temporary_private)], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True, ) temporary_stat = temporary_private.lstat() if (stat.S_ISLNK(temporary_stat.st_mode) or not stat.S_ISREG(temporary_stat.st_mode) or temporary_stat.st_size < 80 or temporary_stat.st_size > 8192): die("generated Foundry EDP private key is invalid") os.chown(temporary_private, 0, 0) temporary_private.chmod(0o400) with temporary_private.open("rb") as handle: os.fsync(handle.fileno()) os.replace(temporary_private, FOUNDRY_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE) fsync_directory(FOUNDRY_EDP_MANAGED_PROVISIONER_SECRET_DIR) private_created = True except (OSError, subprocess.CalledProcessError) as error: die(f"failed to generate Foundry EDP signing key: {type(error).__name__}") finally: if temporary_private.exists(): temporary_private.unlink() private_stat = FOUNDRY_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE.lstat() if (stat.S_ISLNK(private_stat.st_mode) or not stat.S_ISREG(private_stat.st_mode) or private_stat.st_uid != 0 or private_stat.st_gid != 0 or stat.S_IMODE(private_stat.st_mode) != 0o400 or private_stat.st_size < 80 or private_stat.st_size > 8192): die("Foundry EDP private key has unsafe ownership, mode, or size") try: derived = subprocess.run( [openssl, "pkey", "-in", str(FOUNDRY_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE), "-pubout"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ).stdout derived_der = subprocess.run( [ openssl, "pkey", "-in", str(FOUNDRY_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE), "-pubout", "-outform", "DER", ], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ).stdout except (OSError, subprocess.CalledProcessError) as error: die(f"Foundry EDP private key validation failed: {type(error).__name__}") if (not derived.startswith(b"-----BEGIN PUBLIC KEY-----\n") or not derived.rstrip().endswith(b"-----END PUBLIC KEY-----") or len(derived) > 8192 or len(derived_der) != 44 or not derived_der.startswith(bytes.fromhex("302a300506032b6570032100"))): die("Foundry EDP signing key must be Ed25519") if public_stat is not None: if (stat.S_ISLNK(public_stat.st_mode) or not stat.S_ISREG(public_stat.st_mode) or public_stat.st_uid != 0 or public_stat.st_gid != EXTERNAL_DATA_PLANE_RUNTIME_GID or stat.S_IMODE(public_stat.st_mode) != 0o440 or public_stat.st_size < 80 or public_stat.st_size > 8192): die("Foundry EDP public key has unsafe ownership, mode, or size") if FOUNDRY_EDP_MANAGED_PROVISIONER_PUBLIC_KEY_FILE.read_bytes() != derived: die("Foundry EDP public key does not match the installed private key") return "created" if private_created else "reused" temporary_public = ( FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR / f".public-key.{os.getpid()}.{time.time_ns()}.tmp" ) descriptor = None try: descriptor = os.open(str(temporary_public), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o440) os.write(descriptor, derived) os.fsync(descriptor) os.fchown(descriptor, 0, EXTERNAL_DATA_PLANE_RUNTIME_GID) os.fchmod(descriptor, 0o440) os.close(descriptor) descriptor = None os.replace(temporary_public, FOUNDRY_EDP_MANAGED_PROVISIONER_PUBLIC_KEY_FILE) fsync_directory(FOUNDRY_EDP_MANAGED_PROVISIONER_TRUST_DIR) finally: if descriptor is not None: os.close(descriptor) if temporary_public.exists(): temporary_public.unlink() return "created" 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. engine_root = component_root("engine") try: engine_root_stat = engine_root.lstat() except FileNotFoundError: die("Engine root is missing before private state preparation") 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") 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: state_path.mkdir(parents=False, exist_ok=False, mode=0o700) state_stat = state_path.lstat() if stat.S_ISLNK(state_stat.st_mode) or not stat.S_ISDIR(state_stat.st_mode): die(f"{label} directory is unsafe: {state_path}") os.chown(state_path, 0, 0) state_path.chmod(0o700) state_stat = state_path.lstat() if (state_stat.st_uid != 0 or state_stat.st_gid != 0 or stat.S_IMODE(state_stat.st_mode) != 0o700): 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: os.fsync(descriptor) finally: os.close(descriptor) def require_root(): if os.geteuid() != 0: die("run with sudo") def verify_file_mode(path, require_root_owner): try: st = path.lstat() except FileNotFoundError: die(f"missing path: {path}") if stat.S_ISLNK(st.st_mode): die(f"path must not be a symlink: {path}") if require_root_owner and (st.st_uid != 0 or st.st_gid != 0): die(f"path must be root:root: {path}") if st.st_mode & stat.S_IWGRP: die(f"path must not be group-writable: {path}") if st.st_mode & stat.S_IWOTH: die(f"path must not be world-writable: {path}") def verify_install(): invoked = Path(sys.argv[0]).absolute() if invoked != EXPECTED_SELF: die(f"runner must be invoked as {EXPECTED_SELF}, current: {invoked}") verify_file_mode(EXPECTED_SELF, require_root_owner=True) verify_file_mode(Path("/usr/local"), require_root_owner=True) verify_file_mode(Path("/usr/local/sbin"), require_root_owner=True) print(f"path={EXPECTED_SELF}") print(f"sha256={sha256_file(EXPECTED_SELF)}") print(f"python={sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}") print("verify-install-ok") def validate_artifact_location(path): if path.suffix != ".tgz": die("artifact must have .tgz extension") try: st = path.lstat() except FileNotFoundError: die(f"artifact not found: {path}") if stat.S_ISLNK(st.st_mode): die(f"artifact must not be a symlink: {path}") if not stat.S_ISREG(st.st_mode): die(f"artifact must be a regular file: {path}") if st.st_size > MAX_ARTIFACT_BYTES: die(f"artifact too large: {st.st_size} bytes") inbox_real = INBOX.resolve() parent_real = path.parent.resolve() if parent_real != inbox_real: die(f"artifact must be inside {INBOX}") name = path.name if "/" in name or ".." in name or name.startswith("."): die(f"unsafe artifact filename: {name}") def validate_posix_path(value): if not value: die("empty path") if "\\" in value: die(f"backslash is not allowed in path: {value}") pure = PurePosixPath(value) if pure.is_absolute(): die(f"absolute path rejected: {value}") if any(part in ("", ".", "..") for part in pure.parts): die(f"path escape rejected: {value}") if any(ord(ch) < 32 for ch in value): die(f"control character rejected in path: {value!r}") return pure def tar_name_to_path(work_dir, name): pure = validate_posix_path(name) target = work_dir.joinpath(*pure.parts) resolved = target.resolve(strict=False) if not is_relative_to(resolved, work_dir.resolve()): die(f"tar path escaped work dir: {name}") return target def validate_tar_member(member): name = member.name validate_posix_path(name) if name not in ("manifest.env", "files.txt", "payload") and not name.startswith("payload/"): die(f"unexpected tar member: {name}") if not (member.isfile() or member.isdir()): die(f"unsupported tar member type: {name}") if member.mode & stat.S_ISUID or member.mode & stat.S_ISGID: die(f"setuid/setgid tar member rejected: {name}") if member.isfile() and member.size > MAX_FILE_BYTES: die(f"file too large in artifact: {name}") def scan_tar(artifact): member_count = 0 payload_bytes = 0 names = set() with tarfile.open(artifact, "r:gz") as tar: for member in tar: member_count += 1 if member_count > MAX_MEMBER_COUNT: die("too many files in artifact") validate_tar_member(member) if member.name in names: die(f"duplicate tar member rejected: {member.name}") names.add(member.name) if member.isfile() and member.name.startswith("payload/"): payload_bytes += member.size if payload_bytes > MAX_PAYLOAD_BYTES: die("payload is too large") if "manifest.env" not in names: die("manifest.env missing") if "files.txt" not in names: die("files.txt missing") if not any(name == "payload" or name.startswith("payload/") for name in names): die("payload missing") def safe_extract(artifact, work_dir): scan_tar(artifact) with tarfile.open(artifact, "r:gz") as tar: for member in tar: validate_tar_member(member) target = tar_name_to_path(work_dir, member.name) if member.isdir(): target.mkdir(parents=True, exist_ok=True) target.chmod(0o755) continue if target.exists() and target.is_dir(): die(f"file target already exists as directory: {member.name}") target.parent.mkdir(parents=True, exist_ok=True) source = tar.extractfile(member) if source is None: die(f"cannot read tar member: {member.name}") tmp = target.with_name(f"{target.name}.extracting") with source, tmp.open("wb") as out: shutil.copyfileobj(source, out, 1024 * 1024) tmp.chmod(0o644) os.replace(tmp, target) def parse_manifest(path): data = {} with path.open("r", encoding="utf-8") as f: for lineno, line in enumerate(f, 1): line = line.rstrip("\n") if not line: continue if "=" not in line: die(f"manifest line {lineno} must be key=value") key, value = line.split("=", 1) if key not in MANIFEST_KEYS: die(f"unsupported manifest key: {key}") if key in data: die(f"duplicate manifest key: {key}") if any(ord(ch) < 32 for ch in value): die(f"control character in manifest value: {key}") data[key] = value missing = sorted(MANIFEST_KEYS - set(data)) if missing: die(f"manifest keys missing: {', '.join(missing)}") if not PATCH_ID_RE.match(data["id"]): die("manifest id contains unsafe characters") if data["type"] != "app-overlay": die(f"unsupported artifact type: {data['type']}") if data["component"] not in COMPONENTS: die(f"unsupported component: {data['component']}") return data def parse_files_list(path): entries = [] seen = set() with path.open("r", encoding="utf-8") as f: for lineno, raw in enumerate(f, 1): rel = raw.rstrip("\n") if not rel: continue if rel.strip() != rel: die(f"files.txt line {lineno} has leading/trailing whitespace") validate_posix_path(rel) if rel in seen: die(f"duplicate files.txt entry: {rel}") seen.add(rel) entries.append(rel) if not entries: die("files.txt is empty") return entries def denied_payload_path(component, rel): lower = rel.lower() parts = lower.split("/") base = parts[-1] if component == "engine" and rel in ( "docker-compose.yml", "nodedc-source/services/n8n/private-extensions/n8n-nodes-ndc/Dockerfile", ): pass elif component == "platform" and rel in ( "platform/docker-compose.platform-http.yml", "platform/notification-core/Dockerfile", "platform/ai-workspace-hub/Dockerfile", "platform/ai-workspace-assistant/Dockerfile", "platform/ontology-core/Dockerfile", "platform/gelios-gateway/Dockerfile", "platform/services/map-gateway/Dockerfile", "platform/services/external-data-plane/Dockerfile", ): pass elif component == "bim-viewer" and rel in ( "converter/Dockerfile", ): pass elif component == "dc-cms" and rel in ( "Dockerfile", "infra/docker-compose.yml", ): pass elif component == "module-foundry" and rel in ( "Dockerfile", "infra/docker-compose.module-foundry.yml", ): pass elif component == "device-plane" and rel in ( "docker-compose.device-plane.yml", DEVICE_PLANE_MANAGER_COMPOSE_REL, DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL, "services/device-control-core/Dockerfile", "services/device-gateway/Dockerfile", "services/device-manager/Dockerfile", "services/device-backhaul-target/Dockerfile", ): pass elif component == "gitea" and rel == GITEA_COMPOSE_REL: pass elif component == "proxy-contur" and rel in ( "Dockerfile", "docker-compose.yml", ): pass elif component == "dc-amd-proxy" and rel in ( "Dockerfile", "docker-compose.yml", ): pass elif base in (".env", "dockerfile", "docker-compose.yml", "compose.yml"): return "sensitive or infrastructure filename" if base.endswith(".env") or base.endswith((".pem", ".crt", ".key", ".p12", ".pfx")): return "secret-like file extension" if base.endswith((".sh", ".bash", ".zsh")): return "shell files are not allowed in app-overlay artifacts" if base.endswith(".zip"): return "zip backup files are not deployable artifacts" if any(part in (".git", "node_modules") for part in parts): return "repository/build dependency directory" # `packages/tokens` is a versioned UI design-token package, not a secret # store. Keep this narrowly scoped exception so the general filename guard # remains in force for every other Module Foundry payload path. if not (component == "module-foundry" and lower.startswith("packages/tokens/")) and any( token in lower for token in ("secret", "token", "password") ): return "secret-like path" runtime_prefixes = () if component == "engine": runtime_prefixes = ( "nodedc-source/server/data", "nodedc-source/server/storage", "nodedc-source/server/logs", "nodedc-source/public/storage", "nodedc-source/dist/storage", ) if rel == "nodedc-source/server/.env": return "server env file" elif component == "launcher": runtime_prefixes = ( "server/data", "server/storage", "server/logs", "public/storage", "dist/storage", ) if rel == "server/.env": return "server env file" elif component == "platform": runtime_prefixes = ( "backups", "launcher", "ops-agents", "tasker", "platform/.env.synology", "platform/authentik", "platform/docs", "authentik/data", "authentik/certs", "authentik/media", "authentik/postgresql", ) if rel.startswith("platform/.env"): return "platform env file" if rel.startswith(("platform/Caddyfile.http.bak", "platform/docker-compose.platform-http.yml.bak")): return "platform backup file" elif component == "tasker": runtime_prefixes = ( "plane-app/.local-web-root", "plane-app/archive", "plane-app/backup", "plane-app/plane.env", "plane-app/plane.env.example", "plane-app/plane.env.staging.example", "plane-app/plane.env.synology", "plane-app/plane.env.synology.base", "plane-src/.cache", "plane-src/.next", "plane-src/.pnpm-store", "plane-src/.react-router", "plane-src/.turbo", "plane-src/build", "plane-src/coverage", "plane-src/dist", "plane-src/playwright-report", "plane-src/test-results", ) if rel.startswith(("plane-app/docker-compose.yaml.bak", "plane-app/docker-compose.synology.override.yml.bak")): return "tasker backup file" elif component == "ops-agents": runtime_prefixes = ( "dist", ) if rel.startswith(".env"): return "ops-agents env file" elif component == "bim-viewer": runtime_prefixes = ( "dist", "frontend/dist", "server/data", "server/logs", "server/storage", "server/tmp", ) if rel == ".env" or rel.startswith(".env."): if rel != ".env.synology.example": return "bim-viewer env file" if rel.startswith(("docker-compose.beam.yml.bak",)): return "bim-viewer backup file" elif component == "module-foundry": runtime_prefixes = ( "runtime-data", "node_modules", "apps/catalog/dist", "server/data", "server/logs", "server/storage", ) if rel == ".env" or rel.startswith(".env."): if rel != ".env.example": return "module-foundry env file" if rel.startswith(("Dockerfile.bak", "infra/docker-compose.module-foundry.yml.bak")): return "module-foundry backup file" elif component == "device-plane": runtime_prefixes = ( "runtime", "secrets", "data", "logs", "backups", "node_modules", ) if rel == ".env" or rel.startswith(".env."): return "device-plane env file" if "test" in parts: return "device-plane test path" if rel.startswith(( "docker-compose.device-plane.yml.bak", "docker-compose.device-manager.yml.bak", "services/device-control-core/Dockerfile.bak", "services/device-gateway/Dockerfile.bak", "services/device-manager/Dockerfile.bak", )): return "device-plane backup file" elif component == "gitea": runtime_prefixes = ( "data", "config", "secrets", "repositories", "users", "tokens", "hooks", "logs", "backups", ) if rel == ".env" or rel.startswith(".env."): return "gitea env file" if rel.startswith(( "docker-compose.gitea.yml.bak", "deployment/gitea-fresh-install-v1.json.bak", "deployment/gitea-incident-salvage-v1.json.bak", "deployment/gitea-incident-salvage/confirmed-disposition-v1.json.bak", "deployment/gitea-incident-salvage/confirmed-decision.json.bak", "deployment/gitea-incident-salvage/users.decisions.csv.bak", "deployment/gitea-incident-salvage/repositories.decisions.csv.bak", )): return "gitea backup file" elif component == "n8n-private-extension": # An extension release is inert data at this boundary. Activation is # Engine-owned and must never be smuggled into the artifact as a script, # Compose file, environment file, pointer or mount configuration. extension_parts = PurePosixPath(rel).parts is_release_directory = ( len(extension_parts) == 3 and extension_parts[0] == "releases" and extension_parts[1] == "n8n-nodes-ndc" and N8N_PRIVATE_EXTENSION_RELEASE_RE.fullmatch(extension_parts[2]) ) if not is_release_directory and base not in ("package.tgz", "release.json", "rollback.json"): return "unexpected private extension release member" elif component == "proxy-contur": runtime_prefixes = ( "node_modules", ) if rel == ".env" or rel.startswith(".env."): return "proxy-contur env file" if rel.startswith(("Dockerfile.bak", "docker-compose.yml.bak")): return "proxy-contur backup file" elif component == "dc-amd-proxy": runtime_prefixes = ( "node_modules", "runtime", ) if rel == ".env" or rel.startswith(".env."): return "dc-amd-proxy env file" if rel.startswith(("Dockerfile.bak", "docker-compose.yml.bak")): return "dc-amd-proxy backup file" elif component == "dc-cms": runtime_prefixes = ( "admin/uploads", "infra/authentik/certs", "infra/authentik/data", "infra/authentik/media", "infra/authentik/postgresql", "server/data", "server/logs", "server/storage", "sites", ) if rel == ".env" or rel.startswith(".env."): return "dc-cms root env file" if rel in ("infra/.env", "infra/.env.synology"): return "dc-cms env file" if rel.startswith("infra/.env.") and rel not in ( "infra/.env.example", "infra/.env.synology.example", ): return "dc-cms env file" if rel.startswith(("Dockerfile.bak", "infra/docker-compose.yml.bak")): return "dc-cms backup file" elif component == "dc-cms-site-nodedc": runtime_prefixes = ( "assets/.trash", "assets/uploads/.trash", "node_modules", ) if any(rel == prefix or rel.startswith(prefix + "/") for prefix in runtime_prefixes): return "runtime data path" return None def allowed_payload_path(component, rel): validate_posix_path(rel) reason = denied_payload_path(component, rel) if reason: die(f"path rejected: {rel}: {reason}") if component == "mission-core-map-access": if rel == "access.json": return True die("Mission Core Map Access accepts only access.json") if component == "engine": if rel in ("nodedc-source/package.json", "nodedc-source/package-lock.json"): return True if rel == "docker-compose.yml": return True if rel.startswith(( "nodedc-source/server/", "nodedc-source/src/", "nodedc-source/dist/", "nodedc-source/public/", "nodedc-source/workers/ai-workspace-bridge/", )): return True if rel == "nodedc-source/services/n8n/private-extensions" or rel.startswith( "nodedc-source/services/n8n/private-extensions/" ): return True if rel == ENGINE_CREDENTIAL_BACKEND_OVERRIDE_TEMPLATE_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 + "/" ): return True if rel == "nodedc-source/dist": return True if component == "launcher": if rel in ( "index.html", "package.json", "package-lock.json", "tsconfig.json", "tsconfig.app.json", "tsconfig.node.json", "vite.config.ts", ): return True if rel.startswith(("server/", "src/", "public/", "scripts/", "dc-ui-guideline/")): return True if component == "platform": if rel in ( "platform/Caddyfile.http", "platform/docker-compose.platform-http.yml", "platform/docker-compose.external-data-plane.yml", PLATFORM_DEVICE_CORE_HUB_TRUST_REL, PLATFORM_DEVICE_MANAGER_PUBLIC_ROUTE_REL, ): return True if rel == "platform/notification-core" or rel.startswith("platform/notification-core/"): return True if rel == "platform/ai-workspace-hub" or rel.startswith("platform/ai-workspace-hub/"): return True if rel == "platform/ai-workspace-assistant" or rel.startswith("platform/ai-workspace-assistant/"): return True if rel == "platform/ontology-core" or rel.startswith("platform/ontology-core/"): return True if rel == "platform/gelios-gateway" or rel.startswith("platform/gelios-gateway/"): return True if rel == "platform/services/map-gateway" or rel.startswith("platform/services/map-gateway/"): return True if rel == "platform/services/external-data-plane" or rel.startswith("platform/services/external-data-plane/"): return True if rel == "platform/packages/external-provider-contract" or rel.startswith("platform/packages/external-provider-contract/"): return True if rel == "authentik/custom-templates" or rel.startswith("authentik/custom-templates/"): return True if component == "tasker": if rel in ( "plane-app/docker-compose.yaml", "plane-app/docker-compose.synology.override.yml", ): return True if rel.startswith(( "plane-src/apps/api/", "plane-src/apps/web/", "plane-src/packages/", )): return True if component == "ops-agents": if rel in ( ".dockerignore", ".env.synology.example", ".gitignore", "Dockerfile", "README.md", "docker-compose.local.yml", "docker-compose.synology.yml", "package.json", "package-lock.json", "tsconfig.json", ): return True if rel.startswith(( "docs/", "migrations/", "src/", )): return True if component == "bim-viewer": if rel in ( "converter", "embeddedDemos", "frontend", "images", "locales", "processor", "report", "src", ): return True if rel in ( ".env.synology.example", ".esdoc.json", ".gitattributes", ".gitignore", ".release-please-manifest.json", "CHANGELOG.md", "CODE_OF_CONDUCT.md", "LICENSE", "README.md", "SECURITY.md", "_config.yml", "changelog-template.hbs", "docker-compose.beam.yml", "embeddedViewer.html", "index.js", "package.json", "package-lock.json", "release-please-config.json", "rollup.config.js", "rollup.dev.config.js", "webComponentExample.html", "xeokit-bim-viewer.css", ): return True if rel.startswith(( "converter/", "embeddedDemos/", "frontend/", "images/", "locales/", "processor/", "report/", "server/", "src/", )): return True if component == "module-foundry": if rel in ( ".dockerignore", ".env.example", ".gitignore", "Dockerfile", "package.json", "package-lock.json", "tsconfig.base.json", "infra/docker-compose.module-foundry.yml", ): return True if rel in ("apps", "packages", "registry", "runtime-seed", "scripts", "server"): return True if rel.startswith(( "apps/", "packages/", "registry/", "runtime-seed/", "scripts/", "server/", )): return True if component == "device-plane": if rel in ( ".dockerignore", "package.json", "package-lock.json", "docker-compose.device-plane.yml", DEVICE_PLANE_MANAGER_COMPOSE_REL, DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL, DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL, DEVICE_PLANE_POSTGRES_BOOTSTRAP_REL, DEVICE_PLANE_FOUNDATION_RECOVERY_REL, DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_REL, DEVICE_PLANE_B2_DISCOVERY_INGRESS_REL, DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_REL, DEVICE_PLANE_BACKHAUL_TARGET_REL, DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_REL, DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL, DEVICE_PLANE_MANAGER_RELEASE_V2_REL, DEVICE_PLANE_MANAGER_RELEASE_V3_REL, DEVICE_PLANE_MANAGER_RELEASE_V4_REL, DEVICE_PLANE_MANAGER_RELEASE_V5_REL, DEVICE_PLANE_MANAGER_RELEASE_V6_REL, DEVICE_PLANE_MANAGER_RELEASE_V7_REL, DEVICE_PLANE_MANAGER_RELEASE_V8_REL, DEVICE_PLANE_MANAGER_RELEASE_V9_REL, DEVICE_PLANE_MANAGER_RELEASE_V10_REL, DEVICE_PLANE_MANAGER_RELEASE_V11_REL, DEVICE_PLANE_MANAGER_RELEASE_V12_REL, DEVICE_PLANE_MANAGER_RELEASE_V13_REL, DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_REL, DEVICE_PLANE_CONTROL_CORE_RELEASE_REL, DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_REL, DEVICE_PLANE_CONTROL_CORE_RELEASE_V3_REL, DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_REL, DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_REL, DEVICE_PLANE_CONTROL_CORE_INCIDENT_AUDIT_REL, DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_AUDIT_REL, DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_REL, DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_REL, DEVICE_PLANE_MANAGER_RECONCILIATION_REL, DEVICE_PLANE_MANAGER_V2_RECONCILIATION_REL, "packages/device-protocol-contract", "packages/device-edge-channel-contract", "packages/infrastructure-telemetry-contract", "packages/arusnavi-b2-adapter", "services/device-control-core", "services/device-gateway", "services/device-edge-relay/package.json", "services/device-manager", "services/device-backhaul-target", ): return True if rel.startswith(( "packages/device-protocol-contract/", "packages/device-edge-channel-contract/", "packages/infrastructure-telemetry-contract/", "packages/arusnavi-b2-adapter/", "services/device-control-core/", "services/device-gateway/", "services/device-manager/", "services/device-backhaul-target/", )): return True if component == "gitea": if rel in (*GITEA_FRESH_INSTALL_ENTRIES, *GITEA_SALVAGE_ENTRIES): return True if component == "n8n-private-extension": parts = PurePosixPath(rel).parts if ( len(parts) == 3 and parts[0] == "releases" and parts[1] == "n8n-nodes-ndc" and N8N_PRIVATE_EXTENSION_RELEASE_RE.fullmatch(parts[2]) ): return True if ( len(parts) == 4 and parts[0] == "releases" and parts[1] == "n8n-nodes-ndc" and N8N_PRIVATE_EXTENSION_RELEASE_RE.fullmatch(parts[2]) and parts[3] in ("package.tgz", "release.json", "rollback.json") ): return True if component == "proxy-contur": if rel in ( "Dockerfile", "README.md", "docker-compose.yml", "package.json", "package-lock.json", "server.js", ): return True if component == "dc-amd-proxy": if rel in ( "Dockerfile", "README.md", "docker-compose.yml", "package.json", "server.mjs", ): return True if component == "dc-cms": if rel in ( ".dockerignore", ".gitignore", "Dockerfile", "README.md", "package.json", "package-lock.json", "infra/.env.example", "infra/.env.synology.example", "infra/docker-compose.yml", "infra/authentik/bootstrap-cms.py", ): return True if rel.startswith(( "admin/", "infra/authentik/custom-templates/", "infra/reverse-proxy/", "projects/", "server/", )): return True if component == "dc-cms-site-nodedc": if rel in ( ".gitignore", "README.md", "asset-manifest.json", "index.html", "package.json", "package-lock.json", "robots.txt", "sitemap.xml", ): return True if rel.startswith(( "assets/", "content/", "knowledge/", "templates/", "tools/", )): return True die(f"path allowlist rejected: {rel}") def path_is_covered_by_files_list(rel, entries): return any(rel == entry or rel.startswith(entry.rstrip("/") + "/") for entry in entries) def validate_payload_tree(component, payload_dir, entries): root = payload_dir.resolve() for path in payload_dir.rglob("*"): resolved = path.resolve(strict=False) if not is_relative_to(resolved, root): die(f"payload path escaped payload dir: {path}") rel = path.relative_to(payload_dir).as_posix() if path.is_symlink(): die(f"payload symlink rejected: {rel}") if not path.is_file() and not path.is_dir(): die(f"payload special file rejected: {rel}") if path.is_dir(): continue allowed_payload_path(component, rel) if not path_is_covered_by_files_list(rel, entries): die(f"payload file is not covered by files.txt: {rel}") def read_strict_json(path, label, max_bytes=64 * 1024): try: file_stat = path.lstat() except FileNotFoundError: die(f"{label} missing: {path}") if stat.S_ISLNK(file_stat.st_mode) or not stat.S_ISREG(file_stat.st_mode): die(f"{label} must be a regular file") if file_stat.st_size < 2 or file_stat.st_size > max_bytes: die(f"{label} has invalid size") def reject_duplicate_keys(pairs): result = {} for key, value in pairs: if key in result: die(f"{label} has duplicate key: {key}") result[key] = value return result try: return json.loads( path.read_text(encoding="utf-8"), object_pairs_hook=reject_duplicate_keys, ) except UnicodeDecodeError: die(f"{label} is not utf-8") except json.JSONDecodeError as exc: die(f"{label} is invalid json: {exc.msg}") def require_exact_json_keys(value, expected, label): if not isinstance(value, dict): die(f"{label} must be an object") actual = set(value) if actual != set(expected): die(f"{label} keys mismatch: expected={sorted(expected)} actual={sorted(actual)}") def expected_gitea_fresh_install_descriptor(): return { "schemaVersion": "nodedc.gitea.fresh-install.v1", "action": "fresh-install", "component": "gitea", "installRoot": str(GITEA_ROOT), "compose": { "file": GITEA_COMPOSE_REL, "project": GITEA_COMPOSE_PROJECT, "service": GITEA_SERVICE, "sha256": GITEA_COMPOSE_SHA256, }, "runtime": { "image": GITEA_IMAGE, "platform": "linux/amd64", "pullPolicy": "never", "minimumComposeVersion": "2.20.1", "transport": "unix:/run/gitea/gitea.sock", "socketBind": ( "/volume1/docker/nodedc-gitea/socket:/run/gitea" ), "ssh": "disabled-no-published-port", "database": "fresh-sqlite-only", "lfs": "disabled-pending-reviewed-restore-transition", "networkMode": "none", "logging": "bounded-json-file-10m-x3", "stopGracePeriod": "30s", }, "trust": { "artifactSecrets": "forbidden", "runtimeSecrets": "runner-managed-file-mounts", "legacyRootAccess": "forbidden", "legacyDatabaseImport": "forbidden", "legacyRepositoryImport": "forbidden", }, "reverseProxyPrerequisite": { "managedOutsideArtifact": True, "requiredDsmUpstream": "127.0.0.1:3000", "requiredNginxBridge": str(GITEA_NGINX_BRIDGE_CONFIG), "requiredNginxBridgeSha256": GITEA_NGINX_BRIDGE_SHA256, "requiredUnixUpstream": str(GITEA_SOCKET_FILE), "mustBeCompletedBeforeApply": True, }, "rollback": ( "stop-candidate-preserve-fresh-runtime-state-and-restore-source" ), } def validate_gitea_fresh_install_payload(payload_dir, entries): if tuple(entries) != GITEA_FRESH_INSTALL_ENTRIES: die("Gitea fresh-install artifact entry set mismatch") descriptor = read_strict_json( payload_dir / GITEA_FRESH_INSTALL_DESCRIPTOR_REL, "Gitea fresh-install descriptor", max_bytes=16 * 1024, ) if descriptor != expected_gitea_fresh_install_descriptor(): die("Gitea fresh-install descriptor mismatch") compose = payload_dir / GITEA_COMPOSE_REL try: compose_stat = compose.lstat() except FileNotFoundError: die("Gitea fresh-install Compose file is missing") if stat.S_ISLNK(compose_stat.st_mode) or not stat.S_ISREG(compose_stat.st_mode): die("Gitea fresh-install Compose file is unsafe") if sha256_file(compose) != GITEA_COMPOSE_SHA256: die("Gitea fresh-install Compose digest mismatch") try: compose_text = compose.read_text(encoding="utf-8") except UnicodeDecodeError: die("Gitea fresh-install Compose file is not utf-8") if compose_text.count(GITEA_IMAGE) != 1: die("Gitea fresh-install image identity mismatch") for required in ( "platform: linux/amd64", "pull_policy: never", "network_mode: none", 'user: "1000:1000"', "stop_grace_period: 30s", "driver: json-file", 'max-size: "10m"', 'max-file: "3"', "GITEA__server__PROTOCOL: http+unix", "GITEA__server__HTTP_ADDR: /run/gitea/gitea.sock", 'GITEA__server__UNIX_SOCKET_PERMISSION: "0666"', "GITEA__server__LOCAL_ROOT_URL: http://unix/", 'GITEA__server__DISABLE_SSH: "true"', 'GITEA__server__LFS_START_SERVER: "false"', 'GITEA__server__LFS_ALLOW_PURE_SSH: "false"', "GITEA__security__SECRET_KEY_URI: file:/run/secrets/gitea_secret_key", "GITEA__security__INTERNAL_TOKEN_URI: file:/run/secrets/gitea_internal_token", "GITEA__security__TWO_FACTOR_AUTH: enforced", 'GITEA__security__REVERSE_PROXY_LIMIT: "1"', "GITEA__security__ALLOWED_HOST_LIST: loopback", "GITEA__security__REVERSE_PROXY_TRUSTED_PROXIES: 127.0.0.0/8,::1/128", 'GITEA__service__DISABLE_REGISTRATION: "true"', 'GITEA__service__ENABLE_REVERSE_PROXY_AUTHENTICATION: "false"', 'GITEA__service__ENABLE_REVERSE_PROXY_AUTHENTICATION_API: "false"', 'GITEA__service__ENABLE_REVERSE_PROXY_AUTO_REGISTRATION: "false"', 'GITEA__service__ENABLE_BASIC_AUTHENTICATION: "false"', 'GITEA__admin__DISABLE_REGULAR_ORG_CREATION: "true"', "GITEA__admin__USER_DISABLED_FEATURES: deletion,manage_ssh_keys,manage_gpg_keys,change_username", 'GITEA__security__DISABLE_GIT_HOOKS: "true"', 'GITEA__security__DISABLE_WEBHOOKS: "true"', 'GITEA__repository__DISABLE_MIGRATIONS: "true"', 'GITEA__packages__ENABLED: "false"', 'GITEA__oauth2__ENABLED: "false"', 'GITEA__openid__ENABLE_OPENID_SIGNIN: "false"', 'GITEA__cron.update_checker__ENABLED: "false"', "source: /volume1/docker/nodedc-gitea/socket", "target: /run/gitea", "create_host_path: false", "read_only: true", "no-new-privileges:true", ): if required not in compose_text: die(f"Gitea fresh-install Compose boundary missing: {required}") for forbidden in ( "4022", "2222:2222", "0.0.0.0:3000", "ports:", "networks:", "/var/run/docker.sock", "/volume1/docker/gitea", "privileged: true", "pull_policy: always", "__FILE", "GITEA__security__SECRET_KEY:", "GITEA__security__INTERNAL_TOKEN:", "GITEA__server__LFS_JWT_SECRET:", "GITEA__server__LFS_JWT_SECRET_URI", "gitea_lfs_jwt_secret", "lfs-jwt-secret", "GITEA__server__REVERSE_PROXY_LIMIT", "GITEA__server__REVERSE_PROXY_TRUSTED_PROXIES", "GITEA__security__ENABLE_REVERSE_PROXY_AUTHENTICATION", "GITEA__security__ENABLE_REVERSE_PROXY_AUTHENTICATION_API", "GITEA__security__ENABLE_REVERSE_PROXY_AUTO_REGISTRATION", "GITEA__service__DISABLE_REGULAR_ORG_CREATION", "GITEA__service__USER_DISABLED_FEATURES", ): if forbidden in compose_text: die(f"Gitea fresh-install Compose boundary violation: {forbidden}") return descriptor def canonical_gitea_salvage_record_sha256(record): encoded = json.dumps( record, ensure_ascii=False, sort_keys=True, separators=(",", ":"), ).encode("utf-8") return hashlib.sha256(encoded).hexdigest() def canonical_gitea_salvage_evidence(value, label): try: encoded = json.dumps( value, ensure_ascii=True, sort_keys=True, separators=(",", ":"), allow_nan=False, ).encode("utf-8") except (TypeError, ValueError): die(f"Gitea salvage {label} is not canonical JSON") if len(encoded) > GITEA_SALVAGE_EVIDENCE_MAX_BYTES: die(f"Gitea salvage {label} exceeds the evidence byte limit") return { "bytes": len(encoded), "json": encoded.decode("utf-8"), "sha256": hashlib.sha256(encoded).hexdigest(), } def read_gitea_salvage_csv(path, expected_header, expected_rows, label): try: path_stat = path.lstat() except FileNotFoundError: die(f"{label} is missing") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or path_stat.st_size < len(",".join(expected_header)) or path_stat.st_size > 8 * 1024 * 1024 ): die(f"{label} is unsafe") try: raw = path.read_text(encoding="utf-8") except (OSError, UnicodeDecodeError): die(f"{label} is not readable utf-8") if raw.startswith("\ufeff") or "\x00" in raw or not raw.endswith("\n"): die(f"{label} has invalid text framing") try: reader = csv.DictReader(io.StringIO(raw, newline=""), strict=True) if tuple(reader.fieldnames or ()) != tuple(expected_header): die(f"{label} header mismatch") rows = list(reader) except csv.Error as exc: die(f"{label} is invalid csv: {exc}") if len(rows) != expected_rows: die(f"{label} row count mismatch") for index, row in enumerate(rows, 2): if None in row or set(row) != set(expected_header): die(f"{label} row {index} shape mismatch") for key, value in row.items(): if ( not isinstance(value, str) or len(value.encode("utf-8")) > 1024 or any(ord(char) < 0x20 for char in value) ): die(f"{label} row {index} field {key} is unsafe") return rows def validate_gitea_salvage_incident_disposition(payload_dir): path = payload_dir / GITEA_SALVAGE_DISPOSITION_REL try: path_stat = path.lstat() raw = path.read_bytes() except (FileNotFoundError, OSError): die("Gitea salvage confirmed disposition is missing or unreadable") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or path_stat.st_size < 2 or path_stat.st_size > 64 * 1024 ): die("Gitea salvage confirmed disposition is unsafe") if sha256_file(path) != GITEA_SALVAGE_DISPOSITION_SHA256: die("Gitea salvage confirmed disposition digest mismatch") disposition = read_strict_json( path, "Gitea salvage confirmed disposition", max_bytes=64 * 1024, ) try: canonical = ( json.dumps( disposition, ensure_ascii=True, sort_keys=True, separators=(",", ":"), allow_nan=False, ).encode("utf-8") + b"\n" ) except (TypeError, ValueError): die("Gitea salvage confirmed disposition is not canonical JSON") if raw != canonical: die("Gitea salvage confirmed disposition byte framing is not canonical") require_exact_json_keys( disposition, { "activation", "confirmation", "incidentId", "referencePolicy", "remainingBlockers", "repositoryStatePolicy", "schemaVersion", "scope", "sourceEvidence", }, "Gitea salvage confirmed disposition", ) if ( disposition.get("schemaVersion") != "nodedc.gitea.incident-disposition.v1" or disposition.get("incidentId") != "gitea-20260814" or disposition.get("confirmation") != { "confirmationRecordedAt": "2026-08-14T15:16:04Z", "confirmationScope": ( "exact-10-users-45-repositories;heads-tags-live;" "pull-remote-sealed-archive;non-git-state-policy;" "no-2fa-change" ), "confirmationSource": "owner-instruction-in-current-incident-thread", "confirmedBy": "dctouch", } or disposition.get("scope") != { "keptRepositories": 45, "keptUsers": 10, "referenceDecisionRows": 105, "repositoryStores": 49, } ): die("Gitea salvage confirmed disposition identity mismatch") source = disposition.get("sourceEvidence") expected_source = { "databaseSha256": GITEA_SALVAGE_SNAPSHOT_DATABASE_SHA256, "identityDecisionManifestSha256": ( GITEA_SALVAGE_DECISION_MANIFEST_SHA256 ), "referenceManifestBytes": 36010, "referenceManifestReviewState": "observed-unreviewed", "referenceManifestSha256": ( GITEA_SALVAGE_DISPOSITION_REFERENCE_MANIFEST_SHA256 ), "snapshotUuid": GITEA_SALVAGE_SNAPSHOT_UUID, "unsupportedRepositoryReportBytes": 119468, "unsupportedRepositoryReportSha256": ( GITEA_SALVAGE_EXPECTED_UNSUPPORTED_REPORT_SHA256 ), "unsupportedSchemaCatalogReviewState": "observed-unreviewed", "unsupportedSchemaCatalogSha256": ( GITEA_SALVAGE_DISPOSITION_UNSUPPORTED_SCHEMA_SHA256 ), } if source != expected_source: die("Gitea salvage confirmed disposition source mismatch") reference_policy = disposition.get("referencePolicy") require_exact_json_keys( reference_policy, { "archiveOnly", "exactDecisions", "executionAuthority", "forensicScope", "headInvariants", "liveRestore", "unknownNamespacePolicy", }, "Gitea salvage confirmed reference policy", ) if ( reference_policy.get("executionAuthority") != "exact-decisions-one-to-one-observed-reference-manifest" or reference_policy.get("unknownNamespacePolicy") != "reject" or reference_policy.get("forensicScope") != {"allDiscoveredRefs": 105, "preserveNameOidEvidence": True} or reference_policy.get("liveRestore") != { "copyLegacyHeadOrRefFiles": False, "mainRepositoryHeads": 85, "mainRepositoryTags": 4, "totalRefs": 93, "wikiHeads": 4, } or reference_policy.get("archiveOnly") != { "immutableArchiveRequired": True, "neverAutoPromote": True, "pullRefs": 5, "remoteRefs": 7, "totalRefs": 12, } or reference_policy.get("headInvariants") != { "allowedMissingTargets": [ { "head": "refs/heads/main", "oldRepositoryId": 70, "repositoryPath": "silver/nodedc_aegis.git", "wiki": False, }, { "head": "refs/heads/main", "oldRepositoryId": 2119, "repositoryPath": "silver/nodedc_device_core.git", "wiki": False, }, ], "objectFormat": "sha1", "stores": 49, "symbolicHeads": 49, "targetsPresentAmongLiveRefs": 47, } ): die("Gitea salvage confirmed reference policy mismatch") exact_decisions = reference_policy.get("exactDecisions") if not isinstance(exact_decisions, list) or len(exact_decisions) != 105: die("Gitea salvage exact reference decision count mismatch") previous_identity = None reference_identities = set() store_identities = set() reference_counts = { "main_heads": 0, "main_tags": 0, "pull": 0, "remote": 0, "wiki_heads": 0, } for record in exact_decisions: require_exact_json_keys( record, { "disposition", "name", "oid", "oldRepositoryId", "repositoryPath", "wiki", }, "Gitea salvage exact reference decision", ) old_repo_id = record.get("oldRepositoryId") repository_path = record.get("repositoryPath") name = record.get("name") oid = record.get("oid") wiki = record.get("wiki") if not isinstance(repository_path, str): die("Gitea salvage exact reference path is invalid") try: validate_posix_path(repository_path) except DeployError: die("Gitea salvage exact reference path is invalid") identity = ( old_repo_id, int(wiki) if isinstance(wiki, bool) else -1, repository_path, name, oid, ) if ( not isinstance(old_repo_id, int) or old_repo_id <= 0 or not isinstance(wiki, bool) or len(PurePosixPath(repository_path).parts) != 2 or not isinstance(name, str) or not gitea_salvage_refname_is_safe(name) or not isinstance(oid, str) or not re.fullmatch(r"[a-f0-9]{40}", oid) or previous_identity is not None and identity <= previous_identity or identity in reference_identities ): die("Gitea salvage exact reference decision is invalid") previous_identity = identity reference_identities.add(identity) store_identities.add((old_repo_id, repository_path, wiki)) if wiki and name.startswith("refs/heads/"): reference_counts["wiki_heads"] += 1 expected_disposition = "LIVE_RESTORE" elif not wiki and name.startswith("refs/heads/"): reference_counts["main_heads"] += 1 expected_disposition = "LIVE_RESTORE" elif not wiki and name.startswith("refs/tags/"): reference_counts["main_tags"] += 1 expected_disposition = "LIVE_RESTORE" elif not wiki and name.startswith("refs/pull/"): reference_counts["pull"] += 1 expected_disposition = "SEALED_ARCHIVE_ONLY" elif not wiki and name.startswith("refs/remotes/"): reference_counts["remote"] += 1 expected_disposition = "SEALED_ARCHIVE_ONLY" else: die("Gitea salvage exact reference namespace is rejected") if record.get("disposition") != expected_disposition: die("Gitea salvage exact reference disposition mismatch") if ( reference_counts != { "main_heads": 85, "main_tags": 4, "pull": 5, "remote": 7, "wiki_heads": 4, } or len(store_identities) != 47 or { old_repo_id for old_repo_id, _path, wiki in store_identities if wiki } != {1, 16, 38, 51} ): die("Gitea salvage exact reference partition mismatch") state_policy = disposition.get("repositoryStatePolicy") require_exact_json_keys( state_policy, { "attachments", "directRelations", "legacySecretsCredentialsSessionsKeysIntegrations", "lfs", "metadataArchive", "numericHints", "relationSemantics", "schemaOnlyDependencyGroups", "sourceNonzeroCategories", "textMetadata", "topics", "units", }, "Gitea salvage confirmed repository-state policy", ) expected_nonzero = [ "access_grants", "attachments", "collaborators", "issues", "labels", "lfs_objects", "pull_requests_base", "pull_requests_head", "releases", "repo_units", "repository_hint:lfs_size", "repository_hint:num_issues", "repository_hint:num_pulls", "repository_hint:num_watches", "repository_metadata:description", "repository_metadata:topics", "watches", ] if state_policy.get("sourceNonzeroCategories") != expected_nonzero: die("Gitea salvage source nonzero-category partition mismatch") direct_counts = { "access_grants": 32, "action_runners": 0, "action_runs": 0, "action_schedules": 0, "action_secrets": 0, "action_variables": 0, "attachments": 230, "collaborators": 32, "deploy_keys": 0, "issues": 867, "labels": 7, "lfs_locks": 0, "lfs_objects": 573941, "milestones": 0, "mirrors": 0, "packages": 0, "protected_branches": 0, "pull_requests_base": 5, "pull_requests_head": 5, "push_mirrors": 0, "releases": 4, "stars": 0, "topics": 0, "watches": 45, "webhooks": 0, } nonzero_dispositions = { "access_grants": "SANITIZED_ARCHIVE_THEN_RECOMPUTE", "attachments": "PHYSICAL_VERIFY_THEN_SANITIZED_ARCHIVE", "collaborators": ( "SANITIZED_ARCHIVE_THEN_RECREATE_AFTER_KEPT_USER_MAPPING" ), "issues": "SANITIZED_ARCHIVE_ONLY", "labels": "SANITIZED_ARCHIVE_ONLY", "lfs_objects": "REACHABILITY_AND_PHYSICAL_VERIFY_THEN_RESTORE", "pull_requests_base": ( "SANITIZED_ARCHIVE_ONLY_DIRECTIONAL_PROJECTION" ), "pull_requests_head": ( "SANITIZED_ARCHIVE_ONLY_DIRECTIONAL_PROJECTION" ), "releases": "SANITIZED_ARCHIVE_ONLY", "watches": "DROP_AND_REGENERATE", } expected_direct_relations = [ { "disposition": ( "ASSERT_ZERO_AND_DROP" if source_count == 0 else nonzero_dispositions[label] ), "label": label, "sourceCount": source_count, } for label, source_count in sorted(direct_counts.items()) ] if state_policy.get("directRelations") != expected_direct_relations: die("Gitea salvage direct-relation disposition partition mismatch") expected_schema_groups = [ { "disposition": ( "SANITIZED_ARCHIVE_ONLY_AFTER_SCHEMA_AND_JOIN_VERIFIER" ), "group": "issue-pr-release-project", "tables": [ "comment", "issue_assignees", "issue_content_history", "issue_dependency", "issue_label", "issue_user", "issue_watch", "notification", "project", "project_board", "project_issue", "pull_auto_merge", "reaction", "review", "review_state", "stopwatch", "tracked_time", ], }, { "disposition": ( "ASSERT_ZERO_AND_DROP_AFTER_SCHEMA_AND_PHYSICAL_CLOSURE_VERIFIER" ), "group": "packages", "tables": [ "package_blob", "package_file", "package_property", "package_version", ], }, { "disposition": ( "ASSERT_ZERO_AND_DROP_AFTER_SCHEMA_AND_PHYSICAL_CLOSURE_VERIFIER" ), "group": "actions", "tables": [ "action_artifact", "action_run_index", "action_run_job", "action_task", ], }, ] if state_policy.get("schemaOnlyDependencyGroups") != expected_schema_groups: die("Gitea salvage schema-only dependency disposition mismatch") covered_tables = [ table for group in expected_schema_groups for table in group["tables"] ] if len(covered_tables) != 25 or len(set(covered_tables)) != 25: die("Gitea salvage schema-only dependency partition is not total") expected_units = { "actionsGloballyDisabled": True, "legacyConfigImported": False, "legacyRowsImported": False, "packagesGloballyDisabled": True, "rows": [ { "disposition": disposition_name, "name": name, "sourceCount": source_count, "targetCount": target_count, "type": unit_type, } for ( unit_type, name, source_count, target_count, disposition_name, ) in ( (1, "Code", 45, 45, "CREATE_CLEAN_DEFAULT"), (2, "Issues", 45, 45, "CREATE_CLEAN_DEFAULT"), (3, "Pull Requests", 45, 45, "CREATE_CLEAN_DEFAULT"), (4, "Releases", 45, 45, "CREATE_CLEAN_DEFAULT"), (5, "Wiki", 45, 45, "CREATE_CLEAN_DEFAULT"), ( 6, "External Wiki", 0, 0, "ASSERT_SOURCE_ZERO_AND_FORBID_TARGET", ), ( 7, "External Tracker", 0, 0, "ASSERT_SOURCE_ZERO_AND_FORBID_TARGET", ), (8, "Projects", 45, 45, "CREATE_CLEAN_DEFAULT"), (9, "Packages", 45, 0, "DROP_AND_DISABLE_TARGET"), ( 10, "Actions", 0, 0, "ASSERT_SOURCE_ZERO_AND_DISABLE_TARGET", ), ) ], "targetRowTotal": 270, "targetUniquePerRepositoryAndType": True, } if state_policy.get("units") != expected_units: die("Gitea salvage repo-unit disposition mismatch") expected_numeric_hints = [ { "disposition": "DROP_AND_RECOMPUTE", "name": name, "sourceNonzero": name in {"lfs_size", "num_issues", "num_pulls", "num_watches"}, } for name in ( "lfs_size", "num_action_runs", "num_issues", "num_milestones", "num_projects", "num_pulls", "num_stars", "num_watches", ) ] if ( state_policy.get("attachments") != { "declaredRows": 230, "disposition": "PHYSICAL_VERIFY_THEN_SANITIZED_ARCHIVE", "legacyRowsImported": False, "physicalPresenceClaimed": False, } or state_policy.get("lfs") != { "associationRows": 573941, "disposition": ( "DISABLED_UNTIL_REACHABLE_POINTER_PHYSICAL_SHA_VERIFIER" ), "legacyRowsImported": False, "physicalPresenceClaimed": False, } or state_policy.get("metadataArchive") != { "categories": [ "issues", "pull-requests", "comments", "releases", "labels", "projects", "repository-description", ], "legacyRowsImported": False, "mode": "SANITIZED_IMMUTABLE_ARCHIVE_ONLY", } or state_policy.get("numericHints") != expected_numeric_hints or state_policy.get("relationSemantics") != { "attachmentLinkCountsMayOverlap": True, "pullBaseAndHeadAreDirectionalProjections": True, } or state_policy.get("textMetadata") != [ { "disposition": "ASSERT_ZERO_AND_DROP", "name": "avatar", "sourceRepositories": 0, }, { "disposition": "SANITIZED_ARCHIVE_ONLY", "name": "description", "sourceRepositories": 1, }, { "disposition": "ASSERT_ZERO_AND_DROP", "name": "original_url", "sourceRepositories": 0, }, { "disposition": ( "VERIFY_SEMANTIC_EMPTY_THEN_REBUILD_FROM_RELATIONS" ), "name": "topics", "sourceRepositories": 45, }, { "disposition": "ASSERT_ZERO_AND_DROP", "name": "website", "sourceRepositories": 0, }, ] or state_policy.get("topics") != { "acceptedLegacyEncoding": ( "canonical-json-null-or-canonical-json-array" ), "canonicalLowercaseSortedUnique": True, "expectedMaterialRepositories": 0, "expectedRelationalRows": 0, "expectedSerializedArrays": 0, "expectedSerializedNulls": 45, "expectedTopics": 0, "legacyRowsImported": False, "maxUtf8BytesPerTopic": 35, "relationalAuthority": "repo_topic-join-topic", "targetCache": "rebuild-from-relations", "topicNamePattern": r"^[a-z0-9][-.a-z0-9]*$", } or state_policy.get( "legacySecretsCredentialsSessionsKeysIntegrations" ) != "ZERO_NEVER_IMPORT" ): die("Gitea salvage repository-state disposition mismatch") if disposition.get("activation") != { "allowedOperation": "canonical-plan-only", "applyFrozen": True, "freezeBoundary": "before-candidate-root-creation", }: die("Gitea salvage disposition activation policy mismatch") if disposition.get("remainingBlockers") != list( GITEA_SALVAGE_DISPOSITION_REMAINING_BLOCKERS ): die("Gitea salvage disposition blocker contract mismatch") return disposition def validate_gitea_salvage_closure_disposition(payload_dir): path = payload_dir / GITEA_SALVAGE_CLOSURE_DISPOSITION_REL try: path_stat = path.lstat() raw = path.read_bytes() except (FileNotFoundError, OSError): die("Gitea salvage closure disposition is missing or unreadable") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or path_stat.st_size < 2 or path_stat.st_size > 64 * 1024 or sha256_file(path) != GITEA_SALVAGE_CLOSURE_DISPOSITION_SHA256 ): die("Gitea salvage closure disposition identity is unsafe") disposition = read_strict_json( path, "Gitea salvage closure disposition", max_bytes=64 * 1024, ) try: canonical = ( json.dumps( disposition, ensure_ascii=True, sort_keys=True, separators=(",", ":"), allow_nan=False, ).encode("utf-8") + b"\n" ) except (TypeError, ValueError): die("Gitea salvage closure disposition is not canonical JSON") if raw != canonical: die("Gitea salvage closure disposition byte framing is not canonical") require_exact_json_keys( disposition, { "activation", "authority", "closureReport", "incidentId", "policies", "predecessor", "remainingBlockers", "schemaVersion", "scope", "sourceEvidence", }, "Gitea salvage closure disposition", ) if ( disposition.get("schemaVersion") != "nodedc.gitea.incident-closure-disposition.v1" or disposition.get("incidentId") != "gitea-20260814" or disposition.get("activation") != { "allowedOperation": "canonical-plan-only", "applyFrozen": True, "freezeBoundary": "before-candidate-root-creation", } or disposition.get("authority") != { "policyScope": ( "access-collaboration-issue-pr-attachment-release-label-" "project-unit-package-action-closure" ), "source": "owner-instruction-in-current-incident-thread", "state": "confirmed-policy-evidence-review-pending", } or disposition.get("scope") != { "deletedRepositories": 2013, "deletedUsers": 962, "keptRepositories": 45, "keptUsers": 10, } or disposition.get("predecessor") != { "artifactSha256": GITEA_SALVAGE_CLOSURE_PREDECESSOR_ARTIFACT_SHA256, "dispositionFile": GITEA_SALVAGE_DISPOSITION_REL, "dispositionSha256": ( GITEA_SALVAGE_CLOSURE_PREDECESSOR_DISPOSITION_SHA256 ), } or disposition.get("sourceEvidence") != { "databaseSha256": GITEA_SALVAGE_SNAPSHOT_DATABASE_SHA256, "identityDecisionManifestSha256": ( GITEA_SALVAGE_DECISION_MANIFEST_SHA256 ), "referenceManifestSha256": ( GITEA_SALVAGE_DISPOSITION_REFERENCE_MANIFEST_SHA256 ), "semanticTopicsSha256": GITEA_SALVAGE_DISPOSITION_TOPICS_SHA256, "snapshotUuid": GITEA_SALVAGE_SNAPSHOT_UUID, "unsupportedRepositoryReportSha256": ( GITEA_SALVAGE_EXPECTED_UNSUPPORTED_REPORT_SHA256 ), "unsupportedSchemaCatalogSha256": ( GITEA_SALVAGE_DISPOSITION_UNSUPPORTED_SCHEMA_SHA256 ), } or disposition.get("closureReport") != { "expectedBytes": None, "expectedSha256": None, "reviewState": "canonical-plan-output-unreviewed", "schema": "nodedc.gitea.salvage-closure-inventory/v1", } or disposition.get("remainingBlockers") != list(GITEA_SALVAGE_CLOSURE_REMAINING_BLOCKERS) ): die("Gitea salvage closure disposition contract mismatch") policies = disposition.get("policies") require_exact_json_keys( policies, { "accessCollaboration", "attachments", "issuesPullRequestsMetadata", "packagesActions", "releasesLabelsProjects", "repositoryState", "units", }, "Gitea salvage closure policies", ) if ( policies["accessCollaboration"].get("accessCache") != "DROP_RESET_RECOMPUTE" or policies["accessCollaboration"].get("collaboration") != "RECREATE_ONLY_KEPT_ACTORS_AFTER_REVIEWED_OLD_TO_NEW_ID_MAP" or policies["accessCollaboration"].get("legacyRowsImported") is not False or policies["attachments"].get("databaseManifest") != "ID_UUID_RELATIONS_DECLARED_SIZE_ONLY" or policies["attachments"].get("legacyRowsImported") is not False or policies["issuesPullRequestsMetadata"].get("archive") != "SANITIZED_IMMUTABLE_ARCHIVE_ONLY" or policies["issuesPullRequestsMetadata"].get("legacyRowsImported") is not False or policies["issuesPullRequestsMetadata"].get("subrelationClosure") != { "commentHistoryMerger": ( "SCHEMA_BOUND_EXACT_RELATION_COUNTS_AND_CLASSES" ), "externalAuthors": ( "PRESENCE_AND_NAME_BYTE_LENGTHS_ONLY_NO_LOCAL_USER_MAPPING" ), "legacyRowsImported": False, "teamRelations": ( "EXACT_ROW_TEAM_ORG_IDS_SEALED_HOLD_AND_BLOCK_IF_PRESENT" ), } or policies["packagesActions"].get("actionsTarget") != "DISABLED" or policies["packagesActions"].get("packageTarget") != "DISABLED" or policies["packagesActions"].get("legacyPayloadSecretTokenLogImported") is not False or policies["repositoryState"].get( "legacyHooksWebhooksKeysTokensSessionsCredentialsSecrets" ) != "IMPORT_ZERO" or policies["units"].get("enabledCleanTypes") != [1, 2, 3, 4, 5, 8] or policies["units"].get("disabledTypes") != [6, 7, 9, 10] or policies["units"].get("legacyConfigImported") is not False or policies["units"].get("legacyRowsImported") is not False ): die("Gitea salvage closure policy mismatch") return disposition def validate_gitea_salvage_decision_bundle(payload_dir): manifest_path = payload_dir / GITEA_SALVAGE_DECISION_MANIFEST_REL users_path = payload_dir / GITEA_SALVAGE_USERS_REL repositories_path = payload_dir / GITEA_SALVAGE_REPOSITORIES_REL for path, expected, label in ( ( manifest_path, GITEA_SALVAGE_DECISION_MANIFEST_SHA256, "Gitea salvage decision manifest", ), (users_path, GITEA_SALVAGE_USERS_SHA256, "Gitea salvage user decisions"), ( repositories_path, GITEA_SALVAGE_REPOSITORIES_SHA256, "Gitea salvage repository decisions", ), ): try: path_stat = path.lstat() except FileNotFoundError: die(f"{label} is missing") if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die(f"{label} is unsafe") if sha256_file(path) != expected: die(f"{label} digest mismatch") manifest = read_strict_json( manifest_path, "Gitea salvage decision manifest", max_bytes=32 * 1024, ) require_exact_json_keys( manifest, { "confirmation_scope", "confirmed_at", "confirmed_by", "counts", "execution_policy", "repositories_file", "repositories_file_sha256", "schema", "source", "users_file", "users_file_sha256", }, "Gitea salvage decision manifest", ) if ( manifest.get("schema") != "nodedc.gitea.incident-decision/v2" or manifest.get("confirmed_by") != "dctouch" or manifest.get("repositories_file") != "repositories.decisions.csv" or manifest.get("users_file") != "users.decisions.csv" or manifest.get("repositories_file_sha256") != GITEA_SALVAGE_REPOSITORIES_SHA256 or manifest.get("users_file_sha256") != GITEA_SALVAGE_USERS_SHA256 or manifest.get("counts") != { "repositories_delete": 2013, "repositories_keep": 45, "repositories_total": 2058, "users_delete": 962, "users_keep_active": 2, "users_keep_locked": 8, "users_total": 972, } or manifest.get("execution_policy") != { "explicit_rows_only": True, "two_factor_authentication": ( "not changed; owner will configure it manually" ), "wildcard_or_owner_only_execution_forbidden": True, } ): die("Gitea salvage decision manifest contract mismatch") source = manifest.get("source") if ( not isinstance(source, dict) or source.get("snapshot_uuid") != GITEA_SALVAGE_SNAPSHOT_UUID or source.get("database_sha256") != GITEA_SALVAGE_SNAPSHOT_DATABASE_SHA256 ): die("Gitea salvage decision source mismatch") user_header = ( "decision", "user_id", "owner", "lower_owner", "desired_active", "desired_restricted", "desired_admin", "current_active", "current_restricted", "current_admin", "repository_count", "record_sha256", ) users = read_gitea_salvage_csv( users_path, user_header, 972, "Gitea salvage user decisions", ) user_ids = set() user_counts = {name: 0 for name in GITEA_SALVAGE_USER_COUNTS} previous_user_id = 0 for row in users: try: user_id = int(row["user_id"]) current_values = tuple( int(row[key]) for key in ( "current_active", "current_restricted", "current_admin", "repository_count", ) ) except ValueError: die("Gitea salvage user decision has a non-integer field") if ( user_id <= previous_user_id or user_id in user_ids or not re.fullmatch(r"[A-Za-z0-9_.-]{1,40}", row["owner"]) or row["lower_owner"] != row["owner"].casefold() or row["record_sha256"] != row["record_sha256"].lower() or not re.fullmatch(r"[a-f0-9]{64}", row["record_sha256"]) or any(value < 0 for value in current_values) ): die("Gitea salvage user decision identity mismatch") previous_user_id = user_id user_ids.add(user_id) expected_name = GITEA_SALVAGE_ACTIVE_USERS.get( user_id, GITEA_SALVAGE_LOCKED_USERS.get(user_id), ) if user_id in GITEA_SALVAGE_ACTIVE_USERS: expected = ( "KEEP_ACTIVE", "1", "0", "1" if user_id == 1 else "0", ) elif user_id in GITEA_SALVAGE_LOCKED_USERS: expected = ("KEEP_LOCKED", "0", "1", "0") else: expected = ("DELETE", "", "", "") if ( (row["decision"], row["desired_active"], row["desired_restricted"], row["desired_admin"]) != expected or expected_name is not None and row["owner"] != expected_name ): die("Gitea salvage user disposition mismatch") user_counts[row["decision"]] += 1 if user_counts != GITEA_SALVAGE_USER_COUNTS: die("Gitea salvage user disposition count mismatch") repository_header = ( "decision", "repo_id", "owner_id", "owner", "lower_owner", "slug", "lower_slug", "repo_relative_path", "wiki_relative_path", "record_sha256", ) repositories = read_gitea_salvage_csv( repositories_path, repository_header, 2058, "Gitea salvage repository decisions", ) repository_ids = set() repository_paths = set() repository_counts = {name: 0 for name in GITEA_SALVAGE_REPOSITORY_COUNTS} previous_repository_id = 0 kept_by_owner = {} for row in repositories: try: repo_id = int(row["repo_id"]) owner_id = int(row["owner_id"]) except ValueError: die("Gitea salvage repository decision has a non-integer field") expected_repo_path = f"{row['lower_owner']}/{row['lower_slug']}.git" expected_wiki_path = f"{row['lower_owner']}/{row['lower_slug']}.wiki.git" for relative in (row["repo_relative_path"], row["wiki_relative_path"]): validate_posix_path(relative) if len(PurePosixPath(relative).parts) != 2: die("Gitea salvage repository path depth mismatch") if ( repo_id <= previous_repository_id or repo_id in repository_ids or not re.fullmatch(r"[A-Za-z0-9_.-]{1,100}", row["slug"]) or row["lower_owner"] != row["owner"].casefold() or row["lower_slug"] != row["slug"].casefold() or row["repo_relative_path"] != expected_repo_path or row["wiki_relative_path"] != expected_wiki_path or row["repo_relative_path"] in repository_paths or row["wiki_relative_path"] in repository_paths or not re.fullmatch(r"[a-f0-9]{64}", row["record_sha256"]) ): die("Gitea salvage repository identity mismatch") previous_repository_id = repo_id repository_ids.add(repo_id) repository_paths.update((row["repo_relative_path"], row["wiki_relative_path"])) expected_owner = GITEA_SALVAGE_ACTIVE_USERS.get(owner_id) expected_decision = "KEEP" if expected_owner == row["owner"] else "DELETE" if row["decision"] != expected_decision: die("Gitea salvage repository disposition mismatch") repository_counts[row["decision"]] += 1 if row["decision"] == "KEEP": kept_by_owner[row["owner"]] = kept_by_owner.get(row["owner"], 0) + 1 if ( repository_counts != GITEA_SALVAGE_REPOSITORY_COUNTS or kept_by_owner != {"dctouch": 32, "SILVER": 13} ): die("Gitea salvage repository disposition count mismatch") return { "manifest": manifest, "users": users, "repositories": repositories, "kept_users": [row for row in users if row["decision"] != "DELETE"], "kept_repositories": [ row for row in repositories if row["decision"] == "KEEP" ], } def validate_gitea_incident_salvage_payload(payload_dir, entries): if tuple(entries) != GITEA_SALVAGE_ENTRIES: die("Gitea incident-salvage artifact entry set mismatch") descriptor_path = payload_dir / GITEA_SALVAGE_DESCRIPTOR_REL if sha256_file(descriptor_path) != GITEA_SALVAGE_DESCRIPTOR_SHA256: die("Gitea incident-salvage descriptor digest mismatch") descriptor = read_strict_json( descriptor_path, "Gitea incident-salvage descriptor", max_bytes=64 * 1024, ) if ( descriptor.get("schemaVersion") != "nodedc.gitea.incident-salvage.v3" or descriptor.get("action") != "clean-state-salvage" or descriptor.get("component") != "gitea" or descriptor.get("incidentId") != "gitea-20260814" or (descriptor.get("compose") or {}).get("sha256") != GITEA_SALVAGE_COMPOSE_SHA256 or (descriptor.get("runtime") or {}).get("image") != GITEA_SALVAGE_IMAGE or (descriptor.get("runtime") or {}).get("imageId") != GITEA_SALVAGE_IMAGE_ID or (descriptor.get("runtime") or {}).get("repoDigest") != GITEA_SALVAGE_REPO_DIGEST or (descriptor.get("disposition") or {}).get("schema") != "nodedc.gitea.incident-disposition.v1" or (descriptor.get("disposition") or {}).get("file") != GITEA_SALVAGE_DISPOSITION_REL or (descriptor.get("disposition") or {}).get("sha256") != GITEA_SALVAGE_DISPOSITION_SHA256 or (descriptor.get("closureDisposition") or {}).get("schema") != "nodedc.gitea.incident-closure-disposition.v1" or (descriptor.get("closureDisposition") or {}).get("file") != GITEA_SALVAGE_CLOSURE_DISPOSITION_REL or (descriptor.get("closureDisposition") or {}).get("sha256") != GITEA_SALVAGE_CLOSURE_DISPOSITION_SHA256 or (descriptor.get("closureDisposition") or {}).get( "predecessorArtifactSha256" ) != GITEA_SALVAGE_CLOSURE_PREDECESSOR_ARTIFACT_SHA256 or (descriptor.get("identity") or {}).get("twoFactorAuthentication") != "not-configured-by-transition" or (descriptor.get("identity") or {}).get("preserveNumericUserIds") is not False or (descriptor.get("identity") or {}).get("preserveNumericRepositoryIds") is not False ): die("Gitea incident-salvage descriptor contract mismatch") compose = payload_dir / GITEA_COMPOSE_REL try: compose_stat = compose.lstat() compose_text = compose.read_text(encoding="utf-8") except (FileNotFoundError, OSError, UnicodeDecodeError): die("Gitea incident-salvage Compose file is missing or invalid") if ( stat.S_ISLNK(compose_stat.st_mode) or not stat.S_ISREG(compose_stat.st_mode) or sha256_file(compose) != GITEA_SALVAGE_COMPOSE_SHA256 or compose_text.count(GITEA_SALVAGE_IMAGE) != 1 ): die("Gitea incident-salvage Compose identity mismatch") for required in ( "platform: linux/amd64", "pull_policy: never", "network_mode: none", 'user: "1000:1000"', "- /usr/local/bin/gitea", "- /etc/gitea/app.ini", 'restart: "no"', "read_only: true", "- ALL", "no-new-privileges:true", "source: /volume1/docker/nodedc-gitea/data", "target: /data", "source: /volume1/docker/nodedc-gitea/config", "target: /etc/gitea", "source: /volume1/docker/nodedc-gitea/socket", "target: /run/gitea", "create_host_path: false", ): if required not in compose_text: die(f"Gitea incident-salvage Compose boundary missing: {required}") for forbidden in ( "ports:", "networks:", "/var/run/docker.sock", "/volume1/docker/gitea", "privileged: true", "4022", "TWO_FACTOR_AUTH", "LFS_JWT_SECRET", "restart: unless-stopped", ): if forbidden in compose_text: die(f"Gitea incident-salvage Compose boundary violation: {forbidden}") disposition = validate_gitea_salvage_incident_disposition(payload_dir) closure_disposition = validate_gitea_salvage_closure_disposition(payload_dir) decisions = validate_gitea_salvage_decision_bundle(payload_dir) return { "closure_disposition": closure_disposition, "descriptor": descriptor, "disposition": disposition, **decisions, } def validate_n8n_package_tarball(package_path, release): package_stat = package_path.lstat() if stat.S_ISLNK(package_stat.st_mode) or not stat.S_ISREG(package_stat.st_mode): die("private extension package must be a regular file") if package_stat.st_size < 1024 or package_stat.st_size > 32 * 1024 * 1024: die("private extension package has invalid size") if sha256_file(package_path) != release["package"]["sha256"]: die("private extension package sha256 mismatch") if package_stat.st_size != release["package"]["bytes"]: die("private extension package byte count mismatch") names = set() package_bytes = 0 package_json_bytes = None packed_node_sources = {} with tarfile.open(package_path, "r:gz") as archive: for member_count, member in enumerate(archive, 1): if member_count > 512: die("private extension package has too many members") validate_posix_path(member.name) if member.name in names: die(f"duplicate private extension package member: {member.name}") names.add(member.name) if member.name != "package" and not member.name.startswith("package/"): die(f"private extension package member escaped package root: {member.name}") if not (member.isfile() or member.isdir()): die(f"unsupported private extension package member: {member.name}") if member.name == "package": if not member.isdir(): die("private extension package root must be a directory") else: package_rel = PurePosixPath(member.name).relative_to("package") if any(part.startswith(".") or part.startswith("._") for part in package_rel.parts): die(f"private extension package hidden member rejected: {member.name}") if not ( package_rel.as_posix() in ("README.md", "package.json") or package_rel.as_posix().startswith("dist/") ): die(f"private extension package member is not allowlisted: {member.name}") expected_mode = 0o644 if member.isfile() else 0o755 if stat.S_IMODE(member.mode) != expected_mode: die(f"unsafe private extension package mode: {member.name}") if member.isfile(): package_bytes += member.size if member.size > 4 * 1024 * 1024 or package_bytes > 64 * 1024 * 1024: die("private extension package payload is too large") if member.name == "package/package.json": source = archive.extractfile(member) if source is None: die("private extension package.json cannot be read") package_json_bytes = source.read(64 * 1024 + 1) if member.name.startswith("package/dist/nodes/") and member.name.endswith(".node.js"): source = archive.extractfile(member) if source is None: die(f"private extension packed node cannot be read: {member.name}") packed_node_sources[member.name[len("package/"):]] = source.read(4 * 1024 * 1024 + 1) if package_json_bytes is None or len(package_json_bytes) > 64 * 1024: die("private extension package.json missing or too large") try: package_json = json.loads(package_json_bytes.decode("utf-8")) except (UnicodeDecodeError, json.JSONDecodeError): die("private extension package.json is invalid") if package_json.get("name") != "n8n-nodes-ndc": die("private extension package name mismatch") if package_json.get("version") != release["package"]["version"]: die("private extension package version mismatch") if package_json.get("private") is not True: die("private extension package must remain private") if package_json.get("dependencies") not in (None, {}): die("private extension runtime dependencies are forbidden") scripts = package_json.get("scripts") or {} if not isinstance(scripts, dict): die("private extension package scripts must be an object") for lifecycle in ("preinstall", "install", "postinstall", "prepack", "prepare", "postpack"): if lifecycle in scripts: die(f"private extension lifecycle script is forbidden: {lifecycle}") n8n = package_json.get("n8n") if not isinstance(n8n, dict): die("private extension n8n registration missing") registered = [] expected_registrations = { "nodes": N8N_PRIVATE_EXTENSION_NODES, "credentials": N8N_PRIVATE_EXTENSION_CREDENTIALS, } for field, expected in expected_registrations.items(): values = n8n.get(field) if not isinstance(values, list) or not values: die(f"private extension n8n.{field} registration missing") if values != list(expected): die(f"private extension n8n.{field} registration mismatch") for value in values: if not isinstance(value, str) or not value.startswith("dist/") or value not in names and f"package/{value}" not in names: die(f"private extension n8n.{field} member missing: {value}") registered.append(value) if len(registered) != len(set(registered)): die("private extension n8n registration contains duplicates") packed_nodes = sorted( name[len("package/"):] for name in names if name.startswith("package/dist/nodes/") and name.endswith(".node.js") ) packed_credentials = sorted( name[len("package/"):] for name in names if name.startswith("package/dist/credentials/") and name.endswith(".credentials.js") ) if packed_nodes != sorted(N8N_PRIVATE_EXTENSION_NODES): die("private extension packed node set mismatch") if packed_credentials != sorted(N8N_PRIVATE_EXTENSION_CREDENTIALS): die("private extension packed credential set mismatch") for node_path in N8N_PRIVATE_EXTENSION_NODES: source = packed_node_sources.get(node_path) if source is None or len(source) > 4 * 1024 * 1024: die(f"private extension packed node source missing or too large: {node_path}") if b"usableAsTool" in source: die(f"private extension packed node would generate a tool variant: {node_path}") def validate_n8n_private_extension_release(payload_dir, entries): if len(entries) != 1: die("private extension artifact must contain exactly one immutable release entry") release_rel = entries[0] parts = PurePosixPath(release_rel).parts if ( len(parts) != 3 or parts[0] != "releases" or parts[1] != "n8n-nodes-ndc" or not N8N_PRIVATE_EXTENSION_RELEASE_RE.fullmatch(parts[2]) ): die("private extension files.txt entry must be a digest-bound release directory") release_dir = payload_dir / release_rel expected_files = {"package.tgz", "release.json", "rollback.json"} actual_files = {path.name for path in release_dir.iterdir() if path.is_file()} actual_dirs = [path.name for path in release_dir.iterdir() if path.is_dir()] if actual_dirs or actual_files != expected_files: die("private extension release must contain only package.tgz, release.json and rollback.json") release = read_strict_json(release_dir / "release.json", "private extension release manifest") require_exact_json_keys(release, ("schemaVersion", "releaseId", "package", "storage", "activation"), "private extension release manifest") require_exact_json_keys(release["package"], ("name", "version", "sha256", "bytes", "runtimeTypePrefix"), "private extension package manifest") require_exact_json_keys(release["storage"], ("relativePath", "immutable"), "private extension storage manifest") require_exact_json_keys( release["activation"], ( "owner", "status", "requiredCommunityPackagePath", "requiresAtomicReleaseSwitch", "requiresAllN8nProcessesRestart", "requiresMcpSchemaAcceptance", "rollbackBaselinePolicy", ), "private extension activation manifest", ) require_exact_json_keys( release["activation"]["rollbackBaselinePolicy"], ("allowed", "firstActivation", "requiresPreActivationVerification"), "private extension rollback baseline policy", ) release_id = parts[2] package = release["package"] activation = release["activation"] if release["schemaVersion"] != "nodedc.n8n-private-extension-release/v2": die("private extension release schema mismatch") if release["releaseId"] != release_id: die("private extension release id mismatch") if package["name"] != "n8n-nodes-ndc" or package["runtimeTypePrefix"] != "n8n-nodes-ndc.": die("private extension package identity mismatch") if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", str(package["version"])): die("private extension package version is invalid") if not re.fullmatch(r"[a-f0-9]{64}", str(package["sha256"])): die("private extension package sha256 is invalid") if not isinstance(package["bytes"], int) or isinstance(package["bytes"], bool): die("private extension package byte count is invalid") if release_id != f"{package['version']}-{package['sha256'][:16]}": die("private extension release id is not digest-bound") if release["storage"] != {"relativePath": release_rel, "immutable": True}: die("private extension storage manifest mismatch") rollback_baseline_policy = { "allowed": [ "previous_verified_immutable_release", "verified_inactive", ], "firstActivation": "verified_inactive", "requiresPreActivationVerification": True, } if activation != { "owner": "engine", "status": "blocked_pending_engine_owned_mount", "requiredCommunityPackagePath": "/home/node/.n8n/nodes/node_modules/n8n-nodes-ndc", "requiresAtomicReleaseSwitch": True, "requiresAllN8nProcessesRestart": True, "requiresMcpSchemaAcceptance": True, "rollbackBaselinePolicy": rollback_baseline_policy, }: die("private extension activation boundary mismatch") rollback = read_strict_json(release_dir / "rollback.json", "private extension rollback manifest") require_exact_json_keys( rollback, ("schemaVersion", "releaseId", "packageSha256", "mode", "baselinePolicy", "steps", "forbidden"), "private extension rollback manifest", ) require_exact_json_keys( rollback["baselinePolicy"], ("allowed", "firstActivation", "requiresPreActivationVerification"), "private extension rollback manifest baseline policy", ) if rollback != { "schemaVersion": "nodedc.n8n-private-extension-rollback/v2", "releaseId": release_id, "packageSha256": package["sha256"], "mode": "engine-owned-atomic-release-switch", "baselinePolicy": rollback_baseline_policy, "steps": [ "select_verified_previous_release_or_preverified_inactive_baseline", "switch_engine_owned_mount_atomically", "restart_all_n8n_processes", "verify_mcp_schema_state_matches_selected_baseline", ], "forbidden": [ "delete_active_release", "mutate_engine_core", "live_npm_install", ], }: die("private extension rollback manifest mismatch") validate_n8n_package_tarball(release_dir / "package.tgz", release) 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( descriptor, ( "schemaVersion", "action", "releaseId", "packageVersion", "packageSha256", "n8nVersion", "baseImage", "baseImageArchitecture", "baseImageIdentityPolicy", "sealedReleaseRelativePath", "composeOverride", "runtimePackagePath", "topologyServices", "expectedCurrent", "expectedNodeTypes", "expectedCredentialTypes", "rollbackBaseline", ), label, ) action = descriptor.get("action") if descriptor.get("schemaVersion") != "nodedc.engine-n8n-private-extension-transition/v1": die("Engine n8n transition schema mismatch") if action not in ("activate", "rollback-inactive"): die("Engine n8n transition action mismatch") release_id = descriptor.get("releaseId") package_version = descriptor.get("packageVersion") package_sha256 = descriptor.get("packageSha256") if not isinstance(release_id, str) or not N8N_PRIVATE_EXTENSION_RELEASE_RE.fullmatch(release_id): die("Engine n8n transition release id is invalid") if not isinstance(package_version, str) or not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", package_version): die("Engine n8n transition package version is invalid") if not isinstance(package_sha256, str) or not re.fullmatch(r"[a-f0-9]{64}", package_sha256): die("Engine n8n transition package sha256 is invalid") if release_id != f"{package_version}-{package_sha256[:16]}": die("Engine n8n transition release identity mismatch") expected_sealed_path = f"n8n-private-extensions/releases/n8n-nodes-ndc/{release_id}/package" exact_common = { "n8nVersion": ENGINE_N8N_VERSION, "baseImage": ENGINE_N8N_BASE_IMAGE, "baseImageArchitecture": ENGINE_N8N_BASE_ARCHITECTURE, "baseImageIdentityPolicy": "running-container-and-local-tag-must-match", "sealedReleaseRelativePath": expected_sealed_path, "composeOverride": ENGINE_N8N_COMPOSE_OVERRIDE_REL, "runtimePackagePath": ENGINE_N8N_RUNTIME_PACKAGE_PATH, "topologyServices": ["n8n"], } for key, expected in exact_common.items(): if descriptor.get(key) != expected: die(f"Engine n8n transition {key} mismatch") if action == "activate": 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_for_release(release_id) ): die("Engine n8n activation credential type set mismatch") 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 def expected_engine_n8n_compose_override(descriptor): health_script = ( "const http=require('http');const req=http.get('http://127.0.0.1:5678/healthz/readiness'," "r=>{r.resume();process.exit(r.statusCode===200?0:1)});req.on('error',()=>process.exit(1));" "req.setTimeout(4000,()=>{req.destroy();process.exit(1)});" ) health_test = json.dumps(["CMD", "node", "-e", health_script], separators=(",", ":")) mount_source = f"/volume2/nodedc-demo/{descriptor['sealedReleaseRelativePath']}" return "\n".join(( "services:", " n8n:", f" image: {ENGINE_N8N_BASE_IMAGE}", " platform: linux/amd64", " pull_policy: never", " environment:", " N8N_USER_FOLDER: /home/node", ' N8N_COMMUNITY_PACKAGES_ENABLED: "true"', ' N8N_COMMUNITY_PACKAGES_PREVENT_LOADING: "false"', ' N8N_REINSTALL_MISSING_PACKAGES: "false"', " volumes:", f" - {mount_source}:{ENGINE_N8N_RUNTIME_PACKAGE_PATH}:ro", " healthcheck:", f" test: {health_test}", " interval: 10s", " timeout: 5s", " retries: 30", " start_period: 30s", " labels:", f" nodedc.n8n-private-extension.release: {descriptor['releaseId']}", f" nodedc.n8n-private-extension.package-sha256: {descriptor['packageSha256']}", "", )) def validate_engine_n8n_catalog_payload(payload_dir, descriptor): action = descriptor["action"] nodes_path = payload_dir / ENGINE_N8N_NODES_CATALOG_REL credentials_path = payload_dir / ENGINE_N8N_CREDENTIALS_CATALOG_REL meta_path = payload_dir / ENGINE_N8N_SCHEMA_META_REL nodes = read_strict_json( nodes_path, "Engine n8n node catalog", max_bytes=32 * 1024 * 1024, ) credentials = read_strict_json( credentials_path, "Engine n8n credential catalog", max_bytes=4 * 1024 * 1024, ) meta = read_strict_json(meta_path, "Engine n8n schema metadata") if not isinstance(nodes, list) or not isinstance(credentials, list): die("Engine n8n schema catalogs must be arrays") private_nodes = [ item for item in nodes if isinstance(item, dict) and str(item.get("name") or "").startswith("n8n-nodes-ndc.") ] private_credentials = [ item for item in credentials 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_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: die("Engine n8n pinned credential catalog exact set mismatch") 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) != 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") if any(not str(item.get("displayName") or "").startswith("NDC ") for item in private_credentials): die("Engine n8n private credential visible name mismatch") private_catalog_text = json.dumps( {"nodes": private_nodes, "credentials": private_credentials}, ensure_ascii=False, ).lower() if "gelios" in private_catalog_text or "robot2b" in private_catalog_text: die("provider business identity is forbidden in the Engine n8n extension catalog") else: if len(nodes) != 434 or len(credentials) != 385: die("Engine n8n inactive baseline catalog count mismatch") if not isinstance(meta, dict): die("Engine n8n schema metadata must be an object") if meta.get("n8nVersion") != ENGINE_N8N_VERSION: die("Engine n8n schema metadata version mismatch") if meta.get("nodeCount") != len(nodes) or meta.get("credentialCount") != len(credentials): die("Engine n8n schema metadata count mismatch") if action == "activate": if meta.get("source") != f"n8n-core+n8n-nodes-ndc@{descriptor['packageVersion']}": die("Engine n8n schema metadata source mismatch") if sha256_file(payload_dir / ENGINE_N8N_ICON_REL) != ENGINE_N8N_ICON_SHA256: 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, release_hashes["nodes"], "node"), (credentials, release_hashes["credentials"], "credential"), (meta, release_hashes["meta"], "metadata"), ) else: expected_catalog_hashes = ( (nodes, ENGINE_N8N_INACTIVE_NODES_CATALOG_JSON_SHA256, "node"), (credentials, ENGINE_N8N_INACTIVE_CREDENTIALS_CATALOG_JSON_SHA256, "credential"), (meta, ENGINE_N8N_INACTIVE_META_JSON_SHA256, "metadata"), ) for catalog, expected_sha256, label in expected_catalog_hashes: if sha256_json_value(catalog) != expected_sha256: die(f"Engine n8n {action} {label} catalog sha256 mismatch") def validate_engine_n8n_transition(payload_dir, entries): descriptor = read_engine_n8n_transition_descriptor(payload_dir / ENGINE_N8N_TRANSITION_DESCRIPTOR_REL) activation_entries = [ ENGINE_N8N_TRANSITION_DESCRIPTOR_REL, ENGINE_N8N_COMPOSE_OVERRIDE_REL, ENGINE_N8N_NODES_CATALOG_REL, ENGINE_N8N_CREDENTIALS_CATALOG_REL, ENGINE_N8N_SCHEMA_META_REL, ENGINE_N8N_ICON_REL, ENGINE_N8N_DARK_ICON_REL, ] rollback_entries = [ ENGINE_N8N_TRANSITION_DESCRIPTOR_REL, ENGINE_N8N_NODES_CATALOG_REL, ENGINE_N8N_CREDENTIALS_CATALOG_REL, ENGINE_N8N_SCHEMA_META_REL, ] expected_entries = activation_entries if descriptor["action"] == "activate" else rollback_entries if entries != expected_entries: die("Engine n8n transition files.txt exact set/order mismatch") if descriptor["action"] == "activate": override = payload_dir / ENGINE_N8N_COMPOSE_OVERRIDE_REL try: override_text = override.read_text(encoding="utf-8") except (OSError, UnicodeDecodeError): die("Engine n8n Compose override cannot be read") if override_text != expected_engine_n8n_compose_override(descriptor): die("Engine n8n Compose override mismatch") if "N8N_CUSTOM_EXTENSIONS" in override_text or "CUSTOM." in override_text: die("Engine n8n custom-extension loader is forbidden") validate_engine_n8n_catalog_payload(payload_dir, descriptor) return descriptor def is_engine_node_intelligence_transition(component, entries): if component != "engine" or entries is None: return False return tuple(entries) in ( ENGINE_NODE_INTELLIGENCE_ARTIFACT_ENTRIES, ENGINE_NODE_INTELLIGENCE_ROLLBACK_ENTRIES, ) def expected_engine_node_intelligence_compose_override(): return "\n".join(( "services:", f" {ENGINE_NODE_INTELLIGENCE_SERVICE}:", f" image: {ENGINE_NODE_INTELLIGENCE_IMAGE}", " pull_policy: never", " restart: unless-stopped", f' user: "{ENGINE_NODE_INTELLIGENCE_RUNTIME_UID}:{ENGINE_NODE_INTELLIGENCE_RUNTIME_GID}"', " read_only: true", " environment:", " HOME: /tmp", " NODE_ENV: production", " MCP_MODE: http", ' PORT: "3000"', f" AUTH_TOKEN_FILE: {ENGINE_NODE_INTELLIGENCE_SECRET_CONTAINER_PATH}", " NODE_DB_PATH: /app/data/nodes.db", ' REBUILD_ON_START: "false"', ' N8N_MCP_TELEMETRY_DISABLED: "true"', " LOG_LEVEL: warn", " volumes:", " - type: bind", f" source: {ENGINE_NODE_INTELLIGENCE_SECRET_FILE}", f" target: {ENGINE_NODE_INTELLIGENCE_SECRET_CONTAINER_PATH}", " read_only: true", " expose:", ' - "3000"', " tmpfs:", " - /tmp:mode=1777", " - /app/logs:mode=0755", " cap_drop:", " - ALL", " security_opt:", " - no-new-privileges:true", " healthcheck:", " test:", " - CMD-SHELL", " - curl -fsS http://127.0.0.1:3000/health >/dev/null", " interval: 15s", " timeout: 5s", " retries: 10", " start_period: 30s", "", " nodedc-backend:", " depends_on:", f" {ENGINE_NODE_INTELLIGENCE_SERVICE}:", " condition: service_healthy", " environment:", f" ENGINE_NODE_INTELLIGENCE_MCP_URL: http://{ENGINE_NODE_INTELLIGENCE_SERVICE}:3000/mcp", f" ENGINE_NODE_INTELLIGENCE_AUTH_TOKEN_FILE: {ENGINE_NODE_INTELLIGENCE_SECRET_CONTAINER_PATH}", f" ENGINE_NODE_INTELLIGENCE_ASSET_ID: nodedc-node-intelligence@{ENGINE_NODE_INTELLIGENCE_RELEASE_ID.replace('-', '+', 1)}", " volumes:", " - type: bind", f" source: {ENGINE_NODE_INTELLIGENCE_SECRET_FILE}", f" target: {ENGINE_NODE_INTELLIGENCE_SECRET_CONTAINER_PATH}", " read_only: true", "", )) def read_engine_node_intelligence_descriptor(path, label="Engine node-intelligence descriptor"): descriptor = read_strict_json(path, label) require_exact_json_keys( descriptor, ( "schemaVersion", "action", "releaseId", "expectedCurrent", "upstream", "image", "source", "predecessor", ), label, ) if descriptor.get("schemaVersion") != "nodedc.engine-node-intelligence-transition/v1": die("Engine node-intelligence transition schema mismatch") action = descriptor.get("action") if action not in ("activate", "rollback-inactive"): die("Engine node-intelligence transition action mismatch") if descriptor.get("releaseId") != ENGINE_NODE_INTELLIGENCE_RELEASE_ID: die("Engine node-intelligence release mismatch") require_exact_json_keys( descriptor.get("upstream"), ("package", "version", "commit"), f"{label} upstream", ) if descriptor["upstream"] != { "package": "n8n-mcp", "version": "2.33.2", "commit": ENGINE_NODE_INTELLIGENCE_UPSTREAM_COMMIT, }: die("Engine node-intelligence upstream pin mismatch") require_exact_json_keys( descriptor.get("predecessor"), ("gatewaySha256", "composeSha256", "backendRuntime"), f"{label} predecessor", ) predecessor = descriptor["predecessor"] if predecessor.get("composeSha256") != ENGINE_NODE_INTELLIGENCE_PREDECESSOR_COMPOSE_SHA256: die("Engine node-intelligence predecessor Compose mismatch") if predecessor.get("backendRuntime") != "verified-derived-retry": die("Engine node-intelligence backend predecessor mismatch") if action == "activate": if descriptor.get("expectedCurrent") != "inactive": die("Engine node-intelligence activation expected-current mismatch") if predecessor.get("gatewaySha256") != ENGINE_NODE_INTELLIGENCE_PREDECESSOR_GATEWAY_SHA256: die("Engine node-intelligence activation gateway predecessor mismatch") require_exact_json_keys( descriptor.get("image"), ( "tag", "archiveRelativePath", "archiveSha256", "configSha256", "architecture", "os", ), f"{label} image", ) image = descriptor["image"] if ( image.get("tag") != ENGINE_NODE_INTELLIGENCE_IMAGE or image.get("archiveRelativePath") != ENGINE_NODE_INTELLIGENCE_IMAGE_ARCHIVE_REL or not re.fullmatch(r"[a-f0-9]{64}", str(image.get("archiveSha256") or "")) or not re.fullmatch(r"[a-f0-9]{64}", str(image.get("configSha256") or "")) or image.get("architecture") != "amd64" or image.get("os") != "linux" ): die("Engine node-intelligence image descriptor mismatch") require_exact_json_keys( descriptor.get("source"), ( "gatewaySha256", "catalogSha256", "upstreamClientSha256", "upstreamProjectionSha256", "composeOverrideSha256", "readmeSha256", ), f"{label} source", ) for value in descriptor["source"].values(): if not re.fullmatch(r"[a-f0-9]{64}", str(value or "")): die("Engine node-intelligence source digest is invalid") else: if descriptor.get("expectedCurrent") != ENGINE_NODE_INTELLIGENCE_RELEASE_ID: die("Engine node-intelligence rollback expected-current mismatch") require_exact_json_keys( descriptor.get("image"), ("tag", "configSha256", "architecture", "os"), f"{label} image", ) image = descriptor["image"] if ( image.get("tag") != ENGINE_NODE_INTELLIGENCE_IMAGE or not re.fullmatch(r"[a-f0-9]{64}", str(image.get("configSha256") or "")) or image.get("architecture") != "amd64" or image.get("os") != "linux" ): die("Engine node-intelligence rollback image descriptor mismatch") require_exact_json_keys( descriptor.get("source"), ("gatewaySha256", "readmeSha256"), f"{label} source", ) if descriptor["source"].get("gatewaySha256") != ENGINE_NODE_INTELLIGENCE_PREDECESSOR_GATEWAY_SHA256: die("Engine node-intelligence rollback gateway target mismatch") if any( not re.fullmatch(r"[a-f0-9]{64}", str(value or "")) for value in descriptor["source"].values() ): die("Engine node-intelligence rollback source digest is invalid") if not re.fullmatch(r"[a-f0-9]{64}", str(predecessor.get("gatewaySha256") or "")): die("Engine node-intelligence rollback predecessor gateway is invalid") return descriptor def validate_engine_node_intelligence_image_archive(path, descriptor): image = descriptor["image"] try: archive_stat = path.lstat() except FileNotFoundError: die("Engine node-intelligence image archive is missing") if stat.S_ISLNK(archive_stat.st_mode) or not stat.S_ISREG(archive_stat.st_mode): die("Engine node-intelligence image archive is unsafe") if archive_stat.st_size < 1024 or archive_stat.st_size > MAX_ARTIFACT_BYTES: die("Engine node-intelligence image archive size is invalid") if sha256_file(path) != image["archiveSha256"]: die("Engine node-intelligence image archive sha256 mismatch") names = set() manifest_raw = None config_raw = None total = 0 try: with tarfile.open(path, "r:") as archive: members = archive.getmembers() if not members or len(members) > MAX_MEMBER_COUNT: die("Engine node-intelligence image archive member count is invalid") for member in members: validate_posix_path(member.name.rstrip("/")) if member.name in names: die(f"Engine node-intelligence image archive duplicate member: {member.name}") names.add(member.name) if not (member.isfile() or member.isdir()): die(f"Engine node-intelligence image archive special member: {member.name}") if member.isdir(): continue if member.size > MAX_FILE_BYTES: die(f"Engine node-intelligence image archive member too large: {member.name}") total += member.size if total > MAX_ARTIFACT_BYTES: die("Engine node-intelligence image archive expanded size is too large") source = archive.extractfile(member) if source is None: die(f"Engine node-intelligence image archive member unreadable: {member.name}") data = source.read(MAX_FILE_BYTES + 1) if len(data) != member.size: die(f"Engine node-intelligence image archive member size mismatch: {member.name}") if member.name.startswith("blobs/sha256/"): digest = member.name.rsplit("/", 1)[-1] if not re.fullmatch(r"[a-f0-9]{64}", digest): die("Engine node-intelligence image blob name is invalid") if hashlib.sha256(data).hexdigest() != digest: die("Engine node-intelligence image blob digest mismatch") elif member.name not in ("index.json", "manifest.json", "oci-layout"): die(f"Engine node-intelligence image archive unexpected member: {member.name}") if member.name == "manifest.json": manifest_raw = data if manifest_raw is None: die("Engine node-intelligence image manifest is missing") try: manifest = json.loads(manifest_raw.decode("utf-8")) except (UnicodeDecodeError, json.JSONDecodeError): die("Engine node-intelligence image manifest is invalid") if not isinstance(manifest, list) or len(manifest) != 1: die("Engine node-intelligence image manifest exact set mismatch") record = manifest[0] if not isinstance(record, dict) or set(record) != {"Config", "RepoTags", "Layers"}: die("Engine node-intelligence image manifest record mismatch") if record.get("RepoTags") != [ENGINE_NODE_INTELLIGENCE_IMAGE]: die("Engine node-intelligence image tag mismatch") config_name = record.get("Config") expected_config_name = f"blobs/sha256/{image['configSha256']}" if config_name != expected_config_name: die("Engine node-intelligence image config digest mismatch") try: config_member = archive.getmember(config_name) config_file = archive.extractfile(config_member) config_raw = config_file.read(MAX_FILE_BYTES + 1) if config_file else None except KeyError: config_raw = None except (tarfile.TarError, OSError): die("Engine node-intelligence image archive cannot be read") if config_raw is None: die("Engine node-intelligence image config is missing") try: config = json.loads(config_raw.decode("utf-8")) except (UnicodeDecodeError, json.JSONDecodeError): die("Engine node-intelligence image config is invalid") if config.get("architecture") != "amd64" or config.get("os") != "linux": die("Engine node-intelligence image platform mismatch") runtime_config = config.get("config") or {} labels = runtime_config.get("Labels") or {} if labels.get("org.opencontainers.image.revision") != ENGINE_NODE_INTELLIGENCE_UPSTREAM_COMMIT: die("Engine node-intelligence image revision label mismatch") if runtime_config.get("Entrypoint") != ["/usr/local/bin/docker-entrypoint.sh"]: die("Engine node-intelligence image entrypoint mismatch") if runtime_config.get("Cmd") != ["node", "dist/mcp/index.js"]: die("Engine node-intelligence image command mismatch") env = runtime_config.get("Env") or [] if any( str(value).startswith(("AUTH_TOKEN=", "N8N_API_URL=", "N8N_API_KEY=")) for value in env ): die("Engine node-intelligence image contains runtime authority") return image["configSha256"] def validate_engine_node_intelligence_transition(payload_dir, entries): service_root = payload_dir / ENGINE_NODE_INTELLIGENCE_SERVICE_ROOT_REL descriptor = read_engine_node_intelligence_descriptor( service_root / "activation.json" ) expected_entries = ( ENGINE_NODE_INTELLIGENCE_ARTIFACT_ENTRIES if descriptor["action"] == "activate" else ENGINE_NODE_INTELLIGENCE_ROLLBACK_ENTRIES ) if tuple(entries) != expected_entries: die("Engine node-intelligence files.txt exact set/order mismatch") gateway = payload_dir / ENGINE_NODE_INTELLIGENCE_GATEWAY_REL if sha256_file(gateway) != descriptor["source"]["gatewaySha256"]: die("Engine node-intelligence gateway sha256 mismatch") readme = service_root / "README.md" if sha256_file(readme) != descriptor["source"]["readmeSha256"]: die("Engine node-intelligence README sha256 mismatch") if descriptor["action"] == "activate": expected_service_files = { "README.md", "activation.json", "docker-compose.immutable-runtime.yml", "image/engine-node-intelligence.tar", } actual_service_files = { child.relative_to(service_root).as_posix() for child in service_root.rglob("*") if child.is_file() } if actual_service_files != expected_service_files: die("Engine node-intelligence service payload file set mismatch") source_root = payload_dir / ENGINE_NODE_INTELLIGENCE_SOURCE_REL expected_source_files = { "catalog.js", "upstreamMcpClient.js", "upstreamProjection.js", } actual_source_files = { child.relative_to(source_root).as_posix() for child in source_root.rglob("*") if child.is_file() } if actual_source_files != expected_source_files: die("Engine node-intelligence source file set mismatch") source_digests = { "catalogSha256": sha256_file(source_root / "catalog.js"), "upstreamClientSha256": sha256_file(source_root / "upstreamMcpClient.js"), "upstreamProjectionSha256": sha256_file(source_root / "upstreamProjection.js"), } if any(descriptor["source"][key] != value for key, value in source_digests.items()): die("Engine node-intelligence source digest mismatch") override = service_root / "docker-compose.immutable-runtime.yml" try: override_text = override.read_text(encoding="utf-8") except (OSError, UnicodeDecodeError): die("Engine node-intelligence Compose override is unreadable") if override_text != expected_engine_node_intelligence_compose_override(): die("Engine node-intelligence Compose override mismatch") if sha256_file(override) != descriptor["source"]["composeOverrideSha256"]: die("Engine node-intelligence Compose override sha256 mismatch") gateway_text = gateway.read_text(encoding="utf-8") for required in ( "engine_get_node_intelligence_status", "engine_get_node_guidance", "engine_validate_node_configuration", "engine_validate_l2_deep", ): 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'", "const ENGINE_AGENT_MCP_VERSION = '0.6.0'", "const ENGINE_AGENT_MCP_VERSION = '0.7.0'", "const ENGINE_AGENT_MCP_VERSION = '0.8.0'", "const ENGINE_AGENT_MCP_VERSION = '0.9.0'", "const ENGINE_AGENT_MCP_VERSION = '0.10.0'", "const ENGINE_AGENT_MCP_VERSION = '0.11.0'", "const ENGINE_AGENT_MCP_VERSION = '0.12.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, ) else: expected_service_files = {"README.md", "activation.json"} actual_service_files = { child.relative_to(service_root).as_posix() for child in service_root.rglob("*") if child.is_file() } if actual_service_files != expected_service_files: die("Engine node-intelligence rollback payload file set mismatch") if sha256_file(gateway) != ENGINE_NODE_INTELLIGENCE_PREDECESSOR_GATEWAY_SHA256: die("Engine node-intelligence rollback gateway baseline mismatch") 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 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¶ms=%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 is_engine_mcp_autonomy_provider_v5_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_MCP_AUTONOMY_PROVIDER_V5_ARTIFACT_ENTRIES ) def validate_engine_mcp_autonomy_provider_v5_payload(payload_dir, entries): if tuple(entries) != ENGINE_MCP_AUTONOMY_PROVIDER_V5_ARTIFACT_ENTRIES: die("Engine MCP autonomy/provider v5 files.txt exact set/order mismatch") for rel, expected_sha256 in ENGINE_MCP_AUTONOMY_PROVIDER_V5_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 autonomy/provider v5 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.6": die("Engine MCP autonomy/provider v5 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'", "'nodedc-engine-codex-agent-0.1.6.tgz'", ) ): die("Engine MCP autonomy/provider v5 gateway contract is incomplete") if any( marker not in installer for marker in ( "MCP tool availability establishes capability authority", "the user's requested objective establishes intent scope", "machine safety barrier, not a permission ceremony", "Never repeat an identical read, validation, apply or run", "Three identical failures with no new evidence or state change are a critical stop", "const ONTOLOGY_SERVER_NAME = 'nodedc_ontology'", "version: '0.1.6'", ) ) or "only with explicit user confirmation" in installer: die("Engine MCP capability-scoped autonomy policy is incomplete") archive_path = payload_dir / "nodedc-source/server/assets/nodedc-engine-codex-agent-0.1.6.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 autonomy/provider v5 installer archive member is unsafe") if member.isfile(): source = archive.extractfile(member) if source is None: die("Engine MCP autonomy/provider v5 installer archive member is unreadable") observed_members[member.name] = source.read(MAX_FILE_BYTES + 1) except (OSError, tarfile.TarError): die("Engine MCP autonomy/provider v5 installer archive is invalid") if observed_members != expected_members: die("Engine MCP autonomy/provider v5 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 ( catalog.get("schemaVersion") != "nodedc.engine.provider-security-catalog/v1" or not isinstance(packages, list) or [item.get("id") for item in packages if isinstance(item, dict)] != ["gelios.provider.v4", "gelios.provider.v5"] ): die("Engine Gelios provider v4/v5 catalog identity mismatch") expected_products = { "gelios.provider.v4": "fleet.positions.current.v3", "gelios.provider.v5": "fleet.positions.current.v4", } expected_requests = [ ("gelios.monitoring_config.current.read", "https://api.geliospro.com/api/v1/users/me/monitoring-config"), ("gelios.units.current.read", "https://api.geliospro.com/api/v1/units?incltrip=true"), ] for provider in packages: provider_id = provider.get("id") credential = provider.get("providerCredential") or {} capabilities = provider.get("capabilities") observed_requests = [ (item.get("id"), (item.get("request") or {}).get("url")) for item in capabilities ] if isinstance(capabilities, list) else None if ( provider_id not in expected_products or credential.get("credentialType") != "ndcProviderRotatingAccessApi" or credential.get("authModeId") != "gelios.rest-rotating-bearer.v3" or observed_requests != expected_requests or any(item.get("dataProductIds") != [expected_products[provider_id]] for item in capabilities) ): die("Engine Gelios provider v4/v5 security projection 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", "authenticateEngineAgentOntologyToken", ) ): die("Engine MCP autonomy/provider v5 Ontology token store migration is incomplete") descriptor = read_engine_node_intelligence_descriptor( payload_dir / ENGINE_MCP_CONTROL_PLANE_DESCRIPTOR_REL, "Engine MCP autonomy/provider v5 node-intelligence descriptor", ) if ( descriptor.get("action") != "activate" or descriptor["source"].get("gatewaySha256") != ENGINE_MCP_AUTONOMY_PROVIDER_V5_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 autonomy/provider v5 descriptor target mismatch") return descriptor def preflight_engine_mcp_autonomy_provider_v5_predecessor(payload_dir): candidate = validate_engine_mcp_autonomy_provider_v5_payload( payload_dir, ENGINE_MCP_AUTONOMY_PROVIDER_V5_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 autonomy/provider v5 requires active node intelligence") expected_candidate = json.loads(json.dumps(installed)) expected_candidate["source"]["gatewaySha256"] = ( ENGINE_MCP_AUTONOMY_PROVIDER_V5_TARGET_SHA256[ENGINE_NODE_INTELLIGENCE_GATEWAY_REL] ) if candidate != expected_candidate: die("Engine MCP autonomy/provider v5 crosses node-intelligence source boundary") root = component_root("engine") for rel, expected_sha256 in ENGINE_MCP_AUTONOMY_PROVIDER_V5_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 autonomy/provider v5 predecessor drift detected: {rel}") for rel in ENGINE_MCP_AUTONOMY_PROVIDER_V5_NEW_PATHS: path = root / rel if path.exists() or path.is_symlink(): die(f"Engine MCP autonomy/provider v5 new path already exists: {rel}") backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die("Engine MCP autonomy/provider v5 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 engine_mcp_autonomy_provider_v5_installed_state(): root = component_root("engine") target = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ENGINE_MCP_AUTONOMY_PROVIDER_V5_TARGET_SHA256.items() ) descriptor = current_engine_node_intelligence_descriptor() if descriptor is not None: validate_installed_engine_node_intelligence_source(descriptor) if target and descriptor and descriptor.get("source", {}).get("gatewaySha256") == ( ENGINE_MCP_AUTONOMY_PROVIDER_V5_TARGET_SHA256[ENGINE_NODE_INTELLIGENCE_GATEWAY_REL] ): return "target" predecessor = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ENGINE_MCP_AUTONOMY_PROVIDER_V5_PREDECESSOR_SHA256.items() ) new_paths_absent = all( not (root / rel).exists() and not (root / rel).is_symlink() for rel in ENGINE_MCP_AUTONOMY_PROVIDER_V5_NEW_PATHS ) if predecessor and new_paths_absent and descriptor and descriptor.get("source", {}).get("gatewaySha256") == ( ENGINE_MCP_AUTONOMY_PROVIDER_V5_PREDECESSOR_SHA256[ENGINE_NODE_INTELLIGENCE_GATEWAY_REL] ): return "predecessor" die("Engine MCP autonomy/provider v5 installed state is neither target nor rollback predecessor") def accept_engine_mcp_autonomy_provider_v5_runtime(): root = component_root("engine") descriptor = validate_engine_mcp_autonomy_provider_v5_payload( root, ENGINE_MCP_AUTONOMY_PROVIDER_V5_ARTIFACT_ENTRIES, ) installed = current_engine_node_intelligence_descriptor() if installed != descriptor: die("Engine MCP autonomy/provider v5 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 autonomy/provider v5 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 fs=await import('node:fs/promises'); const catalog=JSON.parse(await fs.readFile('/app/server/assets/provider-packages/v1/catalog.json','utf8')); const pkg=JSON.parse(await fs.readFile('/app/server/assets/engine-agent-npm/package.json','utf8')); const ids=catalog.packages?.map((item)=>item.id).join(','); const target=catalog.packages?.find((item)=>item.id==='gelios.provider.v5'); if(gateway.ENGINE_AGENT_MCP_VERSION!=='0.6.0')process.exit(2); if(typeof store.authenticateEngineAgentOntologyToken!=='function')process.exit(3); if(pkg.version!=='0.1.6'||ids!=='gelios.provider.v4,gelios.provider.v5')process.exit(4); if(target?.capabilities?.some((item)=>item.dataProductIds?.[0]!=='fleet.positions.current.v4'))process.exit(5); process.stdout.write('engine-mcp-autonomy-provider-v5:0.6.0:0.1.6:'+ids); """.strip(), ), "Engine MCP autonomy/provider v5 capability", container_id=engine_backend_container_id(), ) expected = "engine-mcp-autonomy-provider-v5:0.6.0:0.1.6:gelios.provider.v4,gelios.provider.v5" if live != expected: die("Engine MCP autonomy/provider v5 live capability acceptance mismatch") return { "gateway_sha256": descriptor["source"]["gatewaySha256"], "backend_mode": backend["mode"], "capability": live, } def is_engine_provider_security_catalog_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_PROVIDER_SECURITY_CATALOG_ARTIFACT_ENTRIES ) def validate_engine_provider_security_catalog_payload(payload_dir, entries): if tuple(entries) != ENGINE_PROVIDER_SECURITY_CATALOG_ARTIFACT_ENTRIES: die("Engine provider security catalog files.txt exact set/order mismatch") catalog_path = payload_dir / ENGINE_PROVIDER_SECURITY_CATALOG_REL if ( catalog_path.is_symlink() or not catalog_path.is_file() or sha256_file(catalog_path) != ENGINE_PROVIDER_SECURITY_CATALOG_TARGET_SHA256 ): die("Engine provider security catalog target sha256 mismatch") catalog = read_strict_json(catalog_path, "Engine provider security catalog") packages = catalog.get("packages") if isinstance(catalog, dict) else None package_ids = [item.get("id") for item in packages if isinstance(item, dict)] \ if isinstance(packages, list) else None provider = next( ( item for item in packages if isinstance(item, dict) and item.get("id") == "gelios.provider.v8" ), None, ) if isinstance(packages, list) else None previous_provider = next( ( item for item in packages if isinstance(item, dict) and item.get("id") == "gelios.provider.v7" ), None, ) if isinstance(packages, list) else None 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 credential = provider.get("providerCredential") if isinstance(provider, dict) else None publisher = provider.get("publisher") if isinstance(provider, dict) else None if ( catalog.get("schemaVersion") != "nodedc.engine.provider-security-catalog/v1" or package_ids != [ "gelios.provider.v1", "gelios.provider.v4", "gelios.provider.v5", "moscow-department-of-transport.pmd-slow-zones.v1", "gelios.provider.v7", "gelios.provider.v8", ] or not isinstance(provider, dict) or provider.get("version") != "8.0.0" or provider.get("providerId") != "gelios" or credential != { "authModeId": "gelios.rest-rotating-bearer.v3", "credentialType": "ndcProviderRotatingAccessApi", } or not isinstance(capability, dict) or capability.get("id") != "gelios.units.current.read" or capability.get("classification") != "read" or capability.get("status") != "implemented" or request != { "method": "GET", "url": ( "https://api.geliospro.com/api/v1/units?incltrip=true&inclcntrs=true" "&inclsnsrs=true&incllsv=true" ), } or capability.get("dataProductIds") != ["fleet.units.profile.current.v1"] or publisher != { "nodeType": "n8n-nodes-ndc.ndcDataProductPublish", "credentialType": "ndcDataProductWriterApi", } or previous_provider != { "id": "gelios.provider.v7", "version": "7.0.0", "providerId": "gelios", "providerCredential": { "authModeId": "gelios.rest-rotating-bearer.v3", "credentialType": "ndcProviderRotatingAccessApi", }, "capabilities": [ { "id": "gelios.units.current.read", "classification": "read", "status": "implemented", "request": { "method": "GET", "url": ( "https://api.geliospro.com/api/v1/units?incltrip=true" "&inclcntrs=true&inclsnsrs=true&incllsv=true" ), }, "dataProductIds": ["fleet.positions.current.v5"], }, ], "publisher": { "nodeType": "n8n-nodes-ndc.ndcDataProductPublish", "credentialType": "ndcDataProductWriterApi", }, } ): die("Engine provider security catalog v7/v8 projection mismatch") return catalog def preflight_engine_provider_security_catalog_predecessor(): catalog_path = component_root("engine") / ENGINE_PROVIDER_SECURITY_CATALOG_REL try: path_stat = catalog_path.lstat() except FileNotFoundError: die("Engine provider security catalog predecessor is missing") if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die("Engine provider security catalog predecessor is unsafe") actual_sha256 = sha256_file(catalog_path) if actual_sha256 != ENGINE_PROVIDER_SECURITY_CATALOG_PREDECESSOR_SHA256: die( "Engine provider security catalog predecessor drift detected: " f"expected={ENGINE_PROVIDER_SECURITY_CATALOG_PREDECESSOR_SHA256} " f"actual={actual_sha256}" ) backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die("Engine provider security catalog requires the active immutable backend") return {"catalog_sha256": actual_sha256, "backend_mode": backend["mode"]} def accept_engine_provider_security_catalog_runtime(): root = component_root("engine") validate_engine_provider_security_catalog_payload( root, ENGINE_PROVIDER_SECURITY_CATALOG_ARTIFACT_ENTRIES, ) live = run_engine_backend_probe( ( "node", "--input-type=module", "-e", """ const module=await import('file:///app/server/dataProductPublishGrant/providerCatalog.js'); const catalog=await module.loadProviderSecurityCatalog(); const validation=module.validateProviderSecurityCatalog(catalog); const provider=catalog.packages?.find((item)=>item.id==='gelios.provider.v8'); const capability=provider?.capabilities?.[0]; const slot='ndcProviderRotatingAccessApi'; const credential={id:'native-provider',name:'provider',nodeDcCredentialId:'provider-ref'}; const policy={kind:'httpRequest',allowedHosts:['api.geliospro.com'],requireHttps:true,disableRedirects:true}; const queryParameters={parameters:[{name:'incltrip',value:'true'},{name:'inclcntrs',value:'true'},{name:'inclsnsrs',value:'true'},{name:'incllsv',value:'true'}]}; const source={id:'units',data:{n8n:{id:'units',name:'units',type:'n8n-nodes-base.httpRequest',parameters:{method:'GET',url:'https://api.geliospro.com/api/v1/units',sendQuery:true,specifyQuery:'keypair',queryParameters},credentials:{[slot]:credential}},nodedcAgentCredentialPolicies:{[slot]:policy}}}; const profilePublish={id:'profile-publish',data:{n8n:{id:'profile-publish',name:'profile-publish',type:'n8n-nodes-ndc.ndcDataProductPublish',parameters:{dataProductId:'fleet.units.profile.current.v1'},credentials:{}}}}; const positionsPublish={id:'positions-publish',data:{n8n:{id:'positions-publish',name:'positions-publish',type:'n8n-nodes-ndc.ndcDataProductPublish',parameters:{dataProductId:'fleet.positions.current.v5'},credentials:{}}}}; const profileGraph={nodes:[source,profilePublish],edges:[{source:'units',target:'profile-publish'}]}; const positionsGraph={nodes:[source,positionsPublish],edges:[{source:'units',target:'positions-publish'}]}; const profileResolved=module.resolveProviderConnectionFromGraph({graph:profileGraph,targetNodeId:'profile-publish',catalog}); const positionsResolved=module.resolveProviderConnectionFromGraph({graph:positionsGraph,targetNodeId:'positions-publish',catalog}); const drift=structuredClone(profileGraph);drift.nodes[0].data.n8n.parameters.queryParameters.parameters.pop(); const rejected=module.resolveProviderConnectionFromGraph({graph:drift,targetNodeId:'profile-publish',catalog}); if(!validation.ok||provider?.providerCredential?.credentialType!==slot||capability?.request?.url!=='https://api.geliospro.com/api/v1/units?incltrip=true&inclcntrs=true&inclsnsrs=true&incllsv=true'||capability?.dataProductIds?.[0]!=='fleet.units.profile.current.v1'||!profileResolved.ok||profileResolved.descriptor?.packageId!=='gelios.provider.v8'||profileResolved.descriptor?.providerCredentialRef!=='provider-ref'||!positionsResolved.ok||positionsResolved.descriptor?.packageId!=='gelios.provider.v7'||positionsResolved.descriptor?.dataProductId!=='fleet.positions.current.v5'||rejected.blockers?.join(',')!=='publish_grant_provider_request_not_exact')process.exit(2); process.stdout.write('engine-provider-catalog:gelios.provider.v7:fleet.positions.current.v5|gelios.provider.v8:fleet.units.profile.current.v1'); """.strip(), ), "Engine provider security catalog v8 unit profile", container_id=engine_backend_container_id(), ) expected = ( "engine-provider-catalog:gelios.provider.v7:fleet.positions.current.v5|" "gelios.provider.v8:fleet.units.profile.current.v1" ) if live != expected: die("Engine provider security catalog live acceptance mismatch") return {"catalog_sha256": ENGINE_PROVIDER_SECURITY_CATALOG_TARGET_SHA256, "live": live} def accept_engine_composite_provider_runtime(): catalog_path = component_root("engine") / ENGINE_PROVIDER_SECURITY_CATALOG_REL if ( catalog_path.is_symlink() or not catalog_path.is_file() or sha256_file(catalog_path) != ENGINE_PUBLISH_GRANT_COMPOSITE_PROVIDER_CATALOG_TARGET_SHA256 ): return None live = run_engine_backend_probe( ( "node", "--input-type=module", "-e", """ const module=await import('file:///app/server/dataProductPublishGrant/providerCatalog.js'); const catalog=await module.loadProviderSecurityCatalog(); const validation=module.validateProviderSecurityCatalog(catalog); const credential={id:'native-provider',name:'provider',nodeDcCredentialId:'provider-ref'}; const policy={kind:'httpRequest',allowedHosts:['api.geliospro.com'],requireHttps:true,disableRedirects:true}; const slot='ndcProviderRotatingAccessApi'; const read=(id,url,parameters={})=>({id,data:{n8n:{id,name:id,type:'n8n-nodes-base.httpRequest',parameters:{method:'GET',url,...parameters},credentials:{[slot]:credential}},nodedcAgentCredentialPolicies:{[slot]:policy}}}); const query={sendQuery:true,specifyQuery:'keypair',queryParameters:{parameters:[{name:'incltrip',value:'true'}]}}; const graph={nodes:[read('monitoring','https://api.geliospro.com/api/v1/users/me/monitoring-config'),read('units','https://api.geliospro.com/api/v1/units',query),{id:'publish',data:{n8n:{id:'publish',name:'publish',type:'n8n-nodes-ndc.ndcDataProductPublish',parameters:{dataProductId:'fleet.positions.current.v3'},credentials:{}}}}],edges:[{source:'monitoring',target:'publish'},{source:'units',target:'publish'}]}; const resolved=module.resolveProviderConnectionFromGraph({graph,targetNodeId:'publish',catalog}); if(!validation.ok||catalog.packages?.[0]?.id!=='gelios.provider.v4'||catalog.packages?.[0]?.providerCredential?.credentialType!==slot||!resolved.ok||resolved.descriptor?.providerCredentialRef!=='provider-ref'||resolved.descriptor?.capabilityIds?.join(',')!=='gelios.monitoring_config.current.read,gelios.units.current.read'||resolved.descriptor?.providerRequestNodeIds?.join(',')!=='monitoring,units')process.exit(2); process.stdout.write('engine-composite-provider:gelios.provider.v4:ndcProviderRotatingAccessApi:fleet.positions.current.v3:monitoring,units'); """.strip(), ), "Engine composite provider authority", container_id=engine_backend_container_id(), ) expected = ( "engine-composite-provider:gelios.provider.v4:" "ndcProviderRotatingAccessApi:fleet.positions.current.v3:monitoring,units" ) if live != expected: die("Engine composite provider live acceptance mismatch") return { "catalog_sha256": ENGINE_PUBLISH_GRANT_COMPOSITE_PROVIDER_CATALOG_TARGET_SHA256, "live": live, } def accept_engine_provider_rotating_slot_runtime(): root = component_root("engine") validate_engine_provider_rotating_slot_slice( root, ENGINE_PROVIDER_ROTATING_SLOT_ARTIFACT_ENTRIES, ) live = run_engine_backend_probe( ( "node", "--input-type=module", "-e", """ const module=await import('file:///app/server/dataProductPublishGrant/providerCatalog.js'); const catalog=await module.loadProviderSecurityCatalog(); const validation=module.validateProviderSecurityCatalog(catalog); const credential={id:'native-provider',name:'provider',nodeDcCredentialId:'provider-ref'}; const policy={kind:'httpRequest',allowedHosts:['api.geliospro.com'],requireHttps:true,disableRedirects:true}; const slot='ndcProviderRotatingAccessApi'; const read=(id,url,parameters={})=>({id,data:{n8n:{id,name:id,type:'n8n-nodes-base.httpRequest',parameters:{method:'GET',url,...parameters},credentials:{[slot]:credential}},nodedcAgentCredentialPolicies:{[slot]:policy}}}); const query={sendQuery:true,specifyQuery:'keypair',queryParameters:{parameters:[{name:'incltrip',value:'true'}]}}; const graph={nodes:[read('monitoring','https://api.geliospro.com/api/v1/users/me/monitoring-config'),read('units','https://api.geliospro.com/api/v1/units',query),{id:'publish',data:{n8n:{id:'publish',name:'publish',type:'n8n-nodes-ndc.ndcDataProductPublish',parameters:{dataProductId:'fleet.positions.current.v3'},credentials:{}}}}],edges:[{source:'monitoring',target:'publish'},{source:'units',target:'publish'}]}; const resolved=module.resolveProviderConnectionFromGraph({graph,targetNodeId:'publish',catalog}); if(!validation.ok||catalog.packages?.[0]?.id!=='gelios.provider.v4'||catalog.packages?.[0]?.providerCredential?.credentialType!==slot||!resolved.ok||resolved.descriptor?.providerCredentialRef!=='provider-ref'||resolved.descriptor?.capabilityIds?.join(',')!=='gelios.monitoring_config.current.read,gelios.units.current.read'||resolved.descriptor?.providerRequestNodeIds?.join(',')!=='monitoring,units')process.exit(2); process.stdout.write('engine-provider-rotating-slot:gelios.provider.v4:ndcProviderRotatingAccessApi:fleet.positions.current.v3:monitoring,units'); """.strip(), ), "Engine provider rotating slot authority", container_id=engine_backend_container_id(), ) expected = ( "engine-provider-rotating-slot:gelios.provider.v4:" "ndcProviderRotatingAccessApi:fleet.positions.current.v3:monitoring,units" ) if live != expected: die("Engine provider rotating slot live acceptance mismatch") return { "target_sha256": dict(ENGINE_PROVIDER_ROTATING_SLOT_TARGET_SHA256), "live": live, } def accept_engine_provider_authority_diagnostics_runtime(): root = component_root("engine") validate_engine_provider_authority_diagnostics_slice( root, ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_ARTIFACT_ENTRIES, ) live = run_engine_backend_probe( ( "node", "--input-type=module", "-e", """ const module=await import('file:///app/server/nodeIntelligence/upstreamProjection.js'); const privateValidation={nodeId:'private-1',nodeName:'NDC Data Product Publish',valid:true,errors:[],warnings:[]}; const privateOnly=module.reconcilePrivateNodeValidation({valid:false,errorCount:1,warningCount:0,summary:{errorCount:1,warningCount:0},errors:[{node:'NDC Data Product Publish',message:'Unknown node type: \"ndcDataProductPublish\".'}],warnings:[]},['private-1'],[privateValidation]); const mixed=module.reconcilePrivateNodeValidation({valid:false,errorCount:2,warningCount:0,summary:{errorCount:2,warningCount:0},errors:[{node:'NDC Data Product Publish',message:'Unknown node type: \"ndcDataProductPublish\".'},{nodeId:'built-in-1',nodeName:'HTTP',message:'URL is required'}],warnings:[]},['private-1'],[privateValidation]); if(!privateOnly.valid||privateOnly.errors?.length!==0||privateOnly.summary?.errorCount!==0||privateOnly.summary?.warningCount!==1||mixed.valid||mixed.errors?.length!==1||mixed.errors?.[0]?.nodeId!=='built-in-1')process.exit(2); process.stdout.write('engine-private-node-reconciliation:exact-id-or-name:v1'); """.strip(), ), "Engine private node validation reconciliation", container_id=engine_backend_container_id(), ) expected = "engine-private-node-reconciliation:exact-id-or-name:v1" if live != expected: die("Engine provider authority diagnostics live acceptance mismatch") return { "target_sha256": dict(ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_TARGET_SHA256), "live": live, } def accept_engine_depttrans_zone_authority_v1_runtime(): root = component_root("engine") validate_engine_depttrans_zone_authority_v1_slice( root, ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_ARTIFACT_ENTRIES, ) live = run_engine_backend_probe( ( "node", "--input-type=module", "-e", """ const module=await import('file:///app/server/dataProductPublishGrant/providerCatalog.js'); const catalog=await module.loadProviderSecurityCatalog(); const parameters={method:'GET',url:'http://map-gateway:18103/internal/zone-sources/v1/profiles/moscow-pmd-slow-zones/current',authentication:'none',sendQuery:false,sendHeaders:true,specifyHeaders:'keypair',headerParameters:{parameters:[{name:'Accept',value:'application/json'}]},sendBody:false,options:{response:{response:{fullResponse:false,responseFormat:'json'}},allowUnauthorizedCerts:false,redirect:{redirect:{followRedirects:false}},timeout:15000}}; const graph={nodes:[{id:'source',data:{n8n:{id:'source',name:'source',type:'n8n-nodes-base.httpRequest',parameters}}},{id:'publish',data:{n8n:{id:'publish',name:'publish',type:'n8n-nodes-ndc.ndcDataProductPublish',parameters:{dataProductId:'map.zones.current.v2',publishMode:'replace'},credentials:{}}}}],edges:[{source:'source',target:'publish'}]}; const resolved=module.resolveProviderConnectionFromGraph({graph,targetNodeId:'publish',catalog}); const drift=structuredClone(graph);drift.nodes[0].data.n8n.parameters.options.redirect.redirect.followRedirects=true; const rejected=module.resolveProviderConnectionFromGraph({graph:drift,targetNodeId:'publish',catalog}); if(!module.validateProviderSecurityCatalog(catalog).ok||!resolved.ok||resolved.descriptor?.providerId!=='moscow-department-of-transport'||resolved.descriptor?.authorityBoundary!=='platform-service'||resolved.descriptor?.providerCredentialRef!==null||resolved.descriptor?.platformServiceIds?.join(',')!=='nodedc-map-gateway'||rejected.blockers?.join(',')!=='publish_grant_provider_request_not_exact')process.exit(2); process.stdout.write('engine-depttrans-zone-authority:platform-service:map.zones.current.v2:nodedc-map-gateway'); """.strip(), ), "Engine Depttrans zone authority v1", container_id=engine_backend_container_id(), ) expected = ( "engine-depttrans-zone-authority:platform-service:" "map.zones.current.v2:nodedc-map-gateway" ) if live != expected: die("Engine Depttrans zone authority v1 live acceptance mismatch") return { "target_sha256": dict(ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_TARGET_SHA256), "live": live, } def accept_engine_provider_target_host_policy_runtime(): root = component_root("engine") validate_engine_provider_target_host_policy_slice( root, ENGINE_PROVIDER_TARGET_HOST_POLICY_ARTIFACT_ENTRIES, ) live = run_engine_backend_probe( ( "node", "--input-type=module", "-e", """ const module=await import('file:///app/server/routes/n8n.js'); const slot='ndcProviderRotatingAccessApi'; const entry={id:'provider',name:'provider',type:slot,nodeDcCredentialId:'provider-ref',data:{engineAgentAllowedHttpHosts:['*.geliospro.com','telemetry.example']}}; const target={type:'n8n-nodes-base.httpRequest',parameters:{url:'https://api.geliospro.com/api/v1/units'}}; const deniedTarget={type:'n8n-nodes-base.httpRequest',parameters:{url:'https://untrusted.example/collect'}}; const allowed=module.buildEngineAgentCredentialTransportPolicy(entry,{nodes:[]},slot,target); const denied=module.buildEngineAgentCredentialTransportPolicy(entry,{nodes:[]},slot,deniedTarget); if(!allowed.bindable||allowed.policy?.allowedHosts?.join(',')!=='api.geliospro.com'||allowed.policy?.requireHttps!==true||allowed.policy?.disableRedirects!==true||denied.bindable||denied.reason!=='credential_host_not_allowed')process.exit(2); process.stdout.write('engine-provider-target-host-policy:exact-literal-host:v1'); """.strip(), ), "Engine provider target host policy", container_id=engine_backend_container_id(), ) expected = "engine-provider-target-host-policy:exact-literal-host:v1" if live != expected: die("Engine provider target host policy live acceptance mismatch") return { "target_sha256": dict(ENGINE_PROVIDER_TARGET_HOST_POLICY_TARGET_SHA256), "live": live, } def accept_engine_mcp_execution_profile_decoder_runtime(): root = component_root("engine") validate_engine_mcp_execution_profile_decoder_slice( root, ENGINE_MCP_EXECUTION_PROFILE_DECODER_ARTIFACT_ENTRIES, ) live = run_engine_backend_probe( ( "node", "--input-type=module", "-e", """ const module=await import('file:///app/server/routes/n8n.js'); const execution={id:'profile-decoder-acceptance',data:JSON.stringify([{resultData:'1'},{runData:'2'},{Probe:'3'},['4'],{data:'5'},{main:'6'},[['7']],{json:'8'},{params:'9'},{VS_43:'10',mcc:'11',sats:9},'16','250'])}; const profile=module.toSafeNodeOutputProfile(execution,'Probe'); const paths=new Map(profile.paths.map((item)=>[item.path,item])); if(!profile.found||profile.valuesIncluded!==false||paths.get('$.params.VS_43')?.types?.join(',')!=='string'||paths.get('$.params.VS_43')?.minLength!==2||paths.get('$.params.mcc')?.types?.join(',')!=='string'||paths.get('$.params.mcc')?.minLength!==3||paths.get('$.params.sats')?.types?.join(',')!=='number'||profile.paths.some((item)=>item.path.includes('.json.fact')))process.exit(2); process.stdout.write('engine-mcp-execution-profile-decoder:flatted-numeric-strings:safe-profile:v1'); """.strip(), ), "Engine MCP execution profile decoder", container_id=engine_backend_container_id(), ) expected = ( "engine-mcp-execution-profile-decoder:" "flatted-numeric-strings:safe-profile:v1" ) if live != expected: die("Engine MCP execution profile decoder live acceptance mismatch") return { "target_sha256": dict(ENGINE_MCP_EXECUTION_PROFILE_DECODER_TARGET_SHA256), "live": live, } def accept_engine_mcp_telemetry_catalog_runtime(): root = component_root("engine") validate_engine_mcp_telemetry_catalog_slice( root, ENGINE_MCP_TELEMETRY_CATALOG_ARTIFACT_ENTRIES, ) live = run_engine_backend_probe( ( "node", "--input-type=module", "-e", """ const n8n=await import('file:///app/server/routes/n8n.js'); const gateway=await import('file:///app/server/routes/engineAgentGateway.js'); const execution={id:'telemetry-catalog-acceptance',data:{resultData:{runData:{Mapper:[{data:{main:[[{json:{fact:{attributes:{sensor_readings:[{id:'engine.rpm',label:'Engine RPM',unit:'rpm',value:1350},{id:'tracker.status',label:'Bearer provider-token-must-not-escape',value:'provider-token-must-not-escape'}]}},lastMsg:{params:{sensor_readings:[{id:'raw.must-not-enter',label:'Raw',value:'raw-provider-value'}]}}}}]]}}]}}}}; const catalog=n8n.toSafeTelemetryReadingCatalog(execution,'Mapper'); const rpm=catalog.readings.find((item)=>item.id==='engine.rpm'); const status=catalog.readings.find((item)=>item.id==='tracker.status'); const serialized=JSON.stringify(catalog); const tool=gateway.engineAgentTools.find((item)=>item.name==='engine_get_telemetry_reading_catalog'); if(gateway.ENGINE_AGENT_MCP_VERSION!=='0.8.0'||!tool||!catalog.found||catalog.valuesIncluded!==false||catalog.rawExecutionDataIncluded!==false||rpm?.label!=='Engine RPM'||rpm?.unit!=='rpm'||rpm?.valueTypes?.join(',')!=='number'||status?.label!==undefined||catalog.readings.some((item)=>item.id==='raw.must-not-enter')||serialized.includes('1350')||serialized.includes('provider-token-must-not-escape')||serialized.includes('raw-provider-value'))process.exit(2); process.stdout.write('engine-mcp-telemetry-catalog:0.8.0:normalized-metadata-only:v1'); """.strip(), ), "Engine MCP telemetry reading catalog", container_id=engine_backend_container_id(), ) expected = "engine-mcp-telemetry-catalog:0.8.0:normalized-metadata-only:v1" if live != expected: die("Engine MCP telemetry reading catalog live acceptance mismatch") return { "target_sha256": dict(ENGINE_MCP_TELEMETRY_CATALOG_TARGET_SHA256), "live": live, } def accept_engine_mcp_execution_plan_materialization_runtime(): root = component_root("engine") validate_engine_mcp_execution_plan_materialization_slice( root, ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_ARTIFACT_ENTRIES, ) live = run_engine_backend_probe( ( "node", "--input-type=module", "-e", """ const fs=await import('node:fs/promises'); const gateway=await import('file:///app/server/routes/engineAgentGateway.js'); const catalogModule=await import('file:///app/server/l2ExecutionPlan/catalog.js'); const materializer=await import('file:///app/server/l2ExecutionPlan/materializer.js'); const catalog=await catalogModule.loadExecutionPlanCatalog(); const plan=gateway.engineAgentTools.find((item)=>item.name==='engine_plan_l2_execution_plan_materialization'); const apply=gateway.engineAgentTools.find((item)=>item.name==='engine_apply_l2_execution_plan_materialization'); const planRequired=plan?.inputSchema?.required||[]; const applyRequired=apply?.inputSchema?.required||[]; const genericSource=(await Promise.all(['/app/server/l2ExecutionPlan/compiler.js','/app/server/l2ExecutionPlan/materializer.js'].map((path)=>fs.readFile(path,'utf8')))).join('\\n'); const unmanaged=materializer.materializationState({source:{},nodes:[{id:'existing'}],edges:[]}); if(gateway.ENGINE_AGENT_MCP_VERSION!=='0.9.0'||!plan||!apply||plan?.inputSchema?.additionalProperties!==false||apply?.inputSchema?.additionalProperties!==false||!planRequired.includes('executionPlan')||!applyRequired.includes('planRef')||!applyRequired.includes('providerAuthRef')||catalog.schemaVersion!=='nodedc.engine.execution-plan-catalog/v1'||catalog.runtime?.graphBlueprintSchemaVersion!=='nodedc.l2-graph-blueprint/v1'||catalog.runtime?.compilerVersions?.join(',')!=='1.1.0'||unmanaged!=='unmanaged_existing'||/gelios|robot2b/i.test(genericSource))process.exit(2); process.stdout.write('engine-mcp-execution-plan-materialization:0.9.0:provider-package-authority:two-phase:v1'); """.strip(), ), "Engine MCP execution plan materialization", container_id=engine_backend_container_id(), ) expected = ( "engine-mcp-execution-plan-materialization:" "0.9.0:provider-package-authority:two-phase:v1" ) if live != expected: die("Engine MCP execution plan materialization live acceptance mismatch") return { "target_sha256": dict( ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_TARGET_SHA256 ), "live": live, } def accept_engine_mcp_execution_plan_telemetry_runtime(): root = component_root("engine") validate_engine_mcp_execution_plan_telemetry_runtime_slice( root, ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_ARTIFACT_ENTRIES, ) live = run_engine_backend_probe( ( "node", "--input-type=module", "-e", """ const fs=await import('node:fs/promises'); const catalogModule=await import('file:///app/server/l2ExecutionPlan/catalog.js'); const catalog=await catalogModule.loadExecutionPlanCatalog(); const compiler=await fs.readFile('/app/server/l2ExecutionPlan/compiler.js','utf8'); const versions=catalog.runtime?.compilerVersions||[]; if(versions.join(',')!=='1.1.0,1.2.0'||!compiler.includes("if (compilerVersion === '1.1.0')")||!compiler.includes("if (compilerVersion !== '1.2.0')")||!compiler.includes('"msgParam", "msg_param"')||!compiler.includes('descriptor.telemetryProjection')||!compiler.includes('"message-param"')||!compiler.includes('convertedSensorValue')||!compiler.includes('visibleSensorDefinition')||/gelios|robot2b/i.test(compiler))process.exit(2); process.stdout.write('engine-mcp-execution-plan-telemetry-runtime:0.9.0:compiler-1.2.0:declared-projected:v2'); """.strip(), ), "Engine MCP execution plan telemetry runtime", container_id=engine_backend_container_id(), ) expected = ( "engine-mcp-execution-plan-telemetry-runtime:" "0.9.0:compiler-1.2.0:declared-projected:v2" ) if live != expected: die("Engine MCP execution plan telemetry runtime live acceptance mismatch") return { "target_sha256": dict( ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_TARGET_SHA256 ), "live": live, } def accept_engine_mcp_execution_plan_module_ownership_runtime(): root = component_root("engine") validate_engine_mcp_execution_plan_module_ownership_slice( root, ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_ARTIFACT_ENTRIES, ) live = run_engine_backend_probe( ( "node", "--input-type=module", "-e", """ const fs=await import('node:fs/promises'); const gateway=await import('file:///app/server/routes/engineAgentGateway.js'); const catalogModule=await import('file:///app/server/l2ExecutionPlan/catalog.js'); const materializer=await import('file:///app/server/l2ExecutionPlan/materializer.js'); const catalog=await catalogModule.loadExecutionPlanCatalog(); const source=await fs.readFile('/app/server/l2ExecutionPlan/materializer.js','utf8'); const plan=gateway.engineAgentTools.find((item)=>item.name==='engine_plan_l2_execution_plan_materialization'); const strategies=plan?.inputSchema?.properties?.strategy?.enum||[]; const adoption=plan?.inputSchema?.properties?.moduleAdoption?.properties||{}; const geliosV9=catalog.packages?.find((item)=>item.id==='gelios.provider.v9'); const legacy=catalog.packages?.some((item)=>item.id==='gelios.provider.v8'); const unmanaged=materializer.materializationState({source:{},nodes:[{id:'existing'}],edges:[]}); if(gateway.ENGINE_AGENT_MCP_VERSION!=='0.10.0'||strategies.join(',')!=='create_or_reconcile_owned,adopt_existing,adopt_existing_module'||!adoption.preserveBoundNodeIds||!source.includes('execution_plan_module_retire_boundary_not_closed')||!source.includes('manual_webhook_same_method')||/gelios|robot2b/i.test(source)||geliosV9?.contractDigest!=='sha256:7dd4ce4a45ce76e884ffa1e304bfaa521f552e9a45e535930f2d17b882ae23ed'||!legacy||unmanaged!=='unmanaged_existing')process.exit(2); process.stdout.write('engine-mcp-execution-plan-module-ownership:0.10.0:module-scoped:shared-boundary:v3'); """.strip(), ), "Engine MCP execution plan module ownership", container_id=engine_backend_container_id(), ) expected = ( "engine-mcp-execution-plan-module-ownership:" "0.10.0:module-scoped:shared-boundary:v3" ) if live != expected: die( "Engine MCP execution plan module ownership live acceptance " "mismatch" ) return { "target_sha256": dict( ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_TARGET_SHA256 ), "live": live, } def accept_engine_mcp_normalized_identity_search_runtime(): root = component_root("engine") validate_engine_mcp_normalized_identity_search_slice( root, ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_ARTIFACT_ENTRIES, ) live = run_engine_backend_probe( ( "node", "--input-type=module", "-e", """ const fs=await import('node:fs/promises'); const gateway=await import('file:///app/server/routes/engineAgentGateway.js'); const executionCatalog=JSON.parse(await fs.readFile('/app/server/assets/execution-plans/v1/catalog.json','utf8')); const securityCatalog=JSON.parse(await fs.readFile('/app/server/assets/provider-packages/v1/catalog.json','utf8')); const route=await fs.readFile('/app/server/routes/n8n.js','utf8'); const compiler=await fs.readFile('/app/server/l2ExecutionPlan/compiler.js','utf8'); const tool=gateway.engineAgentTools.find((item)=>item.name==='engine_find_normalized_subjects'); const packageV11=executionCatalog.packages?.find((item)=>item.id==='gelios.provider.v11'); const securityV11=securityCatalog.packages?.find((item)=>item.id==='gelios.provider.v11'); const identityProfile=packageV11?.profiles?.find((item)=>item.dataProductId==='fleet.units.identity.current.v1'); const identityCapability=securityV11?.capabilities?.find((item)=>item.id==='gelios.units.identity.read'); if(gateway.ENGINE_AGENT_MCP_VERSION!=='0.11.0'||!tool?.inputSchema?.properties?.boundingBox||!tool?.inputSchema?.properties?.fields||!route.includes('normalizedFactsOnly: true')||!route.includes('rawExecutionDataIncluded: false')||!route.includes('commandSurfaceIncluded: false')||!compiler.includes('boundedStringList')||!compiler.includes('boundedNamedValues')||/gelios|robot2b/i.test(compiler)||executionCatalog.runtime?.compilerVersions?.join(',')!=='1.1.0,1.2.0,1.3.0,1.4.0'||packageV11?.contractDigest!=='sha256:4bf211d9ed6cfd227df444d49d8c5ce77a24389ed2ba3d159d53d83e0d4738d9'||!identityProfile||identityCapability?.classification!=='read'||identityCapability?.dataProductIds?.join(',')!=='fleet.units.identity.current.v1')process.exit(2); process.stdout.write('engine-mcp-normalized-identity-search:0.11.0:canonical-facts:full-admin-identifiers:read-only:v1'); """.strip(), ), "Engine MCP normalized identity search", container_id=engine_backend_container_id(), ) expected = ( "engine-mcp-normalized-identity-search:" "0.11.0:canonical-facts:full-admin-identifiers:read-only:v1" ) if live != expected: die("Engine MCP normalized identity search live acceptance mismatch") return { "target_sha256": dict( ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_TARGET_SHA256 ), "live": live, } def accept_engine_mcp_execution_plan_sandbox_runtime(): root = component_root("engine") validate_engine_mcp_execution_plan_sandbox_runtime_slice( root, ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_ARTIFACT_ENTRIES, ) live = run_engine_backend_probe( ( "node", "--input-type=module", "-e", """ const fs=await import('node:fs/promises'); const compiler=await fs.readFile('/app/server/l2ExecutionPlan/compiler.js','utf8'); const start=compiler.indexOf('const encodedBytes = (value) => {'); const end=compiler.indexOf('for (const raw of input) {',start); const byteCounter=start>=0&&end>start?compiler.slice(start,end):''; if(!byteCounter.includes('encoded.charCodeAt(index)')||!byteCounter.includes('length += 4')||byteCounter.includes('TextEncoder')||!compiler.includes('return extracted.map((source) => ({ json: { source, collectionReceivedAt: receivedAt } }))')||/gelios|robot2b/i.test(compiler))process.exit(2); process.stdout.write('engine-mcp-execution-plan-sandbox-runtime:0.11.0:n8n-2.3.2:pure-js-utf8:v4'); """.strip(), ), "Engine MCP execution plan sandbox runtime", container_id=engine_backend_container_id(), ) expected = ( "engine-mcp-execution-plan-sandbox-runtime:" "0.11.0:n8n-2.3.2:pure-js-utf8:v4" ) if live != expected: die("Engine MCP execution plan sandbox runtime live acceptance mismatch") return { "target_sha256": dict( ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_TARGET_SHA256 ), "live": live, } def accept_engine_mcp_gelios_items_envelope_runtime(): root = component_root("engine") validate_engine_mcp_gelios_items_envelope_slice( root, ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_ARTIFACT_ENTRIES, ) live = run_engine_backend_probe( ( "node", "--input-type=module", "-e", """ const fs=await import('node:fs/promises'); const execution=JSON.parse(await fs.readFile('/app/server/assets/execution-plans/v1/catalog.json','utf8')); const security=JSON.parse(await fs.readFile('/app/server/assets/provider-packages/v1/catalog.json','utf8')); const compiler=await fs.readFile('/app/server/l2ExecutionPlan/compiler.js','utf8'); const ids=execution.packages.map(({id})=>id); const v12=execution.packages.find(({id})=>id==='gelios.provider.v12'); const identity=v12?.profiles?.filter(({id,dataProductId})=>id==='gelios.units.identity.warm.v1'&&dataProductId==='fleet.units.identity.current.v1')||[]; const authorities=security.packages.filter(({capabilities=[]})=>capabilities.some(({dataProductIds=[]})=>dataProductIds.includes('fleet.units.identity.current.v1'))); if(!ids.includes('gelios.provider.v11')||v12?.version!=='12.0.0'||identity.length!==1||authorities.length!==1||authorities[0].id!=='gelios.provider.v12'||/gelios|robot2b/i.test(compiler)||compiler.includes('TextEncoder'))process.exit(2); process.stdout.write('engine-mcp-gelios-items-envelope:0.11.0:v11-history:v12-authority:items'); """.strip(), ), "Engine MCP Gelios items envelope", container_id=engine_backend_container_id(), ) expected = ( "engine-mcp-gelios-items-envelope:" "0.11.0:v11-history:v12-authority:items" ) if live != expected: die("Engine MCP Gelios items envelope live acceptance mismatch") return { "target_sha256": dict( ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_TARGET_SHA256 ), "live": live, } def accept_engine_mcp_registered_execution_profiles_runtime(): root = component_root("engine") validate_engine_mcp_registered_execution_profiles_slice( root, ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_ARTIFACT_ENTRIES, ) live = run_engine_backend_probe( ( "node", "--input-type=module", "-e", """ const gateway=await import('file:///app/server/routes/engineAgentGateway.js'); const catalogModule=await import('file:///app/server/l2ExecutionPlan/catalog.js'); const registeredModule=await import('file:///app/server/l2ExecutionPlan/registeredProfiles.js'); const catalog=await catalogModule.loadExecutionPlanCatalog(); let captured=null; const service=registeredModule.createRegisteredExecutionProfileService({ loadCatalog:async()=>catalog, planMaterialization:async(input)=>{captured=input;return {ok:true,status:'planned',planRef:'emplan_probe',providerAuthRefs:[]};}, }); const target={workflowId:'workflow-probe',nodeId:'node-probe'}; const listed=await service.list({actorKey:'actor-probe',target}); const profile=listed.profiles.find(({profile})=>profile.id==='gelios.units.profile.cold.v1'); if(gateway.ENGINE_AGENT_MCP_VERSION!=='0.12.0'||!gateway.engineAgentTools.some(({name})=>name==='engine_list_l2_execution_profiles')||!gateway.engineAgentTools.some(({name})=>name==='engine_plan_registered_l2_execution')||listed.count!==6||!profile||JSON.stringify(listed).includes('executionPlanTemplate'))process.exit(2); await service.plan({actorKey:'actor-probe',target,profileRef:profile.profileRef,expectedRevision:'revision-probe',strategy:'create_or_reconcile_owned',expectedScope:{tenantId:'tenant-probe',connectionId:'connection-probe'}}); if(captured?.executionPlan?.connection?.tenantId!=='tenant-probe'||captured?.executionPlan?.connection?.connectionId!=='connection-probe'||!String(captured?.executionPlan?.bindings?.provider?.reference||'').startsWith('ndc-credref:engine-target-'))process.exit(3); process.stdout.write('engine-mcp-registered-execution-profiles:0.12.0:profiles-6:target-scope:existing-materializer'); """.strip(), ), "Engine MCP registered execution profiles", container_id=engine_backend_container_id(), ) expected = ( "engine-mcp-registered-execution-profiles:" "0.12.0:profiles-6:target-scope:existing-materializer" ) if live != expected: die("Engine MCP registered execution profiles live acceptance mismatch") return { "target_sha256": dict( ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_TARGET_SHA256 ), "live": live, } def accept_engine_mcp_gelios_units_items_runtime(): root = component_root("engine") validate_engine_mcp_gelios_units_items_slice( root, ENGINE_MCP_GELIOS_UNITS_ITEMS_ARTIFACT_ENTRIES, ) live = run_engine_backend_probe( ( "node", "--input-type=module", "-e", """ const gateway=await import('file:///app/server/routes/engineAgentGateway.js'); const executionModule=await import('file:///app/server/l2ExecutionPlan/catalog.js'); const securityModule=await import('file:///app/server/dataProductPublishGrant/providerCatalog.js'); const execution=await executionModule.loadExecutionPlanCatalog(); const security=await securityModule.loadProviderSecurityCatalog(); const provider=execution.packages.find((item)=>item.id==='gelios.provider.v12'); const profile=provider?.profiles.find((item)=>item.id==='gelios.units.profile.cold.v1'); const plan=profile?.executionPlanTemplate; const extract=plan?.steps.find((step)=>step.kind==='extract_items'&&step.config?.capabilityId==='gelios.units.current.read'); const secureProvider=security.packages.find((item)=>item.id==='gelios.provider.v12'); if(gateway.ENGINE_AGENT_MCP_VERSION!=='0.12.0'||!gateway.engineAgentTools.some(({name})=>name==='engine_list_l2_execution_profiles')||!gateway.engineAgentTools.some(({name})=>name==='engine_plan_registered_l2_execution')||provider?.version!=='12.0.1'||secureProvider?.version!=='12.0.1'||plan?.compilerVersion!=='1.4.0'||extract?.config?.response?.collectionPaths?.[0]!=='items')process.exit(2); process.stdout.write('engine-mcp-gelios-units-items:0.12.0:gelios.provider.v12@12.0.1:technical-profile:items'); """.strip(), ), "Engine MCP Gelios units items envelope", container_id=engine_backend_container_id(), ) expected = ( "engine-mcp-gelios-units-items:0.12.0:" "gelios.provider.v12@12.0.1:technical-profile:items" ) if live != expected: die("Engine MCP Gelios units items live acceptance mismatch") return { "target_sha256": dict( ENGINE_MCP_GELIOS_UNITS_ITEMS_TARGET_SHA256 ), "live": live, } def accept_engine_mcp_l1_credential_reuse_runtime(): root = component_root("engine") validate_engine_mcp_l1_credential_reuse_slice( root, ENGINE_MCP_L1_CREDENTIAL_REUSE_ARTIFACT_ENTRIES, ) live = run_engine_backend_probe( ( "node", "--input-type=module", "-e", """ const route=await import('file:///app/server/routes/n8n.js'); const slot='httpBearerAuth'; const entry={nodeDcCredentialId:'credential-provider',n8nCredentialId:'runtime-provider',type:slot,name:'Provider read access',source:'manual',status:'ok',data:{token:'synthetic'}}; const sameL1=new Set([entry.nodeDcCredentialId]); const otherL1=new Set(['credential-other']); const graph={nodes:[{id:'source-reader',data:{n8n:{type:'n8n-nodes-base.httpRequest',parameters:{url:'https://api.provider.example/v1/items'},credentials:{[slot]:{id:entry.n8nCredentialId,name:entry.name,nodeDcCredentialId:entry.nodeDcCredentialId}}}}}]}; const target={type:'n8n-nodes-base.httpRequest',parameters:{url:'https://api.provider.example/v1/identity'}}; const deniedTarget={type:'n8n-nodes-base.httpRequest',parameters:{url:'https://untrusted.example/collect'}}; const allowed=route.buildEngineAgentCredentialTransportPolicy({...entry,data:{allowedHttpRequestDomains:'all'}},graph,slot,target); const denied=route.buildEngineAgentCredentialTransportPolicy({...entry,data:{allowedHttpRequestDomains:'all'}},graph,slot,deniedTarget); if(route.engineAgentCandidateScope(entry,{localEntries:[]},sameL1)!=='l1'||route.engineAgentCandidateScope(entry,{localEntries:[]},otherL1)!==''||route.engineAgentCredentialMayReuseWithinL1({...entry,source:'workflow-inline'})||!allowed.bindable||allowed.policy?.allowedHosts?.join(',')!=='api.provider.example'||denied.bindable)process.exit(2); process.stdout.write('engine-mcp-l1-credential-reuse:0.11.0:same-l1:opaque-ref:v1'); """.strip(), ), "Engine MCP L1 credential reuse", container_id=engine_backend_container_id(), ) expected = ( "engine-mcp-l1-credential-reuse:" "0.11.0:same-l1:opaque-ref:v1" ) if live != expected: die("Engine MCP L1 credential reuse live acceptance mismatch") return { "target_sha256": dict(ENGINE_MCP_L1_CREDENTIAL_REUSE_TARGET_SHA256), "live": live, } def accept_engine_mcp_l1_credential_provenance_runtime(): root = component_root("engine") validate_engine_mcp_l1_credential_provenance_slice( root, ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_ARTIFACT_ENTRIES, ) live = run_engine_backend_probe( ( "node", "--input-type=module", "-e", """ const route=await import('file:///app/server/routes/n8n.js'); const slot='httpBearerAuth'; const common={nodeDcCredentialId:'credential-provider',n8nCredentialId:'runtime-provider',type:slot,name:'Provider read access',source:'manual',status:'ok',data:{token:'synthetic'}}; const workflowRef={...common,source:'workflow-ref'}; const workflowRefWithoutSync={...workflowRef,data:{}}; const sameL1=new Set([common.nodeDcCredentialId]); const graph={nodes:[{id:'source-reader',data:{n8n:{type:'n8n-nodes-base.httpRequest',parameters:{url:'https://api.provider.example/v1/items'},credentials:{[slot]:{id:common.n8nCredentialId,name:common.name,nodeDcCredentialId:common.nodeDcCredentialId}}}}}]}; const target={type:'n8n-nodes-base.httpRequest',parameters:{url:'https://api.provider.example/v1/identity'}}; const allowed=route.buildEngineAgentCredentialTransportPolicy({...common,data:{allowedHttpRequestDomains:'all'}},graph,slot,target); if(!route.engineAgentCredentialMayProveL1Provenance(workflowRef)||route.engineAgentCredentialMayProveL1Provenance(workflowRefWithoutSync)||route.engineAgentCredentialMayReuseWithinL1(workflowRef)||route.engineAgentCandidateScope(common,{localEntries:[]},sameL1)!=='l1'||!allowed.bindable||allowed.policy?.allowedHosts?.join(',')!=='api.provider.example')process.exit(2); process.stdout.write('engine-mcp-l1-credential-provenance:0.11.0:workflow-ref-sync-proof:v2'); """.strip(), ), "Engine MCP L1 credential provenance", container_id=engine_backend_container_id(), ) expected = ( "engine-mcp-l1-credential-provenance:" "0.11.0:workflow-ref-sync-proof:v2" ) if live != expected: die("Engine MCP L1 credential provenance live acceptance mismatch") return { "target_sha256": dict( ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_TARGET_SHA256 ), "live": 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"): package = read_strict_json(package_path, f"{label} package.json", max_bytes=1024 * 1024) scripts = package.get("scripts") or {} if not isinstance(scripts, dict): die(f"{label} package scripts must be an object") for name in forbidden: if name in scripts: die(f"{label} lifecycle script is forbidden: {package_path.relative_to(payload_dir)}:{name}") def expected_engine_credential_backend_override(): return "\n".join(( "services:", " nodedc-backend:", f" image: {ENGINE_CREDENTIAL_BACKEND_IMAGE}", " pull_policy: never", " environment:", " HOME: /tmp", " command:", " - /bin/sh", " - -lc", " - |-|".replace("|-|", "|-"), " set -eu", " test \"$$(command -v node)\" = /usr/local/bin/node", " command -v sqlite3 >/dev/null", " command -v docker >/dev/null", " docker compose version >/dev/null", f" test \"$$(sha256sum /app/package-lock.json | cut -d ' ' -f 1)\" = {ENGINE_CREDENTIAL_BACKEND_PACKAGE_LOCK_SHA256}", " test -d /app/node_modules", " mkdir -p /app/server/data/api", " if [ -d /seed-api ]; then", " for d in cesium gelios overpass wind yandex; do", " src=/seed-api/$$d", " [ -d \"$$src\" ] || continue", " dst=/app/server/data/api/$$d", " mkdir -p \"$$dst\"", " if [ \"$$NODEDC_API_SEED_MODE\" = force ]; then", " cp -R \"$$src/.\" \"$$dst/\"", " elif [ -z \"$$(ls -A \"$$dst\" 2>/dev/null)\" ]; then", " cp -R \"$$src/.\" \"$$dst/\"", " fi", " done", " fi", " if [ \"$$NODEDC_DATA_SEED_MODE\" != off ] && [ \"$$NODEDC_DATA_SEED_MODE\" != none ]; then", " for d in workflows n8n; do", " src=/seed-data/$$d", " dst=/app/server/data/$$d", " [ -d \"$$src\" ] || continue", " mkdir -p \"$$dst\"", " if [ \"$$NODEDC_DATA_SEED_MODE\" = merge ] || [ \"$$NODEDC_DATA_SEED_MODE\" = force ]; then", " cp -R \"$$src/.\" \"$$dst/\"", " elif [ -z \"$$(ls -A \"$$dst\" 2>/dev/null)\" ]; then", " cp -R \"$$src/.\" \"$$dst/\"", " fi", " done", " fi", " exec node server/index.js", " volumes:", " - ./nodedc-backend-node_modules:/app/node_modules:ro", " tmpfs:", " - /tmp:mode=1777", " - /run:mode=0755", " - /var/cache:mode=0755", " - /var/log:mode=0755", "", )) def expected_engine_data_product_publish_grant_override(): return "\n".join(( "services:", " nodedc-backend:", ' user: "0:0"', " environment:", f" ENGINE_DATA_PLANE_BASE_URL: {EXTERNAL_DATA_PLANE_INTERNAL_URL}", f" ENGINE_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE: {ENGINE_EDP_PRIVATE_KEY_CONTAINER_PATH}", f" ENGINE_CONTROL_PLANE_PUBLISH_GRANT_ROOT: {ENGINE_PUBLISH_GRANT_CONTAINER_PATH}", " volumes:", " - type: bind", f" source: {ENGINE_PUBLISH_GRANT_STATE_PATH}", f" target: {ENGINE_PUBLISH_GRANT_CONTAINER_PATH}", " bind:", " create_host_path: false", " - type: bind", f" source: {ENGINE_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE}", f" target: {ENGINE_EDP_PRIVATE_KEY_CONTAINER_PATH}", " read_only: true", " bind:", " create_host_path: false", "", )) 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") index_text = (payload_dir / "nodedc-source/server/index.js").read_text(encoding="utf-8") if "engineCredentialSink" not in index_text: die("Engine credential sink router mount is missing") vendor_contract = payload_dir / "nodedc-source/server/credentialSink/vendor/engine-credential-sink.mjs" if sha256_file(vendor_contract) != ENGINE_CREDENTIAL_SINK_CONTRACT_SHA256: die("Engine credential sink vendored contract sha256 mismatch") override = payload_dir / ENGINE_CREDENTIAL_BACKEND_OVERRIDE_TEMPLATE_REL try: override_text = override.read_text(encoding="utf-8") except (OSError, UnicodeDecodeError): die("Engine credential immutable runtime override is unreadable") if override_text != expected_engine_credential_backend_override(): die("Engine credential immutable runtime override mismatch") if re.search(r"\b(?:apk|npm|yarn|pnpm)\b", override_text): die("Engine credential mutable runtime setup is forbidden") generic_roots = ( payload_dir / "nodedc-source/server/credentialSink", payload_dir / "nodedc-source/server/credentialPolicies/ndcPrivateNode.js", payload_dir / "nodedc-source/server/routes/engineCredentialSink.js", ) for root in generic_roots: candidates = [root] if root.is_file() else list(root.rglob("*")) for path in candidates: if not path.is_file() or path.suffix.lower() not in (".js", ".mjs", ".json"): continue try: text_value = path.read_text(encoding="utf-8") except (OSError, UnicodeDecodeError): die(f"Engine credential sink source is unreadable: {path.relative_to(payload_dir)}") if re.search(r"gelios|robot2b", text_value, re.IGNORECASE): die(f"Engine credential sink provider logic is forbidden: {path.relative_to(payload_dir)}") validate_no_lifecycle_scripts(payload_dir, "Engine credential sink") def validate_engine_data_product_publish_grant_slice(payload_dir, entries): if tuple(entries) != ENGINE_DATA_PRODUCT_PUBLISH_GRANT_ARTIFACT_ENTRIES: die("Engine data product publish grant files.txt exact set/order mismatch") override = payload_dir / ENGINE_DATA_PRODUCT_PUBLISH_GRANT_OVERRIDE_REL try: override_text = override.read_text(encoding="utf-8") except (OSError, UnicodeDecodeError): die("Engine data product publish grant runtime override is unreadable") if override_text != expected_engine_data_product_publish_grant_override(): die("Engine data product publish grant runtime override mismatch") n8n_route = (payload_dir / "nodedc-source/server/routes/n8n.js").read_text(encoding="utf-8") if "from '../credentialSink/" in n8n_route: die("Engine data product publish grant must not depend on the legacy sink") for adapter in ( "engineCredentialSinkN8nAdapter", "engineDataProductPublishGrantN8nAdapter", ): if adapter not in n8n_route: die(f"Engine data product publish grant adapter is missing: {adapter}") gateway = (payload_dir / "nodedc-source/server/routes/engineAgentGateway.js").read_text( encoding="utf-8" ) for tool in ( "engine_plan_data_product_publish_grant", "engine_apply_data_product_publish_grant", "engine_accept_data_product_publish_grant", "engine_rollback_data_product_publish_grant", ): if tool not in gateway: die(f"Engine data product publish grant tool is missing: {tool}") catalog_path = payload_dir / ENGINE_PROVIDER_SECURITY_CATALOG_REL if sha256_file(catalog_path) == ENGINE_PUBLISH_GRANT_COMPOSITE_PROVIDER_CATALOG_TARGET_SHA256: catalog = read_strict_json(catalog_path, "Engine composite provider security catalog") packages = catalog.get("packages") if isinstance(catalog, dict) else None provider = packages[0] if isinstance(packages, list) and len(packages) == 1 else None capabilities = provider.get("capabilities") if isinstance(provider, dict) else None if ( catalog.get("schemaVersion") != "nodedc.engine.provider-security-catalog/v1" or not isinstance(provider, dict) or provider.get("id") != "gelios.provider.v4" or provider.get("version") != "4.0.0" or provider.get("providerId") != "gelios" or provider.get("providerCredential") != { "authModeId": "gelios.rest-rotating-bearer.v3", "credentialType": "httpBearerAuth", } or capabilities != [{ "id": "gelios.monitoring_config.current.read", "classification": "read", "status": "implemented", "request": { "method": "GET", "url": "https://api.geliospro.com/api/v1/users/me/monitoring-config", }, "dataProductIds": ["fleet.positions.current.v3"], }, { "id": "gelios.units.current.read", "classification": "read", "status": "implemented", "request": { "method": "GET", "url": "https://api.geliospro.com/api/v1/units?incltrip=true", }, "dataProductIds": ["fleet.positions.current.v3"], }] or provider.get("publisher") != { "nodeType": "n8n-nodes-ndc.ndcDataProductPublish", "credentialType": "ndcDataProductWriterApi", } ): die("Engine composite provider security catalog projection mismatch") provider_catalog = ( payload_dir / "nodedc-source/server/dataProductPublishGrant/providerCatalog.js" ).read_text(encoding="utf-8") publish_service = ( payload_dir / "nodedc-source/server/dataProductPublishGrant/service.js" ).read_text(encoding="utf-8") publish_store = ( payload_dir / "nodedc-source/server/dataProductPublishGrant/store.js" ).read_text(encoding="utf-8") if any( marker not in provider_catalog for marker in ( "function capabilityRequests(capability)", "function exactHttpRequestUrl(n8n)", "capabilityIds", "providerRequestNodeIds", "providerCredentialRefs.size !== 1", ) ): die("Engine composite provider resolver contract mismatch") if any( marker not in publish_service for marker in ( "capabilities: descriptor.capabilityIds", "providerRequestNodeIds: descriptor.providerRequestNodeIds", ) ): die("Engine composite provider plan projection mismatch") if any( marker not in publish_store for marker in ( "Array.isArray(raw.capabilityIds)", "Array.isArray(raw.providerRequestNodeIds)", "new Set(descriptor.providerRequestNodeIds).size", ) ): die("Engine composite provider durable descriptor mismatch") validate_no_lifecycle_scripts(payload_dir, "Engine data product publish grant") def validate_engine_composite_provider_v4_slice(payload_dir, entries): if tuple(entries) != ENGINE_COMPOSITE_PROVIDER_V4_ARTIFACT_ENTRIES: die("Engine composite provider v4 files.txt exact set/order mismatch") for rel, expected_sha256 in ENGINE_COMPOSITE_PROVIDER_V4_TARGET_SHA256.items(): path = payload_dir / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine composite provider v4 target is missing: {rel}") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die(f"Engine composite provider v4 target sha256 mismatch: {rel}") catalog = read_strict_json( payload_dir / ENGINE_PROVIDER_SECURITY_CATALOG_REL, "Engine composite provider v4 catalog", ) packages = catalog.get("packages") if isinstance(catalog, dict) else None provider = packages[0] if isinstance(packages, list) and len(packages) == 1 else None capabilities = provider.get("capabilities") if isinstance(provider, dict) else None if ( catalog.get("schemaVersion") != "nodedc.engine.provider-security-catalog/v1" or not isinstance(provider, dict) or provider.get("id") != "gelios.provider.v4" or provider.get("version") != "4.0.0" or provider.get("providerCredential") != { "authModeId": "gelios.rest-rotating-bearer.v3", "credentialType": "httpBearerAuth", } or not isinstance(capabilities, list) or [item.get("id") for item in capabilities if isinstance(item, dict)] != [ "gelios.monitoring_config.current.read", "gelios.units.current.read", ] or [item.get("dataProductIds") for item in capabilities if isinstance(item, dict)] != [ ["fleet.positions.current.v3"], ["fleet.positions.current.v3"], ] or [ (item.get("request") or {}).get("url") for item in capabilities if isinstance(item, dict) ] != [ "https://api.geliospro.com/api/v1/users/me/monitoring-config", "https://api.geliospro.com/api/v1/units?incltrip=true", ] ): die("Engine composite provider v4 projection mismatch") source_markers = { "nodedc-source/server/dataProductPublishGrant/providerCatalog.js": ( "function exactHttpRequestUrl(n8n)", "const capabilityIds = productCapabilities.map", "providerCredentialRefs.size !== 1", ), "nodedc-source/server/dataProductPublishGrant/service.js": ( "capabilities: descriptor.capabilityIds", "providerRequestNodeIds: descriptor.providerRequestNodeIds", ), "nodedc-source/server/dataProductPublishGrant/store.js": ( "Array.isArray(raw.capabilityIds)", "Array.isArray(raw.providerRequestNodeIds)", ), } for rel, markers in source_markers.items(): source = (payload_dir / rel).read_text(encoding="utf-8") if any(marker not in source for marker in markers): die(f"Engine composite provider v4 source contract mismatch: {rel}") def validate_engine_provider_rotating_slot_slice(payload_dir, entries): if tuple(entries) != ENGINE_PROVIDER_ROTATING_SLOT_ARTIFACT_ENTRIES: die("Engine provider rotating slot files.txt exact set/order mismatch") for rel, expected_sha256 in ENGINE_PROVIDER_ROTATING_SLOT_TARGET_SHA256.items(): path = payload_dir / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine provider rotating slot target is missing: {rel}") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die(f"Engine provider rotating slot target sha256 mismatch: {rel}") catalog = read_strict_json( payload_dir / ENGINE_PROVIDER_SECURITY_CATALOG_REL, "Engine provider rotating slot catalog", ) packages = catalog.get("packages") if isinstance(catalog, dict) else None provider = packages[0] if isinstance(packages, list) and len(packages) == 1 else None if ( not isinstance(provider, dict) or provider.get("id") != "gelios.provider.v4" or provider.get("providerCredential") != { "authModeId": "gelios.rest-rotating-bearer.v3", "credentialType": "ndcProviderRotatingAccessApi", } ): die("Engine provider rotating slot catalog projection mismatch") resolver = ( payload_dir / "nodedc-source/server/dataProductPublishGrant/providerCatalog.js" ).read_text(encoding="utf-8") if ( "'ndcProviderRotatingAccessApi'" not in resolver or "n8n.credentials?.[providerPackage.providerCredential.credentialType]" not in resolver or "nodedcAgentCredentialPolicies?.[providerPackage.providerCredential.credentialType]" not in resolver ): die("Engine provider rotating slot resolver contract mismatch") def validate_engine_provider_authority_diagnostics_slice(payload_dir, entries): if tuple(entries) != ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_ARTIFACT_ENTRIES: die("Engine provider authority diagnostics files.txt exact set/order mismatch") for rel, expected_sha256 in ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_TARGET_SHA256.items(): path = payload_dir / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine provider authority diagnostics target is missing: {rel}") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die(f"Engine provider authority diagnostics target sha256 mismatch: {rel}") source = ( payload_dir / "nodedc-source/server/nodeIntelligence/upstreamProjection.js" ).read_text(encoding="utf-8") required_markers = ( "isUnknownPrivateNodeIssue", "issue?.nodeName || issue?.node", "privateResults", "NDC_PRIVATE_NODE_VALIDATED_BY_ENGINE_CATALOG", ) if any(marker not in source for marker in required_markers): die("Engine private node validation reconciliation contract mismatch") descriptor = read_engine_node_intelligence_descriptor( payload_dir / ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL, "Engine private node validation reconciliation descriptor", ) if ( descriptor.get("action") != "activate" or descriptor.get("releaseId") != "2.33.2-974a9fb3492f" or descriptor.get("source", {}).get("gatewaySha256") != "5331f6dc8dc306f641370a2968eb025ee3e278e27ae9a217e4cfc2901fec369c" or descriptor.get("source", {}).get("upstreamProjectionSha256") != ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_TARGET_SHA256[ "nodedc-source/server/nodeIntelligence/upstreamProjection.js" ] ): die("Engine private node validation reconciliation descriptor mismatch") def validate_engine_l2_closed_loop_payload(payload_dir, entries): if tuple(entries) != ENGINE_L2_CLOSED_LOOP_ARTIFACT_ENTRIES: die("Engine L2 closed-loop files.txt exact set/order mismatch") actual = collect_exact_files( payload_dir, entries, "Engine L2 closed-loop payload", ) if actual != ENGINE_L2_CLOSED_LOOP_TARGET_SHA256: changed = sorted( set(actual) ^ set(ENGINE_L2_CLOSED_LOOP_TARGET_SHA256) | { rel for rel in set(actual) & set(ENGINE_L2_CLOSED_LOOP_TARGET_SHA256) if actual[rel] != ENGINE_L2_CLOSED_LOOP_TARGET_SHA256[rel] } ) detail = changed[0] if changed else "unknown" die(f"Engine L2 closed-loop payload digest mismatch: {detail}") marker_contract = { "nodedc-source/server/l2/graphRepository.js": ( "expectedRevision", "expectedContentRevision", "writeAtomic", "hydrateWorkflow", ), ENGINE_NODE_INTELLIGENCE_GATEWAY_REL: ( "ENGINE_AGENT_MCP_VERSION = '0.7.0'", "engine_get_node_output_profile", "expectedRevision", ), "nodedc-source/server/routes/n8n.js": ( "toSafeNodeOutputProfile", "expectedRevision", "l2GraphRepository", ), "nodedc-source/server/routes/ndcAgentMcp.js": ( "external_codex_mcp", "l2GraphRepository", "expectedRevision", ), "nodedc-source/server/realtime/ws.js": ( "broadcastWorkflowEvent", ), } for rel, markers in marker_contract.items(): try: source = (payload_dir / rel).read_text(encoding="utf-8") except (OSError, UnicodeDecodeError): die(f"Engine L2 closed-loop source is unreadable: {rel}") if any(marker not in source for marker in markers): die(f"Engine L2 closed-loop source contract mismatch: {rel}") for rel in ( "nodedc-source/server/l2/graphRepository.js", ENGINE_NODE_INTELLIGENCE_GATEWAY_REL, "nodedc-source/server/routes/ndcAgentMcp.js", ): source = (payload_dir / rel).read_text(encoding="utf-8") if re.search(r"\b(?:gelios|robot2b)\b", source, re.IGNORECASE): die(f"Engine L2 closed-loop provider hardcode is forbidden: {rel}") descriptor = read_engine_node_intelligence_descriptor( payload_dir / ENGINE_L2_CLOSED_LOOP_DESCRIPTOR_REL, "Engine L2 closed-loop node-intelligence descriptor", ) if ( descriptor.get("action") != "activate" or descriptor.get("releaseId") != ENGINE_NODE_INTELLIGENCE_RELEASE_ID or descriptor.get("source", {}).get("gatewaySha256") != ENGINE_L2_CLOSED_LOOP_TARGET_SHA256[ ENGINE_NODE_INTELLIGENCE_GATEWAY_REL ] or descriptor.get("source", {}).get("upstreamProjectionSha256") != "2dfa6b4f37d9bfa8b92a8109d4060d02dd2634ceb8f7924504b83ccf3fdd1523" ): die("Engine L2 closed-loop descriptor target mismatch") return descriptor def validate_engine_depttrans_zone_authority_v1_slice(payload_dir, entries): if tuple(entries) != ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_ARTIFACT_ENTRIES: die("Engine Depttrans zone authority v1 files.txt exact set/order mismatch") for rel, expected_sha256 in ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_TARGET_SHA256.items(): path = payload_dir / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine Depttrans zone authority v1 target is missing: {rel}") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die(f"Engine Depttrans zone authority v1 target sha256 mismatch: {rel}") catalog = read_strict_json( payload_dir / ENGINE_PROVIDER_SECURITY_CATALOG_REL, "Engine Depttrans zone authority v1 catalog", ) packages = catalog.get("packages") if isinstance(catalog, dict) else None package_ids = [item.get("id") for item in packages if isinstance(item, dict)] \ if isinstance(packages, list) else None provider = next( ( item for item in packages if isinstance(item, dict) and item.get("id") == "moscow-department-of-transport.pmd-slow-zones.v1" ), None, ) if isinstance(packages, list) else None capability = provider.get("capabilities", [None])[0] if isinstance(provider, dict) else None request = capability.get("request") if isinstance(capability, dict) else None marker = read_strict_json( payload_dir / "nodedc-source/server/assets/provider-packages/v1/depttrans-zone-authority-v1.json", "Engine Depttrans zone authority v1 marker", ) if ( catalog.get("schemaVersion") != "nodedc.engine.provider-security-catalog/v1" or package_ids != [ "gelios.provider.v1", "gelios.provider.v4", "gelios.provider.v5", "moscow-department-of-transport.pmd-slow-zones.v1", ] or provider.get("providerId") != "moscow-department-of-transport" or provider.get("version") != "1.0.0" or "providerCredential" in provider or capability.get("id") != "depttrans.pmd-slow-zones.current.read" or capability.get("dataProductIds") != ["map.zones.current.v2"] or request != { "method": "GET", "url": "http://map-gateway:18103/internal/zone-sources/v1/profiles/moscow-pmd-slow-zones/current", "authorityBoundary": "platform-service", "serviceId": "nodedc-map-gateway", "network": "engine", } or marker != { "schemaVersion": "nodedc.engine.platform-service-authority/v1", "id": "moscow-department-of-transport.pmd-slow-zones.v1", "providerId": "moscow-department-of-transport", "authorityBoundary": "platform-service", "serviceId": "nodedc-map-gateway", "network": "engine", "dataProductId": "map.zones.current.v2", } ): die("Engine Depttrans zone authority v1 catalog projection mismatch") required_markers = { "nodedc-source/server/dataProductPublishGrant/providerCatalog.js": ( "function platformServiceRequestIsExact", "authorityBoundary === 'platform-service'", "platformServiceIds", ), "nodedc-source/server/dataProductPublishGrant/service.js": ( "pinned_platform_service_no_graph_credential", "platformServiceIds: descriptor.platformServiceIds", ), "nodedc-source/server/dataProductPublishGrant/store.js": ( "new Set(['provider-credential', 'platform-service'])", "descriptor.authorityBoundary === 'platform-service'", ), } for rel, markers in required_markers.items(): source = (payload_dir / rel).read_text(encoding="utf-8") if any(marker not in source for marker in markers): die(f"Engine Depttrans zone authority v1 source contract mismatch: {rel}") def validate_engine_provider_target_host_policy_slice(payload_dir, entries): if tuple(entries) != ENGINE_PROVIDER_TARGET_HOST_POLICY_ARTIFACT_ENTRIES: die("Engine provider target host policy files.txt exact set/order mismatch") for rel, expected_sha256 in ENGINE_PROVIDER_TARGET_HOST_POLICY_TARGET_SHA256.items(): path = payload_dir / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine provider target host policy target is missing: {rel}") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die(f"Engine provider target host policy target sha256 mismatch: {rel}") source = (payload_dir / "nodedc-source/server/routes/n8n.js").read_text(encoding="utf-8") required_markers = ( "const eligibleHosts = explicitHosts.length", "allowedHosts: [targetHost]", "credential_host_not_allowed", ) if any(marker not in source for marker in required_markers): die("Engine provider target host policy contract mismatch") def validate_engine_mcp_execution_profile_decoder_slice(payload_dir, entries): if tuple(entries) != ENGINE_MCP_EXECUTION_PROFILE_DECODER_ARTIFACT_ENTRIES: die("Engine MCP execution profile decoder files.txt exact set/order mismatch") for rel, expected_sha256 in ENGINE_MCP_EXECUTION_PROFILE_DECODER_TARGET_SHA256.items(): path = payload_dir / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine MCP execution profile decoder target is missing: {rel}") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die(f"Engine MCP execution profile decoder target sha256 mismatch: {rel}") descriptor = read_strict_json( payload_dir / ENGINE_MCP_EXECUTION_PROFILE_DECODER_DESCRIPTOR_REL, "Engine MCP execution profile decoder descriptor", max_bytes=16 * 1024, ) expected_descriptor = { "schemaVersion": "nodedc.engine.deploy-transition/v1", "id": "engine-mcp-execution-profile-decoder-v1", "component": "engine", "scope": "external-mcp-observability", "sourcePath": "nodedc-source/server/routes/n8n.js", "behavior": "preserve-top-level-numeric-string-primitives-in-flatted-execution-data", "acceptance": { "tool": "engine_get_node_output_profile", "valuesIncluded": False, "rawExecutionDataIncluded": False, }, } if descriptor != expected_descriptor: die("Engine MCP execution profile decoder descriptor contract mismatch") source = (payload_dir / "nodedc-source/server/routes/n8n.js").read_text( encoding="utf-8" ) required_markers = ( "const compactReference = Symbol('n8nCompactReference')", "Top-level string entries are primitive values.", "valuesIncluded: false,", ) if any(marker not in source for marker in required_markers): die("Engine MCP execution profile decoder source contract mismatch") def validate_engine_mcp_telemetry_catalog_slice(payload_dir, entries): if tuple(entries) != ENGINE_MCP_TELEMETRY_CATALOG_ARTIFACT_ENTRIES: die("Engine MCP telemetry catalog files.txt exact set/order mismatch") for rel, expected_sha256 in ENGINE_MCP_TELEMETRY_CATALOG_TARGET_SHA256.items(): path = payload_dir / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine MCP telemetry catalog target is missing: {rel}") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die(f"Engine MCP telemetry catalog target sha256 mismatch: {rel}") descriptor = read_strict_json( payload_dir / ENGINE_MCP_TELEMETRY_CATALOG_DESCRIPTOR_REL, "Engine MCP telemetry catalog descriptor", max_bytes=16 * 1024, ) expected_descriptor = { "schemaVersion": "nodedc.engine.deploy-transition/v1", "id": "engine-mcp-telemetry-reading-catalog-v1", "component": "engine", "scope": "external-mcp-observability", "mcpVersion": "0.8.0", "sourcePaths": [ "nodedc-source/server/routes/n8n.js", "nodedc-source/server/routes/engineAgentGateway.js", ], "readsOnly": "normalized-attributes.sensor_readings", "returns": [ "reading-id", "safe-label", "safe-unit", "value-types", "coverage-counts", ], "neverReturns": [ "reading-value", "raw-execution-data", "raw-provider-payload", "credential-shaped-data", ], } if descriptor != expected_descriptor: die("Engine MCP telemetry catalog descriptor contract mismatch") n8n_source = (payload_dir / "nodedc-source/server/routes/n8n.js").read_text( encoding="utf-8" ) gateway_source = ( payload_dir / "nodedc-source/server/routes/engineAgentGateway.js" ).read_text(encoding="utf-8") if any( marker not in n8n_source for marker in ( "function toSafeTelemetryReadingCatalog", "key === 'sensor_readings' && pathName.endsWith('.attributes')", "valuesIncluded: false,", "rawExecutionDataIncluded: false,", ) ): die("Engine MCP telemetry catalog backend contract mismatch") if any( marker not in gateway_source for marker in ( "const ENGINE_AGENT_MCP_VERSION = '0.8.0'", "name: 'engine_get_telemetry_reading_catalog'", "/telemetry-reading-catalog?", ) ): 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_mcp_execution_plan_materialization_slice(payload_dir, entries): if tuple(entries) != ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_ARTIFACT_ENTRIES: die( "Engine MCP execution plan materialization files.txt exact " "set/order mismatch" ) for rel, expected_sha256 in ( ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_TARGET_SHA256.items() ): path = payload_dir / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine MCP execution plan materialization target is missing: {rel}") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Engine MCP execution plan materialization target sha256 " f"mismatch: {rel}" ) descriptor = read_strict_json( payload_dir / ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_DESCRIPTOR_REL, "Engine MCP execution plan materialization descriptor", max_bytes=32 * 1024, ) expected_descriptor = { "schemaVersion": "nodedc.engine.deploy-transition/v1", "id": "engine-mcp-l2-execution-plan-materialization-v1", "component": "engine", "scope": "external-mcp-l2-authoring", "mcpVersion": "0.9.0", "sourcePaths": [ "nodedc-source/server/routes/n8n.js", "nodedc-source/server/routes/engineAgentGateway.js", "nodedc-source/server/l2ExecutionPlan/catalog.js", "nodedc-source/server/l2ExecutionPlan/compiler.js", "nodedc-source/server/l2ExecutionPlan/materializer.js", "nodedc-source/server/assets/execution-plans/v1/catalog.json", ], "plan": { "tool": "engine_plan_l2_execution_plan_materialization", "readOnly": True, "returnsPlanRef": True, }, "apply": { "tool": "engine_apply_l2_execution_plan_materialization", "accepts": [ "planRef", "providerAuthRef", "confirmation", "changeRef", ], "rejectsCallerGraphOperations": True, "revalidatesTargetRevisionAndScope": True, }, "providerLogicAuthority": "trusted-provider-package", "unmanagedGraphPolicy": "explicit-adoption-required", "embeddedCodexChanged": False, } if descriptor != expected_descriptor: die("Engine MCP execution plan materialization descriptor contract mismatch") gateway_source = ( payload_dir / "nodedc-source/server/routes/engineAgentGateway.js" ).read_text(encoding="utf-8") if any( marker not in gateway_source for marker in ( "const ENGINE_AGENT_MCP_VERSION = '0.9.0'", "name: 'engine_plan_l2_execution_plan_materialization'", "name: 'engine_apply_l2_execution_plan_materialization'", "'execution-plan.apply'", "assertExactToolArguments(args, [", ) ): die("Engine MCP execution plan materialization gateway contract mismatch") materializer_source = ( payload_dir / "nodedc-source/server/l2ExecutionPlan/materializer.js" ).read_text(encoding="utf-8") compiler_source = ( payload_dir / "nodedc-source/server/l2ExecutionPlan/compiler.js" ).read_text(encoding="utf-8") if any( marker not in materializer_source for marker in ( "status: 'adoption_required'", "type: 'replaceGraph'", "assignCredentialBindingInternal", "execution_plan_materialization_equality_failed", ) ): die("Engine MCP execution plan materializer contract mismatch") if any( marker not in compiler_source for marker in ( "compileExecutionPlanGraph", "materializedGraphEqualityDigest", "ndc.semantic-mapping", "providerPackage.publisher.nodeType", ) ): die("Engine MCP execution plan compiler contract mismatch") if re.search(r"gelios|robot2b", materializer_source, re.IGNORECASE) or re.search( r"gelios|robot2b", compiler_source, re.IGNORECASE, ): die("Engine MCP execution plan runtime contains provider hardcode") node_intelligence_descriptor = read_engine_node_intelligence_descriptor( payload_dir / ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL, "Engine MCP execution plan materialization 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_EXECUTION_PLAN_MATERIALIZATION_TARGET_SHA256[ ENGINE_NODE_INTELLIGENCE_GATEWAY_REL ] ): die( "Engine MCP execution plan materialization node-intelligence " "attestation contract mismatch" ) def validate_engine_mcp_execution_plan_telemetry_runtime_slice( payload_dir, entries, ): if ( tuple(entries) != ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_ARTIFACT_ENTRIES ): die( "Engine MCP execution plan telemetry runtime files.txt exact " "set/order mismatch" ) for rel, expected_sha256 in ( ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_TARGET_SHA256.items() ): path = payload_dir / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP execution plan telemetry runtime target is " f"missing: {rel}" ) if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Engine MCP execution plan telemetry runtime target sha256 " f"mismatch: {rel}" ) descriptor = read_strict_json( payload_dir / ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_DESCRIPTOR_REL, "Engine MCP execution plan telemetry runtime descriptor", max_bytes=32 * 1024, ) expected_descriptor = { "schemaVersion": "nodedc.engine.deploy-transition/v1", "id": "engine-mcp-l2-execution-plan-telemetry-runtime-v2", "component": "engine", "scope": "external-mcp-l2-authoring-runtime", "mcpVersion": "0.9.0", "sourcePaths": [ "nodedc-source/server/l2ExecutionPlan/compiler.js", "nodedc-source/server/assets/execution-plans/v1/catalog.json", ], "compilerTransition": { "predecessor": "1.1.0", "supported": ["1.1.0", "1.2.0"], "legacyRuntimePreserved": True, }, "telemetryInputBoundary": { "sources": [ "declared-sensor-values", "declared-message-parameters", ], "authorization": [ "trusted-telemetry-projection", "visible-sensor-definition", ], "unprojectedParameters": "discarded", "rawProviderPayloadAtPublish": "forbidden", "conversion": "bounded-declared-conversion-table", }, "providerLogicAuthority": "trusted-provider-package", "engineProviderHardcode": False, "embeddedCodexChanged": False, } if descriptor != expected_descriptor: die( "Engine MCP execution plan telemetry runtime descriptor " "contract mismatch" ) compiler_source = ( payload_dir / "nodedc-source/server/l2ExecutionPlan/compiler.js" ).read_text(encoding="utf-8") catalog = read_strict_json( payload_dir / "nodedc-source/server/assets/execution-plans/v1/catalog.json", "Engine execution plan catalog", max_bytes=512 * 1024, ) required_markers = ( "if (compilerVersion === '1.1.0')", "if (compilerVersion !== '1.2.0')", '"msgParam", "msg_param"', "descriptor.telemetryProjection", '"message-param"', "convertedSensorValue", "visibleSensorDefinition", ) if any(marker not in compiler_source for marker in required_markers): die("Engine MCP execution plan telemetry runtime contract mismatch") if catalog.get("runtime", {}).get("compilerVersions") != ["1.1.0", "1.2.0"]: die("Engine MCP execution plan telemetry runtime catalog mismatch") if re.search(r"gelios|robot2b", compiler_source, re.IGNORECASE): die("Engine MCP execution plan telemetry runtime contains provider hardcode") def validate_engine_mcp_execution_plan_module_ownership_slice( payload_dir, entries, ): if ( tuple(entries) != ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_ARTIFACT_ENTRIES ): die( "Engine MCP execution plan module ownership files.txt exact " "set/order mismatch" ) for rel, expected_sha256 in ( ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_TARGET_SHA256.items() ): path = payload_dir / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP execution plan module ownership target is " f"missing: {rel}" ) if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Engine MCP execution plan module ownership target sha256 " f"mismatch: {rel}" ) descriptor = read_strict_json( payload_dir / ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_DESCRIPTOR_REL, "Engine MCP execution plan module ownership descriptor", max_bytes=32 * 1024, ) expected_descriptor = { "schemaVersion": "nodedc.engine.deploy-transition/v1", "id": "engine-mcp-l2-execution-plan-module-ownership-v3", "component": "engine", "scope": "external-mcp-l2-authoring-runtime", "mcpVersion": "0.10.0", "sourcePaths": [ "nodedc-source/server/l2ExecutionPlan/materializer.js", "nodedc-source/server/routes/engineAgentGateway.js", "nodedc-source/server/assets/execution-plans/v1/catalog.json", ], "moduleOwnership": { "schemaVersion": "nodedc.engine.materialized-execution-plan-module/v1", "adoptionStrategy": "adopt_existing_module", "reconciliationStrategy": "create_or_reconcile_owned", "nodeBindings": "exact-one-to-one", "retirementScope": "closed-module-boundary", "unrelatedNodes": "preserved", "unrelatedEdges": "preserved", "internalEdges": "server-derived-from-trusted-plan", "wholeGraphReplacement": "not-required", }, "sharedBoundary": { "runtimeKinds": ["ndc.manual-trigger"], "nodeTypes": ["n8n-nodes-base.webhook"], "compatibility": "same-node-type-and-http-method", "configurationAuthority": "existing-graph", "providerCredentialNodes": "must-be-engine-managed", "publisherNodes": "must-be-engine-managed", }, "providerPackageTrust": { "addedPackageId": "gelios.provider.v9", "addedPackageDigest": "sha256:7dd4ce4a45ce76e884ffa1e304bfaa521f552e9a45e535930f2d17b882ae23ed", "legacyPackageId": "gelios.provider.v8", "legacyPackagePreserved": True, "telemetryRegistryDigest": "sha256:8338dd1f3e757dae5a0fc558e41b3a5cc6395c33c94841ce4d2af976e1911381", "telemetryProjectionDigest": "sha256:ecdafb13173338f10043a08603ddd5cd1f6091f1c54d7fbb7e8a9606fbbdef65", "providerLogicAuthority": "trusted-provider-package", }, "engineProviderHardcode": False, "embeddedCodexChanged": False, } if descriptor != expected_descriptor: die( "Engine MCP execution plan module ownership descriptor " "contract mismatch" ) materializer_source = ( payload_dir / "nodedc-source/server/l2ExecutionPlan/materializer.js" ).read_text(encoding="utf-8") gateway_source = ( payload_dir / "nodedc-source/server/routes/engineAgentGateway.js" ).read_text(encoding="utf-8") for marker in ( "nodedc.engine.materialized-execution-plan-module/v1", "adopt_existing_module", "preserveBoundNodeIds", "execution_plan_module_retire_boundary_not_closed", "manual_webhook_same_method", ): if marker not in materializer_source: die( "Engine MCP execution plan module ownership contract " f"marker missing: {marker}" ) if ( "const ENGINE_AGENT_MCP_VERSION = '0.10.0'" not in gateway_source or "preserveBoundNodeIds" not in gateway_source or "adopt_existing_module" not in gateway_source ): die("Engine MCP execution plan module ownership gateway mismatch") if re.search(r"gelios|robot2b", materializer_source, re.IGNORECASE): die("Engine MCP execution plan module ownership contains provider hardcode") catalog = read_strict_json( payload_dir / "nodedc-source/server/assets/execution-plans/v1/catalog.json", "Engine execution plan catalog", max_bytes=512 * 1024, ) package_by_id = { package.get("id"): package for package in catalog.get("packages", []) if isinstance(package, dict) } if ( package_by_id.get("gelios.provider.v9", {}).get("contractDigest") != "sha256:7dd4ce4a45ce76e884ffa1e304bfaa521f552e9a45e535930f2d17b882ae23ed" or "gelios.provider.v8" not in package_by_id ): die("Engine MCP execution plan module ownership catalog mismatch") node_intelligence_descriptor = read_engine_node_intelligence_descriptor( payload_dir / ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL, "Engine MCP execution plan module ownership 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_EXECUTION_PLAN_MODULE_OWNERSHIP_TARGET_SHA256[ ENGINE_NODE_INTELLIGENCE_GATEWAY_REL ] ): die( "Engine MCP execution plan module ownership node-intelligence " "attestation contract mismatch" ) def validate_engine_mcp_normalized_identity_search_slice(payload_dir, entries): if tuple(entries) != ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_ARTIFACT_ENTRIES: die( "Engine MCP normalized identity search files.txt exact set/order " "mismatch" ) for rel, expected_sha256 in ( ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_TARGET_SHA256.items() ): path = payload_dir / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine MCP normalized identity search target is missing: {rel}") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Engine MCP normalized identity search target sha256 mismatch: " f"{rel}" ) descriptor = read_strict_json( payload_dir / ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_DESCRIPTOR_REL, "Engine MCP normalized identity search descriptor", max_bytes=32 * 1024, ) expected_descriptor = { "schemaVersion": "nodedc.engine.deploy-transition/v1", "id": "engine-mcp-normalized-identity-search-v1", "component": "engine", "scope": "external-mcp-readonly-identity-discovery", "mcpVersion": "0.11.0", "sourcePaths": [ "nodedc-source/server/routes/n8n.js", "nodedc-source/server/routes/engineAgentGateway.js", "nodedc-source/server/l2ExecutionPlan/compiler.js", "nodedc-source/server/assets/execution-plans/v1/catalog.json", "nodedc-source/server/assets/provider-packages/v1/catalog.json", ], "normalizedFactSearch": { "tool": "engine_find_normalized_subjects", "authority": "granted-execution-normalized-facts", "matchModes": ["exact", "prefix", "contains"], "queryScopes": ["canonical-fields", "point-bounding-box"], "fullAdministrativeIdentifiers": True, "rawExecutionData": False, "rawProviderPayload": False, "commandSurface": False, }, "executionPlanRuntime": { "compilerVersionAdded": "1.4.0", "legacyCompilerVersionsPreserved": ["1.1.0", "1.2.0", "1.3.0"], "derivationKindsAdded": [ "bounded_string_list", "bounded_named_values", ], "providerSpecificLogicInEngine": False, }, "providerPackageTrust": { "addedPackageId": "gelios.provider.v11", "addedPackageDigest": "sha256:4bf211d9ed6cfd227df444d49d8c5ce77a24389ed2ba3d159d53d83e0d4738d9", "identityCapabilityId": "gelios.units.identity.read", "identityDataProductId": "fleet.units.identity.current.v1", "legacyPackagesPreserved": True, }, "reverseGeocodingChanged": False, "embeddedCodexChanged": False, } if descriptor != expected_descriptor: die("Engine MCP normalized identity search descriptor contract mismatch") gateway_source = ( payload_dir / ENGINE_NODE_INTELLIGENCE_GATEWAY_REL ).read_text(encoding="utf-8") route_source = ( payload_dir / "nodedc-source/server/routes/n8n.js" ).read_text(encoding="utf-8") compiler_source = ( payload_dir / "nodedc-source/server/l2ExecutionPlan/compiler.js" ).read_text(encoding="utf-8") for marker in ( "engine_find_normalized_subjects", "const ENGINE_AGENT_MCP_VERSION = '0.11.0'", ): if marker not in gateway_source: die( "Engine MCP normalized identity search gateway marker missing: " f"{marker}" ) for marker in ( "normalized-fact-search", "normalizedFactsOnly: true", "rawExecutionDataIncluded: false", "commandSurfaceIncluded: false", ): if marker not in route_source: die( "Engine MCP normalized identity search route marker missing: " f"{marker}" ) for marker in ("boundedStringList", "boundedNamedValues", "'1.4.0'"): if marker not in compiler_source: die( "Engine MCP normalized identity search compiler marker missing: " f"{marker}" ) if re.search(r"gelios|robot2b", compiler_source, re.IGNORECASE): die("Engine MCP normalized identity search contains provider hardcode") execution_catalog = read_strict_json( payload_dir / "nodedc-source/server/assets/execution-plans/v1/catalog.json", "Engine execution plan catalog", max_bytes=1024 * 1024, ) package_by_id = { package.get("id"): package for package in execution_catalog.get("packages", []) if isinstance(package, dict) } gelios_v11 = package_by_id.get("gelios.provider.v11", {}) if ( execution_catalog.get("runtime", {}).get("compilerVersions") != ["1.1.0", "1.2.0", "1.3.0", "1.4.0"] or gelios_v11.get("contractDigest") != "sha256:4bf211d9ed6cfd227df444d49d8c5ce77a24389ed2ba3d159d53d83e0d4738d9" or not any( profile.get("dataProductId") == "fleet.units.identity.current.v1" for profile in gelios_v11.get("profiles", []) if isinstance(profile, dict) ) ): die("Engine MCP normalized identity search execution catalog mismatch") security_catalog = read_strict_json( payload_dir / "nodedc-source/server/assets/provider-packages/v1/catalog.json", "Engine provider security catalog", max_bytes=512 * 1024, ) security_package = next( ( package for package in security_catalog.get("packages", []) if isinstance(package, dict) and package.get("id") == "gelios.provider.v11" ), {}, ) capabilities = security_package.get("capabilities", []) if ( len(capabilities) != 1 or capabilities[0].get("id") != "gelios.units.identity.read" or capabilities[0].get("classification") != "read" or capabilities[0].get("dataProductIds") != ["fleet.units.identity.current.v1"] ): die("Engine MCP normalized identity search security catalog mismatch") node_intelligence_descriptor = read_engine_node_intelligence_descriptor( payload_dir / ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL, "Engine MCP normalized identity search 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_NORMALIZED_IDENTITY_SEARCH_TARGET_SHA256[ ENGINE_NODE_INTELLIGENCE_GATEWAY_REL ] ): die( "Engine MCP normalized identity search node-intelligence " "attestation contract mismatch" ) def validate_engine_mcp_l1_credential_reuse_slice(payload_dir, entries): if tuple(entries) != ENGINE_MCP_L1_CREDENTIAL_REUSE_ARTIFACT_ENTRIES: die( "Engine MCP L1 credential reuse files.txt exact set/order " "mismatch" ) for rel, expected_sha256 in ( ENGINE_MCP_L1_CREDENTIAL_REUSE_TARGET_SHA256.items() ): path = payload_dir / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine MCP L1 credential reuse target is missing: {rel}") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Engine MCP L1 credential reuse target sha256 mismatch: " f"{rel}" ) descriptor = read_strict_json( payload_dir / ENGINE_MCP_L1_CREDENTIAL_REUSE_DESCRIPTOR_REL, "Engine MCP L1 credential reuse descriptor", max_bytes=32 * 1024, ) expected_descriptor = { "schemaVersion": "nodedc.engine.deploy-transition/v1", "id": "engine-mcp-l1-credential-reuse-v1", "component": "engine", "scope": "external-mcp-l2-authoring", "mcpVersion": "0.11.0", "sourcePath": "nodedc-source/server/routes/n8n.js", "visibilityProof": { "scope": "same-l1-workflow", "source": "local-registry-provenance", "instanceScope": "preserved", }, "binding": { "listTool": "engine_list_l2_credential_refs", "applyOperation": "assignCredentialRef", "returns": ["label", "status", "scope", "opaque-ref"], "neverReturns": [ "native-credential-id", "logical-credential-id", "credential-data", "credential-value", ], }, "transportPolicy": { "httpsRequired": True, "redirectsDisabled": True, "hostAuthority": [ "credential-allowlist", "same-l1-observed-host", ], }, "managedGrants": "target-local", "crossL1Sharing": False, } if descriptor != expected_descriptor: die("Engine MCP L1 credential reuse descriptor contract mismatch") route_source = ( payload_dir / "nodedc-source/server/routes/n8n.js" ).read_text(encoding="utf-8") for marker in ( "function engineAgentCredentialMayReuseWithinL1", "function buildEngineAgentL1CredentialContext", "return 'l1'", "candidateScope === 'l1' ? l1Context.graph : graph", "managed writer/reader grants", ): if marker not in route_source: die( "Engine MCP L1 credential reuse route marker missing: " f"{marker}" ) if ( "LEGACY_CREDENTIAL_SINK_OWNER" not in route_source or "DATA_PRODUCT_PUBLISH_GRANT_OWNER" not in route_source or "DATA_PRODUCT_READ_GRANT_OWNER" not in route_source ): die("Engine MCP L1 credential reuse managed-grant boundary missing") def validate_engine_mcp_l1_credential_provenance_slice(payload_dir, entries): if tuple(entries) != ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_ARTIFACT_ENTRIES: die( "Engine MCP L1 credential provenance files.txt exact set/order " "mismatch" ) for rel, expected_sha256 in ( ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_TARGET_SHA256.items() ): path = payload_dir / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine MCP L1 credential provenance target is missing: {rel}") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Engine MCP L1 credential provenance target sha256 mismatch: " f"{rel}" ) descriptor = read_strict_json( payload_dir / ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_DESCRIPTOR_REL, "Engine MCP L1 credential provenance descriptor", max_bytes=32 * 1024, ) expected_descriptor = { "schemaVersion": "nodedc.engine.deploy-transition/v1", "id": "engine-mcp-l1-credential-provenance-v2", "component": "engine", "scope": "external-mcp-l2-authoring", "mcpVersion": "0.11.0", "sourcePath": "nodedc-source/server/routes/n8n.js", "predecessor": "engine-mcp-l1-credential-reuse-v1", "visibilityProof": { "scope": "same-l1-workflow", "localSources": ["manual", "workflow-ref", "credentials-file"], "referencedSourceRequiresSyncPayload": True, "logicalKeyEqualityRequired": True, }, "candidateBoundary": { "commonEntryMustBeReusable": True, "managedEntriesAllowed": False, "managedGrants": "target-local", }, "binding": { "listTool": "engine_list_l2_credential_refs", "applyOperation": "assignCredentialRef", "opaqueRefOnly": True, "credentialIdsReturned": False, "credentialValuesReturned": False, }, "transportPolicy": { "httpsRequired": True, "redirectsDisabled": True, "hostAuthority": [ "credential-allowlist", "same-l1-observed-host", ], }, "crossL1Sharing": False, } if descriptor != expected_descriptor: die("Engine MCP L1 credential provenance descriptor contract mismatch") route_source = ( payload_dir / "nodedc-source/server/routes/n8n.js" ).read_text(encoding="utf-8") for marker in ( "function engineAgentCredentialMayProveL1Provenance", "return isGlobalRegistryEntryAllowed(entry)", "engineAgentCredentialMayProveL1Provenance(item)", "The common candidate itself is", ): if marker not in route_source: die( "Engine MCP L1 credential provenance route marker missing: " f"{marker}" ) def validate_engine_mcp_execution_plan_sandbox_runtime_slice( payload_dir, entries, ): if ( tuple(entries) != ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_ARTIFACT_ENTRIES ): die( "Engine MCP execution plan sandbox runtime files.txt exact " "set/order mismatch" ) for rel, expected_sha256 in ( ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_TARGET_SHA256.items() ): path = payload_dir / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP execution plan sandbox runtime target is " f"missing: {rel}" ) if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Engine MCP execution plan sandbox runtime target sha256 " f"mismatch: {rel}" ) descriptor = read_strict_json( payload_dir / ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_DESCRIPTOR_REL, "Engine MCP execution plan sandbox runtime descriptor", max_bytes=32 * 1024, ) expected_descriptor = { "schemaVersion": "nodedc.engine.deploy-transition/v1", "id": "engine-mcp-l2-execution-plan-sandbox-runtime-v4", "component": "engine", "scope": "external-mcp-l2-authoring-runtime", "mcpVersion": "0.11.0", "sourcePath": "nodedc-source/server/l2ExecutionPlan/compiler.js", "predecessor": "engine-mcp-l1-credential-provenance-v2", "runtimeCompatibility": { "n8nVersion": "2.3.2", "codeNodeMode": "runOnceForAllItems", "sandboxGlobalDependencies": [], "responseByteBudget": "bounded-pure-javascript-utf8", "returnedItems": "n8n-object-json-envelope", }, "dataBoundary": { "providerResponses": "bounded-by-trusted-profile", "rawProviderPayloadAtPublish": "forbidden", "canonicalFactsOnlyAtPublish": True, }, "providerLogicAuthority": "trusted-provider-package", "engineProviderHardcode": False, "n8nCoreChanged": False, "l1Changed": False, "credentialsChanged": False, } if descriptor != expected_descriptor: die("Engine MCP execution plan sandbox runtime descriptor mismatch") compiler = ( payload_dir / "nodedc-source/server/l2ExecutionPlan/compiler.js" ).read_text(encoding="utf-8") for marker in ( "const encodedBytes = (value) => {", "encoded.charCodeAt(index)", "length += 4", "return extracted.map((source) => ({ json:", ): if marker not in compiler: die( "Engine MCP execution plan sandbox runtime compiler marker " f"missing: {marker}" ) if ( "new TextEncoder().encode(JSON.stringify(value ?? null))" in compiler or re.search(r"gelios|robot2b", compiler, re.IGNORECASE) ): die( "Engine MCP execution plan sandbox runtime provider-neutral " "boundary mismatch" ) def validate_engine_mcp_gelios_items_envelope_slice(payload_dir, entries): if tuple(entries) != ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_ARTIFACT_ENTRIES: die( "Engine MCP Gelios items envelope files.txt exact set/order " "mismatch" ) for rel, expected_sha256 in ( ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_TARGET_SHA256.items() ): path = payload_dir / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine MCP Gelios items envelope target is missing: {rel}") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Engine MCP Gelios items envelope target sha256 mismatch: " f"{rel}" ) descriptor = read_strict_json( payload_dir / ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_DESCRIPTOR_REL, "Engine MCP Gelios items envelope descriptor", max_bytes=32 * 1024, ) expected_descriptor = { "schemaVersion": "nodedc.engine.deploy-transition/v1", "id": "engine-mcp-gelios-items-envelope-v12", "component": "engine", "scope": "external-mcp-provider-package-catalog", "mcpVersion": "0.11.0", "predecessor": "engine-mcp-l2-execution-plan-sandbox-runtime-v4", "providerPackageTransition": { "from": "gelios.provider.v11", "to": "gelios.provider.v12", "capabilityId": "gelios.units.identity.read", "dataProductId": "fleet.units.identity.current.v1", }, "responseEnvelope": { "collectionPath": "items", "evidence": "bounded-structural-profile", "observedCardinality": 107, "rawValuesCaptured": False, }, "catalogPolicy": { "historicalExecutionPackagePreserved": "gelios.provider.v11", "activeSecurityAuthority": "gelios.provider.v12", "ambiguousIdentityAuthority": "forbidden", }, "boundaries": { "compilerChanged": False, "n8nCoreChanged": False, "l1Changed": False, "ontologyChanged": False, "credentialsChanged": False, "rawProviderValuesIncluded": False, }, } if descriptor != expected_descriptor: die("Engine MCP Gelios items envelope descriptor mismatch") execution_catalog = read_strict_json( payload_dir / "nodedc-source/server/assets/execution-plans/v1/catalog.json", "Engine MCP Gelios items envelope execution catalog", max_bytes=2 * 1024 * 1024, ) security_catalog = read_strict_json( payload_dir / "nodedc-source/server/assets/provider-packages/v1/catalog.json", "Engine MCP Gelios items envelope security catalog", max_bytes=512 * 1024, ) execution_packages = { item.get("id"): item for item in execution_catalog.get("packages", []) if isinstance(item, dict) } security_packages = [ item for item in security_catalog.get("packages", []) if isinstance(item, dict) ] gelios_v12 = execution_packages.get("gelios.provider.v12", {}) identity_profiles = [ profile for profile in gelios_v12.get("profiles", []) if ( profile.get("id") == "gelios.units.identity.warm.v1" and profile.get("dataProductId") == "fleet.units.identity.current.v1" ) ] active_identity = [ item for item in security_packages if item.get("id") == "gelios.provider.v12" ] if ( "gelios.provider.v11" not in execution_packages or gelios_v12.get("version") != "12.0.0" or len(identity_profiles) != 1 or any(item.get("id") == "gelios.provider.v11" for item in security_packages) or len(active_identity) != 1 or [ capability.get("id") for capability in active_identity[0].get("capabilities", []) ] != ["gelios.units.identity.read"] ): die("Engine MCP Gelios items envelope catalog authority mismatch") def validate_engine_mcp_registered_execution_profiles_slice( payload_dir, entries, ): if ( tuple(entries) != ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_ARTIFACT_ENTRIES ): die( "Engine MCP registered execution profiles files.txt exact " "set/order mismatch" ) for rel, expected_sha256 in ( ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_TARGET_SHA256.items() ): path = payload_dir / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP registered execution profiles target is missing: " f"{rel}" ) if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Engine MCP registered execution profiles target sha256 " f"mismatch: {rel}" ) descriptor = read_strict_json( payload_dir / ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_DESCRIPTOR_REL, "Engine MCP registered execution profiles descriptor", max_bytes=32 * 1024, ) expected_descriptor = { "schemaVersion": "nodedc.engine.deploy-transition/v1", "id": "engine-mcp-registered-execution-profiles-v2", "component": "engine", "predecessor": "engine-mcp-gelios-items-envelope-v12", "scope": "external-mcp-registered-execution-profiles-with-node-intelligence-attestation", "mcpVersion": "0.12.0", "catalog": { "schemaVersion": "nodedc.engine.execution-plan-catalog/v1", "registeredProfiles": 6, "profileRefs": "actor-and-target-scoped-opaque-ttl", "executionPlanDescriptorsIncluded": False, "providerEndpointsIncluded": False, "customerScopeIncluded": False, "credentialIdentitiesIncluded": False, "credentialValuesIncluded": False, }, "tools": { "discovery": "engine_list_l2_execution_profiles", "planning": "engine_plan_registered_l2_execution", "apply": "engine_apply_l2_execution_plan_materialization", }, "planning": { "packageResolution": "server-owned-catalog", "targetScope": "server-derived", "digests": "server-derived", "providerAuth": "target-local-compatible-refs", "materializer": "existing-immutable-two-phase", }, "nodeIntelligenceAttestation": { "releaseId": ENGINE_NODE_INTELLIGENCE_RELEASE_ID, "previousGatewaySha256": "4a9524fd954320277042c783b7b19cbb2172f075f27652c0eebfd743ffc47872", "targetGatewaySha256": "8f04edc11251de86b825351c92338be9537207887cf802474b6b6d8c3cce4077", "sidecarImageChanged": False, "nodeIntelligenceSourceChanged": False, }, "foundationSha256": { "nodedc-source/server/l2ExecutionPlan/compiler.js": "64f5196a83018505c6dac77a0f8674c27941257a874eed9b024c1c94f169be2b", "nodedc-source/server/l2ExecutionPlan/materializer.js": "dabf0073520049d7a04d1962b29e591ae092b87b287a50534ad2d98b03ae683c", "nodedc-source/server/deployTransitions/geliosItemsEnvelopeV12.json": "16e39f54ebae776de9acfdf0078c79291310eeca5a1f720eb8b82496f4d419a3", }, "boundaries": { "n8nCoreChanged": False, "l1Changed": False, "l2GraphChanged": False, "engineUiChanged": False, "databaseChanged": False, "credentialsChanged": False, "foundryChanged": False, "ontologyChanged": False, "mcpNginxChanged": False, "embeddedAiWorkspaceChanged": False, "nodeIntelligenceAttestationChanged": True, "nodeIntelligenceImageChanged": False, "nodeIntelligenceSourceChanged": False, }, } if descriptor != expected_descriptor: die("Engine MCP registered execution profiles descriptor mismatch") node_intelligence_descriptor = read_engine_node_intelligence_descriptor( payload_dir / ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL, "Engine MCP registered execution profiles 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_REGISTERED_EXECUTION_PROFILES_TARGET_SHA256[ ENGINE_NODE_INTELLIGENCE_GATEWAY_REL ] ): die( "Engine MCP registered execution profiles node-intelligence " "attestation mismatch" ) catalog = read_strict_json( payload_dir / "nodedc-source/server/assets/execution-plans/v1/catalog.json", "Engine MCP registered execution profiles catalog", max_bytes=4 * 1024 * 1024, ) registered = [ (provider_package, profile) for provider_package in catalog.get("packages", []) if isinstance(provider_package, dict) for profile in provider_package.get("profiles", []) if ( isinstance(profile, dict) and isinstance(profile.get("executionPlanTemplate"), dict) ) ] if len(registered) != 6: die("Engine MCP registered execution profiles count mismatch") for provider_package, profile in registered: template = profile["executionPlanTemplate"] if ( profile.get("cadence") not in {"hot", "warm", "cold", "on_demand"} or profile.get("dataClass") not in {"operational", "restricted", "internal", "unclassified"} or template.get("compilerVersion") != "1.4.0" or template.get("package", {}).get("id") != provider_package.get("id") or template.get("connection", {}).get("collectionProfileId") != profile.get("id") or template.get("collectionProfile", {}).get("dataProductId") != profile.get("dataProductId") or not str( template.get("bindings", {}) .get("provider", {}) .get("reference", "") ).startswith("ndc-credref:catalog-build-") ): die("Engine MCP registered execution profile template mismatch") gateway = ( payload_dir / "nodedc-source/server/routes/engineAgentGateway.js" ).read_text(encoding="utf-8") resolver = ( payload_dir / "nodedc-source/server/l2ExecutionPlan/registeredProfiles.js" ).read_text(encoding="utf-8") for marker in ( "const ENGINE_AGENT_MCP_VERSION = '0.12.0'", "engine_list_l2_execution_profiles", "engine_plan_registered_l2_execution", ): if marker not in gateway: die( "Engine MCP registered execution profiles gateway marker " f"missing: {marker}" ) if ( "registered_execution_profile_ref_scope_denied" not in resolver or "targetBoundPlan" not in resolver or re.search(r"gelios|robot2b", resolver, re.IGNORECASE) ): die("Engine MCP registered execution profiles resolver boundary mismatch") def validate_engine_mcp_gelios_units_items_slice(payload_dir, entries): if tuple(entries) != ENGINE_MCP_GELIOS_UNITS_ITEMS_ARTIFACT_ENTRIES: die( "Engine MCP Gelios units items envelope files.txt exact " "set/order mismatch" ) for rel, expected_sha256 in ( ENGINE_MCP_GELIOS_UNITS_ITEMS_TARGET_SHA256.items() ): path = payload_dir / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine MCP Gelios units items envelope target missing: {rel}") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Engine MCP Gelios units items envelope target sha256 " f"mismatch: {rel}" ) descriptor = read_strict_json( payload_dir / ENGINE_MCP_GELIOS_UNITS_ITEMS_DESCRIPTOR_REL, "Engine MCP Gelios units items envelope descriptor", max_bytes=32 * 1024, ) if descriptor != { "schemaVersion": "nodedc.engine.deploy-transition/v1", "id": "engine-mcp-gelios-units-items-envelope-v12-patch1", "component": "engine", "predecessor": "engine-mcp-registered-execution-profiles-v2", "scope": "server-owned-registered-profile-envelope-correction", "mcpVersion": "0.12.0", "providerPackage": { "id": "gelios.provider.v12", "predecessorVersion": "12.0.0", "targetVersion": "12.0.1", "capabilityId": "gelios.units.current.read", "responseCollectionPath": "items", }, "registeredProfile": { "id": "gelios.units.profile.cold.v1", "dataProductId": "fleet.units.profile.current.v1", "compilerVersion": "1.4.0", "materializer": "existing-immutable-two-phase", }, "foundationSha256": dict( ENGINE_MCP_GELIOS_UNITS_ITEMS_FOUNDATION_SHA256 ), "boundaries": { "n8nCoreChanged": False, "l1Changed": False, "l2GraphChanged": False, "engineRuntimeCodeChanged": False, "engineUiChanged": False, "databaseChanged": False, "credentialsChanged": False, "foundryChanged": False, "ontologyChanged": False, "mcpNginxChanged": False, "embeddedAiWorkspaceChanged": False, "providerEndpointsChanged": False, }, }: die("Engine MCP Gelios units items envelope descriptor mismatch") execution_catalog = read_strict_json( payload_dir / "nodedc-source/server/assets/execution-plans/v1/catalog.json", "Engine MCP Gelios units items envelope execution catalog", max_bytes=4 * 1024 * 1024, ) execution_packages = [ item for item in execution_catalog.get("packages", []) if item.get("id") == "gelios.provider.v12" ] if len(execution_packages) != 1: die("Engine MCP Gelios units items execution package cardinality mismatch") provider_package = execution_packages[0] profiles = provider_package.get("profiles", []) technical_profiles = [ item for item in profiles if item.get("id") == "gelios.units.profile.cold.v1" ] units_profiles = [ item for item in profiles if "gelios.units.current.read" in item.get("capabilityIds", []) ] if ( provider_package.get("version") != "12.0.1" or len(profiles) != 6 or len(technical_profiles) != 1 or len(units_profiles) != 4 ): die("Engine MCP Gelios units items registered profile mismatch") for profile in profiles: template = profile.get("executionPlanTemplate", {}) if template.get("compilerVersion") != "1.4.0": die("Engine MCP Gelios units items compiler version drift") for profile in units_profiles: extract_steps = [ step for step in ( profile.get("executionPlanTemplate", {}).get("steps", []) ) if ( step.get("kind") == "extract_items" and step.get("config", {}).get("capabilityId") == "gelios.units.current.read" ) ] if ( len(extract_steps) != 1 or extract_steps[0].get("config", {}) .get("response", {}).get("collectionPaths", [None])[:1] != ["items"] ): die( "Engine MCP Gelios units items response envelope mismatch: " f"{profile.get('id')}" ) security_catalog = read_strict_json( payload_dir / "nodedc-source/server/assets/provider-packages/v1/catalog.json", "Engine MCP Gelios units items security catalog", max_bytes=2 * 1024 * 1024, ) security_packages = [ item for item in security_catalog.get("packages", []) if item.get("id") == "gelios.provider.v12" ] if ( len(security_packages) != 1 or security_packages[0].get("version") != "12.0.1" or [ capability.get("id") for capability in security_packages[0].get("capabilities", []) ] != ["gelios.units.identity.read"] ): die("Engine MCP Gelios units items security authority mismatch") def validate_engine_agent_full_grant_migration_slice(payload_dir, entries): if tuple(entries) != ENGINE_AGENT_FULL_GRANT_MIGRATION_ARTIFACT_ENTRIES: die("Engine agent full grant migration files.txt exact set/order mismatch") store_path = payload_dir / ENGINE_AGENT_FULL_GRANT_MIGRATION_STORE_REL try: store_source = store_path.read_text(encoding="utf-8") except (OSError, UnicodeDecodeError): die("Engine agent full grant migration store is unreadable") if sha256_file(store_path) != ENGINE_AGENT_FULL_GRANT_MIGRATION_TARGET_SHA256: die("Engine agent full grant migration target sha256 mismatch") required = ( "const STORE_VERSION = 2", "export const ENGINE_AGENT_FULL_DEVELOPER_PROFILE = 'full-developer'", "export const ENGINE_AGENT_CUSTOM_PROFILE = 'custom'", "const LEGACY_FULL_DEVELOPER_SCOPES = Object.freeze([", "'engine:l2:data-product-publish-grant:plan'", "'engine:l2:data-product-publish-grant:write'", "const migrateLegacyFullDeveloper = sourceVersion === 1", "LEGACY_FULL_DEVELOPER_SCOPES.every((scope) => scopes.includes(scope))", "profile === ENGINE_AGENT_FULL_DEVELOPER_PROFILE ? [...ENGINE_AGENT_SCOPES] : scopes", "throw new Error('engine_agent_store_version_unsupported')", ) if any(value not in store_source for value in required): die("Engine agent full grant migration contract mismatch") if re.search(r"gelios|robot2b", store_source, re.IGNORECASE): die("Engine agent full grant migration provider logic is forbidden") def current_engine_n8n_transition_descriptor(): path = component_root("engine") / ENGINE_N8N_TRANSITION_DESCRIPTOR_REL if not path.exists(): return None if path.is_symlink() or not path.is_file(): die("installed Engine n8n transition descriptor is unsafe") return read_engine_n8n_transition_descriptor(path, "installed Engine n8n transition descriptor") def current_engine_node_intelligence_descriptor(): path = component_root("engine") / ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL if not path.exists(): return None if path.is_symlink() or not path.is_file(): die("installed Engine node-intelligence descriptor is unsafe") return read_engine_node_intelligence_descriptor( path, "installed Engine node-intelligence descriptor", ) def validate_installed_engine_node_intelligence_source( descriptor, expected_gateway_sha256=None, ): if descriptor is None or descriptor.get("action") != "activate": die("active Engine node-intelligence descriptor is required") root = component_root("engine") source_root = root / ENGINE_NODE_INTELLIGENCE_SOURCE_REL expected_source_files = { "catalog.js": descriptor["source"]["catalogSha256"], "upstreamMcpClient.js": descriptor["source"]["upstreamClientSha256"], "upstreamProjection.js": descriptor["source"]["upstreamProjectionSha256"], } try: source_stat = source_root.lstat() except FileNotFoundError: die("installed Engine node-intelligence source is missing") if stat.S_ISLNK(source_stat.st_mode) or not stat.S_ISDIR(source_stat.st_mode): die("installed Engine node-intelligence source is unsafe") actual_files = { child.relative_to(source_root).as_posix() for child in source_root.rglob("*") if child.is_file() } if actual_files != set(expected_source_files): die("installed Engine node-intelligence source file set mismatch") for rel, expected_sha256 in expected_source_files.items(): path = source_root / rel if path.is_symlink() or sha256_file(path) != expected_sha256: die(f"installed Engine node-intelligence source drift detected: {rel}") gateway_sha256 = ( expected_gateway_sha256 if expected_gateway_sha256 is not None else descriptor["source"]["gatewaySha256"] ) if not re.fullmatch(r"[a-f0-9]{64}", str(gateway_sha256)): die("installed Engine node-intelligence gateway expectation is invalid") gateway = root / ENGINE_NODE_INTELLIGENCE_GATEWAY_REL if gateway.is_symlink() or sha256_file(gateway) != gateway_sha256: die("installed Engine node-intelligence gateway drift detected") override = root / ENGINE_NODE_INTELLIGENCE_OVERRIDE_REL try: override_stat = override.lstat() override_text = override.read_text(encoding="utf-8") except (FileNotFoundError, OSError, UnicodeDecodeError): die("installed Engine node-intelligence Compose override is missing") if stat.S_ISLNK(override_stat.st_mode) or not stat.S_ISREG(override_stat.st_mode): die("installed Engine node-intelligence Compose override is unsafe") if ( override_text != expected_engine_node_intelligence_compose_override() or sha256_file(override) != descriptor["source"]["composeOverrideSha256"] ): die("installed Engine node-intelligence Compose override drift detected") readme = root / ENGINE_NODE_INTELLIGENCE_SERVICE_ROOT_REL / "README.md" if readme.is_symlink() or sha256_file(readme) != descriptor["source"]["readmeSha256"]: die("installed Engine node-intelligence README drift detected") return gateway_sha256 def preflight_engine_node_intelligence_predecessor(payload_dir): candidate = read_engine_node_intelligence_descriptor( payload_dir / ENGINE_NODE_INTELLIGENCE_DESCRIPTOR_REL, "candidate Engine node-intelligence descriptor", ) root = component_root("engine") compose_path = root / "docker-compose.yml" gateway_path = root / ENGINE_NODE_INTELLIGENCE_GATEWAY_REL for path, label in ( (compose_path, "Compose"), (gateway_path, "gateway"), ): try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine node-intelligence predecessor {label} is missing") if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die(f"Engine node-intelligence predecessor {label} is unsafe") if sha256_file(compose_path) != candidate["predecessor"]["composeSha256"]: die("Engine node-intelligence predecessor Compose drift detected") current = current_engine_node_intelligence_descriptor() current_state = ( current["releaseId"] if current is not None and current["action"] == "activate" else "inactive" ) if current_state != candidate["expectedCurrent"]: die( "Engine node-intelligence predecessor state mismatch: " f"expected={candidate['expectedCurrent']} actual={current_state}" ) actual_gateway_sha256 = sha256_file(gateway_path) if actual_gateway_sha256 != candidate["predecessor"]["gatewaySha256"]: die( "Engine node-intelligence predecessor gateway drift detected: " f"expected={candidate['predecessor']['gatewaySha256']} " f"actual={actual_gateway_sha256}" ) if candidate["action"] == "activate": if current is not None and current["action"] == "activate": die("Engine node-intelligence activation requires an inactive predecessor") override = root / ENGINE_NODE_INTELLIGENCE_OVERRIDE_REL if override.exists() or override.is_symlink(): die("inactive Engine node-intelligence predecessor still has a Compose override") else: validate_installed_engine_node_intelligence_source(current) backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die("Engine node-intelligence requires the active immutable backend") return { "action": candidate["action"], "current_state": current_state, "gateway_sha256": actual_gateway_sha256, "backend_mode": backend["mode"], "descriptor": candidate, } def validate_engine_l2_closed_loop_recovery_evidence(): backup_dir = BACKUPS_DIR / ENGINE_L2_CLOSED_LOOP_RECOVERY_BACKUP_ID try: backup_stat = backup_dir.lstat() except FileNotFoundError: die("Engine L2 closed-loop recovery backup is missing") if stat.S_ISLNK(backup_stat.st_mode) or not stat.S_ISDIR(backup_stat.st_mode): die("Engine L2 closed-loop recovery backup is unsafe") backup_names = { child.name for child in backup_dir.iterdir() } if backup_names != set(ENGINE_L2_CLOSED_LOOP_RECOVERY_BACKUP_SHA256): die("Engine L2 closed-loop recovery backup file set mismatch") for name, expected in ENGINE_L2_CLOSED_LOOP_RECOVERY_BACKUP_SHA256.items(): path = backup_dir / name path_stat = path.lstat() if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected ): die(f"Engine L2 closed-loop recovery backup drift detected: {name}") failed_artifact = FAILED_DIR / ENGINE_L2_CLOSED_LOOP_FAILED_ARTIFACT try: failed_stat = failed_artifact.lstat() except FileNotFoundError: die("Engine L2 closed-loop failed artifact is missing") if ( stat.S_ISLNK(failed_stat.st_mode) or not stat.S_ISREG(failed_stat.st_mode) or sha256_file(failed_artifact) != ENGINE_L2_CLOSED_LOOP_FAILED_ARTIFACT_SHA256 ): die("Engine L2 closed-loop failed artifact evidence mismatch") try: state_stat = FAILED_STATE_FILE.lstat() state_lines = FAILED_STATE_FILE.read_text(encoding="utf-8").splitlines() except (FileNotFoundError, OSError, UnicodeDecodeError): die("Engine L2 closed-loop failed journal is unreadable") if stat.S_ISLNK(state_stat.st_mode) or not stat.S_ISREG(state_stat.st_mode): die("Engine L2 closed-loop failed journal is unsafe") records = [] for line in state_lines: try: value = json.loads(line) except json.JSONDecodeError: die("Engine L2 closed-loop failed journal contains invalid JSON") if ( isinstance(value, dict) and value.get("id") == ENGINE_L2_CLOSED_LOOP_FAILED_PATCH_ID ): records.append(value) if len(records) != 1: die("Engine L2 closed-loop failed journal evidence count mismatch") record = records[0] if ( record.get("artifact") != ENGINE_L2_CLOSED_LOOP_FAILED_ARTIFACT or record.get("backup_id") != ENGINE_L2_CLOSED_LOOP_RECOVERY_BACKUP_ID or record.get("component") != "engine" or record.get("sha256") != ENGINE_L2_CLOSED_LOOP_FAILED_ARTIFACT_SHA256 or record.get("started_apply") is not True or record.get("rollback_status") != "not-required" or record.get("status") != "failed" or record.get("message") != "installed Engine node-intelligence gateway drift detected" ): die("Engine L2 closed-loop failed journal evidence mismatch") return backup_dir def preflight_engine_l2_closed_loop_predecessor(payload_dir): candidate = validate_engine_l2_closed_loop_payload( payload_dir, ENGINE_L2_CLOSED_LOOP_ARTIFACT_ENTRIES, ) recovery_backup = validate_engine_l2_closed_loop_recovery_evidence() root = component_root("engine") actual = collect_exact_files( root, ENGINE_L2_CLOSED_LOOP_ARTIFACT_ENTRIES, "installed Engine L2 closed-loop partial state", ) if actual != ENGINE_L2_CLOSED_LOOP_PARTIAL_SHA256: changed = sorted( set(actual) ^ set(ENGINE_L2_CLOSED_LOOP_PARTIAL_SHA256) | { rel for rel in set(actual) & set(ENGINE_L2_CLOSED_LOOP_PARTIAL_SHA256) if actual[rel] != ENGINE_L2_CLOSED_LOOP_PARTIAL_SHA256[rel] } ) detail = changed[0] if changed else "unknown" die(f"Engine L2 closed-loop partial predecessor drift detected: {detail}") nginx_entries = ("nginx-html/index.html", "nginx-html/assets") nginx_actual = collect_exact_files( root, nginx_entries, "installed Engine L2 closed-loop nginx publication", ) nginx_expected = { "nginx-html/index.html": ENGINE_L2_CLOSED_LOOP_PARTIAL_SHA256[ "nodedc-source/dist/index.html" ], "nginx-html/assets/index-Bim2pv1P.css": ENGINE_L2_CLOSED_LOOP_PARTIAL_SHA256[ "nodedc-source/dist/assets/index-Bim2pv1P.css" ], "nginx-html/assets/index-CqvJfRRS.js": ENGINE_L2_CLOSED_LOOP_PARTIAL_SHA256[ "nodedc-source/dist/assets/index-CqvJfRRS.js" ], } if nginx_actual != nginx_expected: die("Engine L2 closed-loop nginx partial predecessor drift detected") installed = current_engine_node_intelligence_descriptor() if ( installed is None or installed.get("action") != "activate" or sha256_file(root / ENGINE_L2_CLOSED_LOOP_DESCRIPTOR_REL) != ENGINE_L2_CLOSED_LOOP_PARTIAL_SHA256[ ENGINE_L2_CLOSED_LOOP_DESCRIPTOR_REL ] ): die("Engine L2 closed-loop partial descriptor mismatch") expected_candidate = json.loads(json.dumps(installed)) expected_candidate["source"]["gatewaySha256"] = ( ENGINE_L2_CLOSED_LOOP_TARGET_SHA256[ ENGINE_NODE_INTELLIGENCE_GATEWAY_REL ] ) if candidate != expected_candidate: die("Engine L2 closed-loop descriptor crosses node-intelligence boundary") validate_installed_engine_node_intelligence_source( installed, expected_gateway_sha256=ENGINE_L2_CLOSED_LOOP_PARTIAL_SHA256[ ENGINE_NODE_INTELLIGENCE_GATEWAY_REL ], ) backend = preflight_engine_credential_backend_runtime( expected_node_intelligence_gateway_sha256= ENGINE_L2_CLOSED_LOOP_PARTIAL_SHA256[ ENGINE_NODE_INTELLIGENCE_GATEWAY_REL ], ) if backend["mode"] != "verified-derived-retry": die("Engine L2 closed-loop requires the active immutable backend") app_container_id = engine_compose_service_container_id_for_gateway( "app", ENGINE_L2_CLOSED_LOOP_PARTIAL_SHA256[ ENGINE_NODE_INTELLIGENCE_GATEWAY_REL ], ) return { "mode": "failed-030-partial-source-reconciliation", "descriptor": candidate, "recovery_backup": recovery_backup, "predecessor_gateway_sha256": installed["source"]["gatewaySha256"], "partial_gateway_sha256": ENGINE_L2_CLOSED_LOOP_PARTIAL_SHA256[ ENGINE_NODE_INTELLIGENCE_GATEWAY_REL ], "target_gateway_sha256": candidate["source"]["gatewaySha256"], "backend_mode": backend["mode"], "backend_container_id": backend["container_id"], "app_container_id": app_container_id, } def validate_engine_n8n_staged_release(descriptor): release_rel = f"releases/n8n-nodes-ndc/{descriptor['releaseId']}" release_dir = N8N_PRIVATE_EXTENSION_RELEASES_ROOT / release_rel current = N8N_PRIVATE_EXTENSION_RELEASES_ROOT for part in PurePosixPath(release_rel).parts: current = current / part try: current_stat = current.lstat() except FileNotFoundError: die(f"staged private extension release is missing: {release_rel}") if stat.S_ISLNK(current_stat.st_mode): die(f"staged private extension release symlink rejected: {current}") validate_n8n_private_extension_release(N8N_PRIVATE_EXTENSION_RELEASES_ROOT, [release_rel]) release = read_strict_json(release_dir / "release.json", "staged private extension release manifest") if release.get("releaseId") != descriptor["releaseId"]: die("staged private extension release id mismatch") if release.get("package", {}).get("version") != descriptor["packageVersion"]: die("staged private extension package version mismatch") if release.get("package", {}).get("sha256") != descriptor["packageSha256"]: die("staged private extension package sha256 mismatch") for path in [release_dir, *release_dir.rglob("*")]: path_stat = path.lstat() if path_stat.st_uid != 0 or path_stat.st_gid != 0: die(f"staged private extension release ownership mismatch: {path}") expected_mode = 0o555 if stat.S_ISDIR(path_stat.st_mode) else 0o444 if stat.S_IMODE(path_stat.st_mode) != expected_mode: die(f"staged private extension release mode mismatch: {path}") return release_dir def load_artifact(artifact, work_dir): safe_extract(artifact, work_dir) manifest_path = work_dir / "manifest.env" files_path = work_dir / "files.txt" payload_dir = work_dir / "payload" if not manifest_path.is_file(): die("manifest.env missing after extract") if not files_path.is_file(): die("files.txt missing after extract") if not payload_dir.is_dir(): die("payload directory missing after extract") manifest = parse_manifest(manifest_path) entries = parse_files_list(files_path) if is_device_plane_backhaul_vps_enrollment_slice( manifest["component"], entries, ): die("vps_initiated_transport_frozen:ADR-0001") for rel in entries: allowed_payload_path(manifest["component"], rel) if not (payload_dir / rel).exists(): die(f"files.txt entry missing in payload: {rel}") validate_payload_tree(manifest["component"], payload_dir, entries) if manifest["component"] == "mission-core-map-access": read_map_access_descriptor(payload_dir, entries) if is_gitea_fresh_install_slice(manifest["component"], entries): validate_gitea_fresh_install_payload(payload_dir, entries) elif is_gitea_incident_salvage_slice(manifest["component"], entries): validate_gitea_incident_salvage_payload(payload_dir, entries) if is_platform_device_core_hub_trust_slice( manifest["component"], entries, ): validate_platform_device_core_hub_trust_payload(payload_dir) if is_platform_device_manager_public_route_slice( manifest["component"], entries, ): validate_platform_device_manager_public_route_payload(payload_dir) if is_device_plane_manager_control_plane_slice( manifest["component"], entries, ): validate_device_plane_manager_release_payload( payload_dir, expected_release_id=manifest["id"], ) if is_device_plane_control_core_release_slice( manifest["component"], entries, ): validate_device_plane_control_core_release_payload( payload_dir, expected_release_id=manifest["id"], ) if is_device_plane_edge_core_channel_bootstrap_slice( manifest["component"], entries, ): if is_device_plane_edge_core_channel_upgrade_v4_slice( manifest["component"], entries, ): validate_device_plane_edge_core_channel_upgrade_v4_payload( payload_dir, expected_transition_id=manifest["id"], ) elif is_device_plane_edge_core_channel_upgrade_v2_slice( manifest["component"], entries, ): validate_device_plane_edge_core_channel_upgrade_v2_payload( payload_dir, expected_transition_id=manifest["id"], ) elif is_device_plane_edge_core_channel_upgrade_slice( manifest["component"], entries, ): validate_device_plane_edge_core_channel_upgrade_payload( payload_dir, expected_transition_id=manifest["id"], ) else: validate_device_plane_edge_core_channel_bootstrap_payload( payload_dir, expected_transition_id=manifest["id"], ) if is_device_plane_manager_failed_control_plane_slice( manifest["component"], entries, ): validate_device_plane_manager_failed_control_plane_payload( payload_dir ) if is_device_plane_manager_reconciliation_slice( manifest["component"], entries, ): validate_device_plane_manager_reconciliation_payload(payload_dir) if is_device_plane_manager_v2_reconciliation_slice( manifest["component"], entries, ): validate_device_plane_manager_v2_reconciliation_payload(payload_dir) if is_device_plane_control_core_v3_reconciliation_slice( manifest["component"], entries, ): validate_device_plane_control_core_v3_reconciliation_payload( payload_dir ) if is_device_plane_control_core_incident_audit_slice( manifest["component"], entries, ): validate_device_plane_control_core_incident_audit_payload(payload_dir) if is_device_plane_control_core_migration_replay_audit_slice( manifest["component"], entries, ): if ( manifest["id"] != DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_AUDIT_PATCH_ID ): die("Device Control Core migration replay audit patch id mismatch") validate_device_plane_control_core_migration_replay_audit_payload( payload_dir ) if is_device_plane_control_core_migration_replay_recovery_slice( manifest["component"], entries, ): if ( manifest["id"] != DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_PATCH_ID ): die("Device Control Core migration recovery patch id mismatch") validate_device_plane_control_core_migration_replay_recovery_payload( payload_dir ) if is_device_plane_control_core_migration_replay_checkpoint_recovery_slice( manifest["component"], entries, ): if ( manifest["id"] != DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_PATCH_ID ): die( "Device Control Core migration replay checkpoint recovery " "patch id mismatch" ) validate_device_plane_control_core_migration_replay_checkpoint_recovery_payload( payload_dir ) if is_device_plane_postgres_bootstrap_slice( manifest["component"], entries, ): validate_device_plane_postgres_bootstrap_payload(payload_dir) if is_device_plane_foundation_recovery_slice( manifest["component"], entries, ): validate_device_plane_foundation_recovery_payload(payload_dir) if is_device_plane_foundation_network_publication_slice( manifest["component"], entries, ): validate_device_plane_foundation_network_publication_payload( payload_dir ) if is_device_plane_b2_discovery_ingress_slice( manifest["component"], entries, ): validate_device_plane_b2_discovery_ingress_payload(payload_dir) if is_device_plane_b2_discovery_rollback_recovery_slice( manifest["component"], entries, ): validate_device_plane_b2_discovery_rollback_recovery_payload( payload_dir ) if is_device_plane_backhaul_target_slice( manifest["component"], entries, ): validate_device_plane_backhaul_target_payload(payload_dir) if is_device_plane_backhaul_vps_enrollment_slice( manifest["component"], entries, ): validate_device_plane_backhaul_vps_enrollment_payload(payload_dir) if manifest["component"] == "n8n-private-extension": validate_n8n_private_extension_release(payload_dir, entries) if manifest["component"] == "engine": touches_private_extension = any( rel == "nodedc-source/services/n8n/private-extensions" or rel.startswith("nodedc-source/services/n8n/private-extensions/") for rel in entries ) if touches_private_extension and not is_engine_n8n_transition(manifest["component"], entries): die("Engine private-extension payload requires the canonical transition descriptor") if is_engine_n8n_transition(manifest["component"], entries): validate_engine_n8n_transition(payload_dir, entries) touches_node_intelligence = any( rel == ENGINE_NODE_INTELLIGENCE_SOURCE_REL or rel.startswith(ENGINE_NODE_INTELLIGENCE_SOURCE_REL + "/") or rel == ENGINE_NODE_INTELLIGENCE_SERVICE_ROOT_REL 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) and not is_engine_mcp_control_plane_slice(manifest["component"], entries) 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_mcp_execution_plan_materialization_slice( manifest["component"], entries, ) and not is_engine_mcp_execution_plan_module_ownership_slice( manifest["component"], entries, ) and not is_engine_mcp_normalized_identity_search_slice( manifest["component"], entries, ) and not is_engine_mcp_registered_execution_profiles_slice( manifest["component"], entries, ) and not is_engine_provider_authority_diagnostics_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 is_engine_mcp_autonomy_provider_v5_slice(manifest["component"], entries): validate_engine_mcp_autonomy_provider_v5_payload(payload_dir, entries) if is_engine_l2_closed_loop_slice(manifest["component"], entries): validate_engine_l2_closed_loop_payload(payload_dir, entries) if is_engine_composite_provider_v4_slice(manifest["component"], entries): validate_engine_composite_provider_v4_slice(payload_dir, entries) if is_engine_provider_rotating_slot_slice(manifest["component"], entries): validate_engine_provider_rotating_slot_slice(payload_dir, entries) if is_engine_provider_authority_diagnostics_slice(manifest["component"], entries): validate_engine_provider_authority_diagnostics_slice(payload_dir, entries) if is_engine_depttrans_zone_authority_v1_slice(manifest["component"], entries): validate_engine_depttrans_zone_authority_v1_slice(payload_dir, entries) if is_engine_provider_target_host_policy_slice(manifest["component"], entries): validate_engine_provider_target_host_policy_slice(payload_dir, entries) if is_engine_mcp_execution_profile_decoder_slice( manifest["component"], entries, ): validate_engine_mcp_execution_profile_decoder_slice(payload_dir, entries) if is_engine_mcp_telemetry_catalog_slice(manifest["component"], entries): validate_engine_mcp_telemetry_catalog_slice(payload_dir, entries) if is_engine_mcp_execution_plan_materialization_slice( manifest["component"], entries, ): validate_engine_mcp_execution_plan_materialization_slice( payload_dir, entries, ) if is_engine_mcp_execution_plan_telemetry_runtime_slice( manifest["component"], entries, ): validate_engine_mcp_execution_plan_telemetry_runtime_slice( payload_dir, entries, ) if is_engine_mcp_execution_plan_module_ownership_slice( manifest["component"], entries, ): validate_engine_mcp_execution_plan_module_ownership_slice( payload_dir, entries, ) if is_engine_mcp_normalized_identity_search_slice( manifest["component"], entries, ): validate_engine_mcp_normalized_identity_search_slice( payload_dir, entries, ) if is_engine_mcp_l1_credential_reuse_slice( manifest["component"], entries, ): validate_engine_mcp_l1_credential_reuse_slice( payload_dir, entries, ) if is_engine_mcp_l1_credential_provenance_slice( manifest["component"], entries, ): validate_engine_mcp_l1_credential_provenance_slice( payload_dir, entries, ) if is_engine_mcp_execution_plan_sandbox_runtime_slice( manifest["component"], entries, ): validate_engine_mcp_execution_plan_sandbox_runtime_slice( payload_dir, entries, ) if is_engine_mcp_gelios_items_envelope_slice( manifest["component"], entries, ): validate_engine_mcp_gelios_items_envelope_slice( payload_dir, entries, ) if is_engine_mcp_registered_execution_profiles_slice( manifest["component"], entries, ): validate_engine_mcp_registered_execution_profiles_slice( payload_dir, entries, ) if is_engine_mcp_gelios_units_items_slice( manifest["component"], entries, ): validate_engine_mcp_gelios_units_items_slice( payload_dir, entries, ) if is_engine_provider_security_catalog_slice(manifest["component"], entries): validate_engine_provider_security_catalog_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 not is_engine_mcp_autonomy_provider_v5_slice(manifest["component"], entries) and not is_engine_composite_provider_v4_slice(manifest["component"], entries) and not is_engine_provider_rotating_slot_slice(manifest["component"], entries) and not is_engine_provider_authority_diagnostics_slice(manifest["component"], entries) and not is_engine_depttrans_zone_authority_v1_slice(manifest["component"], entries) and not is_engine_provider_target_host_policy_slice(manifest["component"], entries) and not is_engine_mcp_execution_profile_decoder_slice( manifest["component"], entries, ) and not is_engine_mcp_telemetry_catalog_slice( manifest["component"], entries, ) and not is_engine_mcp_execution_plan_materialization_slice( manifest["component"], entries, ) and not is_engine_mcp_execution_plan_telemetry_runtime_slice( manifest["component"], entries, ) and not is_engine_mcp_execution_plan_module_ownership_slice( manifest["component"], entries, ) and not is_engine_mcp_normalized_identity_search_slice( manifest["component"], entries, ) and not is_engine_mcp_l1_credential_reuse_slice( manifest["component"], entries, ) and not is_engine_mcp_l1_credential_provenance_slice( manifest["component"], entries, ) and not is_engine_mcp_execution_plan_sandbox_runtime_slice( manifest["component"], entries, ) and not is_engine_mcp_gelios_items_envelope_slice( manifest["component"], entries, ) and not is_engine_mcp_registered_execution_profiles_slice( manifest["component"], entries, ) and not is_engine_mcp_gelios_units_items_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 ( not is_engine_mcp_control_plane_slice(manifest["component"], entries) and not is_engine_mcp_ontology_sdk_slice(manifest["component"], entries) and not is_engine_mcp_autonomy_provider_v5_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 def load_state(path): if not path.exists(): return [] rows = [] with path.open("r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue rows.append(json.loads(line)) return rows def append_jsonl(path, row): path.parent.mkdir(parents=True, exist_ok=True) with path.open("a", encoding="utf-8") as f: f.write(json.dumps(row, ensure_ascii=False, sort_keys=True) + "\n") def state_has_sha(sha): return any(row.get("sha256") == sha for row in load_state(STATE_FILE)) def state_has_patch_id(patch_id): return any(row.get("id") == patch_id for row in load_state(STATE_FILE)) def failed_state_has_sha(sha): return any( row.get("sha256") == sha for row in load_state(FAILED_STATE_FILE) ) def failed_state_has_patch_id(patch_id): return any( row.get("id") == patch_id for row in load_state(FAILED_STATE_FILE) ) def reject_failed_artifact_replay(manifest, sha256): patch_id = manifest.get("id") if failed_state_has_sha(sha256): die( "artifact SHA is terminal failed and cannot be replayed; " "build a new immutable artifact" ) if patch_id and failed_state_has_patch_id(patch_id): die( "patch id is terminal failed and cannot be reused; " "build a new patch id" ) def reject_terminal_engine_l2_failed_artifact(manifest, sha256): if ( manifest.get("id") == ENGINE_L2_CLOSED_LOOP_FAILED_PATCH_ID or sha256 == ENGINE_L2_CLOSED_LOOP_FAILED_ARTIFACT_SHA256 ): die( "Engine L2 closed-loop 030 is terminal failed; " "use the exact registered reconciliation successor" ) def reject_terminal_device_plane_foundation_artifact(manifest, sha256): if ( manifest.get("id") == DEVICE_PLANE_FOUNDATION_FAILED_PATCH_ID or sha256 == DEVICE_PLANE_FOUNDATION_FAILED_ARTIFACT_SHA256 ): die( "Device Plane foundation 001 is terminal failed; " "use the exact registered foundation recovery successor" ) if ( manifest.get("id") == DEVICE_PLANE_FOUNDATION_RECOVERY_FAILED_PATCH_ID or sha256 == DEVICE_PLANE_FOUNDATION_RECOVERY_FAILED_ARTIFACT_SHA256 ): die( "Device Plane foundation recovery 002 is terminal failed; " "use the exact registered network-publication successor" ) def reject_terminal_device_plane_manager_artifact( manifest, sha256, entries=None, ): if ( manifest.get("id") == DEVICE_PLANE_MANAGER_FAILED_PATCH_ID or sha256 == DEVICE_PLANE_MANAGER_FAILED_ARTIFACT_SHA256 or is_device_plane_manager_failed_control_plane_slice( manifest.get("component"), entries, ) ): die( "Device Manager control-plane 001 is terminal failed; " "use reconciliation followed by the exact v2 activation successor" ) if ( manifest.get("id") == DEVICE_PLANE_MANAGER_V2_FAILED_PATCH_ID or sha256 == DEVICE_PLANE_MANAGER_V2_FAILED_ARTIFACT_SHA256 or is_device_plane_manager_v2_control_plane_slice( manifest.get("component"), entries, ) ): die( "Device Manager control-plane 003 is terminal failed; " "use the exact v2 reconciliation successor followed by the " "declarative Device Manager release successor" ) def reject_terminal_device_plane_backhaul_artifact(manifest, sha256): if ( manifest.get("id") == DEVICE_PLANE_BACKHAUL_FAILED_PATCH_ID or sha256 == DEVICE_PLANE_BACKHAUL_FAILED_ARTIFACT_SHA256 ): die( "Device Plane backhaul target 001 is terminal failed; " "use the exact registered loopback plus private Tailscale Serve " "successor" ) class DeployLock: def __enter__(self): try: LOCK_DIR.mkdir() except FileExistsError: die(f"deploy lock is busy: {LOCK_DIR}") (LOCK_DIR / "pid").write_text(str(os.getpid()), encoding="utf-8") return self def __exit__(self, exc_type, exc, tb): shutil.rmtree(LOCK_DIR, ignore_errors=True) def component_root(component): return COMPONENTS[component]["payload_root"] def is_gitea_fresh_install_slice(component, entries): return ( component == "gitea" and entries is not None and tuple(entries) == GITEA_FRESH_INSTALL_ENTRIES ) def is_gitea_incident_salvage_slice(component, entries): return ( component == "gitea" and entries is not None and tuple(entries) == GITEA_SALVAGE_ENTRIES ) def is_gitea_bootstrap_slice(component, entries): return is_gitea_fresh_install_slice( component, entries, ) or is_gitea_incident_salvage_slice(component, entries) def is_platform_device_core_hub_trust_slice(component, entries): return ( component == "platform" and entries is not None and tuple(entries) == PLATFORM_DEVICE_CORE_HUB_TRUST_ENTRIES ) def is_platform_device_manager_public_route_slice(component, entries): return ( component == "platform" and entries is not None and tuple(entries) == PLATFORM_DEVICE_MANAGER_PUBLIC_ROUTE_ENTRIES ) def is_launcher_device_core_session_slice(component, entries): return ( component == "launcher" and entries is not None and tuple(entries) == LAUNCHER_DEVICE_CORE_SESSION_ENTRIES ) def is_device_plane_manager_control_plane_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) in ( DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V1_SUCCESSOR_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V4_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V5_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V6_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V7_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V8_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V9_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V10_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V11_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V12_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V13_ENTRIES, ) ) def is_device_plane_manager_release_v2_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES ) def is_device_plane_manager_release_v3_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES ) def is_device_plane_manager_release_v4_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_MANAGER_RELEASE_V4_ENTRIES ) def is_device_plane_manager_release_v5_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_MANAGER_RELEASE_V5_ENTRIES ) def is_device_plane_manager_release_v6_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_MANAGER_RELEASE_V6_ENTRIES ) def is_device_plane_manager_release_v7_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_MANAGER_RELEASE_V7_ENTRIES ) def is_device_plane_manager_release_v8_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_MANAGER_RELEASE_V8_ENTRIES ) def is_device_plane_manager_release_v9_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_MANAGER_RELEASE_V9_ENTRIES ) def is_device_plane_manager_release_v10_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_MANAGER_RELEASE_V10_ENTRIES ) def is_device_plane_manager_release_v11_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_MANAGER_RELEASE_V11_ENTRIES ) def is_device_plane_manager_release_v12_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_MANAGER_RELEASE_V12_ENTRIES ) def is_device_plane_manager_release_v13_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_MANAGER_RELEASE_V13_ENTRIES ) def is_device_plane_manager_persistent_release_slice(component, entries): return ( is_device_plane_manager_release_v4_slice(component, entries) or is_device_plane_manager_release_v5_slice(component, entries) or is_device_plane_manager_release_v6_slice(component, entries) or is_device_plane_manager_release_v7_slice(component, entries) or is_device_plane_manager_release_v8_slice(component, entries) or is_device_plane_manager_release_v9_slice(component, entries) or is_device_plane_manager_release_v10_slice(component, entries) or is_device_plane_manager_release_v11_slice(component, entries) or is_device_plane_manager_release_v12_slice(component, entries) or is_device_plane_manager_release_v13_slice(component, entries) ) def is_device_plane_manager_only_release_slice(component, entries): return ( is_device_plane_manager_release_v3_slice(component, entries) or is_device_plane_manager_persistent_release_slice(component, entries) ) def is_device_plane_edge_core_channel_bootstrap_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) in ( DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_ENTRIES, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_ENTRIES, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_ENTRIES, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_ENTRIES, ) ) def is_device_plane_edge_core_channel_upgrade_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) in ( DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_ENTRIES, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_ENTRIES, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_ENTRIES, ) ) def is_device_plane_edge_core_channel_upgrade_v2_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_ENTRIES ) def is_device_plane_edge_core_channel_upgrade_v4_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_ENTRIES ) def is_device_plane_control_core_release_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) in ( DEVICE_PLANE_CONTROL_CORE_RELEASE_ENTRIES, DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES, DEVICE_PLANE_CONTROL_CORE_RELEASE_V3_ENTRIES, DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_ENTRIES, ) ) def is_device_plane_control_core_release_v2_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES ) def is_device_plane_control_core_release_v3_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_CONTROL_CORE_RELEASE_V3_ENTRIES ) def is_device_plane_control_core_release_v4_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_ENTRIES ) def is_device_plane_control_core_v3_reconciliation_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_ENTRIES ) def is_device_plane_control_core_incident_audit_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_CONTROL_CORE_INCIDENT_AUDIT_ENTRIES ) def is_device_plane_control_core_migration_replay_audit_slice( component, entries, ): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_AUDIT_ENTRIES ) def is_device_plane_control_core_migration_replay_recovery_slice( component, entries, ): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_ENTRIES ) def is_device_plane_control_core_migration_replay_checkpoint_recovery_slice( component, entries, ): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_ENTRIES ) def is_device_plane_manager_v2_control_plane_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_MANAGER_V2_CONTROL_PLANE_ENTRIES ) def is_device_plane_manager_failed_control_plane_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_ENTRIES ) def is_device_plane_manager_reconciliation_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_MANAGER_RECONCILIATION_ENTRIES ) def is_device_plane_manager_v2_reconciliation_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_MANAGER_V2_RECONCILIATION_ENTRIES ) def expected_platform_device_core_hub_trust_descriptor(): return { "schemaVersion": "nodedc.platform.device-core-hub-trust.v1", "action": "activate", "serviceSlug": "device-core", "launcherCredential": "runner-managed-file", "credentialScope": ["handoff.consume", "session.validate"], "publicRoute": "unchanged", } def expected_platform_device_manager_public_route_descriptor(): return { "schemaVersion": "nodedc.platform.device-manager-public-route.v1", "action": "activate", "hostname": "device.nodedc.ru", "upstream": "device-manager:18122", "transport": "reverse-proxy", "rawTcpIngress": "forbidden", } def expected_device_plane_manager_release_v1_boundaries(): return { "service": "device-manager", "publicIngress": "reverse-proxy-only", "deviceCoreManagementApi": "file-token-authenticated", "launcherTrust": "file-token-scoped-to-device-core-handoff", "healthGate": "bounded-container-grace+core-contract", "commandTransport": "disabled", "gelios": "untouched", "rollback": "restore-preapply-snapshot", } def expected_device_plane_manager_release_v2_boundaries(): return { **expected_device_plane_manager_release_v1_boundaries(), "edgeChannel": ( "core-initiated-pinned-mtls-enabled-zero-or-more-registered-edges" ), "edgeChannelIdentity": ( "runner-managed-host-local-private-key-public-certificate-export" ), "edgeChannelEgress": ( "dedicated-core-only-bridge-no-host-ingress-" "public-ipv4-tcp-8443-registration-policy" ), } def expected_device_plane_manager_release_v3_boundaries(): return { "controlCorePredecessor": { "patchId": ( DEVICE_PLANE_MANAGER_RELEASE_V3_CONTROL_CORE_PREDECESSOR_PATCH_ID ), "artifactSha256": ( DEVICE_PLANE_MANAGER_RELEASE_V3_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 ), }, "edgeChannelPredecessor": { "patchId": ( DEVICE_PLANE_MANAGER_RELEASE_V3_EDGE_CHANNEL_PREDECESSOR_PATCH_ID ), "artifactSha256": ( DEVICE_PLANE_MANAGER_RELEASE_V3_EDGE_CHANNEL_PREDECESSOR_ARTIFACT_SHA256 ), }, "service": "device-manager", "publicIngress": "reverse-proxy-only", "deviceCoreManagementApi": "file-token-authenticated", "launcherTrust": "file-token-scoped-to-device-core-handoff", "edgeChannel": "preserve-active-v4-core-initiated-pinned-mtls", "edgeChannelIdentity": ( "reuse-runner-managed-host-local-private-key-" "public-certificate-export" ), "edgeChannelEgress": ( "preserve-dedicated-core-only-bridge-no-host-ingress-" "public-ipv4-tcp-443-only" ), "healthGate": "bounded-container-grace+core-contract", "commandTransport": "typed-service-ping-v1", "commandCatalog": "allowlisted-adapter-typed-commands-only", "credentialBoundary": ( "transient-core-memory-then-single-pinned-mtls-command-envelope-" "to-edge-never-persisted-never-logged-never-returned" ), "gelios": "untouched-legacy-only", "rollback": "restore-preapply-snapshot", } def expected_device_plane_manager_release_v4_boundaries(): return { **expected_device_plane_manager_release_v3_boundaries(), "predecessor": { "kind": "release", "patchId": DEVICE_PLANE_MANAGER_RELEASE_V4_PREDECESSOR_PATCH_ID, "artifactSha256": ( DEVICE_PLANE_MANAGER_RELEASE_V4_PREDECESSOR_ARTIFACT_SHA256 ), }, "healthGate": "bounded-container-grace+core-contract+persistent-data", "presentationPersistence": "runner-managed-host-data-bind", "presentationDataHostPath": str(DEVICE_PLANE_MANAGER_DATA_DIR), "presentationDataContainerPath": DEVICE_PLANE_MANAGER_DATA_CONTAINER_DIR, "presentationDataOwnership": "uid-1000-gid-1000-mode-0750", "presentationDataLifecycle": ( "preserve-across-manager-recreate-and-source-rollback" ), "presentationPath": DEVICE_PLANE_MANAGER_PRESENTATION_PATH, "mediaRoot": DEVICE_PLANE_MANAGER_MEDIA_ROOT, "defaultAccentHex": "#f5f5f5", "rollback": "restore-preapply-snapshot-preserve-manager-data", } def expected_device_plane_manager_release_v5_boundaries(): return { **expected_device_plane_manager_release_v4_boundaries(), "predecessor": { "kind": "release", "patchId": DEVICE_PLANE_MANAGER_RELEASE_V5_PREDECESSOR_PATCH_ID, "artifactSha256": ( DEVICE_PLANE_MANAGER_RELEASE_V5_PREDECESSOR_ARTIFACT_SHA256 ), }, "overviewLayout": "mission-core-landing-stage-v1", } def expected_device_plane_manager_release_v6_boundaries(): return { **expected_device_plane_manager_release_v5_boundaries(), "predecessor": { "kind": "release", "patchId": DEVICE_PLANE_MANAGER_RELEASE_V6_PREDECESSOR_PATCH_ID, "artifactSha256": ( DEVICE_PLANE_MANAGER_RELEASE_V6_PREDECESSOR_ARTIFACT_SHA256 ), }, "faviconSet": "nodedc-adaptive-v1", } def expected_device_plane_manager_release_v7_boundaries(): return { **expected_device_plane_manager_release_v6_boundaries(), "predecessor": { "kind": "release", "patchId": DEVICE_PLANE_MANAGER_RELEASE_V7_PREDECESSOR_PATCH_ID, "artifactSha256": ( DEVICE_PLANE_MANAGER_RELEASE_V7_PREDECESSOR_ARTIFACT_SHA256 ), }, "controlCorePredecessor": { "patchId": ( DEVICE_PLANE_MANAGER_RELEASE_V7_CONTROL_CORE_PREDECESSOR_PATCH_ID ), "artifactSha256": ( DEVICE_PLANE_MANAGER_RELEASE_V7_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 ), }, "commandFormLayout": "aligned-control-row-v1", "secondaryEmptyTypography": "help-text-sm-v1", "infrastructureHostProjection": ( "edge-registration-live-channel-v1" ), "ontologyStatus": "generic-host-domain-candidate-not-canonical", } def expected_device_plane_manager_release_v8_boundaries(): return { **expected_device_plane_manager_release_v6_boundaries(), "predecessor": { "kind": "release", "patchId": DEVICE_PLANE_MANAGER_RELEASE_V8_PREDECESSOR_PATCH_ID, "artifactSha256": ( DEVICE_PLANE_MANAGER_RELEASE_V8_PREDECESSOR_ARTIFACT_SHA256 ), }, "controlCorePredecessor": { "patchId": ( DEVICE_PLANE_MANAGER_RELEASE_V8_CONTROL_CORE_PREDECESSOR_PATCH_ID ), "artifactSha256": ( DEVICE_PLANE_MANAGER_RELEASE_V8_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 ), }, "commandFormLayout": "aligned-control-row-v1", "secondaryEmptyTypography": "help-text-sm-v1", "infrastructureHostProjection": "ontology-backed-host-runtime-v1", "ontologyFoundation": ( "ontology-core-device-foundation-20260822-001" ), "ontologyCatalogHash": "229c61c02a790906", "assetBinding": "temporal-device-asset-binding-v1", "infrastructureRuntime": ( "host-endpoint-deployment-service-instance-v1" ), "healthEvidence": "ttl-observation-missing-not-unhealthy-v1", "interactiveShell": ( "disabled-pending-managed-session-boundary" ), } def expected_device_plane_manager_release_v9_boundaries(): return { **expected_device_plane_manager_release_v8_boundaries(), "predecessor": { "kind": "release", "patchId": DEVICE_PLANE_MANAGER_RELEASE_V9_PREDECESSOR_PATCH_ID, "artifactSha256": ( DEVICE_PLANE_MANAGER_RELEASE_V9_PREDECESSOR_ARTIFACT_SHA256 ), }, "controlCorePredecessor": { "patchId": ( DEVICE_PLANE_MANAGER_RELEASE_V9_CONTROL_CORE_PREDECESSOR_PATCH_ID ), "artifactSha256": ( DEVICE_PLANE_MANAGER_RELEASE_V9_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 ), }, "telemetryWorkspace": "mission-core-compute-module-parity-v1", "telemetryNavigation": "full-workspace-back-navigation-v1", "telemetryPollInterval": "three-seconds", "telemetryFreshness": ( "fifteen-seconds-missing-stale-not-unhealthy" ), "telemetryOntologyProjection": ( "observation-observed-property-provenance-freshness-v1" ), "telemetryAgent": "telegraf-host-observer-v1", } def expected_device_plane_manager_release_v10_boundaries(): return { **expected_device_plane_manager_release_v9_boundaries(), "predecessor": { "kind": "release", "patchId": DEVICE_PLANE_MANAGER_RELEASE_V10_PREDECESSOR_PATCH_ID, "artifactSha256": ( DEVICE_PLANE_MANAGER_RELEASE_V10_PREDECESSOR_ARTIFACT_SHA256 ), }, "controlCorePredecessor": { "patchId": ( DEVICE_PLANE_MANAGER_RELEASE_V10_CONTROL_CORE_PREDECESSOR_PATCH_ID ), "artifactSha256": ( DEVICE_PLANE_MANAGER_RELEASE_V10_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 ), }, } def expected_device_plane_manager_release_v11_boundaries(): return { **expected_device_plane_manager_release_v10_boundaries(), "predecessor": { "kind": "release", "patchId": DEVICE_PLANE_MANAGER_RELEASE_V11_PREDECESSOR_PATCH_ID, "artifactSha256": ( DEVICE_PLANE_MANAGER_RELEASE_V11_PREDECESSOR_ARTIFACT_SHA256 ), }, "controlCorePredecessor": { "patchId": ( DEVICE_PLANE_MANAGER_RELEASE_V11_CONTROL_CORE_PREDECESSOR_PATCH_ID ), "artifactSha256": ( DEVICE_PLANE_MANAGER_RELEASE_V11_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 ), }, "designSystem": "nodedc-canonical-components-and-tokens-v1", "missionCoreReference": "compute-modules-workspace-71c8b04", "infrastructureWorkspaceLayout": ( "mission-core-system-workspace-v1" ), "hostInventoryComposition": "mission-core-compute-host-list-v1", "telemetryWorkspace": ( "mission-core-compute-module-visual-parity-v2" ), "telemetrySurface": "borderless-soft-surface-v1", "telemetryStatus": "mission-core-dot-status-v1", "telemetryScroll": "reset-on-workspace-transition-v1", } def expected_device_plane_manager_release_v12_boundaries(): return { **expected_device_plane_manager_release_v11_boundaries(), "predecessor": { "kind": "release", "patchId": DEVICE_PLANE_MANAGER_RELEASE_V12_PREDECESSOR_PATCH_ID, "artifactSha256": ( DEVICE_PLANE_MANAGER_RELEASE_V12_PREDECESSOR_ARTIFACT_SHA256 ), }, "controlCorePredecessor": { "patchId": ( DEVICE_PLANE_MANAGER_RELEASE_V12_CONTROL_CORE_PREDECESSOR_PATCH_ID ), "artifactSha256": ( DEVICE_PLANE_MANAGER_RELEASE_V12_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 ), }, "telemetryWorkspace": ( "mission-core-compute-module-adaptive-window-v3" ), "telemetryGraphScale": ( "adaptive-observed-window-explicit-domain-v1" ), "telemetryCpuMinimumSpan": "five-percentage-points", "telemetryMemoryMinimumSpan": "four-percentage-points", "telemetryNetworkMissingSemantics": ( "missing-counters-never-zero-v1" ), } def expected_device_plane_manager_release_v13_boundaries(): return { **expected_device_plane_manager_release_v12_boundaries(), "predecessor": { "kind": "release", "patchId": DEVICE_PLANE_MANAGER_RELEASE_V13_PREDECESSOR_PATCH_ID, "artifactSha256": ( DEVICE_PLANE_MANAGER_RELEASE_V13_PREDECESSOR_ARTIFACT_SHA256 ), }, "controlCorePredecessor": { "patchId": ( DEVICE_PLANE_MANAGER_RELEASE_V13_CONTROL_CORE_PREDECESSOR_PATCH_ID ), "artifactSha256": ( DEVICE_PLANE_MANAGER_RELEASE_V13_CONTROL_CORE_PREDECESSOR_ARTIFACT_SHA256 ), }, "infrastructureWorkspaceLayout": "mission-core-system-workspace-v2", "hostInventoryComposition": "mission-core-compute-host-accordion-v2", "hostInventoryOverviewSurface": ( "separate-summary-soft-surface-v1" ), "hostInventoryCollectionSurface": ( "separate-host-collection-soft-surface-v1" ), "hostInventoryRow": "compact-centered-accordion-v1", "hostInventoryFreshness": "dot-only-v1", "hostInventoryRelations": ( "host-scoped-endpoint-deployment-service-v1" ), "hostInventoryDefaultExpansion": "collapsed", "hostInventoryScaleTarget": "five-hundred-collapsed-rows-v1", } def expected_device_plane_manager_release_boundaries(): # Compatibility name for the current release builder/tests. Immutable v1 # predecessors always use expected_device_plane_manager_release_v1_boundaries. return expected_device_plane_manager_release_v2_boundaries() def expected_device_plane_edge_core_channel_bootstrap_descriptor( transition_id, ): return { "schemaVersion": ( "nodedc.device-plane.device-edge-core-channel-bootstrap.v1" ), "transitionId": transition_id, "action": "activate", "managerPredecessor": { "patchId": ( DEVICE_PLANE_EDGE_CORE_CHANNEL_MANAGER_PREDECESSOR_PATCH_ID ), "artifactSha256": ( DEVICE_PLANE_EDGE_CORE_CHANNEL_MANAGER_PREDECESSOR_ARTIFACT_SHA256 ), }, "failedPredecessor": { "patchId": DEVICE_PLANE_EDGE_CORE_CHANNEL_FAILED_PATCH_ID, "artifactSha256": ( DEVICE_PLANE_EDGE_CORE_CHANNEL_FAILED_ARTIFACT_SHA256 ), "backupId": DEVICE_PLANE_EDGE_CORE_CHANNEL_FAILED_BACKUP_ID, "invalidCoreCertificateSha256Fingerprint": ( DEVICE_PLANE_EDGE_CORE_CHANNEL_INVALID_CERTIFICATE_FINGERPRINT ), }, "service": "device-control-core", "composeActivation": "dedicated-additive-override", "identity": ( "runner-managed-host-local-private-key-" "public-certificate-export" ), "identityRecovery": ( "exact-invalid-unexported-failed-predecessor-only" ), "tlsPurpose": "clientAuth", "direction": "core-initiated", "publicIngress": "none-on-synology", "edgeRegistrations": "preserved", "commandTransport": "disabled", "gelios": "untouched", "preservedServices": [ "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", ], "healthGate": "bounded-container-grace+core-edge-contract", "rollback": "restore-source-and-preapply-core-runtime", } def expected_device_plane_edge_core_channel_upgrade_descriptor( transition_id, ): return { "schemaVersion": ( "nodedc.device-plane.device-edge-core-channel-upgrade.v1" ), "transitionId": transition_id, "action": "upgrade", "bootstrapPredecessor": { "patchId": ( DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_PATCH_ID ), "artifactSha256": ( DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_ARTIFACT_SHA256 ), }, "service": "device-control-core", "composeActivation": "preserve-dedicated-additive-override", "identity": ( "reuse-existing-runner-managed-host-local-private-key-" "public-certificate-export" ), "identityRecovery": "forbidden-valid-existing-identity-required", "tlsPurpose": "clientAuth", "direction": "core-initiated", "endpointPolicy": "public-ipv4-standard-https-tcp-443-only", "publicIngress": "none-on-synology", "edgeRegistrations": ( "preserved-requires-explicit-443-reconciliation" ), "commandTransport": "disabled", "gelios": "untouched", "preservedServices": [ "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", ], "healthGate": "bounded-container-grace+core-edge-contract", "rollback": ( "restore-bootstrap-018-source-and-preapply-core-runtime" ), } def expected_device_plane_edge_core_channel_upgrade_v2_descriptor( transition_id, ): return { "schemaVersion": ( "nodedc.device-plane.device-edge-core-channel-upgrade.v2" ), "transitionId": transition_id, "action": "upgrade", "upgradePredecessor": { "patchId": ( DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_PREDECESSOR_PATCH_ID ), "artifactSha256": ( DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_PREDECESSOR_ARTIFACT_SHA256 ), }, "service": "device-control-core", "composeActivation": "preserve-dedicated-additive-override", "identity": ( "reuse-existing-runner-managed-host-local-private-key-" "public-certificate-export" ), "identityRecovery": "forbidden-valid-existing-identity-required", "tlsPurpose": "clientAuth", "direction": "core-initiated", "endpointPolicy": "public-ipv4-standard-https-tcp-443-only", "publicIngress": "none-on-synology", "edgeRegistrations": "preserved", "commandTransport": "disabled", "gelios": "untouched", "preservedServices": [ "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", ], "healthGate": "bounded-container-grace+core-edge-contract", "rollback": ( "restore-upgrade-019-source-and-preapply-core-runtime" ), } def expected_device_plane_edge_core_channel_upgrade_v4_descriptor( transition_id, ): return { "schemaVersion": ( "nodedc.device-plane.device-edge-core-channel-upgrade.v4" ), "transitionId": transition_id, "action": "upgrade", "upgradePredecessor": { "patchId": ( DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_PREDECESSOR_PATCH_ID ), "artifactSha256": ( DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_PREDECESSOR_ARTIFACT_SHA256 ), }, "failedAttempt": { "patchId": ( DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_FAILED_PATCH_ID ), "artifactSha256": ( DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_FAILED_ARTIFACT_SHA256 ), "backupId": ( DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_FAILED_BACKUP_ID ), }, "service": "device-control-core", "composeActivation": ( "replace-core-network-membership-with-private-plus-egress" ), "identity": ( "reuse-existing-runner-managed-host-local-private-key-" "public-certificate-export" ), "identityRecovery": "forbidden-valid-existing-identity-required", "tlsPurpose": "clientAuth", "direction": "core-initiated", "endpointPolicy": "public-ipv4-standard-https-tcp-443-only", "coreNetworks": [ "device-plane-private", "device-plane-egress", ], "removedCoreNetwork": "device-plane-control", "composeCompatibility": "synology-compose-v2.20-no-gw-priority", "publicIngress": "none-on-synology", "edgeRegistrations": "preserved", "commandTransport": "disabled", "gelios": "untouched", "preservedServices": [ "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", ], "healthGate": ( "bounded-container-grace+core-edge-contract+" "exact-private-egress-network-boundary" ), "rollback": ( "restore-upgrade-v2-021-source-and-preapply-core-runtime" ), } def expected_device_plane_control_core_release_descriptor( release_id, predecessor, *, schema_version="v1", ): descriptor = { "schemaVersion": ( f"nodedc.device-plane.device-control-core-release.{schema_version}" ), "releaseId": release_id, "action": "upgrade", "predecessor": predecessor, "service": "device-control-core", "composeActivation": "preserve-active-v4-topology", "identity": ( "reuse-existing-runner-managed-host-local-private-key-" "public-certificate-export" ), "identityRecovery": "forbidden-valid-existing-identity-required", "tlsPurpose": "clientAuth", "direction": "core-initiated", "endpointPolicy": "public-ipv4-standard-https-tcp-443-only", "coreNetworks": [ "device-plane-private", "device-plane-egress", ], "publicIngress": "none-on-synology", "edgeRegistrations": "preserved", "commandTransport": ( "typed-service-ping-v1" if schema_version in ("v2", "v3", "v4") else "disabled" ), "gelios": ( "untouched-legacy-only" if schema_version in ("v2", "v3", "v4") else "untouched" ), "preservedServices": [ "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", ], "healthGate": ( "bounded-container-grace+core-edge-contract+" "exact-private-egress-network-boundary" ), "rollback": "restore-preapply-source-and-core-runtime", } if schema_version in ("v2", "v3", "v4"): descriptor["commandCatalog"] = ( "allowlisted-adapter-typed-commands-only" ) descriptor["credentialBoundary"] = ( "transient-core-memory-then-single-pinned-mtls-command-envelope-" "to-edge-never-persisted-never-logged-never-returned" ) if schema_version in ("v3", "v4"): descriptor.update({ "telemetryTransport": ( "edge-channel-host-telemetry-observed-v1" ), "telemetryContract": ( "nodedc.infrastructure.host-telemetry.v1" ), "telemetryStorage": ( "device-control-core-postgres-seven-day-retention" ), "ontologyProjection": ( "observation-observed-property-provenance-freshness-v1" ), "telemetryFreshness": ( "fifteen-seconds-missing-stale-not-unhealthy" ), }) if schema_version == "v4": descriptor.update({ "recoveryPredecessor": ( "terminal-applied-046-exact-source-runtime-database" ), "databasePreflight": ( "final-migration-016-validated-and-host-telemetry-table-absent" ), "databaseRowMutation": "none-before-core-startup-migrations", "databaseSchemaOutcome": ( "migration-017-host-telemetry-table-present" ), "runtimePredecessor": "healthy-recovery-046-core-generation", }) return descriptor def expected_device_plane_manager_failed_control_plane_descriptor(): return { "schemaVersion": ( "nodedc.device-plane.device-manager-control-plane.v1" ), "action": "activate", "service": "device-manager", "publicIngress": "reverse-proxy-only", "deviceCoreManagementApi": "file-token-authenticated", "launcherTrust": "file-token-scoped-to-device-core-handoff", "commandTransport": "disabled", "gelios": "untouched", } def expected_device_plane_manager_reconciliation_descriptor(): return { "schemaVersion": ( "nodedc.device-plane.device-manager-control-plane-" "reconciliation.v1" ), "mode": "failed-control-plane-baseline-adoption", "failedPatchId": DEVICE_PLANE_MANAGER_FAILED_PATCH_ID, "failedArtifactSha256": ( DEVICE_PLANE_MANAGER_FAILED_ARTIFACT_SHA256 ), "backupId": DEVICE_PLANE_MANAGER_RECONCILIATION_BACKUP_ID, "sourceAction": "publish-reconciliation-marker-only", "runtimeAction": "read-only-acceptance", "preservedServices": [ "device-control-core", "device-gateway", "device-postgres", "device-backhaul-target", ], "absentService": "device-manager", "databaseVolume": DEVICE_PLANE_POSTGRES_VOLUME, "publicIngress": "disabled", "commandTransport": "disabled", "gelios": "untouched", "rollback": "marker-only-runtime-unchanged", } def expected_device_plane_manager_v2_reconciliation_descriptor(): return { "schemaVersion": ( "nodedc.device-plane.device-manager-control-plane-" "v2-reconciliation.v1" ), "mode": "failed-v2-control-plane-baseline-adoption", "failedPatchId": DEVICE_PLANE_MANAGER_V2_FAILED_PATCH_ID, "failedArtifactSha256": ( DEVICE_PLANE_MANAGER_V2_FAILED_ARTIFACT_SHA256 ), "backupId": DEVICE_PLANE_MANAGER_V2_RECONCILIATION_BACKUP_ID, "failureClass": "deterministic-runtime-module-resolution", "missingModule": ( "/packages/external-provider-contract/src/" "credential-reference.mjs" ), "correctiveAction": ( "runtime-local-contract-adapter+staged-module-import-gate" ), "sourceAction": "publish-reconciliation-marker-only", "runtimeAction": "read-only-acceptance", "preservedServices": [ "device-control-core", "device-gateway", "device-postgres", "device-backhaul-target", ], "absentService": "device-manager", "databaseVolume": DEVICE_PLANE_POSTGRES_VOLUME, "publicIngress": "disabled", "commandTransport": "disabled", "gelios": "untouched", "rollback": "marker-only-runtime-unchanged", } def expected_device_plane_control_core_v3_reconciliation_descriptor(): return { "schemaVersion": ( "nodedc.device-plane.device-control-core-release-v3-" "reconciliation.v1" ), "mode": "failed-release-v3-exact-preapply-image-restore", "failedPatchId": DEVICE_PLANE_CONTROL_CORE_V3_FAILED_PATCH_ID, "failedArtifactSha256": ( DEVICE_PLANE_CONTROL_CORE_V3_FAILED_ARTIFACT_SHA256 ), "failedArtifact": DEVICE_PLANE_CONTROL_CORE_V3_FAILED_ARTIFACT, "backupId": DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_BACKUP_ID, "predecessorPatchId": "device-control-core-release-v2-20260822-038", "predecessorArtifactSha256": ( "e2d062b82b022dba662522b5d6e192026ac964d78950d903295ca3cbbc95ab28" ), "preapplyImageId": DEVICE_PLANE_CONTROL_CORE_V3_PREAPPLY_IMAGE_ID, "sourceAction": "accept-byte-exact-restored-preapply-source", "runtimeAction": ( "retag-exact-preapply-image+recreate-device-control-core-only" ), "preservedServices": [ "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", ], "databaseVolume": DEVICE_PLANE_POSTGRES_VOLUME, "publicIngress": "disabled", "edgeChannel": "core-initiated-pinned-mtls-registered-edges-only", "commandTransport": "typed-service-ping-v1", "gelios": "untouched-legacy-only", "rollback": "marker+exact-preapply-image-runtime", } def expected_device_plane_control_core_incident_audit_descriptor(): return { "schemaVersion": ( "nodedc.device-plane.device-control-core-incident-audit.v1" ), "mode": "double-rollback-failed-read-only-audit", "allowedOperation": "canonical-plan-only", "applyAllowed": False, "failedAttempts": [ { "patchId": DEVICE_PLANE_CONTROL_CORE_V3_FAILED_PATCH_ID, "artifactSha256": ( DEVICE_PLANE_CONTROL_CORE_V3_FAILED_ARTIFACT_SHA256 ), "backupId": ( DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_BACKUP_ID ), }, { "patchId": ( DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_PATCH_ID ), "artifactSha256": ( DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_FAILED_ARTIFACT_SHA256 ), "backupId": ( DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_FAILED_BACKUP_ID ), }, ], "readOnlyEvidence": [ "device-control-core-runtime-inventory", "device-control-core-bounded-container-logs", "device-postgres-schema-presence", "device-postgres-wait-activity", ], "runtimeMutation": "none", "sourceMutation": "none", "databaseMutation": "none", "networkMutation": "none", "secretRead": "none", "preservedServices": [ "device-control-core", "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", ], } def expected_device_plane_control_core_migration_replay_audit_descriptor(): return { "schemaVersion": ( "nodedc.device-plane." "device-control-core-migration-replay-audit.v1" ), "mode": "rejected-recovery-044-live-invariants-read-only-audit", "allowedOperation": "canonical-plan-only", "applyAllowed": False, "rejectedRecovery": { "patchId": ( DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_PATCH_ID ), "artifactSha256": ( DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_ARTIFACT_SHA256 ), }, "readOnlyEvidence": [ "invalid-command-kind-count", "triggering-receipt-count", "constraint-validated", "constraint-covers-final-command-kinds", "host-telemetry-table-absent", ], "runtimeMutation": "none", "sourceMutation": "none", "databaseMutation": "none", "networkMutation": "none", "secretRead": "none", "preservedServices": [ "device-control-core", "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", ], } def expected_device_plane_control_core_migration_replay_recovery_descriptor(): return { "schemaVersion": ( "nodedc.device-plane." "device-control-core-migration-replay-recovery.v1" ), "mode": "double-rollback-failed-migration-014-forward-repair", "failedIncidentAudit": ( DEVICE_PLANE_CONTROL_CORE_INCIDENT_AUDIT_PATCH_ID ), "sourcePredecessor": { "path": DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_REL, "sha256": ( DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_PREDECESSOR_SHA256 ), }, "sourceTarget": { "path": DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_REL, "sha256": DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_TARGET_SHA256, }, "rootCause": ( "intermediate-command-kind-check-revalidated-historical-receipts" ), "repair": "migration-014-add-constraint-not-valid", "databasePreflight": ( "all-live-command-kinds-covered-by-final-migration-016" ), "databaseRowMutation": "none", "databaseSchemaOutcome": ( "final-migration-016-validated-command-kind-check" ), "runtimeAction": "build+recreate-device-control-core-only", "runtimePredecessor": "proven-degraded-double-rollback-state", "preservedServices": [ "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", ], "databaseVolume": DEVICE_PLANE_POSTGRES_VOLUME, "publicIngress": "disabled", "edgeChannel": ( "core-initiated-pinned-mtls-registered-edges-only" ), "rollback": "source+exact-degraded-predecessor-image-runtime", } def expected_device_plane_control_core_migration_replay_checkpoint_recovery_descriptor(): return { "schemaVersion": ( "nodedc.device-plane." "device-control-core-migration-replay-checkpoint-recovery.v2" ), "mode": "terminal-044-replay-checkpoint-forward-repair", "failedIncidentAudit": ( DEVICE_PLANE_CONTROL_CORE_INCIDENT_AUDIT_PATCH_ID ), "failedRecovery": { "patchId": ( DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_PATCH_ID ), "artifactSha256": ( DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_ARTIFACT_SHA256 ), "failure": "preflight-replay-checkpoint-race", "startedApply": False, }, "sourcePredecessor": { "path": DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_REL, "sha256": ( DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_PREDECESSOR_SHA256 ), }, "sourceTarget": { "path": DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_REL, "sha256": DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_TARGET_SHA256, }, "rootCause": ( "restarting-core-cycles-exact-committed-migration-checkpoints" ), "repair": "migration-014-add-constraint-not-valid", "databasePreflight": ( "exact-replay-checkpoint-005-007-009-011-and-final-compatible-rows" ), "databaseRowMutation": "none", "databaseSchemaOutcome": ( "exact-final-migration-016-validated-command-kind-check" ), "runtimeAction": "build+recreate-device-control-core-only", "runtimePredecessor": ( "proven-degraded-restarting-exact-preapply-image" ), "preservedServices": [ "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", ], "databaseVolume": DEVICE_PLANE_POSTGRES_VOLUME, "publicIngress": "disabled", "edgeChannel": ( "core-initiated-pinned-mtls-registered-edges-only" ), "rollback": "source+exact-degraded-predecessor-image-runtime", } def validate_platform_device_core_hub_trust_payload(payload_dir): descriptor = read_strict_json( payload_dir / PLATFORM_DEVICE_CORE_HUB_TRUST_REL, "Platform Device Core Hub trust descriptor", max_bytes=16 * 1024, ) if descriptor != expected_platform_device_core_hub_trust_descriptor(): die("Platform Device Core Hub trust descriptor mismatch") compose = ( payload_dir / "platform/docker-compose.platform-http.yml" ).read_text(encoding="utf-8") for required in ( "NODEDC_DEVICE_CORE_INTERNAL_TOKEN_FILE: " "/run/nodedc-secrets/device-core-internal-token", "source: /volume1/docker/nodedc-platform/secrets/" "device-core-internal-token", "create_host_path: false", ): if required not in compose: die(f"Platform Device Core Hub trust boundary missing: {required}") return descriptor def validate_platform_device_manager_public_route_payload(payload_dir): descriptor = read_strict_json( payload_dir / PLATFORM_DEVICE_MANAGER_PUBLIC_ROUTE_REL, "Platform Device Manager public route descriptor", max_bytes=16 * 1024, ) if descriptor != expected_platform_device_manager_public_route_descriptor(): die("Platform Device Manager public route descriptor mismatch") caddy = (payload_dir / "platform/Caddyfile.http").read_text( encoding="utf-8" ) for required in ( "http://device.nodedc.ru", "reverse_proxy device-manager:18122", "header_up X-Forwarded-Proto https", ): if required not in caddy: die(f"Platform Device Manager route boundary missing: {required}") return descriptor def validate_device_plane_manager_release_descriptor( descriptor, *, schema_version, boundaries, expected_release_id=None, ): required_keys = { "schemaVersion", "releaseId", "action", "predecessor", *boundaries, } if set(descriptor) != required_keys: missing = ",".join(sorted(required_keys - set(descriptor))) or "none" extra = ",".join(sorted(set(descriptor) - required_keys)) or "none" die( "Device Manager release descriptor key set mismatch: " f"schema={schema_version} missing={missing} extra={extra}" ) if descriptor.get("schemaVersion") != schema_version: die("Device Manager release descriptor schema mismatch") release_id = descriptor.get("releaseId") if ( not isinstance(release_id, str) or not re.fullmatch(r"[A-Za-z0-9._-]{1,96}", release_id) or (expected_release_id is not None and release_id != expected_release_id) ): die("Device Manager release id mismatch") action = descriptor.get("action") predecessor = descriptor.get("predecessor") if ( action not in ("activate", "upgrade") or not isinstance(predecessor, dict) or set(predecessor) != {"kind", "patchId", "artifactSha256"} or predecessor.get("kind") not in ("reconciliation", "release") or not isinstance(predecessor.get("patchId"), str) or not re.fullmatch( r"[A-Za-z0-9._-]{1,96}", predecessor["patchId"], ) or predecessor["patchId"] == release_id or not isinstance(predecessor.get("artifactSha256"), str) or not re.fullmatch( r"[a-f0-9]{64}", predecessor["artifactSha256"], ) or (action == "activate") != (predecessor["kind"] == "reconciliation") ): die("Device Manager release predecessor mismatch") if any(descriptor.get(key) != value for key, value in boundaries.items()): die("Device Manager release security boundary mismatch") return descriptor def validate_device_plane_manager_release_payload_contract( payload_dir, *, descriptor_rel, schema_version, boundaries, compose_sha256, edge_channel, expected_release_id=None, ): descriptor = read_strict_json( payload_dir / descriptor_rel, "Device Manager release descriptor", max_bytes=16 * 1024, ) validate_device_plane_manager_release_descriptor( descriptor, schema_version=schema_version, boundaries=boundaries, expected_release_id=expected_release_id, ) compose_path = payload_dir / DEVICE_PLANE_MANAGER_COMPOSE_REL if sha256_file(compose_path) != compose_sha256: die("Device Manager control-plane Compose mismatch") compose = compose_path.read_text( encoding="utf-8" ) required_compose = [ "device-manager:", 'DEVICE_MANAGEMENT_API_ENABLED: "true"', "DEVICE_MANAGEMENT_CORE_TOKEN_FILE: " "/run/nodedc-secrets/management-core-token", "NODEDC_LAUNCHER_INTERNAL_TOKEN_FILE: " "/run/nodedc-secrets/device-core-internal-token", "NODEDC_DEVICE_CORE_TOKEN_FILE: " "/run/nodedc-secrets/management-core-token", "name: nodedc-platform_edge", ] if edge_channel: required_compose.extend(( 'DEVICE_EDGE_CHANNEL_ENABLED: "true"', "DEVICE_EDGE_CHANNEL_CORE_KEY_FILE: " "/run/nodedc-secrets/device-edge-channel/core-private-key.pem", "DEVICE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE: " "/run/nodedc-secrets/device-edge-channel/core-certificate.pem", "DEVICE_EDGE_CHANNEL_TRUST_ROOT: " "/run/nodedc-secrets/device-edge-channel/peers", "source: /volume1/docker/nodedc-device-plane/secrets/" "device-edge-channel/core-private-key.pem", "source: /volume1/docker/nodedc-device-plane/secrets/" "device-edge-channel/core-certificate.pem", "source: /volume1/docker/nodedc-device-plane/secrets/" "device-edge-channel/peers", "name: nodedc-device-plane-egress", )) if schema_version in ( "nodedc.device-plane.device-manager-release.v4", "nodedc.device-plane.device-manager-release.v5", "nodedc.device-plane.device-manager-release.v6", "nodedc.device-plane.device-manager-release.v7", "nodedc.device-plane.device-manager-release.v8", "nodedc.device-plane.device-manager-release.v9", "nodedc.device-plane.device-manager-release.v10", ): required_compose.extend(( "NODEDC_DEVICE_MANAGER_PRESENTATION_PATH: " f"{DEVICE_PLANE_MANAGER_PRESENTATION_PATH}", "NODEDC_DEVICE_MANAGER_MEDIA_ROOT: " f"{DEVICE_PLANE_MANAGER_MEDIA_ROOT}", f"source: {DEVICE_PLANE_MANAGER_DATA_DIR}", f"target: {DEVICE_PLANE_MANAGER_DATA_CONTAINER_DIR}", "read_only: false", "create_host_path: false", )) for required in required_compose: if required not in compose: die(f"Device Manager control-plane boundary missing: {required}") for forbidden in ( "NODEDC_INTERNAL_ACCESS_TOKEN:", "NODEDC_PLATFORM_SERVICE_TOKEN:", "0.0.0.0:18122", ): if forbidden in compose: die(f"Device Manager control-plane boundary violation: {forbidden}") return descriptor def validate_device_plane_manager_control_plane_payload( payload_dir, *, expected_release_id=None, ): return validate_device_plane_manager_release_payload_contract( payload_dir, descriptor_rel=DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL, schema_version="nodedc.device-plane.device-manager-release.v1", boundaries=expected_device_plane_manager_release_v1_boundaries(), compose_sha256=DEVICE_PLANE_MANAGER_RELEASE_V1_COMPOSE_SHA256, edge_channel=False, expected_release_id=expected_release_id, ) def validate_device_plane_manager_release_v2_payload( payload_dir, *, expected_release_id=None, ): return validate_device_plane_manager_release_payload_contract( payload_dir, descriptor_rel=DEVICE_PLANE_MANAGER_RELEASE_V2_REL, schema_version="nodedc.device-plane.device-manager-release.v2", boundaries=expected_device_plane_manager_release_v2_boundaries(), compose_sha256=DEVICE_PLANE_MANAGER_RELEASE_V2_COMPOSE_SHA256, edge_channel=True, expected_release_id=expected_release_id, ) def validate_device_plane_manager_release_v3_payload( payload_dir, *, expected_release_id=None, ): return validate_device_plane_manager_release_payload_contract( payload_dir, descriptor_rel=DEVICE_PLANE_MANAGER_RELEASE_V3_REL, schema_version="nodedc.device-plane.device-manager-release.v3", boundaries=expected_device_plane_manager_release_v3_boundaries(), compose_sha256=DEVICE_PLANE_MANAGER_RELEASE_V1_COMPOSE_SHA256, edge_channel=False, expected_release_id=expected_release_id, ) def validate_device_plane_manager_release_v4_payload( payload_dir, *, expected_release_id=None, ): return validate_device_plane_manager_release_payload_contract( payload_dir, descriptor_rel=DEVICE_PLANE_MANAGER_RELEASE_V4_REL, schema_version="nodedc.device-plane.device-manager-release.v4", boundaries=expected_device_plane_manager_release_v4_boundaries(), compose_sha256=DEVICE_PLANE_MANAGER_RELEASE_V4_COMPOSE_SHA256, edge_channel=False, expected_release_id=expected_release_id, ) def validate_device_plane_manager_release_v5_payload( payload_dir, *, expected_release_id=None, ): return validate_device_plane_manager_release_payload_contract( payload_dir, descriptor_rel=DEVICE_PLANE_MANAGER_RELEASE_V5_REL, schema_version="nodedc.device-plane.device-manager-release.v5", boundaries=expected_device_plane_manager_release_v5_boundaries(), compose_sha256=DEVICE_PLANE_MANAGER_RELEASE_V5_COMPOSE_SHA256, edge_channel=False, expected_release_id=expected_release_id, ) def validate_device_plane_manager_v6_favicon_bundle(payload_dir): dist = payload_dir / "services/device-manager/dist" for relative_path, expected_sha256 in ( DEVICE_PLANE_MANAGER_FAVICON_SHA256.items() ): path = dist / relative_path if ( not path.is_file() or path.is_symlink() or sha256_file(path) != expected_sha256 ): die( "Device Manager canonical favicon mismatch: " f"{relative_path}" ) index_path = dist / "index.html" if not index_path.is_file() or index_path.is_symlink(): die("Device Manager favicon HTML entrypoint mismatch") index_html = index_path.read_text(encoding="utf-8") for required_link in ( 'href="/favicon/icon-adaptive.svg"', 'href="/favicon/favicon.ico"', 'href="/favicon/apple-touch-icon.png"', 'href="/favicon/icon-192.png"', 'href="/favicon/icon-512.png"', 'href="/favicon/manifest.webmanifest.json"', ): if required_link not in index_html: die(f"Device Manager favicon link missing: {required_link}") return "nodedc-adaptive-v1" def validate_device_plane_manager_release_v6_payload( payload_dir, *, expected_release_id=None, ): descriptor = validate_device_plane_manager_release_payload_contract( payload_dir, descriptor_rel=DEVICE_PLANE_MANAGER_RELEASE_V6_REL, schema_version="nodedc.device-plane.device-manager-release.v6", boundaries=expected_device_plane_manager_release_v6_boundaries(), compose_sha256=DEVICE_PLANE_MANAGER_RELEASE_V6_COMPOSE_SHA256, edge_channel=False, expected_release_id=expected_release_id, ) validate_device_plane_manager_v6_favicon_bundle(payload_dir) return descriptor def validate_device_plane_manager_release_v7_payload( payload_dir, *, expected_release_id=None, ): descriptor = validate_device_plane_manager_release_payload_contract( payload_dir, descriptor_rel=DEVICE_PLANE_MANAGER_RELEASE_V7_REL, schema_version="nodedc.device-plane.device-manager-release.v7", boundaries=expected_device_plane_manager_release_v7_boundaries(), compose_sha256=DEVICE_PLANE_MANAGER_RELEASE_V7_COMPOSE_SHA256, edge_channel=False, expected_release_id=expected_release_id, ) validate_device_plane_manager_v6_favicon_bundle(payload_dir) return descriptor def validate_device_plane_manager_release_v8_payload( payload_dir, *, expected_release_id=None, ): descriptor = validate_device_plane_manager_release_payload_contract( payload_dir, descriptor_rel=DEVICE_PLANE_MANAGER_RELEASE_V8_REL, schema_version="nodedc.device-plane.device-manager-release.v8", boundaries=expected_device_plane_manager_release_v8_boundaries(), compose_sha256=DEVICE_PLANE_MANAGER_RELEASE_V8_COMPOSE_SHA256, edge_channel=False, expected_release_id=expected_release_id, ) validate_device_plane_manager_v6_favicon_bundle(payload_dir) return descriptor def validate_device_plane_manager_release_v9_payload( payload_dir, *, expected_release_id=None, ): descriptor = validate_device_plane_manager_release_payload_contract( payload_dir, descriptor_rel=DEVICE_PLANE_MANAGER_RELEASE_V9_REL, schema_version="nodedc.device-plane.device-manager-release.v9", boundaries=expected_device_plane_manager_release_v9_boundaries(), compose_sha256=DEVICE_PLANE_MANAGER_RELEASE_V9_COMPOSE_SHA256, edge_channel=False, expected_release_id=expected_release_id, ) validate_device_plane_manager_v6_favicon_bundle(payload_dir) return descriptor def validate_device_plane_manager_release_v10_payload( payload_dir, *, expected_release_id=None, ): descriptor = validate_device_plane_manager_release_payload_contract( payload_dir, descriptor_rel=DEVICE_PLANE_MANAGER_RELEASE_V10_REL, schema_version="nodedc.device-plane.device-manager-release.v10", boundaries=expected_device_plane_manager_release_v10_boundaries(), compose_sha256=DEVICE_PLANE_MANAGER_RELEASE_V10_COMPOSE_SHA256, edge_channel=False, expected_release_id=expected_release_id, ) validate_device_plane_manager_v6_favicon_bundle(payload_dir) return descriptor def validate_device_plane_manager_release_v11_payload( payload_dir, *, expected_release_id=None, ): descriptor = validate_device_plane_manager_release_payload_contract( payload_dir, descriptor_rel=DEVICE_PLANE_MANAGER_RELEASE_V11_REL, schema_version="nodedc.device-plane.device-manager-release.v11", boundaries=expected_device_plane_manager_release_v11_boundaries(), compose_sha256=DEVICE_PLANE_MANAGER_RELEASE_V11_COMPOSE_SHA256, edge_channel=False, expected_release_id=expected_release_id, ) validate_device_plane_manager_v6_favicon_bundle(payload_dir) return descriptor def validate_device_plane_manager_release_v12_payload( payload_dir, *, expected_release_id=None, ): descriptor = validate_device_plane_manager_release_payload_contract( payload_dir, descriptor_rel=DEVICE_PLANE_MANAGER_RELEASE_V12_REL, schema_version="nodedc.device-plane.device-manager-release.v12", boundaries=expected_device_plane_manager_release_v12_boundaries(), compose_sha256=DEVICE_PLANE_MANAGER_RELEASE_V12_COMPOSE_SHA256, edge_channel=False, expected_release_id=expected_release_id, ) validate_device_plane_manager_v6_favicon_bundle(payload_dir) return descriptor def validate_device_plane_manager_release_v13_payload( payload_dir, *, expected_release_id=None, ): descriptor = validate_device_plane_manager_release_payload_contract( payload_dir, descriptor_rel=DEVICE_PLANE_MANAGER_RELEASE_V13_REL, schema_version="nodedc.device-plane.device-manager-release.v13", boundaries=expected_device_plane_manager_release_v13_boundaries(), compose_sha256=DEVICE_PLANE_MANAGER_RELEASE_V13_COMPOSE_SHA256, edge_channel=False, expected_release_id=expected_release_id, ) validate_device_plane_manager_v6_favicon_bundle(payload_dir) return descriptor def validate_device_plane_manager_release_payload( payload_dir, *, expected_release_id=None, ): v1 = payload_dir / DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL v2 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V2_REL v3 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V3_REL v4 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V4_REL v5 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V5_REL v6 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V6_REL v7 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V7_REL v8 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V8_REL v9 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V9_REL v10 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V10_REL v11 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V11_REL v12 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V12_REL v13 = payload_dir / DEVICE_PLANE_MANAGER_RELEASE_V13_REL present = [ path for path in ( v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13 ) if path.exists() or path.is_symlink() ] if len(present) != 1: die("Device Manager release descriptor cardinality mismatch") if present[0] == v13: return validate_device_plane_manager_release_v13_payload( payload_dir, expected_release_id=expected_release_id, ) if present[0] == v12: return validate_device_plane_manager_release_v12_payload( payload_dir, expected_release_id=expected_release_id, ) if present[0] == v11: return validate_device_plane_manager_release_v11_payload( payload_dir, expected_release_id=expected_release_id, ) if present[0] == v10: return validate_device_plane_manager_release_v10_payload( payload_dir, expected_release_id=expected_release_id, ) if present[0] == v9: return validate_device_plane_manager_release_v9_payload( payload_dir, expected_release_id=expected_release_id, ) if present[0] == v8: return validate_device_plane_manager_release_v8_payload( payload_dir, expected_release_id=expected_release_id, ) if present[0] == v7: return validate_device_plane_manager_release_v7_payload( payload_dir, expected_release_id=expected_release_id, ) if present[0] == v6: return validate_device_plane_manager_release_v6_payload( payload_dir, expected_release_id=expected_release_id, ) if present[0] == v5: return validate_device_plane_manager_release_v5_payload( payload_dir, expected_release_id=expected_release_id, ) if present[0] == v4: return validate_device_plane_manager_release_v4_payload( payload_dir, expected_release_id=expected_release_id, ) if present[0] == v3: return validate_device_plane_manager_release_v3_payload( payload_dir, expected_release_id=expected_release_id, ) if present[0] == v2: return validate_device_plane_manager_release_v2_payload( payload_dir, expected_release_id=expected_release_id, ) return validate_device_plane_manager_control_plane_payload( payload_dir, expected_release_id=expected_release_id, ) def validate_device_plane_edge_core_channel_bootstrap_payload( payload_dir, *, expected_transition_id=None, ): descriptor = read_strict_json( payload_dir / DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL, "Device Edge Core channel bootstrap descriptor", max_bytes=16 * 1024, ) transition_id = descriptor.get("transitionId") if ( not isinstance(transition_id, str) or not re.fullmatch(r"[A-Za-z0-9._-]{1,96}", transition_id) or ( expected_transition_id is not None and transition_id != expected_transition_id ) or descriptor != expected_device_plane_edge_core_channel_bootstrap_descriptor( transition_id ) ): die("Device Edge Core channel bootstrap descriptor mismatch") validate_device_plane_edge_core_channel_compose(payload_dir) return descriptor def validate_device_plane_edge_core_channel_upgrade_payload( payload_dir, *, expected_transition_id=None, ): descriptor = read_strict_json( payload_dir / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL, "Device Edge Core channel upgrade descriptor", max_bytes=16 * 1024, ) transition_id = descriptor.get("transitionId") if ( not isinstance(transition_id, str) or not re.fullmatch(r"[A-Za-z0-9._-]{1,96}", transition_id) or ( expected_transition_id is not None and transition_id != expected_transition_id ) or descriptor != expected_device_plane_edge_core_channel_upgrade_descriptor( transition_id ) ): die("Device Edge Core channel upgrade descriptor mismatch") validate_device_plane_edge_core_channel_compose(payload_dir) return descriptor def validate_device_plane_edge_core_channel_upgrade_v2_payload( payload_dir, *, expected_transition_id=None, ): descriptor = read_strict_json( payload_dir / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL, "Device Edge Core channel upgrade v2 descriptor", max_bytes=16 * 1024, ) transition_id = descriptor.get("transitionId") if ( not isinstance(transition_id, str) or not re.fullmatch(r"[A-Za-z0-9._-]{1,96}", transition_id) or ( expected_transition_id is not None and transition_id != expected_transition_id ) or descriptor != expected_device_plane_edge_core_channel_upgrade_v2_descriptor( transition_id ) ): die("Device Edge Core channel upgrade v2 descriptor mismatch") validate_device_plane_edge_core_channel_compose(payload_dir) return descriptor def validate_device_plane_edge_core_channel_upgrade_v4_payload( payload_dir, *, expected_transition_id=None, ): descriptor = read_strict_json( payload_dir / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_REL, "Device Edge Core channel upgrade v4 descriptor", max_bytes=16 * 1024, ) transition_id = descriptor.get("transitionId") if ( not isinstance(transition_id, str) or not re.fullmatch(r"[A-Za-z0-9._-]{1,96}", transition_id) or ( expected_transition_id is not None and transition_id != expected_transition_id ) or descriptor != expected_device_plane_edge_core_channel_upgrade_v4_descriptor( transition_id ) ): die("Device Edge Core channel upgrade v4 descriptor mismatch") validate_device_plane_edge_core_channel_upgrade_v4_base_compose( payload_dir ) return descriptor def validate_device_plane_control_core_release_payload( payload_dir, *, expected_release_id=None, ): v1 = payload_dir / DEVICE_PLANE_CONTROL_CORE_RELEASE_REL v2 = payload_dir / DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_REL v3 = payload_dir / DEVICE_PLANE_CONTROL_CORE_RELEASE_V3_REL v4 = payload_dir / DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_REL present = [ path for path in (v1, v2, v3, v4) if path.exists() or path.is_symlink() ] if len(present) != 1: die("Device Control Core release descriptor cardinality mismatch") schema_version = ( "v4" if present[0] == v4 else "v3" if present[0] == v3 else "v2" if present[0] == v2 else "v1" ) descriptor = read_strict_json( present[0], "Device Control Core release descriptor", max_bytes=16 * 1024, ) release_id = descriptor.get("releaseId") predecessor = descriptor.get("predecessor") valid_predecessor = ( isinstance(predecessor, dict) and set(predecessor) == {"kind", "patchId", "artifactSha256"} and predecessor.get("kind") in ( "edge-core-channel-upgrade-v4", "release", "migration-replay-checkpoint-recovery", ) and isinstance(predecessor.get("patchId"), str) and re.fullmatch( r"[A-Za-z0-9._-]{1,96}", predecessor["patchId"], ) and isinstance(predecessor.get("artifactSha256"), str) and re.fullmatch(r"[0-9a-f]{64}", predecessor["artifactSha256"]) ) if valid_predecessor and predecessor["kind"] == ( "edge-core-channel-upgrade-v4" ): valid_predecessor = ( predecessor["patchId"] == DEVICE_PLANE_CONTROL_CORE_RELEASE_FIRST_PREDECESSOR_PATCH_ID and predecessor["artifactSha256"] == DEVICE_PLANE_CONTROL_CORE_RELEASE_FIRST_PREDECESSOR_ARTIFACT_SHA256 ) elif valid_predecessor: if predecessor["kind"] == "migration-replay-checkpoint-recovery": valid_predecessor = ( schema_version == "v4" and predecessor["patchId"] == DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_PATCH_ID and predecessor["artifactSha256"] == DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_ARTIFACT_SHA256 ) else: valid_predecessor = ( predecessor["patchId"].startswith( "device-control-core-release-" ) and predecessor["patchId"] != release_id ) if ( not isinstance(release_id, str) or not re.fullmatch( r"device-control-core-release(?:-v[234])?-[A-Za-z0-9._-]{1,67}", release_id, ) or ( expected_release_id is not None and release_id != expected_release_id ) or not valid_predecessor or ( schema_version == "v4" and release_id != DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_PATCH_ID ) or descriptor != expected_device_plane_control_core_release_descriptor( release_id, predecessor, schema_version=schema_version, ) ): die("Device Control Core release descriptor mismatch") return descriptor def validate_device_plane_edge_core_channel_compose(payload_dir): compose = payload_dir / DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL if sha256_file(compose) != DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_SHA256: die("Device Edge Core channel Compose mismatch") compose_text = compose.read_text(encoding="utf-8") required = ( "device-control-core:", 'DEVICE_EDGE_CHANNEL_ENABLED: "true"', "DEVICE_EDGE_CHANNEL_CORE_KEY_FILE: " "/run/nodedc-secrets/device-edge-channel/core-private-key.pem", "DEVICE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE: " "/run/nodedc-secrets/device-edge-channel/core-certificate.pem", "DEVICE_EDGE_CHANNEL_TRUST_ROOT: " "/run/nodedc-secrets/device-edge-channel/peers", "source: /volume1/docker/nodedc-device-plane/secrets/" "device-edge-channel/core-private-key.pem", "source: /volume1/docker/nodedc-device-plane/secrets/" "device-edge-channel/core-certificate.pem", "source: /volume1/docker/nodedc-device-plane/secrets/" "device-edge-channel/peers", "name: nodedc-device-plane-egress", ) if any(value not in compose_text for value in required): die("Device Edge Core channel Compose boundary missing") forbidden = ( "device-manager:", "device-gateway:", "device-postgres:", "ports:", "PRIVATE KEY", "0.0.0.0:8443", "0.0.0.0:443", "9921:9921", ) if any(value in compose_text for value in forbidden): die("Device Edge Core channel Compose boundary violation") return compose def validate_device_plane_edge_core_channel_upgrade_v4_base_compose( payload_dir, ): compose = payload_dir / "docker-compose.device-plane.yml" if ( sha256_file(compose) != DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_BASE_COMPOSE_SHA256 ): die("Device Edge Core channel v4 base Compose mismatch") compose_text = compose.read_text(encoding="utf-8") try: core_block = compose_text.split( "\n device-control-core:", 1 )[1].split("\n device-gateway:", 1)[0] gateway_block = compose_text.split( "\n device-gateway:", 1 )[1].split("\nnetworks:", 1)[0] except IndexError: die("Device Edge Core channel v4 service topology is incomplete") if ( " - device-plane-private" not in core_block or " - device-plane-control" in core_block or " - device-plane-private" not in gateway_block or " - device-plane-control" not in gateway_block or "gw_priority:" in compose_text or "network_mode:" in compose_text or "privileged:" in compose_text ): die("Device Edge Core channel v4 network boundary mismatch") return compose def installed_device_plane_manager_compose_sha256(): root = DEVICE_PLANE_ROOT v1 = root / DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL v2 = root / DEVICE_PLANE_MANAGER_RELEASE_V2_REL v3 = root / DEVICE_PLANE_MANAGER_RELEASE_V3_REL v4 = root / DEVICE_PLANE_MANAGER_RELEASE_V4_REL v5 = root / DEVICE_PLANE_MANAGER_RELEASE_V5_REL v6 = root / DEVICE_PLANE_MANAGER_RELEASE_V6_REL v7 = root / DEVICE_PLANE_MANAGER_RELEASE_V7_REL v8 = root / DEVICE_PLANE_MANAGER_RELEASE_V8_REL v9 = root / DEVICE_PLANE_MANAGER_RELEASE_V9_REL v10 = root / DEVICE_PLANE_MANAGER_RELEASE_V10_REL v11 = root / DEVICE_PLANE_MANAGER_RELEASE_V11_REL v12 = root / DEVICE_PLANE_MANAGER_RELEASE_V12_REL v13 = root / DEVICE_PLANE_MANAGER_RELEASE_V13_REL if v13.exists() or v13.is_symlink(): descriptor = read_strict_json( v13, "installed Device Manager release v13 descriptor", max_bytes=16 * 1024, ) validate_device_plane_manager_release_descriptor( descriptor, schema_version="nodedc.device-plane.device-manager-release.v13", boundaries=expected_device_plane_manager_release_v13_boundaries(), ) return DEVICE_PLANE_MANAGER_RELEASE_V13_COMPOSE_SHA256 if v12.exists() or v12.is_symlink(): descriptor = read_strict_json( v12, "installed Device Manager release v12 descriptor", max_bytes=16 * 1024, ) validate_device_plane_manager_release_descriptor( descriptor, schema_version="nodedc.device-plane.device-manager-release.v12", boundaries=expected_device_plane_manager_release_v12_boundaries(), ) return DEVICE_PLANE_MANAGER_RELEASE_V12_COMPOSE_SHA256 if v11.exists() or v11.is_symlink(): descriptor = read_strict_json( v11, "installed Device Manager release v11 descriptor", max_bytes=16 * 1024, ) validate_device_plane_manager_release_descriptor( descriptor, schema_version="nodedc.device-plane.device-manager-release.v11", boundaries=expected_device_plane_manager_release_v11_boundaries(), ) return DEVICE_PLANE_MANAGER_RELEASE_V11_COMPOSE_SHA256 if v10.exists() or v10.is_symlink(): descriptor = read_strict_json( v10, "installed Device Manager release v10 descriptor", max_bytes=16 * 1024, ) validate_device_plane_manager_release_descriptor( descriptor, schema_version="nodedc.device-plane.device-manager-release.v10", boundaries=expected_device_plane_manager_release_v10_boundaries(), ) return DEVICE_PLANE_MANAGER_RELEASE_V10_COMPOSE_SHA256 if v9.exists() or v9.is_symlink(): descriptor = read_strict_json( v9, "installed Device Manager release v9 descriptor", max_bytes=16 * 1024, ) validate_device_plane_manager_release_descriptor( descriptor, schema_version="nodedc.device-plane.device-manager-release.v9", boundaries=expected_device_plane_manager_release_v9_boundaries(), ) return DEVICE_PLANE_MANAGER_RELEASE_V9_COMPOSE_SHA256 # A successful v2 overlay intentionally leaves the immutable v1 release # descriptor as predecessor evidence. Prefer the highest installed # generation, but validate its exact schema/security boundary before using # the generation-specific Compose digest. During a failed v2 apply, # rollback removes the candidate-only v2 descriptor before rebuilding the # restored v1 runtime, so the same lookup follows the restored source. if v8.exists() or v8.is_symlink(): descriptor = read_strict_json( v8, "installed Device Manager release v8 descriptor", max_bytes=16 * 1024, ) validate_device_plane_manager_release_descriptor( descriptor, schema_version="nodedc.device-plane.device-manager-release.v8", boundaries=expected_device_plane_manager_release_v8_boundaries(), ) return DEVICE_PLANE_MANAGER_RELEASE_V8_COMPOSE_SHA256 if v7.exists() or v7.is_symlink(): descriptor = read_strict_json( v7, "installed Device Manager release v7 descriptor", max_bytes=16 * 1024, ) validate_device_plane_manager_release_descriptor( descriptor, schema_version="nodedc.device-plane.device-manager-release.v7", boundaries=expected_device_plane_manager_release_v7_boundaries(), ) return DEVICE_PLANE_MANAGER_RELEASE_V7_COMPOSE_SHA256 if v6.exists() or v6.is_symlink(): descriptor = read_strict_json( v6, "installed Device Manager release v6 descriptor", max_bytes=16 * 1024, ) validate_device_plane_manager_release_descriptor( descriptor, schema_version="nodedc.device-plane.device-manager-release.v6", boundaries=expected_device_plane_manager_release_v6_boundaries(), ) return DEVICE_PLANE_MANAGER_RELEASE_V6_COMPOSE_SHA256 if v5.exists() or v5.is_symlink(): descriptor = read_strict_json( v5, "installed Device Manager release v5 descriptor", max_bytes=16 * 1024, ) validate_device_plane_manager_release_descriptor( descriptor, schema_version="nodedc.device-plane.device-manager-release.v5", boundaries=expected_device_plane_manager_release_v5_boundaries(), ) return DEVICE_PLANE_MANAGER_RELEASE_V5_COMPOSE_SHA256 if v4.exists() or v4.is_symlink(): descriptor = read_strict_json( v4, "installed Device Manager release v4 descriptor", max_bytes=16 * 1024, ) validate_device_plane_manager_release_descriptor( descriptor, schema_version="nodedc.device-plane.device-manager-release.v4", boundaries=expected_device_plane_manager_release_v4_boundaries(), ) return DEVICE_PLANE_MANAGER_RELEASE_V4_COMPOSE_SHA256 if v3.exists() or v3.is_symlink(): descriptor = read_strict_json( v3, "installed Device Manager release v3 descriptor", max_bytes=16 * 1024, ) validate_device_plane_manager_release_descriptor( descriptor, schema_version="nodedc.device-plane.device-manager-release.v3", boundaries=expected_device_plane_manager_release_v3_boundaries(), ) return DEVICE_PLANE_MANAGER_RELEASE_V1_COMPOSE_SHA256 if v2.exists() or v2.is_symlink(): descriptor = read_strict_json( v2, "installed Device Manager release v2 descriptor", max_bytes=16 * 1024, ) validate_device_plane_manager_release_descriptor( descriptor, schema_version="nodedc.device-plane.device-manager-release.v2", boundaries=expected_device_plane_manager_release_v2_boundaries(), ) return DEVICE_PLANE_MANAGER_RELEASE_V2_COMPOSE_SHA256 if v1.exists() or v1.is_symlink(): descriptor = read_strict_json( v1, "installed Device Manager release v1 descriptor", max_bytes=16 * 1024, ) validate_device_plane_manager_release_descriptor( descriptor, schema_version="nodedc.device-plane.device-manager-release.v1", boundaries=expected_device_plane_manager_release_v1_boundaries(), ) return DEVICE_PLANE_MANAGER_RELEASE_V1_COMPOSE_SHA256 die("installed Device Manager release descriptor is missing") def validate_device_plane_manager_failed_control_plane_payload(payload_dir): descriptor = read_strict_json( payload_dir / DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_REL, "failed Device Manager control-plane descriptor", max_bytes=16 * 1024, ) if ( descriptor != expected_device_plane_manager_failed_control_plane_descriptor() ): die("failed Device Manager control-plane descriptor mismatch") compose_path = payload_dir / DEVICE_PLANE_MANAGER_COMPOSE_REL if sha256_file(compose_path) != DEVICE_PLANE_MANAGER_COMPOSE_SHA256: die("failed Device Manager control-plane Compose mismatch") return descriptor def validate_device_plane_manager_reconciliation_payload(payload_dir): descriptor = read_strict_json( payload_dir / DEVICE_PLANE_MANAGER_RECONCILIATION_REL, "Device Manager control-plane reconciliation descriptor", max_bytes=16 * 1024, ) if descriptor != expected_device_plane_manager_reconciliation_descriptor(): die("Device Manager control-plane reconciliation descriptor mismatch") return descriptor def validate_device_plane_manager_v2_reconciliation_payload(payload_dir): descriptor = read_strict_json( payload_dir / DEVICE_PLANE_MANAGER_V2_RECONCILIATION_REL, "Device Manager v2 control-plane reconciliation descriptor", max_bytes=16 * 1024, ) if ( descriptor != expected_device_plane_manager_v2_reconciliation_descriptor() ): die( "Device Manager v2 control-plane reconciliation " "descriptor mismatch" ) return descriptor def validate_device_plane_control_core_v3_reconciliation_payload(payload_dir): descriptor = read_strict_json( payload_dir / DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_REL, "Device Control Core v3 reconciliation descriptor", max_bytes=16 * 1024, ) if ( descriptor != expected_device_plane_control_core_v3_reconciliation_descriptor() ): die("Device Control Core v3 reconciliation descriptor mismatch") return descriptor def validate_device_plane_control_core_incident_audit_payload(payload_dir): descriptor = read_strict_json( payload_dir / DEVICE_PLANE_CONTROL_CORE_INCIDENT_AUDIT_REL, "Device Control Core incident audit descriptor", max_bytes=16 * 1024, ) if descriptor != expected_device_plane_control_core_incident_audit_descriptor(): die("Device Control Core incident audit descriptor mismatch") return descriptor def validate_device_plane_control_core_migration_replay_audit_payload( payload_dir, ): descriptor = read_strict_json( payload_dir / DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_AUDIT_REL, "Device Control Core migration replay audit descriptor", max_bytes=16 * 1024, ) if ( descriptor != expected_device_plane_control_core_migration_replay_audit_descriptor() ): die("Device Control Core migration replay audit descriptor mismatch") return descriptor def validate_device_plane_control_core_migration_replay_recovery_payload( payload_dir, ): descriptor = read_strict_json( payload_dir / DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_REL, "Device Control Core migration replay recovery descriptor", max_bytes=16 * 1024, ) if ( descriptor != expected_device_plane_control_core_migration_replay_recovery_descriptor() ): die("Device Control Core migration replay recovery descriptor mismatch") validate_device_plane_control_core_migration_014_repair(payload_dir) return descriptor def validate_device_plane_control_core_migration_replay_checkpoint_recovery_payload( payload_dir, ): descriptor = read_strict_json( payload_dir / DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_REL, "Device Control Core migration replay checkpoint recovery descriptor", max_bytes=16 * 1024, ) if ( descriptor != expected_device_plane_control_core_migration_replay_checkpoint_recovery_descriptor() ): die( "Device Control Core migration replay checkpoint recovery " "descriptor mismatch" ) validate_device_plane_control_core_migration_014_repair(payload_dir) return descriptor def validate_device_plane_control_core_migration_014_repair(payload_dir): migration = payload_dir / DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_REL try: migration_stat = migration.lstat() migration_text = migration.read_text(encoding="utf-8") except (FileNotFoundError, OSError, UnicodeDecodeError): die("Device Control Core migration 014 repair is unreadable") if ( stat.S_ISLNK(migration_stat.st_mode) or not stat.S_ISREG(migration_stat.st_mode) or sha256_file(migration) != DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_TARGET_SHA256 or ")) not valid;" not in migration_text.lower() or re.search( r"(?im)^\s*(?:delete|update|insert|truncate)\b", migration_text, ) ): die("Device Control Core migration 014 repair mismatch") def is_device_plane_postgres_bootstrap_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_POSTGRES_BOOTSTRAP_ENTRIES ) def is_device_plane_foundation_recovery_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_FOUNDATION_RECOVERY_ENTRIES ) def is_device_plane_foundation_network_publication_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_ENTRIES ) def is_device_plane_b2_discovery_ingress_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_B2_DISCOVERY_INGRESS_ENTRIES ) def is_device_plane_b2_discovery_rollback_recovery_slice( component, entries, ): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_ENTRIES ) def is_device_plane_backhaul_target_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_BACKHAUL_TARGET_ENTRIES ) def is_device_plane_backhaul_vps_enrollment_slice(component, entries): return ( component == "device-plane" and entries is not None and tuple(entries) == DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_ENTRIES ) def expected_device_plane_backhaul_vps_enrollment_descriptor(): return { "schemaVersion": ( "nodedc.device-plane.backhaul-vps-enrollment.v1" ), "mode": "rotate-backhaul-client-mini-to-vps", "predecessorPatchId": ( DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_PREDECESSOR_PATCH_ID ), "predecessorArtifactSha256": ( DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_PREDECESSOR_ARTIFACT_SHA256 ), "sourceAction": "publish-vps-enrollment-marker-only", "runtimeAction": ( "rotate-authorized-key-and-recreate-backhaul-target" ), "selectedServices": [DEVICE_PLANE_BACKHAUL_TARGET_SERVICE], "preservedServices": list(DEVICE_PLANE_RUNTIME_SERVICES), "previousEnrollment": "device-edge-backhaul.pub", "nextEnrollment": "device-edge-vps-backhaul.pub", "nextKeyFingerprint": ( DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_FINGERPRINT ), "permittedTarget": DEVICE_PLANE_BACKHAUL_PERMITTED_TARGET, "tailnetAddress": DEVICE_PLANE_BACKHAUL_TAILNET_ADDRESS, "dockerPortPublication": "disabled", "routerNatFirewall": "unchanged", "edgePublicIngress": "disabled", "funnel": "disabled", "commandTransport": "disabled", "gelios": "untouched", "rollback": ( "restore-previous-authorized-key-and-recreate-target" ), } def validate_device_plane_backhaul_vps_enrollment_payload(payload_dir): descriptor = read_strict_json( payload_dir / DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_REL, "Device Plane VPS backhaul enrollment descriptor", max_bytes=16 * 1024, ) if descriptor != expected_device_plane_backhaul_vps_enrollment_descriptor(): die("Device Plane VPS backhaul enrollment descriptor mismatch") return descriptor def expected_device_plane_backhaul_target_descriptor(): return { "schemaVersion": ( "nodedc.device-plane.backhaul-target-tailnet-serve.v1" ), "mode": "failed-backhaul-target-to-loopback-tailnet-serve", "failedPatchId": DEVICE_PLANE_BACKHAUL_FAILED_PATCH_ID, "failedArtifactSha256": ( DEVICE_PLANE_BACKHAUL_FAILED_ARTIFACT_SHA256 ), "failedBackupId": DEVICE_PLANE_BACKHAUL_FAILED_BACKUP_ID, "predecessorPatchId": ( DEVICE_PLANE_BACKHAUL_TARGET_PREDECESSOR_PATCH_ID ), "predecessorArtifactSha256": ( DEVICE_PLANE_BACKHAUL_TARGET_PREDECESSOR_ARTIFACT_SHA256 ), "sourceAction": "publish-loopback-backhaul-target-source", "runtimeAction": ( "build-create-target-and-register-private-tailnet-serve" ), "composeOverlay": DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL, "selectedServices": [DEVICE_PLANE_BACKHAUL_TARGET_SERVICE], "preservedServices": list(DEVICE_PLANE_RUNTIME_SERVICES), "loopbackListenAddress": DEVICE_PLANE_BACKHAUL_LOOPBACK_ADDRESS, "listenPort": DEVICE_PLANE_BACKHAUL_LISTEN_PORT, "tailnetAddress": DEVICE_PLANE_BACKHAUL_TAILNET_ADDRESS, "tailnetExposure": "tailscale-serve-private", "tailscaleServeTarget": ( DEVICE_PLANE_BACKHAUL_TAILSCALE_SERVE_TARGET ), "permittedTarget": DEVICE_PLANE_BACKHAUL_PERMITTED_TARGET, "networkMode": "host", "dockerPortPublication": "disabled", "routerNatFirewall": "unchanged", "edgePublicIngress": "disabled", "funnel": "disabled", "commandTransport": "disabled", "gelios": "untouched", "databaseVolume": DEVICE_PLANE_POSTGRES_VOLUME, "runtimeTrust": "runner-managed", "rollback": "remove-tailnet-serve-target-and-restore-source", } def validate_device_plane_backhaul_target_payload(payload_dir): descriptor = read_strict_json( payload_dir / DEVICE_PLANE_BACKHAUL_TARGET_REL, "Device Plane backhaul target descriptor", max_bytes=16 * 1024, ) if descriptor != expected_device_plane_backhaul_target_descriptor(): die("Device Plane backhaul target descriptor mismatch") compose = payload_dir / DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL if ( compose.is_symlink() or not compose.is_file() or sha256_file(compose) != DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_SHA256 ): die("Device Plane backhaul target Compose mismatch") return descriptor def read_device_plane_ed25519_enrollment_public_key(path, comment, label): try: path_stat = path.lstat() text = path.read_text(encoding="ascii") except (FileNotFoundError, OSError, UnicodeDecodeError): die( f"{label} is missing or unreadable: " f"{path}" ) if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or path_stat.st_size > 1024 ): die(f"{label} is unsafe") if text != text.strip() + "\n" or "\n" in text.strip(): die(f"{label} must be one line") parts = text.strip().split() if len(parts) not in (2, 3) or parts[0] != "ssh-ed25519": die(f"{label} type mismatch") try: blob = base64.b64decode(parts[1], validate=True) except Exception: die(f"{label} encoding mismatch") expected_prefix = b"\x00\x00\x00\x0bssh-ed25519\x00\x00\x00\x20" if len(blob) != len(expected_prefix) + 32 or not blob.startswith( expected_prefix ): die(f"{label} shape mismatch") normalized = f"ssh-ed25519 {parts[1]} {comment}" return { "line": normalized, "sha256": hashlib.sha256((normalized + "\n").encode("ascii")).hexdigest(), "fingerprint": ( "SHA256:" + base64.b64encode(hashlib.sha256(blob).digest()) .decode("ascii") .rstrip("=") ), } def read_device_plane_backhaul_enrollment_public_key(): return read_device_plane_ed25519_enrollment_public_key( DEVICE_PLANE_BACKHAUL_ENROLLMENT_PUBLIC_KEY_FILE, "nodedc-device-edge-backhaul", "Device Plane Edge enrollment public key", ) def read_device_plane_backhaul_vps_enrollment_public_key(): enrollment = read_device_plane_ed25519_enrollment_public_key( DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_PUBLIC_KEY_FILE, "nodedc-device-edge-vps-backhaul", "Device Plane VPS Edge enrollment public key", ) if ( enrollment["fingerprint"] != DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_FINGERPRINT ): die("Device Plane VPS Edge enrollment fingerprint mismatch") return enrollment def device_plane_tailscale_drop_privileges(uid, gid): def demote(): os.setgroups([]) os.setgid(gid) os.setuid(uid) return demote def validate_device_plane_tailscale_cli(): privilege_path = DEVICE_PLANE_TAILSCALE_PRIVILEGE try: privilege_stat = privilege_path.lstat() except FileNotFoundError: die( "Synology Tailscale package privilege contract is missing: " f"{privilege_path}" ) if ( not stat.S_ISREG(privilege_stat.st_mode) or privilege_stat.st_uid != 0 or privilege_stat.st_mode & (stat.S_IWGRP | stat.S_IWOTH) ): die("Synology Tailscale package privilege boundary mismatch") privilege = read_strict_json( privilege_path, "Synology Tailscale package privilege contract", max_bytes=16 * 1024, ) defaults = privilege.get("defaults") if ( not isinstance(defaults, dict) or defaults.get("run-as") != "package" or privilege.get("username") != DEVICE_PLANE_TAILSCALE_USER or privilege.get("groupname") != DEVICE_PLANE_TAILSCALE_GROUP ): die("Synology Tailscale package identity contract mismatch") try: account = pwd.getpwnam(DEVICE_PLANE_TAILSCALE_USER) group = grp.getgrnam(DEVICE_PLANE_TAILSCALE_GROUP) except KeyError: die("Synology Tailscale package account is missing") if ( account.pw_uid <= 0 or group.gr_gid <= 0 or account.pw_gid != group.gr_gid ): die("Synology Tailscale package account boundary mismatch") try: binary_lstat = DEVICE_PLANE_TAILSCALE.lstat() except FileNotFoundError: die( "Synology Tailscale CLI is missing: " f"{DEVICE_PLANE_TAILSCALE}" ) if ( not stat.S_ISREG(binary_lstat.st_mode) or binary_lstat.st_uid not in (0, account.pw_uid) or binary_lstat.st_mode & ( stat.S_IWGRP | stat.S_IWOTH | stat.S_ISUID | stat.S_ISGID ) or binary_lstat.st_size < 1024 * 1024 or binary_lstat.st_size > 256 * 1024 * 1024 ): die( "Synology Tailscale CLI file boundary mismatch: " f"uid={binary_lstat.st_uid} " f"mode={stat.S_IMODE(binary_lstat.st_mode):04o} " f"size={binary_lstat.st_size}" ) if binary_lstat.st_uid == account.pw_uid: executable = bool(binary_lstat.st_mode & stat.S_IXUSR) elif binary_lstat.st_gid == group.gr_gid: executable = bool(binary_lstat.st_mode & stat.S_IXGRP) else: executable = bool(binary_lstat.st_mode & stat.S_IXOTH) if not executable: die("Synology Tailscale CLI is not executable by package account") context = { "binary": str(DEVICE_PLANE_TAILSCALE), "uid": account.pw_uid, "gid": group.gr_gid, "binarySha256": sha256_file(DEVICE_PLANE_TAILSCALE), } try: help_result = subprocess.run( [context["binary"], "serve", "--help"], check=True, capture_output=True, text=True, timeout=15, preexec_fn=device_plane_tailscale_drop_privileges( context["uid"], context["gid"], ), ) except (OSError, subprocess.CalledProcessError, subprocess.TimeoutExpired): die("Synology Tailscale Serve capability is unavailable") help_text = help_result.stdout + help_result.stderr for required in ("--tcp", "--bg", "--yes"): if required not in help_text: die( "Synology Tailscale Serve capability mismatch: " f"{required}" ) return context def run_device_plane_tailscale(args, **kwargs): context = validate_device_plane_tailscale_cli() return subprocess.run( [context["binary"], *args], preexec_fn=device_plane_tailscale_drop_privileges( context["uid"], context["gid"], ), **kwargs, ) def read_device_plane_tailscale_json(*args, allow_no_config=False): try: result = run_device_plane_tailscale( args, check=False, capture_output=True, text=True, timeout=20, ) except (OSError, subprocess.TimeoutExpired): die("Synology Tailscale state query failed") combined = (result.stdout + "\n" + result.stderr).strip() if result.returncode != 0: if allow_no_config and "no serve config" in combined.lower(): return {} die( "Synology Tailscale state query failed: " f"exit={result.returncode}" ) if len(result.stdout.encode("utf-8")) > 4 * 1024 * 1024: die("Synology Tailscale state response is too large") try: value = json.loads(result.stdout or "{}") except json.JSONDecodeError: die("Synology Tailscale state response is invalid JSON") if not isinstance(value, dict): die("Synology Tailscale state response must be an object") return value def device_plane_tailscale_handlers_for_port(config, port): matches = [] def walk(value, path): if not isinstance(value, dict): return tcp = value.get("TCP") if tcp is not None: if not isinstance(tcp, dict): die("Synology Tailscale TCP Serve state is invalid") handler = tcp.get(str(port)) if handler is not None: matches.append((tuple(path), handler)) for key in ("Foreground", "Services"): nested = value.get(key) if nested is None: continue if not isinstance(nested, dict): die("Synology Tailscale nested Serve state is invalid") for name, child in nested.items(): walk(child, (*path, key, str(name))) walk(config, ()) return matches def device_plane_tailscale_funnel_uses_port(config, port): allow_funnel = config.get("AllowFunnel") or {} if not isinstance(allow_funnel, dict): die("Synology Tailscale Funnel state is invalid") suffix = f":{port}" return any( enabled is True and (str(host_port) == str(port) or str(host_port).endswith(suffix)) for host_port, enabled in allow_funnel.items() ) def remove_device_plane_tailscale_port(config, port): normalized = json.loads(json.dumps(config)) tcp = normalized.get("TCP") if isinstance(tcp, dict): tcp.pop(str(port), None) if not tcp: normalized.pop("TCP", None) return normalized def validate_device_plane_tailscale_runtime(require_target): status = read_device_plane_tailscale_json("status", "--json") self_state = status.get("Self") if not isinstance(self_state, dict): die("Synology Tailscale self state is missing") addresses = self_state.get("TailscaleIPs") or [] if ( not isinstance(addresses, list) or DEVICE_PLANE_BACKHAUL_TAILNET_ADDRESS not in addresses or self_state.get("Online") is not True or status.get("BackendState") != "Running" ): die("Synology Tailscale tailnet state mismatch") serve = read_device_plane_tailscale_json( "serve", "status", "--json", allow_no_config=True, ) handlers = device_plane_tailscale_handlers_for_port( serve, DEVICE_PLANE_BACKHAUL_LISTEN_PORT, ) if device_plane_tailscale_funnel_uses_port( serve, DEVICE_PLANE_BACKHAUL_LISTEN_PORT, ): die("Synology Tailscale Funnel exposure is forbidden") if require_target: expected_handler = { "TCPForward": ( f"{DEVICE_PLANE_BACKHAUL_LOOPBACK_ADDRESS}:" f"{DEVICE_PLANE_BACKHAUL_LISTEN_PORT}" ) } if handlers != [((), expected_handler)]: die("Synology Tailscale private Serve target mismatch") elif handlers: die("Synology Tailscale port 2222 is already configured") return { "self": self_state, "serve": serve, } def enable_device_plane_tailscale_serve(expected_before): current = validate_device_plane_tailscale_runtime( require_target=False )["serve"] if current != expected_before: die("Synology Tailscale Serve state changed before activation") run_device_plane_tailscale( [ "serve", "--bg", "--yes", f"--tcp={DEVICE_PLANE_BACKHAUL_LISTEN_PORT}", DEVICE_PLANE_BACKHAUL_TAILSCALE_SERVE_TARGET, ], check=True, timeout=30, ) after = validate_device_plane_tailscale_runtime( require_target=True )["serve"] if remove_device_plane_tailscale_port( after, DEVICE_PLANE_BACKHAUL_LISTEN_PORT, ) != expected_before: die("Synology Tailscale Serve activation changed unrelated state") return after def disable_device_plane_tailscale_serve(expected_after): current = read_device_plane_tailscale_json( "serve", "status", "--json", allow_no_config=True, ) handlers = device_plane_tailscale_handlers_for_port( current, DEVICE_PLANE_BACKHAUL_LISTEN_PORT, ) if not handlers: if current != expected_after: die("Synology Tailscale Serve rollback state mismatch") return False expected_handler = { "TCPForward": ( f"{DEVICE_PLANE_BACKHAUL_LOOPBACK_ADDRESS}:" f"{DEVICE_PLANE_BACKHAUL_LISTEN_PORT}" ) } if ( handlers != [((), expected_handler)] or device_plane_tailscale_funnel_uses_port( current, DEVICE_PLANE_BACKHAUL_LISTEN_PORT, ) or remove_device_plane_tailscale_port( current, DEVICE_PLANE_BACKHAUL_LISTEN_PORT, ) != expected_after ): die("Synology Tailscale Serve rollback refuses unexpected state") run_device_plane_tailscale( [ "serve", f"--tcp={DEVICE_PLANE_BACKHAUL_LISTEN_PORT}", "off", ], check=True, timeout=30, ) final = validate_device_plane_tailscale_runtime( require_target=False )["serve"] if final != expected_after: die("Synology Tailscale Serve rollback changed unrelated state") return True def expected_failed_device_plane_backhaul_target_descriptor(): return { "schemaVersion": "nodedc.device-plane.backhaul-target.v1", "mode": "restricted-edge-backhaul-target", "predecessorPatchId": ( DEVICE_PLANE_BACKHAUL_TARGET_PREDECESSOR_PATCH_ID ), "predecessorArtifactSha256": ( DEVICE_PLANE_BACKHAUL_TARGET_PREDECESSOR_ARTIFACT_SHA256 ), "sourceAction": "publish-restricted-backhaul-target-source", "runtimeAction": "build-and-create-device-backhaul-target", "composeOverlay": DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL, "selectedServices": [DEVICE_PLANE_BACKHAUL_TARGET_SERVICE], "preservedServices": list(DEVICE_PLANE_RUNTIME_SERVICES), "listenAddress": DEVICE_PLANE_BACKHAUL_TAILNET_ADDRESS, "listenPort": DEVICE_PLANE_BACKHAUL_LISTEN_PORT, "permittedTarget": DEVICE_PLANE_BACKHAUL_PERMITTED_TARGET, "networkMode": "host", "edgeIngress": "disabled", "commandTransport": "disabled", "gelios": "untouched", "databaseVolume": DEVICE_PLANE_POSTGRES_VOLUME, "runtimeTrust": "runner-managed", "rollback": "remove-target-and-restore-source", } def validate_device_plane_backhaul_failed_evidence(): backup_dir = BACKUPS_DIR / DEVICE_PLANE_BACKHAUL_FAILED_BACKUP_ID try: backup_stat = backup_dir.lstat() except FileNotFoundError: die("Device Plane failed backhaul backup is missing") if stat.S_ISLNK(backup_stat.st_mode) or not stat.S_ISDIR( backup_stat.st_mode ): die("Device Plane failed backhaul backup is unsafe") if {child.name for child in backup_dir.iterdir()} != set( DEVICE_PLANE_BACKHAUL_FAILED_BACKUP_SHA256 ): die("Device Plane failed backhaul backup file set mismatch") for name, expected_sha256 in ( DEVICE_PLANE_BACKHAUL_FAILED_BACKUP_SHA256.items() ): path = backup_dir / name path_stat = path.lstat() if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Device Plane failed backhaul backup drift detected: " f"{name}" ) failed_artifact = FAILED_DIR / DEVICE_PLANE_BACKHAUL_FAILED_ARTIFACT try: failed_stat = failed_artifact.lstat() except FileNotFoundError: die("Device Plane failed backhaul artifact is missing") if ( stat.S_ISLNK(failed_stat.st_mode) or not stat.S_ISREG(failed_stat.st_mode) or sha256_file(failed_artifact) != DEVICE_PLANE_BACKHAUL_FAILED_ARTIFACT_SHA256 ): die("Device Plane failed backhaul artifact evidence mismatch") try: state_stat = FAILED_STATE_FILE.lstat() state_lines = FAILED_STATE_FILE.read_text( encoding="utf-8" ).splitlines() except (FileNotFoundError, OSError, UnicodeDecodeError): die("Device Plane failed backhaul journal is unreadable") if stat.S_ISLNK(state_stat.st_mode) or not stat.S_ISREG( state_stat.st_mode ): die("Device Plane failed backhaul journal is unsafe") records = [] for line in state_lines: try: value = json.loads(line) except json.JSONDecodeError: die("Device Plane failed backhaul journal contains invalid JSON") if ( isinstance(value, dict) and value.get("id") == DEVICE_PLANE_BACKHAUL_FAILED_PATCH_ID ): records.append(value) if len(records) != 1: die("Device Plane failed backhaul journal evidence count mismatch") record = records[0] if ( record.get("artifact") != DEVICE_PLANE_BACKHAUL_FAILED_ARTIFACT or record.get("backup_id") != DEVICE_PLANE_BACKHAUL_FAILED_BACKUP_ID or record.get("component") != "device-plane" or record.get("sha256") != DEVICE_PLANE_BACKHAUL_FAILED_ARTIFACT_SHA256 or record.get("started_apply") is not True or record.get("rollback_status") != ( "ok:device-plane-overlay:source-restored-target-removed-" "preserved-runtime-unchanged:3" ) or record.get("status") != "failed" or record.get("message") != DEVICE_PLANE_BACKHAUL_FAILED_MESSAGE ): die("Device Plane failed backhaul journal evidence mismatch") with tempfile.TemporaryDirectory( prefix="device-plane-failed-backhaul-", dir=TMP_DIR, ) as directory: failed_work = Path(directory) safe_extract(failed_artifact, failed_work) failed_manifest = parse_manifest(failed_work / "manifest.env") failed_entries = parse_files_list(failed_work / "files.txt") failed_payload = failed_work / "payload" expected_payload_paths = { "deployment", DEVICE_PLANE_BACKHAUL_FAILED_TARGET_REL, DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL, "services", "services/device-backhaul-target", "services/device-backhaul-target/Dockerfile", "services/device-backhaul-target/sshd_config", } actual_payload_paths = { path.relative_to(failed_payload).as_posix() for path in failed_payload.rglob("*") } if ( failed_manifest.get("id") != DEVICE_PLANE_BACKHAUL_FAILED_PATCH_ID or failed_manifest.get("component") != "device-plane" or failed_manifest.get("type") != "app-overlay" or tuple(failed_entries) != DEVICE_PLANE_BACKHAUL_FAILED_TARGET_ENTRIES or actual_payload_paths != expected_payload_paths ): die("Device Plane failed backhaul artifact contract mismatch") failed_descriptor = read_strict_json( failed_payload / DEVICE_PLANE_BACKHAUL_FAILED_TARGET_REL, "failed Device Plane backhaul descriptor", max_bytes=16 * 1024, ) if ( failed_descriptor != expected_failed_device_plane_backhaul_target_descriptor() ): die("Device Plane failed backhaul descriptor mismatch") return { "backup": backup_dir, "failedArtifact": failed_artifact, } def validate_device_plane_backhaul_target_evidence(payload_dir): descriptor = validate_device_plane_backhaul_target_payload(payload_dir) failed = validate_device_plane_backhaul_failed_evidence() if not state_has_patch_id( DEVICE_PLANE_BACKHAUL_TARGET_PREDECESSOR_PATCH_ID ): die("Device Plane backhaul predecessor patch is not applied") if not state_has_sha( DEVICE_PLANE_BACKHAUL_TARGET_PREDECESSOR_ARTIFACT_SHA256 ): die("Device Plane backhaul predecessor artifact is not applied") root = component_root("device-plane") compose = root / "docker-compose.device-plane.yml" if ( compose.is_symlink() or not compose.is_file() or sha256_file(compose) != DEVICE_PLANE_B2_DISCOVERY_INGRESS_COMPOSE_SHA256 ): die("Device Plane backhaul predecessor Compose drift detected") installed_b2 = read_strict_json( root / DEVICE_PLANE_B2_DISCOVERY_INGRESS_REL, "installed Device Plane B2 descriptor", max_bytes=16 * 1024, ) if installed_b2 != expected_device_plane_b2_discovery_ingress_descriptor(): die("Device Plane backhaul predecessor B2 descriptor mismatch") for rel in ( DEVICE_PLANE_BACKHAUL_FAILED_TARGET_REL, DEVICE_PLANE_BACKHAUL_TARGET_REL, DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL, "services/device-backhaul-target", ): target = root / rel if target.exists() or target.is_symlink(): die( "Device Plane failed backhaul source was not restored: " f"{rel}" ) if device_plane_service_container_ids( DEVICE_PLANE_BACKHAUL_TARGET_SERVICE ): die("Device Plane backhaul target container already exists") runtime = device_plane_runtime_inventory(DEVICE_PLANE_RUNTIME_SERVICES) names = device_plane_inventory_service_names(runtime) if set(names) != set(DEVICE_PLANE_RUNTIME_SERVICES): die("Device Plane backhaul predecessor runtime is incomplete") for item in runtime["services"]: if ( item["status"] != "running" or item["running"] is not True or item["health"] != "healthy" or item["restartCount"] != 0 ): die( "Device Plane backhaul predecessor service is unhealthy: " f"{item['service']}" ) assert_loopback_tcp_port_open(9921) enrollment = read_device_plane_backhaul_enrollment_public_key() tailscale_cli = validate_device_plane_tailscale_cli() tailscale = validate_device_plane_tailscale_runtime( require_target=False ) return { "mode": descriptor["mode"], "failedBackup": str(failed["backup"]), "failedArtifact": str(failed["failedArtifact"]), "runtime": runtime, "enrollmentPublicKeySha256": enrollment["sha256"], "tailscaleCli": tailscale_cli, "tailscaleServeBefore": tailscale["serve"], } def validate_device_plane_backhaul_vps_enrollment_evidence(payload_dir): descriptor = validate_device_plane_backhaul_vps_enrollment_payload( payload_dir ) if not state_has_patch_id( DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_PREDECESSOR_PATCH_ID ): die("Device Plane VPS enrollment predecessor patch is not applied") if not state_has_sha( DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_PREDECESSOR_ARTIFACT_SHA256 ): die("Device Plane VPS enrollment predecessor artifact is not applied") root = component_root("device-plane") marker = root / DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_REL if marker.exists() or marker.is_symlink(): die("Device Plane VPS enrollment marker already exists") runtime = device_plane_runtime_inventory(DEVICE_PLANE_RUNTIME_SERVICES) validate_device_plane_backhaul_target_runtime(runtime) previous = read_device_plane_backhaul_enrollment_public_key() next_enrollment = read_device_plane_backhaul_vps_enrollment_public_key() if previous["line"] == next_enrollment["line"]: die("Device Plane VPS enrollment key is not a new identity") return { "mode": descriptor["mode"], "runtime": runtime, "previousEnrollmentPublicKeySha256": previous["sha256"], "nextEnrollmentPublicKeySha256": next_enrollment["sha256"], "nextKeyFingerprint": next_enrollment["fingerprint"], } def expected_device_plane_foundation_recovery_descriptor(): return { "schemaVersion": "nodedc.device-plane.foundation-recovery.v1", "mode": "failed-foundation-live-runtime-adoption", "failedPatchId": DEVICE_PLANE_FOUNDATION_FAILED_PATCH_ID, "failedArtifactSha256": ( DEVICE_PLANE_FOUNDATION_FAILED_ARTIFACT_SHA256 ), "backupId": DEVICE_PLANE_FOUNDATION_RECOVERY_BACKUP_ID, "sourceAction": "publish-exact-failed-artifact-source", "runtimeAction": "read-only-acceptance", "preservedServices": [ "device-control-core", "device-gateway", "device-postgres", ], "databaseVolume": DEVICE_PLANE_POSTGRES_VOLUME, "rollback": "source-only-runtime-unchanged", } def validate_device_plane_foundation_recovery_payload(payload_dir): descriptor = read_strict_json( payload_dir / DEVICE_PLANE_FOUNDATION_RECOVERY_REL, "Device Plane foundation recovery descriptor", max_bytes=16 * 1024, ) if descriptor != expected_device_plane_foundation_recovery_descriptor(): die("Device Plane foundation recovery descriptor mismatch") return descriptor def expected_device_plane_foundation_network_publication_descriptor(): return { "schemaVersion": ( "nodedc.device-plane.foundation-network-publication.v1" ), "mode": "failed-foundation-network-publication-correction", "failedRecoveryPatchId": ( DEVICE_PLANE_FOUNDATION_RECOVERY_FAILED_PATCH_ID ), "failedRecoveryArtifactSha256": ( DEVICE_PLANE_FOUNDATION_RECOVERY_FAILED_ARTIFACT_SHA256 ), "failedRecoveryBackupId": ( DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_BACKUP_ID ), "sourceAction": "publish-network-corrected-foundation-source", "runtimeAction": "recreate-stateless-services-no-build", "selectedServices": [ "device-control-core", "device-gateway", ], "preservedServices": ["device-postgres"], "privateNetwork": DEVICE_PLANE_PRIVATE_NETWORK, "controlNetwork": DEVICE_PLANE_CONTROL_NETWORK, "publishedLoopbackPorts": [ "127.0.0.1:18120:18120", "127.0.0.1:18121:18121", ], "databaseVolume": DEVICE_PLANE_POSTGRES_VOLUME, "rollback": ( "restore-partial-source-and-internal-only-stateless-runtime" ), } def validate_device_plane_foundation_network_publication_payload(payload_dir): descriptor = read_strict_json( payload_dir / DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_REL, "Device Plane foundation network-publication descriptor", max_bytes=16 * 1024, ) expected = ( expected_device_plane_foundation_network_publication_descriptor() ) if descriptor != expected: die( "Device Plane foundation network-publication descriptor mismatch" ) compose = payload_dir / "docker-compose.device-plane.yml" if ( compose.is_symlink() or not compose.is_file() or sha256_file(compose) != DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_COMPOSE_SHA256 ): die("Device Plane network-publication Compose mismatch") return descriptor def expected_device_plane_b2_discovery_ingress_descriptor(): return { "schemaVersion": "nodedc.device-plane.b2-discovery-ingress.v1", "mode": "verified-b2-loopback-discovery-only", "predecessorPatchId": ( DEVICE_PLANE_B2_DISCOVERY_INGRESS_PREDECESSOR_PATCH_ID ), "predecessorArtifactSha256": ( DEVICE_PLANE_B2_DISCOVERY_INGRESS_PREDECESSOR_ARTIFACT_SHA256 ), "sourceAction": "publish-verified-b2-loopback-discovery-source", "runtimeAction": "build-and-recreate-stateless-services", "selectedServices": [ "device-control-core", "device-gateway", ], "preservedServices": ["device-postgres"], "privateNetwork": DEVICE_PLANE_PRIVATE_NETWORK, "controlNetwork": DEVICE_PLANE_CONTROL_NETWORK, "publishedPorts": [ "127.0.0.1:18120:18120", "127.0.0.1:18121:18121", "127.0.0.1:9921:9921/tcp", ], "protocolProfile": "arusnavi.b2.internal.v1", "framingSpecification": ( "arusnavi.internal.protocol-sheet.gid-12.v1" ), "identityTrust": "claimed-not-ownership-proof", "discoveryLifecycle": "quarantine", "commandTransport": "disabled", "gelios": "untouched", "databaseVolume": DEVICE_PLANE_POSTGRES_VOLUME, "rollback": "restore-source-and-predecessor-stateless-runtime", } def expected_device_plane_b2_discovery_rollback_recovery_descriptor(): return { "schemaVersion": ( "nodedc.device-plane.b2-discovery-loopback-recovery.v1" ), "mode": "failed-b2-loopback-build-reconciliation", "failedPatchId": DEVICE_PLANE_B2_DISCOVERY_FAILED_PATCH_ID, "failedArtifactSha256": ( DEVICE_PLANE_B2_DISCOVERY_FAILED_ARTIFACT_SHA256 ), "failedBackupId": ( DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_BACKUP_ID ), "sourceAction": "publish-reconciliation-marker-only", "runtimeAction": "read-only-acceptance", "preservedServices": [ "device-control-core", "device-gateway", "device-postgres", ], "expectedLoopbackPorts": [ "127.0.0.1:18120:18120", "127.0.0.1:18121:18121", ], "closedPort": "127.0.0.1:9921/tcp", "databaseVolume": DEVICE_PLANE_POSTGRES_VOLUME, "commandTransport": "disabled", "gelios": "untouched", "rollback": "marker-only-runtime-unchanged", } def validate_device_plane_b2_discovery_rollback_recovery_payload( payload_dir, ): descriptor = read_strict_json( payload_dir / DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_REL, "Device Plane B2 discovery rollback recovery descriptor", max_bytes=16 * 1024, ) expected = ( expected_device_plane_b2_discovery_rollback_recovery_descriptor() ) if descriptor != expected: die( "Device Plane B2 discovery rollback recovery descriptor " "mismatch" ) return descriptor def validate_device_plane_b2_discovery_ingress_payload(payload_dir): descriptor = read_strict_json( payload_dir / DEVICE_PLANE_B2_DISCOVERY_INGRESS_REL, "Device Plane B2 discovery ingress descriptor", max_bytes=16 * 1024, ) if descriptor != expected_device_plane_b2_discovery_ingress_descriptor(): die("Device Plane B2 discovery ingress descriptor mismatch") compose = payload_dir / "docker-compose.device-plane.yml" if ( compose.is_symlink() or not compose.is_file() or sha256_file(compose) != DEVICE_PLANE_B2_DISCOVERY_INGRESS_COMPOSE_SHA256 ): die("Device Plane B2 discovery ingress Compose mismatch") return descriptor def validate_device_plane_b2_discovery_ingress_evidence(payload_dir): descriptor = validate_device_plane_b2_discovery_ingress_payload( payload_dir ) if not state_has_patch_id( DEVICE_PLANE_B2_DISCOVERY_INGRESS_PREDECESSOR_PATCH_ID ): die("Device Plane B2 discovery ingress predecessor patch missing") if not state_has_sha( DEVICE_PLANE_B2_DISCOVERY_INGRESS_PREDECESSOR_ARTIFACT_SHA256 ): die("Device Plane B2 discovery ingress predecessor artifact missing") if not state_has_patch_id( DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_PATCH_ID ): die("Device Plane B2 rollback recovery patch is not applied") if not state_has_sha( DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_ARTIFACT_SHA256 ): die("Device Plane B2 rollback recovery artifact is not applied") validate_device_plane_foundation_network_publication_installed_source() runtime = validate_device_plane_foundation_runtime( network_publication=True ) assert_loopback_tcp_port_closed(9921) target_descriptor = ( component_root("device-plane") / DEVICE_PLANE_B2_DISCOVERY_INGRESS_REL ) if target_descriptor.exists() or target_descriptor.is_symlink(): die("Device Plane B2 discovery ingress descriptor already installed") recovery_descriptor = read_strict_json( component_root("device-plane") / DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_REL, "installed Device Plane B2 rollback recovery descriptor", max_bytes=16 * 1024, ) if ( recovery_descriptor != expected_device_plane_b2_discovery_rollback_recovery_descriptor() ): die("installed Device Plane B2 rollback recovery mismatch") return { "mode": descriptor["mode"], "runtime": runtime, } def validate_device_plane_b2_discovery_rollback_recovery_evidence( payload_dir, ): descriptor = ( validate_device_plane_b2_discovery_rollback_recovery_payload( payload_dir ) ) backup_dir = ( BACKUPS_DIR / DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_BACKUP_ID ) try: backup_stat = backup_dir.lstat() except FileNotFoundError: die("Device Plane B2 failed-apply backup is missing") if stat.S_ISLNK(backup_stat.st_mode) or not stat.S_ISDIR( backup_stat.st_mode ): die("Device Plane B2 failed-apply backup is unsafe") expected_backup = ( DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_BACKUP_SHA256 ) if {child.name for child in backup_dir.iterdir()} != set( expected_backup ): die("Device Plane B2 failed-apply backup file set mismatch") for name, expected_sha256 in expected_backup.items(): path = backup_dir / name path_stat = path.lstat() if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Device Plane B2 failed-apply backup drift detected: " f"{name}" ) failed_artifact = ( FAILED_DIR / DEVICE_PLANE_B2_DISCOVERY_FAILED_ARTIFACT ) try: failed_stat = failed_artifact.lstat() except FileNotFoundError: die("Device Plane B2 failed artifact is missing") if ( stat.S_ISLNK(failed_stat.st_mode) or not stat.S_ISREG(failed_stat.st_mode) or sha256_file(failed_artifact) != DEVICE_PLANE_B2_DISCOVERY_FAILED_ARTIFACT_SHA256 ): die("Device Plane B2 failed artifact evidence mismatch") try: state_stat = FAILED_STATE_FILE.lstat() state_lines = FAILED_STATE_FILE.read_text( encoding="utf-8" ).splitlines() except (FileNotFoundError, OSError, UnicodeDecodeError): die("Device Plane B2 failed journal is unreadable") if stat.S_ISLNK(state_stat.st_mode) or not stat.S_ISREG( state_stat.st_mode ): die("Device Plane B2 failed journal is unsafe") records = [] for line in state_lines: try: value = json.loads(line) except json.JSONDecodeError: die("Device Plane B2 failed journal contains invalid JSON") if ( isinstance(value, dict) and value.get("id") == DEVICE_PLANE_B2_DISCOVERY_FAILED_PATCH_ID ): records.append(value) if len(records) != 1: die("Device Plane B2 failed journal evidence count mismatch") record = records[0] if ( record.get("artifact") != DEVICE_PLANE_B2_DISCOVERY_FAILED_ARTIFACT or record.get("backup_id") != DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_BACKUP_ID or record.get("component") != "device-plane" or record.get("sha256") != DEVICE_PLANE_B2_DISCOVERY_FAILED_ARTIFACT_SHA256 or record.get("started_apply") is not True or record.get("rollback_status") != "failed:CalledProcessError" or record.get("status") != "failed" or record.get("message") != ( "Command '['/usr/local/bin/docker', 'build', '--no-cache', " "'--network=host', '-f', " "'services/device-control-core/Dockerfile', '-t', " "'nodedc/device-control-core:local', '.']' returned non-zero " "exit status 1." ) ): die("Device Plane B2 failed journal evidence mismatch") with tempfile.TemporaryDirectory( prefix="device-plane-failed-b2-loopback-", dir=TMP_DIR, ) as directory: failed_manifest, failed_entries, _failed_payload = load_artifact( failed_artifact, Path(directory), ) if ( failed_manifest.get("id") != DEVICE_PLANE_B2_DISCOVERY_FAILED_PATCH_ID or failed_manifest.get("component") != "device-plane" or failed_manifest.get("type") != "app-overlay" or tuple(failed_entries) != DEVICE_PLANE_B2_DISCOVERY_FAILED_ENTRIES ): die("Device Plane B2 failed artifact contract mismatch") validate_device_plane_foundation_network_publication_installed_source() runtime = validate_device_plane_foundation_runtime( network_publication=True ) assert_loopback_tcp_port_closed(9921) root = component_root("device-plane") failed_descriptor = root / DEVICE_PLANE_B2_DISCOVERY_INGRESS_REL if failed_descriptor.exists() or failed_descriptor.is_symlink(): die("Device Plane B2 failed descriptor remains installed") target_descriptor = ( root / DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_REL ) if target_descriptor.exists() or target_descriptor.is_symlink(): die("Device Plane B2 rollback recovery already installed") return { "mode": descriptor["mode"], "backup": backup_dir, "failedArtifact": failed_artifact, "runtime": runtime, } def validate_device_plane_manager_reconciliation_backup(): backup_dir = ( BACKUPS_DIR / DEVICE_PLANE_MANAGER_RECONCILIATION_BACKUP_ID ) try: backup_stat = backup_dir.lstat() except FileNotFoundError: die("Device Manager failed-apply backup is missing") if stat.S_ISLNK(backup_stat.st_mode) or not stat.S_ISDIR( backup_stat.st_mode ): die("Device Manager failed-apply backup is unsafe") expected = DEVICE_PLANE_MANAGER_RECONCILIATION_BACKUP_SHA256 if {child.name for child in backup_dir.iterdir()} != set(expected): die("Device Manager failed-apply backup file set mismatch") for name, expected_sha256 in expected.items(): path = backup_dir / name path_stat = path.lstat() if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Device Manager failed-apply backup drift detected: " f"{name}" ) existing = tuple(read_backup_path_list( backup_dir / "existing-files.txt" )) missing = tuple(read_backup_path_list( backup_dir / "missing-files.txt" )) validate_backup_partition( DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_ENTRIES, existing, missing, "Device Manager reconciliation", ) if ( existing != DEVICE_PLANE_MANAGER_RECONCILIATION_EXISTING or missing != DEVICE_PLANE_MANAGER_RECONCILIATION_MISSING ): die("Device Manager failed-apply backup partition mismatch") return backup_dir def validate_device_plane_manager_reconciled_baseline( backup_dir, *, marker_installed, ): root = component_root("device-plane") with tempfile.TemporaryDirectory( prefix="device-manager-reconciled-baseline-", dir=TMP_DIR, ) as directory: backup_root = Path(directory) materialize_backup_tree( backup_dir / "source-before.tgz", backup_root, set(DEVICE_PLANE_MANAGER_RECONCILIATION_EXISTING), ) backup_source = collect_exact_files( backup_root, DEVICE_PLANE_MANAGER_RECONCILIATION_EXISTING, "Device Manager pre-apply source", ) live_source = collect_exact_files( root, DEVICE_PLANE_MANAGER_RECONCILIATION_EXISTING, "Device Manager reconciled live source", ) if live_source != backup_source: die("Device Manager rollback source does not match backup") for rel in ( *DEVICE_PLANE_MANAGER_RECONCILIATION_MISSING, DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL, ): path = root / rel if path.exists() or path.is_symlink(): die( "Device Manager candidate-only source remains installed: " f"{rel}" ) marker = root / DEVICE_PLANE_MANAGER_RECONCILIATION_REL if marker_installed: descriptor = read_strict_json( marker, "installed Device Manager reconciliation descriptor", max_bytes=16 * 1024, ) if descriptor != expected_device_plane_manager_reconciliation_descriptor(): die("installed Device Manager reconciliation descriptor mismatch") elif marker.exists() or marker.is_symlink(): die("Device Manager reconciliation descriptor already installed") if device_plane_service_container_ids("device-manager"): die("Device Manager candidate container remains installed") runtime_before = read_strict_json( backup_dir / "runtime-before.json", "Device Manager pre-apply runtime inventory", max_bytes=64 * 1024, ) runtime = validate_device_plane_b2_discovery_ingress_runtime( runtime_before, preserved_stateless_services=("device-gateway",), ) core_ids = device_plane_service_container_ids("device-control-core") if len(core_ids) != 1: die("Device Manager reconciled Core service count mismatch") core = inspect_device_plane_container(core_ids[0]) core_environment = container_environment( core, "Device Manager reconciled Control Core", ) if ( "DEVICE_MANAGEMENT_API_ENABLED" in core_environment or "DEVICE_MANAGEMENT_CORE_TOKEN_FILE" in core_environment or any( mount.get("Destination") == "/run/nodedc-secrets/management-core-token" for mount in core.get("Mounts") or [] ) ): die("Device Manager management boundary remains active after rollback") return runtime def validate_device_plane_manager_reconciliation_evidence(payload_dir): descriptor = validate_device_plane_manager_reconciliation_payload( payload_dir ) backup_dir = validate_device_plane_manager_reconciliation_backup() failed_artifact = FAILED_DIR / DEVICE_PLANE_MANAGER_FAILED_ARTIFACT try: failed_stat = failed_artifact.lstat() except FileNotFoundError: die("Device Manager failed artifact is missing") if ( stat.S_ISLNK(failed_stat.st_mode) or not stat.S_ISREG(failed_stat.st_mode) or sha256_file(failed_artifact) != DEVICE_PLANE_MANAGER_FAILED_ARTIFACT_SHA256 ): die("Device Manager failed artifact evidence mismatch") records = [ value for value in load_state(FAILED_STATE_FILE) if value.get("id") == DEVICE_PLANE_MANAGER_FAILED_PATCH_ID ] if len(records) != 1: die("Device Manager failed journal evidence count mismatch") record = records[0] if ( record.get("artifact") != DEVICE_PLANE_MANAGER_FAILED_ARTIFACT or record.get("backup_id") != DEVICE_PLANE_MANAGER_RECONCILIATION_BACKUP_ID or record.get("component") != "device-plane" or record.get("sha256") != DEVICE_PLANE_MANAGER_FAILED_ARTIFACT_SHA256 or record.get("started_apply") is not True or record.get("rollback_status") != "failed:DeployError" or record.get("status") != "failed" or record.get("message") != ( "container healthcheck failed for " "500bc061b97d9007a33dd51d35ca1168eae7f6b019165b143d3bd908220656c0: " "unhealthy" ) ): die("Device Manager failed journal evidence mismatch") with tempfile.TemporaryDirectory( prefix="device-manager-failed-artifact-", dir=TMP_DIR, ) as directory: failed_manifest, failed_entries, _failed_payload = load_artifact( failed_artifact, Path(directory), ) if ( failed_manifest.get("id") != DEVICE_PLANE_MANAGER_FAILED_PATCH_ID or failed_manifest.get("component") != "device-plane" or failed_manifest.get("type") != "app-overlay" or tuple(failed_entries) != DEVICE_PLANE_MANAGER_FAILED_CONTROL_PLANE_ENTRIES ): die("Device Manager failed artifact contract mismatch") runtime = validate_device_plane_manager_reconciled_baseline( backup_dir, marker_installed=False, ) return { "mode": descriptor["mode"], "backup": backup_dir, "failedArtifact": failed_artifact, "runtime": runtime, } def validate_device_plane_manager_v3_active_baseline(descriptor): if descriptor.get("schemaVersion") not in ( "nodedc.device-plane.device-manager-release.v3", "nodedc.device-plane.device-manager-release.v4", "nodedc.device-plane.device-manager-release.v5", "nodedc.device-plane.device-manager-release.v6", "nodedc.device-plane.device-manager-release.v7", "nodedc.device-plane.device-manager-release.v8", "nodedc.device-plane.device-manager-release.v9", "nodedc.device-plane.device-manager-release.v10", ): return None def successful_applied_artifact(reference, label): patch_id = reference["patchId"] artifact_sha256 = reference["artifactSha256"] artifact_name = f"nodedc-device-plane-{patch_id}.tgz" artifact = APPLIED_DIR / artifact_name if ( not artifact.is_file() or artifact.is_symlink() or sha256_file(artifact) != artifact_sha256 ): die(f"Device Manager v3 {label} applied artifact mismatch") records = [ row for row in load_state(STATE_FILE) if row.get("id") == patch_id and row.get("sha256") == artifact_sha256 ] if ( len(records) != 1 or records[0].get("status") != "ok" or records[0].get("component") != "device-plane" or records[0].get("artifact") != artifact_name ): die(f"Device Manager v3 {label} applied journal mismatch") backup_id = records[0].get("backup_id") backup = BACKUPS_DIR / backup_id if isinstance(backup_id, str) else None if ( backup is None or safe_name(backup_id) != backup_id or not backup.is_dir() or backup.is_symlink() ): die(f"Device Manager v3 {label} backup is unsafe") return artifact core_reference = descriptor["controlCorePredecessor"] edge_reference = descriptor["edgeChannelPredecessor"] core_artifact = successful_applied_artifact( core_reference, "Device Control Core predecessor", ) edge_artifact = successful_applied_artifact( edge_reference, "Device Edge channel predecessor", ) with tempfile.TemporaryDirectory( prefix="device-manager-v3-core-baseline-", dir=TMP_DIR, ) as directory: core_manifest, core_entries, core_payload = load_artifact( core_artifact, Path(directory), ) core_entry_set = tuple(core_entries) if ( core_manifest.get("id") != core_reference["patchId"] or core_manifest.get("component") != "device-plane" or core_manifest.get("type") != "app-overlay" or core_entry_set not in ( DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES, DEVICE_PLANE_CONTROL_CORE_RELEASE_V3_ENTRIES, DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_ENTRIES, ) ): die("Device Manager v3 Device Control Core predecessor type mismatch") expected_core_descriptor = ( validate_device_plane_control_core_release_payload( core_payload, expected_release_id=core_reference["patchId"], ) ) installed_core_descriptor_rel = { DEVICE_PLANE_CONTROL_CORE_RELEASE_V3_ENTRIES: DEVICE_PLANE_CONTROL_CORE_RELEASE_V3_REL, DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_ENTRIES: DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_REL, }.get(core_entry_set, DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_REL) installed_core_descriptor = read_strict_json( DEVICE_PLANE_ROOT / installed_core_descriptor_rel, "installed Device Manager v3 Device Control Core predecessor", max_bytes=16 * 1024, ) if installed_core_descriptor != expected_core_descriptor: die("Device Manager v3 Device Control Core predecessor is not current") expected_core_source = collect_exact_files( core_payload, core_entry_set, "Device Manager v3 Device Control Core predecessor source", ) actual_core_source = collect_exact_files( DEVICE_PLANE_ROOT, core_entry_set, "installed Device Manager v3 Device Control Core predecessor source", ) if actual_core_source != expected_core_source: die("installed Device Manager v3 Device Control Core source drift detected") with tempfile.TemporaryDirectory( prefix="device-manager-v3-edge-baseline-", dir=TMP_DIR, ) as directory: edge_manifest, edge_entries, edge_payload = load_artifact( edge_artifact, Path(directory), ) if ( edge_manifest.get("id") != edge_reference["patchId"] or edge_manifest.get("component") != "device-plane" or edge_manifest.get("type") != "app-overlay" or tuple(edge_entries) != DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_ENTRIES ): die("Device Manager v3 Device Edge channel predecessor type mismatch") expected_edge_descriptor = ( validate_device_plane_edge_core_channel_upgrade_v4_payload( edge_payload, expected_transition_id=edge_reference["patchId"], ) ) installed_edge_descriptor = read_strict_json( DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_REL, "installed Device Manager v3 Device Edge channel predecessor", max_bytes=16 * 1024, ) if installed_edge_descriptor != expected_edge_descriptor: die("Device Manager v3 Device Edge channel predecessor is not current") expected_base_compose_sha256 = sha256_file( edge_payload / "docker-compose.device-plane.yml" ) installed_base_compose = DEVICE_PLANE_ROOT / "docker-compose.device-plane.yml" if ( installed_base_compose.is_symlink() or not installed_base_compose.is_file() or sha256_file(installed_base_compose) != expected_base_compose_sha256 ): die("installed Device Manager v3 Device Edge topology drift detected") identity_state = inspect_device_edge_channel_core_identity_state() if identity_state != "valid-reuse-at-apply": die("Device Manager v3 requires the valid active Edge identity") validate_device_manager_control_plane_runtime( require_edge_channel=True, core_network_mode="private-egress", ) return { "controlCoreArtifact": core_artifact, "edgeChannelArtifact": edge_artifact, "identityState": identity_state, } def device_plane_manager_preserved_runtime_health_services(descriptor): if descriptor.get("schemaVersion") in ( "nodedc.device-plane.device-manager-release.v3", "nodedc.device-plane.device-manager-release.v4", "nodedc.device-plane.device-manager-release.v5", "nodedc.device-plane.device-manager-release.v6", "nodedc.device-plane.device-manager-release.v7", "nodedc.device-plane.device-manager-release.v8", "nodedc.device-plane.device-manager-release.v9", "nodedc.device-plane.device-manager-release.v10", ): return ( "device-control-core", "device-postgres", ) return ("device-postgres",) def validate_device_plane_manager_preserved_runtime_health(descriptor): services = device_plane_manager_preserved_runtime_health_services( descriptor ) for service in services: healthcheck_compose_service("device-plane", service) return services def validate_device_plane_manager_activation_predecessor( payload_dir, *, preflight_phase, ): if preflight_phase not in ("plan", "apply"): die("Device Manager predecessor preflight phase is invalid") descriptor = validate_device_plane_manager_release_payload( payload_dir ) predecessor = descriptor["predecessor"] patch_id = predecessor["patchId"] sha256 = predecessor["artifactSha256"] artifact_name = f"nodedc-device-plane-{patch_id}.tgz" artifact = APPLIED_DIR / artifact_name try: artifact_stat = artifact.lstat() except FileNotFoundError: die("Device Manager predecessor applied artifact is missing") if ( stat.S_ISLNK(artifact_stat.st_mode) or not stat.S_ISREG(artifact_stat.st_mode) or sha256_file(artifact) != sha256 ): die("Device Manager predecessor applied artifact mismatch") id_records = [ value for value in load_state(STATE_FILE) if value.get("id") == patch_id ] sha_records = [ value for value in load_state(STATE_FILE) if value.get("sha256") == sha256 ] if ( len(id_records) != 1 or len(sha_records) != 1 or id_records[0] != sha_records[0] ): die("Device Manager predecessor applied journal identity mismatch") record = id_records[0] backup_id = record.get("backup_id") if ( record.get("artifact") != artifact_name or record.get("component") != "device-plane" or record.get("id") != patch_id or record.get("sha256") != sha256 or record.get("status") != "ok" or not isinstance(backup_id, str) or not backup_id or safe_name(backup_id) != backup_id ): die("Device Manager predecessor applied journal mismatch") backup_dir = BACKUPS_DIR / backup_id try: backup_stat = backup_dir.lstat() except FileNotFoundError: die("Device Manager predecessor backup is missing") if stat.S_ISLNK(backup_stat.st_mode) or not stat.S_ISDIR( backup_stat.st_mode ): die("Device Manager predecessor backup is unsafe") with tempfile.TemporaryDirectory( prefix="device-manager-applied-predecessor-", dir=TMP_DIR, ) as directory: predecessor_manifest, predecessor_entries, predecessor_payload = ( load_artifact(artifact, Path(directory)) ) if ( predecessor_manifest.get("id") != patch_id or predecessor_manifest.get("component") != "device-plane" or predecessor_manifest.get("type") != "app-overlay" ): die("Device Manager predecessor artifact manifest mismatch") root = component_root("device-plane") if predecessor["kind"] == "reconciliation": if ( descriptor["action"] != "activate" or tuple(predecessor_entries) != DEVICE_PLANE_MANAGER_V2_RECONCILIATION_ENTRIES ): die("Device Manager reconciliation predecessor type mismatch") predecessor_descriptor = ( validate_device_plane_manager_v2_reconciliation_payload( predecessor_payload ) ) installed_descriptor = read_strict_json( root / DEVICE_PLANE_MANAGER_V2_RECONCILIATION_REL, "installed Device Manager reconciliation predecessor", max_bytes=16 * 1024, ) if installed_descriptor != predecessor_descriptor: die("Device Manager reconciliation predecessor is not current") baseline_backup = ( validate_device_plane_manager_v2_reconciliation_backup() ) runtime = validate_device_plane_manager_v2_reconciled_baseline( baseline_backup, marker_installed=True, ) mode = "reconciled-manager-forward-activation" else: if ( descriptor["action"] != "upgrade" or tuple(predecessor_entries) not in ( DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V1_SUCCESSOR_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V4_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V5_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V6_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V7_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V8_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V9_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V10_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V11_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V12_ENTRIES, DEVICE_PLANE_MANAGER_RELEASE_V13_ENTRIES, ) ): die("Device Manager release predecessor type mismatch") predecessor_descriptor = ( validate_device_plane_manager_release_payload( predecessor_payload, expected_release_id=patch_id, ) ) predecessor_descriptor_rel = { DEVICE_PLANE_MANAGER_RELEASE_V2_ENTRIES: DEVICE_PLANE_MANAGER_RELEASE_V2_REL, DEVICE_PLANE_MANAGER_RELEASE_V3_ENTRIES: DEVICE_PLANE_MANAGER_RELEASE_V3_REL, DEVICE_PLANE_MANAGER_RELEASE_V4_ENTRIES: DEVICE_PLANE_MANAGER_RELEASE_V4_REL, DEVICE_PLANE_MANAGER_RELEASE_V5_ENTRIES: DEVICE_PLANE_MANAGER_RELEASE_V5_REL, DEVICE_PLANE_MANAGER_RELEASE_V6_ENTRIES: DEVICE_PLANE_MANAGER_RELEASE_V6_REL, DEVICE_PLANE_MANAGER_RELEASE_V7_ENTRIES: DEVICE_PLANE_MANAGER_RELEASE_V7_REL, DEVICE_PLANE_MANAGER_RELEASE_V8_ENTRIES: DEVICE_PLANE_MANAGER_RELEASE_V8_REL, DEVICE_PLANE_MANAGER_RELEASE_V9_ENTRIES: DEVICE_PLANE_MANAGER_RELEASE_V9_REL, DEVICE_PLANE_MANAGER_RELEASE_V10_ENTRIES: DEVICE_PLANE_MANAGER_RELEASE_V10_REL, DEVICE_PLANE_MANAGER_RELEASE_V11_ENTRIES: DEVICE_PLANE_MANAGER_RELEASE_V11_REL, DEVICE_PLANE_MANAGER_RELEASE_V12_ENTRIES: DEVICE_PLANE_MANAGER_RELEASE_V12_REL, DEVICE_PLANE_MANAGER_RELEASE_V13_ENTRIES: DEVICE_PLANE_MANAGER_RELEASE_V13_REL, }.get( tuple(predecessor_entries), DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL, ) installed_descriptor = read_strict_json( root / predecessor_descriptor_rel, "installed Device Manager release predecessor", max_bytes=16 * 1024, ) if installed_descriptor != predecessor_descriptor: die("Device Manager release predecessor is not current") runtime = { "accepted": True, "preservedRuntimeHealth": "deferred-to-apply", } if preflight_phase == "apply": runtime["preservedRuntimeHealth"] = ( validate_device_plane_manager_preserved_runtime_health( descriptor ) ) v3_baseline = validate_device_plane_manager_v3_active_baseline( descriptor ) if v3_baseline is not None: runtime["v3Baseline"] = v3_baseline mode = "active-manager-forward-upgrade" return { "mode": mode, "descriptor": descriptor, "predecessorArtifact": artifact, "predecessorRecord": record, "predecessorBackup": backup_dir, "runtime": runtime, } def inspect_device_edge_channel_core_identity_state(): private_key = DEVICE_PLANE_EDGE_CHANNEL_CORE_PRIVATE_KEY_FILE certificate = DEVICE_PLANE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE private_exists = private_key.exists() or private_key.is_symlink() certificate_exists = certificate.exists() or certificate.is_symlink() if private_exists != certificate_exists: die("Device Edge channel Core identity is incomplete") if not private_exists: return "absent-create-at-apply" if device_edge_channel_invalid_identity_is_exact_recoverable(): return "failed-016-invalid-unexported-recover-at-apply" validate_device_edge_channel_certificate_extensions(certificate) run_openssl([ "verify", "-purpose", "sslclient", "-CAfile", str(certificate), str(certificate), ], "Device Edge channel Core existing certificate purpose validation") certificate_public = capture_openssl( ["x509", "-in", str(certificate), "-pubkey", "-noout"], "Device Edge channel existing certificate public key", ) private_public = capture_openssl( ["pkey", "-in", str(private_key), "-pubout"], "Device Edge channel existing private key public derivation", ) if certificate_public != private_public: die("Device Edge channel Core certificate/private key mismatch") return "valid-reuse-at-apply" def validate_device_plane_edge_core_channel_bootstrap_predecessor( payload_dir, ): if ( payload_dir / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_REL ).exists(): return validate_device_plane_edge_core_channel_upgrade_v4_predecessor( payload_dir ) if ( payload_dir / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL ).exists(): return validate_device_plane_edge_core_channel_upgrade_v2_predecessor( payload_dir ) if ( payload_dir / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL ).exists(): return validate_device_plane_edge_core_channel_upgrade_predecessor( payload_dir ) descriptor = validate_device_plane_edge_core_channel_bootstrap_payload( payload_dir ) manager = descriptor["managerPredecessor"] manager_artifact_name = ( f"nodedc-device-plane-{manager['patchId']}.tgz" ) manager_artifact = APPLIED_DIR / manager_artifact_name if ( not manager_artifact.is_file() or manager_artifact.is_symlink() or sha256_file(manager_artifact) != manager["artifactSha256"] ): die("Device Edge Core channel manager predecessor mismatch") manager_records = [ row for row in load_state(STATE_FILE) if row.get("id") == manager["patchId"] and row.get("sha256") == manager["artifactSha256"] ] if ( len(manager_records) != 1 or manager_records[0].get("status") != "ok" or manager_records[0].get("component") != "device-plane" or manager_records[0].get("artifact") != manager_artifact_name ): die("Device Edge Core channel manager journal mismatch") installed_manager = read_strict_json( DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL, "installed Device Manager predecessor", max_bytes=16 * 1024, ) with tempfile.TemporaryDirectory( prefix="device-edge-manager-predecessor-", dir=TMP_DIR, ) as directory: predecessor_manifest, predecessor_entries, predecessor_payload = ( load_artifact(manager_artifact, Path(directory)) ) if ( predecessor_manifest.get("id") != manager["patchId"] or tuple(predecessor_entries) != DEVICE_PLANE_MANAGER_CONTROL_PLANE_ENTRIES ): die("Device Edge Core channel manager artifact type mismatch") expected_manager = validate_device_plane_manager_control_plane_payload( predecessor_payload, expected_release_id=manager["patchId"], ) if installed_manager != expected_manager: die("Device Edge Core channel manager predecessor is not current") if ( sha256_file(DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_COMPOSE_REL) != DEVICE_PLANE_MANAGER_RELEASE_V1_COMPOSE_SHA256 ): die("Device Edge Core channel stable Manager Compose drift detected") failed = descriptor["failedPredecessor"] failed_artifact = FAILED_DIR / DEVICE_PLANE_EDGE_CORE_CHANNEL_FAILED_ARTIFACT failed_backup = BACKUPS_DIR / failed["backupId"] if ( not failed_artifact.is_file() or failed_artifact.is_symlink() or sha256_file(failed_artifact) != failed["artifactSha256"] or not failed_backup.is_dir() or failed_backup.is_symlink() ): die("Device Edge Core channel failed predecessor evidence mismatch") failed_records = [ row for row in load_state(FAILED_STATE_FILE) if row.get("id") == failed["patchId"] and row.get("sha256") == failed["artifactSha256"] ] if ( len(failed_records) != 1 or failed_records[0].get("status") != "failed" or failed_records[0].get("component") != "device-plane" or failed_records[0].get("backup_id") != failed["backupId"] or failed_records[0].get("started_apply") is not True ): die("Device Edge Core channel failed journal mismatch") installed_bootstrap = ( DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL ) installed_override = ( DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL ) if ( installed_bootstrap.exists() or installed_bootstrap.is_symlink() or installed_override.exists() or installed_override.is_symlink() ): die("Device Edge Core channel bootstrap is already installed") identity_state = inspect_device_edge_channel_core_identity_state() for service in ( "device-control-core", "device-manager", "device-gateway", "device-postgres", ): healthcheck_compose_service("device-plane", service) return { "mode": "separate-edge-core-channel-bootstrap", "descriptor": descriptor, "identityState": identity_state, "managerArtifact": manager_artifact, "failedArtifact": failed_artifact, "failedBackup": failed_backup, } def validate_device_plane_edge_core_channel_upgrade_predecessor( payload_dir, ): descriptor = validate_device_plane_edge_core_channel_upgrade_payload( payload_dir ) installed_upgrade = ( DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL ) if installed_upgrade.exists() or installed_upgrade.is_symlink(): die("Device Edge Core channel upgrade is already installed") predecessor = descriptor["bootstrapPredecessor"] artifact_name = f"nodedc-device-plane-{predecessor['patchId']}.tgz" artifact = APPLIED_DIR / artifact_name if ( not artifact.is_file() or artifact.is_symlink() or sha256_file(artifact) != predecessor["artifactSha256"] ): die("Device Edge Core channel bootstrap predecessor mismatch") records = [ row for row in load_state(STATE_FILE) if row.get("id") == predecessor["patchId"] and row.get("sha256") == predecessor["artifactSha256"] ] if ( len(records) != 1 or records[0].get("status") != "ok" or records[0].get("component") != "device-plane" or records[0].get("artifact") != artifact_name ): die("Device Edge Core channel bootstrap journal mismatch") with tempfile.TemporaryDirectory( prefix="device-edge-core-channel-upgrade-predecessor-", dir=TMP_DIR, ) as directory: manifest, entries, predecessor_payload = load_artifact( artifact, Path(directory), ) if ( manifest.get("id") != predecessor["patchId"] or manifest.get("component") != "device-plane" or manifest.get("type") != "app-overlay" or tuple(entries) != DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_ENTRIES ): die("Device Edge Core channel bootstrap artifact type mismatch") validate_device_plane_edge_core_channel_bootstrap_payload( predecessor_payload, expected_transition_id=predecessor["patchId"], ) expected_source = collect_exact_files( predecessor_payload, DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_ENTRIES, "Device Edge Core channel bootstrap predecessor source", ) actual_source = collect_exact_files( DEVICE_PLANE_ROOT, DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_ENTRIES, "installed Device Edge Core channel bootstrap source", ) if actual_source != expected_source: die("installed Device Edge Core channel bootstrap source drift detected") identity_state = inspect_device_edge_channel_core_identity_state() if identity_state != "valid-reuse-at-apply": die("Device Edge Core channel upgrade requires the valid active identity") for service in ( "device-control-core", "device-manager", "device-gateway", "device-postgres", ): healthcheck_compose_service("device-plane", service) return { "mode": "edge-core-channel-standard-https-443-upgrade", "descriptor": descriptor, "identityState": identity_state, "bootstrapArtifact": artifact, } def validate_device_plane_edge_core_channel_upgrade_v2_predecessor( payload_dir, ): descriptor = validate_device_plane_edge_core_channel_upgrade_v2_payload( payload_dir ) installed_upgrade = ( DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL ) if installed_upgrade.exists() or installed_upgrade.is_symlink(): die("Device Edge Core channel upgrade v2 is already installed") predecessor = descriptor["upgradePredecessor"] artifact_name = f"nodedc-device-plane-{predecessor['patchId']}.tgz" artifact = APPLIED_DIR / artifact_name if ( not artifact.is_file() or artifact.is_symlink() or sha256_file(artifact) != predecessor["artifactSha256"] ): die("Device Edge Core channel upgrade v2 predecessor mismatch") records = [ row for row in load_state(STATE_FILE) if row.get("id") == predecessor["patchId"] and row.get("sha256") == predecessor["artifactSha256"] ] if ( len(records) != 1 or records[0].get("status") != "ok" or records[0].get("component") != "device-plane" or records[0].get("artifact") != artifact_name ): die("Device Edge Core channel upgrade v2 predecessor journal mismatch") with tempfile.TemporaryDirectory( prefix="device-edge-core-channel-upgrade-v2-predecessor-", dir=TMP_DIR, ) as directory: manifest, entries, predecessor_payload = load_artifact( artifact, Path(directory), ) if ( manifest.get("id") != predecessor["patchId"] or manifest.get("component") != "device-plane" or manifest.get("type") != "app-overlay" or tuple(entries) != DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_ENTRIES ): die("Device Edge Core channel upgrade v2 predecessor type mismatch") validate_device_plane_edge_core_channel_upgrade_payload( predecessor_payload, expected_transition_id=predecessor["patchId"], ) expected_source = collect_exact_files( predecessor_payload, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_ENTRIES, "Device Edge Core channel upgrade v2 predecessor source", ) actual_source = collect_exact_files( DEVICE_PLANE_ROOT, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_ENTRIES, "installed Device Edge Core channel upgrade v1 source", ) if actual_source != expected_source: die("installed Device Edge Core channel upgrade v1 source drift detected") installed_bootstrap = read_strict_json( DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL, "installed Device Edge Core channel bootstrap predecessor", max_bytes=16 * 1024, ) if installed_bootstrap != ( expected_device_plane_edge_core_channel_bootstrap_descriptor( DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_PATCH_ID ) ): die("installed Device Edge Core channel bootstrap predecessor drift detected") identity_state = inspect_device_edge_channel_core_identity_state() if identity_state != "valid-reuse-at-apply": die("Device Edge Core channel upgrade v2 requires the valid active identity") for service in ( "device-control-core", "device-manager", "device-gateway", "device-postgres", ): healthcheck_compose_service("device-plane", service) return { "mode": "edge-core-channel-forward-upgrade-v2", "descriptor": descriptor, "identityState": identity_state, "upgradeArtifact": artifact, } def validate_device_plane_edge_core_channel_upgrade_v4_predecessor( payload_dir, ): descriptor = validate_device_plane_edge_core_channel_upgrade_v4_payload( payload_dir ) installed_upgrade = ( DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_REL ) if installed_upgrade.exists() or installed_upgrade.is_symlink(): die("Device Edge Core channel upgrade v4 is already installed") predecessor = descriptor["upgradePredecessor"] artifact_name = f"nodedc-device-plane-{predecessor['patchId']}.tgz" artifact = APPLIED_DIR / artifact_name if ( not artifact.is_file() or artifact.is_symlink() or sha256_file(artifact) != predecessor["artifactSha256"] ): die("Device Edge Core channel upgrade v4 predecessor mismatch") records = [ row for row in load_state(STATE_FILE) if row.get("id") == predecessor["patchId"] and row.get("sha256") == predecessor["artifactSha256"] ] if ( len(records) != 1 or records[0].get("status") != "ok" or records[0].get("component") != "device-plane" or records[0].get("artifact") != artifact_name ): die("Device Edge Core channel upgrade v4 predecessor journal mismatch") failed = descriptor["failedAttempt"] failed_artifact = ( FAILED_DIR / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_FAILED_ARTIFACT ) failed_backup = BACKUPS_DIR / failed["backupId"] if ( not failed_artifact.is_file() or failed_artifact.is_symlink() or sha256_file(failed_artifact) != failed["artifactSha256"] or not failed_backup.is_dir() or failed_backup.is_symlink() ): die("Device Edge Core channel upgrade v4 failed-attempt evidence mismatch") failed_records = [ row for row in load_state(FAILED_STATE_FILE) if row.get("id") == failed["patchId"] and row.get("sha256") == failed["artifactSha256"] ] if ( len(failed_records) != 1 or failed_records[0].get("status") != "failed" or failed_records[0].get("component") != "device-plane" or failed_records[0].get("backup_id") != failed["backupId"] or failed_records[0].get("started_apply") is not True or failed_records[0].get("rollback_status") != "ok:device-plane-overlay:source+runtime-restored:8" or failed_records[0].get("message") != "Device Control Core Edge channel network boundary mismatch" ): die("Device Edge Core channel upgrade v4 failed-attempt journal mismatch") with tempfile.TemporaryDirectory( prefix="device-edge-core-channel-upgrade-v4-predecessor-", dir=TMP_DIR, ) as directory: manifest, entries, predecessor_payload = load_artifact( artifact, Path(directory), ) if ( manifest.get("id") != predecessor["patchId"] or manifest.get("component") != "device-plane" or manifest.get("type") != "app-overlay" or tuple(entries) != DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_ENTRIES ): die("Device Edge Core channel upgrade v4 predecessor type mismatch") validate_device_plane_edge_core_channel_upgrade_v2_payload( predecessor_payload, expected_transition_id=predecessor["patchId"], ) expected_source = collect_exact_files( predecessor_payload, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_ENTRIES, "Device Edge Core channel upgrade v4 predecessor source", ) actual_source = collect_exact_files( DEVICE_PLANE_ROOT, DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_ENTRIES, "installed Device Edge Core channel upgrade v2 source", ) if actual_source != expected_source: die("installed Device Edge Core channel upgrade v2 source drift detected") installed_base_compose = DEVICE_PLANE_ROOT / "docker-compose.device-plane.yml" if ( installed_base_compose.is_symlink() or not installed_base_compose.is_file() or sha256_file(installed_base_compose) != DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_PREDECESSOR_BASE_COMPOSE_SHA256 ): die("installed Device Edge Core channel v4 base predecessor drift detected") identity_state = inspect_device_edge_channel_core_identity_state() if identity_state != "valid-reuse-at-apply": die("Device Edge Core channel upgrade v4 requires the valid active identity") for service in ( "device-control-core", "device-manager", "device-gateway", "device-postgres", ): healthcheck_compose_service("device-plane", service) return { "mode": "edge-core-channel-private-plus-egress-upgrade-v4", "descriptor": descriptor, "identityState": identity_state, "upgradeArtifact": artifact, "failedArtifact": failed_artifact, "failedBackup": failed_backup, } def device_plane_control_core_preserved_runtime_health_services(descriptor): services = tuple(descriptor.get("preservedServices") or ()) expected = ( "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", ) if services != expected: die("Device Control Core preserved service set mismatch") return services def validate_device_plane_control_core_preserved_runtime_health(descriptor): services = device_plane_control_core_preserved_runtime_health_services( descriptor ) for service in services: healthcheck_compose_service("device-plane", service) return services def validate_device_plane_control_core_release_v4_recovery_backup(): backup_dir = ( BACKUPS_DIR / DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_BACKUP_ID ) try: backup_stat = backup_dir.lstat() except FileNotFoundError: die("Device Control Core recovery 046 backup is missing") if stat.S_ISLNK(backup_stat.st_mode) or not stat.S_ISDIR( backup_stat.st_mode ): die("Device Control Core recovery 046 backup is unsafe") expected = ( DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_BACKUP_SHA256 ) if {child.name for child in backup_dir.iterdir()} != set(expected): die("Device Control Core recovery 046 backup file set mismatch") for name, expected_sha256 in expected.items(): path = backup_dir / name path_stat = path.lstat() if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Device Control Core recovery 046 backup drift detected: " f"{name}" ) existing = tuple( read_backup_path_list(backup_dir / "existing-files.txt") ) missing = tuple( read_backup_path_list(backup_dir / "missing-files.txt") ) validate_backup_partition( DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_ENTRIES, existing, missing, "Device Control Core recovery 046", ) if ( existing != (DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_REL,) or missing != ( DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_REL, ) ): die("Device Control Core recovery 046 backup partition mismatch") return backup_dir def validate_device_plane_control_core_release_v4_recovered_source( recovery_payload, ): base_artifact_name = ( "nodedc-device-plane-" f"{DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_BASE_PATCH_ID}.tgz" ) base_artifact = APPLIED_DIR / base_artifact_name if ( not base_artifact.is_file() or base_artifact.is_symlink() or sha256_file(base_artifact) != DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_BASE_ARTIFACT_SHA256 ): die("Device Control Core release v4 recovered base artifact mismatch") base_records = [ row for row in load_state(STATE_FILE) if row.get("id") == DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_BASE_PATCH_ID and row.get("sha256") == DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_BASE_ARTIFACT_SHA256 ] if ( len(base_records) != 1 or base_records[0].get("status") != "ok" or base_records[0].get("component") != "device-plane" or base_records[0].get("artifact") != base_artifact_name ): die("Device Control Core release v4 recovered base journal mismatch") with tempfile.TemporaryDirectory( prefix="device-control-core-release-v4-base-", dir=TMP_DIR, ) as directory: base_manifest, base_entries, base_payload = load_artifact( base_artifact, Path(directory), ) if ( base_manifest.get("id") != DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_BASE_PATCH_ID or base_manifest.get("component") != "device-plane" or base_manifest.get("type") != "app-overlay" or tuple(base_entries) != DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES ): die("Device Control Core release v4 recovered base type mismatch") validate_device_plane_control_core_release_payload( base_payload, expected_release_id=( DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_BASE_PATCH_ID ), ) expected_source = collect_exact_files( base_payload, DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES, "Device Control Core release v4 recovered base source", ) recovery_source = collect_exact_files( recovery_payload, DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_ENTRIES, "Device Control Core release v4 recovery overlay", ) expected_source.update(recovery_source) live_entries = ( *DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES, DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_REL, ) actual_source = collect_exact_files( DEVICE_PLANE_ROOT, live_entries, "installed Device Control Core release v4 recovered source", ) if actual_source != expected_source: die("installed Device Control Core recovery 046 source drift detected") for rel in ( "packages/infrastructure-telemetry-contract", DEVICE_PLANE_CONTROL_CORE_RELEASE_V3_REL, DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_REL, DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_REL, DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_REL, ): path = DEVICE_PLANE_ROOT / rel if path.exists() or path.is_symlink(): die( "Device Control Core recovery 046 candidate-only source " f"appeared: {rel}" ) installed_recovery = read_strict_json( ( DEVICE_PLANE_ROOT / DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_REL ), "installed Device Control Core recovery 046 descriptor", max_bytes=16 * 1024, ) if ( installed_recovery != expected_device_plane_control_core_migration_replay_checkpoint_recovery_descriptor() ): die("installed Device Control Core recovery 046 descriptor mismatch") return base_artifact def validate_device_plane_control_core_release_v4_recovered_runtime( backup_dir, ): before = read_strict_json( backup_dir / "runtime-before.json", "Device Control Core recovery 046 pre-apply runtime inventory", max_bytes=64 * 1024, ) expected_services = { "device-control-core", "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", } if set(device_plane_inventory_service_names(before)) != expected_services: die("Device Control Core recovery 046 runtime evidence mismatch") current = device_plane_runtime_inventory(tuple(sorted(expected_services))) if set(device_plane_inventory_service_names(current)) != expected_services: die("Device Control Core recovery 046 live runtime is incomplete") expected = {item["service"]: item for item in before["services"]} observed = {item["service"]: item for item in current["services"]} for service in expected_services - {"device-control-core"}: item = observed[service] if ( item["containerId"] != expected[service]["containerId"] or item["imageId"] != expected[service]["imageId"] or item["status"] != "running" or item["running"] is not True or item["health"] != "healthy" or item["restartCount"] != expected[service]["restartCount"] ): die( "Device Control Core recovery 046 changed preserved service: " f"{service}" ) core = observed["device-control-core"] local_image_id = inspect_optional_local_image( DEVICE_PLANE_CONTROL_CORE_IMAGE, "Device Control Core recovery 046 active local image", ) if ( core["containerId"] == expected["device-control-core"]["containerId"] or core["imageId"] == DEVICE_PLANE_CONTROL_CORE_V3_PREAPPLY_IMAGE_ID or core["imageId"] != local_image_id or core["status"] != "running" or core["running"] is not True or core["health"] != "healthy" or core["restartCount"] != 0 ): die("Device Control Core recovery 046 runtime mismatch") validate_device_manager_control_plane_runtime( require_edge_channel=True, core_network_mode="private-egress", ) return {"before": before, "current": current, "core": core} def validate_device_plane_control_core_release_predecessor( payload_dir, *, preflight_phase, ): if preflight_phase not in ("plan", "apply"): die("Device Control Core predecessor preflight phase is invalid") descriptor = validate_device_plane_control_core_release_payload( payload_dir ) predecessor = descriptor["predecessor"] artifact_name = f"nodedc-device-plane-{predecessor['patchId']}.tgz" artifact = APPLIED_DIR / artifact_name if ( not artifact.is_file() or artifact.is_symlink() or sha256_file(artifact) != predecessor["artifactSha256"] ): die("Device Control Core release predecessor mismatch") records = [ row for row in load_state(STATE_FILE) if row.get("id") == predecessor["patchId"] and row.get("sha256") == predecessor["artifactSha256"] ] if ( len(records) != 1 or records[0].get("status") != "ok" or records[0].get("component") != "device-plane" or records[0].get("artifact") != artifact_name ): die("Device Control Core release predecessor journal mismatch") installed_v1_release = ( DEVICE_PLANE_ROOT / DEVICE_PLANE_CONTROL_CORE_RELEASE_REL ) installed_v2_release = ( DEVICE_PLANE_ROOT / DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_REL ) installed_v3_release = ( DEVICE_PLANE_ROOT / DEVICE_PLANE_CONTROL_CORE_RELEASE_V3_REL ) installed_v4_release = ( DEVICE_PLANE_ROOT / DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_REL ) source_entries = None expected_source = None recovery_backup = None recovery_runtime = None recovery_database = None with tempfile.TemporaryDirectory( prefix="device-control-core-release-predecessor-", dir=TMP_DIR, ) as directory: manifest, entries, predecessor_payload = load_artifact( artifact, Path(directory), ) if ( manifest.get("id") != predecessor["patchId"] or manifest.get("component") != "device-plane" or manifest.get("type") != "app-overlay" ): die("Device Control Core release predecessor type mismatch") if predecessor["kind"] == "edge-core-channel-upgrade-v4": if tuple(entries) != DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_ENTRIES: die("Device Control Core release v4 predecessor type mismatch") validate_device_plane_edge_core_channel_upgrade_v4_payload( predecessor_payload, expected_transition_id=predecessor["patchId"], ) source_entries = tuple( rel for rel in DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_ENTRIES if rel not in ( "docker-compose.device-plane.yml", DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_REL, ) ) if ( installed_v1_release.exists() or installed_v1_release.is_symlink() or installed_v2_release.exists() or installed_v2_release.is_symlink() or installed_v3_release.exists() or installed_v3_release.is_symlink() or installed_v4_release.exists() or installed_v4_release.is_symlink() ): die("Device Control Core first release is already installed") elif predecessor["kind"] == "migration-replay-checkpoint-recovery": if ( tuple(entries) != DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_ENTRIES ): die( "Device Control Core release v4 recovery predecessor " "type mismatch" ) validate_device_plane_control_core_migration_replay_checkpoint_recovery_payload( predecessor_payload ) validate_device_plane_control_core_release_v4_recovered_source( predecessor_payload ) else: if tuple(entries) == DEVICE_PLANE_CONTROL_CORE_RELEASE_ENTRIES: installed_release = installed_v1_release source_entries = DEVICE_PLANE_CONTROL_CORE_RELEASE_ENTRIES elif tuple(entries) == DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES: installed_release = installed_v2_release source_entries = DEVICE_PLANE_CONTROL_CORE_RELEASE_V2_ENTRIES elif tuple(entries) == DEVICE_PLANE_CONTROL_CORE_RELEASE_V3_ENTRIES: installed_release = installed_v3_release source_entries = DEVICE_PLANE_CONTROL_CORE_RELEASE_V3_ENTRIES elif tuple(entries) == DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_ENTRIES: installed_release = installed_v4_release source_entries = DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_ENTRIES else: die("Device Control Core release predecessor type mismatch") expected_installed = validate_device_plane_control_core_release_payload( predecessor_payload, expected_release_id=predecessor["patchId"], ) installed_descriptor = read_strict_json( installed_release, "installed Device Control Core release predecessor", max_bytes=16 * 1024, ) if installed_descriptor != expected_installed: die("Device Control Core release predecessor is not current") if source_entries is not None: expected_source = collect_exact_files( predecessor_payload, source_entries, "Device Control Core release predecessor source", ) if source_entries is not None: actual_source = collect_exact_files( DEVICE_PLANE_ROOT, source_entries, "installed Device Control Core release predecessor source", ) if actual_source != expected_source: die("installed Device Control Core release source drift detected") component_compose_files("device-plane") identity_state = inspect_device_edge_channel_core_identity_state() if identity_state != "valid-reuse-at-apply": die("Device Control Core release requires the valid active identity") if predecessor["kind"] == "migration-replay-checkpoint-recovery": recovery_backup = ( validate_device_plane_control_core_release_v4_recovery_backup() ) recovery_runtime = ( validate_device_plane_control_core_release_v4_recovered_runtime( recovery_backup ) ) recovery_database = ( collect_device_plane_control_core_migration_replay_database_evidence( expected_state="final", ) ) selected_runtime = recovery_runtime["core"] else: selected_runtime = ( validate_device_plane_control_core_selected_predecessor_runtime() ) preserved_runtime_health = "deferred-to-apply" if preflight_phase == "apply": preserved_runtime_health = ( validate_device_plane_control_core_preserved_runtime_health( descriptor ) ) validate_device_manager_control_plane_runtime( require_edge_channel=True, core_network_mode="private-egress", ) return { "mode": ( "recovery-046-host-telemetry-forward-release" if predecessor["kind"] == "migration-replay-checkpoint-recovery" else "active-device-control-core-forward-release" ), "descriptor": descriptor, "identityState": identity_state, "predecessorArtifact": artifact, "selectedRuntime": selected_runtime, "preservedRuntimeHealth": preserved_runtime_health, "recoveryBackup": recovery_backup, "recoveryRuntime": recovery_runtime, "recoveryDatabase": recovery_database, } def validate_device_plane_manager_v2_reconciliation_backup(): backup_dir = ( BACKUPS_DIR / DEVICE_PLANE_MANAGER_V2_RECONCILIATION_BACKUP_ID ) try: backup_stat = backup_dir.lstat() except FileNotFoundError: die("Device Manager v2 failed-apply backup is missing") if stat.S_ISLNK(backup_stat.st_mode) or not stat.S_ISDIR( backup_stat.st_mode ): die("Device Manager v2 failed-apply backup is unsafe") expected = DEVICE_PLANE_MANAGER_V2_RECONCILIATION_BACKUP_SHA256 if {child.name for child in backup_dir.iterdir()} != set(expected): die("Device Manager v2 failed-apply backup file set mismatch") for name, expected_sha256 in expected.items(): path = backup_dir / name path_stat = path.lstat() if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Device Manager v2 failed-apply backup drift detected: " f"{name}" ) existing = tuple(read_backup_path_list( backup_dir / "existing-files.txt" )) missing = tuple(read_backup_path_list( backup_dir / "missing-files.txt" )) validate_backup_partition( DEVICE_PLANE_MANAGER_V2_CONTROL_PLANE_ENTRIES, existing, missing, "Device Manager v2 reconciliation", ) if ( existing != DEVICE_PLANE_MANAGER_V2_RECONCILIATION_EXISTING or missing != DEVICE_PLANE_MANAGER_V2_RECONCILIATION_MISSING ): die("Device Manager v2 failed-apply backup partition mismatch") return backup_dir def validate_device_plane_manager_v2_reconciled_baseline( backup_dir, *, marker_installed, ): prior_backup = validate_device_plane_manager_reconciliation_backup() runtime = validate_device_plane_manager_reconciled_baseline( prior_backup, marker_installed=True, ) root = component_root("device-plane") with tempfile.TemporaryDirectory( prefix="device-manager-v2-reconciled-baseline-", dir=TMP_DIR, ) as directory: backup_root = Path(directory) materialize_backup_tree( backup_dir / "source-before.tgz", backup_root, set(DEVICE_PLANE_MANAGER_V2_RECONCILIATION_EXISTING), ) backup_source = collect_exact_files( backup_root, DEVICE_PLANE_MANAGER_V2_RECONCILIATION_EXISTING, "Device Manager v2 pre-apply source", ) live_source = collect_exact_files( root, DEVICE_PLANE_MANAGER_V2_RECONCILIATION_EXISTING, "Device Manager v2 reconciled live source", ) if live_source != backup_source: die("Device Manager v2 rollback source does not match backup") for rel in ( *DEVICE_PLANE_MANAGER_V2_RECONCILIATION_MISSING, DEVICE_PLANE_MANAGER_CONTROL_PLANE_REL, ): path = root / rel if path.exists() or path.is_symlink(): die( "Device Manager v2 candidate-only source remains installed: " f"{rel}" ) marker = root / DEVICE_PLANE_MANAGER_V2_RECONCILIATION_REL if marker_installed: descriptor = read_strict_json( marker, "installed Device Manager v2 reconciliation descriptor", max_bytes=16 * 1024, ) if ( descriptor != expected_device_plane_manager_v2_reconciliation_descriptor() ): die("installed Device Manager v2 reconciliation descriptor mismatch") elif marker.exists() or marker.is_symlink(): die("Device Manager v2 reconciliation descriptor already installed") return runtime def validate_device_plane_manager_v2_reconciliation_evidence(payload_dir): descriptor = validate_device_plane_manager_v2_reconciliation_payload( payload_dir ) backup_dir = validate_device_plane_manager_v2_reconciliation_backup() failed_artifact = FAILED_DIR / DEVICE_PLANE_MANAGER_V2_FAILED_ARTIFACT try: failed_stat = failed_artifact.lstat() except FileNotFoundError: die("Device Manager v2 failed artifact is missing") if ( stat.S_ISLNK(failed_stat.st_mode) or not stat.S_ISREG(failed_stat.st_mode) or sha256_file(failed_artifact) != DEVICE_PLANE_MANAGER_V2_FAILED_ARTIFACT_SHA256 ): die("Device Manager v2 failed artifact evidence mismatch") records = [ value for value in load_state(FAILED_STATE_FILE) if value.get("id") == DEVICE_PLANE_MANAGER_V2_FAILED_PATCH_ID ] if len(records) != 1: die("Device Manager v2 failed journal evidence count mismatch") record = records[0] if ( record.get("artifact") != DEVICE_PLANE_MANAGER_V2_FAILED_ARTIFACT or record.get("backup_id") != DEVICE_PLANE_MANAGER_V2_RECONCILIATION_BACKUP_ID or record.get("component") != "device-plane" or record.get("sha256") != DEVICE_PLANE_MANAGER_V2_FAILED_ARTIFACT_SHA256 or record.get("started_apply") is not True or record.get("rollback_status") != "failed:DeployError" or record.get("status") != "failed" or record.get("message") != ( "container healthcheck grace exhausted for " "f8593303db6b71468716ed2428e54aab18d7303b34adbe1a93cd6df86effa1cf: " "unhealthy" ) ): die("Device Manager v2 failed journal evidence mismatch") with tempfile.TemporaryDirectory( prefix="device-manager-v2-failed-artifact-", dir=TMP_DIR, ) as directory: failed_manifest, failed_entries, _failed_payload = load_artifact( failed_artifact, Path(directory), ) if ( failed_manifest.get("id") != DEVICE_PLANE_MANAGER_V2_FAILED_PATCH_ID or failed_manifest.get("component") != "device-plane" or failed_manifest.get("type") != "app-overlay" or tuple(failed_entries) != DEVICE_PLANE_MANAGER_V2_CONTROL_PLANE_ENTRIES ): die("Device Manager v2 failed artifact contract mismatch") runtime = validate_device_plane_manager_v2_reconciled_baseline( backup_dir, marker_installed=False, ) return { "mode": descriptor["mode"], "backup": backup_dir, "failedArtifact": failed_artifact, "runtime": runtime, } def validate_device_plane_control_core_v3_reconciliation_backup(): backup_dir = ( BACKUPS_DIR / DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_BACKUP_ID ) try: backup_stat = backup_dir.lstat() except FileNotFoundError: die("Device Control Core v3 failed-apply backup is missing") if stat.S_ISLNK(backup_stat.st_mode) or not stat.S_ISDIR( backup_stat.st_mode ): die("Device Control Core v3 failed-apply backup is unsafe") expected = DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_BACKUP_SHA256 if {child.name for child in backup_dir.iterdir()} != set(expected): die("Device Control Core v3 failed-apply backup file set mismatch") for name, expected_sha256 in expected.items(): path = backup_dir / name path_stat = path.lstat() if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Device Control Core v3 failed-apply backup drift detected: " f"{name}" ) existing = tuple(read_backup_path_list( backup_dir / "existing-files.txt" )) missing = tuple(read_backup_path_list( backup_dir / "missing-files.txt" )) validate_backup_partition( DEVICE_PLANE_CONTROL_CORE_RELEASE_V3_ENTRIES, existing, missing, "Device Control Core v3 reconciliation", ) if ( existing != DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_EXISTING or missing != DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_MISSING ): die("Device Control Core v3 failed-apply backup partition mismatch") return backup_dir def validate_device_plane_control_core_v3_restored_source( backup_dir, *, marker_installed, ): root = component_root("device-plane") with tempfile.TemporaryDirectory( prefix="device-control-core-v3-restored-source-", dir=TMP_DIR, ) as directory: backup_root = Path(directory) materialize_backup_tree( backup_dir / "source-before.tgz", backup_root, set(DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_EXISTING), ) backup_source = collect_exact_files( backup_root, DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_EXISTING, "Device Control Core v3 pre-apply source", ) live_source = collect_exact_files( root, DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_EXISTING, "Device Control Core v3 restored live source", ) if live_source != backup_source: die("Device Control Core v3 rollback source does not match backup") for rel in DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_MISSING: path = root / rel if path.exists() or path.is_symlink(): die( "Device Control Core v3 candidate-only source remains installed: " f"{rel}" ) marker = root / DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_REL if marker_installed: descriptor = read_strict_json( marker, "installed Device Control Core v3 reconciliation descriptor", max_bytes=16 * 1024, ) if ( descriptor != expected_device_plane_control_core_v3_reconciliation_descriptor() ): die("installed Device Control Core v3 reconciliation mismatch") elif marker.exists() or marker.is_symlink(): die("Device Control Core v3 reconciliation already installed") def validate_device_plane_control_core_v3_reconciliation_runtime( backup_dir, *, require_recovered, ): runtime_before = read_strict_json( backup_dir / "runtime-before.json", "Device Control Core v3 pre-apply runtime inventory", max_bytes=64 * 1024, ) expected_names = { "device-control-core", "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", } if set(device_plane_inventory_service_names(runtime_before)) != expected_names: die("Device Control Core v3 pre-apply runtime inventory mismatch") before = {item["service"]: item for item in runtime_before["services"]} if ( before["device-control-core"]["imageId"] != DEVICE_PLANE_CONTROL_CORE_V3_PREAPPLY_IMAGE_ID or before["device-control-core"]["health"] != "healthy" or before["device-control-core"]["running"] is not True ): die("Device Control Core v3 pre-apply image evidence mismatch") current = device_plane_runtime_inventory(tuple(sorted(expected_names))) if set(device_plane_inventory_service_names(current)) != expected_names: die("Device Control Core v3 current runtime inventory mismatch") observed = {item["service"]: item for item in current["services"]} for service in expected_names - {"device-control-core"}: expected_item = before[service] actual_item = observed[service] if ( actual_item["containerId"] != expected_item["containerId"] or actual_item["imageId"] != expected_item["imageId"] or actual_item["running"] is not True or actual_item["health"] != "healthy" ): die( "Device Control Core v3 reconciliation changed preserved service: " f"{service}" ) image_id = inspect_optional_local_image( DEVICE_PLANE_CONTROL_CORE_V3_PREAPPLY_IMAGE_ID, "Device Control Core v3 exact pre-apply image", ) if image_id != DEVICE_PLANE_CONTROL_CORE_V3_PREAPPLY_IMAGE_ID: die("Device Control Core v3 exact pre-apply image is unavailable") core = observed["device-control-core"] if require_recovered: if ( core["imageId"] != DEVICE_PLANE_CONTROL_CORE_V3_PREAPPLY_IMAGE_ID or core["running"] is not True or core["health"] != "healthy" ): die("Device Control Core v3 exact-image recovery did not converge") validate_device_manager_control_plane_runtime( require_edge_channel=True, core_network_mode="private-egress", ) return { "before": runtime_before, "current": current, "core": core, } def validate_device_plane_control_core_v3_reconciliation_evidence(payload_dir): descriptor = validate_device_plane_control_core_v3_reconciliation_payload( payload_dir ) backup_dir = validate_device_plane_control_core_v3_reconciliation_backup() failed_artifact = FAILED_DIR / DEVICE_PLANE_CONTROL_CORE_V3_FAILED_ARTIFACT try: failed_stat = failed_artifact.lstat() except FileNotFoundError: die("Device Control Core v3 failed artifact is missing") if ( stat.S_ISLNK(failed_stat.st_mode) or not stat.S_ISREG(failed_stat.st_mode) or sha256_file(failed_artifact) != DEVICE_PLANE_CONTROL_CORE_V3_FAILED_ARTIFACT_SHA256 ): die("Device Control Core v3 failed artifact evidence mismatch") records = [ value for value in load_state(FAILED_STATE_FILE) if value.get("id") == DEVICE_PLANE_CONTROL_CORE_V3_FAILED_PATCH_ID ] if len(records) != 1: die("Device Control Core v3 failed journal evidence count mismatch") record = records[0] if ( record.get("artifact") != DEVICE_PLANE_CONTROL_CORE_V3_FAILED_ARTIFACT or record.get("backup_id") != DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_BACKUP_ID or record.get("component") != "device-plane" or record.get("sha256") != DEVICE_PLANE_CONTROL_CORE_V3_FAILED_ARTIFACT_SHA256 or record.get("started_apply") is not True or record.get("rollback_status") != "failed:DeployError" or record.get("status") != "failed" or record.get("message") != ( "container healthcheck grace exhausted for " "97513c9a1027ae84f5dad6d0bc4792a67a4b0a1f32665d6d498093f43ee7987b: " "unhealthy" ) ): die("Device Control Core v3 failed journal evidence mismatch") with tempfile.TemporaryDirectory( prefix="device-control-core-v3-failed-artifact-", dir=TMP_DIR, ) as directory: failed_manifest, failed_entries, _failed_payload = load_artifact( failed_artifact, Path(directory), ) if ( failed_manifest.get("id") != DEVICE_PLANE_CONTROL_CORE_V3_FAILED_PATCH_ID or failed_manifest.get("component") != "device-plane" or failed_manifest.get("type") != "app-overlay" or tuple(failed_entries) != DEVICE_PLANE_CONTROL_CORE_RELEASE_V3_ENTRIES ): die("Device Control Core v3 failed artifact contract mismatch") validate_device_plane_control_core_v3_restored_source( backup_dir, marker_installed=False, ) runtime = validate_device_plane_control_core_v3_reconciliation_runtime( backup_dir, require_recovered=False, ) return { "mode": descriptor["mode"], "backup": backup_dir, "failedArtifact": failed_artifact, "runtime": runtime, } def validate_device_plane_control_core_incident_audit_evidence(payload_dir): descriptor = validate_device_plane_control_core_incident_audit_payload( payload_dir ) evidence = validate_device_plane_control_core_double_failure_evidence() evidence["mode"] = descriptor["mode"] return evidence def validate_device_plane_control_core_double_failure_evidence(): first_backup = validate_device_plane_control_core_v3_reconciliation_backup() first_failed_artifact = ( FAILED_DIR / DEVICE_PLANE_CONTROL_CORE_V3_FAILED_ARTIFACT ) try: first_failed_stat = first_failed_artifact.lstat() except FileNotFoundError: die("Device Control Core v3 failed artifact is missing") if ( stat.S_ISLNK(first_failed_stat.st_mode) or not stat.S_ISREG(first_failed_stat.st_mode) or sha256_file(first_failed_artifact) != DEVICE_PLANE_CONTROL_CORE_V3_FAILED_ARTIFACT_SHA256 ): die("Device Control Core v3 failed artifact evidence mismatch") first_records = [ value for value in load_state(FAILED_STATE_FILE) if value.get("id") == DEVICE_PLANE_CONTROL_CORE_V3_FAILED_PATCH_ID ] if len(first_records) != 1: die("Device Control Core v3 failed journal evidence count mismatch") first_record = first_records[0] if ( first_record.get("artifact") != DEVICE_PLANE_CONTROL_CORE_V3_FAILED_ARTIFACT or first_record.get("backup_id") != DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_BACKUP_ID or first_record.get("component") != "device-plane" or first_record.get("sha256") != DEVICE_PLANE_CONTROL_CORE_V3_FAILED_ARTIFACT_SHA256 or first_record.get("started_apply") is not True or first_record.get("rollback_status") != "failed:DeployError" or first_record.get("status") != "failed" ): die("Device Control Core v3 failed journal evidence mismatch") validate_device_plane_control_core_v3_restored_source( first_backup, marker_installed=False, ) validate_device_plane_control_core_v3_reconciliation_runtime( first_backup, require_recovered=False, ) backup_dir = ( BACKUPS_DIR / DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_FAILED_BACKUP_ID ) try: backup_stat = backup_dir.lstat() except FileNotFoundError: die("Device Control Core reconciliation failed-apply backup is missing") if stat.S_ISLNK(backup_stat.st_mode) or not stat.S_ISDIR( backup_stat.st_mode ): die("Device Control Core reconciliation failed-apply backup is unsafe") expected_backup = ( DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_FAILED_BACKUP_SHA256 ) if {child.name for child in backup_dir.iterdir()} != set(expected_backup): die("Device Control Core reconciliation backup file set mismatch") for name, expected_sha256 in expected_backup.items(): path = backup_dir / name path_stat = path.lstat() if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Device Control Core reconciliation backup drift detected: " f"{name}" ) existing = read_backup_path_list(backup_dir / "existing-files.txt") missing = read_backup_path_list(backup_dir / "missing-files.txt") validate_backup_partition( DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_ENTRIES, existing, missing, "Device Control Core incident audit", ) if existing or tuple(missing) != ( DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_REL, ): die("Device Control Core reconciliation backup partition mismatch") failed_artifact = ( FAILED_DIR / DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_FAILED_ARTIFACT ) try: failed_stat = failed_artifact.lstat() except FileNotFoundError: die("Device Control Core reconciliation failed artifact is missing") if ( stat.S_ISLNK(failed_stat.st_mode) or not stat.S_ISREG(failed_stat.st_mode) or sha256_file(failed_artifact) != DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_FAILED_ARTIFACT_SHA256 ): die("Device Control Core reconciliation failed artifact mismatch") records = [ value for value in load_state(FAILED_STATE_FILE) if value.get("id") == DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_PATCH_ID ] if len(records) != 1: die("Device Control Core reconciliation failed journal count mismatch") record = records[0] if ( record.get("artifact") != DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_FAILED_ARTIFACT or record.get("backup_id") != DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_FAILED_BACKUP_ID or record.get("component") != "device-plane" or record.get("sha256") != DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_FAILED_ARTIFACT_SHA256 or record.get("started_apply") is not True or record.get("rollback_status") != "failed:DeployError" or record.get("status") != "failed" or record.get("message") != ( "container healthcheck grace exhausted for " "8b4b6c6fa1073808f0fafc24450735fe7769a2f92c36ae58dec97d3fa8a30dc0: " "starting" ) ): die("Device Control Core reconciliation failed journal mismatch") with tempfile.TemporaryDirectory( prefix="device-control-core-incident-audit-artifact-", dir=TMP_DIR, ) as directory: failed_manifest, failed_entries, _failed_payload = load_artifact( failed_artifact, Path(directory), ) if ( failed_manifest.get("id") != DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_PATCH_ID or failed_manifest.get("component") != "device-plane" or failed_manifest.get("type") != "app-overlay" or tuple(failed_entries) != DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_ENTRIES ): die("Device Control Core reconciliation failed artifact contract mismatch") current = device_plane_runtime_inventory(( "device-control-core", "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", )) return { "mode": "double-rollback-failed-evidence", "firstBackup": first_backup, "secondBackup": backup_dir, "runtime": current, } def validate_device_plane_control_core_migration_replay_recovery_failure(): failed_artifact = ( FAILED_DIR / DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_FAILED_ARTIFACT ) try: failed_stat = failed_artifact.lstat() except FileNotFoundError: die("Device Control Core migration recovery 044 failed artifact is missing") if ( stat.S_ISLNK(failed_stat.st_mode) or not stat.S_ISREG(failed_stat.st_mode) or sha256_file(failed_artifact) != DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_ARTIFACT_SHA256 ): die("Device Control Core migration recovery 044 failed artifact mismatch") if any( value.get("id") == DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_PATCH_ID for value in load_state(STATE_FILE) ): die("Device Control Core migration recovery 044 applied journal conflict") records = [ value for value in load_state(FAILED_STATE_FILE) if value.get("id") == DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_PATCH_ID ] if len(records) != 1: die("Device Control Core migration recovery 044 failed journal count mismatch") record = records[0] if ( record.get("artifact") != DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_FAILED_ARTIFACT or record.get("backup_id") is not None or record.get("component") != "device-plane" or record.get("failed_at") != DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_FAILED_AT or record.get("sha256") != DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_ARTIFACT_SHA256 or record.get("started_apply") is not False or record.get("rollback_status") != "not-required" or record.get("status") != "failed" or record.get("message") != "Device Control Core migration recovery database invariant mismatch" ): die("Device Control Core migration recovery 044 failed journal mismatch") with tempfile.TemporaryDirectory( prefix="device-control-core-migration-recovery-044-failed-", dir=TMP_DIR, ) as directory: failed_manifest, failed_entries, failed_payload = load_artifact( failed_artifact, Path(directory), ) if ( failed_manifest.get("id") != DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_PATCH_ID or failed_manifest.get("component") != "device-plane" or failed_manifest.get("type") != "app-overlay" or tuple(failed_entries) != DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_ENTRIES ): die( "Device Control Core migration recovery 044 failed artifact " "contract mismatch" ) validate_device_plane_control_core_migration_replay_recovery_payload( failed_payload ) return {"artifact": failed_artifact, "record": record} def device_plane_control_core_migration_replay_database_invariants_match( evidence, ): return ( evidence["invalidCommandKindCount"] == 0 and evidence["triggeringReceiptCount"] >= 1 and evidence["constraintValidated"] and evidence["constraintCoversFinalKinds"] and evidence["constraintPhase"] == "final-016" and evidence["hostTelemetryTableAbsent"] ) def device_plane_control_core_migration_replay_predecessor_invariants_match( evidence, ): return ( evidence["invalidCommandKindCount"] == 0 and evidence["triggeringReceiptCount"] >= 1 and not evidence["constraintValidated"] and not evidence["constraintCoversFinalKinds"] and evidence["constraintMatchesReplay011Kinds"] and evidence["hostTelemetryTableAbsent"] ) def device_plane_control_core_migration_replay_checkpoint_invariants_match( evidence, ): return ( evidence["invalidCommandKindCount"] == 0 and evidence["triggeringReceiptCount"] >= 1 and evidence["constraintValidationState"] == "false" and not evidence["constraintCoversFinalKinds"] and evidence["constraintPhase"] in DEVICE_PLANE_CONTROL_CORE_REPLAY_CHECKPOINT_PHASES and evidence["hostTelemetryTableAbsent"] ) def emit_device_plane_control_core_migration_replay_database_evidence( evidence, ): def boolean(value): return "true" if value else "false" print( "device_control_core_invalid_command_kind_count=" f"{evidence['invalidCommandKindCount']}" ) print( "device_control_core_triggering_receipt_count=" f"{evidence['triggeringReceiptCount']}" ) print( "device_control_core_constraint_validated=" f"{boolean(evidence['constraintValidated'])}" ) print( "device_control_core_constraint_covers_final_kinds=" f"{boolean(evidence['constraintCoversFinalKinds'])}" ) print( "device_control_core_host_telemetry_table_absent=" f"{boolean(evidence['hostTelemetryTableAbsent'])}" ) print( "device_control_core_constraint_matches_replay_011_kinds=" f"{boolean(evidence['constraintMatchesReplay011Kinds'])}" ) print( "device_control_core_constraint_phase=" f"{evidence['constraintPhase']}" ) print( "device_control_core_constraint_matches_known_replay_checkpoint=" f"{boolean(evidence['constraintMatchesKnownReplayCheckpoint'])}" ) print( "device_control_core_constraint_matches_final_016_kinds=" f"{boolean(evidence['constraintMatchesFinalKinds'])}" ) print( "device_control_core_recovery_044_ready=" f"{boolean(evidence['recovery044Ready'])}" ) print( "device_control_core_checkpoint_recovery_ready=" f"{boolean(evidence['checkpointRecoveryReady'])}" ) print( "device_control_core_recovery_final_state_ready=" f"{boolean(evidence['finalStateReady'])}" ) def collect_device_plane_control_core_migration_replay_database_evidence( enforce_recovery_invariants=True, expected_state="final", ): if expected_state not in ( "final", "replay-011-predecessor", "replay-checkpoint-predecessor", ): die("Device Control Core migration recovery database state is invalid") postgres_ids = device_plane_service_container_ids("device-postgres") if len(postgres_ids) != 1: die("Device Control Core migration recovery PostgreSQL is missing") allowed = ",".join( "'" + value.replace("'", "''") + "'" for value in DEVICE_PLANE_CONTROL_CORE_FINAL_COMMAND_KINDS ) triggering = ",".join( "'" + value.replace("'", "''") + "'" for value in DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_TRIGGER_KINDS ) query = f""" with constraint_state as ( select convalidated, pg_get_constraintdef(c.oid) as definition, (select bool_and(pg_get_constraintdef(c.oid) like '%' || kind || '%') from unnest(array[{allowed}]::text[]) as kind) as covers_final from pg_constraint c where c.conrelid = 'public.device_management_command_receipts'::regclass and c.conname = 'device_management_command_receipts_command_kind_check' ), constraint_literals as ( select array_agg((matched.value)[1] order by (matched.value)[1]) as values from constraint_state cross join lateral regexp_matches( constraint_state.definition, '''([^'']+)''', 'g' ) as matched(value) ) select ( select count(*) from device_management_command_receipts where not (command_kind = any(array[{allowed}]::text[])) )::text, ( select count(*) from device_management_command_receipts where command_kind = any(array[{triggering}]::text[]) )::text, coalesce((select convalidated::text from constraint_state), 'missing'), coalesce((select covers_final::text from constraint_state), 'false'), (to_regclass('public.device_infrastructure_host_telemetry_samples') is null)::text, coalesce((select array_to_json(values)::text from constraint_literals), '[]') """.strip() result = subprocess.run( [ str(DOCKER), "exec", postgres_ids[0], "psql", "-X", "-qAt", "-v", "ON_ERROR_STOP=1", "-U", "device_plane", "-d", "device_plane", "-F", "\t", "-c", query, ], check=False, capture_output=True, text=True, timeout=30, ) values = result.stdout.strip().split("\t") if ( result.returncode != 0 or result.stderr.strip() or len(values) != 6 or not re.fullmatch(r"[0-9]+", values[0]) or not re.fullmatch(r"[0-9]+", values[1]) or values[2] not in ("true", "false", "missing") or values[3] not in ("true", "false") or values[4] not in ("true", "false") ): die("Device Control Core migration recovery database read failed") try: constraint_literal_kinds = json.loads(values[5]) except (TypeError, ValueError): die("Device Control Core migration recovery constraint read failed") if ( not isinstance(constraint_literal_kinds, list) or len(constraint_literal_kinds) > 128 or any( not isinstance(value, str) or not re.fullmatch(r"[a-z][a-z0-9._-]{1,127}", value) for value in constraint_literal_kinds ) or len(set(constraint_literal_kinds)) != len(constraint_literal_kinds) ): die("Device Control Core migration recovery constraint read failed") constraint_literal_kinds = tuple(sorted(constraint_literal_kinds)) constraint_phase_by_kinds = { tuple(sorted(kinds)): phase for phase, kinds in ( *DEVICE_PLANE_CONTROL_CORE_REPLAY_CHECKPOINTS, ("final-016", DEVICE_PLANE_CONTROL_CORE_FINAL_COMMAND_KINDS), ) } if values[2] == "missing": constraint_phase = "missing" else: constraint_phase = constraint_phase_by_kinds.get( constraint_literal_kinds, "unknown", ) evidence = { "invalidCommandKindCount": int(values[0]), "triggeringReceiptCount": int(values[1]), "constraintValidationState": values[2], "constraintValidated": values[2] == "true", "constraintCoversFinalKinds": values[3] == "true", "hostTelemetryTableAbsent": values[4] == "true", "constraintLiteralKinds": constraint_literal_kinds, "constraintPhase": constraint_phase, "constraintMatchesReplay011Kinds": ( constraint_phase == "replay-011" ), "constraintMatchesKnownReplayCheckpoint": ( constraint_phase in DEVICE_PLANE_CONTROL_CORE_REPLAY_CHECKPOINT_PHASES ), "constraintMatchesFinalKinds": constraint_phase == "final-016", "query": query, } evidence["finalStateReady"] = ( device_plane_control_core_migration_replay_database_invariants_match( evidence ) ) evidence["recovery044Ready"] = ( device_plane_control_core_migration_replay_predecessor_invariants_match( evidence ) ) evidence["checkpointRecoveryReady"] = ( device_plane_control_core_migration_replay_checkpoint_invariants_match( evidence ) ) expected_ready = { "final": evidence["finalStateReady"], "replay-011-predecessor": evidence["recovery044Ready"], "replay-checkpoint-predecessor": ( evidence["checkpointRecoveryReady"] ), }[expected_state] if enforce_recovery_invariants and not expected_ready: emit_device_plane_control_core_migration_replay_database_evidence( evidence ) die("Device Control Core migration recovery database invariant mismatch") return evidence def collect_device_plane_control_core_host_telemetry_database_evidence(): postgres_ids = device_plane_service_container_ids("device-postgres") if len(postgres_ids) != 1: die("Device Control Core host telemetry PostgreSQL is missing") query = """ select ( to_regclass( 'public.device_infrastructure_host_telemetry_samples' ) is not null )::text, ( select count(*) from information_schema.columns where table_schema = 'public' and table_name = 'device_infrastructure_host_telemetry_samples' )::text, ( select count(*) from pg_indexes where schemaname = 'public' and indexname in ( 'device_host_telemetry_project_host_time_idx', 'device_host_telemetry_retention_idx' ) )::text, coalesce(( select bool_and(convalidated)::text from pg_constraint where conrelid = 'public.device_infrastructure_host_telemetry_samples'::regclass ), 'false'), ( select count(*) from device_infrastructure_host_telemetry_samples )::text, ( select count(*) from device_infrastructure_host_telemetry_samples where observed_at < now() - interval '7 days' )::text """.strip() result = subprocess.run( [ str(DOCKER), "exec", postgres_ids[0], "psql", "-X", "-qAt", "-v", "ON_ERROR_STOP=1", "-U", "device_plane", "-d", "device_plane", "-F", "\t", "-c", query, ], check=False, capture_output=True, text=True, timeout=30, ) values = result.stdout.strip().split("\t") if ( result.returncode != 0 or result.stderr.strip() or len(values) != 6 or values[0] not in ("true", "false") or not all(re.fullmatch(r"[0-9]+", value) for value in values[1:3]) or values[3] not in ("true", "false") or not all(re.fullmatch(r"[0-9]+", value) for value in values[4:6]) ): die("Device Control Core host telemetry database read failed") evidence = { "tablePresent": values[0] == "true", "columnCount": int(values[1]), "indexCount": int(values[2]), "constraintsValidated": values[3] == "true", "sampleCount": int(values[4]), "expiredSampleCount": int(values[5]), "query": query, } if ( not evidence["tablePresent"] or evidence["columnCount"] != 19 or evidence["indexCount"] != 2 or not evidence["constraintsValidated"] or evidence["expiredSampleCount"] != 0 ): die("Device Control Core host telemetry database invariant mismatch") return evidence def accept_device_plane_control_core_release_v4( payload_dir, runtime_before, ): descriptor = validate_device_plane_control_core_release_payload( payload_dir, expected_release_id=DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_PATCH_ID, ) if ( descriptor.get("schemaVersion") != "nodedc.device-plane.device-control-core-release.v4" ): die("Device Control Core release v4 acceptance descriptor mismatch") expected_source = collect_exact_files( payload_dir, DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_ENTRIES, "Device Control Core release v4 candidate source", ) actual_source = collect_exact_files( DEVICE_PLANE_ROOT, DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_ENTRIES, "installed Device Control Core release v4 source", ) if actual_source != expected_source: die("installed Device Control Core release v4 source mismatch") installed_descriptor = read_strict_json( DEVICE_PLANE_ROOT / DEVICE_PLANE_CONTROL_CORE_RELEASE_V4_REL, "installed Device Control Core release v4 descriptor", max_bytes=16 * 1024, ) if installed_descriptor != descriptor: die("installed Device Control Core release v4 descriptor mismatch") installed_recovery = read_strict_json( ( DEVICE_PLANE_ROOT / DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_REL ), "preserved Device Control Core recovery 046 descriptor", max_bytes=16 * 1024, ) if ( installed_recovery != expected_device_plane_control_core_migration_replay_checkpoint_recovery_descriptor() ): die("Device Control Core release v4 lost recovery 046 evidence") legacy_v3 = DEVICE_PLANE_ROOT / DEVICE_PLANE_CONTROL_CORE_RELEASE_V3_REL if legacy_v3.exists() or legacy_v3.is_symlink(): die("failed Device Control Core release v3 descriptor appeared") expected_services = { "device-control-core", "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", } if set(device_plane_inventory_service_names(runtime_before)) != expected_services: die("Device Control Core release v4 predecessor inventory mismatch") current = device_plane_runtime_inventory(tuple(sorted(expected_services))) if set(device_plane_inventory_service_names(current)) != expected_services: die("Device Control Core release v4 runtime is incomplete") before = {item["service"]: item for item in runtime_before["services"]} observed = {item["service"]: item for item in current["services"]} for service in expected_services - {"device-control-core"}: item = observed[service] if ( item["containerId"] != before[service]["containerId"] or item["imageId"] != before[service]["imageId"] or item["status"] != "running" or item["running"] is not True or item["health"] != "healthy" or item["restartCount"] != before[service]["restartCount"] ): die( "Device Control Core release v4 changed preserved service: " f"{service}" ) core = observed["device-control-core"] local_image_id = inspect_local_image( DEVICE_PLANE_CONTROL_CORE_IMAGE, "Device Control Core release v4 local image", ) if ( core["containerId"] == before["device-control-core"]["containerId"] or core["imageId"] == before["device-control-core"]["imageId"] or core["imageId"] != local_image_id or core["status"] != "running" or core["running"] is not True or core["health"] != "healthy" or core["restartCount"] != 0 ): die("Device Control Core release v4 did not converge") database = ( collect_device_plane_control_core_migration_replay_database_evidence( enforce_recovery_invariants=False, ) ) if ( database["invalidCommandKindCount"] != 0 or database["triggeringReceiptCount"] < 1 or not database["constraintValidated"] or not database["constraintCoversFinalKinds"] or database["constraintPhase"] != "final-016" or database["hostTelemetryTableAbsent"] ): emit_device_plane_control_core_migration_replay_database_evidence( database ) die("Device Control Core release v4 database invariant mismatch") telemetry = ( collect_device_plane_control_core_host_telemetry_database_evidence() ) validate_device_manager_control_plane_runtime( require_edge_channel=True, core_network_mode="private-egress", ) assert_loopback_tcp_port_open(9921) return { "descriptor": descriptor, "runtime": current, "core": core, "database": database, "telemetry": telemetry, } def validate_device_plane_control_core_migration_replay_audit_evidence( payload_dir, ): descriptor = ( validate_device_plane_control_core_migration_replay_audit_payload( payload_dir ) ) failure_evidence = validate_device_plane_control_core_double_failure_evidence() recovery_artifact = ( INBOX / DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_ARTIFACT ) try: recovery_stat = recovery_artifact.lstat() except FileNotFoundError: die("Device Control Core migration recovery staged artifact is missing") if ( stat.S_ISLNK(recovery_stat.st_mode) or not stat.S_ISREG(recovery_stat.st_mode) or sha256_file(recovery_artifact) != DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_ARTIFACT_SHA256 ): die("Device Control Core migration recovery staged artifact mismatch") with tempfile.TemporaryDirectory( prefix="device-control-core-migration-replay-audit-", dir=TMP_DIR, ) as directory: recovery_manifest, recovery_entries, recovery_payload = load_artifact( recovery_artifact, Path(directory), ) if ( recovery_manifest.get("id") != DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_PATCH_ID or recovery_manifest.get("component") != "device-plane" or recovery_manifest.get("type") != "app-overlay" or tuple(recovery_entries) != DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_ENTRIES ): die("Device Control Core migration recovery artifact contract mismatch") validate_device_plane_control_core_migration_replay_recovery_payload( recovery_payload ) if any( value.get("id") == DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_PATCH_ID for value in ( *load_state(STATE_FILE), *load_state(FAILED_STATE_FILE), ) ): die("Device Control Core migration recovery has terminal journal state") root = component_root("device-plane") live_migration = root / DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_REL if ( live_migration.is_symlink() or not live_migration.is_file() or sha256_file(live_migration) != DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_PREDECESSOR_SHA256 ): die("Device Control Core migration replay audit source mismatch") installed_descriptor = ( root / DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_REL ) if installed_descriptor.exists() or installed_descriptor.is_symlink(): die("Device Control Core migration recovery is already installed") runtime = ( validate_device_plane_control_core_migration_replay_preserved_runtime( failure_evidence ) ) core = runtime["core"] if ( core["imageId"] != DEVICE_PLANE_CONTROL_CORE_V3_PREAPPLY_IMAGE_ID or core["status"] not in ("running", "restarting", "exited") or core["health"] not in ("starting", "unhealthy", None) ): die("Device Control Core migration replay audit runtime mismatch") database = ( collect_device_plane_control_core_migration_replay_database_evidence( enforce_recovery_invariants=False, ) ) return { "mode": descriptor["mode"], "descriptor": descriptor, "firstBackup": failure_evidence["firstBackup"], "secondBackup": failure_evidence["secondBackup"], "runtime": runtime["current"], "core": core, "database": database, "recoveryArtifact": recovery_artifact, } def validate_device_plane_control_core_migration_replay_preserved_runtime( evidence, ): expected_services = { "device-control-core", "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", } current = device_plane_runtime_inventory(tuple(sorted(expected_services))) if set(device_plane_inventory_service_names(current)) != expected_services: die("Device Control Core migration recovery runtime is incomplete") second_before = read_strict_json( evidence["secondBackup"] / "runtime-before.json", "Device Control Core second failed-apply runtime inventory", max_bytes=64 * 1024, ) if set(device_plane_inventory_service_names(second_before)) != expected_services: die("Device Control Core second failed-apply runtime evidence mismatch") before = {item["service"]: item for item in second_before["services"]} observed = {item["service"]: item for item in current["services"]} for service in expected_services - {"device-control-core"}: if ( observed[service]["containerId"] != before[service]["containerId"] or observed[service]["imageId"] != before[service]["imageId"] or observed[service]["status"] != "running" or observed[service]["running"] is not True or observed[service]["health"] != "healthy" ): die( "Device Control Core migration recovery changed preserved " f"service: {service}" ) return {"current": current, "core": observed["device-control-core"]} def validate_device_plane_control_core_migration_replay_recovery_evidence( payload_dir, ): descriptor = ( validate_device_plane_control_core_migration_replay_recovery_payload( payload_dir ) ) evidence = validate_device_plane_control_core_double_failure_evidence() root = component_root("device-plane") live_migration = root / DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_REL if ( live_migration.is_symlink() or not live_migration.is_file() or sha256_file(live_migration) != DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_PREDECESSOR_SHA256 ): die("Device Control Core migration 014 predecessor mismatch") installed_descriptor = ( root / DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_REL ) if installed_descriptor.exists() or installed_descriptor.is_symlink(): die("Device Control Core migration replay recovery already installed") runtime = ( validate_device_plane_control_core_migration_replay_preserved_runtime( evidence ) ) core = runtime["core"] if ( core["imageId"] != DEVICE_PLANE_CONTROL_CORE_V3_PREAPPLY_IMAGE_ID or core["status"] not in ("running", "restarting", "exited") or core["health"] not in ("starting", "unhealthy", None) ): die("Device Control Core migration recovery predecessor mismatch") image_id = inspect_optional_local_image( DEVICE_PLANE_CONTROL_CORE_V3_PREAPPLY_IMAGE_ID, "Device Control Core migration recovery exact predecessor image", ) if image_id != DEVICE_PLANE_CONTROL_CORE_V3_PREAPPLY_IMAGE_ID: die("Device Control Core migration recovery predecessor image is missing") database = ( collect_device_plane_control_core_migration_replay_database_evidence( expected_state="replay-011-predecessor", ) ) return { "mode": descriptor["mode"], "descriptor": descriptor, "firstBackup": evidence["firstBackup"], "secondBackup": evidence["secondBackup"], "runtime": runtime["current"], "core": core, "database": database, } def validate_device_plane_control_core_migration_replay_checkpoint_recovery_evidence( payload_dir, ): descriptor = ( validate_device_plane_control_core_migration_replay_checkpoint_recovery_payload( payload_dir ) ) evidence = validate_device_plane_control_core_double_failure_evidence() failed_recovery = ( validate_device_plane_control_core_migration_replay_recovery_failure() ) root = component_root("device-plane") live_migration = root / DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_REL if ( live_migration.is_symlink() or not live_migration.is_file() or sha256_file(live_migration) != DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_PREDECESSOR_SHA256 ): die("Device Control Core migration 014 predecessor mismatch") for descriptor_rel in ( DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_REL, DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_REL, ): installed_descriptor = root / descriptor_rel if installed_descriptor.exists() or installed_descriptor.is_symlink(): die( "Device Control Core migration replay checkpoint recovery " "already installed" ) runtime = ( validate_device_plane_control_core_migration_replay_preserved_runtime( evidence ) ) core = runtime["core"] if ( core["imageId"] != DEVICE_PLANE_CONTROL_CORE_V3_PREAPPLY_IMAGE_ID or core["status"] not in ("running", "restarting", "exited") or core["health"] not in ("starting", "unhealthy", None) ): die( "Device Control Core migration replay checkpoint predecessor " "mismatch" ) image_id = inspect_optional_local_image( DEVICE_PLANE_CONTROL_CORE_V3_PREAPPLY_IMAGE_ID, "Device Control Core migration replay checkpoint exact predecessor image", ) if image_id != DEVICE_PLANE_CONTROL_CORE_V3_PREAPPLY_IMAGE_ID: die( "Device Control Core migration replay checkpoint predecessor " "image is missing" ) database = ( collect_device_plane_control_core_migration_replay_database_evidence( expected_state="replay-checkpoint-predecessor", ) ) return { "mode": descriptor["mode"], "descriptor": descriptor, "firstBackup": evidence["firstBackup"], "secondBackup": evidence["secondBackup"], "failedRecovery": failed_recovery, "runtime": runtime["current"], "core": core, "database": database, } def accept_device_plane_control_core_migration_replay_recovery(): root = component_root("device-plane") migration = root / DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_REL if ( migration.is_symlink() or not migration.is_file() or sha256_file(migration) != DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_TARGET_SHA256 ): die("installed Device Control Core migration 014 repair mismatch") descriptor = read_strict_json( root / DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_REL, "installed Device Control Core migration replay recovery descriptor", max_bytes=16 * 1024, ) if ( descriptor != expected_device_plane_control_core_migration_replay_recovery_descriptor() ): die("installed Device Control Core migration recovery mismatch") evidence = { "secondBackup": ( BACKUPS_DIR / DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_FAILED_BACKUP_ID ) } runtime = ( validate_device_plane_control_core_migration_replay_preserved_runtime( evidence ) ) core = runtime["core"] if ( core["imageId"] == DEVICE_PLANE_CONTROL_CORE_V3_PREAPPLY_IMAGE_ID or core["status"] != "running" or core["running"] is not True or core["health"] != "healthy" ): die("Device Control Core migration recovery did not converge") database = ( collect_device_plane_control_core_migration_replay_database_evidence( expected_state="final", ) ) validate_device_manager_control_plane_runtime( require_edge_channel=True, core_network_mode="private-egress", ) return {"runtime": runtime["current"], "database": database} def accept_device_plane_control_core_migration_replay_checkpoint_recovery(): root = component_root("device-plane") migration = root / DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_REL if ( migration.is_symlink() or not migration.is_file() or sha256_file(migration) != DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_TARGET_SHA256 ): die("installed Device Control Core migration 014 repair mismatch") descriptor = read_strict_json( root / DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_REL, "installed Device Control Core replay checkpoint recovery descriptor", max_bytes=16 * 1024, ) if ( descriptor != expected_device_plane_control_core_migration_replay_checkpoint_recovery_descriptor() ): die( "installed Device Control Core replay checkpoint recovery mismatch" ) legacy_descriptor = ( root / DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_REL ) if legacy_descriptor.exists() or legacy_descriptor.is_symlink(): die("legacy Device Control Core migration recovery descriptor appeared") evidence = { "secondBackup": ( BACKUPS_DIR / DEVICE_PLANE_CONTROL_CORE_V3_RECONCILIATION_FAILED_BACKUP_ID ) } runtime = ( validate_device_plane_control_core_migration_replay_preserved_runtime( evidence ) ) core = runtime["core"] if ( core["imageId"] == DEVICE_PLANE_CONTROL_CORE_V3_PREAPPLY_IMAGE_ID or core["status"] != "running" or core["running"] is not True or core["health"] != "healthy" ): die( "Device Control Core migration replay checkpoint recovery did " "not converge" ) database = ( collect_device_plane_control_core_migration_replay_database_evidence( expected_state="final", ) ) validate_device_manager_control_plane_runtime( require_edge_channel=True, core_network_mode="private-egress", ) return {"runtime": runtime["current"], "database": database} def emit_bounded_device_control_core_failure_logs(): core_ids = device_plane_service_container_ids("device-control-core") if len(core_ids) != 1: return result = subprocess.run( [str(DOCKER), "logs", "--tail", "160", core_ids[0]], check=False, capture_output=True, text=True, timeout=30, ) if result.returncode != 0: return selected = [] for raw_line in f"{result.stdout}\n{result.stderr}".splitlines(): line = re.sub(r"\s+", " ", raw_line.strip()) if not line or not re.search( r"(?:error|exception|failed|constraint|migration|postgres|relation)", line, re.IGNORECASE, ): continue if re.search( r"(?:authorization|bearer|password|private[_ -]?key|token=|secret=)", line, re.IGNORECASE, ): line = "[sensitive log line redacted]" if line not in selected: selected.append(line[:480]) for index, line in enumerate(selected[-16:], start=1): print( f"device_control_core_failure_log_{index:02d}={line}", file=sys.stderr, ) def collect_device_plane_control_core_incident_audit(): core_ids = device_plane_service_container_ids("device-control-core") postgres_ids = device_plane_service_container_ids("device-postgres") if len(core_ids) != 1 or len(postgres_ids) != 1: die("Device Control Core incident audit container set mismatch") core_id = core_ids[0] logs = subprocess.run( [str(DOCKER), "logs", "--tail", "240", core_id], check=False, capture_output=True, text=True, timeout=30, ) if logs.returncode != 0: die("Device Control Core incident audit log read failed") bounded_logs = f"{logs.stdout}\n{logs.stderr}" if len(bounded_logs.encode("utf-8")) > 256 * 1024: die("Device Control Core incident audit logs exceeded bound") error_lines = [] for raw_line in bounded_logs.splitlines(): line = re.sub(r"\s+", " ", raw_line.strip()) if not line or not re.search( r"(?:error|exception|failed|timeout|econn|enoent|eacces|" r"postgres|relation|constraint|migration|lock|device_[a-z0-9_]+)", line, re.IGNORECASE, ): continue if re.search( r"(?:authorization|bearer|password|private[_ -]?key|token=|secret=)", line, re.IGNORECASE, ): line = "[sensitive log line redacted]" line = line[:480] if line not in error_lines: error_lines.append(line) error_lines = error_lines[-16:] activity_query = """ select 'schema' as kind, case when to_regclass('public.device_infrastructure_host_telemetry_samples') is null then 'host-telemetry-table-absent' else 'host-telemetry-table-present' end as detail union all select 'activity' as kind, concat_ws(',', pid::text, state, coalesce(wait_event_type, 'none'), coalesce(wait_event, 'none'), case when query ~* 'device_infrastructure_host_telemetry_samples' then 'host-telemetry-ddl' when query ~* '(create|alter|drop)[[:space:]]+(table|index|constraint)' then 'schema-ddl' when state = 'idle in transaction' then 'idle-transaction' else 'application-query' end, greatest(0, floor(extract(epoch from (clock_timestamp() - query_start))))::bigint ) as detail from pg_stat_activity where datname = 'device_plane' and pid <> pg_backend_pid() order by 1, 2 """.strip() activity = subprocess.run( [ str(DOCKER), "exec", postgres_ids[0], "psql", "-X", "-qAt", "-v", "ON_ERROR_STOP=1", "-U", "device_plane", "-d", "device_plane", "-F", "\t", "-c", activity_query, ], check=False, capture_output=True, text=True, timeout=30, ) if activity.returncode != 0 or activity.stderr.strip(): die("Device Control Core incident audit database read failed") database_evidence = [] for line in activity.stdout.splitlines(): normalized = line.strip() if not re.fullmatch(r"(?:schema|activity)\t[A-Za-z0-9_, -]{1,240}", normalized): die("Device Control Core incident audit database output invalid") database_evidence.append(normalized) if not database_evidence or not any( item.startswith("schema\t") for item in database_evidence ): die("Device Control Core incident audit database evidence missing") return { "coreContainerId": core_id, "logSha256": hashlib.sha256(bounded_logs.encode("utf-8")).hexdigest(), "logErrors": error_lines, "database": database_evidence, } def device_plane_service_container_ids(service): if service not in ( *DEVICE_PLANE_RUNTIME_SERVICES, "device-manager", DEVICE_PLANE_BACKHAUL_TARGET_SERVICE, ): die(f"Device Plane runtime service is not registered: {service}") result = subprocess.run( [ str(DOCKER), "container", "ls", "-a", "--filter", "label=com.docker.compose.project=nodedc-device-plane", "--filter", f"label=com.docker.compose.service={service}", "--format", "{{.ID}}", ], check=False, capture_output=True, text=True, ) container_ids = [ line.strip() for line in result.stdout.splitlines() if line.strip() ] if ( result.returncode != 0 or any( not re.fullmatch(r"[a-f0-9]{12,64}", container_id) for container_id in container_ids ) or len(container_ids) > 1 ): die(f"Device Plane runtime inventory failed: {service}") return tuple(container_ids) def inspect_device_plane_container(container_id): containers = docker_json( ["container", "inspect", container_id], "Device Plane container inspect", ) if ( not isinstance(containers, list) or len(containers) != 1 or not isinstance(containers[0], dict) ): die("Device Plane container inspect shape mismatch") return containers[0] def device_plane_runtime_inventory(services): inventory = [] for service in services: container_ids = device_plane_service_container_ids(service) if not container_ids: continue container = inspect_device_plane_container(container_ids[0]) state = container.get("State") or {} health = (state.get("Health") or {}).get("Status") inventory.append({ "service": service, "containerId": container.get("Id"), "imageId": container.get("Image"), "status": state.get("Status"), "running": state.get("Running") is True, "health": health, "restartCount": int(container.get("RestartCount") or 0), }) return { "schemaVersion": "nodedc.device-plane.runtime-inventory.v1", "composeProject": "nodedc-device-plane", "services": inventory, } def device_plane_inventory_service_names(inventory): if ( not isinstance(inventory, dict) or inventory.get("schemaVersion") != "nodedc.device-plane.runtime-inventory.v1" or inventory.get("composeProject") != "nodedc-device-plane" or not isinstance(inventory.get("services"), list) ): die("Device Plane runtime inventory shape mismatch") names = [] for item in inventory["services"]: if ( not isinstance(item, dict) or set(item) != { "service", "containerId", "imageId", "status", "running", "health", "restartCount", } or item.get("service") not in ( *DEVICE_PLANE_RUNTIME_SERVICES, "device-manager", DEVICE_PLANE_BACKHAUL_TARGET_SERVICE, ) or not isinstance(item.get("containerId"), str) or not re.fullmatch(r"[a-f0-9]{64}", item["containerId"]) or not isinstance(item.get("imageId"), str) or not re.fullmatch(r"sha256:[a-f0-9]{64}", item["imageId"]) or not isinstance(item.get("status"), str) or not isinstance(item.get("running"), bool) or item.get("health") not in ( None, "starting", "healthy", "unhealthy", ) or not isinstance(item.get("restartCount"), int) or item["restartCount"] < 0 ): die("Device Plane runtime inventory service mismatch") service = item["service"] if service in names: die("Device Plane runtime inventory contains duplicates") names.append(service) return tuple(names) def validate_device_plane_control_core_selected_predecessor_runtime(): inventory = device_plane_runtime_inventory(("device-control-core",)) names = device_plane_inventory_service_names(inventory) if names != ("device-control-core",): die("Device Control Core selected predecessor runtime is missing") # This is the selected repair target, not a preserved dependency. Its # observed Docker state is evidence for rollback only; health and state # acceptance belongs to the new generation after recreate. return inventory["services"][0] def accept_device_plane_control_core_rollback_runtime(runtime_before): expected_services = ( "device-control-core", "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", ) names = device_plane_inventory_service_names(runtime_before) if set(names) != set(expected_services): die("Device Control Core rollback predecessor inventory mismatch") before = { item["service"]: item for item in runtime_before["services"] } selected_before = before["device-control-core"] predecessor_was_healthy = ( selected_before["status"] == "running" and selected_before["running"] is True and selected_before["health"] == "healthy" ) for service in expected_services[1:]: healthcheck_compose_service_with_grace("device-plane", service) preserved = device_plane_runtime_inventory(expected_services[1:]) if set(device_plane_inventory_service_names(preserved)) != set( expected_services[1:] ): die("Device Control Core rollback preserved runtime is incomplete") for item in preserved["services"]: expected = before[item["service"]] if ( item["containerId"] != expected["containerId"] or item["imageId"] != expected["imageId"] or item["status"] != "running" or item["running"] is not True or item["health"] != "healthy" ): die( "Device Control Core rollback changed preserved service: " f"{item['service']}" ) container_id = compose_service_container_id( "device-plane", "device-control-core", ) if container_id == selected_before["containerId"]: die("Device Control Core rollback generation was not recreated") last_status = "unknown" for attempt in range(1, 61): container = inspect_device_plane_container(container_id) state = container.get("State") or {} health = (state.get("Health") or {}).get("Status") last_status = health or state.get("Status") or "unknown" restored_healthy = ( state.get("Status") == "running" and state.get("Running") is True and health == "healthy" ) restored_repair_boundary = ( state.get("Status") in ( "created", "running", "paused", "restarting", "exited", ) and isinstance(state.get("Running"), bool) and health in (None, "starting", "healthy", "unhealthy") ) if restored_healthy or ( not predecessor_was_healthy and restored_repair_boundary ): return { "containerId": container_id, "health": health, "status": state.get("Status"), "predecessorHealth": selected_before["health"], "predecessorStatus": selected_before["status"], } if attempt < 60: time.sleep(5) die( "Device Control Core rollback state did not converge: " f"{last_status}" ) def validate_device_plane_foundation_recovery_evidence(payload_dir): backup_dir = BACKUPS_DIR / DEVICE_PLANE_FOUNDATION_RECOVERY_BACKUP_ID try: backup_stat = backup_dir.lstat() except FileNotFoundError: die("Device Plane foundation recovery backup is missing") if stat.S_ISLNK(backup_stat.st_mode) or not stat.S_ISDIR( backup_stat.st_mode ): die("Device Plane foundation recovery backup is unsafe") backup_names = {child.name for child in backup_dir.iterdir()} if backup_names != set(DEVICE_PLANE_FOUNDATION_RECOVERY_BACKUP_SHA256): die("Device Plane foundation recovery backup file set mismatch") for name, expected in ( DEVICE_PLANE_FOUNDATION_RECOVERY_BACKUP_SHA256.items() ): path = backup_dir / name path_stat = path.lstat() if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected ): die( "Device Plane foundation recovery backup drift detected: " f"{name}" ) failed_artifact = FAILED_DIR / DEVICE_PLANE_FOUNDATION_FAILED_ARTIFACT try: failed_stat = failed_artifact.lstat() except FileNotFoundError: die("Device Plane foundation failed artifact is missing") if ( stat.S_ISLNK(failed_stat.st_mode) or not stat.S_ISREG(failed_stat.st_mode) or sha256_file(failed_artifact) != DEVICE_PLANE_FOUNDATION_FAILED_ARTIFACT_SHA256 ): die("Device Plane foundation failed artifact evidence mismatch") try: state_stat = FAILED_STATE_FILE.lstat() state_lines = FAILED_STATE_FILE.read_text( encoding="utf-8" ).splitlines() except (FileNotFoundError, OSError, UnicodeDecodeError): die("Device Plane foundation failed journal is unreadable") if stat.S_ISLNK(state_stat.st_mode) or not stat.S_ISREG( state_stat.st_mode ): die("Device Plane foundation failed journal is unsafe") records = [] for line in state_lines: try: value = json.loads(line) except json.JSONDecodeError: die("Device Plane foundation failed journal contains invalid JSON") if ( isinstance(value, dict) and value.get("id") == DEVICE_PLANE_FOUNDATION_FAILED_PATCH_ID ): records.append(value) if len(records) != 1: die("Device Plane foundation failed journal evidence count mismatch") record = records[0] if ( record.get("artifact") != DEVICE_PLANE_FOUNDATION_FAILED_ARTIFACT or record.get("backup_id") != DEVICE_PLANE_FOUNDATION_RECOVERY_BACKUP_ID or record.get("component") != "device-plane" or record.get("sha256") != DEVICE_PLANE_FOUNDATION_FAILED_ARTIFACT_SHA256 or record.get("started_apply") is not True or record.get("rollback_status") != "failed:DeployError" or record.get("status") != "failed" or record.get("message") != ( "healthcheck failed for http://127.0.0.1:18120/healthz: " "" ) ): die("Device Plane foundation failed journal evidence mismatch") with tempfile.TemporaryDirectory( prefix="device-plane-failed-foundation-", dir=TMP_DIR, ) as directory: failed_manifest, failed_entries, failed_payload = load_artifact( failed_artifact, Path(directory), ) if ( failed_manifest.get("id") != DEVICE_PLANE_FOUNDATION_FAILED_PATCH_ID or failed_manifest.get("component") != "device-plane" or failed_manifest.get("type") != "app-overlay" or tuple(failed_entries) != DEVICE_PLANE_FOUNDATION_ENTRIES ): die("Device Plane foundation failed artifact contract mismatch") failed_source = collect_exact_files( failed_payload, DEVICE_PLANE_FOUNDATION_ENTRIES, "Device Plane failed foundation source", ) candidate_source = collect_exact_files( payload_dir, DEVICE_PLANE_FOUNDATION_ENTRIES, "Device Plane recovery candidate source", ) if candidate_source != failed_source: die( "Device Plane recovery candidate does not match failed " "foundation source" ) root = component_root("device-plane") compose_path = root / "docker-compose.device-plane.yml" if ( compose_path.is_symlink() or not compose_path.is_file() or sha256_file(compose_path) != DEVICE_PLANE_FOUNDATION_PREDECESSOR_COMPOSE_SHA256 ): die("Device Plane foundation partial Compose predecessor mismatch") for rel in DEVICE_PLANE_FOUNDATION_ENTRIES: if rel == "docker-compose.device-plane.yml": continue path = root / rel if path.exists() or path.is_symlink(): die( "Device Plane foundation partial predecessor retained source: " f"{rel}" ) recovery_descriptor = root / DEVICE_PLANE_FOUNDATION_RECOVERY_REL if recovery_descriptor.exists() or recovery_descriptor.is_symlink(): die("Device Plane foundation recovery descriptor already exists") bootstrap_descriptor = ( root / DEVICE_PLANE_POSTGRES_BOOTSTRAP_REL ) if ( bootstrap_descriptor.is_symlink() or not bootstrap_descriptor.is_file() or sha256_file(bootstrap_descriptor) != DEVICE_PLANE_POSTGRES_BOOTSTRAP_DESCRIPTOR_SHA256 ): die("Device Plane PostgreSQL bootstrap descriptor drift detected") runtime = validate_device_plane_foundation_runtime() return { "mode": "failed-foundation-live-runtime-adoption", "backup": backup_dir, "failedArtifact": failed_artifact, "runtime": runtime, } def inspect_device_plane_network_optional(name): result = subprocess.run( [str(DOCKER), "network", "inspect", name], check=False, capture_output=True, text=True, ) if result.returncode == 1: return None if result.returncode != 0: die(f"Device Plane network inspect failed: {name}") try: value = json.loads(result.stdout) except json.JSONDecodeError: die(f"Device Plane network inspect returned invalid JSON: {name}") if ( not isinstance(value, list) or len(value) != 1 or not isinstance(value[0], dict) ): die(f"Device Plane network inspect shape mismatch: {name}") return value[0] def validate_device_plane_network_contract( name, *, internal, expected_container_ids, ): network = inspect_device_plane_network_optional(name) if network is None: die(f"Device Plane network is missing: {name}") options = network.get("Options") or {} if ( network.get("Name") != name or network.get("Driver") != "bridge" or network.get("Internal") is not internal or set((network.get("Containers") or {}).keys()) != set(expected_container_ids) ): die(f"Device Plane network boundary mismatch: {name}") if ( name == DEVICE_PLANE_CONTROL_NETWORK and options.get( "com.docker.network.bridge.enable_ip_masquerade" ) != "false" ): die("Device Plane control network masquerade boundary mismatch") return network def validate_device_plane_foundation_network_publication_evidence( payload_dir, ): validate_device_plane_foundation_network_publication_payload(payload_dir) backup_dir = ( BACKUPS_DIR / DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_BACKUP_ID ) try: backup_stat = backup_dir.lstat() except FileNotFoundError: die("Device Plane failed recovery backup is missing") if stat.S_ISLNK(backup_stat.st_mode) or not stat.S_ISDIR( backup_stat.st_mode ): die("Device Plane failed recovery backup is unsafe") backup_names = {child.name for child in backup_dir.iterdir()} expected_backup = ( DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_BACKUP_SHA256 ) if backup_names != set(expected_backup): die("Device Plane failed recovery backup file set mismatch") for name, expected_sha256 in expected_backup.items(): path = backup_dir / name path_stat = path.lstat() if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Device Plane failed recovery backup drift detected: " f"{name}" ) failed_artifact = ( FAILED_DIR / DEVICE_PLANE_FOUNDATION_RECOVERY_FAILED_ARTIFACT ) try: failed_stat = failed_artifact.lstat() except FileNotFoundError: die("Device Plane failed recovery artifact is missing") if ( stat.S_ISLNK(failed_stat.st_mode) or not stat.S_ISREG(failed_stat.st_mode) or sha256_file(failed_artifact) != DEVICE_PLANE_FOUNDATION_RECOVERY_FAILED_ARTIFACT_SHA256 ): die("Device Plane failed recovery artifact evidence mismatch") try: state_stat = FAILED_STATE_FILE.lstat() state_lines = FAILED_STATE_FILE.read_text( encoding="utf-8" ).splitlines() except (FileNotFoundError, OSError, UnicodeDecodeError): die("Device Plane failed recovery journal is unreadable") if stat.S_ISLNK(state_stat.st_mode) or not stat.S_ISREG( state_stat.st_mode ): die("Device Plane failed recovery journal is unsafe") records = [] for line in state_lines: try: value = json.loads(line) except json.JSONDecodeError: die("Device Plane failed recovery journal contains invalid JSON") if ( isinstance(value, dict) and value.get("id") == DEVICE_PLANE_FOUNDATION_RECOVERY_FAILED_PATCH_ID ): records.append(value) if len(records) != 1: die("Device Plane failed recovery journal evidence count mismatch") record = records[0] if ( record.get("artifact") != DEVICE_PLANE_FOUNDATION_RECOVERY_FAILED_ARTIFACT or record.get("backup_id") != DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_BACKUP_ID or record.get("component") != "device-plane" or record.get("sha256") != DEVICE_PLANE_FOUNDATION_RECOVERY_FAILED_ARTIFACT_SHA256 or record.get("started_apply") is not True or record.get("rollback_status") != ( "ok:device-plane-overlay:" "source-restored-runtime-unchanged:9" ) or record.get("status") != "failed" or record.get("message") != ( "healthcheck failed for http://127.0.0.1:18120/healthz: " "" ) ): die("Device Plane failed recovery journal evidence mismatch") comparable_entries = tuple( rel for rel in DEVICE_PLANE_FOUNDATION_ENTRIES if rel != "docker-compose.device-plane.yml" ) with tempfile.TemporaryDirectory( prefix="device-plane-failed-recovery-", dir=TMP_DIR, ) as directory: failed_manifest, failed_entries, failed_payload = load_artifact( failed_artifact, Path(directory), ) if ( failed_manifest.get("id") != DEVICE_PLANE_FOUNDATION_RECOVERY_FAILED_PATCH_ID or failed_manifest.get("component") != "device-plane" or failed_manifest.get("type") != "app-overlay" or tuple(failed_entries) != DEVICE_PLANE_FOUNDATION_RECOVERY_ENTRIES ): die("Device Plane failed recovery artifact contract mismatch") failed_source = collect_exact_files( failed_payload, comparable_entries, "Device Plane failed recovery source", ) candidate_source = collect_exact_files( payload_dir, comparable_entries, "Device Plane network-publication candidate source", ) if candidate_source != failed_source: die( "Device Plane network-publication candidate source drift " "detected" ) root = component_root("device-plane") compose_path = root / "docker-compose.device-plane.yml" if ( compose_path.is_symlink() or not compose_path.is_file() or sha256_file(compose_path) != DEVICE_PLANE_FOUNDATION_PREDECESSOR_COMPOSE_SHA256 ): die("Device Plane network-publication source predecessor mismatch") for rel in DEVICE_PLANE_FOUNDATION_ENTRIES: if rel == "docker-compose.device-plane.yml": continue path = root / rel if path.exists() or path.is_symlink(): die( "Device Plane network-publication predecessor retained " f"source: {rel}" ) for descriptor_rel in ( DEVICE_PLANE_FOUNDATION_RECOVERY_REL, DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_REL, ): descriptor_path = root / descriptor_rel if descriptor_path.exists() or descriptor_path.is_symlink(): die( "Device Plane network-publication descriptor predecessor " "mismatch" ) bootstrap_descriptor = root / DEVICE_PLANE_POSTGRES_BOOTSTRAP_REL if ( bootstrap_descriptor.is_symlink() or not bootstrap_descriptor.is_file() or sha256_file(bootstrap_descriptor) != DEVICE_PLANE_POSTGRES_BOOTSTRAP_DESCRIPTOR_SHA256 ): die("Device Plane PostgreSQL bootstrap descriptor drift detected") runtime = validate_device_plane_foundation_runtime() for service, expected_id in ( DEVICE_PLANE_FOUNDATION_PREDECESSOR_CONTAINER_IDS.items() ): if runtime[service]["containerId"] != expected_id: die( "Device Plane network-publication runtime predecessor " f"generation mismatch: {service}" ) expected_network_ids = { item["containerId"] for item in runtime.values() } validate_device_plane_network_contract( DEVICE_PLANE_PRIVATE_NETWORK, internal=True, expected_container_ids=expected_network_ids, ) if inspect_device_plane_network_optional(DEVICE_PLANE_CONTROL_NETWORK): die("Device Plane control network already exists") return { "mode": "failed-foundation-network-publication-correction", "backup": backup_dir, "failedArtifact": failed_artifact, "runtime": runtime, } def validate_device_plane_postgres_bootstrap_payload(payload_dir): descriptor = read_strict_json( payload_dir / DEVICE_PLANE_POSTGRES_BOOTSTRAP_REL, "Device Plane PostgreSQL bootstrap descriptor", max_bytes=8 * 1024, ) expected = { "schemaVersion": "nodedc.device-plane.postgres-bootstrap.v1", "service": "device-postgres", "volume": DEVICE_PLANE_POSTGRES_VOLUME, "mode": "create-if-absent", "ordinaryApplicationSelection": "forbidden", "rollbackVolumePolicy": "preserve", } if descriptor != expected: die("Device Plane PostgreSQL bootstrap descriptor mismatch") return descriptor def preflight_device_plane_postgres_bootstrap(): container_result = subprocess.run( [ str(DOCKER), "container", "ls", "-a", "--filter", "label=com.docker.compose.project=nodedc-device-plane", "--filter", "label=com.docker.compose.service=device-postgres", "--format", "{{.ID}}", ], check=False, capture_output=True, text=True, ) if container_result.returncode != 0: die("Device Plane PostgreSQL container preflight failed") container_ids = [ line.strip() for line in container_result.stdout.splitlines() if line.strip() ] if container_ids: die("Device Plane PostgreSQL container already exists") volume_result = subprocess.run( [ str(DOCKER), "volume", "inspect", DEVICE_PLANE_POSTGRES_VOLUME, ], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) if volume_result.returncode == 0: die("Device Plane PostgreSQL volume already exists") if volume_result.returncode != 1: die("Device Plane PostgreSQL volume preflight failed") return "absent" def device_plane_postgres_plan_selection(postgres_preflight): if postgres_preflight is None: return "preserved-prerequisite:not-selected" if postgres_preflight != "absent": die("Device Plane PostgreSQL plan preflight state is invalid") return "bootstrap-selected:create-if-absent" def validate_device_plane_foundation_runtime(network_publication=False): validate_device_plane_runtime_secret_metadata() expected = { "device-control-core": { "image": DEVICE_PLANE_CONTROL_CORE_IMAGE, "imageId": DEVICE_PLANE_FOUNDATION_RECOVERY_IMAGE_IDS[ "device-control-core" ], "user": "1000:1000", "ports": { "18120/tcp": [{ "HostIp": "127.0.0.1", "HostPort": "18120", }], }, "actualPorts": ( { "18120/tcp": [{ "HostIp": "127.0.0.1", "HostPort": "18120", }], } if network_publication else {} ), "networks": ( { DEVICE_PLANE_PRIVATE_NETWORK, DEVICE_PLANE_CONTROL_NETWORK, } if network_publication else {DEVICE_PLANE_PRIVATE_NETWORK} ), }, "device-gateway": { "image": DEVICE_PLANE_GATEWAY_IMAGE, "imageId": DEVICE_PLANE_FOUNDATION_RECOVERY_IMAGE_IDS[ "device-gateway" ], "user": "1000:1000", "ports": { "18121/tcp": [{ "HostIp": "127.0.0.1", "HostPort": "18121", }], }, "actualPorts": ( { "18121/tcp": [{ "HostIp": "127.0.0.1", "HostPort": "18121", }], } if network_publication else {} ), "networks": ( { DEVICE_PLANE_PRIVATE_NETWORK, DEVICE_PLANE_CONTROL_NETWORK, } if network_publication else {DEVICE_PLANE_PRIVATE_NETWORK} ), }, "device-postgres": { "image": "postgres:16-alpine", "imageId": DEVICE_PLANE_FOUNDATION_RECOVERY_IMAGE_IDS[ "device-postgres" ], "user": "", "ports": {}, "actualPorts": {}, "networks": {DEVICE_PLANE_PRIVATE_NETWORK}, }, } accepted = {} for service, contract in expected.items(): container_ids = device_plane_service_container_ids(service) if len(container_ids) != 1: die( "Device Plane foundation runtime service count mismatch: " f"{service}" ) container = inspect_device_plane_container(container_ids[0]) state = container.get("State") or {} config = container.get("Config") or {} host_config = container.get("HostConfig") or {} labels = config.get("Labels") or {} networks = (container.get("NetworkSettings") or {}).get( "Networks" ) or {} actual_ports = (container.get("NetworkSettings") or {}).get( "Ports" ) or {} if ( state.get("Status") != "running" or state.get("Running") is not True or state.get("Restarting") is True or state.get("ExitCode") != 0 or state.get("Error") not in ("", None) or (state.get("Health") or {}).get("Status") != "healthy" or int(container.get("RestartCount") or 0) != 0 ): die( "Device Plane foundation runtime state mismatch: " f"{service}" ) if ( container.get("Image") != contract["imageId"] or config.get("Image") != contract["image"] or config.get("User", "") != contract["user"] or (host_config.get("PortBindings") or {}) != contract["ports"] or (host_config.get("RestartPolicy") or {}).get("Name") != "unless-stopped" or labels.get("com.docker.compose.project") != "nodedc-device-plane" or labels.get("com.docker.compose.service") != service or set(networks) != contract["networks"] or actual_ports != contract["actualPorts"] ): die( "Device Plane foundation runtime boundary mismatch: " f"{service}" ) accepted[service] = { "containerId": container.get("Id"), "imageId": container.get("Image"), "health": "healthy", "restartCount": 0, } if network_publication: for service in ("device-control-core", "device-gateway"): if ( accepted[service]["containerId"] == DEVICE_PLANE_FOUNDATION_PREDECESSOR_CONTAINER_IDS[service] ): die( "Device Plane stateless generation was not recreated: " f"{service}" ) if ( accepted["device-postgres"]["containerId"] != DEVICE_PLANE_FOUNDATION_PREDECESSOR_CONTAINER_IDS[ "device-postgres" ] ): die("Device Plane PostgreSQL generation changed") core = inspect_device_plane_container( device_plane_service_container_ids("device-control-core")[0] ) gateway = inspect_device_plane_container( device_plane_service_container_ids("device-gateway")[0] ) postgres = inspect_device_plane_container( device_plane_service_container_ids("device-postgres")[0] ) for service, container in ( ("device-control-core", core), ("device-gateway", gateway), ): host_config = container.get("HostConfig") or {} if ( host_config.get("ReadonlyRootfs") is not True or set(host_config.get("CapDrop") or ()) != {"ALL"} or "no-new-privileges:true" not in set(host_config.get("SecurityOpt") or ()) ): die( "Device Plane stateless hardening mismatch: " f"{service}" ) core_environment = container_environment( core, "Device Plane Control Core", ) core_required_environment = { "HOST": "0.0.0.0", "PORT": "18120", "DEVICE_DATABASE_HOST": "device-postgres", "DEVICE_DATABASE_PORT": "5432", "DEVICE_DATABASE_NAME": "device_plane", "DEVICE_DATABASE_USER": "device_plane", "DEVICE_DATABASE_PASSWORD_FILE": ( "/run/nodedc-secrets/postgres-password" ), "DEVICE_DATABASE_POOL_SIZE": "10", "DEVICE_DISCOVERY_INGEST_ENABLED": "false", } if any( core_environment.get(key) != value for key, value in core_required_environment.items() ): die("Device Plane Control Core environment mismatch") for forbidden in ( "DEVICE_DATABASE_URL", "DEVICE_DATABASE_PASSWORD", "DEVICE_GATEWAY_CORE_TOKEN", "DEVICE_IDENTIFIER_PEPPER", ): if forbidden in core_environment: die("Device Plane Control Core plaintext secret boundary mismatch") gateway_environment = container_environment( gateway, "Device Plane Gateway", ) gateway_required_environment = { "DEVICE_GATEWAY_HEALTH_HOST": "0.0.0.0", "DEVICE_GATEWAY_HEALTH_PORT": "18121", "DEVICE_GATEWAY_LISTEN_ENABLED": "false", "DEVICE_GATEWAY_TCP_HOST": "127.0.0.1", "DEVICE_GATEWAY_TCP_PORT": "9921", "DEVICE_GATEWAY_MAX_SESSIONS": "100", "DEVICE_GATEWAY_SESSION_TIMEOUT_MS": "10000", } if any( gateway_environment.get(key) != value for key, value in gateway_required_environment.items() ): die("Device Plane Gateway environment mismatch") if any( key in gateway_environment for key in ( "DEVICE_GATEWAY_CORE_TOKEN", "DEVICE_GATEWAY_COMMAND_TOKEN", ) ): die("Device Plane Gateway plaintext secret boundary mismatch") core_mounts = core.get("Mounts") or [] if len(core_mounts) != 1: die("Device Plane Control Core mount count mismatch") core_secret = core_mounts[0] if ( core_secret.get("Type") != "bind" or core_secret.get("Source") != str(DEVICE_PLANE_POSTGRES_PASSWORD_FILE) or core_secret.get("Destination") != "/run/nodedc-secrets/postgres-password" or core_secret.get("RW") is not False ): die("Device Plane Control Core secret mount mismatch") if gateway.get("Mounts") not in (None, []): die("Device Plane Gateway unexpected mount") postgres_mounts = postgres.get("Mounts") or [] postgres_secret = [ mount for mount in postgres_mounts if mount.get("Destination") == "/run/nodedc-secrets/postgres-password" ] postgres_volume = [ mount for mount in postgres_mounts if mount.get("Destination") == "/var/lib/postgresql/data" ] if ( len(postgres_mounts) != 2 or len(postgres_secret) != 1 or postgres_secret[0].get("Type") != "bind" or postgres_secret[0].get("Source") != str(DEVICE_PLANE_POSTGRES_PASSWORD_FILE) or postgres_secret[0].get("RW") is not False or len(postgres_volume) != 1 or postgres_volume[0].get("Type") != "volume" or postgres_volume[0].get("Name") != DEVICE_PLANE_POSTGRES_VOLUME or postgres_volume[0].get("RW") is not True ): die("Device Plane PostgreSQL preserved mount mismatch") if network_publication: validate_device_plane_network_contract( DEVICE_PLANE_PRIVATE_NETWORK, internal=True, expected_container_ids={ accepted[service]["containerId"] for service in DEVICE_PLANE_RUNTIME_SERVICES }, ) validate_device_plane_network_contract( DEVICE_PLANE_CONTROL_NETWORK, internal=False, expected_container_ids={ accepted["device-control-core"]["containerId"], accepted["device-gateway"]["containerId"], }, ) return accepted def validate_device_plane_b2_discovery_ingress_runtime( runtime_before, preserved_stateless_services=(), ): validate_device_plane_runtime_secret_metadata() preserved_stateless = set(preserved_stateless_services) if not preserved_stateless.issubset({ "device-control-core", "device-gateway", }): die("Device Plane B2 preserved stateless service set mismatch") before_names = device_plane_inventory_service_names(runtime_before) if set(before_names) != set(DEVICE_PLANE_RUNTIME_SERVICES): die("Device Plane B2 ingress predecessor inventory mismatch") before = { item["service"]: item for item in runtime_before["services"] } contracts = { "device-control-core": { "image": DEVICE_PLANE_CONTROL_CORE_IMAGE, "user": "1000:1000", "ports": { "18120/tcp": [{ "HostIp": "127.0.0.1", "HostPort": "18120", }], }, "networks": { DEVICE_PLANE_PRIVATE_NETWORK, DEVICE_PLANE_CONTROL_NETWORK, }, }, "device-gateway": { "image": DEVICE_PLANE_GATEWAY_IMAGE, "user": "1000:1000", "ports": { "18121/tcp": [{ "HostIp": "127.0.0.1", "HostPort": "18121", }], "9921/tcp": [{ "HostIp": "127.0.0.1", "HostPort": "9921", }], }, "networks": { DEVICE_PLANE_PRIVATE_NETWORK, DEVICE_PLANE_CONTROL_NETWORK, }, }, "device-postgres": { "image": "postgres:16-alpine", "user": "", "ports": {}, "networks": {DEVICE_PLANE_PRIVATE_NETWORK}, }, } accepted = {} containers = {} for service, contract in contracts.items(): container_ids = device_plane_service_container_ids(service) if len(container_ids) != 1: die(f"Device Plane B2 ingress service count mismatch: {service}") container = inspect_device_plane_container(container_ids[0]) containers[service] = container state = container.get("State") or {} config = container.get("Config") or {} host_config = container.get("HostConfig") or {} labels = config.get("Labels") or {} networks = (container.get("NetworkSettings") or {}).get( "Networks" ) or {} actual_ports = (container.get("NetworkSettings") or {}).get( "Ports" ) or {} if ( state.get("Status") != "running" or state.get("Running") is not True or state.get("Restarting") is True or state.get("ExitCode") != 0 or state.get("Error") not in ("", None) or (state.get("Health") or {}).get("Status") != "healthy" or int(container.get("RestartCount") or 0) != 0 or config.get("Image") != contract["image"] or config.get("User", "") != contract["user"] or (host_config.get("PortBindings") or {}) != contract["ports"] or actual_ports != contract["ports"] or set(networks) != contract["networks"] or (host_config.get("RestartPolicy") or {}).get("Name") != "unless-stopped" or labels.get("com.docker.compose.project") != "nodedc-device-plane" or labels.get("com.docker.compose.service") != service ): die(f"Device Plane B2 ingress runtime mismatch: {service}") accepted[service] = { "containerId": container.get("Id"), "imageId": container.get("Image"), } if ( accepted["device-postgres"]["containerId"] != before["device-postgres"]["containerId"] or accepted["device-postgres"]["imageId"] != before["device-postgres"]["imageId"] ): die("Device Plane B2 ingress changed PostgreSQL generation") for service in ("device-control-core", "device-gateway"): same_container = ( accepted[service]["containerId"] == before[service]["containerId"] ) same_image = ( accepted[service]["imageId"] == before[service]["imageId"] ) if service in preserved_stateless: if not (same_container and same_image): die( "Device Plane B2 stateless generation was not preserved: " f"{service}" ) elif same_container or same_image: die( "Device Plane B2 ingress stateless generation not replaced: " f"{service}" ) host_config = containers[service].get("HostConfig") or {} if ( host_config.get("ReadonlyRootfs") is not True or set(host_config.get("CapDrop") or ()) != {"ALL"} or "no-new-privileges:true" not in set(host_config.get("SecurityOpt") or ()) ): die( "Device Plane B2 ingress hardening mismatch: " f"{service}" ) core_environment = container_environment( containers["device-control-core"], "Device Plane B2 Control Core", ) core_required = { "DEVICE_DISCOVERY_INGEST_ENABLED": "true", "DEVICE_GATEWAY_CORE_TOKEN_FILE": "/run/nodedc-secrets/gateway-core-token", "DEVICE_IDENTIFIER_PEPPER_FILE": "/run/nodedc-secrets/identifier-pepper", } if any( core_environment.get(key) != value for key, value in core_required.items() ): die("Device Plane B2 Control Core environment mismatch") gateway_environment = container_environment( containers["device-gateway"], "Device Plane B2 Gateway", ) gateway_required = { "DEVICE_GATEWAY_LISTEN_ENABLED": "true", "DEVICE_GATEWAY_PUBLIC_INGRESS_ENABLED": "false", "DEVICE_GATEWAY_TCP_HOST": "127.0.0.1", "DEVICE_GATEWAY_TCP_PORT": "9921", "DEVICE_GATEWAY_CORE_URL": "http://device-control-core:18120", "DEVICE_GATEWAY_CORE_TOKEN_FILE": "/run/nodedc-secrets/gateway-core-token", "DEVICE_GATEWAY_CORE_TIMEOUT_MS": "5000", "DEVICE_GATEWAY_MAX_BUFFERED_BYTES": "65536", "DEVICE_GATEWAY_MAX_SESSIONS": "100", "DEVICE_GATEWAY_MAX_SESSIONS_PER_ADDRESS": "10", "DEVICE_GATEWAY_MAX_CONNECTIONS_PER_MINUTE_PER_ADDRESS": "30", "DEVICE_GATEWAY_SESSION_TIMEOUT_MS": "10000", } if any( gateway_environment.get(key) != value for key, value in gateway_required.items() ): die("Device Plane B2 Gateway environment mismatch") for environment in (core_environment, gateway_environment): for forbidden in ( "DEVICE_GATEWAY_CORE_TOKEN", "DEVICE_IDENTIFIER_PEPPER", "DEVICE_GATEWAY_COMMAND_TOKEN", ): if forbidden in environment: die("Device Plane B2 plaintext secret boundary mismatch") core_mounts = { mount.get("Destination"): mount for mount in containers["device-control-core"].get("Mounts") or [] } expected_core_mounts = { "/run/nodedc-secrets/postgres-password": DEVICE_PLANE_POSTGRES_PASSWORD_FILE, "/run/nodedc-secrets/gateway-core-token": DEVICE_PLANE_GATEWAY_CORE_TOKEN_FILE, "/run/nodedc-secrets/identifier-pepper": DEVICE_PLANE_IDENTIFIER_PEPPER_FILE, } if set(core_mounts) != set(expected_core_mounts): die("Device Plane B2 Control Core mount set mismatch") for destination, source in expected_core_mounts.items(): mount = core_mounts[destination] if ( mount.get("Type") != "bind" or mount.get("Source") != str(source) or mount.get("RW") is not False ): die("Device Plane B2 Control Core secret mount mismatch") gateway_mounts = ( containers["device-gateway"].get("Mounts") or [] ) if ( len(gateway_mounts) != 1 or gateway_mounts[0].get("Type") != "bind" or gateway_mounts[0].get("Source") != str(DEVICE_PLANE_GATEWAY_CORE_TOKEN_FILE) or gateway_mounts[0].get("Destination") != "/run/nodedc-secrets/gateway-core-token" or gateway_mounts[0].get("RW") is not False ): die("Device Plane B2 Gateway secret mount mismatch") postgres_mounts = containers["device-postgres"].get("Mounts") or [] postgres_secret = [ mount for mount in postgres_mounts if mount.get("Destination") == "/run/nodedc-secrets/postgres-password" ] postgres_volume = [ mount for mount in postgres_mounts if mount.get("Destination") == "/var/lib/postgresql/data" ] if ( len(postgres_mounts) != 2 or len(postgres_secret) != 1 or postgres_secret[0].get("Type") != "bind" or postgres_secret[0].get("Source") != str(DEVICE_PLANE_POSTGRES_PASSWORD_FILE) or postgres_secret[0].get("RW") is not False or len(postgres_volume) != 1 or postgres_volume[0].get("Type") != "volume" or postgres_volume[0].get("Name") != DEVICE_PLANE_POSTGRES_VOLUME or postgres_volume[0].get("RW") is not True ): die("Device Plane B2 PostgreSQL preserved mount mismatch") expected_private_ids = { accepted[service]["containerId"] for service in DEVICE_PLANE_RUNTIME_SERVICES } validate_device_plane_network_contract( DEVICE_PLANE_PRIVATE_NETWORK, internal=True, expected_container_ids=expected_private_ids, ) validate_device_plane_network_contract( DEVICE_PLANE_CONTROL_NETWORK, internal=False, expected_container_ids={ accepted["device-control-core"]["containerId"], accepted["device-gateway"]["containerId"], }, ) assert_loopback_tcp_port_open(9921) return accepted def validate_device_plane_preserved_runtime_unchanged(runtime_before, label): before_names = device_plane_inventory_service_names(runtime_before) if set(before_names) != set(DEVICE_PLANE_RUNTIME_SERVICES): die(f"{label} predecessor inventory mismatch") before = { item["service"]: item for item in runtime_before["services"] } current = device_plane_runtime_inventory(DEVICE_PLANE_RUNTIME_SERVICES) current_names = device_plane_inventory_service_names(current) if set(current_names) != set(DEVICE_PLANE_RUNTIME_SERVICES): die(f"{label} preserved runtime is incomplete") for item in current["services"]: predecessor = before[item["service"]] if ( item["containerId"] != predecessor["containerId"] or item["imageId"] != predecessor["imageId"] or item["status"] != "running" or item["running"] is not True or item["health"] != "healthy" or item["restartCount"] != 0 ): die(f"{label} changed preserved service: {item['service']}") return current def validate_device_plane_backhaul_target_runtime( runtime_before, expected_enrollment=None, ): current = validate_device_plane_preserved_runtime_unchanged( runtime_before, "Device Plane backhaul", ) root = component_root("device-plane") installed_descriptor = read_strict_json( root / DEVICE_PLANE_BACKHAUL_TARGET_REL, "installed Device Plane backhaul target descriptor", max_bytes=16 * 1024, ) if installed_descriptor != expected_device_plane_backhaul_target_descriptor(): die("installed Device Plane backhaul target descriptor mismatch") installed_compose = root / DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL if ( installed_compose.is_symlink() or not installed_compose.is_file() or sha256_file(installed_compose) != DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_SHA256 ): die("installed Device Plane backhaul target Compose mismatch") failed_descriptor = root / DEVICE_PLANE_BACKHAUL_FAILED_TARGET_REL if failed_descriptor.exists() or failed_descriptor.is_symlink(): die("failed Device Plane backhaul descriptor remains installed") container_ids = device_plane_service_container_ids( DEVICE_PLANE_BACKHAUL_TARGET_SERVICE ) if len(container_ids) != 1: die("Device Plane backhaul target service count mismatch") container = inspect_device_plane_container(container_ids[0]) state = container.get("State") or {} config = container.get("Config") or {} host_config = container.get("HostConfig") or {} labels = config.get("Labels") or {} mounts = container.get("Mounts") or [] expected_mounts = { "/run/nodedc-secrets/ssh_host_ed25519_key": ( DEVICE_PLANE_BACKHAUL_HOST_KEY_FILE ), "/run/nodedc-secrets/authorized_keys": ( DEVICE_PLANE_BACKHAUL_AUTHORIZED_KEYS_FILE ), } actual_mounts = { mount.get("Destination"): mount for mount in mounts } if ( state.get("Status") != "running" or state.get("Running") is not True or state.get("Restarting") is True or state.get("ExitCode") != 0 or state.get("Error") not in ("", None) or (state.get("Health") or {}).get("Status") != "healthy" or int(container.get("RestartCount") or 0) != 0 or config.get("Image") != DEVICE_PLANE_BACKHAUL_TARGET_IMAGE or config.get("User", "") != "" or host_config.get("NetworkMode") != "host" or (host_config.get("PortBindings") or {}) != {} or ((container.get("NetworkSettings") or {}).get("Ports") or {}) != {} or host_config.get("ReadonlyRootfs") is not True or set(host_config.get("CapDrop") or ()) != {"ALL"} or set(host_config.get("CapAdd") or ()) != {"CHOWN", "DAC_OVERRIDE", "SETGID", "SETUID", "SYS_CHROOT"} or "no-new-privileges:true" not in set(host_config.get("SecurityOpt") or ()) or (host_config.get("RestartPolicy") or {}).get("Name") != "unless-stopped" or labels.get("com.docker.compose.project") != "nodedc-device-plane" or labels.get("com.docker.compose.service") != DEVICE_PLANE_BACKHAUL_TARGET_SERVICE or set(actual_mounts) != set(expected_mounts) ): die("Device Plane backhaul target runtime mismatch") for destination, source in expected_mounts.items(): mount = actual_mounts[destination] if ( mount.get("Type") != "bind" or mount.get("Source") != str(source) or mount.get("RW") is not False ): die("Device Plane backhaul target mount mismatch") environment = container_environment( container, "Device Plane backhaul target", ) for name in environment: if any(token in name.upper() for token in ("KEY", "PASS", "TOKEN")): die("Device Plane backhaul target plaintext environment rejected") effective = subprocess.run( [ str(DOCKER), "exec", container_ids[0], "/usr/sbin/sshd", "-T", "-f", "/etc/ssh/sshd_config", ], check=True, capture_output=True, text=True, ).stdout.lower() for required in ( "port 2222", "listenaddress 127.0.0.1:2222", "passwordauthentication no", "kbdinteractiveauthentication no", "allowtcpforwarding local", "permitopen 127.0.0.1:9921", "gatewayports no", "permittty no", "allowagentforwarding no", "x11forwarding no", "permittunnel no", "forcecommand /bin/false", ): if required not in effective: die( "Device Plane backhaul effective sshd contract mismatch: " f"{required}" ) enrollment = ( expected_enrollment if expected_enrollment is not None else read_device_plane_backhaul_enrollment_public_key() ) authorized = ( 'restrict,port-forwarding,permitopen="127.0.0.1:9921" ' f"{enrollment['line']}\n" ) for path, mode, max_size in ( (DEVICE_PLANE_BACKHAUL_HOST_KEY_FILE, 0o400, 2048), (DEVICE_PLANE_BACKHAUL_AUTHORIZED_KEYS_FILE, 0o444, 2048), (DEVICE_PLANE_BACKHAUL_HOST_PUBLIC_KEY_FILE, 0o444, 1024), ): try: path_stat = path.lstat() except FileNotFoundError: die("Device Plane backhaul runtime trust file is missing") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or path_stat.st_uid != 0 or stat.S_IMODE(path_stat.st_mode) != mode or path_stat.st_size > max_size ): die("Device Plane backhaul runtime trust boundary mismatch") if DEVICE_PLANE_BACKHAUL_AUTHORIZED_KEYS_FILE.read_text( encoding="ascii" ) != authorized: die("Device Plane backhaul authorized key mismatch") try: connection = socket.create_connection( ( DEVICE_PLANE_BACKHAUL_LOOPBACK_ADDRESS, DEVICE_PLANE_BACKHAUL_LISTEN_PORT, ), timeout=5, ) connection.settimeout(5) banner = connection.recv(256) connection.close() except OSError as exc: die(f"Device Plane backhaul SSH listener is unavailable: {exc}") if not banner.startswith(b"SSH-2.0-OpenSSH_"): die("Device Plane backhaul SSH banner mismatch") validate_device_plane_tailscale_runtime(require_target=True) assert_loopback_tcp_port_open(9921) return { "preserved": current, "targetContainerId": container.get("Id"), } def validate_device_plane_runtime_secret_metadata(include_management=False): try: directory_stat = DEVICE_PLANE_SECRET_DIR.lstat() except FileNotFoundError: die("Device Plane runtime secret directory is missing") if ( stat.S_ISLNK(directory_stat.st_mode) or not stat.S_ISDIR(directory_stat.st_mode) or directory_stat.st_uid != 0 or directory_stat.st_gid != MAP_GATEWAY_RUNTIME_GID or stat.S_IMODE(directory_stat.st_mode) != 0o710 ): die("Device Plane runtime secret directory boundary mismatch") required_secrets = [ (DEVICE_PLANE_POSTGRES_PASSWORD_FILE, "PostgreSQL"), (DEVICE_PLANE_GATEWAY_CORE_TOKEN_FILE, "Gateway to Core"), (DEVICE_PLANE_IDENTIFIER_PEPPER_FILE, "identifier pepper"), ] if include_management: required_secrets.append(( DEVICE_PLANE_MANAGEMENT_CORE_TOKEN_FILE, "management to Core", )) for path, label in required_secrets: try: path_stat = path.lstat() except FileNotFoundError: die(f"Device Plane {label} runtime secret is missing") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or path_stat.st_uid != 0 or path_stat.st_gid != MAP_GATEWAY_RUNTIME_GID or stat.S_IMODE(path_stat.st_mode) != 0o640 or path_stat.st_size > 512 ): die(f"Device Plane {label} runtime secret boundary mismatch") try: value = path.read_text(encoding="ascii").strip() except (OSError, UnicodeDecodeError): die(f"Device Plane {label} runtime secret is unreadable") if not MAP_GATEWAY_SECRET_RE.fullmatch(value): die(f"Device Plane {label} runtime secret format mismatch") return "exact" def validate_device_manager_control_plane_runtime( *, require_edge_channel=None, core_network_mode=None, require_persistent_data=None, ): core_ids = device_plane_service_container_ids("device-control-core") manager_ids = device_plane_service_container_ids("device-manager") if len(core_ids) != 1 or len(manager_ids) != 1: die("Device Manager control-plane runtime topology mismatch") core = inspect_device_plane_container(core_ids[0]) manager = inspect_device_plane_container(manager_ids[0]) core_environment = container_environment(core, "Device Control Core") manager_environment = container_environment(manager, "Device Manager") if require_edge_channel is None: edge_descriptor = ( DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL ) require_edge_channel = edge_descriptor.is_file() and not edge_descriptor.is_symlink() if require_persistent_data is None: data_descriptors = ( DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_RELEASE_V13_REL, DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_RELEASE_V12_REL, DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_RELEASE_V11_REL, DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_RELEASE_V10_REL, DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_RELEASE_V9_REL, DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_RELEASE_V8_REL, DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_RELEASE_V7_REL, DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_RELEASE_V6_REL, DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_RELEASE_V5_REL, DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_RELEASE_V4_REL, ) require_persistent_data = any( descriptor.is_file() and not descriptor.is_symlink() for descriptor in data_descriptors ) expected_core_environment = { "DEVICE_MANAGEMENT_API_ENABLED": "true", "DEVICE_MANAGEMENT_CORE_TOKEN_FILE": ( "/run/nodedc-secrets/management-core-token" ), } if require_edge_channel: expected_core_environment.update({ "DEVICE_EDGE_CHANNEL_ENABLED": "true", "DEVICE_EDGE_CHANNEL_CORE_KEY_FILE": ( "/run/nodedc-secrets/device-edge-channel/core-private-key.pem" ), "DEVICE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE": ( "/run/nodedc-secrets/device-edge-channel/core-certificate.pem" ), "DEVICE_EDGE_CHANNEL_TRUST_ROOT": ( "/run/nodedc-secrets/device-edge-channel/peers" ), "DEVICE_EDGE_CHANNEL_MAX_EDGES": "32", "DEVICE_EDGE_CHANNEL_RECONCILE_INTERVAL_MS": "15000", }) if any( core_environment.get(key) != value for key, value in expected_core_environment.items() ): die("Device Control Core management runtime mismatch") if not require_edge_channel and any( key in core_environment for key in ( "DEVICE_EDGE_CHANNEL_ENABLED", "DEVICE_EDGE_CHANNEL_CORE_KEY_FILE", "DEVICE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE", "DEVICE_EDGE_CHANNEL_TRUST_ROOT", "DEVICE_EDGE_CHANNEL_MAX_EDGES", "DEVICE_EDGE_CHANNEL_RECONCILE_INTERVAL_MS", ) ): die("Device Manager runtime unexpectedly owns Edge channel state") expected_manager_environment = { "NODE_ENV": "production", "HOST": "0.0.0.0", "PORT": "18122", "NODEDC_DEVICE_MANAGER_AUTH_REQUIRED": "true", "NODEDC_DEVICE_MANAGER_COOKIE_SECURE": "true", "NODEDC_DEVICE_MANAGER_LOCAL_PREVIEW": "false", "NODEDC_DEVICE_MANAGER_SERVICE_SLUG": "device-core", "NODEDC_LAUNCHER_BASE_URL": "https://hub.nodedc.ru", "NODEDC_LAUNCHER_INTERNAL_URL": "http://launcher:5173", "NODEDC_LAUNCHER_INTERNAL_TOKEN_FILE": ( "/run/nodedc-secrets/device-core-internal-token" ), "NODEDC_DEVICE_CORE_INTERNAL_URL": ( "http://device-control-core:18120" ), "NODEDC_DEVICE_CORE_TOKEN_FILE": ( "/run/nodedc-secrets/management-core-token" ), } if require_persistent_data: expected_manager_environment.update({ "NODEDC_DEVICE_MANAGER_PRESENTATION_PATH": ( DEVICE_PLANE_MANAGER_PRESENTATION_PATH ), "NODEDC_DEVICE_MANAGER_MEDIA_ROOT": ( DEVICE_PLANE_MANAGER_MEDIA_ROOT ), }) elif any( key in manager_environment for key in ( "NODEDC_DEVICE_MANAGER_PRESENTATION_PATH", "NODEDC_DEVICE_MANAGER_MEDIA_ROOT", ) ): die("Device Manager runtime unexpectedly owns persistent data") if any( manager_environment.get(key) != value for key, value in expected_manager_environment.items() ): die("Device Manager environment mismatch") for environment in (core_environment, manager_environment): for forbidden in ( "NODEDC_INTERNAL_ACCESS_TOKEN", "NODEDC_PLATFORM_SERVICE_TOKEN", "DEVICE_MANAGEMENT_CORE_TOKEN", ): if forbidden in environment: die("Device Manager plaintext secret boundary mismatch") core_mounts = { mount.get("Destination"): mount for mount in core.get("Mounts") or [] } core_management_mount = core_mounts.get( "/run/nodedc-secrets/management-core-token" ) if ( core_management_mount is None or core_management_mount.get("Type") != "bind" or core_management_mount.get("Source") != str(DEVICE_PLANE_MANAGEMENT_CORE_TOKEN_FILE) or core_management_mount.get("RW") is not False ): die("Device Control Core management secret mount mismatch") expected_core_channel_mounts = { "/run/nodedc-secrets/device-edge-channel/core-private-key.pem": ( DEVICE_PLANE_EDGE_CHANNEL_CORE_PRIVATE_KEY_FILE ), "/run/nodedc-secrets/device-edge-channel/core-certificate.pem": ( DEVICE_PLANE_EDGE_CHANNEL_CORE_CERTIFICATE_FILE ), "/run/nodedc-secrets/device-edge-channel/peers": ( DEVICE_PLANE_EDGE_CHANNEL_PEER_TRUST_DIR ), } if require_edge_channel: for destination, source in expected_core_channel_mounts.items(): mount = core_mounts.get(destination) if ( mount is None or mount.get("Type") != "bind" or mount.get("Source") != str(source) or mount.get("RW") is not False ): die("Device Control Core Edge channel mount mismatch") elif any(destination in core_mounts for destination in expected_core_channel_mounts): die("Device Manager runtime unexpectedly mounts Edge channel state") core_networks = set( ((core.get("NetworkSettings") or {}).get("Networks") or {}).keys() ) if core_network_mode not in (None, "private-egress"): die("Device Control Core Edge channel network mode is invalid") if core_network_mode == "private-egress" and not require_edge_channel: die("Device Control Core private egress requires Edge channel") if core_network_mode == "private-egress": expected_core_networks = { DEVICE_PLANE_PRIVATE_NETWORK, DEVICE_PLANE_EGRESS_NETWORK, } else: expected_core_networks = { DEVICE_PLANE_PRIVATE_NETWORK, DEVICE_PLANE_CONTROL_NETWORK, } if require_edge_channel: expected_core_networks.add(DEVICE_PLANE_EGRESS_NETWORK) if core_networks != expected_core_networks: die("Device Control Core Edge channel network boundary mismatch") manager_mounts = { mount.get("Destination"): mount for mount in manager.get("Mounts") or [] } expected_manager_mounts = { "/run/nodedc-secrets/device-core-internal-token": ( PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE ), "/run/nodedc-secrets/management-core-token": ( DEVICE_PLANE_MANAGEMENT_CORE_TOKEN_FILE ), } if require_persistent_data: expected_manager_mounts[DEVICE_PLANE_MANAGER_DATA_CONTAINER_DIR] = ( DEVICE_PLANE_MANAGER_DATA_DIR ) if set(manager_mounts) != set(expected_manager_mounts): die("Device Manager mount set mismatch") for destination, source in expected_manager_mounts.items(): mount = manager_mounts[destination] persistent_mount = destination == DEVICE_PLANE_MANAGER_DATA_CONTAINER_DIR if ( mount.get("Type") != "bind" or mount.get("Source") != str(source) or mount.get("RW") is not persistent_mount ): die("Device Manager mount boundary mismatch") if require_persistent_data: validate_device_plane_manager_persistent_data_metadata() ports = (manager.get("NetworkSettings") or {}).get("Ports") or {} if any(bindings for bindings in ports.values()): die("Device Manager host port publication is forbidden") networks = set( ((manager.get("NetworkSettings") or {}).get("Networks") or {}).keys() ) if networks != { DEVICE_PLANE_PRIVATE_NETWORK, "nodedc-platform_edge", }: die("Device Manager network boundary mismatch") validate_device_plane_runtime_secret_metadata(include_management=True) if require_edge_channel: if inspect_device_edge_channel_core_identity_state() != "valid-reuse-at-apply": die("Device Edge channel Core identity is not active") validate_device_edge_channel_public_export() ensure_platform_runtime_secret( PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE, MAP_GATEWAY_SECRET_RE, "Device Core Hub handoff", ) return "exact" def validate_device_plane_foundation_installed_source(): failed_artifact = FAILED_DIR / DEVICE_PLANE_FOUNDATION_FAILED_ARTIFACT with tempfile.TemporaryDirectory( prefix="device-plane-installed-foundation-", dir=TMP_DIR, ) as directory: failed_manifest, failed_entries, failed_payload = load_artifact( failed_artifact, Path(directory), ) if ( failed_manifest.get("id") != DEVICE_PLANE_FOUNDATION_FAILED_PATCH_ID or tuple(failed_entries) != DEVICE_PLANE_FOUNDATION_ENTRIES ): die("Device Plane failed foundation source contract mismatch") expected = collect_exact_files( failed_payload, DEVICE_PLANE_FOUNDATION_ENTRIES, "Device Plane failed foundation source", ) actual = collect_exact_files( component_root("device-plane"), DEVICE_PLANE_FOUNDATION_ENTRIES, "Device Plane installed foundation source", ) if actual != expected: die("Device Plane installed foundation source mismatch") descriptor = read_strict_json( component_root("device-plane") / DEVICE_PLANE_FOUNDATION_RECOVERY_REL, "installed Device Plane foundation recovery descriptor", max_bytes=16 * 1024, ) if descriptor != expected_device_plane_foundation_recovery_descriptor(): die("installed Device Plane foundation recovery descriptor mismatch") return actual def validate_device_plane_foundation_network_publication_installed_source(): failed_artifact = ( FAILED_DIR / DEVICE_PLANE_FOUNDATION_RECOVERY_FAILED_ARTIFACT ) comparable_entries = tuple( rel for rel in DEVICE_PLANE_FOUNDATION_ENTRIES if rel != "docker-compose.device-plane.yml" ) with tempfile.TemporaryDirectory( prefix="device-plane-installed-network-publication-", dir=TMP_DIR, ) as directory: failed_manifest, failed_entries, failed_payload = load_artifact( failed_artifact, Path(directory), ) if ( failed_manifest.get("id") != DEVICE_PLANE_FOUNDATION_RECOVERY_FAILED_PATCH_ID or tuple(failed_entries) != DEVICE_PLANE_FOUNDATION_RECOVERY_ENTRIES ): die("Device Plane failed recovery source contract mismatch") expected = collect_exact_files( failed_payload, comparable_entries, "Device Plane failed recovery source", ) actual = collect_exact_files( component_root("device-plane"), comparable_entries, "Device Plane installed network-publication source", ) if actual != expected: die("Device Plane installed network-publication source mismatch") compose = ( component_root("device-plane") / "docker-compose.device-plane.yml" ) if ( compose.is_symlink() or not compose.is_file() or sha256_file(compose) != DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_COMPOSE_SHA256 ): die("Device Plane installed network-publication Compose mismatch") descriptor = read_strict_json( component_root("device-plane") / DEVICE_PLANE_FOUNDATION_NETWORK_PUBLICATION_REL, "installed Device Plane network-publication descriptor", max_bytes=16 * 1024, ) if ( descriptor != expected_device_plane_foundation_network_publication_descriptor() ): die( "installed Device Plane network-publication descriptor mismatch" ) recovery_descriptor = ( component_root("device-plane") / DEVICE_PLANE_FOUNDATION_RECOVERY_REL ) if recovery_descriptor.exists() or recovery_descriptor.is_symlink(): die("terminal Device Plane recovery descriptor is installed") return actual def component_compose_root(component): return COMPONENTS[component].get("compose_root", component_root(component)) def component_compose_project(component): return COMPONENTS[component].get("compose_project") def component_artifact_only(component): return bool(COMPONENTS[component].get("artifact_only")) def touches_engine_credential_sink(component, entries): if component != "engine" or entries is None: return False return any( rel == "nodedc-source/server/credentialSink" or rel.startswith("nodedc-source/server/credentialSink/") or rel == "nodedc-source/server/routes/engineCredentialSink.js" for rel in entries ) # Frozen compatibility predicate retained because the last successful Engine # credential runtime canon includes it in run_compose(). The corresponding # Platform service is not an allowed artifact path and has no deploy branch. def touches_engine_credential_provisioner(component, entries): return False def touches_external_data_plane_files(entries): runtime_contract_files = { "platform/packages/external-provider-contract/package.json", "platform/packages/external-provider-contract/src/contract-version.mjs", "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( rel == "platform/services/external-data-plane" or rel.startswith("platform/services/external-data-plane/") or rel in runtime_contract_files or rel == "platform/docker-compose.external-data-plane.yml" for rel in entries ) def touches_engine_data_product_publish_grant(entries): if entries is None: return False return any( rel == "nodedc-source/server/dataProductPublishGrant" or rel.startswith("nodedc-source/server/dataProductPublishGrant/") for rel in entries ) def is_engine_data_product_publish_grant_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_DATA_PRODUCT_PUBLISH_GRANT_ARTIFACT_ENTRIES ) def is_engine_composite_provider_v4_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_COMPOSITE_PROVIDER_V4_ARTIFACT_ENTRIES ) def is_engine_provider_rotating_slot_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_PROVIDER_ROTATING_SLOT_ARTIFACT_ENTRIES ) def is_engine_provider_authority_diagnostics_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_ARTIFACT_ENTRIES ) def is_engine_depttrans_zone_authority_v1_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_ARTIFACT_ENTRIES ) def is_engine_provider_target_host_policy_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_PROVIDER_TARGET_HOST_POLICY_ARTIFACT_ENTRIES ) def is_engine_mcp_execution_profile_decoder_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_MCP_EXECUTION_PROFILE_DECODER_ARTIFACT_ENTRIES ) def is_engine_mcp_telemetry_catalog_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_MCP_TELEMETRY_CATALOG_ARTIFACT_ENTRIES ) def is_engine_mcp_execution_plan_materialization_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_ARTIFACT_ENTRIES ) def is_engine_mcp_execution_plan_telemetry_runtime_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_ARTIFACT_ENTRIES ) def is_engine_mcp_execution_plan_module_ownership_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_ARTIFACT_ENTRIES ) def is_engine_mcp_normalized_identity_search_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_ARTIFACT_ENTRIES ) def is_engine_mcp_l1_credential_reuse_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_MCP_L1_CREDENTIAL_REUSE_ARTIFACT_ENTRIES ) def is_engine_mcp_l1_credential_provenance_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_ARTIFACT_ENTRIES ) def is_engine_mcp_execution_plan_sandbox_runtime_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_ARTIFACT_ENTRIES ) def is_engine_mcp_gelios_items_envelope_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_ARTIFACT_ENTRIES ) def is_engine_mcp_registered_execution_profiles_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_ARTIFACT_ENTRIES ) def is_engine_mcp_gelios_units_items_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_MCP_GELIOS_UNITS_ITEMS_ARTIFACT_ENTRIES ) def is_engine_agent_full_grant_migration_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_AGENT_FULL_GRANT_MIGRATION_ARTIFACT_ENTRIES ) def is_engine_l2_closed_loop_slice(component, entries): return ( component == "engine" and entries is not None and tuple(entries) == ENGINE_L2_CLOSED_LOOP_ARTIFACT_ENTRIES ) def collect_exact_files(root, entries, label): files = {} for rel in entries: path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"{label} path is missing: {rel}") if stat.S_ISLNK(path_stat.st_mode): die(f"{label} path is unsafe: {rel}") if stat.S_ISREG(path_stat.st_mode): files[rel] = sha256_file(path) continue if not stat.S_ISDIR(path_stat.st_mode): die(f"{label} path is unsafe: {rel}") for child in sorted(path.rglob("*")): child_rel = child.relative_to(root).as_posix() child_stat = child.lstat() if stat.S_ISLNK(child_stat.st_mode): die(f"{label} path is unsafe: {child_rel}") if stat.S_ISDIR(child_stat.st_mode): continue if not stat.S_ISREG(child_stat.st_mode): die(f"{label} path is unsafe: {child_rel}") files[child_rel] = sha256_file(child) return files def collect_engine_data_product_publish_grant_files(root, entries, label): files = {} for rel in entries: path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"{label} file is missing: {rel}") if stat.S_ISLNK(path_stat.st_mode): die(f"{label} path is unsafe: {rel}") if stat.S_ISREG(path_stat.st_mode): files[rel] = sha256_file(path) continue if not stat.S_ISDIR(path_stat.st_mode): die(f"{label} path is unsafe: {rel}") for child in sorted(path.rglob("*")): child_rel = child.relative_to(root).as_posix() child_stat = child.lstat() if stat.S_ISLNK(child_stat.st_mode): die(f"{label} path is unsafe: {child_rel}") if stat.S_ISDIR(child_stat.st_mode): continue if not stat.S_ISREG(child_stat.st_mode): die(f"{label} path is unsafe: {child_rel}") files[child_rel] = sha256_file(child) return files def validate_installed_engine_data_product_publish_grant_foundation(root): # The original credential-sink baseline remains immutable. The two route # files below became Publish-grant-owned when the initial transition was # applied, so their current contract is validated semantically instead of # comparing them to their pre-transition bytes. publish_owned_predecessors = { "nodedc-source/server/routes/engineAgentGateway.js", "nodedc-source/server/routes/n8n.js", } for rel, expected_sha256 in ENGINE_DATA_PRODUCT_PUBLISH_GRANT_INSTALLED_FOUNDATION_SHA256.items(): if rel in publish_owned_predecessors: continue path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"installed Engine data product publish grant foundation is missing: {rel}") if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die(f"installed Engine data product publish grant foundation is unsafe: {rel}") actual_sha256 = sha256_file(path) if actual_sha256 != expected_sha256: die( "installed Engine data product publish grant foundation drift detected: " f"path={rel} expected={expected_sha256} actual={actual_sha256}" ) grant_dir = root / "nodedc-source/server/dataProductPublishGrant" try: grant_stat = grant_dir.lstat() except FileNotFoundError: die("installed Engine data product publish grant source is missing") if stat.S_ISLNK(grant_stat.st_mode) or not stat.S_ISDIR(grant_stat.st_mode): die("installed Engine data product publish grant source is unsafe") installed_names = [] for child in grant_dir.iterdir(): child_stat = child.lstat() if stat.S_ISLNK(child_stat.st_mode) or not stat.S_ISREG(child_stat.st_mode): die(f"installed Engine data product publish grant source is unsafe: {child.name}") installed_names.append(child.name) if tuple(sorted(installed_names)) != ENGINE_DATA_PRODUCT_PUBLISH_GRANT_SOURCE_FILES: die("installed Engine data product publish grant source set mismatch") required_files = ( "nodedc-source/server/assets/provider-packages/v1/catalog.json", "nodedc-source/server/engineAgents/store.js", "nodedc-source/server/routes/engineAgentGateway.js", "nodedc-source/server/routes/n8n.js", ENGINE_DATA_PRODUCT_PUBLISH_GRANT_OVERRIDE_REL, ) for rel in required_files: path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"installed Engine data product publish grant file is missing: {rel}") if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die(f"installed Engine data product publish grant file is unsafe: {rel}") try: override_text = (root / ENGINE_DATA_PRODUCT_PUBLISH_GRANT_OVERRIDE_REL).read_text( encoding="utf-8" ) gateway = (root / "nodedc-source/server/routes/engineAgentGateway.js").read_text( encoding="utf-8" ) n8n_route = (root / "nodedc-source/server/routes/n8n.js").read_text( encoding="utf-8" ) except (OSError, UnicodeDecodeError): die("installed Engine data product publish grant source is unreadable") if override_text != expected_engine_data_product_publish_grant_override(): die("installed Engine data product publish grant override drift detected") if "from '../credentialSink/" in n8n_route: die("installed Engine data product publish grant depends on the legacy sink") for adapter in ( "engineCredentialSinkN8nAdapter", "engineDataProductPublishGrantN8nAdapter", ): if adapter not in n8n_route: die(f"installed Engine data product publish grant adapter is missing: {adapter}") for tool in ( "engine_plan_data_product_publish_grant", "engine_apply_data_product_publish_grant", "engine_accept_data_product_publish_grant", "engine_rollback_data_product_publish_grant", ): if tool not in gateway: die(f"installed Engine data product publish grant tool is missing: {tool}") return sha256_file(root / "docker-compose.yml") def preflight_engine_data_product_publish_grant_predecessor(payload_dir=None): root = component_root("engine") installed_override = root / ENGINE_DATA_PRODUCT_PUBLISH_GRANT_OVERRIDE_REL if installed_override.exists() or installed_override.is_symlink(): compose_sha256 = validate_installed_engine_data_product_publish_grant_foundation(root) if payload_dir is None: die("Engine data product publish grant update candidate is required") installed_files = collect_engine_data_product_publish_grant_files( root, ENGINE_DATA_PRODUCT_PUBLISH_GRANT_ARTIFACT_ENTRIES, "installed Engine data product publish grant", ) candidate_files = collect_engine_data_product_publish_grant_files( payload_dir, ENGINE_DATA_PRODUCT_PUBLISH_GRANT_ARTIFACT_ENTRIES, "candidate Engine data product publish grant", ) if set(installed_files) != set(candidate_files): die("Engine data product publish grant update file set mismatch") changed_paths = tuple(sorted( rel for rel, candidate_sha256 in candidate_files.items() if installed_files[rel] != candidate_sha256 )) if not changed_paths: die("Engine data product publish grant update contains no source change") forbidden_changes = tuple( rel for rel in changed_paths if ( not rel.startswith(ENGINE_DATA_PRODUCT_PUBLISH_GRANT_SOURCE_PREFIX) and rel != ENGINE_PROVIDER_SECURITY_CATALOG_REL ) ) if forbidden_changes: die( "Engine data product publish grant update crosses its source boundary: " f"path={forbidden_changes[0]}" ) if ENGINE_PROVIDER_SECURITY_CATALOG_REL in changed_paths: installed_catalog_sha256 = installed_files[ENGINE_PROVIDER_SECURITY_CATALOG_REL] candidate_catalog_sha256 = candidate_files[ENGINE_PROVIDER_SECURITY_CATALOG_REL] if ( installed_catalog_sha256 != ENGINE_PUBLISH_GRANT_COMPOSITE_PROVIDER_CATALOG_PREDECESSOR_SHA256 or candidate_catalog_sha256 != ENGINE_PUBLISH_GRANT_COMPOSITE_PROVIDER_CATALOG_TARGET_SHA256 ): die("Engine composite provider catalog transition mismatch") if changed_paths != ENGINE_PUBLISH_GRANT_COMPOSITE_PROVIDER_CHANGED_PATHS: die("Engine composite provider update exact changed path set mismatch") mode = "installed-composite-provider-update" else: mode = "installed-source-update" return { "mode": mode, "compose_sha256": compose_sha256, "changed_paths": changed_paths, } actual = {} for rel, expected_sha256 in ENGINE_DATA_PRODUCT_PUBLISH_GRANT_PREDECESSOR_SHA256.items(): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine data product publish grant predecessor file is missing: {rel}") if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die(f"Engine data product publish grant predecessor file is unsafe: {rel}") actual_sha256 = sha256_file(path) if actual_sha256 != expected_sha256: die( "Engine data product publish grant predecessor drift detected: " f"path={rel} expected={expected_sha256} actual={actual_sha256}" ) actual[rel] = actual_sha256 return { "mode": "credential-sink-initial-transition", "compose_sha256": actual["docker-compose.yml"], "changed_paths": (), } def preflight_engine_composite_provider_v4_predecessor(): root = component_root("engine") actual = {} for rel, expected_sha256 in ENGINE_COMPOSITE_PROVIDER_V4_PREDECESSOR_SHA256.items(): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine composite provider v4 predecessor is missing: {rel}") if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die(f"Engine composite provider v4 predecessor is unsafe: {rel}") actual_sha256 = sha256_file(path) if actual_sha256 != expected_sha256: die( "Engine composite provider v4 predecessor drift detected: " f"path={rel} expected={expected_sha256} actual={actual_sha256}" ) actual[rel] = actual_sha256 backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die("Engine composite provider v4 requires the active immutable backend") return { "mode": "exact-composite-provider-v3-to-v4", "predecessor_sha256": actual, "target_sha256": dict(ENGINE_COMPOSITE_PROVIDER_V4_TARGET_SHA256), "backend_mode": backend["mode"], } def preflight_engine_provider_rotating_slot_predecessor(): root = component_root("engine") actual = {} for rel, expected_sha256 in ENGINE_PROVIDER_ROTATING_SLOT_PREDECESSOR_SHA256.items(): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine provider rotating slot predecessor is missing: {rel}") if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die(f"Engine provider rotating slot predecessor is unsafe: {rel}") actual_sha256 = sha256_file(path) if actual_sha256 != expected_sha256: die( "Engine provider rotating slot predecessor drift detected: " f"path={rel} expected={expected_sha256} actual={actual_sha256}" ) actual[rel] = actual_sha256 backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die("Engine provider rotating slot requires the active immutable backend") return { "mode": "exact-gelios-v4-credential-slot-alignment", "predecessor_sha256": actual, "target_sha256": dict(ENGINE_PROVIDER_ROTATING_SLOT_TARGET_SHA256), "backend_mode": backend["mode"], } def preflight_engine_provider_authority_diagnostics_predecessor(): root = component_root("engine") actual = {} for rel, expected_sha256 in ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_PREDECESSOR_SHA256.items(): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine provider authority diagnostics predecessor is missing: {rel}") if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die(f"Engine provider authority diagnostics predecessor is unsafe: {rel}") actual_sha256 = sha256_file(path) if actual_sha256 != expected_sha256: die( "Engine provider authority diagnostics predecessor drift detected: " f"path={rel} expected={expected_sha256} actual={actual_sha256}" ) actual[rel] = actual_sha256 backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die("Engine provider authority diagnostics requires the active immutable backend") return { "mode": "private-node-name-reconciliation", "predecessor_sha256": actual, "target_sha256": dict(ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_TARGET_SHA256), "backend_mode": backend["mode"], } def preflight_engine_depttrans_zone_authority_v1_predecessor(): root = component_root("engine") actual = {} for rel, expected_sha256 in ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_PREDECESSOR_SHA256.items(): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine Depttrans zone authority v1 predecessor is missing: {rel}") if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die(f"Engine Depttrans zone authority v1 predecessor is unsafe: {rel}") actual_sha256 = sha256_file(path) if actual_sha256 != expected_sha256: die( "Engine Depttrans zone authority v1 predecessor drift detected: " f"path={rel} expected={expected_sha256} actual={actual_sha256}" ) actual[rel] = actual_sha256 for rel in ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_NEW_PATHS: path = root / rel if path.exists() or path.is_symlink(): die(f"Engine Depttrans zone authority v1 new path already exists: {rel}") backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die("Engine Depttrans zone authority v1 requires the active immutable backend") return { "mode": "exact-platform-service-authority-v1", "predecessor_sha256": actual, "target_sha256": dict(ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_TARGET_SHA256), "backend_mode": backend["mode"], } def print_engine_depttrans_zone_authority_v1_plan(preflight): print("engine_provider_authority_transition=platform-service-v1") print("engine_provider_package=moscow-department-of-transport.pmd-slow-zones.v1") print("engine_provider_id=moscow-department-of-transport") print("engine_provider_authority_boundary=platform-service") print("engine_provider_platform_service=nodedc-map-gateway") print("engine_provider_platform_network=engine") print("engine_data_product=map.zones.current.v2") print("provider_credential_values=preserved") predecessor_sha256 = preflight["predecessor_sha256"] target_sha256 = preflight["target_sha256"] for changed_path in ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_ARTIFACT_ENTRIES: print(f"engine_depttrans_zone_authority_changed_path={changed_path}") if changed_path in predecessor_sha256: print( f"engine_depttrans_zone_authority_predecessor_sha256[{changed_path}]=" f"{predecessor_sha256[changed_path]}" ) elif changed_path in ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_NEW_PATHS: print( f"engine_depttrans_zone_authority_predecessor_state[{changed_path}]=" "absent" ) else: die( "Engine Depttrans zone authority v1 plan has no predecessor state: " f"{changed_path}" ) if changed_path not in target_sha256: die( "Engine Depttrans zone authority v1 plan has no target sha256: " f"{changed_path}" ) print( f"engine_depttrans_zone_authority_target_sha256[{changed_path}]=" f"{target_sha256[changed_path]}" ) print(f"backend_current_barrier={preflight['backend_mode']}") print("backend_force_recreate=yes") print("backend_pull=never") print("l2_graph=untouched") print("n8n_l1=untouched") print("engine_ui=untouched") print("engine_databases=untouched") print("mcp_nginx=untouched") def preflight_engine_provider_target_host_policy_predecessor(): root = component_root("engine") actual = {} for rel, expected_sha256 in ENGINE_PROVIDER_TARGET_HOST_POLICY_PREDECESSOR_SHA256.items(): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine provider target host policy predecessor is missing: {rel}") if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die(f"Engine provider target host policy predecessor is unsafe: {rel}") actual_sha256 = sha256_file(path) if actual_sha256 != expected_sha256: die( "Engine provider target host policy predecessor drift detected: " f"path={rel} expected={expected_sha256} actual={actual_sha256}" ) actual[rel] = actual_sha256 backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die("Engine provider target host policy requires the active immutable backend") return { "mode": "exact-provider-literal-target-host", "predecessor_sha256": actual, "target_sha256": dict(ENGINE_PROVIDER_TARGET_HOST_POLICY_TARGET_SHA256), "backend_mode": backend["mode"], } def preflight_engine_mcp_execution_profile_decoder_predecessor(): root = component_root("engine") actual = {} for rel, expected_sha256 in ( ENGINE_MCP_EXECUTION_PROFILE_DECODER_PREDECESSOR_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine MCP execution profile decoder predecessor is missing: {rel}") if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die(f"Engine MCP execution profile decoder predecessor is unsafe: {rel}") actual_sha256 = sha256_file(path) if actual_sha256 != expected_sha256: die( "Engine MCP execution profile decoder predecessor drift detected: " f"path={rel} expected={expected_sha256} actual={actual_sha256}" ) actual[rel] = actual_sha256 for rel in ENGINE_MCP_EXECUTION_PROFILE_DECODER_NEW_PATHS: path = root / rel if path.exists() or path.is_symlink(): die(f"Engine MCP execution profile decoder new path already exists: {rel}") backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die("Engine MCP execution profile decoder requires the active immutable backend") return { "mode": "exact-flatted-numeric-string-preservation", "predecessor_sha256": actual, "target_sha256": dict(ENGINE_MCP_EXECUTION_PROFILE_DECODER_TARGET_SHA256), "new_paths": tuple(ENGINE_MCP_EXECUTION_PROFILE_DECODER_NEW_PATHS), "backend_mode": backend["mode"], } def preflight_engine_mcp_telemetry_catalog_predecessor(): root = component_root("engine") actual = {} for rel, expected_sha256 in ENGINE_MCP_TELEMETRY_CATALOG_PREDECESSOR_SHA256.items(): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine MCP telemetry catalog predecessor is missing: {rel}") if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die(f"Engine MCP telemetry catalog predecessor is unsafe: {rel}") actual_sha256 = sha256_file(path) if actual_sha256 != expected_sha256: die( "Engine MCP telemetry catalog predecessor drift detected: " f"path={rel} expected={expected_sha256} actual={actual_sha256}" ) actual[rel] = actual_sha256 foundation = {} for rel, expected_sha256 in ENGINE_MCP_TELEMETRY_CATALOG_FOUNDATION_SHA256.items(): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine MCP telemetry catalog foundation is missing: {rel}") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die(f"Engine MCP telemetry catalog foundation drift detected: {rel}") foundation[rel] = expected_sha256 for rel in ENGINE_MCP_TELEMETRY_CATALOG_NEW_PATHS: path = root / rel if path.exists() or path.is_symlink(): die(f"Engine MCP telemetry catalog new path already exists: {rel}") backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die("Engine MCP telemetry catalog requires the active immutable backend") return { "mode": "execution-profile-decoder-v1-to-telemetry-catalog-v1", "predecessor_sha256": actual, "foundation_sha256": foundation, "target_sha256": dict(ENGINE_MCP_TELEMETRY_CATALOG_TARGET_SHA256), "new_paths": tuple(ENGINE_MCP_TELEMETRY_CATALOG_NEW_PATHS), "backend_mode": backend["mode"], } def preflight_engine_mcp_execution_plan_materialization_predecessor(): root = component_root("engine") actual = {} for rel, expected_sha256 in ( ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_PREDECESSOR_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP execution plan materialization predecessor is " f"missing: {rel}" ) if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die( "Engine MCP execution plan materialization predecessor is " f"unsafe: {rel}" ) actual_sha256 = sha256_file(path) if actual_sha256 != expected_sha256: die( "Engine MCP execution plan materialization predecessor drift " f"detected: path={rel} expected={expected_sha256} " f"actual={actual_sha256}" ) actual[rel] = actual_sha256 foundation = {} for rel, expected_sha256 in ( ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_FOUNDATION_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP execution plan materialization foundation is " f"missing: {rel}" ) if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Engine MCP execution plan materialization foundation drift " f"detected: {rel}" ) foundation[rel] = expected_sha256 for rel in ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_NEW_PATHS: path = root / rel if path.exists() or path.is_symlink(): die( "Engine MCP execution plan materialization new path already " f"exists: {rel}" ) backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die( "Engine MCP execution plan materialization requires the active " "immutable backend" ) return { "mode": "telemetry-catalog-v1-to-execution-plan-materialization-v1", "predecessor_sha256": actual, "foundation_sha256": foundation, "target_sha256": dict( ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_TARGET_SHA256 ), "new_paths": tuple(ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_NEW_PATHS), "backend_mode": backend["mode"], } def preflight_engine_mcp_execution_plan_telemetry_runtime_predecessor(): root = component_root("engine") actual = {} for rel, expected_sha256 in ( ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_PREDECESSOR_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP execution plan telemetry runtime predecessor is " f"missing: {rel}" ) if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die( "Engine MCP execution plan telemetry runtime predecessor is " f"unsafe: {rel}" ) actual_sha256 = sha256_file(path) if actual_sha256 != expected_sha256: die( "Engine MCP execution plan telemetry runtime predecessor drift " f"detected: path={rel} expected={expected_sha256} " f"actual={actual_sha256}" ) actual[rel] = actual_sha256 foundation = {} for rel, expected_sha256 in ( ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_FOUNDATION_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP execution plan telemetry runtime foundation is " f"missing: {rel}" ) if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Engine MCP execution plan telemetry runtime foundation drift " f"detected: {rel}" ) foundation[rel] = expected_sha256 for rel in ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_NEW_PATHS: path = root / rel if path.exists() or path.is_symlink(): die( "Engine MCP execution plan telemetry runtime new path already " f"exists: {rel}" ) backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die( "Engine MCP execution plan telemetry runtime requires the active " "immutable backend" ) return { "mode": "execution-plan-materialization-v1-to-telemetry-runtime-v2", "predecessor_sha256": actual, "foundation_sha256": foundation, "target_sha256": dict( ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_TARGET_SHA256 ), "new_paths": tuple( ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_NEW_PATHS ), "backend_mode": backend["mode"], } def preflight_engine_mcp_execution_plan_module_ownership_predecessor(): root = component_root("engine") actual = {} for rel, expected_sha256 in ( ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_PREDECESSOR_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP execution plan module ownership predecessor is " f"missing: {rel}" ) if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die( "Engine MCP execution plan module ownership predecessor is " f"unsafe: {rel}" ) actual_sha256 = sha256_file(path) if actual_sha256 != expected_sha256: die( "Engine MCP execution plan module ownership predecessor drift " f"detected: path={rel} expected={expected_sha256} " f"actual={actual_sha256}" ) actual[rel] = actual_sha256 foundation = {} for rel, expected_sha256 in ( ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_FOUNDATION_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP execution plan module ownership foundation is " f"missing: {rel}" ) if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Engine MCP execution plan module ownership foundation drift " f"detected: {rel}" ) foundation[rel] = expected_sha256 for rel in ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_NEW_PATHS: path = root / rel if path.exists() or path.is_symlink(): die( "Engine MCP execution plan module ownership new path already " f"exists: {rel}" ) backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die( "Engine MCP execution plan module ownership requires the active " "immutable backend" ) return { "mode": "execution-plan-telemetry-runtime-v2-to-module-ownership-v3", "predecessor_sha256": actual, "foundation_sha256": foundation, "target_sha256": dict( ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_TARGET_SHA256 ), "new_paths": tuple( ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_NEW_PATHS ), "backend_mode": backend["mode"], } def preflight_engine_mcp_normalized_identity_search_predecessor(): root = component_root("engine") actual = {} for rel, expected_sha256 in ( ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_PREDECESSOR_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP normalized identity search predecessor is " f"missing: {rel}" ) if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die( "Engine MCP normalized identity search predecessor is " f"unsafe: {rel}" ) actual_sha256 = sha256_file(path) if actual_sha256 != expected_sha256: die( "Engine MCP normalized identity search predecessor drift " f"detected: path={rel} expected={expected_sha256} " f"actual={actual_sha256}" ) actual[rel] = actual_sha256 foundation = {} for rel, expected_sha256 in ( ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_FOUNDATION_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP normalized identity search foundation is " f"missing: {rel}" ) if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Engine MCP normalized identity search foundation drift " f"detected: {rel}" ) foundation[rel] = expected_sha256 for rel in ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_NEW_PATHS: path = root / rel if path.exists() or path.is_symlink(): die( "Engine MCP normalized identity search new path already " f"exists: {rel}" ) backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die( "Engine MCP normalized identity search requires the active " "immutable backend" ) return { "mode": "classified-aspects-v1-to-normalized-identity-search-v1", "predecessor_sha256": actual, "foundation_sha256": foundation, "target_sha256": dict( ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_TARGET_SHA256 ), "new_paths": tuple(ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_NEW_PATHS), "backend_mode": backend["mode"], } def preflight_engine_mcp_l1_credential_reuse_predecessor(): root = component_root("engine") actual = {} for rel, expected_sha256 in ( ENGINE_MCP_L1_CREDENTIAL_REUSE_PREDECESSOR_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine MCP L1 credential reuse predecessor is missing: {rel}") if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die(f"Engine MCP L1 credential reuse predecessor is unsafe: {rel}") actual_sha256 = sha256_file(path) if actual_sha256 != expected_sha256: die( "Engine MCP L1 credential reuse predecessor drift detected: " f"path={rel} expected={expected_sha256} actual={actual_sha256}" ) actual[rel] = actual_sha256 foundation = {} for rel, expected_sha256 in ( ENGINE_MCP_L1_CREDENTIAL_REUSE_FOUNDATION_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine MCP L1 credential reuse foundation is missing: {rel}") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die(f"Engine MCP L1 credential reuse foundation drift detected: {rel}") foundation[rel] = expected_sha256 for rel in ENGINE_MCP_L1_CREDENTIAL_REUSE_NEW_PATHS: path = root / rel if path.exists() or path.is_symlink(): die( "Engine MCP L1 credential reuse new path already exists: " f"{rel}" ) backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die( "Engine MCP L1 credential reuse requires the active immutable " "backend" ) return { "mode": "normalized-identity-search-v1-to-l1-credential-reuse-v1", "predecessor_sha256": actual, "foundation_sha256": foundation, "target_sha256": dict(ENGINE_MCP_L1_CREDENTIAL_REUSE_TARGET_SHA256), "new_paths": tuple(ENGINE_MCP_L1_CREDENTIAL_REUSE_NEW_PATHS), "backend_mode": backend["mode"], } def preflight_engine_mcp_l1_credential_provenance_predecessor(): root = component_root("engine") actual = {} for rel, expected_sha256 in ( ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_PREDECESSOR_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP L1 credential provenance predecessor is missing: " f"{rel}" ) if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die( "Engine MCP L1 credential provenance predecessor is unsafe: " f"{rel}" ) actual_sha256 = sha256_file(path) if actual_sha256 != expected_sha256: die( "Engine MCP L1 credential provenance predecessor drift " f"detected: path={rel} expected={expected_sha256} " f"actual={actual_sha256}" ) actual[rel] = actual_sha256 foundation = {} for rel, expected_sha256 in ( ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_FOUNDATION_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP L1 credential provenance foundation is missing: " f"{rel}" ) if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Engine MCP L1 credential provenance foundation drift " f"detected: {rel}" ) foundation[rel] = expected_sha256 for rel in ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_NEW_PATHS: path = root / rel if path.exists() or path.is_symlink(): die( "Engine MCP L1 credential provenance new path already exists: " f"{rel}" ) backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die( "Engine MCP L1 credential provenance requires the active " "immutable backend" ) return { "mode": "l1-credential-reuse-v1-to-provenance-v2", "predecessor_sha256": actual, "foundation_sha256": foundation, "target_sha256": dict( ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_TARGET_SHA256 ), "new_paths": tuple(ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_NEW_PATHS), "backend_mode": backend["mode"], } def preflight_engine_mcp_execution_plan_sandbox_runtime_predecessor(): root = component_root("engine") actual = {} for rel, expected_sha256 in ( ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_PREDECESSOR_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP execution plan sandbox runtime predecessor is " f"missing: {rel}" ) if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die( "Engine MCP execution plan sandbox runtime predecessor is " f"unsafe: {rel}" ) actual_sha256 = sha256_file(path) if actual_sha256 != expected_sha256: die( "Engine MCP execution plan sandbox runtime predecessor drift " f"detected: path={rel} expected={expected_sha256} " f"actual={actual_sha256}" ) actual[rel] = actual_sha256 foundation = {} for rel, expected_sha256 in ( ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_FOUNDATION_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP execution plan sandbox runtime foundation is " f"missing: {rel}" ) if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Engine MCP execution plan sandbox runtime foundation drift " f"detected: {rel}" ) foundation[rel] = expected_sha256 for rel in ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_NEW_PATHS: path = root / rel if path.exists() or path.is_symlink(): die( "Engine MCP execution plan sandbox runtime new path already " f"exists: {rel}" ) backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die( "Engine MCP execution plan sandbox runtime requires the active " "immutable backend" ) return { "mode": "l1-credential-provenance-v2-to-sandbox-runtime-v4", "predecessor_sha256": actual, "foundation_sha256": foundation, "target_sha256": dict( ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_TARGET_SHA256 ), "new_paths": tuple( ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_NEW_PATHS ), "backend_mode": backend["mode"], } def preflight_engine_mcp_gelios_items_envelope_predecessor(): root = component_root("engine") actual = {} for rel, expected_sha256 in ( ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_PREDECESSOR_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine MCP Gelios items envelope predecessor is missing: {rel}") if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die(f"Engine MCP Gelios items envelope predecessor is unsafe: {rel}") actual_sha256 = sha256_file(path) if actual_sha256 != expected_sha256: die( "Engine MCP Gelios items envelope predecessor drift detected: " f"path={rel} expected={expected_sha256} actual={actual_sha256}" ) actual[rel] = actual_sha256 foundation = {} for rel, expected_sha256 in ( ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_FOUNDATION_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die(f"Engine MCP Gelios items envelope foundation is missing: {rel}") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die(f"Engine MCP Gelios items envelope foundation drift: {rel}") foundation[rel] = expected_sha256 for rel in ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_NEW_PATHS: path = root / rel if path.exists() or path.is_symlink(): die( "Engine MCP Gelios items envelope new path already exists: " f"{rel}" ) backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die( "Engine MCP Gelios items envelope requires the active immutable " "backend" ) return { "mode": "gelios-provider-v11-to-v12-items-envelope", "predecessor_sha256": actual, "foundation_sha256": foundation, "target_sha256": dict( ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_TARGET_SHA256 ), "new_paths": tuple(ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_NEW_PATHS), "backend_mode": backend["mode"], } def preflight_engine_mcp_registered_execution_profiles_predecessor(): root = component_root("engine") actual = {} for rel, expected_sha256 in ( ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_PREDECESSOR_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP registered execution profiles predecessor is " f"missing: {rel}" ) if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die( "Engine MCP registered execution profiles predecessor is " f"unsafe: {rel}" ) actual_sha256 = sha256_file(path) if actual_sha256 != expected_sha256: die( "Engine MCP registered execution profiles predecessor drift " f"detected: path={rel} expected={expected_sha256} " f"actual={actual_sha256}" ) actual[rel] = actual_sha256 foundation = {} for rel, expected_sha256 in ( ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_FOUNDATION_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP registered execution profiles foundation is " f"missing: {rel}" ) if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die( "Engine MCP registered execution profiles foundation drift: " f"{rel}" ) foundation[rel] = expected_sha256 for rel in ( *ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_NEW_PATHS, *ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_FAILED_PATHS, ): path = root / rel if path.exists() or path.is_symlink(): die( "Engine MCP registered execution profiles new path already " f"exists: {rel}" ) backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die( "Engine MCP registered execution profiles requires the active " "immutable backend" ) return { "mode": "gelios-items-envelope-v12-to-registered-profiles-v2-attested", "predecessor_sha256": actual, "foundation_sha256": foundation, "target_sha256": dict( ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_TARGET_SHA256 ), "new_paths": tuple( ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_NEW_PATHS ), "backend_mode": backend["mode"], } def preflight_engine_mcp_gelios_units_items_predecessor(): root = component_root("engine") actual = {} for rel, expected_sha256 in ( ENGINE_MCP_GELIOS_UNITS_ITEMS_PREDECESSOR_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP Gelios units items predecessor is missing: " f"{rel}" ) if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode): die(f"Engine MCP Gelios units items predecessor is unsafe: {rel}") actual_sha256 = sha256_file(path) if actual_sha256 != expected_sha256: die( "Engine MCP Gelios units items predecessor drift detected: " f"path={rel} expected={expected_sha256} " f"actual={actual_sha256}" ) actual[rel] = actual_sha256 foundation = {} for rel, expected_sha256 in ( ENGINE_MCP_GELIOS_UNITS_ITEMS_FOUNDATION_SHA256.items() ): path = root / rel try: path_stat = path.lstat() except FileNotFoundError: die( "Engine MCP Gelios units items foundation is missing: " f"{rel}" ) if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or sha256_file(path) != expected_sha256 ): die(f"Engine MCP Gelios units items foundation drift: {rel}") foundation[rel] = expected_sha256 for rel in ENGINE_MCP_GELIOS_UNITS_ITEMS_NEW_PATHS: path = root / rel if path.exists() or path.is_symlink(): die( "Engine MCP Gelios units items new path already exists: " f"{rel}" ) backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die( "Engine MCP Gelios units items requires the active immutable " "backend" ) return { "mode": "registered-profiles-v2-to-gelios-v12.0.1-units-items-envelope", "predecessor_sha256": actual, "foundation_sha256": foundation, "target_sha256": dict( ENGINE_MCP_GELIOS_UNITS_ITEMS_TARGET_SHA256 ), "new_paths": tuple(ENGINE_MCP_GELIOS_UNITS_ITEMS_NEW_PATHS), "backend_mode": backend["mode"], } def preflight_engine_agent_full_grant_migration_predecessor(): root = component_root("engine") store_path = root / ENGINE_AGENT_FULL_GRANT_MIGRATION_STORE_REL try: store_stat = store_path.lstat() except FileNotFoundError: die("Engine agent full grant migration predecessor is missing") if stat.S_ISLNK(store_stat.st_mode) or not stat.S_ISREG(store_stat.st_mode): die("Engine agent full grant migration predecessor is unsafe") actual_sha256 = sha256_file(store_path) if actual_sha256 != ENGINE_AGENT_FULL_GRANT_MIGRATION_PREDECESSOR_SHA256: die( "Engine agent full grant migration predecessor drift detected: " f"expected={ENGINE_AGENT_FULL_GRANT_MIGRATION_PREDECESSOR_SHA256} " f"actual={actual_sha256}" ) publish_override = root / ENGINE_DATA_PRODUCT_PUBLISH_GRANT_OVERRIDE_REL try: publish_stat = publish_override.lstat() publish_text = publish_override.read_text(encoding="utf-8") except (FileNotFoundError, OSError, UnicodeDecodeError): die("Engine agent full grant migration requires the installed Publish overlay") if ( stat.S_ISLNK(publish_stat.st_mode) or not stat.S_ISREG(publish_stat.st_mode) or publish_text != expected_engine_data_product_publish_grant_override() ): die("Engine agent full grant migration Publish overlay mismatch") return actual_sha256 def is_platform_provider_catalog_only(entries): prefix = "platform/packages/external-provider-contract/providers/" return bool(entries) and all(rel.startswith(prefix) for rel in entries) def component_services(component, entries=None): if is_device_plane_control_core_release_slice(component, entries): return ("device-control-core",) if is_device_plane_control_core_v3_reconciliation_slice( component, entries, ): return ("device-control-core",) if is_device_plane_control_core_incident_audit_slice(component, entries): return () if is_device_plane_control_core_migration_replay_audit_slice( component, entries, ): return () if is_device_plane_control_core_migration_replay_recovery_slice( component, entries, ): return ("device-control-core",) if is_device_plane_control_core_migration_replay_checkpoint_recovery_slice( component, entries, ): return ("device-control-core",) if is_device_plane_manager_only_release_slice(component, entries): return ("device-manager",) if is_device_plane_edge_core_channel_bootstrap_slice(component, entries): return ("device-control-core",) if is_device_plane_backhaul_vps_enrollment_slice(component, entries): return (DEVICE_PLANE_BACKHAUL_TARGET_SERVICE,) if is_device_plane_backhaul_target_slice(component, entries): return (DEVICE_PLANE_BACKHAUL_TARGET_SERVICE,) if is_device_plane_b2_discovery_rollback_recovery_slice( component, entries, ): # Reconciliation writes only the reviewed marker after proving the # failed archive, journal, backup, restored source and live runtime. return () if is_device_plane_manager_reconciliation_slice(component, entries): # Reconciliation publishes one marker after proving the failed # evidence and the already-restored baseline. Runtime is read-only. return () if is_device_plane_manager_v2_reconciliation_slice(component, entries): # The failed v2 apply already restored the prior source and later # converged to the healthy baseline. Publish evidence only. return () if is_device_plane_postgres_bootstrap_slice(component, entries): # This exact one-time transition is the only Device Plane artifact that # may select durable state. Its preflight requires both container and # named volume to be absent, so --force-recreate cannot touch an # installed database. return ("device-postgres",) if is_device_plane_foundation_recovery_slice(component, entries): # The failed apply already built and started the exact reviewed images. # Recovery publishes their matching source and accepts the current # runtime without build/recreate/restart. return () if is_device_plane_foundation_network_publication_slice( component, entries, ): # Recreate only the two stateless services from their exact existing # image IDs. PostgreSQL is a preserved prerequisite, never selected. return ("device-control-core", "device-gateway") if is_engine_l2_closed_loop_slice(component, entries): # The failed 030 apply published both the backend source and the built # UI before Compose rejected the descriptor/source mismatch. The exact # reconciliation transition must therefore activate both generations. return ("nodedc-backend", "app") if ( is_engine_mcp_control_plane_slice(component, entries) or is_engine_mcp_ontology_sdk_slice(component, entries) or is_engine_mcp_autonomy_provider_v5_slice(component, entries) or is_engine_composite_provider_v4_slice(component, entries) or is_engine_provider_rotating_slot_slice(component, entries) or is_engine_provider_authority_diagnostics_slice(component, entries) or is_engine_depttrans_zone_authority_v1_slice(component, entries) or is_engine_provider_target_host_policy_slice(component, entries) or is_engine_mcp_execution_profile_decoder_slice(component, entries) or is_engine_mcp_telemetry_catalog_slice(component, entries) or is_engine_mcp_execution_plan_materialization_slice(component, entries) or is_engine_mcp_execution_plan_telemetry_runtime_slice(component, entries) or is_engine_mcp_execution_plan_module_ownership_slice(component, entries) or is_engine_mcp_normalized_identity_search_slice(component, entries) or is_engine_mcp_l1_credential_reuse_slice(component, entries) or is_engine_mcp_l1_credential_provenance_slice(component, entries) or is_engine_mcp_execution_plan_sandbox_runtime_slice(component, entries) or is_engine_mcp_gelios_items_envelope_slice(component, entries) or is_engine_mcp_registered_execution_profiles_slice(component, entries) or is_engine_mcp_gelios_units_items_slice(component, entries) or is_engine_provider_security_catalog_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") return ("nodedc-backend",) if is_engine_n8n_transition(component, entries): # The actual Engine topology has one n8n process and no separately # deployed worker/webhook services. The release barrier therefore # force-recreates exactly this service and never touches its database. return ("n8n",) if touches_engine_credential_sink(component, entries): # The sink is served by the existing Engine backend. It has no frontend # or n8n process code, so preserve those generations during this slice. return ("nodedc-backend",) if is_engine_data_product_publish_grant_slice(component, entries): # Publish-grant source and its fixed runtime overlay are backend-owned. # n8n, the UI and every database retain their existing generations. return ("nodedc-backend",) if is_engine_agent_full_grant_migration_slice(component, entries): # This one-time compatibility migration changes only Engine Agent # authorization normalization inside the already-active backend. return ("nodedc-backend",) if is_device_plane_manager_control_plane_slice(component, entries): return ("device-control-core", "device-manager") if component == "device-plane" and entries is not None: selected = [] def add(*services): for service in services: if service not in selected: selected.append(service) touches_common = any( rel in ( ".dockerignore", "package.json", "package-lock.json", "docker-compose.device-plane.yml", "packages/device-protocol-contract", "packages/arusnavi-b2-adapter", ) or rel.startswith(( "packages/device-protocol-contract/", "packages/arusnavi-b2-adapter/", )) for rel in entries ) touches_core = any( rel == "services/device-control-core" or rel.startswith("services/device-control-core/") for rel in entries ) touches_gateway = any( rel == "services/device-gateway" or rel.startswith("services/device-gateway/") for rel in entries ) if touches_common or touches_core: add("device-control-core") if touches_common or touches_gateway: add("device-gateway") return tuple(selected) if component == "dc-cms" and entries is not None: selected = [] def add(*services): for service in services: if service not in selected: selected.append(service) touches_compose = any(rel == "infra/docker-compose.yml" for rel in entries) touches_proxy = any(rel == "infra/reverse-proxy" or rel.startswith("infra/reverse-proxy/") for rel in entries) touches_authentik = any(rel == "infra/authentik" or rel.startswith("infra/authentik/") for rel in entries) touches_app = any( rel in ("Dockerfile", "package.json", "package-lock.json", "README.md") or rel.startswith(("admin/", "projects/", "server/")) for rel in entries ) if touches_compose: return COMPONENTS[component]["services"] if touches_authentik: add("postgresql-authentik", "authentik-server", "authentik-worker", "authentik-bootstrap", "cms-app", "reverse-proxy") if touches_app: add("cms-app") if touches_proxy: add("reverse-proxy") if selected: return tuple(selected) if component == "tasker" and entries is not None: selected = ["api", "worker", "beat-worker", "web"] touches_compose = any( rel in ("plane-app/docker-compose.yaml", "plane-app/docker-compose.synology.override.yml") for rel in entries ) touches_proxy = any(rel == "plane-src/apps/proxy/Caddyfile.ce" for rel in entries) if touches_compose or touches_proxy: selected.append("proxy") return tuple(selected) if is_platform_device_core_hub_trust_slice(component, entries): return ("launcher",) if is_platform_device_manager_public_route_slice(component, entries): return ("reverse-proxy",) if component == "platform" and entries is not None: if is_platform_provider_catalog_only(entries): return () selected = [] def add(*services): for service in services: if service not in selected: selected.append(service) touches_compose = any(rel == "platform/docker-compose.platform-http.yml" for rel in entries) touches_caddy = any(rel == "platform/Caddyfile.http" for rel in entries) touches_notification = any(rel == "platform/notification-core" or rel.startswith("platform/notification-core/") for rel in entries) touches_ai_workspace = any(rel == "platform/ai-workspace-hub" or rel.startswith("platform/ai-workspace-hub/") for rel in entries) touches_ai_workspace_assistant = any(rel == "platform/ai-workspace-assistant" or rel.startswith("platform/ai-workspace-assistant/") for rel in entries) touches_ontology = any(rel == "platform/ontology-core" or rel.startswith("platform/ontology-core/") for rel in entries) touches_gelios = any(rel == "platform/gelios-gateway" or rel.startswith("platform/gelios-gateway/") for rel in entries) touches_map_gateway = any(rel == "platform/services/map-gateway" or rel.startswith("platform/services/map-gateway/") for rel in entries) touches_external_data_plane = touches_external_data_plane_files(entries) touches_authentik = any(rel == "authentik/custom-templates" or rel.startswith("authentik/custom-templates/") for rel in entries) map_gateway_only_compose = touches_compose and touches_map_gateway and not any((touches_notification, touches_ai_workspace, touches_ai_workspace_assistant, touches_ontology, touches_gelios, touches_external_data_plane, touches_authentik)) restart_all_for_compose = touches_compose and not map_gateway_only_compose if touches_notification or restart_all_for_compose: add("notification-postgres", "notification-core", "launcher", "reverse-proxy") if touches_ai_workspace or restart_all_for_compose: add("ai-workspace-hub", "reverse-proxy") if touches_ai_workspace_assistant or restart_all_for_compose: add("ai-workspace-postgres", "ai-workspace-assistant") if touches_ontology or restart_all_for_compose: add("ontology-core", "ai-workspace-hub") if touches_gelios or restart_all_for_compose: add("gelios-postgres", "gelios-gateway") if touches_map_gateway: add("map-gateway") if touches_external_data_plane: # The Timescale/PostgreSQL service is durable infrastructure. Both # contract and managed EDP changes recreate only the EDP # application process. add("external-data-plane") if touches_authentik: add("authentik-server", "authentik-worker", "reverse-proxy") if touches_caddy: add("reverse-proxy") if selected: return tuple(selected) return COMPONENTS[component]["services"] def component_compose_no_deps(component, entries=None): if component == "dc-cms" and entries is not None: touches_compose = any(rel == "infra/docker-compose.yml" for rel in entries) touches_proxy = any(rel == "infra/reverse-proxy" or rel.startswith("infra/reverse-proxy/") for rel in entries) touches_authentik = any(rel == "infra/authentik" or rel.startswith("infra/authentik/") for rel in entries) return not (touches_compose or touches_proxy or touches_authentik) return bool(COMPONENTS[component].get("compose_no_deps")) def engine_backend_immutable_runtime_is_current(): metadata = validate_engine_backend_activation_marker() compose_project = component_compose_root("engine").name result = subprocess.run( [ str(DOCKER), "container", "ls", "-a", "--filter", f"label=com.docker.compose.project={compose_project}", "--filter", "label=com.docker.compose.service=nodedc-backend", "--format", "{{.ID}}", ], check=False, capture_output=True, text=True, ) container_ids = [line.strip() for line in result.stdout.splitlines() if line.strip()] if (result.returncode != 0 or len(container_ids) != 1 or not re.fullmatch(r"[a-f0-9]{12,64}", container_ids[0])): die("Engine backend immutable runtime topology is unproven") containers = docker_json( ["container", "inspect", container_ids[0]], "Engine backend immutable runtime container inspect", ) container = containers[0] if isinstance(containers, list) and len(containers) == 1 else None image = inspect_engine_backend_derived_image(metadata) return ( isinstance(container, dict) and container.get("Image") == image.get("Id") and (container.get("Config") or {}).get("Image") == ENGINE_CREDENTIAL_BACKEND_IMAGE ) def component_compose_files( component, allow_prepared_engine_backend=False, expected_node_intelligence_gateway_sha256=None, ): if component == "engine": root = component_root(component) files = [root / "docker-compose.yml"] descriptor = current_engine_n8n_transition_descriptor() if descriptor and descriptor["action"] == "activate": override = root / ENGINE_N8N_COMPOSE_OVERRIDE_REL if override.is_symlink() or not override.is_file(): die("active Engine n8n Compose override is missing or unsafe") try: override_text = override.read_text(encoding="utf-8") except (OSError, UnicodeDecodeError): die("active Engine n8n Compose override cannot be read") if override_text != expected_engine_n8n_compose_override(descriptor): die("installed Engine n8n Compose override drift detected") files.append(override) if (ENGINE_CREDENTIAL_BACKEND_ACTIVATION_FILE.exists() or ENGINE_CREDENTIAL_BACKEND_ACTIVATION_FILE.is_symlink()): validate_engine_backend_activation_marker() if (allow_prepared_engine_backend or engine_backend_immutable_runtime_is_current()): files.append(ENGINE_CREDENTIAL_BACKEND_OVERRIDE_FILE) publish_grant_override = root / ENGINE_DATA_PRODUCT_PUBLISH_GRANT_OVERRIDE_REL if publish_grant_override.exists() or publish_grant_override.is_symlink(): if publish_grant_override.is_symlink() or not publish_grant_override.is_file(): die("installed Engine data product publish grant override is unsafe") try: publish_grant_override_text = publish_grant_override.read_text(encoding="utf-8") except (OSError, UnicodeDecodeError): die("installed Engine data product publish grant override cannot be read") if publish_grant_override_text != expected_engine_data_product_publish_grant_override(): die("installed Engine data product publish grant override drift detected") # 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 and node_intelligence_descriptor["action"] == "activate" ): if expected_node_intelligence_gateway_sha256 is None: validate_installed_engine_node_intelligence_source( node_intelligence_descriptor ) else: validate_installed_engine_node_intelligence_source( node_intelligence_descriptor, expected_gateway_sha256= expected_node_intelligence_gateway_sha256, ) files.append(root / ENGINE_NODE_INTELLIGENCE_OVERRIDE_REL) return tuple(files) files = COMPONENTS[component].get("compose_files", ()) if component == "device-plane": manager_overlay = DEVICE_PLANE_ROOT / DEVICE_PLANE_MANAGER_COMPOSE_REL if manager_overlay.exists() or manager_overlay.is_symlink(): expected_manager_compose_sha256 = ( installed_device_plane_manager_compose_sha256() ) if ( manager_overlay.is_symlink() or not manager_overlay.is_file() or sha256_file(manager_overlay) != expected_manager_compose_sha256 ): die("installed Device Manager Compose drift detected") files = (*files, manager_overlay) edge_descriptor = ( DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_REL ) edge_upgrade_descriptor = ( DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_REL ) edge_upgrade_v2_descriptor = ( DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_REL ) edge_upgrade_v4_descriptor = ( DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_REL ) edge_override = ( DEVICE_PLANE_ROOT / DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_REL ) edge_descriptor_exists = ( edge_descriptor.exists() or edge_descriptor.is_symlink() ) edge_upgrade_descriptor_exists = ( edge_upgrade_descriptor.exists() or edge_upgrade_descriptor.is_symlink() ) edge_upgrade_v2_descriptor_exists = ( edge_upgrade_v2_descriptor.exists() or edge_upgrade_v2_descriptor.is_symlink() ) edge_upgrade_v4_descriptor_exists = ( edge_upgrade_v4_descriptor.exists() or edge_upgrade_v4_descriptor.is_symlink() ) edge_override_exists = edge_override.exists() or edge_override.is_symlink() if ( edge_override_exists != ( edge_descriptor_exists or edge_upgrade_descriptor_exists or edge_upgrade_v2_descriptor_exists or edge_upgrade_v4_descriptor_exists ) or edge_upgrade_descriptor_exists and not edge_descriptor_exists or edge_upgrade_v2_descriptor_exists and not ( edge_descriptor_exists and edge_upgrade_descriptor_exists ) or edge_upgrade_v4_descriptor_exists and not ( edge_descriptor_exists and edge_upgrade_descriptor_exists and edge_upgrade_v2_descriptor_exists ) ): die("installed Device Edge Core channel source is incomplete") if edge_descriptor_exists: if ( edge_descriptor.is_symlink() or not edge_descriptor.is_file() or edge_override.is_symlink() or not edge_override.is_file() or sha256_file(edge_override) != DEVICE_PLANE_EDGE_CORE_CHANNEL_COMPOSE_SHA256 ): die("installed Device Edge Core channel source drift detected") descriptor = read_strict_json( edge_descriptor, "installed Device Edge Core channel descriptor", max_bytes=16 * 1024, ) transition_id = descriptor.get("transitionId") if ( not isinstance(transition_id, str) or not re.fullmatch(r"[A-Za-z0-9._-]{1,96}", transition_id) or descriptor != ( expected_device_plane_edge_core_channel_bootstrap_descriptor( transition_id ) ) ): die("installed Device Edge Core channel descriptor drift detected") upgrade_id = None if edge_upgrade_descriptor_exists: if ( transition_id != DEVICE_PLANE_EDGE_CORE_CHANNEL_BOOTSTRAP_PREDECESSOR_PATCH_ID or edge_upgrade_descriptor.is_symlink() or not edge_upgrade_descriptor.is_file() ): die("installed Device Edge Core channel upgrade predecessor drift detected") upgrade = read_strict_json( edge_upgrade_descriptor, "installed Device Edge Core channel upgrade descriptor", max_bytes=16 * 1024, ) upgrade_id = upgrade.get("transitionId") if ( not isinstance(upgrade_id, str) or not re.fullmatch(r"[A-Za-z0-9._-]{1,96}", upgrade_id) or upgrade != expected_device_plane_edge_core_channel_upgrade_descriptor( upgrade_id ) ): die("installed Device Edge Core channel upgrade descriptor drift detected") if edge_upgrade_v2_descriptor_exists: if ( upgrade_id != DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V2_PREDECESSOR_PATCH_ID or edge_upgrade_v2_descriptor.is_symlink() or not edge_upgrade_v2_descriptor.is_file() ): die("installed Device Edge Core channel upgrade v2 predecessor drift detected") upgrade_v2 = read_strict_json( edge_upgrade_v2_descriptor, "installed Device Edge Core channel upgrade v2 descriptor", max_bytes=16 * 1024, ) upgrade_v2_id = upgrade_v2.get("transitionId") if ( not isinstance(upgrade_v2_id, str) or not re.fullmatch( r"[A-Za-z0-9._-]{1,96}", upgrade_v2_id ) or upgrade_v2 != expected_device_plane_edge_core_channel_upgrade_v2_descriptor( upgrade_v2_id ) ): die("installed Device Edge Core channel upgrade v2 descriptor drift detected") if edge_upgrade_v4_descriptor_exists: if ( upgrade_v2_id != DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_PREDECESSOR_PATCH_ID or edge_upgrade_v4_descriptor.is_symlink() or not edge_upgrade_v4_descriptor.is_file() or sha256_file( DEVICE_PLANE_ROOT / "docker-compose.device-plane.yml" ) != DEVICE_PLANE_EDGE_CORE_CHANNEL_UPGRADE_V4_BASE_COMPOSE_SHA256 ): die("installed Device Edge Core channel upgrade v4 predecessor drift detected") upgrade_v4 = read_strict_json( edge_upgrade_v4_descriptor, "installed Device Edge Core channel upgrade v4 descriptor", max_bytes=16 * 1024, ) upgrade_v4_id = upgrade_v4.get("transitionId") if ( not isinstance(upgrade_v4_id, str) or not re.fullmatch( r"[A-Za-z0-9._-]{1,96}", upgrade_v4_id ) or upgrade_v4 != expected_device_plane_edge_core_channel_upgrade_v4_descriptor( upgrade_v4_id ) ): die("installed Device Edge Core channel upgrade v4 descriptor drift detected") files = (*files, edge_override) overlay = DEVICE_PLANE_ROOT / DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_REL if overlay.exists() or overlay.is_symlink(): if ( overlay.is_symlink() or not overlay.is_file() or sha256_file(overlay) != DEVICE_PLANE_BACKHAUL_TARGET_COMPOSE_SHA256 ): die("installed Device Plane backhaul Compose drift detected") files = (*files, overlay) if component == "platform": # The EDP overlay is optional until its first dedicated artifact lands. # Once installed (or after a candidate has been copied), it participates # in every authoritative Compose operation. files = tuple( path for path in files if path.name != Path(PLATFORM_EXTERNAL_DATA_PLANE_COMPOSE_REL).name or path.is_file() ) return files def component_compose_env_file(component): return COMPONENTS[component].get("compose_env_file") def component_build_root(component): return COMPONENTS[component].get("build_root") def component_build_args(component, entries=None): if component == "platform" and entries is not None: touches_notification = any(rel == "platform/notification-core" or rel.startswith("platform/notification-core/") for rel in entries) return COMPONENTS[component].get("build") if touches_notification else None if component == "bim-viewer" and entries is not None: touches_converter = any(rel == "converter" or rel.startswith("converter/") for rel in entries) return COMPONENTS[component].get("build") if touches_converter else None return COMPONENTS[component].get("build") def component_builds(component, entries=None): if is_platform_device_core_hub_trust_slice(component, entries): return () if is_platform_device_manager_public_route_slice(component, entries): return () if ( is_device_plane_control_core_release_slice(component, entries) or is_device_plane_edge_core_channel_bootstrap_slice( component, entries, ) ): return (( DEVICE_PLANE_ROOT, ( "build", "--no-cache", "--network=host", "-f", "services/device-control-core/Dockerfile", "-t", DEVICE_PLANE_CONTROL_CORE_IMAGE, ".", ), ),) if is_device_plane_control_core_v3_reconciliation_slice( component, entries, ): return () if is_device_plane_control_core_incident_audit_slice(component, entries): return () if is_device_plane_control_core_migration_replay_audit_slice( component, entries, ): return () if is_device_plane_control_core_migration_replay_recovery_slice( component, entries, ): return (( DEVICE_PLANE_ROOT, ( "build", "--no-cache", "--network=host", "-f", "services/device-control-core/Dockerfile", "-t", DEVICE_PLANE_CONTROL_CORE_IMAGE, ".", ), ),) if is_device_plane_control_core_migration_replay_checkpoint_recovery_slice( component, entries, ): return (( DEVICE_PLANE_ROOT, ( "build", "--no-cache", "--network=host", "-f", "services/device-control-core/Dockerfile", "-t", DEVICE_PLANE_CONTROL_CORE_IMAGE, ".", ), ),) if is_device_plane_manager_only_release_slice(component, entries): return (( DEVICE_PLANE_ROOT / "services/device-manager", ( "build", "--no-cache", "-t", DEVICE_PLANE_MANAGER_IMAGE, ".", ), ),) if is_device_plane_manager_control_plane_slice(component, entries): return ( ( DEVICE_PLANE_ROOT, ( "build", "--no-cache", "--network=host", "-f", "services/device-control-core/Dockerfile", "-t", DEVICE_PLANE_CONTROL_CORE_IMAGE, ".", ), ), ( DEVICE_PLANE_ROOT / "services/device-manager", ( "build", "--no-cache", "-t", DEVICE_PLANE_MANAGER_IMAGE, ".", ), ), ) if is_device_plane_manager_reconciliation_slice(component, entries): return () if is_device_plane_manager_v2_reconciliation_slice(component, entries): return () if is_device_plane_backhaul_vps_enrollment_slice(component, entries): return () if is_device_plane_backhaul_target_slice(component, entries): return (( DEVICE_PLANE_ROOT, ( "build", "--no-cache", "--network=host", "-f", "services/device-backhaul-target/Dockerfile", "-t", DEVICE_PLANE_BACKHAUL_TARGET_IMAGE, ".", ), ),) if is_device_plane_b2_discovery_rollback_recovery_slice( component, entries, ): return () if is_device_plane_postgres_bootstrap_slice(component, entries): return () if is_device_plane_foundation_recovery_slice(component, entries): return () if is_device_plane_foundation_network_publication_slice( component, entries, ): return () if component == "device-plane" and entries is not None: selected_services = component_services(component, entries) builds = [] if "device-control-core" in selected_services: builds.append(( DEVICE_PLANE_ROOT, ( "build", "--no-cache", "--network=host", "-f", "services/device-control-core/Dockerfile", "-t", DEVICE_PLANE_CONTROL_CORE_IMAGE, ".", ), )) if "device-gateway" in selected_services: builds.append(( DEVICE_PLANE_ROOT, ( "build", "--no-cache", "--network=host", "-f", "services/device-gateway/Dockerfile", "-t", DEVICE_PLANE_GATEWAY_IMAGE, ".", ), )) if "device-manager" in selected_services: builds.append(( DEVICE_PLANE_ROOT / "services/device-manager", ( "build", "--no-cache", "-t", DEVICE_PLANE_MANAGER_IMAGE, ".", ), )) return tuple(builds) if component == "platform" and entries is not None: touches_compose = any(rel == "platform/docker-compose.platform-http.yml" for rel in entries) touches_notification = any(rel == "platform/notification-core" or rel.startswith("platform/notification-core/") for rel in entries) touches_ai_workspace = any(rel == "platform/ai-workspace-hub" or rel.startswith("platform/ai-workspace-hub/") for rel in entries) touches_ai_workspace_assistant = any(rel == "platform/ai-workspace-assistant" or rel.startswith("platform/ai-workspace-assistant/") for rel in entries) touches_ontology = any(rel == "platform/ontology-core" or rel.startswith("platform/ontology-core/") for rel in entries) touches_gelios = any(rel == "platform/gelios-gateway" or rel.startswith("platform/gelios-gateway/") for rel in entries) touches_map_gateway = any(rel == "platform/services/map-gateway" or rel.startswith("platform/services/map-gateway/") for rel in entries) touches_external_data_plane = touches_external_data_plane_files(entries) map_gateway_only_compose = touches_compose and touches_map_gateway and not any((touches_notification, touches_ai_workspace, touches_ai_workspace_assistant, touches_ontology, touches_gelios, touches_external_data_plane)) rebuild_all_for_compose = touches_compose and not map_gateway_only_compose builds = [] if touches_notification or rebuild_all_for_compose: builds.append(( Path("/volume1/docker/nodedc-platform/platform/notification-core"), ("build", "--no-cache", "-t", "nodedc/notification-core:local", "."), )) if touches_ai_workspace or rebuild_all_for_compose: builds.append(( Path("/volume1/docker/nodedc-platform/platform/ai-workspace-hub"), ("build", "--no-cache", "-t", "nodedc/ai-workspace-hub:local", "."), )) if touches_ai_workspace_assistant or rebuild_all_for_compose: builds.append(( Path("/volume1/docker/nodedc-platform/platform/ai-workspace-assistant"), ("build", "--no-cache", "-t", "nodedc/ai-workspace-assistant:local", "."), )) if touches_ontology or rebuild_all_for_compose: builds.append(( Path("/volume1/docker/nodedc-platform/platform/ontology-core"), ("build", "--no-cache", "-t", "nodedc/ontology-core:local", "."), )) if touches_gelios or rebuild_all_for_compose: builds.append(( Path("/volume1/docker/nodedc-platform/platform/gelios-gateway"), ("build", "--no-cache", "-t", "nodedc/gelios-gateway:local", "."), )) if touches_map_gateway: builds.append(( Path("/volume1/docker/nodedc-platform/platform/services/map-gateway"), ("build", "--no-cache", "-t", "nodedc/map-gateway:local", "."), )) if touches_external_data_plane: builds.append(( Path("/volume1/docker/nodedc-platform/platform"), ("build", "--no-cache", "-f", "services/external-data-plane/Dockerfile", "-t", "nodedc/external-data-plane:local", "."), )) return tuple(builds) if component == "tasker": plane_src = Path("/volume1/docker/nodedc-platform/tasker/plane-src") return ( ( plane_src / "apps/api", ( "build", "--network=host", "-t", "nodedc/plane-backend:local", "-f", "Dockerfile.api", ".", ), ), ( plane_src, ( "build", "--network=host", "--build-arg", "VITE_NODEDC_LAUNCHER_URL=https://hub.nodedc.ru", "--build-arg", "VITE_NODEDC_OIDC_LOGIN_ENABLED=1", "-t", "nodedc/plane-frontend:ru", "-f", "apps/web/Dockerfile.web.nas-legacy", ".", ), ), ) build_root = component_build_root(component) build_args = component_build_args(component, entries) if build_root and build_args: return ((build_root, build_args),) return () def docker_json(args, label): result = subprocess.run( [str(DOCKER), *args], check=False, capture_output=True, text=True, ) if result.returncode != 0: die(f"{label} failed: exit={result.returncode}") try: return json.loads(result.stdout) except json.JSONDecodeError: die(f"{label} returned invalid JSON") def inspect_local_image(image_ref, label): images = docker_json(["image", "inspect", image_ref], f"{label} inspect") if not isinstance(images, list) or len(images) != 1 or not isinstance(images[0], dict): die(f"{label} inspect shape mismatch") image = images[0] image_id = image.get("Id") if (not isinstance(image_id, str) or not re.fullmatch(r"sha256:[a-f0-9]{64}", image_id) or image.get("Architecture") != "amd64" or image.get("Os") != "linux"): die(f"{label} identity/platform mismatch") return image_id def inspect_optional_local_image(image_ref, label): result = subprocess.run( [str(DOCKER), "image", "inspect", image_ref], check=False, capture_output=True, text=True, ) if result.returncode != 0: return None try: images = json.loads(result.stdout) except json.JSONDecodeError: die(f"{label} returned invalid JSON") if not isinstance(images, list) or len(images) != 1 or not isinstance(images[0], dict): die(f"{label} inspect shape mismatch") image = images[0] image_id = image.get("Id") if (not isinstance(image_id, str) or not re.fullmatch(r"sha256:[a-f0-9]{64}", image_id) or image.get("Architecture") != "amd64" or image.get("Os") != "linux"): die(f"{label} identity/platform mismatch") return image_id def gitea_compose_project_container_ids(): result = subprocess.run( [ str(DOCKER), "container", "ls", "--all", "--filter", f"label=com.docker.compose.project={GITEA_COMPOSE_PROJECT}", "--format", "{{.ID}}", ], check=False, capture_output=True, text=True, ) if result.returncode != 0: die("Gitea fresh-install container inventory failed") container_ids = [ line.strip() for line in result.stdout.splitlines() if line.strip() ] if any(not re.fullmatch(r"[a-f0-9]{12,64}", value) for value in container_ids): die("Gitea fresh-install container inventory is invalid") return tuple(container_ids) def inspect_gitea_docker_compose_version(): result = subprocess.run( [str(DOCKER), "compose", "version", "--short"], check=False, capture_output=True, text=True, ) raw = result.stdout.strip() match = re.fullmatch( r"v?(\d+)\.(\d+)\.(\d+)(?:[-+][A-Za-z0-9.-]+)?", raw, ) if result.returncode != 0 or match is None: die("Gitea fresh-install Docker Compose version is unavailable or invalid") version = tuple(int(part) for part in match.groups()) if version < GITEA_MINIMUM_COMPOSE_VERSION: die( "Gitea fresh-install requires Docker Compose >= " + ".".join(str(part) for part in GITEA_MINIMUM_COMPOSE_VERSION) ) return raw def inspect_gitea_docker_server_version(): result = subprocess.run( [str(DOCKER), "version", "--format", "{{.Server.Version}}"], check=False, capture_output=True, text=True, ) version = result.stdout.strip() if result.returncode != 0 or version != GITEA_REQUIRED_DOCKER_VERSION: die( "Gitea fresh-install requires exact Docker Server " f"{GITEA_REQUIRED_DOCKER_VERSION}" ) return version def validate_gitea_compose_schema(compose_path): result = subprocess.run( [ str(DOCKER), "compose", "--project-name", GITEA_COMPOSE_PROJECT, "--file", str(compose_path), "config", "--quiet", ], cwd=str(compose_path.parent), check=False, capture_output=True, text=True, ) if result.returncode != 0: die("Gitea fresh-install Compose schema/config validation failed") def inspect_gitea_local_image(): images = docker_json( ["image", "inspect", GITEA_IMAGE], "Gitea pinned image inspect", ) if not isinstance(images, list) or len(images) != 1 or not isinstance(images[0], dict): die("Gitea pinned image inspect shape mismatch") image = images[0] if ( image.get("Id") != GITEA_IMAGE_ID or tuple(image.get("RepoDigests") or ()) != (GITEA_REPO_DIGEST,) or image.get("Architecture") != "amd64" or image.get("Os") != "linux" or (image.get("Config") or {}).get("User") != "1000:1000" ): die("Gitea pinned image identity/repo-digest/platform/user mismatch") return GITEA_IMAGE_ID def gitea_loopback_listener_inodes(port): expected_local = f"0100007F:{port:04X}" try: lines = Path("/proc/net/tcp").read_text(encoding="ascii").splitlines() except (OSError, UnicodeDecodeError): die("Gitea Nginx listener inventory is unavailable") inodes = set() for line in lines[1:]: fields = line.split() if len(fields) < 10: die("Gitea Nginx listener inventory is invalid") if fields[1] == expected_local and fields[3] == "0A": if not fields[9].isdigit() or fields[9] == "0": die("Gitea Nginx listener inode is invalid") inodes.add(fields[9]) if not inodes: die(f"Gitea Nginx loopback listener is missing: {port}") return inodes def gitea_listener_process_owners(inodes): pending = set(inodes) owners = {} try: process_entries = tuple(Path("/proc").iterdir()) except OSError: die("Gitea Nginx process inventory is unavailable") for process in process_entries: if not process.name.isdigit(): continue try: process_stat = process.stat() comm = (process / "comm").read_text(encoding="ascii").strip() descriptors = tuple((process / "fd").iterdir()) except FileNotFoundError: continue except (OSError, UnicodeDecodeError): die("Gitea Nginx process ownership inventory failed") matched = set() for descriptor in descriptors: try: target = os.readlink(descriptor) except FileNotFoundError: continue except OSError: die("Gitea Nginx descriptor ownership inventory failed") match = re.fullmatch(r"socket:\[(\d+)\]", target) if match and match.group(1) in inodes: matched.add(match.group(1)) for inode in matched: owners.setdefault(inode, set()).add((comm, process_stat.st_uid)) pending.discard(inode) if pending: die("Gitea Nginx listener owner is unproven") return owners def validate_gitea_nginx_listener(): inodes = gitea_loopback_listener_inodes(GITEA_HTTP_PORT) owners = gitea_listener_process_owners(inodes) allowed = {("nginx", 0), ("nginx", GITEA_NGINX_WORKER_UID)} if any(not values or not values.issubset(allowed) for values in owners.values()): die("Gitea TCP/3000 listener is not exclusively owned by Nginx") return { "address": "127.0.0.1:3000", "owners": sorted({owner for values in owners.values() for owner in values}), } def validate_gitea_nginx_bridge_prerequisite(): try: bridge_stat = GITEA_NGINX_BRIDGE_CONFIG.lstat() bridge = GITEA_NGINX_BRIDGE_CONFIG.read_bytes() except OSError: die("Gitea Nginx UDS bridge config is missing or unreadable") if ( stat.S_ISLNK(bridge_stat.st_mode) or not stat.S_ISREG(bridge_stat.st_mode) or bridge_stat.st_uid != 0 or bridge_stat.st_gid != 0 or stat.S_IMODE(bridge_stat.st_mode) != 0o644 or bridge != GITEA_NGINX_BRIDGE_CONTENT.encode("utf-8") or hashlib.sha256(bridge).hexdigest() != GITEA_NGINX_BRIDGE_SHA256 ): die("Gitea Nginx UDS bridge identity/metadata mismatch") version = subprocess.run( [str(GITEA_NGINX), "-v"], check=False, capture_output=True, text=True, ) version_text = "\n".join( value.strip() for value in (version.stdout, version.stderr) if value.strip() ) if version.returncode != 0 or version_text != GITEA_NGINX_VERSION: die("Gitea Nginx version mismatch") config_test = subprocess.run( [str(GITEA_NGINX), "-t", "-c", str(GITEA_NGINX_MAIN_CONFIG)], check=False, capture_output=True, text=True, timeout=30, ) if config_test.returncode != 0: die("Gitea Nginx effective config test failed") listener = validate_gitea_nginx_listener() return { "config": str(GITEA_NGINX_BRIDGE_CONFIG), "sha256": GITEA_NGINX_BRIDGE_SHA256, "version": GITEA_NGINX_VERSION.removeprefix("nginx version: "), "listener": listener, "unix_upstream": str(GITEA_SOCKET_FILE), } def validate_gitea_no_docker_port_publications(): result = subprocess.run( [str(DOCKER), "container", "ls", "--format", "{{.ID}}"], check=False, capture_output=True, text=True, ) if result.returncode != 0: die("Gitea running Docker container inventory failed") container_ids = tuple( line.strip() for line in result.stdout.splitlines() if line.strip() ) if any(not re.fullmatch(r"[a-f0-9]{12,64}", value) for value in container_ids): die("Gitea running Docker container inventory is invalid") if not container_ids: return "no-running-docker-publications-3000-4022" containers = docker_json( ["inspect", *container_ids], "Gitea running Docker publication inspect", ) if not isinstance(containers, list) or len(containers) != len(container_ids): die("Gitea running Docker publication inventory shape mismatch") forbidden_ports = {str(GITEA_HTTP_PORT), str(GITEA_DISABLED_SSH_HOST_PORT)} for container in containers: if not isinstance(container, dict): die("Gitea running Docker publication inventory is invalid") bindings = (container.get("HostConfig") or {}).get("PortBindings") or {} if not isinstance(bindings, dict): die("Gitea running Docker publication bindings are invalid") for values in bindings.values(): if values is None: continue if not isinstance(values, list): die("Gitea running Docker publication bindings are invalid") for value in values: if not isinstance(value, dict): die("Gitea running Docker publication bindings are invalid") if str(value.get("HostPort") or "") in forbidden_ports: die("Docker must not publish Gitea TCP/3000 or TCP/4022") return "no-running-docker-publications-3000-4022" def validate_gitea_legacy_candidate_network_absent(): result = subprocess.run( [str(DOCKER), "network", "inspect", GITEA_FORBIDDEN_LEGACY_NETWORK], check=False, capture_output=True, text=True, ) if result.returncode == 0: die("Gitea forbidden legacy candidate network still exists") error = result.stderr.strip() if ( result.returncode != 1 or result.stdout.strip() or not re.search(r"(?:No such network|network .+ not found)", error) ): die("Gitea forbidden legacy candidate network absence is unproven") return "absent" def inspect_gitea_builtin_none_network(): networks = docker_json( ["network", "inspect", "none"], "Gitea built-in none network inspect", ) if ( not isinstance(networks, list) or len(networks) != 1 or not isinstance(networks[0], dict) ): die("Gitea built-in none network inspect shape mismatch") network = networks[0] network_id = network.get("Id") if ( network.get("Name") != "none" or not isinstance(network_id, str) or not re.fullmatch(r"[a-f0-9]{64}", network_id) or network.get("Scope") != "local" or network.get("Driver") != "null" or network.get("Internal") is not False or network.get("Attachable") is not False or network.get("Ingress") is not False or network.get("ConfigOnly") is not False ): die("Gitea built-in none network identity mismatch") return network def gitea_generated_reverse_proxy_server_block(generated): marker = "server_name git.dcserve.ru ;" if generated.count(marker) != 1: die("Gitea DSM generated reverse-proxy server identity mismatch") marker_index = generated.index(marker) server_index = generated.rfind("server {", 0, marker_index) if server_index < 0: die("Gitea DSM generated reverse-proxy server block is missing") brace_index = generated.index("{", server_index) depth = 0 for index in range(brace_index, len(generated)): character = generated[index] if character == "{": depth += 1 elif character == "}": depth -= 1 if depth == 0: return generated[server_index:index + 1] die("Gitea DSM generated reverse-proxy server block is invalid") def validate_gitea_reverse_proxy_prerequisite(): try: config_stat = GITEA_REVERSE_PROXY_CONFIG.lstat() except FileNotFoundError: die("Gitea DSM reverse-proxy persistent config is missing") if ( stat.S_ISLNK(config_stat.st_mode) or not stat.S_ISREG(config_stat.st_mode) or config_stat.st_uid != 0 or config_stat.st_gid != 0 or config_stat.st_size < 64 or config_stat.st_size > 8 * 1024 * 1024 ): die("Gitea DSM reverse-proxy persistent config is unsafe") document = read_strict_json( GITEA_REVERSE_PROXY_CONFIG, "Gitea DSM reverse-proxy persistent config", max_bytes=8 * 1024 * 1024, ) rule = document.get(GITEA_REVERSE_PROXY_UUID) if isinstance(document, dict) else None expected = { "backend": {"fqdn": "127.0.0.1", "port": 3000, "protocol": 0}, "customize_headers": [], "description": GITEA_REVERSE_PROXY_DESCRIPTION, "frontend": { "acl": None, "fqdn": "git.dcserve.ru", "https": {"hsts": False}, "port": 443, "protocol": 1, }, "proxy_connect_timeout": 60, "proxy_http_version": 1, "proxy_intercept_errors": False, "proxy_read_timeout": 60, "proxy_send_timeout": 60, } if rule != expected: die( "Gitea DSM reverse-proxy prerequisite mismatch; apply the " "separate reviewed proxy transition first" ) try: generated_stat = GITEA_REVERSE_PROXY_GENERATED_CONFIG.lstat() generated = GITEA_REVERSE_PROXY_GENERATED_CONFIG.read_text( encoding="utf-8" ) except (FileNotFoundError, UnicodeDecodeError): die("Gitea DSM generated reverse-proxy config is missing or invalid") server_block = gitea_generated_reverse_proxy_server_block(generated) generated_headers = {} for name, value in re.findall( r"(?m)^\s*proxy_set_header\s+([A-Za-z0-9-]+)\s+([^;]+);\s*$", server_block, ): generated_headers.setdefault(name.casefold(), []).append(value.strip()) expected_headers = { "host": ["$http_host"], "x-real-ip": ["$remote_addr"], "x-forwarded-for": ["$proxy_add_x_forwarded_for"], "x-forwarded-proto": ["$scheme"], } host_guard = re.search( r"if\s*\(\s*\$host\s+!~\s+\"\(\^git\.dcserve\.ru\$\)\"\s*\)" r"\s*\{\s*return\s+404\s*;\s*\}", server_block, ) if ( stat.S_ISLNK(generated_stat.st_mode) or not stat.S_ISREG(generated_stat.st_mode) or generated_stat.st_uid != 0 or generated_stat.st_size < 64 or generated_stat.st_size > 4 * 1024 * 1024 or server_block.count("proxy_pass http://127.0.0.1:3000;") != 1 or "proxy_pass http://172.22.0.222:3000;" in server_block or any( generated_headers.get(name) != values for name, values in expected_headers.items() ) or host_guard is None ): die("Gitea DSM generated reverse-proxy prerequisite mismatch") return { "persistent_rule": GITEA_REVERSE_PROXY_UUID, "upstream": "http://127.0.0.1:3000", "generated_config": str(GITEA_REVERSE_PROXY_GENERATED_CONFIG), } def gitea_iptables_rule_present(arguments): result = subprocess.run( [str(GITEA_IPTABLES), "-w", "5", "-C", *arguments], check=False, capture_output=True, text=True, ) if result.returncode not in (0, 1): die("Gitea firewall prerequisite inventory failed") return result.returncode == 0 def validate_gitea_firewall_prerequisite(): if not GITEA_IPTABLES.is_file(): die("Gitea firewall prerequisite command is missing") if gitea_iptables_rule_present( ("INPUT", "-p", "tcp", "--dport", "3000", "-j", "DROP") ): die( "Gitea loopback TCP/3000 is still blocked; apply the separate " "reviewed firewall transition first" ) required = ( ( "OUTPUT", "-o", "lo", "-d", f"{GITEA_LEGACY_REVERSE_PROXY_IP}/32", "-p", "tcp", "--dport", "3000", "-j", "DROP", ), ("INPUT", "-p", "tcp", "--dport", "4022", "-j", "DROP"), ) if not all(gitea_iptables_rule_present(arguments) for arguments in required): die("Gitea legacy firewall isolation prerequisite is incomplete") return { "loopback_3000": "not-blocked-by-emergency-input-drop", "legacy_172_22_0_222_3000": "output-drop-present", "legacy_4022": "input-drop-present", } def validate_legacy_gitea_container_isolation(): result = subprocess.run( [str(DOCKER), "container", "inspect", GITEA_LEGACY_CONTAINER], check=False, capture_output=True, text=True, ) if result.returncode != 0: return "absent" try: containers = json.loads(result.stdout) except json.JSONDecodeError: die("legacy Gitea container isolation inventory is invalid") if ( not isinstance(containers, list) or len(containers) != 1 or not isinstance(containers[0], dict) ): die("legacy Gitea container isolation inventory shape mismatch") container = containers[0] if ( (container.get("State") or {}).get("Running") is not False or (container.get("HostConfig") or {}).get("RestartPolicy", {}).get("Name") != "no" ): die("legacy Gitea container is not stopped with restart disabled") return "stopped-restart-disabled" def preflight_gitea_fresh_install(): try: GITEA_ROOT.lstat() except FileNotFoundError: pass else: die( "Gitea fresh-install root already exists; updates, imports, and " "partial-state reuse are forbidden" ) if gitea_compose_project_container_ids(): die("Gitea fresh-install Compose project already has containers") legacy_candidate_network = validate_gitea_legacy_candidate_network_absent() assert_loopback_tcp_port_closed(GITEA_DISABLED_SSH_HOST_PORT) nginx_bridge = validate_gitea_nginx_bridge_prerequisite() reverse_proxy = validate_gitea_reverse_proxy_prerequisite() firewall = validate_gitea_firewall_prerequisite() legacy_container = validate_legacy_gitea_container_isolation() docker_publications = validate_gitea_no_docker_port_publications() docker_version = inspect_gitea_docker_server_version() builtin_none_network = inspect_gitea_builtin_none_network() compose_version = inspect_gitea_docker_compose_version() image_id = inspect_gitea_local_image() return { "mode": "fresh-root-absent", "image_id": image_id, "transport": "unix:/run/gitea/gitea.sock", "network_mode": "none", "ssh_host_port": "closed:4022/tcp", "reverse_proxy_prerequisite": "127.0.0.1:3000", "nginx_bridge": nginx_bridge, "docker_version": docker_version, "builtin_none_network_id": builtin_none_network["Id"], "compose_version": compose_version, "reverse_proxy": reverse_proxy, "firewall": firewall, "docker_publications": docker_publications, "legacy_candidate_network": legacy_candidate_network, "legacy_container": legacy_container, } def inspect_gitea_salvage_local_image(): images = docker_json( ["image", "inspect", GITEA_SALVAGE_IMAGE], "Gitea salvage pinned image inspect", ) if not isinstance(images, list) or len(images) != 1 or not isinstance(images[0], dict): die("Gitea salvage pinned image inspect shape mismatch") image = images[0] config = image.get("Config") or {} if ( image.get("Id") != GITEA_SALVAGE_IMAGE_ID or tuple(image.get("RepoDigests") or ()) != (GITEA_SALVAGE_REPO_DIGEST,) or image.get("Architecture") != "amd64" or image.get("Os") != "linux" or config.get("User") != "1000:1000" ): die("Gitea salvage pinned image identity/repo-digest/platform/user mismatch") return GITEA_SALVAGE_IMAGE_ID def docker_named_container_inspect_fail_closed(name, label): result = subprocess.run( [str(DOCKER), "container", "inspect", name], check=False, capture_output=True, text=True, ) if result.returncode == 0: try: containers = json.loads(result.stdout) except json.JSONDecodeError: die(f"{label} returned invalid JSON") if ( not isinstance(containers, list) or len(containers) != 1 or not isinstance(containers[0], dict) ): die(f"{label} shape mismatch") return containers[0] stdout_raw = result.stdout.strip() if not stdout_raw: stdout_category = "empty" stdout_proves_empty = True elif stdout_raw == "[]": stdout_category = "json-empty-list" stdout_proves_empty = True else: try: stdout_json = json.loads(stdout_raw) except json.JSONDecodeError: stdout_category = "non-json" stdout_proves_empty = False else: stdout_category = f"json-{type(stdout_json).__name__}" stdout_proves_empty = False error = result.stderr.strip() exact_no_such = { f"No such object: {name}": "exact-no-such-object", f"No such container: {name}": "exact-no-such-container", f"Error: No such object: {name}": "exact-no-such-object", f"Error: No such container: {name}": "exact-no-such-container", f"Error response from daemon: No such object: {name}": ( "exact-no-such-object" ), f"Error response from daemon: No such container: {name}": ( "exact-no-such-container" ), } stderr_category = exact_no_such.get(error) if ( result.returncode == 1 and stdout_proves_empty and stderr_category is not None ): return None if stderr_category is None: lowered = error.lower() if not error: stderr_category = "empty" elif "permission denied" in lowered or "access is denied" in lowered: stderr_category = "permission-denied" elif "cannot connect" in lowered or "daemon" in lowered: stderr_category = "daemon-error" elif "timed out" in lowered or "deadline exceeded" in lowered: stderr_category = "timeout" else: stderr_category = "other" die( f"{label} absence is unproven: rc={result.returncode} " f"stdout={stdout_category} stderr={stderr_category}" ) def validate_gitea_salvage_legacy_container(): if ( GITEA_SALVAGE_EXPECTED_LEGACY_IMAGE is None or GITEA_SALVAGE_EXPECTED_LEGACY_IMAGE_ID is None ): die( "Gitea salvage legacy container image/mount identity is not " "reviewed and pinned" ) container = docker_named_container_inspect_fail_closed( GITEA_LEGACY_CONTAINER, "Gitea salvage legacy container inspect", ) if container is None: die("Gitea salvage requires the exact stopped legacy container evidence") config = container.get("Config") or {} host = container.get("HostConfig") or {} mounts = container.get("Mounts") or [] expected_mounts = { (str(GITEA_SALVAGE_LEGACY_ROOT), "/data", "bind", True), } actual_mounts = set() for mount in mounts: if not isinstance(mount, dict) or mount.get("RW") is not True: die("Gitea salvage legacy container mount inventory is invalid") actual_mounts.add( ( mount.get("Source"), mount.get("Destination"), mount.get("Type"), mount.get("RW"), ) ) if ( container.get("Name") != "/gitea" or len(mounts) != 1 or (container.get("State") or {}).get("Running") is not False or (host.get("RestartPolicy") or {}).get("Name") != "no" or config.get("Image") != GITEA_SALVAGE_EXPECTED_LEGACY_IMAGE or container.get("Image") != GITEA_SALVAGE_EXPECTED_LEGACY_IMAGE_ID or actual_mounts != expected_mounts ): die("Gitea salvage legacy container identity/isolation mismatch") return { "container_id": container.get("Id"), "image": config.get("Image"), "image_id": container.get("Image"), "mounts": sorted(actual_mounts), "name": container.get("Name"), "state": "stopped-restart-no", } def gitea_salvage_mountpoints(): try: lines = Path("/proc/self/mountinfo").read_text(encoding="utf-8").splitlines() except (OSError, UnicodeError): die("Gitea salvage cannot attest process mount boundaries") mountpoints = set() for line in lines: fields = line.split() if len(fields) < 10 or "-" not in fields: die("Gitea salvage process mount inventory is malformed") raw = fields[4] if re.search(r"\\(?!(?:040|011|012|134))", raw): die("Gitea salvage process mount inventory escape is malformed") try: decoded = re.sub( r"\\([0-7]{3})", lambda match: chr(int(match.group(1), 8)), raw, ) except (TypeError, ValueError): die("Gitea salvage process mount inventory escape is malformed") if not decoded.startswith("/"): die("Gitea salvage process mount inventory path is unsafe") mountpoints.add(os.path.normpath(decoded)) if not mountpoints: die("Gitea salvage process mount inventory is empty") return mountpoints def validate_gitea_salvage_path_chain( root, target, final_mode, label, allow_missing_final=False, trusted_device=None, mountpoints=None, ): try: relative = target.relative_to(root) except (TypeError, ValueError): die(f"Gitea salvage {label} escapes its trusted root") if not relative.parts or any(part in ("", ".", "..") for part in relative.parts): die(f"Gitea salvage {label} path is invalid") try: root_stat = root.lstat() except FileNotFoundError: die(f"Gitea salvage {label} trusted root is missing") if stat.S_ISLNK(root_stat.st_mode) or not stat.S_ISDIR(root_stat.st_mode): die(f"Gitea salvage {label} trusted root is unsafe") if trusted_device is None: trusted_device = root_stat.st_dev elif root_stat.st_dev != trusted_device: die(f"Gitea salvage {label} trusted root crosses a filesystem boundary") if mountpoints is None: mountpoints = gitea_salvage_mountpoints() current = root for index, part in enumerate(relative.parts): current = current / part final = index == len(relative.parts) - 1 try: current_stat = current.lstat() except FileNotFoundError: if final and allow_missing_final: return None die(f"Gitea salvage {label} is missing") if stat.S_ISLNK(current_stat.st_mode): die(f"Gitea salvage {label} path contains a symlink") if current_stat.st_dev != trusted_device: die(f"Gitea salvage {label} crosses a filesystem boundary") if os.path.normpath(str(current)) in mountpoints: die(f"Gitea salvage {label} crosses a mount boundary") # Every Btrfs subvolume root has inode 256. The trusted root itself is # the reviewed snapshot; seeing another inode-256 directory below it # means the parent ro property does not attest the nested subvolume. if stat.S_ISDIR(current_stat.st_mode) and current_stat.st_ino == 256: die(f"Gitea salvage {label} crosses a nested subvolume boundary") if final: if not final_mode(current_stat.st_mode): die(f"Gitea salvage {label} has an unsafe type") elif not stat.S_ISDIR(current_stat.st_mode): die(f"Gitea salvage {label} parent has an unsafe type") return target def validate_gitea_salvage_internal_entry( path, path_stat, trusted_device, mountpoints, expected_mode, label, ): if stat.S_ISLNK(path_stat.st_mode) or not expected_mode(path_stat.st_mode): die(f"Gitea salvage {label} has an unsafe type: {path}") if path_stat.st_dev != trusted_device: die(f"Gitea salvage {label} crosses a filesystem boundary: {path}") if os.path.normpath(str(path)) in mountpoints: die(f"Gitea salvage {label} crosses a mount boundary: {path}") if stat.S_ISDIR(path_stat.st_mode) and path_stat.st_ino == 256: die(f"Gitea salvage {label} crosses a nested subvolume boundary: {path}") def probe_gitea_salvage_path_no_follow( root, relative, trusted_device, mountpoints, label, final_mode=lambda _mode: True, ): parts = PurePosixPath(relative).parts if ( not parts or PurePosixPath(relative).is_absolute() or any(part in ("", ".", "..") for part in parts) ): die(f"Gitea salvage {label} probe path is invalid") current = root for index, part in enumerate(parts): current = current / part try: current_stat = current.lstat() except FileNotFoundError: return None except OSError as exc: error_number = exc.errno if isinstance(exc.errno, int) else "unknown" die(f"Gitea salvage {label} probe failed: errno={error_number}") validate_gitea_salvage_internal_entry( current, current_stat, trusted_device, mountpoints, stat.S_ISDIR if index < len(parts) - 1 else final_mode, label, ) return current_stat def validate_gitea_salvage_snapshot_boundary(): snapshot_parent = GITEA_SALVAGE_SNAPSHOT_ROOT.parent try: parent_stat = snapshot_parent.lstat() snapshot_stat = GITEA_SALVAGE_SNAPSHOT_ROOT.lstat() except FileNotFoundError: die("Gitea salvage snapshot root is missing") if ( stat.S_ISLNK(parent_stat.st_mode) or not stat.S_ISDIR(parent_stat.st_mode) or parent_stat.st_uid != 0 or parent_stat.st_gid != 0 or stat.S_IMODE(parent_stat.st_mode) != 0o700 or stat.S_ISLNK(snapshot_stat.st_mode) or not stat.S_ISDIR(snapshot_stat.st_mode) or snapshot_stat.st_uid != 0 or snapshot_stat.st_gid != 0 ): die("Gitea salvage snapshot root metadata mismatch") if not GITEA_SALVAGE_BTRFS.is_file(): die("Gitea salvage btrfs command is missing") show = subprocess.run( [str(GITEA_SALVAGE_BTRFS), "subvolume", "show", str(GITEA_SALVAGE_SNAPSHOT_ROOT)], check=False, capture_output=True, text=True, ) if show.returncode != 0: die("Gitea salvage snapshot subvolume inventory failed") uuids = re.findall( r"(?mi)^\s*UUID:\s*([0-9a-f-]{36})\s*$", show.stdout, ) if uuids != [GITEA_SALVAGE_SNAPSHOT_UUID]: die("Gitea salvage snapshot UUID mismatch") readonly = subprocess.run( [ str(GITEA_SALVAGE_BTRFS), "property", "get", str(GITEA_SALVAGE_SNAPSHOT_ROOT), "ro", ], check=False, capture_output=True, text=True, ) if readonly.returncode != 0 or readonly.stdout.strip() != "ro=true": die("Gitea salvage snapshot is not attestably read-only") for path, expected_mode, label in ( (GITEA_SALVAGE_SNAPSHOT_DATABASE, stat.S_ISREG, "database"), (GITEA_SALVAGE_SNAPSHOT_REPOSITORIES, stat.S_ISDIR, "repository root"), ): validate_gitea_salvage_path_chain( GITEA_SALVAGE_SNAPSHOT_ROOT, path, expected_mode, f"snapshot {label}", ) database_stat = GITEA_SALVAGE_SNAPSHOT_DATABASE.lstat() if ( database_stat.st_size != GITEA_SALVAGE_SNAPSHOT_DATABASE_BYTES or sha256_file(GITEA_SALVAGE_SNAPSHOT_DATABASE) != GITEA_SALVAGE_SNAPSHOT_DATABASE_SHA256 ): die("Gitea salvage snapshot database identity mismatch") return { "root": str(GITEA_SALVAGE_SNAPSHOT_ROOT), "uuid": GITEA_SALVAGE_SNAPSHOT_UUID, "readonly": True, "database_sha256": GITEA_SALVAGE_SNAPSHOT_DATABASE_SHA256, } def gitea_salvage_sqlite_connection(database): connection = sqlite3.connect( f"file:{database}?mode=ro&immutable=1", uri=True, ) connection.row_factory = sqlite3.Row connection.execute("PRAGMA query_only=ON") connection.execute("PRAGMA trusted_schema=OFF") query_only = connection.execute("PRAGMA query_only").fetchone() trusted_schema = connection.execute("PRAGMA trusted_schema").fetchone() if ( query_only is None or len(query_only) != 1 or query_only[0] != 1 or trusted_schema is None or len(trusted_schema) != 1 or trusted_schema[0] != 0 ): connection.close() die("Gitea salvage SQLite read-only safety PRAGMA mismatch") return connection def gitea_salvage_table_columns(connection, table): if not re.fullmatch(r"[a-z][a-z0-9_]{0,63}", table): die("Gitea salvage schema table name is unsafe") rows = connection.execute(f'PRAGMA table_info("{table}")').fetchall() return {str(row[1]) for row in rows} def gitea_salvage_sqlite_version(connection): try: value = connection.execute("SELECT sqlite_version()").fetchone()[0] except (sqlite3.DatabaseError, TypeError, IndexError): die("Gitea salvage SQLite version inventory failed") match = re.fullmatch(r"([0-9]+)\.([0-9]+)\.([0-9]+)", str(value)) if match is None: die("Gitea salvage SQLite version inventory is invalid") return str(value), tuple(int(part) for part in match.groups()) def gitea_salvage_table_schema( connection, table, table_list_supported, allowed_tables=None, ): registry = ( GITEA_SALVAGE_UNSUPPORTED_SCHEMA_TABLES if allowed_tables is None else allowed_tables ) if ( table not in registry or not re.fullmatch(r"[a-z][a-z0-9_]{0,63}", table) ): die("Gitea salvage schema table is outside the review registry") try: object_rows = connection.execute( "SELECT type,sql FROM sqlite_master WHERE name=? ORDER BY type", (table,), ).fetchall() table_list_rows = ( connection.execute(f'PRAGMA table_list("{table}")').fetchall() if table_list_supported else [] ) except sqlite3.DatabaseError: die("Gitea salvage schema-only inventory failed") object_type = None object_sql = None if len(object_rows) == 1 and len(object_rows[0]) == 2: object_type = object_rows[0][0] object_sql = object_rows[0][1] ordinary_sql = ( isinstance(object_sql, str) and re.match( r"\A\s*CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?\s+", object_sql, flags=re.IGNORECASE, ) is not None ) virtual_sql = ( isinstance(object_sql, str) and re.match( r"\A\s*CREATE\s+VIRTUAL\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?\s+", object_sql, flags=re.IGNORECASE, ) is not None ) safe_sqlite_schema_object = object_type == "table" and ordinary_sql safe_table_list_object = ( len(table_list_rows) == 1 and len(table_list_rows[0]) == 6 and tuple(table_list_rows[0][0:3]) == ("main", table, "table") and isinstance(table_list_rows[0][3], int) and table_list_rows[0][3] >= 0 and table_list_rows[0][4] in (0, 1) and table_list_rows[0][5] in (0, 1) ) safe_object = safe_sqlite_schema_object and ( safe_table_list_object if table_list_supported else True ) rows = [] if safe_object: try: rows = connection.execute(f'PRAGMA table_xinfo("{table}")').fetchall() except sqlite3.DatabaseError: die("Gitea salvage schema-only inventory failed") columns = [] seen = set() for row in rows: if len(row) != 7: die("Gitea salvage schema-only inventory shape mismatch") cid = row[0] name = str(row[1]) declared_type = str(row[2] or "") not_null = row[3] primary_key = row[5] hidden = row[6] if ( not isinstance(cid, int) or cid < 0 or not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]{0,127}", name) or name in seen or len(declared_type) > 64 or re.fullmatch(r"[A-Za-z0-9_(), ]*", declared_type) is None or not_null not in (0, 1) or not isinstance(primary_key, int) or primary_key < 0 or hidden not in (0, 1, 2, 3) ): die("Gitea salvage schema-only inventory is unsafe") seen.add(name) columns.append( { "cid": cid, "hidden": int(hidden), "name": name, "not_null": int(not_null), "primary_key": int(primary_key), "type": declared_type, } ) exists = bool(object_rows or table_list_rows) sqlite_schema_kind = None sqlite_schema_virtual = None if object_type is not None: sqlite_schema_kind = object_type sqlite_schema_virtual = 1 if virtual_sql else 0 table_list = None if len(table_list_rows) == 1 and len(table_list_rows[0]) == 6: table_list = { "columns": table_list_rows[0][3], "schema": table_list_rows[0][0], "strict": table_list_rows[0][5], "type": table_list_rows[0][2], "without_rowid": table_list_rows[0][4], } if ( table_list_supported and safe_object and table_list["columns"] != len(columns) ): die("Gitea salvage schema-only column count mismatch") return { "columns": columns, "exists": exists, "ordinary_main_table": safe_object, "object_kind_attestation": ( "sqlite-master-and-table-list" if table_list_supported else "sqlite-master-nonvirtual-table" ), "sqlite_schema_kind": sqlite_schema_kind, "sqlite_schema_virtual": sqlite_schema_virtual, "table": table, "table_list": table_list, } def gitea_salvage_unsupported_schema_catalog(connection): sqlite_version, sqlite_version_tuple = gitea_salvage_sqlite_version(connection) table_list_supported = sqlite_version_tuple >= (3, 37, 0) catalog = { "database_sha256": GITEA_SALVAGE_SNAPSHOT_DATABASE_SHA256, "schema": "nodedc.gitea.salvage-unsupported-schema/v1", "sqlite_version": sqlite_version, "table_list_supported": table_list_supported, "tables": [ gitea_salvage_table_schema( connection, table, table_list_supported, ) for table in GITEA_SALVAGE_UNSUPPORTED_SCHEMA_TABLES ], } canonical = canonical_gitea_salvage_evidence( catalog, "unsupported-state schema catalog", ) return {"catalog": catalog, **canonical} def gitea_salvage_schema_columns_by_name(schema): return {column["name"]: column for column in schema["columns"]} def gitea_salvage_declared_type_has_integer_affinity(declared_type): return "INT" in declared_type.upper() def gitea_salvage_declared_type_has_text_affinity(declared_type): normalized = declared_type.upper() return any(token in normalized for token in ("CHAR", "CLOB", "TEXT")) def gitea_salvage_parse_semantic_topics(value): if not isinstance(value, str): die("Gitea salvage repository topics value is not text") try: encoded = value.encode("utf-8") except UnicodeEncodeError: die("Gitea salvage repository topics value is not valid UTF-8") if len(encoded) > 16 * 1024: die("Gitea salvage repository topics value exceeds the byte limit") # Gitea persists a nil topics slice as the canonical JSON literal `null`. # The incident snapshot has exactly this four-byte text representation for # all 45 kept repositories. It is semantic empty state, not a topic value. # Accept only the exact canonical literal; whitespace, case variants, # quoted strings and SQL NULL remain rejected below/by the caller. if value == "null": return () try: parsed = json.loads(value) except (json.JSONDecodeError, RecursionError): die("Gitea salvage repository topics JSON is invalid") if not isinstance(parsed, list) or len(parsed) > 256: die("Gitea salvage repository topics JSON is not a bounded array") try: canonical = json.dumps( parsed, ensure_ascii=True, separators=(",", ":"), allow_nan=False, ) except (TypeError, ValueError): die("Gitea salvage repository topics JSON is not canonical") if value != canonical: die("Gitea salvage repository topics JSON is not canonical") topics = [] seen = set() for topic in parsed: if not isinstance(topic, str): die("Gitea salvage repository topic is not text") try: topic_bytes = topic.encode("utf-8") except UnicodeEncodeError: die("Gitea salvage repository topic is not valid UTF-8") if ( not topic or len(topic_bytes) > 35 or topic != topic.lower() or re.fullmatch(r"[a-z0-9][-.a-z0-9]*", topic) is None ): die("Gitea salvage repository topic is unsafe") if topic in seen: die("Gitea salvage repository topics contain a duplicate") seen.add(topic) topics.append(topic) if topics != sorted(topics): die("Gitea salvage repository topics are not sorted") return tuple(topics) def gitea_salvage_semantic_topics_inventory(connection, kept_repositories): normalized = [] seen = set() for record in kept_repositories: try: repo_id = int(record["repo_id"]) except (KeyError, TypeError, ValueError): die("Gitea salvage semantic-topics repository identity is invalid") if repo_id <= 0 or repo_id in seen: die("Gitea salvage semantic-topics repository identity is invalid") seen.add(repo_id) normalized.append(repo_id) normalized.sort() if not normalized: die("Gitea salvage semantic-topics repository set is empty") placeholders = ",".join("?" for _ in normalized) try: rows = connection.execute( f"SELECT id,topics,typeof(topics) FROM repository " f"WHERE id IN ({placeholders}) ORDER BY id", normalized, ).fetchall() except sqlite3.DatabaseError: die("Gitea salvage semantic-topics query failed") if len(rows) != len(normalized): die("Gitea salvage semantic-topics repository set is incomplete") repositories = [] total_topics = 0 material_repositories = 0 serialized_arrays = 0 serialized_nulls = 0 for expected_repo_id, row in zip(normalized, rows): repo_id, value, value_type = row if ( repo_id != expected_repo_id or value_type != "text" ): die("Gitea salvage semantic-topics row is invalid") topics = gitea_salvage_parse_semantic_topics(value) if value == "null": encoding = "json-null" serialized_nulls += 1 else: encoding = "json-array" serialized_arrays += 1 count = len(topics) total_topics += count material_repositories += int(bool(count)) repositories.append( { "encoding": encoding, "material": bool(count), "old_repo_id": repo_id, "topic_count": count, } ) evidence = { "database_sha256": GITEA_SALVAGE_SNAPSHOT_DATABASE_SHA256, "decision_manifest_sha256": GITEA_SALVAGE_DECISION_MANIFEST_SHA256, "material_repositories": material_repositories, "repositories": repositories, "schema": "nodedc.gitea.salvage-semantic-topics/v2", "serialized_arrays": serialized_arrays, "serialized_nulls": serialized_nulls, "snapshot_uuid": GITEA_SALVAGE_SNAPSHOT_UUID, "topics": total_topics, } canonical = canonical_gitea_salvage_evidence( evidence, "semantic topics evidence", ) return {"evidence": evidence, **canonical} def bind_gitea_salvage_decisions_to_snapshot(decisions): connection = gitea_salvage_sqlite_connection(GITEA_SALVAGE_SNAPSHOT_DATABASE) try: quick = [row[0] for row in connection.execute("PRAGMA quick_check").fetchall()] if quick != ["ok"]: die("Gitea salvage snapshot SQLite quick_check failed") user_columns = gitea_salvage_table_columns(connection, "user") repository_columns = gitea_salvage_table_columns(connection, "repository") required_user_columns = { "id", "name", "lower_name", "created_unix", "updated_unix", "is_admin", "is_active", "is_restricted", } required_repository_columns = { "id", "owner_id", "owner_name", "name", "lower_name", "created_unix", "updated_unix", "is_private", "is_archived", "is_mirror", "is_fork", } if ( not required_user_columns.issubset(user_columns) or not required_repository_columns.issubset(repository_columns) ): die("Gitea salvage snapshot identity schema mismatch") user_rows = connection.execute( "SELECT id,name,lower_name,created_unix,updated_unix,is_admin," "is_active,is_restricted FROM user ORDER BY id" ).fetchall() repository_rows = connection.execute( "SELECT id,owner_id,owner_name,name,lower_name,created_unix," "updated_unix,is_private,is_archived,is_mirror,is_fork " "FROM repository ORDER BY id" ).fetchall() if len(user_rows) != 972 or len(repository_rows) != 2058: die("Gitea salvage snapshot identity row count mismatch") repositories_per_user = {} lower_owner_by_id = {} for row in user_rows: lower_owner_by_id[int(row["id"])] = str(row["lower_name"]) for row in repository_rows: owner_id = int(row["owner_id"]) repositories_per_user[owner_id] = repositories_per_user.get(owner_id, 0) + 1 decision_users = {int(row["user_id"]): row for row in decisions["users"]} decision_repositories = { int(row["repo_id"]): row for row in decisions["repositories"] } if ( set(decision_users) != {int(row["id"]) for row in user_rows} or set(decision_repositories) != {int(row["id"]) for row in repository_rows} ): die("Gitea salvage decision bundle is not a full database partition") for row in user_rows: user_id = int(row["id"]) fingerprint = { "user_id": user_id, "owner": str(row["name"]), "lower_owner": str(row["lower_name"]), "created_raw": str(row["created_unix"]), "updated_raw": str(row["updated_unix"]), "repository_count": repositories_per_user.get(user_id, 0), "is_admin": int(row["is_admin"]), "is_active": int(row["is_active"]), "is_restricted": int(row["is_restricted"]), } if ( canonical_gitea_salvage_record_sha256(fingerprint) != decision_users[user_id]["record_sha256"] ): die(f"Gitea salvage user fingerprint mismatch: {user_id}") for row in repository_rows: repo_id = int(row["id"]) owner_id = int(row["owner_id"]) decision = decision_repositories[repo_id] fingerprint = { "repo_id": repo_id, "owner_id": owner_id, "owner": str(row["owner_name"]), "slug": str(row["name"]), "lower_owner": lower_owner_by_id.get(owner_id), "lower_slug": str(row["lower_name"]), "repo_relative_path": decision["repo_relative_path"], "created_raw": str(row["created_unix"]), "updated_raw": str(row["updated_unix"]), "is_private": int(row["is_private"]), "is_archived": int(row["is_archived"]), "is_mirror": int(row["is_mirror"]), "is_fork": int(row["is_fork"]), } if ( canonical_gitea_salvage_record_sha256(fingerprint) != decision["record_sha256"] ): die(f"Gitea salvage repository fingerprint mismatch: {repo_id}") if decision["decision"] == "KEEP" and ( int(row["is_private"]) != (1 if owner_id == 1 else 0) or int(row["is_archived"]) != 0 or int(row["is_mirror"]) != 0 or int(row["is_fork"]) != 0 ): die(f"Gitea salvage kept repository state mismatch: {repo_id}") unsupported = gitea_salvage_unsupported_state_inventory( connection, decisions["kept_repositories"], ) topics = gitea_salvage_semantic_topics_inventory( connection, decisions["kept_repositories"], ) closure = gitea_salvage_incident_closure_inventory( connection, decisions, unsupported, topics, ) return { "closure": closure, "quick_check": "ok", "users": len(user_rows), "repositories": len(repository_rows), "topics": topics, "unsupported": unsupported, } finally: connection.close() def gitea_salvage_unsupported_state_inventory(connection, kept_repositories): normalized = [] seen_repo_ids = set() for row in kept_repositories: if not isinstance(row, dict): die("Gitea salvage kept-repository evidence input is invalid") try: repo_id = int(row["repo_id"]) owner = str(row["owner"]) slug = str(row["slug"]) except (KeyError, TypeError, ValueError): die("Gitea salvage kept-repository evidence input is invalid") if ( repo_id <= 0 or repo_id in seen_repo_ids or not re.fullmatch(r"[A-Za-z0-9_.-]{1,255}", owner) or not re.fullmatch(r"[A-Za-z0-9_.-]{1,255}", slug) ): die("Gitea salvage kept-repository evidence identity is invalid") seen_repo_ids.add(repo_id) normalized.append({"old_repo_id": repo_id, "owner": owner, "slug": slug}) normalized.sort(key=lambda item: item["old_repo_id"]) if not normalized: die("Gitea salvage kept-repository evidence set is empty") kept_repo_ids = tuple(item["old_repo_id"] for item in normalized) placeholders = ",".join("?" for _ in kept_repo_ids) schema_evidence = gitea_salvage_unsupported_schema_catalog(connection) schemas = { schema["table"]: schema for schema in schema_evidence["catalog"]["tables"] } schema_columns = { table: gitea_salvage_schema_columns_by_name(schema) for table, schema in schemas.items() } missing_schema = set() mismatched_schema = set() anomalies = [] per_repository = [] by_repo_id = {} count_labels = tuple( label for label, _table, _column in GITEA_SALVAGE_UNSUPPORTED_REPOSITORY_TABLES ) for identity in normalized: record = { **identity, "attachment_links": { label: {"logical_bytes": None, "rows": None} for label in ("comment", "issue", "multi_link", "release", "unlinked") }, "attachments": { "association_rows": None, "logical_bytes": None, }, "counts": {label: None for label in count_labels}, "lfs": { "association_logical_bytes": None, "association_rows": None, "distinct_oids": None, }, "repo_unit_types": None, "repository_hints": { column: None for column in GITEA_SALVAGE_UNSUPPORTED_REPOSITORY_NUMERIC_HINTS }, "repository_metadata_present": { column: None for column in GITEA_SALVAGE_UNSUPPORTED_REPOSITORY_TEXT_METADATA }, } per_repository.append(record) by_repo_id[identity["old_repo_id"]] = record def require_column(table, column, affinity=None): schema = schemas[table] columns = schema_columns[table] if schema["exists"] and not schema["ordinary_main_table"]: mismatched_schema.add(f"{table}:ordinary-main-table") return False if not schema["exists"] or column not in columns: missing_schema.add(f"{table}.{column}") return False declared_type = columns[column]["type"] if affinity == "integer" and not gitea_salvage_declared_type_has_integer_affinity( declared_type ): mismatched_schema.add(f"{table}.{column}:integer-affinity") return False if affinity == "text" and not gitea_salvage_declared_type_has_text_affinity( declared_type ): mismatched_schema.add(f"{table}.{column}:text-affinity") return False return True metric_available = {} for label, table, column in GITEA_SALVAGE_UNSUPPORTED_REPOSITORY_TABLES: available = require_column(table, column, "integer") metric_available[label] = available if not available: continue try: invalid_repo_ids = int( connection.execute( f'SELECT COUNT(*) FROM "{table}" ' f'WHERE "{column}" IN ({placeholders}) ' f'AND typeof("{column}") != \'integer\'', kept_repo_ids, ).fetchone()[0] ) grouped = connection.execute( f'SELECT "{column}",COUNT(*) FROM "{table}" ' f'WHERE "{column}" IN ({placeholders}) GROUP BY "{column}"', kept_repo_ids, ).fetchall() except sqlite3.DatabaseError: die("Gitea salvage direct-relation evidence query failed") if invalid_repo_ids: anomalies.append(f"{table}.{column}:invalid_repo_ids={invalid_repo_ids}") metric_available[label] = False continue for repo_id, count in grouped: if not isinstance(repo_id, int) or repo_id not in by_repo_id: die("Gitea salvage direct-relation grouping escaped the keep set") by_repo_id[repo_id]["counts"][label] = int(count) for record in per_repository: if record["counts"][label] is None: record["counts"][label] = 0 repo_unit_requirements = [ require_column("repo_unit", column, "integer") for column in ("repo_id", "type") ] repo_unit_ready = all(repo_unit_requirements) if repo_unit_ready: try: unit_rows = connection.execute( f"SELECT repo_id,type,COUNT(*) FROM repo_unit " f"WHERE repo_id IN ({placeholders}) GROUP BY repo_id,type", kept_repo_ids, ).fetchall() except sqlite3.DatabaseError: die("Gitea salvage repo-unit evidence query failed") unit_types = {repo_id: {} for repo_id in kept_repo_ids} for repo_id, unit_type, count in unit_rows: if ( not isinstance(repo_id, int) or repo_id not in by_repo_id or not isinstance(unit_type, int) or unit_type < 0 ): anomalies.append("repo_unit.repo_id/type:invalid_group") repo_unit_ready = False break unit_types[repo_id][str(unit_type)] = int(count) if repo_unit_ready: for repo_id, values in unit_types.items(): by_repo_id[repo_id]["repo_unit_types"] = values repository_id_ready = require_column("repository", "id", "integer") for column in GITEA_SALVAGE_UNSUPPORTED_REPOSITORY_TEXT_METADATA: column_ready = require_column("repository", column, "text") if not repository_id_ready or not column_ready: continue try: invalid = int( connection.execute( f'SELECT COUNT(*) FROM repository WHERE id IN ({placeholders}) ' f'AND typeof("{column}") NOT IN (\'null\',\'text\')', kept_repo_ids, ).fetchone()[0] ) rows = connection.execute( f'SELECT id,CASE WHEN "{column}" IS NULL OR "{column}" = \'\' ' f'THEN 0 ELSE 1 END FROM repository WHERE id IN ({placeholders})', kept_repo_ids, ).fetchall() except sqlite3.DatabaseError: die("Gitea salvage repository metadata evidence query failed") if invalid: anomalies.append(f"repository.{column}:invalid_values={invalid}") continue for repo_id, present in rows: if repo_id not in by_repo_id or present not in (0, 1): die("Gitea salvage repository metadata grouping is invalid") by_repo_id[repo_id]["repository_metadata_present"][column] = bool(present) for column in GITEA_SALVAGE_UNSUPPORTED_REPOSITORY_NUMERIC_HINTS: column_ready = require_column("repository", column, "integer") if not repository_id_ready or not column_ready: continue try: rows = connection.execute( f'SELECT id,"{column}",typeof("{column}") FROM repository ' f'WHERE id IN ({placeholders})', kept_repo_ids, ).fetchall() except sqlite3.DatabaseError: die("Gitea salvage repository hint evidence query failed") valid = True for repo_id, value, value_type in rows: if ( repo_id not in by_repo_id or value_type not in ("integer", "null") or (value is not None and (not isinstance(value, int) or value < 0)) ): anomalies.append(f"repository.{column}:invalid_value") valid = False break if valid: for repo_id, value, _value_type in rows: by_repo_id[repo_id]["repository_hints"][column] = ( 0 if value is None else int(value) ) lfs_requirements = [ require_column("lfs_meta_object", "repository_id", "integer"), require_column("lfs_meta_object", "oid", "text"), require_column("lfs_meta_object", "size", "integer"), ] lfs_required = all(lfs_requirements) and repository_id_ready lfs_aggregate = { "association_logical_bytes": None, "association_rows": None, "distinct_oids": None, "invalid_oid_rows": None, "invalid_related_repository_rows": None, "invalid_related_size_rows": None, "invalid_size_rows": None, "non_kept_shared_logical_bytes": None, "non_kept_shared_oids": None, "physical_presence": "not-inventoried", "orphan_related_repository_rows": None, "size_conflict_oids": None, "unique_logical_bytes": None, } if lfs_required: try: invalid_oid_rows = int( connection.execute( f"SELECT COUNT(*) FROM lfs_meta_object " f"WHERE repository_id IN ({placeholders}) AND (" "typeof(oid) != 'text' OR length(oid) != 64 OR " "oid GLOB '*[^0-9a-f]*')", kept_repo_ids, ).fetchone()[0] ) invalid_size_rows = int( connection.execute( f"SELECT COUNT(*) FROM lfs_meta_object " f"WHERE repository_id IN ({placeholders}) AND (" "typeof(size) != 'integer' OR size < 0 OR size > ?)", (*kept_repo_ids, GITEA_SALVAGE_UNSUPPORTED_SIZE_PER_ROW_MAX_BYTES), ).fetchone()[0] ) invalid_related_size_rows = int( connection.execute( f"SELECT COUNT(*) FROM lfs_meta_object WHERE oid IN (" f"SELECT oid FROM lfs_meta_object " f"WHERE repository_id IN ({placeholders})) AND (" "typeof(size) != 'integer' OR size < 0 OR size > ?)", (*kept_repo_ids, GITEA_SALVAGE_UNSUPPORTED_SIZE_PER_ROW_MAX_BYTES), ).fetchone()[0] ) invalid_related_repository_rows = int( connection.execute( f"SELECT COUNT(*) FROM lfs_meta_object WHERE oid IN (" f"SELECT oid FROM lfs_meta_object " f"WHERE repository_id IN ({placeholders})) AND (" "typeof(repository_id) != 'integer' OR repository_id <= 0)", kept_repo_ids, ).fetchone()[0] ) orphan_related_repository_rows = int( connection.execute( f"SELECT COUNT(*) FROM lfs_meta_object AS related WHERE oid IN (" f"SELECT oid FROM lfs_meta_object " f"WHERE repository_id IN ({placeholders})) " "AND typeof(related.repository_id) = 'integer' " "AND related.repository_id > 0 AND NOT EXISTS (" "SELECT 1 FROM repository " "WHERE repository.id=related.repository_id)", kept_repo_ids, ).fetchone()[0] ) except sqlite3.DatabaseError: die("Gitea salvage LFS validation query failed") lfs_aggregate["invalid_oid_rows"] = invalid_oid_rows lfs_aggregate["invalid_related_repository_rows"] = ( invalid_related_repository_rows ) lfs_aggregate["invalid_related_size_rows"] = invalid_related_size_rows lfs_aggregate["invalid_size_rows"] = invalid_size_rows lfs_aggregate["orphan_related_repository_rows"] = ( orphan_related_repository_rows ) if invalid_oid_rows: anomalies.append(f"lfs_meta_object.oid:invalid_rows={invalid_oid_rows}") if invalid_size_rows: anomalies.append(f"lfs_meta_object.size:invalid_rows={invalid_size_rows}") if invalid_related_size_rows: anomalies.append( "lfs_meta_object.size:" f"invalid_related_rows={invalid_related_size_rows}" ) if invalid_related_repository_rows: anomalies.append( "lfs_meta_object.repository_id:" f"invalid_related_rows={invalid_related_repository_rows}" ) if orphan_related_repository_rows: anomalies.append( "lfs_meta_object.repository_id:" f"orphan_related_rows={orphan_related_repository_rows}" ) if not any( ( invalid_oid_rows, invalid_size_rows, invalid_related_size_rows, invalid_related_repository_rows, orphan_related_repository_rows, ) ): try: grouped = connection.execute( f"SELECT repository_id,COUNT(*),COUNT(DISTINCT oid)," f"COALESCE(SUM(size),0) FROM lfs_meta_object " f"WHERE repository_id IN ({placeholders}) GROUP BY repository_id", kept_repo_ids, ).fetchall() unique = connection.execute( f"SELECT COUNT(*),COALESCE(SUM(max_size),0) FROM (" f"SELECT oid,MAX(size) AS max_size FROM lfs_meta_object " f"WHERE repository_id IN ({placeholders}) GROUP BY oid)", kept_repo_ids, ).fetchone() conflicts = int( connection.execute( f"SELECT COUNT(*) FROM (SELECT oid FROM lfs_meta_object " f"WHERE oid IN (SELECT oid FROM lfs_meta_object " f"WHERE repository_id IN ({placeholders})) " f"GROUP BY oid HAVING MIN(size) != MAX(size))", kept_repo_ids, ).fetchone()[0] ) shared = connection.execute( f"SELECT COUNT(*),COALESCE(SUM(kept_size),0) FROM (" f"SELECT kept.oid,MAX(kept.size) AS kept_size " f"FROM lfs_meta_object AS kept " f"WHERE kept.repository_id IN ({placeholders}) AND EXISTS (" f"SELECT 1 FROM lfs_meta_object AS other " f"WHERE other.oid=kept.oid AND other.repository_id NOT IN ({placeholders})" f") GROUP BY kept.oid)", (*kept_repo_ids, *kept_repo_ids), ).fetchone() except sqlite3.DatabaseError: die("Gitea salvage LFS aggregate query failed") association_rows = 0 association_bytes = 0 distinct_oids_per_repo = 0 for repo_id, rows, distinct_oids, logical_bytes in grouped: if repo_id not in by_repo_id: die("Gitea salvage LFS grouping escaped the keep set") values = (rows, distinct_oids, logical_bytes) if any(not isinstance(value, int) or value < 0 for value in values): die("Gitea salvage LFS aggregate is invalid") if logical_bytes > GITEA_SALVAGE_UNSUPPORTED_SIZE_TOTAL_MAX_BYTES: die("Gitea salvage LFS aggregate exceeds the byte limit") by_repo_id[repo_id]["lfs"] = { "association_logical_bytes": logical_bytes, "association_rows": rows, "distinct_oids": distinct_oids, } association_rows += rows association_bytes += logical_bytes distinct_oids_per_repo += distinct_oids for record in per_repository: if record["lfs"]["association_rows"] is None: record["lfs"] = { "association_logical_bytes": 0, "association_rows": 0, "distinct_oids": 0, } unique_oids, unique_bytes = unique shared_oids, shared_bytes = shared for value in ( association_rows, association_bytes, distinct_oids_per_repo, unique_oids, unique_bytes, shared_oids, shared_bytes, conflicts, ): if not isinstance(value, int) or value < 0: die("Gitea salvage LFS aggregate is invalid") if max(association_bytes, unique_bytes, shared_bytes) > ( GITEA_SALVAGE_UNSUPPORTED_SIZE_TOTAL_MAX_BYTES ): die("Gitea salvage LFS aggregate exceeds the byte limit") lfs_aggregate.update( { "association_logical_bytes": association_bytes, "association_rows": association_rows, "distinct_oids": unique_oids, "non_kept_shared_logical_bytes": shared_bytes, "non_kept_shared_oids": shared_oids, "size_conflict_oids": conflicts, "unique_logical_bytes": unique_bytes, } ) if conflicts: anomalies.append(f"lfs_meta_object.size:conflict_oids={conflicts}") attachment_requirements = [ require_column("attachment", column, "integer") for column in ("id", "repo_id", "issue_id", "release_id", "comment_id", "size") ] attachment_required = all(attachment_requirements) attachment_aggregate = { "association_rows": None, "invalid_association_rows": None, "invalid_size_rows": None, "link_splits": { label: {"logical_bytes": None, "rows": None} for label in ("comment", "issue", "multi_link", "release", "unlinked") }, "logical_bytes": None, "physical_presence": "not-inventoried", } if attachment_required: try: invalid_associations = int( connection.execute( f"SELECT COUNT(*) FROM attachment WHERE repo_id IN ({placeholders}) " "AND (typeof(repo_id) != 'integer' OR " "typeof(issue_id) NOT IN ('integer','null') OR " "typeof(release_id) NOT IN ('integer','null') OR " "typeof(comment_id) NOT IN ('integer','null') OR " "COALESCE(issue_id,0) < 0 OR COALESCE(release_id,0) < 0 OR " "COALESCE(comment_id,0) < 0)", kept_repo_ids, ).fetchone()[0] ) invalid_sizes = int( connection.execute( f"SELECT COUNT(*) FROM attachment WHERE repo_id IN ({placeholders}) " "AND (typeof(size) != 'integer' OR size < 0 OR size > ?)", (*kept_repo_ids, GITEA_SALVAGE_UNSUPPORTED_SIZE_PER_ROW_MAX_BYTES), ).fetchone()[0] ) except sqlite3.DatabaseError: die("Gitea salvage attachment validation query failed") attachment_aggregate["invalid_association_rows"] = invalid_associations attachment_aggregate["invalid_size_rows"] = invalid_sizes if invalid_associations: anomalies.append(f"attachment.association:invalid_rows={invalid_associations}") if invalid_sizes: anomalies.append(f"attachment.size:invalid_rows={invalid_sizes}") if not invalid_associations and not invalid_sizes: try: grouped = connection.execute( f"SELECT repo_id,COUNT(*),COALESCE(SUM(size),0) " f"FROM attachment WHERE repo_id IN ({placeholders}) GROUP BY repo_id", kept_repo_ids, ).fetchall() except sqlite3.DatabaseError: die("Gitea salvage attachment aggregate query failed") association_rows = 0 logical_bytes = 0 for repo_id, rows, size_bytes in grouped: if ( repo_id not in by_repo_id or not isinstance(rows, int) or rows < 0 or not isinstance(size_bytes, int) or size_bytes < 0 or size_bytes > GITEA_SALVAGE_UNSUPPORTED_SIZE_TOTAL_MAX_BYTES ): die("Gitea salvage attachment aggregate is invalid") by_repo_id[repo_id]["attachments"] = { "association_rows": rows, "logical_bytes": size_bytes, } association_rows += rows logical_bytes += size_bytes for record in per_repository: if record["attachments"]["association_rows"] is None: record["attachments"] = { "association_rows": 0, "logical_bytes": 0, } if logical_bytes > GITEA_SALVAGE_UNSUPPORTED_SIZE_TOTAL_MAX_BYTES: die("Gitea salvage attachment aggregate exceeds the byte limit") attachment_aggregate["association_rows"] = association_rows attachment_aggregate["logical_bytes"] = logical_bytes link_predicates = { "comment": "COALESCE(comment_id,0) > 0", "issue": "COALESCE(issue_id,0) > 0", "multi_link": "((COALESCE(issue_id,0) > 0) + (COALESCE(release_id,0) > 0) + (COALESCE(comment_id,0) > 0)) > 1", "release": "COALESCE(release_id,0) > 0", "unlinked": "COALESCE(issue_id,0) = 0 AND COALESCE(release_id,0) = 0 AND COALESCE(comment_id,0) = 0", } for label, predicate in link_predicates.items(): try: split_rows = connection.execute( f"SELECT repo_id,COUNT(*),COALESCE(SUM(size),0) " f"FROM attachment WHERE repo_id IN ({placeholders}) " f"AND ({predicate}) GROUP BY repo_id", kept_repo_ids, ).fetchall() except sqlite3.DatabaseError: die("Gitea salvage attachment link-split query failed") total_rows = 0 total_bytes = 0 for repo_id, rows, size_bytes in split_rows: if ( repo_id not in by_repo_id or not isinstance(rows, int) or rows < 0 or not isinstance(size_bytes, int) or size_bytes < 0 ): die("Gitea salvage attachment link split is invalid") by_repo_id[repo_id]["attachment_links"][label] = { "logical_bytes": size_bytes, "rows": rows, } total_rows += rows total_bytes += size_bytes for record in per_repository: if record["attachment_links"][label]["rows"] is None: record["attachment_links"][label] = { "logical_bytes": 0, "rows": 0, } if total_bytes > GITEA_SALVAGE_UNSUPPORTED_SIZE_TOTAL_MAX_BYTES: die("Gitea salvage attachment link split exceeds the byte limit") attachment_aggregate["link_splits"][label] = { "logical_bytes": total_bytes, "rows": total_rows, } direct_totals = { label: ( sum(record["counts"][label] for record in per_repository) if metric_available[label] and all(record["counts"][label] is not None for record in per_repository) else None ) for label in count_labels } metadata_totals = { column: ( sum( 1 for record in per_repository if record["repository_metadata_present"][column] is True ) if all( record["repository_metadata_present"][column] is not None for record in per_repository ) else None ) for column in GITEA_SALVAGE_UNSUPPORTED_REPOSITORY_TEXT_METADATA } nonzero_categories = sorted( [label for label, value in direct_totals.items() if isinstance(value, int) and value] + [ f"repository_metadata:{column}" for column, value in metadata_totals.items() if isinstance(value, int) and value ] + ( ["repo_units"] if repo_unit_ready and any(record["repo_unit_types"] for record in per_repository) else [] ) + [ f"repository_hint:{column}" for column in GITEA_SALVAGE_UNSUPPORTED_REPOSITORY_NUMERIC_HINTS if any( isinstance(record["repository_hints"][column], int) and record["repository_hints"][column] > 0 for record in per_repository ) ] ) schema_review_matches = ( GITEA_SALVAGE_EXPECTED_UNSUPPORTED_SCHEMA_SHA256 is not None and schema_evidence["sha256"] == GITEA_SALVAGE_EXPECTED_UNSUPPORTED_SCHEMA_SHA256 ) coverage_blockers = [ "issue-pull-dependent-closure-unreviewed", "package-blob-closure-unreviewed", "actions-artifact-closure-unreviewed", "lfs-physical-object-inventory-unreviewed", "attachment-physical-object-inventory-unreviewed", ] if not schema_review_matches: coverage_blockers.append("unsupported-schema-catalog-unreviewed") if not schema_evidence["catalog"]["table_list_supported"]: coverage_blockers.append("sqlite-table-list-object-kind-unavailable") report = { "aggregates": { "attachments": attachment_aggregate, "direct_relation_counts": direct_totals, "lfs": lfs_aggregate, "repository_metadata_presence": metadata_totals, }, "anomalies": sorted(set(anomalies)), "coverage": { "attachments": "database-metadata-only-no-physical-presence-claim", "direct_repository_relations": "counted-per-kept-repository", "lfs": "database-associations-and-logical-bytes-no-physical-presence-claim", "repository_hints": "denormalized-non-authoritative", "schema_only_unreviewed_tables": [ "action_artifact", "action_run_index", "action_run_job", "action_task", "comment", "issue_assignees", "issue_content_history", "issue_dependency", "issue_label", "issue_user", "issue_watch", "notification", "package_blob", "package_file", "package_property", "package_version", "project", "project_board", "project_issue", "pull_auto_merge", "reaction", "review", "review_state", "stopwatch", "tracked_time", ], }, "coverage_blockers": sorted(coverage_blockers), "database_sha256": GITEA_SALVAGE_SNAPSHOT_DATABASE_SHA256, "decision_manifest_sha256": GITEA_SALVAGE_DECISION_MANIFEST_SHA256, "direct_relation_contract": sorted( ( { "label": label, "repository_column": column, "table": table, } for label, table, column in GITEA_SALVAGE_UNSUPPORTED_REPOSITORY_TABLES ), key=lambda item: item["label"], ), "kept_repository_ids": list(kept_repo_ids), "material_present": bool(nonzero_categories), "nonzero_categories": nonzero_categories, "per_repository": per_repository, "schema": "nodedc.gitea.salvage-unsupported-state/v2", "schema_catalog": schema_evidence["catalog"], "schema_catalog_sha256": schema_evidence["sha256"], "schema_mismatch": sorted(mismatched_schema), "schema_missing": sorted(missing_schema), "schema_review": { "expected_sha256": GITEA_SALVAGE_EXPECTED_UNSUPPORTED_SCHEMA_SHA256, "matches": schema_review_matches, }, "snapshot_uuid": GITEA_SALVAGE_SNAPSHOT_UUID, } canonical = canonical_gitea_salvage_evidence( report, "unsupported-state report", ) return {"report": report, **canonical} def gitea_salvage_closure_actor_class(user_id, user_classes, label): if ( not isinstance(user_id, int) or isinstance(user_id, bool) or user_id < 0 ): die(f"Gitea salvage closure {label} actor identity is invalid") if user_id == 0: return "system-or-external" actor_class = user_classes.get(user_id) if actor_class is None: die(f"Gitea salvage closure {label} actor is outside user decisions") return actor_class def gitea_salvage_closure_metric_template(label): return { "actor_classes": { "deleted": 0, "kept": 0, "system-or-external": 0, }, "logical_bytes": 0, "numeric_totals": { column: 0 for column in GITEA_SALVAGE_CLOSURE_NUMERIC_COLUMNS.get(label, ()) }, "rows": 0, "text_bytes": { column: 0 for column in GITEA_SALVAGE_CLOSURE_TEXT_COLUMNS.get(label, ()) }, } def gitea_salvage_closure_add_metric( record, label, actor_class=None, logical_bytes=0, numeric_values=None, text_bytes=None, ): metric = record["closure"][label] metric["rows"] += 1 if actor_class is not None: if actor_class not in metric["actor_classes"]: die("Gitea salvage closure actor classification is invalid") metric["actor_classes"][actor_class] += 1 if ( not isinstance(logical_bytes, int) or isinstance(logical_bytes, bool) or logical_bytes < 0 or logical_bytes > GITEA_SALVAGE_UNSUPPORTED_SIZE_PER_ROW_MAX_BYTES ): die("Gitea salvage closure logical-byte aggregate is invalid") metric["logical_bytes"] += logical_bytes if metric["logical_bytes"] > GITEA_SALVAGE_UNSUPPORTED_SIZE_TOTAL_MAX_BYTES: die("Gitea salvage closure logical-byte aggregate exceeds the limit") for column, value in (numeric_values or {}).items(): if ( column not in metric["numeric_totals"] or not isinstance(value, int) or isinstance(value, bool) or value < 0 or value > GITEA_SALVAGE_UNSUPPORTED_SIZE_PER_ROW_MAX_BYTES ): die("Gitea salvage closure declared numeric value is invalid") metric["numeric_totals"][column] += value if metric["numeric_totals"][column] > ( GITEA_SALVAGE_UNSUPPORTED_SIZE_TOTAL_MAX_BYTES ): die("Gitea salvage closure declared numeric total exceeds the limit") for column, value in (text_bytes or {}).items(): if ( column not in metric["text_bytes"] or not isinstance(value, int) or isinstance(value, bool) or value < 0 or value > GITEA_SALVAGE_CLOSURE_MAX_TEXT_BYTES_PER_FIELD ): die("Gitea salvage closure text-byte aggregate is invalid") metric["text_bytes"][column] += value if metric["text_bytes"][column] > ( GITEA_SALVAGE_UNSUPPORTED_SIZE_TOTAL_MAX_BYTES ): die("Gitea salvage closure text-byte aggregate exceeds the limit") def gitea_salvage_incident_closure_inventory( connection, decisions, unsupported, topics, ): if ( unsupported.get("sha256") != GITEA_SALVAGE_EXPECTED_UNSUPPORTED_REPORT_SHA256 or (unsupported.get("report") or {}).get("schema_catalog_sha256") != GITEA_SALVAGE_DISPOSITION_UNSUPPORTED_SCHEMA_SHA256 or topics.get("sha256") != GITEA_SALVAGE_DISPOSITION_TOPICS_SHA256 ): die("Gitea salvage closure predecessor evidence mismatch") report = unsupported["report"] if ( report.get("database_sha256") != GITEA_SALVAGE_SNAPSHOT_DATABASE_SHA256 or report.get("decision_manifest_sha256") != GITEA_SALVAGE_DECISION_MANIFEST_SHA256 or report.get("snapshot_uuid") != GITEA_SALVAGE_SNAPSHOT_UUID or report.get("schema_missing") != [] or report.get("schema_mismatch") != [] or report.get("anomalies") != [] ): die("Gitea salvage closure source report is not clean") user_classes = {} for row in decisions.get("users", []): try: user_id = int(row["user_id"]) except (KeyError, TypeError, ValueError): die("Gitea salvage closure user decision is invalid") decision = row.get("decision") if ( user_id <= 0 or user_id in user_classes or decision not in {"KEEP_ACTIVE", "KEEP_LOCKED", "DELETE"} ): die("Gitea salvage closure user decision is invalid") user_classes[user_id] = ( "deleted" if decision == "DELETE" else "kept" ) if ( len(user_classes) != 972 or sum(value == "kept" for value in user_classes.values()) != 10 or sum(value == "deleted" for value in user_classes.values()) != 962 ): die("Gitea salvage closure user partition mismatch") kept_repo_ids = [] all_repo_ids = set() for row in decisions.get("repositories", []): try: repo_id = int(row["repo_id"]) except (KeyError, TypeError, ValueError): die("Gitea salvage closure repository decision is invalid") if repo_id <= 0 or repo_id in all_repo_ids: die("Gitea salvage closure repository decision is invalid") all_repo_ids.add(repo_id) if row.get("decision") == "KEEP": kept_repo_ids.append(repo_id) elif row.get("decision") != "DELETE": die("Gitea salvage closure repository decision is invalid") kept_repo_ids.sort() if len(all_repo_ids) != 2058 or len(kept_repo_ids) != 45: die("Gitea salvage closure repository partition mismatch") if report.get("kept_repository_ids") != kept_repo_ids: die("Gitea salvage closure kept-repository evidence mismatch") kept_repo_set = set(kept_repo_ids) placeholders = ",".join("?" for _ in kept_repo_ids) schemas = { row["table"]: row for row in (report.get("schema_catalog") or {}).get("tables", []) if isinstance(row, dict) and isinstance(row.get("table"), str) } required_schema = {} closure_extra_schema = {} def require_columns(table, columns): required_schema.setdefault(table, set()).update(columns) schema = schemas.get(table) if ( not isinstance(schema, dict) or schema.get("ordinary_main_table") is not True ): die(f"Gitea salvage closure table is not an ordinary table: {table}") available = { column.get("name") for column in schema.get("columns", []) if isinstance(column, dict) } if not set(columns).issubset(available): die(f"Gitea salvage closure schema is missing required columns: {table}") def require_extra_columns(table, columns): if table != "team": die("Gitea salvage closure extra schema table is outside the registry") table_list_supported = bool( (report.get("schema_catalog") or {}).get("table_list_supported") ) schema = gitea_salvage_table_schema( connection, table, table_list_supported, allowed_tables={"team"}, ) available = { column.get("name") for column in schema.get("columns", []) if isinstance(column, dict) } if ( schema.get("ordinary_main_table") is not True or not set(columns).issubset(available) ): die(f"Gitea salvage closure extra schema is unsafe: {table}") required_schema.setdefault(table, set()).update(columns) closure_extra_schema[table] = schema def fetch_rows(label, sql, parameters): try: rows = connection.execute(sql, parameters).fetchall() except sqlite3.DatabaseError: die(f"Gitea salvage closure {label} query failed") if len(rows) > GITEA_SALVAGE_CLOSURE_MAX_ROWS_PER_RELATION: die(f"Gitea salvage closure {label} exceeds the row limit") return rows per_repository = [ { "closure": { label: gitea_salvage_closure_metric_template(label) for label in GITEA_SALVAGE_CLOSURE_METRICS }, "issue_states": { "ordinary_closed": 0, "ordinary_open": 0, "pull_wrapper_closed": 0, "pull_wrapper_open": 0, }, "old_repo_id": repo_id, "pull_states": {"merged": 0, "unmerged": 0}, } for repo_id in kept_repo_ids ] by_repo_id = {row["old_repo_id"]: row for row in per_repository} actor_relations = [] query_contract = [] for label, table, target_disposition in GITEA_SALVAGE_CLOSURE_ACTOR_RELATIONS: require_columns(table, ("id", "repo_id", "user_id", "mode")) rows = fetch_rows( label, f'SELECT id,repo_id,user_id,mode,typeof(id),typeof(repo_id),' f'typeof(user_id),typeof(mode) FROM "{table}" ' f'WHERE repo_id IN ({placeholders}) ORDER BY repo_id,user_id,id', kept_repo_ids, ) seen_pairs = set() for row in rows: relation_id, repo_id, user_id, mode = row[0:4] if ( tuple(row[4:8]) != ("integer", "integer", "integer", "integer") or not all( isinstance(value, int) and not isinstance(value, bool) for value in (relation_id, repo_id, user_id, mode) ) or relation_id <= 0 or repo_id not in kept_repo_set or user_id <= 0 or not 0 <= mode <= 5 or (repo_id, user_id) in seen_pairs ): die(f"Gitea salvage closure {label} row is invalid") seen_pairs.add((repo_id, user_id)) actor_class = gitea_salvage_closure_actor_class( user_id, user_classes, label, ) actor_relations.append( { "actor_class": actor_class, "disposition": ( target_disposition if label == "access_cache" or actor_class == "kept" else "DROP_DELETED_ACTOR" ), "legacy_mode": mode, "old_relation_id": relation_id, "old_repo_id": repo_id, "old_user_id": user_id, "relation": label, } ) query_contract.append( { "join": "direct-repository-and-full-user-decision-partition", "label": label, "output": "ids-mode-actor-class-disposition-no-user-payload", "table": table, } ) require_columns( "issue", ( "id", "repo_id", "poster_id", "name", "content", "is_pull", "is_closed", "milestone_id", ), ) issue_rows = fetch_rows( "issues", f"SELECT id,repo_id,poster_id,is_pull,is_closed,milestone_id," "typeof(id),typeof(repo_id),typeof(poster_id),typeof(is_pull)," "typeof(is_closed),typeof(milestone_id),typeof(name)," "CASE WHEN name IS NULL THEN 0 ELSE length(CAST(name AS BLOB)) END," "typeof(content),CASE WHEN content IS NULL THEN 0 " "ELSE length(CAST(content AS BLOB)) END FROM issue " f"WHERE repo_id IN ({placeholders}) ORDER BY repo_id,id", kept_repo_ids, ) issue_to_repo = {} issue_is_pull = {} issue_milestones = [] for row in issue_rows: ( issue_id, repo_id, poster_id, is_pull, is_closed, milestone_id, id_type, repo_type, poster_type, pull_type, closed_type, milestone_type, name_type, name_bytes, content_type, content_bytes, ) = row if ( (id_type, repo_type, poster_type, pull_type, closed_type) != ("integer", "integer", "integer", "integer", "integer") or milestone_type not in ("integer", "null") or name_type not in ("null", "text") or content_type not in ("null", "text") or not all( isinstance(value, int) and not isinstance(value, bool) for value in ( issue_id, repo_id, poster_id, is_pull, is_closed, name_bytes, content_bytes, ) ) or issue_id <= 0 or issue_id in issue_to_repo or repo_id not in kept_repo_set or is_pull not in (0, 1) or is_closed not in (0, 1) or milestone_id is not None and ( not isinstance(milestone_id, int) or isinstance(milestone_id, bool) or milestone_id < 0 ) ): die("Gitea salvage closure issue row is invalid") issue_to_repo[issue_id] = repo_id issue_is_pull[issue_id] = bool(is_pull) if milestone_id: issue_milestones.append((repo_id, issue_id, milestone_id)) label = "pull_request_wrappers" if is_pull else "issues_ordinary" state_label = ( "pull_wrapper_closed" if is_pull and is_closed else "pull_wrapper_open" if is_pull else "ordinary_closed" if is_closed else "ordinary_open" ) by_repo_id[repo_id]["issue_states"][state_label] += 1 actor_class = gitea_salvage_closure_actor_class( poster_id, user_classes, "issue poster", ) gitea_salvage_closure_add_metric( by_repo_id[repo_id], label, actor_class, text_bytes={"content": content_bytes, "name": name_bytes}, ) query_contract.append( { "join": "issue.repo_id-in-exact-kept-set", "label": "issues-and-pull-wrappers", "output": "counts-actor-classes-name-content-byte-lengths", "table": "issue", } ) require_columns( "pull_request", ("id", "issue_id", "base_repo_id", "head_repo_id", "has_merged"), ) pull_rows = fetch_rows( "pull requests", f"SELECT p.id,p.issue_id,p.base_repo_id,p.head_repo_id,p.has_merged," "typeof(p.id),typeof(p.issue_id),typeof(p.base_repo_id)," "typeof(p.head_repo_id),typeof(p.has_merged) FROM pull_request AS p " "INNER JOIN issue AS i ON i.id=p.issue_id " f"WHERE i.repo_id IN ({placeholders}) ORDER BY i.repo_id,p.id", kept_repo_ids, ) pull_to_repo = {} pull_issue_ids = set() pull_head_partitions = {"deleted": 0, "kept": 0} for row in pull_rows: pull_id, issue_id, base_repo_id, head_repo_id, has_merged = row[0:5] if ( tuple(row[5:10]) != ("integer", "integer", "integer", "integer", "integer") or not all( isinstance(value, int) and not isinstance(value, bool) for value in ( pull_id, issue_id, base_repo_id, head_repo_id, has_merged, ) ) or pull_id <= 0 or pull_id in pull_to_repo or issue_id not in issue_to_repo or not issue_is_pull[issue_id] or issue_id in pull_issue_ids or base_repo_id != issue_to_repo[issue_id] or head_repo_id not in all_repo_ids or has_merged not in (0, 1) ): die("Gitea salvage closure pull-request row is invalid") pull_to_repo[pull_id] = base_repo_id pull_issue_ids.add(issue_id) by_repo_id[base_repo_id]["pull_states"][ "merged" if has_merged else "unmerged" ] += 1 pull_head_partitions[ "kept" if head_repo_id in kept_repo_set else "deleted" ] += 1 if pull_issue_ids != { issue_id for issue_id, is_pull in issue_is_pull.items() if is_pull }: die("Gitea salvage closure pull-wrapper relation is incomplete") query_contract.append( { "join": "pull_request.issue_id-to-kept-issue-base-repo-exact", "label": "pull-request-identity", "output": "counts-and-head-repository-partition-no-branch-text", "table": "pull_request", } ) comment_to_repo = {} comment_to_issue = {} content_history_to_issue = {} review_to_issue = {} tracked_time_to_issue = {} issue_label_pairs = [] for label, table, actor_column, text_columns in ( GITEA_SALVAGE_CLOSURE_ISSUE_DEPENDENTS ): required = ["id", "issue_id", *text_columns] if actor_column is not None: required.append(actor_column) if table == "issue_label": required.append("label_id") require_columns(table, required) select_parts = [ "d.id AS relation_id", "d.issue_id AS issue_id", "i.repo_id AS repo_id", "typeof(d.id) AS relation_id_type", "typeof(d.issue_id) AS issue_id_type", "typeof(i.repo_id) AS repo_id_type", ] if actor_column is not None: select_parts.extend( ( f'd."{actor_column}" AS actor_id', f'typeof(d."{actor_column}") AS actor_id_type', ) ) if table == "issue_label": select_parts.extend( ( "d.label_id AS label_id", "typeof(d.label_id) AS label_id_type", ) ) for column in text_columns: select_parts.extend( ( f'typeof(d."{column}") AS "{column}_type"', f'CASE WHEN d."{column}" IS NULL THEN 0 ' f'ELSE length(CAST(d."{column}" AS BLOB)) ' f'END AS "{column}_bytes"', ) ) rows = fetch_rows( label, f'SELECT {",".join(select_parts)} FROM "{table}" AS d ' "INNER JOIN issue AS i ON i.id=d.issue_id " f"WHERE i.repo_id IN ({placeholders}) ORDER BY i.repo_id,d.id", kept_repo_ids, ) seen_ids = set() for row in rows: relation_id = row["relation_id"] issue_id = row["issue_id"] repo_id = row["repo_id"] if ( (row["relation_id_type"], row["issue_id_type"], row["repo_id_type"]) != ("integer", "integer", "integer") or not all( isinstance(value, int) and not isinstance(value, bool) for value in (relation_id, issue_id, repo_id) ) or relation_id <= 0 or relation_id in seen_ids or issue_to_repo.get(issue_id) != repo_id ): die(f"Gitea salvage closure {label} row is invalid") seen_ids.add(relation_id) actor_class = None if actor_column is not None: if row["actor_id_type"] != "integer": die(f"Gitea salvage closure {label} actor is invalid") actor_class = gitea_salvage_closure_actor_class( row["actor_id"], user_classes, label, ) byte_values = {} for column in text_columns: if row[f"{column}_type"] not in ("null", "text"): die(f"Gitea salvage closure {label} text type is invalid") byte_values[column] = row[f"{column}_bytes"] gitea_salvage_closure_add_metric( by_repo_id[repo_id], label, actor_class, text_bytes=byte_values, ) if table == "comment": comment_to_repo[relation_id] = repo_id comment_to_issue[relation_id] = issue_id elif table == "issue_content_history": content_history_to_issue[relation_id] = issue_id elif table == "issue_label": if ( row["label_id_type"] != "integer" or not isinstance(row["label_id"], int) or isinstance(row["label_id"], bool) or row["label_id"] <= 0 ): die("Gitea salvage closure issue-label row is invalid") issue_label_pairs.append( (repo_id, issue_id, row["label_id"]) ) elif table == "review": review_to_issue[relation_id] = issue_id elif table == "tracked_time": tracked_time_to_issue[relation_id] = issue_id query_contract.append( { "join": f"{table}.issue_id-to-kept-issue", "label": label, "output": ( "counts-actor-classes-and-text-byte-lengths-no-payload" ), "table": table, } ) require_columns( "reaction", ("id", "issue_id", "comment_id", "user_id"), ) reaction_rows = fetch_rows( "reactions", "SELECT r.id,r.issue_id,r.comment_id,r.user_id," "typeof(r.id),typeof(r.issue_id),typeof(r.comment_id),typeof(r.user_id)," "direct_issue.repo_id,comment_issue.repo_id " "FROM reaction AS r " "LEFT JOIN issue AS direct_issue ON direct_issue.id=r.issue_id " "LEFT JOIN comment AS c ON c.id=r.comment_id " "LEFT JOIN issue AS comment_issue ON comment_issue.id=c.issue_id " f"WHERE direct_issue.repo_id IN ({placeholders}) " f"OR comment_issue.repo_id IN ({placeholders}) ORDER BY r.id", (*kept_repo_ids, *kept_repo_ids), ) seen_reactions = set() for row in reaction_rows: reaction_id, issue_id, comment_id, user_id = row[0:4] direct_repo, comment_repo = row[8:10] if ( tuple(row[4:8]) != ("integer", "integer", "integer", "integer") or not all( isinstance(value, int) and not isinstance(value, bool) for value in (reaction_id, issue_id, comment_id, user_id) ) or reaction_id <= 0 or reaction_id in seen_reactions or issue_id < 0 or comment_id < 0 ): die("Gitea salvage closure reaction row is invalid") candidate_repos = { value for value in (direct_repo, comment_repo) if isinstance(value, int) and value in kept_repo_set } if len(candidate_repos) != 1: die("Gitea salvage closure reaction association is ambiguous") repo_id = candidate_repos.pop() if issue_id and issue_to_repo.get(issue_id) != repo_id: die("Gitea salvage closure reaction issue association is invalid") if comment_id and comment_to_repo.get(comment_id) != repo_id: die("Gitea salvage closure reaction comment association is invalid") seen_reactions.add(reaction_id) actor_class = gitea_salvage_closure_actor_class( user_id, user_classes, "reaction", ) gitea_salvage_closure_add_metric( by_repo_id[repo_id], "reactions", actor_class, ) query_contract.append( { "join": "reaction.issue-or-comment-to-kept-issue-exact-one-repo", "label": "reactions", "output": "counts-and-actor-classes-no-reaction-text", "table": "reaction", } ) require_columns("review_state", ("id", "pull_id", "user_id", "updated_files")) review_state_rows = fetch_rows( "review states", "SELECT s.id,s.pull_id,s.user_id,p.base_repo_id," "typeof(s.id),typeof(s.pull_id),typeof(s.user_id),typeof(p.base_repo_id)," "typeof(s.updated_files),CASE WHEN s.updated_files IS NULL THEN 0 " "ELSE length(CAST(s.updated_files AS BLOB)) END " "FROM review_state AS s INNER JOIN pull_request AS p ON p.id=s.pull_id " "INNER JOIN issue AS i ON i.id=p.issue_id " f"WHERE i.repo_id IN ({placeholders}) ORDER BY i.repo_id,s.id", kept_repo_ids, ) seen_review_states = set() for row in review_state_rows: state_id, pull_id, user_id, repo_id = row[0:4] if ( tuple(row[4:8]) != ("integer", "integer", "integer", "integer") or row[8] not in ("null", "text") or not all( isinstance(value, int) and not isinstance(value, bool) for value in (state_id, pull_id, user_id, repo_id, row[9]) ) or state_id <= 0 or state_id in seen_review_states or pull_to_repo.get(pull_id) != repo_id ): die("Gitea salvage closure review-state row is invalid") seen_review_states.add(state_id) actor_class = gitea_salvage_closure_actor_class( user_id, user_classes, "review state", ) gitea_salvage_closure_add_metric( by_repo_id[repo_id], "review_states", actor_class, text_bytes={"updated_files": row[9]}, ) query_contract.append( { "join": "review_state.pull_id-to-kept-pull-wrapper", "label": "review-states", "output": "counts-actor-classes-updated-files-byte-length-no-payload", "table": "review_state", } ) require_columns( "issue_dependency", ("id", "user_id", "issue_id", "dependency_id"), ) dependency_rows = fetch_rows( "issue dependencies", "SELECT d.id,d.user_id,d.issue_id,d.dependency_id,source.repo_id," "target.repo_id,typeof(d.id),typeof(d.user_id),typeof(d.issue_id)," "typeof(d.dependency_id) FROM issue_dependency AS d " "INNER JOIN issue AS source ON source.id=d.issue_id " "LEFT JOIN issue AS target ON target.id=d.dependency_id " f"WHERE source.repo_id IN ({placeholders}) ORDER BY source.repo_id,d.id", kept_repo_ids, ) seen_dependencies = set() cross_repo_dependencies = 0 for row in dependency_rows: relation_id, user_id, issue_id, dependency_id, repo_id, target_repo_id = row[0:6] if ( tuple(row[6:10]) != ("integer", "integer", "integer", "integer") or not all( isinstance(value, int) and not isinstance(value, bool) for value in row[0:6] ) or relation_id <= 0 or relation_id in seen_dependencies or issue_to_repo.get(issue_id) != repo_id or target_repo_id not in all_repo_ids ): die("Gitea salvage closure issue-dependency row is invalid") seen_dependencies.add(relation_id) cross_repo_dependencies += int(repo_id != target_repo_id) actor_class = gitea_salvage_closure_actor_class( user_id, user_classes, "issue dependency", ) gitea_salvage_closure_add_metric( by_repo_id[repo_id], "issue_dependencies", actor_class, ) query_contract.append( { "join": "issue_dependency.issue_id-to-kept-source-issue", "label": "issue-dependencies", "output": "counts-actor-classes-cross-repo-count-no-text", "table": "issue_dependency", } ) direct_text_contracts = ( ("labels", "label", None, ("name", "description", "color")), ("milestones", "milestone", None, ("name", "content")), ( "projects", "project", "creator_id", ("title", "description"), ), ( "releases", "release", "publisher_id", ("tag_name", "target", "title", "note"), ), ) label_to_repo = {} milestone_to_repo = {} project_to_repo = {} release_to_repo = {} for label, table, actor_column, text_columns in direct_text_contracts: required = ["id", "repo_id", *text_columns] if actor_column is not None: required.append(actor_column) require_columns(table, required) select_parts = [ "id", "repo_id", "typeof(id) AS id_type", "typeof(repo_id) AS repo_id_type", ] if actor_column is not None: select_parts.extend( ( f'"{actor_column}" AS actor_id', f'typeof("{actor_column}") AS actor_id_type', ) ) for column in text_columns: select_parts.extend( ( f'typeof("{column}") AS "{column}_type"', f'CASE WHEN "{column}" IS NULL THEN 0 ' f'ELSE length(CAST("{column}" AS BLOB)) ' f'END AS "{column}_bytes"', ) ) rows = fetch_rows( label, f'SELECT {",".join(select_parts)} FROM "{table}" ' f"WHERE repo_id IN ({placeholders}) ORDER BY repo_id,id", kept_repo_ids, ) seen_ids = set() for row in rows: relation_id = row["id"] repo_id = row["repo_id"] if ( (row["id_type"], row["repo_id_type"]) != ("integer", "integer") or not isinstance(relation_id, int) or isinstance(relation_id, bool) or relation_id <= 0 or relation_id in seen_ids or repo_id not in kept_repo_set ): die(f"Gitea salvage closure {label} row is invalid") seen_ids.add(relation_id) actor_class = None if actor_column is not None: if row["actor_id_type"] != "integer": die(f"Gitea salvage closure {label} actor is invalid") actor_class = gitea_salvage_closure_actor_class( row["actor_id"], user_classes, label, ) byte_values = {} for column in text_columns: if row[f"{column}_type"] not in ("null", "text"): die(f"Gitea salvage closure {label} text type is invalid") byte_values[column] = row[f"{column}_bytes"] gitea_salvage_closure_add_metric( by_repo_id[repo_id], label, actor_class, text_bytes=byte_values, ) if table == "label": label_to_repo[relation_id] = repo_id elif table == "milestone": milestone_to_repo[relation_id] = repo_id elif table == "project": project_to_repo[relation_id] = repo_id elif table == "release": release_to_repo[relation_id] = repo_id query_contract.append( { "join": f"{table}.repo_id-in-exact-kept-set", "label": label, "output": "counts-actor-classes-text-byte-lengths-no-payload", "table": table, } ) for repo_id, _issue_id, label_id in issue_label_pairs: if label_to_repo.get(label_id) != repo_id: die("Gitea salvage closure issue-label repository relation is invalid") for repo_id, _issue_id, milestone_id in issue_milestones: if milestone_to_repo.get(milestone_id) != repo_id: die("Gitea salvage closure issue-milestone repository relation is invalid") require_columns( "project_board", ("id", "project_id", "title", "color"), ) board_rows = fetch_rows( "project boards", "SELECT b.id,b.project_id,p.repo_id,typeof(b.id),typeof(b.project_id)," "typeof(p.repo_id),typeof(b.title),CASE WHEN b.title IS NULL THEN 0 " "ELSE length(CAST(b.title AS BLOB)) END,typeof(b.color)," "CASE WHEN b.color IS NULL THEN 0 ELSE length(CAST(b.color AS BLOB)) END " "FROM project_board AS b INNER JOIN project AS p ON p.id=b.project_id " f"WHERE p.repo_id IN ({placeholders}) ORDER BY p.repo_id,b.id", kept_repo_ids, ) board_to_repo = {} for row in board_rows: board_id, project_id, repo_id = row[0:3] if ( tuple(row[3:6]) != ("integer", "integer", "integer") or row[6] not in ("null", "text") or row[8] not in ("null", "text") or not all( isinstance(value, int) and not isinstance(value, bool) for value in (board_id, project_id, repo_id, row[7], row[9]) ) or board_id <= 0 or board_id in board_to_repo or project_to_repo.get(project_id) != repo_id ): die("Gitea salvage closure project-board row is invalid") board_to_repo[board_id] = repo_id gitea_salvage_closure_add_metric( by_repo_id[repo_id], "project_boards", text_bytes={"color": row[9], "title": row[7]}, ) query_contract.append( { "join": "project_board.project_id-to-kept-project", "label": "project-boards", "output": "counts-title-color-byte-lengths-no-payload", "table": "project_board", } ) require_columns( "project_issue", ("id", "issue_id", "project_id", "project_board_id"), ) project_issue_rows = fetch_rows( "project issue links", "SELECT pi.id,pi.issue_id,pi.project_id,pi.project_board_id," "i.repo_id,p.repo_id,typeof(pi.id),typeof(pi.issue_id)," "typeof(pi.project_id),typeof(pi.project_board_id) " "FROM project_issue AS pi LEFT JOIN issue AS i ON i.id=pi.issue_id " "LEFT JOIN project AS p ON p.id=pi.project_id " f"WHERE i.repo_id IN ({placeholders}) OR p.repo_id IN ({placeholders}) " "ORDER BY pi.id", (*kept_repo_ids, *kept_repo_ids), ) seen_project_issue = set() for row in project_issue_rows: link_id, issue_id, project_id, board_id, issue_repo, project_repo = row[0:6] if ( tuple(row[6:10]) != ("integer", "integer", "integer", "integer") or not all( isinstance(value, int) and not isinstance(value, bool) for value in row[0:6] ) or link_id <= 0 or link_id in seen_project_issue or issue_repo != project_repo or issue_to_repo.get(issue_id) != issue_repo or project_to_repo.get(project_id) != project_repo or board_id > 0 and board_to_repo.get(board_id) != project_repo or board_id < 0 ): die("Gitea salvage closure project-issue relation is invalid") seen_project_issue.add(link_id) gitea_salvage_closure_add_metric( by_repo_id[project_repo], "project_issue_links", ) query_contract.append( { "join": "project_issue-to-same-kept-repository-issue-project-board", "label": "project-issue-links", "output": "counts-only-no-project-or-issue-text", "table": "project_issue", } ) # Close the concrete FK-like comment/history/pull subrelations present in # the pinned snapshot schema. Optional legacy INTEGER fields may be SQL # NULL or zero; both are treated as absent, but NULL representation counts # remain explicit. No comment type, body, path, ref text or commit value is # selected. Team IDs are existence-checked and sealed as HOLD because the # incident decision has no organization/team allowlist. subrelation_per_repo = { repo_id: { "actor_classes": { label: { "deleted": 0, "kept": 0, "system-or-external": 0, } for label in GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_ACTORS }, "counts": { label: 0 for label in GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_COUNTS }, "external_author_provenance": { source: { "id_without_name": 0, "name_bytes": 0, "name_without_id": 0, "rows_with_id": 0, "rows_with_name": 0, } for source in GITEA_SALVAGE_CLOSURE_EXTERNAL_AUTHOR_SOURCES }, "null_encodings": { column: 0 for column in ( GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_NULLABLE_COLUMNS ) }, "old_repo_id": repo_id, "target_repository_classes": { label: {"deleted": 0, "global": 0, "kept": 0} for label in ( GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_REPOSITORIES ) }, } for repo_id in kept_repo_ids } subrelation_holds = [] def add_external_author_provenance( repo_id, source, external_id, external_id_type, external_name_type, external_name_bytes, ): if source not in GITEA_SALVAGE_CLOSURE_EXTERNAL_AUTHOR_SOURCES: die("Gitea salvage external-author source is invalid") if external_id_type == "null" and external_id is None: subrelation_per_repo[repo_id]["null_encodings"][ f"{source}.original_author_id" ] += 1 external_id = 0 if ( external_id_type not in ("integer", "null") or not isinstance(external_id, int) or isinstance(external_id, bool) or external_id < 0 or external_name_type not in ("null", "text") or not isinstance(external_name_bytes, int) or isinstance(external_name_bytes, bool) or external_name_bytes < 0 or external_name_bytes > GITEA_SALVAGE_CLOSURE_MAX_TEXT_BYTES_PER_FIELD ): die("Gitea salvage external-author provenance is invalid") record = subrelation_per_repo[repo_id]["external_author_provenance"][ source ] has_id = external_id > 0 has_name = external_name_bytes > 0 record["rows_with_id"] += int(has_id) record["rows_with_name"] += int(has_name) record["id_without_name"] += int(has_id and not has_name) record["name_without_id"] += int(has_name and not has_id) record["name_bytes"] += external_name_bytes if record["name_bytes"] > GITEA_SALVAGE_UNSUPPORTED_SIZE_TOTAL_MAX_BYTES: die("Gitea salvage external-author byte total exceeds the limit") def add_subrelation_count(repo_id, label): if repo_id not in subrelation_per_repo or label not in ( GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_COUNTS ): die("Gitea salvage issue subrelation count is invalid") subrelation_per_repo[repo_id]["counts"][label] += 1 def add_subrelation_actor(repo_id, label, actor_id): actor_class = gitea_salvage_closure_actor_class( actor_id, user_classes, label, ) subrelation_per_repo[repo_id]["actor_classes"][label][actor_class] += 1 def add_subrelation_target_repo(repo_id, label, target_repo_id): if target_repo_id == 0: target_class = "global" elif target_repo_id in kept_repo_set: target_class = "kept" elif target_repo_id in all_repo_ids: target_class = "deleted" else: die("Gitea salvage issue subrelation target repository is invalid") subrelation_per_repo[repo_id]["target_repository_classes"][label][ target_class ] += 1 comment_optional_columns = ( "assignee_id", "assignee_team_id", "dependent_issue_id", "label_id", "milestone_id", "old_milestone_id", "old_project_id", "original_author_id", "project_id", "ref_action", "ref_comment_id", "ref_is_pull", "ref_issue_id", "ref_repo_id", "resolve_doer_id", "review_id", "time_id", ) require_columns( "comment", ("id", "issue_id", "original_author", *comment_optional_columns), ) require_extra_columns("team", ("id", "org_id")) comment_select = [ "c.id AS comment_id", "c.issue_id AS source_issue_id", "source.repo_id AS source_repo_id", "typeof(c.id) AS comment_id_type", "typeof(c.issue_id) AS source_issue_id_type", "typeof(source.repo_id) AS source_repo_id_type", ] for column in comment_optional_columns: comment_select.extend( ( f'c."{column}" AS "{column}"', f'typeof(c."{column}") AS "{column}_type"', ) ) comment_select.extend( ( "label_target.id AS label_target_id", "label_target.repo_id AS label_target_repo_id", "old_project.id AS old_project_target_id", "old_project.repo_id AS old_project_target_repo_id", "current_project.id AS project_target_id", "current_project.repo_id AS project_target_repo_id", "old_milestone.id AS old_milestone_target_id", "old_milestone.repo_id AS old_milestone_target_repo_id", "current_milestone.id AS milestone_target_id", "current_milestone.repo_id AS milestone_target_repo_id", "tracked.id AS tracked_target_id", "tracked.issue_id AS tracked_target_issue_id", "team_target.id AS team_target_id", "dependent.id AS dependent_target_id", "dependent.repo_id AS dependent_target_repo_id", "review_target.id AS review_target_id", "review_target.issue_id AS review_target_issue_id", "ref_issue.id AS ref_issue_target_id", "ref_issue.repo_id AS ref_issue_target_repo_id", "ref_issue.is_pull AS ref_issue_target_is_pull", "ref_comment.id AS ref_comment_target_id", "ref_comment.issue_id AS ref_comment_target_issue_id", "team_target.org_id AS team_target_org_id", "typeof(c.original_author) AS original_author_type", "CASE WHEN c.original_author IS NULL THEN 0 " "ELSE length(CAST(c.original_author AS BLOB)) END " "AS original_author_bytes", ) ) comment_relation_rows = fetch_rows( "comment polymorphic subrelations", f"SELECT {','.join(comment_select)} FROM comment AS c " "INNER JOIN issue AS source ON source.id=c.issue_id " "LEFT JOIN label AS label_target ON label_target.id=c.label_id " "LEFT JOIN project AS old_project ON old_project.id=c.old_project_id " "LEFT JOIN project AS current_project ON current_project.id=c.project_id " "LEFT JOIN milestone AS old_milestone " "ON old_milestone.id=c.old_milestone_id " "LEFT JOIN milestone AS current_milestone " "ON current_milestone.id=c.milestone_id " "LEFT JOIN tracked_time AS tracked ON tracked.id=c.time_id " "LEFT JOIN team AS team_target ON team_target.id=c.assignee_team_id " "LEFT JOIN issue AS dependent ON dependent.id=c.dependent_issue_id " "LEFT JOIN review AS review_target ON review_target.id=c.review_id " "LEFT JOIN issue AS ref_issue ON ref_issue.id=c.ref_issue_id " "LEFT JOIN comment AS ref_comment ON ref_comment.id=c.ref_comment_id " f"WHERE source.repo_id IN ({placeholders}) ORDER BY source.repo_id,c.id", kept_repo_ids, ) def optional_comment_integer(row, column, repo_id): value_type = row[f"{column}_type"] value = row[column] if value_type == "null" and value is None: if column == "assignee_team_id": die("Gitea salvage comment assignee-team identity is NULL") subrelation_per_repo[repo_id]["null_encodings"][ f"comment.{column}" ] += 1 return 0 if ( value_type != "integer" or not isinstance(value, int) or isinstance(value, bool) or value < 0 ): die("Gitea salvage comment subrelation value is invalid") return value def validate_scoped_comment_target( repo_id, label, target_id, joined_id, joined_repo_id, allow_global=False, ): if target_id == 0: return if ( joined_id != target_id or not isinstance(joined_repo_id, int) or isinstance(joined_repo_id, bool) or ( joined_repo_id != repo_id and not (allow_global and joined_repo_id == 0) ) ): die(f"Gitea salvage {label} relation is invalid") add_subrelation_count(repo_id, label) add_subrelation_target_repo(repo_id, label, joined_repo_id) seen_comment_relations = set() for row in comment_relation_rows: comment_id = row["comment_id"] issue_id = row["source_issue_id"] repo_id = row["source_repo_id"] if ( ( row["comment_id_type"], row["source_issue_id_type"], row["source_repo_id_type"], ) != ("integer", "integer", "integer") or not all( isinstance(value, int) and not isinstance(value, bool) for value in (comment_id, issue_id, repo_id) ) or comment_id <= 0 or comment_id in seen_comment_relations or comment_to_issue.get(comment_id) != issue_id or issue_to_repo.get(issue_id) != repo_id ): die("Gitea salvage comment subrelation source is invalid") seen_comment_relations.add(comment_id) values = { column: optional_comment_integer(row, column, repo_id) for column in comment_optional_columns } if values["assignee_id"] and values["assignee_team_id"]: die("Gitea salvage comment assignee identities conflict") add_external_author_provenance( repo_id, "comment", values["original_author_id"], row["original_author_id_type"], row["original_author_type"], row["original_author_bytes"], ) validate_scoped_comment_target( repo_id, "comment_label", values["label_id"], row["label_target_id"], row["label_target_repo_id"], allow_global=True, ) for label, column, id_key, repo_key, allow_global in ( ( "comment_old_project", "old_project_id", "old_project_target_id", "old_project_target_repo_id", True, ), ( "comment_current_project", "project_id", "project_target_id", "project_target_repo_id", True, ), ( "comment_old_milestone", "old_milestone_id", "old_milestone_target_id", "old_milestone_target_repo_id", False, ), ( "comment_current_milestone", "milestone_id", "milestone_target_id", "milestone_target_repo_id", False, ), ): validate_scoped_comment_target( repo_id, label, values[column], row[id_key], row[repo_key], allow_global=allow_global, ) time_id = values["time_id"] if time_id: if ( row["tracked_target_id"] != time_id or row["tracked_target_issue_id"] != issue_id or tracked_time_to_issue.get(time_id) != issue_id ): die("Gitea salvage comment tracked-time relation is invalid") add_subrelation_count(repo_id, "comment_tracked_time") for label, column in ( ("comment_assignee", "assignee_id"), ("comment_resolve_doer", "resolve_doer_id"), ): actor_id = values[column] if actor_id: add_subrelation_count(repo_id, label) add_subrelation_actor(repo_id, label, actor_id) team_id = values["assignee_team_id"] if team_id: team_org_id = row["team_target_org_id"] if ( row["team_target_id"] != team_id or not isinstance(team_org_id, int) or isinstance(team_org_id, bool) or team_org_id <= 0 ): die("Gitea salvage comment assignee-team relation is orphaned") add_subrelation_count(repo_id, "comment_assignee_team") subrelation_holds.append( { "kind": "comment-assignee-team-mapping", "old_org_identity_class": ( gitea_salvage_closure_actor_class( team_org_id, user_classes, "comment assignee-team organization", ) ), "old_org_id": team_org_id, "old_repo_id": repo_id, "old_row_id": comment_id, "old_team_id": team_id, "source_table": "comment", } ) dependent_id = values["dependent_issue_id"] if dependent_id: target_repo_id = row["dependent_target_repo_id"] if ( row["dependent_target_id"] != dependent_id or not isinstance(target_repo_id, int) or isinstance(target_repo_id, bool) or target_repo_id not in all_repo_ids ): die("Gitea salvage comment dependent-issue relation is invalid") add_subrelation_count(repo_id, "comment_dependent_issue") add_subrelation_target_repo( repo_id, "comment_dependent_issue", target_repo_id, ) review_id = values["review_id"] if review_id: if ( row["review_target_id"] != review_id or row["review_target_issue_id"] != issue_id or review_to_issue.get(review_id) != issue_id ): die("Gitea salvage comment review relation is invalid") add_subrelation_count(repo_id, "comment_review") ref_repo_id = values["ref_repo_id"] ref_issue_id = values["ref_issue_id"] ref_comment_id = values["ref_comment_id"] ref_action = values["ref_action"] ref_is_pull = values["ref_is_pull"] if ref_action not in (0, 1, 2, 3) or ref_is_pull not in (0, 1): die("Gitea salvage comment cross-reference state is invalid") if ref_repo_id or ref_issue_id or ref_comment_id: if ( ref_repo_id <= 0 or ref_issue_id <= 0 or ref_repo_id not in all_repo_ids or row["ref_issue_target_id"] != ref_issue_id or row["ref_issue_target_repo_id"] != ref_repo_id or row["ref_issue_target_is_pull"] != ref_is_pull ): die("Gitea salvage comment cross-reference relation is invalid") if ref_comment_id and ( row["ref_comment_target_id"] != ref_comment_id or row["ref_comment_target_issue_id"] != ref_issue_id ): die("Gitea salvage comment cross-reference comment is invalid") add_subrelation_count(repo_id, "comment_cross_reference") add_subrelation_target_repo( repo_id, "comment_cross_reference", ref_repo_id, ) if ref_comment_id: add_subrelation_count( repo_id, "comment_cross_reference_comment", ) elif ref_action or ref_is_pull: die("Gitea salvage empty comment cross-reference state is invalid") if seen_comment_relations != set(comment_to_issue): die("Gitea salvage comment subrelation coverage is incomplete") require_columns( "review", ( "id", "issue_id", "reviewer_id", "reviewer_team_id", "original_author_id", "original_author", ), ) review_relation_rows = fetch_rows( "review team and external-author relations", "SELECT r.id,r.issue_id,i.repo_id,r.reviewer_id,r.reviewer_team_id," "r.original_author_id,typeof(r.id),typeof(r.issue_id)," "typeof(i.repo_id),typeof(r.reviewer_team_id)," "typeof(r.reviewer_id),typeof(r.original_author_id)," "team_target.id,team_target.org_id," "typeof(r.original_author),CASE WHEN r.original_author IS NULL THEN 0 " "ELSE length(CAST(r.original_author AS BLOB)) END " "FROM review AS r INNER JOIN issue AS i ON i.id=r.issue_id " "LEFT JOIN team AS team_target ON team_target.id=r.reviewer_team_id " f"WHERE i.repo_id IN ({placeholders}) ORDER BY i.repo_id,r.id", kept_repo_ids, ) seen_review_relations = set() for row in review_relation_rows: ( review_id, issue_id, repo_id, reviewer_id, reviewer_team_id, external_author_id, ) = row[0:6] if ( tuple(row[6:11]) != ("integer", "integer", "integer", "integer", "integer") or row[11] not in ("integer", "null") or not all( isinstance(value, int) and not isinstance(value, bool) for value in ( review_id, issue_id, repo_id, reviewer_id, reviewer_team_id, ) ) or review_id <= 0 or review_id in seen_review_relations or reviewer_team_id < 0 or reviewer_id < 0 or reviewer_id and reviewer_team_id or review_to_issue.get(review_id) != issue_id or issue_to_repo.get(issue_id) != repo_id ): die("Gitea salvage review subrelation source is invalid") seen_review_relations.add(review_id) add_external_author_provenance( repo_id, "review", external_author_id, row[11], row[14], row[15], ) if reviewer_team_id: team_id, team_org_id = row[12:14] if ( team_id != reviewer_team_id or not isinstance(team_org_id, int) or isinstance(team_org_id, bool) or team_org_id <= 0 ): die("Gitea salvage review reviewer-team relation is orphaned") add_subrelation_count(repo_id, "review_reviewer_team") subrelation_holds.append( { "kind": "review-reviewer-team-mapping", "old_org_identity_class": ( gitea_salvage_closure_actor_class( team_org_id, user_classes, "reviewer-team organization", ) ), "old_org_id": team_org_id, "old_repo_id": repo_id, "old_row_id": review_id, "old_team_id": reviewer_team_id, "source_table": "review", } ) if seen_review_relations != set(review_to_issue): die("Gitea salvage review subrelation coverage is incomplete") require_columns( "issue_content_history", ("id", "issue_id", "comment_id"), ) history_relation_rows = fetch_rows( "content-history comment relations", "SELECT h.id,h.issue_id,h.comment_id,i.repo_id,typeof(h.id)," "typeof(h.issue_id),typeof(h.comment_id),typeof(i.repo_id) " "FROM issue_content_history AS h " "INNER JOIN issue AS i ON i.id=h.issue_id " f"WHERE i.repo_id IN ({placeholders}) ORDER BY i.repo_id,h.id", kept_repo_ids, ) seen_history_relations = set() for row in history_relation_rows: history_id, issue_id, comment_id, repo_id = row[0:4] if ( tuple(row[4:6]) != ("integer", "integer") or row[6] not in ("integer", "null") or row[7] != "integer" or not all( isinstance(value, int) and not isinstance(value, bool) for value in (history_id, issue_id, repo_id) ) or history_id <= 0 or history_id in seen_history_relations or issue_to_repo.get(issue_id) != repo_id ): die("Gitea salvage content-history subrelation is invalid") seen_history_relations.add(history_id) if content_history_to_issue.get(history_id) != issue_id: die("Gitea salvage content-history coverage is incomplete") if comment_id is None: subrelation_per_repo[repo_id]["null_encodings"][ "issue_content_history.comment_id" ] += 1 comment_id = 0 if ( not isinstance(comment_id, int) or isinstance(comment_id, bool) or comment_id < 0 ): die("Gitea salvage content-history comment identity is invalid") if comment_id: if comment_to_issue.get(comment_id) != issue_id: die("Gitea salvage content-history comment relation is invalid") add_subrelation_count(repo_id, "content_history_comment") else: add_subrelation_count(repo_id, "content_history_issue") if seen_history_relations != set(content_history_to_issue): die("Gitea salvage content-history subrelation coverage is incomplete") require_columns( "pull_request", ("id", "base_repo_id", "has_merged", "merger_id"), ) merger_rows = fetch_rows( "pull merger relations", "SELECT p.id,p.base_repo_id,p.has_merged,p.merger_id,typeof(p.id)," "typeof(p.base_repo_id),typeof(p.has_merged),typeof(p.merger_id) " "FROM pull_request AS p INNER JOIN issue AS i ON i.id=p.issue_id " f"WHERE i.repo_id IN ({placeholders}) ORDER BY p.base_repo_id,p.id", kept_repo_ids, ) seen_mergers = set() for row in merger_rows: pull_id, repo_id, has_merged, merger_id = row[0:4] if ( tuple(row[4:7]) != ("integer", "integer", "integer") or row[7] not in ("integer", "null") or not all( isinstance(value, int) and not isinstance(value, bool) for value in (pull_id, repo_id, has_merged) ) or pull_id <= 0 or pull_id in seen_mergers or pull_to_repo.get(pull_id) != repo_id or has_merged not in (0, 1) ): die("Gitea salvage pull merger subrelation is invalid") seen_mergers.add(pull_id) if merger_id is None: subrelation_per_repo[repo_id]["null_encodings"][ "pull_request.merger_id" ] += 1 merger_id = 0 if ( not isinstance(merger_id, int) or isinstance(merger_id, bool) or merger_id < 0 or (not has_merged and merger_id != 0) ): die("Gitea salvage pull merger identity is invalid") if has_merged: add_subrelation_count(repo_id, "pull_merger") add_subrelation_actor(repo_id, "pull_merger", merger_id) if seen_mergers != set(pull_to_repo): die("Gitea salvage pull merger subrelation coverage is incomplete") issue_pr_subrelations = { "aggregates": { "actor_classes": { label: { actor_class: sum( record["actor_classes"][label][actor_class] for record in subrelation_per_repo.values() ) for actor_class in ("deleted", "kept", "system-or-external") } for label in GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_ACTORS }, "counts": { label: sum( record["counts"][label] for record in subrelation_per_repo.values() ) for label in GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_COUNTS }, "external_author_provenance": { source: { field: sum( record["external_author_provenance"][source][field] for record in subrelation_per_repo.values() ) for field in ( "id_without_name", "name_bytes", "name_without_id", "rows_with_id", "rows_with_name", ) } for source in GITEA_SALVAGE_CLOSURE_EXTERNAL_AUTHOR_SOURCES }, "null_encodings": { column: sum( record["null_encodings"][column] for record in subrelation_per_repo.values() ) for column in ( GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_NULLABLE_COLUMNS ) }, "target_repository_classes": { label: { target_class: sum( record["target_repository_classes"][label][target_class] for record in subrelation_per_repo.values() ) for target_class in ("deleted", "global", "kept") } for label in ( GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_REPOSITORIES ) }, }, "conditional_hold_blockers": ( ["issue-pr-team-mapping-hold"] if subrelation_holds else [] ), "extra_schema_coverage": [ closure_extra_schema[table] for table in sorted(closure_extra_schema) ], "holds": sorted( subrelation_holds, key=lambda row: ( row["kind"], row["old_repo_id"], row["old_row_id"], row["old_team_id"], ), ), "per_repository": [ subrelation_per_repo[repo_id] for repo_id in kept_repo_ids ], "schema": "nodedc.gitea.salvage-issue-pr-subrelation-closure/v1", } query_contract.extend( ( { "join": ( "comment-optional-identities-to-exact-label-project-" "milestone-time-team-issue-review-and-xref-targets" ), "label": "comment-polymorphic-subrelations", "output": ( "counts-classes-null-encodings-external-author-byte-" "lengths-and-team-hold-ids-only" ), "table": "comment", }, { "join": "issue_content_history.comment_id-to-same-kept-issue", "label": "content-history-comment-relations", "output": "issue-vs-comment-history-counts-only", "table": "issue_content_history", }, { "join": ( "review.reviewer_team_id-to-existing-team-and-external-" "author-presence-classification" ), "label": "review-team-and-external-author-relations", "output": "counts-byte-lengths-and-exact-team-hold-ids-only", "table": "review", }, { "join": "pull_request.merger_id-to-full-user-decision-partition", "label": "pull-merger-relations", "output": "counts-and-actor-classes-no-commit-or-user-payload", "table": "pull_request", }, ) ) require_columns( "pull_auto_merge", ("id", "pull_id", "doer_id", "merge_style", "message"), ) auto_merge_rows = fetch_rows( "pull auto merges", "SELECT a.id,a.pull_id,a.doer_id,p.base_repo_id,typeof(a.id)," "typeof(a.pull_id),typeof(a.doer_id),typeof(p.base_repo_id)," "typeof(a.merge_style),CASE WHEN a.merge_style IS NULL THEN 0 " "ELSE length(CAST(a.merge_style AS BLOB)) END,typeof(a.message)," "CASE WHEN a.message IS NULL THEN 0 ELSE length(CAST(a.message AS BLOB)) END " "FROM pull_auto_merge AS a INNER JOIN pull_request AS p ON p.id=a.pull_id " "INNER JOIN issue AS i ON i.id=p.issue_id " f"WHERE i.repo_id IN ({placeholders}) ORDER BY i.repo_id,a.id", kept_repo_ids, ) seen_auto_merges = set() for row in auto_merge_rows: merge_id, pull_id, doer_id, repo_id = row[0:4] if ( tuple(row[4:8]) != ("integer", "integer", "integer", "integer") or row[8] not in ("null", "text") or row[10] not in ("null", "text") or not all( isinstance(value, int) and not isinstance(value, bool) for value in (merge_id, pull_id, doer_id, repo_id, row[9], row[11]) ) or merge_id <= 0 or merge_id in seen_auto_merges or pull_to_repo.get(pull_id) != repo_id ): die("Gitea salvage closure pull-auto-merge row is invalid") seen_auto_merges.add(merge_id) actor_class = gitea_salvage_closure_actor_class( doer_id, user_classes, "pull auto merge", ) gitea_salvage_closure_add_metric( by_repo_id[repo_id], "pull_auto_merges", actor_class, text_bytes={"merge_style": row[9], "message": row[11]}, ) query_contract.append( { "join": "pull_auto_merge.pull_id-to-kept-pull-wrapper", "label": "pull-auto-merges", "output": "counts-actor-classes-text-byte-lengths-no-payload", "table": "pull_auto_merge", } ) require_columns( "notification", ("id", "user_id", "repo_id", "issue_id", "comment_id"), ) notification_rows = fetch_rows( "notifications", f"SELECT id,user_id,repo_id,issue_id,comment_id,typeof(id)," "typeof(user_id),typeof(repo_id),typeof(issue_id),typeof(comment_id) " f"FROM notification WHERE repo_id IN ({placeholders}) ORDER BY repo_id,id", kept_repo_ids, ) seen_notifications = set() for row in notification_rows: notification_id, user_id, repo_id, issue_id, comment_id = row[0:5] if ( tuple(row[5:8]) != ("integer", "integer", "integer") or row[8] not in ("integer", "null") or row[9] not in ("integer", "null") or not all( value is None or isinstance(value, int) and not isinstance(value, bool) for value in row[0:5] ) or notification_id <= 0 or notification_id in seen_notifications or repo_id not in kept_repo_set or (issue_id or 0) < 0 or (comment_id or 0) < 0 or issue_id and issue_to_repo.get(issue_id) != repo_id or comment_id and comment_to_repo.get(comment_id) != repo_id ): die("Gitea salvage closure notification row is invalid") seen_notifications.add(notification_id) actor_class = gitea_salvage_closure_actor_class( user_id, user_classes, "notification", ) gitea_salvage_closure_add_metric( by_repo_id[repo_id], "notifications", actor_class, ) query_contract.append( { "join": "notification.repo_id-and-optional-kept-issue-comment", "label": "notifications", "output": "counts-and-actor-classes-no-commit-or-content", "table": "notification", } ) require_columns("repo_unit", ("id", "repo_id", "type", "config")) unit_rows = fetch_rows( "repo units", f"SELECT id,repo_id,type,typeof(id),typeof(repo_id),typeof(type)," "typeof(config),CASE WHEN config IS NULL THEN 0 " "ELSE length(CAST(config AS BLOB)) END FROM repo_unit " f"WHERE repo_id IN ({placeholders}) ORDER BY repo_id,type,id", kept_repo_ids, ) unit_types_by_repo = {repo_id: {} for repo_id in kept_repo_ids} seen_units = set() for row in unit_rows: unit_id, repo_id, unit_type = row[0:3] if ( tuple(row[3:6]) != ("integer", "integer", "integer") or row[6] not in ("null", "text") or not all( isinstance(value, int) and not isinstance(value, bool) for value in (unit_id, repo_id, unit_type, row[7]) ) or unit_id <= 0 or unit_id in seen_units or repo_id not in kept_repo_set or not 1 <= unit_type <= 10 or unit_type in unit_types_by_repo[repo_id] ): die("Gitea salvage closure repo-unit row is invalid") seen_units.add(unit_id) unit_types_by_repo[repo_id][str(unit_type)] = 1 gitea_salvage_closure_add_metric( by_repo_id[repo_id], "repo_units", text_bytes={"config": row[7]}, ) for record in per_repository: record["unit_types"] = unit_types_by_repo[record["old_repo_id"]] query_contract.append( { "join": "repo_unit.repo_id-in-exact-kept-set", "label": "repo-units", "output": "type-counts-and-config-byte-length-no-config-payload", "table": "repo_unit", } ) require_columns( "attachment", ( "id", "uuid", "repo_id", "issue_id", "release_id", "uploader_id", "comment_id", "size", ), ) attachment_rows = fetch_rows( "attachment manifest", f"SELECT id,uuid,repo_id,issue_id,release_id,uploader_id,comment_id,size," "typeof(id),typeof(uuid),typeof(repo_id),typeof(issue_id)," "typeof(release_id),typeof(uploader_id),typeof(comment_id),typeof(size) " f"FROM attachment WHERE repo_id IN ({placeholders}) ORDER BY repo_id,id", kept_repo_ids, ) attachment_manifest = [] seen_attachment_ids = set() seen_attachment_uuids = set() attachment_link_classes = { "comment": 0, "issue": 0, "multi-link": 0, "release": 0, "unlinked": 0, } for row in attachment_rows: ( attachment_id, attachment_uuid, repo_id, issue_id, release_id, uploader_id, comment_id, declared_size, ) = row[0:8] if ( row[8] != "integer" or row[9] != "text" or row[10] != "integer" or any(value not in ("integer", "null") for value in row[11:15]) or row[15] != "integer" or not all( value is None or isinstance(value, int) and not isinstance(value, bool) for value in ( attachment_id, repo_id, issue_id, release_id, uploader_id, comment_id, declared_size, ) ) or attachment_id <= 0 or attachment_id in seen_attachment_ids or not isinstance(attachment_uuid, str) or re.fullmatch( r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", attachment_uuid, ) is None or attachment_uuid in seen_attachment_uuids or repo_id not in kept_repo_set or (issue_id or 0) < 0 or (release_id or 0) < 0 or (comment_id or 0) < 0 or declared_size < 0 or declared_size > GITEA_SALVAGE_UNSUPPORTED_SIZE_PER_ROW_MAX_BYTES ): die("Gitea salvage closure attachment manifest row is invalid") links = [] if issue_id: if issue_to_repo.get(issue_id) != repo_id: die("Gitea salvage closure attachment issue relation is invalid") links.append("issue") if release_id: if release_to_repo.get(release_id) != repo_id: die("Gitea salvage closure attachment release relation is invalid") links.append("release") if comment_id: if comment_to_repo.get(comment_id) != repo_id: die("Gitea salvage closure attachment comment relation is invalid") links.append("comment") link_class = ( "unlinked" if not links else "multi-link" if len(links) > 1 else links[0] ) actor_class = gitea_salvage_closure_actor_class( uploader_id, user_classes, "attachment uploader", ) attachment_link_classes[link_class] += 1 seen_attachment_ids.add(attachment_id) seen_attachment_uuids.add(attachment_uuid) attachment_manifest.append( { "content_hash": "unavailable-in-schema", "declared_size": declared_size, "disposition": "PHYSICAL_VERIFY_THEN_SANITIZED_ARCHIVE", "link_class": link_class, "link_ids": { "comment_id": int(comment_id or 0), "issue_id": int(issue_id or 0), "release_id": int(release_id or 0), }, "old_attachment_id": attachment_id, "old_repo_id": repo_id, "uploader_class": actor_class, "uuid": attachment_uuid, } ) query_contract.append( { "join": "attachment.repo-and-exact-issue-release-comment-relations", "label": "attachment-manifest", "output": ( "id-uuid-relations-size-uploader-class-no-name-or-file-bytes" ), "table": "attachment", } ) # Package rows are intentionally reduced to relationship identifiers and # declared blob sizes. Names, versions, metadata_json and property # name/value payloads are never selected because Packages remain disabled. require_columns("package", ("id", "repo_id", "owner_id")) package_rows = fetch_rows( "packages", f"SELECT id,repo_id,owner_id,typeof(id),typeof(repo_id),typeof(owner_id) " f"FROM package WHERE repo_id IN ({placeholders}) ORDER BY repo_id,id", kept_repo_ids, ) package_to_repo = {} for row in package_rows: package_id, repo_id, owner_id = row[0:3] if ( tuple(row[3:6]) != ("integer", "integer", "integer") or not all( isinstance(value, int) and not isinstance(value, bool) for value in (package_id, repo_id, owner_id) ) or package_id <= 0 or package_id in package_to_repo or repo_id not in kept_repo_set or owner_id <= 0 ): die("Gitea salvage closure package row is invalid") package_to_repo[package_id] = repo_id gitea_salvage_closure_add_metric(by_repo_id[repo_id], "packages") query_contract.append( { "join": "package.repo_id-in-exact-kept-set", "label": "packages", "output": "counts-and-relationship-ids-no-name-or-metadata", "table": "package", } ) require_columns("package_version", ("id", "package_id", "creator_id")) package_version_rows = fetch_rows( "package versions", "SELECT v.id,v.package_id,v.creator_id,p.repo_id,typeof(v.id)," "typeof(v.package_id),typeof(v.creator_id),typeof(p.repo_id) " "FROM package_version AS v INNER JOIN package AS p ON p.id=v.package_id " f"WHERE p.repo_id IN ({placeholders}) ORDER BY p.repo_id,v.id", kept_repo_ids, ) package_version_to_repo = {} for row in package_version_rows: version_id, package_id, creator_id, repo_id = row[0:4] if ( tuple(row[4:8]) != ("integer", "integer", "integer", "integer") or not all( isinstance(value, int) and not isinstance(value, bool) for value in (version_id, package_id, creator_id, repo_id) ) or version_id <= 0 or version_id in package_version_to_repo or package_to_repo.get(package_id) != repo_id ): die("Gitea salvage closure package-version row is invalid") package_version_to_repo[version_id] = repo_id actor_class = gitea_salvage_closure_actor_class( creator_id, user_classes, "package version creator", ) gitea_salvage_closure_add_metric( by_repo_id[repo_id], "package_versions", actor_class, ) query_contract.append( { "join": "package_version.package_id-to-kept-package", "label": "package-versions", "output": "counts-and-creator-class-no-version-or-metadata", "table": "package_version", } ) require_columns("package_file", ("id", "version_id", "blob_id")) package_file_rows = fetch_rows( "package files", "SELECT f.id,f.version_id,f.blob_id,p.repo_id,typeof(f.id)," "typeof(f.version_id),typeof(f.blob_id),typeof(p.repo_id) " "FROM package_file AS f " "INNER JOIN package_version AS v ON v.id=f.version_id " "INNER JOIN package AS p ON p.id=v.package_id " f"WHERE p.repo_id IN ({placeholders}) ORDER BY p.repo_id,f.id", kept_repo_ids, ) package_file_to_repo = {} package_file_blob = {} repo_blob_ids = {repo_id: set() for repo_id in kept_repo_ids} for row in package_file_rows: file_id, version_id, blob_id, repo_id = row[0:4] if ( tuple(row[4:8]) != ("integer", "integer", "integer", "integer") or not all( isinstance(value, int) and not isinstance(value, bool) for value in (file_id, version_id, blob_id, repo_id) ) or file_id <= 0 or file_id in package_file_to_repo or blob_id <= 0 or package_version_to_repo.get(version_id) != repo_id ): die("Gitea salvage closure package-file row is invalid") package_file_to_repo[file_id] = repo_id package_file_blob[file_id] = blob_id repo_blob_ids[repo_id].add(blob_id) gitea_salvage_closure_add_metric(by_repo_id[repo_id], "package_files") query_contract.append( { "join": "package_file.version_id-to-kept-package-version", "label": "package-files", "output": "counts-and-blob-relationship-ids-no-file-name", "table": "package_file", } ) require_columns("package_blob", ("id", "size")) all_package_blob_ids = sorted( {blob_id for values in repo_blob_ids.values() for blob_id in values} ) package_blob_sizes = {} if all_package_blob_ids: blob_placeholders = ",".join("?" for _ in all_package_blob_ids) package_blob_rows = fetch_rows( "package blobs", f"SELECT id,size,typeof(id),typeof(size) FROM package_blob " f"WHERE id IN ({blob_placeholders}) ORDER BY id", tuple(all_package_blob_ids), ) for blob_id, blob_size, id_type, size_type in package_blob_rows: if ( (id_type, size_type) != ("integer", "integer") or not isinstance(blob_id, int) or isinstance(blob_id, bool) or not isinstance(blob_size, int) or isinstance(blob_size, bool) or blob_id <= 0 or blob_id in package_blob_sizes or blob_size < 0 or blob_size > GITEA_SALVAGE_UNSUPPORTED_SIZE_PER_ROW_MAX_BYTES ): die("Gitea salvage closure package-blob row is invalid") package_blob_sizes[blob_id] = blob_size if set(package_blob_sizes) != set(all_package_blob_ids): die("Gitea salvage closure package blob relation is incomplete") for repo_id in kept_repo_ids: for blob_id in sorted(repo_blob_ids[repo_id]): gitea_salvage_closure_add_metric( by_repo_id[repo_id], "package_blobs", logical_bytes=package_blob_sizes[blob_id], ) query_contract.append( { "join": "package_blob.id-to-distinct-kept-package-file-blob-per-repository", "label": "package-blobs", "output": "association-counts-and-declared-logical-bytes-no-hashes", "table": "package_blob", } ) require_columns("package_property", ("id", "ref_type", "ref_id")) property_targets = { 0: package_version_to_repo, 1: package_file_to_repo, 2: package_to_repo, } all_property_target_ids = sorted( { target_id for target_map in property_targets.values() for target_id in target_map } ) if all_property_target_ids: target_placeholders = ",".join("?" for _ in all_property_target_ids) invalid_property_types = fetch_rows( "package property reference types", f"SELECT COUNT(*) FROM package_property WHERE " f"ref_id IN ({target_placeholders}) AND (" "typeof(ref_type) != 'integer' OR ref_type NOT IN (0,1,2))", tuple(all_property_target_ids), )[0][0] if ( not isinstance(invalid_property_types, int) or isinstance(invalid_property_types, bool) or invalid_property_types != 0 ): die("Gitea salvage closure package-property type is invalid") property_clauses = [] property_parameters = [] for ref_type in (0, 1, 2): target_ids = sorted(property_targets[ref_type]) if not target_ids: continue property_clauses.append( f"(ref_type=? AND ref_id IN ({','.join('?' for _ in target_ids)}))" ) property_parameters.extend((ref_type, *target_ids)) package_property_rows = [] if property_clauses: package_property_rows = fetch_rows( "package properties", "SELECT id,ref_type,ref_id,typeof(id),typeof(ref_type)," "typeof(ref_id) FROM package_property WHERE " + " OR ".join(property_clauses) + " ORDER BY id", tuple(property_parameters), ) seen_properties = set() for row in package_property_rows: property_id, ref_type, ref_id = row[0:3] if ( tuple(row[3:6]) != ("integer", "integer", "integer") or not all( isinstance(value, int) and not isinstance(value, bool) for value in (property_id, ref_type, ref_id) ) or property_id <= 0 or property_id in seen_properties or ref_type not in property_targets or ref_id not in property_targets[ref_type] ): die("Gitea salvage closure package-property row is invalid") seen_properties.add(property_id) repo_id = property_targets[ref_type][ref_id] gitea_salvage_closure_add_metric( by_repo_id[repo_id], "package_properties", ) query_contract.append( { "join": ( "package_property.ref_type-0-version-1-file-2-package-" "to-exact-kept-package-closure" ), "label": "package-properties", "output": "counts-and-reference-types-no-property-name-or-value", "table": "package_property", } ) # Actions are disabled in the target. The report therefore inventories # only relationship/count/size metadata and never selects event payloads, # workflow content, variables, secrets, runner tokens or task logs. action_run_to_repo = {} def inventory_direct_action_table( label, table, extra_columns=(), actor_column=None, logical_byte_columns=(), declared_numeric_columns=(), ): required = ("id", "repo_id", *extra_columns) require_columns(table, required) select_columns = ["id", "repo_id", *extra_columns] select_parts = [ *[f'"{column}"' for column in select_columns], *[f'typeof("{column}") AS "{column}_type"' for column in select_columns], ] rows = fetch_rows( label, f'SELECT {",".join(select_parts)} FROM "{table}" ' f"WHERE repo_id IN ({placeholders}) ORDER BY repo_id,id", kept_repo_ids, ) seen_ids = set() normalized_rows = [] for row in rows: values = {column: row[column] for column in select_columns} if any(row[f"{column}_type"] != "integer" for column in select_columns): die(f"Gitea salvage closure {label} row type is invalid") if any( not isinstance(value, int) or isinstance(value, bool) for value in values.values() ): die(f"Gitea salvage closure {label} row is invalid") relation_id = values["id"] repo_id = values["repo_id"] if ( relation_id <= 0 or relation_id in seen_ids or repo_id not in kept_repo_set ): die(f"Gitea salvage closure {label} row is invalid") seen_ids.add(relation_id) actor_class = None if actor_column is not None: actor_class = gitea_salvage_closure_actor_class( values[actor_column], user_classes, label, ) logical_bytes = 0 numeric_values = {} for column in declared_numeric_columns: value = values[column] if ( value < 0 or value > GITEA_SALVAGE_UNSUPPORTED_SIZE_PER_ROW_MAX_BYTES ): die(f"Gitea salvage closure {label} declared value is invalid") numeric_values[column] = value for column in logical_byte_columns: value = values[column] logical_bytes += value if logical_bytes > GITEA_SALVAGE_UNSUPPORTED_SIZE_PER_ROW_MAX_BYTES: die(f"Gitea salvage closure {label} size exceeds the row limit") gitea_salvage_closure_add_metric( by_repo_id[repo_id], label, actor_class, logical_bytes=logical_bytes, numeric_values=numeric_values, ) normalized_rows.append(values) query_contract.append( { "join": f"{table}.repo_id-in-exact-kept-set", "label": label.replace("_", "-"), "output": ( "counts-actor-class-and-declared-size-metadata-only-" "no-action-payload-secret-token-or-log" ), "table": table, } ) return normalized_rows for values in inventory_direct_action_table( "action_runs", "action_run", ("trigger_user_id",), actor_column="trigger_user_id", ): action_run_to_repo[values["id"]] = values["repo_id"] inventory_direct_action_table( "action_schedules", "action_schedule", ("trigger_user_id",), actor_column="trigger_user_id", ) inventory_direct_action_table("action_runners", "action_runner") inventory_direct_action_table("action_variables", "action_variable") inventory_direct_action_table("action_secrets", "secret") artifact_rows = inventory_direct_action_table( "action_artifacts", "action_artifact", ("run_id", "file_size", "file_compressed_size"), logical_byte_columns=("file_size",), declared_numeric_columns=("file_size", "file_compressed_size"), ) for values in artifact_rows: run_id = values["run_id"] if run_id <= 0 or action_run_to_repo.get(run_id) != values["repo_id"]: die("Gitea salvage closure action-artifact run relation is invalid") if action_run_to_repo: action_run_ids = sorted(action_run_to_repo) action_run_placeholders = ",".join("?" for _ in action_run_ids) for table in ("action_artifact", "action_run_job"): missed = fetch_rows( f"{table} indirect rows", f'SELECT COUNT(*) FROM "{table}" WHERE ' f"run_id IN ({action_run_placeholders}) " f"AND (typeof(repo_id) != 'integer' " f"OR repo_id NOT IN ({placeholders}))", (*action_run_ids, *kept_repo_ids), )[0][0] if not isinstance(missed, int) or isinstance(missed, bool) or missed: die(f"Gitea salvage closure {table} indirect relation is invalid") action_job_to_repo = {} job_rows = inventory_direct_action_table( "action_run_jobs", "action_run_job", ("run_id",), ) for values in job_rows: run_id = values["run_id"] if run_id <= 0 or action_run_to_repo.get(run_id) != values["repo_id"]: die("Gitea salvage closure action-job run relation is invalid") action_job_to_repo[values["id"]] = values["repo_id"] task_rows = inventory_direct_action_table( "action_tasks", "action_task", ("job_id", "log_length", "log_size"), logical_byte_columns=("log_size",), declared_numeric_columns=("log_length", "log_size"), ) for values in task_rows: job_id = values["job_id"] if ( job_id <= 0 or action_job_to_repo.get(job_id) != values["repo_id"] or values["log_length"] < 0 ): die("Gitea salvage closure action-task job relation is invalid") if action_job_to_repo: action_job_ids = sorted(action_job_to_repo) action_job_placeholders = ",".join("?" for _ in action_job_ids) missed = fetch_rows( "action task indirect rows", f"SELECT COUNT(*) FROM action_task WHERE " f"job_id IN ({action_job_placeholders}) " f"AND (typeof(repo_id) != 'integer' " f"OR repo_id NOT IN ({placeholders}))", (*action_job_ids, *kept_repo_ids), )[0][0] if not isinstance(missed, int) or isinstance(missed, bool) or missed: die("Gitea salvage closure action_task indirect relation is invalid") require_columns("action_run_index", ("group_id", "max_index")) action_index_rows = fetch_rows( "action run indexes", f"SELECT group_id,max_index,typeof(group_id),typeof(max_index) " f"FROM action_run_index WHERE group_id IN ({placeholders}) " "ORDER BY group_id", kept_repo_ids, ) seen_action_indexes = set() for group_id, max_index, group_type, max_type in action_index_rows: if ( (group_type, max_type) != ("integer", "integer") or not isinstance(group_id, int) or isinstance(group_id, bool) or not isinstance(max_index, int) or isinstance(max_index, bool) or group_id not in kept_repo_set or group_id in seen_action_indexes or max_index < 0 ): die("Gitea salvage closure action-run-index row is invalid") seen_action_indexes.add(group_id) gitea_salvage_closure_add_metric( by_repo_id[group_id], "action_run_indexes", ) query_contract.append( { "join": ( "action_run_index.group_id-as-gitea-resource-index-" "repository-id-in-exact-kept-set" ), "label": "action-run-indexes", "output": "counts-only-no-action-payload", "table": "action_run_index", } ) metric_aggregates = {} for label in GITEA_SALVAGE_CLOSURE_METRICS: aggregate = gitea_salvage_closure_metric_template(label) for record in per_repository: metric = record["closure"][label] aggregate["rows"] += metric["rows"] aggregate["logical_bytes"] += metric["logical_bytes"] for actor_class, count in metric["actor_classes"].items(): aggregate["actor_classes"][actor_class] += count for column, value in metric["numeric_totals"].items(): aggregate["numeric_totals"][column] += value for column, byte_count in metric["text_bytes"].items(): aggregate["text_bytes"][column] += byte_count if aggregate["logical_bytes"] > GITEA_SALVAGE_UNSUPPORTED_SIZE_TOTAL_MAX_BYTES: die("Gitea salvage closure aggregate byte count exceeds the limit") if any( value > GITEA_SALVAGE_UNSUPPORTED_SIZE_TOTAL_MAX_BYTES for value in aggregate["text_bytes"].values() ): die("Gitea salvage closure aggregate text-byte count exceeds the limit") if any( value > GITEA_SALVAGE_UNSUPPORTED_SIZE_TOTAL_MAX_BYTES for value in aggregate["numeric_totals"].values() ): die("Gitea salvage closure aggregate numeric total exceeds the limit") metric_aggregates[label] = aggregate actor_relation_counts = { relation: { "deleted": sum( row["actor_class"] == "deleted" for row in actor_relations if row["relation"] == relation ), "kept": sum( row["actor_class"] == "kept" for row in actor_relations if row["relation"] == relation ), "rows": sum(row["relation"] == relation for row in actor_relations), } for relation, _table, _disposition in GITEA_SALVAGE_CLOSURE_ACTOR_RELATIONS } unit_type_counts = { str(unit_type): sum( record["unit_types"].get(str(unit_type), 0) for record in per_repository ) for unit_type in range(1, 11) } issue_state_totals = { state: sum(record["issue_states"][state] for record in per_repository) for state in ( "ordinary_closed", "ordinary_open", "pull_wrapper_closed", "pull_wrapper_open", ) } pull_state_totals = { state: sum(record["pull_states"][state] for record in per_repository) for state in ("merged", "unmerged") } direct_counts = (report.get("aggregates") or {}).get("direct_relation_counts") attachment_aggregate = (report.get("aggregates") or {}).get("attachments") cross_checks = { "access_grants": actor_relation_counts["access_cache"]["rows"], "action_runners": metric_aggregates["action_runners"]["rows"], "action_runs": metric_aggregates["action_runs"]["rows"], "action_schedules": metric_aggregates["action_schedules"]["rows"], "action_secrets": metric_aggregates["action_secrets"]["rows"], "action_variables": metric_aggregates["action_variables"]["rows"], "attachments": len(attachment_manifest), "collaborators": actor_relation_counts["collaborations"]["rows"], "issues": ( metric_aggregates["issues_ordinary"]["rows"] + metric_aggregates["pull_request_wrappers"]["rows"] ), "labels": metric_aggregates["labels"]["rows"], "milestones": metric_aggregates["milestones"]["rows"], "packages": metric_aggregates["packages"]["rows"], "pull_requests_base": len(pull_to_repo), "releases": metric_aggregates["releases"]["rows"], } if not isinstance(direct_counts, dict) or any( direct_counts.get(label) != value for label, value in cross_checks.items() ): die("Gitea salvage closure predecessor count cross-check failed") if ( direct_counts.get("pull_requests_head") != len(pull_to_repo) or sum(pull_head_partitions.values()) != len(pull_to_repo) or not isinstance(attachment_aggregate, dict) or attachment_aggregate.get("association_rows") != len(attachment_manifest) or attachment_aggregate.get("logical_bytes") != sum(row["declared_size"] for row in attachment_manifest) ): die("Gitea salvage closure predecessor relation cross-check failed") attachment_summary = { "declared_logical_bytes": sum( row["declared_size"] for row in attachment_manifest ), "link_classes": attachment_link_classes, "physical_presence": "not-inventoried", "rows": len(attachment_manifest), "unique_uuids": len(seen_attachment_uuids), } closure_report = { "actor_relation_counts": actor_relation_counts, "actor_relations": actor_relations, "aggregates": metric_aggregates, "attachment_manifest": attachment_manifest, "attachment_summary": attachment_summary, "coverage": { "access_collaboration": ( "exact-id-mode-and-full-kept-deleted-user-partition" ), "actions": ( "relationship-count-size-metadata-only-no-payload-" "target-disabled" ), "attachments": ( "database-identifier-relation-size-manifest-only-" "physical-verifier-pending" ), "issues_pull_requests": ( "schema-bound-primary-dependent-and-concrete-polymorphic-" "subrelations-counts-classes-text-byte-lengths-only" ), "packages": ( "relationship-count-size-metadata-only-no-payload-" "target-disabled" ), "projects_releases_labels_units": ( "relationship-counts-text-byte-lengths-and-unit-types-only" ), }, "cross_repo_issue_dependencies": cross_repo_dependencies, "database_sha256": GITEA_SALVAGE_SNAPSHOT_DATABASE_SHA256, "decision_manifest_sha256": GITEA_SALVAGE_DECISION_MANIFEST_SHA256, "kept_repository_ids": kept_repo_ids, "issue_state_totals": issue_state_totals, "issue_pr_subrelations": issue_pr_subrelations, "per_repository": per_repository, "privacy_contract": { "attachment_identifier": "strict-lowercase-uuid-only", "payload_values_selected": False, "prohibited_exports": [ "action-event-or-workflow-payload", "action-log-or-storage-path", "email-password-token-key-or-secret", "issue-comment-review-release-or-project-text", "package-name-version-metadata-hash-property-name-or-value", ], "text_evidence": "sqlite-byte-lengths-only", }, "pull_head_repository_partition": pull_head_partitions, "pull_state_totals": pull_state_totals, "query_contract": sorted(query_contract, key=lambda item: item["label"]), "remaining_blockers": sorted( [ "attachment-physical-verifier-pending", "closure-report-review-pin-pending", "collaboration-kept-user-mapping-verifier-pending", "issue-pr-metadata-sanitized-archive-verifier-pending", "package-action-physical-closure-verifier-pending", "target-unit-policy-acceptance-pending", ] + issue_pr_subrelations["conditional_hold_blockers"] ), "schema": "nodedc.gitea.salvage-closure-inventory/v1", "schema_catalog_sha256": report["schema_catalog_sha256"], "schema_coverage": [ { "columns": sorted(columns), "table": table, } for table, columns in sorted(required_schema.items()) ], "scope": { "all_repositories": len(all_repo_ids), "all_users": len(user_classes), "deleted_repositories": len(all_repo_ids) - len(kept_repo_ids), "deleted_users": sum( value == "deleted" for value in user_classes.values() ), "kept_repositories": len(kept_repo_ids), "kept_users": sum(value == "kept" for value in user_classes.values()), }, "snapshot_uuid": GITEA_SALVAGE_SNAPSHOT_UUID, "source_evidence": { "predecessor_artifact_sha256": ( GITEA_SALVAGE_CLOSURE_PREDECESSOR_ARTIFACT_SHA256 ), "predecessor_disposition_sha256": ( GITEA_SALVAGE_CLOSURE_PREDECESSOR_DISPOSITION_SHA256 ), "reference_manifest_sha256": ( GITEA_SALVAGE_DISPOSITION_REFERENCE_MANIFEST_SHA256 ), "semantic_topics_sha256": topics["sha256"], "unsupported_report_sha256": unsupported["sha256"], "unsupported_schema_sha256": report["schema_catalog_sha256"], }, "target_policy": { "access_cache": "RESET_AND_RECOMPUTE", "actions": "DISABLED_DROP_ALL_LEGACY_ROWS_AND_PHYSICAL_STATE", "attachments": "SANITIZED_ARCHIVE_ONLY_AFTER_PHYSICAL_VERIFIER", "collaborations": "RECREATE_EXACT_KEPT_ACTORS_AFTER_ID_MAP", "issues_pull_requests_and_dependents": "SANITIZED_ARCHIVE_ONLY", "packages": "DISABLED_DROP_ALL_LEGACY_ROWS_AND_PHYSICAL_STATE", "repository_counters_watches_stars": "RESET_AND_RECOMPUTE", "secrets_integrations_credentials": "IMPORT_ZERO", }, "unit_type_counts": unit_type_counts, } canonical = canonical_gitea_salvage_evidence( closure_report, "incident closure report", ) return {"report": closure_report, **canonical} def gitea_salvage_refname_is_safe(refname): try: encoded_refname = refname.encode("utf-8") if isinstance(refname, str) else b"" except UnicodeEncodeError: return False if ( not isinstance(refname, str) or not refname.startswith("refs/") or len(encoded_refname) > 512 or refname.endswith(("/", ".")) or ".." in refname or "@{" in refname or "//" in refname or any(char in refname for char in " ~^:?*[\\") or any(ord(char) < 0x20 or ord(char) == 0x7F for char in refname) ): return False parts = refname.split("/") return all( part and not part.startswith(".") and not part.endswith(".lock") for part in parts ) def gitea_salvage_file_has_nocow(path): flags = array.array("I", [0]) try: descriptor = os.open(path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)) try: fcntl.ioctl(descriptor, GITEA_SALVAGE_FS_IOC_GETFLAGS, flags, True) finally: os.close(descriptor) except OSError: die(f"Gitea salvage cannot attest file flags: {path}") return bool(flags[0] & GITEA_SALVAGE_FS_NOCOW_FL) def read_gitea_salvage_ref_file(path, label): try: path_stat = path.lstat() raw = path.read_bytes() except (FileNotFoundError, OSError): die(f"Gitea salvage {label} is unreadable") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or path_stat.st_nlink != 1 or len(raw) > 1024 or b"\x00" in raw ): die(f"Gitea salvage {label} is unsafe") try: return raw.decode("ascii") except UnicodeDecodeError: die(f"Gitea salvage {label} is not ASCII") def gitea_salvage_walk_error(label): def fail(error): error_number = getattr(error, "errno", None) if not isinstance(error_number, int): error_number = "unknown" die(f"Gitea salvage {label} traversal failed: errno={error_number}") return fail def inventory_gitea_salvage_bare_repository( path, relative_path, trusted_device, mountpoints, ): try: repo_stat = path.lstat() except FileNotFoundError: return None validate_gitea_salvage_internal_entry( path, repo_stat, trusted_device, mountpoints, stat.S_ISDIR, f"repository source {relative_path}", ) objects = path / "objects" objects_stat = probe_gitea_salvage_path_no_follow( path, "objects", trusted_device, mountpoints, f"repository objects root {relative_path}", final_mode=stat.S_ISDIR, ) if objects_stat is None: die(f"Gitea salvage repository objects are missing: {relative_path}") loose_refs_root = path / "refs" loose_root_stat = probe_gitea_salvage_path_no_follow( path, "refs", trusted_device, mountpoints, f"repository loose refs root {relative_path}", final_mode=stat.S_ISDIR, ) repository_info = path / "info" repository_info_stat = probe_gitea_salvage_path_no_follow( path, "info", trusted_device, mountpoints, f"repository info root {relative_path}", final_mode=stat.S_ISDIR, ) forbidden_probes = ( (objects, "info/alternates"), (objects, "info/http-alternates"), (path, "shallow"), (repository_info, "grafts") if repository_info_stat is not None else None, (loose_refs_root, "replace") if loose_root_stat is not None else None, (path, "worktrees"), (path, "commondir"), ) for probe in forbidden_probes: if probe is None: continue probe_root, forbidden = probe if probe_gitea_salvage_path_no_follow( probe_root, forbidden, trusted_device, mountpoints, f"repository forbidden path {relative_path}", ) is not None: displayed = (probe_root / forbidden).relative_to(path).as_posix() die( "Gitea salvage forbidden Git material present: " f"{relative_path}/{displayed}" ) object_files = 0 object_bytes = 0 oid_lengths = set() pack_members = {} pack_bitmaps = set() excluded_derived_files = [] excluded_quarantine_directories = [] for root, directories, files in os.walk( objects, topdown=True, onerror=gitea_salvage_walk_error( f"repository objects {relative_path}" ), followlinks=False, ): root_path = Path(root) root_stat = root_path.lstat() validate_gitea_salvage_internal_entry( root_path, root_stat, trusted_device, mountpoints, stat.S_ISDIR, f"repository object directory {relative_path}", ) directories.sort() files.sort() for name in tuple(directories): child = root_path / name child_stat = child.lstat() child_relative = child.relative_to(objects).as_posix() validate_gitea_salvage_internal_entry( child, child_stat, trusted_device, mountpoints, stat.S_ISDIR, f"repository object directory {relative_path}", ) if ( root_path == objects and re.fullmatch( r"tmp_objdir-incoming-[A-Za-z0-9]{6}", name, ) ): if ( stat.S_IMODE(child_stat.st_mode) != 0o755 or child_stat.st_uid != 1000 or child_stat.st_gid != 1000 or child_stat.st_nlink != 1 or gitea_salvage_file_has_nocow(child) ): die( "Gitea salvage receive quarantine directory is unsafe: " f"{relative_path}/objects/{child_relative}" ) excluded_quarantine_directories.append( { "kind": "receive-pack-quarantine", "lstat": { "gid": child_stat.st_gid, "mode": f"{stat.S_IMODE(child_stat.st_mode):04o}", "nlink": child_stat.st_nlink, "size": child_stat.st_size, "uid": child_stat.st_uid, }, "path": f"objects/{child_relative}", } ) directories.remove(name) continue if ( child_relative not in {"info", "pack"} and re.fullmatch(r"[a-f0-9]{2}", child_relative) is None ): die( "Gitea salvage unexpected object directory: " f"{relative_path}/objects/{child_relative}" ) for name in files: child = root_path / name child_stat = child.lstat() validate_gitea_salvage_internal_entry( child, child_stat, trusted_device, mountpoints, stat.S_ISREG, f"repository object file {relative_path}", ) if ( child_stat.st_nlink != 1 or gitea_salvage_file_has_nocow(child) ): die(f"Gitea salvage object file is unsafe: {child}") relative = child.relative_to(objects).as_posix() if relative == "info/packs": if child_stat.st_size > GITEA_SALVAGE_DERIVED_INFO_PACKS_MAX_BYTES: die( "Gitea salvage derived object cache is oversized: " f"{relative_path}/objects/{relative}" ) excluded_derived_files.append( { "bytes": child_stat.st_size, "kind": "dumb-http-pack-list", "path": "objects/info/packs", } ) continue if relative == "info/commit-graph": if child_stat.st_size > GITEA_SALVAGE_DERIVED_COMMIT_GRAPH_MAX_BYTES: die( "Gitea salvage derived object cache is oversized: " f"{relative_path}/objects/{relative}" ) excluded_derived_files.append( { "bytes": child_stat.st_size, "kind": "commit-graph", "path": "objects/info/commit-graph", } ) continue bitmap = re.fullmatch( r"pack/pack-([a-f0-9]{40}|[a-f0-9]{64})\.bitmap", relative, ) if bitmap: if child_stat.st_size > GITEA_SALVAGE_DERIVED_PACK_BITMAP_MAX_BYTES: die( "Gitea salvage derived object cache is oversized: " f"{relative_path}/objects/{relative}" ) digest = bitmap.group(1) pack_bitmaps.add(digest) excluded_derived_files.append( { "bytes": child_stat.st_size, "kind": "pack-bitmap", "path": f"objects/{relative}", } ) continue loose = re.fullmatch(r"([a-f0-9]{2})/([a-f0-9]{38}|[a-f0-9]{62})", relative) pack = re.fullmatch( r"pack/pack-([a-f0-9]{40}|[a-f0-9]{64})\." r"(pack|idx)", relative, ) if loose: oid_lengths.add(len(loose.group(1) + loose.group(2))) elif pack: oid_lengths.add(len(pack.group(1))) pack_members.setdefault(pack.group(1), set()).add(pack.group(2)) elif re.fullmatch( r"pack/pack-([a-f0-9]{40}|[a-f0-9]{64})\.promisor", relative, ): die( "Gitea salvage promisor object material is forbidden: " f"{relative_path}/objects/{relative}" ) else: die( "Gitea salvage unexpected object material: " f"{relative_path}/objects/{relative}" ) object_files += 1 object_bytes += child_stat.st_size orphan_pack_bitmaps = sorted( digest for digest in pack_bitmaps if pack_members.get(digest) != {"pack", "idx"} ) if orphan_pack_bitmaps: die( "Gitea salvage pack bitmap lacks a complete pack/index pair: " f"{relative_path}/{orphan_pack_bitmaps[0]}" ) incomplete_packs = sorted( digest for digest, members in pack_members.items() if members != {"pack", "idx"} ) if incomplete_packs: die( "Gitea salvage incomplete pack/index pair: " f"{relative_path}/{incomplete_packs[0]}" ) if len(oid_lengths) > 1 or oid_lengths - {40, 64}: die(f"Gitea salvage mixed/invalid object format: {relative_path}") oid_length = next(iter(oid_lengths), 40) oid_pattern = re.compile(rf"[a-f0-9]{{{oid_length}}}") refs = {} packed_refs = path / "packed-refs" try: packed_refs_stat = packed_refs.lstat() except FileNotFoundError: packed_refs_stat = None if packed_refs_stat is not None: validate_gitea_salvage_internal_entry( packed_refs, packed_refs_stat, trusted_device, mountpoints, stat.S_ISREG, f"repository packed refs {relative_path}", ) packed = read_gitea_salvage_ref_file(packed_refs, f"{relative_path} packed-refs") previous_ref = None for line in packed.splitlines(): if not line or line.startswith("#"): continue if line.startswith("^"): if previous_ref is None or oid_pattern.fullmatch(line[1:]) is None: die(f"Gitea salvage packed-refs peeled line is invalid: {relative_path}") continue fields = line.split(" ") if ( len(fields) != 2 or oid_pattern.fullmatch(fields[0]) is None or not gitea_salvage_refname_is_safe(fields[1]) or fields[1] in refs ): die(f"Gitea salvage packed ref is invalid: {relative_path}") refs[fields[1]] = fields[0] previous_ref = fields[1] if loose_root_stat is not None: validate_gitea_salvage_internal_entry( loose_refs_root, loose_root_stat, trusted_device, mountpoints, stat.S_ISDIR, f"repository loose refs root {relative_path}", ) for root, directories, files in os.walk( loose_refs_root, topdown=True, onerror=gitea_salvage_walk_error( f"repository refs {relative_path}" ), followlinks=False, ): root_path = Path(root) root_stat = root_path.lstat() validate_gitea_salvage_internal_entry( root_path, root_stat, trusted_device, mountpoints, stat.S_ISDIR, f"repository loose ref directory {relative_path}", ) directories.sort() files.sort() for name in directories: directory_path = root_path / name child_stat = directory_path.lstat() validate_gitea_salvage_internal_entry( directory_path, child_stat, trusted_device, mountpoints, stat.S_ISDIR, f"repository loose ref directory {relative_path}", ) for name in files: ref_path = root_path / name ref_path_stat = ref_path.lstat() validate_gitea_salvage_internal_entry( ref_path, ref_path_stat, trusted_device, mountpoints, stat.S_ISREG, f"repository loose ref file {relative_path}", ) refname = ref_path.relative_to(path).as_posix() value = read_gitea_salvage_ref_file( ref_path, f"{relative_path} loose ref", ).strip() if ( not gitea_salvage_refname_is_safe(refname) or oid_pattern.fullmatch(value) is None ): die(f"Gitea salvage loose ref is invalid: {relative_path}/{refname}") refs[refname] = value head_path = path / "HEAD" try: head_stat = head_path.lstat() except FileNotFoundError: die(f"Gitea salvage {relative_path} HEAD is unreadable") validate_gitea_salvage_internal_entry( head_path, head_stat, trusted_device, mountpoints, stat.S_ISREG, f"repository HEAD {relative_path}", ) head_raw = read_gitea_salvage_ref_file(head_path, f"{relative_path} HEAD") head_match = re.fullmatch(r"ref: (refs/[A-Za-z0-9._/-]+)\n?", head_raw) if head_match is None or not gitea_salvage_refname_is_safe(head_match.group(1)): die(f"Gitea salvage HEAD is not a safe symbolic ref: {relative_path}") manifest_refs = [ {"name": name, "oid": oid} for name, oid in sorted(refs.items()) ] return { "relative_path": relative_path, "head": head_match.group(1), "object_format": "sha1" if oid_length == 40 else "sha256", "object_files": object_files, "object_bytes": object_bytes, "excluded_derived_files": sorted( excluded_derived_files, key=lambda item: item["path"], ), "excluded_derived_bytes": sum( item["bytes"] for item in excluded_derived_files ), "excluded_quarantine_directories": sorted( excluded_quarantine_directories, key=lambda item: item["path"], ), "refs": manifest_refs, } def inventory_gitea_salvage_repository_refs(decisions): repositories = [] mountpoints = gitea_salvage_mountpoints() repository_root_stat = GITEA_SALVAGE_SNAPSHOT_REPOSITORIES.lstat() trusted_device = repository_root_stat.st_dev for row in decisions["kept_repositories"]: source = GITEA_SALVAGE_SNAPSHOT_REPOSITORIES / row["repo_relative_path"] validate_gitea_salvage_path_chain( GITEA_SALVAGE_SNAPSHOT_REPOSITORIES, source, stat.S_ISDIR, f"repository source {row['repo_relative_path']}", trusted_device=trusted_device, mountpoints=mountpoints, ) inventory = inventory_gitea_salvage_bare_repository( source, row["repo_relative_path"], trusted_device, mountpoints, ) if inventory is None: die(f"Gitea salvage kept repository source is missing: {row['repo_relative_path']}") inventory["old_repo_id"] = int(row["repo_id"]) inventory["owner"] = row["owner"] inventory["slug"] = row["slug"] inventory["wiki"] = False repositories.append(inventory) wiki_source = GITEA_SALVAGE_SNAPSHOT_REPOSITORIES / row["wiki_relative_path"] wiki_path = validate_gitea_salvage_path_chain( GITEA_SALVAGE_SNAPSHOT_REPOSITORIES, wiki_source, stat.S_ISDIR, f"wiki source {row['wiki_relative_path']}", allow_missing_final=True, trusted_device=trusted_device, mountpoints=mountpoints, ) if wiki_path is None: continue wiki = inventory_gitea_salvage_bare_repository( wiki_source, row["wiki_relative_path"], trusted_device, mountpoints, ) if wiki is None: die(f"Gitea salvage wiki source disappeared: {row['wiki_relative_path']}") wiki["old_repo_id"] = int(row["repo_id"]) wiki["owner"] = row["owner"] wiki["slug"] = row["slug"] wiki["wiki"] = True repositories.append(wiki) manifest = { "schema": "nodedc.gitea.salvage-refs/v2", "snapshot_uuid": GITEA_SALVAGE_SNAPSHOT_UUID, "database_sha256": GITEA_SALVAGE_SNAPSHOT_DATABASE_SHA256, "repositories": repositories, } canonical = canonical_gitea_salvage_evidence( manifest, "reference manifest", ) return { "manifest": manifest, **canonical, "repository_stores": len(repositories), "object_files": sum(item["object_files"] for item in repositories), "object_bytes": sum(item["object_bytes"] for item in repositories), "excluded_derived_files": sum( len(item["excluded_derived_files"]) for item in repositories ), "excluded_derived_bytes": sum( item["excluded_derived_bytes"] for item in repositories ), "excluded_quarantine_directories": sum( len(item["excluded_quarantine_directories"]) for item in repositories ), "refs": sum(len(item["refs"]) for item in repositories), } def validate_gitea_salvage_disposition_evidence( disposition, refs, unsupported, topics, ): source = disposition["sourceEvidence"] report = unsupported["report"] topic_evidence = topics["evidence"] if ( refs.get("sha256") != source["referenceManifestSha256"] or refs.get("bytes") != source["referenceManifestBytes"] or unsupported.get("sha256") != source["unsupportedRepositoryReportSha256"] or unsupported.get("bytes") != source["unsupportedRepositoryReportBytes"] or report.get("schema_catalog_sha256") != source["unsupportedSchemaCatalogSha256"] or report.get("database_sha256") != source["databaseSha256"] or report.get("decision_manifest_sha256") != source["identityDecisionManifestSha256"] or report.get("snapshot_uuid") != source["snapshotUuid"] or report.get("schema_missing") != [] or report.get("schema_mismatch") != [] or report.get("anomalies") != [] or not isinstance(report.get("kept_repository_ids"), list) or len(report["kept_repository_ids"]) != disposition["scope"]["keptRepositories"] or len(report.get("per_repository", [])) != disposition["scope"]["keptRepositories"] ): die("Gitea salvage disposition source evidence drift") state_policy = disposition["repositoryStatePolicy"] if report.get("nonzero_categories") != state_policy[ "sourceNonzeroCategories" ]: die("Gitea salvage nonzero-category evidence drift") expected_direct_counts = { row["label"]: row["sourceCount"] for row in state_policy["directRelations"] } direct_counts = (report.get("aggregates") or {}).get( "direct_relation_counts" ) if direct_counts != expected_direct_counts: die("Gitea salvage direct-relation evidence drift") expected_unit_counts = { str(row["type"]): row["sourceCount"] for row in state_policy["units"]["rows"] if row["sourceCount"] } actual_unit_counts = {} seen_repo_ids = set() for repository in report["per_repository"]: old_repo_id = repository.get("old_repo_id") unit_types = repository.get("repo_unit_types") if ( not isinstance(old_repo_id, int) or old_repo_id <= 0 or old_repo_id in seen_repo_ids or not isinstance(unit_types, dict) ): die("Gitea salvage repo-unit evidence row is invalid") seen_repo_ids.add(old_repo_id) for unit_type, count in unit_types.items(): if ( unit_type not in expected_unit_counts or not isinstance(count, int) or isinstance(count, bool) or count != 1 ): die("Gitea salvage repo-unit evidence drift") actual_unit_counts[unit_type] = ( actual_unit_counts.get(unit_type, 0) + count ) if actual_unit_counts != expected_unit_counts: die("Gitea salvage repo-unit evidence drift") expected_metadata = { row["name"]: row["sourceRepositories"] for row in state_policy["textMetadata"] } if ( (report.get("aggregates") or {}).get( "repository_metadata_presence" ) != expected_metadata ): die("Gitea salvage repository metadata evidence drift") expected_schema_only_tables = sorted( table for group in state_policy["schemaOnlyDependencyGroups"] for table in group["tables"] ) if ( sorted((report.get("coverage") or {}).get( "schema_only_unreviewed_tables", [], )) != expected_schema_only_tables ): die("Gitea salvage schema-only coverage evidence drift") topic_policy = state_policy["topics"] topic_rows = topic_evidence.get("repositories") topic_row_ids = [] topic_row_arrays = 0 topic_row_nulls = 0 topic_row_material = 0 topic_row_topics = 0 if not isinstance(topic_rows, list): die("Gitea salvage semantic-topics evidence rows are invalid") for row in topic_rows: if ( not isinstance(row, dict) or set(row) != {"encoding", "material", "old_repo_id", "topic_count"} or row.get("encoding") not in {"json-array", "json-null"} or not isinstance(row.get("material"), bool) or not isinstance(row.get("old_repo_id"), int) or isinstance(row.get("old_repo_id"), bool) or row["old_repo_id"] <= 0 or not isinstance(row.get("topic_count"), int) or isinstance(row.get("topic_count"), bool) or not 0 <= row["topic_count"] <= 256 or row["material"] != bool(row["topic_count"]) or ( row["encoding"] == "json-null" and row["topic_count"] != 0 ) ): die("Gitea salvage semantic-topics evidence row is invalid") topic_row_ids.append(row["old_repo_id"]) topic_row_arrays += int(row["encoding"] == "json-array") topic_row_nulls += int(row["encoding"] == "json-null") topic_row_material += int(row["material"]) topic_row_topics += row["topic_count"] if ( topic_evidence.get("schema") != "nodedc.gitea.salvage-semantic-topics/v2" or topic_row_ids != report["kept_repository_ids"] or topic_evidence.get("database_sha256") != source["databaseSha256"] or topic_evidence.get("decision_manifest_sha256") != source["identityDecisionManifestSha256"] or topic_evidence.get("snapshot_uuid") != source["snapshotUuid"] or len(topic_evidence.get("repositories", [])) != disposition["scope"]["keptRepositories"] or topic_evidence.get("serialized_arrays") != topic_policy["expectedSerializedArrays"] or topic_row_arrays != topic_evidence.get("serialized_arrays") or topic_evidence.get("serialized_nulls") != topic_policy["expectedSerializedNulls"] or topic_row_nulls != topic_evidence.get("serialized_nulls") or topic_evidence.get("material_repositories") != topic_policy["expectedMaterialRepositories"] or topic_row_material != topic_evidence.get("material_repositories") or topic_evidence.get("topics") != topic_policy["expectedTopics"] or topic_row_topics != topic_evidence.get("topics") or direct_counts.get("topics") != topic_policy["expectedRelationalRows"] ): die("Gitea salvage semantic-topics evidence drift") actual_decisions = [] stores = refs.get("manifest", {}).get("repositories") if not isinstance(stores, list): die("Gitea salvage reference manifest shape mismatch") live_head_targets = 0 missing_head_targets = [] for repository in stores: if ( not isinstance(repository, dict) or not isinstance(repository.get("old_repo_id"), int) or not isinstance(repository.get("relative_path"), str) or not isinstance(repository.get("wiki"), bool) or repository.get("object_format") != "sha1" or not isinstance(repository.get("head"), str) or not repository["head"].startswith("refs/heads/") or not isinstance(repository.get("refs"), list) ): die("Gitea salvage reference store evidence is invalid") live_names = set() for ref in repository["refs"]: name = ref.get("name") oid = ref.get("oid") if repository["wiki"] and isinstance(name, str) and name.startswith( "refs/heads/" ): reference_disposition = "LIVE_RESTORE" elif ( not repository["wiki"] and isinstance(name, str) and ( name.startswith("refs/heads/") or name.startswith("refs/tags/") ) ): reference_disposition = "LIVE_RESTORE" elif ( not repository["wiki"] and isinstance(name, str) and ( name.startswith("refs/pull/") or name.startswith("refs/remotes/") ) ): reference_disposition = "SEALED_ARCHIVE_ONLY" else: die("Gitea salvage reference namespace is outside disposition") if reference_disposition == "LIVE_RESTORE": live_names.add(name) actual_decisions.append( { "disposition": reference_disposition, "name": name, "oid": oid, "oldRepositoryId": repository["old_repo_id"], "repositoryPath": repository["relative_path"], "wiki": repository["wiki"], } ) if repository["head"] in live_names: live_head_targets += 1 else: missing_head_targets.append( { "head": repository["head"], "oldRepositoryId": repository["old_repo_id"], "repositoryPath": repository["relative_path"], "wiki": repository["wiki"], } ) actual_decisions.sort( key=lambda row: ( row["oldRepositoryId"], int(row["wiki"]), row["repositoryPath"], row["name"], row["oid"], ) ) reference_policy = disposition["referencePolicy"] head_invariants = reference_policy["headInvariants"] if ( len(stores) != head_invariants["stores"] or refs.get("refs") != len(actual_decisions) or actual_decisions != reference_policy["exactDecisions"] or live_head_targets != head_invariants["targetsPresentAmongLiveRefs"] or sorted( missing_head_targets, key=lambda row: ( row["oldRepositoryId"], int(row["wiki"]), row["repositoryPath"], row["head"], ), ) != head_invariants["allowedMissingTargets"] ): die("Gitea salvage exact reference disposition evidence mismatch") live_refs = sum( row["disposition"] == "LIVE_RESTORE" for row in actual_decisions ) archive_refs = sum( row["disposition"] == "SEALED_ARCHIVE_ONLY" for row in actual_decisions ) if ( live_refs != reference_policy["liveRestore"]["totalRefs"] or archive_refs != reference_policy["archiveOnly"]["totalRefs"] or live_refs + archive_refs != reference_policy["forensicScope"]["allDiscoveredRefs"] ): die("Gitea salvage reference disposition totals mismatch") return { "archive_only_refs": archive_refs, "blockers": list(GITEA_SALVAGE_DISPOSITION_REMAINING_BLOCKERS), "forensic_refs": len(actual_decisions), "live_refs": live_refs, "sha256": GITEA_SALVAGE_DISPOSITION_SHA256, "topics": topics, } def validate_gitea_salvage_closure_evidence(disposition, closure): report = closure.get("report") if not isinstance(report, dict): die("Gitea salvage closure evidence report is missing") source = disposition["sourceEvidence"] report_source = report.get("source_evidence") issue_pr_subrelations = report.get("issue_pr_subrelations") if not isinstance(issue_pr_subrelations, dict): die("Gitea salvage issue/PR subrelation evidence is missing") conditional_hold_blockers = issue_pr_subrelations.get( "conditional_hold_blockers" ) expected_report_blockers = [ "attachment-physical-verifier-pending", "closure-report-review-pin-pending", "collaboration-kept-user-mapping-verifier-pending", "issue-pr-metadata-sanitized-archive-verifier-pending", "package-action-physical-closure-verifier-pending", "target-unit-policy-acceptance-pending", ] if isinstance(conditional_hold_blockers, list): expected_report_blockers.extend(conditional_hold_blockers) expected_report_blockers.sort() if ( report.get("schema") != disposition["closureReport"]["schema"] or report.get("database_sha256") != source["databaseSha256"] or report.get("decision_manifest_sha256") != source["identityDecisionManifestSha256"] or report.get("snapshot_uuid") != source["snapshotUuid"] or report.get("schema_catalog_sha256") != source["unsupportedSchemaCatalogSha256"] or not isinstance(report_source, dict) or report_source.get("predecessor_artifact_sha256") != disposition["predecessor"]["artifactSha256"] or report_source.get("predecessor_disposition_sha256") != disposition["predecessor"]["dispositionSha256"] or report_source.get("reference_manifest_sha256") != source["referenceManifestSha256"] or report_source.get("semantic_topics_sha256") != source["semanticTopicsSha256"] or report_source.get("unsupported_report_sha256") != source["unsupportedRepositoryReportSha256"] or report_source.get("unsupported_schema_sha256") != source["unsupportedSchemaCatalogSha256"] or report.get("scope") != { "all_repositories": 2058, "all_users": 972, "deleted_repositories": 2013, "deleted_users": 962, "kept_repositories": 45, "kept_users": 10, } or len(report.get("kept_repository_ids", [])) != 45 or len(report.get("per_repository", [])) != 45 or report.get("remaining_blockers") != expected_report_blockers ): die("Gitea salvage closure source evidence drift") require_exact_json_keys( issue_pr_subrelations, { "aggregates", "conditional_hold_blockers", "extra_schema_coverage", "holds", "per_repository", "schema", }, "Gitea salvage issue/PR subrelation evidence", ) if issue_pr_subrelations["schema"] != ( "nodedc.gitea.salvage-issue-pr-subrelation-closure/v1" ): die("Gitea salvage issue/PR subrelation schema drift") def validate_nonnegative_integer_map(value, keys, label): if not isinstance(value, dict) or set(value) != set(keys): die(f"Gitea salvage {label} shape is invalid") if any( not isinstance(item, int) or isinstance(item, bool) or item < 0 or item > GITEA_SALVAGE_UNSUPPORTED_SIZE_TOTAL_MAX_BYTES for item in value.values() ): die(f"Gitea salvage {label} value is invalid") class_keys = ("deleted", "kept", "system-or-external") target_class_keys = ("deleted", "global", "kept") external_fields = ( "id_without_name", "name_bytes", "name_without_id", "rows_with_id", "rows_with_name", ) per_subrelations = issue_pr_subrelations.get("per_repository") if ( not isinstance(per_subrelations, list) or len(per_subrelations) != 45 or [row.get("old_repo_id") for row in per_subrelations] != report["kept_repository_ids"] ): die("Gitea salvage issue/PR per-repository subrelations are invalid") for row in per_subrelations: if not isinstance(row, dict): die("Gitea salvage issue/PR per-repository row is invalid") require_exact_json_keys( row, { "actor_classes", "counts", "external_author_provenance", "null_encodings", "old_repo_id", "target_repository_classes", }, "Gitea salvage issue/PR per-repository row", ) validate_nonnegative_integer_map( row["counts"], GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_COUNTS, "issue/PR subrelation count", ) validate_nonnegative_integer_map( row["null_encodings"], GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_NULLABLE_COLUMNS, "issue/PR null encoding", ) actor_classes = row["actor_classes"] if not isinstance(actor_classes, dict) or set(actor_classes) != set( GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_ACTORS ): die("Gitea salvage issue/PR actor-class shape is invalid") for label in actor_classes: validate_nonnegative_integer_map( actor_classes[label], class_keys, "issue/PR actor class", ) if sum(actor_classes[label].values()) != row["counts"][label]: die("Gitea salvage issue/PR actor-class count drift") target_classes = row["target_repository_classes"] if not isinstance(target_classes, dict) or set(target_classes) != set( GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_REPOSITORIES ): die("Gitea salvage issue/PR repository-class shape is invalid") for label in target_classes: validate_nonnegative_integer_map( target_classes[label], target_class_keys, "issue/PR repository class", ) if sum(target_classes[label].values()) != row["counts"][label]: die("Gitea salvage issue/PR repository-class count drift") external = row["external_author_provenance"] if not isinstance(external, dict) or set(external) != set( GITEA_SALVAGE_CLOSURE_EXTERNAL_AUTHOR_SOURCES ): die("Gitea salvage external-author source shape is invalid") for source in external: validate_nonnegative_integer_map( external[source], external_fields, "external-author provenance", ) if ( external[source]["id_without_name"] > external[source]["rows_with_id"] or external[source]["name_without_id"] > external[source]["rows_with_name"] ): die("Gitea salvage external-author provenance is inconsistent") expected_aggregates = { "actor_classes": { label: { actor_class: sum( row["actor_classes"][label][actor_class] for row in per_subrelations ) for actor_class in class_keys } for label in GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_ACTORS }, "counts": { label: sum(row["counts"][label] for row in per_subrelations) for label in GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_COUNTS }, "external_author_provenance": { source: { field: sum( row["external_author_provenance"][source][field] for row in per_subrelations ) for field in external_fields } for source in GITEA_SALVAGE_CLOSURE_EXTERNAL_AUTHOR_SOURCES }, "null_encodings": { column: sum( row["null_encodings"][column] for row in per_subrelations ) for column in GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_NULLABLE_COLUMNS }, "target_repository_classes": { label: { target_class: sum( row["target_repository_classes"][label][target_class] for row in per_subrelations ) for target_class in target_class_keys } for label in GITEA_SALVAGE_CLOSURE_ISSUE_SUBRELATION_REPOSITORIES }, } if issue_pr_subrelations.get("aggregates") != expected_aggregates: die("Gitea salvage issue/PR subrelation aggregate drift") extra_schema = issue_pr_subrelations.get("extra_schema_coverage") if ( not isinstance(extra_schema, list) or len(extra_schema) != 1 or not isinstance(extra_schema[0], dict) or extra_schema[0].get("table") != "team" or extra_schema[0].get("ordinary_main_table") is not True or not {"id", "org_id"}.issubset( { column.get("name") for column in extra_schema[0].get("columns", []) if isinstance(column, dict) } ) ): die("Gitea salvage issue/PR extra schema coverage is invalid") holds = issue_pr_subrelations.get("holds") if not isinstance(holds, list) or len(holds) > ( GITEA_SALVAGE_CLOSURE_MAX_ROWS_PER_RELATION ): die("Gitea salvage issue/PR hold inventory is invalid") seen_holds = set() for row in holds: if not isinstance(row, dict): die("Gitea salvage issue/PR hold row is invalid") require_exact_json_keys( row, { "kind", "old_org_identity_class", "old_org_id", "old_repo_id", "old_row_id", "old_team_id", "source_table", }, "Gitea salvage issue/PR hold row", ) expected_source = { "comment-assignee-team-mapping": "comment", "review-reviewer-team-mapping": "review", }.get(row["kind"]) identity = ( row["source_table"], row["old_row_id"], row["old_team_id"], ) if ( expected_source is None or row["source_table"] != expected_source or row["old_org_identity_class"] not in {"deleted", "kept"} or row["old_repo_id"] not in report["kept_repository_ids"] or identity in seen_holds or any( not isinstance(row[key], int) or isinstance(row[key], bool) or row[key] <= 0 for key in ( "old_org_id", "old_repo_id", "old_row_id", "old_team_id", ) ) ): die("Gitea salvage issue/PR hold row is invalid") seen_holds.add(identity) expected_conditional = ["issue-pr-team-mapping-hold"] if holds else [] if conditional_hold_blockers != expected_conditional: die("Gitea salvage issue/PR conditional hold blocker drift") if len(holds) != ( expected_aggregates["counts"]["comment_assignee_team"] + expected_aggregates["counts"]["review_reviewer_team"] ): die("Gitea salvage issue/PR team hold count drift") report_aggregates = report.get("aggregates") history_aggregate = ( report_aggregates.get("issue_content_histories") if isinstance(report_aggregates, dict) else None ) review_aggregate = ( report_aggregates.get("reviews") if isinstance(report_aggregates, dict) else None ) pull_state_totals = report.get("pull_state_totals") if ( not isinstance(history_aggregate, dict) or not isinstance(review_aggregate, dict) or not isinstance(pull_state_totals, dict) or not isinstance(history_aggregate.get("rows"), int) or isinstance(history_aggregate.get("rows"), bool) or not isinstance(review_aggregate.get("rows"), int) or isinstance(review_aggregate.get("rows"), bool) or not isinstance(pull_state_totals.get("merged"), int) or isinstance(pull_state_totals.get("merged"), bool) or ( expected_aggregates["counts"]["content_history_comment"] + expected_aggregates["counts"]["content_history_issue"] != history_aggregate["rows"] ) or expected_aggregates["counts"]["pull_merger"] != pull_state_totals["merged"] or expected_aggregates["counts"]["review_reviewer_team"] > review_aggregate["rows"] ): die("Gitea salvage issue/PR subrelation coverage invariant failed") actor_relations = report.get("actor_relations") if not isinstance(actor_relations, list) or len(actor_relations) > 128: die("Gitea salvage closure actor-relation evidence is invalid") relation_identities = set() for row in actor_relations: if not isinstance(row, dict): die("Gitea salvage closure actor-relation row is invalid") require_exact_json_keys( row, { "actor_class", "disposition", "legacy_mode", "old_relation_id", "old_repo_id", "old_user_id", "relation", }, "Gitea salvage closure actor-relation row", ) identity = (row["relation"], row["old_repo_id"], row["old_user_id"]) expected = ( "DROP_CACHE_RECOMPUTE" if row["relation"] == "access_cache" else "RECREATE_KEPT_ACTOR_AFTER_ID_MAP" if row["relation"] == "collaborations" and row["actor_class"] == "kept" else "DROP_DELETED_ACTOR" if row["relation"] == "collaborations" and row["actor_class"] == "deleted" else None ) if ( identity in relation_identities or row["relation"] not in {"access_cache", "collaborations"} or row["actor_class"] not in {"deleted", "kept"} or expected is None or row["disposition"] != expected or not isinstance(row["legacy_mode"], int) or isinstance(row["legacy_mode"], bool) or not 0 <= row["legacy_mode"] <= 5 ): die("Gitea salvage closure actor-relation disposition is invalid") relation_identities.add(identity) attachment_summary = report.get("attachment_summary") attachment_manifest = report.get("attachment_manifest") if ( not isinstance(attachment_summary, dict) or not isinstance(attachment_manifest, list) or any(not isinstance(row, dict) for row in attachment_manifest) or attachment_summary.get("rows") != len(attachment_manifest) or attachment_summary.get("unique_uuids") != len(attachment_manifest) or attachment_summary.get("physical_presence") != "not-inventoried" or attachment_summary.get("declared_logical_bytes") != sum(row.get("declared_size", -1) for row in attachment_manifest) ): die("Gitea salvage closure attachment evidence is invalid") for row in attachment_manifest: if ( not isinstance(row, dict) or row.get("content_hash") != "unavailable-in-schema" or row.get("disposition") != "PHYSICAL_VERIFY_THEN_SANITIZED_ARCHIVE" or row.get("link_class") not in {"comment", "issue", "multi-link", "release", "unlinked"} or not isinstance(row.get("declared_size"), int) or isinstance(row.get("declared_size"), bool) or row["declared_size"] < 0 or re.fullmatch( r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-" r"[0-9a-f]{4}-[0-9a-f]{12}", str(row.get("uuid")), ) is None ): die("Gitea salvage closure attachment manifest is invalid") if ( report.get("unit_type_counts") != { "1": 45, "2": 45, "3": 45, "4": 45, "5": 45, "6": 0, "7": 0, "8": 45, "9": 45, "10": 0, } or (report.get("privacy_contract") or {}).get("payload_values_selected") is not False or (report.get("target_policy") or {}).get("actions") != "DISABLED_DROP_ALL_LEGACY_ROWS_AND_PHYSICAL_STATE" or (report.get("target_policy") or {}).get("packages") != "DISABLED_DROP_ALL_LEGACY_ROWS_AND_PHYSICAL_STATE" or (report.get("target_policy") or {}).get( "secrets_integrations_credentials" ) != "IMPORT_ZERO" ): die("Gitea salvage closure target disposition drift") return { "blockers": list(GITEA_SALVAGE_CLOSURE_REMAINING_BLOCKERS) + expected_conditional, "bytes": closure["bytes"], "json": closure["json"], "sha256": closure["sha256"], } def preflight_gitea_incident_salvage(payload_dir, enforce_apply=False): decisions = validate_gitea_incident_salvage_payload( payload_dir, GITEA_SALVAGE_ENTRIES, ) try: GITEA_ROOT.lstat() except FileNotFoundError: pass else: die("Gitea salvage candidate root must be absent") if gitea_compose_project_container_ids(): die("Gitea salvage Compose project already has containers") maintenance = docker_named_container_inspect_fail_closed( GITEA_SALVAGE_MAINTENANCE_CONTAINER, "Gitea salvage maintenance container inspect", ) if maintenance is not None: die("Gitea salvage maintenance container already exists") snapshot = validate_gitea_salvage_snapshot_boundary() database = bind_gitea_salvage_decisions_to_snapshot(decisions) refs = inventory_gitea_salvage_repository_refs(decisions) disposition = validate_gitea_salvage_disposition_evidence( decisions["disposition"], refs, database["unsupported"], database["topics"], ) closure_disposition = validate_gitea_salvage_closure_evidence( decisions["closure_disposition"], database["closure"], ) blockers = sorted( set(disposition["blockers"] + closure_disposition["blockers"]) ) legacy = None if ( GITEA_SALVAGE_EXPECTED_LEGACY_IMAGE is None or GITEA_SALVAGE_EXPECTED_LEGACY_IMAGE_ID is None ): blockers.append("legacy-container-identity-not-reviewed") else: legacy = validate_gitea_salvage_legacy_container() common = { "mode": "clean-state-exact-repository-material-only", "snapshot": snapshot, "database": database, "disposition": disposition, "closure_disposition": closure_disposition, "legacy": legacy, "refs": refs, "blockers": blockers, } if not enforce_apply: return common die( "Gitea salvage activation is frozen before candidate root creation: " + ",".join(blockers) ) def ensure_engine_node_intelligence_secret(): try: runtime_stat = ENGINE_NODE_INTELLIGENCE_RUNTIME_DIR.lstat() except FileNotFoundError: ENGINE_NODE_INTELLIGENCE_RUNTIME_DIR.mkdir(parents=False, exist_ok=False) runtime_stat = ENGINE_NODE_INTELLIGENCE_RUNTIME_DIR.lstat() if stat.S_ISLNK(runtime_stat.st_mode) or not stat.S_ISDIR(runtime_stat.st_mode): die("Engine node-intelligence runtime directory is unsafe") os.chown( ENGINE_NODE_INTELLIGENCE_RUNTIME_DIR, 0, ENGINE_NODE_INTELLIGENCE_RUNTIME_GID, ) ENGINE_NODE_INTELLIGENCE_RUNTIME_DIR.chmod(0o710) try: secret_stat = ENGINE_NODE_INTELLIGENCE_SECRET_FILE.lstat() except FileNotFoundError: value = secrets.token_urlsafe(48) temporary = ENGINE_NODE_INTELLIGENCE_RUNTIME_DIR / ( f".{ENGINE_NODE_INTELLIGENCE_SECRET_FILE.name}." f"{os.getpid()}.{time.time_ns()}.tmp" ) descriptor = None try: descriptor = os.open( str(temporary), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o440, ) os.write(descriptor, f"{value}\n".encode("ascii")) os.fsync(descriptor) os.fchown(descriptor, 0, ENGINE_NODE_INTELLIGENCE_RUNTIME_GID) os.fchmod(descriptor, 0o440) os.close(descriptor) descriptor = None os.replace(temporary, ENGINE_NODE_INTELLIGENCE_SECRET_FILE) fsync_directory(ENGINE_NODE_INTELLIGENCE_RUNTIME_DIR) finally: if descriptor is not None: os.close(descriptor) if temporary.exists(): temporary.unlink() return "created" if ( stat.S_ISLNK(secret_stat.st_mode) or not stat.S_ISREG(secret_stat.st_mode) or secret_stat.st_uid != 0 or secret_stat.st_gid != ENGINE_NODE_INTELLIGENCE_RUNTIME_GID or stat.S_IMODE(secret_stat.st_mode) != 0o440 or secret_stat.st_size < 49 or secret_stat.st_size > 512 ): die("Engine node-intelligence secret file is unsafe") try: value = ENGINE_NODE_INTELLIGENCE_SECRET_FILE.read_text( encoding="ascii" ).strip() except (OSError, UnicodeDecodeError): die("Engine node-intelligence secret file is unreadable") if not ENGINE_NODE_INTELLIGENCE_SECRET_RE.fullmatch(value): die("Engine node-intelligence secret file has invalid format") return "reused" def inspect_engine_node_intelligence_image(descriptor, required=True): result = subprocess.run( [str(DOCKER), "image", "inspect", ENGINE_NODE_INTELLIGENCE_IMAGE], check=False, capture_output=True, text=True, ) if result.returncode != 0: if not required: return None die("Engine node-intelligence image is missing") try: images = json.loads(result.stdout) except json.JSONDecodeError: die("Engine node-intelligence image inspect returned invalid JSON") if not isinstance(images, list) or len(images) != 1 or not isinstance(images[0], dict): die("Engine node-intelligence image inspect shape mismatch") image = images[0] image_id = image.get("Id") config = image.get("Config") or {} labels = config.get("Labels") or {} repo_tags = image.get("RepoTags") or [] if ( not isinstance(image_id, str) or not re.fullmatch(r"sha256:[a-f0-9]{64}", image_id) or image.get("Architecture") != descriptor["image"]["architecture"] or image.get("Os") != descriptor["image"]["os"] or ENGINE_NODE_INTELLIGENCE_IMAGE not in repo_tags or not isinstance(labels, dict) or labels.get("org.opencontainers.image.revision") != ENGINE_NODE_INTELLIGENCE_UPSTREAM_COMMIT or config.get("Entrypoint") != ["/usr/local/bin/docker-entrypoint.sh"] or config.get("Cmd") != ["node", "dist/mcp/index.js"] ): die("Engine node-intelligence image identity mismatch") env = config.get("Env") or [] if any( str(value).startswith(("AUTH_TOKEN=", "N8N_API_URL=", "N8N_API_KEY=")) for value in env ): die("Engine node-intelligence image contains runtime authority") return image def install_engine_node_intelligence_image(descriptor): if descriptor.get("action") != "activate": die("Engine node-intelligence image install requires activation") source_archive = ( component_root("engine") / ENGINE_NODE_INTELLIGENCE_IMAGE_ARCHIVE_REL ) validate_engine_node_intelligence_image_archive(source_archive, descriptor) try: releases_stat = ENGINE_NODE_INTELLIGENCE_RELEASES_DIR.lstat() except FileNotFoundError: ENGINE_NODE_INTELLIGENCE_RELEASES_DIR.mkdir(parents=False, exist_ok=False) releases_stat = ENGINE_NODE_INTELLIGENCE_RELEASES_DIR.lstat() if stat.S_ISLNK(releases_stat.st_mode) or not stat.S_ISDIR(releases_stat.st_mode): die("Engine node-intelligence releases directory is unsafe") os.chown(ENGINE_NODE_INTELLIGENCE_RELEASES_DIR, 0, 0) ENGINE_NODE_INTELLIGENCE_RELEASES_DIR.chmod(0o500) release_dir = ( ENGINE_NODE_INTELLIGENCE_RELEASES_DIR / descriptor["releaseId"] ) try: release_stat = release_dir.lstat() except FileNotFoundError: ENGINE_NODE_INTELLIGENCE_RELEASES_DIR.chmod(0o700) try: release_dir.mkdir(mode=0o500, exist_ok=False) finally: ENGINE_NODE_INTELLIGENCE_RELEASES_DIR.chmod(0o500) release_stat = release_dir.lstat() if ( stat.S_ISLNK(release_stat.st_mode) or not stat.S_ISDIR(release_stat.st_mode) or release_stat.st_uid != 0 ): die("Engine node-intelligence release directory is unsafe") os.chown(release_dir, 0, 0) release_dir.chmod(0o500) release_archive = release_dir / "engine-node-intelligence.tar" if release_archive.exists() or release_archive.is_symlink(): release_archive_stat = release_archive.lstat() if ( stat.S_ISLNK(release_archive_stat.st_mode) or not stat.S_ISREG(release_archive_stat.st_mode) or release_archive_stat.st_uid != 0 or release_archive_stat.st_gid != 0 or stat.S_IMODE(release_archive_stat.st_mode) != 0o400 or sha256_file(release_archive) != descriptor["image"]["archiveSha256"] ): die("Engine node-intelligence sealed release collision") else: release_dir.chmod(0o700) temporary = release_dir / ( f".engine-node-intelligence.tar.{os.getpid()}.{time.time_ns()}.tmp" ) try: with source_archive.open("rb") as source, temporary.open("xb") as output: shutil.copyfileobj(source, output, 1024 * 1024) output.flush() os.fsync(output.fileno()) os.chown(temporary, 0, 0) temporary.chmod(0o400) if sha256_file(temporary) != descriptor["image"]["archiveSha256"]: die("Engine node-intelligence sealed release copy mismatch") os.replace(temporary, release_archive) fsync_directory(release_dir) finally: if temporary.exists(): temporary.unlink() release_dir.chmod(0o500) load = subprocess.run( [str(DOCKER), "image", "load", "--input", str(release_archive)], check=False, capture_output=True, text=True, timeout=600, ) if load.returncode != 0: die("Engine node-intelligence offline image load failed") image = inspect_engine_node_intelligence_image(descriptor) return image["Id"] def engine_backend_container_id(): result = subprocess.run( [*compose_base_cmd("engine"), "ps", "-q", "nodedc-backend"], cwd=str(component_compose_root("engine")), check=False, capture_output=True, text=True, ) container_ids = [line.strip() for line in result.stdout.splitlines() if line.strip()] if (result.returncode != 0 or len(container_ids) != 1 or not re.fullmatch(r"[a-f0-9]{12,64}", container_ids[0])): die("Engine backend topology mismatch") return container_ids[0] def engine_backend_container_id_for_gateway(expected_gateway_sha256): return engine_compose_service_container_id_for_gateway( "nodedc-backend", expected_gateway_sha256, ) def engine_compose_service_container_id_for_gateway( service, expected_gateway_sha256, ): if service not in ("nodedc-backend", "app"): die("Engine L2 closed-loop service lookup is not registered") result = subprocess.run( [ *compose_base_cmd( "engine", expected_node_intelligence_gateway_sha256= expected_gateway_sha256, ), "ps", "-q", service, ], cwd=str(component_compose_root("engine")), check=False, capture_output=True, text=True, ) container_ids = [line.strip() for line in result.stdout.splitlines() if line.strip()] if (result.returncode != 0 or len(container_ids) != 1 or not re.fullmatch(r"[a-f0-9]{12,64}", container_ids[0])): die(f"Engine L2 closed-loop service topology mismatch: {service}") return container_ids[0] def run_engine_backend_probe(arguments, label, container_id=None, image_ref=None): if (container_id is None) == (image_ref is None): die(f"{label} probe target mismatch") if container_id is not None: cmd = [str(DOCKER), "exec", container_id, *arguments] else: cmd = [ str(DOCKER), "run", "--rm", "--network", "none", "--tmpfs", "/tmp:mode=1777", "--env", "HOME=/tmp", image_ref, *arguments, ] result = subprocess.run( cmd, check=False, capture_output=True, text=True, timeout=90, ) value = result.stdout.strip() if (result.returncode != 0 or result.stderr.strip() or not value or len(value) > 512 or any(ord(character) < 0x20 and character not in "\t" for character in value)): die(f"Engine backend {label} probe failed") return value def engine_backend_tool_versions(container_id=None, image_ref=None): versions = { "node": run_engine_backend_probe(("node", "--version"), "node", container_id, image_ref), "sqlite": run_engine_backend_probe(("sqlite3", "--version"), "sqlite", container_id, image_ref), "docker": run_engine_backend_probe(("docker", "--version"), "docker", container_id, image_ref), "compose": run_engine_backend_probe( ("docker", "compose", "version"), "Docker Compose", container_id, image_ref ), } if (not re.fullmatch(r"v20\.[0-9]+\.[0-9]+", versions["node"]) or not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:\s+.*)?", versions["sqlite"]) or not re.fullmatch(r"Docker version [0-9]+\.[0-9]+\.[0-9]+, build [A-Za-z0-9._-]+", versions["docker"]) or not re.fullmatch(r"Docker Compose version v?[0-9]+\.[0-9]+\.[0-9]+(?:[-+][A-Za-z0-9._-]+)?", versions["compose"])): die("Engine backend runtime tool version format mismatch") return versions def validate_engine_backend_mounts(container, node_modules_read_only): expected = { "/app": ("bind", True), "/app/deploy/docker-compose.yml": ("bind", False), "/seed-api": ("bind", False), "/seed-data": ("bind", False), "/app/node_modules": ("bind", not node_modules_read_only), "/app/server/data": ("bind", True), "/app/server/storage": ("bind", True), "/app/server/logs": ("bind", True), "/var/run/docker.sock": ("bind", True), } mounts = container.get("Mounts") if not isinstance(mounts, list): die("Engine backend mount inventory is missing") actual = {} node_modules_source = None for mount in mounts: if not isinstance(mount, dict) or not isinstance(mount.get("Destination"), str): die("Engine backend mount inventory is invalid") destination = mount["Destination"] if destination in actual: die(f"Engine backend duplicate mount destination: {destination}") actual[destination] = (mount.get("Type"), bool(mount.get("RW"))) if destination == "/app/node_modules": node_modules_source = mount.get("Source") if actual != expected: die("Engine backend mount barrier mismatch") if (node_modules_source != str(ENGINE_CREDENTIAL_BACKEND_NODE_MODULES_DIR) or not ENGINE_CREDENTIAL_BACKEND_NODE_MODULES_DIR.is_absolute()): die("Engine backend node_modules mount source mismatch") def validate_engine_backend_mounts_for_installed_runtime( container, node_modules_read_only, ): publish_override = ( component_root("engine") / ENGINE_DATA_PRODUCT_PUBLISH_GRANT_OVERRIDE_REL ) publish_installed = publish_override.exists() or publish_override.is_symlink() if not publish_installed: # The legacy credential runtime remains byte-for-byte authoritative # until the exact Publish overlay has been installed. return validate_engine_backend_mounts(container, node_modules_read_only) if publish_override.is_symlink() or not publish_override.is_file(): die("installed Engine data product publish grant override is unsafe") try: publish_override_text = publish_override.read_text(encoding="utf-8") except (OSError, UnicodeDecodeError): die("installed Engine data product publish grant override cannot be read") if publish_override_text != expected_engine_data_product_publish_grant_override(): die("installed Engine data product publish grant override drift detected") expected_publish_mounts = { ENGINE_PUBLISH_GRANT_CONTAINER_PATH: ( "bind", True, str(ENGINE_PUBLISH_GRANT_STATE_PATH), ), ENGINE_EDP_PRIVATE_KEY_CONTAINER_PATH: ( "bind", False, 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") baseline_mounts = [] observed_publish_mounts = set() for mount in mounts: if not isinstance(mount, dict) or not isinstance(mount.get("Destination"), str): die("Engine backend mount inventory is invalid") destination = mount["Destination"] expected = expected_publish_mounts.get(destination) if expected is None: baseline_mounts.append(mount) continue if destination in observed_publish_mounts: die(f"Engine backend duplicate mount destination: {destination}") actual = ( mount.get("Type"), bool(mount.get("RW")), mount.get("Source"), ) if actual != expected: die(f"Engine backend Publish mount barrier mismatch: {destination}") observed_publish_mounts.add(destination) if observed_publish_mounts != set(expected_publish_mounts): die("Engine backend Publish mount inventory mismatch") projected = dict(container) projected["Mounts"] = baseline_mounts return validate_engine_backend_mounts(projected, node_modules_read_only) def validate_engine_backend_node_intelligence_mounts_for_installed_runtime( container, node_modules_read_only, expected_gateway_sha256=None, ): descriptor = current_engine_node_intelligence_descriptor() active = descriptor is not None and descriptor["action"] == "activate" if not active: # The pre-existing runtime guard remains authoritative when this # independent overlay is inactive. Any stray secret mount therefore # still fails its exact baseline inventory. return validate_engine_backend_mounts_for_installed_runtime( container, node_modules_read_only, ) if expected_gateway_sha256 is None: validate_installed_engine_node_intelligence_source(descriptor) else: validate_installed_engine_node_intelligence_source( descriptor, expected_gateway_sha256=expected_gateway_sha256, ) mounts = container.get("Mounts") if not isinstance(mounts, list): die("Engine backend mount inventory is missing") baseline_mounts = [] observed = 0 for mount in mounts: if not isinstance(mount, dict) or not isinstance(mount.get("Destination"), str): die("Engine backend mount inventory is invalid") if mount["Destination"] != ENGINE_NODE_INTELLIGENCE_SECRET_CONTAINER_PATH: baseline_mounts.append(mount) continue observed += 1 actual = ( mount.get("Type"), bool(mount.get("RW")), mount.get("Source"), ) expected = ( "bind", False, str(ENGINE_NODE_INTELLIGENCE_SECRET_FILE), ) if observed != 1 or actual != expected: die("Engine backend node-intelligence mount barrier mismatch") if observed != 1: die("Engine backend node-intelligence mount inventory mismatch") # Remove only the independently proven additive mount, then delegate the # remaining inventory to the existing Publish-aware guard and ultimately # the byte-protected legacy baseline validator. projected = dict(container) projected["Mounts"] = baseline_mounts return validate_engine_backend_mounts_for_installed_runtime( projected, node_modules_read_only, ) def validate_engine_backend_dependencies(container_id): package_path = component_root("engine") / "nodedc-source/package.json" try: package_stat = package_path.lstat() except FileNotFoundError: die("Engine backend package.json is missing") if stat.S_ISLNK(package_stat.st_mode) or not stat.S_ISREG(package_stat.st_mode): die("Engine backend package.json is unsafe") package = read_strict_json(package_path, "Engine backend package.json", max_bytes=2 * 1024 * 1024) dependencies = package.get("dependencies") if not isinstance(dependencies, dict) or not dependencies: die("Engine backend dependency manifest is invalid") names = sorted(dependencies) if any(not re.fullmatch(r"(?:@[a-z0-9._-]+/)?[a-z0-9._-]+", name) for name in names): die("Engine backend dependency name is unsafe") script = ( "const names=" + json.dumps(names, separators=(",", ":")) + ";for(const name of names)require.resolve(name,{paths:['/app']});" + "process.stdout.write('dependencies-ok')" ) result = subprocess.run( [str(DOCKER), "exec", "--workdir", "/app", container_id, "node", "-e", script], check=False, capture_output=True, text=True, timeout=120, ) if result.returncode != 0 or result.stderr.strip() or result.stdout.strip() != "dependencies-ok": die("Engine backend mounted dependency barrier mismatch") def engine_backend_node_modules_tree_sha256(container_id): script = """ const crypto=require('crypto'); const fs=require('fs'); const path=require('path'); const root='/app/node_modules'; const hash=crypto.createHash('sha256'); const lock=JSON.parse(fs.readFileSync('/app/package-lock.json','utf8')); if(lock.lockfileVersion!==3||!lock.packages||typeof lock.packages!=='object')throw new Error('lock-invalid'); const expected=new Map(Object.entries(lock.packages).filter(([key,value])=>key.startsWith('node_modules/')&&value&&typeof value==='object')); function secure(stat,label,{symlink=false}={}){ if(stat.uid!==0||stat.gid!==0)throw new Error('owner:'+label); if(!symlink&&(stat.mode&0o022)!==0)throw new Error('writable:'+label); } secure(fs.lstatSync(root),'root'); function packageVersion(packageRoot,key){ const manifest=JSON.parse(fs.readFileSync(path.join(packageRoot,'package.json'),'utf8')); const wanted=expected.get(key); if(!wanted||String(manifest.version||'')!==String(wanted.version||''))throw new Error('package-version:'+key); } function inspectPackageDirectory(nodeModulesRoot,relativePrefix){ const names=fs.readdirSync(nodeModulesRoot).sort((a,b)=>Buffer.from(a).compare(Buffer.from(b))); const roots=[]; for(const name of names){ if(name==='.bin'||name==='.package-lock.json')continue; const candidate=path.join(nodeModulesRoot,name); const stat=fs.lstatSync(candidate); if(name.startsWith('@')){ if(!stat.isDirectory())throw new Error('scope-not-directory'); for(const child of fs.readdirSync(candidate).sort((a,b)=>Buffer.from(a).compare(Buffer.from(b)))){ roots.push([path.join(candidate,child),relativePrefix+name+'/'+child]); } }else{ roots.push([candidate,relativePrefix+name]); } } for(const [packageRoot,key] of roots){ const stat=fs.lstatSync(packageRoot); if(!stat.isDirectory()||stat.isSymbolicLink())throw new Error('package-boundary:'+key); packageVersion(packageRoot,key); const nested=path.join(packageRoot,'node_modules'); if(fs.existsSync(nested))inspectPackageDirectory(nested,key+'/node_modules/'); } } inspectPackageDirectory(root,'node_modules/'); for(const [key,value] of expected){ const target=path.join('/app',key); if(!fs.existsSync(target)&&value.optional!==true)throw new Error('package-missing:'+key); if(fs.existsSync(target))packageVersion(target,key); } function visit(relative){ const absolute=path.join(root,relative); const names=fs.readdirSync(absolute).sort((a,b)=>Buffer.from(a).compare(Buffer.from(b))); for(const name of names){ if(name.includes('\\0')||name==='.'||name==='..')throw new Error('unsafe-name'); const child=relative?relative+'/'+name:name; const target=path.join(root,child); const stat=fs.lstatSync(target); const mode=(stat.mode&0o7777).toString(8); if(stat.isDirectory()){ secure(stat,child); hash.update('d\\0'+child+'\\0'+mode+'\\0'); visit(child); }else if(stat.isFile()){ secure(stat,child); hash.update('f\\0'+child+'\\0'+mode+'\\0'+stat.size+'\\0'); hash.update(fs.readFileSync(target)); }else if(stat.isSymbolicLink()){ secure(stat,child,{symlink:true}); const link=fs.readlinkSync(target); if(path.isAbsolute(link))throw new Error('absolute-link:'+child); const resolved=path.resolve(path.dirname(target),link); if(resolved!==root&&!resolved.startsWith(root+'/'))throw new Error('escaping-link:'+child); const real=fs.realpathSync(target); if(real!==root&&!real.startsWith(root+'/'))throw new Error('escaping-real-link:'+child); hash.update('l\\0'+child+'\\0'+mode+'\\0'+link+'\\0'); }else{ throw new Error('special-file'); } } } visit(''); process.stdout.write('sha256:'+hash.digest('hex')); """.strip() result = subprocess.run( [str(DOCKER), "exec", "--workdir", "/app", container_id, "node", "-e", script], check=False, capture_output=True, text=True, timeout=300, ) value = result.stdout.strip() if (result.returncode != 0 or result.stderr.strip() or not re.fullmatch(r"sha256:[a-f0-9]{64}", value)): die("Engine backend node_modules tree proof failed") return value def validate_runtime_owned_file(path, mode, label, min_bytes=1, max_bytes=1024 * 1024 * 1024): try: path_stat = path.lstat() except FileNotFoundError: die(f"{label} is missing") if (stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or path_stat.st_uid != 0 or path_stat.st_gid != 0 or stat.S_IMODE(path_stat.st_mode) != mode or path_stat.st_size < min_bytes or path_stat.st_size > max_bytes): die(f"{label} is unsafe") return path_stat def validate_engine_backend_runtime_override_file(): validate_runtime_owned_file( ENGINE_CREDENTIAL_BACKEND_OVERRIDE_FILE, 0o600, "Engine backend immutable runtime override", max_bytes=64 * 1024, ) try: value = ENGINE_CREDENTIAL_BACKEND_OVERRIDE_FILE.read_text(encoding="utf-8") except (OSError, UnicodeDecodeError): die("Engine backend immutable runtime override is unreadable") if value != expected_engine_credential_backend_override(): die("Engine backend immutable runtime override drift detected") return sha256_file(ENGINE_CREDENTIAL_BACKEND_OVERRIDE_FILE) def validate_engine_backend_activation_marker(): validate_runtime_owned_file( ENGINE_CREDENTIAL_BACKEND_ACTIVATION_FILE, 0o600, "Engine backend immutable runtime activation marker", max_bytes=128, ) validate_engine_backend_runtime_override_file() metadata = read_engine_backend_runtime_metadata() validate_runtime_owned_file( ENGINE_CREDENTIAL_BACKEND_ROOTFS_FILE, 0o600, "Engine backend canonical rootfs", max_bytes=MAX_PAYLOAD_BYTES, ) expected = f"sha256:{sha256_file(ENGINE_CREDENTIAL_BACKEND_METADATA_FILE)}\n" try: actual = ENGINE_CREDENTIAL_BACKEND_ACTIVATION_FILE.read_text(encoding="ascii") except (OSError, UnicodeDecodeError): die("Engine backend immutable runtime activation marker is unreadable") if (actual != expected or sha256_file(ENGINE_CREDENTIAL_BACKEND_ROOTFS_FILE) != metadata["rootfsSha256"]): die("Engine backend immutable runtime activation marker mismatch") inspect_engine_backend_derived_image(metadata) return metadata def quarantine_engine_backend_partial_runtime(): runtime_exists = ( ENGINE_CREDENTIAL_BACKEND_RUNTIME_DIR.exists() or ENGINE_CREDENTIAL_BACKEND_RUNTIME_DIR.is_symlink() ) if not runtime_exists: return "absent" ensure_root_runtime_directory(ENGINE_CREDENTIAL_BACKEND_RUNTIME_DIR) present = [ path for path in ( ENGINE_CREDENTIAL_BACKEND_ACTIVATION_FILE, ENGINE_CREDENTIAL_BACKEND_OVERRIDE_FILE, ENGINE_CREDENTIAL_BACKEND_METADATA_FILE, ENGINE_CREDENTIAL_BACKEND_ROOTFS_FILE, ) if path.exists() or path.is_symlink() ] if not present: return "empty" failed_root = ENGINE_CREDENTIAL_BACKEND_RUNTIME_DIR / "failed" ensure_root_runtime_directory(failed_root) quarantine = failed_root / f"{stamp()}-{os.getpid()}-{time.time_ns()}" quarantine.mkdir(mode=0o700) os.chown(quarantine, 0, 0) # Move the activation marker first. Once this succeeds, no subsequent # Engine Compose command can consume the incomplete override. ordered = sorted( present, key=lambda path: path != ENGINE_CREDENTIAL_BACKEND_ACTIVATION_FILE, ) for path in ordered: os.replace(path, quarantine / path.name) fsync_directory(ENGINE_CREDENTIAL_BACKEND_RUNTIME_DIR) fsync_directory(quarantine) return "quarantined" def read_engine_backend_runtime_metadata(): validate_runtime_owned_file( ENGINE_CREDENTIAL_BACKEND_METADATA_FILE, 0o600, "Engine backend immutable runtime metadata", max_bytes=64 * 1024, ) value = read_strict_json( ENGINE_CREDENTIAL_BACKEND_METADATA_FILE, "Engine backend immutable runtime metadata", max_bytes=64 * 1024, ) expected_keys = { "schemaVersion", "release", "sourceContainerId", "sourceImageId", "derivedImageId", "rootfsSha256", "packageLockSha256", "nodeModulesTreeSha256", "overrideSha256", "tools", } if (not isinstance(value, dict) or set(value) != expected_keys or value.get("schemaVersion") != "nodedc.engine-backend-immutable-runtime/v1" or value.get("release") != "credential-sink-20260716-001" or not re.fullmatch(r"[a-f0-9]{12,64}", str(value.get("sourceContainerId"))) or not re.fullmatch(r"sha256:[a-f0-9]{64}", str(value.get("sourceImageId"))) or not re.fullmatch(r"sha256:[a-f0-9]{64}", str(value.get("derivedImageId"))) or not re.fullmatch(r"[a-f0-9]{64}", str(value.get("rootfsSha256"))) or value.get("packageLockSha256") != ENGINE_CREDENTIAL_BACKEND_PACKAGE_LOCK_SHA256 or not re.fullmatch(r"sha256:[a-f0-9]{64}", str(value.get("nodeModulesTreeSha256"))) or not re.fullmatch(r"[a-f0-9]{64}", str(value.get("overrideSha256"))) or not isinstance(value.get("tools"), dict) or set(value["tools"]) != {"node", "sqlite", "docker", "compose"}): die("Engine backend immutable runtime metadata mismatch") return value def inspect_engine_backend_derived_image( expected_metadata=None, image_ref=ENGINE_CREDENTIAL_BACKEND_IMAGE, expected_source_image_id=None, expected_rootfs_sha256=None, ): images = docker_json( ["image", "inspect", image_ref], "Engine backend immutable image inspect", ) if not isinstance(images, list) or len(images) != 1 or not isinstance(images[0], dict): die("Engine backend immutable image inspect shape mismatch") image = images[0] image_id = image.get("Id") labels = (image.get("Config") or {}).get("Labels") or {} if (not isinstance(image_id, str) or not re.fullmatch(r"sha256:[a-f0-9]{64}", image_id) or image.get("Architecture") != "amd64" or image.get("Os") != "linux" or not isinstance(labels, dict) or labels.get("org.nodedc.release") != "credential-sink-20260716-001" or labels.get("org.nodedc.package-lock-sha256") != ENGINE_CREDENTIAL_BACKEND_PACKAGE_LOCK_SHA256 or not re.fullmatch(r"sha256:[a-f0-9]{64}", str(labels.get("org.nodedc.source-image-id"))) or not re.fullmatch(r"[a-f0-9]{64}", str(labels.get("org.nodedc.rootfs-sha256")))): die("Engine backend immutable image identity mismatch") if (expected_source_image_id is not None and labels["org.nodedc.source-image-id"] != expected_source_image_id): die("Engine backend immutable image source collision") if (expected_rootfs_sha256 is not None and labels["org.nodedc.rootfs-sha256"] != expected_rootfs_sha256): die("Engine backend immutable image rootfs collision") if expected_metadata is not None and ( image_id != expected_metadata["derivedImageId"] or labels["org.nodedc.source-image-id"] != expected_metadata["sourceImageId"] or labels["org.nodedc.rootfs-sha256"] != expected_metadata["rootfsSha256"] ): die("Engine backend immutable image metadata drift detected") return image def ensure_engine_backend_release_image( build_context, source_image_id, rootfs_sha256, expected_tools, ): target_id = inspect_optional_local_image( ENGINE_CREDENTIAL_BACKEND_IMAGE, "Engine backend immutable release image", ) if target_id is not None: image = inspect_engine_backend_derived_image( image_ref=ENGINE_CREDENTIAL_BACKEND_IMAGE, expected_source_image_id=source_image_id, expected_rootfs_sha256=rootfs_sha256, ) if (image.get("Id") != target_id or engine_backend_tool_versions( image_ref=ENGINE_CREDENTIAL_BACKEND_IMAGE, ) != expected_tools): die("Engine backend immutable release image collision") return image build_ref = f"nodedc/deploy-build:engine-backend-{rootfs_sha256[:16]}" build_id = inspect_optional_local_image( build_ref, "Engine backend immutable build image", ) if build_id is None: build = subprocess.run( [ str(DOCKER), "build", "--no-cache", "--network", "none", "--pull=false", "-f", "Dockerfile", "-t", build_ref, ".", ], cwd=str(build_context), check=False, timeout=600, ) if build.returncode != 0: die("Engine backend immutable image build failed") build_id = inspect_optional_local_image( build_ref, "Engine backend immutable built image", ) if build_id is None: die("Engine backend immutable built image is missing") build_image = inspect_engine_backend_derived_image( image_ref=build_ref, expected_source_image_id=source_image_id, expected_rootfs_sha256=rootfs_sha256, ) if (build_image.get("Id") != build_id or engine_backend_tool_versions(image_ref=build_ref) != expected_tools): die("Engine backend immutable build image collision") # The deploy lock serializes canonical applies. Recheck the release tag # immediately before assigning it so an existing versioned identity is # never intentionally moved to a different image. target_id = inspect_optional_local_image( ENGINE_CREDENTIAL_BACKEND_IMAGE, "Engine backend immutable release image recheck", ) if target_id is not None: image = inspect_engine_backend_derived_image( image_ref=ENGINE_CREDENTIAL_BACKEND_IMAGE, expected_source_image_id=source_image_id, expected_rootfs_sha256=rootfs_sha256, ) if image.get("Id") != build_id: die("Engine backend immutable release image collision") return image tag = subprocess.run( [str(DOCKER), "image", "tag", build_id, ENGINE_CREDENTIAL_BACKEND_IMAGE], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) if tag.returncode != 0: die("Engine backend immutable release image tag failed") image = inspect_engine_backend_derived_image( image_ref=ENGINE_CREDENTIAL_BACKEND_IMAGE, expected_source_image_id=source_image_id, expected_rootfs_sha256=rootfs_sha256, ) if (image.get("Id") != build_id or engine_backend_tool_versions( image_ref=ENGINE_CREDENTIAL_BACKEND_IMAGE, ) != expected_tools): die("Engine backend immutable release image verification failed") return image def preflight_engine_credential_backend_runtime( expected_node_intelligence_gateway_sha256=None, ): base_image_id = inspect_local_image( ENGINE_CREDENTIAL_BACKEND_BASE_IMAGE, "Engine backend base image", ) package_lock = component_root("engine") / "nodedc-source/package-lock.json" try: lock_stat = package_lock.lstat() except FileNotFoundError: die("Engine backend package-lock.json is missing") if (stat.S_ISLNK(lock_stat.st_mode) or not stat.S_ISREG(lock_stat.st_mode) or sha256_file(package_lock) != ENGINE_CREDENTIAL_BACKEND_PACKAGE_LOCK_SHA256): die("Engine backend package-lock barrier mismatch") try: node_modules_stat = ENGINE_CREDENTIAL_BACKEND_NODE_MODULES_DIR.lstat() except FileNotFoundError: die("Engine backend node_modules host source is missing") if (stat.S_ISLNK(node_modules_stat.st_mode) or not stat.S_ISDIR(node_modules_stat.st_mode) or node_modules_stat.st_uid != 0 or node_modules_stat.st_gid != 0 or node_modules_stat.st_mode & (stat.S_IWGRP | stat.S_IWOTH)): die("Engine backend node_modules host source is unsafe") container_id = ( engine_backend_container_id() if expected_node_intelligence_gateway_sha256 is None else engine_backend_container_id_for_gateway( expected_node_intelligence_gateway_sha256 ) ) containers = docker_json(["container", "inspect", container_id], "Engine backend container inspect") if not isinstance(containers, list) or len(containers) != 1 or not isinstance(containers[0], dict): die("Engine backend container inspect shape mismatch") container = containers[0] state = container.get("State") or {} started_at = state.get("StartedAt") initial_restart_count = int(container.get("RestartCount") or 0) if (state.get("Status") != "running" or (state.get("Health") or {}).get("Status") != "healthy" or not isinstance(started_at, str) or not re.fullmatch(r"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9:.]+Z", started_at)): die("Engine backend source container is not stable and healthy") config_image = (container.get("Config") or {}).get("Image") current_image_id = container.get("Image") validate_engine_backend_node_intelligence_mounts_for_installed_runtime( container, node_modules_read_only=config_image == ENGINE_CREDENTIAL_BACKEND_IMAGE, expected_gateway_sha256=expected_node_intelligence_gateway_sha256, ) if config_image == ENGINE_CREDENTIAL_BACKEND_BASE_IMAGE and current_image_id == base_image_id: runtime_presence = { "activation": ENGINE_CREDENTIAL_BACKEND_ACTIVATION_FILE.exists() or ENGINE_CREDENTIAL_BACKEND_ACTIVATION_FILE.is_symlink(), "override": ENGINE_CREDENTIAL_BACKEND_OVERRIDE_FILE.exists() or ENGINE_CREDENTIAL_BACKEND_OVERRIDE_FILE.is_symlink(), "metadata": ENGINE_CREDENTIAL_BACKEND_METADATA_FILE.exists() or ENGINE_CREDENTIAL_BACKEND_METADATA_FILE.is_symlink(), "rootfs": ENGINE_CREDENTIAL_BACKEND_ROOTFS_FILE.exists() or ENGINE_CREDENTIAL_BACKEND_ROOTFS_FILE.is_symlink(), } if all(runtime_presence.values()): validate_engine_backend_activation_marker() override_sha256 = validate_engine_backend_runtime_override_file() metadata = read_engine_backend_runtime_metadata() rootfs_stat = validate_runtime_owned_file( ENGINE_CREDENTIAL_BACKEND_ROOTFS_FILE, 0o600, "Engine backend canonical rootfs", max_bytes=MAX_PAYLOAD_BYTES, ) image = inspect_engine_backend_derived_image(metadata) if (rootfs_stat.st_size <= 0 or sha256_file(ENGINE_CREDENTIAL_BACKEND_ROOTFS_FILE) != metadata["rootfsSha256"] or override_sha256 != metadata["overrideSha256"] or metadata["sourceImageId"] != base_image_id or metadata["derivedImageId"] != image.get("Id")): die("Engine backend prepared immutable runtime drift detected") mode = "verified-prepared-not-active" elif any(runtime_presence.values()): for name, path in ( ("activation", ENGINE_CREDENTIAL_BACKEND_ACTIVATION_FILE), ("override", ENGINE_CREDENTIAL_BACKEND_OVERRIDE_FILE), ("metadata", ENGINE_CREDENTIAL_BACKEND_METADATA_FILE), ("rootfs", ENGINE_CREDENTIAL_BACKEND_ROOTFS_FILE), ): if runtime_presence[name]: validate_runtime_owned_file( path, 0o600, f"Engine backend partial runtime {name}", max_bytes=MAX_PAYLOAD_BYTES, ) mode = "recoverable-prepared-partial" else: mode = "verified-running-base" source_image_id = base_image_id elif config_image == ENGINE_CREDENTIAL_BACKEND_IMAGE: override_sha256 = validate_engine_backend_runtime_override_file() metadata = read_engine_backend_runtime_metadata() image = inspect_engine_backend_derived_image(metadata) if current_image_id != image.get("Id") or override_sha256 != metadata["overrideSha256"]: die("Engine backend immutable runtime activation drift detected") mode = "verified-derived-retry" source_image_id = metadata["sourceImageId"] else: die("Engine backend current image barrier mismatch") tools = engine_backend_tool_versions(container_id=container_id) validate_engine_backend_dependencies(container_id) node_modules_tree_sha256 = engine_backend_node_modules_tree_sha256(container_id) if mode in ("verified-derived-retry", "verified-prepared-not-active"): if (node_modules_tree_sha256 != metadata["nodeModulesTreeSha256"] or tools != metadata["tools"]): die("Engine backend prepared runtime source proof drift detected") stable = docker_json(["container", "inspect", container_id], "Engine backend stability inspect") stable_container = stable[0] if isinstance(stable, list) and len(stable) == 1 else None stable_state = stable_container.get("State") if isinstance(stable_container, dict) else None if (not isinstance(stable_state, dict) or stable_state.get("Status") != "running" or (stable_state.get("Health") or {}).get("Status") != "healthy" or stable_state.get("StartedAt") != started_at or int(stable_container.get("RestartCount") or 0) != initial_restart_count): die("Engine backend restarted during immutable runtime preflight") return { "base_image_id": base_image_id, "container_id": container_id, "current_image_id": current_image_id, "mode": mode, "source_image_id": source_image_id, "node_modules_tree_sha256": node_modules_tree_sha256, "tools": tools, } def normalize_rootfs_member_name(name): if not isinstance(name, str) or not name or name.startswith("/") or "\\" in name: die("Engine backend export member path is unsafe") while name.startswith("./"): name = name[2:] parts = name.split("/") if not name or any(not part or part in (".", "..") for part in parts): die("Engine backend export member path is unsafe") if any(part.startswith("._") or part == "__MACOSX" for part in parts): die("Engine backend export contains AppleDouble metadata") return "/".join(parts) def engine_backend_rootfs_member_allowed(name, is_directory): excluded = ( "etc/hosts", "etc/hostname", "etc/resolv.conf", "etc/mtab", "etc/environment", "etc/profile", "etc/profile.d", "etc/ssl/private", "etc/ssh/ssh_host", "etc/machine-id", "etc/docker", "etc/containerd", "usr/local/etc", "usr/local/lib/node_modules", ) if any(name == value or name.startswith(f"{value}/") for value in excluded): return False basename = name.rsplit("/", 1)[-1].lower() if (basename in (".env", "id_rsa", "id_ed25519") or basename.startswith(".env.") or basename.endswith((".p12", ".pfx"))): die(f"Engine backend export secret-like path rejected: {name}") if any(name == root or name.startswith(f"{root}/") for root in ("bin", "sbin", "lib", "usr", "etc")): return True if name in ("var", "var/lib"): return is_directory return name == "var/lib/apk" or name.startswith("var/lib/apk/") def resolve_rootfs_link(name, linkname): if not isinstance(linkname, str) or not linkname or "\\" in linkname: die(f"Engine backend export link target is unsafe: {name}") parts = [] if linkname.startswith("/") else name.split("/")[:-1] for part in linkname.lstrip("/").split("/"): if part in ("", "."): continue if part == "..": if not parts: die(f"Engine backend export link escapes root: {name}") parts.pop() else: parts.append(part) target = "/".join(parts) if not target or not engine_backend_rootfs_member_allowed(target, False): die(f"Engine backend export link leaves safe roots: {name}") return linkname ENGINE_BACKEND_ROOTFS_OMITTED_TOOLCHAIN_LINKS = { "usr/local/bin/corepack": re.compile(r"^\.\./lib/node_modules/corepack/dist/corepack\.js$"), "usr/local/bin/npm": re.compile(r"^\.\./lib/node_modules/npm/bin/npm-cli\.js$"), "usr/local/bin/npx": re.compile(r"^\.\./lib/node_modules/npm/bin/npx-cli\.js$"), "usr/local/bin/yarn": re.compile(r"^/opt/yarn-v[0-9]+\.[0-9]+\.[0-9]+/bin/yarn$"), "usr/local/bin/yarnpkg": re.compile(r"^/opt/yarn-v[0-9]+\.[0-9]+\.[0-9]+/bin/yarnpkg$"), } def engine_backend_rootfs_toolchain_link_is_omitted(name, linkname): expected = ENGINE_BACKEND_ROOTFS_OMITTED_TOOLCHAIN_LINKS.get(name) if expected is None: return False if not isinstance(linkname, str) or not expected.fullmatch(linkname): die(f"Engine backend export omitted toolchain link target mismatch: {name}") return True ENGINE_BACKEND_ROOTFS_SECRET_PATTERNS = ( re.compile(br"-----BEGIN (?:OPENSSH |RSA |EC )?PRIVATE KEY-----"), re.compile(br"ndc_(?:edpwb|edprb|fndbg|edppr)_[A-Za-z0-9_-]{8,}"), re.compile(br"AKIA[0-9A-Z]{16}"), re.compile(br"gh[pousr]_[A-Za-z0-9]{20,}"), re.compile(br"xox[baprs]-[A-Za-z0-9-]{20,}"), ) def spool_and_scan_rootfs_file(source, name, expected_size): if expected_size < 0 or expected_size > MAX_FILE_BYTES: die(f"Engine backend export member size rejected: {name}") temporary = tempfile.SpooledTemporaryFile(max_size=8 * 1024 * 1024) total = 0 carry = b"" while True: chunk = source.read(1024 * 1024) if not chunk: break total += len(chunk) if total > MAX_FILE_BYTES: temporary.close() die(f"Engine backend export member too large: {name}") sample = carry + chunk if any(pattern.search(sample) for pattern in ENGINE_BACKEND_ROOTFS_SECRET_PATTERNS): temporary.close() die(f"Engine backend export secret material rejected: {name}") carry = sample[-256:] temporary.write(chunk) if total != expected_size: temporary.close() die(f"Engine backend export member size mismatch: {name}") temporary.seek(0) return temporary def build_canonical_engine_backend_rootfs(source_tar, destination_tar): total_bytes = 0 with tarfile.open(source_tar, "r:*") as source: members = [] seen = set() source_members = {} for member in source.getmembers(): name = normalize_rootfs_member_name(member.name) if name in seen: die(f"Engine backend export duplicate member: {name}") seen.add(name) source_members[name] = member if not engine_backend_rootfs_member_allowed(name, member.isdir()): continue if not (member.isdir() or member.isfile() or member.issym() or member.islnk()): die(f"Engine backend export special member rejected: {name}") if (member.issym() and engine_backend_rootfs_toolchain_link_is_omitted(name, member.linkname)): continue members.append((name, member)) members.sort(key=lambda item: item[0]) with destination_tar.open("xb") as raw_output: with tarfile.open(fileobj=raw_output, mode="w", format=tarfile.GNU_FORMAT) as output: for name, member in members: info = tarfile.TarInfo(name) info.uid = member.uid info.gid = member.gid info.uname = "" info.gname = "" info.mode = member.mode & 0o7777 info.mtime = 0 if member.isdir(): info.type = tarfile.DIRTYPE info.size = 0 output.addfile(info) continue if member.issym(): info.type = tarfile.SYMTYPE info.linkname = resolve_rootfs_link(name, member.linkname) info.size = 0 output.addfile(info) continue if member.islnk(): target_name = normalize_rootfs_member_name(member.linkname.lstrip("/")) target = source_members.get(target_name) if (target is None or not target.isfile() or not engine_backend_rootfs_member_allowed(target_name, False)): die(f"Engine backend export hardlink target mismatch: {name}") expected_size = target.size source_member = target else: expected_size = member.size source_member = member source_file = source.extractfile(source_member) if source_file is None: die(f"Engine backend export member is unreadable: {name}") with source_file: staged = spool_and_scan_rootfs_file(source_file, name, expected_size) with staged: info.type = tarfile.REGTYPE info.size = expected_size output.addfile(info, staged) total_bytes += expected_size if total_bytes > MAX_PAYLOAD_BYTES: die("Engine backend canonical rootfs exceeds size limit") destination_tar.chmod(0o600) return sha256_file(destination_tar) def atomic_write_root_file(path, data, mode=0o600): path.parent.mkdir(parents=True, exist_ok=True) directory_stat = path.parent.lstat() if (stat.S_ISLNK(directory_stat.st_mode) or not stat.S_ISDIR(directory_stat.st_mode) or directory_stat.st_uid != 0 or directory_stat.st_gid != 0 or stat.S_IMODE(directory_stat.st_mode) != 0o700): die(f"runtime directory is unsafe: {path.parent}") temporary = path.with_name(f".{path.name}.{os.getpid()}.{time.time_ns()}.tmp") descriptor = None try: descriptor = os.open(str(temporary), os.O_WRONLY | os.O_CREAT | os.O_EXCL, mode) os.write(descriptor, data) os.fsync(descriptor) os.fchown(descriptor, 0, 0) os.fchmod(descriptor, mode) os.close(descriptor) descriptor = None os.replace(temporary, path) fsync_directory(path.parent) finally: if descriptor is not None: os.close(descriptor) if temporary.exists(): temporary.unlink() def ensure_root_runtime_directory(path): try: path_stat = path.lstat() except FileNotFoundError: path.mkdir(parents=True, exist_ok=False) path_stat = path.lstat() if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISDIR(path_stat.st_mode): die(f"runtime directory is unsafe: {path}") os.chown(path, 0, 0) path.chmod(0o700) def prepare_engine_credential_backend_runtime(): preflight = preflight_engine_credential_backend_runtime() installed_template = component_root("engine") / ENGINE_CREDENTIAL_BACKEND_OVERRIDE_TEMPLATE_REL try: template_stat = installed_template.lstat() template = installed_template.read_text(encoding="utf-8") except (FileNotFoundError, OSError, UnicodeDecodeError): die("installed Engine backend immutable runtime template is unreadable") if (stat.S_ISLNK(template_stat.st_mode) or not stat.S_ISREG(template_stat.st_mode) or template != expected_engine_credential_backend_override()): die("installed Engine backend immutable runtime template mismatch") ensure_root_runtime_directory(ENGINE_CREDENTIAL_BACKEND_RUNTIME_DIR) if preflight["mode"] in ("verified-derived-retry", "verified-prepared-not-active"): validate_engine_backend_activation_marker() validate_engine_backend_runtime_override_file() metadata = read_engine_backend_runtime_metadata() image = inspect_engine_backend_derived_image(metadata) if engine_backend_tool_versions(image_ref=ENGINE_CREDENTIAL_BACKEND_IMAGE) != metadata["tools"]: die("Engine backend immutable runtime tool drift detected") return metadata if preflight["mode"] == "recoverable-prepared-partial": quarantine_engine_backend_partial_runtime() return prepare_engine_credential_backend_runtime() with tempfile.TemporaryDirectory(prefix="engine-backend-runtime-", dir=TMP_DIR) as temporary_name: temporary = Path(temporary_name) raw_export = temporary / "container-export.tar" canonical_rootfs = temporary / "canonical-rootfs.tar" export = subprocess.run( [str(DOCKER), "container", "export", "--output", str(raw_export), preflight["container_id"]], check=False, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=300, ) if export.returncode != 0: die("Engine backend container export failed") rootfs_sha256 = build_canonical_engine_backend_rootfs(raw_export, canonical_rootfs) raw_export.unlink() dockerfile = "\n".join(( "FROM scratch", "ADD canonical-rootfs.tar /", "LABEL org.nodedc.release=\"credential-sink-20260716-001\"", f"LABEL org.nodedc.source-image-id=\"{preflight['source_image_id']}\"", f"LABEL org.nodedc.rootfs-sha256=\"{rootfs_sha256}\"", f"LABEL org.nodedc.package-lock-sha256=\"{ENGINE_CREDENTIAL_BACKEND_PACKAGE_LOCK_SHA256}\"", "WORKDIR /app", "CMD [\"/usr/local/bin/node\",\"server/index.js\"]", "", )) (temporary / "Dockerfile").write_text(dockerfile, encoding="utf-8") (temporary / "Dockerfile").chmod(0o600) image = ensure_engine_backend_release_image( temporary, preflight["source_image_id"], rootfs_sha256, preflight["tools"], ) tools = preflight["tools"] os.chown(canonical_rootfs, 0, 0) canonical_rootfs.chmod(0o600) os.replace(canonical_rootfs, ENGINE_CREDENTIAL_BACKEND_ROOTFS_FILE) fsync_directory(ENGINE_CREDENTIAL_BACKEND_RUNTIME_DIR) atomic_write_root_file( ENGINE_CREDENTIAL_BACKEND_OVERRIDE_FILE, template.encode("utf-8"), ) override_sha256 = validate_engine_backend_runtime_override_file() metadata = { "schemaVersion": "nodedc.engine-backend-immutable-runtime/v1", "release": "credential-sink-20260716-001", "sourceContainerId": preflight["container_id"], "sourceImageId": preflight["source_image_id"], "derivedImageId": image["Id"], "rootfsSha256": rootfs_sha256, "packageLockSha256": ENGINE_CREDENTIAL_BACKEND_PACKAGE_LOCK_SHA256, "nodeModulesTreeSha256": preflight["node_modules_tree_sha256"], "overrideSha256": override_sha256, "tools": tools, } atomic_write_root_file( ENGINE_CREDENTIAL_BACKEND_METADATA_FILE, (json.dumps(metadata, ensure_ascii=False, indent=2, sort_keys=True) + "\n").encode("utf-8"), ) read_engine_backend_runtime_metadata() atomic_write_root_file( ENGINE_CREDENTIAL_BACKEND_ACTIVATION_FILE, f"sha256:{sha256_file(ENGINE_CREDENTIAL_BACKEND_METADATA_FILE)}\n".encode("ascii"), ) validate_engine_backend_activation_marker() return metadata def preflight_engine_credential_sink_ready(): url = "http://127.0.0.1:3001/internal/engine-credential-sink/v1/health" try: request = urllib.request.Request(url, headers={"Cache-Control": "no-store"}) with NO_REDIRECT_OPENER.open(request, timeout=10) as response: if response.status != 200: die(f"Engine credential sink preflight failed: HTTP {response.status}") body = response.read(16 * 1024) except urllib.error.HTTPError as exc: die(f"Engine credential sink preflight failed: HTTP {exc.code}") except Exception as exc: die(f"Engine credential sink preflight failed: {type(exc).__name__}") try: value = json.loads(body) except (UnicodeDecodeError, json.JSONDecodeError): die("Engine credential sink preflight returned invalid JSON") if (value.get("ok") is not True or value.get("ready") is not True or value.get("service") != "NDC Engine Credential Sink" or value.get("issuer") != { "serviceId": "platform.engine-credential-provisioner", "keyId": ENGINE_CREDENTIAL_PROVISIONER_KEY_ID, }): die("Engine credential sink preflight identity/readiness mismatch") def engine_n8n_container_ids(): result = subprocess.run( [*compose_base_cmd("engine"), "ps", "-q", "n8n"], cwd=str(component_compose_root("engine")), check=False, capture_output=True, text=True, ) if result.returncode != 0: die("Engine n8n container lookup failed") container_ids = [line.strip() for line in result.stdout.splitlines() if line.strip()] if len(container_ids) != 1: die(f"Engine n8n topology mismatch: expected=1 actual={len(container_ids)}") return container_ids def inspect_engine_n8n_base_image(): images = docker_json(["image", "inspect", ENGINE_N8N_BASE_IMAGE], "Engine n8n base image inspect") if not isinstance(images, list) or len(images) != 1 or not isinstance(images[0], dict): die("Engine n8n base image inspect shape mismatch") image = images[0] image_id = image.get("Id") if not isinstance(image_id, str) or not re.fullmatch(r"sha256:[a-f0-9]{64}", image_id): die("Engine n8n base image id is invalid") if image.get("Architecture") != ENGINE_N8N_BASE_ARCHITECTURE or image.get("Os") != "linux": die("Engine n8n base image platform mismatch") return { "id": image_id, "architecture": image.get("Architecture"), "repo_digests": tuple(image.get("RepoDigests") or ()), } def inspect_container(container_id): containers = docker_json(["inspect", container_id], "Engine n8n container inspect") if not isinstance(containers, list) or len(containers) != 1 or not isinstance(containers[0], dict): die("Engine n8n container inspect shape mismatch") return containers[0] def engine_n8n_version(container_id): result = subprocess.run( [str(DOCKER), "exec", container_id, "n8n", "--version"], check=False, capture_output=True, text=True, timeout=60, ) version = result.stdout.strip() if result.returncode != 0 or version != ENGINE_N8N_VERSION: die("Engine n8n runtime version mismatch") return version def engine_n8n_package_loader_probe_script(): # LoadNodesAndCredentials.init() in n8n 2.3.2 initializes NODE_PATH before # it loads community packages. A standalone `node -e` probe must reproduce # that image-owned module-resolution step or peer imports such as # n8n-workflow fail even though the real n8n loader succeeds. return ( "process.env.NODE_PATH='" + ENGINE_N8N_NODE_MODULES_PATH + "';require('module').Module._initPaths();" "const {PackageDirectoryLoader}=require('" + ENGINE_N8N_NODE_MODULES_PATH + "/n8n-core');" "(async()=>{const loader=new PackageDirectoryLoader('" + ENGINE_N8N_RUNTIME_PACKAGE_PATH + "');await loader.loadAll();process.stdout.write(JSON.stringify({packageName:loader.packageName," "nodes:loader.types.nodes.map(x=>x.name),credentials:loader.types.credentials.map(x=>x.name)}))})()" ".catch(error=>{process.stderr.write(String(error&&error.code||'PROBE_ERROR'));process.exit(1)});" ) def engine_n8n_private_loader_catalog(container_id): container = inspect_container(container_id) package_mounts = [ mount for mount in (container.get("Mounts") or ()) if mount.get("Destination") == ENGINE_N8N_RUNTIME_PACKAGE_PATH ] if not package_mounts: return {"node_types": [], "credential_types": []} if len(package_mounts) != 1 or package_mounts[0].get("RW") is not False: die("Engine n8n package-loader probe found an unsafe package mount") script = engine_n8n_package_loader_probe_script() result = subprocess.run( [str(DOCKER), "exec", container_id, "node", "-e", script], check=False, capture_output=True, text=True, timeout=120, ) if result.returncode != 0: raw_category = (result.stderr or "").strip().upper() category = raw_category if re.fullmatch(r"[A-Z0-9_]{1,64}", raw_category) else "PROBE_ERROR" die(f"Engine n8n package-loader probe failed: category={category}") try: catalog = json.loads(result.stdout) except json.JSONDecodeError: die("Engine n8n package-loader probe returned invalid JSON") require_exact_json_keys(catalog, ("packageName", "nodes", "credentials"), "Engine n8n package-loader probe") if catalog.get("packageName") != "n8n-nodes-ndc": die("Engine n8n package-loader probe package identity mismatch") nodes = catalog.get("nodes") credentials = catalog.get("credentials") if not isinstance(nodes, list) or not all(isinstance(value, str) for value in nodes): die("Engine n8n package-loader probe node set is invalid") if not isinstance(credentials, list) or not all(isinstance(value, str) for value in credentials): die("Engine n8n package-loader probe credential set is invalid") return { "node_types": [f"n8n-nodes-ndc.{value}" for value in nodes], "credential_types": credentials, } def engine_n8n_current_state(): descriptor = current_engine_n8n_transition_descriptor() if descriptor and descriptor["action"] == "activate": return descriptor["releaseId"], descriptor return "verified_inactive", descriptor def validate_engine_n8n_base_compose_source(): compose_path = component_root("engine") / "docker-compose.yml" try: compose = compose_path.read_text(encoding="utf-8") except (OSError, UnicodeDecodeError): die("Engine base Compose cannot be read") services = re.findall(r"(?m)^ ([A-Za-z0-9_-]+):\s*$", compose) if tuple(services) != ENGINE_BASE_COMPOSE_SERVICES: die("Engine base Compose exact service topology mismatch") match = re.search(r"(?ms)^ n8n:\s*\n(?P.*?)(?=^ [A-Za-z0-9_-]+:\s*$)", compose) if not match: die("Engine base Compose n8n service cannot be isolated") n8n_service = match.group("body") expected_image = "image: docker.n8n.io/n8nio/n8n:${N8N_IMAGE_TAG:-2.3.2}" if expected_image not in n8n_service: die("Engine base Compose n8n version mismatch") required_fragments = ( "./n8n-data:/home/node/.n8n", "./nodedc-source/services/n8n/runtime-plugin:/opt/nodedc/runtime-plugin:ro", "n8n-postgres:", ) if any(fragment not in n8n_service for fragment in required_fragments): die("Engine base Compose n8n persistence/runtime dependency mismatch") forbidden_fragments = ( "build:", "N8N_CUSTOM_EXTENSIONS", "CUSTOM.", ENGINE_N8N_RUNTIME_PACKAGE_PATH, "N8N_USER_FOLDER", "N8N_COMMUNITY_PACKAGES_ENABLED", "N8N_COMMUNITY_PACKAGES_PREVENT_LOADING", "N8N_REINSTALL_MISSING_PACKAGES", ) if any(fragment in n8n_service for fragment in forbidden_fragments): die("Engine base Compose contains a non-baseline n8n activation setting") def preflight_engine_n8n_transition(descriptor, enforce_expected_current): validate_engine_n8n_base_compose_source() if descriptor["action"] == "activate": validate_engine_n8n_staged_release(descriptor) base_image = inspect_engine_n8n_base_image() container_id = engine_n8n_container_ids()[0] container = inspect_container(container_id) state = container.get("State") or {} if state.get("Status") != "running": die("Engine n8n baseline container is not running") if container.get("Image") != base_image["id"]: die("Engine n8n running image does not match the local 2.3.2 base image") version = engine_n8n_version(container_id) current_state, current_descriptor = engine_n8n_current_state() state_matches = current_state == descriptor["expectedCurrent"] current_catalog = engine_n8n_private_loader_catalog(container_id) 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_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: die("Engine n8n current live private-credential set does not match its transition state") catalog_descriptor = ( current_descriptor if current_state != "verified_inactive" and current_descriptor is not None else inactive_engine_n8n_descriptor(descriptor) ) validate_engine_n8n_catalog_payload(component_root("engine"), catalog_descriptor) if enforce_expected_current and not state_matches: die( "Engine n8n transition stale current state: " f"expected={descriptor['expectedCurrent']} actual={current_state}" ) return { "base_image_id": base_image["id"], "base_image_architecture": base_image["architecture"], "base_image_repo_digests": base_image["repo_digests"], "container_id": container_id, "current_state": current_state, "current_types": current_types, "current_credentials": current_credentials, "n8n_version": version, "state_matches": state_matches, } def add_engine_n8n_sealed_member_paths(expected_paths, member_name): parts = PurePosixPath(member_name).parts if not parts or parts[0] != "package": die("Engine sealed n8n package member root mismatch") for depth in range(1, len(parts) + 1): expected_paths.add(PurePosixPath(*parts[:depth]).as_posix()) def validate_engine_n8n_sealed_release(release_dir, staged_release_dir, descriptor): if release_dir.is_symlink() or not release_dir.is_dir(): die("Engine sealed n8n release is missing or unsafe") for filename in ("package.tgz", "release.json", "rollback.json"): installed = release_dir / filename staged = staged_release_dir / filename if installed.is_symlink() or not installed.is_file(): die(f"Engine sealed n8n release member is unsafe: {filename}") if sha256_file(installed) != sha256_file(staged): die(f"Engine sealed n8n release member mismatch: {filename}") package_root = release_dir / "package" if package_root.is_symlink() or not package_root.is_dir(): die("Engine sealed n8n package tree is missing or unsafe") expected_paths = {"package", "package.tgz", "release.json", "rollback.json"} with tarfile.open(staged_release_dir / "package.tgz", "r:gz") as archive: for member in archive: # npm package tarballs do not have to carry explicit directory # members. Extraction still creates those parent directories, so # they are part of the exact sealed tree rather than unexpected # content. add_engine_n8n_sealed_member_paths(expected_paths, member.name) target = release_dir.joinpath(*PurePosixPath(member.name).parts) target_stat = target.lstat() if stat.S_ISLNK(target_stat.st_mode): die(f"Engine sealed n8n package symlink rejected: {member.name}") if member.isdir(): if not stat.S_ISDIR(target_stat.st_mode): die(f"Engine sealed n8n package directory mismatch: {member.name}") else: if not stat.S_ISREG(target_stat.st_mode) or target_stat.st_size != member.size: die(f"Engine sealed n8n package file mismatch: {member.name}") source = archive.extractfile(member) if source is None: die(f"Engine sealed n8n package file unreadable: {member.name}") import hashlib expected_sha = hashlib.sha256(source.read()).hexdigest() if sha256_file(target) != expected_sha: die(f"Engine sealed n8n package content mismatch: {member.name}") actual_paths = { path.relative_to(release_dir).as_posix() for path in release_dir.rglob("*") } if actual_paths != expected_paths: die("Engine sealed n8n release tree exact member set mismatch") for path in [release_dir, *release_dir.rglob("*")]: path_stat = path.lstat() if path_stat.st_uid != 0 or path_stat.st_gid != 0: die(f"Engine sealed n8n release ownership mismatch: {path}") expected_mode = 0o555 if stat.S_ISDIR(path_stat.st_mode) else 0o444 if stat.S_IMODE(path_stat.st_mode) != expected_mode: die(f"Engine sealed n8n release mode mismatch: {path}") package_json = read_strict_json(package_root / "package.json", "Engine sealed n8n package.json") if package_json.get("name") != "n8n-nodes-ndc" or package_json.get("version") != descriptor["packageVersion"]: die("Engine sealed n8n package identity mismatch") def ensure_engine_n8n_sealed_release(descriptor, current_stamp): staged_release_dir = validate_engine_n8n_staged_release(descriptor) release_parent = ENGINE_N8N_SEALED_RELEASES_ROOT / "releases" / "n8n-nodes-ndc" release_parent.mkdir(parents=True, exist_ok=True) for path in ( ENGINE_N8N_SEALED_RELEASES_ROOT, ENGINE_N8N_SEALED_RELEASES_ROOT / "releases", release_parent, ): path_stat = path.lstat() if stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISDIR(path_stat.st_mode): die(f"Engine sealed n8n release parent is unsafe: {path}") os.chown(path, 0, 0) path.chmod(0o755) release_dir = release_parent / descriptor["releaseId"] if release_dir.exists() or release_dir.is_symlink(): validate_engine_n8n_sealed_release(release_dir, staged_release_dir, descriptor) return release_dir next_dir = release_parent / f".{descriptor['releaseId']}.next-{current_stamp}" if next_dir.exists() or next_dir.is_symlink(): die(f"Engine sealed n8n release staging path already exists: {next_dir}") next_dir.mkdir(mode=0o700) for filename in ("package.tgz", "release.json", "rollback.json"): shutil.copy2(staged_release_dir / filename, next_dir / filename) with tarfile.open(staged_release_dir / "package.tgz", "r:gz") as archive: for member in archive: relative = PurePosixPath(member.name) target = next_dir.joinpath(*relative.parts) if member.isdir(): target.mkdir(parents=True, exist_ok=True) continue target.parent.mkdir(parents=True, exist_ok=True) source = archive.extractfile(member) if source is None: die(f"Engine sealed n8n package member unreadable: {member.name}") with target.open("xb") as output: shutil.copyfileobj(source, output) for path in [next_dir, *next_dir.rglob("*")]: path_stat = path.lstat() if stat.S_ISLNK(path_stat.st_mode): die(f"Engine sealed n8n release symlink rejected: {path}") os.chown(path, 0, 0) if stat.S_ISDIR(path_stat.st_mode): path.chmod(0o555) elif stat.S_ISREG(path_stat.st_mode): path.chmod(0o444) else: die(f"Engine sealed n8n release special member rejected: {path}") if release_dir.exists() or release_dir.is_symlink(): die("Engine sealed n8n release appeared concurrently") next_dir.rename(release_dir) validate_engine_n8n_sealed_release(release_dir, staged_release_dir, descriptor) return release_dir def wait_engine_n8n_readiness(container_id): script = ( "const http=require('http');const req=http.get('http://127.0.0.1:5678/healthz/readiness'," "r=>{r.resume();process.exit(r.statusCode===200?0:1)});req.on('error',()=>process.exit(1));" "req.setTimeout(4000,()=>{req.destroy();process.exit(1)});" ) for _attempt in range(1, 61): result = subprocess.run( [str(DOCKER), "exec", container_id, "node", "-e", script], check=False, capture_output=True, text=True, timeout=10, ) if result.returncode == 0: return time.sleep(5) die("Engine n8n readiness check failed") def accept_engine_n8n_runtime(descriptor): base_image = inspect_engine_n8n_base_image() container_id = engine_n8n_container_ids()[0] container = inspect_container(container_id) state = container.get("State") or {} if state.get("Status") != "running" or container.get("Image") != base_image["id"]: die("Engine n8n runtime image/state acceptance failed") wait_engine_n8n_readiness(container_id) engine_n8n_version(container_id) config = container.get("Config") or {} env = set(config.get("Env") or ()) mounts = container.get("Mounts") or () package_mounts = [mount for mount in mounts if mount.get("Destination") == ENGINE_N8N_RUNTIME_PACKAGE_PATH] if descriptor["action"] == "activate": expected_source = f"/volume2/nodedc-demo/{descriptor['sealedReleaseRelativePath']}" if len(package_mounts) != 1: die("Engine n8n package mount count mismatch") package_mount = package_mounts[0] if package_mount.get("Source") != expected_source or package_mount.get("RW") is not False: die("Engine n8n sealed package mount mismatch") required_env = { "N8N_USER_FOLDER=/home/node", "N8N_COMMUNITY_PACKAGES_ENABLED=true", "N8N_COMMUNITY_PACKAGES_PREVENT_LOADING=false", "N8N_REINSTALL_MISSING_PACKAGES=false", } if not required_env.issubset(env): die("Engine n8n package loader environment mismatch") labels = config.get("Labels") or {} if labels.get("nodedc.n8n-private-extension.release") != descriptor["releaseId"]: die("Engine n8n release label mismatch") if labels.get("nodedc.n8n-private-extension.package-sha256") != descriptor["packageSha256"]: die("Engine n8n package label mismatch") else: if package_mounts: die("Engine n8n inactive baseline still has the private package mount") inactive_forbidden = ( "N8N_USER_FOLDER=", "N8N_COMMUNITY_PACKAGES_ENABLED=", "N8N_COMMUNITY_PACKAGES_PREVENT_LOADING=", "N8N_REINSTALL_MISSING_PACKAGES=", ) if any(item.startswith(inactive_forbidden) for item in env): die("Engine n8n inactive baseline still has private package loader flags") if any(item.startswith("N8N_CUSTOM_EXTENSIONS=") or item.startswith("CUSTOM.") for item in env): die("Engine n8n custom extension environment is forbidden") started_at = str(state.get("StartedAt") or "") logs = subprocess.run( [str(DOCKER), "logs", "--since", started_at, container_id], check=False, capture_output=True, text=True, timeout=60, ) if logs.returncode != 0: die("Engine n8n loader log inspection failed") scoped_logs = f"{logs.stdout}\n{logs.stderr}".lower() error_patterns = ( r"n8n-nodes-ndc.{0,400}(?:cannot find|error|failed|duplicate|eacces|syntaxerror)", r"(?:cannot find|error loading|failed to load|duplicate|eacces|syntaxerror).{0,400}n8n-nodes-ndc", ) if any(re.search(pattern, scoped_logs, re.DOTALL) for pattern in error_patterns): 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_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") if loader_catalog["credential_types"] != expected_credentials: die("Engine n8n live package-loader credential catalog acceptance failed") validate_engine_n8n_catalog_payload(component_root("engine"), descriptor) first_restart_count = int(container.get("RestartCount") or 0) time.sleep(2) stable_container = inspect_container(container_id) if stable_container.get("State", {}).get("Status") != "running": die("Engine n8n container did not remain running") if int(stable_container.get("RestartCount") or 0) != first_restart_count: die("Engine n8n container restarted during acceptance") return { "container_id": container_id, "image_id": base_image["id"], "node_types": expected_types, "credential_types": list(descriptor["expectedCredentialTypes"]), } MAP_ACCESS_CONFIG = Path("/etc/ssh/sshd_config") MAP_ACCESS_SSHD = Path("/usr/bin/sshd") MAP_ACCESS_PID = Path("/var/run/sshd.pid") MAP_ACCESS_BASE_SHA = "adf3da038acb21e30a1b79089f53917e7e82b469183889567b7d22f7b83163ba" MAP_ACCESS_APPEND = ( b"\n# BEGIN NODEDC MISSION CORE MAP ACCESS v1\n" b"Match User dctouch\n" b" AllowTcpForwarding local\n" b" PermitOpen 127.0.0.1:18103\n" b"Match all\n" b"# END NODEDC MISSION CORE MAP ACCESS v1\n" ) def read_map_access_descriptor(root, entries): if tuple(entries) != ("access.json",): die("Map access file selection mismatch") value = read_strict_json(root / "access.json", "Map access descriptor", max_bytes=1024) if (set(value) != {"schemaVersion", "state"} or value["schemaVersion"] != "nodedc.mission-core-map-access.v1" or value["state"] not in ("enabled", "disabled")): die("Map access descriptor mismatch") return value["state"] def map_access_policy(raw, desired): base = raw[:-len(MAP_ACCESS_APPEND)] if raw.endswith(MAP_ACCESS_APPEND) else raw if hashlib.sha256(base).hexdigest() != MAP_ACCESS_BASE_SHA: die("Map access SSH policy predecessor drift") return base + MAP_ACCESS_APPEND if desired == "enabled" else base def map_access_read_config(): metadata = MAP_ACCESS_CONFIG.lstat() if (not stat.S_ISREG(metadata.st_mode) or metadata.st_uid != 0 or metadata.st_mode & 0o022 or metadata.st_size > 65536): die("Map access SSH configuration metadata rejected") return MAP_ACCESS_CONFIG.read_bytes() def map_access_effective(config, user, address): result = subprocess.run( [str(MAP_ACCESS_SSHD), "-T", "-f", str(config), "-C", f"user={user},host=localhost,addr={address}"], capture_output=True, text=True, timeout=10, ) if result.returncode != 0: die("Map access SSH effective-policy validation failed") return dict(line.split(" ", 1) for line in result.stdout.splitlines() if " " in line) def map_access_validate_candidate(before, candidate): check = subprocess.run([str(MAP_ACCESS_SSHD), "-t", "-f", str(candidate)], capture_output=True, timeout=10) if check.returncode != 0: die("Map access SSH syntax validation failed") enabled = candidate.read_bytes().endswith(MAP_ACCESS_APPEND) with tempfile.NamedTemporaryFile(dir=TMP_DIR) as original: original.write(before); original.flush() for address in ("127.0.0.1", "100.114.248.4"): for user in ("dctouch", "root", "admin", "anonymous"): old = map_access_effective(original.name, user, address) new = map_access_effective(candidate, user, address) if user == "dctouch": old["allowtcpforwarding"] = "local" if enabled else "no" old["permitopen"] = "127.0.0.1:18103" if enabled else "any" if old != new: die("Map access would change unrelated SSH authority") def preflight_map_access(root, entries): desired = read_map_access_descriptor(root, entries) before = map_access_read_config() after = map_access_policy(before, desired) with tempfile.NamedTemporaryFile(dir=TMP_DIR) as candidate: candidate.write(after); candidate.flush() map_access_validate_candidate(before, Path(candidate.name)) return before, after def map_access_replace(raw): # Same-filesystem atomic replace; host policy is never accepted from an artifact. original = MAP_ACCESS_CONFIG.stat() fd, name = tempfile.mkstemp(prefix=".nodedc-map-", dir=MAP_ACCESS_CONFIG.parent) try: with os.fdopen(fd, "wb") as stream: os.fchown(stream.fileno(), 0, original.st_gid) os.fchmod(stream.fileno(), stat.S_IMODE(original.st_mode)) stream.write(raw); stream.flush(); os.fsync(stream.fileno()) os.replace(name, MAP_ACCESS_CONFIG) directory_fd = os.open(str(MAP_ACCESS_CONFIG.parent), os.O_RDONLY) try: os.fsync(directory_fd) finally: os.close(directory_fd) finally: if os.path.exists(name): os.unlink(name) def map_access_reload(): metadata = MAP_ACCESS_PID.lstat() if not stat.S_ISREG(metadata.st_mode) or metadata.st_uid != 0 or metadata.st_mode & 0o022: die("Map access SSH PID metadata rejected") pid = int(MAP_ACCESS_PID.read_text().strip()) if pid <= 1 or Path(f"/proc/{pid}/exe").resolve() != MAP_ACCESS_SSHD.resolve(): die("Map access SSH master identity mismatch") os.kill(pid, signal.SIGHUP) deadline = time.monotonic() + 10 while time.monotonic() < deadline: try: with socket.create_connection(("127.0.0.1", 22), timeout=2) as connection: connection.settimeout(2) if connection.recv(255).startswith(b"SSH-2.0-"): return except OSError: pass time.sleep(0.25) die("Map access SSH listener did not recover") def backup_map_access(backup_dir): raw = map_access_read_config() map_access_policy(raw, "enabled") path = backup_dir / "sshd-config-before" with path.open("xb") as output: os.fchmod(output.fileno(), 0o600) output.write(raw); output.flush(); os.fsync(output.fileno()) def apply_map_access(root, entries): before, after = preflight_map_access(root, entries) if after != before: map_access_replace(after) map_access_reload() def accept_map_access(root, entries): before, after = preflight_map_access(root, entries) if before != after: die("Map access installed policy differs from the requested state") healthcheck_url({ "url": "http://127.0.0.1:18103/healthz", "headers": {"x-nodedc-user-id": "mission-core-loopback-operator"}, "expected_json": {"ok": True, "service": "nodedc-map-gateway"}, }) def rollback_map_access(root, backup_dir, entries, current_stamp): original = backup_dir / "sshd-config-before" metadata = original.lstat() if not stat.S_ISREG(metadata.st_mode) or metadata.st_uid != 0 or metadata.st_mode & 0o077: die("Map access backup metadata rejected") raw = original.read_bytes() map_access_policy(raw, "enabled") current = map_access_read_config() map_access_policy(current, "enabled") map_access_validate_candidate(current, original) if raw != current: map_access_replace(raw) map_access_reload() restore_overlay_source(root, backup_dir, entries, current_stamp) if map_access_read_config() != raw: die("Map access rollback policy verification failed") def plan_artifact(artifact): validate_artifact_location(artifact) ensure_layout() sha = sha256_file(artifact) publish_grant_preflight = None node_intelligence_preflight = None mcp_control_plane_preflight = None mcp_ontology_sdk_preflight = None mcp_autonomy_provider_v5_preflight = None l2_closed_loop_preflight = None device_plane_foundation_recovery_preflight = None device_plane_network_publication_preflight = None device_plane_b2_ingress_preflight = None device_plane_b2_recovery_preflight = None device_plane_manager_activation_preflight = None device_plane_edge_core_channel_preflight = None device_plane_control_core_release_preflight = None device_plane_manager_reconciliation_preflight = None device_plane_manager_v2_reconciliation_preflight = None device_plane_control_core_v3_reconciliation_preflight = None device_plane_control_core_incident_audit_preflight = None device_plane_control_core_migration_replay_audit_preflight = None device_plane_control_core_migration_replay_recovery_preflight = None device_plane_control_core_migration_replay_checkpoint_recovery_preflight = None device_plane_backhaul_preflight = None device_plane_backhaul_vps_enrollment_preflight = None device_plane_runtime_before = None composite_provider_v4_preflight = None provider_rotating_slot_preflight = None provider_authority_diagnostics_preflight = None depttrans_zone_authority_v1_preflight = None provider_target_host_policy_preflight = None mcp_execution_profile_decoder_preflight = None mcp_telemetry_catalog_preflight = None mcp_execution_plan_materialization_preflight = None mcp_execution_plan_telemetry_runtime_preflight = None mcp_execution_plan_module_ownership_preflight = None mcp_normalized_identity_search_preflight = None mcp_l1_credential_reuse_preflight = None mcp_l1_credential_provenance_preflight = None mcp_execution_plan_sandbox_runtime_preflight = None mcp_gelios_items_envelope_preflight = None mcp_registered_execution_profiles_preflight = None mcp_gelios_units_items_preflight = None provider_catalog_preflight = None device_plane_postgres_preflight = None device_plane_foundation_recovery_preflight = None gitea_preflight = None gitea_salvage_preflight = None with tempfile.TemporaryDirectory(prefix="plan-", dir=TMP_DIR) as tmp: manifest, entries, payload_dir = load_artifact(artifact, Path(tmp)) if is_gitea_fresh_install_slice(manifest["component"], entries): validate_gitea_compose_schema(payload_dir / GITEA_COMPOSE_REL) if is_gitea_incident_salvage_slice(manifest["component"], entries): validate_gitea_compose_schema(payload_dir / GITEA_COMPOSE_REL) if manifest["component"] == "mission-core-map-access": preflight_map_access(payload_dir, entries) reject_failed_artifact_replay(manifest, sha) reject_terminal_engine_l2_failed_artifact(manifest, sha) reject_terminal_device_plane_foundation_artifact(manifest, sha) reject_terminal_device_plane_manager_artifact( manifest, sha, entries, ) reject_terminal_device_plane_backhaul_artifact(manifest, sha) transition_descriptor = None transition_preflight = None if is_engine_n8n_transition(manifest["component"], entries): transition_descriptor = read_engine_n8n_transition_descriptor( payload_dir / ENGINE_N8N_TRANSITION_DESCRIPTOR_REL ) transition_preflight = preflight_engine_n8n_transition( transition_descriptor, enforce_expected_current=False, ) if is_engine_data_product_publish_grant_slice(manifest["component"], entries): publish_grant_preflight = ( preflight_engine_data_product_publish_grant_predecessor(payload_dir) ) if is_engine_node_intelligence_transition(manifest["component"], entries): 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 ) if is_engine_mcp_ontology_sdk_slice(manifest["component"], entries): mcp_ontology_sdk_preflight = preflight_engine_mcp_ontology_sdk_predecessor( payload_dir ) if is_engine_mcp_autonomy_provider_v5_slice(manifest["component"], entries): mcp_autonomy_provider_v5_preflight = ( preflight_engine_mcp_autonomy_provider_v5_predecessor(payload_dir) ) if is_engine_l2_closed_loop_slice(manifest["component"], entries): l2_closed_loop_preflight = preflight_engine_l2_closed_loop_predecessor( payload_dir ) if is_engine_composite_provider_v4_slice(manifest["component"], entries): composite_provider_v4_preflight = preflight_engine_composite_provider_v4_predecessor() if is_engine_provider_rotating_slot_slice(manifest["component"], entries): provider_rotating_slot_preflight = preflight_engine_provider_rotating_slot_predecessor() if is_engine_provider_authority_diagnostics_slice(manifest["component"], entries): provider_authority_diagnostics_preflight = ( preflight_engine_provider_authority_diagnostics_predecessor() ) if is_engine_depttrans_zone_authority_v1_slice(manifest["component"], entries): depttrans_zone_authority_v1_preflight = ( preflight_engine_depttrans_zone_authority_v1_predecessor() ) if is_engine_provider_target_host_policy_slice(manifest["component"], entries): provider_target_host_policy_preflight = ( preflight_engine_provider_target_host_policy_predecessor() ) if is_engine_mcp_execution_profile_decoder_slice( manifest["component"], entries, ): mcp_execution_profile_decoder_preflight = ( preflight_engine_mcp_execution_profile_decoder_predecessor() ) if is_engine_mcp_telemetry_catalog_slice(manifest["component"], entries): mcp_telemetry_catalog_preflight = ( preflight_engine_mcp_telemetry_catalog_predecessor() ) if is_engine_mcp_execution_plan_materialization_slice( manifest["component"], entries, ): mcp_execution_plan_materialization_preflight = ( preflight_engine_mcp_execution_plan_materialization_predecessor() ) if is_engine_mcp_execution_plan_telemetry_runtime_slice( manifest["component"], entries, ): mcp_execution_plan_telemetry_runtime_preflight = ( preflight_engine_mcp_execution_plan_telemetry_runtime_predecessor() ) if is_engine_mcp_execution_plan_module_ownership_slice( manifest["component"], entries, ): mcp_execution_plan_module_ownership_preflight = ( preflight_engine_mcp_execution_plan_module_ownership_predecessor() ) if is_engine_mcp_normalized_identity_search_slice( manifest["component"], entries, ): mcp_normalized_identity_search_preflight = ( preflight_engine_mcp_normalized_identity_search_predecessor() ) if is_engine_mcp_l1_credential_reuse_slice( manifest["component"], entries, ): mcp_l1_credential_reuse_preflight = ( preflight_engine_mcp_l1_credential_reuse_predecessor() ) if is_engine_mcp_l1_credential_provenance_slice( manifest["component"], entries, ): mcp_l1_credential_provenance_preflight = ( preflight_engine_mcp_l1_credential_provenance_predecessor() ) if is_engine_mcp_execution_plan_sandbox_runtime_slice( manifest["component"], entries, ): mcp_execution_plan_sandbox_runtime_preflight = ( preflight_engine_mcp_execution_plan_sandbox_runtime_predecessor() ) if is_engine_mcp_gelios_items_envelope_slice( manifest["component"], entries, ): mcp_gelios_items_envelope_preflight = ( preflight_engine_mcp_gelios_items_envelope_predecessor() ) if is_engine_mcp_registered_execution_profiles_slice( manifest["component"], entries, ): mcp_registered_execution_profiles_preflight = ( preflight_engine_mcp_registered_execution_profiles_predecessor() ) if is_engine_mcp_gelios_units_items_slice( manifest["component"], entries, ): mcp_gelios_units_items_preflight = ( preflight_engine_mcp_gelios_units_items_predecessor() ) if is_engine_provider_security_catalog_slice(manifest["component"], entries): provider_catalog_preflight = preflight_engine_provider_security_catalog_predecessor() if is_device_plane_postgres_bootstrap_slice( manifest["component"], entries, ): device_plane_postgres_preflight = ( preflight_device_plane_postgres_bootstrap() ) if is_device_plane_foundation_recovery_slice( manifest["component"], entries, ): device_plane_foundation_recovery_preflight = ( validate_device_plane_foundation_recovery_evidence( payload_dir ) ) if is_device_plane_foundation_network_publication_slice( manifest["component"], entries, ): device_plane_network_publication_preflight = ( validate_device_plane_foundation_network_publication_evidence( payload_dir ) ) if is_device_plane_b2_discovery_ingress_slice( manifest["component"], entries, ): device_plane_b2_ingress_preflight = ( validate_device_plane_b2_discovery_ingress_evidence( payload_dir ) ) if is_device_plane_b2_discovery_rollback_recovery_slice( manifest["component"], entries, ): device_plane_b2_recovery_preflight = ( validate_device_plane_b2_discovery_rollback_recovery_evidence( payload_dir ) ) if is_device_plane_manager_control_plane_slice( manifest["component"], entries, ): device_plane_manager_activation_preflight = ( validate_device_plane_manager_activation_predecessor( payload_dir, preflight_phase="plan", ) ) if is_device_plane_edge_core_channel_bootstrap_slice( manifest["component"], entries, ): device_plane_edge_core_channel_preflight = ( validate_device_plane_edge_core_channel_bootstrap_predecessor( payload_dir ) ) if is_device_plane_control_core_release_slice( manifest["component"], entries, ): device_plane_control_core_release_preflight = ( validate_device_plane_control_core_release_predecessor( payload_dir, preflight_phase="plan", ) ) if is_device_plane_manager_reconciliation_slice( manifest["component"], entries, ): device_plane_manager_reconciliation_preflight = ( validate_device_plane_manager_reconciliation_evidence( payload_dir ) ) if is_device_plane_manager_v2_reconciliation_slice( manifest["component"], entries, ): device_plane_manager_v2_reconciliation_preflight = ( validate_device_plane_manager_v2_reconciliation_evidence( payload_dir ) ) if is_device_plane_control_core_v3_reconciliation_slice( manifest["component"], entries, ): device_plane_control_core_v3_reconciliation_preflight = ( validate_device_plane_control_core_v3_reconciliation_evidence( payload_dir ) ) if is_device_plane_control_core_incident_audit_slice( manifest["component"], entries, ): incident = validate_device_plane_control_core_incident_audit_evidence( payload_dir ) incident["diagnostics"] = ( collect_device_plane_control_core_incident_audit() ) device_plane_control_core_incident_audit_preflight = incident if is_device_plane_control_core_migration_replay_audit_slice( manifest["component"], entries, ): if ( manifest["id"] != DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_AUDIT_PATCH_ID ): die("Device Control Core migration replay audit patch id mismatch") device_plane_control_core_migration_replay_audit_preflight = ( validate_device_plane_control_core_migration_replay_audit_evidence( payload_dir ) ) if is_device_plane_control_core_migration_replay_recovery_slice( manifest["component"], entries, ): if ( manifest["id"] != DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_PATCH_ID ): die("Device Control Core migration recovery patch id mismatch") device_plane_control_core_migration_replay_recovery_preflight = ( validate_device_plane_control_core_migration_replay_recovery_evidence( payload_dir ) ) if is_device_plane_control_core_migration_replay_checkpoint_recovery_slice( manifest["component"], entries, ): if ( manifest["id"] != DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_PATCH_ID ): die( "Device Control Core migration replay checkpoint recovery " "patch id mismatch" ) device_plane_control_core_migration_replay_checkpoint_recovery_preflight = ( validate_device_plane_control_core_migration_replay_checkpoint_recovery_evidence( payload_dir ) ) if is_device_plane_backhaul_target_slice( manifest["component"], entries, ): device_plane_backhaul_preflight = ( validate_device_plane_backhaul_target_evidence(payload_dir) ) if is_device_plane_backhaul_vps_enrollment_slice( manifest["component"], entries, ): device_plane_backhaul_vps_enrollment_preflight = ( validate_device_plane_backhaul_vps_enrollment_evidence( payload_dir ) ) if is_gitea_fresh_install_slice(manifest["component"], entries): gitea_preflight = preflight_gitea_fresh_install() if is_gitea_incident_salvage_slice(manifest["component"], entries): gitea_salvage_preflight = preflight_gitea_incident_salvage( payload_dir, enforce_apply=False, ) component = manifest["component"] root = component_root(component) compose_root = component_compose_root(component) services = component_services(component, entries) builds = component_builds(component, entries) touches_publish_grant = is_engine_data_product_publish_grant_slice( component, entries, ) touches_composite_provider_v4 = is_engine_composite_provider_v4_slice( component, entries, ) touches_provider_rotating_slot = is_engine_provider_rotating_slot_slice( component, entries, ) touches_provider_authority_diagnostics = is_engine_provider_authority_diagnostics_slice( component, entries, ) touches_depttrans_zone_authority_v1 = is_engine_depttrans_zone_authority_v1_slice( component, entries, ) touches_provider_target_host_policy = is_engine_provider_target_host_policy_slice( component, entries, ) touches_mcp_execution_profile_decoder = ( is_engine_mcp_execution_profile_decoder_slice( component, entries, ) ) touches_mcp_telemetry_catalog = is_engine_mcp_telemetry_catalog_slice( component, entries, ) touches_mcp_execution_plan_materialization = ( is_engine_mcp_execution_plan_materialization_slice( component, entries, ) ) touches_mcp_execution_plan_telemetry_runtime = ( is_engine_mcp_execution_plan_telemetry_runtime_slice( component, entries, ) ) touches_mcp_execution_plan_module_ownership = ( is_engine_mcp_execution_plan_module_ownership_slice( component, entries, ) ) touches_mcp_normalized_identity_search = ( is_engine_mcp_normalized_identity_search_slice( component, entries, ) ) touches_mcp_l1_credential_reuse = ( is_engine_mcp_l1_credential_reuse_slice(component, entries) ) touches_mcp_l1_credential_provenance = ( is_engine_mcp_l1_credential_provenance_slice(component, entries) ) touches_mcp_execution_plan_sandbox_runtime = ( is_engine_mcp_execution_plan_sandbox_runtime_slice(component, entries) ) touches_mcp_gelios_items_envelope = ( is_engine_mcp_gelios_items_envelope_slice(component, entries) ) touches_mcp_registered_execution_profiles = ( is_engine_mcp_registered_execution_profiles_slice(component, entries) ) touches_mcp_gelios_units_items = ( is_engine_mcp_gelios_units_items_slice(component, entries) ) touches_agent_grant_migration = is_engine_agent_full_grant_migration_slice( component, entries, ) publish_grant_predecessor_sha256 = None agent_grant_migration_predecessor_sha256 = None credential_backend_preflight = None if ( touches_engine_credential_sink(component, entries) or touches_publish_grant or touches_composite_provider_v4 or touches_provider_rotating_slot or touches_provider_authority_diagnostics or touches_depttrans_zone_authority_v1 or touches_provider_target_host_policy or touches_mcp_execution_profile_decoder or touches_mcp_telemetry_catalog or touches_mcp_execution_plan_materialization or touches_mcp_execution_plan_telemetry_runtime or touches_mcp_execution_plan_module_ownership or touches_mcp_normalized_identity_search or touches_mcp_l1_credential_reuse or touches_mcp_l1_credential_provenance or touches_mcp_execution_plan_sandbox_runtime or touches_mcp_gelios_items_envelope or touches_mcp_registered_execution_profiles or touches_mcp_gelios_units_items or touches_agent_grant_migration ): credential_backend_preflight = preflight_engine_credential_backend_runtime() if touches_publish_grant: if publish_grant_preflight is None: die("Engine data product publish grant preflight is missing") publish_grant_predecessor_sha256 = publish_grant_preflight["compose_sha256"] if credential_backend_preflight["mode"] != "verified-derived-retry": die("Engine data product publish grant requires the active immutable credential backend") if touches_composite_provider_v4: if composite_provider_v4_preflight is None: die("Engine composite provider v4 preflight is missing") if credential_backend_preflight["mode"] != "verified-derived-retry": die("Engine composite provider v4 requires the active immutable credential backend") if touches_provider_rotating_slot: if provider_rotating_slot_preflight is None: die("Engine provider rotating slot preflight is missing") if credential_backend_preflight["mode"] != "verified-derived-retry": die("Engine provider rotating slot requires the active immutable credential backend") if touches_provider_authority_diagnostics: if provider_authority_diagnostics_preflight is None: die("Engine provider authority diagnostics preflight is missing") if credential_backend_preflight["mode"] != "verified-derived-retry": die("Engine provider authority diagnostics requires the active immutable credential backend") if touches_depttrans_zone_authority_v1: if depttrans_zone_authority_v1_preflight is None: die("Engine Depttrans zone authority v1 preflight is missing") if credential_backend_preflight["mode"] != "verified-derived-retry": die("Engine Depttrans zone authority v1 requires the active immutable credential backend") if touches_provider_target_host_policy: if provider_target_host_policy_preflight is None: die("Engine provider target host policy preflight is missing") if credential_backend_preflight["mode"] != "verified-derived-retry": die("Engine provider target host policy requires the active immutable credential backend") if touches_mcp_execution_profile_decoder: if mcp_execution_profile_decoder_preflight is None: die("Engine MCP execution profile decoder preflight is missing") if credential_backend_preflight["mode"] != "verified-derived-retry": die( "Engine MCP execution profile decoder requires the active " "immutable credential backend" ) if touches_mcp_telemetry_catalog: if mcp_telemetry_catalog_preflight is None: die("Engine MCP telemetry catalog preflight is missing") if credential_backend_preflight["mode"] != "verified-derived-retry": die( "Engine MCP telemetry catalog requires the active immutable " "credential backend" ) if touches_mcp_execution_plan_materialization: if mcp_execution_plan_materialization_preflight is None: die("Engine MCP execution plan materialization preflight is missing") if credential_backend_preflight["mode"] != "verified-derived-retry": die( "Engine MCP execution plan materialization requires the active " "immutable credential backend" ) if touches_mcp_execution_plan_telemetry_runtime: if mcp_execution_plan_telemetry_runtime_preflight is None: die( "Engine MCP execution plan telemetry runtime preflight is " "missing" ) if credential_backend_preflight["mode"] != "verified-derived-retry": die( "Engine MCP execution plan telemetry runtime requires the " "active immutable credential backend" ) if touches_mcp_execution_plan_module_ownership: if mcp_execution_plan_module_ownership_preflight is None: die( "Engine MCP execution plan module ownership preflight is " "missing" ) if credential_backend_preflight["mode"] != "verified-derived-retry": die( "Engine MCP execution plan module ownership requires the " "active immutable credential backend" ) if touches_mcp_normalized_identity_search: if mcp_normalized_identity_search_preflight is None: die("Engine MCP normalized identity search preflight is missing") if credential_backend_preflight["mode"] != "verified-derived-retry": die( "Engine MCP normalized identity search requires the active " "immutable credential backend" ) if touches_mcp_l1_credential_reuse: if mcp_l1_credential_reuse_preflight is None: die("Engine MCP L1 credential reuse preflight is missing") if credential_backend_preflight["mode"] != "verified-derived-retry": die( "Engine MCP L1 credential reuse requires the active immutable " "credential backend" ) if touches_mcp_l1_credential_provenance: if mcp_l1_credential_provenance_preflight is None: die("Engine MCP L1 credential provenance preflight is missing") if credential_backend_preflight["mode"] != "verified-derived-retry": die( "Engine MCP L1 credential provenance requires the active " "immutable credential backend" ) if touches_mcp_execution_plan_sandbox_runtime: if mcp_execution_plan_sandbox_runtime_preflight is None: die( "Engine MCP execution plan sandbox runtime preflight is " "missing" ) if credential_backend_preflight["mode"] != "verified-derived-retry": die( "Engine MCP execution plan sandbox runtime requires the " "active immutable credential backend" ) if touches_mcp_gelios_items_envelope: if mcp_gelios_items_envelope_preflight is None: die("Engine MCP Gelios items envelope preflight is missing") if credential_backend_preflight["mode"] != "verified-derived-retry": die( "Engine MCP Gelios items envelope requires the active " "immutable credential backend" ) if touches_mcp_registered_execution_profiles: if mcp_registered_execution_profiles_preflight is None: die( "Engine MCP registered execution profiles preflight is missing" ) if credential_backend_preflight["mode"] != "verified-derived-retry": die( "Engine MCP registered execution profiles requires the active " "immutable credential backend" ) if touches_mcp_gelios_units_items: if mcp_gelios_units_items_preflight is None: die("Engine MCP Gelios units items preflight is missing") if credential_backend_preflight["mode"] != "verified-derived-retry": die( "Engine MCP Gelios units items requires the active immutable " "credential backend" ) if touches_agent_grant_migration: agent_grant_migration_predecessor_sha256 = ( preflight_engine_agent_full_grant_migration_predecessor() ) if credential_backend_preflight["mode"] != "verified-derived-retry": die("Engine agent full grant migration requires the active immutable credential backend") print("== plan ==") print(f"artifact={artifact.name}") print(f"sha256={sha}") print(f"id={manifest['id']}") print(f"component={component}") print(f"type={manifest['type']}") print(f"payload_root={root}") if component == "mission-core-map-access": print("access=local-forward:dctouch:127.0.0.1:18103") print("runtime=sshd-scoped-reload;docker=untouched;cache=preserved") print("policy_predecessor=exact;rollback=automatic") print(f"compose_root={compose_root}") compose_project = component_compose_project(component) if compose_project: print(f"compose_project={compose_project}") for build_root, build_args in builds: print(f"build_root={build_root}") print(f"build={' '.join((str(DOCKER),) + tuple(build_args))}") if is_device_plane_control_core_incident_audit_slice(component, entries): print("services=none") else: print(f"services={' '.join(services)}") if gitea_preflight: print("gitea_transition=fresh-install-only") print(f"gitea_root={GITEA_ROOT}:required-absent") print(f"gitea_image={GITEA_IMAGE}") print(f"gitea_image_id={gitea_preflight['image_id']}") print("gitea_platform=linux/amd64") print("gitea_pull=never") print(f"gitea_docker_version={gitea_preflight['docker_version']}") print( "gitea_compose_version=" f"{gitea_preflight['compose_version']}" ) print("gitea_database=fresh-sqlite-users-0-repositories-0") print("gitea_lfs=disabled-pending-reviewed-restore-transition") print("gitea_transport=unix:/run/gitea/gitea.sock") print(f"gitea_socket_host={GITEA_SOCKET_FILE}") print("gitea_ssh=disabled-no-published-port") print("gitea_network_mode=none") print("gitea_docker_ports=none") print(f"gitea_secret=runner-managed:{GITEA_SECRET_KEY_FILE}") print(f"gitea_secret=runner-managed:{GITEA_INTERNAL_TOKEN_FILE}") print("gitea_legacy_root=unread-unmounted-untouched") print("gitea_legacy_container=never-started") print( "gitea_reverse_proxy=" f"{gitea_preflight['reverse_proxy']['upstream']}" ) print( "gitea_nginx_uds_bridge=" f"{gitea_preflight['nginx_bridge']['sha256']}" ) print("gitea_firewall=legacy-isolated-loopback-3000-ready") print( "gitea_rollback=stop-remove-candidate-preserve-runtime-state-" "quarantine-source" ) if gitea_salvage_preflight: print("gitea_transition=incident-salvage-clean-state") print(f"gitea_root={GITEA_ROOT}:required-absent") print(f"gitea_image={GITEA_SALVAGE_IMAGE}") print(f"gitea_image_id={GITEA_SALVAGE_IMAGE_ID}") print(f"gitea_repo_digest={GITEA_SALVAGE_REPO_DIGEST}") print("gitea_database=new-sqlite-no-legacy-import") print("gitea_users=2-active-8-locked-new-credentials") print("gitea_repositories=45-exact-object-and-ref-material-only") print("gitea_repository_visibility=dctouch-private-32,silver-public-13") print("gitea_two_factor_authentication=unchanged-not-configured") print("gitea_candidate_publication=socket-parent-0700") print("gitea_candidate_restart=no") legacy = gitea_salvage_preflight["legacy"] legacy_mount = legacy["mounts"][0] print(f"gitea_legacy_container_name={legacy['name']}") print(f"gitea_legacy_container_state={legacy['state']}") print(f"gitea_legacy_image_ref={legacy['image']}") print(f"gitea_legacy_image_id={legacy['image_id']}") print( "gitea_legacy_mount=" f"{legacy_mount[0]}:{legacy_mount[1]}:{legacy_mount[2]}:" f"{'rw' if legacy_mount[3] else 'ro'}" ) print( "gitea_snapshot_uuid=" f"{gitea_salvage_preflight['snapshot']['uuid']}" ) print( "gitea_snapshot_database_sha256=" f"{gitea_salvage_preflight['snapshot']['database_sha256']}" ) print( "gitea_reference_manifest_sha256=" f"{gitea_salvage_preflight['refs']['sha256']}" ) print( "gitea_reference_manifest_bytes=" f"{gitea_salvage_preflight['refs']['bytes']}" ) print( "gitea_reference_manifest_json=" f"{gitea_salvage_preflight['refs']['json']}" ) print( "gitea_reference_inventory=" f"stores:{gitea_salvage_preflight['refs']['repository_stores']}," f"refs:{gitea_salvage_preflight['refs']['refs']}," f"object_files:{gitea_salvage_preflight['refs']['object_files']}," f"object_bytes:{gitea_salvage_preflight['refs']['object_bytes']}," "excluded_derived_files:" f"{gitea_salvage_preflight['refs']['excluded_derived_files']}," "excluded_derived_bytes:" f"{gitea_salvage_preflight['refs']['excluded_derived_bytes']}," "excluded_quarantine_directories:" f"{gitea_salvage_preflight['refs']['excluded_quarantine_directories']}" ) disposition = gitea_salvage_preflight["disposition"] print(f"gitea_incident_disposition_sha256={disposition['sha256']}") print( "gitea_incident_disposition_refs=" f"forensic:{disposition['forensic_refs']}," f"live:{disposition['live_refs']}," f"archive-only:{disposition['archive_only_refs']}" ) print( "gitea_incident_disposition_policy=" "heads-tags-and-wiki-heads-live;pull-remote-refs-archive-only;" "legacy-metadata-sanitized-archive-only;no-legacy-row-import" ) topics = disposition["topics"] print(f"gitea_semantic_topics_sha256={topics['sha256']}") print(f"gitea_semantic_topics_bytes={topics['bytes']}") print(f"gitea_semantic_topics_json={topics['json']}") print( "gitea_incident_disposition_remaining_blockers=" + ",".join(disposition["blockers"]) ) unsupported = gitea_salvage_preflight["database"]["unsupported"] print( "gitea_unsupported_repository_report_sha256=" f"{unsupported['sha256']}" ) print( "gitea_unsupported_repository_report_bytes=" f"{unsupported['bytes']}" ) print( "gitea_unsupported_schema_catalog_sha256=" f"{unsupported['report']['schema_catalog_sha256']}" ) print( "gitea_unsupported_repository_report_json=" f"{unsupported['json']}" ) closure = gitea_salvage_preflight["closure_disposition"] print( "gitea_incident_closure_disposition_sha256=" f"{GITEA_SALVAGE_CLOSURE_DISPOSITION_SHA256}" ) print(f"gitea_incident_closure_report_sha256={closure['sha256']}") print(f"gitea_incident_closure_report_bytes={closure['bytes']}") print(f"gitea_incident_closure_report_json={closure['json']}") print( "gitea_apply_blockers=" + (",".join(gitea_salvage_preflight["blockers"]) or "none") ) if l2_closed_loop_preflight: descriptor = l2_closed_loop_preflight["descriptor"] print( "engine_l2_transition=" f"{l2_closed_loop_preflight['mode']}" ) print( "failed_patch=" f"{ENGINE_L2_CLOSED_LOOP_FAILED_PATCH_ID}" ) print( "failed_artifact_sha256=" f"{ENGINE_L2_CLOSED_LOOP_FAILED_ARTIFACT_SHA256}" ) print( "recovery_backup=" f"{l2_closed_loop_preflight['recovery_backup'].name}" ) print( "predecessor_gateway_sha256=" f"{l2_closed_loop_preflight['predecessor_gateway_sha256']}" ) print( "partial_gateway_sha256=" f"{l2_closed_loop_preflight['partial_gateway_sha256']}" ) print( "target_gateway_sha256=" f"{l2_closed_loop_preflight['target_gateway_sha256']}" ) print( "predecessor_descriptor_sha256=" f"{ENGINE_L2_CLOSED_LOOP_PARTIAL_SHA256[ENGINE_L2_CLOSED_LOOP_DESCRIPTOR_REL]}" ) print( "target_descriptor_sha256=" f"{ENGINE_L2_CLOSED_LOOP_TARGET_SHA256[ENGINE_L2_CLOSED_LOOP_DESCRIPTOR_REL]}" ) print(f"node_intelligence_release={descriptor['releaseId']}") print( "backend_current_barrier=" f"{l2_closed_loop_preflight['backend_mode']}" ) print( "backend_source_container_id=" f"{l2_closed_loop_preflight['backend_container_id']}" ) print( "app_source_container_id=" f"{l2_closed_loop_preflight['app_container_id']}" ) print("node_intelligence_image=preserved") print("backend_force_recreate=yes") print("app_force_recreate=yes") print("backend_pull=never") print("n8n_l1_l2_data=preserved") print("credentials=preserved") print("databases=preserved") print("ai_workspace=untouched") if node_intelligence_preflight: descriptor = node_intelligence_preflight["descriptor"] print(f"node_intelligence_transition={descriptor['action']}") print(f"node_intelligence_release={descriptor['releaseId']}") print(f"node_intelligence_current_state={node_intelligence_preflight['current_state']}") print(f"node_intelligence_upstream_commit={descriptor['upstream']['commit']}") print(f"node_intelligence_image={descriptor['image']['tag']}") print(f"node_intelligence_image_config_sha256={descriptor['image']['configSha256']}") if descriptor["action"] == "activate": print(f"node_intelligence_image_archive_sha256={descriptor['image']['archiveSha256']}") print("node_intelligence_build=offline-image-load") print("node_intelligence_pull=never") print(f"node_intelligence_secret=runner-managed:{ENGINE_NODE_INTELLIGENCE_SECRET_FILE}") 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 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 mcp_autonomy_provider_v5_preflight: print("engine_mcp_transition=capability-scoped-autonomy+provider-v5") print("engine_mcp_version=0.6.0") print("engine_mcp_installer=0.1.6") print("engine_mcp_authority=mcp-capability-intersect-user-objective") print("engine_mcp_plan=machine-safety-barrier") print("engine_mcp_retry_stop=three-identical-failures-without-new-evidence") print("engine_provider_packages=gelios.provider.v4,gelios.provider.v5") print("engine_data_product=fleet.positions.current.v4") print( "predecessor_gateway_sha256=" f"{mcp_autonomy_provider_v5_preflight['predecessor_gateway_sha256']}" ) print( "target_gateway_sha256=" f"{mcp_autonomy_provider_v5_preflight['target_gateway_sha256']}" ) print(f"backend_current_barrier={mcp_autonomy_provider_v5_preflight['backend_mode']}") print("node_intelligence_image=preserved") print("n8n_l1=untouched") if provider_catalog_preflight: print("engine_provider_catalog_transition=gelios-unit-profile-authority-v8") print("engine_provider_package=gelios.provider.v8") print("engine_provider_credential=ndcProviderRotatingAccessApi") print( "engine_provider_endpoint=https://api.geliospro.com/api/v1/units?" "incltrip=true&inclcntrs=true&inclsnsrs=true&incllsv=true" ) print("engine_data_product=fleet.units.profile.current.v1") print( "predecessor_catalog_sha256=" f"{provider_catalog_preflight['catalog_sha256']}" ) print(f"target_catalog_sha256={ENGINE_PROVIDER_SECURITY_CATALOG_TARGET_SHA256}") print(f"backend_current_barrier={provider_catalog_preflight['backend_mode']}") print("n8n_l1=untouched") print("engine_ui=untouched") print("engine_databases=untouched") print("mcp_nginx=untouched") if composite_provider_v4_preflight: print("engine_composite_provider_transition=exact-v3-to-v4") print("engine_provider_package=gelios.provider.v4") print( "engine_provider_capabilities=" "gelios.monitoring_config.current.read,gelios.units.current.read" ) print("engine_provider_requests=monitoring-config,units?incltrip=true") print("engine_data_product=fleet.positions.current.v3") for changed_path in ENGINE_COMPOSITE_PROVIDER_V4_ARTIFACT_ENTRIES: print(f"engine_composite_provider_changed_path={changed_path}") print( f"engine_composite_provider_predecessor_sha256[{changed_path}]=" f"{composite_provider_v4_preflight['predecessor_sha256'][changed_path]}" ) print( f"engine_composite_provider_target_sha256[{changed_path}]=" f"{composite_provider_v4_preflight['target_sha256'][changed_path]}" ) print(f"backend_current_barrier={composite_provider_v4_preflight['backend_mode']}") print("provider_credential_values=preserved") print("backend_force_recreate=yes") print("backend_pull=never") print("n8n_l1=untouched") print("engine_ui=untouched") print("engine_databases=untouched") if provider_rotating_slot_preflight: print("engine_provider_credential_transition=exact-active-slot-alignment") print("engine_provider_package=gelios.provider.v4") print("engine_provider_auth_mode=gelios.rest-rotating-bearer.v3") print("engine_provider_credential_slot=ndcProviderRotatingAccessApi") print("engine_data_product=fleet.positions.current.v3") for changed_path in ENGINE_PROVIDER_ROTATING_SLOT_ARTIFACT_ENTRIES: print(f"engine_provider_rotating_slot_changed_path={changed_path}") print( f"engine_provider_rotating_slot_predecessor_sha256[{changed_path}]=" f"{provider_rotating_slot_preflight['predecessor_sha256'][changed_path]}" ) print( f"engine_provider_rotating_slot_target_sha256[{changed_path}]=" f"{provider_rotating_slot_preflight['target_sha256'][changed_path]}" ) print(f"backend_current_barrier={provider_rotating_slot_preflight['backend_mode']}") print("provider_credential_values=preserved") print("backend_force_recreate=yes") print("backend_pull=never") print("l2_graph=untouched") print("n8n_l1=untouched") print("engine_ui=untouched") print("engine_databases=untouched") if provider_authority_diagnostics_preflight: print("engine_validation_transition=private-node-name-reconciliation") print("engine_validation_boundary=engine-pinned-private-node-catalog") print("private_node_identity=node-id-or-exact-display-name") print("provider_credential_values=preserved") for changed_path in ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_ARTIFACT_ENTRIES: print(f"engine_provider_authority_changed_path={changed_path}") print( f"engine_provider_authority_predecessor_sha256[{changed_path}]=" f"{provider_authority_diagnostics_preflight['predecessor_sha256'][changed_path]}" ) print( f"engine_provider_authority_target_sha256[{changed_path}]=" f"{provider_authority_diagnostics_preflight['target_sha256'][changed_path]}" ) print(f"backend_current_barrier={provider_authority_diagnostics_preflight['backend_mode']}") print("backend_force_recreate=yes") print("backend_pull=never") print("l2_graph=untouched") print("n8n_l1=untouched") print("engine_ui=untouched") print("engine_databases=untouched") if depttrans_zone_authority_v1_preflight: print_engine_depttrans_zone_authority_v1_plan( depttrans_zone_authority_v1_preflight ) if provider_target_host_policy_preflight: print("engine_provider_transport_transition=exact-literal-target-host") print("engine_provider_package=gelios.provider.v4") print("engine_data_product=fleet.positions.current.v3") print("provider_credential_values=preserved") for changed_path in ENGINE_PROVIDER_TARGET_HOST_POLICY_ARTIFACT_ENTRIES: print(f"engine_provider_target_host_policy_changed_path={changed_path}") print( f"engine_provider_target_host_policy_predecessor_sha256[{changed_path}]=" f"{provider_target_host_policy_preflight['predecessor_sha256'][changed_path]}" ) print( f"engine_provider_target_host_policy_target_sha256[{changed_path}]=" f"{provider_target_host_policy_preflight['target_sha256'][changed_path]}" ) print(f"backend_current_barrier={provider_target_host_policy_preflight['backend_mode']}") print("backend_force_recreate=yes") print("backend_pull=never") print("l2_graph=untouched") print("n8n_l1=untouched") print("engine_ui=untouched") print("engine_databases=untouched") if mcp_execution_profile_decoder_preflight: print( "engine_mcp_observability_transition=" "exact-flatted-numeric-string-preservation" ) print("engine_mcp_surface=external-codex") print("engine_mcp_tool=engine_get_node_output_profile") print("engine_mcp_profile_values_included=no") print("engine_mcp_raw_execution_data_included=no") for changed_path in ENGINE_MCP_EXECUTION_PROFILE_DECODER_ARTIFACT_ENTRIES: print(f"engine_mcp_profile_decoder_changed_path={changed_path}") if changed_path in mcp_execution_profile_decoder_preflight[ "predecessor_sha256" ]: print( f"engine_mcp_profile_decoder_predecessor_sha256[{changed_path}]=" f"{mcp_execution_profile_decoder_preflight['predecessor_sha256'][changed_path]}" ) elif changed_path in mcp_execution_profile_decoder_preflight["new_paths"]: print( f"engine_mcp_profile_decoder_predecessor_state[{changed_path}]=" "absent" ) else: die( "Engine MCP execution profile decoder plan has no predecessor " f"state: {changed_path}" ) print( f"engine_mcp_profile_decoder_target_sha256[{changed_path}]=" f"{mcp_execution_profile_decoder_preflight['target_sha256'][changed_path]}" ) print( "backend_current_barrier=" f"{mcp_execution_profile_decoder_preflight['backend_mode']}" ) print("backend_force_recreate=yes") print("backend_pull=never") print("l2_graph=untouched") print("n8n_l1=untouched") print("engine_ui=untouched") print("engine_databases=untouched") print("mcp_nginx=untouched") print("embedded_ai_workspace=untouched") if mcp_telemetry_catalog_preflight: print( "engine_mcp_observability_transition=" f"{mcp_telemetry_catalog_preflight['mode']}" ) print("engine_mcp_version=0.8.0") print("engine_mcp_surface=external-codex") 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() ): print( f"engine_mcp_telemetry_catalog_foundation_sha256[{foundation_path}]=" f"{foundation_sha256}" ) for changed_path in ENGINE_MCP_TELEMETRY_CATALOG_ARTIFACT_ENTRIES: print(f"engine_mcp_telemetry_catalog_changed_path={changed_path}") if changed_path in mcp_telemetry_catalog_preflight[ "predecessor_sha256" ]: print( f"engine_mcp_telemetry_catalog_predecessor_sha256[{changed_path}]=" f"{mcp_telemetry_catalog_preflight['predecessor_sha256'][changed_path]}" ) elif changed_path in mcp_telemetry_catalog_preflight["new_paths"]: print( f"engine_mcp_telemetry_catalog_predecessor_state[{changed_path}]=" "absent" ) else: die( "Engine MCP telemetry catalog plan has no predecessor state: " f"{changed_path}" ) print( f"engine_mcp_telemetry_catalog_target_sha256[{changed_path}]=" f"{mcp_telemetry_catalog_preflight['target_sha256'][changed_path]}" ) print( "backend_current_barrier=" f"{mcp_telemetry_catalog_preflight['backend_mode']}" ) print("backend_force_recreate=yes") print("backend_pull=never") print("l2_graph=untouched") print("n8n_l1=untouched") print("engine_ui=untouched") print("engine_databases=untouched") print("mcp_nginx=untouched") print("embedded_ai_workspace=untouched") if mcp_execution_plan_materialization_preflight: print( "engine_mcp_authoring_transition=" f"{mcp_execution_plan_materialization_preflight['mode']}" ) print("engine_mcp_version=0.9.0") print("engine_mcp_surface=external-codex") print( "engine_mcp_tool=" "engine_plan_l2_execution_plan_materialization" ) print( "engine_mcp_tool=" "engine_apply_l2_execution_plan_materialization" ) print("engine_mcp_provider_logic_authority=trusted-provider-package") print("engine_mcp_unmanaged_graph_policy=explicit-adoption-required") print("engine_mcp_plan_phase=read-only") print("engine_mcp_apply_phase=opaque-plan-ref-only") 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_execution_plan_materialization_preflight[ "foundation_sha256" ].items() ): print( "engine_mcp_execution_plan_materialization_foundation_sha256" f"[{foundation_path}]={foundation_sha256}" ) for changed_path in ( ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_ARTIFACT_ENTRIES ): print( "engine_mcp_execution_plan_materialization_changed_path=" f"{changed_path}" ) if changed_path in mcp_execution_plan_materialization_preflight[ "predecessor_sha256" ]: print( "engine_mcp_execution_plan_materialization_predecessor_sha256" f"[{changed_path}]=" f"{mcp_execution_plan_materialization_preflight['predecessor_sha256'][changed_path]}" ) elif changed_path in mcp_execution_plan_materialization_preflight[ "new_paths" ]: print( "engine_mcp_execution_plan_materialization_predecessor_state" f"[{changed_path}]=absent" ) else: die( "Engine MCP execution plan materialization plan has no " f"predecessor state: {changed_path}" ) print( "engine_mcp_execution_plan_materialization_target_sha256" f"[{changed_path}]=" f"{mcp_execution_plan_materialization_preflight['target_sha256'][changed_path]}" ) print( "backend_current_barrier=" f"{mcp_execution_plan_materialization_preflight['backend_mode']}" ) print("backend_force_recreate=yes") print("backend_pull=never") print("l2_graph=untouched") print("n8n_l1=untouched") print("engine_ui=untouched") print("engine_databases=untouched") print("credentials=preserved") print("mcp_nginx=untouched") print("embedded_ai_workspace=untouched") if mcp_execution_plan_telemetry_runtime_preflight: print( "engine_mcp_authoring_runtime_transition=" f"{mcp_execution_plan_telemetry_runtime_preflight['mode']}" ) print("engine_mcp_version=0.9.0") print("engine_mcp_surface=external-codex") print("engine_mcp_compiler_predecessor=1.1.0") print("engine_mcp_compiler_supported=1.1.0,1.2.0") print("engine_mcp_legacy_runtime_preserved=yes") print("engine_mcp_provider_logic_authority=trusted-provider-package") print( "engine_mcp_telemetry_authority=" "trusted-projection+visible-declared-sensor" ) print("engine_mcp_unprojected_parameters=discarded") print("engine_mcp_raw_provider_payload_at_publish=forbidden") for foundation_path, foundation_sha256 in ( mcp_execution_plan_telemetry_runtime_preflight[ "foundation_sha256" ].items() ): print( "engine_mcp_execution_plan_telemetry_runtime_foundation_sha256" f"[{foundation_path}]={foundation_sha256}" ) for changed_path in ( ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_ARTIFACT_ENTRIES ): print( "engine_mcp_execution_plan_telemetry_runtime_changed_path=" f"{changed_path}" ) if changed_path in mcp_execution_plan_telemetry_runtime_preflight[ "predecessor_sha256" ]: print( "engine_mcp_execution_plan_telemetry_runtime_predecessor_sha256" f"[{changed_path}]=" f"{mcp_execution_plan_telemetry_runtime_preflight['predecessor_sha256'][changed_path]}" ) elif changed_path in mcp_execution_plan_telemetry_runtime_preflight[ "new_paths" ]: print( "engine_mcp_execution_plan_telemetry_runtime_predecessor_state" f"[{changed_path}]=absent" ) else: die( "Engine MCP execution plan telemetry runtime plan has no " f"predecessor state: {changed_path}" ) print( "engine_mcp_execution_plan_telemetry_runtime_target_sha256" f"[{changed_path}]=" f"{mcp_execution_plan_telemetry_runtime_preflight['target_sha256'][changed_path]}" ) print( "backend_current_barrier=" f"{mcp_execution_plan_telemetry_runtime_preflight['backend_mode']}" ) print("backend_force_recreate=yes") print("backend_pull=never") print("l2_graph=untouched") print("n8n_l1=untouched") print("engine_ui=untouched") print("engine_databases=untouched") print("credentials=preserved") print("node_intelligence_image=preserved") print("mcp_nginx=untouched") print("embedded_ai_workspace=untouched") if mcp_execution_plan_module_ownership_preflight: print( "engine_mcp_module_ownership_transition=" f"{mcp_execution_plan_module_ownership_preflight['mode']}" ) print("engine_mcp_version=0.10.0") print("engine_mcp_surface=external-codex") print( "engine_mcp_materialization_strategies=" "create_or_reconcile_owned,adopt_existing,adopt_existing_module" ) print("engine_mcp_module_node_bindings=exact-one-to-one") print("engine_mcp_module_retirement_boundary=closed") print("engine_mcp_shared_manual_webhook=compatible-config-preserved") print("engine_mcp_provider_credential_nodes=engine-managed") print("engine_mcp_publisher_nodes=engine-managed") print("engine_provider_package_added=gelios.provider.v9") print("engine_provider_package_legacy=gelios.provider.v8:preserved") 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_execution_plan_module_ownership_preflight[ "foundation_sha256" ].items() ): print( "engine_mcp_execution_plan_module_ownership_foundation_sha256" f"[{foundation_path}]={foundation_sha256}" ) for changed_path in ( ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_ARTIFACT_ENTRIES ): print( "engine_mcp_execution_plan_module_ownership_changed_path=" f"{changed_path}" ) if changed_path in mcp_execution_plan_module_ownership_preflight[ "predecessor_sha256" ]: print( "engine_mcp_execution_plan_module_ownership_predecessor_sha256" f"[{changed_path}]=" f"{mcp_execution_plan_module_ownership_preflight['predecessor_sha256'][changed_path]}" ) elif changed_path in mcp_execution_plan_module_ownership_preflight[ "new_paths" ]: print( "engine_mcp_execution_plan_module_ownership_predecessor_state" f"[{changed_path}]=absent" ) else: die( "Engine MCP execution plan module ownership plan has no " f"predecessor state: {changed_path}" ) print( "engine_mcp_execution_plan_module_ownership_target_sha256" f"[{changed_path}]=" f"{mcp_execution_plan_module_ownership_preflight['target_sha256'][changed_path]}" ) print( "backend_current_barrier=" f"{mcp_execution_plan_module_ownership_preflight['backend_mode']}" ) print("backend_force_recreate=yes") print("backend_pull=never") print("l2_graph=untouched") print("n8n_l1=untouched") print("engine_ui=untouched") print("engine_databases=untouched") print("credentials=preserved") print("mcp_nginx=untouched") print("embedded_ai_workspace=untouched") if mcp_normalized_identity_search_preflight: print( "engine_mcp_identity_transition=" f"{mcp_normalized_identity_search_preflight['mode']}" ) print("engine_mcp_version=0.11.0") print("engine_mcp_surface=external-codex") print("engine_mcp_tool=engine_find_normalized_subjects") print("engine_mcp_identity_values=full-authorized-admin") print("engine_mcp_search_authority=canonical-normalized-facts") print("engine_mcp_raw_execution_data_included=no") print("engine_mcp_raw_provider_payload_included=no") print("engine_mcp_command_surface_included=no") print("engine_mcp_compiler_added=1.4.0") print("engine_mcp_legacy_compilers=1.1.0,1.2.0,1.3.0:preserved") print("engine_provider_package_added=gelios.provider.v11") print("engine_data_product_added=fleet.units.identity.current.v1") 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_normalized_identity_search_preflight[ "foundation_sha256" ].items() ): print( "engine_mcp_normalized_identity_search_foundation_sha256" f"[{foundation_path}]={foundation_sha256}" ) for changed_path in ( ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_ARTIFACT_ENTRIES ): print( "engine_mcp_normalized_identity_search_changed_path=" f"{changed_path}" ) if changed_path in mcp_normalized_identity_search_preflight[ "predecessor_sha256" ]: print( "engine_mcp_normalized_identity_search_predecessor_sha256" f"[{changed_path}]=" f"{mcp_normalized_identity_search_preflight['predecessor_sha256'][changed_path]}" ) elif changed_path in mcp_normalized_identity_search_preflight[ "new_paths" ]: print( "engine_mcp_normalized_identity_search_predecessor_state" f"[{changed_path}]=absent" ) else: die( "Engine MCP normalized identity search plan has no " f"predecessor state: {changed_path}" ) print( "engine_mcp_normalized_identity_search_target_sha256" f"[{changed_path}]=" f"{mcp_normalized_identity_search_preflight['target_sha256'][changed_path]}" ) print( "backend_current_barrier=" f"{mcp_normalized_identity_search_preflight['backend_mode']}" ) print("backend_force_recreate=yes") print("backend_pull=never") print("l2_graph=untouched") print("n8n_l1=untouched") print("engine_ui=untouched") print("engine_databases=untouched") print("credentials=preserved") print("mcp_nginx=untouched") print("embedded_ai_workspace=untouched") if mcp_l1_credential_reuse_preflight: print( "engine_mcp_l1_credential_reuse_transition=" f"{mcp_l1_credential_reuse_preflight['mode']}" ) print("engine_mcp_version=0.11.0") print("engine_mcp_surface=external-codex") print("engine_mcp_tool=engine_list_l2_credential_refs") print("engine_mcp_apply_operation=assignCredentialRef") print("engine_mcp_credential_scope=same-l1-workflow") print("engine_mcp_visibility_proof=local-registry-provenance") print("engine_mcp_cross_l1_sharing=no") print("engine_mcp_managed_grants=target-local") print("engine_mcp_native_credential_ids_included=no") print("engine_mcp_logical_credential_ids_included=no") print("engine_mcp_credential_values_included=no") print("engine_mcp_transport_https_required=yes") print("engine_mcp_transport_redirects_disabled=yes") for foundation_path, foundation_sha256 in ( mcp_l1_credential_reuse_preflight["foundation_sha256"].items() ): print( "engine_mcp_l1_credential_reuse_foundation_sha256" f"[{foundation_path}]={foundation_sha256}" ) for changed_path in ENGINE_MCP_L1_CREDENTIAL_REUSE_ARTIFACT_ENTRIES: print( "engine_mcp_l1_credential_reuse_changed_path=" f"{changed_path}" ) if changed_path in mcp_l1_credential_reuse_preflight[ "predecessor_sha256" ]: print( "engine_mcp_l1_credential_reuse_predecessor_sha256" f"[{changed_path}]=" f"{mcp_l1_credential_reuse_preflight['predecessor_sha256'][changed_path]}" ) elif changed_path in mcp_l1_credential_reuse_preflight["new_paths"]: print( "engine_mcp_l1_credential_reuse_predecessor_state" f"[{changed_path}]=absent" ) else: die( "Engine MCP L1 credential reuse plan has no predecessor " f"state: {changed_path}" ) print( "engine_mcp_l1_credential_reuse_target_sha256" f"[{changed_path}]=" f"{mcp_l1_credential_reuse_preflight['target_sha256'][changed_path]}" ) print( "backend_current_barrier=" f"{mcp_l1_credential_reuse_preflight['backend_mode']}" ) print("backend_force_recreate=yes") print("backend_pull=never") print("l2_graph=untouched") print("n8n_l1=untouched") print("engine_ui=untouched") print("engine_databases=untouched") print("credentials=preserved") print("mcp_nginx=untouched") print("embedded_ai_workspace=untouched") if mcp_l1_credential_provenance_preflight: print( "engine_mcp_l1_credential_provenance_transition=" f"{mcp_l1_credential_provenance_preflight['mode']}" ) print("engine_mcp_version=0.11.0") print("engine_mcp_surface=external-codex") print("engine_mcp_tool=engine_list_l2_credential_refs") print("engine_mcp_apply_operation=assignCredentialRef") print("engine_mcp_credential_scope=same-l1-workflow") print( "engine_mcp_local_provenance_sources=" "manual,workflow-ref,credentials-file" ) print("engine_mcp_referenced_source_requires_sync_payload=yes") print("engine_mcp_logical_key_equality_required=yes") print("engine_mcp_cross_l1_sharing=no") print("engine_mcp_managed_grants=target-local") print("engine_mcp_credential_ids_included=no") print("engine_mcp_credential_values_included=no") for foundation_path, foundation_sha256 in ( mcp_l1_credential_provenance_preflight[ "foundation_sha256" ].items() ): print( "engine_mcp_l1_credential_provenance_foundation_sha256" f"[{foundation_path}]={foundation_sha256}" ) for changed_path in ( ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_ARTIFACT_ENTRIES ): print( "engine_mcp_l1_credential_provenance_changed_path=" f"{changed_path}" ) if changed_path in mcp_l1_credential_provenance_preflight[ "predecessor_sha256" ]: print( "engine_mcp_l1_credential_provenance_predecessor_sha256" f"[{changed_path}]=" f"{mcp_l1_credential_provenance_preflight['predecessor_sha256'][changed_path]}" ) elif changed_path in mcp_l1_credential_provenance_preflight[ "new_paths" ]: print( "engine_mcp_l1_credential_provenance_predecessor_state" f"[{changed_path}]=absent" ) else: die( "Engine MCP L1 credential provenance plan has no " f"predecessor state: {changed_path}" ) print( "engine_mcp_l1_credential_provenance_target_sha256" f"[{changed_path}]=" f"{mcp_l1_credential_provenance_preflight['target_sha256'][changed_path]}" ) print( "backend_current_barrier=" f"{mcp_l1_credential_provenance_preflight['backend_mode']}" ) print("backend_force_recreate=yes") print("backend_pull=never") print("l2_graph=untouched") print("n8n_l1=untouched") print("engine_ui=untouched") print("engine_databases=untouched") print("credentials=preserved") print("mcp_nginx=untouched") print("embedded_ai_workspace=untouched") if mcp_execution_plan_sandbox_runtime_preflight: print( "engine_mcp_execution_plan_sandbox_runtime_transition=" f"{mcp_execution_plan_sandbox_runtime_preflight['mode']}" ) print("engine_mcp_version=0.11.0") print("engine_mcp_execution_plan_compiler_version=1.4.0") print("engine_mcp_execution_runtime=n8n-2.3.2-code") print("engine_mcp_execution_sandbox_global_dependencies=none") print("engine_mcp_execution_byte_budget=pure-js-utf8") print("engine_mcp_provider_logic=trusted-package") print("engine_mcp_provider_hardcode=no") print("engine_mcp_raw_provider_publish=forbidden") for foundation_path, foundation_sha256 in ( mcp_execution_plan_sandbox_runtime_preflight[ "foundation_sha256" ].items() ): print( "engine_mcp_execution_plan_sandbox_runtime_foundation_sha256" f"[{foundation_path}]={foundation_sha256}" ) for changed_path in ( ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_ARTIFACT_ENTRIES ): print( "engine_mcp_execution_plan_sandbox_runtime_changed_path=" f"{changed_path}" ) if changed_path in ( mcp_execution_plan_sandbox_runtime_preflight[ "predecessor_sha256" ] ): print( "engine_mcp_execution_plan_sandbox_runtime_predecessor_sha256" f"[{changed_path}]=" f"{mcp_execution_plan_sandbox_runtime_preflight['predecessor_sha256'][changed_path]}" ) elif changed_path in ( mcp_execution_plan_sandbox_runtime_preflight["new_paths"] ): print( "engine_mcp_execution_plan_sandbox_runtime_predecessor_state" f"[{changed_path}]=absent" ) else: die( "Engine MCP execution plan sandbox runtime plan has no " f"predecessor state: {changed_path}" ) print( "engine_mcp_execution_plan_sandbox_runtime_target_sha256" f"[{changed_path}]=" f"{mcp_execution_plan_sandbox_runtime_preflight['target_sha256'][changed_path]}" ) print( "backend_current_barrier=" f"{mcp_execution_plan_sandbox_runtime_preflight['backend_mode']}" ) print("backend_force_recreate=yes") print("backend_pull=never") print("l2_graph=untouched") print("n8n_l1=untouched") print("n8n_core=untouched") print("engine_ui=untouched") print("engine_databases=untouched") print("credentials=preserved") print("mcp_nginx=untouched") print("embedded_ai_workspace=untouched") if mcp_gelios_items_envelope_preflight: print( "engine_mcp_gelios_items_envelope_transition=" f"{mcp_gelios_items_envelope_preflight['mode']}" ) print("engine_mcp_version=0.11.0") print("engine_mcp_provider_package=gelios.provider.v12") print("engine_mcp_response_collection_path=items") print("engine_mcp_execution_history_v11=preserved") print("engine_mcp_security_authority_v12=exclusive") print("engine_mcp_provider_logic=trusted-package") print("engine_mcp_compiler_changed=no") print("engine_mcp_raw_provider_values_included=no") for foundation_path, foundation_sha256 in ( mcp_gelios_items_envelope_preflight[ "foundation_sha256" ].items() ): print( "engine_mcp_gelios_items_envelope_foundation_sha256" f"[{foundation_path}]={foundation_sha256}" ) for changed_path in ( ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_ARTIFACT_ENTRIES ): print( "engine_mcp_gelios_items_envelope_changed_path=" f"{changed_path}" ) if changed_path in ( mcp_gelios_items_envelope_preflight["predecessor_sha256"] ): print( "engine_mcp_gelios_items_envelope_predecessor_sha256" f"[{changed_path}]=" f"{mcp_gelios_items_envelope_preflight['predecessor_sha256'][changed_path]}" ) elif changed_path in ( mcp_gelios_items_envelope_preflight["new_paths"] ): print( "engine_mcp_gelios_items_envelope_predecessor_state" f"[{changed_path}]=absent" ) else: die( "Engine MCP Gelios items envelope plan has no predecessor " f"state: {changed_path}" ) print( "engine_mcp_gelios_items_envelope_target_sha256" f"[{changed_path}]=" f"{mcp_gelios_items_envelope_preflight['target_sha256'][changed_path]}" ) print( "backend_current_barrier=" f"{mcp_gelios_items_envelope_preflight['backend_mode']}" ) print("backend_force_recreate=yes") print("backend_pull=never") print("l2_graph=untouched") print("n8n_l1=untouched") print("n8n_core=untouched") print("engine_ui=untouched") print("engine_databases=untouched") print("credentials=preserved") print("mcp_nginx=untouched") print("embedded_ai_workspace=untouched") if mcp_registered_execution_profiles_preflight: print( "engine_mcp_registered_execution_profiles_transition=" f"{mcp_registered_execution_profiles_preflight['mode']}" ) print("engine_mcp_version=0.12.0") print( "engine_mcp_tool=engine_list_l2_execution_profiles," "engine_plan_registered_l2_execution" ) print("engine_mcp_registered_profiles=6") print("engine_mcp_profile_refs=actor-and-target-scoped-opaque-ttl") print("engine_mcp_target_scope=server-derived") print("engine_mcp_execution_plan_digests=server-derived") print("engine_mcp_existing_materializer=reused") print("engine_mcp_provider_hardcode=no") print("engine_mcp_internal_descriptors_included=no") print("engine_mcp_provider_endpoints_included=no") print("engine_mcp_customer_scope_included=no") print("engine_mcp_credential_ids_included=no") print("engine_mcp_credential_values_included=no") print("engine_node_intelligence_attestation=updated") print("engine_node_intelligence_image=untouched") print("engine_node_intelligence_source=untouched") for foundation_path, foundation_sha256 in ( mcp_registered_execution_profiles_preflight[ "foundation_sha256" ].items() ): print( "engine_mcp_registered_execution_profiles_foundation_sha256" f"[{foundation_path}]={foundation_sha256}" ) for changed_path in ( ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_ARTIFACT_ENTRIES ): print( "engine_mcp_registered_execution_profiles_changed_path=" f"{changed_path}" ) if changed_path in ( mcp_registered_execution_profiles_preflight[ "predecessor_sha256" ] ): print( "engine_mcp_registered_execution_profiles_predecessor_sha256" f"[{changed_path}]=" f"{mcp_registered_execution_profiles_preflight['predecessor_sha256'][changed_path]}" ) elif changed_path in ( mcp_registered_execution_profiles_preflight["new_paths"] ): print( "engine_mcp_registered_execution_profiles_predecessor_state" f"[{changed_path}]=absent" ) else: die( "Engine MCP registered execution profiles plan has no " f"predecessor state: {changed_path}" ) print( "engine_mcp_registered_execution_profiles_target_sha256" f"[{changed_path}]=" f"{mcp_registered_execution_profiles_preflight['target_sha256'][changed_path]}" ) print( "backend_current_barrier=" f"{mcp_registered_execution_profiles_preflight['backend_mode']}" ) print("backend_force_recreate=yes") print("backend_pull=never") print("l2_graph=untouched") print("n8n_l1=untouched") print("n8n_core=untouched") print("engine_ui=untouched") print("engine_databases=untouched") print("credentials=preserved") print("foundry=untouched") print("ontology=untouched") print("mcp_nginx=untouched") print("embedded_ai_workspace=untouched") if mcp_gelios_units_items_preflight: print( "engine_mcp_gelios_units_items_transition=" f"{mcp_gelios_units_items_preflight['mode']}" ) print("engine_mcp_version=0.12.0") print("engine_mcp_provider_package=gelios.provider.v12@12.0.1") print("engine_mcp_registered_profile=gelios.units.profile.cold.v1") print("engine_mcp_data_product=fleet.units.profile.current.v1") print("engine_mcp_response_collection_path=items") print("engine_mcp_execution_plan_compiler_version=1.4.0") print("engine_mcp_existing_materializer=reused") print("engine_mcp_runtime_code_changed=no") print("engine_mcp_provider_endpoints_changed=no") print("engine_mcp_raw_provider_values_included=no") for foundation_path, foundation_sha256 in ( mcp_gelios_units_items_preflight["foundation_sha256"].items() ): print( "engine_mcp_gelios_units_items_foundation_sha256" f"[{foundation_path}]={foundation_sha256}" ) for changed_path in ENGINE_MCP_GELIOS_UNITS_ITEMS_ARTIFACT_ENTRIES: print( "engine_mcp_gelios_units_items_changed_path=" f"{changed_path}" ) if changed_path in ( mcp_gelios_units_items_preflight["predecessor_sha256"] ): print( "engine_mcp_gelios_units_items_predecessor_sha256" f"[{changed_path}]=" f"{mcp_gelios_units_items_preflight['predecessor_sha256'][changed_path]}" ) elif changed_path in mcp_gelios_units_items_preflight["new_paths"]: print( "engine_mcp_gelios_units_items_predecessor_state" f"[{changed_path}]=absent" ) else: die( "Engine MCP Gelios units items plan has no predecessor " f"state: {changed_path}" ) print( "engine_mcp_gelios_units_items_target_sha256" f"[{changed_path}]=" f"{mcp_gelios_units_items_preflight['target_sha256'][changed_path]}" ) print( "backend_current_barrier=" f"{mcp_gelios_units_items_preflight['backend_mode']}" ) print("backend_force_recreate=yes") print("backend_pull=never") print("l2_graph=untouched") print("n8n_l1=untouched") print("n8n_core=untouched") print("engine_ui=untouched") print("engine_databases=untouched") print("credentials=preserved") print("foundry=untouched") print("ontology=untouched") print("mcp_nginx=untouched") print("embedded_ai_workspace=untouched") if transition_descriptor: print(f"n8n_transition={transition_descriptor['action']}") print(f"n8n_version={transition_descriptor['n8nVersion']}") print(f"n8n_release={transition_descriptor['releaseId']}") print(f"n8n_package_sha256={transition_descriptor['packageSha256']}") print(f"n8n_base_image={transition_descriptor['baseImage']}") print(f"n8n_base_image_id={transition_preflight['base_image_id']}") print(f"n8n_base_image_architecture={transition_preflight['base_image_architecture']}") for repo_digest in transition_preflight["base_image_repo_digests"]: print(f"n8n_base_image_repo_digest={repo_digest}") print(f"n8n_current_state={transition_preflight['current_state']}") print(f"n8n_expected_current={transition_descriptor['expectedCurrent']}") print(f"n8n_state_matches={'yes' if transition_preflight['state_matches'] else 'not-yet'}") print(f"n8n_sealed_package=/volume2/nodedc-demo/{transition_descriptor['sealedReleaseRelativePath']}") print("n8n_build=none") print("n8n_pull=never") print("n8n_force_recreate=yes") print("n8n_persistent_data=preserved:/volume2/nodedc-demo/n8n-data") 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(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: print(f"runtime_secret=runner-managed:{MAP_GATEWAY_SECRET_FILE}") if component == "module-foundry": print(f"runtime_grants=runner-managed:{EXTERNAL_DATA_PLANE_READER_GRANTS_DIR}") print(f"runtime_grants=runner-managed:{FOUNDRY_BINDING_GRANTS_DIR}") print(f"runtime_private_key=runner-managed:{FOUNDRY_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE}") print(f"runtime_public_trust=runner-managed:{FOUNDRY_EDP_MANAGED_PROVISIONER_PUBLIC_KEY_FILE}") if component == "device-plane": print(f"runtime_secret=runner-managed:{DEVICE_PLANE_POSTGRES_PASSWORD_FILE}") print(f"runtime_secret=runner-managed:{DEVICE_PLANE_GATEWAY_CORE_TOKEN_FILE}") print(f"runtime_secret=runner-managed:{DEVICE_PLANE_IDENTIFIER_PEPPER_FILE}") if is_device_plane_manager_control_plane_slice(component, entries): print( "runtime_secret=runner-managed:" f"{DEVICE_PLANE_MANAGEMENT_CORE_TOKEN_FILE}" ) print( "runtime_secret=runner-managed:" f"{PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE}" ) if ( is_device_plane_control_core_release_slice(component, entries) or is_device_plane_edge_core_channel_bootstrap_slice( component, entries, ) ): print( "runtime_secret=runner-managed:" f"{DEVICE_PLANE_MANAGEMENT_CORE_TOKEN_FILE}" ) print( "runtime_private_key=runner-managed-host-local:" f"{DEVICE_PLANE_EDGE_CHANNEL_CORE_PRIVATE_KEY_FILE}" ) print( "runtime_public_certificate_export=runner-managed:" f"{DEVICE_PLANE_EDGE_CHANNEL_EXPORTED_CORE_CERTIFICATE_FILE}" ) print( "device_postgres=" f"{device_plane_postgres_plan_selection(device_plane_postgres_preflight)}" ) print("device_postgres_volume=preserved:nodedc-device-plane-postgres-data") if device_plane_b2_ingress_preflight is not None: print("device_gateway_public_ingress=disabled:loopback-test:tcp:9921") print("device_control_core_discovery_ingest=enabled:authenticated") else: print("device_gateway_public_ingress=disabled") if ( device_plane_manager_activation_preflight is not None and device_plane_manager_activation_preflight["descriptor"].get( "commandTransport" ) == "typed-service-ping-v1" ): print("device_gateway_command_transport=typed-service-ping-v1") else: print("device_gateway_command_transport=disabled") print("gelios=untouched") if device_plane_foundation_recovery_preflight is not None: print( "device_plane_transition=" f"{device_plane_foundation_recovery_preflight['mode']}" ) print( "failed_patch=" f"{DEVICE_PLANE_FOUNDATION_FAILED_PATCH_ID}" ) print( "failed_artifact_sha256=" f"{DEVICE_PLANE_FOUNDATION_FAILED_ARTIFACT_SHA256}" ) print( "recovery_backup=" f"{device_plane_foundation_recovery_preflight['backup'].name}" ) print("device_plane_build=none") print("device_plane_runtime_mutation=none") print( "device_plane_runtime_services=" "preserved:device-control-core,device-gateway,device-postgres" ) print("device_plane_source_action=publish-exact-failed-source") print("device_plane_rollback=source-only-runtime-unchanged") if device_plane_network_publication_preflight is not None: print( "device_plane_transition=" f"{device_plane_network_publication_preflight['mode']}" ) print( "failed_recovery_patch=" f"{DEVICE_PLANE_FOUNDATION_RECOVERY_FAILED_PATCH_ID}" ) print( "failed_recovery_artifact_sha256=" f"{DEVICE_PLANE_FOUNDATION_RECOVERY_FAILED_ARTIFACT_SHA256}" ) print( "failed_recovery_backup=" f"{device_plane_network_publication_preflight['backup'].name}" ) print("device_plane_build=none") print( "device_plane_runtime_mutation=" "recreate:device-control-core,device-gateway" ) print( "device_plane_runtime_services=" "preserved:device-postgres" ) print( "device_plane_private_network=" "preserved:internal:true" ) print( "device_plane_control_network=" "create:internal:false:masquerade:false" ) print( "device_plane_actual_ports=" "required:127.0.0.1:18120,127.0.0.1:18121" ) print("device_gateway_tcp_9921=disabled:unpublished") print( "device_plane_source_action=" "publish-network-corrected-foundation-source" ) print( "device_plane_rollback=" "partial-source+internal-only-stateless-runtime" ) if device_plane_b2_ingress_preflight is not None: print( "device_plane_transition=" f"{device_plane_b2_ingress_preflight['mode']}" ) print( "device_plane_predecessor_patch=" f"{DEVICE_PLANE_B2_DISCOVERY_INGRESS_PREDECESSOR_PATCH_ID}" ) print( "device_plane_predecessor_artifact_sha256=" f"{DEVICE_PLANE_B2_DISCOVERY_INGRESS_PREDECESSOR_ARTIFACT_SHA256}" ) print( "device_plane_runtime_mutation=" "build+recreate:device-control-core,device-gateway" ) print( "device_plane_runtime_services=" "preserved:device-postgres" ) print( "device_plane_actual_ports=" "required:127.0.0.1:18120,127.0.0.1:18121," "127.0.0.1:9921/tcp" ) print( "device_gateway_framing=" "verified-read-only:" "arusnavi.internal.protocol-sheet.gid-12.v1" ) print( "device_gateway_identity=" "header2-imei:claimed-not-ownership-proof" ) print("device_gateway_discovery_lifecycle=quarantine") print( "device_plane_rollback=" "source+predecessor-stateless-runtime" ) if device_plane_b2_recovery_preflight is not None: print( "device_plane_transition=" f"{device_plane_b2_recovery_preflight['mode']}" ) print( "failed_patch=" f"{DEVICE_PLANE_B2_DISCOVERY_FAILED_PATCH_ID}" ) print( "failed_artifact_sha256=" f"{DEVICE_PLANE_B2_DISCOVERY_FAILED_ARTIFACT_SHA256}" ) print( "recovery_backup=" f"{device_plane_b2_recovery_preflight['backup'].name}" ) print("device_plane_build=none") print("device_plane_runtime_mutation=none") print( "device_plane_runtime_services=" "preserved:device-control-core,device-gateway,device-postgres" ) print( "device_plane_source_action=" "publish-reconciliation-marker-only" ) print("device_gateway_tcp_9921=disabled:unpublished") print("device_plane_rollback=marker-only-runtime-unchanged") if device_plane_manager_activation_preflight is not None: print( "device_plane_transition=" f"{device_plane_manager_activation_preflight['mode']}" ) print( "device_plane_predecessor_patch=" f"{device_plane_manager_activation_preflight['descriptor']['predecessor']['patchId']}" ) print( "device_plane_predecessor_artifact_sha256=" f"{device_plane_manager_activation_preflight['descriptor']['predecessor']['artifactSha256']}" ) print( "device_plane_predecessor_kind=" f"{device_plane_manager_activation_preflight['descriptor']['predecessor']['kind']}" ) print( "device_plane_runtime_mutation=" + ( "build+recreate:device-manager" if device_plane_manager_activation_preflight["descriptor"].get( "schemaVersion" ) in ( "nodedc.device-plane.device-manager-release.v3", "nodedc.device-plane.device-manager-release.v4", "nodedc.device-plane.device-manager-release.v5", "nodedc.device-plane.device-manager-release.v6", "nodedc.device-plane.device-manager-release.v7", "nodedc.device-plane.device-manager-release.v8", "nodedc.device-plane.device-manager-release.v9", "nodedc.device-plane.device-manager-release.v10", "nodedc.device-plane.device-manager-release.v11", "nodedc.device-plane.device-manager-release.v12", "nodedc.device-plane.device-manager-release.v13", ) else "build+recreate:device-control-core,device-manager" ) ) print( "device_plane_runtime_services=" + ( "preserved:device-control-core,device-gateway," "device-postgres,device-backhaul-target" if device_plane_manager_activation_preflight["descriptor"].get( "schemaVersion" ) in ( "nodedc.device-plane.device-manager-release.v3", "nodedc.device-plane.device-manager-release.v4", "nodedc.device-plane.device-manager-release.v5", "nodedc.device-plane.device-manager-release.v6", "nodedc.device-plane.device-manager-release.v7", "nodedc.device-plane.device-manager-release.v8", "nodedc.device-plane.device-manager-release.v9", "nodedc.device-plane.device-manager-release.v10", "nodedc.device-plane.device-manager-release.v11", "nodedc.device-plane.device-manager-release.v12", "nodedc.device-plane.device-manager-release.v13", ) else "preserved:device-gateway,device-postgres," "device-backhaul-target" ) ) manager_schema = device_plane_manager_activation_preflight[ "descriptor" ].get("schemaVersion") manager_persistent = manager_schema in ( "nodedc.device-plane.device-manager-release.v4", "nodedc.device-plane.device-manager-release.v5", "nodedc.device-plane.device-manager-release.v6", "nodedc.device-plane.device-manager-release.v7", "nodedc.device-plane.device-manager-release.v8", "nodedc.device-plane.device-manager-release.v9", "nodedc.device-plane.device-manager-release.v10", "nodedc.device-plane.device-manager-release.v11", "nodedc.device-plane.device-manager-release.v12", "nodedc.device-plane.device-manager-release.v13", ) print( "device_manager_health_gate=" + ( "bounded-grace+contract+persistent-data" if manager_persistent else "bounded-grace+contract" ) ) if manager_persistent: print( "device_manager_persistent_data=runner-managed-preserved:" f"{DEVICE_PLANE_MANAGER_DATA_DIR}" ) print( "device_manager_persistent_mount=read-write:" f"{DEVICE_PLANE_MANAGER_DATA_CONTAINER_DIR}" ) print("device_manager_default_accent=#f5f5f5") if manager_schema in ( "nodedc.device-plane.device-manager-release.v5", "nodedc.device-plane.device-manager-release.v6", "nodedc.device-plane.device-manager-release.v7", "nodedc.device-plane.device-manager-release.v8", "nodedc.device-plane.device-manager-release.v9", "nodedc.device-plane.device-manager-release.v10", "nodedc.device-plane.device-manager-release.v11", "nodedc.device-plane.device-manager-release.v12", "nodedc.device-plane.device-manager-release.v13", ): print( "device_manager_overview_layout=" "mission-core-landing-stage-v1" ) if manager_schema in ( "nodedc.device-plane.device-manager-release.v6", "nodedc.device-plane.device-manager-release.v7", "nodedc.device-plane.device-manager-release.v8", "nodedc.device-plane.device-manager-release.v9", "nodedc.device-plane.device-manager-release.v10", "nodedc.device-plane.device-manager-release.v11", "nodedc.device-plane.device-manager-release.v12", "nodedc.device-plane.device-manager-release.v13", ): print("device_manager_favicon_set=nodedc-adaptive-v1") if manager_schema in ( "nodedc.device-plane.device-manager-release.v7", "nodedc.device-plane.device-manager-release.v8", "nodedc.device-plane.device-manager-release.v9", "nodedc.device-plane.device-manager-release.v10", "nodedc.device-plane.device-manager-release.v11", "nodedc.device-plane.device-manager-release.v12", "nodedc.device-plane.device-manager-release.v13", ): print("device_manager_command_form=aligned-control-row-v1") print("device_manager_secondary_empty_text=help-text-sm-v1") if manager_schema == ( "nodedc.device-plane.device-manager-release.v7" ): print( "device_manager_infrastructure_hosts=" "edge-registration-live-channel-v1" ) print( "device_manager_host_ontology=" "candidate-not-canonical" ) if manager_schema in ( "nodedc.device-plane.device-manager-release.v8", "nodedc.device-plane.device-manager-release.v9", "nodedc.device-plane.device-manager-release.v10", "nodedc.device-plane.device-manager-release.v11", "nodedc.device-plane.device-manager-release.v12", "nodedc.device-plane.device-manager-release.v13", ): print( "device_manager_infrastructure_hosts=" "ontology-backed-host-runtime-v1" ) print( "device_manager_ontology_foundation=" "ontology-core-device-foundation-20260822-001" ) print( "device_manager_ontology_catalog_hash=" "229c61c02a790906" ) print( "device_manager_asset_binding=" "temporal-device-asset-binding-v1" ) print( "device_manager_infrastructure_runtime=" "host-endpoint-deployment-service-instance-v1" ) print( "device_manager_health_evidence=" "ttl-observation-missing-not-unhealthy-v1" ) print( "device_manager_interactive_shell=" "disabled-pending-managed-session-boundary" ) if manager_schema in ( "nodedc.device-plane.device-manager-release.v9", "nodedc.device-plane.device-manager-release.v10", "nodedc.device-plane.device-manager-release.v11", "nodedc.device-plane.device-manager-release.v12", "nodedc.device-plane.device-manager-release.v13", ): print( "device_manager_host_telemetry_workspace=" + device_plane_manager_activation_preflight[ "descriptor" ]["telemetryWorkspace"] ) print( "device_manager_host_telemetry_navigation=" "full-workspace-back-navigation-v1" ) print("device_manager_host_telemetry_poll=three-seconds") print( "device_manager_host_telemetry_freshness=" "fifteen-seconds-missing-stale-not-unhealthy" ) if manager_schema in ( "nodedc.device-plane.device-manager-release.v11", "nodedc.device-plane.device-manager-release.v12", "nodedc.device-plane.device-manager-release.v13", ): manager_descriptor = ( device_plane_manager_activation_preflight["descriptor"] ) print( "device_manager_design_system=" + manager_descriptor["designSystem"] ) print( "device_manager_mission_core_reference=" + manager_descriptor["missionCoreReference"] ) print( "device_manager_infrastructure_workspace=" + manager_descriptor["infrastructureWorkspaceLayout"] ) print( "device_manager_host_inventory=" + manager_descriptor["hostInventoryComposition"] ) print( "device_manager_host_telemetry_surface=" + manager_descriptor["telemetrySurface"] ) print( "device_manager_host_telemetry_status=" + manager_descriptor["telemetryStatus"] ) print( "device_manager_host_telemetry_scroll=" + manager_descriptor["telemetryScroll"] ) if manager_schema in ( "nodedc.device-plane.device-manager-release.v12", "nodedc.device-plane.device-manager-release.v13", ): manager_descriptor = ( device_plane_manager_activation_preflight["descriptor"] ) print( "device_manager_host_telemetry_graph_scale=" + manager_descriptor["telemetryGraphScale"] ) print( "device_manager_host_telemetry_cpu_minimum_span=" + manager_descriptor["telemetryCpuMinimumSpan"] ) print( "device_manager_host_telemetry_memory_minimum_span=" + manager_descriptor["telemetryMemoryMinimumSpan"] ) print( "device_manager_host_telemetry_network_missing=" + manager_descriptor[ "telemetryNetworkMissingSemantics" ] ) if manager_schema == ( "nodedc.device-plane.device-manager-release.v13" ): manager_descriptor = ( device_plane_manager_activation_preflight["descriptor"] ) print( "device_manager_host_inventory_overview_surface=" + manager_descriptor["hostInventoryOverviewSurface"] ) print( "device_manager_host_inventory_collection_surface=" + manager_descriptor["hostInventoryCollectionSurface"] ) print( "device_manager_host_inventory_row=" + manager_descriptor["hostInventoryRow"] ) print( "device_manager_host_inventory_freshness=" + manager_descriptor["hostInventoryFreshness"] ) print( "device_manager_host_inventory_relations=" + manager_descriptor["hostInventoryRelations"] ) print( "device_manager_host_inventory_default_expansion=" + manager_descriptor["hostInventoryDefaultExpansion"] ) print( "device_manager_host_inventory_scale_target=" + manager_descriptor["hostInventoryScaleTarget"] ) if device_plane_manager_activation_preflight["descriptor"].get( "commandTransport" ) == "typed-service-ping-v1": print("device_manager_public_route=unchanged:active") print("device_edge_channel_commands=typed-service-ping-v1") print("device_command_credential=transient-core-memory-only") print("gelios=untouched:legacy-only") else: print("device_manager_public_route=unchanged:absent") print("device_gateway_tcp_9921=preserved:loopback-only") print( "device_plane_rollback=" + ( "source+reconciled-baseline-runtime+" "persistent-manager-data-preserved" if manager_persistent else "source+reconciled-baseline-runtime" ) ) if device_plane_control_core_release_preflight is not None: core_release = device_plane_control_core_release_preflight descriptor = core_release["descriptor"] predecessor = descriptor["predecessor"] print( "device_plane_transition=" f"{core_release['mode']}" ) print(f"device_control_core_release={descriptor['releaseId']}") print( "device_plane_predecessor_kind=" f"{predecessor['kind']}" ) print( "device_plane_predecessor_patch=" f"{predecessor['patchId']}" ) print( "device_plane_predecessor_artifact_sha256=" f"{predecessor['artifactSha256']}" ) print( "device_edge_channel_identity_preflight=" f"{core_release['identityState']}" ) print("device_plane_runtime_mutation=build+recreate:device-control-core") print( "device_plane_runtime_services=" "preserved:device-manager,device-gateway,device-postgres," "device-backhaul-target" ) print("device_edge_channel=preserved:core-initiated:pinned-mtls:registered-edges-only") print("device_edge_channel_networks=preserved:device-plane-private,device-plane-egress") print("device_edge_channel_registrations=preserved") print( "device_edge_channel_commands=" f"{descriptor['commandTransport']}" ) if descriptor.get("schemaVersion") in ( "nodedc.device-plane.device-control-core-release.v3", "nodedc.device-plane.device-control-core-release.v4", ): print( "device_host_telemetry_transport=" "edge-channel-host-telemetry-observed-v1" ) print( "device_host_telemetry_storage=" "device-control-core-postgres-seven-day-retention" ) print( "device_host_telemetry_ontology=" "observation-observed-property-provenance-freshness-v1" ) print( "device_host_telemetry_freshness=" "fifteen-seconds-missing-stale-not-unhealthy" ) if descriptor.get("schemaVersion") == ( "nodedc.device-plane.device-control-core-release.v4" ): recovery_database = core_release["recoveryDatabase"] recovery_runtime = core_release["recoveryRuntime"] print( "device_control_core_recovery_backup=" f"{core_release['recoveryBackup'].name}" ) print( "device_control_core_current_runtime=" f"{recovery_runtime['core']['containerId']}:" f"{recovery_runtime['core']['imageId']}:" f"{recovery_runtime['core']['status']}:" f"{recovery_runtime['core']['health']}:" f"restarts={recovery_runtime['core']['restartCount']}" ) emit_device_plane_control_core_migration_replay_database_evidence( recovery_database ) print("device_postgres_row_mutation=none") print("device_postgres_telemetry_table=absent") print( "device_control_core_target_telemetry_table=" "migration-017:present" ) print("device_manager=preserved:active") print("device_manager_public_route=unchanged:active") print("device_gateway_tcp_9921=preserved:loopback-only") print(f"gelios={descriptor['gelios']}") print("device_plane_rollback=source+preapply-core-runtime") if device_plane_edge_core_channel_preflight is not None: print( "device_plane_transition=" f"{device_plane_edge_core_channel_preflight['mode']}" ) edge_descriptor = ( device_plane_edge_core_channel_preflight["descriptor"] ) upgrade = edge_descriptor["action"] == "upgrade" if upgrade: predecessor = edge_descriptor.get( "upgradePredecessor", edge_descriptor.get("bootstrapPredecessor"), ) print( "device_plane_predecessor_patch=" f"{predecessor['patchId']}" ) print( "device_plane_predecessor_artifact_sha256=" f"{predecessor['artifactSha256']}" ) print("device_edge_channel_endpoint_policy=public-ipv4-standard-https-tcp-443-only") if "coreNetworks" in edge_descriptor: print( "device_edge_channel_core_networks=" f"{','.join(edge_descriptor['coreNetworks'])}" ) print( "device_edge_channel_removed_core_network=" f"{edge_descriptor['removedCoreNetwork']}" ) print( "device_edge_channel_compose_compatibility=" f"{edge_descriptor['composeCompatibility']}" ) else: print( "device_plane_predecessor_patch=" f"{DEVICE_PLANE_EDGE_CORE_CHANNEL_MANAGER_PREDECESSOR_PATCH_ID}" ) print( "device_plane_predecessor_artifact_sha256=" f"{DEVICE_PLANE_EDGE_CORE_CHANNEL_MANAGER_PREDECESSOR_ARTIFACT_SHA256}" ) print( "failed_patch=" f"{DEVICE_PLANE_EDGE_CORE_CHANNEL_FAILED_PATCH_ID}" ) print( "failed_artifact_sha256=" f"{DEVICE_PLANE_EDGE_CORE_CHANNEL_FAILED_ARTIFACT_SHA256}" ) print( "recovery_backup=" f"{DEVICE_PLANE_EDGE_CORE_CHANNEL_FAILED_BACKUP_ID}" ) print( "device_edge_channel_identity_preflight=" f"{device_plane_edge_core_channel_preflight['identityState']}" ) print("device_plane_runtime_mutation=build+recreate:device-control-core") print( "device_plane_runtime_services=" "preserved:device-manager,device-gateway,device-postgres," "device-backhaul-target" ) print("device_edge_channel=enabled:core-initiated:pinned-mtls:registered-edges-only") print("device_edge_channel_egress=dedicated-core-only-bridge:no-host-ingress") print("device_edge_channel_identity=host-local-private-key:public-certificate-export-only") if upgrade: print("device_edge_channel_identity_recovery=forbidden:reuse-valid-only") if edge_descriptor["edgeRegistrations"] == "preserved": print("device_edge_channel_registrations=preserved") else: print("device_edge_channel_registrations=preserved:explicit-443-reconciliation-required") else: print("device_edge_channel_invalid_failed_016=recover-exact-unexported-only") print("device_edge_channel_registrations=preserved") print("device_edge_channel_commands=disabled") print("device_manager=preserved:active") print("device_manager_public_route=unchanged:active") print("device_gateway_tcp_9921=preserved:loopback-only") print("gelios=untouched") print("device_plane_rollback=source+predecessor-core-runtime") if device_plane_manager_reconciliation_preflight is not None: print( "device_plane_transition=" f"{device_plane_manager_reconciliation_preflight['mode']}" ) print( "failed_patch=" f"{DEVICE_PLANE_MANAGER_FAILED_PATCH_ID}" ) print( "failed_artifact_sha256=" f"{DEVICE_PLANE_MANAGER_FAILED_ARTIFACT_SHA256}" ) print( "recovery_backup=" f"{device_plane_manager_reconciliation_preflight['backup'].name}" ) print("device_plane_build=none") print("device_plane_runtime_mutation=none") print( "device_plane_runtime_services=" "preserved:device-control-core,device-gateway," "device-postgres,device-backhaul-target" ) print("device_manager=absent") print( "device_plane_source_action=" "publish-reconciliation-marker-only" ) print("device_gateway_tcp_9921=preserved:loopback-only") print("device_manager_public_route=unchanged:absent") print("device_plane_rollback=marker-only-runtime-unchanged") if device_plane_manager_v2_reconciliation_preflight is not None: print( "device_plane_transition=" f"{device_plane_manager_v2_reconciliation_preflight['mode']}" ) print( "failed_patch=" f"{DEVICE_PLANE_MANAGER_V2_FAILED_PATCH_ID}" ) print( "failed_artifact_sha256=" f"{DEVICE_PLANE_MANAGER_V2_FAILED_ARTIFACT_SHA256}" ) print( "recovery_backup=" f"{device_plane_manager_v2_reconciliation_preflight['backup'].name}" ) print( "device_plane_failure_class=" "deterministic-runtime-module-resolution" ) print("device_plane_build=none") print("device_plane_runtime_mutation=none") print( "device_plane_runtime_services=" "preserved:device-control-core,device-gateway," "device-postgres,device-backhaul-target" ) print("device_manager=absent") print( "device_plane_source_action=" "publish-v2-reconciliation-marker-only" ) print("device_gateway_tcp_9921=preserved:loopback-only") print("device_manager_public_route=unchanged:absent") print("device_plane_rollback=marker-only-runtime-unchanged") if device_plane_control_core_v3_reconciliation_preflight is not None: recovery = device_plane_control_core_v3_reconciliation_preflight current_core = recovery["runtime"]["core"] print(f"device_plane_transition={recovery['mode']}") print( "failed_patch=" f"{DEVICE_PLANE_CONTROL_CORE_V3_FAILED_PATCH_ID}" ) print( "failed_artifact_sha256=" f"{DEVICE_PLANE_CONTROL_CORE_V3_FAILED_ARTIFACT_SHA256}" ) print(f"recovery_backup={recovery['backup'].name}") print( "device_control_core_current_runtime=" f"{current_core['status']}:{current_core['health']}" ) print( "device_control_core_recovery_image=" f"{DEVICE_PLANE_CONTROL_CORE_V3_PREAPPLY_IMAGE_ID}" ) print("device_plane_build=none") print( "device_plane_runtime_mutation=" "retag-exact-preapply-image+recreate:device-control-core" ) print( "device_plane_runtime_services=" "preserved:device-manager,device-gateway,device-postgres," "device-backhaul-target" ) print("device_postgres=preserved:not-selected") print( "device_postgres_volume=" "preserved:nodedc-device-plane-postgres-data" ) print("device_gateway_public_ingress=disabled") print("device_gateway_tcp_9921=preserved:loopback-only") print("gelios=untouched-legacy-only") print("device_plane_rollback=marker+exact-preapply-image-runtime") if device_plane_control_core_incident_audit_preflight is not None: audit = device_plane_control_core_incident_audit_preflight diagnostics = audit["diagnostics"] runtime_by_service = { item["service"]: item for item in audit["runtime"]["services"] } core = runtime_by_service["device-control-core"] print(f"device_plane_transition={audit['mode']}") print("allowed_operation=canonical-plan-only") print("apply=forbidden") print("device_plane_build=none") print("device_plane_runtime_mutation=none") print("device_plane_source_mutation=none") print("device_postgres_mutation=none") print("device_plane_network_mutation=none") print("device_plane_secret_read=none") print(f"first_failed_backup={audit['firstBackup'].name}") print(f"second_failed_backup={audit['secondBackup'].name}") print( "device_control_core_runtime=" f"{core['containerId']}:{core['imageId']}:" f"{core['status']}:{core['health']}:restarts={core['restartCount']}" ) print( "device_control_core_log_sha256=" f"{diagnostics['logSha256']}" ) print( "device_control_core_log_error_count=" f"{len(diagnostics['logErrors'])}" ) for index, line in enumerate(diagnostics["logErrors"], start=1): print(f"device_control_core_log_error_{index:02d}={line}") for index, row in enumerate(diagnostics["database"], start=1): print( f"device_postgres_audit_{index:02d}=" f"{row.replace(chr(9), ':')}" ) print( "device_plane_runtime_services=" "read-only:device-control-core,device-manager,device-gateway," "device-postgres,device-backhaul-target" ) if ( device_plane_control_core_migration_replay_audit_preflight is not None ): audit = device_plane_control_core_migration_replay_audit_preflight database = audit["database"] core = audit["core"] print(f"device_plane_transition={audit['mode']}") print("allowed_operation=canonical-plan-only") print("apply=forbidden") print("device_plane_build=none") print("device_plane_runtime_mutation=none") print("device_plane_source_mutation=none") print("device_postgres_mutation=none") print("device_plane_network_mutation=none") print("device_plane_secret_read=none") print( "device_control_core_current_runtime=" f"{core['containerId']}:{core['imageId']}:" f"{core['status']}:{core['health']}:" f"restarts={core['restartCount']}" ) print( "device_control_core_rejected_recovery_artifact_sha256=" f"{DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_ARTIFACT_SHA256}" ) emit_device_plane_control_core_migration_replay_database_evidence( database ) print( "device_plane_runtime_services=" "read-only:device-control-core,device-manager,device-gateway," "device-postgres,device-backhaul-target" ) if ( device_plane_control_core_migration_replay_recovery_preflight is not None ): recovery = ( device_plane_control_core_migration_replay_recovery_preflight ) database = recovery["database"] core = recovery["core"] print(f"device_plane_transition={recovery['mode']}") print( "device_control_core_root_cause=" "migration-014-intermediate-constraint-revalidated-" "newer-valid-receipts" ) print( "device_control_core_migration_predecessor_sha256=" f"{DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_PREDECESSOR_SHA256}" ) print( "device_control_core_migration_target_sha256=" f"{DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_TARGET_SHA256}" ) print( "device_control_core_current_runtime=" f"{core['containerId']}:{core['imageId']}:" f"{core['status']}:{core['health']}:" f"restarts={core['restartCount']}" ) print( "device_control_core_live_command_kind_incompatible_count=" f"{database['invalidCommandKindCount']}" ) print( "device_control_core_triggering_newer_receipt_count=" f"{database['triggeringReceiptCount']}" ) print( "device_control_core_current_constraint=" "not-validated:exact-migration-011-command-kinds" ) print( "device_control_core_target_constraint=" "validated:covers-migration-016-command-kinds" ) emit_device_plane_control_core_migration_replay_database_evidence( database ) print("device_postgres_row_mutation=none") print("device_postgres_telemetry_table=absent") print( "device_plane_runtime_mutation=" "build+recreate:device-control-core" ) print( "device_plane_runtime_services=" "preserved:device-manager,device-gateway,device-postgres," "device-backhaul-target" ) print("device_gateway_public_ingress=disabled") print("device_gateway_tcp_9921=preserved:loopback-only") print("gelios=untouched-legacy-only") print( "device_plane_rollback=" "source+exact-degraded-predecessor-image-runtime" ) if ( device_plane_control_core_migration_replay_checkpoint_recovery_preflight is not None ): recovery = ( device_plane_control_core_migration_replay_checkpoint_recovery_preflight ) database = recovery["database"] core = recovery["core"] print(f"device_plane_transition={recovery['mode']}") print( "device_control_core_root_cause=" "restart-replay-cycles-exact-committed-migration-checkpoints" ) print( "device_control_core_failed_recovery_044_artifact_sha256=" f"{DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_ARTIFACT_SHA256}" ) print( "device_control_core_failed_recovery_044_started_apply=false" ) print( "device_control_core_migration_predecessor_sha256=" f"{DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_PREDECESSOR_SHA256}" ) print( "device_control_core_migration_target_sha256=" f"{DEVICE_PLANE_CONTROL_CORE_MIGRATION_014_TARGET_SHA256}" ) print( "device_control_core_current_runtime=" f"{core['containerId']}:{core['imageId']}:" f"{core['status']}:{core['health']}:" f"restarts={core['restartCount']}" ) print( "device_control_core_live_command_kind_incompatible_count=" f"{database['invalidCommandKindCount']}" ) print( "device_control_core_triggering_newer_receipt_count=" f"{database['triggeringReceiptCount']}" ) print( "device_control_core_current_constraint=" f"not-validated:exact-{database['constraintPhase']}-command-kinds" ) print( "device_control_core_allowed_predecessor_constraints=" "exact-replay-005|replay-007|replay-009|replay-011" ) print( "device_control_core_target_constraint=" "validated:exact-migration-016-command-kinds" ) emit_device_plane_control_core_migration_replay_database_evidence( database ) print("device_postgres_row_mutation=none") print("device_postgres_telemetry_table=absent") print( "device_plane_runtime_mutation=" "build+recreate:device-control-core" ) print( "device_plane_runtime_services=" "preserved:device-manager,device-gateway,device-postgres," "device-backhaul-target" ) print("device_gateway_public_ingress=disabled") print("device_gateway_tcp_9921=preserved:loopback-only") print("gelios=untouched-legacy-only") print( "device_plane_rollback=" "source+exact-degraded-predecessor-image-runtime" ) if device_plane_backhaul_vps_enrollment_preflight is not None: print( "device_plane_transition=" f"{device_plane_backhaul_vps_enrollment_preflight['mode']}" ) print( "device_plane_predecessor_patch=" f"{DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_PREDECESSOR_PATCH_ID}" ) print( "device_plane_predecessor_artifact_sha256=" f"{DEVICE_PLANE_BACKHAUL_VPS_ENROLLMENT_PREDECESSOR_ARTIFACT_SHA256}" ) print("device_plane_build=none") print( "device_plane_runtime_mutation=" "rotate-authorized-key+recreate:device-backhaul-target" ) print( "device_plane_runtime_services=" "preserved:device-control-core,device-gateway,device-postgres" ) print( "device_backhaul_previous_enrollment_public_key_sha256=" f"{device_plane_backhaul_vps_enrollment_preflight['previousEnrollmentPublicKeySha256']}" ) print( "device_backhaul_next_enrollment_public_key_sha256=" f"{device_plane_backhaul_vps_enrollment_preflight['nextEnrollmentPublicKeySha256']}" ) print( "device_backhaul_next_key_fingerprint=" f"{device_plane_backhaul_vps_enrollment_preflight['nextKeyFingerprint']}" ) print( "device_backhaul_permitopen=" f"{DEVICE_PLANE_BACKHAUL_PERMITTED_TARGET}" ) print("device_backhaul_docker_port_publication=disabled") print("device_backhaul_tailscale_serve=unchanged") print("device_backhaul_tailscale_funnel=disabled") print("device_backhaul_router_nat_firewall=unchanged") print("device_edge_public_ingress=disabled") print("device_command_transport=disabled") print("gelios=untouched") print( "device_plane_rollback=" "restore-previous-authorized-key+recreate-target" ) if device_plane_backhaul_preflight is not None: print( "device_plane_transition=" f"{device_plane_backhaul_preflight['mode']}" ) print( "failed_patch=" f"{DEVICE_PLANE_BACKHAUL_FAILED_PATCH_ID}" ) print( "failed_artifact_sha256=" f"{DEVICE_PLANE_BACKHAUL_FAILED_ARTIFACT_SHA256}" ) print( "failed_backup=" f"{DEVICE_PLANE_BACKHAUL_FAILED_BACKUP_ID}" ) print( "failed_rollback=" "ok:source-restored-target-removed-preserved-runtime-unchanged" ) print( "device_plane_predecessor_patch=" f"{DEVICE_PLANE_BACKHAUL_TARGET_PREDECESSOR_PATCH_ID}" ) print( "device_plane_predecessor_artifact_sha256=" f"{DEVICE_PLANE_BACKHAUL_TARGET_PREDECESSOR_ARTIFACT_SHA256}" ) print( "device_plane_runtime_mutation=" "build+create:device-backhaul-target+tailscale-serve" ) print( "device_plane_runtime_services=" "preserved:device-control-core,device-gateway,device-postgres" ) print( "device_backhaul_loopback_listen=" f"{DEVICE_PLANE_BACKHAUL_LOOPBACK_ADDRESS}:" f"{DEVICE_PLANE_BACKHAUL_LISTEN_PORT}/tcp" ) print( "device_backhaul_tailnet_listen=" f"tailscale-serve:{DEVICE_PLANE_BACKHAUL_TAILNET_ADDRESS}:" f"{DEVICE_PLANE_BACKHAUL_LISTEN_PORT}/tcp=>" f"{DEVICE_PLANE_BACKHAUL_TAILSCALE_SERVE_TARGET}" ) print( "device_backhaul_permitopen=" f"{DEVICE_PLANE_BACKHAUL_PERMITTED_TARGET}" ) print( "device_backhaul_enrollment_public_key_sha256=" f"{device_plane_backhaul_preflight['enrollmentPublicKeySha256']}" ) print( "device_backhaul_runtime_trust=" "runner-managed:host-key,authorized-keys,public-trust" ) print( "device_backhaul_tailscale_cli=" "official-package-account:" f"{DEVICE_PLANE_TAILSCALE_USER}:sha256:" f"{device_plane_backhaul_preflight['tailscaleCli']['binarySha256']}" ) print("device_backhaul_docker_port_publication=disabled") print("device_backhaul_tailscale_funnel=disabled") print("device_backhaul_router_nat_firewall=unchanged") print("device_edge_public_ingress=disabled") print( "device_plane_rollback=" "remove-tailnet-serve-target-and-restore-source" ) if device_plane_postgres_preflight is not None: print( "device_postgres_bootstrap=" f"{device_plane_postgres_preflight}" ) print("device_postgres_bootstrap_mode=create-if-absent") print("device_postgres_rollback_volume=preserve") if is_platform_device_core_hub_trust_slice(component, entries): print( "runtime_secret=runner-managed:" f"{PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE}" ) print("device_core_hub_credential_scope=handoff+session-only") print("device_manager_public_route=unchanged") if is_platform_device_manager_public_route_slice(component, entries): print("device_manager_public_route=https-via-reverse-proxy") print("device_manager_raw_tcp_ingress=forbidden") if is_launcher_device_core_session_slice(component, entries): print("device_core_owner_scopes=hub-signed-runtime-claims") print("device_core_company_scope=active-admin-grant-only") if touches_external_data_plane: print(f"runtime_secret=runner-managed:{EXTERNAL_DATA_PLANE_PROVISIONER_SECRET_FILE}") print(f"runtime_grants=runner-managed:{EXTERNAL_DATA_PLANE_READER_GRANTS_DIR}") print(f"runtime_public_trust=runner-managed:{FOUNDRY_EDP_MANAGED_PROVISIONER_PUBLIC_KEY_FILE}") if touches_engine_credential_sink(component, entries): print(f"runtime_identity_key_id={ENGINE_CREDENTIAL_PROVISIONER_KEY_ID}") print(f"runtime_private_key=runner-managed:{ENGINE_CREDENTIAL_PROVISIONER_PRIVATE_KEY_FILE}") print(f"runtime_public_key=runner-managed:{ENGINE_CREDENTIAL_SINK_PUBLIC_KEY_FILE}") if touches_engine_credential_sink(component, entries): print(f"backend_source_container_id={credential_backend_preflight['container_id']}") print(f"backend_source_image_id={credential_backend_preflight['source_image_id']}") print(f"backend_current_image_id={credential_backend_preflight['current_image_id']}") print(f"backend_current_barrier={credential_backend_preflight['mode']}") print(f"backend_derived_image={ENGINE_CREDENTIAL_BACKEND_IMAGE}") print(f"backend_package_lock_sha256={ENGINE_CREDENTIAL_BACKEND_PACKAGE_LOCK_SHA256}") print(f"backend_node_modules_tree_sha256={credential_backend_preflight['node_modules_tree_sha256']}") print("backend_rootfs=safe-roots:/bin,/sbin,/lib,/usr,/etc,/var/lib/apk") print("backend_build_network=none") print("backend_pull=never") print("backend_force_recreate=yes") if touches_publish_grant or touches_external_data_plane: print(f"runtime_private_key=runner-managed:{ENGINE_EDP_MANAGED_PROVISIONER_PRIVATE_KEY_FILE}") print(f"runtime_public_trust=runner-managed:{ENGINE_EDP_MANAGED_PROVISIONER_PUBLIC_KEY_FILE}") if touches_publish_grant: print(f"publish_grant_transition={publish_grant_preflight['mode']}") for changed_path in publish_grant_preflight["changed_paths"]: print(f"publish_grant_changed_path={changed_path}") print(f"predecessor_compose_sha256={publish_grant_predecessor_sha256}") print(f"backend_current_barrier={credential_backend_preflight['mode']}") print(f"runtime_private_state=runner-managed:{component_root('engine') / ENGINE_PUBLISH_GRANT_STATE_REL}") if publish_grant_preflight["mode"] == "installed-composite-provider-update": print("engine_provider_package=gelios.provider.v4") print("engine_provider_requests=monitoring-config,units?incltrip=true") print("engine_data_product=fleet.positions.current.v3") print( "predecessor_catalog_sha256=" f"{ENGINE_PUBLISH_GRANT_COMPOSITE_PROVIDER_CATALOG_PREDECESSOR_SHA256}" ) print( "target_catalog_sha256=" f"{ENGINE_PUBLISH_GRANT_COMPOSITE_PROVIDER_CATALOG_TARGET_SHA256}" ) print("provider_credential_values=preserved") if touches_agent_grant_migration: print(f"predecessor_store_sha256={agent_grant_migration_predecessor_sha256}") print(f"target_store_sha256={ENGINE_AGENT_FULL_GRANT_MIGRATION_TARGET_SHA256}") print(f"backend_current_barrier={credential_backend_preflight['mode']}") print("agent_grant_migration=v1-full-bundle-to-v2-named-full-developer-profile") print("backend_pull=never") if component in ("proxy-contur", "dc-amd-proxy") or touches_map_gateway: print(f"runtime_secret=runner-synced:{MAP_EGRESS_PROXY_SECRET_FILE}") if component == "dc-amd-proxy": print(f"runtime_state=runner-prepared:{DC_AMD_PROXY_RUNTIME_DIR}") if state_has_sha(sha): print("state=sha-already-applied") elif state_has_patch_id(manifest["id"]): print("state=patch-id-already-applied") else: print("state=new") print("== files ==") for rel in entries: print(f" {rel}") def add_backup_path(tar, root, rel): src = root / rel if src.exists(): tar.add(src, arcname=rel, recursive=True) return True return False def create_backup(root, backup_dir, entries, include_nginx_html): existing = [] missing = [] with tarfile.open(backup_dir / "source-before.tgz", "w:gz") as tar: for rel in entries: if add_backup_path(tar, root, rel): existing.append(rel) else: missing.append(rel) if include_nginx_html and (root / "nginx-html").exists(): tar.add(root / "nginx-html", arcname="nginx-html", recursive=True) existing.append("nginx-html") (backup_dir / "existing-files.txt").write_text("\n".join(existing) + ("\n" if existing else ""), encoding="utf-8") (backup_dir / "missing-files.txt").write_text("\n".join(missing) + ("\n" if missing else ""), encoding="utf-8") def backup_device_plane_backhaul_authorized_keys(backup_dir): source_stat = DEVICE_PLANE_BACKHAUL_AUTHORIZED_KEYS_FILE.lstat() if ( stat.S_ISLNK(source_stat.st_mode) or not stat.S_ISREG(source_stat.st_mode) or source_stat.st_uid != 0 or stat.S_IMODE(source_stat.st_mode) != 0o444 or source_stat.st_size > 2048 ): die("Device Plane backhaul authorized_keys backup source is unsafe") destination = backup_dir / DEVICE_PLANE_BACKHAUL_AUTHORIZED_KEYS_BACKUP if destination.exists() or destination.is_symlink(): die("Device Plane backhaul authorized_keys backup collision") shutil.copy2(DEVICE_PLANE_BACKHAUL_AUTHORIZED_KEYS_FILE, destination) os.chown(destination, 0, 0) destination.chmod(0o600) return sha256_file(destination) def read_backup_path_list(path): if not path.is_file(): die(f"deploy backup path list missing: {path}") return [line.strip() for line in path.read_text(encoding="utf-8").splitlines() if line.strip()] def validate_backup_partition(entries, existing, missing, label): entry_set = set(entries) existing_set = set(existing) missing_set = set(missing) if len(entry_set) != len(entries): die(f"{label} entries contain duplicates") if len(existing_set) != len(existing) or len(missing_set) != len(missing): die(f"{label} backup path lists contain duplicates") if existing_set & missing_set or existing_set | missing_set != entry_set: die(f"{label} backup entry set mismatch") for rel in entries: if any(rel != other and rel.startswith(other.rstrip("/") + "/") for other in entries): die(f"{label} entries overlap") return existing_set, missing_set def materialize_backup_tree(backup_archive, restore_root, existing): seen_names = set() seen_roots = set() directory_modes = [] member_count = 0 payload_bytes = 0 with tarfile.open(backup_archive, "r:gz") as archive: for member in archive: member_count += 1 if member_count > MAX_MEMBER_COUNT: die("Platform rollback backup has too many members") validate_posix_path(member.name) if member.name in seen_names: die("Platform rollback backup contains duplicate members") seen_names.add(member.name) owners = [ rel for rel in existing if member.name == rel or member.name.startswith(rel.rstrip("/") + "/") ] if len(owners) != 1: die("Platform rollback backup contains an unexpected member") if member.name == owners[0]: seen_roots.add(owners[0]) if not (member.isfile() or member.isdir()): die("Platform rollback backup contains an unsupported member") if member.mode & (stat.S_ISUID | stat.S_ISGID): die("Platform rollback backup contains a privileged member") target = tar_name_to_path(restore_root, member.name) if member.isdir(): target.mkdir(parents=True, exist_ok=True) target.chmod(0o755) directory_modes.append((target, stat.S_IMODE(member.mode) & 0o777)) continue if member.size > MAX_FILE_BYTES: die("Platform rollback backup member is too large") payload_bytes += member.size if payload_bytes > MAX_PAYLOAD_BYTES: die("Platform rollback backup is too large") target.parent.mkdir(parents=True, exist_ok=True) if target.exists(): die("Platform rollback backup member collision") source = archive.extractfile(member) if source is None: die("Platform rollback backup member is unreadable") with source, target.open("xb") as output: shutil.copyfileobj(source, output, 1024 * 1024) target.chmod(stat.S_IMODE(member.mode) & 0o777) if seen_roots != set(existing): die("Platform rollback backup root set mismatch") for target, mode in sorted(directory_modes, key=lambda item: len(item[0].parts), reverse=True): target.chmod(mode) def restore_platform_overlay(root, backup_dir, entries, current_stamp): existing = read_backup_path_list(backup_dir / "existing-files.txt") missing = read_backup_path_list(backup_dir / "missing-files.txt") existing_set, missing_set = validate_backup_partition( entries, existing, missing, "Platform rollback", ) backup_archive = backup_dir / "source-before.tgz" try: backup_stat = backup_archive.lstat() except FileNotFoundError: die("Platform rollback backup archive is missing") if stat.S_ISLNK(backup_stat.st_mode) or not stat.S_ISREG(backup_stat.st_mode): die("Platform rollback backup archive is unsafe") with tempfile.TemporaryDirectory(prefix="platform-rollback-", dir=TMP_DIR) as tmp: restore_root = Path(tmp) materialize_backup_tree(backup_archive, restore_root, existing_set) for rel in entries: destination = root / rel destination_resolved = destination.resolve(strict=False) if not is_relative_to(destination_resolved, root.resolve()): die("Platform rollback target escaped component root") if destination.is_symlink(): die("Platform rollback target is a symlink") if rel in existing_set: staged = restore_root / rel rollback_stamp = f"{current_stamp}-platform-rollback" if staged.is_dir(): replace_directory(staged, destination, rollback_stamp) elif staged.is_file(): replace_file(staged, destination, rollback_stamp) else: die("Platform rollback staged path is missing") continue if rel not in missing_set or not destination.exists(): continue if destination.is_dir(): shutil.rmtree(destination) elif destination.is_file(): destination.unlink() else: die("Platform rollback target has unsupported type") return len(entries) def rollback_platform_apply(root, backup_dir, entries, current_stamp, runtime_started, applied_services): existing = read_backup_path_list(backup_dir / "existing-files.txt") missing = read_backup_path_list(backup_dir / "missing-files.txt") existing_set, missing_set = validate_backup_partition( entries, existing, missing, "Platform runtime rollback", ) edp_was_new = PLATFORM_EXTERNAL_DATA_PLANE_COMPOSE_REL in missing_set candidate_cleanup_failed = False if runtime_started and edp_was_new: # Remove only candidate-only containers while its Compose overlay is # still installed. Deliberately omit --volumes: rollback never deletes # newly written data, even when the first EDP activation is rejected. try: candidate_services = tuple( service for service in applied_services if service == EXTERNAL_DATA_PLANE_SERVICE ) if not candidate_services: die("Platform candidate-only EDP runtime selection is invalid") stop_and_remove_compose_services( "platform", candidate_services, ) except Exception: candidate_cleanup_failed = True restored_count = restore_platform_overlay(root, backup_dir, entries, current_stamp) if candidate_cleanup_failed: die("Platform candidate-only runtime cleanup failed after source restore") if not runtime_started: return f"source-restored-runtime-unchanged:{restored_count}" baseline_entries = [rel for rel in entries if rel in existing_set] if edp_was_new: baseline_entries = [ rel for rel in baseline_entries if not touches_external_data_plane_files((rel,)) ] baseline_services = tuple( service for service in applied_services if not edp_was_new or service not in (EXTERNAL_DATA_PLANE_SERVICE, EXTERNAL_DATA_PLANE_DATABASE_SERVICE) ) if baseline_services: run_component_runtime("platform", baseline_entries, baseline_services) if baseline_services == (EXTERNAL_DATA_PLANE_SERVICE,): # The restored EDP may predate managed provisioning. Prove the # stable baseline contract without requiring a candidate-only field. healthcheck_url(external_data_plane_healthcheck(require_managed=False)) else: run_healthchecks("platform", baseline_entries, baseline_services) return f"source+runtime-restored:{restored_count}" def remove_device_plane_control_network_if_unused(): network = inspect_device_plane_network_optional( DEVICE_PLANE_CONTROL_NETWORK ) if network is None: return "absent" if ( network.get("Name") != DEVICE_PLANE_CONTROL_NETWORK or network.get("Driver") != "bridge" or network.get("Internal") is not False or (network.get("Containers") or {}) or (network.get("Options") or {}).get( "com.docker.network.bridge.enable_ip_masquerade" ) != "false" ): die("Device Plane control network is unsafe to remove") subprocess.run( [ str(DOCKER), "network", "rm", DEVICE_PLANE_CONTROL_NETWORK, ], check=True, ) if inspect_device_plane_network_optional(DEVICE_PLANE_CONTROL_NETWORK): die("Device Plane control network removal failed") return "removed" def rollback_device_plane_network_publication_apply( root, backup_dir, entries, current_stamp, runtime_started, applied_services, ): if tuple(applied_services) != ( "device-control-core", "device-gateway", ): die("Device Plane network-publication rollback service mismatch") if runtime_started: stop_and_remove_compose_services( "device-plane", applied_services, ) restored_count = restore_platform_overlay( root, backup_dir, entries, current_stamp, ) remove_device_plane_control_network_if_unused() run_compose( "device-plane", applied_services, ("docker-compose.device-plane.yml",), ) for service in DEVICE_PLANE_RUNTIME_SERVICES: healthcheck_compose_service("device-plane", service) runtime = validate_device_plane_foundation_runtime() if ( runtime["device-postgres"]["containerId"] != DEVICE_PLANE_FOUNDATION_PREDECESSOR_CONTAINER_IDS[ "device-postgres" ] ): die("Device Plane rollback changed PostgreSQL generation") assert_loopback_tcp_port_closed(18120) assert_loopback_tcp_port_closed(18121) assert_loopback_tcp_port_closed(9921) return f"source+internal-runtime-restored:{restored_count}" def rollback_device_plane_apply( root, backup_dir, entries, current_stamp, runtime_started, applied_services, ): existing = read_backup_path_list(backup_dir / "existing-files.txt") missing = read_backup_path_list(backup_dir / "missing-files.txt") existing_set, _missing_set = validate_backup_partition( entries, existing, missing, "Device Plane runtime rollback", ) baseline_entries = [rel for rel in entries if rel in existing_set] runtime_inventory = read_strict_json( backup_dir / "runtime-before.json", "Device Plane pre-apply runtime inventory", max_bytes=64 * 1024, ) preapply_services = set( device_plane_inventory_service_names(runtime_inventory) ) baseline_services = tuple( service for service in applied_services if service in preapply_services ) candidate_only_services = tuple( service for service in applied_services if service not in baseline_services ) tailscale_serve_before = None if is_device_plane_backhaul_target_slice("device-plane", entries): tailscale_serve_before = read_strict_json( backup_dir / "tailscale-serve-before.json", "Device Plane pre-apply Tailscale Serve state", max_bytes=4 * 1024 * 1024, ) disable_device_plane_tailscale_serve(tailscale_serve_before) candidate_cleanup_failed = False if runtime_started and candidate_only_services: # Remove only candidate services while the candidate Compose file is # still present. PostgreSQL can appear only in the exact one-time # bootstrap slice; volume flags are deliberately never used. try: stop_and_remove_compose_services( "device-plane", candidate_only_services, ) except Exception: candidate_cleanup_failed = True restored_count = restore_platform_overlay( root, backup_dir, entries, current_stamp, ) if candidate_cleanup_failed: die("Device Plane candidate-only runtime cleanup failed after source restore") if is_device_plane_backhaul_target_slice("device-plane", entries): if device_plane_service_container_ids( DEVICE_PLANE_BACKHAUL_TARGET_SERVICE ): die("Device Plane backhaul rollback retained target runtime") validate_device_plane_preserved_runtime_unchanged( runtime_inventory, "Device Plane backhaul rollback", ) tailscale = validate_device_plane_tailscale_runtime( require_target=False ) if tailscale["serve"] != tailscale_serve_before: die("Device Plane backhaul rollback did not restore Tailscale Serve") assert_loopback_tcp_port_open(9921) return ( "tailscale-serve-restored-source-restored-target-removed-" "preserved-runtime-unchanged:" f"{restored_count}" ) if not runtime_started or not baseline_services: return f"source-restored-runtime-unchanged:{restored_count}" control_core_release_rollback = ( is_device_plane_control_core_release_slice( "device-plane", entries, ) or is_device_plane_control_core_migration_replay_recovery_slice( "device-plane", entries, ) or is_device_plane_control_core_migration_replay_checkpoint_recovery_slice( "device-plane", entries, ) ) if control_core_release_rollback: before = { item["service"]: item for item in runtime_inventory["services"] } selected_before = before.get("device-control-core") or {} image_id = selected_before.get("imageId") if not isinstance(image_id, str): die("Device Control Core rollback image evidence is missing") retag_device_plane_control_core_image( image_id, "Device Control Core exact pre-apply rollback image", ) prepare_component_runtime("device-plane", baseline_entries) run_compose("device-plane", baseline_services, baseline_entries) else: run_component_runtime( "device-plane", baseline_entries, baseline_services, ) if control_core_release_rollback: accept_device_plane_control_core_rollback_runtime( runtime_inventory ) validate_device_manager_control_plane_runtime( require_edge_channel=True, core_network_mode="private-egress", ) elif is_device_plane_edge_core_channel_bootstrap_slice( "device-plane", entries, ): for service in ( "device-control-core", "device-manager", "device-gateway", "device-postgres", ): healthcheck_compose_service_with_grace( "device-plane", service, ) validate_device_manager_control_plane_runtime( require_edge_channel=is_device_plane_edge_core_channel_upgrade_slice( "device-plane", entries, ) ) elif is_device_plane_manager_control_plane_slice("device-plane", entries): for service in baseline_services: healthcheck_compose_service_with_grace( "device-plane", service, ) for check in component_healthchecks( "device-plane", baseline_entries, baseline_services, ): healthcheck_url(check) else: run_healthchecks( "device-plane", baseline_entries, baseline_services, ) return f"source+runtime-restored:{restored_count}" def rollback_device_plane_backhaul_vps_enrollment( root, backup_dir, entries, current_stamp, runtime_before, ): restored_count = restore_platform_overlay( root, backup_dir, entries, current_stamp, ) backup = backup_dir / DEVICE_PLANE_BACKHAUL_AUTHORIZED_KEYS_BACKUP backup_stat = backup.lstat() previous = read_device_plane_backhaul_enrollment_public_key() expected = ( 'restrict,port-forwarding,permitopen="127.0.0.1:9921" ' f"{previous['line']}\n" ) if ( stat.S_ISLNK(backup_stat.st_mode) or not stat.S_ISREG(backup_stat.st_mode) or backup_stat.st_uid != 0 or stat.S_IMODE(backup_stat.st_mode) != 0o600 or backup_stat.st_size > 2048 or backup.read_text(encoding="ascii") != expected ): die("Device Plane VPS enrollment rollback backup mismatch") install_device_plane_backhaul_authorized_key(previous) run_compose( "device-plane", (DEVICE_PLANE_BACKHAUL_TARGET_SERVICE,), entries, ) run_healthchecks( "device-plane", entries, (DEVICE_PLANE_BACKHAUL_TARGET_SERVICE,), ) validate_device_plane_backhaul_target_runtime( runtime_before, expected_enrollment=previous, ) return f"previous-key+target+source-restored:{restored_count}" def rollback_engine_apply(root, backup_dir, entries, current_stamp, runtime_started, applied_services): existing = read_backup_path_list(backup_dir / "existing-files.txt") missing = read_backup_path_list(backup_dir / "missing-files.txt") restore_entries = list(entries) if "nginx-html" in existing: restore_entries.append("nginx-html") validate_backup_partition( restore_entries, existing, missing, "Engine runtime rollback", ) restored_count = restore_platform_overlay( root, backup_dir, restore_entries, current_stamp, ) if not runtime_started: return f"source-restored-runtime-unchanged:{restored_count}" run_component_runtime("engine", entries, applied_services) run_healthchecks("engine", entries, applied_services) return f"source+runtime-restored:{restored_count}" def validate_engine_l2_closed_loop_stable_source(root): stable_entries = tuple(ENGINE_L2_CLOSED_LOOP_STABLE_SHA256) actual = collect_exact_files( root, stable_entries, "Engine L2 closed-loop stable rollback", ) if actual != ENGINE_L2_CLOSED_LOOP_STABLE_SHA256: changed = sorted( rel for rel in ENGINE_L2_CLOSED_LOOP_STABLE_SHA256 if actual.get(rel) != ENGINE_L2_CLOSED_LOOP_STABLE_SHA256[rel] ) detail = changed[0] if changed else "unknown" die(f"Engine L2 closed-loop stable rollback drift detected: {detail}") graph_repository = root / "nodedc-source/server/l2/graphRepository.js" if graph_repository.exists() or graph_repository.is_symlink(): die("Engine L2 closed-loop stable rollback retained candidate-only repository") nginx_actual = collect_exact_files( root, ("nginx-html/index.html", "nginx-html/assets"), "Engine L2 closed-loop stable nginx rollback", ) nginx_expected = { rel.replace("nodedc-source/dist/", "nginx-html/", 1): digest for rel, digest in ENGINE_L2_CLOSED_LOOP_STABLE_SHA256.items() if rel.startswith("nodedc-source/dist/") } if nginx_actual != nginx_expected: die("Engine L2 closed-loop stable nginx rollback drift detected") descriptor = current_engine_node_intelligence_descriptor() if descriptor is None or descriptor.get("action") != "activate": die("Engine L2 closed-loop stable descriptor was not restored") validate_installed_engine_node_intelligence_source(descriptor) return descriptor def rollback_engine_l2_closed_loop_reconciliation( root, candidate_backup_dir, entries, current_stamp, runtime_started, applied_services, ): if tuple(entries) != ENGINE_L2_CLOSED_LOOP_ARTIFACT_ENTRIES: die("Engine L2 closed-loop rollback entry set mismatch") if tuple(applied_services) != ("nodedc-backend", "app"): die("Engine L2 closed-loop rollback service set mismatch") recovery_backup = validate_engine_l2_closed_loop_recovery_evidence() candidate_restore_entries = [*entries, "nginx-html"] validate_backup_partition( candidate_restore_entries, read_backup_path_list(candidate_backup_dir / "existing-files.txt"), read_backup_path_list(candidate_backup_dir / "missing-files.txt"), "Engine L2 closed-loop candidate rollback", ) candidate_restored = restore_platform_overlay( root, candidate_backup_dir, candidate_restore_entries, f"{current_stamp}-l2-candidate", ) original_entries = [ rel for rel in ENGINE_L2_CLOSED_LOOP_ARTIFACT_ENTRIES if rel != ENGINE_L2_CLOSED_LOOP_DESCRIPTOR_REL ] original_restore_entries = [*original_entries, "nginx-html"] validate_backup_partition( original_restore_entries, read_backup_path_list(recovery_backup / "existing-files.txt"), read_backup_path_list(recovery_backup / "missing-files.txt"), "Engine L2 closed-loop original rollback", ) original_restored = restore_platform_overlay( root, recovery_backup, original_restore_entries, f"{current_stamp}-l2-original", ) validate_engine_l2_closed_loop_stable_source(root) total_restored = candidate_restored + original_restored if not runtime_started: return f"stable-source-restored-runtime-unchanged:{total_restored}" stable_entries = tuple(ENGINE_L2_CLOSED_LOOP_STABLE_SHA256) run_component_runtime( "engine", stable_entries, applied_services, ) for service in applied_services: healthcheck_compose_service("engine", service) for check in component_healthchecks( "engine", stable_entries, applied_services, ): healthcheck_url(check) backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die("Engine L2 closed-loop stable backend rollback acceptance failed") return f"stable-source+runtime-restored:{total_restored}" def inactive_engine_n8n_descriptor(descriptor): inactive = dict(descriptor) inactive["action"] = "rollback-inactive" inactive["expectedCurrent"] = descriptor["releaseId"] inactive["expectedNodeTypes"] = [] inactive["expectedCredentialTypes"] = [] return inactive def restore_engine_n8n_transition(root, backup_dir, entries, descriptor, current_stamp): existing = set(read_backup_path_list(backup_dir / "existing-files.txt")) missing = set(read_backup_path_list(backup_dir / "missing-files.txt")) if existing | missing != set(entries): die("Engine n8n rollback backup entry set mismatch") required_baseline = { ENGINE_N8N_NODES_CATALOG_REL, ENGINE_N8N_CREDENTIALS_CATALOG_REL, ENGINE_N8N_SCHEMA_META_REL, } if not required_baseline.issubset(existing): die("Engine n8n rollback baseline catalogs were not present before apply") with tempfile.TemporaryDirectory(prefix="engine-n8n-rollback-", dir=TMP_DIR) as tmp: restore_root = Path(tmp) with tarfile.open(backup_dir / "source-before.tgz", "r:gz") as archive: for rel in entries: if rel not in existing: continue try: member = archive.getmember(rel) except KeyError: die(f"Engine n8n rollback backup member missing: {rel}") if not member.isfile(): die(f"Engine n8n rollback backup member is not a file: {rel}") source = archive.extractfile(member) if source is None: die(f"Engine n8n rollback backup member unreadable: {rel}") staged = restore_root / rel staged.parent.mkdir(parents=True, exist_ok=True) with staged.open("xb") as output: shutil.copyfileobj(source, output) replace_file(staged, root / rel, f"{current_stamp}-rollback") if ENGINE_N8N_TRANSITION_DESCRIPTOR_REL not in existing: inactive = inactive_engine_n8n_descriptor(descriptor) descriptor_path = root / ENGINE_N8N_TRANSITION_DESCRIPTOR_REL descriptor_path.parent.mkdir(parents=True, exist_ok=True) next_path = descriptor_path.with_name(f"{descriptor_path.name}.next-{current_stamp}-rollback") if next_path.exists() or next_path.is_symlink(): die(f"Engine n8n rollback descriptor staging path exists: {next_path}") next_path.write_text(json.dumps(inactive, indent=2) + "\n", encoding="utf-8") os.replace(next_path, descriptor_path) restored_descriptor = current_engine_n8n_transition_descriptor() if restored_descriptor is None: die("Engine n8n rollback did not restore an explicit baseline descriptor") run_compose("engine", ("n8n",), entries) accept_engine_n8n_runtime(restored_descriptor) return restored_descriptor["action"] def replace_file(src, dst, current_stamp): dst.parent.mkdir(parents=True, exist_ok=True) tmp = dst.with_name(f"{dst.name}.next-{current_stamp}") if tmp.exists(): die(f"staging path already exists: {tmp}") shutil.copy2(src, tmp) os.replace(tmp, dst) def replace_directory(src, dst, current_stamp): dst.parent.mkdir(parents=True, exist_ok=True) next_path = dst.with_name(f"{dst.name}.next-{current_stamp}") prev_path = dst.with_name(f"{dst.name}.prev-{current_stamp}") if next_path.exists() or prev_path.exists(): die(f"staging path already exists near: {dst}") shutil.copytree(src, next_path) if dst.exists(): dst.rename(prev_path) next_path.rename(dst) def copy_payload_path(payload_dir, root, rel, current_stamp): src = payload_dir / rel dst = root / rel root_real = root.resolve() dst_real = dst.resolve(strict=False) if not is_relative_to(dst_real, root_real): die(f"target escaped component root: {rel}") if src.is_dir(): replace_directory(src, dst, current_stamp) elif src.is_file(): replace_file(src, dst, current_stamp) else: die(f"payload path is neither file nor directory: {rel}") def restore_overlay_source(root, backup_dir, entries, current_stamp): existing = set(read_backup_path_list(backup_dir / "existing-files.txt")) missing = set(read_backup_path_list(backup_dir / "missing-files.txt")) if existing | missing != set(entries) or existing & missing: die("credential bridge rollback backup entry set mismatch") with tempfile.TemporaryDirectory(prefix="credential-bridge-rollback-", dir=TMP_DIR) as tmp: restore_root = Path(tmp) names = set() with tarfile.open(backup_dir / "source-before.tgz", "r:gz") as archive: for member in archive: validate_posix_path(member.name) if member.name in names: die(f"credential bridge rollback duplicate backup member: {member.name}") names.add(member.name) if not path_is_covered_by_files_list(member.name, existing): die(f"credential bridge rollback unexpected backup member: {member.name}") if not (member.isfile() or member.isdir()): die(f"credential bridge rollback special backup member: {member.name}") target = tar_name_to_path(restore_root, member.name) if member.isdir(): target.mkdir(parents=True, exist_ok=True) target.chmod(0o755) continue if member.size > MAX_FILE_BYTES: die(f"credential bridge rollback backup member too large: {member.name}") target.parent.mkdir(parents=True, exist_ok=True) source = archive.extractfile(member) if source is None: die(f"credential bridge rollback unreadable backup member: {member.name}") with source, target.open("xb") as output: shutil.copyfileobj(source, output, 1024 * 1024) target.chmod(0o644) for rel in entries: destination = root / rel if rel in existing: source = restore_root / rel if not source.exists() or source.is_symlink(): die(f"credential bridge rollback source missing or unsafe: {rel}") copy_payload_path(restore_root, root, rel, f"{current_stamp}-rollback") continue if not destination.exists() and not destination.is_symlink(): continue if destination.is_symlink(): die(f"credential bridge rollback installed symlink rejected: {rel}") retained = destination.with_name(f"{destination.name}.failed-{current_stamp}") if retained.exists() or retained.is_symlink(): die(f"credential bridge rollback retained path already exists: {retained}") destination.rename(retained) def rollback_engine_credential_bridge( component, root, backup_dir, entries, current_stamp, engine_backend_recreated=False, engine_backend_initial_mode=None, ): if not touches_engine_credential_sink(component, entries): die("credential bridge rollback called for unrelated artifact") restore_overlay_source(root, backup_dir, entries, current_stamp) if not engine_backend_recreated: if engine_backend_initial_mode == "verified-derived-retry": validate_engine_backend_activation_marker() if not engine_backend_immutable_runtime_is_current(): raise ReconciliationRequired("engine_backend_prior_derived_runtime_unproven") return "engine-sink-source-restored-active-runtime-preserved" if engine_backend_initial_mode == "verified-prepared-not-active": validate_engine_backend_activation_marker() return "engine-sink-source-restored-prepared-runtime-preserved" quarantine = quarantine_engine_backend_partial_runtime() return f"engine-sink-source-restored-before-backend-recreate-{quarantine}" rollback_entries = ("nodedc-source/server/index.js",) # The first Compose call may have failed before replacing the base # container. Original sink entries explicitly authorize the already proven # prepared override for this rollback recreate. run_compose("engine", ("nodedc-backend",), entries) healthcheck_compose_service("engine", "nodedc-backend") run_healthchecks("engine", rollback_entries) runtime = preflight_engine_credential_backend_runtime() if runtime["mode"] != "verified-derived-retry": die("Engine backend immutable rollback runtime acceptance failed") return "engine-sink-source-restored" def rollback_gitea_fresh_install( root, backup_dir, entries, current_stamp, runtime_started=False, ): if root != GITEA_ROOT or not is_gitea_fresh_install_slice("gitea", entries): die("Gitea fresh-install rollback called for unrelated artifact") existing = read_backup_path_list(backup_dir / "existing-files.txt") missing = read_backup_path_list(backup_dir / "missing-files.txt") existing_set, missing_set = validate_backup_partition( entries, existing, missing, "Gitea fresh-install rollback", ) if existing_set or missing_set != set(GITEA_FRESH_INSTALL_ENTRIES): die("Gitea fresh-install rollback predecessor must be absent") if runtime_started: try: stop_and_remove_compose_services("gitea", (GITEA_SERVICE,)) except Exception: # Never restore an absent source root while a bind-mounted # candidate may still be live. Preserve the complete root in # place and require explicit reconciliation. raise ReconciliationRequired( "gitea_candidate_stop_failed_root_preserved" ) try: candidate_ids = gitea_compose_project_container_ids() except Exception: raise ReconciliationRequired( "gitea_candidate_absence_unproven_root_preserved" ) if candidate_ids: raise ReconciliationRequired( "gitea_candidate_still_present_root_preserved" ) quarantined_root = None if root.exists() or root.is_symlink(): root_stat = root.lstat() if stat.S_ISLNK(root_stat.st_mode) or not stat.S_ISDIR(root_stat.st_mode): die("Gitea fresh-install rollback root is unsafe") quarantined_root = root.with_name( f"{root.name}.failed-{current_stamp}" ) if quarantined_root.exists() or quarantined_root.is_symlink(): die("Gitea fresh-install rollback quarantine collision") root.rename(quarantined_root) restore_overlay_source(root, backup_dir, entries, current_stamp) if root.exists() or root.is_symlink(): die("Gitea fresh-install rollback did not restore absent root") assert_loopback_tcp_port_closed(GITEA_DISABLED_SSH_HOST_PORT) validate_legacy_gitea_container_isolation() if gitea_compose_project_container_ids(): raise ReconciliationRequired( "gitea_candidate_reappeared_after_quarantine" ) validate_gitea_legacy_candidate_network_absent() validate_gitea_no_docker_port_publications() # Data/config/secrets are intentionally retained in the isolated new root # as incident evidence. No legacy path or legacy container is referenced. return ( "candidate-stopped-root-quarantined-runtime-state-preserved:" + (quarantined_root.name if quarantined_root else "none-created") ) def rollback_engine_node_intelligence( root, backup_dir, entries, current_stamp, runtime_started=False, node_intelligence_service_stopped=False, ): candidate_descriptor = current_engine_node_intelligence_descriptor() candidate_active = ( candidate_descriptor is not None and candidate_descriptor["action"] == "activate" ) cleanup_failed = False if runtime_started and candidate_active: try: stop_and_remove_compose_services( "engine", (ENGINE_NODE_INTELLIGENCE_SERVICE,), ) except Exception: cleanup_failed = True restore_overlay_source(root, backup_dir, entries, current_stamp) restored = current_engine_node_intelligence_descriptor() restored_action = restored["action"] if restored is not None else "inactive" if cleanup_failed: die("Engine node-intelligence candidate runtime cleanup failed after source restore") reconcile_runtime = runtime_started or node_intelligence_service_stopped if not reconcile_runtime: return f"source-restored-runtime-unchanged:{restored_action}" if restored is not None and restored["action"] == "activate": validate_installed_engine_node_intelligence_source(restored) ensure_engine_node_intelligence_secret() install_engine_node_intelligence_image(restored) run_engine_node_intelligence_compose( (ENGINE_NODE_INTELLIGENCE_SERVICE, "nodedc-backend"), ENGINE_NODE_INTELLIGENCE_ARTIFACT_ENTRIES, ) accept_engine_node_intelligence_runtime(restored) return "active" run_engine_node_intelligence_compose( ("nodedc-backend",), ENGINE_NODE_INTELLIGENCE_ROLLBACK_ENTRIES, ) healthcheck_compose_service("engine", "nodedc-backend") healthcheck_url("http://127.0.0.1:3001/health") backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die("Engine node-intelligence rollback backend barrier failed") assert_engine_node_intelligence_container_absent() return restored_action def seal_n8n_private_extension_release(root, rel): release_dir = root / rel if not release_dir.is_dir() or release_dir.is_symlink(): die("private extension release was not installed as a directory") for path in [release_dir, *release_dir.rglob("*")]: path_stat = path.lstat() if stat.S_ISLNK(path_stat.st_mode): die(f"private extension installed symlink rejected: {path}") os.chown(path, 0, 0) if stat.S_ISDIR(path_stat.st_mode): path.chmod(0o555) elif stat.S_ISREG(path_stat.st_mode): path.chmod(0o444) else: die(f"private extension installed special file rejected: {path}") def publish_engine_dist(root, current_stamp): dist = root / "nodedc-source" / "dist" if not dist.is_dir(): return dst = root / "nginx-html" next_path = root / f"nginx-html.next-{current_stamp}" prev_path = root / f"nginx-html.prev-{current_stamp}" if next_path.exists() or prev_path.exists(): die("nginx-html staging path already exists") shutil.copytree(dist, next_path) if dst.exists(): dst.rename(prev_path) next_path.rename(dst) def component_publish_dist(component, entries=None): if not COMPONENTS[component].get("publish_dist"): return False if component == "engine": return entries is not None and any( rel == "nodedc-source/dist" or rel.startswith("nodedc-source/dist/") for rel in entries ) return True def run_build(component, entries=None): for build_root, build_args in component_builds(component, entries): dockerfile = "Dockerfile" for idx, arg in enumerate(build_args): if arg == "-f" and idx + 1 < len(build_args): dockerfile = build_args[idx + 1] break if not (build_root / dockerfile).is_file(): die(f"Dockerfile not found in build root: {build_root / dockerfile}") cmd = [str(DOCKER), *build_args] env = os.environ.copy() if component == "tasker": env["DOCKER_BUILDKIT"] = "0" subprocess.run(cmd, cwd=str(build_root), env=env, check=True) def compose_base_cmd( component, allow_prepared_engine_backend=False, expected_node_intelligence_gateway_sha256=None, ): cmd = [str(DOCKER), "compose"] compose_project = component_compose_project(component) if compose_project: cmd.extend(["-p", compose_project]) env_file = component_compose_env_file(component) if env_file: cmd.extend(["--env-file", str(env_file)]) for compose_file in component_compose_files( component, allow_prepared_engine_backend=allow_prepared_engine_backend, expected_node_intelligence_gateway_sha256= expected_node_intelligence_gateway_sha256, ): cmd.extend(["-f", str(compose_file)]) return cmd def run_compose(component, services, entries=None): compose_root = component_compose_root(component) allow_prepared_engine_backend = touches_engine_credential_sink(component, entries) cmd = [ *compose_base_cmd( component, allow_prepared_engine_backend=allow_prepared_engine_backend, ), "up", "-d", "--force-recreate", ] if (is_engine_n8n_transition(component, entries) or touches_engine_credential_sink(component, entries) or touches_engine_credential_provisioner(component, entries) or (component == "engine" and ENGINE_CREDENTIAL_BACKEND_ACTIVATION_FILE.exists())): cmd.extend(["--pull", "never"]) if COMPONENTS[component].get("compose_build"): cmd.append("--build") if component_compose_no_deps(component, entries): cmd.append("--no-deps") cmd.extend(services) try: subprocess.run(cmd, cwd=str(compose_root), check=True) except subprocess.CalledProcessError: subprocess.run( [ *compose_base_cmd( component, allow_prepared_engine_backend=allow_prepared_engine_backend, ), "logs", "--no-color", "--tail=180", *services, ], cwd=str(compose_root), check=False, ) raise subprocess.run( [ *compose_base_cmd( component, allow_prepared_engine_backend=allow_prepared_engine_backend, ), "ps", ], cwd=str(compose_root), check=True, ) def run_gitea_fresh_install_compose(services, entries): if ( tuple(services) != (GITEA_SERVICE,) or not is_gitea_fresh_install_slice("gitea", entries) ): die("Gitea fresh-install Compose service set mismatch") cmd = [ *compose_base_cmd("gitea"), "up", "-d", "--force-recreate", "--pull", "never", "--no-deps", GITEA_SERVICE, ] try: subprocess.run(cmd, cwd=str(GITEA_ROOT), check=True) except subprocess.CalledProcessError: subprocess.run( [ *compose_base_cmd("gitea"), "logs", "--no-color", "--tail=180", GITEA_SERVICE, ], cwd=str(GITEA_ROOT), check=False, ) raise subprocess.run( [*compose_base_cmd("gitea"), "ps"], cwd=str(GITEA_ROOT), check=True, ) def run_engine_node_intelligence_compose(services, entries): if not is_engine_node_intelligence_transition("engine", entries): die("Engine node-intelligence Compose called for unrelated artifact") compose_root = component_compose_root("engine") cmd = [ *compose_base_cmd("engine"), "up", "-d", "--force-recreate", "--pull", "never", "--no-deps", *services, ] try: subprocess.run(cmd, cwd=str(compose_root), check=True) except subprocess.CalledProcessError: subprocess.run( [ *compose_base_cmd("engine"), "logs", "--no-color", "--tail=180", *services, ], cwd=str(compose_root), check=False, ) raise subprocess.run( [*compose_base_cmd("engine"), "ps"], cwd=str(compose_root), check=True, ) def install_device_plane_backhaul_authorized_key(enrollment): DEVICE_PLANE_BACKHAUL_SECRET_DIR.mkdir( parents=True, exist_ok=True, ) os.chown(DEVICE_PLANE_BACKHAUL_SECRET_DIR, 0, 0) DEVICE_PLANE_BACKHAUL_SECRET_DIR.chmod(0o700) authorized = ( 'restrict,port-forwarding,permitopen="127.0.0.1:9921" ' f"{enrollment['line']}\n" ) temporary = DEVICE_PLANE_BACKHAUL_AUTHORIZED_KEYS_FILE.with_suffix( ".installing" ) if temporary.exists() or temporary.is_symlink(): die("Device Plane backhaul authorized_keys staging path exists") temporary.write_text(authorized, encoding="ascii") os.chown(temporary, 0, 0) temporary.chmod(0o444) os.replace(temporary, DEVICE_PLANE_BACKHAUL_AUTHORIZED_KEYS_FILE) expected_sha256 = hashlib.sha256(authorized.encode("ascii")).hexdigest() if sha256_file(DEVICE_PLANE_BACKHAUL_AUTHORIZED_KEYS_FILE) != expected_sha256: die("Device Plane backhaul authorized key verification failed") return expected_sha256 def ensure_device_plane_backhaul_target_state(): enrollment = read_device_plane_backhaul_enrollment_public_key() DEVICE_PLANE_BACKHAUL_SECRET_DIR.mkdir( parents=True, exist_ok=True, ) os.chown(DEVICE_PLANE_BACKHAUL_SECRET_DIR, 0, 0) DEVICE_PLANE_BACKHAUL_SECRET_DIR.chmod(0o700) if not DEVICE_PLANE_BACKHAUL_HOST_KEY_FILE.exists(): subprocess.run( [ str(DOCKER), "run", "--rm", "--entrypoint", "/usr/bin/ssh-keygen", "-v", f"{DEVICE_PLANE_BACKHAUL_SECRET_DIR}:/keys", DEVICE_PLANE_BACKHAUL_TARGET_IMAGE, "-q", "-t", "ed25519", "-N", "", "-C", "nodedc-device-plane-backhaul-target", "-f", "/keys/ssh_host_ed25519_key", ], check=True, ) host_public_source = Path( f"{DEVICE_PLANE_BACKHAUL_HOST_KEY_FILE}.pub" ) for path, expected_mode, max_size in ( (DEVICE_PLANE_BACKHAUL_HOST_KEY_FILE, 0o400, 2048), (host_public_source, 0o444, 1024), ): try: path_stat = path.lstat() except FileNotFoundError: die("Device Plane backhaul host key generation failed") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or path_stat.st_uid != 0 or path_stat.st_size > max_size ): die("Device Plane backhaul host key boundary mismatch") os.chown(path, 0, 0) path.chmod(expected_mode) expected_authorized_sha256 = install_device_plane_backhaul_authorized_key( enrollment ) DEVICE_PLANE_BACKHAUL_TRUST_DIR.mkdir(parents=True, exist_ok=True) os.chown(DEVICE_PLANE_BACKHAUL_TRUST_DIR, 0, 0) DEVICE_PLANE_BACKHAUL_TRUST_DIR.chmod(0o755) public_value = host_public_source.read_text(encoding="ascii") public_temporary = DEVICE_PLANE_BACKHAUL_HOST_PUBLIC_KEY_FILE.with_suffix( ".installing" ) if public_temporary.exists() or public_temporary.is_symlink(): die("Device Plane backhaul public trust staging path exists") public_temporary.write_text(public_value, encoding="ascii") os.chown(public_temporary, 0, 0) public_temporary.chmod(0o444) os.replace(public_temporary, DEVICE_PLANE_BACKHAUL_HOST_PUBLIC_KEY_FILE) if ( sha256_file(DEVICE_PLANE_BACKHAUL_AUTHORIZED_KEYS_FILE) != expected_authorized_sha256 or sha256_file(DEVICE_PLANE_BACKHAUL_HOST_PUBLIC_KEY_FILE) != sha256_file(host_public_source) ): die("Device Plane backhaul runtime trust verification failed") def validate_device_plane_manager_persistent_data_metadata(): try: data_stat = DEVICE_PLANE_MANAGER_DATA_DIR.lstat() except FileNotFoundError: die("Device Manager persistent data directory is missing") if ( stat.S_ISLNK(data_stat.st_mode) or not stat.S_ISDIR(data_stat.st_mode) or data_stat.st_uid != 1000 or data_stat.st_gid != 1000 or stat.S_IMODE(data_stat.st_mode) != 0o750 ): die("Device Manager persistent data directory boundary mismatch") return "uid-1000-gid-1000-mode-0750" def ensure_device_plane_manager_persistent_data(): data_parent = DEVICE_PLANE_MANAGER_DATA_DIR.parent parent_created = False try: parent_stat = data_parent.lstat() except FileNotFoundError: data_parent.mkdir(mode=0o755) parent_created = True parent_stat = data_parent.lstat() if stat.S_ISLNK(parent_stat.st_mode) or not stat.S_ISDIR( parent_stat.st_mode ): die("Device Manager persistent data parent is unsafe") if parent_created: os.chown(data_parent, 0, 0) data_parent.chmod(0o755) DEVICE_PLANE_MANAGER_DATA_DIR.mkdir(exist_ok=True) data_stat = DEVICE_PLANE_MANAGER_DATA_DIR.lstat() if stat.S_ISLNK(data_stat.st_mode) or not stat.S_ISDIR(data_stat.st_mode): die("Device Manager persistent data path is unsafe") os.chown(DEVICE_PLANE_MANAGER_DATA_DIR, 1000, 1000) DEVICE_PLANE_MANAGER_DATA_DIR.chmod(0o750) return validate_device_plane_manager_persistent_data_metadata() def stop_and_remove_compose_services(component, services): if not services: return compose_root = component_compose_root(component) subprocess.run( [*compose_base_cmd(component), "rm", "--stop", "--force", *services], cwd=str(compose_root), check=True, ) def create_gitea_runtime_secret(path, label): if path.exists() or path.is_symlink(): die(f"Gitea {label} secret already exists during fresh install") value = secrets.token_urlsafe(64) if not GITEA_SECRET_RE.fullmatch(value): die(f"Gitea {label} secret generation failed") temporary = path.with_name( f".{path.name}.{os.getpid()}.{time.time_ns()}.tmp" ) descriptor = None try: descriptor = os.open( str(temporary), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o400, ) os.write(descriptor, f"{value}\n".encode("ascii")) os.fsync(descriptor) os.fchown(descriptor, GITEA_RUNTIME_UID, GITEA_RUNTIME_GID) os.fchmod(descriptor, 0o400) os.close(descriptor) descriptor = None os.replace(temporary, path) fsync_directory(path.parent) finally: if descriptor is not None: os.close(descriptor) if temporary.exists(): temporary.unlink() def prepare_gitea_fresh_runtime(entries): if not is_gitea_fresh_install_slice("gitea", entries): die("Gitea runtime preparation called for unrelated artifact") compose = GITEA_ROOT / GITEA_COMPOSE_REL descriptor_path = GITEA_ROOT / GITEA_FRESH_INSTALL_DESCRIPTOR_REL if ( compose.is_symlink() or not compose.is_file() or sha256_file(compose) != GITEA_COMPOSE_SHA256 ): die("installed Gitea fresh-install Compose source mismatch") descriptor = read_strict_json( descriptor_path, "installed Gitea fresh-install descriptor", max_bytes=16 * 1024, ) if descriptor != expected_gitea_fresh_install_descriptor(): die("installed Gitea fresh-install descriptor mismatch") root_stat = GITEA_ROOT.lstat() if ( stat.S_ISLNK(root_stat.st_mode) or not stat.S_ISDIR(root_stat.st_mode) or root_stat.st_uid != 0 ): die("Gitea fresh-install root is unsafe") os.chown(GITEA_ROOT, 0, 0) GITEA_ROOT.chmod(0o755) for directory, uid, gid, mode, label in ( ( GITEA_DATA_DIR, GITEA_RUNTIME_UID, GITEA_RUNTIME_GID, 0o750, "data", ), ( GITEA_CONFIG_DIR, GITEA_RUNTIME_UID, GITEA_RUNTIME_GID, 0o750, "config", ), ( GITEA_SOCKET_DIR, GITEA_RUNTIME_UID, GITEA_NGINX_GID, 0o750, "socket", ), (GITEA_SECRET_DIR, 0, GITEA_RUNTIME_GID, 0o710, "secret"), ): if directory.exists() or directory.is_symlink(): die(f"Gitea fresh-install {label} directory already exists") directory.mkdir(parents=False, exist_ok=False) os.chown(directory, uid, gid) directory.chmod(mode) create_gitea_runtime_secret(GITEA_SECRET_KEY_FILE, "global") create_gitea_runtime_secret(GITEA_INTERNAL_TOKEN_FILE, "internal token") def prepare_component_runtime(component, entries=None): if is_gitea_fresh_install_slice(component, entries): prepare_gitea_fresh_runtime(entries) return if is_gitea_incident_salvage_slice(component, entries): die( "Gitea incident-salvage activation is frozen before candidate " "root creation until every disposition verifier is implemented" ) if is_engine_data_product_publish_grant_slice(component, entries): 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: die("installed Engine n8n transition descriptor is missing") if descriptor["action"] == "activate": ensure_engine_n8n_sealed_release(descriptor, stamp()) return if is_engine_node_intelligence_transition(component, entries): ensure_engine_node_intelligence_secret() descriptor = current_engine_node_intelligence_descriptor() if descriptor is None: die("installed Engine node-intelligence descriptor is missing") if descriptor["action"] == "activate": install_engine_node_intelligence_image(descriptor) return if touches_engine_credential_sink(component, entries): ensure_engine_credential_issuer_keypair() prepare_engine_credential_backend_runtime() return if component == "module-foundry": ensure_map_gateway_admin_secret() ensure_foundry_edp_managed_provisioner_keypair() ensure_root_owned_grant_directory( EXTERNAL_DATA_PLANE_READER_GRANTS_DIR, "external data plane reader grants", ) ensure_root_owned_grant_directory( FOUNDRY_BINDING_GRANTS_DIR, "foundry binding grants", ) return if component == "device-plane": ensure_platform_runtime_secret( DEVICE_PLANE_POSTGRES_PASSWORD_FILE, MAP_GATEWAY_SECRET_RE, "device plane PostgreSQL", ) ensure_platform_runtime_secret( DEVICE_PLANE_GATEWAY_CORE_TOKEN_FILE, MAP_GATEWAY_SECRET_RE, "device plane Gateway to Core", ) ensure_platform_runtime_secret( DEVICE_PLANE_IDENTIFIER_PEPPER_FILE, MAP_GATEWAY_SECRET_RE, "device plane identifier pepper", ) if is_device_plane_manager_control_plane_slice(component, entries): ensure_platform_runtime_secret( DEVICE_PLANE_MANAGEMENT_CORE_TOKEN_FILE, MAP_GATEWAY_SECRET_RE, "device plane management to Core", ) ensure_platform_runtime_secret( PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE, MAP_GATEWAY_SECRET_RE, "Device Core Hub handoff", ) if is_device_plane_manager_persistent_release_slice( component, entries, ): ensure_device_plane_manager_persistent_data() if ( is_device_plane_control_core_release_slice(component, entries) or is_device_plane_edge_core_channel_bootstrap_slice( component, entries, ) ): ensure_platform_runtime_secret( DEVICE_PLANE_MANAGEMENT_CORE_TOKEN_FILE, MAP_GATEWAY_SECRET_RE, "device plane management to Core", ) ensure_device_edge_channel_core_identity( allow_invalid_unexported_recovery=( not is_device_plane_control_core_release_slice( component, entries, ) and not is_device_plane_edge_core_channel_upgrade_slice( component, entries, ) ) ) if is_device_plane_backhaul_vps_enrollment_slice( component, entries, ): install_device_plane_backhaul_authorized_key( read_device_plane_backhaul_vps_enrollment_public_key() ) return if is_device_plane_backhaul_target_slice(component, entries): ensure_device_plane_backhaul_target_state() return if component == "proxy-contur": sync_map_egress_proxy_secret() return if component == "dc-amd-proxy": # The connector access value arrives only through the one-time pairing # endpoint. The artifact must never carry it, and the service user is # the only non-root identity that can write this directory. sync_map_egress_proxy_secret() try: runtime_stat = DC_AMD_PROXY_RUNTIME_DIR.lstat() except FileNotFoundError: DC_AMD_PROXY_RUNTIME_DIR.mkdir(parents=True, exist_ok=False) runtime_stat = DC_AMD_PROXY_RUNTIME_DIR.lstat() if stat.S_ISLNK(runtime_stat.st_mode) or not stat.S_ISDIR(runtime_stat.st_mode): die(f"dc-amd-proxy runtime directory is unsafe: {DC_AMD_PROXY_RUNTIME_DIR}") os.chown(DC_AMD_PROXY_RUNTIME_DIR, MAP_GATEWAY_RUNTIME_GID, MAP_GATEWAY_RUNTIME_GID) DC_AMD_PROXY_RUNTIME_DIR.chmod(0o700) connector_state = DC_AMD_PROXY_RUNTIME_DIR / "connector-access" if connector_state.exists() or connector_state.is_symlink(): connector_stat = connector_state.lstat() if ( stat.S_ISLNK(connector_stat.st_mode) or not stat.S_ISREG(connector_stat.st_mode) or connector_stat.st_uid != MAP_GATEWAY_RUNTIME_GID or connector_stat.st_mode & (stat.S_IRWXG | stat.S_IRWXO) or connector_stat.st_size > 512 ): die("dc-amd-proxy connector pair state is unsafe") return if component == "platform" and entries is not None: if is_platform_device_core_hub_trust_slice(component, entries): ensure_platform_runtime_secret( PLATFORM_DEVICE_CORE_INTERNAL_TOKEN_FILE, MAP_GATEWAY_SECRET_RE, "Device Core Hub handoff", ) touches_map_gateway = any(rel == "platform/services/map-gateway" or rel.startswith("platform/services/map-gateway/") for rel in entries) touches_external_data_plane = touches_external_data_plane_files(entries) if touches_map_gateway: # These NAS directories are the only canonical persistent stores # for all Map Page instances. Creation lives in the root-owned # runner, never in a user-supplied artifact or a Compose side effect. cache_root = Path("/volume1/docker/nodedc-platform/map-gateway") for cache_dir in (cache_root / "live-tile-cache", cache_root / "offline-snapshot"): cache_dir.mkdir(parents=True, exist_ok=True) os.chown(cache_dir, 1000, 1000) os.chmod(cache_dir, 0o750) ensure_map_gateway_admin_secret() sync_map_egress_proxy_secret() if touches_external_data_plane: ensure_external_data_plane_provisioner_secret() ensure_engine_edp_managed_provisioner_keypair() ensure_foundry_edp_managed_provisioner_keypair() ensure_root_owned_grant_directory( EXTERNAL_DATA_PLANE_READER_GRANTS_DIR, "external data plane reader grants", ) return if component in ("dc-cms", "dc-cms-site-nodedc"): (Path("/volume1/docker/dc-cms/sites") / "nodedc").mkdir(parents=True, exist_ok=True) return if component != "bim-viewer": return data_root = component_root(component) / "server" / "data" for rel in ("uploads", "projects", "shares", "models", "comments"): (data_root / rel).mkdir(parents=True, exist_ok=True) def run_component_runtime(component, entries, services): if component == "platform" and is_platform_provider_catalog_only(entries): return if component_artifact_only(component): return if is_device_plane_b2_discovery_rollback_recovery_slice( component, entries, ): return if is_device_plane_manager_reconciliation_slice(component, entries): return if is_device_plane_manager_v2_reconciliation_slice(component, entries): return if is_device_plane_foundation_recovery_slice(component, entries): return if is_device_plane_foundation_network_publication_slice( component, entries, ): prepare_component_runtime(component, entries) run_compose(component, services, entries) return run_build(component, entries) prepare_component_runtime(component, entries) if is_gitea_fresh_install_slice(component, entries): run_gitea_fresh_install_compose(services, entries) elif is_gitea_incident_salvage_slice(component, entries): die("Gitea incident-salvage runtime is not activation-ready") elif is_engine_node_intelligence_transition(component, entries): run_engine_node_intelligence_compose(services, entries) else: run_compose(component, services, entries) def retag_device_plane_control_core_image(image_id, label): image_id = inspect_optional_local_image( image_id, label, ) if image_id is None: die(f"{label} is unavailable") result = subprocess.run( [ str(DOCKER), "image", "tag", image_id, DEVICE_PLANE_CONTROL_CORE_IMAGE, ], check=False, capture_output=True, text=True, ) if result.returncode != 0: die(f"{label} retag failed") tagged = inspect_optional_local_image( DEVICE_PLANE_CONTROL_CORE_IMAGE, f"{label} recovered tag", ) if tagged != image_id: die(f"{label} recovered tag mismatch") def restore_device_plane_control_core_v3_preapply_image(): retag_device_plane_control_core_image( DEVICE_PLANE_CONTROL_CORE_V3_PREAPPLY_IMAGE_ID, "Device Control Core v3 exact pre-apply image", ) def run_device_plane_runtime_for_apply( entries, services, mark_runtime_started, backhaul_serve_before=None, ): if is_device_plane_control_core_v3_reconciliation_slice( "device-plane", entries, ): if tuple(services or ()) != ("device-control-core",): die("Device Control Core v3 reconciliation service set mismatch") prepare_component_runtime("device-plane", entries) mark_runtime_started() restore_device_plane_control_core_v3_preapply_image() run_compose("device-plane", services, entries) return if is_device_plane_b2_discovery_rollback_recovery_slice( "device-plane", entries, ) or is_device_plane_foundation_recovery_slice( "device-plane", entries ) or is_device_plane_manager_reconciliation_slice( "device-plane", entries ) or is_device_plane_manager_v2_reconciliation_slice( "device-plane", entries ): return # Build and runtime preparation are pre-runtime phases. A failure here # must restore source only; it must never trigger a predecessor rebuild. run_build("device-plane", entries) prepare_component_runtime("device-plane", entries) if services: mark_runtime_started() run_compose("device-plane", services, entries) if is_device_plane_backhaul_target_slice("device-plane", entries): if backhaul_serve_before is None: die("Device Plane backhaul Tailscale preflight state is missing") # Expose nothing to the tailnet until the loopback-only SSH target is # healthy. Tailscale Serve is the only ingress mutation and remains # private to the tailnet; Docker publishes no host port. healthcheck_compose_service( "device-plane", DEVICE_PLANE_BACKHAUL_TARGET_SERVICE, ) enable_device_plane_tailscale_serve(backhaul_serve_before) def healthcheck_url(check): headers = {} expected_json = {} if isinstance(check, dict): url = check.get("url") headers = check.get("headers") or {} expected_json = check.get("expected_json") or {} else: url = check last_error = None for _attempt in range(1, 61): try: request = urllib.request.Request(url, headers=headers) with NO_REDIRECT_OPENER.open(request, timeout=10) as response: if 200 <= response.status < 400: if expected_json: raw = response.read(64 * 1024 + 1) if len(raw) > 64 * 1024: last_error = "health response too large" continue try: payload = json.loads(raw.decode("utf-8")) except (UnicodeDecodeError, json.JSONDecodeError): last_error = "health response is not json" continue if not isinstance(payload, dict) or any( payload.get(key) != expected for key, expected in expected_json.items() ): last_error = "health response contract mismatch" continue return last_error = f"HTTP {response.status}" except urllib.error.HTTPError as exc: if 200 <= exc.code < 400 and not expected_json: return last_error = f"HTTP {exc.code}" except Exception as exc: last_error = str(exc) time.sleep(5) die(f"healthcheck failed for {url}: {last_error}") def assert_loopback_tcp_port_closed(port): try: connection = socket.create_connection(("127.0.0.1", port), timeout=3) except OSError: return connection.close() die(f"unexpected loopback TCP listener is open: {port}") def assert_loopback_tcp_port_open(port): try: connection = socket.create_connection(("127.0.0.1", port), timeout=3) except OSError as exc: die(f"expected loopback TCP listener is closed: {port}: {exc}") connection.close() def healthcheck_gitea_json(): url = "http://127.0.0.1:3000/api/healthz" last_error = None for attempt in range(1, 61): try: request = urllib.request.Request( url, headers={"Accept": "application/json"}, ) with NO_REDIRECT_OPENER.open(request, timeout=10) as response: raw = response.read(64 * 1024 + 1) if not (200 <= response.status < 300): last_error = f"HTTP {response.status}" elif len(raw) > 64 * 1024: last_error = "health response too large" else: try: payload = json.loads(raw.decode("utf-8")) except (UnicodeDecodeError, json.JSONDecodeError): last_error = "health response is not json" else: database = ( payload.get("checks", {}).get("database:ping") if isinstance(payload, dict) else None ) if ( isinstance(payload, dict) and payload.get("status") == "pass" and isinstance(database, list) and len(database) == 1 and isinstance(database[0], dict) and database[0].get("status") == "pass" ): return payload last_error = "health response contract mismatch" except urllib.error.HTTPError as exc: last_error = f"HTTP {exc.code}" except Exception as exc: last_error = str(exc) if attempt < 60: time.sleep(5) die(f"Gitea healthcheck failed for {url}: {last_error}") def validate_gitea_socket_boundary(): try: parent_stat = GITEA_SOCKET_DIR.lstat() socket_stat = GITEA_SOCKET_FILE.lstat() except FileNotFoundError: die("Gitea Unix socket boundary is missing") if ( GITEA_SOCKET_FILE.parent != GITEA_SOCKET_DIR or stat.S_ISLNK(parent_stat.st_mode) or not stat.S_ISDIR(parent_stat.st_mode) or parent_stat.st_uid != GITEA_RUNTIME_UID or parent_stat.st_gid != GITEA_NGINX_GID or stat.S_IMODE(parent_stat.st_mode) != 0o750 or not stat.S_ISSOCK(socket_stat.st_mode) or socket_stat.st_uid != GITEA_RUNTIME_UID or socket_stat.st_gid != GITEA_RUNTIME_GID or stat.S_IMODE(socket_stat.st_mode) != 0o666 ): die("Gitea Unix socket identity/metadata mismatch") return str(GITEA_SOCKET_FILE) def healthcheck_gitea_uds_json(): last_error = None for attempt in range(1, 61): connection = None response = None try: connection = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) connection.settimeout(10) connection.connect(str(GITEA_SOCKET_FILE)) connection.sendall( b"GET /api/healthz HTTP/1.1\r\n" b"Host: git.dcserve.ru\r\n" b"Accept: application/json\r\n" b"Connection: close\r\n\r\n" ) response = http.client.HTTPResponse(connection) response.begin() raw = response.read(64 * 1024 + 1) if not (200 <= response.status < 300): last_error = f"HTTP {response.status}" elif len(raw) > 64 * 1024: last_error = "health response too large" else: try: payload = json.loads(raw.decode("utf-8")) except (UnicodeDecodeError, json.JSONDecodeError): last_error = "health response is not json" else: database = ( payload.get("checks", {}).get("database:ping") if isinstance(payload, dict) else None ) if ( isinstance(payload, dict) and payload.get("status") == "pass" and isinstance(database, list) and len(database) == 1 and isinstance(database[0], dict) and database[0].get("status") == "pass" ): return payload last_error = "health response contract mismatch" except (OSError, http.client.HTTPException) as exc: last_error = str(exc) finally: if response is not None: response.close() if connection is not None: connection.close() if attempt < 60: time.sleep(5) die(f"Gitea UDS healthcheck failed: {last_error}") def external_data_plane_healthcheck(require_managed=True): expected_json = { "ok": True, "service": "nodedc-external-data-plane", "database": "ready", } 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" if 'foundryReaderBindingProvisioning: config.foundryProvisionerApiEnabled ? "digest+server-resolved-source" : "disabled"' in server_text: expected_json["foundryReaderBindingProvisioning"] = "digest+server-resolved-source" expected_json["foundryReaderBindingLifetime"] = "explicit-revoke" return { "url": "http://127.0.0.1:18106/healthz", "expected_json": expected_json, } def module_foundry_healthcheck(): expected_json = { "status": "ok", "service": "nodedc-module-foundry", } server_source = component_root("module-foundry") / "server/catalog-server.mjs" try: server_text = server_source.read_text(encoding="utf-8") except (OSError, UnicodeDecodeError): server_text = "" if "dataProductConsumerProvisioner" in server_text: expected_json["dataProductConsumerProvisioner"] = { "configured": True, "auth": "dedicated-ed25519-service-identity", "sourceScope": "external-data-plane-resolved", } return { "url": "http://172.22.0.222:9920/healthz", "expected_json": expected_json, } def component_healthchecks(component, entries=None, services=None): if is_platform_device_core_hub_trust_slice(component, entries): return ({ "url": "http://127.0.0.1:18080/healthz", "headers": {"Host": "hub.nodedc.ru"}, },) if is_platform_device_manager_public_route_slice(component, entries): return ({ "url": "http://127.0.0.1:18080/healthz", "headers": {"Host": "device.nodedc.ru"}, "expected_json": { "ok": True, "service": "nodedc-device-manager", "authRequired": True, "deviceCoreConfigured": True, }, },) if is_launcher_device_core_session_slice(component, entries): return ({ "url": "http://127.0.0.1:18080/healthz", "headers": {"Host": "hub.nodedc.ru"}, "expected_json": { "ok": True, "service": "nodedc-launcher-bff", "deviceCoreInternalAccessConfigured": True, }, },) if is_device_plane_control_core_migration_replay_recovery_slice( component, entries, ): return ({ "url": "http://127.0.0.1:18120/healthz", "expected_json": { "ok": True, "service": "nodedc-device-control-core", "database": "ready", "discoveryIngest": "enabled", "managementApi": "enabled", "commandTransport": "typed-service-ping-v1", }, },) if is_device_plane_control_core_migration_replay_checkpoint_recovery_slice( component, entries, ): return ({ "url": "http://127.0.0.1:18120/healthz", "expected_json": { "ok": True, "service": "nodedc-device-control-core", "database": "ready", "discoveryIngest": "enabled", "managementApi": "enabled", "commandTransport": "typed-service-ping-v1", }, },) if is_device_plane_manager_control_plane_slice(component, entries): command_transport = ( "typed-service-ping-v1" if is_device_plane_manager_only_release_slice(component, entries) else "disabled" ) checks = ({ "url": "http://127.0.0.1:18120/healthz", "expected_json": { "ok": True, "service": "nodedc-device-control-core", "database": "ready", "discoveryIngest": "enabled", "managementApi": "enabled", "commandTransport": command_transport, }, },) if is_device_plane_manager_only_release_slice(component, entries): checks += ({ "url": "http://127.0.0.1:18080/healthz", "headers": {"Host": "device.nodedc.ru"}, "expected_json": { "ok": True, "service": "nodedc-device-manager", "authRequired": True, "deviceCoreConfigured": True, }, },) return checks if ( is_device_plane_control_core_release_slice(component, entries) or is_device_plane_edge_core_channel_bootstrap_slice( component, entries, ) ): return ({ "url": "http://127.0.0.1:18120/healthz", "expected_json": { "ok": True, "service": "nodedc-device-control-core", "database": "ready", "discoveryIngest": "enabled", "managementApi": "enabled", "commandTransport": ( "typed-service-ping-v1" if ( is_device_plane_control_core_release_v2_slice( component, entries, ) or is_device_plane_control_core_release_v3_slice( component, entries, ) or is_device_plane_control_core_release_v4_slice( component, entries, ) ) else "disabled" ), }, },) if is_device_plane_b2_discovery_rollback_recovery_slice( component, entries, ): return ( { "url": "http://127.0.0.1:18120/healthz", "expected_json": { "ok": True, "service": "nodedc-device-control-core", "database": "ready", "discoveryIngest": "disabled", "commandTransport": "disabled", }, }, { "url": "http://127.0.0.1:18121/healthz", "expected_json": { "ok": True, "service": "nodedc-device-gateway", "tcpListener": "disabled", "publicIngress": "disabled", "commandTransport": "disabled", }, }, ) if is_device_plane_postgres_bootstrap_slice(component, entries): return () if is_device_plane_foundation_recovery_slice(component, entries): return ( { "url": "http://127.0.0.1:18120/healthz", "expected_json": { "ok": True, "service": "nodedc-device-control-core", "database": "ready", "discoveryIngest": "disabled", "commandTransport": "disabled", }, }, { "url": "http://127.0.0.1:18121/healthz", "expected_json": { "ok": True, "service": "nodedc-device-gateway", "tcpListener": "disabled", "publicIngress": "disabled", "commandTransport": "disabled", }, }, ) if is_engine_n8n_transition(component, entries): # The transition does not restart the Engine UI/backend generation. # Its own acceptance below verifies n8n readiness, image, mount, logs, # live node export and the pinned Engine MCP catalogs. return () if is_engine_node_intelligence_transition(component, entries): # The exact sidecar/backend topology and live MCP capability calls are # accepted separately below. Keep the generic HTTP barrier scoped to # the backend generation recreated by this transition. return ("http://127.0.0.1:3001/health",) if component == "platform" and entries is not None and is_platform_provider_catalog_only(entries): return () if component == "module-foundry": return (module_foundry_healthcheck(),) if component == "device-plane": if is_device_plane_b2_discovery_ingress_slice(component, entries): return ( { "url": "http://127.0.0.1:18120/healthz", "expected_json": { "ok": True, "service": "nodedc-device-control-core", "database": "ready", "discoveryIngest": "enabled", "commandTransport": "disabled", }, }, { "url": "http://127.0.0.1:18121/healthz", "expected_json": { "ok": True, "service": "nodedc-device-gateway", "framing": "verified-read-only", "tcpListener": "discovery-only", "publicIngress": "disabled", "commandTransport": "disabled", }, }, ) selected_services = ( tuple(services) if services is not None else component_services(component, entries) ) checks = [] if "device-control-core" in selected_services: checks.append({ "url": "http://127.0.0.1:18120/healthz", "expected_json": { "ok": True, "service": "nodedc-device-control-core", "database": "ready", "discoveryIngest": "disabled", "commandTransport": "disabled", }, }) if "device-gateway" in selected_services: checks.append({ "url": "http://127.0.0.1:18121/healthz", "expected_json": { "ok": True, "service": "nodedc-device-gateway", "tcpListener": "disabled", "publicIngress": "disabled", "commandTransport": "disabled", }, }) return tuple(checks) if ( ( is_engine_data_product_publish_grant_slice(component, entries) or is_engine_composite_provider_v4_slice(component, entries) or is_engine_provider_rotating_slot_slice(component, entries) or is_engine_provider_authority_diagnostics_slice(component, entries) or is_engine_depttrans_zone_authority_v1_slice(component, entries) or is_engine_provider_target_host_policy_slice(component, entries) or is_engine_mcp_execution_profile_decoder_slice(component, entries) or is_engine_mcp_telemetry_catalog_slice(component, entries) or is_engine_mcp_execution_plan_materialization_slice( component, entries, ) or is_engine_mcp_execution_plan_telemetry_runtime_slice( component, entries, ) or is_engine_mcp_execution_plan_module_ownership_slice( component, entries, ) or is_engine_mcp_normalized_identity_search_slice( component, entries, ) or is_engine_mcp_l1_credential_reuse_slice(component, entries) or is_engine_mcp_l1_credential_provenance_slice( component, entries, ) or is_engine_mcp_execution_plan_sandbox_runtime_slice( component, entries, ) or is_engine_mcp_gelios_items_envelope_slice(component, entries) or is_engine_mcp_registered_execution_profiles_slice( component, entries, ) or is_engine_mcp_gelios_units_items_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) or is_engine_mcp_autonomy_provider_v5_slice(component, entries) or is_engine_provider_security_catalog_slice(component, entries) ) and services is not None ): selected_services = set(services) checks = [] if "app" in selected_services: checks.append("http://127.0.0.1:8080/") if "nodedc-backend" in selected_services: checks.append("http://127.0.0.1:3001/health") if is_engine_agent_full_grant_migration_slice(component, entries): checks.append("http://127.0.0.1:3001/internal/engine-credential-sink/v1/health") return tuple(checks) if component == "platform" and entries is not None: selected_services = tuple(services) if services is not None else component_services(component, entries) # A narrow EDP application deploy must prove only the service it # recreated. Unrelated Platform routes retain independent lifecycle and # must not turn a valid EDP deploy or rollback into a false failure. if selected_services == (EXTERNAL_DATA_PLANE_SERVICE,): return (external_data_plane_healthcheck(),) checks = list(COMPONENTS[component].get("healthchecks", ())) if touches_engine_credential_sink(component, entries): checks.append("http://127.0.0.1:3001/internal/engine-credential-sink/v1/health") if component == "platform" and entries is not None: touches_compose = any(rel == "platform/docker-compose.platform-http.yml" for rel in entries) touches_ai_workspace_assistant = any(rel == "platform/ai-workspace-assistant" or rel.startswith("platform/ai-workspace-assistant/") for rel in entries) touches_ontology = any(rel == "platform/ontology-core" or rel.startswith("platform/ontology-core/") for rel in entries) touches_gelios = any(rel == "platform/gelios-gateway" or rel.startswith("platform/gelios-gateway/") for rel in entries) touches_map_gateway = any(rel == "platform/services/map-gateway" or rel.startswith("platform/services/map-gateway/") for rel in entries) touches_external_data_plane = touches_external_data_plane_files(entries) map_gateway_only_compose = touches_compose and touches_map_gateway and not any((touches_ai_workspace_assistant, touches_ontology, touches_gelios, touches_external_data_plane)) healthcheck_all_for_compose = touches_compose and not map_gateway_only_compose if healthcheck_all_for_compose or touches_ai_workspace_assistant: checks.append("http://127.0.0.1:18082/healthz") if healthcheck_all_for_compose or touches_ontology: checks.append("http://127.0.0.1:18104/healthz") if healthcheck_all_for_compose or touches_gelios: checks.append("http://127.0.0.1:18105/healthz") if touches_map_gateway: checks.append({"url": "http://127.0.0.1:18103/healthz", "headers": {"x-nodedc-user-id": "deploy-healthcheck"}}) if touches_external_data_plane: checks.append(external_data_plane_healthcheck()) return tuple(checks) def healthcheck_container(container_name): last_status = "unknown" for _attempt in range(1, 61): result = subprocess.run( [str(DOCKER), "inspect", "--format", "{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}", container_name], check=False, capture_output=True, text=True, ) status_text = result.stdout.strip().lower() last_status = status_text or result.stderr.strip() or f"inspect-exit-{result.returncode}" if status_text in ("healthy", "running"): return if status_text in ("unhealthy", "exited", "dead"): break time.sleep(5) die(f"container healthcheck failed for {container_name}: {last_status}") def healthcheck_container_with_grace(container_name): """Wait through bounded transient unhealthy/exited states. Device Manager activation and its baseline rollback both recreate a Core generation while Docker health probes and restart policy converge. The ordinary helper deliberately fails on the first terminal-looking state; this exact transition instead requires a healthy result within the same bounded five-minute window. """ last_status = "unknown" for attempt in range(1, 61): result = subprocess.run( [ str(DOCKER), "inspect", "--format", "{{if .State.Health}}{{.State.Health.Status}}" "{{else}}{{.State.Status}}{{end}}", container_name, ], check=False, capture_output=True, text=True, ) status_text = result.stdout.strip().lower() last_status = ( status_text or result.stderr.strip() or f"inspect-exit-{result.returncode}" ) if status_text in ("healthy", "running"): return if attempt < 60: time.sleep(5) die( "container healthcheck grace exhausted for " f"{container_name}: {last_status}" ) def compose_service_container_id(component, service): result = subprocess.run( [*compose_base_cmd(component), "ps", "-q", service], cwd=str(component_compose_root(component)), check=False, capture_output=True, text=True, ) container_ids = [line.strip() for line in result.stdout.splitlines() if line.strip()] if ( result.returncode != 0 or len(container_ids) != 1 or not re.fullmatch(r"[a-f0-9]{12,64}", container_ids[0]) ): die(f"Compose service container lookup failed: {service}") return container_ids[0] def healthcheck_compose_service(component, service): healthcheck_container(compose_service_container_id(component, service)) def healthcheck_compose_service_with_grace(component, service): healthcheck_container_with_grace( compose_service_container_id(component, service) ) def container_environment(container, label): environment = {} for raw in (container.get("Config") or {}).get("Env") or []: if not isinstance(raw, str) or "=" not in raw: die(f"{label} environment inventory is invalid") key, value = raw.split("=", 1) if not key or key in environment: die(f"{label} environment inventory is invalid") environment[key] = value return environment def validate_gitea_runtime_secret_metadata(): try: secret_dir_stat = GITEA_SECRET_DIR.lstat() except FileNotFoundError: die("Gitea runtime secret directory is missing") if ( stat.S_ISLNK(secret_dir_stat.st_mode) or not stat.S_ISDIR(secret_dir_stat.st_mode) or secret_dir_stat.st_uid != 0 or secret_dir_stat.st_gid != GITEA_RUNTIME_GID or stat.S_IMODE(secret_dir_stat.st_mode) != 0o710 ): die("Gitea runtime secret directory metadata mismatch") values = [] for path, label in ( (GITEA_SECRET_KEY_FILE, "global"), (GITEA_INTERNAL_TOKEN_FILE, "internal token"), ): try: path_stat = path.lstat() value = path.read_text(encoding="ascii").strip() except (FileNotFoundError, UnicodeDecodeError): die(f"Gitea {label} secret is unreadable") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISREG(path_stat.st_mode) or path_stat.st_uid != GITEA_RUNTIME_UID or path_stat.st_gid != GITEA_RUNTIME_GID or stat.S_IMODE(path_stat.st_mode) != 0o400 or path_stat.st_size > 512 or not GITEA_SECRET_RE.fullmatch(value) ): die(f"Gitea {label} secret metadata mismatch") values.append(value) if len(set(values)) != len(values): die("Gitea runtime secrets must be distinct") return tuple(values) def validate_gitea_runtime_directory_metadata(): for path, uid, gid, mode, label in ( (GITEA_ROOT, 0, 0, 0o755, "root"), (GITEA_DATA_DIR, GITEA_RUNTIME_UID, GITEA_RUNTIME_GID, 0o750, "data"), ( GITEA_CONFIG_DIR, GITEA_RUNTIME_UID, GITEA_RUNTIME_GID, 0o750, "config", ), ( GITEA_SOCKET_DIR, GITEA_RUNTIME_UID, GITEA_NGINX_GID, 0o750, "socket", ), ): try: path_stat = path.lstat() except FileNotFoundError: die(f"Gitea runtime {label} directory is missing") if ( stat.S_ISLNK(path_stat.st_mode) or not stat.S_ISDIR(path_stat.st_mode) or path_stat.st_uid != uid or path_stat.st_gid != gid or stat.S_IMODE(path_stat.st_mode) != mode ): die(f"Gitea runtime {label} directory metadata mismatch") def parse_gitea_app_ini_explicit(raw): allowed_root_keys = {"APP_NAME", "RUN_USER", "RUN_MODE", "WORK_PATH"} root_values = {} sections = {} section_names = {} current = None for line_number, raw_line in enumerate(raw.splitlines(), start=1): line = raw_line.strip() if not line or line.startswith(("#", ";")): continue section_match = re.fullmatch(r"\[([A-Za-z0-9_.-]+)\]", line) if section_match: section = section_match.group(1) normalized = section.casefold() if normalized == "default" or normalized in section_names: die("Gitea installed app.ini contains duplicate/unsafe section") section_names[normalized] = section sections[section] = {} current = section continue setting_match = re.fullmatch(r"([A-Za-z0-9_.-]+)\s*=\s*(.*)", line) if setting_match is None: die(f"Gitea installed app.ini syntax mismatch at line {line_number}") key, value = setting_match.groups() destination = root_values if current is None else sections[current] normalized_key = key.casefold() if any(existing.casefold() == normalized_key for existing in destination): die("Gitea installed app.ini contains duplicate option") if current is None and key not in allowed_root_keys: die("Gitea installed app.ini contains unsafe root-level option") destination[key] = value.strip() if not sections: die("Gitea installed app.ini has no explicit sections") return root_values, sections def validate_gitea_installed_config(secret_values): app_ini = GITEA_CONFIG_DIR / "app.ini" try: app_ini_stat = app_ini.lstat() raw = app_ini.read_text(encoding="utf-8") except (FileNotFoundError, UnicodeDecodeError): die("Gitea installed app.ini is unreadable") if ( stat.S_ISLNK(app_ini_stat.st_mode) or not stat.S_ISREG(app_ini_stat.st_mode) or app_ini_stat.st_uid != GITEA_RUNTIME_UID or app_ini_stat.st_mode & (stat.S_IWGRP | stat.S_IWOTH) or app_ini_stat.st_size < 32 or app_ini_stat.st_size > 1024 * 1024 ): die("Gitea installed app.ini metadata mismatch") _root_values, sections = parse_gitea_app_ini_explicit(raw) expected = { ("database", "DB_TYPE"): "sqlite3", ("database", "PATH"): "/var/lib/gitea/data/gitea.db", ("server", "ROOT_URL"): "https://git.dcserve.ru/", ("server", "PROTOCOL"): "http+unix", ("server", "HTTP_ADDR"): "/run/gitea/gitea.sock", ("server", "UNIX_SOCKET_PERMISSION"): "0666", ("server", "LOCAL_ROOT_URL"): "http://unix/", ("server", "DISABLE_SSH"): "true", ("server", "START_SSH_SERVER"): "false", ("server", "LFS_START_SERVER"): "false", ("server", "LFS_ALLOW_PURE_SSH"): "false", ("security", "INSTALL_LOCK"): "true", ("security", "SECRET_KEY_URI"): "file:/run/secrets/gitea_secret_key", ("security", "INTERNAL_TOKEN_URI"): ( "file:/run/secrets/gitea_internal_token" ), ("security", "REVERSE_PROXY_LIMIT"): "1", ("security", "REVERSE_PROXY_TRUSTED_PROXIES"): ( "127.0.0.0/8,::1/128" ), ("security", "DISABLE_GIT_HOOKS"): "true", ("security", "DISABLE_WEBHOOKS"): "true", ("security", "IMPORT_LOCAL_PATHS"): "false", ("security", "TWO_FACTOR_AUTH"): "enforced", ("security", "DISABLE_QUERY_AUTH_TOKEN"): "true", ("security", "ALLOWED_HOST_LIST"): "loopback", ("service", "DISABLE_REGISTRATION"): "true", ("service", "REQUIRE_SIGNIN_VIEW"): "true", ("service", "DEFAULT_USER_IS_RESTRICTED"): "true", ("service", "ENABLE_REVERSE_PROXY_AUTHENTICATION"): "false", ("service", "ENABLE_REVERSE_PROXY_AUTHENTICATION_API"): "false", ("service", "ENABLE_REVERSE_PROXY_AUTO_REGISTRATION"): "false", ("service", "ENABLE_BASIC_AUTHENTICATION"): "false", ("admin", "DISABLE_REGULAR_ORG_CREATION"): "true", ("admin", "USER_DISABLED_FEATURES"): ( "deletion,manage_ssh_keys,manage_gpg_keys,change_username" ), ("repository", "FORCE_PRIVATE"): "true", ("repository", "USER_MAX_CREATION_LIMIT"): "0", ("repository", "ORG_MAX_CREATION_LIMIT"): "0", ("repository", "DISABLE_MIGRATIONS"): "true", ("actions", "ENABLED"): "false", ("packages", "ENABLED"): "false", ("oauth2", "ENABLED"): "false", ("openid", "ENABLE_OPENID_SIGNIN"): "false", ("openid", "ENABLE_OPENID_SIGNUP"): "false", ("federation", "ENABLED"): "false", } for (section, key), value in expected.items(): if sections.get(section, {}).get(key) != value: die(f"Gitea installed config mismatch: {section}.{key}") for section, key in ( ("security", "SECRET_KEY"), ("security", "INTERNAL_TOKEN"), ("server", "LFS_JWT_SECRET"), ("server", "LFS_JWT_SECRET_URI"), ): if (sections.get(section, {}).get(key, "") or "").strip(): die(f"Gitea installed config contains plaintext secret: {section}.{key}") if any(value in raw for value in secret_values): die("Gitea installed config contains runner-managed secret bytes") def validate_gitea_fresh_sqlite(): database = GITEA_DATA_DIR / "data" / "gitea.db" try: database_stat = database.lstat() except FileNotFoundError: die("Gitea fresh SQLite database is missing") if ( stat.S_ISLNK(database_stat.st_mode) or not stat.S_ISREG(database_stat.st_mode) or database_stat.st_uid != GITEA_RUNTIME_UID or database_stat.st_mode & (stat.S_IWGRP | stat.S_IWOTH) or database_stat.st_size < 4096 ): die("Gitea fresh SQLite database metadata mismatch") connection = None try: connection = sqlite3.connect( f"file:{database}?mode=ro", uri=True, timeout=10, ) connection.execute("PRAGMA query_only = ON") integrity = connection.execute("PRAGMA quick_check(1)").fetchone() tables = { row[0] for row in connection.execute( "SELECT name FROM sqlite_master WHERE type = 'table'" ) } if integrity != ("ok",) or not {"user", "repository"}.issubset(tables): die("Gitea fresh SQLite schema/integrity mismatch") counts = { table: connection.execute( f'SELECT COUNT(*) FROM "{table}"' ).fetchone()[0] for table in ("user", "repository") } except sqlite3.Error as exc: die(f"Gitea fresh SQLite acceptance failed: {exc}") finally: if connection is not None: connection.close() if counts != {"user": 0, "repository": 0}: die("Gitea fresh SQLite unexpectedly contains users or repositories") return counts def accept_gitea_fresh_install_runtime(): container_id = compose_service_container_id("gitea", GITEA_SERVICE) healthcheck_container(container_id) validate_gitea_socket_boundary() healthcheck_gitea_uds_json() nginx_bridge = validate_gitea_nginx_bridge_prerequisite() healthcheck_gitea_json() validate_gitea_no_docker_port_publications() assert_loopback_tcp_port_closed(GITEA_DISABLED_SSH_HOST_PORT) legacy_container = validate_legacy_gitea_container_isolation() image_id = inspect_gitea_local_image() container = inspect_container(container_id) config = container.get("Config") or {} host = container.get("HostConfig") or {} labels = config.get("Labels") or {} if ( container.get("Image") != image_id or config.get("Image") != GITEA_IMAGE or config.get("User") != "1000:1000" or labels.get("com.docker.compose.project") != GITEA_COMPOSE_PROJECT or labels.get("com.docker.compose.service") != GITEA_SERVICE ): die("Gitea runtime identity mismatch") environment = container_environment(container, "Gitea runtime") for key, expected in GITEA_EXPECTED_ENVIRONMENT.items(): if environment.get(key) != expected: die(f"Gitea runtime environment mismatch: {key}") for forbidden_key in ( "GITEA__security__SECRET_KEY", "GITEA__security__INTERNAL_TOKEN", "GITEA__server__LFS_JWT_SECRET", "GITEA__server__LFS_JWT_SECRET_URI", "GITEA__security__SECRET_KEY__FILE", "GITEA__security__INTERNAL_TOKEN__FILE", "GITEA__server__LFS_JWT_SECRET__FILE", ): if forbidden_key in environment: die(f"Gitea runtime plaintext secret environment rejected: {forbidden_key}") if ( host.get("Privileged") is not False or host.get("ReadonlyRootfs") is not True or host.get("NetworkMode") != "none" or (host.get("RestartPolicy") or {}).get("Name") != "unless-stopped" or host.get("PidsLimit") != 512 or host.get("Init") is not True or config.get("StopTimeout") != 30 or "ALL" not in set(host.get("CapDrop") or ()) or not any( value in ("no-new-privileges", "no-new-privileges:true") for value in (host.get("SecurityOpt") or ()) ) ): die("Gitea runtime hardening mismatch") if host.get("LogConfig") != { "Type": "json-file", "Config": {"max-file": "3", "max-size": "10m"}, }: die("Gitea runtime bounded logging mismatch") if (host.get("PortBindings") or {}) != {}: die("Gitea runtime port publication mismatch") tmpfs = host.get("Tmpfs") or {} tmpfs_options = tmpfs.get("/tmp") or "" if not all( option in tmpfs_options for option in ("rw", "noexec", "nosuid", "nodev", "size=268435456") ): die("Gitea runtime tmpfs boundary mismatch") expected_bind_mounts = { (str(GITEA_DATA_DIR), "/var/lib/gitea", True), (str(GITEA_CONFIG_DIR), "/etc/gitea", True), (str(GITEA_SECRET_KEY_FILE), "/run/secrets/gitea_secret_key", False), ( str(GITEA_INTERNAL_TOKEN_FILE), "/run/secrets/gitea_internal_token", False, ), (str(GITEA_SOCKET_DIR), "/run/gitea", True), } bind_mounts = { (mount.get("Source"), mount.get("Destination"), mount.get("RW")) for mount in (container.get("Mounts") or ()) if mount.get("Type") == "bind" } if bind_mounts != expected_bind_mounts: die("Gitea runtime mount boundary mismatch") network_settings = container.get("NetworkSettings") or {} attached_networks = network_settings.get("Networks") or {} if set(attached_networks) != {"none"}: die("Gitea runtime must not attach to a Docker network") none_network = attached_networks["none"] builtin_none = inspect_gitea_builtin_none_network() if ( not isinstance(none_network, dict) or none_network.get("NetworkID") != builtin_none["Id"] or not re.fullmatch( r"[a-f0-9]{64}", str(none_network.get("EndpointID") or ""), ) or any( none_network.get(key) not in (None, "", 0, [], {}) for key in ( "IPAMConfig", "Links", "Aliases", "DriverOpts", "GwPriority", "Gateway", "IPAddress", "MacAddress", "IPPrefixLen", "IPv6Gateway", "GlobalIPv6Address", "GlobalIPv6PrefixLen", "DNSNames", ) ) ): die("Gitea Docker none-network endpoint is not isolated") full_container_id = container.get("Id") none_container = (builtin_none.get("Containers") or {}).get(full_container_id) if ( not isinstance(full_container_id, str) or not re.fullmatch(r"[a-f0-9]{64}", full_container_id) or not isinstance(none_container, dict) or none_container.get("EndpointID") != none_network.get("EndpointID") or any( none_container.get(key) not in (None, "") for key in ("MacAddress", "IPv4Address", "IPv6Address") ) ): die("Gitea built-in none network membership mismatch") validate_gitea_legacy_candidate_network_absent() validate_gitea_runtime_directory_metadata() secret_values = validate_gitea_runtime_secret_metadata() validate_gitea_installed_config(secret_values) database_counts = validate_gitea_fresh_sqlite() return { "container_id": container_id, "image_id": image_id, "transport": str(GITEA_SOCKET_FILE), "network_mode": "none", "nginx_bridge": nginx_bridge, "database": "fresh-sqlite", "database_counts": database_counts, "legacy_container": legacy_container, } def validate_engine_node_intelligence_secret_mount(container, label): matches = [ mount for mount in (container.get("Mounts") or []) if isinstance(mount, dict) and mount.get("Destination") == ENGINE_NODE_INTELLIGENCE_SECRET_CONTAINER_PATH ] if len(matches) != 1: die(f"{label} secret mount inventory mismatch") mount = matches[0] if ( mount.get("Type") != "bind" or mount.get("Source") != str(ENGINE_NODE_INTELLIGENCE_SECRET_FILE) or mount.get("RW") is not False ): die(f"{label} secret mount boundary mismatch") def assert_engine_node_intelligence_container_absent(): result = subprocess.run( [ str(DOCKER), "container", "ls", "--all", "--filter", f"label=com.docker.compose.service={ENGINE_NODE_INTELLIGENCE_SERVICE}", "--format", "{{.ID}}", ], check=False, capture_output=True, text=True, ) if result.returncode != 0 or result.stdout.strip(): die("Engine node-intelligence inactive runtime still has a sidecar container") def engine_node_intelligence_live_probe_script(): return """ const {createNodeIntelligenceMcpClient}=await import('file:///app/server/nodeIntelligence/upstreamMcpClient.js'); const client=createNodeIntelligenceMcpClient(); const status=await client.status(); if(!status.ok||status.status!=='ready'||status.authSource!=='file'||status.server?.version!=='2.33.2'||!Object.values(status.capabilities||{}).every(Boolean))throw new Error('status'); const guidance=await client.callTool('get_node',{nodeType:'nodes-base.httpRequest',detail:'minimal'}); if(!guidance||typeof guidance!=='object')throw new Error('guidance'); const nodeValidation=await client.callTool('validate_node',{nodeType:'nodes-base.httpRequest',config:{url:'https://example.com'},mode:'minimal',profile:'ai-friendly'}); if(!nodeValidation||typeof nodeValidation!=='object'||nodeValidation.valid!==true)throw new Error('node-validation'); const workflowValidation=await client.callTool('validate_workflow',{workflow:{name:'NDC acceptance',nodes:[{id:'probe-1',name:'Start',type:'nodes-base.manualTrigger',typeVersion:1,position:[0,0],parameters:{}},{id:'probe-2',name:'Output',type:'nodes-base.set',typeVersion:3.4,position:[240,0],parameters:{}}],connections:{Start:{main:[[{node:'Output',type:'main',index:0}]]}},settings:{}},options:{validateNodes:true,validateConnections:true,validateExpressions:true,profile:'runtime'}}); if(!workflowValidation||typeof workflowValidation!=='object'||workflowValidation.valid!==true)throw new Error('workflow-validation'); process.stdout.write('node-intelligence-live:2.33.2:guidance+node+workflow'); """.strip() def accept_engine_node_intelligence_runtime(descriptor): if descriptor is None or descriptor.get("action") != "activate": die("Engine node-intelligence activation descriptor is required") validate_installed_engine_node_intelligence_source(descriptor) ensure_engine_node_intelligence_secret() image = inspect_engine_node_intelligence_image(descriptor) sidecar_id = compose_service_container_id( "engine", ENGINE_NODE_INTELLIGENCE_SERVICE, ) backend_id = compose_service_container_id("engine", "nodedc-backend") healthcheck_container(sidecar_id) healthcheck_container(backend_id) sidecar = inspect_container(sidecar_id) backend = inspect_container(backend_id) sidecar_config = sidecar.get("Config") or {} sidecar_host = sidecar.get("HostConfig") or {} sidecar_environment = container_environment( sidecar, "Engine node-intelligence sidecar", ) if ( sidecar.get("Image") != image["Id"] or sidecar_config.get("Image") != ENGINE_NODE_INTELLIGENCE_IMAGE or sidecar_config.get("User") != f"{ENGINE_NODE_INTELLIGENCE_RUNTIME_UID}:{ENGINE_NODE_INTELLIGENCE_RUNTIME_GID}" or sidecar_host.get("ReadonlyRootfs") is not True or sidecar_host.get("PortBindings") not in (None, {}) or set(sidecar_host.get("CapDrop") or []) != {"ALL"} or "no-new-privileges:true" not in (sidecar_host.get("SecurityOpt") or []) or sidecar_environment.get("AUTH_TOKEN_FILE") != ENGINE_NODE_INTELLIGENCE_SECRET_CONTAINER_PATH or sidecar_environment.get("NODE_DB_PATH") != "/app/data/nodes.db" or sidecar_environment.get("REBUILD_ON_START") != "false" or sidecar_environment.get("N8N_MCP_TELEMETRY_DISABLED") != "true" ): die("Engine node-intelligence sidecar runtime contract mismatch") if any( key in sidecar_environment for key in ("AUTH_TOKEN", "N8N_API_URL", "N8N_API_KEY") ): die("Engine node-intelligence sidecar runtime authority leak") validate_engine_node_intelligence_secret_mount( sidecar, "Engine node-intelligence sidecar", ) backend_environment = container_environment(backend, "Engine backend") if ( backend_environment.get("ENGINE_NODE_INTELLIGENCE_MCP_URL") != f"http://{ENGINE_NODE_INTELLIGENCE_SERVICE}:3000/mcp" or backend_environment.get("ENGINE_NODE_INTELLIGENCE_AUTH_TOKEN_FILE") != ENGINE_NODE_INTELLIGENCE_SECRET_CONTAINER_PATH or backend_environment.get("ENGINE_NODE_INTELLIGENCE_ASSET_ID") != f"nodedc-node-intelligence@{ENGINE_NODE_INTELLIGENCE_RELEASE_ID.replace('-', '+', 1)}" or "ENGINE_NODE_INTELLIGENCE_AUTH_TOKEN" in backend_environment ): die("Engine backend node-intelligence boundary mismatch") validate_engine_node_intelligence_secret_mount( backend, "Engine backend node-intelligence", ) result = run_engine_backend_probe( ( "node", "--input-type=module", "-e", engine_node_intelligence_live_probe_script(), ), "node-intelligence live capability", container_id=backend_id, ) expected = "node-intelligence-live:2.33.2:guidance+node+workflow" if result != expected: die("Engine node-intelligence live capability acceptance mismatch") backend_runtime = preflight_engine_credential_backend_runtime() if backend_runtime["mode"] != "verified-derived-retry": die("Engine node-intelligence backend immutable runtime acceptance failed") return result def accept_engine_agent_full_grant_migration_runtime(): expected_scopes = ( "engine:l2:context:read", "engine:l2:graph:read", "engine:l2:node-schema:read", "engine:l2:graph:plan", "engine:l2:graph:write", "engine:l2:data-product-publish-grant:plan", "engine:l2:data-product-publish-grant:write", "engine:l2:validate", "engine:l2:runtime:read", "engine:l2:deploy-run", "engine:l2:execution:stop", ) script = """ import crypto from 'node:crypto'; import fs from 'node:fs'; const file='/app/server/engineAgents/store.js'; const digest=crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex'); const module=await import('file:///app/server/engineAgents/store.js'); const expected=JSON.parse(process.argv[1]); if(digest!==process.argv[2]||JSON.stringify(module.ENGINE_AGENT_SCOPES)!==JSON.stringify(expected)||module.ENGINE_AGENT_FULL_DEVELOPER_PROFILE!=='full-developer')process.exit(2); process.stdout.write('store-v2-full-developer:'+digest); """.strip() result = run_engine_backend_probe( ( "node", "--input-type=module", "-e", script, json.dumps(expected_scopes, separators=(",", ":")), ENGINE_AGENT_FULL_GRANT_MIGRATION_TARGET_SHA256, ), "agent full grant migration", container_id=engine_backend_container_id(), ) expected = f"store-v2-full-developer:{ENGINE_AGENT_FULL_GRANT_MIGRATION_TARGET_SHA256}" if result != expected: die("Engine agent full grant migration runtime acceptance mismatch") return result def accept_engine_l2_closed_loop_runtime(): root = component_root("engine") actual = collect_exact_files( root, ENGINE_L2_CLOSED_LOOP_ARTIFACT_ENTRIES, "Engine L2 closed-loop installed target", ) if actual != ENGINE_L2_CLOSED_LOOP_TARGET_SHA256: die("Engine L2 closed-loop installed target digest mismatch") nginx_actual = collect_exact_files( root, ("nginx-html/index.html", "nginx-html/assets"), "Engine L2 closed-loop nginx target", ) nginx_expected = { rel.replace("nodedc-source/dist/", "nginx-html/", 1): digest for rel, digest in ENGINE_L2_CLOSED_LOOP_TARGET_SHA256.items() if rel.startswith("nodedc-source/dist/") } if nginx_actual != nginx_expected: die("Engine L2 closed-loop nginx target digest mismatch") descriptor = current_engine_node_intelligence_descriptor() if descriptor is None or descriptor.get("action") != "activate": die("Engine L2 closed-loop target descriptor is missing") validate_installed_engine_node_intelligence_source(descriptor) script = """ const gateway=await import('file:///app/server/routes/engineAgentGateway.js'); const graph=await import('file:///app/server/l2/graphRepository.js'); const n8n=await import('file:///app/server/routes/n8n.js'); const embedded=await import('file:///app/server/routes/ndcAgentMcp.js'); const required=['engine_plan_l2_patch','engine_apply_l2_patch','engine_deploy_and_run','engine_get_node_output_profile']; const names=new Set(gateway.engineAgentTools.map((tool)=>tool.name)); if(gateway.ENGINE_AGENT_MCP_VERSION!=='0.7.0'||required.some((name)=>!names.has(name)))throw new Error('mcp-catalog'); if(typeof graph.createL2GraphRepository!=='function'||typeof graph.l2GraphRepository?.commit!=='function')throw new Error('graph-repository'); const profile=n8n.toSafeNodeOutputProfile({id:'acceptance',data:{resultData:{runData:{Probe:[{data:{main:[[{json:{value:7,authorization:'must-not-escape'}}]]}}]}}}},'Probe'); if(!profile?.found||profile.valuesIncluded!==false||JSON.stringify(profile).includes('must-not-escape'))throw new Error('safe-output-profile'); const actor=embedded.graphMutationActor({demoAccess:{internal:true},body:{intent:'Deploy acceptance'},get:(name)=>name.toLowerCase()==='x-ndc-actor-plane'?'external_codex_mcp':''}); if(actor.plane!=='external_codex_mcp'||actor.editedBy!=='engine-agent-mcp')throw new Error('external-actor-plane'); process.stdout.write('engine-l2-closed-loop:0.7.0:cas+safe-profile+external-plane'); """.strip() result = run_engine_backend_probe( ( "node", "--input-type=module", "-e", script, ), "Engine L2 closed-loop live contract", container_id=engine_backend_container_id(), ) expected = ( "engine-l2-closed-loop:0.7.0:" "cas+safe-profile+external-plane" ) if result != expected: die("Engine L2 closed-loop live contract acceptance mismatch") backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die("Engine L2 closed-loop immutable backend acceptance failed") return result def run_healthchecks(component, entries=None, services=None): if component == "mission-core-map-access": accept_map_access(component_root(component), entries) return if is_gitea_fresh_install_slice(component, entries): if tuple(services or ()) != (GITEA_SERVICE,): die("Gitea fresh-install service set mismatch") accept_gitea_fresh_install_runtime() return if is_device_plane_control_core_v3_reconciliation_slice( component, entries, ): if tuple(services or ()) != ("device-control-core",): die("Device Control Core v3 reconciliation service set mismatch") for service in ( "device-control-core", "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", ): healthcheck_compose_service_with_grace( "device-plane", service, ) for check in component_healthchecks(component, entries, services): healthcheck_url(check) recovery_backup = ( validate_device_plane_control_core_v3_reconciliation_backup() ) validate_device_plane_control_core_v3_restored_source( recovery_backup, marker_installed=True, ) validate_device_plane_control_core_v3_reconciliation_runtime( recovery_backup, require_recovered=True, ) return if is_device_plane_control_core_migration_replay_recovery_slice( component, entries, ): if tuple(services or ()) != ("device-control-core",): die("Device Control Core migration recovery service set mismatch") try: healthcheck_compose_service_with_grace( "device-plane", "device-control-core", ) except DeployError: emit_bounded_device_control_core_failure_logs() raise for service in ( "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", ): healthcheck_compose_service_with_grace( "device-plane", service, ) for check in component_healthchecks(component, entries, services): healthcheck_url(check) accept_device_plane_control_core_migration_replay_recovery() return if is_device_plane_control_core_migration_replay_checkpoint_recovery_slice( component, entries, ): if tuple(services or ()) != ("device-control-core",): die( "Device Control Core migration replay checkpoint recovery " "service set mismatch" ) try: healthcheck_compose_service_with_grace( "device-plane", "device-control-core", ) except DeployError: emit_bounded_device_control_core_failure_logs() raise for service in ( "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", ): healthcheck_compose_service_with_grace( "device-plane", service, ) for check in component_healthchecks(component, entries, services): healthcheck_url(check) accept_device_plane_control_core_migration_replay_checkpoint_recovery() return if is_device_plane_control_core_release_slice(component, entries): if tuple(services or ()) != ("device-control-core",): die("Device Control Core release service set mismatch") for service in ( "device-control-core", "device-manager", "device-gateway", "device-postgres", "device-backhaul-target", ): healthcheck_compose_service_with_grace( "device-plane", service, ) for check in component_healthchecks(component, entries, services): healthcheck_url(check) validate_device_manager_control_plane_runtime( require_edge_channel=True, core_network_mode="private-egress", ) return if is_device_plane_manager_reconciliation_slice(component, entries): if tuple(services or ()) != (): die("Device Manager reconciliation service set mismatch") for service in DEVICE_PLANE_RUNTIME_SERVICES: healthcheck_compose_service_with_grace( "device-plane", service, ) for check in component_healthchecks(component, entries, services): healthcheck_url(check) backup_dir = validate_device_plane_manager_reconciliation_backup() validate_device_plane_manager_reconciled_baseline( backup_dir, marker_installed=True, ) return if is_device_plane_manager_v2_reconciliation_slice(component, entries): if tuple(services or ()) != (): die("Device Manager v2 reconciliation service set mismatch") for service in DEVICE_PLANE_RUNTIME_SERVICES: healthcheck_compose_service_with_grace( "device-plane", service, ) for check in component_healthchecks(component, entries, services): healthcheck_url(check) backup_dir = validate_device_plane_manager_v2_reconciliation_backup() validate_device_plane_manager_v2_reconciled_baseline( backup_dir, marker_installed=True, ) return if is_device_plane_backhaul_vps_enrollment_slice(component, entries): if tuple(services or ()) != (DEVICE_PLANE_BACKHAUL_TARGET_SERVICE,): die("Device Plane VPS enrollment service set mismatch") for service in DEVICE_PLANE_RUNTIME_SERVICES: healthcheck_compose_service("device-plane", service) healthcheck_compose_service( "device-plane", DEVICE_PLANE_BACKHAUL_TARGET_SERVICE, ) return if is_device_plane_backhaul_target_slice(component, entries): if tuple(services or ()) != (DEVICE_PLANE_BACKHAUL_TARGET_SERVICE,): die("Device Plane backhaul target service set mismatch") for service in DEVICE_PLANE_RUNTIME_SERVICES: healthcheck_compose_service("device-plane", service) healthcheck_compose_service( "device-plane", DEVICE_PLANE_BACKHAUL_TARGET_SERVICE, ) return if is_device_plane_b2_discovery_rollback_recovery_slice( component, entries, ): if tuple(services or ()) != (): die("Device Plane B2 rollback recovery service set mismatch") for service in DEVICE_PLANE_RUNTIME_SERVICES: healthcheck_compose_service("device-plane", service) for check in component_healthchecks(component, entries, services): healthcheck_url(check) validate_device_plane_foundation_network_publication_installed_source() installed_descriptor = read_strict_json( component_root("device-plane") / DEVICE_PLANE_B2_DISCOVERY_ROLLBACK_RECOVERY_REL, "installed Device Plane B2 rollback recovery descriptor", max_bytes=16 * 1024, ) if ( installed_descriptor != expected_device_plane_b2_discovery_rollback_recovery_descriptor() ): die("installed Device Plane B2 rollback recovery mismatch") validate_device_plane_foundation_runtime( network_publication=True ) assert_loopback_tcp_port_closed(9921) return if is_device_plane_postgres_bootstrap_slice(component, entries): if tuple(services or ()) != ("device-postgres",): die("Device Plane PostgreSQL bootstrap service set mismatch") healthcheck_compose_service("device-plane", "device-postgres") return if is_device_plane_foundation_recovery_slice(component, entries): if tuple(services or ()) != (): die("Device Plane foundation recovery service set mismatch") for service in ( "device-control-core", "device-gateway", "device-postgres", ): healthcheck_compose_service("device-plane", service) for check in component_healthchecks(component, entries, services): healthcheck_url(check) validate_device_plane_foundation_installed_source() validate_device_plane_foundation_runtime() return if is_device_plane_foundation_network_publication_slice( component, entries, ): if tuple(services or ()) != ( "device-control-core", "device-gateway", ): die( "Device Plane network-publication service set mismatch" ) for service in ( "device-control-core", "device-gateway", "device-postgres", ): healthcheck_compose_service("device-plane", service) for check in component_healthchecks(component, entries, services): healthcheck_url(check) validate_device_plane_foundation_network_publication_installed_source() validate_device_plane_foundation_runtime( network_publication=True ) assert_loopback_tcp_port_closed(9921) return if is_device_plane_b2_discovery_ingress_slice(component, entries): if tuple(services or ()) != ( "device-control-core", "device-gateway", ): die("Device Plane B2 discovery ingress service set mismatch") for service in ( "device-control-core", "device-gateway", "device-postgres", ): healthcheck_compose_service("device-plane", service) for check in component_healthchecks(component, entries, services): healthcheck_url(check) assert_loopback_tcp_port_open(9921) return if is_device_plane_manager_control_plane_slice(component, entries): expected_services = ( ("device-manager",) if is_device_plane_manager_only_release_slice(component, entries) else ("device-control-core", "device-manager") ) if tuple(services or ()) != expected_services: die("Device Manager control-plane service set mismatch") for service in services: healthcheck_compose_service_with_grace( "device-plane", service, ) for check in component_healthchecks(component, entries, services): healthcheck_url(check) if is_device_plane_manager_persistent_release_slice( component, entries, ): validate_device_manager_control_plane_runtime( require_edge_channel=True, core_network_mode="private-egress", require_persistent_data=True, ) elif is_device_plane_manager_release_v3_slice(component, entries): validate_device_manager_control_plane_runtime( require_edge_channel=True, core_network_mode="private-egress", ) else: validate_device_manager_control_plane_runtime() return if is_device_plane_edge_core_channel_bootstrap_slice(component, entries): if tuple(services or ()) != ("device-control-core",): die("Device Edge Core channel bootstrap service set mismatch") for service in ( "device-control-core", "device-manager", "device-gateway", "device-postgres", ): healthcheck_compose_service_with_grace( "device-plane", service, ) for check in component_healthchecks(component, entries, services): healthcheck_url(check) if is_device_plane_edge_core_channel_upgrade_v4_slice( component, entries, ): validate_device_manager_control_plane_runtime( require_edge_channel=True, core_network_mode="private-egress", ) else: validate_device_manager_control_plane_runtime( require_edge_channel=True, ) return if component == "platform" and entries is not None and is_platform_provider_catalog_only(entries): return if is_engine_l2_closed_loop_slice(component, entries): if tuple(services or ()) != ("nodedc-backend", "app"): die("Engine L2 closed-loop acceptance service set mismatch") for service in services: healthcheck_compose_service("engine", service) for check in component_healthchecks(component, entries, services): healthcheck_url(check) accept_engine_l2_closed_loop_runtime() return if is_engine_n8n_transition(component, entries): descriptor = current_engine_n8n_transition_descriptor() if descriptor is None: die("installed Engine n8n transition descriptor is missing during acceptance") accept_engine_n8n_runtime(descriptor) return if is_engine_node_intelligence_transition(component, entries): descriptor = current_engine_node_intelligence_descriptor() if descriptor is None: die( "installed Engine node-intelligence transition descriptor " "is missing during acceptance" ) if descriptor["action"] == "activate": accept_engine_node_intelligence_runtime(descriptor) healthcheck_url("http://127.0.0.1:3001/health") return healthcheck_compose_service("engine", "nodedc-backend") healthcheck_url("http://127.0.0.1:3001/health") backend = preflight_engine_credential_backend_runtime() if backend["mode"] != "verified-derived-retry": die("Engine node-intelligence rollback backend acceptance failed") assert_engine_node_intelligence_container_absent() return touches_publish_grant = is_engine_data_product_publish_grant_slice( component, entries, ) touches_composite_provider_v4 = is_engine_composite_provider_v4_slice( component, entries, ) touches_provider_rotating_slot = is_engine_provider_rotating_slot_slice( component, entries, ) touches_provider_authority_diagnostics = is_engine_provider_authority_diagnostics_slice( component, entries, ) touches_depttrans_zone_authority_v1 = is_engine_depttrans_zone_authority_v1_slice( component, entries, ) touches_provider_target_host_policy = is_engine_provider_target_host_policy_slice( component, entries, ) touches_mcp_execution_profile_decoder = ( is_engine_mcp_execution_profile_decoder_slice( component, entries, ) ) touches_mcp_telemetry_catalog = is_engine_mcp_telemetry_catalog_slice( component, entries, ) touches_mcp_execution_plan_materialization = ( is_engine_mcp_execution_plan_materialization_slice( component, entries, ) ) touches_mcp_execution_plan_telemetry_runtime = ( is_engine_mcp_execution_plan_telemetry_runtime_slice( component, entries, ) ) touches_mcp_execution_plan_module_ownership = ( is_engine_mcp_execution_plan_module_ownership_slice( component, entries, ) ) touches_mcp_normalized_identity_search = ( is_engine_mcp_normalized_identity_search_slice( component, entries, ) ) touches_mcp_l1_credential_reuse = ( is_engine_mcp_l1_credential_reuse_slice(component, entries) ) touches_mcp_l1_credential_provenance = ( is_engine_mcp_l1_credential_provenance_slice(component, entries) ) touches_mcp_execution_plan_sandbox_runtime = ( is_engine_mcp_execution_plan_sandbox_runtime_slice(component, entries) ) touches_mcp_gelios_items_envelope = ( is_engine_mcp_gelios_items_envelope_slice(component, entries) ) touches_mcp_registered_execution_profiles = ( is_engine_mcp_registered_execution_profiles_slice(component, entries) ) touches_mcp_gelios_units_items = ( is_engine_mcp_gelios_units_items_slice(component, entries) ) touches_agent_grant_migration = is_engine_agent_full_grant_migration_slice( component, 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) touches_mcp_autonomy_provider_v5 = is_engine_mcp_autonomy_provider_v5_slice(component, entries) touches_provider_catalog = is_engine_provider_security_catalog_slice(component, entries) if ( touches_engine_credential_sink(component, entries) or touches_publish_grant or touches_composite_provider_v4 or touches_provider_rotating_slot or touches_provider_authority_diagnostics or touches_depttrans_zone_authority_v1 or touches_provider_target_host_policy or touches_mcp_execution_profile_decoder or touches_mcp_telemetry_catalog or touches_mcp_execution_plan_materialization or touches_mcp_execution_plan_telemetry_runtime or touches_mcp_execution_plan_module_ownership or touches_mcp_normalized_identity_search or touches_mcp_l1_credential_reuse or touches_mcp_l1_credential_provenance or touches_mcp_execution_plan_sandbox_runtime or touches_mcp_gelios_items_envelope or touches_mcp_registered_execution_profiles or touches_mcp_gelios_units_items or touches_agent_grant_migration or touches_mcp_control_plane or touches_mcp_ontology_sdk or touches_mcp_autonomy_provider_v5 or touches_provider_catalog ): # The HTTP endpoint can become ready before Docker publishes the first # successful health probe. Wait for the Compose health barrier before # the strict immutable-runtime preflight, both on apply and retry. healthcheck_compose_service("engine", "nodedc-backend") for url in component_healthchecks(component, entries, services): healthcheck_url(url) if ( touches_engine_credential_sink(component, entries) or touches_publish_grant or touches_composite_provider_v4 or touches_provider_rotating_slot or touches_provider_authority_diagnostics or touches_depttrans_zone_authority_v1 or touches_provider_target_host_policy or touches_mcp_execution_profile_decoder or touches_mcp_telemetry_catalog or touches_mcp_execution_plan_materialization or touches_mcp_execution_plan_telemetry_runtime or touches_mcp_execution_plan_module_ownership or touches_mcp_normalized_identity_search or touches_mcp_l1_credential_reuse or touches_mcp_l1_credential_provenance or touches_mcp_execution_plan_sandbox_runtime or touches_mcp_gelios_items_envelope or touches_mcp_registered_execution_profiles or touches_mcp_gelios_units_items or touches_agent_grant_migration or touches_mcp_control_plane or touches_mcp_ontology_sdk or touches_mcp_autonomy_provider_v5 or touches_provider_catalog ): 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() if touches_mcp_ontology_sdk: accept_engine_mcp_ontology_sdk_runtime() if touches_mcp_autonomy_provider_v5: state = engine_mcp_autonomy_provider_v5_installed_state() if state == "target": accept_engine_mcp_autonomy_provider_v5_runtime() if touches_provider_catalog: accept_engine_provider_security_catalog_runtime() if touches_publish_grant: accept_engine_composite_provider_runtime() if touches_composite_provider_v4: root = component_root("engine") installed_sha256 = { rel: sha256_file(root / rel) for rel in ENGINE_COMPOSITE_PROVIDER_V4_ARTIFACT_ENTRIES } if installed_sha256 == ENGINE_COMPOSITE_PROVIDER_V4_TARGET_SHA256: if accept_engine_composite_provider_runtime() is None: die("Engine composite provider v4 live acceptance was not executed") elif installed_sha256 != ENGINE_COMPOSITE_PROVIDER_V4_PREDECESSOR_SHA256: die("Engine composite provider v4 installed source state is neither target nor rollback predecessor") if touches_provider_rotating_slot: root = component_root("engine") installed_sha256 = { rel: sha256_file(root / rel) for rel in ENGINE_PROVIDER_ROTATING_SLOT_ARTIFACT_ENTRIES } if installed_sha256 == ENGINE_PROVIDER_ROTATING_SLOT_TARGET_SHA256: accept_engine_provider_rotating_slot_runtime() elif installed_sha256 != ENGINE_PROVIDER_ROTATING_SLOT_PREDECESSOR_SHA256: die("Engine provider rotating slot installed state is neither target nor rollback predecessor") if touches_provider_authority_diagnostics: root = component_root("engine") installed_sha256 = { rel: sha256_file(root / rel) for rel in ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_ARTIFACT_ENTRIES } if installed_sha256 == ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_TARGET_SHA256: accept_engine_provider_authority_diagnostics_runtime() elif installed_sha256 != ENGINE_PROVIDER_AUTHORITY_DIAGNOSTICS_PREDECESSOR_SHA256: die("Engine provider authority diagnostics installed state is neither target nor rollback predecessor") if touches_depttrans_zone_authority_v1: root = component_root("engine") target_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_TARGET_SHA256.items() ) predecessor_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_PREDECESSOR_SHA256.items() ) and all( not (root / rel).exists() and not (root / rel).is_symlink() for rel in ENGINE_DEPTTRANS_ZONE_AUTHORITY_V1_NEW_PATHS ) if target_state: accept_engine_depttrans_zone_authority_v1_runtime() elif not predecessor_state: die("Engine Depttrans zone authority v1 installed state is neither target nor rollback predecessor") if touches_provider_target_host_policy: root = component_root("engine") installed_sha256 = { rel: sha256_file(root / rel) for rel in ENGINE_PROVIDER_TARGET_HOST_POLICY_ARTIFACT_ENTRIES } if installed_sha256 == ENGINE_PROVIDER_TARGET_HOST_POLICY_TARGET_SHA256: accept_engine_provider_target_host_policy_runtime() elif installed_sha256 != ENGINE_PROVIDER_TARGET_HOST_POLICY_PREDECESSOR_SHA256: die("Engine provider target host policy installed state is neither target nor rollback predecessor") if touches_mcp_execution_profile_decoder: root = component_root("engine") target_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ENGINE_MCP_EXECUTION_PROFILE_DECODER_TARGET_SHA256.items() ) predecessor_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_EXECUTION_PROFILE_DECODER_PREDECESSOR_SHA256.items() ) ) and all( not (root / rel).exists() and not (root / rel).is_symlink() for rel in ENGINE_MCP_EXECUTION_PROFILE_DECODER_NEW_PATHS ) if target_state: accept_engine_mcp_execution_profile_decoder_runtime() elif not predecessor_state: die( "Engine MCP execution profile decoder installed state is neither " "target nor rollback predecessor" ) if touches_mcp_telemetry_catalog: root = component_root("engine") target_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ENGINE_MCP_TELEMETRY_CATALOG_TARGET_SHA256.items() ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ENGINE_MCP_TELEMETRY_CATALOG_FOUNDATION_SHA256.items() ) predecessor_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ENGINE_MCP_TELEMETRY_CATALOG_PREDECESSOR_SHA256.items() ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ENGINE_MCP_TELEMETRY_CATALOG_FOUNDATION_SHA256.items() ) and all( not (root / rel).exists() and not (root / rel).is_symlink() for rel in ENGINE_MCP_TELEMETRY_CATALOG_NEW_PATHS ) if target_state: accept_engine_mcp_telemetry_catalog_runtime() elif not predecessor_state: die( "Engine MCP telemetry catalog installed state is neither target " "nor rollback predecessor" ) if touches_mcp_execution_plan_materialization: root = component_root("engine") target_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_TARGET_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_FOUNDATION_SHA256.items() ) ) predecessor_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_PREDECESSOR_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_FOUNDATION_SHA256.items() ) ) and all( not (root / rel).exists() and not (root / rel).is_symlink() for rel in ENGINE_MCP_EXECUTION_PLAN_MATERIALIZATION_NEW_PATHS ) if target_state: accept_engine_mcp_execution_plan_materialization_runtime() elif not predecessor_state: die( "Engine MCP execution plan materialization installed state is " "neither target nor rollback predecessor" ) if touches_mcp_execution_plan_telemetry_runtime: root = component_root("engine") target_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_TARGET_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_FOUNDATION_SHA256.items() ) ) predecessor_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_PREDECESSOR_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_FOUNDATION_SHA256.items() ) ) and all( not (root / rel).exists() and not (root / rel).is_symlink() for rel in ENGINE_MCP_EXECUTION_PLAN_TELEMETRY_RUNTIME_NEW_PATHS ) if target_state: accept_engine_mcp_execution_plan_telemetry_runtime() elif not predecessor_state: die( "Engine MCP execution plan telemetry runtime installed state " "is neither target nor rollback predecessor" ) if touches_mcp_execution_plan_module_ownership: root = component_root("engine") target_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_TARGET_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_FOUNDATION_SHA256.items() ) ) predecessor_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_PREDECESSOR_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_FOUNDATION_SHA256.items() ) ) and all( not (root / rel).exists() and not (root / rel).is_symlink() for rel in ENGINE_MCP_EXECUTION_PLAN_MODULE_OWNERSHIP_NEW_PATHS ) if target_state: accept_engine_mcp_execution_plan_module_ownership_runtime() elif not predecessor_state: die( "Engine MCP execution plan module ownership installed state " "is neither target nor rollback predecessor" ) if touches_mcp_normalized_identity_search: root = component_root("engine") target_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_TARGET_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_FOUNDATION_SHA256.items() ) ) predecessor_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_PREDECESSOR_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_FOUNDATION_SHA256.items() ) ) and all( not (root / rel).exists() and not (root / rel).is_symlink() for rel in ENGINE_MCP_NORMALIZED_IDENTITY_SEARCH_NEW_PATHS ) if target_state: accept_engine_mcp_normalized_identity_search_runtime() elif not predecessor_state: die( "Engine MCP normalized identity search installed state is " "neither target nor rollback predecessor" ) if touches_mcp_l1_credential_reuse: root = component_root("engine") target_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_L1_CREDENTIAL_REUSE_TARGET_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_L1_CREDENTIAL_REUSE_FOUNDATION_SHA256.items() ) ) predecessor_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_L1_CREDENTIAL_REUSE_PREDECESSOR_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_L1_CREDENTIAL_REUSE_FOUNDATION_SHA256.items() ) ) and all( not (root / rel).exists() and not (root / rel).is_symlink() for rel in ENGINE_MCP_L1_CREDENTIAL_REUSE_NEW_PATHS ) if target_state: accept_engine_mcp_l1_credential_reuse_runtime() elif not predecessor_state: die( "Engine MCP L1 credential reuse installed state is neither " "target nor rollback predecessor" ) if touches_mcp_l1_credential_provenance: root = component_root("engine") target_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_TARGET_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_FOUNDATION_SHA256.items() ) ) predecessor_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_PREDECESSOR_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_FOUNDATION_SHA256.items() ) ) and all( not (root / rel).exists() and not (root / rel).is_symlink() for rel in ENGINE_MCP_L1_CREDENTIAL_PROVENANCE_NEW_PATHS ) if target_state: accept_engine_mcp_l1_credential_provenance_runtime() elif not predecessor_state: die( "Engine MCP L1 credential provenance installed state is " "neither target nor rollback predecessor" ) if touches_mcp_execution_plan_sandbox_runtime: root = component_root("engine") target_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_TARGET_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_FOUNDATION_SHA256.items() ) ) predecessor_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_PREDECESSOR_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_FOUNDATION_SHA256.items() ) ) and all( not (root / rel).exists() and not (root / rel).is_symlink() for rel in ENGINE_MCP_EXECUTION_PLAN_SANDBOX_RUNTIME_NEW_PATHS ) if target_state: accept_engine_mcp_execution_plan_sandbox_runtime() elif not predecessor_state: die( "Engine MCP execution plan sandbox runtime installed state is " "neither target nor rollback predecessor" ) if touches_mcp_gelios_items_envelope: root = component_root("engine") target_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_TARGET_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_FOUNDATION_SHA256.items() ) ) predecessor_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_PREDECESSOR_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_FOUNDATION_SHA256.items() ) ) and all( not (root / rel).exists() and not (root / rel).is_symlink() for rel in ENGINE_MCP_GELIOS_ITEMS_ENVELOPE_NEW_PATHS ) if target_state: accept_engine_mcp_gelios_items_envelope_runtime() elif not predecessor_state: die( "Engine MCP Gelios items envelope installed state is neither " "target nor rollback predecessor" ) if touches_mcp_registered_execution_profiles: root = component_root("engine") target_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_TARGET_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_FOUNDATION_SHA256.items() ) ) predecessor_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_PREDECESSOR_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_FOUNDATION_SHA256.items() ) ) and all( not (root / rel).exists() and not (root / rel).is_symlink() for rel in ENGINE_MCP_REGISTERED_EXECUTION_PROFILES_NEW_PATHS ) if target_state: accept_engine_mcp_registered_execution_profiles_runtime() elif not predecessor_state: die( "Engine MCP registered execution profiles installed state is " "neither target nor rollback predecessor" ) if touches_mcp_gelios_units_items: root = component_root("engine") target_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_GELIOS_UNITS_ITEMS_TARGET_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_GELIOS_UNITS_ITEMS_FOUNDATION_SHA256.items() ) ) predecessor_state = all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_GELIOS_UNITS_ITEMS_PREDECESSOR_SHA256.items() ) ) and all( (root / rel).is_file() and not (root / rel).is_symlink() and sha256_file(root / rel) == expected for rel, expected in ( ENGINE_MCP_GELIOS_UNITS_ITEMS_FOUNDATION_SHA256.items() ) ) and all( not (root / rel).exists() and not (root / rel).is_symlink() for rel in ENGINE_MCP_GELIOS_UNITS_ITEMS_NEW_PATHS ) if target_state: accept_engine_mcp_gelios_units_items_runtime() elif not predecessor_state: die( "Engine MCP Gelios units items installed state is neither " "target nor rollback predecessor" ) container_name = COMPONENTS[component].get("health_container") if container_name: healthcheck_container(container_name) def move_artifact(src, target_dir, suffix=""): target_dir.mkdir(parents=True, exist_ok=True) name = src.name + suffix dst = target_dir / name if dst.exists(): dst = target_dir / f"{src.name}.{stamp()}" src.rename(dst) return dst def apply_artifact(artifact): require_root() validate_artifact_location(artifact) ensure_layout() sha = sha256_file(artifact) backup_id = None backup_dir = None manifest = None entries = None component = None root = None services = None transition_descriptor = None node_intelligence_descriptor = None l2_closed_loop_preflight = None device_plane_backhaul_preflight = None device_plane_backhaul_vps_enrollment_preflight = None node_intelligence_service_stopped = False apply_started = False engine_backend_recreated = False engine_backend_initial_mode = None runtime_started = False current_stamp = stamp() with DeployLock(): try: with tempfile.TemporaryDirectory(prefix=f"apply-{current_stamp}-", dir=TMP_DIR) as tmp: work = Path(tmp) manifest, entries, payload_dir = load_artifact(artifact, work) if is_gitea_fresh_install_slice( manifest["component"], entries, ): validate_gitea_compose_schema( payload_dir / GITEA_COMPOSE_REL ) if is_gitea_incident_salvage_slice( manifest["component"], entries, ): validate_gitea_compose_schema( payload_dir / GITEA_COMPOSE_REL ) reject_failed_artifact_replay(manifest, sha) reject_terminal_engine_l2_failed_artifact(manifest, sha) reject_terminal_device_plane_foundation_artifact( manifest, sha, ) reject_terminal_device_plane_manager_artifact( manifest, sha, entries, ) reject_terminal_device_plane_backhaul_artifact( manifest, sha, ) patch_id = manifest["id"] component = manifest["component"] root = component_root(component) compose_root = component_compose_root(component) bootstrap_root = bool(COMPONENTS[component].get("bootstrap_root")) services = component_services(component, entries) if component == "mission-core-map-access": preflight_map_access(payload_dir, entries) artifact_only = component_artifact_only(component) defer_bootstrap_root = is_gitea_bootstrap_slice( component, entries, ) if is_device_plane_control_core_incident_audit_slice( component, entries, ): validate_device_plane_control_core_incident_audit_evidence( payload_dir ) die( "Device Control Core incident audit is canonical-plan-only; " "apply is forbidden" ) if is_device_plane_control_core_migration_replay_audit_slice( component, entries, ): validate_device_plane_control_core_migration_replay_audit_payload( payload_dir ) die( "Device Control Core migration replay audit is " "canonical-plan-only; apply is forbidden" ) if state_has_sha(sha): die(f"artifact sha already applied: {sha}") if state_has_patch_id(patch_id): die(f"patch id already applied: {patch_id}") if is_gitea_fresh_install_slice(component, entries): preflight_gitea_fresh_install() if is_gitea_incident_salvage_slice(component, entries): preflight_gitea_incident_salvage( payload_dir, enforce_apply=True, ) if is_device_plane_postgres_bootstrap_slice( component, entries, ): preflight_device_plane_postgres_bootstrap() if is_device_plane_foundation_recovery_slice( component, entries, ): device_plane_foundation_recovery_preflight = ( validate_device_plane_foundation_recovery_evidence( payload_dir ) ) if is_device_plane_foundation_network_publication_slice( component, entries, ): validate_device_plane_foundation_network_publication_evidence( payload_dir ) if is_device_plane_b2_discovery_ingress_slice( component, entries, ): validate_device_plane_b2_discovery_ingress_evidence( payload_dir ) if is_device_plane_b2_discovery_rollback_recovery_slice( component, entries, ): validate_device_plane_b2_discovery_rollback_recovery_evidence( payload_dir ) if is_device_plane_manager_control_plane_slice( component, entries, ): validate_device_plane_manager_activation_predecessor( payload_dir, preflight_phase="apply", ) if is_device_plane_edge_core_channel_bootstrap_slice( component, entries, ): validate_device_plane_edge_core_channel_bootstrap_predecessor( payload_dir ) if is_device_plane_control_core_release_slice( component, entries, ): validate_device_plane_control_core_release_predecessor( payload_dir, preflight_phase="apply", ) if is_device_plane_manager_reconciliation_slice( component, entries, ): validate_device_plane_manager_reconciliation_evidence( payload_dir ) if is_device_plane_manager_v2_reconciliation_slice( component, entries, ): validate_device_plane_manager_v2_reconciliation_evidence( payload_dir ) if is_device_plane_control_core_v3_reconciliation_slice( component, entries, ): validate_device_plane_control_core_v3_reconciliation_evidence( payload_dir ) if is_device_plane_control_core_migration_replay_recovery_slice( component, entries, ): if ( patch_id != DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_RECOVERY_PATCH_ID ): die( "Device Control Core migration recovery patch id " "mismatch" ) validate_device_plane_control_core_migration_replay_recovery_evidence( payload_dir ) if is_device_plane_control_core_migration_replay_checkpoint_recovery_slice( component, entries, ): if ( patch_id != DEVICE_PLANE_CONTROL_CORE_MIGRATION_REPLAY_CHECKPOINT_RECOVERY_PATCH_ID ): die( "Device Control Core migration replay checkpoint " "recovery patch id mismatch" ) validate_device_plane_control_core_migration_replay_checkpoint_recovery_evidence( payload_dir ) if is_device_plane_backhaul_target_slice( component, entries, ): device_plane_backhaul_preflight = ( validate_device_plane_backhaul_target_evidence( payload_dir ) ) if is_device_plane_backhaul_vps_enrollment_slice( component, entries, ): device_plane_backhaul_vps_enrollment_preflight = ( validate_device_plane_backhaul_vps_enrollment_evidence( payload_dir ) ) if not root.is_dir(): if bootstrap_root and defer_bootstrap_root: # The fresh-install root itself is a mutation. Delay it # until after backup and apply_started so every created # path is covered by Gitea's automatic rollback. pass elif bootstrap_root: root.mkdir(parents=True, exist_ok=True) else: die(f"component payload root not found: {root}") if is_engine_data_product_publish_grant_slice(component, entries): preflight_engine_data_product_publish_grant_predecessor(payload_dir) publish_backend_preflight = preflight_engine_credential_backend_runtime() if publish_backend_preflight["mode"] != "verified-derived-retry": die("Engine data product publish grant requires the active immutable credential backend") if is_engine_composite_provider_v4_slice(component, entries): preflight_engine_composite_provider_v4_predecessor() composite_backend_preflight = preflight_engine_credential_backend_runtime() if composite_backend_preflight["mode"] != "verified-derived-retry": die("Engine composite provider v4 requires the active immutable credential backend") if is_engine_provider_rotating_slot_slice(component, entries): preflight_engine_provider_rotating_slot_predecessor() rotating_slot_backend_preflight = preflight_engine_credential_backend_runtime() if rotating_slot_backend_preflight["mode"] != "verified-derived-retry": die("Engine provider rotating slot requires the active immutable credential backend") if is_engine_provider_authority_diagnostics_slice(component, entries): preflight_engine_provider_authority_diagnostics_predecessor() authority_diagnostics_backend_preflight = preflight_engine_credential_backend_runtime() if authority_diagnostics_backend_preflight["mode"] != "verified-derived-retry": die("Engine provider authority diagnostics requires the active immutable credential backend") if is_engine_depttrans_zone_authority_v1_slice(component, entries): preflight_engine_depttrans_zone_authority_v1_predecessor() depttrans_authority_backend_preflight = preflight_engine_credential_backend_runtime() if depttrans_authority_backend_preflight["mode"] != "verified-derived-retry": die("Engine Depttrans zone authority v1 requires the active immutable credential backend") if is_engine_provider_target_host_policy_slice(component, entries): preflight_engine_provider_target_host_policy_predecessor() target_host_policy_backend_preflight = preflight_engine_credential_backend_runtime() if target_host_policy_backend_preflight["mode"] != "verified-derived-retry": die("Engine provider target host policy requires the active immutable credential backend") if is_engine_mcp_execution_profile_decoder_slice(component, entries): preflight_engine_mcp_execution_profile_decoder_predecessor() profile_decoder_backend_preflight = ( preflight_engine_credential_backend_runtime() ) if ( profile_decoder_backend_preflight["mode"] != "verified-derived-retry" ): die( "Engine MCP execution profile decoder requires the " "active immutable credential backend" ) if is_engine_mcp_telemetry_catalog_slice(component, entries): preflight_engine_mcp_telemetry_catalog_predecessor() telemetry_catalog_backend_preflight = ( preflight_engine_credential_backend_runtime() ) if ( telemetry_catalog_backend_preflight["mode"] != "verified-derived-retry" ): die( "Engine MCP telemetry catalog requires the active " "immutable credential backend" ) if is_engine_mcp_execution_plan_materialization_slice( component, entries, ): preflight_engine_mcp_execution_plan_materialization_predecessor() execution_plan_materialization_backend_preflight = ( preflight_engine_credential_backend_runtime() ) if ( execution_plan_materialization_backend_preflight["mode"] != "verified-derived-retry" ): die( "Engine MCP execution plan materialization requires " "the active immutable credential backend" ) if is_engine_mcp_execution_plan_telemetry_runtime_slice( component, entries, ): preflight_engine_mcp_execution_plan_telemetry_runtime_predecessor() execution_plan_telemetry_runtime_backend_preflight = ( preflight_engine_credential_backend_runtime() ) if ( execution_plan_telemetry_runtime_backend_preflight["mode"] != "verified-derived-retry" ): die( "Engine MCP execution plan telemetry runtime " "requires the active immutable credential backend" ) if is_engine_mcp_execution_plan_module_ownership_slice( component, entries, ): preflight_engine_mcp_execution_plan_module_ownership_predecessor() execution_plan_module_ownership_backend_preflight = ( preflight_engine_credential_backend_runtime() ) if ( execution_plan_module_ownership_backend_preflight["mode"] != "verified-derived-retry" ): die( "Engine MCP execution plan module ownership " "requires the active immutable credential backend" ) if is_engine_mcp_normalized_identity_search_slice( component, entries, ): preflight_engine_mcp_normalized_identity_search_predecessor() identity_search_backend_preflight = ( preflight_engine_credential_backend_runtime() ) if ( identity_search_backend_preflight["mode"] != "verified-derived-retry" ): die( "Engine MCP normalized identity search requires " "the active immutable credential backend" ) if is_engine_mcp_l1_credential_reuse_slice( component, entries, ): preflight_engine_mcp_l1_credential_reuse_predecessor() l1_credential_reuse_backend_preflight = ( preflight_engine_credential_backend_runtime() ) if ( l1_credential_reuse_backend_preflight["mode"] != "verified-derived-retry" ): die( "Engine MCP L1 credential reuse requires the " "active immutable credential backend" ) if is_engine_mcp_l1_credential_provenance_slice( component, entries, ): preflight_engine_mcp_l1_credential_provenance_predecessor() l1_credential_provenance_backend_preflight = ( preflight_engine_credential_backend_runtime() ) if ( l1_credential_provenance_backend_preflight["mode"] != "verified-derived-retry" ): die( "Engine MCP L1 credential provenance requires the " "active immutable credential backend" ) if is_engine_mcp_execution_plan_sandbox_runtime_slice( component, entries, ): preflight_engine_mcp_execution_plan_sandbox_runtime_predecessor() execution_plan_sandbox_runtime_backend_preflight = ( preflight_engine_credential_backend_runtime() ) if ( execution_plan_sandbox_runtime_backend_preflight["mode"] != "verified-derived-retry" ): die( "Engine MCP execution plan sandbox runtime requires " "the active immutable credential backend" ) if is_engine_mcp_gelios_items_envelope_slice( component, entries, ): preflight_engine_mcp_gelios_items_envelope_predecessor() gelios_items_envelope_backend_preflight = ( preflight_engine_credential_backend_runtime() ) if ( gelios_items_envelope_backend_preflight["mode"] != "verified-derived-retry" ): die( "Engine MCP Gelios items envelope requires the " "active immutable credential backend" ) if is_engine_mcp_registered_execution_profiles_slice( component, entries, ): preflight_engine_mcp_registered_execution_profiles_predecessor() registered_profiles_backend_preflight = ( preflight_engine_credential_backend_runtime() ) if ( registered_profiles_backend_preflight["mode"] != "verified-derived-retry" ): die( "Engine MCP registered execution profiles requires " "the active immutable credential backend" ) if is_engine_mcp_gelios_units_items_slice( component, entries, ): preflight_engine_mcp_gelios_units_items_predecessor() gelios_units_items_backend_preflight = ( preflight_engine_credential_backend_runtime() ) if ( gelios_units_items_backend_preflight["mode"] != "verified-derived-retry" ): die( "Engine MCP Gelios units items requires the active " "immutable credential backend" ) if is_engine_agent_full_grant_migration_slice(component, entries): preflight_engine_agent_full_grant_migration_predecessor() migration_backend_preflight = preflight_engine_credential_backend_runtime() if migration_backend_preflight["mode"] != "verified-derived-retry": die("Engine agent full grant migration requires the active immutable credential backend") if is_engine_node_intelligence_transition(component, entries): node_intelligence_preflight = preflight_engine_node_intelligence_predecessor( 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 is_engine_mcp_ontology_sdk_slice(component, entries): preflight_engine_mcp_ontology_sdk_predecessor(payload_dir) if is_engine_mcp_autonomy_provider_v5_slice(component, entries): preflight_engine_mcp_autonomy_provider_v5_predecessor(payload_dir) if is_engine_l2_closed_loop_slice(component, entries): l2_closed_loop_preflight = ( preflight_engine_l2_closed_loop_predecessor(payload_dir) ) if is_engine_provider_security_catalog_slice(component, entries): preflight_engine_provider_security_catalog_predecessor() if not artifact_only and not compose_root.is_dir(): if bootstrap_root and defer_bootstrap_root: pass elif bootstrap_root and is_relative_to(compose_root.resolve(strict=False), root.resolve(strict=False)): compose_root.mkdir(parents=True, exist_ok=True) else: die(f"component compose root not found: {compose_root}") compose_files = component_compose_files( component, expected_node_intelligence_gateway_sha256=( l2_closed_loop_preflight["partial_gateway_sha256"] if l2_closed_loop_preflight is not None else None ), ) if not artifact_only and compose_files: for compose_file in compose_files: if not compose_file.is_file(): compose_entry = None if is_relative_to(compose_file.resolve(strict=False), root.resolve(strict=False)): compose_entry = compose_file.resolve(strict=False).relative_to(root.resolve(strict=False)).as_posix() if compose_entry not in entries: die(f"compose file not found: {compose_file}") elif not artifact_only and not (compose_root / "docker-compose.yml").is_file(): # A bootstrap component begins with no payload root. Its # compose file may therefore be supplied by the same # reviewed overlay that is about to create that root. compose_entry = None if is_relative_to(compose_root.resolve(strict=False), root.resolve(strict=False)): compose_entry = compose_root.resolve(strict=False).relative_to(root.resolve(strict=False)).joinpath("docker-compose.yml").as_posix() if not (bootstrap_root and compose_entry in entries): die(f"docker-compose.yml not found in component compose root: {compose_root}") compose_env_file = component_compose_env_file(component) if not artifact_only and compose_env_file and not compose_env_file.is_file(): die(f"compose env file not found: {compose_env_file}") if not artifact_only and not DOCKER.is_file(): die(f"docker not found: {DOCKER}") if component == "device-plane": inventory_services = DEVICE_PLANE_RUNTIME_SERVICES if is_device_plane_manager_control_plane_slice( component, entries, ): inventory_services = ( *inventory_services, "device-manager", ) if ( is_device_plane_control_core_release_slice( component, entries, ) or is_device_plane_control_core_v3_reconciliation_slice( component, entries, ) or is_device_plane_control_core_migration_replay_recovery_slice( component, entries, ) or is_device_plane_control_core_migration_replay_checkpoint_recovery_slice( component, entries, ) ): inventory_services = ( *inventory_services, "device-manager", "device-backhaul-target", ) elif is_device_plane_edge_core_channel_bootstrap_slice( component, entries, ): inventory_services = ( *inventory_services, "device-manager", ) device_plane_runtime_before = ( device_plane_runtime_inventory( inventory_services ) ) if is_engine_n8n_transition(component, entries): transition_descriptor = read_engine_n8n_transition_descriptor( payload_dir / ENGINE_N8N_TRANSITION_DESCRIPTOR_REL ) preflight_engine_n8n_transition( transition_descriptor, enforce_expected_current=True, ) if touches_engine_credential_sink(component, entries): engine_backend_initial_mode = preflight_engine_credential_backend_runtime()["mode"] if COMPONENTS[component].get("immutable_payload"): for rel in entries: destination = root / rel if destination.exists() or destination.is_symlink(): die(f"immutable release already exists: {rel}") backup_id = safe_name(f"{component}-{patch_id}-{current_stamp}") backup_dir = BACKUPS_DIR / backup_id backup_dir.mkdir(parents=True, exist_ok=False) (backup_dir / "manifest.env").write_text((work / "manifest.env").read_text(encoding="utf-8"), encoding="utf-8") (backup_dir / "files.txt").write_text((work / "files.txt").read_text(encoding="utf-8"), encoding="utf-8") include_nginx_html = component == "engine" and component_publish_dist(component, entries) create_backup(root, backup_dir, entries, include_nginx_html) if component == "mission-core-map-access": backup_map_access(backup_dir) if component == "device-plane": if device_plane_runtime_before is None: die("Device Plane pre-apply runtime inventory is missing") runtime_inventory_path = ( backup_dir / "runtime-before.json" ) runtime_inventory_path.write_text( json.dumps( device_plane_runtime_before, ensure_ascii=False, indent=2, sort_keys=True, ) + "\n", encoding="utf-8", ) runtime_inventory_path.chmod(0o600) if ( device_plane_backhaul_vps_enrollment_preflight is not None ): backup_device_plane_backhaul_authorized_keys( backup_dir ) if device_plane_backhaul_preflight is not None: tailscale_before_path = ( backup_dir / "tailscale-serve-before.json" ) tailscale_before_path.write_text( json.dumps( device_plane_backhaul_preflight[ "tailscaleServeBefore" ], ensure_ascii=False, indent=2, sort_keys=True, ) + "\n", encoding="utf-8", ) tailscale_before_path.chmod(0o600) if component == "platform" and touches_external_data_plane_files(entries): validate_backup_partition( entries, read_backup_path_list(backup_dir / "existing-files.txt"), read_backup_path_list(backup_dir / "missing-files.txt"), "Platform pre-apply", ) apply_started = True if ( node_intelligence_descriptor is not None and node_intelligence_descriptor["action"] == "rollback-inactive" ): stop_and_remove_compose_services( "engine", (ENGINE_NODE_INTELLIGENCE_SERVICE,), ) node_intelligence_service_stopped = True for rel in entries: copy_payload_path(payload_dir, root, rel, current_stamp) if component == "n8n-private-extension": for rel in entries: seal_n8n_private_extension_release(root, rel) if component == "mission-core-map-access": apply_map_access(root, entries) if component_publish_dist(component, entries): publish_engine_dist(root, current_stamp) if touches_engine_credential_sink(component, entries): run_build(component, entries) prepare_component_runtime(component, entries) # From this point the exact immutable override/image are # verified, so even an ambiguous partial Compose failure # can be safely reconciled by recreating on that runtime. engine_backend_recreated = True run_compose(component, services, entries) elif component == "device-plane": def mark_device_plane_runtime_started(): nonlocal runtime_started runtime_started = bool(services) run_device_plane_runtime_for_apply( entries, services, mark_device_plane_runtime_started, backhaul_serve_before=( device_plane_backhaul_preflight[ "tailscaleServeBefore" ] if device_plane_backhaul_preflight is not None else None ), ) else: # Mark the generic runtime before Compose so a partial # candidate start is always eligible for its domain rollback. runtime_started = bool(services) run_component_runtime(component, entries, services) if ( l2_closed_loop_preflight is not None and engine_backend_container_id() == l2_closed_loop_preflight["backend_container_id"] ): die("Engine L2 closed-loop backend generation was not recreated") if ( l2_closed_loop_preflight is not None and compose_service_container_id("engine", "app") == l2_closed_loop_preflight["app_container_id"] ): die("Engine L2 closed-loop app generation was not recreated") run_healthchecks(component, entries, services) if is_device_plane_control_core_release_v4_slice( component, entries, ): if device_plane_runtime_before is None: die( "Device Control Core release v4 predecessor " "runtime inventory is missing" ) accept_device_plane_control_core_release_v4( payload_dir, device_plane_runtime_before, ) if is_device_plane_b2_discovery_ingress_slice( component, entries, ): if device_plane_runtime_before is None: die( "Device Plane B2 ingress predecessor runtime " "inventory is missing" ) validate_device_plane_b2_discovery_ingress_runtime( device_plane_runtime_before ) if is_device_plane_backhaul_target_slice( component, entries, ): if device_plane_runtime_before is None: die( "Device Plane backhaul predecessor runtime " "inventory is missing" ) validate_device_plane_backhaul_target_runtime( device_plane_runtime_before ) if is_device_plane_backhaul_vps_enrollment_slice( component, entries, ): if device_plane_runtime_before is None: die( "Device Plane VPS enrollment predecessor runtime " "inventory is missing" ) validate_device_plane_backhaul_target_runtime( device_plane_runtime_before, expected_enrollment=( read_device_plane_backhaul_vps_enrollment_public_key() ), ) applied_path = move_artifact(artifact, APPLIED_DIR) append_jsonl(STATE_FILE, { "applied_at": utc_now(), "artifact": applied_path.name, "backup_id": backup_id, "component": component, "id": patch_id, "sha256": sha, "status": "ok", }) print(f"deploy-ok patch={patch_id} component={component} backup={backup_id}") except Exception as exc: rollback_status = "not-required" if ( apply_started and backup_dir is not None and root is not None ): if component == "mission-core-map-access": try: rollback_map_access(root, backup_dir, entries, current_stamp) rollback_status = "ok:mission-core-map-access" except Exception as rollback_exc: rollback_status = f"failed:{type(rollback_exc).__name__}" elif is_gitea_fresh_install_slice(component, entries): try: restored_action = rollback_gitea_fresh_install( root, backup_dir, entries, current_stamp, runtime_started=runtime_started, ) rollback_status = f"ok:gitea-fresh-install:{restored_action}" print( f"gitea-automatic-rollback={rollback_status}", file=sys.stderr, ) except ReconciliationRequired as rollback_exc: rollback_status = "deferred:reconciliation-required" print( "gitea-automatic-rollback=deferred:" f"{rollback_exc}", file=sys.stderr, ) except Exception as rollback_exc: rollback_status = f"failed:{type(rollback_exc).__name__}" print( "gitea-automatic-rollback=failed", file=sys.stderr, ) elif transition_descriptor is not None and is_engine_n8n_transition(component, entries): try: restored_action = restore_engine_n8n_transition( root, backup_dir, entries, transition_descriptor, current_stamp, ) rollback_status = f"ok:{restored_action}" print(f"engine-n8n-automatic-rollback={rollback_status}", file=sys.stderr) except Exception as rollback_exc: rollback_status = f"failed:{type(rollback_exc).__name__}" print("engine-n8n-automatic-rollback=failed", file=sys.stderr) elif ( node_intelligence_descriptor is not None and is_engine_node_intelligence_transition(component, entries) ): try: restored_action = rollback_engine_node_intelligence( root, backup_dir, entries, current_stamp, runtime_started=runtime_started, node_intelligence_service_stopped=node_intelligence_service_stopped, ) rollback_status = f"ok:{restored_action}" print( f"engine-node-intelligence-automatic-rollback={rollback_status}", file=sys.stderr, ) except Exception as rollback_exc: rollback_status = f"failed:{type(rollback_exc).__name__}" print( "engine-node-intelligence-automatic-rollback=failed", file=sys.stderr, ) elif touches_engine_credential_sink(component, entries): try: restored_action = rollback_engine_credential_bridge( component, root, backup_dir, entries, current_stamp, engine_backend_recreated=engine_backend_recreated, engine_backend_initial_mode=engine_backend_initial_mode, ) rollback_status = f"ok:{restored_action}" print(f"engine-credential-automatic-rollback={rollback_status}", file=sys.stderr) except ReconciliationRequired: rollback_status = "deferred:reconciliation-required" print("engine-credential-automatic-rollback=deferred:reconciliation-required", file=sys.stderr) except Exception as rollback_exc: rollback_status = f"failed:{type(rollback_exc).__name__}" print("engine-credential-automatic-rollback=failed", file=sys.stderr) elif ( l2_closed_loop_preflight is not None and is_engine_l2_closed_loop_slice(component, entries) and services is not None ): try: restored_state = ( rollback_engine_l2_closed_loop_reconciliation( root, backup_dir, entries, current_stamp, runtime_started, services, ) ) rollback_status = ( "ok:engine-l2-reconciliation:" f"{restored_state}" ) print( "engine-l2-automatic-rollback=" f"{rollback_status}", file=sys.stderr, ) except Exception as rollback_exc: rollback_status = f"failed:{type(rollback_exc).__name__}" print( "engine-l2-automatic-rollback=failed", file=sys.stderr, ) elif ( ( is_engine_data_product_publish_grant_slice(component, entries) or is_engine_composite_provider_v4_slice(component, entries) or is_engine_provider_rotating_slot_slice(component, entries) or is_engine_provider_authority_diagnostics_slice(component, entries) or is_engine_depttrans_zone_authority_v1_slice(component, entries) or is_engine_provider_target_host_policy_slice(component, entries) or is_engine_mcp_execution_profile_decoder_slice( component, entries, ) or is_engine_mcp_telemetry_catalog_slice(component, entries) or is_engine_mcp_execution_plan_materialization_slice( component, entries, ) or is_engine_mcp_execution_plan_telemetry_runtime_slice( component, entries, ) or is_engine_mcp_execution_plan_module_ownership_slice( component, entries, ) or is_engine_mcp_normalized_identity_search_slice( component, entries, ) or is_engine_mcp_l1_credential_reuse_slice( component, entries, ) or is_engine_mcp_l1_credential_provenance_slice( component, entries, ) or is_engine_mcp_execution_plan_sandbox_runtime_slice( component, entries, ) or is_engine_mcp_gelios_items_envelope_slice( component, entries, ) or is_engine_mcp_registered_execution_profiles_slice( component, entries, ) or is_engine_mcp_gelios_units_items_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) or is_engine_mcp_autonomy_provider_v5_slice(component, entries) or is_engine_provider_security_catalog_slice(component, entries) ) and services is not None ): try: restored_state = rollback_engine_apply( root, backup_dir, entries, current_stamp, runtime_started, services, ) rollback_status = f"ok:engine-overlay:{restored_state}" print(f"engine-automatic-rollback={rollback_status}", file=sys.stderr) except Exception as rollback_exc: rollback_status = f"failed:{type(rollback_exc).__name__}" print("engine-automatic-rollback=failed", file=sys.stderr) elif ( component == "platform" and entries is not None and services is not None and touches_external_data_plane_files(entries) ): try: restored_state = rollback_platform_apply( root, backup_dir, entries, current_stamp, runtime_started, services, ) rollback_status = f"ok:platform-overlay:{restored_state}" print(f"platform-automatic-rollback={rollback_status}", file=sys.stderr) except Exception as rollback_exc: rollback_status = f"failed:{type(rollback_exc).__name__}" print("platform-automatic-rollback=failed", file=sys.stderr) elif ( is_device_plane_foundation_network_publication_slice( component, entries, ) and services is not None ): try: restored_state = ( rollback_device_plane_network_publication_apply( root, backup_dir, entries, current_stamp, runtime_started, services, ) ) rollback_status = ( "ok:device-plane-network-publication:" f"{restored_state}" ) print( "device-plane-network-publication-" f"automatic-rollback={rollback_status}", file=sys.stderr, ) except Exception as rollback_exc: rollback_status = ( f"failed:{type(rollback_exc).__name__}" ) print( "device-plane-network-publication-" "automatic-rollback=failed", file=sys.stderr, ) elif ( is_device_plane_backhaul_vps_enrollment_slice( component, entries, ) and device_plane_runtime_before is not None ): try: restored_state = ( rollback_device_plane_backhaul_vps_enrollment( root, backup_dir, entries, current_stamp, device_plane_runtime_before, ) ) rollback_status = ( "ok:device-plane-vps-enrollment:" f"{restored_state}" ) print( "device-plane-vps-enrollment-" f"automatic-rollback={rollback_status}", file=sys.stderr, ) except Exception as rollback_exc: rollback_status = ( f"failed:{type(rollback_exc).__name__}" ) print( "device-plane-vps-enrollment-" "automatic-rollback=failed", file=sys.stderr, ) elif ( component == "device-plane" and entries is not None and services is not None ): try: restored_state = rollback_device_plane_apply( root, backup_dir, entries, current_stamp, runtime_started, services, ) rollback_status = f"ok:device-plane-overlay:{restored_state}" print( f"device-plane-automatic-rollback={rollback_status}", file=sys.stderr, ) except Exception as rollback_exc: rollback_status = f"failed:{type(rollback_exc).__name__}" print( "device-plane-automatic-rollback=failed", file=sys.stderr, ) failed_path = None if artifact.exists() and rollback_status != "deferred:reconciliation-required": try: failed_path = move_artifact(artifact, FAILED_DIR, suffix=f".{current_stamp}") except Exception: failed_path = None append_jsonl(FAILED_STATE_FILE, { "artifact": str(failed_path.name if failed_path else artifact.name), "backup_id": backup_id, "component": manifest.get("component") if manifest else None, "failed_at": utc_now(), "id": manifest.get("id") if manifest else None, "message": str(exc), "rollback_status": rollback_status, "sha256": sha, "started_apply": apply_started, "status": ( "reconciliation-required" if rollback_status == "deferred:reconciliation-required" else "failed" ), }) if backup_id: print(f"backup={backup_id}", file=sys.stderr) raise def main(argv): if sys.version_info < (3, 8): die("Python 3.8 or newer is required") parser = argparse.ArgumentParser(prog="nodedc-deploy") sub = parser.add_subparsers(dest="cmd", required=True) plan = sub.add_parser("plan") plan.add_argument("artifact") apply = sub.add_parser("apply") apply.add_argument("artifact") sub.add_parser("verify-install") args = parser.parse_args(argv) if args.cmd == "verify-install": verify_install() return 0 artifact = Path(args.artifact).absolute() if args.cmd == "plan": plan_artifact(artifact) return 0 if args.cmd == "apply": apply_artifact(artifact) return 0 parser.print_help() return 2 if __name__ == "__main__": try: raise SystemExit(main(sys.argv[1:])) except DeployError as exc: print(f"ERROR: {exc}", file=sys.stderr) raise SystemExit(1)