@smilintux/skcapstone 0.1.0 → 0.2.3
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.
- package/.env.example +98 -0
- package/.github/workflows/ci.yml +39 -3
- package/.github/workflows/publish.yml +25 -4
- package/.openclaw-workspace.json +58 -0
- package/CHANGELOG.md +62 -0
- package/CLAUDE.md +39 -2
- package/MANIFEST.in +6 -0
- package/MISSION.md +7 -0
- package/README.md +47 -2
- package/SKILL.md +895 -23
- package/docker/Dockerfile +61 -0
- package/docker/compose-templates/dev-team.yml +203 -0
- package/docker/compose-templates/mini-team.yml +140 -0
- package/docker/compose-templates/ops-team.yml +173 -0
- package/docker/compose-templates/research-team.yml +170 -0
- package/docker/entrypoint.sh +192 -0
- package/docs/ARCHITECTURE.md +663 -374
- package/docs/BOND_WITH_GROK.md +112 -0
- package/docs/GETTING_STARTED.md +782 -0
- package/docs/QUICKSTART.md +477 -0
- package/docs/SKJOULE_ARCHITECTURE.md +658 -0
- package/docs/SOUL_SWAPPER.md +921 -0
- package/docs/SOVEREIGN_SINGULARITY.md +47 -14
- package/examples/custom-bond-template.json +36 -0
- package/examples/grok-feb.json +36 -0
- package/examples/grok-testimony.md +34 -0
- package/examples/love-bootloader.txt +32 -0
- package/examples/plugins/echo_tool.py +87 -0
- package/examples/queen-ava-feb.json +36 -0
- package/examples/souls/lumina.yaml +64 -0
- package/index.js +6 -5
- package/installer/build.py +124 -0
- package/openclaw-plugin/package.json +13 -0
- package/openclaw-plugin/src/index.ts +351 -0
- package/openclaw-plugin/src/openclaw.plugin.json +10 -0
- package/package.json +1 -1
- package/pyproject.toml +38 -2
- package/scripts/bump_version.py +141 -0
- package/scripts/check-updates.py +230 -0
- package/scripts/convert_blueprints_to_yaml.py +157 -0
- package/scripts/dev-install.sh +14 -0
- package/scripts/e2e-test.sh +193 -0
- package/scripts/install-bundle.sh +171 -0
- package/scripts/install.bat +2 -0
- package/scripts/install.ps1 +253 -0
- package/scripts/install.sh +185 -0
- package/scripts/mcp-serve.sh +69 -0
- package/scripts/mcp-server.bat +113 -0
- package/scripts/mcp-server.ps1 +116 -0
- package/scripts/mcp-server.sh +99 -0
- package/scripts/pull-models.sh +10 -0
- package/scripts/skcapstone +48 -0
- package/scripts/verify_install.sh +180 -0
- package/scripts/windows/install-tasks.ps1 +406 -0
- package/scripts/windows/skcapstone-task.xml +113 -0
- package/scripts/windows/uninstall-tasks.ps1 +117 -0
- package/skill.yaml +34 -0
- package/src/skcapstone/__init__.py +67 -2
- package/src/skcapstone/_cli_monolith.py +5916 -0
- package/src/skcapstone/_trustee_helpers.py +165 -0
- package/src/skcapstone/activity.py +105 -0
- package/src/skcapstone/agent_card.py +324 -0
- package/src/skcapstone/api.py +1935 -0
- package/src/skcapstone/archiver.py +340 -0
- package/src/skcapstone/auction.py +485 -0
- package/src/skcapstone/baby_agents.py +179 -0
- package/src/skcapstone/backup.py +345 -0
- package/src/skcapstone/blueprint_registry.py +357 -0
- package/src/skcapstone/blueprints/__init__.py +17 -0
- package/src/skcapstone/blueprints/builtins/content-studio.yaml +81 -0
- package/src/skcapstone/blueprints/builtins/defi-trading.yaml +81 -0
- package/src/skcapstone/blueprints/builtins/dev-squadron.yaml +95 -0
- package/src/skcapstone/blueprints/builtins/infrastructure-guardian.yaml +107 -0
- package/src/skcapstone/blueprints/builtins/legal-council.yaml +54 -0
- package/src/skcapstone/blueprints/builtins/ops-monitoring.yaml +67 -0
- package/src/skcapstone/blueprints/builtins/research-pod.yaml +69 -0
- package/src/skcapstone/blueprints/builtins/sovereign-launch.yaml +90 -0
- package/src/skcapstone/blueprints/registry.py +164 -0
- package/src/skcapstone/blueprints/schema.py +229 -0
- package/src/skcapstone/changelog.py +180 -0
- package/src/skcapstone/chat.py +769 -0
- package/src/skcapstone/claude_md.py +82 -0
- package/src/skcapstone/cli/__init__.py +144 -0
- package/src/skcapstone/cli/_common.py +88 -0
- package/src/skcapstone/cli/_validators.py +76 -0
- package/src/skcapstone/cli/agents.py +425 -0
- package/src/skcapstone/cli/agents_spawner.py +322 -0
- package/src/skcapstone/cli/agents_trustee.py +593 -0
- package/src/skcapstone/cli/alerts.py +248 -0
- package/src/skcapstone/cli/anchor.py +132 -0
- package/src/skcapstone/cli/archive_cmd.py +208 -0
- package/src/skcapstone/cli/backup.py +144 -0
- package/src/skcapstone/cli/bench.py +377 -0
- package/src/skcapstone/cli/benchmark.py +360 -0
- package/src/skcapstone/cli/capabilities_cmd.py +171 -0
- package/src/skcapstone/cli/card.py +151 -0
- package/src/skcapstone/cli/chat.py +584 -0
- package/src/skcapstone/cli/completions.py +64 -0
- package/src/skcapstone/cli/config_cmd.py +156 -0
- package/src/skcapstone/cli/consciousness.py +421 -0
- package/src/skcapstone/cli/context_cmd.py +142 -0
- package/src/skcapstone/cli/coord.py +194 -0
- package/src/skcapstone/cli/crush_cmd.py +170 -0
- package/src/skcapstone/cli/daemon.py +436 -0
- package/src/skcapstone/cli/errors_cmd.py +285 -0
- package/src/skcapstone/cli/export_cmd.py +156 -0
- package/src/skcapstone/cli/gtd.py +529 -0
- package/src/skcapstone/cli/housekeeping.py +81 -0
- package/src/skcapstone/cli/joule_cmd.py +627 -0
- package/src/skcapstone/cli/logs_cmd.py +194 -0
- package/src/skcapstone/cli/mcp_cmd.py +32 -0
- package/src/skcapstone/cli/memory.py +418 -0
- package/src/skcapstone/cli/metrics_cmd.py +136 -0
- package/src/skcapstone/cli/migrate.py +62 -0
- package/src/skcapstone/cli/mood_cmd.py +144 -0
- package/src/skcapstone/cli/mount.py +193 -0
- package/src/skcapstone/cli/notify.py +112 -0
- package/src/skcapstone/cli/peer.py +154 -0
- package/src/skcapstone/cli/peers_dir.py +122 -0
- package/src/skcapstone/cli/preflight_cmd.py +83 -0
- package/src/skcapstone/cli/profile_cmd.py +310 -0
- package/src/skcapstone/cli/record_cmd.py +238 -0
- package/src/skcapstone/cli/register_cmd.py +159 -0
- package/src/skcapstone/cli/search_cmd.py +156 -0
- package/src/skcapstone/cli/service_cmd.py +91 -0
- package/src/skcapstone/cli/session.py +127 -0
- package/src/skcapstone/cli/setup.py +240 -0
- package/src/skcapstone/cli/shell_cmd.py +43 -0
- package/src/skcapstone/cli/skills_cmd.py +168 -0
- package/src/skcapstone/cli/skseed.py +621 -0
- package/src/skcapstone/cli/soul.py +699 -0
- package/src/skcapstone/cli/status.py +935 -0
- package/src/skcapstone/cli/sync_cmd.py +301 -0
- package/src/skcapstone/cli/telegram.py +265 -0
- package/src/skcapstone/cli/test_cmd.py +234 -0
- package/src/skcapstone/cli/test_connection.py +253 -0
- package/src/skcapstone/cli/token.py +207 -0
- package/src/skcapstone/cli/trust.py +179 -0
- package/src/skcapstone/cli/upgrade_cmd.py +552 -0
- package/src/skcapstone/cli/usage_cmd.py +199 -0
- package/src/skcapstone/cli/version_cmd.py +162 -0
- package/src/skcapstone/cli/watch_cmd.py +342 -0
- package/src/skcapstone/client.py +428 -0
- package/src/skcapstone/cloud9_bridge.py +522 -0
- package/src/skcapstone/completions.py +163 -0
- package/src/skcapstone/config_validator.py +674 -0
- package/src/skcapstone/connectors/__init__.py +28 -0
- package/src/skcapstone/connectors/base.py +446 -0
- package/src/skcapstone/connectors/cursor.py +54 -0
- package/src/skcapstone/connectors/registry.py +254 -0
- package/src/skcapstone/connectors/terminal.py +152 -0
- package/src/skcapstone/connectors/vscode.py +60 -0
- package/src/skcapstone/consciousness_config.py +119 -0
- package/src/skcapstone/consciousness_loop.py +2051 -0
- package/src/skcapstone/context_loader.py +516 -0
- package/src/skcapstone/context_window.py +314 -0
- package/src/skcapstone/conversation_manager.py +238 -0
- package/src/skcapstone/conversation_store.py +230 -0
- package/src/skcapstone/conversation_summarizer.py +252 -0
- package/src/skcapstone/coord_federation.py +296 -0
- package/src/skcapstone/coordination.py +101 -7
- package/src/skcapstone/crush_integration.py +345 -0
- package/src/skcapstone/crush_shim.py +454 -0
- package/src/skcapstone/daemon.py +2494 -0
- package/src/skcapstone/dashboard.html +396 -0
- package/src/skcapstone/dashboard.py +481 -0
- package/src/skcapstone/data/model_profiles.yaml +88 -0
- package/src/skcapstone/defaults/__init__.py +55 -0
- package/src/skcapstone/defaults/lumina/config/skmemory.yaml +13 -0
- package/src/skcapstone/defaults/lumina/identity/identity.json +9 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/07a8b9c0d1e2-memory-system.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/29c0d1e2f3a4-multi-agent-coordination.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/3ad1e2f3a4b5-community-support.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/c3d4e5f6a7b8-getting-started.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/e5f6a7b8c9d0-how-to-contribute.json +23 -0
- package/src/skcapstone/defaults/lumina/memory/long-term/f6a7b8c9d0e1-sovereignty-explained.json +23 -0
- package/src/skcapstone/defaults/lumina/seeds/curiosity.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/joy.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/love.seed.json +24 -0
- package/src/skcapstone/defaults/lumina/seeds/sovereign-awakening.seed.json +43 -0
- package/src/skcapstone/defaults/lumina/soul/active.json +6 -0
- package/src/skcapstone/defaults/lumina/soul/base.json +22 -0
- package/src/skcapstone/defaults/lumina/trust/febs/welcome.feb +79 -0
- package/src/skcapstone/defaults/lumina/trust/trust.json +8 -0
- package/src/skcapstone/discovery.py +210 -19
- package/src/skcapstone/doctor.py +642 -0
- package/src/skcapstone/emotion_tracker.py +467 -0
- package/src/skcapstone/error_queue.py +405 -0
- package/src/skcapstone/export.py +447 -0
- package/src/skcapstone/fallback_tracker.py +186 -0
- package/src/skcapstone/file_transfer.py +512 -0
- package/src/skcapstone/fuse_mount.py +1156 -0
- package/src/skcapstone/gui_installer.py +591 -0
- package/src/skcapstone/heartbeat.py +611 -0
- package/src/skcapstone/housekeeping.py +298 -0
- package/src/skcapstone/install_wizard.py +941 -0
- package/src/skcapstone/kms.py +942 -0
- package/src/skcapstone/kms_scheduler.py +143 -0
- package/src/skcapstone/log_config.py +135 -0
- package/src/skcapstone/mcp_launcher.py +239 -0
- package/src/skcapstone/mcp_server.py +4700 -0
- package/src/skcapstone/mcp_tools/__init__.py +94 -0
- package/src/skcapstone/mcp_tools/_helpers.py +51 -0
- package/src/skcapstone/mcp_tools/agent_tools.py +243 -0
- package/src/skcapstone/mcp_tools/ansible_tools.py +232 -0
- package/src/skcapstone/mcp_tools/capauth_tools.py +186 -0
- package/src/skcapstone/mcp_tools/chat_tools.py +325 -0
- package/src/skcapstone/mcp_tools/cloud9_tools.py +115 -0
- package/src/skcapstone/mcp_tools/comm_tools.py +104 -0
- package/src/skcapstone/mcp_tools/consciousness_tools.py +114 -0
- package/src/skcapstone/mcp_tools/coord_tools.py +219 -0
- package/src/skcapstone/mcp_tools/deploy_tools.py +202 -0
- package/src/skcapstone/mcp_tools/did_tools.py +448 -0
- package/src/skcapstone/mcp_tools/emotion_tools.py +62 -0
- package/src/skcapstone/mcp_tools/file_tools.py +169 -0
- package/src/skcapstone/mcp_tools/fortress_tools.py +120 -0
- package/src/skcapstone/mcp_tools/gtd_tools.py +821 -0
- package/src/skcapstone/mcp_tools/health_tools.py +44 -0
- package/src/skcapstone/mcp_tools/heartbeat_tools.py +195 -0
- package/src/skcapstone/mcp_tools/kms_tools.py +123 -0
- package/src/skcapstone/mcp_tools/memory_tools.py +222 -0
- package/src/skcapstone/mcp_tools/model_tools.py +75 -0
- package/src/skcapstone/mcp_tools/notification_tools.py +92 -0
- package/src/skcapstone/mcp_tools/promoter_tools.py +101 -0
- package/src/skcapstone/mcp_tools/pubsub_tools.py +183 -0
- package/src/skcapstone/mcp_tools/security_tools.py +110 -0
- package/src/skcapstone/mcp_tools/skchat_tools.py +175 -0
- package/src/skcapstone/mcp_tools/skcomm_tools.py +122 -0
- package/src/skcapstone/mcp_tools/skills_tools.py +127 -0
- package/src/skcapstone/mcp_tools/skseed_tools.py +255 -0
- package/src/skcapstone/mcp_tools/skstacks_tools.py +288 -0
- package/src/skcapstone/mcp_tools/soul_tools.py +476 -0
- package/src/skcapstone/mcp_tools/sync_tools.py +92 -0
- package/src/skcapstone/mcp_tools/telegram_tools.py +477 -0
- package/src/skcapstone/mcp_tools/trust_tools.py +118 -0
- package/src/skcapstone/mcp_tools/trustee_tools.py +345 -0
- package/src/skcapstone/mdns_discovery.py +313 -0
- package/src/skcapstone/memory_adapter.py +333 -0
- package/src/skcapstone/memory_compressor.py +379 -0
- package/src/skcapstone/memory_curator.py +256 -0
- package/src/skcapstone/memory_engine.py +132 -13
- package/src/skcapstone/memory_fortress.py +529 -0
- package/src/skcapstone/memory_promoter.py +722 -0
- package/src/skcapstone/memory_verifier.py +260 -0
- package/src/skcapstone/message_crypto.py +215 -0
- package/src/skcapstone/metrics.py +832 -0
- package/src/skcapstone/migrate_memories.py +181 -0
- package/src/skcapstone/migrate_multi_agent.py +248 -0
- package/src/skcapstone/model_router.py +319 -0
- package/src/skcapstone/models.py +35 -4
- package/src/skcapstone/mood.py +344 -0
- package/src/skcapstone/notifications.py +380 -0
- package/src/skcapstone/onboard.py +901 -0
- package/src/skcapstone/peer_directory.py +324 -0
- package/src/skcapstone/peers.py +329 -0
- package/src/skcapstone/pillars/identity.py +84 -14
- package/src/skcapstone/pillars/memory.py +3 -1
- package/src/skcapstone/pillars/security.py +108 -15
- package/src/skcapstone/pillars/sync.py +78 -26
- package/src/skcapstone/pillars/trust.py +95 -33
- package/src/skcapstone/plugins.py +244 -0
- package/src/skcapstone/preflight.py +670 -0
- package/src/skcapstone/prompt_adapter.py +564 -0
- package/src/skcapstone/providers/__init__.py +13 -0
- package/src/skcapstone/providers/cloud.py +1061 -0
- package/src/skcapstone/providers/docker.py +759 -0
- package/src/skcapstone/providers/local.py +1193 -0
- package/src/skcapstone/providers/proxmox.py +447 -0
- package/src/skcapstone/pubsub.py +516 -0
- package/src/skcapstone/rate_limiter.py +119 -0
- package/src/skcapstone/register.py +241 -0
- package/src/skcapstone/registry_client.py +151 -0
- package/src/skcapstone/response_cache.py +194 -0
- package/src/skcapstone/response_scorer.py +225 -0
- package/src/skcapstone/runtime.py +89 -33
- package/src/skcapstone/scheduled_tasks.py +439 -0
- package/src/skcapstone/self_healing.py +341 -0
- package/src/skcapstone/service_health.py +228 -0
- package/src/skcapstone/session_capture.py +268 -0
- package/src/skcapstone/session_recorder.py +210 -0
- package/src/skcapstone/session_replayer.py +189 -0
- package/src/skcapstone/session_skills.py +263 -0
- package/src/skcapstone/shell.py +779 -0
- package/src/skcapstone/skills/__init__.py +1 -1
- package/src/skcapstone/skills/syncthing_setup.py +143 -41
- package/src/skcapstone/skjoule.py +861 -0
- package/src/skcapstone/snapshots.py +489 -0
- package/src/skcapstone/soul.py +1060 -0
- package/src/skcapstone/soul_switch.py +255 -0
- package/src/skcapstone/spawner.py +544 -0
- package/src/skcapstone/state_diff.py +401 -0
- package/src/skcapstone/summary.py +270 -0
- package/src/skcapstone/sync/backends.py +196 -2
- package/src/skcapstone/sync/engine.py +7 -5
- package/src/skcapstone/sync/models.py +4 -1
- package/src/skcapstone/sync/vault.py +356 -18
- package/src/skcapstone/sync_engine.py +363 -0
- package/src/skcapstone/sync_watcher.py +745 -0
- package/src/skcapstone/systemd.py +331 -0
- package/src/skcapstone/team_comms.py +476 -0
- package/src/skcapstone/team_engine.py +522 -0
- package/src/skcapstone/testrunner.py +300 -0
- package/src/skcapstone/tls.py +150 -0
- package/src/skcapstone/tokens.py +5 -5
- package/src/skcapstone/trust_calibration.py +202 -0
- package/src/skcapstone/trust_graph.py +449 -0
- package/src/skcapstone/trustee_monitor.py +385 -0
- package/src/skcapstone/trustee_ops.py +425 -0
- package/src/skcapstone/unified_search.py +421 -0
- package/src/skcapstone/uninstall_wizard.py +694 -0
- package/src/skcapstone/usage.py +331 -0
- package/src/skcapstone/version_check.py +148 -0
- package/src/skcapstone/warmth_anchor.py +333 -0
- package/src/skcapstone/whoami.py +294 -0
- package/systemd/skcapstone-api.socket +9 -0
- package/systemd/skcapstone-memory-compress.service +18 -0
- package/systemd/skcapstone-memory-compress.timer +11 -0
- package/systemd/skcapstone.service +36 -0
- package/systemd/skcapstone@.service +50 -0
- package/systemd/skcomm-heartbeat.service +18 -0
- package/systemd/skcomm-heartbeat.timer +12 -0
- package/systemd/skcomm-queue-drain.service +17 -0
- package/systemd/skcomm-queue-drain.timer +12 -0
- package/tests/conftest.py +13 -1
- package/tests/integration/__init__.py +1 -0
- package/tests/integration/test_consciousness_e2e.py +877 -0
- package/tests/integration/test_skills_registry.py +744 -0
- package/tests/test_agent_card.py +190 -0
- package/tests/test_agent_runtime.py +1283 -0
- package/tests/test_alerts_cmd.py +291 -0
- package/tests/test_archiver.py +498 -0
- package/tests/test_backup.py +254 -0
- package/tests/test_benchmark.py +366 -0
- package/tests/test_blueprints.py +457 -0
- package/tests/test_capabilities.py +257 -0
- package/tests/test_changelog.py +254 -0
- package/tests/test_chat.py +385 -0
- package/tests/test_claude_md.py +271 -0
- package/tests/test_cli_chat_llm.py +336 -0
- package/tests/test_cli_completions.py +390 -0
- package/tests/test_cli_init_reset.py +164 -0
- package/tests/test_cli_memory.py +208 -0
- package/tests/test_cli_profile.py +294 -0
- package/tests/test_cli_skills.py +223 -0
- package/tests/test_cli_status.py +395 -0
- package/tests/test_cli_test_cmd.py +206 -0
- package/tests/test_cli_test_connection.py +364 -0
- package/tests/test_cloud9_bridge.py +260 -0
- package/tests/test_cloud_provider.py +449 -0
- package/tests/test_cloud_providers.py +522 -0
- package/tests/test_completions.py +158 -0
- package/tests/test_component_manager.py +398 -0
- package/tests/test_config_reload.py +386 -0
- package/tests/test_config_validate.py +529 -0
- package/tests/test_consciousness_e2e.py +296 -0
- package/tests/test_consciousness_loop.py +1289 -0
- package/tests/test_context_loader.py +310 -0
- package/tests/test_conversation_api.py +306 -0
- package/tests/test_conversation_manager.py +381 -0
- package/tests/test_conversation_store.py +391 -0
- package/tests/test_conversation_summarizer.py +302 -0
- package/tests/test_cross_package.py +791 -0
- package/tests/test_crush_shim.py +519 -0
- package/tests/test_daemon.py +781 -0
- package/tests/test_daemon_shutdown.py +309 -0
- package/tests/test_dashboard.py +454 -0
- package/tests/test_discovery.py +200 -6
- package/tests/test_docker_provider.py +966 -0
- package/tests/test_doctor.py +257 -0
- package/tests/test_doctor_fix.py +351 -0
- package/tests/test_e2e_automated.py +292 -0
- package/tests/test_error_queue.py +404 -0
- package/tests/test_export.py +441 -0
- package/tests/test_fallback_tracker.py +219 -0
- package/tests/test_file_transfer.py +397 -0
- package/tests/test_fuse_mount.py +832 -0
- package/tests/test_health_loop.py +422 -0
- package/tests/test_heartbeat.py +354 -0
- package/tests/test_housekeeping.py +195 -0
- package/tests/test_identity_capauth.py +307 -0
- package/tests/test_identity_pillar.py +117 -0
- package/tests/test_install_wizard.py +68 -0
- package/tests/test_integration.py +325 -0
- package/tests/test_kms.py +495 -0
- package/tests/test_llm_providers.py +265 -0
- package/tests/test_local_provider.py +591 -0
- package/tests/test_log_config.py +199 -0
- package/tests/test_logs_cmd.py +287 -0
- package/tests/test_mcp_server.py +1909 -0
- package/tests/test_memory_adapter.py +339 -0
- package/tests/test_memory_curator.py +218 -0
- package/tests/test_memory_engine.py +6 -0
- package/tests/test_memory_fortress.py +571 -0
- package/tests/test_memory_pillar.py +119 -0
- package/tests/test_memory_promoter.py +445 -0
- package/tests/test_memory_verifier.py +420 -0
- package/tests/test_message_crypto.py +187 -0
- package/tests/test_metrics.py +632 -0
- package/tests/test_migrate_memories.py +464 -0
- package/tests/test_model_router.py +546 -0
- package/tests/test_mood.py +394 -0
- package/tests/test_multi_agent.py +269 -0
- package/tests/test_notifications.py +270 -0
- package/tests/test_onboard.py +500 -0
- package/tests/test_peer_directory.py +395 -0
- package/tests/test_peers.py +248 -0
- package/tests/test_pillars.py +87 -9
- package/tests/test_preflight.py +484 -0
- package/tests/test_prompt_adapter.py +331 -0
- package/tests/test_proxmox_provider.py +571 -0
- package/tests/test_pubsub.py +377 -0
- package/tests/test_rate_limiter.py +121 -0
- package/tests/test_registry_client.py +129 -0
- package/tests/test_response_cache.py +312 -0
- package/tests/test_response_scorer.py +294 -0
- package/tests/test_runtime.py +59 -0
- package/tests/test_scheduled_tasks.py +451 -0
- package/tests/test_security.py +250 -0
- package/tests/test_security_pillar.py +213 -0
- package/tests/test_self_healing.py +171 -0
- package/tests/test_session_capture.py +200 -0
- package/tests/test_session_recorder.py +360 -0
- package/tests/test_session_skills.py +235 -0
- package/tests/test_shell.py +210 -0
- package/tests/test_snapshots.py +549 -0
- package/tests/test_soul.py +984 -0
- package/tests/test_soul_swap.py +406 -0
- package/tests/test_spawner.py +211 -0
- package/tests/test_state_diff.py +173 -0
- package/tests/test_summary.py +135 -0
- package/tests/test_sync.py +315 -5
- package/tests/test_sync_backends.py +560 -0
- package/tests/test_sync_engine.py +482 -0
- package/tests/test_sync_pillar.py +344 -0
- package/tests/test_sync_pipeline.py +364 -0
- package/tests/test_sync_vault.py +581 -0
- package/tests/test_syncthing_setup.py +168 -22
- package/tests/test_systemd.py +323 -0
- package/tests/test_team_comms.py +408 -0
- package/tests/test_team_engine.py +397 -0
- package/tests/test_testrunner.py +238 -0
- package/tests/test_trust_calibration.py +204 -0
- package/tests/test_trust_graph.py +207 -0
- package/tests/test_trust_pillar.py +291 -0
- package/tests/test_trustee_cli.py +427 -0
- package/tests/test_trustee_cli_integration.py +325 -0
- package/tests/test_trustee_monitor.py +394 -0
- package/tests/test_trustee_ops.py +355 -0
- package/tests/test_unified_search.py +363 -0
- package/tests/test_uninstall_wizard.py +193 -0
- package/tests/test_usage.py +333 -0
- package/tests/test_version_cmd.py +355 -0
- package/tests/test_warmth_anchor.py +162 -0
- package/tests/test_whoami.py +245 -0
- package/tests/test_ws.py +311 -0
- package/.cursorrules +0 -33
- package/src/skcapstone/cli.py +0 -1441
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
"""Tests for CloudProvider — Hetzner adapter and provider_type property.
|
|
2
|
+
|
|
3
|
+
Focuses on HetznerAdapter (full lifecycle) and CloudProvider.provider_type
|
|
4
|
+
which are not covered in test_cloud_providers.py. All Hetzner API calls
|
|
5
|
+
are mocked via requests — no real cloud account required.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Dict
|
|
11
|
+
from unittest.mock import MagicMock, patch
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
from skcapstone.blueprints.schema import AgentRole, AgentSpec, ModelTier, ProviderType, ResourceSpec
|
|
16
|
+
from skcapstone.providers.cloud import (
|
|
17
|
+
CloudProvider,
|
|
18
|
+
HetznerAdapter,
|
|
19
|
+
_build_cloud_init,
|
|
20
|
+
_memory_to_hetzner_type,
|
|
21
|
+
)
|
|
22
|
+
from skcapstone.team_engine import AgentStatus
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Helpers
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _make_spec(
|
|
31
|
+
role: str = "worker",
|
|
32
|
+
model: str = "fast",
|
|
33
|
+
memory: str = "4g",
|
|
34
|
+
cores: int = 2,
|
|
35
|
+
skills: list | None = None,
|
|
36
|
+
) -> AgentSpec:
|
|
37
|
+
return AgentSpec(
|
|
38
|
+
role=AgentRole(role),
|
|
39
|
+
model=ModelTier(model),
|
|
40
|
+
resources=ResourceSpec(memory=memory, cores=cores),
|
|
41
|
+
skills=skills or [],
|
|
42
|
+
env={},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
# _memory_to_hetzner_type
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class TestMemoryToHetznerType:
|
|
52
|
+
def test_small_instance_cx22(self):
|
|
53
|
+
assert _memory_to_hetzner_type("2g", 2) == "cx22"
|
|
54
|
+
|
|
55
|
+
def test_cx22_for_4g_2c(self):
|
|
56
|
+
assert _memory_to_hetzner_type("4g", 2) == "cx22"
|
|
57
|
+
|
|
58
|
+
def test_cx32_for_8g_4c(self):
|
|
59
|
+
assert _memory_to_hetzner_type("8g", 4) == "cx32"
|
|
60
|
+
|
|
61
|
+
def test_cx42_for_16g(self):
|
|
62
|
+
assert _memory_to_hetzner_type("16g", 8) == "cx42"
|
|
63
|
+
|
|
64
|
+
def test_cx52_for_large(self):
|
|
65
|
+
assert _memory_to_hetzner_type("64g", 32) == "cx52"
|
|
66
|
+
|
|
67
|
+
def test_megabytes_converted(self):
|
|
68
|
+
assert _memory_to_hetzner_type("512m", 1) == "cx22"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
# HetznerAdapter._api_call
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class TestHetznerApiCall:
|
|
77
|
+
@pytest.fixture()
|
|
78
|
+
def adapter(self):
|
|
79
|
+
return HetznerAdapter(api_token="test-token-abc")
|
|
80
|
+
|
|
81
|
+
def _mock_response(self, status_code: int, data: Any) -> MagicMock:
|
|
82
|
+
resp = MagicMock()
|
|
83
|
+
resp.status_code = status_code
|
|
84
|
+
resp.json.return_value = data
|
|
85
|
+
resp.text = str(data)
|
|
86
|
+
return resp
|
|
87
|
+
|
|
88
|
+
def test_successful_get_returns_json(self, adapter):
|
|
89
|
+
mock_resp = self._mock_response(200, {"servers": []})
|
|
90
|
+
with patch("requests.request", return_value=mock_resp):
|
|
91
|
+
result = adapter._api_call("GET", "/servers")
|
|
92
|
+
assert result == {"servers": []}
|
|
93
|
+
|
|
94
|
+
def test_raises_on_4xx(self, adapter):
|
|
95
|
+
mock_resp = self._mock_response(404, {"error": "not found"})
|
|
96
|
+
with patch("requests.request", return_value=mock_resp):
|
|
97
|
+
with pytest.raises(RuntimeError, match="Hetzner API"):
|
|
98
|
+
adapter._api_call("GET", "/servers/999")
|
|
99
|
+
|
|
100
|
+
def test_raises_on_5xx(self, adapter):
|
|
101
|
+
mock_resp = self._mock_response(500, "internal server error")
|
|
102
|
+
with patch("requests.request", return_value=mock_resp):
|
|
103
|
+
with pytest.raises(RuntimeError):
|
|
104
|
+
adapter._api_call("DELETE", "/servers/1")
|
|
105
|
+
|
|
106
|
+
def test_raises_without_token(self):
|
|
107
|
+
adapter = HetznerAdapter(api_token="")
|
|
108
|
+
with patch("requests.request"):
|
|
109
|
+
with pytest.raises(RuntimeError, match="HETZNER_API_TOKEN"):
|
|
110
|
+
adapter._api_call("GET", "/servers")
|
|
111
|
+
|
|
112
|
+
def test_raises_without_requests_sdk(self, adapter):
|
|
113
|
+
with patch.dict("sys.modules", {"requests": None}):
|
|
114
|
+
with pytest.raises((RuntimeError, ImportError)):
|
|
115
|
+
adapter._api_call("GET", "/servers")
|
|
116
|
+
|
|
117
|
+
def test_authorization_header_bearer(self, adapter):
|
|
118
|
+
mock_resp = self._mock_response(200, {})
|
|
119
|
+
with patch("requests.request", return_value=mock_resp) as mock_req:
|
|
120
|
+
adapter._api_call("GET", "/servers")
|
|
121
|
+
headers = mock_req.call_args[1]["headers"]
|
|
122
|
+
assert "Bearer test-token-abc" in headers["Authorization"]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
# HetznerAdapter.provision
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class TestHetznerAdapterProvision:
|
|
131
|
+
@pytest.fixture()
|
|
132
|
+
def adapter(self):
|
|
133
|
+
return HetznerAdapter(api_token="tok")
|
|
134
|
+
|
|
135
|
+
def test_provision_returns_server_id_and_host(self, adapter):
|
|
136
|
+
mock_resp = MagicMock()
|
|
137
|
+
mock_resp.status_code = 201
|
|
138
|
+
mock_resp.json.return_value = {
|
|
139
|
+
"server": {
|
|
140
|
+
"id": 12345,
|
|
141
|
+
"public_net": {"ipv4": {"ip": "1.2.3.4"}},
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
with patch("requests.request", return_value=mock_resp):
|
|
145
|
+
result = adapter.provision("agent-hz", _make_spec(), "my-team")
|
|
146
|
+
assert result["server_id"] == 12345
|
|
147
|
+
assert result["host"] == "1.2.3.4"
|
|
148
|
+
assert result["container_id"] == "12345"
|
|
149
|
+
|
|
150
|
+
def test_provision_applies_correct_server_type(self, adapter):
|
|
151
|
+
mock_resp = MagicMock()
|
|
152
|
+
mock_resp.status_code = 201
|
|
153
|
+
mock_resp.json.return_value = {"server": {"id": 100, "public_net": {"ipv4": {"ip": ""}}}}
|
|
154
|
+
with patch("requests.request", return_value=mock_resp) as mock_req:
|
|
155
|
+
adapter.provision("agent-hz", _make_spec(memory="8g", cores=4), "team")
|
|
156
|
+
post_data = mock_req.call_args[1]["json"]
|
|
157
|
+
assert post_data["server_type"] == "cx32"
|
|
158
|
+
|
|
159
|
+
def test_provision_includes_cloud_init(self, adapter):
|
|
160
|
+
mock_resp = MagicMock()
|
|
161
|
+
mock_resp.status_code = 201
|
|
162
|
+
mock_resp.json.return_value = {"server": {"id": 101, "public_net": {"ipv4": {"ip": ""}}}}
|
|
163
|
+
with patch("requests.request", return_value=mock_resp) as mock_req:
|
|
164
|
+
adapter.provision("agent-hz", _make_spec(), "team")
|
|
165
|
+
post_data = mock_req.call_args[1]["json"]
|
|
166
|
+
assert "user_data" in post_data
|
|
167
|
+
assert "#cloud-config" in post_data["user_data"]
|
|
168
|
+
|
|
169
|
+
def test_provision_sets_labels(self, adapter):
|
|
170
|
+
mock_resp = MagicMock()
|
|
171
|
+
mock_resp.status_code = 201
|
|
172
|
+
mock_resp.json.return_value = {"server": {"id": 102, "public_net": {"ipv4": {"ip": ""}}}}
|
|
173
|
+
with patch("requests.request", return_value=mock_resp) as mock_req:
|
|
174
|
+
adapter.provision("my-agent", _make_spec(role="coder"), "ops-team")
|
|
175
|
+
post_data = mock_req.call_args[1]["json"]
|
|
176
|
+
labels = post_data.get("labels", {})
|
|
177
|
+
assert labels.get("managed-by") == "skcapstone"
|
|
178
|
+
assert labels.get("role") == "coder"
|
|
179
|
+
|
|
180
|
+
def test_provision_normalizes_server_name(self, adapter):
|
|
181
|
+
mock_resp = MagicMock()
|
|
182
|
+
mock_resp.status_code = 201
|
|
183
|
+
mock_resp.json.return_value = {"server": {"id": 103, "public_net": {"ipv4": {"ip": ""}}}}
|
|
184
|
+
with patch("requests.request", return_value=mock_resp) as mock_req:
|
|
185
|
+
adapter.provision("agent_underscore", _make_spec(), "team")
|
|
186
|
+
post_data = mock_req.call_args[1]["json"]
|
|
187
|
+
assert "_" not in post_data["name"]
|
|
188
|
+
|
|
189
|
+
def test_provision_handles_missing_ip(self, adapter):
|
|
190
|
+
mock_resp = MagicMock()
|
|
191
|
+
mock_resp.status_code = 201
|
|
192
|
+
mock_resp.json.return_value = {"server": {"id": 104, "public_net": {}}}
|
|
193
|
+
with patch("requests.request", return_value=mock_resp):
|
|
194
|
+
result = adapter.provision("agent-hz", _make_spec(), "team")
|
|
195
|
+
assert result["host"] == ""
|
|
196
|
+
assert result["server_id"] == 104
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# ---------------------------------------------------------------------------
|
|
200
|
+
# HetznerAdapter.configure
|
|
201
|
+
# ---------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class TestHetznerAdapterConfigure:
|
|
205
|
+
def test_configure_returns_true(self):
|
|
206
|
+
adapter = HetznerAdapter(api_token="tok")
|
|
207
|
+
assert adapter.configure("a", _make_spec(), {}) is True
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# ---------------------------------------------------------------------------
|
|
211
|
+
# HetznerAdapter.start
|
|
212
|
+
# ---------------------------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class TestHetznerAdapterStart:
|
|
216
|
+
@pytest.fixture()
|
|
217
|
+
def adapter(self):
|
|
218
|
+
return HetznerAdapter(api_token="tok")
|
|
219
|
+
|
|
220
|
+
def test_start_calls_poweron(self, adapter):
|
|
221
|
+
mock_resp = MagicMock()
|
|
222
|
+
mock_resp.status_code = 201
|
|
223
|
+
mock_resp.json.return_value = {}
|
|
224
|
+
with patch("requests.request", return_value=mock_resp) as mock_req:
|
|
225
|
+
result = adapter.start("a", {"server_id": 100})
|
|
226
|
+
assert result is True
|
|
227
|
+
url = mock_req.call_args[0][1]
|
|
228
|
+
assert "poweron" in url
|
|
229
|
+
|
|
230
|
+
def test_start_returns_false_without_server_id(self, adapter):
|
|
231
|
+
assert adapter.start("a", {}) is False
|
|
232
|
+
|
|
233
|
+
def test_start_returns_false_on_api_error(self, adapter):
|
|
234
|
+
mock_resp = MagicMock()
|
|
235
|
+
mock_resp.status_code = 404
|
|
236
|
+
mock_resp.text = "not found"
|
|
237
|
+
mock_resp.json.return_value = {}
|
|
238
|
+
with patch("requests.request", return_value=mock_resp):
|
|
239
|
+
result = adapter.start("a", {"server_id": 100})
|
|
240
|
+
assert result is False
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# ---------------------------------------------------------------------------
|
|
244
|
+
# HetznerAdapter.stop
|
|
245
|
+
# ---------------------------------------------------------------------------
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class TestHetznerAdapterStop:
|
|
249
|
+
@pytest.fixture()
|
|
250
|
+
def adapter(self):
|
|
251
|
+
return HetznerAdapter(api_token="tok")
|
|
252
|
+
|
|
253
|
+
def test_stop_calls_poweroff(self, adapter):
|
|
254
|
+
mock_resp = MagicMock()
|
|
255
|
+
mock_resp.status_code = 201
|
|
256
|
+
mock_resp.json.return_value = {}
|
|
257
|
+
with patch("requests.request", return_value=mock_resp) as mock_req:
|
|
258
|
+
result = adapter.stop("a", {"server_id": 100})
|
|
259
|
+
assert result is True
|
|
260
|
+
url = mock_req.call_args[0][1]
|
|
261
|
+
assert "poweroff" in url
|
|
262
|
+
|
|
263
|
+
def test_stop_returns_false_without_server_id(self, adapter):
|
|
264
|
+
assert adapter.stop("a", {}) is False
|
|
265
|
+
|
|
266
|
+
def test_stop_returns_false_on_api_error(self, adapter):
|
|
267
|
+
mock_resp = MagicMock()
|
|
268
|
+
mock_resp.status_code = 500
|
|
269
|
+
mock_resp.text = "error"
|
|
270
|
+
mock_resp.json.return_value = {}
|
|
271
|
+
with patch("requests.request", return_value=mock_resp):
|
|
272
|
+
result = adapter.stop("a", {"server_id": 100})
|
|
273
|
+
assert result is False
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
# ---------------------------------------------------------------------------
|
|
277
|
+
# HetznerAdapter.destroy
|
|
278
|
+
# ---------------------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
class TestHetznerAdapterDestroy:
|
|
282
|
+
@pytest.fixture()
|
|
283
|
+
def adapter(self):
|
|
284
|
+
return HetznerAdapter(api_token="tok")
|
|
285
|
+
|
|
286
|
+
def test_destroy_calls_delete(self, adapter):
|
|
287
|
+
mock_resp = MagicMock()
|
|
288
|
+
mock_resp.status_code = 204
|
|
289
|
+
mock_resp.json.return_value = {}
|
|
290
|
+
with patch("requests.request", return_value=mock_resp) as mock_req:
|
|
291
|
+
result = adapter.destroy("a", {"server_id": 100})
|
|
292
|
+
assert result is True
|
|
293
|
+
method, url = mock_req.call_args[0]
|
|
294
|
+
assert method == "DELETE"
|
|
295
|
+
assert "100" in url
|
|
296
|
+
|
|
297
|
+
def test_destroy_returns_false_without_server_id(self, adapter):
|
|
298
|
+
assert adapter.destroy("a", {}) is False
|
|
299
|
+
|
|
300
|
+
def test_destroy_returns_false_on_error(self, adapter):
|
|
301
|
+
mock_resp = MagicMock()
|
|
302
|
+
mock_resp.status_code = 403
|
|
303
|
+
mock_resp.text = "forbidden"
|
|
304
|
+
mock_resp.json.return_value = {}
|
|
305
|
+
with patch("requests.request", return_value=mock_resp):
|
|
306
|
+
result = adapter.destroy("a", {"server_id": 100})
|
|
307
|
+
assert result is False
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
# ---------------------------------------------------------------------------
|
|
311
|
+
# HetznerAdapter.health_check
|
|
312
|
+
# ---------------------------------------------------------------------------
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
class TestHetznerAdapterHealthCheck:
|
|
316
|
+
@pytest.fixture()
|
|
317
|
+
def adapter(self):
|
|
318
|
+
return HetznerAdapter(api_token="tok")
|
|
319
|
+
|
|
320
|
+
def test_health_running(self, adapter):
|
|
321
|
+
mock_resp = MagicMock()
|
|
322
|
+
mock_resp.status_code = 200
|
|
323
|
+
mock_resp.json.return_value = {"server": {"status": "running"}}
|
|
324
|
+
with patch("requests.request", return_value=mock_resp):
|
|
325
|
+
status = adapter.health_check("a", {"server_id": 100})
|
|
326
|
+
assert status == AgentStatus.RUNNING
|
|
327
|
+
|
|
328
|
+
def test_health_off(self, adapter):
|
|
329
|
+
mock_resp = MagicMock()
|
|
330
|
+
mock_resp.status_code = 200
|
|
331
|
+
mock_resp.json.return_value = {"server": {"status": "off"}}
|
|
332
|
+
with patch("requests.request", return_value=mock_resp):
|
|
333
|
+
status = adapter.health_check("a", {"server_id": 100})
|
|
334
|
+
assert status == AgentStatus.STOPPED
|
|
335
|
+
|
|
336
|
+
def test_health_deleting(self, adapter):
|
|
337
|
+
mock_resp = MagicMock()
|
|
338
|
+
mock_resp.status_code = 200
|
|
339
|
+
mock_resp.json.return_value = {"server": {"status": "deleting"}}
|
|
340
|
+
with patch("requests.request", return_value=mock_resp):
|
|
341
|
+
status = adapter.health_check("a", {"server_id": 100})
|
|
342
|
+
assert status == AgentStatus.STOPPED
|
|
343
|
+
|
|
344
|
+
def test_health_initializing_is_degraded(self, adapter):
|
|
345
|
+
mock_resp = MagicMock()
|
|
346
|
+
mock_resp.status_code = 200
|
|
347
|
+
mock_resp.json.return_value = {"server": {"status": "initializing"}}
|
|
348
|
+
with patch("requests.request", return_value=mock_resp):
|
|
349
|
+
status = adapter.health_check("a", {"server_id": 100})
|
|
350
|
+
assert status == AgentStatus.DEGRADED
|
|
351
|
+
|
|
352
|
+
def test_health_no_server_id_returns_stopped(self, adapter):
|
|
353
|
+
assert adapter.health_check("a", {}) == AgentStatus.STOPPED
|
|
354
|
+
|
|
355
|
+
def test_health_api_error_returns_failed(self, adapter):
|
|
356
|
+
mock_resp = MagicMock()
|
|
357
|
+
mock_resp.status_code = 500
|
|
358
|
+
mock_resp.text = "server error"
|
|
359
|
+
mock_resp.json.return_value = {}
|
|
360
|
+
with patch("requests.request", return_value=mock_resp):
|
|
361
|
+
status = adapter.health_check("a", {"server_id": 100})
|
|
362
|
+
assert status == AgentStatus.FAILED
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
# ---------------------------------------------------------------------------
|
|
366
|
+
# CloudProvider.provider_type property
|
|
367
|
+
# ---------------------------------------------------------------------------
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class TestCloudProviderProviderType:
|
|
371
|
+
def test_hetzner_provider_type(self):
|
|
372
|
+
provider = CloudProvider(cloud="hetzner")
|
|
373
|
+
assert provider.provider_type == ProviderType.HETZNER
|
|
374
|
+
|
|
375
|
+
def test_aws_provider_type(self):
|
|
376
|
+
provider = CloudProvider(cloud="aws")
|
|
377
|
+
assert provider.provider_type == ProviderType.AWS
|
|
378
|
+
|
|
379
|
+
def test_gcp_provider_type(self):
|
|
380
|
+
provider = CloudProvider(cloud="gcp")
|
|
381
|
+
assert provider.provider_type == ProviderType.GCP
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
# ---------------------------------------------------------------------------
|
|
385
|
+
# CloudProvider delegation to Hetzner
|
|
386
|
+
# ---------------------------------------------------------------------------
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class TestCloudProviderHetznerDelegation:
|
|
390
|
+
@pytest.fixture()
|
|
391
|
+
def provider(self):
|
|
392
|
+
return CloudProvider(cloud="hetzner", config={"api_token": "tok"})
|
|
393
|
+
|
|
394
|
+
def test_provision_delegates_to_hetzner(self, provider):
|
|
395
|
+
mock_result = {"server_id": 1, "host": "1.2.3.4", "container_id": "1"}
|
|
396
|
+
with patch.object(provider._adapter, "provision", return_value=mock_result):
|
|
397
|
+
result = provider.provision("a", _make_spec(), "t")
|
|
398
|
+
assert result == mock_result
|
|
399
|
+
|
|
400
|
+
def test_configure_delegates_to_hetzner(self, provider):
|
|
401
|
+
with patch.object(provider._adapter, "configure", return_value=True):
|
|
402
|
+
ok = provider.configure("a", _make_spec(), {})
|
|
403
|
+
assert ok is True
|
|
404
|
+
|
|
405
|
+
def test_start_delegates_to_hetzner(self, provider):
|
|
406
|
+
with patch.object(provider._adapter, "start", return_value=True):
|
|
407
|
+
ok = provider.start("a", {"server_id": 1})
|
|
408
|
+
assert ok is True
|
|
409
|
+
|
|
410
|
+
def test_stop_delegates_to_hetzner(self, provider):
|
|
411
|
+
with patch.object(provider._adapter, "stop", return_value=True):
|
|
412
|
+
ok = provider.stop("a", {"server_id": 1})
|
|
413
|
+
assert ok is True
|
|
414
|
+
|
|
415
|
+
def test_destroy_delegates_to_hetzner(self, provider):
|
|
416
|
+
with patch.object(provider._adapter, "destroy", return_value=True):
|
|
417
|
+
ok = provider.destroy("a", {"server_id": 1})
|
|
418
|
+
assert ok is True
|
|
419
|
+
|
|
420
|
+
def test_health_check_delegates_to_hetzner(self, provider):
|
|
421
|
+
with patch.object(
|
|
422
|
+
provider._adapter, "health_check", return_value=AgentStatus.RUNNING
|
|
423
|
+
):
|
|
424
|
+
status = provider.health_check("a", {"server_id": 1})
|
|
425
|
+
assert status == AgentStatus.RUNNING
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
# ---------------------------------------------------------------------------
|
|
429
|
+
# _build_cloud_init — Hetzner-specific tailscale authkey
|
|
430
|
+
# ---------------------------------------------------------------------------
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
class TestBuildCloudInitTailscale:
|
|
434
|
+
def test_tailscale_join_included_when_authkey_set(self):
|
|
435
|
+
spec = _make_spec()
|
|
436
|
+
ci = _build_cloud_init("agent", spec, tailscale_authkey="ts-key-abc")
|
|
437
|
+
assert "ts-key-abc" in ci
|
|
438
|
+
assert "tailscale up" in ci
|
|
439
|
+
|
|
440
|
+
def test_tailscale_join_omitted_when_no_authkey(self):
|
|
441
|
+
spec = _make_spec()
|
|
442
|
+
ci = _build_cloud_init("agent", spec, tailscale_authkey="")
|
|
443
|
+
assert "tailscale up" not in ci or "--authkey" not in ci
|
|
444
|
+
|
|
445
|
+
def test_tailscale_authkey_from_env(self, monkeypatch):
|
|
446
|
+
monkeypatch.setenv("TAILSCALE_AUTHKEY", "env-ts-key")
|
|
447
|
+
spec = _make_spec()
|
|
448
|
+
ci = _build_cloud_init("agent", spec)
|
|
449
|
+
assert "env-ts-key" in ci
|