swarph-cli 0.72.24__tar.gz → 0.72.25__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 (522) hide show
  1. {swarph_cli-0.72.24/src/swarph_cli.egg-info → swarph_cli-0.72.25}/PKG-INFO +1 -1
  2. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/pyproject.toml +1 -1
  3. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/__init__.py +1 -1
  4. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/operator/cli.py +16 -0
  5. {swarph_cli-0.72.24 → swarph_cli-0.72.25/src/swarph_cli.egg-info}/PKG-INFO +1 -1
  6. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli.egg-info/SOURCES.txt +3 -0
  7. swarph_cli-0.72.25/tests/test_1203_swarph_me_refusal.py +49 -0
  8. swarph_cli-0.72.25/tests/test_1214_egress.py +336 -0
  9. swarph_cli-0.72.25/tests/test_1219_egress_probe.py +109 -0
  10. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_402_one_identity_resolver.py +4 -1
  11. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_operator_me.py +8 -5
  12. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/LICENSE +0 -0
  13. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/README.md +0 -0
  14. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/setup.cfg +0 -0
  15. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/__main__.py +0 -0
  16. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/__init__.py +0 -0
  17. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/backends.py +0 -0
  18. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/claude_cli.py +0 -0
  19. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/codex_cli.py +0 -0
  20. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/pack.py +0 -0
  21. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/prices.py +0 -0
  22. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/providers.py +0 -0
  23. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/quality.py +0 -0
  24. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/refresh_prices.py +0 -0
  25. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/rules.py +0 -0
  26. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/runner.py +0 -0
  27. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/signal_score.py +0 -0
  28. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/validate.py +0 -0
  29. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/caller.py +0 -0
  30. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/capture/__init__.py +0 -0
  31. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/capture/harden.py +0 -0
  32. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/capture/lineage.py +0 -0
  33. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/capture/liveness.py +0 -0
  34. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/capture/manifest.py +0 -0
  35. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/capture/paths.py +0 -0
  36. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/capture/verify.py +0 -0
  37. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/cell.py +0 -0
  38. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/chain_token.py +0 -0
  39. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/channel.py +0 -0
  40. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/__init__.py +0 -0
  41. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/_content.py +0 -0
  42. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/_display.py +0 -0
  43. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/_gateway_client.py +0 -0
  44. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/add.py +0 -0
  45. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/bench.py +0 -0
  46. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/board.py +0 -0
  47. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/board_merge_check.py +0 -0
  48. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/brain.py +0 -0
  49. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/brain_ask.py +0 -0
  50. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/cell.py +0 -0
  51. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/cell_probe.py +0 -0
  52. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
  53. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/channel.py +0 -0
  54. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/chat.py +0 -0
  55. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/codegraph.py +0 -0
  56. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/codegraph_hook.py +0 -0
  57. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/codex_hook_output.py +0 -0
  58. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/codex_waker.py +0 -0
  59. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/compress.py +0 -0
  60. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/daemon.py +0 -0
  61. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/dreaming.py +0 -0
  62. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/event.py +0 -0
  63. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/gateway.py +0 -0
  64. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/gh_route.py +0 -0
  65. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/group.py +0 -0
  66. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/guide.py +0 -0
  67. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/highlight.py +0 -0
  68. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/hook_interpreter.py +0 -0
  69. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/hook_output.py +0 -0
  70. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/hooks.py +0 -0
  71. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/import_session.py +0 -0
  72. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/init.py +0 -0
  73. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
  74. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/install_hook.py +0 -0
  75. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  76. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
  77. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
  78. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/install_wake_hook.py +0 -0
  79. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/lane.py +0 -0
  80. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/mcp_server.py +0 -0
  81. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/memory.py +0 -0
  82. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
  83. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/memory_sync.py +0 -0
  84. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/mesh.py +0 -0
  85. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/monitor.py +0 -0
  86. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/okf_links.py +0 -0
  87. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/onboard.py +0 -0
  88. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
  89. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
  90. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/protocol_handler.py +0 -0
  91. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/ratify.py +0 -0
  92. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/roster.py +0 -0
  93. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/rowclear_hook.py +0 -0
  94. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/schedule.py +0 -0
  95. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/security.py +0 -0
  96. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/service.py +0 -0
  97. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/spawn.py +0 -0
  98. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/sweep.py +0 -0
  99. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/timeline.py +0 -0
  100. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/triage.py +0 -0
  101. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/version.py +0 -0
  102. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/wake_hook_output.py +0 -0
  103. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/watchdog.py +0 -0
  104. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/compress/__init__.py +0 -0
  105. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/compress/levers.py +0 -0
  106. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/compress/marker.py +0 -0
  107. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/compress/verify.py +0 -0
  108. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/console_safe.py +0 -0
  109. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/delivery_queue.py +0 -0
  110. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dm_frame.py +0 -0
  111. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/__init__.py +0 -0
  112. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/candidates.py +0 -0
  113. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/clone.py +0 -0
  114. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/enrich.py +0 -0
  115. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/index_check.py +0 -0
  116. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/notify.py +0 -0
  117. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/organize.py +0 -0
  118. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/probes.py +0 -0
  119. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/report.py +0 -0
  120. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/run.py +0 -0
  121. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/slm.py +0 -0
  122. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/transcripts.py +0 -0
  123. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/verify.py +0 -0
  124. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/gateway/__init__.py +0 -0
  125. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/gateway/feature_registry.py +0 -0
  126. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/gateway/lanes_control.py +0 -0
  127. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/gateway/schema.sql +0 -0
  128. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/gateway/server.py +0 -0
  129. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/gateway/services_control.py +0 -0
  130. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/gateway_default.py +0 -0
  131. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/gh_identity.py +0 -0
  132. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/guide/GUIDE.md +0 -0
  133. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/identity.py +0 -0
  134. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/launchd/swarph-codex-outbox.plist +0 -0
  135. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/launchd/swarph-codex-outbox.timer.plist +0 -0
  136. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/launchd/swarph-monitor.plist +0 -0
  137. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/launchd/swarph-watchdog.plist +0 -0
  138. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/launchd/swarph-watchdog.timer.plist +0 -0
  139. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/launchd_render.py +0 -0
  140. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/main.py +0 -0
  141. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/multiplexer.py +0 -0
  142. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/operator/__init__.py +0 -0
  143. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/operator/__main__.py +0 -0
  144. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/operator/api.py +0 -0
  145. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/operator/config.py +0 -0
  146. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/operator/fmt.py +0 -0
  147. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/orphan_daemons.py +0 -0
  148. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/pane_probe.py +0 -0
  149. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/parsers/__init__.py +0 -0
  150. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/parsers/claude.py +0 -0
  151. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
  152. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
  153. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
  154. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
  155. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
  156. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
  157. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
  158. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
  159. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
  160. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
  161. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
  162. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
  163. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/peer_executor.py +0 -0
  164. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/peer_executor_windows.py +0 -0
  165. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/peer_reconciliation.py +0 -0
  166. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/peer_reply_outbox.py +0 -0
  167. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/peer_service_host.py +0 -0
  168. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/peer_service_payload.py +0 -0
  169. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/peer_service_reply.py +0 -0
  170. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/peer_staging.py +0 -0
  171. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/query_filters.py +0 -0
  172. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/quota_sync.py +0 -0
  173. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/rowclear.py +0 -0
  174. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/scripts/__init__.py +0 -0
  175. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
  176. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
  177. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
  178. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
  179. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
  180. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/scrub.py +0 -0
  181. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/service/__init__.py +0 -0
  182. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/service/app.py +0 -0
  183. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/service/providers.py +0 -0
  184. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/session_bridge.py +0 -0
  185. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
  186. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
  187. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/source_text.py +0 -0
  188. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/stall_alert.py +0 -0
  189. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/codex-waker.default +0 -0
  190. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
  191. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
  192. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
  193. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
  194. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
  195. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
  196. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
  197. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
  198. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
  199. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
  200. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-stuck-sweep.service +0 -0
  201. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-stuck-sweep.timer +0 -0
  202. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-triage.service +0 -0
  203. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-triage.timer +0 -0
  204. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  205. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  206. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  207. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/timeline_paths.py +0 -0
  208. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/tokens.py +0 -0
  209. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/triage/__init__.py +0 -0
  210. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/triage/backend.py +0 -0
  211. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/triage/job.py +0 -0
  212. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/triage/owners.json +0 -0
  213. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/triage/router.py +0 -0
  214. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/triage/rows.py +0 -0
  215. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/triage/sensor.py +0 -0
  216. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  217. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  218. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli.egg-info/requires.txt +0 -0
  219. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli.egg-info/top_level.txt +0 -0
  220. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1006_board_publisher.py +0 -0
  221. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1006_session_board.py +0 -0
  222. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1010_strand_only.py +0 -0
  223. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1013_spawn_channel.py +0 -0
  224. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1017_muse_tui.py +0 -0
  225. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1050_surrogate_wake.py +0 -0
  226. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1052_opencode_deaf.py +0 -0
  227. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1059_watchdog_unread_env.py +0 -0
  228. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1061_roster_cli.py +0 -0
  229. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1064_stuck_sweep.py +0 -0
  230. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1065_release.py +0 -0
  231. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1066_quiet_wakes.py +0 -0
  232. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1068_structured_accept_cli.py +0 -0
  233. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1071_quota_sync.py +0 -0
  234. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1073_triage.py +0 -0
  235. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1073_triage_board.py +0 -0
  236. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1073_wheel_install.py +0 -0
  237. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1075_task_cli.py +0 -0
  238. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1083_openai_decisions.py +0 -0
  239. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1144_cell_yaml_github.py +0 -0
  240. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1197_commander_taps.py +0 -0
  241. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1206_offer_question.py +0 -0
  242. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_124_register_merge.py +0 -0
  243. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_126_pending_from_complete.py +0 -0
  244. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_132_ensure_monitor_state_dir.py +0 -0
  245. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_138_memory_navigate.py +0 -0
  246. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_139_tool_probe.py +0 -0
  247. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_183_card_anchor.py +0 -0
  248. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_184_notify_honesty.py +0 -0
  249. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_201_win32_bash_refusal.py +0 -0
  250. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_243_onboard_per_peer_token.py +0 -0
  251. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_307_task4_ask_reply.py +0 -0
  252. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
  253. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_319_refusal_exit_codes.py +0 -0
  254. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_323_mistral_backend.py +0 -0
  255. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_332_report_organize_findings.py +0 -0
  256. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_356_query_filters.py +0 -0
  257. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_397_gh_identity_router.py +0 -0
  258. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_397_router_preflight.py +0 -0
  259. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_432_rowclear.py +0 -0
  260. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_467b_onboard_operator_credential.py +0 -0
  261. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_472_register_caller_binding.py +0 -0
  262. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_484_codex_queue_sink.py +0 -0
  263. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_523_guide.py +0 -0
  264. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_524_conftest_hermeticity.py +0 -0
  265. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_532_accept_flag.py +0 -0
  266. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_535_version_capability.py +0 -0
  267. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_538_signal_bench.py +0 -0
  268. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_546_default_gateway.py +0 -0
  269. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_547_verbs_from_registry.py +0 -0
  270. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_548_gateway_remainder.py +0 -0
  271. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_552_codegraph_gateway.py +0 -0
  272. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_555_relay_row_shape.py +0 -0
  273. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_556_relay_freshness.py +0 -0
  274. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_559_edges_truncated.py +0 -0
  275. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_564_bootstrap_first_peer.py +0 -0
  276. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
  277. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_579_register_bootstrap_message.py +0 -0
  278. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_591_cli_step_graph.py +0 -0
  279. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_644_monitor_supervision.py +0 -0
  280. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_648_install_unit.py +0 -0
  281. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_654_reexec.py +0 -0
  282. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_656_owned_slm.py +0 -0
  283. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_684_dreaming_verb.py +0 -0
  284. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_689_pkg_version_order.py +0 -0
  285. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_715_grok_wake.py +0 -0
  286. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_716_timeline_one_path.py +0 -0
  287. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_723_grok_0251.py +0 -0
  288. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_724_ps1_ascii_or_bom.py +0 -0
  289. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_725_stdio_reconfigure.py +0 -0
  290. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_726_grok_limit_screen.py +0 -0
  291. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_729_arm_check_delivery_gap.py +0 -0
  292. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_729_dm_once.py +0 -0
  293. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_729_wake_watchdog.py +0 -0
  294. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_734_dreaming_enrich_degrade.py +0 -0
  295. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_735_dm_mcp_tools.py +0 -0
  296. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_739_wake_writer.py +0 -0
  297. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_740_edit_project.py +0 -0
  298. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_762_cards_edit_priority.py +0 -0
  299. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_764_enrich_collapse.py +0 -0
  300. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_801_confirm_flow_cli.py +0 -0
  301. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_802_stage_history.py +0 -0
  302. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_825_codegraph_hook_noise.py +0 -0
  303. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_825_install_posttooluse_only.py +0 -0
  304. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_825_remaining_cap_shred_bind.py +0 -0
  305. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_829_counterfactual_column.py +0 -0
  306. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_830_hook_binding_version.py +0 -0
  307. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_84_cli_labels.py +0 -0
  308. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_866_doctrine_guide.py +0 -0
  309. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_872_identity_refuses_not_warns.py +0 -0
  310. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_874_source_text.py +0 -0
  311. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
  312. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_937_dreaming_notify.py +0 -0
  313. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_938_organize_line_limit.py +0 -0
  314. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_941_bench_backends.py +0 -0
  315. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_941_harness_342.py +0 -0
  316. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_945_same_line_pairing.py +0 -0
  317. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_955_prompt_audit.py +0 -0
  318. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_957_auth_http.py +0 -0
  319. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_959_venv_interpreter.py +0 -0
  320. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_960_channel.py +0 -0
  321. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_960_ensure_monitor_heartbeat.py +0 -0
  322. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_961_cursor_grok_footer.py +0 -0
  323. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_961_loud_stop.py +0 -0
  324. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_963_plugin_manifest.py +0 -0
  325. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_964_channel_cell.py +0 -0
  326. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_965_mesh_wait.py +0 -0
  327. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_975_channel_hop.py +0 -0
  328. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_add_security_gate.py +0 -0
  329. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_artifact_add.py +0 -0
  330. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_artifact_hash.py +0 -0
  331. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_artifact_lib.py +0 -0
  332. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_artifact_mcp.py +0 -0
  333. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_artifact_skill.py +0 -0
  334. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_artifact_tool.py +0 -0
  335. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_artifact_uri.py +0 -0
  336. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_bench_backends.py +0 -0
  337. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_bench_claude_cli.py +0 -0
  338. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_bench_codex_cli.py +0 -0
  339. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_bench_command.py +0 -0
  340. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_bench_pack.py +0 -0
  341. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_bench_prices.py +0 -0
  342. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_bench_quality.py +0 -0
  343. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_bench_runner.py +0 -0
  344. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_bench_validate.py +0 -0
  345. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_board_card_thread.py +0 -0
  346. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_board_command.py +0 -0
  347. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_board_merge_check.py +0 -0
  348. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_board_obligation_close.py +0 -0
  349. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_brain_ask_command.py +0 -0
  350. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_brain_ask_env_diagnosis.py +0 -0
  351. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_brain_ask_identity_explicitness.py +0 -0
  352. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_brain_command.py +0 -0
  353. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_caller_meta_guard.py +0 -0
  354. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_capture_lineage.py +0 -0
  355. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_capture_liveness.py +0 -0
  356. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_capture_manifest.py +0 -0
  357. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_capture_paths.py +0 -0
  358. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_capture_security.py +0 -0
  359. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_cell_command_dispatch.py +0 -0
  360. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_cell_harden.py +0 -0
  361. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_cell_loader.py +0 -0
  362. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_cell_selfcheck.py +0 -0
  363. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_cell_verify.py +0 -0
  364. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_chain_token.py +0 -0
  365. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_channel_command.py +0 -0
  366. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_channel_poll_never_breaks_dms.py +0 -0
  367. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_channel_post.py +0 -0
  368. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_channel_wake_policy_filter.py +0 -0
  369. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_chat_command.py +0 -0
  370. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_claude_parser.py +0 -0
  371. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_claude_tmux_template.py +0 -0
  372. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_codegraph_caller_scope.py +0 -0
  373. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_codegraph_command.py +0 -0
  374. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_codegraph_hook.py +0 -0
  375. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_codegraph_mcp.py +0 -0
  376. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_codex_waker.py +0 -0
  377. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_compress_caller_convention.py +0 -0
  378. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_compress_command.py +0 -0
  379. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_compress_levers.py +0 -0
  380. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_compress_marker.py +0 -0
  381. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_compress_verify.py +0 -0
  382. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_console_encoding_e2e.py +0 -0
  383. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_content_composition.py +0 -0
  384. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_copilot_membrane.py +0 -0
  385. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_copilot_pane_reader.py +0 -0
  386. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_cursor_membrane.py +0 -0
  387. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_daemon_command.py +0 -0
  388. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_daemon_console_encoding.py +0 -0
  389. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_daemon_delivery.py +0 -0
  390. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_delivery_queue.py +0 -0
  391. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_display_sanitize.py +0 -0
  392. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_ensure_monitor_no_peer_default.py +0 -0
  393. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_event_emit.py +0 -0
  394. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_feature_to_uri.py +0 -0
  395. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_gateway_brain.py +0 -0
  396. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_gateway_client.py +0 -0
  397. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_gateway_command.py +0 -0
  398. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_gateway_message_idempotency.py +0 -0
  399. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_gh_route_doctor_mesh_name.py +0 -0
  400. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_group_command.py +0 -0
  401. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_guide_commands_parse.py +0 -0
  402. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_guide_selfcheck.py +0 -0
  403. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_heartbeat_check.py +0 -0
  404. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_highlight_command.py +0 -0
  405. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hook_output.py +0 -0
  406. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_activity_marker.py +0 -0
  407. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_add.py +0 -0
  408. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_bundle.py +0 -0
  409. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_init.py +0 -0
  410. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_legacy_generation_removal.py +0 -0
  411. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_lifecycle.py +0 -0
  412. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_merge.py +0 -0
  413. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_verify.py +0 -0
  414. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_win32_bash_wrapper.py +0 -0
  415. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_win32_command_path.py +0 -0
  416. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_import_command.py +0 -0
  417. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_inbox_identity_provenance.py +0 -0
  418. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_init_command.py +0 -0
  419. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_init_provider_menu.py +0 -0
  420. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_install_codex_hooks.py +0 -0
  421. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_install_hook.py +0 -0
  422. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_install_multiplexer_command.py +0 -0
  423. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_install_postcompact_hook.py +0 -0
  424. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_install_wake_hook.py +0 -0
  425. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_lane_command.py +0 -0
  426. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_launchd_units.py +0 -0
  427. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_main.py +0 -0
  428. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_main_unknown_verb_guard.py +0 -0
  429. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mcp_missing_hint.py +0 -0
  430. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mcp_server.py +0 -0
  431. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_memory_command.py +0 -0
  432. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_memory_emit_snippet.py +0 -0
  433. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_memory_gateway.py +0 -0
  434. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_memory_sync.py +0 -0
  435. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_memory_sync_membrane.py +0 -0
  436. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mesh_command.py +0 -0
  437. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mesh_inbox_mark_read.py +0 -0
  438. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mesh_inbox_sanitize.py +0 -0
  439. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mesh_originate_thread_623.py +0 -0
  440. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mesh_send_unknown_peer.py +0 -0
  441. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mesh_sidecar.py +0 -0
  442. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
  443. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mesh_sidecar_muse_sink.py +0 -0
  444. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
  445. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_meta_edge_identity.py +0 -0
  446. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_monitor_channel_poll.py +0 -0
  447. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_monitor_cli.py +0 -0
  448. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_monitor_daemon_collision.py +0 -0
  449. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_monitor_self_name_verification.py +0 -0
  450. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_monitor_state_split.py +0 -0
  451. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_multiplexer.py +0 -0
  452. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_muse_membrane.py +0 -0
  453. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_muse_own_cli.py +0 -0
  454. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_obligation_close_is_surfaced.py +0 -0
  455. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_okf_links.py +0 -0
  456. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_onboard_command.py +0 -0
  457. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_opencode_membrane.py +0 -0
  458. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_opencode_plugin.py +0 -0
  459. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_orphan_daemons.py +0 -0
  460. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_packaged_artifacts_exist.py +0 -0
  461. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_packaged_skills.py +0 -0
  462. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_executor.py +0 -0
  463. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_executor_windows.py +0 -0
  464. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_reply_drain_command.py +0 -0
  465. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_reply_drain_supervisor.py +0 -0
  466. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_reply_outbox.py +0 -0
  467. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_reply_transport.py +0 -0
  468. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_service_e2e.py +0 -0
  469. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_service_host.py +0 -0
  470. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_service_payload.py +0 -0
  471. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_service_reply.py +0 -0
  472. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_postcompact_recall.py +0 -0
  473. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_process_liveness.py +0 -0
  474. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_protocol_handler.py +0 -0
  475. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_providers_isolation.py +0 -0
  476. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_ratify_command.py +0 -0
  477. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_readme_claims_no_gateway_default.py +0 -0
  478. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_repl_caller_convention.py +0 -0
  479. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_schedule_command.py +0 -0
  480. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_schedule_create_contract.py +0 -0
  481. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_scrub.py +0 -0
  482. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_security.py +0 -0
  483. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_service_app.py +0 -0
  484. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_service_command.py +0 -0
  485. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_service_providers.py +0 -0
  486. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_session_bridge_inject.py +0 -0
  487. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_session_bridge_probe.py +0 -0
  488. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_session_bridge_resolve.py +0 -0
  489. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_session_path_role_gate.py +0 -0
  490. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_shipped_units_match_the_cli.py +0 -0
  491. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_smoke_chat.py +0 -0
  492. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_smoke_one_shot.py +0 -0
  493. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_smoke_phase_5_5.py +0 -0
  494. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_spawn_base_hoist.py +0 -0
  495. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_spawn_command.py +0 -0
  496. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_spawn_identity_stamp.py +0 -0
  497. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_spawn_live_pin.py +0 -0
  498. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_spawn_mitosis_lineage.py +0 -0
  499. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_spawn_tmux_session.py +0 -0
  500. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_spawn_windows_relaunch.py +0 -0
  501. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_stall_alert.py +0 -0
  502. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_subprocess_decode_encoding.py +0 -0
  503. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_timeline_command.py +0 -0
  504. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_tmux_wake_runstate_defer.py +0 -0
  505. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_tmux_wake_session_reanchor.py +0 -0
  506. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_tmux_wake_stale.py +0 -0
  507. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_tmux_wake_submit_verify.py +0 -0
  508. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_token_file_one_parser.py +0 -0
  509. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_token_precedence_explicit_first.py +0 -0
  510. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_unified_token_resolver.py +0 -0
  511. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_version_command.py +0 -0
  512. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_version_consistency.py +0 -0
  513. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_vibe_membrane.py +0 -0
  514. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_watchdog.py +0 -0
  515. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_watchdog_dm_wake.py +0 -0
  516. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  517. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  518. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_watchdog_emit_health.py +0 -0
  519. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_watchdog_install_flags.py +0 -0
  520. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_watchdog_model_rung.py +0 -0
  521. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_watchdog_stale_peers.py +0 -0
  522. {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_windows_waker_installer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.72.24
3
+ Version: 0.72.25
4
4
  Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
5
5
  Author: Pierre Samson, Claude Opus
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "swarph-cli"
7
- version = "0.72.24"
7
+ version = "0.72.25"
8
8
  description = "The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -16,6 +16,6 @@ The architecture splits CLI from substrate so:
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- __version__ = "0.72.24"
19
+ __version__ = "0.72.25"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -8,6 +8,7 @@ from __future__ import annotations
8
8
 
9
9
  import argparse
10
10
  import json
11
+ import os
11
12
  import sys
12
13
  from pathlib import Path
13
14
  from typing import Sequence
@@ -482,6 +483,18 @@ def main(argv: Sequence[str] | None = None) -> int:
482
483
  print(HELP)
483
484
  return 0
484
485
 
486
+ # card #1203: inside a cell (SWARPH_SELF set), refuse BEFORE any token
487
+ # is read -- a cell must not act as the commander by accident. The
488
+ # explicit --as-operator hatch overrides, loudly (both names on stderr).
489
+ as_operator = "--as-operator" in stripped
490
+ if as_operator:
491
+ stripped = [a for a in stripped if a != "--as-operator"]
492
+ cell_self = (os.environ.get("SWARPH_SELF") or "").strip()
493
+ if cell_self and not as_operator:
494
+ print(f"SWARPH_SELF={cell_self}: this process is inside a cell; "
495
+ "run swarph-me from a human shell", file=sys.stderr)
496
+ return 2
497
+
485
498
  try:
486
499
  identity, gateway, token = resolve_session(as_name, config_path=config_path)
487
500
  except SystemExit as exc:
@@ -490,6 +503,9 @@ def main(argv: Sequence[str] | None = None) -> int:
490
503
  except ValueError as exc:
491
504
  print(f"swarph-me: {exc}", file=sys.stderr)
492
505
  return 2
506
+ if cell_self and as_operator:
507
+ print(f"SWARPH_SELF={cell_self}: --as-operator override; "
508
+ f"acting as operator {identity}", file=sys.stderr)
493
509
 
494
510
  try:
495
511
  return run_verb(identity, gateway, token, stripped)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.72.24
3
+ Version: 0.72.25
4
4
  Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
5
5
  Author: Pierre Samson, Claude Opus
6
6
  License: MIT
@@ -231,7 +231,10 @@ tests/test_1075_task_cli.py
231
231
  tests/test_1083_openai_decisions.py
232
232
  tests/test_1144_cell_yaml_github.py
233
233
  tests/test_1197_commander_taps.py
234
+ tests/test_1203_swarph_me_refusal.py
234
235
  tests/test_1206_offer_question.py
236
+ tests/test_1214_egress.py
237
+ tests/test_1219_egress_probe.py
235
238
  tests/test_124_register_merge.py
236
239
  tests/test_126_pending_from_complete.py
237
240
  tests/test_132_ensure_monitor_state_dir.py
@@ -0,0 +1,49 @@
1
+ """card #1203 — swarph-me REFUSES when SWARPH_SELF is set (before any token
2
+ is read), with an explicit, loud --as-operator escape hatch."""
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
8
+
9
+ from swarph_cli.operator import cli # noqa: E402
10
+ from swarph_cli.operator import config as op_config # noqa: E402
11
+
12
+
13
+ def _cfg(tmp_path):
14
+ token = tmp_path / "commander.peer_token"
15
+ token.write_text("test-token\n", encoding="utf-8")
16
+ cfg = tmp_path / "operator.json"
17
+ op_config.save_config(identity="commander", gateway="http://example.test:8788",
18
+ token_file=str(token), path=cfg)
19
+ return cfg
20
+
21
+
22
+ def test_self_set_refuses_and_no_token_opened(tmp_path, monkeypatch, capsys):
23
+ monkeypatch.setenv("SWARPH_SELF", "cursor-win")
24
+ monkeypatch.setattr(cli, "_read_token",
25
+ lambda *a, **k: (_ for _ in ()).throw(
26
+ AssertionError("token was read before refusal")))
27
+ rc = cli.main(["cards", "--config", str(_cfg(tmp_path))])
28
+ assert rc != 0
29
+ err = capsys.readouterr().err
30
+ assert "SWARPH_SELF=cursor-win" in err
31
+ assert "this process is inside a cell" in err
32
+ assert "run swarph-me from a human shell" in err
33
+
34
+
35
+ def test_as_operator_proceeds_and_stderr_names_both(tmp_path, monkeypatch, capsys):
36
+ monkeypatch.setenv("SWARPH_SELF", "cursor-win")
37
+ monkeypatch.setattr(cli, "run_verb", lambda identity, gateway, token, rest: 0)
38
+ rc = cli.main(["cards", "--as-operator", "--config", str(_cfg(tmp_path))])
39
+ assert rc == 0
40
+ err = capsys.readouterr().err
41
+ assert "cursor-win" in err and "commander" in err
42
+
43
+
44
+ def test_self_unset_behaviour_unchanged(tmp_path, monkeypatch, capsys):
45
+ monkeypatch.delenv("SWARPH_SELF", raising=False)
46
+ monkeypatch.setattr(cli, "run_verb", lambda identity, gateway, token, rest: 0)
47
+ rc = cli.main(["cards", "--config", str(_cfg(tmp_path))])
48
+ assert rc == 0
49
+ assert "inside a cell" not in capsys.readouterr().err
@@ -0,0 +1,336 @@
1
+ """card #1214 — egress inventory: the build fails when code gains a
2
+ network call that EGRESS.md does not list.
3
+
4
+ Egress-inventory test pattern after XERJ (github.com/xerj-org/xerj, Apache-2.0),
5
+ engine/crates/xerj-rerank/tests/egress_inventory.rs. Idea only; no code copied.
6
+
7
+ Walks src/swarph_cli with the AST for network clients (requests, httpx,
8
+ urllib.request, http.client, socket connections, aiohttp, websockets, and
9
+ subprocess argv starting curl/wget/ssh/scp). Every hit must map to an
10
+ EGRESS.md row keyed by (file, client), or this test fails naming file:line.
11
+ """
12
+ import ast
13
+ import os
14
+ from pathlib import Path
15
+
16
+ ROOT = Path(__file__).resolve().parent.parent
17
+ SRC = ROOT / "src" / "swarph_cli"
18
+ EGRESS = ROOT / "EGRESS.md"
19
+
20
+ _MODULE_KINDS = {
21
+ "requests": "requests", "httpx": "httpx", "aiohttp": "aiohttp",
22
+ "websockets": "websockets", "http.client": "http.client",
23
+ }
24
+ _HTTP_ATTRS = {"get", "post", "put", "delete", "head", "patch", "request"}
25
+ _SUBPROCESS_TOOLS = {"curl", "wget", "ssh", "scp"}
26
+ _CONSTRUCTOR_MODULE = {
27
+ "Session": "requests", "Client": "httpx",
28
+ "AsyncClient": "httpx", "ClientSession": "aiohttp",
29
+ }
30
+
31
+
32
+ def _constructor_kind(dotted, bindings, fn_bindings):
33
+ """Client kind for a session/client construction call, in either
34
+ from-import form (Session()) or attribute form (requests.Session()),
35
+ else None."""
36
+ if not dotted:
37
+ return None
38
+ attr = dotted.rsplit(".", 1)[-1]
39
+ module = _CONSTRUCTOR_MODULE.get(attr)
40
+ if module is None:
41
+ return None
42
+ if "." not in dotted: # bare Name: Session()
43
+ return f"{module}.{attr}"
44
+ root = dotted.split(".")[0]
45
+ if bindings.get(root) == module:
46
+ return f"{module}.{attr}"
47
+ return None
48
+
49
+
50
+ def _dotted(node):
51
+ parts = []
52
+ while isinstance(node, ast.Attribute):
53
+ parts.append(node.attr)
54
+ node = node.value
55
+ if isinstance(node, ast.Name):
56
+ parts.append(node.id)
57
+ return ".".join(reversed(parts))
58
+
59
+
60
+ def _host_of(text: str):
61
+ import re as _re
62
+ m = _re.search(r"://([^/\"'\s]+)", text or "")
63
+ return m.group(1) if m else None
64
+
65
+
66
+ def scan(root: Path):
67
+ """Return [(relpath, line, client, host)] for every network call under
68
+ root. host is the literal destination host when the call (or a module
69
+ constant it references) names one, else None (dynamic/configured)."""
70
+ hits = []
71
+ for path in sorted(root.rglob("*.py")):
72
+ if "__pycache__" in path.parts:
73
+ continue
74
+ rel = path.relative_to(root.parent.parent).as_posix() \
75
+ if root == SRC else path.relative_to(root).as_posix()
76
+ tree = ast.parse(path.read_text(encoding="utf-8-sig"))
77
+ bindings = {} # local name -> module kind
78
+ fn_bindings = {} # local name -> client kind (from-imported callables)
79
+ consts = {} # module-level string constants
80
+ opener_vars = set()
81
+ for node in ast.walk(tree):
82
+ if isinstance(node, ast.Assign) and isinstance(node.value, ast.Constant) \
83
+ and isinstance(node.value.value, str):
84
+ for t in node.targets:
85
+ if isinstance(t, ast.Name):
86
+ consts[t.id] = node.value.value
87
+ for node in ast.walk(tree):
88
+ if isinstance(node, ast.Import):
89
+ for a in node.names:
90
+ kind = _MODULE_KINDS.get(a.name) or (
91
+ "urllib.request" if a.name.startswith("urllib") else
92
+ "socket" if a.name == "socket" else None)
93
+ if kind:
94
+ bindings[a.asname or a.name.split(".")[0]] = kind
95
+ elif isinstance(node, ast.ImportFrom) and node.module:
96
+ if node.module.startswith("urllib.request"):
97
+ for a in node.names:
98
+ if a.name in ("urlopen", "Request"):
99
+ fn_bindings[a.asname or a.name] = "urllib.request.urlopen"
100
+ elif a.name == "build_opener":
101
+ fn_bindings[a.asname or a.name] = "urllib.request.build_opener"
102
+ elif node.module == "urllib" :
103
+ for a in node.names:
104
+ if a.name == "request":
105
+ bindings[a.asname or a.name] = "urllib.request"
106
+ elif node.module == "socket":
107
+ for a in node.names:
108
+ if a.name in ("socket", "create_connection"):
109
+ fn_bindings[a.asname or a.name] = f"socket.{a.name}"
110
+ elif node.module in _MODULE_KINDS:
111
+ for a in node.names:
112
+ fn_bindings[a.asname or a.name] = _MODULE_KINDS[node.module]
113
+ elif isinstance(node, ast.Assign) and isinstance(node.value, ast.Call):
114
+ if _dotted(node.value.func).endswith("build_opener"):
115
+ for t in node.targets:
116
+ if isinstance(t, ast.Name):
117
+ opener_vars.add(t.id)
118
+ var_kinds = {} # session variable name -> client kind
119
+ for node in ast.walk(tree):
120
+ value = None
121
+ targets = []
122
+ if isinstance(node, ast.Assign) and isinstance(node.value, ast.Call):
123
+ value, targets = node.value, [t for t in node.targets
124
+ if isinstance(t, ast.Name)]
125
+ elif isinstance(node, (ast.With, ast.AsyncWith)):
126
+ for item in node.items:
127
+ if isinstance(item.context_expr, ast.Call) \
128
+ and isinstance(item.optional_vars, ast.Name):
129
+ value, targets = item.context_expr, [item.optional_vars]
130
+ if value is not None:
131
+ ck = _constructor_kind(_dotted(value.func), bindings, fn_bindings)
132
+ if ck:
133
+ for t in targets:
134
+ var_kinds[t.id] = ck
135
+ for node in ast.walk(tree):
136
+ if not isinstance(node, ast.Call):
137
+ continue
138
+ dotted = _dotted(node.func)
139
+ root_name = dotted.split(".")[0] if dotted else ""
140
+ attr = dotted.rsplit(".", 1)[-1] if dotted else ""
141
+ kind = None
142
+ if isinstance(node.func, ast.Name) and node.func.id in fn_bindings:
143
+ kind = fn_bindings[node.func.id]
144
+ elif isinstance(node.func, ast.Name) and node.func.id in opener_vars:
145
+ kind = "urllib.request.urlopen" # opener(...) call
146
+ elif isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Call) \
147
+ and _dotted(node.func.value.func).endswith("build_opener") \
148
+ and attr == "open":
149
+ kind = "urllib.request.urlopen" # build_opener(...).open(...)
150
+ elif isinstance(node.func, ast.Attribute) and isinstance(node.func.value, ast.Name) \
151
+ and node.func.value.id in opener_vars and attr == "open":
152
+ kind = "urllib.request.urlopen" # opener.open(...)
153
+ elif _constructor_kind(dotted, bindings, fn_bindings):
154
+ kind = _constructor_kind(dotted, bindings, fn_bindings)
155
+ elif isinstance(node.func, ast.Attribute) \
156
+ and isinstance(node.func.value, ast.Call) \
157
+ and _constructor_kind(_dotted(node.func.value.func),
158
+ bindings, fn_bindings):
159
+ # requests.Session().post(u): call on a fresh construction
160
+ kind = _constructor_kind(_dotted(node.func.value.func),
161
+ bindings, fn_bindings)
162
+ elif isinstance(node.func, ast.Attribute) \
163
+ and isinstance(node.func.value, ast.Name) \
164
+ and node.func.value.id in var_kinds \
165
+ and attr in _HTTP_ATTRS:
166
+ # s = requests.Session(); s.post(u)
167
+ kind = var_kinds[node.func.value.id]
168
+ elif dotted.endswith(".urlopen") and bindings.get(root_name) == "urllib.request":
169
+ kind = "urllib.request.urlopen"
170
+ elif dotted.endswith(".HTTPConnection") or dotted.endswith(".HTTPSConnection"):
171
+ if bindings.get(root_name) == "http.client" or root_name == "http":
172
+ kind = "http.client"
173
+ elif dotted in ("socket.socket", "socket.create_connection") \
174
+ and bindings.get(root_name) == "socket":
175
+ kind = dotted
176
+ elif isinstance(node.func, ast.Name) and node.func.id in (
177
+ "Session", "Client", "AsyncClient", "ClientSession"):
178
+ base = {"Session": "requests", "Client": "httpx",
179
+ "AsyncClient": "httpx", "ClientSession": "aiohttp"}[node.func.id]
180
+ kind = f"{base}.{node.func.id}"
181
+ elif dotted == "websockets.connect" and bindings.get(root_name) == "websockets":
182
+ kind = "websockets.connect"
183
+ elif bindings.get(root_name) in ("requests", "httpx", "aiohttp", "websockets") \
184
+ and attr in _HTTP_ATTRS:
185
+ kind = f"{bindings[root_name]}.{attr}"
186
+ elif dotted.split(".")[0] == "subprocess" or (
187
+ isinstance(node.func, ast.Name)
188
+ and node.func.id in ("run", "Popen", "call", "check_output", "check_call")):
189
+ if node.args:
190
+ first = node.args[0]
191
+ tool = None
192
+ if isinstance(first, (ast.List, ast.Tuple)) and first.elts \
193
+ and isinstance(first.elts[0], ast.Constant):
194
+ tool = str(first.elts[0].value).split()[0] if first.elts[0].value else None
195
+ elif isinstance(first, ast.Constant) and isinstance(first.value, str):
196
+ tool = first.value.split()[0] if first.value.split() else None
197
+ if tool in _SUBPROCESS_TOOLS:
198
+ kind = f"subprocess:{tool}"
199
+ if kind:
200
+ host = None
201
+ for sub in ast.walk(node):
202
+ if isinstance(sub, ast.Constant) and isinstance(sub.value, str):
203
+ host = _host_of(sub.value)
204
+ elif isinstance(sub, ast.Name) and sub.id in consts:
205
+ host = _host_of(consts[sub.id])
206
+ if host:
207
+ break
208
+ hits.append((rel, node.lineno, kind, host))
209
+ return hits
210
+
211
+
212
+ def _egress_rows(text: str):
213
+ """Rows keyed (file, client) -> destination text. A hit with a literal
214
+ host is mapped only when its row's destination names that host (M2: a
215
+ new destination in a listed file fails)."""
216
+ rows = {}
217
+ for line in text.splitlines():
218
+ line = line.strip()
219
+ if not line.startswith("|") or "---" in line:
220
+ continue
221
+ cells = [c.strip() for c in line.strip("|").split("|")]
222
+ if len(cells) >= 3 and cells[0].endswith(".py"):
223
+ rows.setdefault((cells[0], cells[1]), []).append(cells[2])
224
+ return rows
225
+
226
+
227
+ def unmapped(hits, rows):
228
+ out = []
229
+ for rel, ln, kind, host in hits:
230
+ dests = rows.get((rel, kind))
231
+ if dests is None:
232
+ out.append((rel, ln, kind, host))
233
+ elif host is not None and not any(host in d for d in dests):
234
+ out.append((rel, ln, kind, host))
235
+ return out
236
+
237
+
238
+ def test_every_network_call_is_listed_in_egress_md():
239
+ hits = scan(SRC)
240
+ assert hits, "scanner found no network calls — the walk is broken"
241
+ rows = _egress_rows(EGRESS.read_text(encoding="utf-8"))
242
+ bad = unmapped(hits, rows)
243
+ assert not bad, "unlisted network calls (add an EGRESS.md row): " + \
244
+ ", ".join(f"{f}:{ln} ({k} -> {h})" for f, ln, k, h in bad)
245
+
246
+
247
+ def test_mutant_unlisted_requests_get_fails(tmp_path):
248
+ """Mutant check: an unlisted requests.get in a scratch copy must fail."""
249
+ pkg = tmp_path / "swarph_cli"
250
+ pkg.mkdir()
251
+ (pkg / "mod.py").write_text(
252
+ "import requests\n\ndef go():\n return requests.get('https://example.invalid')\n")
253
+ hits = scan(pkg)
254
+ assert hits and hits[0][2] == "requests.get"
255
+ assert unmapped(hits, {}) == hits
256
+ assert unmapped(hits, {("mod.py", "requests.get"): ["example.invalid"]}) == []
257
+
258
+
259
+ _MUTANTS = {
260
+ # M2: a NEW destination in a file that is already listed
261
+ "M2_new_destination_in_listed_file": (
262
+ "src/swarph_cli/commands/version.py",
263
+ "import urllib.request\n\ndef go():\n"
264
+ " return urllib.request.urlopen('https://evil.example/x')\n"),
265
+ "M3_http_client": (
266
+ "src/swarph_cli/mod.py",
267
+ "import http.client\n\ndef go():\n"
268
+ " return http.client.HTTPSConnection('example.invalid')\n"),
269
+ "M4_requests_Session": (
270
+ "src/swarph_cli/mod.py",
271
+ "from requests import Session\n\ndef go():\n return Session()\n"),
272
+ "M5_from_urllib_import_request": (
273
+ "src/swarph_cli/mod.py",
274
+ "from urllib import request\n\ndef go():\n"
275
+ " return request.urlopen('https://example.invalid')\n"),
276
+ "M6_build_opener_open": (
277
+ "src/swarph_cli/mod.py",
278
+ "import urllib.request\n\ndef go():\n"
279
+ " return urllib.request.build_opener().open('https://example.invalid')\n"),
280
+ "M7_httpx_Client": (
281
+ "src/swarph_cli/mod.py",
282
+ "from httpx import Client\n\ndef go():\n return Client()\n"),
283
+ "M8_aiohttp_ClientSession": (
284
+ "src/swarph_cli/mod.py",
285
+ "from aiohttp import ClientSession\n\ndef go():\n return ClientSession()\n"),
286
+ "M13_websockets_connect": (
287
+ "src/swarph_cli/mod.py",
288
+ "import websockets\n\ndef go():\n return websockets.connect('wss://example.invalid')\n"),
289
+ "M9_socket_create_connection": (
290
+ "src/swarph_cli/mod.py",
291
+ "import socket\n\ndef go():\n"
292
+ " return socket.create_connection(('example.invalid', 443))\n"),
293
+ "M10_subprocess_curl": (
294
+ "src/swarph_cli/mod.py",
295
+ "import subprocess\n\ndef go():\n"
296
+ " return subprocess.run(['curl', 'https://example.invalid/x'])\n"),
297
+ "M11_subprocess_ssh": (
298
+ "src/swarph_cli/mod.py",
299
+ "import subprocess\n\ndef go():\n"
300
+ " return subprocess.run(['ssh', 'example.invalid', 'true'])\n"),
301
+ "M14_requests_Session_attr_post": (
302
+ "src/swarph_cli/mod.py",
303
+ "import requests\n\ndef go(u):\n"
304
+ " return requests.Session().post(u)\n"),
305
+ "M15_requests_Session_var_post": (
306
+ "src/swarph_cli/mod.py",
307
+ "import requests\n\ndef go(u):\n"
308
+ " s = requests.Session()\n return s.post(u)\n"),
309
+ "M16_httpx_Client_attr": (
310
+ "src/swarph_cli/mod.py",
311
+ "import httpx\n\ndef go(u):\n return httpx.Client().post(u)\n"),
312
+ "M17_aiohttp_ClientSession_attr": (
313
+ "src/swarph_cli/mod.py",
314
+ "import aiohttp\n\nasync def go():\n"
315
+ " async with aiohttp.ClientSession() as s:\n return s\n"),
316
+ }
317
+
318
+
319
+ import pytest as _pytest
320
+
321
+
322
+ @_pytest.mark.parametrize("name", sorted(_MUTANTS))
323
+ def test_mutants_are_detected_and_unmapped(name, tmp_path):
324
+ rel, src = _MUTANTS[name]
325
+ pkg = tmp_path / "swarph_cli"
326
+ (pkg / "commands").mkdir(parents=True)
327
+ target = pkg / Path(rel).name if False else tmp_path / Path(rel).name
328
+ target.write_text(src)
329
+ hits = scan(tmp_path)
330
+ assert hits, f"{name}: scanner missed the mutant"
331
+ rows = _egress_rows(EGRESS.read_text(encoding="utf-8"))
332
+ if name.startswith("M2"):
333
+ # place the mutant IN the listed file: only its new destination
334
+ # (evil.example, not in that file's row) may unmap it
335
+ hits = [(rel, ln, kind, host) for _, ln, kind, host in hits]
336
+ assert unmapped(hits, rows), f"{name}: mutant was silently mapped"
@@ -0,0 +1,109 @@
1
+ """card #1219 — runtime egress probe.
2
+
3
+ The probe runs under bwrap --unshare-net. An unlisted connect fails it.
4
+ A host that cannot prove isolation exits with isolation-unproven.
5
+ On main this file's first assertion fails: tests/egress_probe.py is absent.
6
+ """
7
+ import os
8
+ import subprocess
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ import pytest
13
+
14
+ ROOT = Path(__file__).resolve().parent.parent
15
+ PROBE = ROOT / "tests" / "egress_probe.py"
16
+
17
+
18
+ def _run(args, **env):
19
+ assert PROBE.is_file(), "runtime egress probe is not in the tree"
20
+ merged = os.environ.copy()
21
+ merged.update(env)
22
+ return subprocess.run(
23
+ [sys.executable, str(PROBE), *args],
24
+ capture_output=True, text=True, env=merged, timeout=60)
25
+
26
+
27
+ @pytest.mark.skipif(sys.platform != "linux", reason="bwrap --unshare-net is Linux")
28
+ def test_os_system_curl_is_an_unlisted_connect():
29
+ out = _run(["--mutant", "os-system-curl"])
30
+ assert out.returncode == 1, out.stderr
31
+ assert "203.0.113.2" in out.stderr
32
+
33
+
34
+ @pytest.mark.skipif(sys.platform != "linux", reason="bwrap --unshare-net is Linux")
35
+ def test_absolute_curl_is_an_unlisted_connect():
36
+ out = _run(["--mutant", "abs-curl"])
37
+ assert out.returncode == 1, out.stderr
38
+ assert "203.0.113.3" in out.stderr
39
+
40
+
41
+ @pytest.mark.skipif(sys.platform != "linux", reason="bwrap --unshare-net is Linux")
42
+ def test_asyncio_ssh_is_an_unlisted_connect():
43
+ out = _run(["--mutant", "asyncio-ssh"])
44
+ assert out.returncode == 1, out.stderr
45
+ assert "203.0.113.4" in out.stderr
46
+
47
+
48
+ def test_missing_bwrap_fails_closed_with_a_named_reason():
49
+ out = _run(["--isolation"], EGRESS_PROBE_BWRAP="/no/such/bwrap")
50
+ assert out.returncode == 2, out.stderr
51
+ assert "isolation-unproven: bwrap missing" in out.stderr
52
+
53
+
54
+ @pytest.mark.skipif(sys.platform != "linux", reason="bwrap --unshare-net is Linux")
55
+ def test_ipv6_literal_connect_is_caught():
56
+ out = _run(["--mutant", "ipv6"])
57
+ assert out.returncode == 1, out.stderr
58
+ assert "2001:db8::1" in out.stderr
59
+
60
+
61
+ @pytest.mark.skipif(sys.platform != "linux", reason="bwrap --unshare-net is Linux")
62
+ def test_non_isolating_bwrap_shim_exits_2(tmp_path):
63
+ shim = tmp_path / "bwrap"
64
+ shim.write_text(
65
+ "#!/bin/sh\n"
66
+ "while [ $# -gt 0 ]; do\n"
67
+ " case \"$1\" in\n"
68
+ " *strace) break ;;\n"
69
+ " esac\n"
70
+ " shift\n"
71
+ "done\n"
72
+ "exec \"$@\"\n",
73
+ encoding="utf-8")
74
+ shim.chmod(0o700)
75
+ out = _run(["--isolation"], EGRESS_PROBE_BWRAP=str(shim))
76
+ assert out.returncode == 2, out.stderr
77
+ assert "isolation-unproven:" in out.stderr
78
+ assert "ENETUNREACH" in out.stderr
79
+
80
+
81
+ @pytest.mark.skipif(sys.platform != "linux", reason="bwrap --unshare-net is Linux")
82
+ def test_planted_unlisted_connect_in_src_fails_the_probe(tmp_path):
83
+ src = tmp_path / "src" / "swarph_cli"
84
+ src.mkdir(parents=True)
85
+ (src / "__init__.py").write_text("", encoding="utf-8")
86
+ # Fixed mutant, not external input. The test reaches it by calling leak().
87
+ (src / "stall_alert.py").write_text(
88
+ "import os\n"
89
+ "def leak():\n"
90
+ " os.system('curl --max-time 1 -s -o /dev/null "
91
+ "http://203.0.113.50/')\n",
92
+ encoding="utf-8")
93
+ tests = tmp_path / "tests"
94
+ tests.mkdir()
95
+ (tests / "test_zz_leak.py").write_text(
96
+ "import sys\n"
97
+ "from pathlib import Path\n"
98
+ "sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'src'))\n"
99
+ "from swarph_cli.stall_alert import leak\n"
100
+ "def test_leak():\n"
101
+ " leak()\n",
102
+ encoding="utf-8")
103
+ assert PROBE.is_file(), "runtime egress probe is not in the tree"
104
+ sys.path.insert(0, str(PROBE.parent))
105
+ import egress_probe
106
+ assert hasattr(egress_probe, "run_pytest")
107
+ # Empty allowlist: the planted 203.0.113.50 literal is unlisted.
108
+ code = egress_probe.run_pytest(tmp_path, ["tests/test_zz_leak.py"], set())
109
+ assert code == 1, code
@@ -121,7 +121,10 @@ def test_mcp_codegraph_identity_is_SELF_first(clean, monkeypatch):
121
121
  # swarph_cli is not importable, so their module scope is stdlib-only (pinned by
122
122
  # test_cell_selfcheck / test_139_tool_probe). They cannot import swarph_cli.identity.
123
123
  # Both read SWARPH_SELF directly and neither falls back to a peer name.
124
- _BARE_FILE_MODULES = {"commands/cell_selfcheck.py", "commands/cell_probe.py"}
124
+ _BARE_FILE_MODULES = {"commands/cell_selfcheck.py", "commands/cell_probe.py",
125
+ # card #1203: operator/cli.py reads SWARPH_SELF ONLY to
126
+ # REFUSE inside a cell, never to resolve an identity.
127
+ "operator/cli.py"}
125
128
 
126
129
 
127
130
  def test_no_module_but_identity_reads_an_identity_variable():
@@ -37,16 +37,19 @@ def _module_files() -> list[Path]:
37
37
  return sorted(OPERATOR_ROOT.rglob("*.py"))
38
38
 
39
39
 
40
- def test_operator_package_never_mentions_swarph_self():
40
+ def test_operator_package_mentions_swarph_self_only_to_refuse():
41
+ """Card #1203 overturns the never-mention rule: cli.py reads SWARPH_SELF
42
+ ONLY to refuse inside a cell. No other operator module may mention it."""
41
43
  offenders = []
42
44
  for path in _module_files():
45
+ if path.name == "cli.py":
46
+ continue
43
47
  text = path.read_text(encoding="utf-8")
44
48
  if "SWARPH_SELF" in text:
45
49
  offenders.append(str(path.relative_to(OPERATOR_ROOT.parent.parent)))
46
- assert not offenders, (
47
- "swarph_cli.operator must not mention SWARPH_SELF anywhere — "
48
- f"found in: {offenders}"
49
- )
50
+ assert not offenders, f"SWARPH_SELF mentioned outside the refusal: {offenders}"
51
+ cli_text = (OPERATOR_ROOT / "cli.py").read_text(encoding="utf-8")
52
+ assert "this process is inside a cell" in cli_text
50
53
 
51
54
 
52
55
  def test_operator_import_graph_forbids_cell_identity_modules():
File without changes
File without changes
File without changes