vmware-debug 1.12.1__tar.gz → 1.13.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.
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/.claude-plugin/plugin.json +1 -1
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/.mcp.json +1 -1
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/PKG-INFO +2 -2
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/RELEASE_NOTES.md +57 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/openclaw.plugin.json +1 -1
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/package.json +1 -1
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/pyproject.toml +2 -2
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/server.json +2 -2
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/skills/vmware-debug/SKILL.md +31 -6
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/skills/vmware-debug/references/agent-guardrails.md +1 -1
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/skills/vmware-debug/references/setup-guide.md +29 -6
- vmware_debug-1.13.0/tests/test_case_grading_per_hypothesis.py +131 -0
- vmware_debug-1.13.0/tests/test_mcp_audit.py +274 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/uv.lock +5 -5
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/__init__.py +1 -1
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/cli.py +3 -3
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/mcp_server/server.py +54 -22
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/ops/cases/api.py +1 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/ops/cases/grading.py +95 -24
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/.clawhubignore +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/.gitignore +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/README-CN.md +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/README.md +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/SECURITY.md +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/docs/plans/2026-08-28-investigation-layer-design.md +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/skills/vmware-debug/references/capabilities.md +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/skills/vmware-debug/references/cli-reference.md +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/skills/vmware-debug/references/event-envelope.md +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/skills/vmware-debug/references/routing.md +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/__init__.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/conftest.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/__init__.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/capability/__init__.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/capability/_family.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/capability/_scores.json +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/capability/_scoring.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/capability/_skill.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/capability/conftest.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/capability/test_entity_reachability.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/capability/test_error_actionability.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/capability/test_tool_description_quality.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/capability/test_tool_manifest_budget.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/regression/__init__.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/regression/_vsphere_event_vocabulary.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/regression/test_audit_isolation.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/regression/test_capability_grader.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/regression/test_case_id_non_ascii.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/regression/test_case_plan_max_steps.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/regression/test_classification_against_vsphere_vocabulary.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/regression/test_debug_regressions.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/regression/test_environment_resolver_isolation.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/regression/test_every_tool_parameter_is_documented.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/regression/test_evidence_payload_contract.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/regression/test_host_lifecycle_classification.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/regression/test_investigation_metrics.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/regression/test_real_alert_titles_classify.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/regression/test_result_envelope.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/regression/test_skill_name_aliases.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/regression/test_spike_resolution.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/eval/regression/test_tool_annotations.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/test_case_conclusion.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/test_case_evidence.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/test_case_grading.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/test_case_hypotheses.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/test_case_ids.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/test_case_ledger_concurrency.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/test_case_plan.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/test_case_readiness.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/test_case_store.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/test_case_timeline_close.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/test_case_tools.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/test_knowledge.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/test_safe_error_passthrough.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/tests/test_timeline.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/envelope.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/mcp/__init__.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/mcp/tools.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/mcp_server/__init__.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/ops/__init__.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/ops/cases/__init__.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/ops/cases/conclusion.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/ops/cases/evidence.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/ops/cases/hypotheses.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/ops/cases/ids.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/ops/cases/knowledge.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/ops/cases/model.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/ops/cases/payloads.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/ops/cases/plan.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/ops/cases/readiness.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/ops/cases/sources.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/ops/cases/store.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/ops/cases/timeline.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/ops/skill_names.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/ops/timeline.py +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/rules/evidence_sources.yaml +0 -0
- {vmware_debug-1.12.1 → vmware_debug-1.13.0}/vmware_debug/rules/grading_rules.yaml +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
3
|
"name": "vmware-debug",
|
|
4
4
|
"displayName": "VMware Debug",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.13.0",
|
|
6
6
|
"description": "VMware incident timeline, root-cause routing, local case ledger; no vSphere access. 14 MCP tools.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Wei Zhou",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vmware-debug
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.13.0
|
|
4
4
|
Summary: VMware diagnostic brain — offline incident triage, log/event correlation, and root-cause routing across the VMware skill family
|
|
5
5
|
Project-URL: Homepage, https://github.com/vmware-skills/VMware-Debug
|
|
6
6
|
Project-URL: Repository, https://github.com/vmware-skills/VMware-Debug
|
|
@@ -18,7 +18,7 @@ Requires-Dist: mcp[cli]<2.0,>=1.10
|
|
|
18
18
|
Requires-Dist: pyyaml<7.0,>=6.0
|
|
19
19
|
Requires-Dist: rich<15.0,>=13.0
|
|
20
20
|
Requires-Dist: typer<1.0,>=0.12
|
|
21
|
-
Requires-Dist: vmware-policy<2.0,>=1.
|
|
21
|
+
Requires-Dist: vmware-policy<2.0,>=1.16.0
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
|
|
24
24
|
<!-- mcp-name: io.github.vmware-skills/vmware-debug -->
|
|
@@ -1,3 +1,60 @@
|
|
|
1
|
+
## v1.13.0 — exclusion is per hypothesis
|
|
2
|
+
|
|
3
|
+
Found in a real investigation of an Aria "vCenter app health is affected" alert on 2026-09-15, then corrected
|
|
4
|
+
after an independent review the same day.
|
|
5
|
+
|
|
6
|
+
**Ruling out one hypothesis no longer excludes the case.** Two sources ruled out H2 (host memory reclaim) while the
|
|
7
|
+
leading H1 stayed open behind three gaps, and `case_grade` graded the whole case Excluded — which reads as "the
|
|
8
|
+
answer was ruled out". Now the case is Excluded only when every registered hypothesis is ruled out; otherwise it
|
|
9
|
+
is graded for what remains.
|
|
10
|
+
|
|
11
|
+
* **Only evidence that rules nothing out counts for the hypotheses still open.** Evidence records what it
|
|
12
|
+
falsifies, never what it supports. The first version of this change counted the observations that ruled H2 out
|
|
13
|
+
as corroboration for H1, and a hypothesis added with no evidence at all inherited Probable. Independent sources
|
|
14
|
+
and decisive items for the open hypotheses are now counted only from items with an empty `falsifies`.
|
|
15
|
+
* A gap whose `blocks` lists only ruled-out hypotheses no longer holds the grade back; the reasons list it under
|
|
16
|
+
"No longer blocking" instead of "Blocking gap(s)".
|
|
17
|
+
* **Behaviour change:** `case_grade` returns `hypotheses`, every registered hypothesis as `{id, status}` with
|
|
18
|
+
status `open` or `excluded`. A case directory without a readable `hypotheses.md` is graded as if none were
|
|
19
|
+
registered, with a reason, instead of failing.
|
|
20
|
+
* Re-running the same investigation after the change: H2 and H3 excluded, H1 open, **Probable** from two sources
|
|
21
|
+
that rule nothing out, held below Confirmed by four open gaps.
|
|
22
|
+
|
|
23
|
+
Known and unchanged: excluding a hypothesis still needs two independent sources across the whole case, not two
|
|
24
|
+
that falsify that hypothesis; `case_hypotheses` marks a hypothesis refuted on a single falsifier.
|
|
25
|
+
|
|
26
|
+
## v1.12.2 — every tool call is audited, failures included
|
|
27
|
+
|
|
28
|
+
Until now no vmware-debug MCP tool wrote to `~/.vmware/audit.db`. The server said that was on
|
|
29
|
+
purpose — nothing here acts on a VMware target — and the case ledger stood in as the record. It
|
|
30
|
+
records the investigation, not the calls: in a live session case evidence was submitted with
|
|
31
|
+
invented fetch times, and nothing could show when the calls had really been made or which failed.
|
|
32
|
+
|
|
33
|
+
* All 14 MCP tools now go through `@vmware_tool`. Each call writes one row under the tool's own
|
|
34
|
+
name; a call that raises or returns `{"error": …}` is recorded as `error`.
|
|
35
|
+
* `payload` (`case_submit_evidence`) and `events` (`incident_timeline`) are kept out of the row.
|
|
36
|
+
The row still has the case id, source skill and tool, query, fetch time and outcome; the data
|
|
37
|
+
itself is in the case folder, or with the caller.
|
|
38
|
+
* The CLI twins audit too: `categories` as `list_symptom_categories`, `triage` as
|
|
39
|
+
`incident_timeline` — including a `triage` that fails on bad input. `version` and `mcp` still
|
|
40
|
+
write no row.
|
|
41
|
+
* **Event text is not copied into the row.** `incident_timeline` and `case_timeline` return event
|
|
42
|
+
text (`hypotheses[].sample_text`, `classification.unmatched_samples`, `rejected`), and a probe
|
|
43
|
+
with `password=…` and `https://user:pass@host` in an event stored both in the row's result. Both
|
|
44
|
+
tools now declare `sensitive_result`: the row keeps the call, arguments and status (a returned
|
|
45
|
+
`{"error": …}` is still `error`) and records the result as `[redacted: return value declared
|
|
46
|
+
sensitive]`. The other tools return ids, counts, grades and text you passed as an argument, and
|
|
47
|
+
are stored after vmware-policy's credential scrubbing.
|
|
48
|
+
* Because the tools now pass through `guard()`, vmware-policy's rules apply to them: a `deny` rule
|
|
49
|
+
naming a debug tool, or with no `operations`, refuses it; a malformed `~/.vmware/rules.yaml`
|
|
50
|
+
refuses every tool (the `categories`/`triage` commands exit 1 with a traceback ending in the
|
|
51
|
+
reason); the runaway guard refuses the 26th identical call within 120 s. It compares a digest of the
|
|
52
|
+
arguments the tool received, not the audit row's redacted copy, so `incident_timeline` calls with
|
|
53
|
+
different events are not identical — this needs vmware-policy 1.16.0, which this release requires.
|
|
54
|
+
Environment-scoped rules still never match (debug has no targets), and maintenance windows never
|
|
55
|
+
gate a debug tool (all are `low` risk). `references/setup-guide.md` said the opposite — that no
|
|
56
|
+
policy rule applies — and is corrected.
|
|
57
|
+
|
|
1
58
|
## v1.12.1 — every CLI command declares what it reaches
|
|
2
59
|
|
|
3
60
|
Across the family, CLI reads never wrote `~/.vmware/audit.db`; they now do (`@audited`, under the
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vmware-debug"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.13.0"
|
|
8
8
|
description = "VMware diagnostic brain — offline incident triage, log/event correlation, and root-cause routing across the VMware skill family"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -21,7 +21,7 @@ dependencies = [
|
|
|
21
21
|
"typer>=0.12,<1.0",
|
|
22
22
|
"rich>=13.0,<15.0",
|
|
23
23
|
"mcp[cli]>=1.10,<2.0",
|
|
24
|
-
"vmware-policy>=1.
|
|
24
|
+
"vmware-policy>=1.16.0,<2.0",
|
|
25
25
|
# Imported directly by ops/cases/grading.py for the rules file. It has
|
|
26
26
|
# always arrived transitively via vmware-policy; a package should
|
|
27
27
|
# declare what it imports rather than rely on a dependency's dependency.
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"url": "https://github.com/vmware-skills/VMware-Debug",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "1.
|
|
10
|
+
"version": "1.13.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "pypi",
|
|
14
14
|
"identifier": "vmware-debug",
|
|
15
|
-
"version": "1.
|
|
15
|
+
"version": "1.13.0",
|
|
16
16
|
"runtimeHint": "uvx",
|
|
17
17
|
"packageArguments": [
|
|
18
18
|
{
|
|
@@ -51,7 +51,7 @@ write tools touch only the local case ledger.
|
|
|
51
51
|
## Quick Install
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
uv tool install vmware-debug==1.
|
|
54
|
+
uv tool install vmware-debug==1.13.0
|
|
55
55
|
vmware-debug categories # see what it can diagnose
|
|
56
56
|
```
|
|
57
57
|
|
|
@@ -175,7 +175,10 @@ ledger — submit the missing evidence, or record the gap that is blocking it.
|
|
|
175
175
|
- **Confirmed** — that, plus a decisive item: a direct hardware diagnostic, a
|
|
176
176
|
version-checked knowledge-base entry, or a vendor SR; and no gap left open
|
|
177
177
|
- **Excluded** — an observation that actually rules it out. "We looked and found
|
|
178
|
-
nothing" is a gap, not an exclusion
|
|
178
|
+
nothing" is a gap, not an exclusion. Exclusion is per hypothesis: the case is
|
|
179
|
+
Excluded only when every registered hypothesis is ruled out. Otherwise
|
|
180
|
+
`case_grade` grades what remains, counting only evidence that rules nothing
|
|
181
|
+
out, and lists each hypothesis as `open` or `excluded` in `hypotheses`
|
|
179
182
|
|
|
180
183
|
`case_plan` is not a checklist: submit evidence and the next plan is shorter,
|
|
181
184
|
lose a source and it routes around it. Its `unavailable` half is the important
|
|
@@ -275,10 +278,32 @@ No network, nothing executed. The seven [WRITE] tools write only to the local ca
|
|
|
275
278
|
ledger under `$OPS_HOME`; nothing here touches a remote VMware estate. Evidence, gaps,
|
|
276
279
|
hypotheses and grade history are only ever added to — `timeline.md` is regenerated from
|
|
277
280
|
the evidence and `case.json` holds the current grade and state. Remediation is always routed to
|
|
278
|
-
aiops/pilot, where the double-confirm / approval
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
281
|
+
aiops/pilot, where the double-confirm / approval gates live.
|
|
282
|
+
|
|
283
|
+
**What the audit row holds.** Every debug tool call — a failed one included — writes one
|
|
284
|
+
row to `~/.vmware/audit.db` through `@vmware_tool`, as do the `categories` and `triage`
|
|
285
|
+
CLI commands under their MCP tools' names. The row records the tool, time, OS user,
|
|
286
|
+
detected agent, arguments, status (`ok`, `error`, `denied`, `budget_exceeded`,
|
|
287
|
+
`interrupted`) and a result. Two arguments are stored as `***`: evidence `payload` and
|
|
288
|
+
`incident_timeline` `events`. Two results are not stored at all — `incident_timeline` and
|
|
289
|
+
`case_timeline` quote event text (`sample_text`, `unmatched_samples`, `rejected`), which
|
|
290
|
+
can carry anything the source logged, so their row says
|
|
291
|
+
`[redacted: return value declared sensitive]`. Every other result (ids, counts, grades,
|
|
292
|
+
your own summaries and statements) is stored after credential scrubbing. The data itself
|
|
293
|
+
is in the case folder, or with you.
|
|
294
|
+
|
|
295
|
+
**What debug inherits from vmware-policy**, since its tools pass through `guard()`:
|
|
296
|
+
- **Runaway guard** — the 26th call to one tool with identical arguments within 120 s is
|
|
297
|
+
refused (`budget_exceeded`; `VMWARE_RUNAWAY_MAX`, `VMWARE_RUNAWAY_WINDOW_SEC`). It
|
|
298
|
+
compares a digest of the arguments the tool received (vmware-policy ≥1.16.0), so
|
|
299
|
+
calls with different `events` are not identical.
|
|
300
|
+
- **A malformed `~/.vmware/rules.yaml` fails closed** — every tool is refused with a reason
|
|
301
|
+
naming the file; `categories`/`triage` exit 1 with a traceback ending in that message.
|
|
302
|
+
- **A `deny` rule without `operations`** matches every tool, debug's included.
|
|
303
|
+
- **Environment-scoped rules never match** — debug registers no resolver (no targets).
|
|
304
|
+
- **Maintenance windows** gate only `high`/`critical` risk; every debug tool is `low`.
|
|
305
|
+
|
|
306
|
+
See `references/setup-guide.md`.
|
|
282
307
|
|
|
283
308
|
**Case data is sensitive and is kept until you delete it.** Each case lives in
|
|
284
309
|
`$OPS_HOME/cases/<case-id>/` (default `~/.vmware/cases/`), created owner-only (`0700`).
|
{vmware_debug-1.12.1 → vmware_debug-1.13.0}/skills/vmware-debug/references/agent-guardrails.md
RENAMED
|
@@ -46,7 +46,7 @@ These are structural, so it cannot.
|
|
|
46
46
|
| "Do not fabricate a timeline — build it from the events I gave you" | **`incident_timeline` correlates only its input.** It is source-agnostic and has no way to fetch anything, so the timeline cannot contain an event the agent did not supply. |
|
|
47
47
|
| "Tell me when the symptom is outside what you can recognise" | **`list_symptom_categories`** states the catalogue, and unmatched symptoms come back as `uncategorized` rather than being forced into the nearest signature. |
|
|
48
48
|
| "Use explicit limits for queries that may return large amounts of data" | **The list envelope.** `list_symptom_categories` returns `{items, returned, limit, total, truncated, hint}` with `truncated` always `false` — which is the point: it states that the catalogue is complete instead of leaving you to infer it. |
|
|
49
|
-
| "Log everything you looked at" |
|
|
49
|
+
| "Log everything you looked at" | **`~/.vmware/audit.db` and the case ledger.** Every debug tool call, a failed one included, writes one audit row (tool, time, user, agent, arguments, status and result — evidence `payload` and `events` are stored as `***`, and the results of `incident_timeline` and `case_timeline`, which quote event text, are not stored at all). Evidence submitted to a case is also recorded in the ledger with its source skill, tool, query and fetch time — so pass the real fetch time, never a placeholder. |
|
|
50
50
|
|
|
51
51
|
---
|
|
52
52
|
|
|
@@ -6,7 +6,7 @@ is a pure, offline correlation engine. There is no `config.yaml` and no `.env`.
|
|
|
6
6
|
## Install
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
|
-
uv tool install vmware-debug==1.
|
|
9
|
+
uv tool install vmware-debug==1.13.0
|
|
10
10
|
vmware-debug categories # verify it runs
|
|
11
11
|
```
|
|
12
12
|
|
|
@@ -15,7 +15,7 @@ vmware-debug categories # verify it runs
|
|
|
15
15
|
```json
|
|
16
16
|
{
|
|
17
17
|
"command": "uvx",
|
|
18
|
-
"args": ["--from", "vmware-debug==1.
|
|
18
|
+
"args": ["--from", "vmware-debug==1.13.0", "vmware-debug-mcp"]
|
|
19
19
|
}
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -53,8 +53,31 @@ routes fixes to (vmware-aiops, vmware-pilot).
|
|
|
53
53
|
`staging` / `lab`) per target in their own `config.yaml` as an optional label
|
|
54
54
|
an environment-scoped `deny` rule can match on. debug has no config and no
|
|
55
55
|
connection to declare one about, so it registers no environment resolver —
|
|
56
|
-
it has no basis to answer for any target
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
it has no basis to answer for any target — and an environment-scoped rule
|
|
57
|
+
never matches its tools. Every other policy rule does apply: each MCP tool
|
|
58
|
+
and the `categories` / `triage` CLI commands pass through vmware-policy's
|
|
59
|
+
`guard()`. Verified behaviour:
|
|
60
|
+
- A `deny` rule naming a debug tool, or with no `operations` key at all,
|
|
61
|
+
refuses it (status `denied`, with the rule's reason).
|
|
62
|
+
- A malformed `~/.vmware/rules.yaml` fails closed: every tool is refused with
|
|
63
|
+
a reason naming the file and the way out (fix it, or
|
|
64
|
+
`VMWARE_POLICY_DISABLED=1`). The `categories` and `triage` commands exit 1
|
|
65
|
+
with a Python traceback whose last lines are that message; `version` and
|
|
66
|
+
`mcp` are unaffected.
|
|
67
|
+
- Runaway guard: within one MCP server process, the 26th call to the same
|
|
68
|
+
tool with identical arguments inside 120 s is refused (`budget_exceeded`).
|
|
69
|
+
Tune with `VMWARE_RUNAWAY_MAX` / `VMWARE_RUNAWAY_WINDOW_SEC`. The guard
|
|
70
|
+
compares a SHA-256 digest of the arguments the tool actually received
|
|
71
|
+
(vmware-policy ≥1.16.0), not the redacted copy in the audit row — so calls
|
|
72
|
+
with different `events` or `payload` are never counted as identical.
|
|
73
|
+
- A `maintenance_window` gates only `high`/`critical` risk operations; every
|
|
74
|
+
debug tool is `low`, so a window never blocks one.
|
|
75
|
+
7. **Audit row contents** — one row per call in `~/.vmware/audit.db`: tool,
|
|
76
|
+
time, OS user, detected agent, arguments, status and result. `payload` and
|
|
77
|
+
`events` are stored as `***`. The results of `incident_timeline` and
|
|
78
|
+
`case_timeline` are not stored (`[redacted: return value declared
|
|
79
|
+
sensitive]`) because they quote event text; other results are stored after
|
|
80
|
+
credential scrubbing. The status is still truthful — a call that returns
|
|
81
|
+
`{"error": …}` is `error`.
|
|
82
|
+
8. **Static analysis** — `uvx bandit -r vmware_debug/` (release bar:
|
|
60
83
|
0 Medium+).
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Excluded is a verdict about a hypothesis, not about the whole case.
|
|
2
|
+
|
|
3
|
+
2026-09-15, a real investigation of a vCenter app-health alert: two sources
|
|
4
|
+
ruled out H2 (host memory reclaim) while the leading H1 stayed open behind three
|
|
5
|
+
gaps. ``case_grade`` graded the whole case Excluded, which reads as "the answer
|
|
6
|
+
was ruled out". The investigator had to explain in prose that only H2 was.
|
|
7
|
+
|
|
8
|
+
The first version of the fix over-corrected, and an independent review caught
|
|
9
|
+
it: the observations that ruled H2 out were then counted as corroboration for
|
|
10
|
+
H1, and a hypothesis added with no evidence at all inherited Probable. Evidence
|
|
11
|
+
records only what it falsifies, never what it supports, so an item that rules a
|
|
12
|
+
hypothesis out must not count for the ones left open.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
from vmware_debug.ops.cases.api import grade
|
|
20
|
+
from vmware_debug.ops.cases.evidence import Evidence, Gap, record_evidence, record_gap
|
|
21
|
+
from vmware_debug.ops.cases.grading import grade_case
|
|
22
|
+
from vmware_debug.ops.cases.hypotheses import add_hypothesis
|
|
23
|
+
from vmware_debug.ops.cases.model import Scope
|
|
24
|
+
from vmware_debug.ops.cases.store import create_case
|
|
25
|
+
|
|
26
|
+
AT = "2026-09-15T13:30:00Z"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.fixture(autouse=True)
|
|
30
|
+
def isolated_home(tmp_path, monkeypatch):
|
|
31
|
+
monkeypatch.setenv("OPS_HOME", str(tmp_path / "vmware"))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
def two_hypotheses():
|
|
36
|
+
cid = create_case(
|
|
37
|
+
Scope(
|
|
38
|
+
summary="vcenter app health",
|
|
39
|
+
determined_by="aria alert",
|
|
40
|
+
product_versions={"vsphere": "8.0.3"},
|
|
41
|
+
),
|
|
42
|
+
at=AT,
|
|
43
|
+
).case_id
|
|
44
|
+
add_hypothesis(cid, "appliance memory pressure") # H1
|
|
45
|
+
add_hypothesis(cid, "host reclaims appliance memory") # H2
|
|
46
|
+
return cid
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _evidence(skill: str, falsifies: tuple[str, ...] = ()) -> Evidence:
|
|
50
|
+
return Evidence(
|
|
51
|
+
source_skill=skill,
|
|
52
|
+
source_tool="t",
|
|
53
|
+
query={},
|
|
54
|
+
fetched_at="2026-09-15T13:31:00Z",
|
|
55
|
+
summary="s",
|
|
56
|
+
falsifies=falsifies,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _gap(blocks: tuple[str, ...], could_falsify: bool = True) -> Gap:
|
|
61
|
+
return Gap(
|
|
62
|
+
what="swap history",
|
|
63
|
+
why="not collected",
|
|
64
|
+
blocks=blocks,
|
|
65
|
+
could_falsify=could_falsify,
|
|
66
|
+
how_to_close="vami",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_ruling_out_one_hypothesis_does_not_exclude_the_case(two_hypotheses):
|
|
71
|
+
record_evidence(two_hypotheses, _evidence("vmware-aria", falsifies=("H2",)))
|
|
72
|
+
record_evidence(two_hypotheses, _evidence("vmware-monitor", falsifies=("H2",)))
|
|
73
|
+
r = grade_case(two_hypotheses)
|
|
74
|
+
assert r.grade == "candidate"
|
|
75
|
+
assert r.hypotheses == (("H1", "open"), ("H2", "excluded"))
|
|
76
|
+
assert any("Still open: H1" in reason for reason in r.reasons)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_the_case_is_excluded_when_every_hypothesis_is_ruled_out(two_hypotheses):
|
|
80
|
+
record_evidence(two_hypotheses, _evidence("vmware-aria", falsifies=("H1", "H2")))
|
|
81
|
+
record_evidence(two_hypotheses, _evidence("vmware-monitor"))
|
|
82
|
+
assert grade_case(two_hypotheses).grade == "excluded"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_evidence_that_rules_one_out_is_not_corroboration_for_the_rest(two_hypotheses):
|
|
86
|
+
"""aria rules H2 out; monitor says something unrelated. Nothing is about H1."""
|
|
87
|
+
record_evidence(two_hypotheses, _evidence("vmware-aria", falsifies=("H2",)))
|
|
88
|
+
record_evidence(two_hypotheses, _evidence("vmware-monitor"))
|
|
89
|
+
assert grade_case(two_hypotheses).grade == "candidate"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_a_new_hypothesis_does_not_inherit_probable(two_hypotheses):
|
|
93
|
+
record_evidence(two_hypotheses, _evidence("vmware-aria", falsifies=("H1", "H2")))
|
|
94
|
+
record_evidence(two_hypotheses, _evidence("vmware-monitor", falsifies=("H1", "H2")))
|
|
95
|
+
assert grade_case(two_hypotheses).grade == "excluded"
|
|
96
|
+
add_hypothesis(two_hypotheses, "something else") # H3, no evidence at all
|
|
97
|
+
r = grade_case(two_hypotheses)
|
|
98
|
+
assert r.grade == "candidate"
|
|
99
|
+
assert ("H3", "open") in r.hypotheses
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_a_gap_that_blocks_only_a_ruled_out_hypothesis_holds_nothing_back(two_hypotheses):
|
|
103
|
+
"""Otherwise a question nobody needs answered any more pins H1 at Candidate."""
|
|
104
|
+
record_evidence(two_hypotheses, _evidence("vmware-aria", falsifies=("H2",)))
|
|
105
|
+
record_evidence(two_hypotheses, _evidence("vmware-monitor", falsifies=("H2",)))
|
|
106
|
+
record_evidence(two_hypotheses, _evidence("vmware-monitor"))
|
|
107
|
+
record_evidence(two_hypotheses, _evidence("vmware-log-insight"))
|
|
108
|
+
record_gap(two_hypotheses, _gap(blocks=("H2",)))
|
|
109
|
+
r = grade_case(two_hypotheses)
|
|
110
|
+
assert r.grade == "probable"
|
|
111
|
+
assert not any(reason.startswith("Blocking gap(s)") for reason in r.reasons)
|
|
112
|
+
assert any("No longer blocking" in reason for reason in r.reasons)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_a_gap_that_still_blocks_an_open_hypothesis_still_holds(two_hypotheses):
|
|
116
|
+
record_evidence(two_hypotheses, _evidence("vmware-aria", falsifies=("H2",)))
|
|
117
|
+
record_evidence(two_hypotheses, _evidence("vmware-monitor", falsifies=("H2",)))
|
|
118
|
+
record_evidence(two_hypotheses, _evidence("vmware-monitor"))
|
|
119
|
+
record_evidence(two_hypotheses, _evidence("vmware-log-insight"))
|
|
120
|
+
record_gap(two_hypotheses, _gap(blocks=("H1", "H2")))
|
|
121
|
+
assert grade_case(two_hypotheses).grade == "candidate"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_the_tool_result_lists_each_hypothesis(two_hypotheses):
|
|
125
|
+
record_evidence(two_hypotheses, _evidence("vmware-aria", falsifies=("H2",)))
|
|
126
|
+
record_evidence(two_hypotheses, _evidence("vmware-monitor"))
|
|
127
|
+
out = grade(two_hypotheses, at=AT)
|
|
128
|
+
assert out["hypotheses"] == [
|
|
129
|
+
{"id": "H1", "status": "open"},
|
|
130
|
+
{"id": "H2", "status": "excluded"},
|
|
131
|
+
]
|