vmware-aiops 2.4.0__tar.gz → 2.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/.claude-plugin/plugin.json +1 -1
  2. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/.mcp.json +1 -1
  3. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/PKG-INFO +3 -3
  4. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/RELEASE_NOTES.md +79 -0
  5. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/openclaw.plugin.json +1 -1
  6. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/package.json +1 -1
  7. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/pyproject.toml +3 -3
  8. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/server.json +2 -2
  9. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/skills/vmware-aiops/SKILL.md +2 -2
  10. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/skills/vmware-aiops/references/setup-guide.md +5 -5
  11. vmware_aiops-2.4.2/tests/eval/regression/test_browse_ds_path_join.py +79 -0
  12. vmware_aiops-2.4.2/tests/eval/regression/test_browse_sees_every_file.py +70 -0
  13. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_cli_writes_guarded.py +3 -1
  14. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_code_review_fixes.py +3 -3
  15. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_daemon_calls_are_audited.py +8 -8
  16. vmware_aiops-2.4.2/tests/eval/regression/test_deploy_failures_are_failures.py +205 -0
  17. vmware_aiops-2.4.2/tests/eval/regression/test_error_hints_fit_the_surface.py +85 -0
  18. vmware_aiops-2.4.2/tests/eval/regression/test_image_registry_cross_process.py +134 -0
  19. vmware_aiops-2.4.2/tests/eval/regression/test_image_registry_legacy_rows.py +150 -0
  20. vmware_aiops-2.4.2/tests/eval/regression/test_image_registry_per_target.py +64 -0
  21. vmware_aiops-2.4.2/tests/eval/regression/test_linked_clone_failure_on_the_wire.py +71 -0
  22. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_list_envelope_conformance.py +1 -1
  23. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_login_without_vsphere_role.py +56 -0
  24. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_set_ttl_safety.py +12 -0
  25. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_snapshot_async.py +6 -1
  26. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_v1_5_25_blockers.py +8 -3
  27. vmware_aiops-2.4.2/tests/eval/regression/test_write_round_low_findings.py +104 -0
  28. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_cluster_ttl_plan_gate.py +2 -2
  29. vmware_aiops-2.4.2/tests/test_composite_tools_obey_policy.py +218 -0
  30. vmware_aiops-2.4.2/tests/test_filelock.py +81 -0
  31. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_gate_review_fixes.py +2 -2
  32. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_host_network_mgmt.py +10 -0
  33. vmware_aiops-2.4.2/tests/test_migrate_compatibility_check.py +405 -0
  34. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_plan_gate_narrow_fixes.py +3 -3
  35. vmware_aiops-2.4.2/tests/test_plan_steps_obey_policy.py +299 -0
  36. vmware_aiops-2.4.2/tests/test_power_off_and_preview_policy.py +581 -0
  37. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/uv.lock +10 -10
  38. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/__init__.py +1 -1
  39. vmware_aiops-2.4.2/vmware_aiops/_filelock.py +94 -0
  40. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/cli/cluster.py +7 -1
  41. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/cli/deploy.py +29 -2
  42. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/cli/vm.py +40 -4
  43. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/config.py +7 -2
  44. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/connection.py +39 -0
  45. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/mcp_server/_shared.py +42 -1
  46. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/mcp_server/tools/datastore.py +15 -3
  47. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/mcp_server/tools/deploy.py +24 -1
  48. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/mcp_server/tools/guest.py +23 -5
  49. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/mcp_server/tools/plan.py +70 -11
  50. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/mcp_server/tools/ttl.py +31 -5
  51. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/mcp_server/tools/vm.py +3 -2
  52. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/cluster_mgmt.py +4 -1
  53. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/datastore_browser.py +147 -24
  54. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/host_network_mgmt.py +16 -0
  55. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/inventory.py +4 -3
  56. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/ova_deploy.py +44 -11
  57. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/plan_executor.py +28 -6
  58. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/plan_gate.py +121 -9
  59. vmware_aiops-2.4.2/vmware_aiops/ops/step_policy.py +225 -0
  60. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/ttl.py +19 -3
  61. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/vm_deploy.py +86 -23
  62. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/vm_gate.py +129 -4
  63. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/vm_lifecycle.py +103 -38
  64. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/policy_roles.py +19 -3
  65. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/scanner/scheduler.py +46 -2
  66. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/.clawhubignore +0 -0
  67. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/.env.example +0 -0
  68. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  69. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  70. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  71. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  72. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/.github/dependabot.yml +0 -0
  73. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/.gitignore +0 -0
  74. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/Dockerfile +0 -0
  75. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/LICENSE +0 -0
  76. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/README-CN.md +0 -0
  77. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/README.md +0 -0
  78. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/SECURITY.md +0 -0
  79. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/config.example.yaml +0 -0
  80. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/docker-compose.yml +0 -0
  81. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/docs/integrations/continue.md +0 -0
  82. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/docs/integrations/cursor.md +0 -0
  83. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/docs/integrations/goose.md +0 -0
  84. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/docs/integrations/localcowork.md +0 -0
  85. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/docs/integrations/mcp-agent.md +0 -0
  86. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/docs/integrations/vscode-copilot.md +0 -0
  87. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/docs/plans/2026-03-09-plan-apply-design.md +0 -0
  88. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/examples/deploy.yaml +0 -0
  89. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/examples/mcp-configs/README.md +0 -0
  90. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/examples/mcp-configs/claude-code.json +0 -0
  91. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/examples/mcp-configs/continue.yaml +0 -0
  92. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/examples/mcp-configs/cursor.json +0 -0
  93. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/examples/mcp-configs/goose.json +0 -0
  94. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/examples/mcp-configs/localcowork.json +0 -0
  95. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/examples/mcp-configs/mcp-agent.yaml +0 -0
  96. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/examples/mcp-configs/vscode-copilot.json +0 -0
  97. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/examples/ollama-local-setup.md +0 -0
  98. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/skills/vmware-aiops/evals/evals.json +0 -0
  99. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/skills/vmware-aiops/references/agent-guardrails.md +0 -0
  100. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/skills/vmware-aiops/references/capabilities.md +0 -0
  101. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/skills/vmware-aiops/references/cli-reference.md +0 -0
  102. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/skills/vmware-aiops/references/investigation-protocol.md +0 -0
  103. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/smithery.yaml +0 -0
  104. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/__init__.py +0 -0
  105. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/conftest.py +0 -0
  106. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/__init__.py +0 -0
  107. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/capability/__init__.py +0 -0
  108. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/capability/_family.py +0 -0
  109. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/capability/_scores.json +0 -0
  110. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/capability/_scoring.py +0 -0
  111. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/capability/_skill.py +0 -0
  112. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/capability/conftest.py +0 -0
  113. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/capability/test_entity_reachability.py +0 -0
  114. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/capability/test_error_actionability.py +0 -0
  115. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/capability/test_tool_description_quality.py +0 -0
  116. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/capability/test_tool_manifest_budget.py +0 -0
  117. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/__init__.py +0 -0
  118. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/_diag_fakes.py +0 -0
  119. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/_pc_fakes.py +0 -0
  120. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_aiops_specific.py +0 -0
  121. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_alarm_health_scale_n1.py +0 -0
  122. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_atexit_handlers_do_not_accumulate.py +0 -0
  123. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_capability_grader.py +0 -0
  124. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_cli_destructive_guards.py +0 -0
  125. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_cluster_health_summary_delegation.py +0 -0
  126. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_config_path_resolution_is_shared.py +0 -0
  127. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_daemon_host_log_cycles.py +0 -0
  128. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_deploy_correctness.py +0 -0
  129. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_doctor_authenticates_every_target.py +0 -0
  130. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_documented_gates_match_the_registry.py +0 -0
  131. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_enum_params_match_implementation.py +0 -0
  132. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_env_secret_obfuscation.py +0 -0
  133. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_event_sets_match_real_class_names.py +0 -0
  134. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_events_are_read_newest_first.py +0 -0
  135. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_guest_download_writes_the_local_disk.py +0 -0
  136. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_guest_username_is_explicit.py +0 -0
  137. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_host_log_scan_reads_real_logs.py +0 -0
  138. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_host_paths_protected.py +0 -0
  139. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_instructions_name_targets.py +0 -0
  140. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_inventory_scale_issue31.py +0 -0
  141. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_list_plans_is_non_destructive.py +0 -0
  142. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_mcp_frame_marks_failures.py +0 -0
  143. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_ova_network_mapping.py +0 -0
  144. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_release_blockers.py +0 -0
  145. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_result_envelope.py +0 -0
  146. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_returned_failure_is_audited.py +0 -0
  147. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_session_probe_eviction.py +0 -0
  148. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_text_io_declares_encoding.py +0 -0
  149. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_tool_annotations_match_behaviour.py +0 -0
  150. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_unreachable_host_vmks.py +0 -0
  151. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/eval/regression/test_vim_attribute_conformance.py +0 -0
  152. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_access_info.py +0 -0
  153. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_cluster_mgmt_sanitize.py +0 -0
  154. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_concurrent_local_state.py +0 -0
  155. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_config.py +0 -0
  156. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_drs_rules.py +0 -0
  157. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_guest_tools_gate.py +0 -0
  158. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_init_and_onboarding.py +0 -0
  159. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_network_mgmt.py +0 -0
  160. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_no_destructive_ops.py +0 -0
  161. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_plan_rollback_final_fixes.py +0 -0
  162. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_policy_roles.py +0 -0
  163. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_reset_alarm_gate.py +0 -0
  164. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_results_name_their_target.py +0 -0
  165. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_safe_error_passthrough.py +0 -0
  166. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_session_pool.py +0 -0
  167. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_stop_signal_runs_atexit.py +0 -0
  168. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_vm_delete_blast_radius.py +0 -0
  169. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/tests/test_vm_tools_gate.py +0 -0
  170. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/_fsutil.py +0 -0
  171. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/access_requirements.py +0 -0
  172. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/cli/__init__.py +0 -0
  173. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/cli/_common.py +0 -0
  174. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/cli/_root.py +0 -0
  175. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/cli/access.py +0 -0
  176. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/cli/alarm.py +0 -0
  177. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/cli/doctor.py +0 -0
  178. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/cli/hub.py +0 -0
  179. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/cli/investigate.py +0 -0
  180. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/cli/mcp_config.py +0 -0
  181. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/cli/plan.py +0 -0
  182. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/cli/scan.py +0 -0
  183. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/cli/summary.py +0 -0
  184. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/doctor.py +0 -0
  185. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/init_wizard.py +0 -0
  186. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/mcp_server/__init__.py +0 -0
  187. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/mcp_server/__main__.py +0 -0
  188. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/mcp_server/server.py +0 -0
  189. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/mcp_server/tools/__init__.py +0 -0
  190. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/mcp_server/tools/access.py +0 -0
  191. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/mcp_server/tools/alarm.py +0 -0
  192. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/mcp_server/tools/cluster.py +0 -0
  193. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/mcp_server/tools/network.py +0 -0
  194. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/mcp_server/tools/summary.py +0 -0
  195. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/notify/__init__.py +0 -0
  196. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/notify/audit.py +0 -0
  197. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/notify/logger.py +0 -0
  198. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/notify/webhook.py +0 -0
  199. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/__init__.py +0 -0
  200. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/_paging.py +0 -0
  201. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/alarm_mgmt.py +0 -0
  202. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/cluster_gate.py +0 -0
  203. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/gate.py +0 -0
  204. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/guest_gate.py +0 -0
  205. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/guest_ops.py +0 -0
  206. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/health.py +0 -0
  207. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/iscsi_config.py +0 -0
  208. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/network_mgmt.py +0 -0
  209. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/planner.py +0 -0
  210. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/ops/vm_delete_gate.py +0 -0
  211. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/policy_environment.py +0 -0
  212. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/scanner/__init__.py +0 -0
  213. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/scanner/alarm_scanner.py +0 -0
  214. {vmware_aiops-2.4.0 → vmware_aiops-2.4.2}/vmware_aiops/scanner/log_scanner.py +0 -0
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "vmware-aiops",
4
4
  "displayName": "VMware AIops",
5
- "version": "2.4.0",
5
+ "version": "2.4.2",
6
6
  "description": "AI-powered VMware vCenter/ESXi VM lifecycle and deployment with 61 MCP tools.",
7
7
  "author": {
8
8
  "name": "Wei Zhou",
@@ -5,7 +5,7 @@
5
5
  "command": "uvx",
6
6
  "args": [
7
7
  "--from",
8
- "vmware-aiops==2.4.0",
8
+ "vmware-aiops==2.4.2",
9
9
  "vmware-aiops-mcp"
10
10
  ]
11
11
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vmware-aiops
3
- Version: 2.4.0
3
+ Version: 2.4.2
4
4
  Summary: VMware vCenter/ESXi AI-powered VM lifecycle and deployment tool
5
5
  Project-URL: Homepage, https://github.com/vmware-skills/VMware-AIops
6
6
  Project-URL: Repository, https://github.com/vmware-skills/VMware-AIops
@@ -12,13 +12,13 @@ License-File: LICENSE
12
12
  Requires-Python: >=3.10
13
13
  Requires-Dist: apscheduler<4.0,>=3.10
14
14
  Requires-Dist: httpx<1.0,>=0.27
15
- Requires-Dist: mcp[cli]<2.0,>=1.28
15
+ Requires-Dist: mcp[cli]<2.0,>=1.30
16
16
  Requires-Dist: pyaml<27.0,>=24.0
17
17
  Requires-Dist: python-dotenv<2.0,>=1.0
18
18
  Requires-Dist: pyvmomi<10.0,>=8.0.3.0
19
19
  Requires-Dist: rich<16.0,>=13.0
20
20
  Requires-Dist: typer<1.0,>=0.12
21
- Requires-Dist: vmware-monitor<3.0,>=2.3.1
21
+ Requires-Dist: vmware-monitor<3.0,>=2.4.2
22
22
  Requires-Dist: vmware-policy<2.0,>=1.23.1
23
23
  Description-Content-Type: text/markdown
24
24
 
@@ -1,3 +1,82 @@
1
+ ## v2.4.2 — a login refused for lack of a role says so; an unreadable host is not called unreachable (2026-10-19)
2
+
3
+ Needs vmware-monitor 2.4.2 (now the floor). Found on a vCenter 8.0.3 lab with two restricted SSO accounts
4
+ (follow-up to AIops#47).
5
+
6
+ * **A login vCenter refuses with `NoPermission` gets the role error, not the raw fault.** For an account with no
7
+ permission anywhere, vCenter refuses the login itself (`System.View` on the root folder), so the post-login
8
+ no-role check never ran and `doctor` and every tool showed the raw fault with a connectivity hint. It now raises
9
+ the no-role error: the "no vSphere role / Global Permissions" remedy only for a vCenter target and `System.View`;
10
+ an ESXi target or another privilege gets a neutral text naming the (sanitized) privilege. A wrong password
11
+ (`InvalidLogin`) is unchanged. Same text as vmware-monitor.
12
+ * **vmk tools and `cluster info` no longer call an unreadable host unreachable.** When the account cannot read a
13
+ host's connection state, `list_host_vmks` rows and the vmk write tools' refusal say the account cannot read the
14
+ host (`reachable: false` means "not read"), instead of advising a reconnect. `cluster info` colours the state
15
+ yellow, not red.
16
+ * **From vmware-monitor 2.4.2 (through `get_cluster_health_summary` / `get_cross_vcenter_attention`):** a host this
17
+ account cannot read is counted `hosts_unreadable` with a warning issue `host_unreadable`, not ranked as a
18
+ critical `host_down`.
19
+
20
+ ## Unreleased — follow-ups to the 2.4.1 review
21
+
22
+ * **Previews show policy refusals.** `restore_vm_clean_slate`, `provision_vm_guest` and
23
+ `run_vm_guest_command_with_output` were refused by `rules.yaml` only at `confirm=True`; the preview now lists that
24
+ refusal in `blast_radius.blockers`, so the user sees it before agreeing.
25
+ * **A delete that powers the VM off first is checked as `power_off_vm` too:** the CLI `vm delete`, the TTL daemon,
26
+ and a plan rollback deleting the VM it created (checked when the VM is running or its state cannot be read). The
27
+ TTL daemon records one `delete_vm` row per change of outcome, as for a deny on `delete_vm`, and keeps the entry.
28
+ **Behaviour change for custom network roles:** a role that allows `set_vm_ttl` must also allow `power_off_vm`.
29
+ * **Image cache:** writes take a lock across processes (a CLI and an MCP scan at the same time lost one side's
30
+ rows); a scan keeps rows on datastores it failed to browse (its own or legacy ones) and drops this target's rows
31
+ on datastores that no longer exist. A cache that cannot be written no longer fails the scan: the images come back
32
+ with a `cache_note`, as in vmware-storage.
33
+ * **Each call is checked for what it will do, at the moment it does it.** `restore_vm_clean_slate` asks about
34
+ `power_off_vm` only when it actually powers the VM off; `provision_vm_guest` asks about the guest tools its step
35
+ types use (an exec-only provision is not refused by a deny on `upload_vm_guest_file`); a plan rollback asks about
36
+ the power-off after confirming it is the VM the plan created, just before powering it off.
37
+ * **`set_vm_ttl` is checked as the deletion it schedules:** the preview lists a deny on `delete_vm` (and on
38
+ `power_off_vm` for a running VM), and `confirm=True` refuses it, instead of scheduling a TTL the daemon can never
39
+ act on. The daemon still asks again at expiry.
40
+ * **`migrate_vm` previews run vCenter's own vMotion compatibility check** (`CheckMigrate_Task`, read-only): its
41
+ errors (CPU features, devices, networks, datastores) become blockers, with the CPU-feature remedy where it applies,
42
+ so a move vCenter would refuse is refused in the preview and before the CLI asks for confirmation; warnings are
43
+ shown and do not block. On a standalone ESXi host, or when the check cannot run, the preview says "not checked"
44
+ and does not block (vCenter checks again when the move starts). Plan migrate steps get the same check.
45
+ * The TTL store takes the same cross-process lock as the image cache (`set_vm_ttl` and the daemon run in separate
46
+ processes).
47
+
48
+ ## v2.4.1 — fixes from the 2026-10-17 real-hardware read and write rounds
49
+
50
+ * **Tools that run other tools' operations obey `rules.yaml` for those tools**, on the MCP and CLI surfaces.
51
+ `restore_vm_clean_slate`, the batch tools, `provision_vm_guest`, and the deploy tools' `power_on` / snapshot /
52
+ CPU-memory follow-ups are refused, with a `denied` audit row, when the policy denies the tool that does that part
53
+ (a deny on `revert_vm_snapshot` used to leave clean slate working; a deny on `power_on_vm` did not stop
54
+ `batch_clone_vms(power_on=True)`). A follow-up is checked only when the call asks for it. `run_vm_guest_command_with_output` is
55
+ checked as `run_vm_guest_command` (and `download_vm_guest_file`) too.
56
+ * **Behaviour change for custom network roles:** a role check reads no arguments, so a role that allows
57
+ `deploy_vm_from_template`, `deploy_vm_from_ova` or `deploy_linked_clone` must now also allow `power_on_vm`,
58
+ `create_vm_snapshot` and `reconfigure_vm` (and one allowing `run_vm_guest_command_with_output` must allow
59
+ `run_vm_guest_command` and `download_vm_guest_file`), or every call is refused. The built-in `reader` and `admin`
60
+ roles are unaffected.
61
+ * **Plan steps obey `rules.yaml`.** Each step of `apply_vm_plan` / `rollback_vm_plan` is authorized by vmware-policy
62
+ under the tool that does the same thing (a `delete_vm` step as `delete_vm`, `power_off` as `power_off_vm`, the
63
+ rollback that deletes a VM the plan created as `delete_vm`), at that tool's risk level — in the preview and again
64
+ just before the step runs, with a `denied` audit row. A deny on `delete_vm` used to stop the tool but not a plan step.
65
+ * **Failures are failures.** Deploy, clone, linked clone, template conversion, ISO attach, `create_vm`, `migrate_vm`,
66
+ `reconfigure_vm` and `configure_cluster` with nothing to change returned their refusals as status strings, which
67
+ the MCP frame and the audit recorded as success; they now raise. A plan that stops part-way, a rollback that leaves
68
+ a step not undone, and a plan that fails validation now carry the family `error` key.
69
+ * **`deploy_vm_from_template` works on a vCenter with standalone hosts**: the clone gets the template's resource
70
+ pool (it failed with `spec.location.pool`), and the tool now takes `target_host`.
71
+ * **`scan_all_datastore_images` keeps each target's images**: the cache was one list replaced by every scan, so
72
+ scanning `datastore1` on an ESXi host erased what a vCenter scan had found. Entries now carry `target`; a corrupt
73
+ cache file is rebuilt by the next scan and explained by the error instead of a bare JSON error.
74
+ * **`browse_datastore` sees every file** (`.ova`, `.ovf`, `.vmx`, `.log` were filtered out), `ds_path` is well formed
75
+ for ESXi and vCenter folder paths, and a leading `/` is refused with the corrected path, as vmware-storage does.
76
+ * Error text names MCP tools and parameters (CLI form in brackets); doctor is suggested only for connectivity errors;
77
+ a CPU-feature vMotion refusal leads with the remedy; `get_vm_task_status` no longer says an unknown id means the
78
+ task finished; powering off a VM that is already off is a plain `noop`.
79
+
1
80
  ## v2.4.0 — roles for network callers (HLD §18)
2
81
 
3
82
  * Declares the role metadata vmware-policy ≥ 1.24.0 needs to apply **roles** to this server over HTTPS
@@ -11,7 +11,7 @@
11
11
  "command": "uvx",
12
12
  "args": [
13
13
  "--from",
14
- "vmware-aiops==2.4.0",
14
+ "vmware-aiops==2.4.2",
15
15
  "vmware-aiops-mcp"
16
16
  ]
17
17
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zw008/vmware-aiops",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "description": "AI-powered VMware vCenter/ESXi VM lifecycle and deployment with 61 MCP tools.",
5
5
  "private": true,
6
6
  "license": "MIT",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "vmware-aiops"
7
- version = "2.4.0"
7
+ version = "2.4.2"
8
8
  description = "VMware vCenter/ESXi AI-powered VM lifecycle and deployment tool"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -18,12 +18,12 @@ dependencies = [
18
18
  "apscheduler>=3.10,<4.0",
19
19
  "httpx>=0.27,<1.0",
20
20
  "python-dotenv>=1.0,<2.0",
21
- "mcp[cli]>=1.28,<2.0",
21
+ "mcp[cli]>=1.30,<2.0",
22
22
  "vmware-policy>=1.23.1,<2.0",
23
23
  # Read-only cluster-health triage (summary + HTML snapshot) is owned by
24
24
  # vmware-monitor; AIops delegates to it so a conversation with the AIops
25
25
  # entry point can produce the same report without duplicating the code.
26
- "vmware-monitor>=2.3.1,<3.0", # attention status floor + role-less login (AIops#47)
26
+ "vmware-monitor>=2.4.2,<3.0", # unreadable host != down, login refused -> role error
27
27
  ]
28
28
 
29
29
 
@@ -7,12 +7,12 @@
7
7
  "url": "https://github.com/vmware-skills/VMware-AIops",
8
8
  "source": "github"
9
9
  },
10
- "version": "2.4.0",
10
+ "version": "2.4.2",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "pypi",
14
14
  "identifier": "vmware-aiops",
15
- "version": "2.4.0",
15
+ "version": "2.4.2",
16
16
  "runtimeHint": "uvx",
17
17
  "packageArguments": [
18
18
  {
@@ -63,7 +63,7 @@ Read before connecting an agent. Per-tool inventory: `references/capabilities.md
63
63
  ## Quick Install
64
64
 
65
65
  ```bash
66
- uv tool install vmware-aiops==2.4.0
66
+ uv tool install vmware-aiops==2.4.2
67
67
  vmware-aiops doctor
68
68
  vmware-aiops hub status # see which family members are installed
69
69
  ```
@@ -303,7 +303,7 @@ The account has no vSphere role. Assign one (Read-Only or higher) on the vCenter
303
303
  ## Setup
304
304
 
305
305
  ```bash
306
- uv tool install vmware-aiops==2.4.0
306
+ uv tool install vmware-aiops==2.4.2
307
307
  mkdir -p ~/.vmware-aiops
308
308
  vmware-aiops init # generates config.yaml and .env templates
309
309
  chmod 600 ~/.vmware-aiops/.env
@@ -6,13 +6,13 @@ All install methods fetch from the same source: [github.com/vmware-skills/VMware
6
6
 
7
7
  ```bash
8
8
  # Via PyPI (recommended for version pinning)
9
- uv tool install vmware-aiops==2.4.0
9
+ uv tool install vmware-aiops==2.4.2
10
10
 
11
11
  # Via Skills.sh (fetches from GitHub)
12
- npx skills add vmware-skills/VMware-AIops#v2.4.0
12
+ npx skills add vmware-skills/VMware-AIops#v2.4.2
13
13
 
14
14
  # Via ClawHub (fetches from ClawHub registry snapshot of GitHub)
15
- clawhub install @zw008/vmware-aiops --version 2.4.0
15
+ clawhub install @zw008/vmware-aiops --version 2.4.2
16
16
  ```
17
17
 
18
18
  ### Claude Code
@@ -35,7 +35,7 @@ claude mcp add vmware-aiops -- vmware-aiops mcp
35
35
 
36
36
  ```bash
37
37
  # 1. Install from PyPI (source: github.com/vmware-skills/VMware-AIops)
38
- uv tool install vmware-aiops==2.4.0
38
+ uv tool install vmware-aiops==2.4.2
39
39
 
40
40
  # 2. Verify installation source
41
41
  vmware-aiops --version # confirms installed version
@@ -73,7 +73,7 @@ The `vmware-aiops` package installs a Python CLI binary and its dependencies (py
73
73
  ## Development Install
74
74
 
75
75
  ```bash
76
- git clone --branch v2.4.0 https://github.com/vmware-skills/VMware-AIops.git
76
+ git clone --branch v2.4.2 https://github.com/vmware-skills/VMware-AIops.git
77
77
  cd VMware-AIops
78
78
  uv venv && source .venv/bin/activate
79
79
  # --no-sources: pyproject's [tool.uv.sources] points vmware-monitor at a sibling
@@ -0,0 +1,79 @@
1
+ """browse_datastore must return a ds_path that deploy/attach tools can use.
2
+
3
+ Regression source: real ESXi 8 host (home-esxi, 2026-10-17). The browse task
4
+ reports each folder's ``folderPath`` with no trailing separator —
5
+ ``"[datastore1]"`` for the root, ``"[datastore1] backup"`` for a folder —
6
+ and the old code glued the file name straight on, producing
7
+ ``"[datastore1] backupVMware-VCSA.iso"`` and ``"[datastore1]Hermers - TT"``.
8
+ Agents pass that string into deploy_vm_from_ova / attach_iso_to_vm, so a
9
+ malformed path breaks the very flow the browse exists for. The earlier tests
10
+ mocked ``"[ds1] images/"`` (trailing slash), the one shape that hid the bug.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from types import SimpleNamespace
15
+
16
+ import pytest
17
+ from pyVmomi import vim
18
+
19
+ from vmware_aiops.ops import datastore_browser as dsb
20
+
21
+
22
+ @pytest.mark.unit
23
+ @pytest.mark.parametrize(
24
+ ("folder", "name", "expected"),
25
+ [
26
+ # Root, as ESXi reports it: no space after the bracket.
27
+ ("[datastore1]", "Hermers - TT", "[datastore1] Hermers - TT"),
28
+ ("[datastore1]", "x.iso", "[datastore1] x.iso"),
29
+ # Root, as vCenter reports it: trailing space.
30
+ ("[datastore1] ", "x.iso", "[datastore1] x.iso"),
31
+ # Folder without a trailing slash (ESXi).
32
+ ("[datastore1] backup", "x.iso", "[datastore1] backup/x.iso"),
33
+ (
34
+ "[datastore1] Hermers - TT",
35
+ "Hermers - TT.vmdk",
36
+ "[datastore1] Hermers - TT/Hermers - TT.vmdk",
37
+ ),
38
+ ("[datastore1] a/b", "x.iso", "[datastore1] a/b/x.iso"),
39
+ # Folder with a trailing slash (vCenter) — must not double it.
40
+ ("[datastore1] backup/", "x.iso", "[datastore1] backup/x.iso"),
41
+ ],
42
+ )
43
+ def test_join_ds_path(folder, name, expected):
44
+ assert dsb._join_ds_path(folder, name) == expected
45
+
46
+
47
+ @pytest.mark.unit
48
+ def test_browse_root_returns_well_formed_paths_for_every_folder(monkeypatch):
49
+ """Root browse is recursive: subfolder rows come back too, each well formed."""
50
+ def _file(path):
51
+ return SimpleNamespace(path=path, fileSize=1024 * 1024, modification="2026-10-17")
52
+
53
+ results = [
54
+ SimpleNamespace(folderPath="[datastore1]", file=[_file("backup")]),
55
+ SimpleNamespace(folderPath="[datastore1] backup", file=[_file("x.iso")]),
56
+ ]
57
+ seen = {}
58
+
59
+ def fake_search(datastorePath, searchSpec): # noqa: N803 — pyVmomi API names
60
+ seen["path"] = datastorePath
61
+ return SimpleNamespace(
62
+ info=SimpleNamespace(state=vim.TaskInfo.State.success, result=results, error=None)
63
+ )
64
+
65
+ monkeypatch.setattr(
66
+ dsb,
67
+ "find_datastore_by_name",
68
+ lambda si, name: SimpleNamespace(
69
+ browser=SimpleNamespace(SearchDatastoreSubFolders_Task=fake_search)
70
+ ),
71
+ )
72
+
73
+ rows = dsb.browse_datastore(object(), "datastore1")["items"]
74
+
75
+ assert seen["path"] == "[datastore1]"
76
+ assert {r["name"]: r["ds_path"] for r in rows} == {
77
+ "backup": "[datastore1] backup",
78
+ "x.iso": "[datastore1] backup/x.iso",
79
+ }
@@ -0,0 +1,70 @@
1
+ """browse_datastore must see every file, and treat a leading '/' one way.
2
+
3
+ Real hardware, 2026-10-17: vmware-aiops' search spec listed only IsoImageQuery,
4
+ VmDiskQuery and FolderQuery under a comment saying "include all file types".
5
+ A DatastoreBrowser query is a filter, not a hint -- with those three, .ova /
6
+ .ovf / .vmx / .log files are never returned. On the lab vCenter it found 0 of
7
+ the 2 OVAs on 'datastore1 (1)' that vmware-storage (which also passes the
8
+ generic Query) found, so scan_all_datastore_images could not do the one job its
9
+ description gives it: finding an OVA for deploy_vm_from_ova.
10
+
11
+ The twin also drifted on a leading slash: vmware-aiops passed '/vcsa8' through,
12
+ vmware-storage refused it as "absolute" (its 1.5.35 hardening). Both now refuse
13
+ it, and the error names the corrected path.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ from types import SimpleNamespace
18
+
19
+ import pytest
20
+ from pyVmomi import vim
21
+
22
+ from vmware_aiops.ops import datastore_browser as dsb
23
+
24
+
25
+ def _browse(monkeypatch, path=""):
26
+ seen: dict = {}
27
+
28
+ def fake_search(datastorePath, searchSpec): # noqa: N803 -- pyVmomi API names
29
+ seen["path"] = datastorePath
30
+ seen["spec"] = searchSpec
31
+ return SimpleNamespace(
32
+ info=SimpleNamespace(state=vim.TaskInfo.State.success, result=[], error=None)
33
+ )
34
+
35
+ monkeypatch.setattr(
36
+ dsb,
37
+ "find_datastore_by_name",
38
+ lambda si, name: SimpleNamespace(
39
+ browser=SimpleNamespace(SearchDatastoreSubFolders_Task=fake_search)
40
+ ),
41
+ )
42
+ dsb.browse_datastore(object(), "datastore1", path=path, pattern="*.ova")
43
+ return seen
44
+
45
+
46
+ @pytest.mark.unit
47
+ def test_search_spec_includes_the_generic_file_query(monkeypatch) -> None:
48
+ spec = _browse(monkeypatch)["spec"]
49
+ # Exact type: every specific query subclasses Query, so isinstance would pass
50
+ # for the three that hid the OVAs.
51
+ assert any(type(q) is vim.host.DatastoreBrowser.Query for q in spec.query)
52
+
53
+
54
+ @pytest.mark.unit
55
+ def test_relative_path_is_joined_under_the_datastore(monkeypatch) -> None:
56
+ assert _browse(monkeypatch, "vcsa8")["path"] == "[datastore1] vcsa8"
57
+
58
+
59
+ @pytest.mark.unit
60
+ @pytest.mark.parametrize("path", ["/vcsa8", "//vcsa8"])
61
+ def test_leading_slash_is_refused_with_the_fix(monkeypatch, path) -> None:
62
+ with pytest.raises(ValueError, match="path='vcsa8'"):
63
+ _browse(monkeypatch, path)
64
+
65
+
66
+ @pytest.mark.unit
67
+ @pytest.mark.parametrize("path", ["../other", "a/../../b", "x\x00y", "\\\\server\\share", "/"])
68
+ def test_traversal_and_odd_paths_are_refused(monkeypatch, path) -> None:
69
+ with pytest.raises(ValueError, match="datastore-relative"):
70
+ _browse(monkeypatch, path)
@@ -51,7 +51,9 @@ def _ops_refs(tree: ast.AST) -> tuple[dict[str, str], set[str]]:
51
51
  for n in ast.walk(tree):
52
52
  if isinstance(n, ast.ImportFrom) and n.module:
53
53
  parts = n.module.split(".")
54
- if "ops" in parts:
54
+ # step_policy authorizes the operation; it is not the operation, and
55
+ # every composite command calls it (2026-10-17).
56
+ if "ops" in parts and parts[-1] != "step_policy":
55
57
  if parts[-1] == "ops":
56
58
  mods.update(a.asname or a.name for a in n.names)
57
59
  else:
@@ -127,7 +127,7 @@ def test_ttl_entry_kept_on_transient_failure(monkeypatch: pytest.MonkeyPatch) ->
127
127
  monkeypatch.setattr(scheduler, "remove_entry", removed.append)
128
128
  monkeypatch.setattr(
129
129
  scheduler, "delete_vm",
130
- lambda si, name: (_ for _ in ()).throw(RuntimeError("vCenter task timeout")),
130
+ lambda si, name, **k: (_ for _ in ()).throw(RuntimeError("vCenter task timeout")),
131
131
  )
132
132
 
133
133
  scheduler._run_ttl_check(MagicMock())
@@ -143,7 +143,7 @@ def test_ttl_entry_removed_when_vm_already_gone(monkeypatch: pytest.MonkeyPatch)
143
143
  monkeypatch.setattr(scheduler, "remove_entry", removed.append)
144
144
  monkeypatch.setattr(
145
145
  scheduler, "delete_vm",
146
- lambda si, name: (_ for _ in ()).throw(VMNotFoundError("VM 'ttl-vm' not found")),
146
+ lambda si, name, **k: (_ for _ in ()).throw(VMNotFoundError("VM 'ttl-vm' not found")),
147
147
  )
148
148
 
149
149
  scheduler._run_ttl_check(MagicMock())
@@ -156,7 +156,7 @@ def test_ttl_entry_removed_on_success(monkeypatch: pytest.MonkeyPatch) -> None:
156
156
  removed: list[str] = []
157
157
  monkeypatch.setattr(scheduler, "get_expired_entries", lambda: [_ttl_entry()])
158
158
  monkeypatch.setattr(scheduler, "remove_entry", removed.append)
159
- monkeypatch.setattr(scheduler, "delete_vm", lambda si, name: f"VM '{name}' deleted")
159
+ monkeypatch.setattr(scheduler, "delete_vm", lambda si, name, **k: f"VM '{name}' deleted")
160
160
 
161
161
  scheduler._run_ttl_check(MagicMock())
162
162
  assert removed == ["ttl-vm"]
@@ -71,7 +71,7 @@ def ttl(monkeypatch):
71
71
 
72
72
  def test_a_ttl_delete_writes_one_vm_delete_row(ttl, monkeypatch):
73
73
  scheduler, removed = ttl
74
- monkeypatch.setattr(scheduler, "delete_vm", lambda si, name: f"VM '{name}' deleted")
74
+ monkeypatch.setattr(scheduler, "delete_vm", lambda si, name, **k: f"VM '{name}' deleted")
75
75
 
76
76
  scheduler._run_ttl_check(MagicMock())
77
77
 
@@ -88,7 +88,7 @@ def test_a_ttl_delete_writes_one_vm_delete_row(ttl, monkeypatch):
88
88
  def test_a_failed_ttl_delete_is_recorded_as_error_and_the_entry_kept(ttl, monkeypatch):
89
89
  scheduler, removed = ttl
90
90
 
91
- def boom(si, name):
91
+ def boom(si, name, **k):
92
92
  raise RuntimeError("vCenter task timeout")
93
93
 
94
94
  monkeypatch.setattr(scheduler, "delete_vm", boom)
@@ -106,7 +106,7 @@ def test_a_ttl_vm_already_gone_is_recorded_and_the_stale_entry_dropped(ttl, monk
106
106
 
107
107
  scheduler, removed = ttl
108
108
 
109
- def gone(si, name):
109
+ def gone(si, name, **k):
110
110
  raise VMNotFoundError(f"VM '{name}' not found")
111
111
 
112
112
  monkeypatch.setattr(scheduler, "delete_vm", gone)
@@ -122,7 +122,7 @@ def test_a_ttl_vm_already_gone_is_recorded_and_the_stale_entry_dropped(ttl, monk
122
122
  def test_a_ttl_delete_refused_by_policy_never_runs(ttl, monkeypatch):
123
123
  scheduler, removed = ttl
124
124
  deleted: list[str] = []
125
- monkeypatch.setattr(scheduler, "delete_vm", lambda si, name: deleted.append(name))
125
+ monkeypatch.setattr(scheduler, "delete_vm", lambda si, name, **k: deleted.append(name))
126
126
 
127
127
  def deny(skill, tool, params=None, *, risk_level="low", target=""):
128
128
  raise PolicyDenied(
@@ -144,7 +144,7 @@ def test_a_refused_ttl_entry_writes_one_row_per_outcome_not_one_per_minute(ttl,
144
144
  failing entry wrote a delete_vm row on every run — 1,440 a day per entry, on
145
145
  this lab whose rules.yaml denies delete_vm — burying real calls."""
146
146
  scheduler, removed = ttl
147
- monkeypatch.setattr(scheduler, "delete_vm", lambda si, name: None)
147
+ monkeypatch.setattr(scheduler, "delete_vm", lambda si, name, **k: None)
148
148
 
149
149
  def deny(skill, tool, params=None, *, risk_level="low", target=""):
150
150
  raise PolicyDenied(
@@ -165,7 +165,7 @@ def test_a_failing_ttl_delete_writes_again_only_when_its_outcome_changes(ttl, mo
165
165
  scheduler, removed = ttl
166
166
  attempts: list[str] = []
167
167
 
168
- def flaky(si, name):
168
+ def flaky(si, name, **k):
169
169
  attempts.append(name)
170
170
  if len(attempts) <= 3:
171
171
  raise RuntimeError("vCenter task timeout")
@@ -315,7 +315,7 @@ def test_a_ttl_vm_whose_name_is_ambiguous_is_not_deleted_and_the_entry_dropped(t
315
315
 
316
316
  scheduler, removed = ttl
317
317
 
318
- def ambiguous(si, name):
318
+ def ambiguous(si, name, **k):
319
319
  raise AmbiguousVMError(f"2 VMs are named '{name}'.")
320
320
 
321
321
  monkeypatch.setattr(scheduler, "delete_vm", ambiguous)
@@ -332,7 +332,7 @@ def test_daemon_rows_say_daemon_not_mcp(ttl, monkeypatch):
332
332
  """HLD §16 D-14: a TTL delete made by the daemon is not an MCP call. Without
333
333
  an explicit surface the row read `mcp-stdio` — non-empty, and wrong."""
334
334
  scheduler, _removed = ttl
335
- monkeypatch.setattr(scheduler, "delete_vm", lambda si, name: f"VM '{name}' deleted")
335
+ monkeypatch.setattr(scheduler, "delete_vm", lambda si, name, **k: f"VM '{name}' deleted")
336
336
 
337
337
  scheduler._run_ttl_check(MagicMock())
338
338