Publish curated Unraid-AppOps-Gateway source from 2e53f628d284213b90ca2f9c5e121671f5933574
This commit is contained in:
1 parent
4f9c5a1106
commit
2facb8de34
13 files changed
+3868
-1749
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user