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.
- {swarph_cli-0.72.24/src/swarph_cli.egg-info → swarph_cli-0.72.25}/PKG-INFO +1 -1
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/pyproject.toml +1 -1
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/operator/cli.py +16 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25/src/swarph_cli.egg-info}/PKG-INFO +1 -1
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli.egg-info/SOURCES.txt +3 -0
- swarph_cli-0.72.25/tests/test_1203_swarph_me_refusal.py +49 -0
- swarph_cli-0.72.25/tests/test_1214_egress.py +336 -0
- swarph_cli-0.72.25/tests/test_1219_egress_probe.py +109 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_402_one_identity_resolver.py +4 -1
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_operator_me.py +8 -5
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/LICENSE +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/README.md +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/setup.cfg +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/__main__.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/__init__.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/backends.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/claude_cli.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/codex_cli.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/pack.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/prices.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/providers.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/quality.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/refresh_prices.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/rules.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/runner.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/signal_score.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/bench/validate.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/chain_token.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/channel.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/_content.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/_display.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/_gateway_client.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/bench.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/board.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/board_merge_check.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/brain.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/brain_ask.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/cell_probe.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/channel.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/codegraph.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/codegraph_hook.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/codex_hook_output.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/codex_waker.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/dreaming.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/event.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/gateway.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/gh_route.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/group.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/guide.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/highlight.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/hook_interpreter.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/hooks.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/init.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/install_multiplexer.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/install_wake_hook.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/lane.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/memory.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/mesh.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/monitor.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/okf_links.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/onboard.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/roster.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/rowclear_hook.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/schedule.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/service.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/spawn.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/sweep.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/timeline.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/triage.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/version.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/wake_hook_output.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/commands/watchdog.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/console_safe.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/delivery_queue.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dm_frame.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/__init__.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/candidates.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/clone.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/enrich.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/index_check.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/notify.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/organize.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/probes.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/report.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/run.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/slm.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/transcripts.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/dreaming/verify.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/gateway/__init__.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/gateway/feature_registry.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/gateway/lanes_control.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/gateway/schema.sql +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/gateway/server.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/gateway/services_control.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/gateway_default.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/gh_identity.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/guide/GUIDE.md +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/identity.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/launchd/swarph-codex-outbox.plist +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/launchd/swarph-codex-outbox.timer.plist +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/launchd/swarph-monitor.plist +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/launchd/swarph-watchdog.plist +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/launchd/swarph-watchdog.timer.plist +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/launchd_render.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/main.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/multiplexer.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/operator/__init__.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/operator/__main__.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/operator/api.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/operator/config.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/operator/fmt.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/orphan_daemons.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/pane_probe.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/peer_executor.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/peer_executor_windows.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/peer_reconciliation.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/peer_reply_outbox.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/peer_service_host.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/peer_service_payload.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/peer_service_reply.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/peer_staging.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/query_filters.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/quota_sync.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/rowclear.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/scripts/__init__.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/scrub.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/service/__init__.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/service/app.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/service/providers.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/session_bridge.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/source_text.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/stall_alert.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/codex-waker.default +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-stuck-sweep.service +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-stuck-sweep.timer +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-triage.service +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-triage.timer +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/timeline_paths.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/tokens.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/triage/__init__.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/triage/backend.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/triage/job.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/triage/owners.json +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/triage/router.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/triage/rows.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli/triage/sensor.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1006_board_publisher.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1006_session_board.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1010_strand_only.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1013_spawn_channel.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1017_muse_tui.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1050_surrogate_wake.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1052_opencode_deaf.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1059_watchdog_unread_env.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1061_roster_cli.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1064_stuck_sweep.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1065_release.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1066_quiet_wakes.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1068_structured_accept_cli.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1071_quota_sync.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1073_triage.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1073_triage_board.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1073_wheel_install.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1075_task_cli.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1083_openai_decisions.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1144_cell_yaml_github.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1197_commander_taps.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_1206_offer_question.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_124_register_merge.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_126_pending_from_complete.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_132_ensure_monitor_state_dir.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_138_memory_navigate.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_139_tool_probe.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_183_card_anchor.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_184_notify_honesty.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_201_win32_bash_refusal.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_243_onboard_per_peer_token.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_307_task4_ask_reply.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_319_refusal_exit_codes.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_323_mistral_backend.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_332_report_organize_findings.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_356_query_filters.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_397_gh_identity_router.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_397_router_preflight.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_432_rowclear.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_467b_onboard_operator_credential.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_472_register_caller_binding.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_484_codex_queue_sink.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_523_guide.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_524_conftest_hermeticity.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_532_accept_flag.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_535_version_capability.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_538_signal_bench.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_546_default_gateway.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_547_verbs_from_registry.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_548_gateway_remainder.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_552_codegraph_gateway.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_555_relay_row_shape.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_556_relay_freshness.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_559_edges_truncated.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_564_bootstrap_first_peer.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_579_register_bootstrap_message.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_591_cli_step_graph.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_644_monitor_supervision.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_648_install_unit.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_654_reexec.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_656_owned_slm.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_684_dreaming_verb.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_689_pkg_version_order.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_715_grok_wake.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_716_timeline_one_path.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_723_grok_0251.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_724_ps1_ascii_or_bom.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_725_stdio_reconfigure.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_726_grok_limit_screen.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_729_arm_check_delivery_gap.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_729_dm_once.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_729_wake_watchdog.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_734_dreaming_enrich_degrade.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_735_dm_mcp_tools.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_739_wake_writer.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_740_edit_project.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_762_cards_edit_priority.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_764_enrich_collapse.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_801_confirm_flow_cli.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_802_stage_history.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_825_codegraph_hook_noise.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_825_install_posttooluse_only.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_825_remaining_cap_shred_bind.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_829_counterfactual_column.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_830_hook_binding_version.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_84_cli_labels.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_866_doctrine_guide.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_872_identity_refuses_not_warns.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_874_source_text.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_937_dreaming_notify.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_938_organize_line_limit.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_941_bench_backends.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_941_harness_342.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_945_same_line_pairing.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_955_prompt_audit.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_957_auth_http.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_959_venv_interpreter.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_960_channel.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_960_ensure_monitor_heartbeat.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_961_cursor_grok_footer.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_961_loud_stop.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_963_plugin_manifest.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_964_channel_cell.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_965_mesh_wait.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_975_channel_hop.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_bench_backends.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_bench_claude_cli.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_bench_codex_cli.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_bench_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_bench_pack.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_bench_prices.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_bench_quality.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_bench_runner.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_bench_validate.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_board_card_thread.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_board_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_board_merge_check.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_board_obligation_close.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_brain_ask_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_brain_ask_env_diagnosis.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_brain_ask_identity_explicitness.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_brain_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_cell_selfcheck.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_chain_token.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_channel_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_channel_poll_never_breaks_dms.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_channel_post.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_channel_wake_policy_filter.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_codegraph_caller_scope.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_codegraph_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_codegraph_hook.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_codegraph_mcp.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_codex_waker.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_console_encoding_e2e.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_content_composition.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_copilot_membrane.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_copilot_pane_reader.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_cursor_membrane.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_daemon_console_encoding.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_daemon_delivery.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_delivery_queue.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_display_sanitize.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_ensure_monitor_no_peer_default.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_event_emit.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_gateway_brain.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_gateway_client.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_gateway_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_gateway_message_idempotency.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_gh_route_doctor_mesh_name.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_group_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_guide_commands_parse.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_guide_selfcheck.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_heartbeat_check.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_highlight_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_activity_marker.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_legacy_generation_removal.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_verify.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_win32_bash_wrapper.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_hooks_win32_command_path.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_import_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_inbox_identity_provenance.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_init_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_init_provider_menu.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_install_codex_hooks.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_install_multiplexer_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_install_postcompact_hook.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_install_wake_hook.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_lane_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_launchd_units.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_main.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_main_unknown_verb_guard.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mcp_missing_hint.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_memory_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_memory_emit_snippet.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_memory_gateway.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_memory_sync_membrane.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mesh_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mesh_inbox_mark_read.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mesh_inbox_sanitize.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mesh_originate_thread_623.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mesh_send_unknown_peer.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mesh_sidecar.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mesh_sidecar_muse_sink.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_meta_edge_identity.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_monitor_channel_poll.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_monitor_cli.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_monitor_daemon_collision.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_monitor_self_name_verification.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_monitor_state_split.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_multiplexer.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_muse_membrane.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_muse_own_cli.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_obligation_close_is_surfaced.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_okf_links.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_opencode_membrane.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_opencode_plugin.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_orphan_daemons.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_packaged_artifacts_exist.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_packaged_skills.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_executor.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_executor_windows.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_reply_drain_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_reply_drain_supervisor.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_reply_outbox.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_reply_transport.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_service_e2e.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_service_host.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_service_payload.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_peer_service_reply.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_postcompact_recall.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_process_liveness.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_providers_isolation.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_readme_claims_no_gateway_default.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_schedule_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_schedule_create_contract.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_scrub.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_security.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_service_app.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_service_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_service_providers.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_session_bridge_inject.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_session_bridge_probe.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_session_bridge_resolve.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_shipped_units_match_the_cli.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_spawn_base_hoist.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_spawn_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_spawn_identity_stamp.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_stall_alert.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_subprocess_decode_encoding.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_timeline_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_tmux_wake_runstate_defer.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_tmux_wake_session_reanchor.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_tmux_wake_stale.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_tmux_wake_submit_verify.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_token_file_one_parser.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_token_precedence_explicit_first.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_unified_token_resolver.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_version_command.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_vibe_membrane.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_watchdog.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_watchdog_emit_health.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_watchdog_install_flags.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.72.24 → swarph_cli-0.72.25}/tests/test_watchdog_stale_peers.py +0 -0
- {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.
|
|
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.
|
|
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" }
|
|
@@ -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.
|
|
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
|
|
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
|
-
|
|
48
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|