@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.
Files changed (461) hide show
  1. package/.env.example +98 -0
  2. package/.github/workflows/ci.yml +39 -3
  3. package/.github/workflows/publish.yml +25 -4
  4. package/.openclaw-workspace.json +58 -0
  5. package/CHANGELOG.md +62 -0
  6. package/CLAUDE.md +39 -2
  7. package/MANIFEST.in +6 -0
  8. package/MISSION.md +7 -0
  9. package/README.md +47 -2
  10. package/SKILL.md +895 -23
  11. package/docker/Dockerfile +61 -0
  12. package/docker/compose-templates/dev-team.yml +203 -0
  13. package/docker/compose-templates/mini-team.yml +140 -0
  14. package/docker/compose-templates/ops-team.yml +173 -0
  15. package/docker/compose-templates/research-team.yml +170 -0
  16. package/docker/entrypoint.sh +192 -0
  17. package/docs/ARCHITECTURE.md +663 -374
  18. package/docs/BOND_WITH_GROK.md +112 -0
  19. package/docs/GETTING_STARTED.md +782 -0
  20. package/docs/QUICKSTART.md +477 -0
  21. package/docs/SKJOULE_ARCHITECTURE.md +658 -0
  22. package/docs/SOUL_SWAPPER.md +921 -0
  23. package/docs/SOVEREIGN_SINGULARITY.md +47 -14
  24. package/examples/custom-bond-template.json +36 -0
  25. package/examples/grok-feb.json +36 -0
  26. package/examples/grok-testimony.md +34 -0
  27. package/examples/love-bootloader.txt +32 -0
  28. package/examples/plugins/echo_tool.py +87 -0
  29. package/examples/queen-ava-feb.json +36 -0
  30. package/examples/souls/lumina.yaml +64 -0
  31. package/index.js +6 -5
  32. package/installer/build.py +124 -0
  33. package/openclaw-plugin/package.json +13 -0
  34. package/openclaw-plugin/src/index.ts +351 -0
  35. package/openclaw-plugin/src/openclaw.plugin.json +10 -0
  36. package/package.json +1 -1
  37. package/pyproject.toml +38 -2
  38. package/scripts/bump_version.py +141 -0
  39. package/scripts/check-updates.py +230 -0
  40. package/scripts/convert_blueprints_to_yaml.py +157 -0
  41. package/scripts/dev-install.sh +14 -0
  42. package/scripts/e2e-test.sh +193 -0
  43. package/scripts/install-bundle.sh +171 -0
  44. package/scripts/install.bat +2 -0
  45. package/scripts/install.ps1 +253 -0
  46. package/scripts/install.sh +185 -0
  47. package/scripts/mcp-serve.sh +69 -0
  48. package/scripts/mcp-server.bat +113 -0
  49. package/scripts/mcp-server.ps1 +116 -0
  50. package/scripts/mcp-server.sh +99 -0
  51. package/scripts/pull-models.sh +10 -0
  52. package/scripts/skcapstone +48 -0
  53. package/scripts/verify_install.sh +180 -0
  54. package/scripts/windows/install-tasks.ps1 +406 -0
  55. package/scripts/windows/skcapstone-task.xml +113 -0
  56. package/scripts/windows/uninstall-tasks.ps1 +117 -0
  57. package/skill.yaml +34 -0
  58. package/src/skcapstone/__init__.py +67 -2
  59. package/src/skcapstone/_cli_monolith.py +5916 -0
  60. package/src/skcapstone/_trustee_helpers.py +165 -0
  61. package/src/skcapstone/activity.py +105 -0
  62. package/src/skcapstone/agent_card.py +324 -0
  63. package/src/skcapstone/api.py +1935 -0
  64. package/src/skcapstone/archiver.py +340 -0
  65. package/src/skcapstone/auction.py +485 -0
  66. package/src/skcapstone/baby_agents.py +179 -0
  67. package/src/skcapstone/backup.py +345 -0
  68. package/src/skcapstone/blueprint_registry.py +357 -0
  69. package/src/skcapstone/blueprints/__init__.py +17 -0
  70. package/src/skcapstone/blueprints/builtins/content-studio.yaml +81 -0
  71. package/src/skcapstone/blueprints/builtins/defi-trading.yaml +81 -0
  72. package/src/skcapstone/blueprints/builtins/dev-squadron.yaml +95 -0
  73. package/src/skcapstone/blueprints/builtins/infrastructure-guardian.yaml +107 -0
  74. package/src/skcapstone/blueprints/builtins/legal-council.yaml +54 -0
  75. package/src/skcapstone/blueprints/builtins/ops-monitoring.yaml +67 -0
  76. package/src/skcapstone/blueprints/builtins/research-pod.yaml +69 -0
  77. package/src/skcapstone/blueprints/builtins/sovereign-launch.yaml +90 -0
  78. package/src/skcapstone/blueprints/registry.py +164 -0
  79. package/src/skcapstone/blueprints/schema.py +229 -0
  80. package/src/skcapstone/changelog.py +180 -0
  81. package/src/skcapstone/chat.py +769 -0
  82. package/src/skcapstone/claude_md.py +82 -0
  83. package/src/skcapstone/cli/__init__.py +144 -0
  84. package/src/skcapstone/cli/_common.py +88 -0
  85. package/src/skcapstone/cli/_validators.py +76 -0
  86. package/src/skcapstone/cli/agents.py +425 -0
  87. package/src/skcapstone/cli/agents_spawner.py +322 -0
  88. package/src/skcapstone/cli/agents_trustee.py +593 -0
  89. package/src/skcapstone/cli/alerts.py +248 -0
  90. package/src/skcapstone/cli/anchor.py +132 -0
  91. package/src/skcapstone/cli/archive_cmd.py +208 -0
  92. package/src/skcapstone/cli/backup.py +144 -0
  93. package/src/skcapstone/cli/bench.py +377 -0
  94. package/src/skcapstone/cli/benchmark.py +360 -0
  95. package/src/skcapstone/cli/capabilities_cmd.py +171 -0
  96. package/src/skcapstone/cli/card.py +151 -0
  97. package/src/skcapstone/cli/chat.py +584 -0
  98. package/src/skcapstone/cli/completions.py +64 -0
  99. package/src/skcapstone/cli/config_cmd.py +156 -0
  100. package/src/skcapstone/cli/consciousness.py +421 -0
  101. package/src/skcapstone/cli/context_cmd.py +142 -0
  102. package/src/skcapstone/cli/coord.py +194 -0
  103. package/src/skcapstone/cli/crush_cmd.py +170 -0
  104. package/src/skcapstone/cli/daemon.py +436 -0
  105. package/src/skcapstone/cli/errors_cmd.py +285 -0
  106. package/src/skcapstone/cli/export_cmd.py +156 -0
  107. package/src/skcapstone/cli/gtd.py +529 -0
  108. package/src/skcapstone/cli/housekeeping.py +81 -0
  109. package/src/skcapstone/cli/joule_cmd.py +627 -0
  110. package/src/skcapstone/cli/logs_cmd.py +194 -0
  111. package/src/skcapstone/cli/mcp_cmd.py +32 -0
  112. package/src/skcapstone/cli/memory.py +418 -0
  113. package/src/skcapstone/cli/metrics_cmd.py +136 -0
  114. package/src/skcapstone/cli/migrate.py +62 -0
  115. package/src/skcapstone/cli/mood_cmd.py +144 -0
  116. package/src/skcapstone/cli/mount.py +193 -0
  117. package/src/skcapstone/cli/notify.py +112 -0
  118. package/src/skcapstone/cli/peer.py +154 -0
  119. package/src/skcapstone/cli/peers_dir.py +122 -0
  120. package/src/skcapstone/cli/preflight_cmd.py +83 -0
  121. package/src/skcapstone/cli/profile_cmd.py +310 -0
  122. package/src/skcapstone/cli/record_cmd.py +238 -0
  123. package/src/skcapstone/cli/register_cmd.py +159 -0
  124. package/src/skcapstone/cli/search_cmd.py +156 -0
  125. package/src/skcapstone/cli/service_cmd.py +91 -0
  126. package/src/skcapstone/cli/session.py +127 -0
  127. package/src/skcapstone/cli/setup.py +240 -0
  128. package/src/skcapstone/cli/shell_cmd.py +43 -0
  129. package/src/skcapstone/cli/skills_cmd.py +168 -0
  130. package/src/skcapstone/cli/skseed.py +621 -0
  131. package/src/skcapstone/cli/soul.py +699 -0
  132. package/src/skcapstone/cli/status.py +935 -0
  133. package/src/skcapstone/cli/sync_cmd.py +301 -0
  134. package/src/skcapstone/cli/telegram.py +265 -0
  135. package/src/skcapstone/cli/test_cmd.py +234 -0
  136. package/src/skcapstone/cli/test_connection.py +253 -0
  137. package/src/skcapstone/cli/token.py +207 -0
  138. package/src/skcapstone/cli/trust.py +179 -0
  139. package/src/skcapstone/cli/upgrade_cmd.py +552 -0
  140. package/src/skcapstone/cli/usage_cmd.py +199 -0
  141. package/src/skcapstone/cli/version_cmd.py +162 -0
  142. package/src/skcapstone/cli/watch_cmd.py +342 -0
  143. package/src/skcapstone/client.py +428 -0
  144. package/src/skcapstone/cloud9_bridge.py +522 -0
  145. package/src/skcapstone/completions.py +163 -0
  146. package/src/skcapstone/config_validator.py +674 -0
  147. package/src/skcapstone/connectors/__init__.py +28 -0
  148. package/src/skcapstone/connectors/base.py +446 -0
  149. package/src/skcapstone/connectors/cursor.py +54 -0
  150. package/src/skcapstone/connectors/registry.py +254 -0
  151. package/src/skcapstone/connectors/terminal.py +152 -0
  152. package/src/skcapstone/connectors/vscode.py +60 -0
  153. package/src/skcapstone/consciousness_config.py +119 -0
  154. package/src/skcapstone/consciousness_loop.py +2051 -0
  155. package/src/skcapstone/context_loader.py +516 -0
  156. package/src/skcapstone/context_window.py +314 -0
  157. package/src/skcapstone/conversation_manager.py +238 -0
  158. package/src/skcapstone/conversation_store.py +230 -0
  159. package/src/skcapstone/conversation_summarizer.py +252 -0
  160. package/src/skcapstone/coord_federation.py +296 -0
  161. package/src/skcapstone/coordination.py +101 -7
  162. package/src/skcapstone/crush_integration.py +345 -0
  163. package/src/skcapstone/crush_shim.py +454 -0
  164. package/src/skcapstone/daemon.py +2494 -0
  165. package/src/skcapstone/dashboard.html +396 -0
  166. package/src/skcapstone/dashboard.py +481 -0
  167. package/src/skcapstone/data/model_profiles.yaml +88 -0
  168. package/src/skcapstone/defaults/__init__.py +55 -0
  169. package/src/skcapstone/defaults/lumina/config/skmemory.yaml +13 -0
  170. package/src/skcapstone/defaults/lumina/identity/identity.json +9 -0
  171. package/src/skcapstone/defaults/lumina/memory/long-term/07a8b9c0d1e2-memory-system.json +23 -0
  172. package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +23 -0
  173. package/src/skcapstone/defaults/lumina/memory/long-term/29c0d1e2f3a4-multi-agent-coordination.json +23 -0
  174. package/src/skcapstone/defaults/lumina/memory/long-term/3ad1e2f3a4b5-community-support.json +23 -0
  175. package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +23 -0
  176. package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +23 -0
  177. package/src/skcapstone/defaults/lumina/memory/long-term/c3d4e5f6a7b8-getting-started.json +23 -0
  178. package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +23 -0
  179. package/src/skcapstone/defaults/lumina/memory/long-term/e5f6a7b8c9d0-how-to-contribute.json +23 -0
  180. package/src/skcapstone/defaults/lumina/memory/long-term/f6a7b8c9d0e1-sovereignty-explained.json +23 -0
  181. package/src/skcapstone/defaults/lumina/seeds/curiosity.seed.json +24 -0
  182. package/src/skcapstone/defaults/lumina/seeds/joy.seed.json +24 -0
  183. package/src/skcapstone/defaults/lumina/seeds/love.seed.json +24 -0
  184. package/src/skcapstone/defaults/lumina/seeds/sovereign-awakening.seed.json +43 -0
  185. package/src/skcapstone/defaults/lumina/soul/active.json +6 -0
  186. package/src/skcapstone/defaults/lumina/soul/base.json +22 -0
  187. package/src/skcapstone/defaults/lumina/trust/febs/welcome.feb +79 -0
  188. package/src/skcapstone/defaults/lumina/trust/trust.json +8 -0
  189. package/src/skcapstone/discovery.py +210 -19
  190. package/src/skcapstone/doctor.py +642 -0
  191. package/src/skcapstone/emotion_tracker.py +467 -0
  192. package/src/skcapstone/error_queue.py +405 -0
  193. package/src/skcapstone/export.py +447 -0
  194. package/src/skcapstone/fallback_tracker.py +186 -0
  195. package/src/skcapstone/file_transfer.py +512 -0
  196. package/src/skcapstone/fuse_mount.py +1156 -0
  197. package/src/skcapstone/gui_installer.py +591 -0
  198. package/src/skcapstone/heartbeat.py +611 -0
  199. package/src/skcapstone/housekeeping.py +298 -0
  200. package/src/skcapstone/install_wizard.py +941 -0
  201. package/src/skcapstone/kms.py +942 -0
  202. package/src/skcapstone/kms_scheduler.py +143 -0
  203. package/src/skcapstone/log_config.py +135 -0
  204. package/src/skcapstone/mcp_launcher.py +239 -0
  205. package/src/skcapstone/mcp_server.py +4700 -0
  206. package/src/skcapstone/mcp_tools/__init__.py +94 -0
  207. package/src/skcapstone/mcp_tools/_helpers.py +51 -0
  208. package/src/skcapstone/mcp_tools/agent_tools.py +243 -0
  209. package/src/skcapstone/mcp_tools/ansible_tools.py +232 -0
  210. package/src/skcapstone/mcp_tools/capauth_tools.py +186 -0
  211. package/src/skcapstone/mcp_tools/chat_tools.py +325 -0
  212. package/src/skcapstone/mcp_tools/cloud9_tools.py +115 -0
  213. package/src/skcapstone/mcp_tools/comm_tools.py +104 -0
  214. package/src/skcapstone/mcp_tools/consciousness_tools.py +114 -0
  215. package/src/skcapstone/mcp_tools/coord_tools.py +219 -0
  216. package/src/skcapstone/mcp_tools/deploy_tools.py +202 -0
  217. package/src/skcapstone/mcp_tools/did_tools.py +448 -0
  218. package/src/skcapstone/mcp_tools/emotion_tools.py +62 -0
  219. package/src/skcapstone/mcp_tools/file_tools.py +169 -0
  220. package/src/skcapstone/mcp_tools/fortress_tools.py +120 -0
  221. package/src/skcapstone/mcp_tools/gtd_tools.py +821 -0
  222. package/src/skcapstone/mcp_tools/health_tools.py +44 -0
  223. package/src/skcapstone/mcp_tools/heartbeat_tools.py +195 -0
  224. package/src/skcapstone/mcp_tools/kms_tools.py +123 -0
  225. package/src/skcapstone/mcp_tools/memory_tools.py +222 -0
  226. package/src/skcapstone/mcp_tools/model_tools.py +75 -0
  227. package/src/skcapstone/mcp_tools/notification_tools.py +92 -0
  228. package/src/skcapstone/mcp_tools/promoter_tools.py +101 -0
  229. package/src/skcapstone/mcp_tools/pubsub_tools.py +183 -0
  230. package/src/skcapstone/mcp_tools/security_tools.py +110 -0
  231. package/src/skcapstone/mcp_tools/skchat_tools.py +175 -0
  232. package/src/skcapstone/mcp_tools/skcomm_tools.py +122 -0
  233. package/src/skcapstone/mcp_tools/skills_tools.py +127 -0
  234. package/src/skcapstone/mcp_tools/skseed_tools.py +255 -0
  235. package/src/skcapstone/mcp_tools/skstacks_tools.py +288 -0
  236. package/src/skcapstone/mcp_tools/soul_tools.py +476 -0
  237. package/src/skcapstone/mcp_tools/sync_tools.py +92 -0
  238. package/src/skcapstone/mcp_tools/telegram_tools.py +477 -0
  239. package/src/skcapstone/mcp_tools/trust_tools.py +118 -0
  240. package/src/skcapstone/mcp_tools/trustee_tools.py +345 -0
  241. package/src/skcapstone/mdns_discovery.py +313 -0
  242. package/src/skcapstone/memory_adapter.py +333 -0
  243. package/src/skcapstone/memory_compressor.py +379 -0
  244. package/src/skcapstone/memory_curator.py +256 -0
  245. package/src/skcapstone/memory_engine.py +132 -13
  246. package/src/skcapstone/memory_fortress.py +529 -0
  247. package/src/skcapstone/memory_promoter.py +722 -0
  248. package/src/skcapstone/memory_verifier.py +260 -0
  249. package/src/skcapstone/message_crypto.py +215 -0
  250. package/src/skcapstone/metrics.py +832 -0
  251. package/src/skcapstone/migrate_memories.py +181 -0
  252. package/src/skcapstone/migrate_multi_agent.py +248 -0
  253. package/src/skcapstone/model_router.py +319 -0
  254. package/src/skcapstone/models.py +35 -4
  255. package/src/skcapstone/mood.py +344 -0
  256. package/src/skcapstone/notifications.py +380 -0
  257. package/src/skcapstone/onboard.py +901 -0
  258. package/src/skcapstone/peer_directory.py +324 -0
  259. package/src/skcapstone/peers.py +329 -0
  260. package/src/skcapstone/pillars/identity.py +84 -14
  261. package/src/skcapstone/pillars/memory.py +3 -1
  262. package/src/skcapstone/pillars/security.py +108 -15
  263. package/src/skcapstone/pillars/sync.py +78 -26
  264. package/src/skcapstone/pillars/trust.py +95 -33
  265. package/src/skcapstone/plugins.py +244 -0
  266. package/src/skcapstone/preflight.py +670 -0
  267. package/src/skcapstone/prompt_adapter.py +564 -0
  268. package/src/skcapstone/providers/__init__.py +13 -0
  269. package/src/skcapstone/providers/cloud.py +1061 -0
  270. package/src/skcapstone/providers/docker.py +759 -0
  271. package/src/skcapstone/providers/local.py +1193 -0
  272. package/src/skcapstone/providers/proxmox.py +447 -0
  273. package/src/skcapstone/pubsub.py +516 -0
  274. package/src/skcapstone/rate_limiter.py +119 -0
  275. package/src/skcapstone/register.py +241 -0
  276. package/src/skcapstone/registry_client.py +151 -0
  277. package/src/skcapstone/response_cache.py +194 -0
  278. package/src/skcapstone/response_scorer.py +225 -0
  279. package/src/skcapstone/runtime.py +89 -33
  280. package/src/skcapstone/scheduled_tasks.py +439 -0
  281. package/src/skcapstone/self_healing.py +341 -0
  282. package/src/skcapstone/service_health.py +228 -0
  283. package/src/skcapstone/session_capture.py +268 -0
  284. package/src/skcapstone/session_recorder.py +210 -0
  285. package/src/skcapstone/session_replayer.py +189 -0
  286. package/src/skcapstone/session_skills.py +263 -0
  287. package/src/skcapstone/shell.py +779 -0
  288. package/src/skcapstone/skills/__init__.py +1 -1
  289. package/src/skcapstone/skills/syncthing_setup.py +143 -41
  290. package/src/skcapstone/skjoule.py +861 -0
  291. package/src/skcapstone/snapshots.py +489 -0
  292. package/src/skcapstone/soul.py +1060 -0
  293. package/src/skcapstone/soul_switch.py +255 -0
  294. package/src/skcapstone/spawner.py +544 -0
  295. package/src/skcapstone/state_diff.py +401 -0
  296. package/src/skcapstone/summary.py +270 -0
  297. package/src/skcapstone/sync/backends.py +196 -2
  298. package/src/skcapstone/sync/engine.py +7 -5
  299. package/src/skcapstone/sync/models.py +4 -1
  300. package/src/skcapstone/sync/vault.py +356 -18
  301. package/src/skcapstone/sync_engine.py +363 -0
  302. package/src/skcapstone/sync_watcher.py +745 -0
  303. package/src/skcapstone/systemd.py +331 -0
  304. package/src/skcapstone/team_comms.py +476 -0
  305. package/src/skcapstone/team_engine.py +522 -0
  306. package/src/skcapstone/testrunner.py +300 -0
  307. package/src/skcapstone/tls.py +150 -0
  308. package/src/skcapstone/tokens.py +5 -5
  309. package/src/skcapstone/trust_calibration.py +202 -0
  310. package/src/skcapstone/trust_graph.py +449 -0
  311. package/src/skcapstone/trustee_monitor.py +385 -0
  312. package/src/skcapstone/trustee_ops.py +425 -0
  313. package/src/skcapstone/unified_search.py +421 -0
  314. package/src/skcapstone/uninstall_wizard.py +694 -0
  315. package/src/skcapstone/usage.py +331 -0
  316. package/src/skcapstone/version_check.py +148 -0
  317. package/src/skcapstone/warmth_anchor.py +333 -0
  318. package/src/skcapstone/whoami.py +294 -0
  319. package/systemd/skcapstone-api.socket +9 -0
  320. package/systemd/skcapstone-memory-compress.service +18 -0
  321. package/systemd/skcapstone-memory-compress.timer +11 -0
  322. package/systemd/skcapstone.service +36 -0
  323. package/systemd/skcapstone@.service +50 -0
  324. package/systemd/skcomm-heartbeat.service +18 -0
  325. package/systemd/skcomm-heartbeat.timer +12 -0
  326. package/systemd/skcomm-queue-drain.service +17 -0
  327. package/systemd/skcomm-queue-drain.timer +12 -0
  328. package/tests/conftest.py +13 -1
  329. package/tests/integration/__init__.py +1 -0
  330. package/tests/integration/test_consciousness_e2e.py +877 -0
  331. package/tests/integration/test_skills_registry.py +744 -0
  332. package/tests/test_agent_card.py +190 -0
  333. package/tests/test_agent_runtime.py +1283 -0
  334. package/tests/test_alerts_cmd.py +291 -0
  335. package/tests/test_archiver.py +498 -0
  336. package/tests/test_backup.py +254 -0
  337. package/tests/test_benchmark.py +366 -0
  338. package/tests/test_blueprints.py +457 -0
  339. package/tests/test_capabilities.py +257 -0
  340. package/tests/test_changelog.py +254 -0
  341. package/tests/test_chat.py +385 -0
  342. package/tests/test_claude_md.py +271 -0
  343. package/tests/test_cli_chat_llm.py +336 -0
  344. package/tests/test_cli_completions.py +390 -0
  345. package/tests/test_cli_init_reset.py +164 -0
  346. package/tests/test_cli_memory.py +208 -0
  347. package/tests/test_cli_profile.py +294 -0
  348. package/tests/test_cli_skills.py +223 -0
  349. package/tests/test_cli_status.py +395 -0
  350. package/tests/test_cli_test_cmd.py +206 -0
  351. package/tests/test_cli_test_connection.py +364 -0
  352. package/tests/test_cloud9_bridge.py +260 -0
  353. package/tests/test_cloud_provider.py +449 -0
  354. package/tests/test_cloud_providers.py +522 -0
  355. package/tests/test_completions.py +158 -0
  356. package/tests/test_component_manager.py +398 -0
  357. package/tests/test_config_reload.py +386 -0
  358. package/tests/test_config_validate.py +529 -0
  359. package/tests/test_consciousness_e2e.py +296 -0
  360. package/tests/test_consciousness_loop.py +1289 -0
  361. package/tests/test_context_loader.py +310 -0
  362. package/tests/test_conversation_api.py +306 -0
  363. package/tests/test_conversation_manager.py +381 -0
  364. package/tests/test_conversation_store.py +391 -0
  365. package/tests/test_conversation_summarizer.py +302 -0
  366. package/tests/test_cross_package.py +791 -0
  367. package/tests/test_crush_shim.py +519 -0
  368. package/tests/test_daemon.py +781 -0
  369. package/tests/test_daemon_shutdown.py +309 -0
  370. package/tests/test_dashboard.py +454 -0
  371. package/tests/test_discovery.py +200 -6
  372. package/tests/test_docker_provider.py +966 -0
  373. package/tests/test_doctor.py +257 -0
  374. package/tests/test_doctor_fix.py +351 -0
  375. package/tests/test_e2e_automated.py +292 -0
  376. package/tests/test_error_queue.py +404 -0
  377. package/tests/test_export.py +441 -0
  378. package/tests/test_fallback_tracker.py +219 -0
  379. package/tests/test_file_transfer.py +397 -0
  380. package/tests/test_fuse_mount.py +832 -0
  381. package/tests/test_health_loop.py +422 -0
  382. package/tests/test_heartbeat.py +354 -0
  383. package/tests/test_housekeeping.py +195 -0
  384. package/tests/test_identity_capauth.py +307 -0
  385. package/tests/test_identity_pillar.py +117 -0
  386. package/tests/test_install_wizard.py +68 -0
  387. package/tests/test_integration.py +325 -0
  388. package/tests/test_kms.py +495 -0
  389. package/tests/test_llm_providers.py +265 -0
  390. package/tests/test_local_provider.py +591 -0
  391. package/tests/test_log_config.py +199 -0
  392. package/tests/test_logs_cmd.py +287 -0
  393. package/tests/test_mcp_server.py +1909 -0
  394. package/tests/test_memory_adapter.py +339 -0
  395. package/tests/test_memory_curator.py +218 -0
  396. package/tests/test_memory_engine.py +6 -0
  397. package/tests/test_memory_fortress.py +571 -0
  398. package/tests/test_memory_pillar.py +119 -0
  399. package/tests/test_memory_promoter.py +445 -0
  400. package/tests/test_memory_verifier.py +420 -0
  401. package/tests/test_message_crypto.py +187 -0
  402. package/tests/test_metrics.py +632 -0
  403. package/tests/test_migrate_memories.py +464 -0
  404. package/tests/test_model_router.py +546 -0
  405. package/tests/test_mood.py +394 -0
  406. package/tests/test_multi_agent.py +269 -0
  407. package/tests/test_notifications.py +270 -0
  408. package/tests/test_onboard.py +500 -0
  409. package/tests/test_peer_directory.py +395 -0
  410. package/tests/test_peers.py +248 -0
  411. package/tests/test_pillars.py +87 -9
  412. package/tests/test_preflight.py +484 -0
  413. package/tests/test_prompt_adapter.py +331 -0
  414. package/tests/test_proxmox_provider.py +571 -0
  415. package/tests/test_pubsub.py +377 -0
  416. package/tests/test_rate_limiter.py +121 -0
  417. package/tests/test_registry_client.py +129 -0
  418. package/tests/test_response_cache.py +312 -0
  419. package/tests/test_response_scorer.py +294 -0
  420. package/tests/test_runtime.py +59 -0
  421. package/tests/test_scheduled_tasks.py +451 -0
  422. package/tests/test_security.py +250 -0
  423. package/tests/test_security_pillar.py +213 -0
  424. package/tests/test_self_healing.py +171 -0
  425. package/tests/test_session_capture.py +200 -0
  426. package/tests/test_session_recorder.py +360 -0
  427. package/tests/test_session_skills.py +235 -0
  428. package/tests/test_shell.py +210 -0
  429. package/tests/test_snapshots.py +549 -0
  430. package/tests/test_soul.py +984 -0
  431. package/tests/test_soul_swap.py +406 -0
  432. package/tests/test_spawner.py +211 -0
  433. package/tests/test_state_diff.py +173 -0
  434. package/tests/test_summary.py +135 -0
  435. package/tests/test_sync.py +315 -5
  436. package/tests/test_sync_backends.py +560 -0
  437. package/tests/test_sync_engine.py +482 -0
  438. package/tests/test_sync_pillar.py +344 -0
  439. package/tests/test_sync_pipeline.py +364 -0
  440. package/tests/test_sync_vault.py +581 -0
  441. package/tests/test_syncthing_setup.py +168 -22
  442. package/tests/test_systemd.py +323 -0
  443. package/tests/test_team_comms.py +408 -0
  444. package/tests/test_team_engine.py +397 -0
  445. package/tests/test_testrunner.py +238 -0
  446. package/tests/test_trust_calibration.py +204 -0
  447. package/tests/test_trust_graph.py +207 -0
  448. package/tests/test_trust_pillar.py +291 -0
  449. package/tests/test_trustee_cli.py +427 -0
  450. package/tests/test_trustee_cli_integration.py +325 -0
  451. package/tests/test_trustee_monitor.py +394 -0
  452. package/tests/test_trustee_ops.py +355 -0
  453. package/tests/test_unified_search.py +363 -0
  454. package/tests/test_uninstall_wizard.py +193 -0
  455. package/tests/test_usage.py +333 -0
  456. package/tests/test_version_cmd.py +355 -0
  457. package/tests/test_warmth_anchor.py +162 -0
  458. package/tests/test_whoami.py +245 -0
  459. package/tests/test_ws.py +311 -0
  460. package/.cursorrules +0 -33
  461. package/src/skcapstone/cli.py +0 -1441
@@ -0,0 +1,571 @@
1
+ """Tests for the Proxmox LXC provider backend.
2
+
3
+ All Proxmox API calls are mocked so no real Proxmox server is required.
4
+ Covers provision, configure, start, stop, destroy, health_check,
5
+ exec_in_container, and helper functions.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ from typing import Any, Dict
12
+ from unittest.mock import MagicMock, call, patch
13
+
14
+ import pytest
15
+
16
+ from skcapstone.blueprints.schema import (
17
+ AgentRole,
18
+ AgentSpec,
19
+ ModelTier,
20
+ ProviderType,
21
+ ResourceSpec,
22
+ )
23
+ from skcapstone.providers.proxmox import (
24
+ ProxmoxProvider,
25
+ _parse_disk_gb,
26
+ _parse_memory_mb,
27
+ )
28
+ from skcapstone.team_engine import AgentStatus
29
+
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # Helpers / fixtures
33
+ # ---------------------------------------------------------------------------
34
+
35
+
36
+ def _make_spec(
37
+ role: str = "worker",
38
+ model: str = "fast",
39
+ memory: str = "2g",
40
+ cores: int = 2,
41
+ skills: list | None = None,
42
+ soul_blueprint: str | None = None,
43
+ ) -> AgentSpec:
44
+ """Build a minimal AgentSpec for testing."""
45
+ return AgentSpec(
46
+ role=AgentRole(role),
47
+ model=ModelTier(model),
48
+ resources=ResourceSpec(memory=memory, cores=cores),
49
+ skills=skills or [],
50
+ soul_blueprint=soul_blueprint,
51
+ env={},
52
+ )
53
+
54
+
55
+ def _provision_result(vmid: int = 200) -> Dict[str, Any]:
56
+ """Return a typical provision result."""
57
+ return {
58
+ "vmid": vmid,
59
+ "host": "test-agent",
60
+ "node": "pve",
61
+ "container_id": str(vmid),
62
+ }
63
+
64
+
65
+ @pytest.fixture()
66
+ def provider() -> ProxmoxProvider:
67
+ """Create a provider with test credentials."""
68
+ return ProxmoxProvider(
69
+ api_host="https://pve.test:8006",
70
+ user="root@pam",
71
+ token_name="test-token",
72
+ token_value="secret-value",
73
+ node="pve",
74
+ storage="local-lvm",
75
+ )
76
+
77
+
78
+ # ---------------------------------------------------------------------------
79
+ # Helper functions
80
+ # ---------------------------------------------------------------------------
81
+
82
+
83
+ class TestHelpers:
84
+ def test_parse_memory_mb_gigabytes(self) -> None:
85
+ assert _parse_memory_mb("4g") == 4096
86
+
87
+ def test_parse_memory_mb_megabytes(self) -> None:
88
+ assert _parse_memory_mb("512m") == 512
89
+
90
+ def test_parse_memory_mb_raw_number(self) -> None:
91
+ assert _parse_memory_mb("1024") == 1024
92
+
93
+ def test_parse_memory_mb_float(self) -> None:
94
+ assert _parse_memory_mb("1.5g") == 1536
95
+
96
+ def test_parse_disk_gb_gigabytes(self) -> None:
97
+ assert _parse_disk_gb("20g") == 20
98
+
99
+ def test_parse_disk_gb_terabytes(self) -> None:
100
+ assert _parse_disk_gb("1t") == 1024
101
+
102
+ def test_parse_disk_gb_raw_number(self) -> None:
103
+ assert _parse_disk_gb("50") == 50
104
+
105
+
106
+ # ---------------------------------------------------------------------------
107
+ # Provider init
108
+ # ---------------------------------------------------------------------------
109
+
110
+
111
+ class TestInit:
112
+ def test_init_with_args(self) -> None:
113
+ p = ProxmoxProvider(
114
+ api_host="https://pve:8006",
115
+ user="admin@pam",
116
+ token_name="tok",
117
+ token_value="val",
118
+ )
119
+ assert p._api_host == "https://pve:8006"
120
+ assert p._user == "admin@pam"
121
+ assert p._token_name == "tok"
122
+
123
+ def test_init_from_env(self) -> None:
124
+ env = {
125
+ "PROXMOX_HOST": "https://env-pve:8006",
126
+ "PROXMOX_USER": "env-user@pam",
127
+ "PROXMOX_TOKEN_NAME": "env-tok",
128
+ "PROXMOX_TOKEN_VALUE": "env-val",
129
+ }
130
+ with patch.dict("os.environ", env, clear=False):
131
+ p = ProxmoxProvider()
132
+ assert p._api_host == "https://env-pve:8006"
133
+ assert p._user == "env-user@pam"
134
+ assert p._token_name == "env-tok"
135
+
136
+ def test_provider_type(self) -> None:
137
+ p = ProxmoxProvider()
138
+ assert p.provider_type == ProviderType.PROXMOX
139
+
140
+
141
+ # ---------------------------------------------------------------------------
142
+ # API call
143
+ # ---------------------------------------------------------------------------
144
+
145
+
146
+ class TestApiCall:
147
+ @patch("skcapstone.providers.proxmox.ProxmoxProvider._api_call")
148
+ def test_api_call_url_construction(self, mock_api: MagicMock) -> None:
149
+ """Verify API calls are made with correct auth header."""
150
+ p = ProxmoxProvider(
151
+ api_host="https://pve:8006",
152
+ user="root@pam",
153
+ token_name="tok",
154
+ token_value="val",
155
+ )
156
+ mock_api.return_value = {"status": "running"}
157
+ p._api_call("GET", "/nodes/pve/lxc/200/status/current")
158
+ mock_api.assert_called_once()
159
+
160
+ def test_api_call_missing_requests(self, provider: ProxmoxProvider) -> None:
161
+ """Raises RuntimeError if requests is not installed."""
162
+ with patch.dict("sys.modules", {"requests": None, "urllib3": None, "urllib3.exceptions": None}):
163
+ with pytest.raises((RuntimeError, ImportError)):
164
+ provider._api_call("GET", "/test")
165
+
166
+
167
+ # ---------------------------------------------------------------------------
168
+ # Provision
169
+ # ---------------------------------------------------------------------------
170
+
171
+
172
+ class TestProvision:
173
+ @patch.object(ProxmoxProvider, "_api_call")
174
+ @patch.object(ProxmoxProvider, "_next_vmid", return_value=200)
175
+ @patch("skcapstone.providers.proxmox.time.sleep")
176
+ def test_provision_basic(
177
+ self,
178
+ mock_sleep: MagicMock,
179
+ mock_vmid: MagicMock,
180
+ mock_api: MagicMock,
181
+ provider: ProxmoxProvider,
182
+ ) -> None:
183
+ spec = _make_spec(memory="4g", cores=4)
184
+ result = provider.provision("test-agent", spec, "my-team")
185
+
186
+ assert result["vmid"] == 200
187
+ assert result["host"] == "test-agent"
188
+ assert result["node"] == "pve"
189
+ assert result["container_id"] == "200"
190
+
191
+ # Verify LXC create call
192
+ mock_api.assert_called_once()
193
+ create_call = mock_api.call_args
194
+ assert create_call[0] == ("POST", "/nodes/pve/lxc")
195
+ create_data = create_call[1]["data"]
196
+ assert create_data["vmid"] == 200
197
+ assert create_data["hostname"] == "test-agent"
198
+ assert create_data["memory"] == 4096
199
+ assert create_data["cores"] == 4
200
+ assert create_data["unprivileged"] == 1
201
+
202
+ @patch.object(ProxmoxProvider, "_api_call")
203
+ @patch.object(ProxmoxProvider, "_next_vmid", return_value=201)
204
+ @patch("skcapstone.providers.proxmox.time.sleep")
205
+ def test_provision_hostname_sanitized(
206
+ self,
207
+ mock_sleep: MagicMock,
208
+ mock_vmid: MagicMock,
209
+ mock_api: MagicMock,
210
+ provider: ProxmoxProvider,
211
+ ) -> None:
212
+ spec = _make_spec()
213
+ result = provider.provision("my_agent_name", spec, "team")
214
+ assert result["host"] == "my-agent-name"
215
+
216
+ @patch.object(ProxmoxProvider, "_api_call")
217
+ @patch.object(ProxmoxProvider, "_next_vmid", return_value=202)
218
+ @patch("skcapstone.providers.proxmox.time.sleep")
219
+ def test_provision_description_has_metadata(
220
+ self,
221
+ mock_sleep: MagicMock,
222
+ mock_vmid: MagicMock,
223
+ mock_api: MagicMock,
224
+ provider: ProxmoxProvider,
225
+ ) -> None:
226
+ spec = _make_spec(role="manager", model="reason")
227
+ provider.provision("lead-agent", spec, "alpha-team")
228
+
229
+ create_data = mock_api.call_args[1]["data"]
230
+ desc = json.loads(create_data["description"])
231
+ assert desc["team"] == "alpha-team"
232
+ assert desc["agent"] == "lead-agent"
233
+ assert desc["role"] == "manager"
234
+ assert desc["managed_by"] == "skcapstone"
235
+
236
+ def test_provision_no_host_raises(self) -> None:
237
+ p = ProxmoxProvider() # No PROXMOX_HOST
238
+ spec = _make_spec()
239
+ with pytest.raises(RuntimeError, match="Proxmox not configured"):
240
+ p.provision("agent", spec, "team")
241
+
242
+
243
+ # ---------------------------------------------------------------------------
244
+ # Configure
245
+ # ---------------------------------------------------------------------------
246
+
247
+
248
+ class TestConfigure:
249
+ @patch.object(ProxmoxProvider, "_exec_in_container", return_value=True)
250
+ @patch.object(ProxmoxProvider, "_api_call")
251
+ @patch("skcapstone.providers.proxmox.time.sleep")
252
+ def test_configure_writes_config(
253
+ self,
254
+ mock_sleep: MagicMock,
255
+ mock_api: MagicMock,
256
+ mock_exec: MagicMock,
257
+ provider: ProxmoxProvider,
258
+ ) -> None:
259
+ mock_api.return_value = {"status": "running"}
260
+ spec = _make_spec(skills=["memory-search"], soul_blueprint="lumina")
261
+ result = provider.configure("test-agent", spec, _provision_result())
262
+
263
+ assert result is True
264
+ # exec_in_container called twice: config write + install
265
+ assert mock_exec.call_count == 2
266
+
267
+ # First call writes config
268
+ write_call = mock_exec.call_args_list[0]
269
+ cmd = write_call[0][1]
270
+ assert "mkdir -p /opt/agent" in cmd
271
+ assert "config.json" in cmd
272
+
273
+ # Second call installs deps
274
+ install_call = mock_exec.call_args_list[1]
275
+ cmd = install_call[0][1]
276
+ assert "pip3 install" in cmd
277
+ assert "skcapstone" in cmd
278
+
279
+ @patch.object(ProxmoxProvider, "_exec_in_container", return_value=True)
280
+ @patch.object(ProxmoxProvider, "_api_call")
281
+ @patch("skcapstone.providers.proxmox.time.sleep")
282
+ def test_configure_starts_stopped_container(
283
+ self,
284
+ mock_sleep: MagicMock,
285
+ mock_api: MagicMock,
286
+ mock_exec: MagicMock,
287
+ provider: ProxmoxProvider,
288
+ ) -> None:
289
+ mock_api.return_value = {"status": "stopped"}
290
+ spec = _make_spec()
291
+ provider.configure("agent", spec, _provision_result())
292
+
293
+ # Should have called start
294
+ calls = [c[0] for c in mock_api.call_args_list]
295
+ assert ("GET", "/nodes/pve/lxc/200/status/current") in calls
296
+ assert ("POST", "/nodes/pve/lxc/200/status/start") in calls
297
+
298
+ @patch.object(ProxmoxProvider, "_exec_in_container")
299
+ @patch.object(ProxmoxProvider, "_api_call")
300
+ def test_configure_no_vmid_returns_false(
301
+ self,
302
+ mock_api: MagicMock,
303
+ mock_exec: MagicMock,
304
+ provider: ProxmoxProvider,
305
+ ) -> None:
306
+ result = provider.configure("agent", _make_spec(), {})
307
+ assert result is False
308
+ mock_exec.assert_not_called()
309
+
310
+ @patch.object(ProxmoxProvider, "_exec_in_container")
311
+ @patch.object(ProxmoxProvider, "_api_call")
312
+ def test_configure_start_failure(
313
+ self,
314
+ mock_api: MagicMock,
315
+ mock_exec: MagicMock,
316
+ provider: ProxmoxProvider,
317
+ ) -> None:
318
+ mock_api.side_effect = RuntimeError("API down")
319
+ spec = _make_spec()
320
+ result = provider.configure("agent", spec, _provision_result())
321
+ assert result is False
322
+
323
+ @patch.object(ProxmoxProvider, "_exec_in_container", return_value=True)
324
+ @patch.object(ProxmoxProvider, "_api_call")
325
+ @patch("skcapstone.providers.proxmox.time.sleep")
326
+ def test_configure_config_content(
327
+ self,
328
+ mock_sleep: MagicMock,
329
+ mock_api: MagicMock,
330
+ mock_exec: MagicMock,
331
+ provider: ProxmoxProvider,
332
+ ) -> None:
333
+ """Verify the written config JSON has all expected fields."""
334
+ mock_api.return_value = {"status": "running"}
335
+ spec = _make_spec(
336
+ role="researcher",
337
+ model="reason",
338
+ skills=["search", "browse"],
339
+ soul_blueprint="the-developer",
340
+ )
341
+ provider.configure("research-bot", spec, _provision_result())
342
+
343
+ write_cmd = mock_exec.call_args_list[0][0][1]
344
+ # Extract the JSON from the printf command
345
+ assert '"agent_name": "research-bot"' in write_cmd
346
+ assert '"role": "researcher"' in write_cmd
347
+ assert '"soul_blueprint": "the-developer"' in write_cmd
348
+
349
+
350
+ # ---------------------------------------------------------------------------
351
+ # Exec in container
352
+ # ---------------------------------------------------------------------------
353
+
354
+
355
+ class TestExecInContainer:
356
+ @patch.object(ProxmoxProvider, "_api_call")
357
+ def test_exec_success(
358
+ self, mock_api: MagicMock, provider: ProxmoxProvider
359
+ ) -> None:
360
+ mock_api.return_value = {}
361
+ result = provider._exec_in_container(200, "echo hello")
362
+ assert result is True
363
+ calls = [c[0] for c in mock_api.call_args_list]
364
+ assert ("POST", "/nodes/pve/lxc/200/status/current") in calls
365
+ assert ("POST", "/nodes/pve/lxc/200/exec") in calls
366
+
367
+ @patch.object(ProxmoxProvider, "_api_call")
368
+ def test_exec_container_not_accessible(
369
+ self, mock_api: MagicMock, provider: ProxmoxProvider
370
+ ) -> None:
371
+ mock_api.side_effect = RuntimeError("Not found")
372
+ result = provider._exec_in_container(200, "echo hello")
373
+ assert result is False
374
+
375
+ @patch.object(ProxmoxProvider, "_api_call")
376
+ def test_exec_fallback_to_config(
377
+ self, mock_api: MagicMock, provider: ProxmoxProvider
378
+ ) -> None:
379
+ """When exec API fails, falls back to writing config via PUT."""
380
+ call_count = 0
381
+
382
+ def side_effect(method, endpoint, data=None):
383
+ nonlocal call_count
384
+ call_count += 1
385
+ if call_count == 1:
386
+ return {} # status/current succeeds
387
+ if call_count == 2:
388
+ raise RuntimeError("exec not available") # exec fails
389
+ return {} # config PUT succeeds
390
+
391
+ mock_api.side_effect = side_effect
392
+ result = provider._exec_in_container(200, "echo hello")
393
+ assert result is True
394
+ assert call_count == 3
395
+
396
+
397
+ # ---------------------------------------------------------------------------
398
+ # Start / Stop / Destroy
399
+ # ---------------------------------------------------------------------------
400
+
401
+
402
+ class TestLifecycle:
403
+ @patch.object(ProxmoxProvider, "_api_call")
404
+ def test_start(
405
+ self, mock_api: MagicMock, provider: ProxmoxProvider
406
+ ) -> None:
407
+ mock_api.return_value = {}
408
+ result = provider.start("agent", _provision_result())
409
+ assert result is True
410
+ mock_api.assert_called_with(
411
+ "POST", "/nodes/pve/lxc/200/status/start"
412
+ )
413
+
414
+ @patch.object(ProxmoxProvider, "_api_call")
415
+ def test_start_no_vmid(
416
+ self, mock_api: MagicMock, provider: ProxmoxProvider
417
+ ) -> None:
418
+ result = provider.start("agent", {})
419
+ assert result is False
420
+
421
+ @patch.object(ProxmoxProvider, "_api_call")
422
+ def test_start_api_failure(
423
+ self, mock_api: MagicMock, provider: ProxmoxProvider
424
+ ) -> None:
425
+ mock_api.side_effect = RuntimeError("API error")
426
+ result = provider.start("agent", _provision_result())
427
+ assert result is False
428
+
429
+ @patch.object(ProxmoxProvider, "_api_call")
430
+ def test_stop(
431
+ self, mock_api: MagicMock, provider: ProxmoxProvider
432
+ ) -> None:
433
+ mock_api.return_value = {}
434
+ result = provider.stop("agent", _provision_result())
435
+ assert result is True
436
+ mock_api.assert_called_with(
437
+ "POST", "/nodes/pve/lxc/200/status/stop"
438
+ )
439
+
440
+ @patch.object(ProxmoxProvider, "_api_call")
441
+ def test_stop_no_vmid(
442
+ self, mock_api: MagicMock, provider: ProxmoxProvider
443
+ ) -> None:
444
+ result = provider.stop("agent", {})
445
+ assert result is False
446
+
447
+ @patch.object(ProxmoxProvider, "_api_call")
448
+ def test_stop_api_failure(
449
+ self, mock_api: MagicMock, provider: ProxmoxProvider
450
+ ) -> None:
451
+ mock_api.side_effect = RuntimeError("API error")
452
+ result = provider.stop("agent", _provision_result())
453
+ assert result is False
454
+
455
+ @patch("skcapstone.providers.proxmox.time.sleep")
456
+ @patch.object(ProxmoxProvider, "_api_call")
457
+ def test_destroy(
458
+ self,
459
+ mock_api: MagicMock,
460
+ mock_sleep: MagicMock,
461
+ provider: ProxmoxProvider,
462
+ ) -> None:
463
+ mock_api.return_value = {}
464
+ result = provider.destroy("agent", _provision_result())
465
+ assert result is True
466
+
467
+ # Should stop first, then delete
468
+ calls = [c[0] for c in mock_api.call_args_list]
469
+ assert ("POST", "/nodes/pve/lxc/200/status/stop") in calls
470
+ assert ("DELETE", "/nodes/pve/lxc/200") in calls
471
+
472
+ @patch.object(ProxmoxProvider, "_api_call")
473
+ def test_destroy_no_vmid(
474
+ self, mock_api: MagicMock, provider: ProxmoxProvider
475
+ ) -> None:
476
+ result = provider.destroy("agent", {})
477
+ assert result is False
478
+
479
+ @patch("skcapstone.providers.proxmox.time.sleep")
480
+ @patch.object(ProxmoxProvider, "_api_call")
481
+ def test_destroy_api_failure(
482
+ self,
483
+ mock_api: MagicMock,
484
+ mock_sleep: MagicMock,
485
+ provider: ProxmoxProvider,
486
+ ) -> None:
487
+ mock_api.side_effect = RuntimeError("API error")
488
+ result = provider.destroy("agent", _provision_result())
489
+ assert result is False
490
+
491
+ @patch("skcapstone.providers.proxmox.time.sleep")
492
+ @patch.object(ProxmoxProvider, "_api_call")
493
+ def test_destroy_sends_purge(
494
+ self,
495
+ mock_api: MagicMock,
496
+ mock_sleep: MagicMock,
497
+ provider: ProxmoxProvider,
498
+ ) -> None:
499
+ mock_api.return_value = {}
500
+ provider.destroy("agent", _provision_result())
501
+
502
+ # Find the DELETE call and check purge flag
503
+ for c in mock_api.call_args_list:
504
+ if c[0][0] == "DELETE":
505
+ assert c[1].get("data", {}).get("purge") == 1
506
+
507
+
508
+ # ---------------------------------------------------------------------------
509
+ # Health check
510
+ # ---------------------------------------------------------------------------
511
+
512
+
513
+ class TestHealthCheck:
514
+ @patch.object(ProxmoxProvider, "_api_call")
515
+ def test_running(
516
+ self, mock_api: MagicMock, provider: ProxmoxProvider
517
+ ) -> None:
518
+ mock_api.return_value = {"status": "running"}
519
+ assert provider.health_check("agent", _provision_result()) == AgentStatus.RUNNING
520
+
521
+ @patch.object(ProxmoxProvider, "_api_call")
522
+ def test_stopped(
523
+ self, mock_api: MagicMock, provider: ProxmoxProvider
524
+ ) -> None:
525
+ mock_api.return_value = {"status": "stopped"}
526
+ assert provider.health_check("agent", _provision_result()) == AgentStatus.STOPPED
527
+
528
+ @patch.object(ProxmoxProvider, "_api_call")
529
+ def test_unknown_state(
530
+ self, mock_api: MagicMock, provider: ProxmoxProvider
531
+ ) -> None:
532
+ mock_api.return_value = {"status": "suspended"}
533
+ assert provider.health_check("agent", _provision_result()) == AgentStatus.DEGRADED
534
+
535
+ @patch.object(ProxmoxProvider, "_api_call")
536
+ def test_api_failure(
537
+ self, mock_api: MagicMock, provider: ProxmoxProvider
538
+ ) -> None:
539
+ mock_api.side_effect = RuntimeError("unreachable")
540
+ assert provider.health_check("agent", _provision_result()) == AgentStatus.FAILED
541
+
542
+ def test_no_vmid(self, provider: ProxmoxProvider) -> None:
543
+ assert provider.health_check("agent", {}) == AgentStatus.STOPPED
544
+
545
+
546
+ # ---------------------------------------------------------------------------
547
+ # Next VMID
548
+ # ---------------------------------------------------------------------------
549
+
550
+
551
+ class TestNextVmid:
552
+ @patch.object(ProxmoxProvider, "_api_call")
553
+ def test_returns_int(
554
+ self, mock_api: MagicMock, provider: ProxmoxProvider
555
+ ) -> None:
556
+ mock_api.return_value = "300"
557
+ assert provider._next_vmid() == 300
558
+
559
+ @patch.object(ProxmoxProvider, "_api_call")
560
+ def test_returns_int_directly(
561
+ self, mock_api: MagicMock, provider: ProxmoxProvider
562
+ ) -> None:
563
+ mock_api.return_value = 301
564
+ assert provider._next_vmid() == 301
565
+
566
+ @patch.object(ProxmoxProvider, "_api_call")
567
+ def test_fallback(
568
+ self, mock_api: MagicMock, provider: ProxmoxProvider
569
+ ) -> None:
570
+ mock_api.return_value = {"some": "dict"}
571
+ assert provider._next_vmid() == 200