Files
Unraid-AppOps-Gateway-Public/backend/tests/test_mcp_hub_release_candidates.py
T

1005 lines
36 KiB
Python

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"),
"compose_project": controller.MCP_HUB_PROJECT,
"_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}"
elif service == "connector-ingestion":
repository = controller.MCP_HUB_OWNED_IMAGE_REPOSITORIES[service]
archive_ref = f"{repository}:{revision}-run-21531-attempt-2"
ref = f"{archive_ref}@sha256:" + f"{index:064x}"
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(
"import-receipt.json",
{
"schema": "itworx.mcp-hub-import-receipt/v1",
"repository": controller.MCP_HUB_REPOSITORY,
"sourceRevision": revision,
"sourceTree": metadata["tree"],
"workflowRunId": 21531,
"workflowRunAttempt": 2,
"artifactId": 882,
"artifactName": "hub-release-candidate-" + revision,
"artifactZipSha256": "1" * 64,
"manifestSha256": "2" * 64,
"dockerTarSha256": "3" * 64,
"candidateSha256": candidate_sha,
"draftExceptionsSha256": exceptions_sha,
"imageIds": {
service: image["imageId"] for service, image in images.items()
},
},
)
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)},
**(
{
"RepoDigests": [
f"{controller.MCP_HUB_OWNED_IMAGE_REPOSITORIES['connector-ingestion']}@{v['imageRef'].rsplit('@', 1)[1]}"
]
}
if service == "connector-ingestion"
else {}
),
}
for service, v in images.items()
}
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
):
prepared.project["environment"] = {
"CONNECTOR_INGESTION_IMAGE": "attacker.invalid/connector:latest"
}
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
)
binding = prepared.project["_mcp_hub_connector_ingestion_binding"]
environment = controller.deployment_environment(
prepared.project,
prepared.revision,
controller.MCP_HUB_REPOSITORY,
prepared.metadata,
)
assert environment["CONNECTOR_INGESTION_IMAGE"] == binding["imageRef"]
@pytest.mark.parametrize("mutation", ["missing", "attempt", "image_id"])
def test_import_receipt_is_required_and_binds_connector_ingestion_attempt(
controller, prepared, mutation
):
path = prepared.directory / "import-receipt.json"
if mutation == "missing":
path.unlink()
else:
receipt = json.loads(path.read_text(encoding="utf-8"))
if mutation == "attempt":
receipt["workflowRunAttempt"] = 1
else:
receipt["imageIds"]["connector-ingestion"] = "sha256:" + "f" * 64
prepared.write("import-receipt.json", receipt)
with pytest.raises(controller.DeployError):
controller.mcp_hub_load_release_candidate(
prepared.project, prepared.revision
)
assert prepared.checks == []
@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"])
if controller.os.name != "nt":
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)
receipt = json.loads(
(prepared.directory / "import-receipt.json").read_text(encoding="utf-8")
)
receipt["candidateSha256"] = candidate_sha
prepared.write("import-receipt.json", receipt)
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)
receipt = json.loads(
(prepared.directory / "import-receipt.json").read_text(encoding="utf-8")
)
receipt["candidateSha256"] = candidate_sha
prepared.write("import-receipt.json", receipt)
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()