vmware-aiops 2.4.2__tar.gz → 2.4.3__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 (220) hide show
  1. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/.claude-plugin/plugin.json +1 -1
  2. vmware_aiops-2.4.3/.github/workflows/windows-file-lock.yml +32 -0
  3. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/.mcp.json +1 -1
  4. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/PKG-INFO +4 -1
  5. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/README-CN.md +2 -0
  6. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/README.md +2 -0
  7. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/RELEASE_NOTES.md +9 -1
  8. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/openclaw.plugin.json +1 -1
  9. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/package.json +1 -1
  10. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/pyproject.toml +2 -1
  11. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/server.json +2 -2
  12. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/skills/vmware-aiops/SKILL.md +4 -2
  13. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/skills/vmware-aiops/references/capabilities.md +2 -0
  14. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/skills/vmware-aiops/references/setup-guide.md +5 -5
  15. vmware_aiops-2.4.3/tests/eval/regression/test_delegated_lookup_errors.py +70 -0
  16. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_mcp_frame_marks_failures.py +1 -1
  17. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_write_round_low_findings.py +1 -1
  18. vmware_aiops-2.4.3/tests/test_ambiguous_inventory_names.py +127 -0
  19. vmware_aiops-2.4.3/tests/test_executors_act_on_the_previewed_vm.py +221 -0
  20. vmware_aiops-2.4.3/tests/test_fsutil.py +53 -0
  21. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_power_off_and_preview_policy.py +1 -1
  22. vmware_aiops-2.4.3/tests/windows_lock_check.py +125 -0
  23. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/uv.lock +10 -8
  24. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/__init__.py +1 -1
  25. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/cli/_common.py +2 -2
  26. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/mcp_server/_shared.py +16 -0
  27. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/mcp_server/tools/guest.py +7 -3
  28. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/mcp_server/tools/vm.py +12 -4
  29. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/guest_ops.py +28 -9
  30. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/inventory.py +69 -17
  31. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/planner.py +6 -3
  32. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/vm_gate.py +5 -0
  33. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/vm_lifecycle.py +64 -9
  34. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/.clawhubignore +0 -0
  35. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/.env.example +0 -0
  36. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  37. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  38. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  39. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  40. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/.github/dependabot.yml +0 -0
  41. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/.gitignore +0 -0
  42. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/Dockerfile +0 -0
  43. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/LICENSE +0 -0
  44. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/SECURITY.md +0 -0
  45. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/config.example.yaml +0 -0
  46. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/docker-compose.yml +0 -0
  47. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/docs/integrations/continue.md +0 -0
  48. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/docs/integrations/cursor.md +0 -0
  49. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/docs/integrations/goose.md +0 -0
  50. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/docs/integrations/localcowork.md +0 -0
  51. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/docs/integrations/mcp-agent.md +0 -0
  52. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/docs/integrations/vscode-copilot.md +0 -0
  53. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/docs/plans/2026-03-09-plan-apply-design.md +0 -0
  54. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/examples/deploy.yaml +0 -0
  55. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/examples/mcp-configs/README.md +0 -0
  56. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/examples/mcp-configs/claude-code.json +0 -0
  57. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/examples/mcp-configs/continue.yaml +0 -0
  58. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/examples/mcp-configs/cursor.json +0 -0
  59. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/examples/mcp-configs/goose.json +0 -0
  60. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/examples/mcp-configs/localcowork.json +0 -0
  61. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/examples/mcp-configs/mcp-agent.yaml +0 -0
  62. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/examples/mcp-configs/vscode-copilot.json +0 -0
  63. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/examples/ollama-local-setup.md +0 -0
  64. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/skills/vmware-aiops/evals/evals.json +0 -0
  65. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/skills/vmware-aiops/references/agent-guardrails.md +0 -0
  66. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/skills/vmware-aiops/references/cli-reference.md +0 -0
  67. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/skills/vmware-aiops/references/investigation-protocol.md +0 -0
  68. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/smithery.yaml +0 -0
  69. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/__init__.py +0 -0
  70. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/conftest.py +0 -0
  71. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/__init__.py +0 -0
  72. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/capability/__init__.py +0 -0
  73. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/capability/_family.py +0 -0
  74. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/capability/_scores.json +0 -0
  75. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/capability/_scoring.py +0 -0
  76. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/capability/_skill.py +0 -0
  77. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/capability/conftest.py +0 -0
  78. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/capability/test_entity_reachability.py +0 -0
  79. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/capability/test_error_actionability.py +0 -0
  80. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/capability/test_tool_description_quality.py +0 -0
  81. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/capability/test_tool_manifest_budget.py +0 -0
  82. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/__init__.py +0 -0
  83. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/_diag_fakes.py +0 -0
  84. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/_pc_fakes.py +0 -0
  85. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_aiops_specific.py +0 -0
  86. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_alarm_health_scale_n1.py +0 -0
  87. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_atexit_handlers_do_not_accumulate.py +0 -0
  88. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_browse_ds_path_join.py +0 -0
  89. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_browse_sees_every_file.py +0 -0
  90. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_capability_grader.py +0 -0
  91. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_cli_destructive_guards.py +0 -0
  92. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_cli_writes_guarded.py +0 -0
  93. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_cluster_health_summary_delegation.py +0 -0
  94. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_code_review_fixes.py +0 -0
  95. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_config_path_resolution_is_shared.py +0 -0
  96. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_daemon_calls_are_audited.py +0 -0
  97. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_daemon_host_log_cycles.py +0 -0
  98. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_deploy_correctness.py +0 -0
  99. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_deploy_failures_are_failures.py +0 -0
  100. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_doctor_authenticates_every_target.py +0 -0
  101. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_documented_gates_match_the_registry.py +0 -0
  102. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_enum_params_match_implementation.py +0 -0
  103. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_env_secret_obfuscation.py +0 -0
  104. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_error_hints_fit_the_surface.py +0 -0
  105. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_event_sets_match_real_class_names.py +0 -0
  106. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_events_are_read_newest_first.py +0 -0
  107. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_guest_download_writes_the_local_disk.py +0 -0
  108. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_guest_username_is_explicit.py +0 -0
  109. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_host_log_scan_reads_real_logs.py +0 -0
  110. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_host_paths_protected.py +0 -0
  111. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_image_registry_cross_process.py +0 -0
  112. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_image_registry_legacy_rows.py +0 -0
  113. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_image_registry_per_target.py +0 -0
  114. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_instructions_name_targets.py +0 -0
  115. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_inventory_scale_issue31.py +0 -0
  116. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_linked_clone_failure_on_the_wire.py +0 -0
  117. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_list_envelope_conformance.py +0 -0
  118. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_list_plans_is_non_destructive.py +0 -0
  119. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_login_without_vsphere_role.py +0 -0
  120. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_ova_network_mapping.py +0 -0
  121. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_release_blockers.py +0 -0
  122. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_result_envelope.py +0 -0
  123. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_returned_failure_is_audited.py +0 -0
  124. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_session_probe_eviction.py +0 -0
  125. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_set_ttl_safety.py +0 -0
  126. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_snapshot_async.py +0 -0
  127. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_text_io_declares_encoding.py +0 -0
  128. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_tool_annotations_match_behaviour.py +0 -0
  129. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_unreachable_host_vmks.py +0 -0
  130. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_v1_5_25_blockers.py +0 -0
  131. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/eval/regression/test_vim_attribute_conformance.py +0 -0
  132. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_access_info.py +0 -0
  133. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_cluster_mgmt_sanitize.py +0 -0
  134. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_cluster_ttl_plan_gate.py +0 -0
  135. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_composite_tools_obey_policy.py +0 -0
  136. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_concurrent_local_state.py +0 -0
  137. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_config.py +0 -0
  138. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_drs_rules.py +0 -0
  139. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_filelock.py +0 -0
  140. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_gate_review_fixes.py +0 -0
  141. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_guest_tools_gate.py +0 -0
  142. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_host_network_mgmt.py +0 -0
  143. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_init_and_onboarding.py +0 -0
  144. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_migrate_compatibility_check.py +0 -0
  145. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_network_mgmt.py +0 -0
  146. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_no_destructive_ops.py +0 -0
  147. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_plan_gate_narrow_fixes.py +0 -0
  148. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_plan_rollback_final_fixes.py +0 -0
  149. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_plan_steps_obey_policy.py +0 -0
  150. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_policy_roles.py +0 -0
  151. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_reset_alarm_gate.py +0 -0
  152. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_results_name_their_target.py +0 -0
  153. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_safe_error_passthrough.py +0 -0
  154. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_session_pool.py +0 -0
  155. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_stop_signal_runs_atexit.py +0 -0
  156. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_vm_delete_blast_radius.py +0 -0
  157. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/tests/test_vm_tools_gate.py +0 -0
  158. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/_filelock.py +0 -0
  159. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/_fsutil.py +0 -0
  160. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/access_requirements.py +0 -0
  161. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/cli/__init__.py +0 -0
  162. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/cli/_root.py +0 -0
  163. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/cli/access.py +0 -0
  164. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/cli/alarm.py +0 -0
  165. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/cli/cluster.py +0 -0
  166. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/cli/deploy.py +0 -0
  167. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/cli/doctor.py +0 -0
  168. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/cli/hub.py +0 -0
  169. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/cli/investigate.py +0 -0
  170. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/cli/mcp_config.py +0 -0
  171. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/cli/plan.py +0 -0
  172. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/cli/scan.py +0 -0
  173. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/cli/summary.py +0 -0
  174. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/cli/vm.py +0 -0
  175. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/config.py +0 -0
  176. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/connection.py +0 -0
  177. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/doctor.py +0 -0
  178. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/init_wizard.py +0 -0
  179. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/mcp_server/__init__.py +0 -0
  180. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/mcp_server/__main__.py +0 -0
  181. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/mcp_server/server.py +0 -0
  182. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/mcp_server/tools/__init__.py +0 -0
  183. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/mcp_server/tools/access.py +0 -0
  184. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/mcp_server/tools/alarm.py +0 -0
  185. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/mcp_server/tools/cluster.py +0 -0
  186. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/mcp_server/tools/datastore.py +0 -0
  187. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/mcp_server/tools/deploy.py +0 -0
  188. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/mcp_server/tools/network.py +0 -0
  189. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/mcp_server/tools/plan.py +0 -0
  190. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/mcp_server/tools/summary.py +0 -0
  191. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/mcp_server/tools/ttl.py +0 -0
  192. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/notify/__init__.py +0 -0
  193. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/notify/audit.py +0 -0
  194. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/notify/logger.py +0 -0
  195. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/notify/webhook.py +0 -0
  196. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/__init__.py +0 -0
  197. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/_paging.py +0 -0
  198. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/alarm_mgmt.py +0 -0
  199. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/cluster_gate.py +0 -0
  200. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/cluster_mgmt.py +0 -0
  201. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/datastore_browser.py +0 -0
  202. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/gate.py +0 -0
  203. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/guest_gate.py +0 -0
  204. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/health.py +0 -0
  205. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/host_network_mgmt.py +0 -0
  206. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/iscsi_config.py +0 -0
  207. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/network_mgmt.py +0 -0
  208. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/ova_deploy.py +0 -0
  209. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/plan_executor.py +0 -0
  210. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/plan_gate.py +0 -0
  211. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/step_policy.py +0 -0
  212. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/ttl.py +0 -0
  213. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/vm_delete_gate.py +0 -0
  214. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/ops/vm_deploy.py +0 -0
  215. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/policy_environment.py +0 -0
  216. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/policy_roles.py +0 -0
  217. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/scanner/__init__.py +0 -0
  218. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/scanner/alarm_scanner.py +0 -0
  219. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/scanner/log_scanner.py +0 -0
  220. {vmware_aiops-2.4.2 → vmware_aiops-2.4.3}/vmware_aiops/scanner/scheduler.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.2",
5
+ "version": "2.4.3",
6
6
  "description": "AI-powered VMware vCenter/ESXi VM lifecycle and deployment with 61 MCP tools.",
7
7
  "author": {
8
8
  "name": "Wei Zhou",
@@ -0,0 +1,32 @@
1
+ name: Windows file lock
2
+
3
+ on:
4
+ pull_request:
5
+ paths:
6
+ - 'vmware_aiops/_filelock.py'
7
+ - 'tests/windows_lock_check.py'
8
+ - '.github/workflows/windows-file-lock.yml'
9
+ push:
10
+ branches: [main]
11
+ paths:
12
+ - 'vmware_aiops/_filelock.py'
13
+ - 'tests/windows_lock_check.py'
14
+ - '.github/workflows/windows-file-lock.yml'
15
+ workflow_dispatch:
16
+
17
+ permissions:
18
+ contents: read
19
+
20
+ jobs:
21
+ real-msvcrt:
22
+ runs-on: windows-latest
23
+ timeout-minutes: 10
24
+ steps:
25
+ - uses: actions/checkout@v7
26
+ with:
27
+ persist-credentials: false
28
+ - uses: actions/setup-python@v7
29
+ with:
30
+ python-version: '3.12'
31
+ - name: Check four-process serialization, bounded timeout, and release
32
+ run: python tests/windows_lock_check.py vmware_aiops/_filelock.py
@@ -5,7 +5,7 @@
5
5
  "command": "uvx",
6
6
  "args": [
7
7
  "--from",
8
- "vmware-aiops==2.4.2",
8
+ "vmware-aiops==2.4.3",
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.2
3
+ Version: 2.4.3
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
@@ -11,6 +11,7 @@ License-Expression: MIT
11
11
  License-File: LICENSE
12
12
  Requires-Python: >=3.10
13
13
  Requires-Dist: apscheduler<4.0,>=3.10
14
+ Requires-Dist: certifi>=2026.10.19
14
15
  Requires-Dist: httpx<1.0,>=0.27
15
16
  Requires-Dist: mcp[cli]<2.0,>=1.30
16
17
  Requires-Dist: pyaml<27.0,>=24.0
@@ -25,6 +26,8 @@ Description-Content-Type: text/markdown
25
26
  <!-- mcp-name: io.github.vmware-skills/vmware-aiops -->
26
27
  # VMware AIops
27
28
 
29
+ For a read-only check of this skill with your model and agent host, use the shared [model-fit self-test](https://github.com/vmware-skills/marketplace/blob/main/MODEL_FIT_SELF_TEST.md) in the marketplace root.
30
+
28
31
  > **Author**: Wei Zhou, VMware by Broadcom — wei-wz.zhou@broadcom.com
29
32
  > This is a community-driven project by a VMware engineer, not an official VMware product.
30
33
  > For official VMware developer tools see [developer.broadcom.com](https://developer.broadcom.com).
@@ -1,6 +1,8 @@
1
1
  <!-- mcp-name: io.github.vmware-skills/vmware-aiops -->
2
2
  # VMware AIops
3
3
 
4
+ 要用自己的模型和 Agent 平台进行只读自测,请看 marketplace 根目录的[共享模型适配自测任务](https://github.com/vmware-skills/marketplace/blob/main/MODEL_FIT_SELF_TEST.md)。
5
+
4
6
  > **作者**: Wei Zhou, VMware by Broadcom — wei-wz.zhou@broadcom.com
5
7
  > 本项目由 VMware 工程师维护的社区项目,非 VMware 官方产品。
6
8
  > VMware 官方开发者工具请访问 [developer.broadcom.com](https://developer.broadcom.com)。
@@ -1,6 +1,8 @@
1
1
  <!-- mcp-name: io.github.vmware-skills/vmware-aiops -->
2
2
  # VMware AIops
3
3
 
4
+ For a read-only check of this skill with your model and agent host, use the shared [model-fit self-test](https://github.com/vmware-skills/marketplace/blob/main/MODEL_FIT_SELF_TEST.md) in the marketplace root.
5
+
4
6
  > **Author**: Wei Zhou, VMware by Broadcom — wei-wz.zhou@broadcom.com
5
7
  > This is a community-driven project by a VMware engineer, not an official VMware product.
6
8
  > For official VMware developer tools see [developer.broadcom.com](https://developer.broadcom.com).
@@ -1,3 +1,11 @@
1
+ ## v2.4.3 — bind confirmed writes to measured identities; reject ambiguous inventory names (2026-11-08)
2
+
3
+ * **Confirmed MCP writes re-check the identities measured during that call.** The eight confirm-gated VM and snapshot tools pass the current blast-radius measurement's VM instance UUID and snapshot id into execution, refusing a replacement object found by the executor. Each `confirm=True` invocation measures afresh; the separate earlier preview supplies no identity token. The existing `delete_vm` acknowledgement requirement is unchanged, as are the CLI and plan-executor paths (AIops#49).
4
+ * **Missing objects in delegated investigation tools keep their remedy.** VM, host and datastore lookup errors from vmware-monitor retain the exact-name/listing guidance through AIops MCP and are classified as request errors; unexpected delegate failures remain reduced to their type.
5
+ * **Ambiguous host, cluster, datastore and datacenter names are refused.** A lookup that finds more than one match raises `AmbiguousNameError` with the matching managed-object ids; plan prechecks and CLI output expose the ambiguity instead of selecting the first result (AIops#48).
6
+ * The image-cache and TTL cross-process file lock is exercised on a real Windows GitHub Actions runner: four concurrent writers retain every row, a held lock times out with the lock path, and the lock is released after the holder exits (AIops#51).
7
+ * Require certifi 2026.10.19 or later to exclude the compromised emSign root CA from the TLS trust bundle (GHSA-6q48-6c7w-9gw2). The development lock resolves 2026.10.23 (AIops#53).
8
+
1
9
  ## v2.4.2 — a login refused for lack of a role says so; an unreadable host is not called unreachable (2026-10-19)
2
10
 
3
11
  Needs vmware-monitor 2.4.2 (now the floor). Found on a vCenter 8.0.3 lab with two restricted SSO accounts
@@ -2642,4 +2650,4 @@ vmware-aiops vks clusters|health|scale|nodes
2642
2650
  多目标配置。
2643
2651
 
2644
2652
  - Environment variable password management.
2645
- 环境变量密码管理。
2653
+ 环境变量密码管理。
@@ -11,7 +11,7 @@
11
11
  "command": "uvx",
12
12
  "args": [
13
13
  "--from",
14
- "vmware-aiops==2.4.2",
14
+ "vmware-aiops==2.4.3",
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.2",
3
+ "version": "2.4.3",
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.2"
7
+ version = "2.4.3"
8
8
  description = "VMware vCenter/ESXi AI-powered VM lifecycle and deployment tool"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -17,6 +17,7 @@ dependencies = [
17
17
  "rich>=13.0,<16.0",
18
18
  "apscheduler>=3.10,<4.0",
19
19
  "httpx>=0.27,<1.0",
20
+ "certifi>=2026.10.19", # GHSA-6q48-6c7w-9gw2: exclude compromised emSign root
20
21
  "python-dotenv>=1.0,<2.0",
21
22
  "mcp[cli]>=1.30,<2.0",
22
23
  "vmware-policy>=1.23.1,<2.0",
@@ -7,12 +7,12 @@
7
7
  "url": "https://github.com/vmware-skills/VMware-AIops",
8
8
  "source": "github"
9
9
  },
10
- "version": "2.4.2",
10
+ "version": "2.4.3",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "pypi",
14
14
  "identifier": "vmware-aiops",
15
- "version": "2.4.2",
15
+ "version": "2.4.3",
16
16
  "runtimeHint": "uvx",
17
17
  "packageArguments": [
18
18
  {
@@ -25,6 +25,8 @@ compatibility: >
25
25
 
26
26
  # VMware AIops
27
27
 
28
+ > Self-test: [guide](https://github.com/vmware-skills/marketplace/blob/main/MODEL_FIT_SELF_TEST.md).
29
+
28
30
  > **Disclaimer**: This is a community-maintained open-source project and is **not affiliated with, endorsed by, or sponsored by VMware, Inc. or Broadcom Inc.** "VMware" and "vSphere" are trademarks of Broadcom. Source code is publicly auditable at [github.com/vmware-skills/VMware-AIops](https://github.com/vmware-skills/VMware-AIops) under the MIT license.
29
31
 
30
32
  VMware family entry point — AI-powered VM lifecycle, deployment, and alarm management — 61 MCP tools.
@@ -63,7 +65,7 @@ Read before connecting an agent. Per-tool inventory: `references/capabilities.md
63
65
  ## Quick Install
64
66
 
65
67
  ```bash
66
- uv tool install vmware-aiops==2.4.2
68
+ uv tool install vmware-aiops==2.4.3
67
69
  vmware-aiops doctor
68
70
  vmware-aiops hub status # see which family members are installed
69
71
  ```
@@ -303,7 +305,7 @@ The account has no vSphere role. Assign one (Read-Only or higher) on the vCenter
303
305
  ## Setup
304
306
 
305
307
  ```bash
306
- uv tool install vmware-aiops==2.4.2
308
+ uv tool install vmware-aiops==2.4.3
307
309
  mkdir -p ~/.vmware-aiops
308
310
  vmware-aiops init # generates config.yaml and .env templates
309
311
  chmod 600 ~/.vmware-aiops/.env
@@ -59,6 +59,8 @@ well-meaning agents, not a replacement for RBAC.
59
59
  - **Cluster** (`delete_cluster`, `remove_cluster_host`): member hosts, VMs, datastores. Refused while hosts or VMs are still in the cluster, or when the host is not in maintenance mode or still runs powered-on VMs.
60
60
  - **Clean Slate / TTL** (`restore_vm_clean_slate`, `set_vm_ttl`): the snapshot and what the revert discards; for a TTL, the VM that will be deleted unattended and when. Clean Slate is refused when the baseline snapshot is missing or its name is ambiguous — before anything is powered off — and reports an error, not `reverted`, if the revert did not happen. The TTL entry records the VM's target and instance UUID; at expiry the daemon deletes only a VM that still has that UUID (another VM under the same name is left alone, the entry dropped and the refusal audited). A TTL is refused on every `delete_vm` blocker except the power state (the daemon powers off first).
61
61
  - **Snapshot names** (`revert_vm_snapshot`, `delete_vm_snapshot`, `restore_vm_clean_slate`): a name matches a snapshot's raw name or the name `list_vm_snapshots` shows (control/invisible characters stripped). Two snapshots matching the same name — duplicates, or names that differ only by stripped characters — are refused by the gate and by the executors, which no longer take the first.
62
+ - **Object names**: looking up a VM, host, cluster, datastore or datacenter by name across the vCenter refuses a name that matches more than one object of that kind, and lists the matches' managed-object ids, instead of taking the first. Cluster and datastore names are unique only within a datacenter, so two datacenters can each have a `Cluster01`.
63
+ - **What a confirmed call acts on** (`power_off_vm`, `migrate_vm`, `revert_vm_snapshot`, `delete_vm_snapshot`, `run_vm_guest_command`, `run_vm_guest_command_with_output`, `upload_vm_guest_file`, `provision_vm_guest`): the VM measured within that invocation, checked by instance UUID, and for snapshots the snapshot measured then, checked by id. Replacement between that measurement and the executor's lookup is refused ("not the VM that was previewed") before it changes anything. Each `confirm=True` invocation measures afresh; an earlier separate preview supplies no identity token. The `delete_vm` acknowledgement below remains the explicit check against a prior preview.
62
64
  - **Plans** (`apply_vm_plan`, `rollback_vm_plan`): every step with its full parameters (passwords and other secret-looking keys redacted), which are destructive and which cannot be rolled back. Each destructive step is measured as its own tool measures it, and that tool's `blockers` / `unmeasured` become the plan's, prefixed `Step N (action):`. A step on an object an earlier step creates or changes (power off, then delete; a VM a step creates) is shown `check: "deferred"` and measured immediately before it runs; every destructive step is re-checked then, and a failed check stops the plan with a teaching error in that step's result. Refused when `target` does not match the plan's (no target is a target of its own), a step would be refused by its tool, or a plan step with action delete_vm carries no `acknowledge_blast_radius`. Every action the executor can run is classified: measured by its tool's gate (including `migrate`, measured as `migrate_vm`), refused, or non-destructive with a stated reason; anything else is refused rather than run unchecked. The plan actions `iscsi_enable`, `iscsi_add_target`, `iscsi_remove_target` and `storage_rescan` (plan-file action names, not tools) are gated in vmware-storage and cannot be measured here, so a plan containing one is refused at preview, apply and rollback — use `enable_iscsi`, `add_iscsi_target`, `remove_iscsi_target` / `rescan_storage` instead. Rollback steps get the same checks: each destructive rollback step (power off, delete snapshot, cluster delete, host removal) is measured in the `rollback_vm_plan` preview and again just before it runs; a refused check stops the rollback with a teaching error for that step, and the plan stays `failed` so rollback can be run again. A step that creates a VM records its instance UUID; rollback deletes the VM only if it still has that UUID, and refuses that rollback step otherwise. Plan responses never echo step passwords; secret keys are matched as whole name tokens (`password`, `passwd`, `pwd`, `secret`, `token`, `api_key`, `auth`, `credential(s)`, `private_key`).
63
65
  - **Refusal messages** lead with the first blocker (problem and remedy) and count the rest ("…and 2 more blockers"), and stay within 480 characters; the preview lists every blocker.
64
66
  - **Network / DRS** (`add_host_vmk`, `remove_host_vmk`, `set_vmk_service`, `create_dvs_portgroup`, `create_drs_rule`, `delete_drs_rule`, `set_drs_rule_enabled`): what would be created, changed or removed, with `blockers` and `unmeasured`; `remove_host_vmk` and `set_vmk_service` fail closed on an unreadable service map. Their refusals (a protected or the only management vmk, a VM-Host DRS rule) are reported as blockers in the preview and still raise on `confirm=True`.
@@ -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.2
9
+ uv tool install vmware-aiops==2.4.3
10
10
 
11
11
  # Via Skills.sh (fetches from GitHub)
12
- npx skills add vmware-skills/VMware-AIops#v2.4.2
12
+ npx skills add vmware-skills/VMware-AIops#v2.4.3
13
13
 
14
14
  # Via ClawHub (fetches from ClawHub registry snapshot of GitHub)
15
- clawhub install @zw008/vmware-aiops --version 2.4.2
15
+ clawhub install @zw008/vmware-aiops --version 2.4.3
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.2
38
+ uv tool install vmware-aiops==2.4.3
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.2 https://github.com/vmware-skills/VMware-AIops.git
76
+ git clone --branch v2.4.3 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,70 @@
1
+ """Monitor's missing-object remedies must survive the AIops MCP error wrapper.
2
+
3
+ Found on real vCenter, 2026-11-08: investigate_vm reduced Monitor's authored
4
+ VMNotFoundError to a generic failure and incorrectly suggested checking login.
5
+ The host and datastore delegates use different authored exception classes too.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ import importlib
11
+ import json
12
+
13
+ import mcp.types as mcp_types
14
+ import pytest
15
+ from vmware_policy.policy import reset_policy_engine
16
+
17
+ from vmware_aiops.mcp_server.server import mcp
18
+ from vmware_aiops.mcp_server.tools import summary
19
+
20
+
21
+ @pytest.fixture(autouse=True)
22
+ def policy_home(tmp_path, monkeypatch):
23
+ monkeypatch.setenv("OPS_HOME", str(tmp_path))
24
+ reset_policy_engine()
25
+ yield
26
+ reset_policy_engine()
27
+
28
+
29
+ def call(tool, arguments):
30
+ handler = mcp._mcp_server.request_handlers[mcp_types.CallToolRequest]
31
+ request = mcp_types.CallToolRequest(
32
+ method="tools/call",
33
+ params=mcp_types.CallToolRequestParams(name=tool, arguments=arguments),
34
+ )
35
+ result = asyncio.run(handler(request)).root
36
+ text = "".join(b.text for b in result.content if isinstance(b, mcp_types.TextContent))
37
+ return result, json.loads(text)
38
+
39
+
40
+ @pytest.mark.parametrize(("kind", "lookup", "listing"), [
41
+ ("vm", "find_vm_by_name", "list_virtual_machines"),
42
+ ("host", "find_host_by_name", "list_esxi_hosts"),
43
+ ("datastore", "find_datastore_by_name", "list_all_datastores"),
44
+ ])
45
+ def test_missing_delegate_object_keeps_its_real_remedy(monkeypatch, kind, lookup, listing):
46
+ # Exercise the real Monitor raise site, not a reconstructed exception.
47
+ module = importlib.import_module(f"vmware_monitor.ops.investigate_{kind}")
48
+ monkeypatch.setattr(module, lookup, lambda *_args: None)
49
+ monkeypatch.setattr(summary, "_get_connection", lambda *_args: object())
50
+ result, payload = call(f"investigate_{kind}", {f"{kind}_name": "missing-\x00object"})
51
+ assert result.isError is True
52
+ assert "not found" in payload["error"]
53
+ assert listing in payload["error"]
54
+ assert "missing-object" in payload["error"]
55
+ assert "\x00" not in payload["error"]
56
+ assert "doctor" not in payload["hint"]
57
+
58
+
59
+ @pytest.mark.parametrize("error_type", [RuntimeError, type("VMNotFoundError", (Exception,), {})])
60
+ def test_unplanned_delegate_errors_are_still_reduced(monkeypatch, error_type):
61
+ def fail(*_args, **_kwargs):
62
+ raise error_type("https://svc:secret-example@internal.invalid/api")
63
+
64
+ monkeypatch.setattr(summary, "_get_connection", lambda *_args: object())
65
+ monkeypatch.setattr(summary, "get_vm_investigation_bundle", fail)
66
+ result, payload = call("investigate_vm", {"vm_name": "missing-object"})
67
+ assert result.isError is True
68
+ assert payload["error"] == f"{error_type.__name__}: operation failed."
69
+ assert "secret-example" not in str(payload)
70
+ assert "doctor" in payload["hint"]
@@ -226,7 +226,7 @@ def test_falsy_error_key_is_not_a_failure(monkeypatch) -> None:
226
226
  # stubbed clean so this stays about the falsy error key.
227
227
  monkeypatch.setattr(
228
228
  "vmware_aiops.mcp_server.tools.guest.measure_guest_provision",
229
- lambda *a, **k: {"blockers": [], "unmeasured": []},
229
+ lambda *a, **k: {"instance_uuid": "uuid-1", "blockers": [], "unmeasured": []},
230
230
  )
231
231
 
232
232
  result = call(
@@ -89,7 +89,7 @@ def test_a_cpu_feature_refusal_leads_with_the_remedy(monkeypatch) -> None:
89
89
  vm.datastore = []
90
90
  host = MagicMock()
91
91
  host.parent.resourcePool = vim.ResourcePool("resgroup-1", None)
92
- monkeypatch.setattr(vm_lifecycle, "_require_vm", lambda si, n: vm)
92
+ monkeypatch.setattr(vm_lifecycle, "_require_vm", lambda si, n, **k: vm)
93
93
  monkeypatch.setattr(vm_lifecycle, "find_host_by_name", lambda si, n: host)
94
94
  monkeypatch.setattr(vm_lifecycle.vim.vm, "RelocateSpec", lambda **k: MagicMock())
95
95
 
@@ -0,0 +1,127 @@
1
+ """Host, cluster, datastore and datacenter lookups refuse a duplicated name.
2
+
3
+ VMs already do (``AmbiguousVMError``, 2026-09-16). The other finders still
4
+ returned whichever object the property collector listed first. Cluster and
5
+ datastore names are unique only within a datacenter, so on a vCenter with two
6
+ datacenters ``add_cluster_host("Cluster01", …)`` could move a host into the
7
+ other datacenter's cluster, and the result reads exactly like a success.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from unittest.mock import MagicMock
13
+
14
+ import pytest
15
+ from pyVmomi import vim
16
+ from vmware_policy.budget import reset_budget
17
+ from vmware_policy.policy import reset_policy_engine
18
+ from vmware_policy.undo import reset_undo_store
19
+
20
+ import vmware_aiops.mcp_server.tools.cluster as cluster_tools
21
+ from vmware_aiops.ops import inventory
22
+ from vmware_aiops.ops.inventory import AmbiguousNameError, AmbiguousVMError, InventoryError
23
+
24
+
25
+ @pytest.fixture(autouse=True)
26
+ def harness(tmp_path, monkeypatch):
27
+ monkeypatch.setenv("OPS_HOME", str(tmp_path))
28
+ reset_policy_engine()
29
+ reset_budget()
30
+ reset_undo_store()
31
+ yield
32
+ reset_policy_engine()
33
+ reset_budget()
34
+ reset_undo_store()
35
+
36
+
37
+ def _obj(name: str, moid: str) -> MagicMock:
38
+ obj = MagicMock(name=f"obj:{name}:{moid}")
39
+ obj.name = name
40
+ obj._moId = moid
41
+ return obj
42
+
43
+
44
+ @pytest.fixture
45
+ def inventory_of(monkeypatch):
46
+ """Serve ``_collect`` per managed-object type from a fixed inventory."""
47
+ def install(by_type: dict):
48
+ monkeypatch.setattr(
49
+ inventory, "_collect",
50
+ lambda _si, types, _props: [(o, {"name": o.name}) for o in by_type.get(types[0], [])],
51
+ )
52
+ return by_type
53
+ return install
54
+
55
+
56
+ FINDERS = [
57
+ (inventory.find_host_by_name, vim.HostSystem, "host"),
58
+ (inventory.find_cluster_by_name, vim.ClusterComputeResource, "cluster"),
59
+ (inventory.find_datastore_by_name, vim.Datastore, "datastore"),
60
+ (inventory.find_datacenter_by_name, vim.Datacenter, "datacenter"),
61
+ ]
62
+
63
+
64
+ @pytest.mark.parametrize(("finder", "kind", "label"), FINDERS)
65
+ def test_a_duplicated_name_is_refused_and_names_both_objects(inventory_of, finder, kind, label):
66
+ inventory_of({kind: [_obj("X", "obj-1"), _obj("X", "obj-2"), _obj("Y", "obj-3")]})
67
+
68
+ with pytest.raises(AmbiguousNameError) as raised:
69
+ finder(MagicMock(), "X")
70
+
71
+ message = str(raised.value)
72
+ assert f"2 {label}s are named 'X'" in message
73
+ assert "obj-1" in message and "obj-2" in message
74
+
75
+
76
+ @pytest.mark.parametrize(("finder", "kind", "label"), FINDERS)
77
+ def test_a_unique_name_still_resolves_and_a_missing_one_is_none(inventory_of, finder, kind, label):
78
+ only = _obj("X", "obj-1")
79
+ inventory_of({kind: [only, _obj("Y", "obj-2")]})
80
+
81
+ assert finder(MagicMock(), "X") is only
82
+ assert finder(MagicMock(), "nope") is None
83
+
84
+
85
+ def test_the_vm_error_is_one_kind_of_ambiguous_name():
86
+ # Callers that catch AmbiguousVMError keep working; new ones catch the base.
87
+ assert issubclass(AmbiguousVMError, AmbiguousNameError)
88
+ assert issubclass(AmbiguousNameError, InventoryError)
89
+
90
+
91
+ def test_add_cluster_host_refuses_a_duplicated_cluster_and_moves_nothing(inventory_of, monkeypatch):
92
+ dc1, dc2 = _obj("Cluster01", "domain-c1"), _obj("Cluster01", "domain-c2")
93
+ inventory_of({vim.ClusterComputeResource: [dc1, dc2], vim.HostSystem: [_obj("esx-01", "host-1")]})
94
+ monkeypatch.setattr(cluster_tools, "_get_connection", lambda _t=None: MagicMock())
95
+
96
+ out = cluster_tools.add_cluster_host("Cluster01", "esx-01")
97
+
98
+ assert "2 clusters are named 'Cluster01'" in out
99
+ dc1.MoveInto_Task.assert_not_called()
100
+ dc2.MoveInto_Task.assert_not_called()
101
+
102
+
103
+ def test_a_plan_that_migrates_to_a_duplicated_host_fails_validation_not_the_planner(inventory_of):
104
+ from vmware_aiops.ops.planner import precheck_targets
105
+
106
+ inventory_of({
107
+ vim.VirtualMachine: [_obj("web-01", "vm-1")],
108
+ vim.HostSystem: [_obj("esx-01", "host-1"), _obj("esx-01", "host-2")],
109
+ })
110
+
111
+ errors = precheck_targets(
112
+ MagicMock(), [{"action": "migrate", "vm_name": "web-01", "target_host": "esx-01"}]
113
+ )
114
+
115
+ assert any("2 hosts are named 'esx-01'" in e for e in errors), errors
116
+
117
+
118
+ def test_many_matches_list_a_few_ids_and_count_the_rest(inventory_of):
119
+ inventory_of({vim.Datastore: [_obj("ds", f"datastore-{i}") for i in range(8)]})
120
+
121
+ with pytest.raises(AmbiguousNameError) as raised:
122
+ inventory.find_datastore_by_name(MagicMock(), "ds")
123
+
124
+ message = str(raised.value)
125
+ assert "8 datastores are named 'ds'" in message
126
+ assert "datastore-4" in message and "datastore-5" not in message
127
+ assert "and 3 more" in message