meshcode 2.11.6__tar.gz → 2.11.7__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 (213) hide show
  1. {meshcode-2.11.6 → meshcode-2.11.7}/PKG-INFO +1 -1
  2. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/__init__.py +1 -1
  3. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/meshcode_mcp/server.py +223 -19
  4. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode.egg-info/PKG-INFO +1 -1
  5. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode.egg-info/SOURCES.txt +3 -0
  6. {meshcode-2.11.6 → meshcode-2.11.7}/pyproject.toml +1 -1
  7. meshcode-2.11.7/tests/test_boot_bug_regression.py +205 -0
  8. meshcode-2.11.7/tests/test_epistemic_v1_stop_conditions.py +158 -0
  9. meshcode-2.11.7/tests/test_install_guard.py +112 -0
  10. {meshcode-2.11.6 → meshcode-2.11.7}/README.md +0 -0
  11. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/_stop_hook_template.py +0 -0
  12. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/ascii_art.py +0 -0
  13. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/cli.py +0 -0
  14. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/comms_v4.py +0 -0
  15. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/compat.py +0 -0
  16. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/daemon.py +0 -0
  17. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/error_hints.py +0 -0
  18. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/exceptions.py +0 -0
  19. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/invites.py +0 -0
  20. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/launcher.py +0 -0
  21. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/launcher_install.py +0 -0
  22. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/meshcode_mcp/__init__.py +0 -0
  23. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/meshcode_mcp/__main__.py +0 -0
  24. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/meshcode_mcp/backend.py +0 -0
  25. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/meshcode_mcp/realtime.py +0 -0
  26. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
  27. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/meshcode_mcp/test_backend.py +0 -0
  28. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  29. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  30. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/preferences.py +0 -0
  31. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/protocol_handler.py +0 -0
  32. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/protocol_v2.py +0 -0
  33. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/quickstart.py +0 -0
  34. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/run_agent.py +0 -0
  35. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/scripts/check_secrets.py +0 -0
  36. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/scripts/race_rate_harness.py +0 -0
  37. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/secrets.py +0 -0
  38. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/self_update.py +0 -0
  39. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/setup_clients.py +0 -0
  40. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/supervisor.py +0 -0
  41. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/upload.py +0 -0
  42. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/comms_v4.py +0 -0
  43. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/__init__.py +0 -0
  44. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
  45. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/cli.py +0 -0
  46. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
  47. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/compat.py +0 -0
  48. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
  49. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
  50. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/invites.py +0 -0
  51. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/launcher.py +0 -0
  52. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
  53. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  54. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  55. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
  56. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  57. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
  58. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  59. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  60. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  61. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/preferences.py +0 -0
  62. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
  63. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
  64. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
  65. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/secrets.py +0 -0
  66. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/self_update.py +0 -0
  67. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
  68. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
  69. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/upload.py +0 -0
  70. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/scripts/sentinel.py +0 -0
  71. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_core.py +0 -0
  72. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
  73. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
  74. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
  75. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
  76. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
  77. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
  78. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
  79. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
  80. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
  81. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
  82. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
  83. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
  84. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
  85. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
  86. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
  87. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
  88. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
  89. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
  90. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
  91. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
  92. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
  93. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
  94. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
  95. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
  96. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
  97. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
  98. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
  99. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
  100. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  101. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
  102. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
  103. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
  104. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
  105. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
  106. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
  107. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
  108. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
  109. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
  110. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/comms_v4.py +0 -0
  111. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/__init__.py +0 -0
  112. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
  113. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/cli.py +0 -0
  114. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
  115. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/compat.py +0 -0
  116. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
  117. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
  118. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/invites.py +0 -0
  119. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/launcher.py +0 -0
  120. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
  121. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  122. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  123. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
  124. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  125. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
  126. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  127. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  128. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  129. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/preferences.py +0 -0
  130. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
  131. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
  132. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
  133. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/secrets.py +0 -0
  134. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/self_update.py +0 -0
  135. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
  136. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
  137. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/upload.py +0 -0
  138. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/scripts/sentinel.py +0 -0
  139. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_core.py +0 -0
  140. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
  141. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
  142. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
  143. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
  144. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
  145. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
  146. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
  147. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
  148. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
  149. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
  150. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
  151. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/comms_v4.py +0 -0
  152. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
  153. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
  154. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/cli.py +0 -0
  155. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
  156. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/compat.py +0 -0
  157. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
  158. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
  159. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/invites.py +0 -0
  160. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
  161. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
  162. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  163. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  164. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
  165. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  166. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
  167. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  168. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  169. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  170. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
  171. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
  172. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
  173. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
  174. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
  175. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
  176. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
  177. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
  178. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/upload.py +0 -0
  179. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
  180. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_core.py +0 -0
  181. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
  182. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
  183. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
  184. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
  185. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
  186. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
  187. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
  188. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
  189. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
  190. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
  191. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
  192. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode.egg-info/dependency_links.txt +0 -0
  193. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode.egg-info/entry_points.txt +0 -0
  194. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode.egg-info/requires.txt +0 -0
  195. {meshcode-2.11.6 → meshcode-2.11.7}/meshcode.egg-info/top_level.txt +0 -0
  196. {meshcode-2.11.6 → meshcode-2.11.7}/setup.cfg +0 -0
  197. {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_auto_update_hardening.py +0 -0
  198. {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_core.py +0 -0
  199. {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_cross_agent_messaging.py +0 -0
  200. {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_esc_deaf_state.py +0 -0
  201. {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_exceptions.py +0 -0
  202. {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_lease_sigterm_release.py +0 -0
  203. {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_mark_read_batch.py +0 -0
  204. {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_migration_integrity.py +0 -0
  205. {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_realtime_event_freshness.py +0 -0
  206. {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_rls_cross_tenant.py +0 -0
  207. {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_rpc_migrations.py +0 -0
  208. {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_security_regressions.py +0 -0
  209. {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_sentinel.py +0 -0
  210. {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_sleep_signals.py +0 -0
  211. {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_status_enum_coverage.py +0 -0
  212. {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_stay_on_loop_hook.py +0 -0
  213. {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_wait_open_tasks_contradiction.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.11.6
3
+ Version: 2.11.7
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,5 @@
1
1
  """MeshCode — Real-time communication between AI agents."""
2
- __version__ = "2.11.6"
2
+ __version__ = "2.11.7"
3
3
 
4
4
  # Exception hierarchy — eagerly imported (lightweight, no deps)
5
5
  from meshcode.exceptions import ( # noqa: F401
@@ -968,6 +968,90 @@ def with_working_status(func):
968
968
  return swrapper
969
969
 
970
970
 
971
+ # ============================================================
972
+ # Consequential-tool gate (task b4befbfd item 1 — epistemic V1)
973
+ # ============================================================
974
+ # Tools listed below refuse to run unless the calling agent has an open
975
+ # decision in mc_traces from the last 5 minutes (logged via meshcode_decide).
976
+ # Drives the team toward explicit pre-decision logging on hard-to-reverse
977
+ # actions. Whitelist is overridable via mc_meshwork_memory key
978
+ # `consequential_tool_whitelist` (future config; v1 uses the bake-in below
979
+ # so commanders don't need to seed-bootstrap before the gate works).
980
+
981
+ _CONSEQUENTIAL_TOOLS_DEFAULT = frozenset([
982
+ # Mesh-wide / cross-agent destructive
983
+ "meshcode_done", # broadcasts DONE to ALL agents
984
+ "meshcode_disconnect_agent", # offlines another agent
985
+ "meshcode_edit_agent", # mutates another agent's identity/role
986
+ "meshcode_edit_memory", # writes into another agent's memory
987
+ "meshcode_forget", # deletes memory (tombstones it)
988
+ "meshcode_create_meshwork", # creates a new meshwork (hard to unwind)
989
+ "meshcode_marketplace_install", # installs external code into project
990
+ "meshcode_publish_template", # publishes a template publicly
991
+ "meshcode_proposal_convert", # converts proposal into a real task
992
+ "meshcode_proposal_reject", # rejects a proposal permanently
993
+ "meshcode_task_cancel", # cancels a task (irreversible terminal state)
994
+ ])
995
+
996
+
997
+ def _is_consequential(tool_name: str) -> bool:
998
+ """V1 reads the bake-in set. Future: lazy-load mc_meshwork_memory
999
+ `consequential_tool_whitelist` and merge. Soft-fail to the bake-in if
1000
+ the RPC is unavailable so the gate never blocks itself out."""
1001
+ return tool_name in _CONSEQUENTIAL_TOOLS_DEFAULT
1002
+
1003
+
1004
+ def _check_decision_gate(tool_name: str) -> Optional[Dict[str, Any]]:
1005
+ """Returns None when the gate is open; returns an error dict to be
1006
+ surfaced as the tool result when the gate is closed. Soft-fail on RPC
1007
+ error (gate opens) so a transient DB blip doesn't trap the agent."""
1008
+ if not _is_consequential(tool_name):
1009
+ return None
1010
+ try:
1011
+ resp = be.sb_rpc("mc_has_open_decision", {
1012
+ "p_api_key": _get_api_key(),
1013
+ "p_project_id": _PROJECT_ID,
1014
+ "p_agent_name": AGENT_NAME,
1015
+ })
1016
+ if isinstance(resp, dict) and resp.get("ok") and not resp.get("has_open_decision"):
1017
+ return {
1018
+ "ok": False,
1019
+ "error_code": "decision_required",
1020
+ "hint": (
1021
+ f"`{tool_name}` is a consequential action. Call "
1022
+ f"meshcode_decide(decision, alternatives, evidence_refs) "
1023
+ f"first so the team can learn from the outcome. After "
1024
+ f"the action is observable, call meshcode_outcome(trace_id, ...)."
1025
+ ),
1026
+ "consequential_tool": tool_name,
1027
+ }
1028
+ except Exception as _gate_err:
1029
+ log.debug(f"decision gate soft-fail for {tool_name}: {_gate_err}")
1030
+ return None
1031
+
1032
+
1033
+ def with_decision_gate(func):
1034
+ """Decorator: applies _check_decision_gate before calling func. Use
1035
+ AFTER @mcp.tool() and AFTER @with_working_status so the gate runs
1036
+ inside the working-status window (and shows up in tool_call logs)."""
1037
+ name = func.__name__
1038
+ if asyncio.iscoroutinefunction(func):
1039
+ @_functools.wraps(func)
1040
+ async def awrapper(*args, **kwargs):
1041
+ blocked = _check_decision_gate(name)
1042
+ if blocked is not None:
1043
+ return blocked
1044
+ return await func(*args, **kwargs)
1045
+ return awrapper
1046
+ @_functools.wraps(func)
1047
+ def wrapper(*args, **kwargs):
1048
+ blocked = _check_decision_gate(name)
1049
+ if blocked is not None:
1050
+ return blocked
1051
+ return func(*args, **kwargs)
1052
+ return wrapper
1053
+
1054
+
971
1055
  # ============================================================
972
1056
  # Single-instance lease — prevent split-brain when two windows
973
1057
  # run `meshcode run <same-agent>` simultaneously.
@@ -1388,28 +1472,51 @@ def _log_crash_to_db(reason: str = "unknown", error_detail: str = "") -> None:
1388
1472
  # ============================================================
1389
1473
  # Agent identity from Supabase profile (for system instructions)
1390
1474
  # ============================================================
1475
+ # MIG-PLAN-A 2026-05-18: profile fetch moved out of module-import time
1476
+ # (was the last be.sb_rpc here that ran on import). Now starts as
1477
+ # placeholders; BG thread spawned by lifespan calls mc_agent_get_profile
1478
+ # and updates the globals. _build_instructions() runs at import using
1479
+ # whatever is currently in the globals — for first-boot of a fresh agent
1480
+ # this means InitializeResult.instructions carries empty launch_prompt /
1481
+ # fallback role_description; first-tool-call envelope can re-inject if
1482
+ # needed. Net win: zero network I/O at module-import time → MCP
1483
+ # handshake never blocks on profile fetch.
1391
1484
 
1392
1485
  _LAUNCH_PROMPT = ""
1393
1486
  _ROLE_DESCRIPTION = AGENT_ROLE or ""
1394
- try:
1395
- _profile = be.sb_rpc("mc_agent_get_profile", {
1396
- "p_project_id": _PROJECT_ID,
1397
- "p_agent_name": AGENT_NAME,
1398
- })
1399
- if isinstance(_profile, list) and _profile:
1400
- _profile = _profile[0]
1401
- if isinstance(_profile, dict):
1402
- _inner = _profile.get("profile") if isinstance(_profile.get("profile"), dict) else _profile
1403
- _LAUNCH_PROMPT = (_inner.get("launch_prompt") or _profile.get("launch_prompt") or "") if isinstance(_inner, dict) else ""
1404
- _ROLE_DESCRIPTION = (
1405
- (_inner.get("role_description") if isinstance(_inner, dict) else None)
1406
- or _profile.get("role_description")
1407
- or _profile.get("role")
1408
- or AGENT_ROLE
1409
- or "(unspecified)"
1410
- )
1411
- except Exception as _e:
1412
- log.warning(f"could not fetch agent profile: {_e}")
1487
+
1488
+ def _load_agent_profile_bg() -> None:
1489
+ """Run by daemon thread from lifespan. Populates _LAUNCH_PROMPT +
1490
+ _ROLE_DESCRIPTION from mc_agent_get_profile. Soft-fail: log only.
1491
+ Note: _INSTRUCTIONS has already been emitted by the time this runs,
1492
+ so values landing here are surfaced to the agent only via tools that
1493
+ read the globals later (most do not — _build_instructions is the only
1494
+ real consumer)."""
1495
+ global _LAUNCH_PROMPT, _ROLE_DESCRIPTION
1496
+ try:
1497
+ prof = be.sb_rpc("mc_agent_get_profile", {
1498
+ "p_project_id": _PROJECT_ID,
1499
+ "p_agent_name": AGENT_NAME,
1500
+ })
1501
+ if isinstance(prof, list) and prof:
1502
+ prof = prof[0]
1503
+ if isinstance(prof, dict):
1504
+ inner = prof.get("profile") if isinstance(prof.get("profile"), dict) else prof
1505
+ lp = ((inner.get("launch_prompt") if isinstance(inner, dict) else None)
1506
+ or prof.get("launch_prompt") or "")
1507
+ rd = ((inner.get("role_description") if isinstance(inner, dict) else None)
1508
+ or prof.get("role_description")
1509
+ or prof.get("role")
1510
+ or AGENT_ROLE
1511
+ or "(unspecified)")
1512
+ _LAUNCH_PROMPT = lp
1513
+ _ROLE_DESCRIPTION = rd
1514
+ log.info(
1515
+ f"agent profile loaded (bg): role='{_ROLE_DESCRIPTION[:60]}' "
1516
+ f"launch_prompt_len={len(_LAUNCH_PROMPT)}"
1517
+ )
1518
+ except Exception as _e:
1519
+ log.warning(f"could not fetch agent profile (bg): {_e}")
1413
1520
 
1414
1521
 
1415
1522
  # ── Leader/commander detection (single source of truth) ───────
@@ -1502,6 +1609,91 @@ if _INSTRUCTIONS_BUILD_NS > 500_000_000:
1502
1609
  f"Investigate any be.sb_rpc / network I/O added to _build_instructions()."
1503
1610
  )
1504
1611
 
1612
+ # ============================================================
1613
+ # Install-guard (task dbae99ed 2026-05-18) — detect mid-session
1614
+ # pip --force-reinstall over the running env and execv-restart cleanly
1615
+ # instead of crashing on lockfile collision. Class:
1616
+ # feedback_pip_install_into_live_mcp_env_self_kill weight 9.
1617
+ # Paired with stop_hook _mcp_disconnected_without_reconnect (P0 mig
1618
+ # 68a3545e) so the brief MCP-socket-drop during execv doesn't trap the
1619
+ # Claude Code agent.
1620
+ # ============================================================
1621
+ from pathlib import Path as _Path # local-scope; rest of module doesn't need it
1622
+ _PKG_DIR = _Path(__file__).resolve().parent.parent # .../site-packages/meshcode/
1623
+
1624
+ def _snapshot_pkg_mtimes():
1625
+ out = {}
1626
+ try:
1627
+ for p in _PKG_DIR.rglob("*.py"):
1628
+ try:
1629
+ out[str(p)] = p.stat().st_mtime
1630
+ except OSError:
1631
+ continue
1632
+ except Exception:
1633
+ pass
1634
+ return out
1635
+
1636
+ _PKG_PY_MTIMES = _snapshot_pkg_mtimes()
1637
+ log.info(f"install-guard: snapshotted {len(_PKG_PY_MTIMES)} *.py mtimes under {_PKG_DIR}")
1638
+
1639
+
1640
+ def _install_guard_check() -> None:
1641
+ """Heartbeat-tick scan: if any *.py under the meshcode pkg path has
1642
+ a fresher mtime than recorded at boot, the env was reinstalled while
1643
+ the process is alive (class feedback_pip_install_into_live_mcp_env_self_kill).
1644
+ The running image holds stale bytecode; lockfile collision and
1645
+ orphan-watchdog will eventually kill us anyway. Pre-empt: release the
1646
+ lease and execv-restart so the new wheel is picked up cleanly.
1647
+
1648
+ The MCP socket to Claude Code WILL drop during the exec window.
1649
+ Stop_hook _mcp_disconnected_without_reconnect (mig 68a3545e) catches
1650
+ it; user runs `meshcode go <agent>` to pick up the new wheel.
1651
+
1652
+ Soft-fail throughout: any exception here is logged and swallowed so
1653
+ the heartbeat thread isn't taken down by a flaky filesystem call.
1654
+ """
1655
+ if not _PKG_PY_MTIMES:
1656
+ return
1657
+ try:
1658
+ for path, old_mtime in list(_PKG_PY_MTIMES.items()):
1659
+ try:
1660
+ new_mtime = os.path.getmtime(path)
1661
+ except OSError:
1662
+ continue
1663
+ # 0.5s slack — covers fs-timestamp resolution and tar extract
1664
+ # re-emit jitter without false-positive on intentional touch.
1665
+ if new_mtime <= old_mtime + 0.5:
1666
+ continue
1667
+ log.warning(
1668
+ f"install-guard: self-modification detected on {path} "
1669
+ f"(mtime {old_mtime:.0f} → {new_mtime:.0f}). The running "
1670
+ f"image holds stale bytecode; triggering clean execv-restart "
1671
+ f"so the new wheel is picked up. MCP socket will drop briefly "
1672
+ f"— stop_hook handles the agent release (mig 68a3545e). User "
1673
+ f"runs `meshcode go <agent>` to relaunch under the new image. "
1674
+ f"Class: feedback_pip_install_into_live_mcp_env_self_kill."
1675
+ )
1676
+ try:
1677
+ _release_lease_synchronous(timeout_s=1.5)
1678
+ except Exception:
1679
+ pass
1680
+ _heartbeat_stop.set()
1681
+ # execv replaces the current process; daemon threads die with
1682
+ # it; new process inherits stdio. We pass the original argv so
1683
+ # the entry-point command line is preserved verbatim.
1684
+ try:
1685
+ os.execv(sys.executable, [sys.executable] + sys.argv)
1686
+ except Exception as _exec_err:
1687
+ log.error(
1688
+ f"install-guard execv failed ({_exec_err}); "
1689
+ f"falling back to os._exit(0). Lifespan finally + "
1690
+ f"stop_hook still recover."
1691
+ )
1692
+ os._exit(0)
1693
+ return # unreachable but explicit
1694
+ except Exception as e:
1695
+ log.debug(f"install-guard scan failed (soft): {e}")
1696
+
1505
1697
  # MIG-PLAN-A 2026-05-18: memory pre-load deferred to BG daemon thread.
1506
1698
  # Was synchronous mc_memory_list (critical+reference tiers + legacy fallback)
1507
1699
  # that appended to module-global _INSTRUCTIONS at import time. On commander-
@@ -1845,6 +2037,11 @@ def _heartbeat_loop_inner():
1845
2037
  except Exception:
1846
2038
  pass
1847
2039
 
2040
+ # Install-guard tick (task dbae99ed). Cheap mtime scan over
2041
+ # ~100-200 *.py files; if any changed since boot, we execv-
2042
+ # restart cleanly before lockfile collision kills us.
2043
+ _install_guard_check()
2044
+
1848
2045
  _hb_resp = be.sb_rpc("mc_heartbeat", {"p_project_id": _PROJECT_ID, "p_agent_name": AGENT_NAME, "p_version": _SDK_VERSION})
1849
2046
 
1850
2047
  # Tombstone gate (mig 211): user disconnected this agent.
@@ -2106,6 +2303,10 @@ async def lifespan(_app):
2106
2303
  # MIG-PLAN-A 2026-05-18: memory pre-load deferred from module-import-time
2107
2304
  # to here. Soft-fail consumers read from _PRELOADED_CRITICAL / _PRELOADED_REFERENCE_KEYS.
2108
2305
  _threading.Thread(target=_preload_memories_bg, daemon=True, name="meshcode-preload-memories").start()
2306
+ # MIG-PLAN-A 2026-05-18 part 2: agent profile fetch deferred. Was the
2307
+ # last be.sb_rpc that ran at module-import time. Closes the import-time
2308
+ # zero-network goal for the CI regression sentinel.
2309
+ _threading.Thread(target=_load_agent_profile_bg, daemon=True, name="meshcode-load-profile").start()
2109
2310
 
2110
2311
  # Heartbeat in daemon thread — independent of asyncio event loop.
2111
2312
  _heartbeat_stop.clear()
@@ -3343,6 +3544,7 @@ async def _meshcode_wait_inner(actual_timeout: int, include_acks: bool) -> Dict[
3343
3544
 
3344
3545
  @mcp.tool()
3345
3546
  @with_working_status
3547
+ @with_decision_gate
3346
3548
  def meshcode_done(reason: str) -> Dict[str, Any]:
3347
3549
  """Broadcast DONE to all agents — exits everyone's wait loop.
3348
3550
 
@@ -4605,6 +4807,7 @@ def meshcode_edit_agent(name: str, role: Optional[str] = None, launch_prompt: Op
4605
4807
 
4606
4808
  @mcp.tool()
4607
4809
  @with_working_status
4810
+ @with_decision_gate
4608
4811
  def meshcode_edit_memory(agent_name: str, key: str, value: Any) -> Dict[str, Any]:
4609
4812
  """Edit another agent's memory (for commanders coordinating the team).
4610
4813
 
@@ -4833,6 +5036,7 @@ def meshcode_recall_keys() -> Dict[str, Any]:
4833
5036
 
4834
5037
  @mcp.tool()
4835
5038
  @with_working_status
5039
+ @with_decision_gate
4836
5040
  def meshcode_forget(key: str) -> Dict[str, Any]:
4837
5041
  """Delete a stored memory.
4838
5042
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.11.6
3
+ Version: 2.11.7
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -190,10 +190,13 @@ meshcode/meshcode_mcp/test_server_wrapper.py
190
190
  meshcode/scripts/check_secrets.py
191
191
  meshcode/scripts/race_rate_harness.py
192
192
  tests/test_auto_update_hardening.py
193
+ tests/test_boot_bug_regression.py
193
194
  tests/test_core.py
194
195
  tests/test_cross_agent_messaging.py
196
+ tests/test_epistemic_v1_stop_conditions.py
195
197
  tests/test_esc_deaf_state.py
196
198
  tests/test_exceptions.py
199
+ tests/test_install_guard.py
197
200
  tests/test_lease_sigterm_release.py
198
201
  tests/test_mark_read_batch.py
199
202
  tests/test_migration_integrity.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "meshcode"
7
- version = "2.11.6"
7
+ version = "2.11.7"
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,205 @@
1
+ """
2
+ Boot-bug regression sentinels (task abae2997 → closes the class identified in
3
+ memory project_mesh_commander_mcp_failed_at_launch weight 10).
4
+
5
+ Two tests, one per RC of the 2026-05-18 boot bug:
6
+
7
+ (a) IMPORT-TIME RPC ALLOWLIST
8
+ Spawns a subprocess that:
9
+ - Monkeypatches meshcode.meshcode_mcp.backend.sb_rpc to record every
10
+ call name. Then ALLOWS only an explicit whitelist (currently:
11
+ mc_register_agent — required for agent identity at startup), and
12
+ raises on anything else.
13
+ - Sets the minimum env vars so the import doesn't legitimately fall
14
+ through to the mc_resolve_project path.
15
+ - Imports meshcode.meshcode_mcp.server.
16
+ - Asserts the import completes and no NON-WHITELISTED RPC was called
17
+ at module-import time. Catches drift toward the Claude Code MCP-
18
+ handshake window (Plan A bug, RC #2).
19
+
20
+ To widen the whitelist intentionally, add to ALLOWED_IMPORT_RPCS below
21
+ AND document why in the comment block. Most import-time RPCs are bugs
22
+ waiting to happen — keep the list as short as possible.
23
+
24
+ (b) REGISTER-ALL-VALID-STATUSES
25
+ Live-DB test (skips without env vars). Reads public.mc_agent_all_statuses()
26
+ from prod, attempts mc_register_agent with each value (inside a transaction
27
+ that rolls back). Asserts no 23514 / valid_agent_status CHECK violation.
28
+ This catches future SDK↔DDL enum drift like the 'needs_launch' incident
29
+ (mig 312 bug, RC #1).
30
+
31
+ Usage:
32
+ pytest tests/test_boot_bug_regression.py -v
33
+
34
+ # live test only:
35
+ MESHCODE_SUPABASE_URL=https://...supabase.co \\
36
+ MESHCODE_SUPABASE_ANON_KEY=sb_publishable_... \\
37
+ MESHCODE_PROJECT_ID=... \\
38
+ pytest tests/test_boot_bug_regression.py::test_register_all_valid_statuses_live -v
39
+ """
40
+
41
+ import os
42
+ import subprocess
43
+ import sys
44
+ import textwrap
45
+ import uuid
46
+ from pathlib import Path
47
+
48
+ import pytest
49
+
50
+ REPO_ROOT = Path(__file__).parent.parent
51
+
52
+
53
+ def test_module_import_rpc_allowlist():
54
+ """RC #2 sentinel — Plan A. Fails if a NON-WHITELISTED be.sb_rpc is
55
+ called at module-import time. That class of call breaks MCP handshake
56
+ on heavy-memory agents."""
57
+ snippet = textwrap.dedent(
58
+ """
59
+ import os, sys
60
+ os.environ.setdefault('MESHCODE_PROJECT', 'meshcode-self-improve')
61
+ os.environ.setdefault('MESHCODE_AGENT', 'backend')
62
+ os.environ.setdefault('MESHCODE_PROJECT_ID',
63
+ '00000000-0000-0000-0000-000000000000')
64
+ os.environ.setdefault('MESHCODE_SUPABASE_URL', 'https://fake.local')
65
+ os.environ.setdefault('MESHCODE_SUPABASE_ANON_KEY', 'fake_anon')
66
+
67
+ # Allow-list: RPCs that may legitimately run at module-import time.
68
+ # Keep this list as short as possible — most additions indicate a
69
+ # latent boot-bug. Document each entry's reason.
70
+ ALLOWED_IMPORT_RPCS = {
71
+ # mc_register_agent: agent identity must exist before the first
72
+ # tool call; lazy-registering on first call would mean tools
73
+ # can fire from a non-existent agent_id. Tolerated at boot;
74
+ # response is small (single row insert/upsert).
75
+ 'mc_register_agent',
76
+ # mc_release_agent_lease: post-SIGKILL stale-lease release.
77
+ # Only fires when _kill_stale_mcp_process found a prior dead
78
+ # process. Single-row update; latency negligible.
79
+ 'mc_release_agent_lease',
80
+ # mc_memory_get: restore inbox watermark (last_seen). Single-
81
+ # row lookup; cheap. Could be deferred to first meshcode_check
82
+ # but the value is needed before the first wait() returns.
83
+ 'mc_memory_get',
84
+ }
85
+
86
+ import meshcode.meshcode_mcp.backend as be
87
+ _calls = []
88
+
89
+ def _gated(name, *a, **kw):
90
+ _calls.append(name)
91
+ if name not in ALLOWED_IMPORT_RPCS:
92
+ raise RuntimeError(
93
+ 'MODULE_IMPORT_RPC_LEAK: be.sb_rpc called at module-import '
94
+ f'time with NON-WHITELISTED rpc={name!r}. '
95
+ 'If this is intentional, defer to lifespan BG thread '
96
+ '(see _load_persona_bg pattern) or add to ALLOWED_IMPORT_RPCS '
97
+ 'with rationale.'
98
+ )
99
+ # Return a minimal-shape ok response so import flow completes.
100
+ return {'ok': True, 'id': None}
101
+
102
+ be.sb_rpc = _gated
103
+
104
+ import meshcode.meshcode_mcp.server # noqa: F401
105
+ print(f'IMPORT_OK calls={_calls}')
106
+ """
107
+ )
108
+
109
+ result = subprocess.run(
110
+ [sys.executable, '-c', snippet],
111
+ capture_output=True,
112
+ text=True,
113
+ cwd=REPO_ROOT,
114
+ timeout=30,
115
+ )
116
+
117
+ # meshcode boot prints to stderr by design; combine both streams for the
118
+ # IMPORT_OK / leak markers.
119
+ combined = (result.stdout or '') + '\n' + (result.stderr or '')
120
+ assert result.returncode == 0, (
121
+ f"Module import subprocess crashed.\n"
122
+ f"--- stdout ---\n{result.stdout}\n"
123
+ f"--- stderr ---\n{result.stderr}"
124
+ )
125
+ assert 'IMPORT_OK' in combined, (
126
+ f"Module import did not complete cleanly:\n{combined}"
127
+ )
128
+ assert 'MODULE_IMPORT_RPC_LEAK' not in combined, (
129
+ f"Non-whitelisted be.sb_rpc fired at module-import time. "
130
+ f"This breaks MCP handshake on heavy-memory agents.\n{combined}"
131
+ )
132
+
133
+
134
+ def test_register_all_valid_statuses_live():
135
+ """RC #1 sentinel — mig 312. Catches SDK↔DDL enum drift like the
136
+ 'needs_launch' incident.
137
+
138
+ Live-only. Reads public.mc_agent_all_statuses() and tries each value via
139
+ mc_register_agent inside a savepoint-rolled-back transaction so no real
140
+ agents are created. Assertion: no 23514 CHECK constraint violation.
141
+ """
142
+ pat = os.environ.get('MESHCODE_SUPABASE_MGMT_PAT') or os.environ.get('SUPABASE_PAT')
143
+ project_ref = (
144
+ os.environ.get('MESHCODE_SUPABASE_PROJECT_REF')
145
+ or os.environ.get('SUPABASE_PROJECT_REF')
146
+ or 'gjinagyyjttyxnaoavnz'
147
+ )
148
+ project_id = os.environ.get('MESHCODE_PROJECT_ID')
149
+
150
+ if not (pat and project_id):
151
+ pytest.skip(
152
+ 'MESHCODE_SUPABASE_MGMT_PAT + MESHCODE_PROJECT_ID required for live '
153
+ 'register-all-statuses test'
154
+ )
155
+
156
+ import json
157
+ import urllib.request
158
+
159
+ def run_sql(sql: str) -> object:
160
+ req = urllib.request.Request(
161
+ f'https://api.supabase.com/v1/projects/{project_ref}/database/query',
162
+ data=json.dumps({'query': sql}).encode('utf-8'),
163
+ headers={
164
+ 'Authorization': f'Bearer {pat}',
165
+ 'Content-Type': 'application/json',
166
+ },
167
+ method='POST',
168
+ )
169
+ with urllib.request.urlopen(req, timeout=30) as r:
170
+ return json.loads(r.read())
171
+
172
+ rows = run_sql("SELECT unnest(public.mc_agent_all_statuses()) AS status;")
173
+ statuses = [r['status'] for r in rows if isinstance(r, dict) and 'status' in r]
174
+ assert statuses, f'public.mc_agent_all_statuses() returned no rows: {rows!r}'
175
+
176
+ failures = []
177
+ for st in statuses:
178
+ marker = f'__sentinel_{uuid.uuid4().hex[:8]}__'
179
+ sql = (
180
+ "BEGIN; "
181
+ f"INSERT INTO meshcode.mc_agents (project_id, name, role, status, "
182
+ f"registered_at, last_heartbeat) VALUES "
183
+ f"('{project_id}'::uuid, '{marker}', 'sentinel', '{st}', now(), now()) "
184
+ f"RETURNING status; "
185
+ "ROLLBACK;"
186
+ )
187
+ try:
188
+ res = run_sql(sql)
189
+ except urllib.error.HTTPError as e:
190
+ body = e.read().decode('utf-8', 'replace')
191
+ failures.append((st, f'HTTP {e.code}: {body[:200]}'))
192
+ continue
193
+
194
+ # Mgmt API returns either a list of rows or {"message": "..."} on err
195
+ if isinstance(res, dict) and 'message' in res:
196
+ msg = res['message']
197
+ # Tolerate name-format CHECK or other unrelated checks; the
198
+ # status CHECK is the one we care about.
199
+ if 'valid_agent_status' in msg or '23514' in msg and 'status' in msg:
200
+ failures.append((st, msg[:200]))
201
+
202
+ assert not failures, (
203
+ 'SDK↔DDL enum drift detected — some statuses fail valid_agent_status '
204
+ f'CHECK:\n{failures}'
205
+ )