from __future__ import annotations import copy import hashlib import importlib.util import json import sys import types from datetime import datetime, timezone from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] @pytest.fixture def controller(): if "fcntl" not in sys.modules: shim = types.ModuleType("fcntl") shim.LOCK_EX, shim.LOCK_UN, shim.flock = 2, 8, lambda *_args: None sys.modules["fcntl"] = shim spec = importlib.util.spec_from_file_location( "hub_candidate_controller", ROOT / "ops/gitea-deploy-broker/deploy.py" ) assert spec and spec.loader module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module @pytest.fixture def prepared(controller, tmp_path, monkeypatch): monkeypatch.setattr(controller, "STATE_ROOT", tmp_path) monkeypatch.setattr(controller, "_mcp_hub_require_trusted_state_root", lambda: None) monkeypatch.setattr(controller, "_mcp_hub_require_no_posix_acl", lambda _path: None) revision = "a" * 40 directory = tmp_path / "mcp-hub-release-candidates" / revision directory.mkdir(parents=True, mode=0o700) directory.parent.chmod(0o700) now = ( datetime.now(timezone.utc) .isoformat(timespec="milliseconds") .replace("+00:00", "Z") ) metadata = { "id": revision[:12], "repository": controller.MCP_HUB_REPOSITORY, "revision": revision, "tree": "b" * 40, "treeDigest": hashlib.sha256(("git-tree:" + "b" * 40).encode()).hexdigest(), "builtAt": now, } project = { "_mcp_hub_contract": controller.MCP_HUB_TOPOLOGY_CONTRACT, "_repository_path": str(tmp_path / "hub.git"), "_deployment_metadata": metadata, "_mcp_hub_source_date_epoch": "1790888863", } monkeypatch.setattr( controller, "deployment_metadata", lambda *_args, **_kwargs: metadata ) monkeypatch.setattr( controller, "mcp_hub_source_date_epoch", lambda *_args: "1790888863" ) monkeypatch.setattr( controller, "mcp_hub_stage_release_source", lambda *_args: tmp_path / "release" ) checks = [] monkeypatch.setattr( controller, "_mcp_hub_check_candidate_exceptions", lambda *_args: checks.append("gate"), ) images = {} model = {"services": {}} def write(name, value): data = ( json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n" ).encode() path = directory / name path.write_bytes(data) path.chmod(0o600) return hashlib.sha256(data).hexdigest() for index, service in enumerate(controller.MCP_HUB_SERVICES, 1): image_id = "sha256:" + f"{index:064x}" ref = f"fixture/{service}@sha256:" + f"{index:064x}" if service == "contextforge": ref = f"itworx-mcp-hub/contextforge-schema:{revision}" model["services"][service] = {"image": ref} vuln = { "SchemaVersion": 2, "Trivy": {"Version": "0.73.0"}, "CreatedAt": now, "Metadata": { "ImageID": image_id, "ImageConfig": {"architecture": "amd64", "os": "linux"}, }, "Results": [ {"Class": "os-pkgs", "Packages": [{"Name": "fixture", "Version": "1"}]} ], } secret = { "ImageID": image_id, "CreatedAt": now, "TrivyVersion": "0.73.0", "FindingCount": 0, "SensitiveFindingDetailsPersisted": False, "Results": [{"FindingCount": 0}], } images[service] = { "imageRef": ref, "imageId": image_id, "vulnerabilitySha256": write(f"vulnerability-{service}.json", vuln), "secretSha256": write(f"secret-{service}.json", secret), } candidate = { "schema": "itworx.mcp-hub-release-candidate/v1", "repository": controller.MCP_HUB_REPOSITORY, "revision": revision, "build": metadata, "sourceDateEpoch": "1790888863", "images": images, "composeSha256": hashlib.sha256( json.dumps(model, sort_keys=True, separators=(",", ":")).encode() ).hexdigest(), "databaseMetadataSha256": write( "trivy-db-metadata.json", {"UpdatedAt": now, "DownloadedAt": now} ), "summarySha256": write("scan-summary.json", {"unresolvedCriticalOrHigh": []}), "digestsSha256": write( "image-digests.json", { "images": { s: {"imageId": v["imageId"], "tag": v["imageRef"]} for s, v in images.items() } }, ), } candidate_sha = write("candidate.json", candidate) exceptions_sha = write("exceptions.json", {"exceptions": []}) write( "approval.json", { "schema": "itworx.mcp-hub-cve-approval/v1", "candidateSha256": candidate_sha, "exceptionsSha256": exceptions_sha, }, ) labels = { "org.opencontainers.image.source": controller.MCP_HUB_OCI_IMAGE_SOURCE, "org.opencontainers.image.revision": revision, "org.opencontainers.image.source-tree": metadata["tree"], "org.opencontainers.image.version": metadata["id"], "org.opencontainers.image.created": metadata["builtAt"], "org.opencontainers.image.source-date-epoch": "1790888863", } facts = { v["imageRef"]: { "Id": v["imageId"], "Config": {"User": "10001:10001", "Labels": dict(labels)}, } for v in images.values() } monkeypatch.setattr(controller, "inspect_image", lambda ref: facts.get(ref)) return types.SimpleNamespace( project=project, revision=revision, directory=directory, candidate=candidate, model=model, metadata=metadata, write=write, checks=checks, facts=facts, ) def test_fresh_complete_candidate_freezes_metadata_and_checks_explicit_gate( controller, prepared ): result = controller.mcp_hub_load_release_candidate( prepared.project, prepared.revision ) assert result["build"] == prepared.metadata assert set(result["images"]) == set(controller.MCP_HUB_SERVICES) assert prepared.checks == ["gate"] controller.mcp_hub_require_candidate_images( prepared.project, prepared.model, prepared.revision ) @pytest.mark.parametrize( "mutation", [ "missing", "extra", "revision", "build", "epoch", "scan_hash", "scan_identity", "scan_empty", "secret", "stale", "db_stale", "approval", "summary", "digests", ], ) def test_candidate_drift_rejects_before_any_production_action( controller, prepared, mutation ): value = copy.deepcopy(prepared.candidate) if mutation == "missing": value["images"].pop("web") elif mutation == "extra": value["images"]["foreign"] = value["images"]["web"] elif mutation == "revision": value["revision"] = "c" * 40 elif mutation == "build": value["build"]["tree"] = "c" * 40 elif mutation == "epoch": value["sourceDateEpoch"] = "1" elif mutation == "scan_hash": value["images"]["web"]["vulnerabilitySha256"] = "c" * 64 elif mutation in {"scan_identity", "scan_empty", "stale"}: path = prepared.directory / "vulnerability-web.json" report = json.loads(path.read_text()) if mutation == "scan_identity": report["Metadata"]["ImageID"] = "sha256:" + "c" * 64 elif mutation == "scan_empty": report["Results"] = [] else: report["CreatedAt"] = "2026-01-01T00:00:00Z" value["images"]["web"]["vulnerabilitySha256"] = prepared.write( path.name, report ) elif mutation == "secret": report = json.loads((prepared.directory / "secret-web.json").read_text()) report["FindingCount"] = 1 value["images"]["web"]["secretSha256"] = prepared.write( "secret-web.json", report ) elif mutation == "db_stale": value["databaseMetadataSha256"] = prepared.write( "trivy-db-metadata.json", { "UpdatedAt": "2026-01-01T00:00:00Z", "DownloadedAt": "2026-01-01T00:00:00Z", }, ) elif mutation == "summary": value["summarySha256"] = prepared.write( "scan-summary.json", {"unresolvedCriticalOrHigh": [{"package": "invented"}]} ) elif mutation == "digests": value["digestsSha256"] = prepared.write("image-digests.json", {"images": {}}) candidate_sha = prepared.write("candidate.json", value) exceptions_sha = hashlib.sha256( (prepared.directory / "exceptions.json").read_bytes() ).hexdigest() prepared.write( "approval.json", { "schema": "itworx.mcp-hub-cve-approval/v1", "candidateSha256": "c" * 64 if mutation == "approval" else candidate_sha, "exceptionsSha256": exceptions_sha, }, ) with pytest.raises(controller.DeployError): controller.mcp_hub_load_release_candidate(prepared.project, prepared.revision) assert prepared.checks == [] def test_pending_or_expired_human_exception_gate_cannot_be_substituted( controller, prepared, monkeypatch ): def denied(*_args): raise controller.DeployError("CVE exception gate did not pass") monkeypatch.setattr(controller, "_mcp_hub_check_candidate_exceptions", denied) with pytest.raises(controller.DeployError, match="CVE exception gate"): controller.mcp_hub_load_release_candidate(prepared.project, prepared.revision) @pytest.mark.parametrize( "service", ["web", "contextforge", "postgres", "connector-ingestion"] ) def test_retagged_or_changed_candidate_image_is_rejected_before_apply( controller, prepared, service ): controller.mcp_hub_load_release_candidate(prepared.project, prepared.revision) prepared.facts[prepared.candidate["images"][service]["imageRef"]]["Id"] = ( "sha256:" + "f" * 64 ) with pytest.raises(controller.DeployError, match="image identity"): controller.mcp_hub_require_candidate_images( prepared.project, prepared.model, prepared.revision ) def test_changed_rendered_model_is_rejected(controller, prepared): controller.mcp_hub_load_release_candidate(prepared.project, prepared.revision) prepared.model["services"]["web"]["environment"] = {"unexpected": "change"} with pytest.raises(controller.DeployError, match="Compose"): controller.mcp_hub_require_candidate_images( prepared.project, prepared.model, prepared.revision ) def test_changed_approval_is_rejected_between_stages(controller, prepared): controller.mcp_hub_load_release_candidate(prepared.project, prepared.revision) prepared.write("exceptions.json", {"exceptions": [{"approvalStatus": "pending"}]}) with pytest.raises(controller.DeployError): controller.mcp_hub_require_candidate_images( prepared.project, prepared.model, prepared.revision ) @pytest.mark.parametrize("service", ["web", "contextforge"]) @pytest.mark.parametrize( "label", [ "org.opencontainers.image.source", "org.opencontainers.image.revision", "org.opencontainers.image.source-tree", "org.opencontainers.image.source-date-epoch", "org.opencontainers.image.created", "org.opencontainers.image.version", ], ) def test_frozen_image_provenance_is_checked_before_database_intent( controller, prepared, service, label ): controller.mcp_hub_load_release_candidate(prepared.project, prepared.revision) prepared.facts[prepared.candidate["images"][service]["imageRef"]]["Config"][ "Labels" ][label] = "wrong" with pytest.raises(controller.DeployError, match="provenance"): controller.mcp_hub_require_candidate_images( prepared.project, prepared.model, prepared.revision ) @pytest.mark.parametrize("service", ["web", "contextforge"]) @pytest.mark.parametrize("user", ["", "root", "root:10001", "0", "00:10001"]) def test_frozen_image_root_user_is_rejected_before_database_intent( controller, prepared, service, user ): controller.mcp_hub_load_release_candidate(prepared.project, prepared.revision) prepared.facts[prepared.candidate["images"][service]["imageRef"]]["Config"][ "User" ] = user with pytest.raises(controller.DeployError, match="non-root runtime"): controller.mcp_hub_require_candidate_images( prepared.project, prepared.model, prepared.revision ) def test_changed_raw_scan_is_rejected_between_stages(controller, prepared): controller.mcp_hub_load_release_candidate(prepared.project, prepared.revision) prepared.write("vulnerability-contextforge.json", {"Results": []}) with pytest.raises(controller.DeployError, match="hash"): controller.mcp_hub_require_candidate_images( prepared.project, prepared.model, prepared.revision ) @pytest.mark.parametrize("changed", [False, True]) def test_staged_start_consumes_no_build_no_pull_and_checks_running_ids( controller, prepared, monkeypatch, changed ): controller.mcp_hub_load_release_candidate(prepared.project, prepared.revision) prepared.project["_mcp_hub_rendered_model"] = prepared.model commands = [] monkeypatch.setattr( controller, "compose_command", lambda _p: (prepared.directory, ["docker", "compose"]), ) monkeypatch.setattr(controller, "deployment_environment", lambda *_args: {}) monkeypatch.setattr( controller, "run", lambda command, **_kwargs: commands.append(command) ) monkeypatch.setattr(controller, "mcp_hub_wait_services", lambda *_args: None) expected = prepared.candidate["images"]["postgres"]["imageId"] monkeypatch.setattr( controller, "inspect_container", lambda _name: {"Image": "sha256:" + "f" * 64 if changed else expected}, ) if changed: with pytest.raises(controller.DeployError, match="exact scanned image"): controller.mcp_hub_up_services( prepared.project, prepared.revision, controller.MCP_HUB_REPOSITORY, ("postgres",), ) else: controller.mcp_hub_up_services( prepared.project, prepared.revision, controller.MCP_HUB_REPOSITORY, ("postgres",), ) assert commands == [ [ "docker", "compose", "up", "--detach", "--no-deps", "--no-build", "--pull", "never", "postgres", ] ] @pytest.mark.parametrize("operation", ["start", "migrate"]) def test_compose_operations_pin_all_scanned_ids_without_changing_source_model( controller, prepared, monkeypatch, operation ): controller.mcp_hub_load_release_candidate(prepared.project, prepared.revision) prepared.project["_mcp_hub_rendered_model"] = prepared.model original = copy.deepcopy(prepared.model) observed = [] def compose(project): path = Path(project["_mcp_hub_image_override_file"]) assert path.stat().st_mode & 0o777 == 0o600 observed.append((path, json.loads(path.read_text()))) return prepared.directory, ["docker", "compose", "-f", str(path)] commands = [] monkeypatch.setattr(controller, "compose_command", compose) monkeypatch.setattr(controller, "deployment_environment", lambda *_args: {}) monkeypatch.setattr(controller, "run", lambda cmd, **_kwargs: commands.append(cmd)) monkeypatch.setattr(controller, "mcp_hub_wait_services", lambda *_args: None) monkeypatch.setattr( controller, "inspect_container", lambda _name: {"Image": prepared.candidate["images"]["postgres"]["imageId"]}, ) if operation == "start": controller.mcp_hub_up_services( prepared.project, prepared.revision, controller.MCP_HUB_REPOSITORY, ("postgres",), ) else: controller.mcp_hub_run_database_migration( prepared.project, prepared.revision, controller.MCP_HUB_REPOSITORY ) assert commands[0][-3:] == [ "-e", "SERVICE_NAME=database-migrate", "control-api", ] assert len(observed) == len(commands) == 1 path, payload = observed[0] assert payload == { "services": { service: {"image": record["imageId"]} for service, record in prepared.candidate["images"].items() } } assert "--no-deps" in commands[0] assert commands[0][commands[0].index("--pull") + 1] == "never" assert not path.exists() assert "_mcp_hub_image_override_file" not in prepared.project assert prepared.model == original def test_migration_rechecks_candidate_before_running(controller, prepared, monkeypatch): controller.mcp_hub_load_release_candidate(prepared.project, prepared.revision) prepared.project["_mcp_hub_rendered_model"] = prepared.model prepared.write("vulnerability-control-api.json", {"Results": []}) monkeypatch.setattr( controller, "run", lambda *_args, **_kwargs: pytest.fail("must not run") ) with pytest.raises(controller.DeployError, match="hash"): controller.mcp_hub_run_database_migration( prepared.project, prepared.revision, controller.MCP_HUB_REPOSITORY ) def test_candidate_runtime_verifies_pinned_ids_after_migration( controller, prepared, monkeypatch ): controller.mcp_hub_load_release_candidate(prepared.project, prepared.revision) project = prepared.project project["compose_project"] = controller.MCP_HUB_PROJECT project["slug"] = "itworx-mcp-hub" project["source_dir"] = str(prepared.directory) project["compose_files"] = [] project["containers"] = list(controller.MCP_HUB_CONTAINERS) project["_mcp_hub_rendered_model"] = prepared.model project["_mcp_hub_candidate_image_ids"] = ( controller.mcp_hub_require_candidate_images( project, prepared.model, prepared.revision ) ) for service in ("control-api", "worker"): prepared.model["services"][service]["environment"] = { "INGESTION_CONNECTOR_ENABLED": "true" } containers = {} for service, record in prepared.candidate["images"].items(): name = f"itworx-mcp-hub-{service}-1" containers[name] = { "Id": "c" * 64, "Image": record["imageId"], "Config": { "Image": record["imageId"], "Env": ( ["INGESTION_CONNECTOR_ENABLED=true"] if service in {"control-api", "worker"} else [] ), "Labels": { "com.docker.compose.project": controller.MCP_HUB_PROJECT, "com.docker.compose.service": service, }, }, "State": {"Running": True, "Health": {"Status": "healthy"}}, } monkeypatch.setattr( controller, "compose_project_container_names", lambda _project: set(containers) ) monkeypatch.setattr( controller, "inspect_container", lambda name: containers.get(name) ) monkeypatch.setattr( controller, "container_state", lambda _name: (True, "healthy", controller.MCP_HUB_PROJECT), ) labels = { "org.opencontainers.image.revision": prepared.revision, "org.opencontainers.image.source": controller.MCP_HUB_OCI_IMAGE_SOURCE, "org.opencontainers.image.source-tree": prepared.metadata["tree"], "org.opencontainers.image.version": prepared.metadata["id"], "org.opencontainers.image.created": prepared.metadata["builtAt"], } image_ids_by_ref = { record["imageRef"]: record["imageId"] for record in prepared.candidate["images"].values() } monkeypatch.setattr( controller, "inspect_image", lambda image_ref: { "Id": image_ids_by_ref.get(image_ref, image_ref), "Config": {"Labels": labels}, "RepoDigests": [], }, ) monkeypatch.setattr( controller, "mcp_hub_attested_live_mounts", lambda *_args, **_kwargs: [] ) assert set(controller.verify_mcp_hub_live_topology(project, prepared.model)) == set( containers ) assert set( controller.verify_containers( project, expected_image_ids=project["_mcp_hub_candidate_image_ids"] ) ) == set(containers) for service in controller.MCP_HUB_APPLICATION_SERVICES: prepared.model["services"][service]["build"] = {"context": "."} manifest = ( "CONTEXTFORGE_UPSTREAM_COMMIT=" + "1" * 40 + "\n" "CONTEXTFORGE_ALEMBIC_HEAD=12d4a0c7789c\n" "CONTEXTFORGE_MCP_SDK=1.30.0\n" "CONTEXTFORGE_LOCAL_IMAGE=itworx-mcp-hub/contextforge-schema:12d4a0c7789c\n" ) builder = "\n".join( [ "--release-candidate", 'CANDIDATE_IMAGE="itworx-mcp-hub/contextforge-schema:${APPOPS_SOURCE_REVISION}"', 'docker tag "$STAGED_IMAGE" "$CANDIDATE_IMAGE"', 'git -C "$SRC" fetch --depth 1 origin "$CONTEXTFORGE_UPSTREAM_COMMIT"', 'git -C "$SRC" checkout --detach FETCH_HEAD', "alembic -c mcpgateway/alembic.ini heads", 'docker tag "$STAGED_IMAGE" "$CONTEXTFORGE_LOCAL_IMAGE"', ] ) monkeypatch.setattr( controller, "_mcp_hub_git_output", lambda _project, _show, path: manifest if path.endswith("compat-image.env") else builder, ) monkeypatch.setattr( controller, "_mcp_hub_dataplane_compat_proven", lambda *_args: True ) monkeypatch.setattr(controller, "_mcp_hub_digest_ref_proven", lambda *_args: True) monkeypatch.setattr( controller, "_mcp_hub_live_image_proven", lambda _project, name, item, source_ref, *_args, **_kwargs: { "container_id": item["Id"], "image_id": item["Image"], "image_ref": source_ref, "container_revision": "", "image_revision": prepared.revision, "image_source": controller.MCP_HUB_OCI_IMAGE_SOURCE, "image_tree": prepared.metadata["tree"], "image_version": prepared.metadata["id"], "image_created": prepared.metadata["builtAt"], }, ) receipt = controller.verify_deployment( project, prepared.metadata, expected_image_ids=project["_mcp_hub_candidate_image_ids"], ) for service in controller.MCP_HUB_APPLICATION_SERVICES: name = f"itworx-mcp-hub-{service}-1" assert ( receipt["containers"][name]["image_ref"] == receipt["containers"][name]["image_id"] ) assert ( controller.validate_recorded_receipt( project, receipt, controller.MCP_HUB_REPOSITORY, prepared.revision ) == prepared.metadata ) state = {} monkeypatch.setattr( controller, "_mcp_hub_read_controller_state_bytes", lambda **_kwargs: state.get("bytes"), ) monkeypatch.setattr( controller, "_mcp_hub_write_controller_state_bytes", lambda payload, **_kwargs: state.__setitem__("bytes", payload), ) controller.write_state( controller.MCP_HUB_REPOSITORY, project, prepared.revision, "success", "verified", live_receipt=receipt, ) saved = json.loads(state["bytes"]) assert saved["live_status"] == "healthy" for name in controller.MCP_HUB_CONTAINERS: assert ( saved["live_artifacts"]["containers"][name]["image_ref"] == receipt["containers"][name]["image_ref"] ) project.pop("_mcp_hub_candidate_image_ids") assert ( controller.verified_current_receipt( controller.MCP_HUB_REPOSITORY, project, prepared.revision, prepared.metadata["tree"], ) == receipt ) assert "_mcp_hub_receipt_image_ids" not in project project["_repository_name"] = controller.MCP_HUB_REPOSITORY rollback_commands = [] monkeypatch.setattr( controller, "run", lambda command, **_kwargs: rollback_commands.append(command) ) snapshot = controller.capture_compose_rollback(project) assert len(snapshot["containers"]) == len(rollback_commands) == 17 for item in snapshot["containers"]: assert item["image_ref"] == receipt["containers"][item["name"]]["image_ref"] cf = prepared.candidate["images"]["contextforge"] assert not controller._mcp_hub_contextforge_compat_proven( project, prepared.revision, cf["imageRef"], "sha256:" + "f" * 64 ) containers["itworx-mcp-hub-web-1"]["Config"]["Image"] = "web:mutable" with pytest.raises(controller.DeployError, match="identity or health"): controller.verify_mcp_hub_live_topology(project, prepared.model) @pytest.mark.parametrize("same_revision", [True, False]) def test_same_revision_receipt_prepares_exact_source_before_verification( controller, prepared, monkeypatch, same_revision ): project = prepared.project project.update({"source_dir": str(prepared.directory), "slug": "itworx-mcp-hub"}) monkeypatch.setattr( controller, "read_state", lambda _project: { "live_revision": prepared.revision if same_revision else "b" * 40 }, ) events = [] monkeypatch.setattr( controller, "sync_source", lambda *_args, **kwargs: events.append(("sync", kwargs["exact"])), ) monkeypatch.setattr( controller, "copy_environment", lambda *_args: events.append("environment") ) monkeypatch.setattr( controller, "mcp_hub_compose_model", lambda *_args: events.append("model") or prepared.model, ) controller.mcp_hub_prepare_current_receipt_model( prepared.directory, project, controller.MCP_HUB_REPOSITORY, prepared.revision ) assert events == ([("sync", True), "environment", "model"] if same_revision else []) assert (project.get("_mcp_hub_rendered_model") is prepared.model) is same_revision @pytest.mark.parametrize("mutation", ["missing", "extra", "tag", "nonmapping"]) def test_candidate_compose_override_rejects_malformed_ids( controller, prepared, mutation ): ids = { f"itworx-mcp-hub-{service}-1": record["imageId"] for service, record in prepared.candidate["images"].items() } if mutation == "missing": ids.pop("itworx-mcp-hub-web-1") elif mutation == "extra": ids["foreign"] = "sha256:" + "a" * 64 elif mutation == "tag": ids["itworx-mcp-hub-web-1"] = "web:latest" else: ids = [] prepared.project["_mcp_hub_rendered_model"] = prepared.model prepared.project["_mcp_hub_candidate_image_ids"] = ids with pytest.raises(controller.DeployError, match="candidate image IDs"): with controller._mcp_hub_compose_image_override(prepared.project): pytest.fail("must reject invalid image IDs") def test_actual_high_findings_are_bound_to_current_service_tags(controller, prepared): report = json.loads((prepared.directory / "vulnerability-web.json").read_text()) report["Results"][0]["Vulnerabilities"] = [ { "PkgName": "fixture", "VulnerabilityID": "CVE-2026-12345", "InstalledVersion": "1", "Severity": "HIGH", } ] prepared.candidate["images"]["web"]["vulnerabilitySha256"] = prepared.write( "vulnerability-web.json", report ) summary = { "unresolvedCriticalOrHigh": [ { "package": "fixture", "cves": ["CVE-2026-12345"], "installedVersion": "1", "severity": "HIGH", "affectedImages": [prepared.candidate["images"]["web"]["imageRef"]], } ] } prepared.candidate["summarySha256"] = prepared.write("scan-summary.json", summary) candidate_sha = prepared.write("candidate.json", prepared.candidate) prepared.write( "approval.json", { "schema": "itworx.mcp-hub-cve-approval/v1", "candidateSha256": candidate_sha, "exceptionsSha256": hashlib.sha256( (prepared.directory / "exceptions.json").read_bytes() ).hexdigest(), }, ) controller.mcp_hub_load_release_candidate(prepared.project, prepared.revision) assert prepared.checks == ["gate"] def test_fixable_high_findings_cannot_be_approved_around(controller, prepared): report = json.loads((prepared.directory / "vulnerability-web.json").read_text()) report["Results"][0]["Vulnerabilities"] = [ { "PkgName": "fixture", "VulnerabilityID": "CVE-2026-12345", "InstalledVersion": "1", "FixedVersion": "2", "Severity": "HIGH", } ] prepared.candidate["images"]["web"]["vulnerabilitySha256"] = prepared.write( "vulnerability-web.json", report ) candidate_sha = prepared.write("candidate.json", prepared.candidate) prepared.write( "approval.json", { "schema": "itworx.mcp-hub-cve-approval/v1", "candidateSha256": candidate_sha, "exceptionsSha256": hashlib.sha256( (prepared.directory / "exceptions.json").read_bytes() ).hexdigest(), }, ) with pytest.raises(controller.DeployError, match="fixable critical/high"): controller.mcp_hub_load_release_candidate(prepared.project, prepared.revision) assert prepared.checks == [] def test_missing_normal_upgrade_volume_baseline_denies_before_intent_or_start( controller, prepared, monkeypatch ): controller.mcp_hub_load_release_candidate(prepared.project, prepared.revision) monkeypatch.setattr(controller, "mcp_hub_require_docker_capacity", lambda: None) monkeypatch.setattr( controller, "mcp_hub_stage_release_source", lambda *_args: prepared.directory ) monkeypatch.setattr( controller, "mcp_hub_compose_model", lambda *_args: prepared.model ) monkeypatch.setattr( controller, "compose_command", lambda *_args: (prepared.directory, ["docker", "compose"]), ) monkeypatch.setattr(controller, "deployment_environment", lambda *_args: {}) monkeypatch.setattr( controller.subprocess, "run", lambda *_args, **_kwargs: types.SimpleNamespace(returncode=0), ) monkeypatch.setattr( controller, "_mcp_hub_begin_migration_intent", lambda *_args: pytest.fail( "missing volume identity must precede durable intent" ), ) monkeypatch.setattr( controller, "mcp_hub_up_services", lambda *_args: pytest.fail( "missing volume identity must precede Compose mutation" ), ) with pytest.raises(controller.DeployError, match="volume baseline"): controller.deploy_mcp_hub_staged( prepared.project, prepared.revision, controller.MCP_HUB_REPOSITORY ) @pytest.mark.parametrize( "mode, candidate_epoch", [("deploy", "1790888863"), ("deploy", "1"), ("preflight", "1790888863")], ) def test_main_hub_path_sets_validated_epoch_before_compose_render( controller, prepared, monkeypatch, mode, candidate_epoch ): class ReachedValidatedRender(Exception): pass target = { **prepared.project, "branch": "main", "slug": controller.MCP_HUB_PROJECT, "source_dir": str(prepared.directory), } target["compose_project"] = controller.MCP_HUB_PROJECT target.pop("_mcp_hub_source_date_epoch", None) monkeypatch.setattr( controller, "load_projects", lambda: {controller.MCP_HUB_REPOSITORY: target} ) monkeypatch.setattr( controller, "effective_mcp_hub_project", lambda _repository, project: dict(project), ) monkeypatch.setattr( controller, "repository_path", lambda *_args: prepared.directory ) monkeypatch.setattr(controller, "validate_commit", lambda *_args: prepared.revision) candidate = copy.deepcopy(prepared.candidate) candidate["sourceDateEpoch"] = candidate_epoch monkeypatch.setattr( controller, "mcp_hub_load_release_candidate", lambda *_args: candidate ) monkeypatch.setenv("SOURCE_DATE_EPOCH", "untrusted-environment-override") def render(_repo, project, revision): if mode == "deploy" and candidate_epoch != "1790888863": pytest.fail( "mismatched candidate epoch must deny before Compose validation" ) assert project["_mcp_hub_source_date_epoch"] == "1790888863" assert ( controller.deployment_environment( project, revision, controller.MCP_HUB_REPOSITORY )["SOURCE_DATE_EPOCH"] == "1790888863" ) raise ReachedValidatedRender() monkeypatch.setattr(controller, "validate_project_config", render) monkeypatch.setattr( sys, "argv", ["deploy.py", mode, controller.MCP_HUB_REPOSITORY, prepared.revision], ) if mode == "deploy" and candidate_epoch != "1790888863": with pytest.raises(controller.DeployError, match="epoch"): controller.main() else: with pytest.raises(ReachedValidatedRender): controller.main()