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.
Files changed (40) hide show
  1. {meshcode-2.10.56 → meshcode-2.10.57}/PKG-INFO +1 -1
  2. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/__init__.py +16 -1
  3. meshcode-2.10.57/meshcode/exceptions.py +52 -0
  4. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/meshcode_mcp/server.py +16 -1
  5. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/setup_clients.py +52 -23
  6. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode.egg-info/PKG-INFO +1 -1
  7. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode.egg-info/SOURCES.txt +2 -0
  8. {meshcode-2.10.56 → meshcode-2.10.57}/pyproject.toml +1 -1
  9. meshcode-2.10.57/tests/test_exceptions.py +107 -0
  10. {meshcode-2.10.56 → meshcode-2.10.57}/tests/test_rpc_migrations.py +22 -14
  11. {meshcode-2.10.56 → meshcode-2.10.57}/README.md +0 -0
  12. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/ascii_art.py +0 -0
  13. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/cli.py +0 -0
  14. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/comms_v4.py +0 -0
  15. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/compat.py +0 -0
  16. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/invites.py +0 -0
  17. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/launcher.py +0 -0
  18. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/launcher_install.py +0 -0
  19. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/meshcode_mcp/__init__.py +0 -0
  20. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/meshcode_mcp/__main__.py +0 -0
  21. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/meshcode_mcp/backend.py +0 -0
  22. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/meshcode_mcp/realtime.py +0 -0
  23. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/meshcode_mcp/test_backend.py +0 -0
  24. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  25. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  26. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/preferences.py +0 -0
  27. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/protocol_v2.py +0 -0
  28. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/run_agent.py +0 -0
  29. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/secrets.py +0 -0
  30. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode/self_update.py +0 -0
  31. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode.egg-info/dependency_links.txt +0 -0
  32. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode.egg-info/entry_points.txt +0 -0
  33. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode.egg-info/requires.txt +0 -0
  34. {meshcode-2.10.56 → meshcode-2.10.57}/meshcode.egg-info/top_level.txt +0 -0
  35. {meshcode-2.10.56 → meshcode-2.10.57}/setup.cfg +0 -0
  36. {meshcode-2.10.56 → meshcode-2.10.57}/tests/test_core.py +0 -0
  37. {meshcode-2.10.56 → meshcode-2.10.57}/tests/test_cross_agent_messaging.py +0 -0
  38. {meshcode-2.10.56 → meshcode-2.10.57}/tests/test_esc_deaf_state.py +0 -0
  39. {meshcode-2.10.56 → meshcode-2.10.57}/tests/test_realtime_event_freshness.py +0 -0
  40. {meshcode-2.10.56 → meshcode-2.10.57}/tests/test_status_enum_coverage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.10.56
3
+ Version: 2.10.57
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -1,5 +1,14 @@
1
1
  """MeshCode — Real-time communication between AI agents."""
2
- __version__ = "2.10.56"
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
- print(f"[meshcode] ERROR: cannot load secrets module: {e}", file=sys.stderr)
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
- print("[meshcode] ERROR: No credentials found. You need to log in first.", file=sys.stderr)
45
- print("[meshcode]", file=sys.stderr)
46
- print("[meshcode] 1. Get your API key at: https://meshcode.io/onboarding", file=sys.stderr)
47
- print("[meshcode] (or https://meshcode.io/settings if you already have an account)", file=sys.stderr)
48
- print("[meshcode] 2. Run: meshcode login mc_<your-api-key>", file=sys.stderr)
49
- print("[meshcode] 3. Then re-run this command.", file=sys.stderr)
50
- sys.exit(2)
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
- print(f"[meshcode] ERROR: could not resolve project '{project}': {data['error']}", file=sys.stderr)
123
- # Try to suggest the user's actual projects
124
- _suggest_projects(api_key, sb)
125
- sys.exit(2)
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
- print(f"[meshcode] ERROR: could not resolve project '{project}': {e}", file=sys.stderr)
128
- print(f"[meshcode] Check your API key and project name. Get help at https://meshcode.io/docs", file=sys.stderr)
129
- sys.exit(2)
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 _suggest_projects(api_key: str, sb: dict):
134
- """Try to list the user's projects to help with typos."""
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
- print(f"[meshcode] Your projects:", file=sys.stderr)
159
+ lines = ["Your projects:"]
153
160
  for p in projects:
154
- print(f"[meshcode] - {p['name']}", file=sys.stderr)
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
- project_id = _resolve_project_id(api_key, project, sb)
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
- creds = _load_credentials()
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
- project_id = _resolve_project_id(api_key, project, sb)
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.10.56
3
+ Version: 2.10.57
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "meshcode"
7
- version = "2.10.56"
7
+ version = "2.10.57"
8
8
  description = "Real-time communication between AI agents — Supabase-backed CLI"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -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 TestResults:
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 test_function_exists(spec: RPCSpec, results: TestResults) -> Optional[tuple[str, str, str]]:
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 test_function_signature(spec: RPCSpec, signature: str, results: TestResults):
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 test_function_body(spec: RPCSpec, body: str, results: TestResults):
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 test_return_type(spec: RPCSpec, body: str, results: TestResults):
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 test_no_security_antipatterns(spec: RPCSpec, body: str, results: TestResults):
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 test_error_handling(spec: RPCSpec, body: str, results: TestResults):
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 = TestResults()
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 = test_function_exists(spec, results)
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
- test_function_signature(spec, signature, results)
347
- test_return_type(spec, body, results)
348
- test_function_body(spec, body, results)
349
- test_no_security_antipatterns(spec, body, results)
350
- test_error_handling(spec, body, results)
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