from __future__ import annotations import time from typing import Any from urllib.parse import quote from ..connectors.portainer import PortainerConnector from ..core.redaction import redact_message _CONTROLLER = "gitea-deploy-control" _REPOSITORY = "jens/itworx_mcp_hub" _COMPOSE_PROJECT = "itworx-mcp-hub" _TOPOLOGY_CONTRACT = "itworx.mcp-hub-topology/17-v1" # Read-only re-attestation for an already-successful modern MCP Hub receipt. # Unknown, failed, legacy and mixed-topology states require the guarded deploy # path. Reconciliation never repairs or writes controller state. _RECONCILE_SCRIPT = r''' import fcntl import errno import hashlib import json import os from pathlib import Path import re import stat import subprocess import sys from datetime import datetime, timezone repository, expected_compose_project = sys.argv[1:3] registry_path = Path("/controller-config/projects.json") host_registry_path = Path("/controller-hub-releases/controller-config/projects.json") state_root = Path("/mnt/cache/appdata/gitea-deploy-runners/state") global_lock_path = Path("/mnt/cache/appdata/gitea-deploy-runners/deploy.lock") owned_image_source = "https://gitea.example.com/jens/itworx-mcp-hub" topology_contract = "itworx.mcp-hub-topology/17-v1" if repository != "jens/itworx_mcp_hub" or expected_compose_project != "itworx-mcp-hub": raise SystemExit(40) if registry_path.is_symlink() or not registry_path.is_file(): raise SystemExit(20) try: if host_registry_path.is_symlink() or not host_registry_path.is_file() or not os.path.samefile(registry_path, host_registry_path): raise SystemExit(36) except OSError: raise SystemExit(36) try: projects = json.loads(registry_path.read_text(encoding="utf-8")) except Exception: raise SystemExit(31) project = projects.get(repository) if isinstance(projects, dict) else None if not isinstance(project, dict): raise SystemExit(22) expected_containers = project.get("containers") expected_fixed = { "itworx-mcp-hub-connector-appops-1", "itworx-mcp-hub-connector-gitea-1", "itworx-mcp-hub-connector-knowledge-1", "itworx-mcp-hub-connector-mobilityops-1", "itworx-mcp-hub-connector-projectbrain-1", "itworx-mcp-hub-connector-stitch-1", "itworx-mcp-hub-connector-unraid-1", "itworx-mcp-hub-contextforge-1", "itworx-mcp-hub-control-api-1", "itworx-mcp-hub-dataplane-router-1", "itworx-mcp-hub-docker-read-broker-1", "itworx-mcp-hub-postgres-1", "itworx-mcp-hub-redis-1", "itworx-mcp-hub-web-1", "itworx-mcp-hub-worker-1", } expected_runtime = expected_fixed | { "itworx-mcp-hub-connector-ingestion-1", "itworx-mcp-hub-ingestion-clamd-1", } if ( project.get("branch") != "main" or project.get("slug") != expected_compose_project or project.get("compose_project") != expected_compose_project or not isinstance(expected_containers, list) or any(not isinstance(item, str) for item in expected_containers) or len(expected_containers) != len(set(expected_containers)) or frozenset(expected_containers) not in {frozenset(expected_fixed), frozenset(expected_runtime)} ): raise SystemExit(23) expected_containers = sorted(expected_runtime) state_path = state_root / f"{expected_compose_project}.json" project_lock_path = state_root / "locks" / f"{expected_compose_project}.lock" owner, name = repository.split("/", 1) bare_repo = Path("/gitea-repositories") / owner / f"{name}.git" if not bare_repo.is_dir() or bare_repo.is_symlink(): raise SystemExit(24) def git_run(*args): return subprocess.run( ["git", f"--git-dir={bare_repo}", *args], text=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=10, check=False, ) main = git_run("rev-parse", "refs/heads/main") main_revision = main.stdout.strip() if main.returncode != 0 or re.fullmatch(r"[0-9a-f]{40}", main_revision) is None: raise SystemExit(26) def main_declares_external_image(image_ref): if re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._/:@-]{1,300}@sha256:[0-9a-f]{64}", image_ref) is None: return False for compose_path in ( "infrastructure/compose/docker-compose.yml", "infrastructure/compose/docker-compose.prod.yml", ): source = git_run("show", f"{main_revision}:{compose_path}") if source.returncode != 0: return False for line in source.stdout.splitlines(): if line.strip() in {f"image: {image_ref}", f'image: "{image_ref}"', f"image: '{image_ref}'"}: return True return False def main_declares_sessionless_dataplane_image(container_name, image_ref): if ( container_name != "itworx-mcp-hub-dataplane-router-1" or image_ref != "itworx-mcp-hub/dataplane:sessionless-final" ): return False sessionless_commit = "b519a8c4cf17cd9df00a575acfe096838aab7789" resolved = git_run("rev-parse", f"{sessionless_commit}^{{commit}}") if resolved.returncode != 0 or resolved.stdout.strip() != sessionless_commit: return False if git_run("merge-base", "--is-ancestor", sessionless_commit, main_revision).returncode != 0: return False router = git_run( "show", f"{sessionless_commit}:services/dataplane_router/router.py", ) tests = git_run( "show", f"{sessionless_commit}:tests/test_dataplane_session_headers.py", ) evidence = git_run( "show", f"{main_revision}:evidence/mcp-session-boundary-20260916/SUMMARY.md", ) if router.returncode != 0 or tests.returncode != 0 or evidence.returncode != 0: return False router_contract = ( 'MCP_TRANSPORT_STATE_HEADERS = frozenset({b"mcp-session-id"})', 'if "mcp-session-id" in response.headers:', 'del response.headers["mcp-session-id"]', ) test_contract = ( "2025-11-25", "2026-07-28", "mcp-session-id", ) evidence_contract = ( sessionless_commit, "itworx-mcp-hub/dataplane:sessionless-final", "permanently enforce stateless public transport", ) return ( all(fragment in router.stdout for fragment in router_contract) and all(fragment in tests.stdout for fragment in test_contract) and all(fragment in evidence.stdout for fragment in evidence_contract) ) def main_declares_contextforge_compat_image(container_name, image_ref): if container_name != "itworx-mcp-hub-contextforge-1": return False candidate_reference = ( re.fullmatch(r"[0-9a-f]{40}", recorded_live) is not None and image_ref == f"itworx-mcp-hub/contextforge-schema:{recorded_live}" ) # The caller already binds recorded_live to the receipt and protected-main # ancestry. Read a candidate's build contract from that exact source tree. proof_revision = recorded_live if candidate_reference else main_revision manifest = git_run( "show", f"{proof_revision}:infrastructure/images/contextforge/compat-image.env", ) builder = git_run( "show", f"{proof_revision}:scripts/build_contextforge_compat_image.sh", ) if manifest.returncode != 0 or builder.returncode != 0: return False values = {} for raw_line in manifest.stdout.splitlines(): line = raw_line.strip() if not line or line.startswith("#"): continue if "=" not in line: return False key, value = line.split("=", 1) if key in values: return False values[key] = value.strip().strip('"').strip("'") expected_keys = { "CONTEXTFORGE_UPSTREAM_COMMIT", "CONTEXTFORGE_ALEMBIC_HEAD", "CONTEXTFORGE_MCP_SDK", "CONTEXTFORGE_LOCAL_IMAGE", } upstream_commit = str(values.get("CONTEXTFORGE_UPSTREAM_COMMIT") or "") alembic_head = str(values.get("CONTEXTFORGE_ALEMBIC_HEAD") or "") mcp_sdk = str(values.get("CONTEXTFORGE_MCP_SDK") or "") local_image = str(values.get("CONTEXTFORGE_LOCAL_IMAGE") or "") if ( set(values) != expected_keys or re.fullmatch(r"[0-9a-f]{40}", upstream_commit) is None or re.fullmatch(r"[0-9a-f]{12}", alembic_head) is None or re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", mcp_sdk) is None or (local_image != image_ref and not candidate_reference) or local_image != f"itworx-mcp-hub/contextforge-schema:{alembic_head}" ): return False required_builder_contract = ( 'git -C "$SRC" fetch --depth 1 origin "$CONTEXTFORGE_UPSTREAM_COMMIT"', 'git -C "$SRC" checkout --detach FETCH_HEAD', 'MCP_REQUIREMENT="mcp==${CONTEXTFORGE_MCP_SDK}"', 'alembic -c mcpgateway/alembic.ini heads', 'docker tag "$STAGED_IMAGE" "$CONTEXTFORGE_LOCAL_IMAGE"', ) if candidate_reference: required_builder_contract += ( '--release-candidate', 'CANDIDATE_IMAGE="itworx-mcp-hub/contextforge-schema:${APPOPS_SOURCE_REVISION}"', 'docker tag "$STAGED_IMAGE" "$CANDIDATE_IMAGE"', ) return all(fragment in builder.stdout for fragment in required_builder_contract) def live_attested_mounts(container): raw_mounts = container.get("Mounts") if not isinstance(raw_mounts, list): raise SystemExit(52) observed = [] for raw in raw_mounts: if not isinstance(raw, dict): raise SystemExit(52) kind = str(raw.get("Type") or "") destination = str(raw.get("Destination") or "") if kind not in {"bind", "volume", "tmpfs"} or not destination: raise SystemExit(52) source = str(raw.get("Source") or "") if kind in {"bind", "volume"} else "" name = str(raw.get("Name") or "") if kind == "volume" else "" mount = { "type": kind, "source": os.path.normpath(source) if source else "", "destination": destination, "rw": raw.get("RW") is True, "name": name, } if kind == "volume": if not name: raise SystemExit(52) volume = subprocess.run( ["docker", "volume", "inspect", name], text=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=10, check=False, ) if volume.returncode != 0: raise SystemExit(52) try: volume_items = json.loads(volume.stdout) volume_item = volume_items[0] options = volume_item.get("Options") or {} mountpoint = os.path.normpath(str(volume_item.get("Mountpoint") or "")) created_at = volume_item.get("CreatedAt") created = datetime.fromisoformat(str(created_at).replace("Z", "+00:00")) except Exception: raise SystemExit(52) if ( not isinstance(options, dict) or str(volume_item.get("Name") or "") != name or mountpoint != mount["source"] or str(volume_item.get("Driver") or "") != str(raw.get("Driver") or "") or not isinstance(created_at, str) or created.tzinfo is None or created.utcoffset() is None or created.astimezone(timezone.utc) > datetime.now(timezone.utc) ): raise SystemExit(52) mount["driver"] = str(volume_item.get("Driver") or "") mount["options"] = {str(key): str(value) for key, value in options.items()} mount["created_at"] = created_at observed.append(mount) return sorted(observed, key=lambda item: str(item["destination"])) def fixed_image_reference(service_name, image_ref): if service_name == "dataplane-router" and image_ref == "itworx-mcp-hub/dataplane:sessionless-final": return main_declares_sessionless_dataplane_image( "itworx-mcp-hub-dataplane-router-1", image_ref ) owned = { "connector-appops": "gitea.example.com/jens/itworx-mcp-hub/connector-appops", "connector-gitea": "gitea.example.com/jens/itworx-mcp-hub/connector-gitea", "connector-ingestion": "gitea.example.com/jens/itworx-mcp-hub/connector-ingestion", "connector-knowledge": "gitea.example.com/jens/itworx-mcp-hub/connector-knowledge", "connector-mobilityops": "gitea.example.com/jens/itworx-mcp-hub/connector-mobilityops", "connector-projectbrain": "gitea.example.com/jens/itworx-mcp-hub/connector-projectbrain", "connector-stitch": "gitea.example.com/jens/itworx-mcp-hub/connector-stitch", "connector-unraid": "gitea.example.com/jens/itworx-mcp-hub/connector-unraid", "control-api": "gitea.example.com/jens/itworx-mcp-hub/control-api", "dataplane-router": "gitea.example.com/jens/itworx-mcp-hub/dataplane", "docker-read-broker": "gitea.example.com/jens/itworx-mcp-hub/docker-read-broker", "web": "gitea.example.com/jens/itworx-mcp-hub/web", "worker": "gitea.example.com/jens/itworx-mcp-hub/worker", } if service_name in owned: return ( re.fullmatch(r"sha256:[0-9a-f]{64}", image_ref) is not None or ( image_ref.startswith(owned[service_name] + "@sha256:") and re.fullmatch(r".+@sha256:[0-9a-f]{64}", image_ref) is not None ) ) if service_name == "postgres": if image_ref == "postgres:18.4@sha256:3a82e1f56c8f0f5616a11103ac3d47e632c3938698946a7ad26da0df1334744a": return True if image_ref in { "postgres:18.6@sha256:0377e72c5289ed2f98cf61b1a9c2db9eb9d300317fe14244492fbc94343b3d04", ( "gitea.example.com/jens/itworx-mcp-hub/postgres:" "wp400-gosu-v2-20261002-a94e0b4d2029@sha256:" "a60d81e06987671ae903757f1b71cdfbbff4d481b42610e4de2b7dcfa32aad73" ), }: return main_declares_external_image(image_ref) return False if service_name == "ingestion-clamd": return image_ref == "clamav/clamav:1.4.6@sha256:a5f03c12a79dbe9f6d8a527b6bb1ea053fa8dd061d3738a26897f055ee2d9303" if service_name == "redis": if image_ref == "redis:8.10.0@sha256:c29e49ab2f85760a3827b53882e6dd9f5c6c3f0bb7d724e07bb31cbf275a5236": return True if image_ref in { "redis:8.10.2@sha256:7ef5b5cec96495a04ca7feff88a9492efeab8053fb284d24bdd73344c9245a48", ( "gitea.example.com/jens/itworx-mcp-hub/redis:" "wp400-remediation-20261002-4e18f61c@sha256:" "7d826b44f245ad32e8fe4bf40d42eae95855c3942c5f2e42784ee8fd3f9547a2" ), }: return main_declares_external_image(image_ref) return False if service_name == "contextforge": return main_declares_contextforge_compat_image( "itworx-mcp-hub-contextforge-1", image_ref ) return False def image_digest_matches(image, image_ref): repository, separator, digest = image_ref.partition("@") if not separator or re.fullmatch(r"sha256:[0-9a-f]{64}", digest) is None: return False first, slash, last = repository.partition("/") if slash: if ":" in last: last = last.rsplit(":", 1)[0] repository = first + slash + last elif ":" in repository: repository = repository.rsplit(":", 1)[0] aliases = {repository} first_component = repository.split("/", 1)[0] is_registry = "." in first_component or ":" in first_component or first_component == "localhost" if not is_registry: if "/" in repository: aliases.add("docker.io/" + repository) else: aliases.add("docker.io/library/" + repository) if repository.startswith("docker.io/"): aliases.add(repository.removeprefix("docker.io/")) digests = image.get("RepoDigests", []) return isinstance(digests, list) and any( "@" in str(item) and str(item).split("@", 1)[0] in aliases and str(item).split("@", 1)[1] == digest for item in digests ) def receipt_bound_image_reference(launch_image, image_id, recorded, receipt_item): if not isinstance(recorded, dict) or not isinstance(receipt_item, dict): raise SystemExit(52) image_ref = receipt_item.get("image_ref") if ( re.fullmatch(r"sha256:[0-9a-f]{64}", image_id) is None or not isinstance(image_ref, str) or not image_ref or recorded.get("image_ref") != image_ref or recorded.get("image_id") != image_id or receipt_item.get("image_id") != image_id or launch_image not in {image_ref, image_id} or (image_ref.startswith("sha256:") and image_ref != image_id) ): raise SystemExit(52) return image_ref def trusted_private_path(path, expected_mode, *, directory): try: info = path.lstat() except OSError: return False if ( path.is_symlink() or (not stat.S_ISDIR(info.st_mode) if directory else not stat.S_ISREG(info.st_mode)) or info.st_uid != 0 or (stat.S_IMODE(info.st_mode) != expected_mode if directory else stat.S_IMODE(info.st_mode) & 0o077 != 0) ): return False try: attributes = os.listxattr(path, follow_symlinks=False) except OSError as exc: if exc.errno in {errno.ENOTSUP, getattr(errno, "EOPNOTSUPP", errno.ENOTSUP)}: attributes = [] else: return False return not any( item in {"system.posix_acl_access", "system.posix_acl_default"} for item in attributes ) def validate_migration_intent_state( state_root, state_bytes, repository, recorded_live, recorded_status, recorded_live_status, last_attempt, attempt_status, attempt_revision, ): attempt_metadata = dict(last_attempt.get("metadata") or {}) migration_attempt_id = str(attempt_metadata.get("mcpHubMigrationAttemptId") or "") migration_root = state_root / "mcp-hub-topology-migrations" migration_path = migration_root / "active-intent.json" if migration_root.is_symlink(): raise SystemExit(56) if migration_root.exists() and not trusted_private_path(migration_root, 0o700, directory=True): raise SystemExit(56) if migration_path.is_symlink(): raise SystemExit(56) if migration_path.exists(): if ( not trusted_private_path(migration_path, 0o600, directory=False) or stat.S_IMODE(migration_path.lstat().st_mode) != 0o600 ): raise SystemExit(56) try: intent_bytes = migration_path.read_bytes() intent = json.loads(intent_bytes.decode("utf-8")) except Exception: raise SystemExit(56) common_intent_fields = { "schema", "status", "attemptId", "repository", "candidateRevision", "priorLiveRevision", "backupManifestSha256", "backupComposeSha256", "baselineStateSha256", "startedAtEpoch", } observed_intent_fields = {"observedBaselinePath", "observedBaselineSha256"} observed_fields_present = set(intent) & observed_intent_fields if isinstance(intent, dict) else set() if observed_fields_present not in (set(), observed_intent_fields): raise SystemExit(56) required_intent_fields = common_intent_fields | observed_fields_present if isinstance(intent, dict) and intent.get("status") == "complete": required_intent_fields |= {"completedAtEpoch", "completedStateSha256"} if ( not isinstance(intent, dict) or intent.get("status") != "complete" or set(intent) != required_intent_fields or intent.get("schema") != "itworx.mcp-hub-migration-intent/v1" or intent.get("repository") != repository or re.fullmatch(r"[0-9a-f]{32}", str(intent.get("attemptId") or "")) is None or intent.get("attemptId") != migration_attempt_id or intent.get("candidateRevision") != recorded_live or re.fullmatch(r"[0-9a-f]{40}", str(intent.get("priorLiveRevision") or "")) is None or any( re.fullmatch(r"[0-9a-f]{64}", str(intent.get(field) or "")) is None for field in ("backupManifestSha256", "backupComposeSha256", "baselineStateSha256", "completedStateSha256") ) or type(intent.get("startedAtEpoch")) is not int or type(intent.get("completedAtEpoch")) is not int or intent.get("completedAtEpoch") < intent.get("startedAtEpoch") or intent.get("completedStateSha256") != hashlib.sha256(state_bytes).hexdigest() or recorded_status != "success" or recorded_live_status != "healthy" or attempt_status != "success" or attempt_revision != recorded_live or not isinstance(attempt_metadata.get("mcpHubBackupReceipt"), dict) or set(attempt_metadata.get("mcpHubBackupReceipt", {})) != { "schema", "repository", "revision", "backupDirectory", "manifestSha256", "composeSha256", "restoreVerified", "createdAtEpoch", "receiptPath", } or attempt_metadata.get("mcpHubBackupReceipt", {}).get("schema") != "itworx.mcp-hub-backup-receipt/v1" or attempt_metadata.get("mcpHubBackupReceipt", {}).get("repository") != repository or attempt_metadata.get("mcpHubBackupReceipt", {}).get("revision") != recorded_live or attempt_metadata.get("mcpHubBackupReceipt", {}).get("restoreVerified") is not True or type(attempt_metadata.get("mcpHubBackupReceipt", {}).get("createdAtEpoch")) is not int or re.fullmatch(r"[0-9a-f]{64}", str(attempt_metadata.get("mcpHubBackupReceipt", {}).get("manifestSha256") or "")) is None or re.fullmatch(r"[0-9a-f]{64}", str(attempt_metadata.get("mcpHubBackupReceipt", {}).get("composeSha256") or "")) is None or attempt_metadata.get("mcpHubBackupReceipt", {}).get("manifestSha256") != intent.get("backupManifestSha256") or attempt_metadata.get("mcpHubBackupReceipt", {}).get("composeSha256") != intent.get("backupComposeSha256") ): raise SystemExit(56) backup_receipt = attempt_metadata.get("mcpHubBackupReceipt", {}) receipt_root = state_root / "mcp-hub-backup-receipts" backup_path = Path(str(backup_receipt.get("receiptPath") or "")) if ( backup_path.parent != receipt_root or re.fullmatch( rf"[0-9]+-{recorded_live[:12]}-[0-9a-f]{{16}}\.json", backup_path.name, ) is None or backup_path.is_symlink() or not trusted_private_path(receipt_root, 0o700, directory=True) or not trusted_private_path(backup_path, 0o600, directory=False) or stat.S_IMODE(backup_path.lstat().st_mode) != 0o600 ): raise SystemExit(56) try: stored_backup_receipt = json.loads(backup_path.read_text(encoding="utf-8")) except Exception: raise SystemExit(56) if stored_backup_receipt != { key: value for key, value in backup_receipt.items() if key != "receiptPath" }: raise SystemExit(56) backup_root = state_root / "mcp-hub-backups" backup_directory = Path(str(backup_receipt.get("backupDirectory") or "")) manifest_path = backup_directory / "manifest.json" if ( backup_directory.parent != backup_root or re.fullmatch( rf"[0-9]+-{recorded_live[:12]}-[0-9a-f]{{16}}", backup_directory.name, ) is None or not trusted_private_path(backup_root, 0o700, directory=True) or not trusted_private_path(backup_directory, 0o700, directory=True) or not trusted_private_path(manifest_path, 0o600, directory=False) or stat.S_IMODE(manifest_path.lstat().st_mode) != 0o600 ): raise SystemExit(56) try: manifest_bytes = manifest_path.read_bytes() manifest = json.loads(manifest_bytes.decode("utf-8")) except Exception: raise SystemExit(56) if ( hashlib.sha256(manifest_bytes).hexdigest() != intent.get("backupManifestSha256") or not isinstance(manifest, dict) or manifest.get("schema") != "itworx.mcp-hub-backup/v1" or manifest.get("repository") != repository or manifest.get("revision") != recorded_live or manifest.get("backupDirectory") != str(backup_directory) or manifest.get("composeSha256") != intent.get("backupComposeSha256") or manifest.get("configRestoreVerified") is not True or dict(manifest.get("restore") or {}).get("status") != "verified" or set(dict(manifest.get("databases") or {})) != {"itworx_mcp_hub", "contextforge"} ): raise SystemExit(56) if observed_fields_present: observed_path = Path(str(intent.get("observedBaselinePath") or "")) if ( observed_path.parent != migration_root or re.fullmatch( rf"observed-baseline-{recorded_live[:12]}-[0-9a-f]{{20}}\.json", observed_path.name, ) is None or observed_path.is_symlink() or not trusted_private_path(observed_path, 0o600, directory=False) or stat.S_IMODE(observed_path.lstat().st_mode) != 0o600 ): raise SystemExit(56) try: observed_bytes = observed_path.read_bytes() observed = json.loads(observed_bytes.decode("utf-8")) except Exception: raise SystemExit(56) if ( hashlib.sha256(observed_bytes).hexdigest() != intent.get("observedBaselineSha256") or not isinstance(observed, dict) or observed.get("schema") != "itworx.mcp-hub-observed-baseline/v1" or observed.get("claim") != "observed_mixed_runtime_facts_only" or observed.get("candidateRevision") != recorded_live or observed.get("historicalStateSha256") != intent.get("baselineStateSha256") or observed.get("backupManifestSha256") != intent.get("backupManifestSha256") or observed.get("backupComposeSha256") != intent.get("backupComposeSha256") or observed.get("backupRestoreVerified") is not True or not isinstance(observed.get("postgresVolumeBaseline"), list) or len(observed.get("postgresVolumeBaseline", [])) != 2 ): raise SystemExit(56) elif migration_attempt_id: raise SystemExit(56) try: project_lock_fd = os.open(project_lock_path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)) global_lock_fd = os.open(global_lock_path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)) except Exception: raise SystemExit(29) with os.fdopen(project_lock_fd, "rb") as project_lock, os.fdopen(global_lock_fd, "rb") as global_lock: try: fcntl.flock(project_lock, fcntl.LOCK_EX) except Exception: raise SystemExit(30) with global_lock: try: fcntl.flock(global_lock, fcntl.LOCK_EX) except Exception: raise SystemExit(30) try: current_projects = json.loads(registry_path.read_text(encoding="utf-8")) except Exception: raise SystemExit(31) if current_projects.get(repository) != project: raise SystemExit(37) if ( not trusted_private_path(state_root, 0o700, directory=True) or not trusted_private_path(state_root / "locks", 0o700, directory=True) or not trusted_private_path(project_lock_path, 0o600, directory=False) or not trusted_private_path(global_lock_path, 0o600, directory=False) or not trusted_private_path(state_path, 0o600, directory=False) ): raise SystemExit(42) try: state_bytes = state_path.read_bytes() state = json.loads(state_bytes.decode("utf-8")) except Exception: raise SystemExit(42) if not isinstance(state, dict) or state.get("schema_version") != 3 or state.get("repository") != repository: raise SystemExit(43) recorded_live = str(state.get("live_revision") or state.get("revision") or "") recorded_status = str(state.get("status") or "") recorded_live_status = str(state.get("live_status") or "") last_attempt = dict(state.get("last_attempt") or {}) attempt_revision = str(last_attempt.get("revision") or "") attempt_status = str(last_attempt.get("status") or "") receipt = state.get("live_receipt") recorded_artifacts = state.get("live_artifacts") validate_migration_intent_state( state_root, state_bytes, repository, recorded_live, recorded_status, recorded_live_status, last_attempt, attempt_status, attempt_revision, ) registered_live_containers = ( recorded_artifacts.get("containers") if isinstance(recorded_artifacts, dict) else None ) if isinstance(registered_live_containers, dict) and set(registered_live_containers) == expected_fixed: print("mcp-hub-topology-migration-required", flush=True) raise SystemExit(56) if set(expected_containers) != expected_runtime: raise SystemExit(56) receipt_payload = receipt if isinstance(receipt, dict) else {} receipt_tree = str(receipt_payload.get("tree") or "") receipt_tree_digest = str(receipt_payload.get("treeDigest") or "") raw_receipt_build = receipt_payload.get("build") receipt_build = raw_receipt_build if isinstance(raw_receipt_build, dict) else {} expected_build = { "id": recorded_live[:12], "repository": repository, "revision": recorded_live, "tree": receipt_tree, "treeDigest": receipt_tree_digest, "builtAt": str(receipt_build.get("builtAt") or ""), } receipt_identity_valid = ( re.fullmatch(r"[0-9a-f]{40}", receipt_tree) is not None and receipt_tree_digest == hashlib.sha256( f"git-tree:{receipt_tree}".encode("utf-8") ).hexdigest() and receipt_build == expected_build and re.fullmatch( r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z", expected_build["builtAt"], ) is not None and isinstance(recorded_artifacts, dict) and set(recorded_artifacts) == {"containers", "compose_sha256", "topology_contract"} and re.fullmatch( r"[0-9a-f]{64}", str(recorded_artifacts.get("compose_sha256") or "") ) is not None ) already_reconciled = ( recorded_status == "success" and recorded_live_status == "healthy" and state.get("revision") == recorded_live and state.get("live_legacy_provenance") is False and re.fullmatch(r"[0-9a-f]{40}", recorded_live) is not None and isinstance(recorded_artifacts, dict) and state.get("compose_project") == expected_compose_project and state.get("slug") == expected_compose_project and attempt_status == "success" and attempt_revision == recorded_live and isinstance(registered_live_containers, dict) and set(registered_live_containers) == expected_runtime and recorded_artifacts.get("topology_contract") == topology_contract and state.get("artifacts") == recorded_artifacts and isinstance(receipt, dict) and set(receipt) == {"schema", "repository", "revision", "tree", "treeDigest", "build", "containers", "live"} and receipt.get("schema") == "gitea-deploy-receipt.v1" and receipt.get("repository") == repository and receipt.get("revision") == recorded_live and receipt_identity_valid and isinstance(receipt.get("containers"), dict) and set(dict(receipt.get("containers") or {})) == expected_runtime and receipt.get("live") is None ) if not already_reconciled: raise SystemExit(56 if recorded_live_status in {"unknown", "healthy"} else 52) resolved_live = git_run("rev-parse", f"{recorded_live}^{{commit}}") if resolved_live.returncode != 0 or resolved_live.stdout.strip() != recorded_live: raise SystemExit(25) if git_run("merge-base", "--is-ancestor", recorded_live, main_revision).returncode != 0: raise SystemExit(52) listed = subprocess.run( [ "docker", "ps", "--all", "--filter", f"label=com.docker.compose.project={expected_compose_project}", "--format", "{{.Names}}", ], text=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=10, check=False, ) project_names = [line.strip() for line in listed.stdout.splitlines() if line.strip()] if listed.returncode != 0 or set(project_names) != set(expected_containers): raise SystemExit(47) old_containers = recorded_artifacts.get("containers") receipt_containers = dict(receipt.get("containers") or {}) if not isinstance(old_containers, dict) or set(old_containers) != set(expected_containers): raise SystemExit(52) for name in expected_containers: inspected = subprocess.run( ["docker", "inspect", name], text=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=10, check=False, ) if inspected.returncode != 0: raise SystemExit(44) try: container = json.loads(inspected.stdout)[0] except Exception: raise SystemExit(44) runtime_state = dict(container.get("State") or {}) health = str(dict(runtime_state.get("Health") or {}).get("Status") or "none") config = dict(container.get("Config") or {}) labels = dict(config.get("Labels") or {}) name_service = name.removeprefix("itworx-mcp-hub-").removesuffix("-1") image_id = str(container.get("Image") or "") launch_image = str(config.get("Image") or "") container_id = str(container.get("Id") or "") if runtime_state.get("Running") is not True or health != "healthy": raise SystemExit(45) if ( labels.get("com.docker.compose.project") != expected_compose_project or labels.get("com.docker.compose.service") != name_service ): raise SystemExit(47) if ( re.fullmatch(r"sha256:[0-9a-f]{64}", image_id) is None or not launch_image or not container_id ): raise SystemExit(46) recorded = old_containers.get(name) receipt_item = receipt_containers.get(name) image_ref = receipt_bound_image_reference( launch_image, image_id, recorded, receipt_item ) image_result = subprocess.run( ["docker", "image", "inspect", image_id], text=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=10, check=False, ) if image_result.returncode != 0: raise SystemExit(50) try: image = json.loads(image_result.stdout)[0] except Exception: raise SystemExit(50) image_labels = dict(dict(image.get("Config") or {}).get("Labels") or {}) artifact = { "container_id": container_id, "image_id": image_id, "image_ref": image_ref, "container_revision": str(labels.get("org.opencontainers.image.revision") or ""), "image_revision": str(image_labels.get("org.opencontainers.image.revision") or ""), "image_source": str(image_labels.get("org.opencontainers.image.source") or ""), "image_tree": str(image_labels.get("org.opencontainers.image.source-tree") or ""), "image_version": str(image_labels.get("org.opencontainers.image.version") or ""), "image_created": str(image_labels.get("org.opencontainers.image.created") or ""), } if str(image.get("Id") or "") != image_id: raise SystemExit(46) if ( set(recorded) != set(artifact) | {"mounts_json"} or set(receipt_item) != set(artifact) | {"mounts_sha256"} ): raise SystemExit(52) for key, value in artifact.items(): if recorded.get(key) != value or receipt_item.get(key) != value: raise SystemExit(52) raw_mounts = recorded.get("mounts_json") if not isinstance(raw_mounts, str): raise SystemExit(52) try: recorded_mounts = json.loads(raw_mounts) except Exception: raise SystemExit(52) mount_bytes = json.dumps( recorded_mounts, sort_keys=True, separators=(",", ":") ).encode("utf-8") if ( not isinstance(recorded_mounts, list) or hashlib.sha256(mount_bytes).hexdigest() != receipt_item.get("mounts_sha256") or live_attested_mounts(container) != recorded_mounts ): raise SystemExit(52) if not fixed_image_reference(name_service, image_ref): raise SystemExit(51) owned_service = name_service in { "connector-appops", "connector-gitea", "connector-ingestion", "connector-knowledge", "connector-mobilityops", "connector-projectbrain", "connector-stitch", "connector-unraid", "control-api", "dataplane-router", "docker-read-broker", "web", "worker", } if owned_service and image_ref != "itworx-mcp-hub/dataplane:sessionless-final": if ( artifact["image_source"] != owned_image_source or artifact["image_revision"] != recorded_live or artifact["image_tree"] != receipt_tree or artifact["image_version"] != recorded_live[:12] or artifact["image_created"] != expected_build["builtAt"] ): raise SystemExit(51) if "@sha256:" in image_ref and not image_digest_matches(image, image_ref): raise SystemExit(51) try: if state_path.read_bytes() != state_bytes: raise SystemExit(52) current_projects = json.loads(registry_path.read_text(encoding="utf-8")) except Exception: raise SystemExit(37) if current_projects.get(repository) != project: raise SystemExit(37) print("already-reconciled", flush=True) ''' class McpHubDeploymentReconciliation: """Read-only verify only an existing, exact modern MCP Hub success receipt.""" def __init__(self, portainer: PortainerConnector | None = None): self.portainer = portainer or PortainerConnector() def run(self) -> dict[str, Any]: status = self.portainer.get_container_status(_CONTROLLER) if status.get("status") != "ok": return { "status": "controller_unavailable", "reason": redact_message( str(status.get("reason") or status.get("status") or "unavailable") ), } controller = dict(status.get("container") or {}) if controller.get("name") != _CONTROLLER or controller.get("state") != "running": return { "status": "controller_unavailable", "reason": "deployment controller is not running", } container_id = str(controller.get("id") or "") config = self.portainer._config() if not config or not container_id: return { "status": "connector_unavailable", "reason": "Portainer deployment control is unavailable", } command = ["python3", "-c", _RECONCILE_SCRIPT, _REPOSITORY, _COMPOSE_PROJECT] endpoint = quote(str(config.endpoint_id), safe="") safe_container = quote(container_id, safe="") try: created = self.portainer._request( "POST", f"/api/endpoints/{endpoint}/docker/containers/{safe_container}/exec", json={ "AttachStdout": False, "AttachStderr": False, "Tty": False, "Cmd": command, }, ).json() exec_id = str(created.get("Id") or "") if not exec_id: return {"status": "error", "reason": "MCP Hub state reconciliation could not start"} safe_exec = quote(exec_id, safe="") self.portainer._request( "POST", f"/api/endpoints/{endpoint}/docker/exec/{safe_exec}/start", json={"Detach": True, "Tty": False}, ) deadline = time.monotonic() + 60 exit_code: int | None = None while time.monotonic() < deadline: inspected = self.portainer._request( "GET", f"/api/endpoints/{endpoint}/docker/exec/{safe_exec}/json" ).json() if not bool(inspected.get("Running")): raw_exit = inspected.get("ExitCode") exit_code = int(raw_exit) if raw_exit is not None else -1 break time.sleep(0.25) except (RuntimeError, ValueError, TypeError) as exc: return {"status": "error", "reason": redact_message(str(exc))} if exit_code is None: return {"status": "error", "reason": "MCP Hub state reconciliation timed out"} reasons = { 20: "deployment projects registry is unavailable or unsafe", 22: "MCP Hub deployment target is missing", 23: "MCP Hub deployment target differs from the bounded production topology", 24: "mounted MCP Hub bare repository is unavailable", 25: "MCP Hub deployment revisions cannot be resolved", 26: "MCP Hub main cannot be resolved", 29: "deployment lock files cannot be opened", 30: "deployment locks cannot be acquired", 31: "deployment projects registry cannot be read", 36: "deployment projects registry host alias is unavailable or mismatched", 37: "MCP Hub deployment target changed while reconciliation was waiting", 40: "fixed MCP Hub reconciliation contract was altered", 42: "MCP Hub deployment state file is unavailable or invalid", 43: "MCP Hub deployment state is not a modern controller state", 44: "a live MCP Hub container cannot be inspected", 45: "a live MCP Hub container is not running and healthy", 46: "a live MCP Hub container has an invalid immutable image identity", 47: "live MCP Hub Compose project differs from the controller target", 50: "a live MCP Hub image cannot be inspected", 51: "live MCP Hub image revision or external immutable image is not proven by the recorded-live to main chain", 52: "controller state or live facts do not match the exact modern MCP Hub receipt", 56: "MCP Hub legacy, unknown, mixed, or unresolved migration state requires guarded deployment or data-aware recovery", } if exit_code != 0: if exit_code == 56: return { "status": "topology_migration_required", "repository": _REPOSITORY, "composeProject": _COMPOSE_PROJECT, "reason": reasons[exit_code], } return { "status": "validation_failed", "reason": reasons.get(exit_code, "MCP Hub deployment reconciliation failed closed"), } return { "status": "reconciled", "repository": _REPOSITORY, "composeProject": _COMPOSE_PROJECT, }