Publish curated Unraid-AppOps-Gateway source from 6d175c3333218e2b580a310496fed6c407f28114

This commit is contained in:
Public Source Publisher committed 2026-10-09 12:15:31 +00:00
1 parent 58f5e3c8c2
commit 41fd48f0bc
7 files changed
+339 -16

No files matched your search

+1 -1
View File
@@ -1,3 +1,3 @@
# Public source export # Public source export
This source snapshot was generated from the private canonical repository at revision `402a0ef2e7d14e98325504f79ab7d7651ffc88b1`. It contains no private Git history or operational evidence. Changes are published from the canonical repository. This source snapshot was generated from the private canonical repository at revision `6d175c3333218e2b580a310496fed6c407f28114`. It contains no private Git history or operational evidence. Changes are published from the canonical repository.
+11 -11
View File
@@ -1,6 +1,6 @@
{ {
"schemaVersion": 1, "schemaVersion": 1,
"sourceRevision": "402a0ef2e7d14e98325504f79ab7d7651ffc88b1", "sourceRevision": "6d175c3333218e2b580a310496fed6c407f28114",
"files": [ "files": [
{ {
"path": ".dockerignore", "path": ".dockerignore",
@@ -404,8 +404,8 @@
}, },
{ {
"path": "backend/app/mcp_server.py", "path": "backend/app/mcp_server.py",
"sha256": "feac22ee7a1c755f0fafd6890500b83442b5f13b51e6a6cde3b0227c5b5ab25b", "sha256": "598e7f7a5020d92becfb143d31dbd096f16bf79ae73b26e6ecae710661f73ca1",
"bytes": 49536 "bytes": 50519
}, },
{ {
"path": "backend/app/runner_statistics_mcp.py", "path": "backend/app/runner_statistics_mcp.py",
@@ -899,8 +899,8 @@
}, },
{ {
"path": "backend/tests/test_appops_recovery_deploy_binding.py", "path": "backend/tests/test_appops_recovery_deploy_binding.py",
"sha256": "4902f2e16ed35b8bd52d8610ab61b9752aa10a4147e5aafc27404bbbfef61e0a", "sha256": "274e401e81eda762ff4cae0b64ecdefd322972c72a004fdd84f02b93a2e8b44d",
"bytes": 50460 "bytes": 52001
}, },
{ {
"path": "backend/tests/test_appops_self_upgrade_controller.py", "path": "backend/tests/test_appops_self_upgrade_controller.py",
@@ -909,8 +909,8 @@
}, },
{ {
"path": "backend/tests/test_appops_self_upgrade_recovery_boundaries.py", "path": "backend/tests/test_appops_self_upgrade_recovery_boundaries.py",
"sha256": "07c3a38c360f5b70b6f3efbc8ba62356d4937170667d07d31d82a6810ed58a0e", "sha256": "676a465700296b081cc07eaeab3d2669f3047001b28b614e8a8a82088010f3f0",
"bytes": 21578 "bytes": 29130
}, },
{ {
"path": "backend/tests/test_appops_self_upgrade_runtime_model.py", "path": "backend/tests/test_appops_self_upgrade_runtime_model.py",
@@ -1004,8 +1004,8 @@
}, },
{ {
"path": "backend/tests/test_deploy_broker.py", "path": "backend/tests/test_deploy_broker.py",
"sha256": "b0e96b1cd2954dc2a6452f5f7c50c5fbae541dc0b4de1e60af11349654594902", "sha256": "40c290925c0569192973d858e5df36b91e3c5dd38ce638b9f19b0ca60e61903a",
"bytes": 438851 "bytes": 442136
}, },
{ {
"path": "backend/tests/test_deployment_auto_rollback.py", "path": "backend/tests/test_deployment_auto_rollback.py",
@@ -1284,8 +1284,8 @@
}, },
{ {
"path": "backend/tests/test_mcp_rag_n8n_integrations.py", "path": "backend/tests/test_mcp_rag_n8n_integrations.py",
"sha256": "0c90431bb77eab27f0da21afd02dd5fb232141483062f7a19c3340d1d91f6c9d", "sha256": "1d72e95529863d3263a178d71d4c7729c8b624b13e734460fb9303a3f6f7416d",
"bytes": 52814 "bytes": 54507
}, },
{ {
"path": "backend/tests/test_media_codec_dashboard_deployment_reconciliation.py", "path": "backend/tests/test_media_codec_dashboard_deployment_reconciliation.py",
+16 -1
View File
@@ -15,7 +15,7 @@ from typing import Any
from mcp.server import MCPServer from mcp.server import MCPServer
from mcp.server.transport_security import TransportSecuritySettings from mcp.server.transport_security import TransportSecuritySettings
from mcp.types import ToolAnnotations from mcp.types import ToolAnnotations
from pydantic import StrictBool from pydantic import StrictBool, ValidationError
from sqlmodel import Session, select from sqlmodel import Session, select
from .api.routes_actions import action_registry as build_action_registry from .api.routes_actions import action_registry as build_action_registry
@@ -894,6 +894,9 @@ def deployment_preflight(plan: dict[str, Any], idempotencyKey: str) -> dict[str,
operation="preflight", operation="preflight",
) )
) )
except ValidationError:
# Pydantic errors may contain arbitrary keys, input values and validator context.
return {"status": "invalid", "executed": False, "reason": "invalid_typed_plan"}
except (ValueError, DeploymentControlError) as exc: except (ValueError, DeploymentControlError) as exc:
return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))} return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))}
@@ -918,6 +921,8 @@ def forge_bootstrap_apply(profile: dict[str, Any], idempotencyKey: str) -> dict[
idempotency_key=idempotencyKey, idempotency_key=idempotencyKey,
) )
) )
except ValidationError:
return {"status": "invalid", "executed": False, "reason": "invalid_typed_plan"}
except ValueError as exc: except ValueError as exc:
return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))} return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))}
finally: finally:
@@ -967,6 +972,8 @@ def enrollment_preflight(plan: dict[str, Any]) -> dict[str, Any]:
typed_plan = EnrollmentPlanV1.model_validate(plan) typed_plan = EnrollmentPlanV1.model_validate(plan)
with Session(engine) as session: with Session(engine) as session:
return _json(EnrollmentPlane().preflight(session, typed_plan)) return _json(EnrollmentPlane().preflight(session, typed_plan))
except ValidationError:
return {"status": "invalid", "executed": False, "reason": "invalid_typed_plan"}
except (ValueError, EnrollmentError) as exc: except (ValueError, EnrollmentError) as exc:
return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))} return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))}
@@ -981,6 +988,8 @@ def target_enroll(plan: dict[str, Any], idempotencyKey: str) -> dict[str, Any]:
typed_plan = EnrollmentPlanV1.model_validate(plan) typed_plan = EnrollmentPlanV1.model_validate(plan)
with Session(engine) as session: with Session(engine) as session:
return _json(EnrollmentPlane().enroll(session, typed_plan, idempotency_key=idempotencyKey)) return _json(EnrollmentPlane().enroll(session, typed_plan, idempotency_key=idempotencyKey))
except ValidationError:
return {"status": "invalid", "executed": False, "reason": "invalid_typed_plan"}
except (ValueError, EnrollmentError) as exc: except (ValueError, EnrollmentError) as exc:
return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))} return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))}
finally: finally:
@@ -1015,6 +1024,9 @@ def deployment_apply(plan: dict[str, Any], idempotencyKey: str) -> dict[str, Any
operation="apply", operation="apply",
) )
) )
except ValidationError:
# Pydantic errors may contain arbitrary keys, input values and validator context.
return {"status": "invalid", "executed": False, "reason": "invalid_typed_plan"}
except (ValueError, DeploymentControlError) as exc: except (ValueError, DeploymentControlError) as exc:
return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))} return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))}
finally: finally:
@@ -1063,6 +1075,9 @@ def deployment_rollback(plan: dict[str, Any], idempotencyKey: str) -> dict[str,
operation="rollback", operation="rollback",
) )
) )
except ValidationError:
# Pydantic errors may contain arbitrary keys, input values and validator context.
return {"status": "invalid", "executed": False, "reason": "invalid_typed_plan"}
except (ValueError, DeploymentControlError) as exc: except (ValueError, DeploymentControlError) as exc:
return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))} return {"status": "invalid", "executed": False, "reason": redact_message(str(exc))}
finally: finally:
@@ -1135,3 +1135,36 @@ def test_followup_transaction_journal_failure_precedes_all_source_runtime_and_st
assert [event[0] for event in events] == ["green", "snapshot", "backup", "journal"] assert [event[0] for event in events] == ["green", "snapshot", "backup", "journal"]
assert marker.read_text() == "prior source remains\n" assert marker.read_text() == "prior source remains\n"
assert state["live_revision"] == prior_revision assert state["live_revision"] == prior_revision
def test_prepare_restored_recovery_rechecks_signed_live_state_and_recaptures_under_new_id(
deploy, monkeypatch, tmp_path,
):
case = _appops_prepare_recovery_case(deploy, monkeypatch, tmp_path)
calls = []
def verify(_project, container, image, **kwargs):
calls.append(kwargs)
assert container["Id"] != deploy.APPOPS_LEGACY_OLD_CONTAINER_ID
monkeypatch.setattr(deploy, "verify_appops_legacy_runtime", verify)
deploy._appops_prepare_recovery(
case.project, APP_OPS_REPOSITORY, case.repo, case.revision, case.metadata, case.state,
)
assert calls == [{"require_original_container": False, "recovery_state": case.state}]
assert case.calls["models"][0][0] == RECOVERY_ID
assert case.calls["models"][0][2]["services"]["appops-gateway"]["environment"] == case.values
assert case.project["_compose_rollback"]["containers"][0]["container_id"] == case.container["Id"]
def test_prepare_recovery_rejects_environment_drift_before_model_backup_or_transaction(
deploy, monkeypatch, tmp_path,
):
case = _appops_prepare_recovery_case(deploy, monkeypatch, tmp_path)
case.container["Config"]["Env"].append("UNEXPECTED_SETTING=private-sentinel")
with pytest.raises(deploy.DeployError, match="complete runtime environment changed"):
deploy._appops_prepare_recovery(
case.project, APP_OPS_REPOSITORY, case.repo, case.revision, case.metadata, case.state,
)
assert not any(case.calls[name] for name in ("models", "capture", "backup", "begin"))
@@ -556,3 +556,166 @@ def test_original_failed_delivery_rejects_modified_legacy_record(controller, mon
assert controller._self_upgrade_original_failed_delivery( assert controller._self_upgrade_original_failed_delivery(
semantic, revision, policy, semantic, revision, policy,
) is None ) is None
@pytest.mark.parametrize(("status", "extra", "valid"), [
("deploying", {}, True),
("failed", {"reason": "deployment_failed"}, True),
("deployed", {"receipt_sha256": "f" * 64}, True),
("deployed", {"receipt_sha256": "invalid"}, False),
("deploying", {"reason": "deployment_failed"}, False),
("failed", {"reason": "private-secret-value"}, False),
("failed", {"receipt_sha256": "f" * 64}, False),
("failed", {"unknown": "value"}, False),
("failed", {"reason": ["deployment_failed"]}, False),
("awaiting_validation", {"reason": "gitea_validation_unavailable"}, True),
("failed", {"reason": "recovery_preimage_changed"}, True),
])
def test_signed_recovery_reader_accepts_only_typed_worker_outcome_fields(
controller, monkeypatch, tmp_path, status, extra, valid,
):
record = _recovery_delivery(controller, secret="sentinel")
record.pop("runtimeEnvironment")
record.pop("privateCredential")
record.update(status=status, **extra)
secret = b"fixture-recovery-signing-key"
journal = {
"recoveryId": record["recovery_id"], "revision": record["revision"],
"idempotencyKeySha256": record["idempotency_key_sha256"],
"preflightSha256": record["preflight_sha256"],
"semanticSha256": record["semantic_sha256"],
"originalRawSha256": record["recovery_of_sha256"],
}
payload = {
"operationId": record["semantic_sha256"], "revision": record["revision"],
"preflightSha256": record["preflight_sha256"],
"idempotencyKeySha256": record["idempotency_key_sha256"],
"recoveryId": record["recovery_id"],
"recoveryOfSha256": record["recovery_of_sha256"],
}
record["body_sha256"] = controller.hashlib.sha256(controller._self_upgrade_canonical_json({
"semanticSha256": record["semantic_sha256"], "recoveryId": record["recovery_id"],
"idempotencyKeySha256": record["idempotency_key_sha256"],
})).hexdigest()
record["recovery_hmac"] = controller._self_upgrade_public_recovery_signature(record, secret)
monkeypatch.setattr(controller, "_self_upgrade_secret", lambda: secret)
monkeypatch.setattr(controller, "_self_upgrade_recovery_journal", lambda _id: journal)
monkeypatch.setattr(controller, "_load_projects", lambda: {controller.SELF_UPGRADE_REPOSITORY: {}})
monkeypatch.setattr(controller, "_validation_policy", lambda *_a: {})
monkeypatch.setattr(controller, "_validation_snapshot_matches", lambda *_a: True)
monkeypatch.setattr(controller, "_self_upgrade_request_root", lambda **_k: tmp_path)
monkeypatch.setattr(controller, "_self_upgrade_read_private_json", lambda _path: (b"", {}))
monkeypatch.setattr(controller, "_self_upgrade_recovery_request_payload", lambda _record: payload)
def verify():
return controller._self_upgrade_recovery_record_valid(
record, semantic_digest=record["semantic_sha256"],
original_raw_sha256=record["recovery_of_sha256"],
)
assert verify() is valid
record["updated_at"] += 1
assert verify() is False # Even permitted outcome fields require the original complete HMAC.
@pytest.mark.parametrize(("uid", "gid", "mode"), [(99, 100, 0o777), (0, 0, 0o755)])
@pytest.mark.parametrize("drift", ["none", "owner", "mode", "symlink", "foreign_cache", "inode"])
def test_legacy_cache_reader_and_state_cas_bind_both_exact_appdata_profiles(
controller, monkeypatch, tmp_path, uid, gid, mode, drift,
):
if os.name == "nt" or os.geteuid() != 0:
pytest.skip("Pinned dirfd ownership contract requires root POSIX")
root = tmp_path / "virtual-host"
root.mkdir(mode=0o755)
dirs = [
("mnt", 0, 0, 0o755), ("mnt/cache", 99, 100, 0o777),
("mnt/cache/appdata", uid, gid, mode),
("mnt/cache/appdata/gitea-deploy-runners", 0, 0, 0o755),
("mnt/cache/appdata/gitea-deploy-runners/state", 0, 0, 0o755),
]
for name, owner, group, permissions in dirs:
path = root / name
path.mkdir()
os.chown(path, owner, group)
path.chmod(permissions)
appdata = root / "mnt/cache/appdata"
state_root = appdata / "gitea-deploy-runners/state"
state_path = state_root / "unraid-appops-gateway.json"
before = controller._self_upgrade_canonical_json({
"schema_version": 3, "repository": controller.SELF_UPGRADE_REPOSITORY, "marker": "old",
}) + b"\n"
state_path.write_bytes(before)
state_path.chmod(0o600)
after = before.replace(b'"old"', b'"new"')
if drift == "owner":
os.chown(appdata, 1000, 1000)
elif drift == "mode":
appdata.chmod(0o775)
elif drift == "foreign_cache":
os.chown(root / "mnt/cache", 0, 0)
(root / "mnt/cache").chmod(0o755)
elif drift == "inode":
replacement = appdata.with_name("different-inode")
replacement.mkdir()
os.chown(replacement, uid, gid)
replacement.chmod(mode)
elif drift == "symlink":
renamed = appdata.with_name("appdata-elsewhere")
appdata.rename(renamed)
appdata.symlink_to(renamed, target_is_directory=True)
monkeypatch.setattr(controller, "STATE_ROOT", Path("/mnt/cache/appdata/gitea-deploy-runners/state"))
real_open = os.open
def virtual_root_open(path, flags, *args, **kwargs):
if drift == "inode" and str(path) == "appdata":
return real_open(replacement, flags)
return real_open(root if str(path) == "/" else path, flags, *args, **kwargs)
monkeypatch.setattr(controller.os, "open", virtual_root_open)
requested = controller.STATE_ROOT / state_path.name
if drift == "none":
assert controller._self_upgrade_read_legacy_cache_file(
requested, maximum_bytes=65536, allowed_modes=frozenset({0o600}),
) == before
assert not controller._self_upgrade_write_legacy_state_cas("0" * 64, after)
assert state_path.read_bytes() == before
assert controller._self_upgrade_write_legacy_state_cas(
controller.hashlib.sha256(before).hexdigest(), after,
)
assert state_path.read_bytes() == after
else:
with pytest.raises(OSError):
controller._self_upgrade_read_legacy_cache_file(
requested, maximum_bytes=65536, allowed_modes=frozenset({0o600}),
)
assert not controller._self_upgrade_write_legacy_state_cas(
controller.hashlib.sha256(before).hexdigest(), after,
)
@pytest.mark.parametrize(("profile", "valid"), [
((99, 100, 0o777), True), ((0, 0, 0o755), True),
((0, 0, 0o775), False), ((99, 100, 0o755), False),
])
def test_source_root_allows_only_legacy_or_root_owned_appdata_anchor(
controller, monkeypatch, profile, valid,
):
from types import SimpleNamespace
root = Path("/srv/example/appdata/unraid-appops-gateway/source")
profiles = {
Path("/mnt"): (0, 0, 0o755), Path("/mnt/user"): (99, 100, 0o777),
Path("/srv/example/appdata"): profile,
root.parent: (0, 0, 0o755), root: (100, 101, 0o755),
}
def lstat(path):
uid, gid, mode = profiles[path]
return SimpleNamespace(st_uid=uid, st_gid=gid, st_mode=stat.S_IFDIR | mode)
monkeypatch.setattr(Path, "lstat", lstat)
monkeypatch.setattr(Path, "is_symlink", lambda _path: False)
assert controller._self_upgrade_source_root_safe(root) is valid
monkeypatch.setattr(Path, "is_symlink", lambda path: path == root.parent)
assert controller._self_upgrade_source_root_safe(root) is False
+74 -3
View File
@@ -379,9 +379,7 @@ def test_appops_legacy_state_anchor_accepts_only_known_preimage_or_current_green
) )
def test_appops_legacy_runtime_requires_pinned_source_image_and_resolved_model( def _fixed_appops_legacy_runtime_case(deploy_module, tmp_path):
deploy_module, tmp_path
):
project = _project(tmp_path) project = _project(tmp_path)
project.update({ project.update({
"branch": "main", "gitea_repository_id": 9, "branch": "main", "gitea_repository_id": 9,
@@ -435,6 +433,15 @@ def test_appops_legacy_runtime_requires_pinned_source_image_and_resolved_model(
"Config": {"Labels": container["Config"]["Labels"]}, "Config": {"Labels": container["Config"]["Labels"]},
} }
return project, container, image
def test_appops_legacy_runtime_requires_pinned_source_image_and_resolved_model(
deploy_module, tmp_path
):
project, container, image = _fixed_appops_legacy_runtime_case(deploy_module, tmp_path)
image_id = deploy_module.APPOPS_LEGACY_OLD_IMAGE_ID
source = "https://gitea.example.com/Jens/Unraid-AppOps-Gateway"
observed = deploy_module.verify_appops_legacy_runtime(project, container, image) observed = deploy_module.verify_appops_legacy_runtime(project, container, image)
assert observed["imageId"] == image_id assert observed["imageId"] == image_id
assert observed["imageRef"] == "unraid-appops-gateway:platform-5553c04ce497" assert observed["imageRef"] == "unraid-appops-gateway:platform-5553c04ce497"
@@ -10604,3 +10611,67 @@ def test_managed_appdata_initializer_http_prerequisite_and_refusal_are_sanitized
server.shutdown() server.shutdown()
server.server_close() server.server_close()
serving.join(timeout=2) serving.join(timeout=2)
@pytest.mark.parametrize("proof", ["signed", "missing", "foreign", "candidate"])
def test_recovery_verifies_restored_container_only_with_signed_live_555_model(
deploy_module, monkeypatch, tmp_path, proof,
):
project, container, image = _fixed_appops_legacy_runtime_case(deploy_module, tmp_path)
container["Id"] = "a" * 64
model_path = "/private/models/signed-555.compose.json"
container["Config"]["Labels"]["com.docker.compose.project.config_files"] = model_path
state = {"live_revision": deploy_module.APPOPS_LEGACY_OLD_REVISION}
active = {"legacy": True, "path": model_path}
if proof == "missing":
active = None
elif proof == "foreign":
active["path"] = "/private/models/foreign.compose.json"
elif proof == "candidate":
active["legacy"] = False
calls = []
def verify_active(actual_state, actual_container, *, project):
calls.append((actual_state, actual_container, project))
return active
reader = types.SimpleNamespace(_self_upgrade_active_compose_model=verify_active)
monkeypatch.setattr(deploy_module, "_appops_status_reader", lambda: reader)
with pytest.raises(deploy_module.DeployError, match="fixed AppOps legacy runtime"):
deploy_module.verify_appops_legacy_runtime(project, container, image)
if proof == "signed":
observed = deploy_module.verify_appops_legacy_runtime(
project, container, image, require_original_container=False, recovery_state=state,
)
assert observed["containerId"] == container["Id"]
else:
with pytest.raises(deploy_module.DeployError):
deploy_module.verify_appops_legacy_runtime(
project, container, image, require_original_container=False, recovery_state=state,
)
assert calls == [(state, container, project)]
@pytest.mark.parametrize("drift", ["image", "ports", "mounts", "health"])
def test_signed_restored_555_proof_preserves_fixed_runtime_checks(
deploy_module, monkeypatch, tmp_path, drift,
):
project, container, image = _fixed_appops_legacy_runtime_case(deploy_module, tmp_path)
container["Id"] = "a" * 64
active = {"legacy": True, "path": "/private/models/signed-555.compose.json"}
monkeypatch.setattr(deploy_module, "_appops_status_reader", lambda: types.SimpleNamespace(
_self_upgrade_active_compose_model=lambda *_a, **_k: active,
))
if drift == "image":
image["Id"] = "sha256:" + "0" * 64
elif drift == "ports":
container["NetworkSettings"]["Ports"]["1216/tcp"][0]["HostIp"] = "127.0.0.1"
elif drift == "mounts":
container["Mounts"][0]["RW"] = False
elif drift == "health":
container["State"]["Health"]["Status"] = "unhealthy"
with pytest.raises(deploy_module.DeployError):
deploy_module.verify_appops_legacy_runtime(
project, container, image, require_original_container=False, recovery_state={},
)
@@ -1369,3 +1369,44 @@ def test_controller_preflight_changed_identity_expires_approval_without_unknown_
promotion = session.get(WorkMeshPromotion, promotion_id) promotion = session.get(WorkMeshPromotion, promotion_id)
assert approval is not None and approval.status == "expired" assert approval is not None and approval.status == "expired"
assert promotion is not None and promotion.status == "preflight_changed" assert promotion is not None and promotion.status == "preflight_changed"
@pytest.mark.parametrize(("name", "argument", "discriminator"), [
("enrollment.preflight", "plan", None),
("target.enroll", "plan", None),
("deployment.preflight", "plan", None),
("deployment.apply", "plan", None),
("deployment.rollback", "plan", None),
("deployment.preflight", "plan", "itworx.forge-bootstrap-preflight/v1"),
("forge.bootstrap.apply", "profile", None),
])
def test_mcp_typed_schema_rejection_never_echoes_opaque_input(
integration_client, monkeypatch, name, argument, discriminator,
):
from backend.app import mcp_server
canary = "syntheticOpaqueInput_9Z7XQ"
payload = {"api_key": canary, canary: {"nested": canary}, "application_id": canary}
if discriminator:
payload["api_version"] = discriminator
arguments = {argument: payload}
if name != "enrollment.preflight":
arguments["idempotencyKey"] = "schema-redaction-fixture"
def forbidden_service(*_args, **_kwargs):
pytest.fail("invalid schema must not reach deployment/enrollment services")
monkeypatch.setattr(mcp_server, "EnrollmentPlane", forbidden_service)
monkeypatch.setattr(mcp_server, "DeploymentControlPlane", forbidden_service)
async def exercise():
async with Client(mcp_server.appops_mcp) as client:
result = await client.call_tool(name, arguments)
assert result.is_error is False
assert result.structured_content == {
"status": "invalid", "executed": False, "reason": "invalid_typed_plan",
}
assert canary not in json.dumps(result.model_dump(mode="json"))
asyncio.run(exercise())
assert mcp_server._MCP_EXECUTION_LOCK.locked() is False