vmware-storage 2.3.1__tar.gz → 2.4.0__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 (113) hide show
  1. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/.claude-plugin/plugin.json +1 -1
  2. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/.mcp.json +1 -1
  3. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/PKG-INFO +2 -1
  4. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/README-CN.md +1 -0
  5. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/README.md +1 -0
  6. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/RELEASE_NOTES.md +9 -0
  7. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/openclaw.plugin.json +1 -1
  8. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/package.json +1 -1
  9. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/pyproject.toml +1 -1
  10. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/server.json +2 -2
  11. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/skills/vmware-storage/SKILL.md +2 -2
  12. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/skills/vmware-storage/references/setup-guide.md +4 -3
  13. vmware_storage-2.4.0/tests/test_policy_roles.py +55 -0
  14. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/uv.lock +4 -4
  15. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/__init__.py +1 -1
  16. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/policy_environment.py +4 -0
  17. vmware_storage-2.4.0/vmware_storage/policy_roles.py +52 -0
  18. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/.clawhubignore +0 -0
  19. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/.env.example +0 -0
  20. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/.gitignore +0 -0
  21. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/Dockerfile +0 -0
  22. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/LICENSE +0 -0
  23. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/SECURITY.md +0 -0
  24. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/config.example.yaml +0 -0
  25. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/docker-compose.yml +0 -0
  26. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/examples/mcp-configs/README.md +0 -0
  27. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/examples/mcp-configs/claude-code.json +0 -0
  28. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/examples/mcp-configs/continue.yaml +0 -0
  29. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/examples/mcp-configs/cursor.json +0 -0
  30. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/examples/mcp-configs/goose.json +0 -0
  31. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/examples/mcp-configs/localcowork.json +0 -0
  32. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/examples/mcp-configs/mcp-agent.yaml +0 -0
  33. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/examples/mcp-configs/vscode-copilot.json +0 -0
  34. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/patterns/README.md +0 -0
  35. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/patterns/iscsi-target-stale-rescan.yaml +0 -0
  36. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/skills/vmware-storage/evals/evals.json +0 -0
  37. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/skills/vmware-storage/references/agent-guardrails.md +0 -0
  38. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/skills/vmware-storage/references/capabilities.md +0 -0
  39. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/skills/vmware-storage/references/cli-reference.md +0 -0
  40. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/smithery.yaml +0 -0
  41. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/__init__.py +0 -0
  42. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/conftest.py +0 -0
  43. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/__init__.py +0 -0
  44. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/capability/__init__.py +0 -0
  45. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/capability/_family.py +0 -0
  46. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/capability/_scores.json +0 -0
  47. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/capability/_scoring.py +0 -0
  48. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/capability/_skill.py +0 -0
  49. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/capability/conftest.py +0 -0
  50. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/capability/test_entity_reachability.py +0 -0
  51. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/capability/test_error_actionability.py +0 -0
  52. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/capability/test_tool_description_quality.py +0 -0
  53. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/capability/test_tool_manifest_budget.py +0 -0
  54. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/__init__.py +0 -0
  55. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_atexit_handlers_do_not_accumulate.py +0 -0
  56. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_audit_isolation.py +0 -0
  57. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_cached_loader_agrees_with_the_resolver.py +0 -0
  58. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_capability_grader.py +0 -0
  59. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_cli_writes_guarded.py +0 -0
  60. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_credential_env_override.py +0 -0
  61. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_datastore_list_renders.py +0 -0
  62. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_doctor_authenticates_every_target.py +0 -0
  63. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_doctor_uses_the_config_the_tools_use.py +0 -0
  64. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_env_secret_obfuscation.py +0 -0
  65. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_fc_multipath.py +0 -0
  66. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_instructions_name_targets.py +0 -0
  67. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_inventory_scale_n1.py +0 -0
  68. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_iscsi_unreachable_host.py +0 -0
  69. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_login_without_a_role.py +0 -0
  70. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_release_blockers.py +0 -0
  71. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_remedy_survives_a_long_home.py +0 -0
  72. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_result_envelope.py +0 -0
  73. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_returned_failure_is_audited.py +0 -0
  74. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_session_probe_eviction.py +0 -0
  75. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_storage_hardening_fixes.py +0 -0
  76. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_storage_ops_behavior.py +0 -0
  77. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_tool_names_2_0.py +0 -0
  78. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_vim_attribute_conformance.py +0 -0
  79. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_vsan_efficiency.py +0 -0
  80. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_vsan_health_is_actually_queried.py +0 -0
  81. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/regression/test_vsan_reports_what_it_could_not_read.py +0 -0
  82. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/spec/__init__.py +0 -0
  83. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/spec/vsan_efficiency_endpoints.py +0 -0
  84. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/eval/spec/vsan_health_endpoints.py +0 -0
  85. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/test_access_info.py +0 -0
  86. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/test_init_and_onboarding.py +0 -0
  87. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/test_iscsi_gate.py +0 -0
  88. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/test_no_destructive_ops.py +0 -0
  89. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/test_no_destructive_vm_code.py +0 -0
  90. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/test_safe_error_passthrough.py +0 -0
  91. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/test_session_pool.py +0 -0
  92. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/tests/test_stop_signal_runs_atexit.py +0 -0
  93. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/access_requirements.py +0 -0
  94. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/cli.py +0 -0
  95. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/config.py +0 -0
  96. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/connection.py +0 -0
  97. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/doctor.py +0 -0
  98. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/init_wizard.py +0 -0
  99. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/mcp_server/__init__.py +0 -0
  100. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/mcp_server/__main__.py +0 -0
  101. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/mcp_server/server.py +0 -0
  102. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/notify/__init__.py +0 -0
  103. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/notify/audit.py +0 -0
  104. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/ops/__init__.py +0 -0
  105. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/ops/datastore_browser.py +0 -0
  106. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/ops/inventory.py +0 -0
  107. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/ops/iscsi_config.py +0 -0
  108. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/ops/iscsi_gate.py +0 -0
  109. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/ops/multipath.py +0 -0
  110. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/ops/storage_paths.py +0 -0
  111. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/ops/vsan.py +0 -0
  112. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/ops/vsan_efficiency.py +0 -0
  113. {vmware_storage-2.3.1 → vmware_storage-2.4.0}/vmware_storage/ops/vsan_sdk.py +0 -0
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "vmware-storage",
4
4
  "displayName": "VMware Storage",
5
- "version": "2.3.1",
5
+ "version": "2.4.0",
6
6
  "description": "VMware vSphere storage with 15 MCP tools: datastores, iSCSI, vSAN, FC multipath.",
7
7
  "author": {
8
8
  "name": "Wei Zhou",
@@ -5,7 +5,7 @@
5
5
  "command": "uvx",
6
6
  "args": [
7
7
  "--from",
8
- "vmware-storage==2.3.1",
8
+ "vmware-storage==2.4.0",
9
9
  "vmware-storage-mcp"
10
10
  ]
11
11
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vmware-storage
3
- Version: 2.3.1
3
+ Version: 2.4.0
4
4
  Summary: VMware vSphere storage management: datastores, iSCSI, vSAN. Domain-focused MCP skill.
5
5
  Project-URL: Homepage, https://github.com/vmware-skills/VMware-Storage
6
6
  Project-URL: Repository, https://github.com/vmware-skills/VMware-Storage
@@ -329,6 +329,7 @@ claude mcp add --transport http vmware-storage https://mcp.example.com:8443/mcp
329
329
  - The audit log records each caller as `token:<name>`. Calls run side by side, also on the same target: the server keeps up to `VMWARE_MCP_POOL_SIZE` (default 2) sessions per target, each one a login on that target, and a call waits at most 300 s for a free session (vmware-policy ≥ 1.22.0).
330
330
  - **Everyone who can reach the endpoint acts with this server's account.** Tokens identify callers; they grant
331
331
  nothing on the platform. Run one endpoint per privilege level. `vmware-storage access-info` shows which account that is and what it may do.
332
+ - **Several people or roles on one endpoint:** run `vmware-audit roles enable` once, then give each token a role (`vmware-audit token create NAME --role reader`, or a role in `roles.yaml` naming tools and targets; vmware-policy ≥ 1.24.0). A role limits what the token may call on this endpoint; it cannot grant more than the account has. See vmware-policy's README, "Roles for network callers".
332
333
 
333
334
  **Offer only read tools, plus the writes you choose:**
334
335
 
@@ -341,6 +341,7 @@ claude mcp add --transport http vmware-storage https://mcp.example.com:8443/mcp
341
341
  - 审计日志把每个调用者记为 `token:<名字>`。调用并行执行,同一 target 上也一样:server 为每个 target 最多保持 `VMWARE_MCP_POOL_SIZE`(默认 2)个会话,每个会话都是在该 target 上的一次登录;一个调用最多等 300 秒拿到空闲会话(vmware-policy ≥ 1.22.0)。
342
342
  - **能连到这个端点的人,都以该 server 配置的账号操作。** token 只用来识别调用者,不在平台上授予任何权限。
343
343
  每个权限级别单独起一个端点。`vmware-storage access-info` 可查看是哪个账号、它能做什么。
344
+ - **一个端点给多人或多种角色用:** 先运行一次 `vmware-audit roles enable`,再给每个 token 指定角色(`vmware-audit token create 名字 --role reader`,或在 `roles.yaml` 里定义可用的工具和 target;需 vmware-policy ≥ 1.24.0)。角色只能限定这个 token 在本端点上能调用什么,不能超出账号本身的权限。详见 vmware-policy README 的「网络调用者的角色」一节。
344
345
 
345
346
  **只提供读工具,加上你选定的写工具:**
346
347
 
@@ -308,6 +308,7 @@ claude mcp add --transport http vmware-storage https://mcp.example.com:8443/mcp
308
308
  - The audit log records each caller as `token:<name>`. Calls run side by side, also on the same target: the server keeps up to `VMWARE_MCP_POOL_SIZE` (default 2) sessions per target, each one a login on that target, and a call waits at most 300 s for a free session (vmware-policy ≥ 1.22.0).
309
309
  - **Everyone who can reach the endpoint acts with this server's account.** Tokens identify callers; they grant
310
310
  nothing on the platform. Run one endpoint per privilege level. `vmware-storage access-info` shows which account that is and what it may do.
311
+ - **Several people or roles on one endpoint:** run `vmware-audit roles enable` once, then give each token a role (`vmware-audit token create NAME --role reader`, or a role in `roles.yaml` naming tools and targets; vmware-policy ≥ 1.24.0). A role limits what the token may call on this endpoint; it cannot grant more than the account has. See vmware-policy's README, "Roles for network callers".
311
312
 
312
313
  **Offer only read tools, plus the writes you choose:**
313
314
 
@@ -1,3 +1,12 @@
1
+ ## v2.4.0 — roles for network callers (HLD §18)
2
+
3
+ * Declares the role metadata vmware-policy ≥ 1.24.0 needs to apply **roles** to this server over HTTPS
4
+ (HLD §18): how a missing target resolves. With an older vmware-policy nothing changes. Once an operator runs
5
+ `vmware-audit roles enable`, a token's role decides which tools and targets it may use here; a refused call is
6
+ audited as `denied`. See vmware-policy's README, "Roles for network callers".
7
+ * A target-limited role is checked against the same config the connection uses: the copy the server's connection manager loaded on its first connection. Edit `config.yaml` and restart for both to see the change.
8
+ * README and setup guide: one endpoint for several people or roles.
9
+
1
10
  ## v2.3.1 — a login with no vSphere role is named as such (AIops#47)
2
11
 
3
12
  * An account that logs in but has no vSphere role (vCenter shows no session for it) is now refused at connect time
@@ -11,7 +11,7 @@
11
11
  "command": "uvx",
12
12
  "args": [
13
13
  "--from",
14
- "vmware-storage==2.3.1",
14
+ "vmware-storage==2.4.0",
15
15
  "vmware-storage-mcp"
16
16
  ]
17
17
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zw008/vmware-storage",
3
- "version": "2.3.1",
3
+ "version": "2.4.0",
4
4
  "description": "VMware vSphere storage with 15 MCP tools: datastores, iSCSI, vSAN, FC multipath.",
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-storage"
7
- version = "2.3.1"
7
+ version = "2.4.0"
8
8
  description = "VMware vSphere storage management: datastores, iSCSI, vSAN. Domain-focused MCP skill."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -7,12 +7,12 @@
7
7
  "url": "https://github.com/vmware-skills/VMware-Storage",
8
8
  "source": "github"
9
9
  },
10
- "version": "2.3.1",
10
+ "version": "2.4.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "pypi",
14
14
  "identifier": "vmware-storage",
15
- "version": "2.3.1",
15
+ "version": "2.4.0",
16
16
  "runtimeHint": "uvx",
17
17
  "packageArguments": [
18
18
  {
@@ -40,7 +40,7 @@ VMware vSphere storage management — 15 MCP tools for datastores, iSCSI, vSAN,
40
40
  ## Quick Install
41
41
 
42
42
  ```bash
43
- uv tool install vmware-storage==2.3.1
43
+ uv tool install vmware-storage==2.4.0
44
44
  vmware-storage init # guided setup: writes config + .env (chmod 600, password grep-safe), then verifies
45
45
  vmware-storage doctor
46
46
  ```
@@ -271,7 +271,7 @@ Corporate TLS proxies inject certificates that uv's bundled CA store doesn't tru
271
271
  ## Setup
272
272
 
273
273
  ```bash
274
- uv tool install vmware-storage==2.3.1
274
+ uv tool install vmware-storage==2.4.0
275
275
  mkdir -p ~/.vmware-storage
276
276
  cp config.example.yaml ~/.vmware-storage/config.yaml
277
277
  # Edit config.yaml with your vCenter/ESXi targets
@@ -13,19 +13,19 @@ Complete setup and security guide for `vmware-storage`.
13
13
  ### Via uv (recommended)
14
14
 
15
15
  ```bash
16
- uv tool install vmware-storage==2.3.1
16
+ uv tool install vmware-storage==2.4.0
17
17
  ```
18
18
 
19
19
  ### Via pip
20
20
 
21
21
  ```bash
22
- pip install vmware-storage==2.3.1
22
+ pip install vmware-storage==2.4.0
23
23
  ```
24
24
 
25
25
  ### From source
26
26
 
27
27
  ```bash
28
- git clone --branch v2.3.1 https://github.com/vmware-skills/VMware-Storage.git
28
+ git clone --branch v2.4.0 https://github.com/vmware-skills/VMware-Storage.git
29
29
  cd VMware-Storage
30
30
  pip install -e .
31
31
  ```
@@ -250,6 +250,7 @@ vmware-storage mcp --transport https --host 0.0.0.0 --port 8443 \
250
250
  - **Everyone who can reach the endpoint acts with this server's account.** Tokens identify callers; they grant
251
251
  nothing on the platform. Run one endpoint per privilege level (for example a read-only account behind
252
252
  `--expose read`, and a separate endpoint for operators).
253
+ - **Several people or roles on one endpoint:** run `vmware-audit roles enable` once, then give each token a role (`vmware-audit token create NAME --role reader`, or a role in `roles.yaml` naming tools and targets; vmware-policy ≥ 1.24.0). A role limits what the token may call on this endpoint; it cannot grant more than the account has. See vmware-policy's README, "Roles for network callers".
253
254
 
254
255
  ## Security Details
255
256
 
@@ -0,0 +1,55 @@
1
+ """HLD §18: the role metadata this skill declares matches its real tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+
7
+ import pytest
8
+
9
+ from vmware_storage import policy_roles
10
+
11
+
12
+ def _registry() -> set[str]:
13
+ from vmware_storage.mcp_server.server import mcp
14
+
15
+ return {t.name for t in asyncio.run(mcp.list_tools())}
16
+
17
+
18
+ @pytest.mark.unit
19
+ def test_every_declared_name_is_a_real_tool():
20
+ named = set(policy_roles.ESCAPES) | set(getattr(policy_roles, "AKO_TOOLS", ()))
21
+ assert named - _registry() == set(), sorted(named - _registry())
22
+
23
+
24
+ @pytest.mark.unit
25
+ def test_metadata_is_declared_when_policy_has_roles():
26
+ roles = pytest.importorskip("vmware_policy.roles")
27
+ import vmware_storage.mcp_server.server # noqa: F401 (registers the metadata on import)
28
+
29
+ meta = roles.metadata("storage")
30
+ assert meta is not None and meta.version == 1
31
+ assert meta.escapes == policy_roles.ESCAPES
32
+ assert (meta.resolver is not None) is True
33
+
34
+
35
+ @pytest.mark.unit
36
+ def test_the_resolver_uses_the_config_the_connection_manager_holds(monkeypatch):
37
+ """Review H1/H-1: once the server's connection manager exists, a config edit must
38
+ not move the role check off the config it connects with; before, read the file."""
39
+ import importlib
40
+ import types
41
+
42
+ from vmware_storage import policy_roles as pr
43
+
44
+ server = importlib.import_module("vmware_storage.mcp_server.server")
45
+ assert hasattr(server, "_conn_mgr"), "policy_roles looks up a global the server does not have"
46
+ edited, loads = object(), []
47
+ monkeypatch.setattr(pr, "load_config", lambda: loads.append(1) or edited)
48
+
49
+ monkeypatch.setattr(server, "_conn_mgr", None)
50
+ assert pr._connection_config() is edited and pr._connection_config() is edited
51
+ assert len(loads) == 2 # no manager yet: the file as it is now, every time
52
+
53
+ held = object()
54
+ monkeypatch.setattr(server, "_conn_mgr", types.SimpleNamespace(_config=held))
55
+ assert pr._connection_config() is held # the manager's copy, not the edited file
@@ -1162,21 +1162,21 @@ wheels = [
1162
1162
 
1163
1163
  [[package]]
1164
1164
  name = "vmware-policy"
1165
- version = "1.23.0"
1165
+ version = "1.24.0"
1166
1166
  source = { registry = "https://pypi.org/simple" }
1167
1167
  dependencies = [
1168
1168
  { name = "pyyaml" },
1169
1169
  { name = "rich" },
1170
1170
  { name = "typer" },
1171
1171
  ]
1172
- sdist = { url = "https://files.pythonhosted.org/packages/1b/ef/2c56cc93e0744d7e68b75f7a288f580215865b4685a07b9d58e49773819b/vmware_policy-1.23.0.tar.gz", hash = "sha256:70a682fa44e36fadee7ca09c7c5411fff51c9e25ad9d638f9877737361e2ae31", size = 325401, upload-time = "2026-10-16T00:10:32.297Z" }
1172
+ sdist = { url = "https://files.pythonhosted.org/packages/4a/62/3a3cb83af77e3dd3c8fc92cb3b7968bfda04a2f71bf0d6f9b72ee3198441/vmware_policy-1.24.0.tar.gz", hash = "sha256:fba12732ccbd4371232c15d9fd014f9f5d697497210aedff2515d1d205ed3577", size = 361302, upload-time = "2026-10-16T07:18:28.317Z" }
1173
1173
  wheels = [
1174
- { url = "https://files.pythonhosted.org/packages/e3/ca/bdfc9e50cedf6684ed7d34348177f7688323617ce26d492f55b3d80cd45c/vmware_policy-1.23.0-py3-none-any.whl", hash = "sha256:2f35a97f7d0997dd49ab5a0d8d0fd5f4eae11ef66ca8ed26bc9e4b395cb0d45c", size = 110836, upload-time = "2026-10-16T00:10:30.55Z" },
1174
+ { url = "https://files.pythonhosted.org/packages/3d/f2/fe3d9d384712023916e2cacddc385f14b095509c5e00aea417d1a9550e98/vmware_policy-1.24.0-py3-none-any.whl", hash = "sha256:cf615d0256134f01fb84c39b083649281a66d2c064c2eaa28786a0fa95ec6e58", size = 131460, upload-time = "2026-10-16T07:18:26.907Z" },
1175
1175
  ]
1176
1176
 
1177
1177
  [[package]]
1178
1178
  name = "vmware-storage"
1179
- version = "2.3.1"
1179
+ version = "2.4.0"
1180
1180
  source = { editable = "." }
1181
1181
  dependencies = [
1182
1182
  { name = "mcp", extra = ["cli"] },
@@ -1,3 +1,3 @@
1
1
  """VMware Storage — vSphere datastore, iSCSI, and vSAN management."""
2
2
 
3
- __version__ = "2.3.1"
3
+ __version__ = "2.4.0"
@@ -38,3 +38,7 @@ def _environment_for(target: str | None) -> str:
38
38
  # to ALLOW. Keyed, a resolver only ever answers for its own skill, so
39
39
  # registering at import time is safe again.
40
40
  set_environment_resolver(_environment_for, skill=skill_name(__name__))
41
+
42
+
43
+ # Role metadata (HLD §18) is declared beside the environment resolver, on every surface.
44
+ import vmware_storage.policy_roles # noqa: E402,F401
@@ -0,0 +1,52 @@
1
+ """What vmware-policy needs to apply roles to this skill (HLD §18 D-21/D-22).
2
+
3
+ Policy cannot see which tools escape a role or how a missing target resolves; this
4
+ module declares them. Imported by ``policy_environment``, so the MCP server and
5
+ the CLI both register it. With a vmware-policy that has no roles module (< 1.24)
6
+ nothing is declared.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import sys
12
+
13
+ from vmware_policy import skill_name
14
+
15
+ from vmware_storage.config import load_config
16
+
17
+ #: Tools that give their holder more than a role. Host-path tools are added by
18
+ #: policy while a file root is set.
19
+ ESCAPES = frozenset()
20
+
21
+
22
+ def _connection_config():
23
+ """The config the connection layer uses for this call (review H1, then H-1).
24
+
25
+ The server's connection manager loads the config once, on its first connection,
26
+ and keeps it. Once it exists, the role is checked against that same object, so
27
+ an edit to config.yaml cannot move the check off the target the call connects
28
+ to. Before it exists, the file as it is now is what the manager is about to load.
29
+ Looked up in ``sys.modules`` so the CLI never imports the MCP server.
30
+ """
31
+ server = sys.modules.get("vmware_storage.mcp_server.server")
32
+ manager = getattr(server, "_conn_mgr", None) if server is not None else None
33
+ return manager._config if manager is not None else load_config()
34
+
35
+
36
+ def _target_name(target: str | None) -> str:
37
+ """The target the connection layer would use for ``target`` (or the default)."""
38
+ config = _connection_config()
39
+ return (config.get_target(target) if target else config.default_target).name
40
+
41
+
42
+ try:
43
+ from vmware_policy.roles import declare_role_metadata
44
+ except ImportError: # vmware-policy without roles: nothing to declare
45
+ declare_role_metadata = None
46
+
47
+ if declare_role_metadata is not None:
48
+ declare_role_metadata(
49
+ skill_name(__name__),
50
+ escapes=ESCAPES,
51
+ resolver=_target_name,
52
+ )
File without changes