Publish curated Unraid-AppOps-Gateway source from 2e53f628d284213b90ca2f9c5e121671f5933574

This commit is contained in:
Public Source Publisher committed 2026-10-02 22:44:18 +00:00
1 parent 4f9c5a1106
commit 2facb8de34
13 files changed
+3868 -1749

No files matched your search

+125
View File
@@ -5718,6 +5718,131 @@ def test_appops_request_is_idempotent_and_runs_only_fixed_deploy(status_module,
assert calls == [["python3", str(status_module.ROOT / "deploy.py"), "deploy", "jens/repo-a", "a" * 40]]
@pytest.mark.parametrize("operation", ["apply", "rollback"])
def test_generic_appops_hub_request_is_rejected_before_inbox_creation(
status_module, tmp_path, monkeypatch, operation
):
inbox = tmp_path / "appops-inbox"
monkeypatch.setattr(status_module, "APPOPS_DEPLOYMENT_INBOX_ROOT", inbox)
status, request_id = status_module._enqueue_appops_request(
repository="Jens/ITWorx_MCP_Hub",
revision="a" * 40,
operation=operation,
idempotency_key=f"appops-hub-{operation}-0001",
plan={},
)
assert (status, request_id) == ("invalid", "")
assert not inbox.exists()
@pytest.mark.parametrize("operation", ["apply", "rollback"])
@pytest.mark.parametrize(
("prior_status", "expected_status"),
[("pending", "failed"), ("running", "outcome_unknown")],
)
def test_direct_appops_hub_record_is_denied_before_project_or_deploy_access(
status_module, tmp_path, monkeypatch, operation, prior_status, expected_status
):
inbox = tmp_path / "appops-inbox"
inbox.mkdir()
request_id = "a" * 64
marker = inbox / f"{request_id}.json"
marker.write_text(
json.dumps(
{
"schema": "appops-deployment-request.v1",
"request": request_id,
"repository": "jens/itworx_mcp_hub",
"revision": "b" * 40,
"operation": operation,
"plan_sha256": "c" * 64,
"status": prior_status,
"received_at": 1,
"updated_at": 1,
}
),
encoding="utf-8",
)
monkeypatch.setattr(
status_module,
"_load_projects",
lambda: pytest.fail("generic Hub records must not inspect deployment projects"),
)
monkeypatch.setattr(
status_module.subprocess,
"run",
lambda *_args, **_kwargs: pytest.fail("generic Hub records must not launch deploy.py"),
)
status_module._process_appops_record(marker)
record = json.loads(marker.read_text(encoding="utf-8"))
assert record["status"] == expected_status
assert record["reason"] == "canonical_hub_push_required"
def test_hub_protected_push_worker_remains_the_fixed_deploy_path(
status_module, tmp_path, monkeypatch
):
repository = "jens/itworx_mcp_hub"
inbox = tmp_path / "webhook-inbox"
projects = tmp_path / "projects.json"
project = {
**_webhook_project(repository),
"slug": "itworx-mcp-hub",
"containers": ["itworx-mcp-hub"],
}
projects.write_text(json.dumps({repository: project}), encoding="utf-8")
monkeypatch.setattr(status_module, "WEBHOOK_INBOX_ROOT", inbox)
monkeypatch.setattr(status_module, "PROJECTS_PATH", projects)
revision = "d" * 40
raw_body = json.dumps(
_push_payload(revision, repository=repository), separators=(",", ":")
).encode()
_, request_id = status_module._enqueue_webhook_delivery(
raw_body,
"145440ab-b7b1-4c74-ae84-996d8d48167e",
77,
"refs/heads/main",
repository,
revision,
status_module._validation_policy(repository, project),
)
marker = inbox / f"{request_id}.json"
calls = []
class Result:
returncode = 0
monkeypatch.setattr(
status_module.subprocess,
"run",
lambda arguments, **_kwargs: calls.append(arguments) or Result(),
)
monkeypatch.setattr(status_module, "_deployment_state", lambda _slug: {})
monkeypatch.setattr(status_module, "_verified_receipt", lambda *_args: {"ok": True})
monkeypatch.setattr(
status_module,
"_fetch_gitea_validation",
lambda *_args: (
"pass",
[{
"context": "Managed validation / full (push)",
"status": "success",
"updated_at": "2026-09-14T00:00:00Z",
}],
),
)
status_module._process_webhook_record(marker)
assert json.loads(marker.read_text(encoding="utf-8"))["status"] == "deployed"
assert calls == [["python3", str(status_module.ROOT / "deploy.py"), "deploy", repository, revision]]
def test_durable_rollback_snapshot_is_receipt_bound_and_not_caller_selected(deploy_module, tmp_path, monkeypatch):
previous_revision = "a" * 40
current_revision = "b" * 40
+62 -265
View File
@@ -1,10 +1,14 @@
from __future__ import annotations
from sqlmodel import Session, SQLModel, create_engine
import pytest
from sqlmodel import Session, SQLModel, create_engine, select
from backend.app.db.models import Policy
from backend.app.db.models import DeploymentJob, Policy
from backend.app.schemas.deployment import DeploymentPlanV1
from backend.app.services.deployment_control_plane import DeploymentControlPlane
from backend.app.services.deployment_control_plane import (
DeploymentControlError,
DeploymentControlPlane,
)
from backend.app.services.secret_store import set_secret
@@ -31,19 +35,22 @@ def _plan() -> DeploymentPlanV1:
class _Broker:
def __init__(self, rollback_status: str = "rolled_back") -> None:
self.rollback_status = rollback_status
def __init__(self) -> None:
self.preflight_calls = 0
self.apply_calls: list[str] = []
self.rollback_calls: list[str] = []
def preflight(self, plan, *, secret):
self.preflight_calls += 1
return {"status": "preflight_ok"}
def apply(self, plan, *, secret, idempotency_key):
self.apply_calls.append(idempotency_key)
return {"status": "deployed", "request": "a" * 64}
def rollback(self, plan, *, secret, idempotency_key):
self.rollback_calls.append(idempotency_key)
return {"status": self.rollback_status, "request": "b" * 64}
return {"status": "rolled_back", "request": "b" * 64}
def status(self, request_id, *, secret):
return {"status": "rolled_back", "request": request_id}
@@ -103,11 +110,10 @@ def _plane(broker: _Broker) -> DeploymentControlPlane:
)
def _configure(session: Session, *, rollback_policy: bool = True) -> None:
def _configure(session: Session) -> None:
set_secret(session, "MCP_HUB_TEST_DEPLOYMENT_BROKER", "x" * 32)
session.add(_policy("deployment.apply"))
if rollback_policy:
session.add(_policy("deployment.rollback"))
session.add(_policy("deployment.rollback"))
session.commit()
@@ -117,284 +123,75 @@ def test_default_compose_file_survives_durable_plan_roundtrip():
assert restored.topology.compose_file == ".itworx/deployment.yml"
def test_post_apply_verification_failure_auto_rolls_back_with_exact_policy(tmp_path):
broker = _Broker("rolled_back")
@pytest.mark.parametrize("operation", ["apply", "rollback"])
def test_hub_typed_mutation_is_rejected_before_job_or_broker_queue(
tmp_path, operation
):
broker = _Broker()
plane = _plane(broker)
with _session(tmp_path) as session:
_configure(session)
result = _plane(broker).execute(
session,
plan=_plan(),
idempotency_key="mcp-hub-apply-0001",
operation="apply",
)
with pytest.raises(DeploymentControlError, match="signed protected push"):
plane.execute(
session,
plan=_plan(),
idempotency_key=f"mcp-hub-{operation}-typed-0001",
operation=operation,
)
assert result["status"] == "rolled_back"
assert result["stage"] == "auto_rollback_verified"
assert result["evidence"]["postApplyVerification"]["status"] == "failed"
assert result["evidence"]["autoRollback"]["status"] == "rolled_back"
assert broker.rollback_calls == [f"auto-rollback:{result['id']}"]
assert session.exec(select(DeploymentJob)).all() == []
def test_post_apply_verification_failure_does_not_rollback_without_exact_policy(tmp_path):
broker = _Broker("rolled_back")
with _session(tmp_path) as session:
_configure(session, rollback_policy=False)
result = _plane(broker).execute(
session,
plan=_plan(),
idempotency_key="mcp-hub-apply-0002",
operation="apply",
)
assert result["status"] == "failed"
assert result["stage"] == "external_integration_failed"
assert result["evidence"]["autoRollback"]["status"] == "blocked"
assert broker.preflight_calls == 0
assert broker.apply_calls == []
assert broker.rollback_calls == []
def test_post_apply_verification_failure_keeps_failure_when_controller_rejects_rollback(tmp_path):
broker = _Broker("rejected")
with _session(tmp_path) as session:
_configure(session)
result = _plane(broker).execute(
session,
plan=_plan(),
idempotency_key="mcp-hub-apply-0003",
operation="apply",
)
assert result["status"] == "failed"
assert result["stage"] == "auto_rollback_failed"
assert result["evidence"]["postApplyVerification"]["status"] == "failed"
assert result["evidence"]["autoRollback"]["status"] == "rejected"
assert len(broker.rollback_calls) == 1
def test_queued_auto_rollback_is_polled_and_finishes_as_auto_rollback(tmp_path):
broker = _Broker("accepted")
def test_hub_read_only_preflight_remains_available(tmp_path):
broker = _Broker()
plane = _plane(broker)
with _session(tmp_path) as session:
_configure(session)
initial = plane.execute(
session,
plan=_plan(),
idempotency_key="mcp-hub-apply-0004",
operation="apply",
)
assert initial["status"] == "rolling_back"
assert initial["stage"] == "auto_rollback_queued"
finished = plane.refresh(session, initial["id"])
assert finished["status"] == "rolled_back"
assert finished["stage"] == "auto_rollback_verified"
assert finished["evidence"]["autoRollback"]["status"] == "rolled_back"
def test_successful_post_apply_verification_never_invokes_auto_rollback(tmp_path):
class _HealthyNpm(_FailingNpm):
def ensure_managed_proxy_host(self, **kwargs):
return {"success": True, "status": "created", "hostId": 42}
broker = _Broker("rolled_back")
plane = DeploymentControlPlane(
broker=broker,
portainer=_Portainer(),
npm=_HealthyNpm(),
authentik=_Authentik(),
unraid=_Unraid(),
)
with _session(tmp_path) as session:
_configure(session)
result = plane.execute(
session,
plan=_plan(),
idempotency_key="mcp-hub-apply-0005",
operation="apply",
idempotency_key="mcp-hub-preflight-0001",
operation="preflight",
)
assert result["status"] == "succeeded"
assert result["stage"] == "controller_and_edge_verified"
assert result["status"] == "preflight_succeeded"
assert broker.preflight_calls == 1
assert broker.apply_calls == []
assert broker.rollback_calls == []
def test_auto_rollback_is_scoped_to_exact_mcp_hub_target(tmp_path):
broker = _Broker("rolled_back")
plan = _plan().model_copy(update={"repository": "jens/other-repository"})
with _session(tmp_path) as session:
_configure(session)
result = _plane(broker).execute(
session,
plan=plan,
idempotency_key="mcp-hub-apply-0006",
operation="apply",
)
assert result["status"] == "failed"
assert result["stage"] == "external_integration_failed"
assert result["evidence"]["postApplyVerification"]["status"] == "failed"
assert "autoRollback" not in result["evidence"]
assert broker.rollback_calls == []
def test_auto_rollback_rejects_same_repository_with_different_application(tmp_path):
broker = _Broker("rolled_back")
plan = _plan().model_copy(
update={
"application_id": "itworx-mcp-hub-copy",
"topology": _plan().topology.model_copy(
update={
"compose_project": "itworx-mcp-hub-copy",
"containers": ("itworx-mcp-hub-copy",),
}
),
}
)
with _session(tmp_path) as session:
set_secret(session, "MCP_HUB_TEST_DEPLOYMENT_BROKER", "x" * 32)
session.add(
Policy(
id="allow-copy-apply",
action="deployment.apply",
target="itworx-mcp-hub-copy",
voice="blocked",
manual="allowed",
risk="high",
reason="Test-only exact target.",
)
)
session.add(
Policy(
id="allow-copy-rollback",
action="deployment.rollback",
target="itworx-mcp-hub-copy",
voice="blocked",
manual="allowed",
risk="high",
reason="Test-only exact target.",
)
)
session.commit()
result = _plane(broker).execute(
session,
plan=plan,
idempotency_key="mcp-hub-apply-0007",
operation="apply",
)
assert result["status"] == "failed"
assert result["stage"] == "external_integration_failed"
assert result["evidence"]["postApplyVerification"]["status"] == "failed"
assert "autoRollback" not in result["evidence"]
assert broker.rollback_calls == []
def test_queued_auto_rollback_rejects_deployed_status_as_rollback_proof(tmp_path):
class _AmbiguousRollbackBroker(_Broker):
def status(self, request_id, *, secret):
return {"status": "deployed", "request": request_id}
broker = _AmbiguousRollbackBroker("accepted")
def test_hub_post_apply_failure_records_data_aware_recovery_without_rollback(tmp_path):
broker = _Broker()
plane = _plane(broker)
plan = _plan()
job = DeploymentJob(
id="mcp-hub-existing-apply",
application_id=plan.application_id,
repository=plan.repository,
revision=plan.revision,
plan_sha256="a" * 64,
idempotency_sha256="b" * 64,
status="applying",
plan_json=plan.model_dump(mode="json"),
evidence_json={"brokerStatus": "deployed"},
)
with _session(tmp_path) as session:
_configure(session)
initial = plane.execute(
session,
plan=_plan(),
idempotency_key="mcp-hub-apply-0008",
operation="apply",
)
assert initial["status"] == "rolling_back"
plane._complete_apply_job(session, job, plan)
finished = plane.refresh(session, initial["id"])
assert finished["status"] == "failed"
assert finished["stage"] == "auto_rollback_failed"
assert finished["evidence"]["autoRollback"]["status"] == "accepted"
assert "deployed state" in finished["result"]
def test_manual_rollback_requires_rolled_back_terminal_proof(tmp_path):
broker = _Broker("deployed")
with _session(tmp_path) as session:
_configure(session)
result = _plane(broker).execute(
session,
plan=_plan(),
idempotency_key="mcp-hub-rollback-0009",
operation="rollback",
)
assert result["status"] == "rollback_failed"
assert result["stage"] == "controller_rejected"
def test_apply_rejects_rolled_back_terminal_status(tmp_path):
class _RolledBackApplyBroker(_Broker):
def apply(self, plan, *, secret, idempotency_key):
return {"status": "rolled_back", "request": "c" * 64}
broker = _RolledBackApplyBroker()
with _session(tmp_path) as session:
_configure(session)
result = _plane(broker).execute(
session,
plan=_plan(),
idempotency_key="mcp-hub-apply-0010",
operation="apply",
)
assert result["status"] == "failed"
assert result["stage"] == "controller_rejected"
assert broker.rollback_calls == []
def test_auto_rollback_refuses_duplicate_exact_rollback_policies(tmp_path):
broker = _Broker("rolled_back")
with _session(tmp_path) as session:
_configure(session)
session.add(
Policy(
id="duplicate-rollback-policy",
action="deployment.rollback",
target="itworx-mcp-hub",
voice="blocked",
manual="allowed",
risk="high",
reason="Duplicate exact rollback policy.",
)
)
session.commit()
result = _plane(broker).execute(
session,
plan=_plan(),
idempotency_key="mcp-hub-apply-0011",
operation="apply",
)
assert result["status"] == "failed"
assert result["stage"] == "external_integration_failed"
assert result["evidence"]["autoRollback"] == {
"status": "blocked",
"reason": "exact-target-policy",
assert job.status == "failed"
assert job.stage == "external_integration_failed"
assert job.evidence_json["postApplyVerification"]["status"] == "failed"
assert job.evidence_json["recovery"] == {
"status": "manual_required",
"reason": "hub_data_aware_recovery_required",
}
assert "Automatic image-only rollback is disabled" in job.result_summary
assert broker.rollback_calls == []
def test_auto_rollback_rejects_unpollable_accepted_response(tmp_path):
class _MissingRequestBroker(_Broker):
def rollback(self, plan, *, secret, idempotency_key):
self.rollback_calls.append(idempotency_key)
return {"status": "accepted"}
broker = _MissingRequestBroker()
with _session(tmp_path) as session:
_configure(session)
result = _plane(broker).execute(
session,
plan=_plan(),
idempotency_key="mcp-hub-apply-0012",
operation="apply",
)
assert result["status"] == "failed"
assert result["stage"] == "auto_rollback_failed"
assert result["evidence"]["autoRollback"] == {
"status": "accepted",
"reason": "missing-controller-request",
}
assert len(broker.rollback_calls) == 1
@@ -0,0 +1,556 @@
from __future__ import annotations
import hashlib
import importlib
import json
import os
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",
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 "",
)
monkeypatch.setattr(
controller, "deployment_metadata", lambda *_args, **_kwargs: dict(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)
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)
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,
)
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["sourceDateEpoch"] == h.epoch
assert len(document["composeSha256"]) == 64
assert not (h.candidate_dir / "candidate.json").exists()
assert not (h.candidate_dir / "approval.json").exists()
assert h.state.mutations == []
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 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",
}
@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
)
@@ -1,5 +1,16 @@
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,
)
@@ -43,259 +54,581 @@ class FakePortainer:
raise AssertionError(path)
def test_reconciliation_is_fixed_to_mcp_hub_and_reconciles_only_verified_failed_forward_state():
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",
}
create = fake.calls[0]
command = create[2]["json"]["Cmd"]
command = fake.calls[0][2]["json"]["Cmd"]
assert command[:2] == ["python3", "-c"]
assert command[3:] == ["jens/itworx_mcp_hub", "itworx-mcp-hub"]
assert create[2]["json"]["AttachStdout"] is False
assert create[2]["json"]["AttachStderr"] is False
script = command[2]
assert "/controller-config/projects.json" in script
assert "/controller-hub-releases/controller-config/projects.json" in script
assert "os.path.samefile(registry_path, host_registry_path)" in script
assert "/mnt/cache/appdata/gitea-deploy-runners/state" in script
assert "/srv/example/appdata/gitea-deploy-runners/deploy.lock" not in script
assert "/mnt/cache/appdata/gitea-deploy-runners/deploy.lock" in script
assert 'project.get("branch") != "main"' in script
assert 'project.get("compose_project") != expected_compose_project' in script
assert 'recorded_status != "failed"' in script
assert 'recorded_live_status not in {"healthy", "unknown"}' in script
assert 'attempt_status != "failed"' in script
assert 'attempt_revision == recorded_live' in script
assert 'git_run("merge-base", "--is-ancestor", recorded_live, attempt_revision)' in script
assert 'git_run("merge-base", "--is-ancestor", attempt_revision, main_revision)' 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_live_status_is_accepted_only_for_a_proven_snapshot_stage_failure_without_rollback_error():
fake = FakePortainer()
McpHubDeploymentReconciliation(fake).run()
script = fake.calls[0][2]["json"]["Cmd"][2]
assert 'last_attempt_message = str(last_attempt.get("message") or "")' in script
assert "snapshot_failure_prefixes = (" in script
assert 'recorded_live_status == "unknown"' in script
assert 'last_attempt_message.startswith(snapshot_failure_prefixes)' in script
assert '"; rollback error:" in last_attempt_message' in script
assert '"; source rollback error:" in last_attempt_message' in script
assert "raise SystemExit(55)" in script
def test_reconciliation_proves_complete_live_compose_topology_and_preserves_actual_image_ids():
fake = FakePortainer()
McpHubDeploymentReconciliation(fake).run()
script = fake.calls[0][2]["json"]["Cmd"][2]
assert 'expected_containers = project.get("containers")' in script
assert "set(project_names) != set(expected_containers)" in script
assert 'labels.get("com.docker.compose.project") != expected_compose_project' in script
assert 'not runtime_state.get("Running")' in script
assert 'health in {"starting", "unhealthy"}' in script
assert 'image_id = str(container.get("Image") or "")' in script
assert '"containers": container_artifacts' in script
assert '"compose_sha256": ""' in script
assert '"topology_contract": topology_contract' in script
assert '"live_legacy_provenance": True' in script
assert '"live_artifacts": artifacts' in script
assert '"live_revision": recorded_live' in script
def test_reconciliation_recognizes_only_the_fixed_mcp_hub_oci_source_as_owned():
fake = FakePortainer()
McpHubDeploymentReconciliation(fake).run()
script = fake.calls[0][2]["json"]["Cmd"][2]
assert 'owned_image_source = "https://gitea.example.com/jens/itworx-mcp-hub"' in script
assert "if image_source == owned_image_source:" in script
assert 'git_run("merge-base", "--is-ancestor", recorded_live, image_revision)' in script
assert 'git_run("merge-base", "--is-ancestor", image_revision, main_revision)' in script
assert 'image_source.startswith("https://gitea.example.com/")' not in script
assert 'image_source == repository' not in script
assert '"live_revision": main_revision' not in script
assert '"live_revision": attempt_revision' not in script
def test_reconciliation_requires_the_controller_17_service_topology_migration():
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 15-to-17 topology migration requires its guarded protected-main deployment",
"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 'topology_contract = "itworx.mcp-hub-topology/17-v1"' in script
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 '"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_owned_image_ancestry_can_fail_closed_to_the_canonical_gitea_store():
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 'canonical_repository_root = Path("/mnt/cache/appdata/gitea/git/repositories")' in script
assert 'canonical_bare_repo = canonical_repository_root / owner / f"{name}.git"' in script
assert "def canonical_git_run(*args):" in script
assert "def canonical_image_ancestry_is_proven(" in script
assert 'resolved_recorded = canonical_git_run("rev-parse", f"{recorded_revision}^{{commit}}")' in script
assert 'resolved_image = canonical_git_run("rev-parse", f"{image_revision}^{{commit}}")' in script
assert 'resolved_main = canonical_git_run("rev-parse", "refs/heads/main")' in script
assert 'resolved_main.stdout.strip() == expected_main' in script
assert '"merge-base", "--is-ancestor", recorded_revision, image_revision' in script
assert '"merge-base", "--is-ancestor", image_revision, expected_main' in script
assert "if not mounted_ancestry_proven and not canonical_image_ancestry_is_proven(" in script
assert "canonical_bare_repo.is_symlink()" in script
def test_unchanged_recorded_image_ids_do_not_reinterpret_historical_oci_revision_labels():
fake = FakePortainer()
McpHubDeploymentReconciliation(fake).run()
script = fake.calls[0][2]["json"]["Cmd"][2]
change_probe = "image_changed = image_id != recorded_image_id"
provenance_gate = "if image_changed:"
compat_probe = "contextforge_compat_image = main_declares_contextforge_compat_image(name, image_ref)"
dataplane_probe = "main_declares_sessionless_dataplane_image(name, image_ref)"
owned_probe = "elif image_source == owned_image_source:"
external_probe = "elif not main_declares_external_image(image_ref):"
assert change_probe in script
assert provenance_gate in script
assert compat_probe in script
assert dataplane_probe in script
assert "not already_reconciled" in script
assert owned_probe in script
assert external_probe in script
assert script.index(change_probe) < script.index(provenance_gate)
assert script.index(provenance_gate) < script.index(compat_probe)
assert script.index(compat_probe) < script.index(dataplane_probe)
assert script.index(dataplane_probe) < script.index(owned_probe)
assert script.index(owned_probe) < script.index(external_probe)
assert "if image_id != recorded_image_id or container_id != recorded_container_id:" in script
def test_external_image_drift_requires_an_immutable_digest_declared_in_reviewed_main_compose():
fake = FakePortainer()
McpHubDeploymentReconciliation(fake).run()
script = fake.calls[0][2]["json"]["Cmd"][2]
assert "def main_declares_external_image(image_ref):" in script
assert "@sha256:" in script
assert 'git_run("show", f"{main_revision}:{compose_path}")' in script
assert '"infrastructure/compose/docker-compose.yml"' in script
assert '"infrastructure/compose/docker-compose.prod.yml"' in script
assert 'line.strip() in {f"image: {image_ref}", f\'image: "{image_ref}"\', f"image: \'{image_ref}\'"}' in script
assert "main_declares_external_image(image_ref)" in script
assert "elif not main_declares_external_image(image_ref):" in script
def test_contextforge_compat_image_drift_requires_exact_reviewed_main_contract():
fake = FakePortainer()
McpHubDeploymentReconciliation(fake).run()
script = fake.calls[0][2]["json"]["Cmd"][2]
assert 'container_name != "itworx-mcp-hub-contextforge-1"' in script
assert 'f"{main_revision}:infrastructure/images/contextforge/compat-image.env"' in script
assert 'f"{main_revision}:scripts/build_contextforge_compat_image.sh"' in script
assert '"CONTEXTFORGE_UPSTREAM_COMMIT"' in script
assert '"CONTEXTFORGE_ALEMBIC_HEAD"' in script
assert '"CONTEXTFORGE_MCP_SDK"' in script
assert '"CONTEXTFORGE_LOCAL_IMAGE"' in script
assert 're.fullmatch(r"[0-9a-f]{40}", upstream_commit)' in script
assert 're.fullmatch(r"[0-9a-f]{12}", alembic_head)' in script
assert 'local_image != f"itworx-mcp-hub/contextforge-schema:{alembic_head}"' in script
assert 'git -C "$SRC" fetch --depth 1 origin "$CONTEXTFORGE_UPSTREAM_COMMIT"' in script
assert 'git -C "$SRC" checkout --detach FETCH_HEAD' in script
assert 'MCP_REQUIREMENT="mcp==${CONTEXTFORGE_MCP_SDK}"' in script
assert 'alembic -c mcpgateway/alembic.ini heads' in script
assert 'docker tag "$STAGED_IMAGE" "$CONTEXTFORGE_LOCAL_IMAGE"' in script
assert 'image_ref.startswith("itworx-mcp-hub/contextforge-schema:")' not in script
compat_probe = "contextforge_compat_image = main_declares_contextforge_compat_image(name, image_ref)"
owned_probe = "elif image_source == owned_image_source:"
assert compat_probe in script
assert "if contextforge_compat_image or sessionless_dataplane_image:" in script
assert owned_probe in script
assert script.index(compat_probe) < script.index(owned_probe)
def test_sessionless_dataplane_baseline_is_one_time_and_commit_bound():
fake = FakePortainer()
McpHubDeploymentReconciliation(fake).run()
script = fake.calls[0][2]["json"]["Cmd"][2]
assert 'container_name != "itworx-mcp-hub-dataplane-router-1"' in script
assert 'image_ref != "itworx-mcp-hub/dataplane:sessionless-final"' in script
assert 'sessionless_commit = "b519a8c4cf17cd9df00a575acfe096838aab7789"' in script
assert 'f"{sessionless_commit}:services/dataplane_router/router.py"' in script
assert 'f"{sessionless_commit}:tests/test_dataplane_session_headers.py"' in script
assert 'f"{main_revision}:evidence/mcp-session-boundary-20260916/SUMMARY.md"' in script
assert 'MCP_TRANSPORT_STATE_HEADERS = frozenset({b"mcp-session-id"})' in script
assert 'del response.headers["mcp-session-id"]' in script
assert '"2025-11-25"' in script
assert '"2026-07-28"' in script
assert '"permanently enforce stateless public transport"' in script
assert (
'git_run("merge-base", "--is-ancestor", sessionless_commit, main_revision)'
in script
)
assert "not already_reconciled" in script
def test_already_reconciled_state_is_reverified_before_idempotent_success():
fake = FakePortainer()
McpHubDeploymentReconciliation(fake).run()
script = fake.calls[0][2]["json"]["Cmd"][2]
assert "already_reconciled = (" in script
assert script.index("already_reconciled = (") < script.index("project_names =")
assert script.index("container_artifacts = {}") < script.index("if already_reconciled:")
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 "if drift_found:" 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_refuses_unknown_runtime_topology():
result = McpHubDeploymentReconciliation(FakePortainer(exit_code=47)).run()
assert result == {
"status": "validation_failed",
"reason": "live MCP Hub Compose project differs from the controller target",
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_reconciliation_refuses_unproven_runtime_revision_drift():
result = McpHubDeploymentReconciliation(FakePortainer(exit_code=51)).run()
assert result == {
"status": "validation_failed",
"reason": "live MCP Hub image revision or external immutable image is not proven by the recorded-live to main chain",
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"
)
declared = {
"postgres:18.6@sha256:0377e72c5289ed2f98cf61b1a9c2db9eb9d300317fe14244492fbc94343b3d04",
"redis:8.10.2@sha256:7ef5b5cec96495a04ca7feff88a9492efeab8053fb284d24bdd73344c9245a48",
}
def test_reconciliation_refuses_state_that_is_not_the_known_failed_forward_shape():
result = McpHubDeploymentReconciliation(FakePortainer(exit_code=52)).run()
assert result == {
"status": "validation_failed",
"reason": "controller state drift is not the exact known recoverable MCP Hub state",
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 not fixed("postgres", "postgres:18.6@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 not fixed("postgres", "postgres:18.6@sha256:0377e72c5289ed2f98cf61b1a9c2db9eb9d300317fe14244492fbc94343b3d04")
assert not fixed("redis", "redis:8.10.2@sha256:7ef5b5cec96495a04ca7feff88a9492efeab8053fb284d24bdd73344c9245a48")
def test_reconciliation_refuses_unknown_state_without_snapshot_stage_proof():
result = McpHubDeploymentReconciliation(FakePortainer(exit_code=55)).run()
assert result == {
"status": "validation_failed",
"reason": "unknown MCP Hub live status was not produced by a proven recoverable snapshot-stage failure",
@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
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,926 @@
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()