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.
- {meshcode-2.11.6 → meshcode-2.11.7}/PKG-INFO +1 -1
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/__init__.py +1 -1
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/meshcode_mcp/server.py +223 -19
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode.egg-info/SOURCES.txt +3 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/pyproject.toml +1 -1
- meshcode-2.11.7/tests/test_boot_bug_regression.py +205 -0
- meshcode-2.11.7/tests/test_epistemic_v1_stop_conditions.py +158 -0
- meshcode-2.11.7/tests/test_install_guard.py +112 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/README.md +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/_stop_hook_template.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/cli.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/compat.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/daemon.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/error_hints.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/exceptions.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/invites.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/launcher.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/preferences.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/protocol_handler.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/quickstart.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/run_agent.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/scripts/check_secrets.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/scripts/race_rate_harness.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/secrets.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/self_update.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/supervisor.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode/upload.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/comms_v4.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_core.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/comms_v4.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_core.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/comms_v4.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/cli.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/compat.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/invites.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/meshcode/upload.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_core.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/setup.cfg +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_auto_update_hardening.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_core.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_cross_agent_messaging.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_esc_deaf_state.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_exceptions.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_lease_sigterm_release.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_mark_read_batch.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_migration_integrity.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_realtime_event_freshness.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_rls_cross_tenant.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_rpc_migrations.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_security_regressions.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_sentinel.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_sleep_signals.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_status_enum_coverage.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_stay_on_loop_hook.py +0 -0
- {meshcode-2.11.6 → meshcode-2.11.7}/tests/test_wait_open_tasks_contradiction.py +0 -0
|
@@ -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
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
)
|
|
1411
|
-
|
|
1412
|
-
|
|
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
|
|
|
@@ -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
|
|
@@ -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
|
+
)
|