meshcode 2.10.56__tar.gz → 2.10.57__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {meshcode-2.10.56 → meshcode-2.10.57}/PKG-INFO +1 -1
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/__init__.py +16 -1
- meshcode-2.10.57/meshcode/exceptions.py +52 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/meshcode_mcp/server.py +16 -1
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/setup_clients.py +52 -23
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode.egg-info/SOURCES.txt +2 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/pyproject.toml +1 -1
- meshcode-2.10.57/tests/test_exceptions.py +107 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/tests/test_rpc_migrations.py +22 -14
- {meshcode-2.10.56 → meshcode-2.10.57}/README.md +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/ascii_art.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/cli.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/comms_v4.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/compat.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/invites.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/launcher.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/launcher_install.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/preferences.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/run_agent.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/secrets.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/self_update.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/setup.cfg +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/tests/test_core.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.10.56 → meshcode-2.10.57}/tests/test_status_enum_coverage.py +0 -0
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.10.
|
|
2
|
+
__version__ = "2.10.57"
|
|
3
|
+
|
|
4
|
+
# Exception hierarchy — eagerly imported (lightweight, no deps)
|
|
5
|
+
from meshcode.exceptions import ( # noqa: F401
|
|
6
|
+
MeshCodeError,
|
|
7
|
+
AuthError,
|
|
8
|
+
RPCError,
|
|
9
|
+
MeshCodeTimeoutError,
|
|
10
|
+
MeshCodeConnectionError,
|
|
11
|
+
)
|
|
3
12
|
|
|
4
13
|
# Public API — lazy imports to avoid heavy deps at import time
|
|
5
14
|
def __getattr__(name):
|
|
@@ -42,6 +51,12 @@ _SECRETS_EXPORTS = {
|
|
|
42
51
|
__all__ = [
|
|
43
52
|
"__version__",
|
|
44
53
|
"backend",
|
|
54
|
+
# Exceptions
|
|
55
|
+
"MeshCodeError",
|
|
56
|
+
"AuthError",
|
|
57
|
+
"RPCError",
|
|
58
|
+
"MeshCodeTimeoutError",
|
|
59
|
+
"MeshCodeConnectionError",
|
|
45
60
|
# Messaging
|
|
46
61
|
"send_message",
|
|
47
62
|
"read_inbox",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""MeshCode exception hierarchy.
|
|
2
|
+
|
|
3
|
+
All SDK-specific errors inherit from :class:`MeshCodeError` so callers can
|
|
4
|
+
catch a single base class, or narrow down to the specific failure mode.
|
|
5
|
+
|
|
6
|
+
CLI entry points (cli.py, launcher.py, server.py boot) catch these and
|
|
7
|
+
convert to ``sys.exit(2)`` so end-user behaviour is unchanged.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MeshCodeError(Exception):
|
|
12
|
+
"""Base exception for all MeshCode SDK errors."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AuthError(MeshCodeError):
|
|
16
|
+
"""Authentication or API-key related failure.
|
|
17
|
+
|
|
18
|
+
Raised when:
|
|
19
|
+
- No API key is found in the keychain or environment
|
|
20
|
+
- The secrets module cannot be loaded
|
|
21
|
+
- An API key is rejected by the server
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RPCError(MeshCodeError):
|
|
26
|
+
"""A Supabase RPC call returned an error payload.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
rpc_name: Name of the RPC function that failed (if known).
|
|
30
|
+
detail: Server-provided error message.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, message: str, *, rpc_name: str = "", detail: str = ""):
|
|
34
|
+
super().__init__(message)
|
|
35
|
+
self.rpc_name = rpc_name
|
|
36
|
+
self.detail = detail
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class MeshCodeTimeoutError(MeshCodeError):
|
|
40
|
+
"""A network or RPC call timed out.
|
|
41
|
+
|
|
42
|
+
Named ``MeshCodeTimeoutError`` to avoid shadowing the builtin
|
|
43
|
+
:class:`TimeoutError`.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class MeshCodeConnectionError(MeshCodeError):
|
|
48
|
+
"""Could not reach the MeshCode / Supabase backend.
|
|
49
|
+
|
|
50
|
+
Named ``MeshCodeConnectionError`` to avoid shadowing the builtin
|
|
51
|
+
:class:`ConnectionError`.
|
|
52
|
+
"""
|
|
@@ -922,7 +922,7 @@ def _acquire_lease() -> bool:
|
|
|
922
922
|
"p_api_key": api_key,
|
|
923
923
|
"p_project_id": _PROJECT_ID,
|
|
924
924
|
"p_agent_name": AGENT_NAME,
|
|
925
|
-
"p_instance_id": r.get("held_by", "unknown"),
|
|
925
|
+
"p_instance_id": r.get("current_instance", r.get("held_by", "unknown")),
|
|
926
926
|
})
|
|
927
927
|
except Exception:
|
|
928
928
|
# Force clear via direct update
|
|
@@ -3900,6 +3900,7 @@ def meshcode_health() -> Dict[str, Any]:
|
|
|
3900
3900
|
|
|
3901
3901
|
# Realtime status
|
|
3902
3902
|
health["realtime_connected"] = _REALTIME.is_connected if _REALTIME else False
|
|
3903
|
+
health["realtime_subscribed"] = _REALTIME.is_subscribed if _REALTIME else False
|
|
3903
3904
|
|
|
3904
3905
|
# Process uptime
|
|
3905
3906
|
try:
|
|
@@ -3909,6 +3910,20 @@ def meshcode_health() -> Dict[str, Any]:
|
|
|
3909
3910
|
except Exception:
|
|
3910
3911
|
health["uptime_seconds"] = "unknown (psutil not available)"
|
|
3911
3912
|
|
|
3913
|
+
# Server-side system health (aggregate metrics from DB)
|
|
3914
|
+
try:
|
|
3915
|
+
sys_health = be.sb_rpc("mc_system_health", {})
|
|
3916
|
+
if isinstance(sys_health, dict) and sys_health.get("ok"):
|
|
3917
|
+
health["system"] = {
|
|
3918
|
+
"active_agents": sys_health.get("active_agent_count"),
|
|
3919
|
+
"stale_agents": sys_health.get("stale_agent_count"),
|
|
3920
|
+
"message_delivery_rate": sys_health.get("message_delivery_rate"),
|
|
3921
|
+
"messages_1h": sys_health.get("total_messages_1h"),
|
|
3922
|
+
"failed_rpcs_1h": sys_health.get("failed_rpc_count_1h"),
|
|
3923
|
+
}
|
|
3924
|
+
except Exception:
|
|
3925
|
+
pass # mc_system_health may not be deployed yet
|
|
3926
|
+
|
|
3912
3927
|
return health
|
|
3913
3928
|
|
|
3914
3929
|
|
|
@@ -22,6 +22,8 @@ import sys
|
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
from typing import Dict, Any, Optional
|
|
24
24
|
|
|
25
|
+
from meshcode.exceptions import AuthError, RPCError, MeshCodeConnectionError
|
|
26
|
+
|
|
25
27
|
|
|
26
28
|
def _load_credentials(profile: str = "default") -> Dict[str, str]:
|
|
27
29
|
"""Load the api key + non-secret metadata for the given keychain profile.
|
|
@@ -36,18 +38,18 @@ def _load_credentials(profile: str = "default") -> Dict[str, str]:
|
|
|
36
38
|
import importlib
|
|
37
39
|
secrets_mod = importlib.import_module("meshcode.secrets")
|
|
38
40
|
except Exception as e:
|
|
39
|
-
|
|
40
|
-
sys.exit(2)
|
|
41
|
+
raise AuthError(f"cannot load secrets module: {e}") from e
|
|
41
42
|
|
|
42
43
|
api_key = secrets_mod.get_api_key(profile=profile)
|
|
43
44
|
if not api_key:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
raise AuthError(
|
|
46
|
+
"No credentials found. You need to log in first.\n"
|
|
47
|
+
"\n"
|
|
48
|
+
" 1. Get your API key at: https://meshcode.io/onboarding\n"
|
|
49
|
+
" (or https://meshcode.io/settings if you already have an account)\n"
|
|
50
|
+
" 2. Run: meshcode login mc_<your-api-key>\n"
|
|
51
|
+
" 3. Then re-run this command."
|
|
52
|
+
)
|
|
51
53
|
|
|
52
54
|
meta_path = Path.home() / ".meshcode" / "profile_meta.json"
|
|
53
55
|
meta: Dict[str, Any] = {"api_key": api_key}
|
|
@@ -119,19 +121,24 @@ def _resolve_project_id(api_key: str, project: str, sb: Dict[str, str]) -> str:
|
|
|
119
121
|
if isinstance(data, dict) and data.get("project_id"):
|
|
120
122
|
return data["project_id"]
|
|
121
123
|
if isinstance(data, dict) and data.get("error"):
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
# Build a helpful message including the user's actual projects
|
|
125
|
+
hint = _suggest_projects_str(api_key, sb)
|
|
126
|
+
msg = f"could not resolve project '{project}': {data['error']}"
|
|
127
|
+
if hint:
|
|
128
|
+
msg += f"\n{hint}"
|
|
129
|
+
raise RPCError(msg, rpc_name="mc_resolve_project", detail=data["error"])
|
|
130
|
+
except (RPCError, AuthError):
|
|
131
|
+
raise
|
|
126
132
|
except Exception as e:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
133
|
+
raise MeshCodeConnectionError(
|
|
134
|
+
f"could not resolve project '{project}': {e}\n"
|
|
135
|
+
f"Check your API key and project name. Get help at https://meshcode.io/docs"
|
|
136
|
+
) from e
|
|
130
137
|
return ""
|
|
131
138
|
|
|
132
139
|
|
|
133
|
-
def
|
|
134
|
-
"""Try to list the user's projects
|
|
140
|
+
def _suggest_projects_str(api_key: str, sb: dict) -> str:
|
|
141
|
+
"""Try to list the user's projects; return a hint string (or empty)."""
|
|
135
142
|
try:
|
|
136
143
|
from urllib.request import Request as _Req, urlopen as _urlopen
|
|
137
144
|
body = json.dumps({"p_api_key": api_key}).encode()
|
|
@@ -149,11 +156,21 @@ def _suggest_projects(api_key: str, sb: dict):
|
|
|
149
156
|
data = json.loads(resp.read().decode())
|
|
150
157
|
projects = data.get("projects", []) if isinstance(data, dict) else []
|
|
151
158
|
if projects:
|
|
152
|
-
|
|
159
|
+
lines = ["Your projects:"]
|
|
153
160
|
for p in projects:
|
|
154
|
-
|
|
161
|
+
lines.append(f" - {p['name']}")
|
|
162
|
+
return "\n".join(lines)
|
|
155
163
|
except Exception:
|
|
156
164
|
pass
|
|
165
|
+
return ""
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _suggest_projects(api_key: str, sb: dict):
|
|
169
|
+
"""Print the user's projects to stderr (CLI convenience wrapper)."""
|
|
170
|
+
hint = _suggest_projects_str(api_key, sb)
|
|
171
|
+
if hint:
|
|
172
|
+
for line in hint.splitlines():
|
|
173
|
+
print(f"[meshcode] {line}", file=sys.stderr)
|
|
157
174
|
|
|
158
175
|
|
|
159
176
|
def _build_server_block(project: str, project_id: str, agent: str, role: str,
|
|
@@ -414,7 +431,11 @@ def setup_workspace(project: str, agent: str, role: str = "",
|
|
|
414
431
|
print(f"[meshcode] This profile is created by `meshcode join <token>`.", file=sys.stderr)
|
|
415
432
|
return 2
|
|
416
433
|
|
|
417
|
-
|
|
434
|
+
try:
|
|
435
|
+
project_id = _resolve_project_id(api_key, project, sb)
|
|
436
|
+
except (RPCError, MeshCodeConnectionError, AuthError) as exc:
|
|
437
|
+
print(f"[meshcode] ERROR: {exc}", file=sys.stderr)
|
|
438
|
+
return 2
|
|
418
439
|
|
|
419
440
|
server_id = f"meshcode-{project}-{agent}"
|
|
420
441
|
server_block = _build_server_block(project, project_id, agent, role, api_key, sb,
|
|
@@ -565,10 +586,18 @@ def setup_global(client: str, project: str, agent: str, role: str = "") -> int:
|
|
|
565
586
|
print(f"[meshcode] ERROR: Unknown client '{client}'. Supported: {', '.join(CLIENT_CONFIG_PATHS)}", file=sys.stderr)
|
|
566
587
|
return 2
|
|
567
588
|
|
|
568
|
-
|
|
589
|
+
try:
|
|
590
|
+
creds = _load_credentials()
|
|
591
|
+
except AuthError as exc:
|
|
592
|
+
print(f"[meshcode] ERROR: {exc}", file=sys.stderr)
|
|
593
|
+
return 2
|
|
569
594
|
sb = _load_supabase_env()
|
|
570
595
|
api_key = creds.get("api_key", "")
|
|
571
|
-
|
|
596
|
+
try:
|
|
597
|
+
project_id = _resolve_project_id(api_key, project, sb)
|
|
598
|
+
except (RPCError, MeshCodeConnectionError) as exc:
|
|
599
|
+
print(f"[meshcode] ERROR: {exc}", file=sys.stderr)
|
|
600
|
+
return 2
|
|
572
601
|
|
|
573
602
|
os_name = platform.system()
|
|
574
603
|
config_path = CLIENT_CONFIG_PATHS[client].get(os_name)
|
|
@@ -5,6 +5,7 @@ meshcode/ascii_art.py
|
|
|
5
5
|
meshcode/cli.py
|
|
6
6
|
meshcode/comms_v4.py
|
|
7
7
|
meshcode/compat.py
|
|
8
|
+
meshcode/exceptions.py
|
|
8
9
|
meshcode/invites.py
|
|
9
10
|
meshcode/launcher.py
|
|
10
11
|
meshcode/launcher_install.py
|
|
@@ -31,6 +32,7 @@ meshcode/meshcode_mcp/test_server_wrapper.py
|
|
|
31
32
|
tests/test_core.py
|
|
32
33
|
tests/test_cross_agent_messaging.py
|
|
33
34
|
tests/test_esc_deaf_state.py
|
|
35
|
+
tests/test_exceptions.py
|
|
34
36
|
tests/test_realtime_event_freshness.py
|
|
35
37
|
tests/test_rpc_migrations.py
|
|
36
38
|
tests/test_status_enum_coverage.py
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Exception Hierarchy Tests
|
|
3
|
+
=========================
|
|
4
|
+
Validates the custom exception classes exported by meshcode.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
pytest tests/test_exceptions.py -v
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestExceptionHierarchy:
|
|
17
|
+
"""All custom exceptions inherit from MeshCodeError."""
|
|
18
|
+
|
|
19
|
+
def test_base_is_exception(self):
|
|
20
|
+
from meshcode.exceptions import MeshCodeError
|
|
21
|
+
assert issubclass(MeshCodeError, Exception)
|
|
22
|
+
|
|
23
|
+
def test_auth_error_inherits_base(self):
|
|
24
|
+
from meshcode.exceptions import AuthError, MeshCodeError
|
|
25
|
+
assert issubclass(AuthError, MeshCodeError)
|
|
26
|
+
|
|
27
|
+
def test_rpc_error_inherits_base(self):
|
|
28
|
+
from meshcode.exceptions import RPCError, MeshCodeError
|
|
29
|
+
assert issubclass(RPCError, MeshCodeError)
|
|
30
|
+
|
|
31
|
+
def test_timeout_error_inherits_base(self):
|
|
32
|
+
from meshcode.exceptions import MeshCodeTimeoutError, MeshCodeError
|
|
33
|
+
assert issubclass(MeshCodeTimeoutError, MeshCodeError)
|
|
34
|
+
|
|
35
|
+
def test_connection_error_inherits_base(self):
|
|
36
|
+
from meshcode.exceptions import MeshCodeConnectionError, MeshCodeError
|
|
37
|
+
assert issubclass(MeshCodeConnectionError, MeshCodeError)
|
|
38
|
+
|
|
39
|
+
def test_catch_all_works(self):
|
|
40
|
+
"""Single `except MeshCodeError` catches all subclasses."""
|
|
41
|
+
from meshcode.exceptions import (
|
|
42
|
+
MeshCodeError, AuthError, RPCError,
|
|
43
|
+
MeshCodeTimeoutError, MeshCodeConnectionError,
|
|
44
|
+
)
|
|
45
|
+
for exc_class in [AuthError, RPCError, MeshCodeTimeoutError, MeshCodeConnectionError]:
|
|
46
|
+
try:
|
|
47
|
+
raise exc_class("test")
|
|
48
|
+
except MeshCodeError:
|
|
49
|
+
pass # Expected
|
|
50
|
+
except Exception:
|
|
51
|
+
assert False, f"{exc_class.__name__} not caught by MeshCodeError"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class TestRPCErrorAttributes:
|
|
55
|
+
"""RPCError carries structured metadata."""
|
|
56
|
+
|
|
57
|
+
def test_rpc_name_attribute(self):
|
|
58
|
+
from meshcode.exceptions import RPCError
|
|
59
|
+
e = RPCError("test", rpc_name="mc_heartbeat")
|
|
60
|
+
assert e.rpc_name == "mc_heartbeat"
|
|
61
|
+
|
|
62
|
+
def test_detail_attribute(self):
|
|
63
|
+
from meshcode.exceptions import RPCError
|
|
64
|
+
e = RPCError("test", detail="not found")
|
|
65
|
+
assert e.detail == "not found"
|
|
66
|
+
|
|
67
|
+
def test_default_attributes_empty(self):
|
|
68
|
+
from meshcode.exceptions import RPCError
|
|
69
|
+
e = RPCError("test")
|
|
70
|
+
assert e.rpc_name == ""
|
|
71
|
+
assert e.detail == ""
|
|
72
|
+
|
|
73
|
+
def test_str_is_message(self):
|
|
74
|
+
from meshcode.exceptions import RPCError
|
|
75
|
+
e = RPCError("something went wrong")
|
|
76
|
+
assert str(e) == "something went wrong"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class TestExceptionImportsFromPackage:
|
|
80
|
+
"""Exceptions are importable from the top-level meshcode package."""
|
|
81
|
+
|
|
82
|
+
def test_import_from_meshcode(self):
|
|
83
|
+
from meshcode import (
|
|
84
|
+
MeshCodeError, AuthError, RPCError,
|
|
85
|
+
MeshCodeTimeoutError, MeshCodeConnectionError,
|
|
86
|
+
)
|
|
87
|
+
assert MeshCodeError is not None
|
|
88
|
+
|
|
89
|
+
def test_in_all(self):
|
|
90
|
+
import meshcode
|
|
91
|
+
for name in ["MeshCodeError", "AuthError", "RPCError",
|
|
92
|
+
"MeshCodeTimeoutError", "MeshCodeConnectionError"]:
|
|
93
|
+
assert name in meshcode.__all__, f"{name} missing from __all__"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class TestNoShadowBuiltins:
|
|
97
|
+
"""Custom names don't shadow Python builtins."""
|
|
98
|
+
|
|
99
|
+
def test_timeout_name_is_prefixed(self):
|
|
100
|
+
from meshcode.exceptions import MeshCodeTimeoutError
|
|
101
|
+
assert MeshCodeTimeoutError.__name__ == "MeshCodeTimeoutError"
|
|
102
|
+
assert MeshCodeTimeoutError is not TimeoutError
|
|
103
|
+
|
|
104
|
+
def test_connection_name_is_prefixed(self):
|
|
105
|
+
from meshcode.exceptions import MeshCodeConnectionError
|
|
106
|
+
assert MeshCodeConnectionError.__name__ == "MeshCodeConnectionError"
|
|
107
|
+
assert MeshCodeConnectionError is not ConnectionError
|
|
@@ -76,6 +76,14 @@ CRITICAL_RPCS: list[RPCSpec] = [
|
|
|
76
76
|
description="Returns effective status based on heartbeat freshness (NOT YET IMPLEMENTED)",
|
|
77
77
|
exists=False,
|
|
78
78
|
),
|
|
79
|
+
RPCSpec(
|
|
80
|
+
name="mc_system_health",
|
|
81
|
+
schema="public",
|
|
82
|
+
params=[],
|
|
83
|
+
return_type="jsonb",
|
|
84
|
+
required_in_body=["mc_agents", "mc_messages", "stale_agent_count", "message_delivery_rate"],
|
|
85
|
+
description="Returns system health metrics (active/stale agents, delivery rate, errors)",
|
|
86
|
+
),
|
|
79
87
|
RPCSpec(
|
|
80
88
|
name="mc_get_mesh_graph",
|
|
81
89
|
schema="public",
|
|
@@ -187,7 +195,7 @@ def parse_params(signature: str) -> list[tuple[str, str]]:
|
|
|
187
195
|
|
|
188
196
|
# ─── Tests ─────────────────────────────────────────────────────────────────────
|
|
189
197
|
|
|
190
|
-
class
|
|
198
|
+
class CheckResults:
|
|
191
199
|
def __init__(self):
|
|
192
200
|
self.passed = 0
|
|
193
201
|
self.failed = 0
|
|
@@ -208,7 +216,7 @@ class TestResults:
|
|
|
208
216
|
print(f" ⊘ {msg}")
|
|
209
217
|
|
|
210
218
|
|
|
211
|
-
def
|
|
219
|
+
def check_function_exists(spec: RPCSpec, results: CheckResults) -> Optional[tuple[str, str, str]]:
|
|
212
220
|
"""Test 1: Verify function exists in migrations."""
|
|
213
221
|
if not spec.exists:
|
|
214
222
|
results.skip(f"{spec.name}: known not-yet-implemented")
|
|
@@ -224,7 +232,7 @@ def test_function_exists(spec: RPCSpec, results: TestResults) -> Optional[tuple[
|
|
|
224
232
|
return None
|
|
225
233
|
|
|
226
234
|
|
|
227
|
-
def
|
|
235
|
+
def check_function_signature(spec: RPCSpec, signature: str, results: CheckResults):
|
|
228
236
|
"""Test 2: Verify function has expected parameters."""
|
|
229
237
|
if not spec.exists:
|
|
230
238
|
return
|
|
@@ -249,7 +257,7 @@ def test_function_signature(spec: RPCSpec, signature: str, results: TestResults)
|
|
|
249
257
|
results.fail(f"{spec.name}: missing required param '{exp_name}'")
|
|
250
258
|
|
|
251
259
|
|
|
252
|
-
def
|
|
260
|
+
def check_function_body(spec: RPCSpec, body: str, results: CheckResults):
|
|
253
261
|
"""Test 3: Verify function body contains required patterns."""
|
|
254
262
|
if not spec.exists:
|
|
255
263
|
return
|
|
@@ -261,7 +269,7 @@ def test_function_body(spec: RPCSpec, body: str, results: TestResults):
|
|
|
261
269
|
results.fail(f"{spec.name}: body MISSING required pattern '{pattern}'")
|
|
262
270
|
|
|
263
271
|
|
|
264
|
-
def
|
|
272
|
+
def check_return_type(spec: RPCSpec, body: str, results: CheckResults):
|
|
265
273
|
"""Test 4: Verify function returns correct type."""
|
|
266
274
|
if not spec.exists:
|
|
267
275
|
return
|
|
@@ -283,7 +291,7 @@ def test_return_type(spec: RPCSpec, body: str, results: TestResults):
|
|
|
283
291
|
results.fail(f"{spec.name}: could not find RETURNS clause")
|
|
284
292
|
|
|
285
293
|
|
|
286
|
-
def
|
|
294
|
+
def check_no_security_antipatterns(spec: RPCSpec, body: str, results: CheckResults):
|
|
287
295
|
"""Test 5: Check for known security anti-patterns in function body."""
|
|
288
296
|
if not spec.exists:
|
|
289
297
|
return
|
|
@@ -301,7 +309,7 @@ def test_no_security_antipatterns(spec: RPCSpec, body: str, results: TestResults
|
|
|
301
309
|
results.ok(f"{spec.name}: no unsafe EXECUTE patterns")
|
|
302
310
|
|
|
303
311
|
|
|
304
|
-
def
|
|
312
|
+
def check_error_handling(spec: RPCSpec, body: str, results: CheckResults):
|
|
305
313
|
"""Test 6: Verify function has error handling (returns error JSON, not raw exception)."""
|
|
306
314
|
if not spec.exists:
|
|
307
315
|
return
|
|
@@ -333,21 +341,21 @@ def run_all_tests():
|
|
|
333
341
|
print("=" * 70)
|
|
334
342
|
print()
|
|
335
343
|
|
|
336
|
-
results =
|
|
344
|
+
results = CheckResults()
|
|
337
345
|
|
|
338
346
|
for spec in CRITICAL_RPCS:
|
|
339
347
|
print(f"\n─── {spec.name} ({'NOT IMPLEMENTED' if not spec.exists else spec.description}) ───")
|
|
340
348
|
|
|
341
|
-
found =
|
|
349
|
+
found = check_function_exists(spec, results)
|
|
342
350
|
if not found:
|
|
343
351
|
continue
|
|
344
352
|
|
|
345
353
|
file_name, signature, body = found
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
354
|
+
check_function_signature(spec, signature, results)
|
|
355
|
+
check_return_type(spec, body, results)
|
|
356
|
+
check_function_body(spec, body, results)
|
|
357
|
+
check_no_security_antipatterns(spec, body, results)
|
|
358
|
+
check_error_handling(spec, body, results)
|
|
351
359
|
|
|
352
360
|
# Summary
|
|
353
361
|
print("\n" + "=" * 70)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|