from __future__ import annotations import hashlib import importlib import json import os import shutil import subprocess import sys import types from datetime import datetime, timezone from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] BROKER_DIR = ROOT / "ops" / "gitea-deploy-broker" @pytest.fixture def modules(monkeypatch): if "fcntl" not in sys.modules: shim = types.ModuleType("fcntl") shim.LOCK_EX, shim.LOCK_UN, shim.flock = 2, 8, lambda *_args: None monkeypatch.setitem(sys.modules, "fcntl", shim) monkeypatch.syspath_prepend(str(BROKER_DIR)) sys.modules.pop("deploy", None) sys.modules.pop("prepare_mcp_hub_candidate", None) controller = importlib.import_module("deploy") utility = importlib.import_module("prepare_mcp_hub_candidate") monkeypatch.setattr(utility, "_is_host_root", lambda: True) yield controller, utility sys.modules.pop("prepare_mcp_hub_candidate", None) sys.modules.pop("deploy", None) @pytest.fixture def harness(modules, tmp_path, monkeypatch): controller, utility = modules revision = "a" * 40 epoch = "1790888863" 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, } repo = tmp_path / "hub.git" project = { "branch": "main", "slug": controller.MCP_HUB_PROJECT, "compose_project": controller.MCP_HUB_PROJECT, "containers": list(controller.MCP_HUB_CONTAINERS), "_mcp_hub_contract": controller.MCP_HUB_TOPOLOGY_CONTRACT, "_repository_path": str(repo), "repo_name": "itworx_mcp_hub", } state = types.SimpleNamespace( source=tmp_path / "releases" / revision, compose_marker="baseline", clock=now, ingestion_digest=None, images={}, mutations=[], inspect_calls=[], ) state.source.mkdir(parents=True) (state.source / "scripts").mkdir() draft = state.source / "scripts" / "draft_cve_exceptions.py" draft.write_text( "import argparse, json\n" "from datetime import date, timedelta\n" "from pathlib import Path\n" "p=argparse.ArgumentParser()\n" "p.add_argument('--scan-summary', required=True)\n" "p.add_argument('--image-digests', required=True)\n" "p.add_argument('--evidence-reference', required=True)\n" "p.add_argument('--output', required=True)\n" "a=p.parse_args()\n" "scan=json.loads(Path(a.scan_summary).read_text())\n" "digest=json.loads(Path(a.image_digests).read_text())['images']\n" "exceptions=[]\n" "for row in scan['unresolvedCriticalOrHigh']:\n" " refs=set(row['affectedImages'])\n" " affected={s:v['imageId'] for s,v in digest.items() if v['tag'] in refs}\n" " for cve in row['cves']:\n" " exceptions.append({'id':row['package']+'-'+cve,'package':row['package'],'cve':cve,\n" " 'installedVersion':row['installedVersion'],'severity':row['severity'],\n" " 'approvalStatus':'pending','riskOwner':'','approver':'','affectedImageDigests':affected,\n" " 'evidenceReference':a.evidence_reference})\n" "Path(a.output).write_text(json.dumps({'schemaVersion':'1.0','exceptions':exceptions}))\n" "", encoding="utf-8", ) raw_project = dict(project) monkeypatch.setattr(controller, "STATE_ROOT", tmp_path) monkeypatch.setattr(controller, "MCP_HUB_RELEASES_ROOT", tmp_path / "releases") monkeypatch.setattr(controller, "_mcp_hub_require_trusted_state_root", lambda: None) monkeypatch.setattr(controller, "_mcp_hub_require_no_posix_acl", lambda _path: None) monkeypatch.setattr(controller, "_root_owned", lambda _path: True) monkeypatch.setattr( controller, "load_projects", lambda: {controller.MCP_HUB_REPOSITORY: raw_project}, ) monkeypatch.setattr( controller, "effective_mcp_hub_project", lambda repository, value: dict(value) ) monkeypatch.setattr( controller, "repository_path", lambda _repository, _project: repo ) monkeypatch.setattr(controller, "output", lambda _args: revision) monkeypatch.setattr( controller, "validate_commit", lambda _repo, branch, commit: commit if branch == "main" else "", ) def deployment_metadata(_repo, _revision, _repository, *, built_at=None): result = dict(metadata) result["builtAt"] = built_at or state.clock datetime.fromisoformat(result["builtAt"].replace("Z", "+00:00")) return result monkeypatch.setattr(controller, "deployment_metadata", deployment_metadata) monkeypatch.setattr(controller, "mcp_hub_source_date_epoch", lambda *_args: epoch) def stage(_project, _repo, commit): assert commit == revision return state.source monkeypatch.setattr(controller, "mcp_hub_stage_release_source", stage) monkeypatch.setattr( controller, "exact_source_matches_revision", lambda *_args: True ) monkeypatch.setattr( controller, "_mcp_hub_verify_release_tree_metadata", lambda *_args: None ) monkeypatch.setattr(controller, "mcp_hub_validate_secret_files", lambda _model: 21) def rendered(project_arg, commit, repository): assert commit == revision assert repository == controller.MCP_HUB_REPOSITORY services = {} for index, service in enumerate(controller.MCP_HUB_SERVICES, 1): if service in controller.MCP_HUB_OWNED_IMAGE_REPOSITORIES: local = controller.MCP_HUB_LOCAL_BUILD_IMAGE_REPOSITORIES.get(service) if service == "connector-ingestion": reference = ( f"{controller.MCP_HUB_OWNED_IMAGE_REPOSITORIES[service]}@sha256:{state.ingestion_digest}" if state.ingestion_digest else "" ) else: reference = ( f"{local}:{revision[:12]}" if local else f"{controller.MCP_HUB_OWNED_IMAGE_REPOSITORIES[service]}@sha256:{index:064x}" ) elif service == "contextforge": reference = f"itworx-mcp-hub/contextforge-schema:{revision}" elif service == "ingestion-clamd": reference = controller.MCP_HUB_INTAKE_CLAMAV_IMAGE elif service == "postgres": reference = controller.MCP_HUB_POSTGRES_IMAGE elif service == "redis": reference = controller.MCP_HUB_REDIS_IMAGE else: reference = f"fixture/{service}@sha256:{index:064x}" services[service] = {"image": reference} return {"services": services, "testMarker": state.compose_marker} monkeypatch.setattr(controller, "mcp_hub_compose_model", rendered) def inspect(reference): state.inspect_calls.append(reference) return state.images.get(reference) monkeypatch.setattr(controller, "inspect_image", inspect) monkeypatch.setattr( controller, "run", lambda *_args, **_kwargs: state.mutations.append("run") ) monkeypatch.setattr(utility, "_is_host_root", lambda: True) utility.freeze_candidate(controller.MCP_HUB_REPOSITORY, revision) freeze_dir = tmp_path / "mcp-hub-source-freezes" / revision freeze_doc = json.loads((freeze_dir / "freeze.json").read_text()) # Publication happens after freeze and advances the wall clock. The plan # uses the immutable timestamp and the now-available registry digest. state.clock = "2026-10-04T02:03:04.567Z" state.ingestion_digest = "f" * 64 plan = utility.plan_candidate(controller.MCP_HUB_REPOSITORY, revision) candidate_dir = tmp_path / "mcp-hub-release-candidates" / revision plan_doc = json.loads((candidate_dir / "build.json").read_text()) model = rendered(project, revision, controller.MCP_HUB_REPOSITORY) 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": epoch, } for index, service in enumerate(controller.MCP_HUB_SERVICES, 1): reference = model["services"][service]["image"] image_id = "sha256:" + f"{index:064x}" config = {"User": "10001:10001"} if ( service in controller.MCP_HUB_OWNED_IMAGE_REPOSITORIES or service == "contextforge" ): config["Labels"] = dict(labels) state.images[reference] = {"Id": image_id, "Config": config} def write_input(name: str, value: object) -> Path: path = candidate_dir / name data = ( json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n" ).encode() descriptor = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) with os.fdopen(descriptor, "wb") as handle: handle.write(data) path.chmod(0o600) return path for index, service in enumerate(controller.MCP_HUB_SERVICES, 1): image_id = "sha256:" + f"{index:064x}" 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"}], "Vulnerabilities": [], } ], } secret = { "ImageID": image_id, "CreatedAt": now, "TrivyVersion": "0.73.0", "FindingCount": 0, "SensitiveFindingDetailsPersisted": False, "Results": [{"FindingCount": 0}], } write_input(f"vulnerability-{service}.json", vuln) write_input(f"secret-{service}.json", secret) write_input("trivy-db-metadata.json", {"UpdatedAt": now, "DownloadedAt": now}) # The reports must match the IDs in the final inspect inventory. Keep a # convenient writer for intentional drift cases in each test. monkeypatch.setattr(utility, "_test_write_input", write_input, raising=False) yield types.SimpleNamespace( controller=controller, utility=utility, revision=revision, epoch=epoch, metadata=metadata, project=project, state=state, candidate_dir=candidate_dir, model=model, write_input=write_input, now=now, plan=plan, plan_doc=plan_doc, freeze_dir=freeze_dir, freeze_doc=freeze_doc, ) def test_plan_freezes_protected_metadata_without_candidate_or_approval(harness): h = harness document = json.loads((h.candidate_dir / "build.json").read_text()) assert document["schema"] == "itworx.mcp-hub-build-plan/v1" assert document["repository"] == h.controller.MCP_HUB_REPOSITORY assert document["revision"] == h.revision assert document["build"] == h.metadata assert document["build"]["builtAt"] == h.freeze_doc["build"]["builtAt"] assert document["build"]["builtAt"] != h.state.clock assert document["sourceDateEpoch"] == h.epoch assert len(document["composeSha256"]) == 64 assert h.state.ingestion_digest in str(h.model) assert not (h.candidate_dir / "candidate.json").exists() assert not (h.candidate_dir / "approval.json").exists() assert h.state.mutations == [] def _remove_candidate_and_freeze(h): shutil.rmtree(h.candidate_dir, ignore_errors=True) shutil.rmtree(h.freeze_dir.parent, ignore_errors=True) def _prepare_freeze_dir(h): h.freeze_dir.parent.mkdir(mode=0o700) h.freeze_dir.mkdir(mode=0o700) def test_plan_requires_existing_freeze_before_any_candidate_write(harness): h = harness _remove_candidate_and_freeze(h) with pytest.raises( h.controller.DeployError, match="run freeze, publish the fixed ingestion image, then run plan", ): h.utility.plan_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) assert not h.candidate_dir.exists() def test_freeze_is_non_replayable(harness): h = harness original = (h.freeze_dir / "freeze.json").read_bytes() with pytest.raises(h.controller.DeployError, match="never overwritten|already exists"): h.utility.freeze_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) assert (h.freeze_dir / "freeze.json").read_bytes() == original @pytest.mark.parametrize( ("mutation", "message"), [ ("revision", "freeze record.*source identity"), ("tree", "freeze record.*source identity"), ("calendar", "freeze record.*source identity"), ("epoch", "freeze record.*source epoch"), ("duplicate", "unavailable or invalid|duplicate"), ("partial", "unavailable or invalid|partial"), ], ) def test_plan_rejects_invalid_freeze_records_without_candidate_writes( harness, mutation, message ): h = harness _remove_candidate_and_freeze(h) _prepare_freeze_dir(h) record = dict(h.freeze_doc) if mutation == "revision": record["revision"] = "c" * 40 payload = json.dumps(record).encode() elif mutation == "tree": record["build"] = dict(record["build"]) record["build"]["tree"] = "d" * 40 payload = json.dumps(record).encode() elif mutation == "calendar": record["build"] = dict(record["build"]) record["build"]["builtAt"] = "2026-99-99T00:00:00.000Z" payload = json.dumps(record).encode() elif mutation == "epoch": record["sourceDateEpoch"] = "1790888864" payload = json.dumps(record).encode() elif mutation == "duplicate": payload = b'{"schema":"duplicate","schema":"duplicate"}' else: payload = b'{"schema":' (h.freeze_dir / "freeze.json").write_bytes(payload) (h.freeze_dir / "freeze.json").chmod(0o600) with pytest.raises(h.controller.DeployError, match=message): h.utility.plan_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) assert not h.candidate_dir.exists() def test_plan_rejects_symlink_freeze_file_without_candidate_writes(harness, tmp_path): h = harness _remove_candidate_and_freeze(h) _prepare_freeze_dir(h) outside = tmp_path / "outside.json" outside.write_text(json.dumps(h.freeze_doc)) try: (h.freeze_dir / "freeze.json").symlink_to(outside) except OSError: pytest.skip("symlink creation is unavailable on this filesystem") with pytest.raises( h.controller.DeployError, match="unavailable or invalid|unsafe|not a regular file", ): h.utility.plan_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) assert not h.candidate_dir.exists() def test_plan_rejects_fifo_freeze_record_without_blocking(harness): if os.name == "nt" or not hasattr(os, "mkfifo"): pytest.skip("FIFO files are a POSIX filesystem contract") h = harness _remove_candidate_and_freeze(h) _prepare_freeze_dir(h) os.mkfifo(h.freeze_dir / "freeze.json", 0o600) with pytest.raises(h.controller.DeployError, match="not a regular file"): h.utility.plan_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) assert not h.candidate_dir.exists() def test_plan_rejects_unsafe_freeze_directory_mode_without_candidate_writes(harness): h = harness if os.name == "nt": pytest.skip("POSIX private-mode enforcement is a Linux controller contract") _remove_candidate_and_freeze(h) _prepare_freeze_dir(h) (h.freeze_dir / "freeze.json").write_text(json.dumps(h.freeze_doc)) (h.freeze_dir / "freeze.json").chmod(0o600) h.freeze_dir.chmod(0o755) with pytest.raises(h.controller.DeployError, match="private|root-owned|ACL"): h.utility.plan_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) assert not h.candidate_dir.exists() @pytest.mark.parametrize( "fault", ["file_mode", "owner", "dir_owner", "acl", "dir_acl"] ) def test_plan_rejects_unsafe_freeze_file_metadata_without_candidate_writes( harness, monkeypatch, fault ): h = harness if fault == "file_mode" and os.name == "nt": pytest.skip("POSIX private-mode enforcement is a Linux controller contract") _remove_candidate_and_freeze(h) _prepare_freeze_dir(h) freeze_file = h.freeze_dir / "freeze.json" freeze_file.write_text(json.dumps(h.freeze_doc)) freeze_file.chmod(0o600) if fault == "file_mode": freeze_file.chmod(0o644) match = "unsafe" elif fault in {"owner", "dir_owner"}: original = h.controller._root_owned denied_path = h.freeze_dir if fault == "dir_owner" else freeze_file monkeypatch.setattr( h.controller, "_root_owned", lambda path: False if Path(path) == denied_path else original(path), ) match = "root-owned|unsafe|owner" else: original = h.controller._mcp_hub_require_no_posix_acl def reject_freeze_acl(path): if Path(path) == (h.freeze_dir if fault == "dir_acl" else freeze_file): raise h.controller.DeployError("freeze ACL denied") original(path) monkeypatch.setattr(h.controller, "_mcp_hub_require_no_posix_acl", reject_freeze_acl) match = "ACL" with pytest.raises(h.controller.DeployError, match=match): h.utility.plan_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) assert not h.candidate_dir.exists() def test_freeze_rejects_nonexact_local_main_head_without_writes(harness, monkeypatch): h = harness _remove_candidate_and_freeze(h) monkeypatch.setattr(h.controller, "output", lambda _args: "c" * 40) with pytest.raises(h.controller.DeployError, match="exact protected main head"): h.utility.freeze_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) assert not h.freeze_dir.parent.exists() def test_freeze_rejects_staged_source_mismatch_without_freeze_record(harness, monkeypatch): h = harness _remove_candidate_and_freeze(h) monkeypatch.setattr(h.controller, "exact_source_matches_revision", lambda *_args: False) with pytest.raises(h.controller.DeployError, match="Staged MCP Hub source differs"): h.utility.freeze_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) assert not h.freeze_dir.parent.exists() def test_freeze_rejects_invalid_epoch_before_staging_or_freeze_record(harness, monkeypatch): h = harness _remove_candidate_and_freeze(h) monkeypatch.setattr(h.controller, "mcp_hub_source_date_epoch", lambda *_args: "invalid") monkeypatch.setattr( h.controller, "mcp_hub_stage_release_source", lambda *_args: pytest.fail("invalid epoch must fail before source staging"), ) with pytest.raises(h.controller.DeployError, match="source epoch"): h.utility.freeze_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) assert not h.freeze_dir.parent.exists() def test_plan_rejects_staged_source_drift_before_candidate_writes(harness, monkeypatch): h = harness shutil.rmtree(h.candidate_dir) monkeypatch.setattr(h.controller, "exact_source_matches_revision", lambda *_args: False) with pytest.raises(h.controller.DeployError, match="Staged MCP Hub source differs"): h.utility.plan_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) assert not h.candidate_dir.exists() def test_freeze_wrong_repository_and_nonroot_fail_before_writes(modules, monkeypatch, tmp_path): controller, utility = modules monkeypatch.setattr(utility, "_is_host_root", lambda: True) monkeypatch.setattr(controller, "STATE_ROOT", tmp_path) with pytest.raises(controller.DeployError, match="fixed MCP Hub repository"): utility.freeze_candidate("someone/else", "a" * 40) assert list(tmp_path.iterdir()) == [] monkeypatch.setattr(utility, "_is_host_root", lambda: False) with pytest.raises(controller.DeployError, match="host root"): utility.freeze_candidate(controller.MCP_HUB_REPOSITORY, "a" * 40) assert list(tmp_path.iterdir()) == [] def test_plan_candidate_freezes_the_selected_workflow_creation_timestamp( harness, monkeypatch ): h = harness revision = h.revision built_at = "2026-10-03T12:34:56.789Z" observed = [] metadata = { "id": revision[:12], "repository": h.controller.MCP_HUB_REPOSITORY, "revision": revision, "tree": "d" * 40, "treeDigest": hashlib.sha256(("git-tree:" + "d" * 40).encode()).hexdigest(), "builtAt": built_at, } monkeypatch.setattr(h.controller, "output", lambda _args: revision) monkeypatch.setattr(h.controller, "validate_commit", lambda _repo, _branch, commit: commit) def deployment_metadata(_repo, supplied_revision, repository, *, built_at=None): observed.append(built_at) assert supplied_revision == revision assert repository == h.controller.MCP_HUB_REPOSITORY return dict(metadata) monkeypatch.setattr(h.controller, "deployment_metadata", deployment_metadata) monkeypatch.setattr(h.controller, "mcp_hub_stage_release_source", lambda *_args: h.state.source) monkeypatch.setattr(h.controller, "exact_source_matches_revision", lambda *_args: True) compose_calls = [] def fixed_compose_modeler(project, revision, repository): compose_calls.append((project, revision, repository)) return { "services": { service: {"image": f"fixture/{service}:candidate"} for service in h.controller.MCP_HUB_SERVICES } } monkeypatch.setattr( h.controller, "mcp_hub_compose_model", lambda *_args: pytest.fail("plan used the ambient Compose renderer"), ) staging = ( h.controller.STATE_ROOT / "mcp-hub-release-imports" / revision / ("1" * 32) / revision ) h.utility.plan_candidate( h.controller.MCP_HUB_REPOSITORY, revision, built_at=built_at, staging_directory=staging, compose_modeler=fixed_compose_modeler, ) plan = json.loads( (staging / "build.json").read_text() ) assert observed == [built_at, built_at] assert plan["build"]["builtAt"] == built_at assert len(compose_calls) == 1 def test_candidate_preparation_rejects_staging_path_outside_private_import_anchor( harness, ): h = harness revision = h.revision staging = h.controller.STATE_ROOT / "outside-imports" / revision with pytest.raises(h.controller.DeployError, match="private anchor|staging path"): h.utility.plan_candidate( h.controller.MCP_HUB_REPOSITORY, revision, built_at="2026-10-03T12:34:56.789Z", staging_directory=staging, ) assert not staging.exists() def test_candidate_preparation_rejects_symlinked_staging_destination(harness): h = harness revision = h.revision anchor = h.controller.STATE_ROOT / "mcp-hub-release-imports" / revision token = anchor / ("2" * 32) token.mkdir(parents=True, mode=0o700) target = h.controller.STATE_ROOT / "untrusted-target" target.mkdir(mode=0o700) staging = token / revision try: staging.symlink_to(target, target_is_directory=True) except OSError: pytest.skip("symlink creation is unavailable on this filesystem") with pytest.raises(h.controller.DeployError): h.utility.plan_candidate( h.controller.MCP_HUB_REPOSITORY, revision, built_at="2026-10-03T12:34:56.789Z", staging_directory=staging, ) def test_candidate_preparation_rejects_nonprivate_staging_parent(harness): if os.name == "nt": pytest.skip("POSIX private-mode enforcement is a Linux controller contract") h = harness revision = h.revision anchor = h.controller.STATE_ROOT / "mcp-hub-release-imports" / revision token = anchor / ("3" * 32) token.mkdir(parents=True, mode=0o700) token.chmod(0o755) staging = token / revision with pytest.raises(h.controller.DeployError, match="private|safe|canonical"): h.utility.plan_candidate( h.controller.MCP_HUB_REPOSITORY, revision, built_at="2026-10-03T12:34:56.789Z", staging_directory=staging, ) def test_wrong_repository_and_nonroot_fail_before_controller_access( modules, monkeypatch ): controller, utility = modules monkeypatch.setattr(utility, "_is_host_root", lambda: True) monkeypatch.setattr( controller, "load_projects", lambda: pytest.fail("must reject repo first") ) with pytest.raises(controller.DeployError, match="fixed MCP Hub repository"): utility.plan_candidate("someone/else", "a" * 40) monkeypatch.setattr(utility, "_is_host_root", lambda: False) with pytest.raises(controller.DeployError, match="root"): utility.plan_candidate(controller.MCP_HUB_REPOSITORY, "a" * 40) def test_non_exact_main_head_is_denied_before_staging(modules, monkeypatch, tmp_path): controller, utility = modules monkeypatch.setattr(utility, "_is_host_root", lambda: True) project = { "branch": "main", "_mcp_hub_contract": controller.MCP_HUB_TOPOLOGY_CONTRACT, } monkeypatch.setattr( controller, "load_projects", lambda: {controller.MCP_HUB_REPOSITORY: project} ) monkeypatch.setattr( controller, "effective_mcp_hub_project", lambda _repo, value: value ) monkeypatch.setattr( controller, "repository_path", lambda *_args: tmp_path / "hub.git" ) monkeypatch.setattr(controller, "output", lambda _args: "c" * 40) monkeypatch.setattr( controller, "mcp_hub_stage_release_source", lambda *_args: pytest.fail("must not stage"), ) with pytest.raises(controller.DeployError, match="exact protected main head"): utility.plan_candidate(controller.MCP_HUB_REPOSITORY, "a" * 40) def test_capture_emits_candidate_and_pending_only_envelope_without_apply_calls( harness, monkeypatch ): h = harness calls = [] original_run = h.utility.subprocess.run def observe_draft(command, **kwargs): calls.append((command, kwargs)) return original_run(command, **kwargs) monkeypatch.setattr(h.utility.subprocess, "run", observe_draft) result = h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) candidate = json.loads((h.candidate_dir / "candidate.json").read_text()) exceptions = json.loads((h.candidate_dir / "exceptions.json").read_text()) digests = json.loads((h.candidate_dir / "image-digests.json").read_text()) assert result["status"] == "pending_review" assert set(candidate["images"]) == set(h.controller.MCP_HUB_SERVICES) assert candidate["images"]["connector-ingestion"]["imageRef"].endswith( "@sha256:" + h.state.ingestion_digest ) assert candidate["build"]["builtAt"] == h.freeze_doc["build"]["builtAt"] assert digests["images"]["web"]["tag"] == candidate["images"]["web"]["imageRef"] assert digests["images"]["web"]["imageId"] == candidate["images"]["web"]["imageId"] candidate_bytes = (h.candidate_dir / "candidate.json").read_bytes() exceptions_bytes = (h.candidate_dir / "exceptions.json").read_bytes() assert result["candidateSha256"] == hashlib.sha256(candidate_bytes).hexdigest() assert result["exceptionsSha256"] == hashlib.sha256(exceptions_bytes).hexdigest() assert not (h.candidate_dir / "approval.json").exists() assert all(item["approvalStatus"] == "pending" for item in exceptions["exceptions"]) assert h.state.mutations == [] assert len(h.state.inspect_calls) == 17 assert len(calls) == 1 assert ( Path(calls[0][0][1]) == h.state.source / "scripts" / "draft_cve_exceptions.py" ) assert calls[0][1]["stdout"] is subprocess.DEVNULL assert calls[0][1]["stderr"] is subprocess.DEVNULL assert set(calls[0][1]["env"]) == { "PATH", "PYTHONDONTWRITEBYTECODE", "PYTHONIOENCODING", } def test_capture_can_use_the_importers_fixed_image_inspector(harness, monkeypatch): h = harness inspected = [] def fixed_inspector(reference): inspected.append(reference) return h.state.images.get(reference) monkeypatch.setattr( h.controller, "inspect_image", lambda _reference: pytest.fail("capture used the ambient Docker context"), ) monkeypatch.setattr( h.controller, "mcp_hub_compose_model", lambda *_args: pytest.fail("capture used the ambient Compose renderer"), ) result = h.utility.capture_candidate( h.controller.MCP_HUB_REPOSITORY, h.revision, image_inspector=fixed_inspector, compose_modeler=lambda *_args: h.model, ) assert result["status"] == "pending_review" assert len(inspected) == 17 assert set(inspected) == { row["image"] for row in h.model["services"].values() } @pytest.mark.parametrize("mutation", ["label", "root"]) def test_capture_rejects_contextforge_provenance_drift_without_candidate( harness, mutation ): h = harness reference = h.model["services"]["contextforge"]["image"] config = h.state.images[reference]["Config"] if mutation == "label": config["Labels"]["org.opencontainers.image.source-tree"] = "wrong" else: config["User"] = "0:10001" with pytest.raises(h.controller.DeployError, match="provenance|non-root runtime"): h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) assert not (h.candidate_dir / "candidate.json").exists() assert not (h.candidate_dir / "approval.json").exists() assert h.state.mutations == [] def test_pending_high_findings_are_drafted_from_the_exact_staged_source(harness): h = harness report_path = h.candidate_dir / "vulnerability-web.json" report = json.loads(report_path.read_text()) report["Results"][0]["Vulnerabilities"] = [ { "PkgName": "libfixture", "VulnerabilityID": "CVE-2026-12345", "InstalledVersion": "1.2.3", "Severity": "HIGH", } ] report_path.unlink() h.write_input(report_path.name, report) result = h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) candidate = json.loads((h.candidate_dir / "candidate.json").read_text()) summary = json.loads((h.candidate_dir / "scan-summary.json").read_text()) exceptions = json.loads((h.candidate_dir / "exceptions.json").read_text())[ "exceptions" ] row = summary["unresolvedCriticalOrHigh"][0] assert row["affectedImages"] == [candidate["images"]["web"]["imageRef"]] assert exceptions[0]["approvalStatus"] == "pending" assert exceptions[0]["package"] == "libfixture" assert exceptions[0]["cve"] == "CVE-2026-12345" assert exceptions[0]["affectedImageDigests"] == { "web": candidate["images"]["web"]["imageId"] } assert result["status"] == "pending_review" assert not h.state.mutations def test_findings_for_same_cve_at_distinct_versions_remain_distinct(harness): h = harness for service, version in (("web", "1.2.3"), ("worker", "4.5.6")): path = h.candidate_dir / f"vulnerability-{service}.json" report = json.loads(path.read_text()) report["Results"][0]["Vulnerabilities"] = [ { "PkgName": "libfixture", "VulnerabilityID": "CVE-2026-12345", "InstalledVersion": version, "Severity": "HIGH", } ] path.unlink() h.write_input(path.name, report) h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) summary = json.loads((h.candidate_dir / "scan-summary.json").read_text()) assert {row["installedVersion"] for row in summary["unresolvedCriticalOrHigh"]} == { "1.2.3", "4.5.6", } def test_same_package_cve_version_with_conflicting_severity_fails_closed(harness): h = harness for service, severity in (("web", "HIGH"), ("worker", "CRITICAL")): path = h.candidate_dir / f"vulnerability-{service}.json" report = json.loads(path.read_text()) report["Results"][0]["Vulnerabilities"] = [ { "PkgName": "libfixture", "VulnerabilityID": "CVE-2026-12345", "InstalledVersion": "1.2.3", "Severity": severity, } ] path.unlink() h.write_input(path.name, report) with pytest.raises(h.controller.DeployError, match="conflicting scan severities"): h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) assert not (h.candidate_dir / "candidate.json").exists() def test_draft_for_all_images_is_rejected_when_finding_scope_is_narrower(harness): h = harness path = h.candidate_dir / "vulnerability-web.json" report = json.loads(path.read_text()) report["Results"][0]["Vulnerabilities"] = [ { "PkgName": "libfixture", "VulnerabilityID": "CVE-2026-12345", "InstalledVersion": "1.2.3", "Severity": "HIGH", } ] path.unlink() h.write_input(path.name, report) draft_script = h.state.source / "scripts" / "draft_cve_exceptions.py" draft_script.write_text( draft_script.read_text().replace("if v['tag'] in refs", "if True"), encoding="utf-8", ) with pytest.raises(h.controller.DeployError, match="pending-only document"): h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) assert not (h.candidate_dir / "candidate.json").exists() def test_missing_scan_report_fails_without_publishing_candidate(harness): h = harness (h.candidate_dir / "secret-web.json").unlink() with pytest.raises(h.controller.DeployError): h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) assert not (h.candidate_dir / "candidate.json").exists() assert not (h.candidate_dir / "approval.json").exists() assert h.state.mutations == [] def test_frozen_compose_drift_and_image_identity_drift_fail_closed(harness): h = harness h.state.compose_marker = "changed" with pytest.raises(h.controller.DeployError, match="Compose"): h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) h.state.compose_marker = "baseline" web_ref = h.model["services"]["web"]["image"] h.state.images[web_ref]["Id"] = "sha256:" + "f" * 64 with pytest.raises(h.controller.DeployError): h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) assert not (h.candidate_dir / "candidate.json").exists() def test_fixable_critical_or_high_finding_is_not_captured(harness): h = harness path = h.candidate_dir / "vulnerability-web.json" report = json.loads(path.read_text()) report["Results"][0]["Vulnerabilities"] = [ { "PkgName": "libfixture", "VulnerabilityID": "CVE-2026-12345", "InstalledVersion": "1.2.3", "FixedVersion": "1.2.4", "Severity": "CRITICAL", } ] path.unlink() h.write_input(path.name, report) with pytest.raises(h.controller.DeployError, match="fixable"): h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) assert not (h.candidate_dir / "candidate.json").exists() def test_capture_never_overwrites_existing_records(harness): h = harness h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) original = (h.candidate_dir / "candidate.json").read_bytes() with pytest.raises(h.controller.DeployError): h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) assert (h.candidate_dir / "candidate.json").read_bytes() == original assert h.state.mutations == [] def test_capture_preserves_full_supported_trivy_database_metadata(harness): h = harness path = h.candidate_dir / "trivy-db-metadata.json" path.unlink() metadata = { "UpdatedAt": h.now, "DownloadedAt": h.now, "Version": 2, "NextUpdate": h.now, } h.write_input(path.name, metadata) h.utility.capture_candidate(h.controller.MCP_HUB_REPOSITORY, h.revision) candidate = json.loads((h.candidate_dir / "candidate.json").read_text()) assert ( candidate["databaseMetadataSha256"] == hashlib.sha256(path.read_bytes()).hexdigest() ) assert json.loads(path.read_text()) == metadata def test_cli_requires_explicit_fixed_target_and_subcommand(modules, monkeypatch): _controller, utility = modules monkeypatch.setattr(utility, "_is_host_root", lambda: False) with pytest.raises(SystemExit): utility.main([]) monkeypatch.setattr(utility, "_is_host_root", lambda: True) assert ( utility.main(["plan", "--repository", "other/repo", "--revision", "a" * 40]) == 2 )