import ast import hashlib import json import os import re import subprocess import tempfile from datetime import datetime, timezone from pathlib import Path from types import SimpleNamespace import pytest from backend.app.services.mcp_hub_deployment_reconciliation import ( McpHubDeploymentReconciliation, ) class FakeResponse: def __init__(self, payload): self.payload = payload def json(self): return self.payload class FakePortainer: def __init__(self, exit_code=0): self.exit_code = exit_code self.calls = [] def get_container_status(self, name): assert name == "gitea-deploy-control" return { "status": "ok", "container": { "id": "controller-id", "name": "gitea-deploy-control", "state": "running", }, } def _config(self): return SimpleNamespace(endpoint_id="3") def _request(self, method, path, **kwargs): self.calls.append((method, path, kwargs)) if path.endswith("/containers/controller-id/exec"): return FakeResponse({"Id": "exec-1"}) if path.endswith("/exec/exec-1/start"): return FakeResponse({}) if path.endswith("/exec/exec-1/json"): return FakeResponse({"Running": False, "ExitCode": self.exit_code}) raise AssertionError(path) def test_reconciliation_is_fixed_and_read_only_for_mcp_hub(): fake = FakePortainer() result = McpHubDeploymentReconciliation(fake).run() assert result == { "status": "reconciled", "repository": "jens/itworx_mcp_hub", "composeProject": "itworx-mcp-hub", } command = fake.calls[0][2]["json"]["Cmd"] assert command[:2] == ["python3", "-c"] assert command[3:] == ["jens/itworx_mcp_hub", "itworx-mcp-hub"] script = command[2] assert "/controller-config/projects.json" in script assert "/controller-hub-releases/controller-config/projects.json" in script assert "/mnt/cache/appdata/gitea-deploy-runners/state" in script assert "os.path.samefile(registry_path, host_registry_path)" in script assert 'os.open(project_lock_path, os.O_RDONLY' in script assert 'os.open(global_lock_path, os.O_RDONLY' in script assert 'state_root.mkdir' not in script assert 'project_lock_path.parent.mkdir' not in script assert 'os.replace(' not in script assert 'shutil.copy2(' not in script assert 'state_path.write_text(' not in script assert '"status": "success"' not in script assert '"live_legacy_provenance": True' not in script def test_unknown_or_legacy_state_requires_the_guarded_deployment_path(): result = McpHubDeploymentReconciliation(FakePortainer(exit_code=56)).run() assert result == { "status": "topology_migration_required", "repository": "jens/itworx_mcp_hub", "composeProject": "itworx-mcp-hub", "reason": "MCP Hub legacy, unknown, mixed, or unresolved migration state requires guarded deployment or data-aware recovery", } fake = FakePortainer() McpHubDeploymentReconciliation(fake).run() script = fake.calls[0][2]["json"]["Cmd"][2] assert 'set(registered_live_containers) == expected_fixed' in script assert 'raise SystemExit(56 if recorded_live_status in {"unknown", "healthy"} else 52)' in script assert 'recorded_status == "success"' in script assert 'recorded_live_status == "healthy"' in script assert 'state.get("live_legacy_provenance") is False' in script assert 'recorded_artifacts.get("topology_contract") == topology_contract' in script assert 'receipt.get("schema") == "gitea-deploy-receipt.v1"' in script assert 'receipt.get("live") is None' in script def test_modern_success_requires_a_complete_exact_receipt_and_protected_ancestry(): fake = FakePortainer() McpHubDeploymentReconciliation(fake).run() script = fake.calls[0][2]["json"]["Cmd"][2] assert 'set(receipt) == {"schema", "repository", "revision", "tree", "treeDigest", "build", "containers", "live"}' in script assert 'receipt_identity_valid' in script assert 'receipt.get("revision") == recorded_live' in script assert 'git_run("merge-base", "--is-ancestor", recorded_live, main_revision)' in script assert 'set(dict(receipt.get("containers") or {})) == expected_runtime' in script assert 'print("already-reconciled", flush=True)' in script assert 'print("reconciled", flush=True)' not in script assert 'migration_path = migration_root / "active-intent.json"' in script assert 'intent.get("status") != "complete"' in script assert 'elif migration_attempt_id:' in script assert 'intent.get("completedStateSha256") != hashlib.sha256(state_bytes).hexdigest()' in script assert 'observed.get("historicalStateSha256") != intent.get("baselineStateSha256")' in script assert 'rf"[0-9]+-{recorded_live[:12]}-[0-9a-f]{{16}}"' in script def test_reconciliation_intent_guard_rejects_unknown_and_accepts_exact_complete_intent(): fake = FakePortainer() McpHubDeploymentReconciliation(fake).run() script = fake.calls[0][2]["json"]["Cmd"][2] parsed = ast.parse(script) function = next( node for node in parsed.body if isinstance(node, ast.FunctionDef) and node.name == "validate_migration_intent_state" ) namespace = { "Path": Path, "hashlib": hashlib, "json": json, "os": os, "re": re, "stat": SimpleNamespace(S_IMODE=lambda _mode: 0o600), "trusted_private_path": lambda *_args, **_kwargs: True, } exec(compile(ast.Module(body=[function], type_ignores=[]), "intent-test", "exec"), namespace) validate = namespace["validate_migration_intent_state"] repository = "jens/itworx_mcp_hub" candidate = "b" * 40 migration_attempt = "a" * 32 state_bytes = b"exact-success-state-bytes" for raw in (b'{"status":"started"}', b"not-json"): with tempfile.TemporaryDirectory() as temporary: state_root = Path(temporary) migration_root = state_root / "mcp-hub-topology-migrations" migration_root.mkdir() (migration_root / "active-intent.json").write_bytes(raw) last_attempt = {"metadata": {"mcpHubMigrationAttemptId": migration_attempt}} with pytest.raises(SystemExit) as exc: validate( state_root, state_bytes, repository, candidate, "success", "healthy", last_attempt, "success", candidate, ) assert exc.value.code == 56 with tempfile.TemporaryDirectory() as temporary: last_attempt = {"metadata": {"mcpHubMigrationAttemptId": migration_attempt}} with pytest.raises(SystemExit) as exc: validate( Path(temporary), state_bytes, repository, candidate, "success", "healthy", last_attempt, "success", candidate, ) assert exc.value.code == 56 with tempfile.TemporaryDirectory() as temporary: state_root = Path(temporary) migration_root = state_root / "mcp-hub-topology-migrations" migration_root.mkdir() intent = { "schema": "itworx.mcp-hub-migration-intent/v1", "status": "complete", "attemptId": migration_attempt, "repository": repository, "candidateRevision": candidate, "priorLiveRevision": "c" * 40, "backupManifestSha256": "d" * 64, "backupComposeSha256": "e" * 64, "baselineStateSha256": "f" * 64, "startedAtEpoch": 10, "completedAtEpoch": 11, "completedStateSha256": "0" * 64, } active_intent = migration_root / "active-intent.json" active_intent.write_text(json.dumps(intent), encoding="utf-8") last_attempt = { "metadata": { "mcpHubMigrationAttemptId": migration_attempt, "mcpHubBackupReceipt": { "repository": repository, "revision": candidate, "restoreVerified": True, "manifestSha256": "d" * 64, "composeSha256": "e" * 64, }, } } with pytest.raises(SystemExit) as exc: validate( state_root, state_bytes, repository, candidate, "success", "healthy", last_attempt, "success", candidate, ) assert exc.value.code == 56 with tempfile.TemporaryDirectory() as temporary: state_root = Path(temporary) migration_root = state_root / "mcp-hub-topology-migrations" receipt_root = state_root / "mcp-hub-backup-receipts" backup_root = state_root / "mcp-hub-backups" migration_root.mkdir() receipt_root.mkdir() backup_root.mkdir() backup_directory = backup_root / f"1780400000-{candidate[:12]}-0123456789abcdef" backup_directory.mkdir() compose_digest = "e" * 64 manifest = { "schema": "itworx.mcp-hub-backup/v1", "repository": repository, "revision": candidate, "backupDirectory": str(backup_directory), "composeSha256": compose_digest, "configRestoreVerified": True, "restore": {"status": "verified"}, "databases": {"itworx_mcp_hub": {}, "contextforge": {}}, } manifest_path = backup_directory / "manifest.json" manifest_path.write_text(json.dumps(manifest), encoding="utf-8") manifest_digest = hashlib.sha256(manifest_path.read_bytes()).hexdigest() receipt_path = receipt_root / f"1780400000-{candidate[:12]}-0123456789abcdef.json" backup_receipt = { "schema": "itworx.mcp-hub-backup-receipt/v1", "repository": repository, "revision": candidate, "backupDirectory": str(backup_directory), "manifestSha256": manifest_digest, "composeSha256": compose_digest, "restoreVerified": True, "createdAtEpoch": 1780400000, "receiptPath": str(receipt_path), } receipt_path.write_text( json.dumps({key: value for key, value in backup_receipt.items() if key != "receiptPath"}), encoding="utf-8", ) observed_path = migration_root / f"observed-baseline-{candidate[:12]}-0123456789abcdef0123.json" observed = { "schema": "itworx.mcp-hub-observed-baseline/v1", "claim": "observed_mixed_runtime_facts_only", "candidateRevision": candidate, "historicalStateSha256": "f" * 64, "backupManifestSha256": manifest_digest, "backupComposeSha256": compose_digest, "backupRestoreVerified": True, "postgresVolumeBaseline": [{}, {}], } observed_path.write_text(json.dumps(observed), encoding="utf-8") observed_digest = hashlib.sha256(observed_path.read_bytes()).hexdigest() successful_state_sha = hashlib.sha256(state_bytes).hexdigest() intent = { "schema": "itworx.mcp-hub-migration-intent/v1", "status": "complete", "attemptId": migration_attempt, "repository": repository, "candidateRevision": candidate, "priorLiveRevision": "c" * 40, "backupManifestSha256": manifest_digest, "backupComposeSha256": compose_digest, "baselineStateSha256": "f" * 64, "startedAtEpoch": 10, "completedAtEpoch": 11, "completedStateSha256": successful_state_sha, "observedBaselinePath": str(observed_path), "observedBaselineSha256": observed_digest, } (migration_root / "active-intent.json").write_text(json.dumps(intent), encoding="utf-8") last_attempt = { "metadata": { "mcpHubMigrationAttemptId": migration_attempt, "mcpHubBackupReceipt": backup_receipt, } } validate( state_root, state_bytes, repository, candidate, "success", "healthy", last_attempt, "success", candidate, ) def test_container_reverification_requires_exact_health_service_label_ids_and_mounts(): fake = FakePortainer() McpHubDeploymentReconciliation(fake).run() script = fake.calls[0][2]["json"]["Cmd"][2] assert 'runtime_state.get("Running") is not True or health != "healthy"' in script assert 'labels.get("com.docker.compose.project") != expected_compose_project' in script assert 'labels.get("com.docker.compose.service") != name_service' in script assert 'if recorded.get(key) != value or receipt_item.get(key) != value:' in script assert 'live_attested_mounts(container) != recorded_mounts' in script assert 'receipt_item.get("mounts_sha256")' in script assert 'def live_attested_mounts(container):' in script assert 'docker", "volume", "inspect", name' in script assert 'mount["created_at"] = created_at' in script assert 'created.astimezone(timezone.utc) > datetime.now(timezone.utc)' in script def test_reconciliation_distinguishes_recreated_postgres_volume_by_created_at(): fake = FakePortainer() McpHubDeploymentReconciliation(fake).run() script = fake.calls[0][2]["json"]["Cmd"][2] parsed = ast.parse(script) function = next( node for node in parsed.body if isinstance(node, ast.FunctionDef) and node.name == "live_attested_mounts" ) volume = { "Name": "hub-postgres", "Driver": "local", "Mountpoint": "/var/lib/docker/volumes/hub-postgres/_data", "Options": {}, "CreatedAt": "2020-01-01T10:00:00Z", } namespace = { "json": json, "os": os, "subprocess": SimpleNamespace( PIPE=subprocess.PIPE, DEVNULL=subprocess.DEVNULL, run=lambda *_args, **_kwargs: SimpleNamespace( returncode=0, stdout=json.dumps([volume]) ) ), "datetime": datetime, "timezone": timezone, } exec(compile(ast.Module(body=[function], type_ignores=[]), "mount-test", "exec"), namespace) attest = namespace["live_attested_mounts"] container = { "Mounts": [{ "Type": "volume", "Name": "hub-postgres", "Source": "/var/lib/docker/volumes/hub-postgres/_data", "Destination": "/var/lib/postgresql/data", "Driver": "local", "RW": True, }] } original = attest(container) assert original[0]["created_at"] == "2020-01-01T10:00:00Z" volume["CreatedAt"] = "2021-01-01T10:00:00Z" recreated = attest(container) assert recreated[0]["name"] == original[0]["name"] assert recreated[0]["source"] == original[0]["source"] assert recreated[0]["created_at"] != original[0]["created_at"] def test_each_service_uses_its_fixed_image_repository_or_reviewed_external_contract(): fake = FakePortainer() McpHubDeploymentReconciliation(fake).run() script = fake.calls[0][2]["json"]["Cmd"][2] assert 'def fixed_image_reference(service_name, image_ref):' in script assert '"dataplane-router": "gitea.example.com/jens/itworx-mcp-hub/dataplane"' in script assert '"docker-read-broker": "gitea.example.com/jens/itworx-mcp-hub/docker-read-broker"' in script assert 'artifact["image_source"] != owned_image_source' in script assert 'artifact["image_revision"] != recorded_live' in script assert 'main_declares_external_image(image_ref)' in script assert 'main_declares_contextforge_compat_image(' in script assert 'def image_digest_matches(image, image_ref):' in script assert 'aliases.add("docker.io/library/" + repository)' in script def test_external_candidate_pins_are_exact_and_declared_by_protected_main(): fake = FakePortainer() McpHubDeploymentReconciliation(fake).run() script = fake.calls[0][2]["json"]["Cmd"][2] parsed = ast.parse(script) function = next( node for node in parsed.body if isinstance(node, ast.FunctionDef) and node.name == "fixed_image_reference" ) postgres = ( "gitea.example.com/jens/itworx-mcp-hub/postgres:" "wp400-gosu-v2-20261002-a94e0b4d2029@sha256:" "a60d81e06987671ae903757f1b71cdfbbff4d481b42610e4de2b7dcfa32aad73" ) redis = ( "gitea.example.com/jens/itworx-mcp-hub/redis:" "wp400-remediation-20261002-4e18f61c@sha256:" "7d826b44f245ad32e8fe4bf40d42eae95855c3942c5f2e42784ee8fd3f9547a2" ) previous_postgres = ( "postgres:18.6@sha256:" "0377e72c5289ed2f98cf61b1a9c2db9eb9d300317fe14244492fbc94343b3d04" ) previous_redis = ( "redis:8.10.2@sha256:" "7ef5b5cec96495a04ca7feff88a9492efeab8053fb284d24bdd73344c9245a48" ) declared = {previous_postgres, previous_redis, postgres, redis} namespace = { "re": re, "main_declares_external_image": lambda image_ref: image_ref in declared, "main_declares_sessionless_dataplane_image": lambda *_args: False, "main_declares_contextforge_compat_image": lambda *_args: False, } exec(compile(ast.Module(body=[function], type_ignores=[]), "fixed-image-test", "exec"), namespace) fixed = namespace["fixed_image_reference"] assert fixed("postgres", "postgres:18.4@sha256:3a82e1f56c8f0f5616a11103ac3d47e632c3938698946a7ad26da0df1334744a") assert fixed("postgres", "postgres:18.6@sha256:0377e72c5289ed2f98cf61b1a9c2db9eb9d300317fe14244492fbc94343b3d04") assert fixed("redis", "redis:8.10.0@sha256:c29e49ab2f85760a3827b53882e6dd9f5c6c3f0bb7d724e07bb31cbf275a5236") assert fixed("redis", "redis:8.10.2@sha256:7ef5b5cec96495a04ca7feff88a9492efeab8053fb284d24bdd73344c9245a48") assert fixed("postgres", postgres) assert fixed("redis", redis) assert not fixed("redis", postgres) assert not fixed("postgres", postgres[:-1] + "4") assert not fixed("postgres", postgres.replace("wp400-gosu-v2-20261002-a94e0b4d2029", "wp400-gosu-v2-20261002-wrong")) assert not fixed("postgres", postgres.replace("gitea.example.com/jens/itworx-mcp-hub", "gitea.example.com/other/itworx-mcp-hub")) assert not fixed("redis", redis[:-1] + "3") assert not fixed("redis", redis.replace("wp400-remediation-20261002-4e18f61c", "wp400-remediation-20261002-wrong")) assert not fixed("redis", redis.replace("gitea.example.com/jens/itworx-mcp-hub", "gitea.example.com/other/itworx-mcp-hub")) assert not fixed("postgres", "postgres:19.0@sha256:" + "a" * 64) assert not fixed("redis", "redis:8.10.3@sha256:" + "b" * 64) for service in ("web", "control-api", "dataplane-router", "connector-ingestion"): assert fixed(service, "sha256:" + "a" * 64) for service in ("postgres", "redis", "contextforge", "ingestion-clamd", "foreign"): assert not fixed(service, "sha256:" + "a" * 64) namespace["main_declares_external_image"] = lambda _image_ref: False assert fixed("postgres", "postgres:18.4@sha256:3a82e1f56c8f0f5616a11103ac3d47e632c3938698946a7ad26da0df1334744a") assert not fixed("postgres", previous_postgres) assert fixed("redis", "redis:8.10.0@sha256:c29e49ab2f85760a3827b53882e6dd9f5c6c3f0bb7d724e07bb31cbf275a5236") assert not fixed("redis", previous_redis) assert not fixed("postgres", postgres) assert not fixed("redis", redis) @pytest.mark.parametrize("service, image_ref", [ ( "postgres", "gitea.example.com/jens/itworx-mcp-hub/postgres:" "wp400-gosu-v2-20261002-a94e0b4d2029@sha256:" "a60d81e06987671ae903757f1b71cdfbbff4d481b42610e4de2b7dcfa32aad73", ), ( "redis", "gitea.example.com/jens/itworx-mcp-hub/redis:" "wp400-remediation-20261002-4e18f61c@sha256:" "7d826b44f245ad32e8fe4bf40d42eae95855c3942c5f2e42784ee8fd3f9547a2", ), ]) def test_new_db_pin_requires_exact_protected_main_compose_declaration(service, image_ref): fake = FakePortainer() McpHubDeploymentReconciliation(fake).run() script = fake.calls[0][2]["json"]["Cmd"][2] parsed = ast.parse(script) function = next( node for node in parsed.body if isinstance(node, ast.FunctionDef) and node.name == "main_declares_external_image" ) main_revision = "a" * 40 content = {"value": f"services:\n {service}:\n image: {image_ref}\n"} calls = [] def git_run(*args): calls.append(args) return subprocess.CompletedProcess(args, 0, stdout=content["value"]) namespace = {"re": re, "main_revision": main_revision, "git_run": git_run} exec(compile(ast.Module(body=[function], type_ignores=[]), "main-image-declaration-test", "exec"), namespace) declares = namespace["main_declares_external_image"] assert declares(image_ref) assert calls == [("show", f"{main_revision}:infrastructure/compose/docker-compose.yml")] calls.clear() content["value"] = f"services:\n {service}:\n image: {image_ref[:-1]}0\n" assert not declares(image_ref) assert calls == [ ("show", f"{main_revision}:infrastructure/compose/docker-compose.yml"), ("show", f"{main_revision}:infrastructure/compose/docker-compose.prod.yml"), ] @pytest.fixture def contextforge_reconciliation_proof(): fake = FakePortainer() McpHubDeploymentReconciliation(fake).run() parsed = ast.parse(fake.calls[0][2]["json"]["Cmd"][2]) functions = [ node for node in parsed.body if isinstance(node, ast.FunctionDef) and node.name in { "main_declares_contextforge_compat_image", "fixed_image_reference", "receipt_bound_image_reference", } ] revision = "a" * 40 main_revision = "b" * 40 manifest = ( f"CONTEXTFORGE_UPSTREAM_COMMIT={'c' * 40}\n" "CONTEXTFORGE_ALEMBIC_HEAD=123456789abc\n" "CONTEXTFORGE_MCP_SDK=1.2.3\n" "CONTEXTFORGE_LOCAL_IMAGE=itworx-mcp-hub/contextforge-schema:123456789abc\n" ) builder = ( 'git -C "$SRC" fetch --depth 1 origin "$CONTEXTFORGE_UPSTREAM_COMMIT"\n' 'git -C "$SRC" checkout --detach FETCH_HEAD\n' 'MCP_REQUIREMENT="mcp==${CONTEXTFORGE_MCP_SDK}"\n' 'alembic -c mcpgateway/alembic.ini heads\n' 'docker tag "$STAGED_IMAGE" "$CONTEXTFORGE_LOCAL_IMAGE"\n' '--release-candidate\n' 'CANDIDATE_IMAGE="itworx-mcp-hub/contextforge-schema:${APPOPS_SOURCE_REVISION}"\n' 'docker tag "$STAGED_IMAGE" "$CANDIDATE_IMAGE"\n' ) files = { f"{commit}:infrastructure/images/contextforge/compat-image.env": manifest for commit in (revision, main_revision) } files.update({ f"{commit}:scripts/build_contextforge_compat_image.sh": builder for commit in (revision, main_revision) }) def git_run(*args): assert args[0] == "show" return SimpleNamespace(returncode=0 if args[1] in files else 1, stdout=files.get(args[1], "")) namespace = {"re": re, "git_run": git_run, "main_revision": main_revision, "recorded_live": revision} exec( # noqa: S102 -- Execute only extracted repository-owned verifier functions. compile(ast.Module(body=functions, type_ignores=[]), "contextforge-proof", "exec"), namespace ) return SimpleNamespace(namespace=namespace, files=files, revision=revision) def test_contextforge_reconciliation_accepts_exact_receipt_revision(contextforge_reconciliation_proof): proof = contextforge_reconciliation_proof namespace = proof.namespace fixed = namespace["fixed_image_reference"] reference = f"itworx-mcp-hub/contextforge-schema:{proof.revision}" image_id = "sha256:" + "d" * 64 record = {"image_id": image_id, "image_ref": reference} bound = namespace["receipt_bound_image_reference"](image_id, image_id, record, dict(record)) assert fixed("contextforge", bound) assert fixed("contextforge", "itworx-mcp-hub/contextforge-schema:123456789abc") # A later main may remove the candidate builder; the recorded receipt's # ancestor revision must supply the source proof for this immutable image. proof.files[f"{'b' * 40}:scripts/build_contextforge_compat_image.sh"] = "" assert fixed("contextforge", bound) @pytest.mark.parametrize("mutation", [ "wrong_revision", "short_revision", "mutable_tag", "wrong_service", "missing_candidate_flag", "missing_candidate_assignment", "missing_candidate_tag", "missing_fetch", "bad_manifest", "missing_recorded_source", ]) def test_contextforge_reconciliation_rejects_unbound_candidates(contextforge_reconciliation_proof, mutation): proof = contextforge_reconciliation_proof reference = f"itworx-mcp-hub/contextforge-schema:{proof.revision}" service = "contextforge" builder_path = f"{proof.revision}:scripts/build_contextforge_compat_image.sh" fragments = { "missing_candidate_flag": '--release-candidate', "missing_candidate_assignment": 'CANDIDATE_IMAGE="itworx-mcp-hub/contextforge-schema:${APPOPS_SOURCE_REVISION}"', "missing_candidate_tag": 'docker tag "$STAGED_IMAGE" "$CANDIDATE_IMAGE"', "missing_fetch": 'git -C "$SRC" fetch --depth 1 origin "$CONTEXTFORGE_UPSTREAM_COMMIT"', } if mutation == "wrong_revision": reference = f"itworx-mcp-hub/contextforge-schema:{'b' * 40}" elif mutation == "short_revision": reference = f"itworx-mcp-hub/contextforge-schema:{proof.revision[:12]}" elif mutation == "mutable_tag": reference = "itworx-mcp-hub/contextforge-schema:latest" elif mutation == "wrong_service": service = "foreign" elif mutation in fragments: proof.files[builder_path] = proof.files[builder_path].replace(fragments[mutation], "") elif mutation == "bad_manifest": path = f"{proof.revision}:infrastructure/images/contextforge/compat-image.env" proof.files[path] = proof.files[path].replace("CONTEXTFORGE_ALEMBIC_HEAD=123456789abc", "CONTEXTFORGE_ALEMBIC_HEAD=invalid") else: proof.files.pop(builder_path) assert not proof.namespace["fixed_image_reference"](service, reference) def test_digest_attestation_normalizes_tagged_docker_hub_repo_digests(): fake = FakePortainer() McpHubDeploymentReconciliation(fake).run() script = fake.calls[0][2]["json"]["Cmd"][2] parsed = ast.parse(script) function = next( node for node in parsed.body if isinstance(node, ast.FunctionDef) and node.name == "image_digest_matches" ) namespace = {"re": re} exec(compile(ast.Module(body=[function], type_ignores=[]), "reconcile-test", "exec"), namespace) matches = namespace["image_digest_matches"] digest = "sha256:" + "a" * 64 assert matches({"RepoDigests": [f"postgres@{digest}"]}, f"postgres:18.6@{digest}") assert matches({"RepoDigests": [f"docker.io/library/redis@{digest}"]}, f"redis:8.10.2@{digest}") assert matches({"RepoDigests": [f"clamav/clamav@{digest}"]}, f"clamav/clamav:1.4.6@{digest}") assert matches( {"RepoDigests": [f"registry.example:5000/team/web@{digest}"]}, f"registry.example:5000/team/web:release@{digest}", ) assert not matches( {"RepoDigests": [f"redis@sha256:{'b' * 64}"]}, f"redis:8.10.2@{digest}" ) assert not matches({"RepoDigests": [f"foreign/redis@{digest}"]}, f"redis:8.10.2@{digest}") def test_id_launched_image_uses_exact_state_and_receipt_reference(): fake = FakePortainer() McpHubDeploymentReconciliation(fake).run() script = fake.calls[0][2]["json"]["Cmd"][2] parsed = ast.parse(script) function = next( node for node in parsed.body if isinstance(node, ast.FunctionDef) and node.name == "receipt_bound_image_reference" ) namespace = {"re": re} exec( # noqa: S102 compile(ast.Module(body=[function], type_ignores=[]), "receipt-image-test", "exec"), namespace, ) bound_reference = namespace["receipt_bound_image_reference"] image_id = "sha256:" + "a" * 64 image_ref = ( "postgres:18.6@sha256:" "0377e72c5289ed2f98cf61b1a9c2db9eb9d300317fe14244492fbc94343b3d04" ) recorded = {"image_id": image_id, "image_ref": image_ref} receipt_item = dict(recorded) assert bound_reference(image_id, image_id, recorded, receipt_item) == image_ref assert bound_reference(image_ref, image_id, recorded, receipt_item) == image_ref assert 'image_ref = receipt_bound_image_reference(' in script assert script.index('image_ref = receipt_bound_image_reference(') < script.index( 'if not fixed_image_reference(name_service, image_ref):' ) assert 'image_digest_matches(image, image_ref)' in script for launch_image, live_id, state, receipt in ( ("sha256:" + "c" * 64, image_id, recorded, receipt_item), ( image_id, image_id, {**recorded, "image_id": "sha256:" + "c" * 64}, receipt_item, ), ( image_id, image_id, recorded, {**receipt_item, "image_id": "sha256:" + "c" * 64}, ), ( image_id, image_id, {**recorded, "image_ref": "redis:8.10.2@sha256:" + "b" * 64}, receipt_item, ), (image_id, image_id, recorded, {**receipt_item, "image_ref": ""}), (image_id, "arbitrary-image-id", recorded, receipt_item), (image_id, image_id, {**recorded, "image_ref": "sha256:" + "b" * 64}, {**receipt_item, "image_ref": "sha256:" + "b" * 64}), ): with pytest.raises(SystemExit) as exc: bound_reference(launch_image, live_id, state, receipt) assert exc.value.code == 52 def test_reconciliation_reports_invalid_state_and_live_evidence_fail_closed(): expected = { 42: ("validation_failed", "MCP Hub deployment state file is unavailable or invalid"), 45: ("validation_failed", "a live MCP Hub container is not running and healthy"), 47: ("validation_failed", "live MCP Hub Compose project differs from the controller target"), 51: ("validation_failed", "live MCP Hub image revision or external immutable image is not proven by the recorded-live to main chain"), 52: ("validation_failed", "controller state or live facts do not match the exact modern MCP Hub receipt"), 56: ("topology_migration_required", "MCP Hub legacy, unknown, mixed, or unresolved migration state requires guarded deployment or data-aware recovery"), } for exit_code, (status, reason) in expected.items(): result = McpHubDeploymentReconciliation(FakePortainer(exit_code=exit_code)).run() assert result["status"] == status assert result["reason"] == reason def test_reconciliation_only_allows_fixed_17_topology_and_rejects_registry_drift(): fake = FakePortainer() McpHubDeploymentReconciliation(fake).run() script = fake.calls[0][2]["json"]["Cmd"][2] assert 'project.get("branch") != "main"' in script assert 'project.get("slug") != expected_compose_project' in script assert 'project.get("compose_project") != expected_compose_project' in script assert 'frozenset(expected_containers) not in {frozenset(expected_fixed), frozenset(expected_runtime)}' in script assert 'set(expected_containers) != expected_runtime' in script assert 'set(project_names) != set(expected_containers)' in script assert '"itworx-mcp-hub-connector-ingestion-1"' in script assert '"itworx-mcp-hub-ingestion-clamd-1"' in script